Skip to content

Commit 999a3c9

Browse files
authored
Rollup merge of rust-lang#84171 - ricobbe:raw-dylib-via-llvm, r=petrochenkov
Partial support for raw-dylib linkage First cut of functionality for issue rust-lang#58713: add support for `#[link(kind = "raw-dylib")]` on `extern` blocks in lib crates compiled to .rlib files. Does not yet support `#[link_name]` attributes on functions, or the `#[link_ordinal]` attribute, or `#[link(kind = "raw-dylib")]` on `extern` blocks in bin crates; I intend to publish subsequent PRs to fill those gaps. It's also not yet clear whether this works for functions in `extern "stdcall"` blocks; I also intend to investigate that shortly and make any necessary changes as a follow-on PR. This implementation calls out to an LLVM function to construct the actual `.idata` sections as temporary `.lib` files on disk and then links those into the generated .rlib.
2 parents 9a700d2 + 7b86665 commit 999a3c9

27 files changed

+465
-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

+78-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,70 @@ 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(|import| CString::new(import.name.to_string()).unwrap())
212+
.collect();
213+
214+
let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
215+
216+
tracing::trace!("invoking LLVMRustWriteImportLibrary");
217+
tracing::trace!(" dll_name {:#?}", dll_name_z);
218+
tracing::trace!(" output_path {}", output_path.display());
219+
tracing::trace!(
220+
" import names: {}",
221+
dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "),
222+
);
223+
224+
let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_vector
225+
.iter()
226+
.map(|name_z| LLVMRustCOFFShortExport::from_name(name_z.as_ptr()))
227+
.collect();
228+
let result = unsafe {
229+
crate::llvm::LLVMRustWriteImportLibrary(
230+
dll_name_z.as_ptr(),
231+
output_path_z.as_ptr(),
232+
ffi_exports.as_ptr(),
233+
ffi_exports.len(),
234+
llvm_machine_type(&self.config.sess.target.arch) as u16,
235+
!self.config.sess.target.is_like_msvc,
236+
)
237+
};
238+
239+
if result == crate::llvm::LLVMRustResult::Failure {
240+
self.config.sess.fatal(&format!(
241+
"Error creating import library for {}: {}",
242+
lib_name,
243+
llvm::last_error().unwrap_or("unknown LLVM error".to_string())
244+
));
245+
}
246+
247+
self.add_archive(&output_path, |_| false).unwrap_or_else(|e| {
248+
self.config.sess.fatal(&format!(
249+
"failed to add native library {}: {}",
250+
output_path.display(),
251+
e
252+
));
253+
});
254+
}
178255
}
179256

180257
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?
@@ -2304,6 +2329,15 @@ extern "C" {
23042329
) -> &'a mut RustArchiveMember<'a>;
23052330
pub fn LLVMRustArchiveMemberFree(Member: &'a mut RustArchiveMember<'a>);
23062331

2332+
pub fn LLVMRustWriteImportLibrary(
2333+
ImportName: *const c_char,
2334+
Path: *const c_char,
2335+
Exports: *const LLVMRustCOFFShortExport,
2336+
NumExports: usize,
2337+
Machine: u16,
2338+
MinGW: bool,
2339+
) -> LLVMRustResult;
2340+
23072341
pub fn LLVMRustSetDataLayoutFromTargetMachine(M: &'a Module, TM: &'a TargetMachine);
23082342

23092343
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::{EncodedMetadata, LibSource};
6+
use rustc_middle::middle::cstore::{DllImport, EncodedMetadata, 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};
@@ -30,6 +30,7 @@ use crate::{
3030
use cc::windows_registry;
3131
use tempfile::Builder as TempFileBuilder;
3232

33+
use std::cmp::Ordering;
3334
use std::ffi::OsString;
3435
use std::path::{Path, PathBuf};
3536
use std::process::{ExitStatus, Output, Stdio};
@@ -339,6 +340,12 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
339340
}
340341
}
341342

343+
for (raw_dylib_name, raw_dylib_imports) in
344+
collate_raw_dylibs(&codegen_results.crate_info.used_libraries)
345+
{
346+
ab.inject_dll_import_lib(&raw_dylib_name, &raw_dylib_imports, tmpdir);
347+
}
348+
342349
// After adding all files to the archive, we need to update the
343350
// symbol table of the archive.
344351
ab.update_symbols();
@@ -389,6 +396,57 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
389396
ab
390397
}
391398

399+
/// Extract all symbols defined in raw-dylib libraries, collated by library name.
400+
///
401+
/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
402+
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
403+
/// linker appears to expect only a single import library for each library used, so we need to
404+
/// collate the symbols together by library name before generating the import libraries.
405+
fn collate_raw_dylibs(used_libraries: &[NativeLib]) -> Vec<(String, Vec<DllImport>)> {
406+
let mut dylib_table: FxHashMap<String, FxHashSet<Symbol>> = FxHashMap::default();
407+
408+
for lib in used_libraries {
409+
if lib.kind == NativeLibKind::RawDylib {
410+
let name = lib.name.unwrap_or_else(||
411+
bug!("`link` attribute with kind = \"raw-dylib\" and no name should have caused error earlier")
412+
);
413+
let name = if matches!(lib.verbatim, Some(true)) {
414+
name.to_string()
415+
} else {
416+
format!("{}.dll", name)
417+
};
418+
dylib_table
419+
.entry(name)
420+
.or_default()
421+
.extend(lib.dll_imports.iter().map(|import| import.name));
422+
}
423+
}
424+
425+
// FIXME: when we add support for ordinals, fix this to propagate ordinals. Also figure out
426+
// what we should do if we have two DllImport values with the same name but different
427+
// ordinals.
428+
let mut result = dylib_table
429+
.into_iter()
430+
.map(|(lib_name, imported_names)| {
431+
let mut names = imported_names
432+
.iter()
433+
.map(|name| DllImport { name: *name, ordinal: None })
434+
.collect::<Vec<_>>();
435+
names.sort_unstable_by(|a: &DllImport, b: &DllImport| {
436+
match a.name.as_str().cmp(&b.name.as_str()) {
437+
Ordering::Equal => a.ordinal.cmp(&b.ordinal),
438+
x => x,
439+
}
440+
});
441+
(lib_name, names)
442+
})
443+
.collect::<Vec<_>>();
444+
result.sort_unstable_by(|a: &(String, Vec<DllImport>), b: &(String, Vec<DllImport>)| {
445+
a.0.cmp(&b.0)
446+
});
447+
result
448+
}
449+
392450
/// Create a static archive.
393451
///
394452
/// This is essentially the same thing as an rlib, but it also involves adding all of the upstream
@@ -2178,10 +2236,7 @@ fn add_upstream_native_libraries(
21782236
// already included them when we included the rust library
21792237
// previously
21802238
NativeLibKind::Static { bundle: None | Some(true), .. } => {}
2181-
NativeLibKind::RawDylib => {
2182-
// FIXME(#58713): Proper handling for raw dylibs.
2183-
bug!("raw_dylib feature not yet implemented");
2184-
}
2239+
NativeLibKind::RawDylib => {}
21852240
}
21862241
}
21872242
}

compiler/rustc_codegen_ssa/src/lib.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,18 @@ pub struct NativeLib {
114114
pub name: Option<Symbol>,
115115
pub cfg: Option<ast::MetaItem>,
116116
pub verbatim: Option<bool>,
117+
pub dll_imports: Vec<cstore::DllImport>,
117118
}
118119

119120
impl From<&cstore::NativeLib> for NativeLib {
120121
fn from(lib: &cstore::NativeLib) -> Self {
121-
NativeLib { kind: lib.kind, name: lib.name, cfg: lib.cfg.clone(), verbatim: lib.verbatim }
122+
NativeLib {
123+
kind: lib.kind,
124+
name: lib.name,
125+
cfg: lib.cfg.clone(),
126+
verbatim: lib.verbatim,
127+
dll_imports: lib.dll_imports.clone(),
128+
}
122129
}
123130
}
124131

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"
@@ -1757,3 +1758,54 @@ extern "C" LLVMValueRef
17571758
LLVMRustBuildMaxNum(LLVMBuilderRef B, LLVMValueRef LHS, LLVMValueRef RHS) {
17581759
return wrap(unwrap(B)->CreateMaxNum(unwrap(LHS),unwrap(RHS)));
17591760
}
1761+
1762+
// This struct contains all necessary info about a symbol exported from a DLL.
1763+
// At the moment, it's just the symbol's name, but we use a separate struct to
1764+
// make it easier to add other information like ordinal later.
1765+
struct LLVMRustCOFFShortExport {
1766+
const char* name;
1767+
};
1768+
1769+
// Machine must be a COFF machine type, as defined in PE specs.
1770+
extern "C" LLVMRustResult LLVMRustWriteImportLibrary(
1771+
const char* ImportName,
1772+
const char* Path,
1773+
const LLVMRustCOFFShortExport* Exports,
1774+
size_t NumExports,
1775+
uint16_t Machine,
1776+
bool MinGW)
1777+
{
1778+
std::vector<llvm::object::COFFShortExport> ConvertedExports;
1779+
ConvertedExports.reserve(NumExports);
1780+
1781+
for (size_t i = 0; i < NumExports; ++i) {
1782+
ConvertedExports.push_back(llvm::object::COFFShortExport{
1783+
Exports[i].name, // Name
1784+
std::string{}, // ExtName
1785+
std::string{}, // SymbolName
1786+
std::string{}, // AliasTarget
1787+
0, // Ordinal
1788+
false, // Noname
1789+
false, // Data
1790+
false, // Private
1791+
false // Constant
1792+
});
1793+
}
1794+
1795+
auto Error = llvm::object::writeImportLibrary(
1796+
ImportName,
1797+
Path,
1798+
ConvertedExports,
1799+
static_cast<llvm::COFF::MachineTypes>(Machine),
1800+
MinGW);
1801+
if (Error) {
1802+
std::string errorString;
1803+
llvm::raw_string_ostream stream(errorString);
1804+
stream << Error;
1805+
stream.flush();
1806+
LLVMRustSetLastError(errorString.c_str());
1807+
return LLVMRustResult::Failure;
1808+
} else {
1809+
return LLVMRustResult::Success;
1810+
}
1811+
}

0 commit comments

Comments
 (0)