Skip to content

Commit 6aa45b7

Browse files
committed
Add first cut of functionality for #58713: support for #[link(kind = "raw-dylib")].
This does not yet support #[link_name] attributes on functions, the #[link_ordinal] attribute, #[link(kind = "raw-dylib")] on extern blocks in bin crates, or stdcall functions on 32-bit x86.
1 parent c79419a commit 6aa45b7

27 files changed

+481
-20
lines changed

compiler/rustc_codegen_cranelift/src/archive.rs

+9
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,15 @@ impl<'a> ArchiveBuilder<'a> for ArArchiveBuilder<'a> {
254254
}
255255
}
256256
}
257+
258+
fn inject_dll_import_lib(
259+
&mut self,
260+
_lib_name: &str,
261+
_dll_imports: &[rustc_middle::middle::cstore::DllImport],
262+
_tmpdir: &rustc_data_structures::temp_dir::MaybeTempDir,
263+
) {
264+
bug!("injecting dll imports is not supported");
265+
}
257266
}
258267

259268
impl<'a> ArArchiveBuilder<'a> {

compiler/rustc_codegen_llvm/src/back/archive.rs

+82-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ use std::ptr;
88
use std::str;
99

1010
use crate::llvm::archive_ro::{ArchiveRO, Child};
11-
use crate::llvm::{self, ArchiveKind};
11+
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};
14+
use rustc_data_structures::temp_dir::MaybeTempDir;
15+
use rustc_middle::middle::cstore::DllImport;
1416
use rustc_session::Session;
1517
use rustc_span::symbol::Symbol;
1618

@@ -61,6 +63,17 @@ fn archive_config<'a>(sess: &'a Session, output: &Path, input: Option<&Path>) ->
6163
}
6264
}
6365

66+
/// Map machine type strings to values of LLVM's MachineTypes enum.
67+
fn llvm_machine_type(cpu: &str) -> LLVMMachineType {
68+
match cpu {
69+
"x86_64" => LLVMMachineType::AMD64,
70+
"x86" => LLVMMachineType::I386,
71+
"aarch64" => LLVMMachineType::ARM64,
72+
"arm" => LLVMMachineType::ARM,
73+
_ => panic!("unsupported cpu type {}", cpu),
74+
}
75+
}
76+
6477
impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
6578
/// Creates a new static archive, ready for modifying the archive specified
6679
/// by `config`.
@@ -175,6 +188,74 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
175188
self.config.sess.fatal(&format!("failed to build archive: {}", e));
176189
}
177190
}
191+
192+
fn inject_dll_import_lib(
193+
&mut self,
194+
lib_name: &str,
195+
dll_imports: &[DllImport],
196+
tmpdir: &MaybeTempDir,
197+
) {
198+
let output_path = {
199+
let mut output_path: PathBuf = tmpdir.as_ref().to_path_buf();
200+
output_path.push(format!("{}_imports", lib_name));
201+
output_path.with_extension("lib")
202+
};
203+
204+
// we've checked for \0 characters in the library name already
205+
let dll_name_z = CString::new(lib_name).unwrap();
206+
// All import names are Rust identifiers and therefore cannot contain \0 characters.
207+
// FIXME: when support for #[link_name] implemented, ensure that import.name values don't
208+
// have any \0 characters
209+
let import_name_vector: Vec<CString> = dll_imports
210+
.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()
215+
})
216+
.collect();
217+
218+
let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
219+
220+
tracing::trace!("invoking LLVMRustWriteImportLibrary");
221+
tracing::trace!(" dll_name {:#?}", dll_name_z);
222+
tracing::trace!(" output_path {}", output_path.display());
223+
tracing::trace!(
224+
" import names: {}",
225+
dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "),
226+
);
227+
228+
let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_vector
229+
.iter()
230+
.map(|name_z| LLVMRustCOFFShortExport::from_name(name_z.as_ptr()))
231+
.collect();
232+
let result = unsafe {
233+
crate::llvm::LLVMRustWriteImportLibrary(
234+
dll_name_z.as_ptr(),
235+
output_path_z.as_ptr(),
236+
ffi_exports.as_ptr(),
237+
ffi_exports.len(),
238+
llvm_machine_type(&self.config.sess.target.arch) as u16,
239+
!self.config.sess.target.is_like_msvc,
240+
)
241+
};
242+
243+
if result == crate::llvm::LLVMRustResult::Failure {
244+
self.config.sess.fatal(&format!(
245+
"Error creating import library for {}: {}",
246+
lib_name,
247+
llvm::last_error().unwrap_or("unknown LLVM error".to_string())
248+
));
249+
}
250+
251+
self.add_archive(&output_path, |_| false).unwrap_or_else(|e| {
252+
self.config.sess.fatal(&format!(
253+
"failed to add native library {}: {}",
254+
output_path.display(),
255+
e
256+
));
257+
});
258+
}
178259
}
179260

180261
impl<'a> LlvmArchiveBuilder<'a> {

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+34
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,31 @@ pub enum LLVMRustResult {
2929
Success,
3030
Failure,
3131
}
32+
33+
// Rust version of the C struct with the same name in rustc_llvm/llvm-wrapper/RustWrapper.cpp.
34+
#[repr(C)]
35+
pub struct LLVMRustCOFFShortExport {
36+
pub name: *const c_char,
37+
}
38+
39+
impl LLVMRustCOFFShortExport {
40+
pub fn from_name(name: *const c_char) -> LLVMRustCOFFShortExport {
41+
LLVMRustCOFFShortExport { name }
42+
}
43+
}
44+
45+
/// Translation of LLVM's MachineTypes enum, defined in llvm\include\llvm\BinaryFormat\COFF.h.
46+
///
47+
/// We include only architectures supported on Windows.
48+
#[derive(Copy, Clone, PartialEq)]
49+
#[repr(C)]
50+
pub enum LLVMMachineType {
51+
AMD64 = 0x8664,
52+
I386 = 0x14c,
53+
ARM64 = 0xaa64,
54+
ARM = 0x01c0,
55+
}
56+
3257
// Consts for the LLVM CallConv type, pre-cast to usize.
3358

3459
/// LLVM CallingConv::ID. Should we wrap this?
@@ -2265,6 +2290,15 @@ extern "C" {
22652290
) -> &'a mut RustArchiveMember<'a>;
22662291
pub fn LLVMRustArchiveMemberFree(Member: &'a mut RustArchiveMember<'a>);
22672292

2293+
pub fn LLVMRustWriteImportLibrary(
2294+
ImportName: *const c_char,
2295+
Path: *const c_char,
2296+
Exports: *const LLVMRustCOFFShortExport,
2297+
NumExports: usize,
2298+
Machine: u16,
2299+
MinGW: bool,
2300+
) -> LLVMRustResult;
2301+
22682302
pub fn LLVMRustSetDataLayoutFromTargetMachine(M: &'a Module, TM: &'a TargetMachine);
22692303

22702304
pub fn LLVMRustBuildOperandBundleDef(

compiler/rustc_codegen_ssa/src/back/archive.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use rustc_data_structures::temp_dir::MaybeTempDir;
2+
use rustc_middle::middle::cstore::DllImport;
13
use rustc_session::Session;
24
use rustc_span::symbol::Symbol;
35

@@ -57,4 +59,11 @@ pub trait ArchiveBuilder<'a> {
5759
fn update_symbols(&mut self);
5860

5961
fn build(self);
62+
63+
fn inject_dll_import_lib(
64+
&mut self,
65+
lib_name: &str,
66+
dll_imports: &[DllImport],
67+
tmpdir: &MaybeTempDir,
68+
);
6069
}

compiler/rustc_codegen_ssa/src/back/link.rs

+61-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use rustc_data_structures::fx::FxHashSet;
1+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
22
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::LibSource;
6+
use rustc_middle::middle::cstore::{DllImport, LibSource};
77
use rustc_middle::middle::dependency_format::Linkage;
88
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo};
99
use rustc_session::config::{OutputFilenames, OutputType, PrintRequest};
@@ -34,6 +34,7 @@ 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;
3738
use std::ffi::OsString;
3839
use std::path::{Path, PathBuf};
3940
use std::process::{ExitStatus, Output, Stdio};
@@ -343,6 +344,12 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
343344
}
344345
}
345346

347+
for (raw_dylib_name, raw_dylib_imports) in
348+
collate_raw_dylibs(&codegen_results.crate_info.used_libraries)
349+
{
350+
ab.inject_dll_import_lib(&raw_dylib_name, &raw_dylib_imports, tmpdir);
351+
}
352+
346353
// After adding all files to the archive, we need to update the
347354
// symbol table of the archive.
348355
ab.update_symbols();
@@ -524,6 +531,57 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
524531
}
525532
}
526533

534+
/// Extract all symbols defined in raw-dylib libraries, collated by library name.
535+
///
536+
/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
537+
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
538+
/// linker appears to expect only a single import library for each library used, so we need to
539+
/// collate the symbols together by library name before generating the import libraries.
540+
fn collate_raw_dylibs(used_libraries: &[NativeLib]) -> Vec<(String, Vec<DllImport>)> {
541+
let mut dylib_table: FxHashMap<String, FxHashSet<Symbol>> = FxHashMap::default();
542+
543+
for lib in used_libraries {
544+
if lib.kind == NativeLibKind::RawDylib {
545+
let name = lib.name.unwrap_or_else(||
546+
bug!("`link` attribute with kind = \"raw-dylib\" and no name should have caused error earlier")
547+
);
548+
let name = if matches!(lib.verbatim, Some(true)) {
549+
name.to_string()
550+
} else {
551+
format!("{}.dll", name)
552+
};
553+
dylib_table
554+
.entry(name)
555+
.or_default()
556+
.extend(lib.dll_imports.iter().map(|import| import.name));
557+
}
558+
}
559+
560+
// FIXME: when we add support for ordinals, fix this to propagate ordinals. Also figure out
561+
// what we should do if we have two DllImport values with the same name but different
562+
// ordinals.
563+
let mut result = dylib_table
564+
.into_iter()
565+
.map(|(lib_name, imported_names)| {
566+
let mut names = imported_names
567+
.iter()
568+
.map(|name| DllImport { name: *name, ordinal: None })
569+
.collect::<Vec<_>>();
570+
names.sort_unstable_by(|a: &DllImport, b: &DllImport| {
571+
match a.name.as_str().cmp(&b.name.as_str()) {
572+
Ordering::Equal => a.ordinal.cmp(&b.ordinal),
573+
x => x,
574+
}
575+
});
576+
(lib_name, names)
577+
})
578+
.collect::<Vec<_>>();
579+
result.sort_unstable_by(|a: &(String, Vec<DllImport>), b: &(String, Vec<DllImport>)| {
580+
a.0.cmp(&b.0)
581+
});
582+
result
583+
}
584+
527585
/// Create a static archive.
528586
///
529587
/// This is essentially the same thing as an rlib, but it also involves adding all of the upstream
@@ -2302,10 +2360,7 @@ fn add_upstream_native_libraries(
23022360
// already included them when we included the rust library
23032361
// previously
23042362
NativeLibKind::Static { bundle: None | Some(true), .. } => {}
2305-
NativeLibKind::RawDylib => {
2306-
// FIXME(#58713): Proper handling for raw dylibs.
2307-
bug!("raw_dylib feature not yet implemented");
2308-
}
2363+
NativeLibKind::RawDylib => {}
23092364
}
23102365
}
23112366
}

compiler/rustc_codegen_ssa/src/lib.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,18 @@ pub struct NativeLib {
110110
pub name: Option<Symbol>,
111111
pub cfg: Option<ast::MetaItem>,
112112
pub verbatim: Option<bool>,
113+
pub dll_imports: Vec<cstore::DllImport>,
113114
}
114115

115116
impl From<&cstore::NativeLib> for NativeLib {
116117
fn from(lib: &cstore::NativeLib) -> Self {
117-
NativeLib { kind: lib.kind, name: lib.name, cfg: lib.cfg.clone(), verbatim: lib.verbatim }
118+
NativeLib {
119+
kind: lib.kind,
120+
name: lib.name,
121+
cfg: lib.cfg.clone(),
122+
verbatim: lib.verbatim,
123+
dll_imports: lib.dll_imports.clone(),
124+
}
118125
}
119126
}
120127

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

+52
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "llvm/IR/Instructions.h"
77
#include "llvm/IR/Intrinsics.h"
88
#include "llvm/Object/Archive.h"
9+
#include "llvm/Object/COFFImportFile.h"
910
#include "llvm/Object/ObjectFile.h"
1011
#include "llvm/Bitcode/BitcodeWriterPass.h"
1112
#include "llvm/Support/Signals.h"
@@ -1722,3 +1723,54 @@ extern "C" LLVMValueRef
17221723
LLVMRustBuildMaxNum(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS) {
17231724
return wrap(unwrap(B)->CreateMaxNum(unwrap(LHS),unwrap(RHS)));
17241725
}
1726+
1727+
// This struct contains all necessary info about a symbol exported from a DLL.
1728+
// At the moment, it's just the symbol's name, but we use a separate struct to
1729+
// make it easier to add other information like ordinal later.
1730+
struct LLVMRustCOFFShortExport {
1731+
const char* name;
1732+
};
1733+
1734+
// Machine must be a COFF machine type, as defined in PE specs.
1735+
extern "C" LLVMRustResult LLVMRustWriteImportLibrary(
1736+
const char* ImportName,
1737+
const char* Path,
1738+
const LLVMRustCOFFShortExport* Exports,
1739+
size_t NumExports,
1740+
uint16_t Machine,
1741+
bool MinGW)
1742+
{
1743+
std::vector<llvm::object::COFFShortExport> ConvertedExports;
1744+
ConvertedExports.reserve(NumExports);
1745+
1746+
for (size_t i = 0; i < NumExports; ++i) {
1747+
ConvertedExports.push_back(llvm::object::COFFShortExport{
1748+
Exports[i].name, // Name
1749+
std::string{}, // ExtName
1750+
std::string{}, // SymbolName
1751+
std::string{}, // AliasTarget
1752+
0, // Ordinal
1753+
false, // Noname
1754+
false, // Data
1755+
false, // Private
1756+
false // Constant
1757+
});
1758+
}
1759+
1760+
auto Error = llvm::object::writeImportLibrary(
1761+
ImportName,
1762+
Path,
1763+
ConvertedExports,
1764+
static_cast<llvm::COFF::MachineTypes>(Machine),
1765+
MinGW);
1766+
if (Error) {
1767+
std::string errorString;
1768+
llvm::raw_string_ostream stream(errorString);
1769+
stream << Error;
1770+
stream.flush();
1771+
LLVMRustSetLastError(errorString.c_str());
1772+
return LLVMRustResult::Failure;
1773+
} else {
1774+
return LLVMRustResult::Success;
1775+
}
1776+
}

0 commit comments

Comments
 (0)