Skip to content

Commit ea6cbd2

Browse files
committed
Support raw-dylib link kind on ELF
raw-dylib is a link kind that allows rustc to link against a library without having any library files present. This currently only exists on Windows. rustc will take all the symbols from raw-dylib link blocks and put them in an import library, where they can then be resolved by the linker. While import libraries don't exist on ELF, it would still be convenient to have this same functionality. Not having the libraries present at build-time can be convenient for several reasons, especially cross-compilation. With raw-dylib, code linking against a library can be cross-compiled without needing to have these libraries available on the build machine. If the libc crate makes use of this, it would allow cross-compilation without having any libc available on the build machine. This is not yet possible with this implementation, at least against libc's like glibc that use symbol versioning. The raw-dylib kind could be extended with support for symbol versioning in the future. This implementation is very experimental and I have not tested it very well. I have tested it for a toy example and the lz4-sys crate, where it was able to successfully link a binary despite not having a corresponding library at build-time.
1 parent 552a959 commit ea6cbd2

File tree

69 files changed

+829
-213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+829
-213
lines changed

compiler/rustc_codegen_ssa/src/back/link.rs

+78-134
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod raw_dylib;
2+
13
use std::collections::BTreeSet;
24
use std::ffi::OsString;
35
use std::fs::{File, OpenOptions, read};
@@ -12,7 +14,7 @@ use itertools::Itertools;
1214
use regex::Regex;
1315
use rustc_arena::TypedArena;
1416
use rustc_ast::CRATE_NODE_ID;
15-
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
17+
use rustc_data_structures::fx::FxIndexSet;
1618
use rustc_data_structures::memmap::Mmap;
1719
use rustc_data_structures::temp_dir::MaybeTempDir;
1820
use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
@@ -30,7 +32,6 @@ use rustc_session::config::{
3032
self, CFGuard, CrateType, DebugInfo, LinkerFeaturesCli, OutFileName, OutputFilenames,
3133
OutputType, PrintKind, SplitDwarfKind, Strip,
3234
};
33-
use rustc_session::cstore::DllImport;
3435
use rustc_session::lint::builtin::LINKER_MESSAGES;
3536
use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
3637
use rustc_session::search_paths::PathKind;
@@ -48,15 +49,14 @@ use rustc_target::spec::{
4849
use tempfile::Builder as TempFileBuilder;
4950
use tracing::{debug, info, warn};
5051

51-
use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder, ImportLibraryItem};
52+
use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder};
5253
use super::command::Command;
5354
use super::linker::{self, Linker};
5455
use super::metadata::{MetadataPosition, create_wrapper_file};
5556
use super::rpath::{self, RPathConfig};
5657
use super::{apple, versioned_llvm_target};
5758
use crate::{
58-
CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors,
59-
looks_like_rust_object_file,
59+
CodegenResults, CompiledModule, CrateInfo, NativeLib, errors, looks_like_rust_object_file,
6060
};
6161

6262
pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
@@ -381,16 +381,22 @@ fn link_rlib<'a>(
381381
}
382382
}
383383

384-
for output_path in create_dll_import_libs(
385-
sess,
386-
archive_builder_builder,
387-
codegen_results.crate_info.used_libraries.iter(),
388-
tmpdir.as_ref(),
389-
true,
390-
) {
391-
ab.add_archive(&output_path, Box::new(|_| false)).unwrap_or_else(|error| {
392-
sess.dcx().emit_fatal(errors::AddNativeLibrary { library_path: output_path, error });
393-
});
384+
// On Windows, we add the raw-dylib import libraries to the rlibs already.
385+
// But on ELF, this is not possible, as a shared object cannot be a member of a static library.
386+
// Instead, we add all raw-dylibs to the final link on ELF.
387+
if sess.target.is_like_windows {
388+
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
389+
sess,
390+
archive_builder_builder,
391+
codegen_results.crate_info.used_libraries.iter(),
392+
tmpdir.as_ref(),
393+
true,
394+
) {
395+
ab.add_archive(&output_path, Box::new(|_| false)).unwrap_or_else(|error| {
396+
sess.dcx()
397+
.emit_fatal(errors::AddNativeLibrary { library_path: output_path, error });
398+
});
399+
}
394400
}
395401

396402
if let Some(trailing_metadata) = trailing_metadata {
@@ -431,108 +437,6 @@ fn link_rlib<'a>(
431437
ab
432438
}
433439

434-
/// Extract all symbols defined in raw-dylib libraries, collated by library name.
435-
///
436-
/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
437-
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
438-
/// linker appears to expect only a single import library for each library used, so we need to
439-
/// collate the symbols together by library name before generating the import libraries.
440-
fn collate_raw_dylibs<'a>(
441-
sess: &Session,
442-
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
443-
) -> Vec<(String, Vec<DllImport>)> {
444-
// Use index maps to preserve original order of imports and libraries.
445-
let mut dylib_table = FxIndexMap::<String, FxIndexMap<Symbol, &DllImport>>::default();
446-
447-
for lib in used_libraries {
448-
if lib.kind == NativeLibKind::RawDylib {
449-
let ext = if lib.verbatim { "" } else { ".dll" };
450-
let name = format!("{}{}", lib.name, ext);
451-
let imports = dylib_table.entry(name.clone()).or_default();
452-
for import in &lib.dll_imports {
453-
if let Some(old_import) = imports.insert(import.name, import) {
454-
// FIXME: when we add support for ordinals, figure out if we need to do anything
455-
// if we have two DllImport values with the same name but different ordinals.
456-
if import.calling_convention != old_import.calling_convention {
457-
sess.dcx().emit_err(errors::MultipleExternalFuncDecl {
458-
span: import.span,
459-
function: import.name,
460-
library_name: &name,
461-
});
462-
}
463-
}
464-
}
465-
}
466-
}
467-
sess.dcx().abort_if_errors();
468-
dylib_table
469-
.into_iter()
470-
.map(|(name, imports)| {
471-
(name, imports.into_iter().map(|(_, import)| import.clone()).collect())
472-
})
473-
.collect()
474-
}
475-
476-
fn create_dll_import_libs<'a>(
477-
sess: &Session,
478-
archive_builder_builder: &dyn ArchiveBuilderBuilder,
479-
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
480-
tmpdir: &Path,
481-
is_direct_dependency: bool,
482-
) -> Vec<PathBuf> {
483-
collate_raw_dylibs(sess, used_libraries)
484-
.into_iter()
485-
.map(|(raw_dylib_name, raw_dylib_imports)| {
486-
let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" };
487-
let output_path = tmpdir.join(format!("{raw_dylib_name}{name_suffix}.lib"));
488-
489-
let mingw_gnu_toolchain = common::is_mingw_gnu_toolchain(&sess.target);
490-
491-
let items: Vec<ImportLibraryItem> = raw_dylib_imports
492-
.iter()
493-
.map(|import: &DllImport| {
494-
if sess.target.arch == "x86" {
495-
ImportLibraryItem {
496-
name: common::i686_decorated_name(
497-
import,
498-
mingw_gnu_toolchain,
499-
false,
500-
false,
501-
),
502-
ordinal: import.ordinal(),
503-
symbol_name: import.is_missing_decorations().then(|| {
504-
common::i686_decorated_name(
505-
import,
506-
mingw_gnu_toolchain,
507-
false,
508-
true,
509-
)
510-
}),
511-
is_data: !import.is_fn,
512-
}
513-
} else {
514-
ImportLibraryItem {
515-
name: import.name.to_string(),
516-
ordinal: import.ordinal(),
517-
symbol_name: None,
518-
is_data: !import.is_fn,
519-
}
520-
}
521-
})
522-
.collect();
523-
524-
archive_builder_builder.create_dll_import_lib(
525-
sess,
526-
&raw_dylib_name,
527-
items,
528-
&output_path,
529-
);
530-
531-
output_path
532-
})
533-
.collect()
534-
}
535-
536440
/// Create a static archive.
537441
///
538442
/// This is essentially the same thing as an rlib, but it also involves adding all of the upstream
@@ -2370,15 +2274,39 @@ fn linker_with_args(
23702274
link_output_kind,
23712275
);
23722276

2277+
// Raw-dylibs from all crates.
2278+
let raw_dylib_dir = tmpdir.join("raw-dylibs");
2279+
if sess.target.binary_format() == object::BinaryFormat::Elf {
2280+
// On ELF we can't pass the raw-dylibs stubs to the linker as a path,
2281+
// instead we need to pass them via -l. To find the stub, we need to add
2282+
// the directory of the stub to the linker search path.
2283+
// We make an extra directory for this to avoid polluting the search path.
2284+
if let Err(error) = fs::create_dir(&raw_dylib_dir) {
2285+
sess.dcx().emit_fatal(errors::CreateTempDir { error })
2286+
}
2287+
cmd.include_path(&raw_dylib_dir);
2288+
}
2289+
23732290
// Link with the import library generated for any raw-dylib functions.
2374-
for output_path in create_dll_import_libs(
2375-
sess,
2376-
archive_builder_builder,
2377-
codegen_results.crate_info.used_libraries.iter(),
2378-
tmpdir,
2379-
true,
2380-
) {
2381-
cmd.add_object(&output_path);
2291+
if sess.target.is_like_windows {
2292+
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
2293+
sess,
2294+
archive_builder_builder,
2295+
codegen_results.crate_info.used_libraries.iter(),
2296+
tmpdir,
2297+
true,
2298+
) {
2299+
cmd.add_object(&output_path);
2300+
}
2301+
} else {
2302+
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
2303+
sess,
2304+
codegen_results.crate_info.used_libraries.iter(),
2305+
&raw_dylib_dir,
2306+
) {
2307+
// Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects.
2308+
cmd.link_dylib_by_name(&link_path, true, false);
2309+
}
23822310
}
23832311
// As with add_upstream_native_libraries, we need to add the upstream raw-dylib symbols in case
23842312
// they are used within inlined functions or instantiated generic functions. We do this *after*
@@ -2397,19 +2325,35 @@ fn linker_with_args(
23972325
.native_libraries
23982326
.iter()
23992327
.filter_map(|(&cnum, libraries)| {
2400-
(dependency_linkage[cnum] != Linkage::Static).then_some(libraries)
2328+
if sess.target.is_like_windows {
2329+
(dependency_linkage[cnum] != Linkage::Static).then_some(libraries)
2330+
} else {
2331+
Some(libraries)
2332+
}
24012333
})
24022334
.flatten()
24032335
.collect::<Vec<_>>();
24042336
native_libraries_from_nonstatics.sort_unstable_by(|a, b| a.name.as_str().cmp(b.name.as_str()));
2405-
for output_path in create_dll_import_libs(
2406-
sess,
2407-
archive_builder_builder,
2408-
native_libraries_from_nonstatics,
2409-
tmpdir,
2410-
false,
2411-
) {
2412-
cmd.add_object(&output_path);
2337+
2338+
if sess.target.is_like_windows {
2339+
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
2340+
sess,
2341+
archive_builder_builder,
2342+
native_libraries_from_nonstatics,
2343+
tmpdir,
2344+
false,
2345+
) {
2346+
cmd.add_object(&output_path);
2347+
}
2348+
} else {
2349+
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
2350+
sess,
2351+
native_libraries_from_nonstatics,
2352+
&raw_dylib_dir,
2353+
) {
2354+
// Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects.
2355+
cmd.link_dylib_by_name(&link_path, true, false);
2356+
}
24132357
}
24142358

24152359
// Library linking above uses some global state for things like `-Bstatic`/`-Bdynamic` to make

0 commit comments

Comments
 (0)