Skip to content

Commit a867dd4

Browse files
committed
Add support for raw-dylib with stdcall, fastcall functions on i686-pc-windows-msvc.
1 parent 8b87e85 commit a867dd4

File tree

19 files changed

+436
-48
lines changed

19 files changed

+436
-48
lines changed

compiler/rustc_codegen_llvm/src/back/archive.rs

+22-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport};
1212
use rustc_codegen_ssa::back::archive::{find_library, ArchiveBuilder};
1313
use rustc_codegen_ssa::{looks_like_rust_object_file, METADATA_FILENAME};
1414
use rustc_data_structures::temp_dir::MaybeTempDir;
15-
use rustc_middle::middle::cstore::DllImport;
15+
use rustc_middle::middle::cstore::{DllCallingConvention, DllImport};
1616
use rustc_session::Session;
1717
use rustc_span::symbol::Symbol;
1818

@@ -208,10 +208,12 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
208208
// have any \0 characters
209209
let import_name_vector: Vec<CString> = dll_imports
210210
.iter()
211-
.map(if self.config.sess.target.arch == "x86" {
212-
|import: &DllImport| CString::new(format!("_{}", import.name.to_string())).unwrap()
213-
} else {
214-
|import: &DllImport| CString::new(import.name.to_string()).unwrap()
211+
.map(|import: &DllImport| {
212+
if self.config.sess.target.arch == "x86" {
213+
LlvmArchiveBuilder::i686_decorated_name(import)
214+
} else {
215+
CString::new(import.name.to_string()).unwrap()
216+
}
215217
})
216218
.collect();
217219

@@ -391,6 +393,21 @@ impl<'a> LlvmArchiveBuilder<'a> {
391393
ret
392394
}
393395
}
396+
397+
fn i686_decorated_name(import: &DllImport) -> CString {
398+
let name = import.name;
399+
// We verified during construction that `name` does not contain any NULL characters, so the
400+
// conversion to CString is guaranteed to succeed.
401+
CString::new(match import.calling_convention {
402+
DllCallingConvention::C => format!("_{}", name),
403+
DllCallingConvention::Stdcall(arg_list_size) => format!("_{}@{}", name, arg_list_size),
404+
DllCallingConvention::Fastcall(arg_list_size) => format!("@{}@{}", name, arg_list_size),
405+
DllCallingConvention::Vectorcall(arg_list_size) => {
406+
format!("{}@@{}", name, arg_list_size)
407+
}
408+
})
409+
.unwrap()
410+
}
394411
}
395412

396413
fn string_to_io_error(s: String) -> io::Error {

compiler/rustc_codegen_ssa/src/back/link.rs

+44-25
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustc_data_structures::temp_dir::MaybeTempDir;
33
use rustc_errors::Handler;
44
use rustc_fs_util::fix_windows_verbatim_for_gcc;
55
use rustc_hir::def_id::CrateNum;
6-
use rustc_middle::middle::cstore::DllImport;
6+
use rustc_middle::middle::cstore::{DllCallingConvention, DllImport};
77
use rustc_middle::middle::dependency_format::Linkage;
88
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip};
99
use rustc_session::config::{OutputFilenames, OutputType, PrintRequest};
@@ -34,8 +34,8 @@ use object::write::Object;
3434
use object::{Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind};
3535
use tempfile::Builder as TempFileBuilder;
3636

37-
use std::cmp::Ordering;
3837
use std::ffi::OsString;
38+
use std::iter::FromIterator;
3939
use std::path::{Path, PathBuf};
4040
use std::process::{ExitStatus, Output, Stdio};
4141
use std::{ascii, char, env, fmt, fs, io, mem, str};
@@ -259,7 +259,7 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
259259
}
260260

261261
for (raw_dylib_name, raw_dylib_imports) in
262-
collate_raw_dylibs(&codegen_results.crate_info.used_libraries)
262+
collate_raw_dylibs(sess, &codegen_results.crate_info.used_libraries)
263263
{
264264
ab.inject_dll_import_lib(&raw_dylib_name, &raw_dylib_imports, tmpdir);
265265
}
@@ -451,8 +451,11 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
451451
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
452452
/// linker appears to expect only a single import library for each library used, so we need to
453453
/// collate the symbols together by library name before generating the import libraries.
454-
fn collate_raw_dylibs(used_libraries: &[NativeLib]) -> Vec<(String, Vec<DllImport>)> {
455-
let mut dylib_table: FxHashMap<String, FxHashSet<Symbol>> = FxHashMap::default();
454+
fn collate_raw_dylibs(
455+
sess: &Session,
456+
used_libraries: &[NativeLib],
457+
) -> Vec<(String, Vec<DllImport>)> {
458+
let mut dylib_table: FxHashMap<String, FxHashSet<DllImport>> = FxHashMap::default();
456459

457460
for lib in used_libraries {
458461
if lib.kind == NativeLibKind::RawDylib {
@@ -464,35 +467,51 @@ fn collate_raw_dylibs(used_libraries: &[NativeLib]) -> Vec<(String, Vec<DllImpor
464467
} else {
465468
format!("{}.dll", name)
466469
};
467-
dylib_table
468-
.entry(name)
469-
.or_default()
470-
.extend(lib.dll_imports.iter().map(|import| import.name));
470+
dylib_table.entry(name).or_default().extend(lib.dll_imports.iter().cloned());
471471
}
472472
}
473473

474-
// FIXME: when we add support for ordinals, fix this to propagate ordinals. Also figure out
475-
// what we should do if we have two DllImport values with the same name but different
476-
// ordinals.
477-
let mut result = dylib_table
474+
// Rustc already signals an error if we have two imports with the same name but different
475+
// calling conventions (or function signatures), so we don't have pay attention to those
476+
// when ordering.
477+
// FIXME: when we add support for ordinals, figure out if we need to do anything if we
478+
// have two DllImport values with the same name but different ordinals.
479+
let mut result: Vec<(String, Vec<DllImport>)> = dylib_table
478480
.into_iter()
479-
.map(|(lib_name, imported_names)| {
480-
let mut names = imported_names
481-
.iter()
482-
.map(|name| DllImport { name: *name, ordinal: None })
483-
.collect::<Vec<_>>();
484-
names.sort_unstable_by(|a: &DllImport, b: &DllImport| {
485-
match a.name.as_str().cmp(&b.name.as_str()) {
486-
Ordering::Equal => a.ordinal.cmp(&b.ordinal),
487-
x => x,
488-
}
489-
});
490-
(lib_name, names)
481+
.map(|(lib_name, import_table)| {
482+
let mut imports = Vec::from_iter(import_table.into_iter());
483+
imports.sort_unstable_by_key(|x: &DllImport| x.name.as_str());
484+
(lib_name, imports)
491485
})
492486
.collect::<Vec<_>>();
493487
result.sort_unstable_by(|a: &(String, Vec<DllImport>), b: &(String, Vec<DllImport>)| {
494488
a.0.cmp(&b.0)
495489
});
490+
let result = result;
491+
492+
// Check for multiple imports with the same name but different calling conventions or
493+
// (when relevant) argument list sizes. Rustc only signals an error for this if the
494+
// declarations are at the same scope level; if one shadows the other, we only get a lint
495+
// warning.
496+
for (library, imports) in &result {
497+
let mut import_table: FxHashMap<Symbol, DllCallingConvention> = FxHashMap::default();
498+
for import in imports {
499+
if let Some(old_convention) =
500+
import_table.insert(import.name, import.calling_convention)
501+
{
502+
if import.calling_convention != old_convention {
503+
sess.span_fatal(
504+
import.span,
505+
&format!(
506+
"multiple definitions of external function `{}` from library `{}` have different calling conventions",
507+
import.name,
508+
library,
509+
));
510+
}
511+
}
512+
}
513+
}
514+
496515
result
497516
}
498517

compiler/rustc_metadata/src/native_libs.rs

+57-15
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use rustc_data_structures::fx::FxHashSet;
33
use rustc_errors::struct_span_err;
44
use rustc_hir as hir;
55
use rustc_hir::itemlikevisit::ItemLikeVisitor;
6-
use rustc_middle::middle::cstore::{DllImport, NativeLib};
7-
use rustc_middle::ty::TyCtxt;
6+
use rustc_middle::middle::cstore::{DllCallingConvention, DllImport, NativeLib};
7+
use rustc_middle::ty::{List, ParamEnv, ParamEnvAnd, Ty, TyCtxt};
88
use rustc_session::parse::feature_err;
99
use rustc_session::utils::NativeLibKind;
1010
use rustc_session::Session;
@@ -199,22 +199,10 @@ impl ItemLikeVisitor<'tcx> for Collector<'tcx> {
199199
}
200200

201201
if lib.kind == NativeLibKind::RawDylib {
202-
match abi {
203-
Abi::C { .. } => (),
204-
Abi::Cdecl => (),
205-
_ => {
206-
if sess.target.arch == "x86" {
207-
sess.span_fatal(
208-
it.span,
209-
r#"`#[link(kind = "raw-dylib")]` only supports C and Cdecl ABIs"#,
210-
);
211-
}
212-
}
213-
};
214202
lib.dll_imports.extend(
215203
foreign_mod_items
216204
.iter()
217-
.map(|child_item| DllImport { name: child_item.ident.name, ordinal: None }),
205+
.map(|child_item| self.build_dll_import(abi, child_item)),
218206
);
219207
}
220208

@@ -396,4 +384,58 @@ impl Collector<'tcx> {
396384
}
397385
}
398386
}
387+
388+
fn i686_arg_list_size(&self, item: &hir::ForeignItemRef<'_>) -> usize {
389+
let argument_types: &List<Ty<'_>> = self.tcx.erase_late_bound_regions(
390+
self.tcx
391+
.type_of(item.id.def_id)
392+
.fn_sig(self.tcx)
393+
.inputs()
394+
.map_bound(|slice| self.tcx.mk_type_list(slice.iter())),
395+
);
396+
397+
argument_types
398+
.iter()
399+
.map(|ty| {
400+
let layout = self
401+
.tcx
402+
.layout_of(ParamEnvAnd { param_env: ParamEnv::empty(), value: ty })
403+
.expect("layout")
404+
.layout;
405+
// In both stdcall and fastcall, we always round up the argument size to the
406+
// nearest multiple of 4 bytes.
407+
(layout.size.bytes_usize() + 3) & !3
408+
})
409+
.sum()
410+
}
411+
412+
fn build_dll_import(&self, abi: Abi, item: &hir::ForeignItemRef<'_>) -> DllImport {
413+
let calling_convention = if self.tcx.sess.target.arch == "x86" {
414+
match abi {
415+
Abi::C { .. } | Abi::Cdecl => DllCallingConvention::C,
416+
Abi::Stdcall { .. } | Abi::System { .. } => {
417+
DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
418+
}
419+
Abi::Fastcall => DllCallingConvention::Fastcall(self.i686_arg_list_size(item)),
420+
// Vectorcall is intentionally not supported at this time.
421+
_ => {
422+
self.tcx.sess.span_fatal(
423+
item.span,
424+
r#"ABI not supported by `#[link(kind = "raw-dylib")]` on i686"#,
425+
);
426+
}
427+
}
428+
} else {
429+
match abi {
430+
Abi::C { .. } | Abi::Win64 | Abi::System { .. } => DllCallingConvention::C,
431+
_ => {
432+
self.tcx.sess.span_fatal(
433+
item.span,
434+
r#"ABI not supported by `#[link(kind = "raw-dylib")]` on this architecture"#,
435+
);
436+
}
437+
}
438+
};
439+
DllImport { name: item.ident.name, ordinal: None, calling_convention, span: item.span }
440+
}
399441
}

compiler/rustc_middle/src/middle/cstore.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,29 @@ pub struct NativeLib {
7777
pub dll_imports: Vec<DllImport>,
7878
}
7979

80-
#[derive(Clone, Debug, Encodable, Decodable, HashStable)]
80+
#[derive(Clone, Debug, PartialEq, Eq, Encodable, Decodable, Hash, HashStable)]
8181
pub struct DllImport {
8282
pub name: Symbol,
8383
pub ordinal: Option<u16>,
84+
/// Calling convention for the function.
85+
///
86+
/// On x86_64, this is always `DllCallingConvention::C`; on i686, it can be any
87+
/// of the values, and we use `DllCallingConvention::C` to represent `"cdecl"`.
88+
pub calling_convention: DllCallingConvention,
89+
/// Span of import's "extern" declaration; used for diagnostics.
90+
pub span: Span,
91+
}
92+
93+
/// Calling convention for a function defined in an external library.
94+
///
95+
/// The usize value, where present, indicates the size of the function's argument list
96+
/// in bytes.
97+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encodable, Decodable, Hash, HashStable)]
98+
pub enum DllCallingConvention {
99+
C,
100+
Stdcall(usize),
101+
Fastcall(usize),
102+
Vectorcall(usize),
84103
}
85104

86105
#[derive(Clone, TyEncodable, TyDecodable, HashStable, Debug)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Test the behavior of #[link(.., kind = "raw-dylib")] with alternative calling conventions.
2+
3+
# only-i686-pc-windows-msvc
4+
5+
-include ../../run-make-fulldeps/tools.mk
6+
7+
all:
8+
$(call COMPILE_OBJ,"$(TMPDIR)"/extern.obj,extern.c)
9+
$(CC) "$(TMPDIR)"/extern.obj -link -dll -out:"$(TMPDIR)"/extern.dll
10+
$(RUSTC) --crate-type lib --crate-name raw_dylib_alt_calling_convention_test lib.rs
11+
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
12+
"$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt
13+
14+
ifdef RUSTC_BLESS_TEST
15+
cp "$(TMPDIR)"/output.txt output.txt
16+
else
17+
$(DIFF) output.txt "$(TMPDIR)"/output.txt
18+
endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extern crate raw_dylib_alt_calling_convention_test;
2+
3+
fn main() {
4+
raw_dylib_alt_calling_convention_test::library_function();
5+
}

0 commit comments

Comments
 (0)