Skip to content

Commit b6e3eb5

Browse files
authored
Rollup merge of rust-lang#135695 - Noratrieb:elf-raw-dylib, r=bjorn3
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. I was inspired by Björn's comments in https://internals.rust-lang.org/t/bundle-zig-cc-in-rustup-by-default/22096/27 Tracking issue: rust-lang#135694 r? bjorn3 try-job: aarch64-apple try-job: x86_64-msvc-1 try-job: x86_64-msvc-2 try-job: test-various
2 parents fd17dea + a954c51 commit b6e3eb5

File tree

69 files changed

+839
-207
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

+839
-207
lines changed

compiler/rustc_codegen_ssa/src/back/link.rs

+81-137
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;
@@ -41,22 +42,21 @@ use rustc_session::{Session, filesearch};
4142
use rustc_span::Symbol;
4243
use rustc_target::spec::crt_objects::CrtObjects;
4344
use rustc_target::spec::{
44-
Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures,
45-
LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
46-
SplitDebuginfo,
45+
BinaryFormat, Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault,
46+
LinkerFeatures, LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel,
47+
SanitizerSet, SplitDebuginfo,
4748
};
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) {
@@ -376,16 +376,22 @@ fn link_rlib<'a>(
376376
}
377377
}
378378

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

391397
if let Some(trailing_metadata) = trailing_metadata {
@@ -426,108 +432,6 @@ fn link_rlib<'a>(
426432
ab
427433
}
428434

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

2329+
// Raw-dylibs from all crates.
2330+
let raw_dylib_dir = tmpdir.join("raw-dylibs");
2331+
if sess.target.binary_format == BinaryFormat::Elf {
2332+
// On ELF we can't pass the raw-dylibs stubs to the linker as a path,
2333+
// instead we need to pass them via -l. To find the stub, we need to add
2334+
// the directory of the stub to the linker search path.
2335+
// We make an extra directory for this to avoid polluting the search path.
2336+
if let Err(error) = fs::create_dir(&raw_dylib_dir) {
2337+
sess.dcx().emit_fatal(errors::CreateTempDir { error })
2338+
}
2339+
cmd.include_path(&raw_dylib_dir);
2340+
}
2341+
24252342
// Link with the import library generated for any raw-dylib functions.
2426-
for output_path in create_dll_import_libs(
2427-
sess,
2428-
archive_builder_builder,
2429-
codegen_results.crate_info.used_libraries.iter(),
2430-
tmpdir,
2431-
true,
2432-
) {
2433-
cmd.add_object(&output_path);
2343+
if sess.target.is_like_windows {
2344+
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
2345+
sess,
2346+
archive_builder_builder,
2347+
codegen_results.crate_info.used_libraries.iter(),
2348+
tmpdir,
2349+
true,
2350+
) {
2351+
cmd.add_object(&output_path);
2352+
}
2353+
} else {
2354+
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
2355+
sess,
2356+
codegen_results.crate_info.used_libraries.iter(),
2357+
&raw_dylib_dir,
2358+
) {
2359+
// Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects.
2360+
cmd.link_dylib_by_name(&link_path, true, false);
2361+
}
24342362
}
24352363
// As with add_upstream_native_libraries, we need to add the upstream raw-dylib symbols in case
24362364
// they are used within inlined functions or instantiated generic functions. We do this *after*
@@ -2449,19 +2377,35 @@ fn linker_with_args(
24492377
.native_libraries
24502378
.iter()
24512379
.filter_map(|(&cnum, libraries)| {
2452-
(dependency_linkage[cnum] != Linkage::Static).then_some(libraries)
2380+
if sess.target.is_like_windows {
2381+
(dependency_linkage[cnum] != Linkage::Static).then_some(libraries)
2382+
} else {
2383+
Some(libraries)
2384+
}
24532385
})
24542386
.flatten()
24552387
.collect::<Vec<_>>();
24562388
native_libraries_from_nonstatics.sort_unstable_by(|a, b| a.name.as_str().cmp(b.name.as_str()));
2457-
for output_path in create_dll_import_libs(
2458-
sess,
2459-
archive_builder_builder,
2460-
native_libraries_from_nonstatics,
2461-
tmpdir,
2462-
false,
2463-
) {
2464-
cmd.add_object(&output_path);
2389+
2390+
if sess.target.is_like_windows {
2391+
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
2392+
sess,
2393+
archive_builder_builder,
2394+
native_libraries_from_nonstatics,
2395+
tmpdir,
2396+
false,
2397+
) {
2398+
cmd.add_object(&output_path);
2399+
}
2400+
} else {
2401+
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
2402+
sess,
2403+
native_libraries_from_nonstatics,
2404+
&raw_dylib_dir,
2405+
) {
2406+
// Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects.
2407+
cmd.link_dylib_by_name(&link_path, true, false);
2408+
}
24652409
}
24662410

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

0 commit comments

Comments
 (0)