Auto merge of #103311 - petrochenkov:pblfix, r=bjorn3

linker: Refactoring and fixes to native library linking

This PR contains a bunch of code cleanup and comment rearrangements + 2 fixes for `-Zpacked-bundled-libs`.
It's better to look at individual commits.
This commit is contained in:
bors 2022-11-12 23:29:11 +00:00
commit fb6667a233
4 changed files with 353 additions and 391 deletions

View file

@ -6,7 +6,7 @@ use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::{ErrorGuaranteed, Handler};
use rustc_fs_util::fix_windows_verbatim_for_gcc;
use rustc_hir::def_id::CrateNum;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_metadata::find_native_static_library;
use rustc_metadata::fs::{emit_metadata, METADATA_FILENAME};
use rustc_middle::middle::dependency_format::Linkage;
@ -24,7 +24,7 @@ use rustc_span::symbol::Symbol;
use rustc_span::DebuggerVisualizerFile;
use rustc_target::spec::crt_objects::{CrtObjects, LinkSelfContainedDefault};
use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy};
use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, Target};
use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo};
use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder};
use super::command::Command;
@ -2007,15 +2007,9 @@ fn linker_with_args<'a>(
cmd.add_as_needed();
// Local native libraries of all kinds.
//
// If `-Zlink-native-libraries=false` is set, then the assumption is that an
// external build system already has the native dependencies defined, and it
// will provide them to the linker itself.
if sess.opts.unstable_opts.link_native_libraries {
add_local_native_libraries(cmd, sess, codegen_results);
}
add_local_native_libraries(cmd, sess, archive_builder_builder, codegen_results, tmpdir);
// Upstream rust libraries and their (possibly bundled) static native libraries.
// Upstream rust crates and their non-dynamic native libraries.
add_upstream_rust_crates(
cmd,
sess,
@ -2026,13 +2020,7 @@ fn linker_with_args<'a>(
);
// Dynamic native libraries from upstream crates.
//
// FIXME: Merge this to `add_upstream_rust_crates` so that all native libraries are linked
// together with their respective upstream crates, and in their originally specified order.
// This may be slightly breaking due to our use of `--as-needed` and needs a crater run.
if sess.opts.unstable_opts.link_native_libraries {
add_upstream_native_libraries(cmd, sess, codegen_results);
}
add_upstream_native_libraries(cmd, sess, archive_builder_builder, codegen_results, tmpdir);
// Link with the import library generated for any raw-dylib functions.
for (raw_dylib_name, raw_dylib_imports) in
@ -2276,42 +2264,46 @@ fn collect_natvis_visualizers(
visualizer_paths
}
/// # Native library linking
///
/// User-supplied library search paths (-L on the command line). These are the same paths used to
/// find Rust crates, so some of them may have been added already by the previous crate linking
/// code. This only allows them to be found at compile time so it is still entirely up to outside
/// forces to make sure that library can be found at runtime.
///
/// Also note that the native libraries linked here are only the ones located in the current crate.
/// Upstream crates with native library dependencies may have their native library pulled in above.
fn add_local_native_libraries(
fn add_native_libs_from_crate(
cmd: &mut dyn Linker,
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
codegen_results: &CodegenResults,
tmpdir: &Path,
search_paths: &OnceCell<Vec<PathBuf>>,
bundled_libs: &FxHashSet<Symbol>,
cnum: CrateNum,
link_static: bool,
link_dynamic: bool,
) {
let filesearch = sess.target_filesearch(PathKind::All);
for search_path in filesearch.search_paths() {
match search_path.kind {
PathKind::Framework => {
cmd.framework_path(&search_path.dir);
}
_ => {
cmd.include_path(&fix_windows_verbatim_for_gcc(&search_path.dir));
}
}
if !sess.opts.unstable_opts.link_native_libraries {
// If `-Zlink-native-libraries=false` is set, then the assumption is that an
// external build system already has the native dependencies defined, and it
// will provide them to the linker itself.
return;
}
let relevant_libs =
codegen_results.crate_info.used_libraries.iter().filter(|l| relevant_lib(sess, l));
if link_static && cnum != LOCAL_CRATE && !bundled_libs.is_empty() {
// If rlib contains native libs as archives, unpack them to tmpdir.
let rlib = &codegen_results.crate_info.used_crate_source[&cnum].rlib.as_ref().unwrap().0;
archive_builder_builder
.extract_bundled_libs(rlib, tmpdir, &bundled_libs)
.unwrap_or_else(|e| sess.emit_fatal(e));
}
let native_libs = match cnum {
LOCAL_CRATE => &codegen_results.crate_info.used_libraries,
_ => &codegen_results.crate_info.native_libraries[&cnum],
};
let search_path = OnceCell::new();
let mut last = (None, NativeLibKind::Unspecified, None);
for lib in relevant_libs {
for lib in native_libs {
let Some(name) = lib.name else {
continue;
};
let name = name.as_str();
if !relevant_lib(sess, lib) {
continue;
}
// Skip if this library is the same as the last.
last = if (lib.name, lib.kind, lib.verbatim) == last {
@ -2320,46 +2312,119 @@ fn add_local_native_libraries(
(lib.name, lib.kind, lib.verbatim)
};
let name = name.as_str();
let verbatim = lib.verbatim.unwrap_or(false);
match lib.kind {
NativeLibKind::Static { bundle, whole_archive } => {
if link_static {
let bundle = bundle.unwrap_or(true);
let whole_archive = whole_archive == Some(true)
// Backward compatibility case: this can be a rlib (so `+whole-archive`
// cannot be added explicitly if necessary, see the error in `fn link_rlib`)
// compiled as an executable due to `--test`. Use whole-archive implicitly,
// like before the introduction of native lib modifiers.
|| (whole_archive == None
&& bundle
&& cnum == LOCAL_CRATE
&& sess.opts.test);
if bundle && cnum != LOCAL_CRATE {
if let Some(filename) = lib.filename {
// If rlib contains native libs as archives, they are unpacked to tmpdir.
let path = tmpdir.join(filename.as_str());
if whole_archive {
cmd.link_whole_rlib(&path);
} else {
cmd.link_rlib(&path);
}
}
} else {
if whole_archive {
cmd.link_whole_staticlib(
name,
verbatim,
&search_paths.get_or_init(|| archive_search_paths(sess)),
);
} else {
// HACK/FIXME: Fixup a circular dependency between libgcc and libc
// with glibc. This logic should be moved to the libc crate.
if cnum != LOCAL_CRATE
&& sess.target.os == "linux"
&& sess.target.env == "gnu"
&& name == "c"
{
cmd.link_staticlib("gcc", false);
}
cmd.link_staticlib(name, verbatim)
}
}
}
}
NativeLibKind::Dylib { as_needed } => {
cmd.link_dylib(name, verbatim, as_needed.unwrap_or(true))
if link_dynamic {
cmd.link_dylib(name, verbatim, as_needed.unwrap_or(true))
}
}
NativeLibKind::Unspecified => {
if link_dynamic {
cmd.link_dylib(name, verbatim, true);
}
}
NativeLibKind::Unspecified => cmd.link_dylib(name, verbatim, true),
NativeLibKind::Framework { as_needed } => {
cmd.link_framework(name, as_needed.unwrap_or(true))
}
NativeLibKind::Static { whole_archive, bundle, .. } => {
if whole_archive == Some(true)
// Backward compatibility case: this can be a rlib (so `+whole-archive` cannot
// be added explicitly if necessary, see the error in `fn link_rlib`) compiled
// as an executable due to `--test`. Use whole-archive implicitly, like before
// the introduction of native lib modifiers.
|| (whole_archive == None && bundle != Some(false) && sess.opts.test)
{
cmd.link_whole_staticlib(
name,
verbatim,
&search_path.get_or_init(|| archive_search_paths(sess)),
);
} else {
cmd.link_staticlib(name, verbatim)
if link_dynamic {
cmd.link_framework(name, as_needed.unwrap_or(true))
}
}
NativeLibKind::RawDylib => {
// Ignore RawDylib here, they are handled separately in linker_with_args().
// Handled separately in `linker_with_args`.
}
NativeLibKind::LinkArg => {
cmd.arg(name);
if link_static {
cmd.arg(name);
}
}
}
}
}
/// # Linking Rust crates and their non-bundled static libraries
///
/// Rust crates are not considered at all when creating an rlib output. All dependencies will be
/// linked when producing the final output (instead of the intermediate rlib version).
fn add_local_native_libraries(
cmd: &mut dyn Linker,
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
codegen_results: &CodegenResults,
tmpdir: &Path,
) {
if sess.opts.unstable_opts.link_native_libraries {
// User-supplied library search paths (-L on the command line). These are the same paths
// used to find Rust crates, so some of them may have been added already by the previous
// crate linking code. This only allows them to be found at compile time so it is still
// entirely up to outside forces to make sure that library can be found at runtime.
for search_path in sess.target_filesearch(PathKind::All).search_paths() {
match search_path.kind {
PathKind::Framework => cmd.framework_path(&search_path.dir),
_ => cmd.include_path(&fix_windows_verbatim_for_gcc(&search_path.dir)),
}
}
}
let search_paths = OnceCell::new();
// All static and dynamic native library dependencies are linked to the local crate.
let link_static = true;
let link_dynamic = true;
add_native_libs_from_crate(
cmd,
sess,
archive_builder_builder,
codegen_results,
tmpdir,
&search_paths,
&Default::default(),
LOCAL_CRATE,
link_static,
link_dynamic,
);
}
fn add_upstream_rust_crates<'a>(
cmd: &mut dyn Linker,
sess: &'a Session,
@ -2375,7 +2440,6 @@ fn add_upstream_rust_crates<'a>(
// Linking to a rlib involves just passing it to the linker (the linker
// will slurp up the object files inside), and linking to a dynamic library
// involves just passing the right -l flag.
let (_, data) = codegen_results
.crate_info
.dependency_formats
@ -2383,348 +2447,236 @@ fn add_upstream_rust_crates<'a>(
.find(|(ty, _)| *ty == crate_type)
.expect("failed to find crate type in dependency format list");
// Invoke get_used_crates to ensure that we get a topological sorting of
// crates.
let deps = &codegen_results.crate_info.used_crates;
let search_paths = OnceCell::new();
for &cnum in &codegen_results.crate_info.used_crates {
// We may not pass all crates through to the linker. Some crates may appear statically in
// an existing dylib, meaning we'll pick up all the symbols from the dylib.
// We must always link crates `compiler_builtins` and `profiler_builtins` statically.
// Even if they were already included into a dylib
// (e.g. `libstd` when `-C prefer-dynamic` is used).
// FIXME: `dependency_formats` can report `profiler_builtins` as `NotLinked` for some
// reason, it shouldn't do that because `profiler_builtins` should indeed be linked.
let linkage = data[cnum.as_usize() - 1];
let link_static_crate = linkage == Linkage::Static
|| (linkage == Linkage::IncludedFromDylib || linkage == Linkage::NotLinked)
&& (codegen_results.crate_info.compiler_builtins == Some(cnum)
|| codegen_results.crate_info.profiler_runtime == Some(cnum));
let mut compiler_builtins = None;
let search_path = OnceCell::new();
for &cnum in deps.iter() {
// We may not pass all crates through to the linker. Some crates may
// appear statically in an existing dylib, meaning we'll pick up all the
// symbols from the dylib.
let src = &codegen_results.crate_info.used_crate_source[&cnum];
match data[cnum.as_usize() - 1] {
_ if codegen_results.crate_info.profiler_runtime == Some(cnum) => {
add_static_crate(
cmd,
sess,
archive_builder_builder,
codegen_results,
tmpdir,
cnum,
&Default::default(),
);
}
// compiler-builtins are always placed last to ensure that they're
// linked correctly.
_ if codegen_results.crate_info.compiler_builtins == Some(cnum) => {
assert!(compiler_builtins.is_none());
compiler_builtins = Some(cnum);
}
Linkage::NotLinked | Linkage::IncludedFromDylib => {}
Linkage::Static => {
let bundled_libs = if sess.opts.unstable_opts.packed_bundled_libs {
codegen_results.crate_info.native_libraries[&cnum]
let mut bundled_libs = Default::default();
match linkage {
Linkage::Static | Linkage::IncludedFromDylib | Linkage::NotLinked => {
if link_static_crate {
bundled_libs = codegen_results.crate_info.native_libraries[&cnum]
.iter()
.filter_map(|lib| lib.filename)
.collect::<FxHashSet<_>>()
} else {
Default::default()
};
add_static_crate(
cmd,
sess,
archive_builder_builder,
codegen_results,
tmpdir,
cnum,
&bundled_libs,
);
// Link static native libs with "-bundle" modifier only if the crate they originate from
// is being linked statically to the current crate. If it's linked dynamically
// or is an rlib already included via some other dylib crate, the symbols from
// native libs will have already been included in that dylib.
//
// If `-Zlink-native-libraries=false` is set, then the assumption is that an
// external build system already has the native dependencies defined, and it
// will provide them to the linker itself.
if sess.opts.unstable_opts.link_native_libraries {
if sess.opts.unstable_opts.packed_bundled_libs {
// If rlib contains native libs as archives, unpack them to tmpdir.
let rlib = &src.rlib.as_ref().unwrap().0;
archive_builder_builder
.extract_bundled_libs(rlib, tmpdir, &bundled_libs)
.unwrap_or_else(|e| sess.emit_fatal(e));
}
let mut last = (None, NativeLibKind::Unspecified, None);
for lib in &codegen_results.crate_info.native_libraries[&cnum] {
let Some(name) = lib.name else {
continue;
};
let name = name.as_str();
if !relevant_lib(sess, lib) {
continue;
}
// Skip if this library is the same as the last.
last = if (lib.name, lib.kind, lib.verbatim) == last {
continue;
} else {
(lib.name, lib.kind, lib.verbatim)
};
match lib.kind {
NativeLibKind::Static {
bundle: Some(false),
whole_archive: Some(true),
} => {
cmd.link_whole_staticlib(
name,
lib.verbatim.unwrap_or(false),
search_path.get_or_init(|| archive_search_paths(sess)),
);
}
NativeLibKind::Static {
bundle: Some(false),
whole_archive: Some(false) | None,
} => {
// HACK/FIXME: Fixup a circular dependency between libgcc and libc
// with glibc. This logic should be moved to the libc crate.
if sess.target.os == "linux"
&& sess.target.env == "gnu"
&& name == "c"
{
cmd.link_staticlib("gcc", false);
}
cmd.link_staticlib(name, lib.verbatim.unwrap_or(false));
}
NativeLibKind::LinkArg => {
cmd.arg(name);
}
NativeLibKind::Dylib { .. }
| NativeLibKind::Framework { .. }
| NativeLibKind::Unspecified
| NativeLibKind::RawDylib => {}
NativeLibKind::Static { bundle: Some(true) | None, whole_archive } => {
if sess.opts.unstable_opts.packed_bundled_libs {
// If rlib contains native libs as archives, they are unpacked to tmpdir.
let path = tmpdir.join(lib.filename.unwrap().as_str());
if whole_archive == Some(true) {
cmd.link_whole_rlib(&path);
} else {
cmd.link_rlib(&path);
}
}
}
}
}
.collect();
add_static_crate(
cmd,
sess,
archive_builder_builder,
codegen_results,
tmpdir,
cnum,
&bundled_libs,
);
}
}
Linkage::Dynamic => add_dynamic_crate(cmd, sess, &src.dylib.as_ref().unwrap().0),
Linkage::Dynamic => {
let src = &codegen_results.crate_info.used_crate_source[&cnum];
add_dynamic_crate(cmd, sess, &src.dylib.as_ref().unwrap().0);
}
}
}
// compiler-builtins are always placed last to ensure that they're
// linked correctly.
// We must always link the `compiler_builtins` crate statically. Even if it
// was already "included" in a dylib (e.g., `libstd` when `-C prefer-dynamic`
// is used)
if let Some(cnum) = compiler_builtins {
add_static_crate(
// Static libraries are linked for a subset of linked upstream crates.
// 1. If the upstream crate is a directly linked rlib then we must link the native library
// because the rlib is just an archive.
// 2. If the upstream crate is a dylib or a rlib linked through dylib, then we do not link
// the native library because it is already linked into the dylib, and even if
// inline/const/generic functions from the dylib can refer to symbols from the native
// library, those symbols should be exported and available from the dylib anyway.
// 3. Libraries bundled into `(compiler,profiler)_builtins` are special, see above.
let link_static = link_static_crate;
// Dynamic libraries are not linked here, see the FIXME in `add_upstream_native_libraries`.
let link_dynamic = false;
add_native_libs_from_crate(
cmd,
sess,
archive_builder_builder,
codegen_results,
tmpdir,
&search_paths,
&bundled_libs,
cnum,
&Default::default(),
);
}
// Converts a library file-stem into a cc -l argument
fn unlib<'a>(target: &Target, stem: &'a str) -> &'a str {
if stem.starts_with("lib") && !target.is_like_windows { &stem[3..] } else { stem }
}
// Adds the static "rlib" versions of all crates to the command line.
// There's a bit of magic which happens here specifically related to LTO,
// namely that we remove upstream object files.
//
// When performing LTO, almost(*) all of the bytecode from the upstream
// libraries has already been included in our object file output. As a
// result we need to remove the object files in the upstream libraries so
// the linker doesn't try to include them twice (or whine about duplicate
// symbols). We must continue to include the rest of the rlib, however, as
// it may contain static native libraries which must be linked in.
//
// (*) Crates marked with `#![no_builtins]` don't participate in LTO and
// their bytecode wasn't included. The object files in those libraries must
// still be passed to the linker.
//
// Note, however, that if we're not doing LTO we can just pass the rlib
// blindly to the linker (fast) because it's fine if it's not actually
// included as we're at the end of the dependency chain.
fn add_static_crate<'a>(
cmd: &mut dyn Linker,
sess: &'a Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
codegen_results: &CodegenResults,
tmpdir: &Path,
cnum: CrateNum,
bundled_lib_file_names: &FxHashSet<Symbol>,
) {
let src = &codegen_results.crate_info.used_crate_source[&cnum];
let cratepath = &src.rlib.as_ref().unwrap().0;
let mut link_upstream = |path: &Path| {
cmd.link_rlib(&fix_windows_verbatim_for_gcc(path));
};
// See the comment above in `link_staticlib` and `link_rlib` for why if
// there's a static library that's not relevant we skip all object
// files.
let native_libs = &codegen_results.crate_info.native_libraries[&cnum];
let skip_native = native_libs.iter().any(|lib| {
matches!(lib.kind, NativeLibKind::Static { bundle: None | Some(true), .. })
&& !relevant_lib(sess, lib)
});
if (!are_upstream_rust_objects_already_included(sess)
|| ignored_for_lto(sess, &codegen_results.crate_info, cnum))
&& !skip_native
{
link_upstream(cratepath);
return;
}
let dst = tmpdir.join(cratepath.file_name().unwrap());
let name = cratepath.file_name().unwrap().to_str().unwrap();
let name = &name[3..name.len() - 5]; // chop off lib/.rlib
let bundled_lib_file_names = bundled_lib_file_names.clone();
sess.prof.generic_activity_with_arg("link_altering_rlib", name).run(|| {
let canonical_name = name.replace('-', "_");
let upstream_rust_objects_already_included =
are_upstream_rust_objects_already_included(sess);
let is_builtins = sess.target.no_builtins
|| !codegen_results.crate_info.is_no_builtins.contains(&cnum);
let mut archive = archive_builder_builder.new_archive_builder(sess);
if let Err(error) = archive.add_archive(
cratepath,
Box::new(move |f| {
if f == METADATA_FILENAME {
return true;
}
let canonical = f.replace('-', "_");
let is_rust_object =
canonical.starts_with(&canonical_name) && looks_like_rust_object_file(&f);
// If we've been requested to skip all native object files
// (those not generated by the rust compiler) then we can skip
// this file. See above for why we may want to do this.
let skip_because_cfg_say_so = skip_native && !is_rust_object;
// If we're performing LTO and this is a rust-generated object
// file, then we don't need the object file as it's part of the
// LTO module. Note that `#![no_builtins]` is excluded from LTO,
// though, so we let that object file slide.
let skip_because_lto =
upstream_rust_objects_already_included && is_rust_object && is_builtins;
// We skip native libraries because:
// 1. This native libraries won't be used from the generated rlib,
// so we can throw them away to avoid the copying work.
// 2. We can't allow it to be a single remaining entry in archive
// as some linkers may complain on that.
if bundled_lib_file_names.contains(&Symbol::intern(f)) {
return true;
}
if skip_because_cfg_say_so || skip_because_lto {
return true;
}
false
}),
) {
sess.emit_fatal(errors::RlibArchiveBuildFailure { error });
}
if archive.build(&dst) {
link_upstream(&dst);
}
});
}
// Same thing as above, but for dynamic crates instead of static crates.
fn add_dynamic_crate(cmd: &mut dyn Linker, sess: &Session, cratepath: &Path) {
// Just need to tell the linker about where the library lives and
// what its name is
let parent = cratepath.parent();
if let Some(dir) = parent {
cmd.include_path(&fix_windows_verbatim_for_gcc(dir));
}
let filestem = cratepath.file_stem().unwrap().to_str().unwrap();
cmd.link_rust_dylib(
&unlib(&sess.target, filestem),
parent.unwrap_or_else(|| Path::new("")),
link_static,
link_dynamic,
);
}
}
/// Link in all of our upstream crates' native dependencies. Remember that all of these upstream
/// native dependencies are all non-static dependencies. We've got two cases then:
///
/// 1. The upstream crate is an rlib. In this case we *must* link in the native dependency because
/// the rlib is just an archive.
///
/// 2. The upstream crate is a dylib. In order to use the dylib, we have to have the dependency
/// present on the system somewhere. Thus, we don't gain a whole lot from not linking in the
/// dynamic dependency to this crate as well.
///
/// The use case for this is a little subtle. In theory the native dependencies of a crate are
/// purely an implementation detail of the crate itself, but the problem arises with generic and
/// inlined functions. If a generic function calls a native function, then the generic function
/// must be instantiated in the target crate, meaning that the native symbol must also be resolved
/// in the target crate.
fn add_upstream_native_libraries(
cmd: &mut dyn Linker,
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
codegen_results: &CodegenResults,
tmpdir: &Path,
) {
let mut last = (None, NativeLibKind::Unspecified, None);
let search_path = OnceCell::new();
for &cnum in &codegen_results.crate_info.used_crates {
for lib in codegen_results.crate_info.native_libraries[&cnum].iter() {
let Some(name) = lib.name else {
continue;
};
let name = name.as_str();
if !relevant_lib(sess, &lib) {
continue;
}
// Skip if this library is the same as the last.
last = if (lib.name, lib.kind, lib.verbatim) == last {
continue;
} else {
(lib.name, lib.kind, lib.verbatim)
};
let verbatim = lib.verbatim.unwrap_or(false);
match lib.kind {
NativeLibKind::Dylib { as_needed } => {
cmd.link_dylib(name, verbatim, as_needed.unwrap_or(true))
}
NativeLibKind::Unspecified => cmd.link_dylib(name, verbatim, true),
NativeLibKind::Framework { as_needed } => {
cmd.link_framework(name, as_needed.unwrap_or(true))
}
// ignore static native libraries here as we've
// already included them in add_local_native_libraries and
// add_upstream_rust_crates
NativeLibKind::Static { .. } => {}
NativeLibKind::RawDylib | NativeLibKind::LinkArg => {}
}
}
// Static libraries are not linked here, they are linked in `add_upstream_rust_crates`.
// FIXME: Merge this function to `add_upstream_rust_crates` so that all native libraries
// are linked together with their respective upstream crates, and in their originally
// specified order. This is slightly breaking due to our use of `--as-needed` (see crater
// results in https://github.com/rust-lang/rust/pull/102832#issuecomment-1279772306).
let link_static = false;
// Dynamic libraries are linked for all linked upstream crates.
// 1. If the upstream crate is a directly linked rlib then we must link the native library
// because the rlib is just an archive.
// 2. If the upstream crate is a dylib or a rlib linked through dylib, then we have to link
// the native library too because inline/const/generic functions from the dylib can refer
// to symbols from the native library, so the native library providing those symbols should
// be available when linking our final binary.
let link_dynamic = true;
add_native_libs_from_crate(
cmd,
sess,
archive_builder_builder,
codegen_results,
tmpdir,
&search_path,
&Default::default(),
cnum,
link_static,
link_dynamic,
);
}
}
// Adds the static "rlib" versions of all crates to the command line.
// There's a bit of magic which happens here specifically related to LTO,
// namely that we remove upstream object files.
//
// When performing LTO, almost(*) all of the bytecode from the upstream
// libraries has already been included in our object file output. As a
// result we need to remove the object files in the upstream libraries so
// the linker doesn't try to include them twice (or whine about duplicate
// symbols). We must continue to include the rest of the rlib, however, as
// it may contain static native libraries which must be linked in.
//
// (*) Crates marked with `#![no_builtins]` don't participate in LTO and
// their bytecode wasn't included. The object files in those libraries must
// still be passed to the linker.
//
// Note, however, that if we're not doing LTO we can just pass the rlib
// blindly to the linker (fast) because it's fine if it's not actually
// included as we're at the end of the dependency chain.
fn add_static_crate<'a>(
cmd: &mut dyn Linker,
sess: &'a Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
codegen_results: &CodegenResults,
tmpdir: &Path,
cnum: CrateNum,
bundled_lib_file_names: &FxHashSet<Symbol>,
) {
let src = &codegen_results.crate_info.used_crate_source[&cnum];
let cratepath = &src.rlib.as_ref().unwrap().0;
let mut link_upstream = |path: &Path| {
cmd.link_rlib(&fix_windows_verbatim_for_gcc(path));
};
// See the comment above in `link_staticlib` and `link_rlib` for why if
// there's a static library that's not relevant we skip all object
// files.
let native_libs = &codegen_results.crate_info.native_libraries[&cnum];
let skip_native = native_libs.iter().any(|lib| {
matches!(lib.kind, NativeLibKind::Static { bundle: None | Some(true), .. })
&& !relevant_lib(sess, lib)
});
if (!are_upstream_rust_objects_already_included(sess)
|| ignored_for_lto(sess, &codegen_results.crate_info, cnum))
&& !skip_native
{
link_upstream(cratepath);
return;
}
let dst = tmpdir.join(cratepath.file_name().unwrap());
let name = cratepath.file_name().unwrap().to_str().unwrap();
let name = &name[3..name.len() - 5]; // chop off lib/.rlib
let bundled_lib_file_names = bundled_lib_file_names.clone();
sess.prof.generic_activity_with_arg("link_altering_rlib", name).run(|| {
let canonical_name = name.replace('-', "_");
let upstream_rust_objects_already_included =
are_upstream_rust_objects_already_included(sess);
let is_builtins =
sess.target.no_builtins || !codegen_results.crate_info.is_no_builtins.contains(&cnum);
let mut archive = archive_builder_builder.new_archive_builder(sess);
if let Err(e) = archive.add_archive(
cratepath,
Box::new(move |f| {
if f == METADATA_FILENAME {
return true;
}
let canonical = f.replace('-', "_");
let is_rust_object =
canonical.starts_with(&canonical_name) && looks_like_rust_object_file(&f);
// If we've been requested to skip all native object files
// (those not generated by the rust compiler) then we can skip
// this file. See above for why we may want to do this.
let skip_because_cfg_say_so = skip_native && !is_rust_object;
// If we're performing LTO and this is a rust-generated object
// file, then we don't need the object file as it's part of the
// LTO module. Note that `#![no_builtins]` is excluded from LTO,
// though, so we let that object file slide.
let skip_because_lto =
upstream_rust_objects_already_included && is_rust_object && is_builtins;
// We skip native libraries because:
// 1. This native libraries won't be used from the generated rlib,
// so we can throw them away to avoid the copying work.
// 2. We can't allow it to be a single remaining entry in archive
// as some linkers may complain on that.
if bundled_lib_file_names.contains(&Symbol::intern(f)) {
return true;
}
if skip_because_cfg_say_so || skip_because_lto {
return true;
}
false
}),
) {
sess.fatal(&format!("failed to build archive from rlib: {}", e));
}
if archive.build(&dst) {
link_upstream(&dst);
}
});
}
// Same thing as above, but for dynamic crates instead of static crates.
fn add_dynamic_crate(cmd: &mut dyn Linker, sess: &Session, cratepath: &Path) {
// Just need to tell the linker about where the library lives and
// what its name is
let parent = cratepath.parent();
if let Some(dir) = parent {
cmd.include_path(&fix_windows_verbatim_for_gcc(dir));
}
let stem = cratepath.file_stem().unwrap().to_str().unwrap();
// Convert library file-stem into a cc -l argument.
let prefix = if stem.starts_with("lib") && !sess.target.is_like_windows { 3 } else { 0 };
cmd.link_rust_dylib(&stem[prefix..], parent.unwrap_or_else(|| Path::new("")));
}
fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {
match lib.cfg {
Some(ref cfg) => rustc_attr::cfg_matches(cfg, &sess.parse_sess, CRATE_NODE_ID, None),

View file

@ -833,20 +833,30 @@ impl CrateInfo {
//
// In order to get this left-to-right dependency ordering, we use the reverse
// postorder of all crates putting the leaves at the right-most positions.
let used_crates = tcx
let mut compiler_builtins = None;
let mut used_crates: Vec<_> = tcx
.postorder_cnums(())
.iter()
.rev()
.copied()
.filter(|&cnum| !tcx.dep_kind(cnum).macros_only())
.filter(|&cnum| {
let link = !tcx.dep_kind(cnum).macros_only();
if link && tcx.is_compiler_builtins(cnum) {
compiler_builtins = Some(cnum);
return false;
}
link
})
.collect();
// `compiler_builtins` are always placed last to ensure that they're linked correctly.
used_crates.extend(compiler_builtins);
let mut info = CrateInfo {
target_cpu,
exported_symbols,
linked_symbols,
local_crate_name,
compiler_builtins: None,
compiler_builtins,
profiler_runtime: None,
is_no_builtins: Default::default(),
native_libraries: Default::default(),
@ -872,9 +882,6 @@ impl CrateInfo {
let used_crate_source = tcx.used_crate_source(cnum);
info.used_crate_source.insert(cnum, used_crate_source.clone());
if tcx.is_compiler_builtins(cnum) {
info.compiler_builtins = Some(cnum);
}
if tcx.is_profiler_runtime(cnum) {
info.profiler_runtime = Some(cnum);
}

View file

@ -9,4 +9,4 @@ all:
$(RUSTC) empty.rs --cfg bar 2>&1 | $(CGREP) '"-ltesta" "-ltestb" "-ltesta"'
$(RUSTC) empty.rs 2>&1 | $(CGREP) '"-ltesta"'
$(RUSTC) empty.rs 2>&1 | $(CGREP) -v '"-ltestb"'
$(RUSTC) empty.rs 2>&1 | $(CGREP) -v '"-ltesta" "-ltesta"'
$(RUSTC) empty.rs 2>&1 | $(CGREP) -v '"-ltesta" "-ltesta" "-ltesta"'

View file

@ -5,3 +5,6 @@ extern "C" {}
#[link(name = "testa")]
extern "C" {}
#[link(name = "testa")]
extern "C" {}