Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_llvm/src/back/lto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fn prepare_lto(
// with either fat or thin LTO
let mut upstream_modules = Vec::new();
if cgcx.lto != Lto::ThinLocal {
if cgcx.opts.cg.prefer_dynamic {
if cgcx.opts.cg.prefer_dynamic.is_non_empty() {
diag_handler
.struct_err("cannot prefer dynamic linking when performing LTO")
.note(
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_llvm/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ impl CodegenCx<'ll, 'tcx> {
debug_assert!(
!(self.tcx.sess.opts.cg.linker_plugin_lto.enabled()
&& self.tcx.sess.target.is_like_windows
&& self.tcx.sess.opts.cg.prefer_dynamic)
&& self.tcx.sess.opts.cg.prefer_dynamic.is_non_empty())
);

if needs_dll_storage_attr {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/back/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1950,7 +1950,7 @@ fn msvc_imps_needed(tcx: TyCtxt<'_>) -> bool {
assert!(
!(tcx.sess.opts.cg.linker_plugin_lto.enabled()
&& tcx.sess.target.is_like_windows
&& tcx.sess.opts.cg.prefer_dynamic)
&& tcx.sess.opts.cg.prefer_dynamic.is_non_empty())
);

tcx.sess.target.is_like_windows &&
Expand Down
14 changes: 12 additions & 2 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc_session::config::{build_configuration, build_session_options, to_crate
use rustc_session::config::{rustc_optgroups, ErrorOutputType, ExternLocation, Options, Passes};
use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath};
use rustc_session::config::{
Externs, OutputType, OutputTypes, SymbolManglingVersion, WasiExecModel,
Externs, OutputType, OutputTypes, PreferDynamicSet, SymbolManglingVersion, WasiExecModel,
};
use rustc_session::lint::Level;
use rustc_session::search_paths::SearchPath;
Expand Down Expand Up @@ -582,14 +582,24 @@ fn test_codegen_options_tracking_hash() {
tracked!(overflow_checks, Some(true));
tracked!(panic, Some(PanicStrategy::Abort));
tracked!(passes, vec![String::from("1"), String::from("2")]);
tracked!(prefer_dynamic, true);
tracked!(prefer_dynamic, PreferDynamicSet::every_crate());
tracked!(profile_generate, SwitchWithOptPath::Enabled(None));
tracked!(profile_use, Some(PathBuf::from("abc")));
tracked!(relocation_model, Some(RelocModel::Pic));
tracked!(soft_float, true);
tracked!(split_debuginfo, Some(SplitDebuginfo::Packed));
tracked!(target_cpu, Some(String::from("abc")));
tracked!(target_feature, String::from("all the features, all of them"));

// Check that changes to the ordering of input to `-C prefer-dynamic=crate,...`
// does not cause the dependency tracking hash to change.
{
let mut v1 = Options::default();
let mut v2 = Options::default();
v1.cg.prefer_dynamic = PreferDynamicSet::subset(["a", "b"].iter());
v2.cg.prefer_dynamic = PreferDynamicSet::subset(["b", "a"].iter());
assert_same_hash(&v1, &v2);
}
}

#[test]
Expand Down
152 changes: 117 additions & 35 deletions compiler/rustc_metadata/src/dependency_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,26 @@
//! all dynamic. This isn't currently very well battle tested, so it will likely
//! fall short in some use cases.
//!
//! Currently, there is no way to specify the preference of linkage with a
//! particular library (other than a global dynamic/static switch).
//! Additionally, the algorithm is geared towards finding *any* solution rather
//! than finding a number of solutions (there are normally quite a few).
//! The algorithm is geared towards finding *any* solution rather than finding a
//! number of solutions (there are normally quite a few). One can specify the
//! preference of linkage for a particular list of crates by specifying that
//! list via `-C prefer-dynamic=crate1,crate2,...`.

use crate::creader::CStore;

use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::CrateNum;
use rustc_middle::middle::cstore::CrateDepKind;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_middle::middle::cstore::LinkagePreference::{self, RequireDynamic, RequireStatic};
use rustc_middle::middle::cstore::{CrateDepKind, CrateSource};
use rustc_middle::middle::dependency_format::{Dependencies, DependencyList, Linkage};
use rustc_middle::ty::TyCtxt;
use rustc_session::config::CrateType;
use rustc_target::spec::PanicStrategy;

type RevDeps = FxHashMap<(CrateNum, LinkagePreference), Vec<CrateNum>>;

use rustc_data_structures::sync::Lrc;

crate fn calculate(tcx: TyCtxt<'_>) -> Dependencies {
tcx.sess
.crate_types()
Expand All @@ -81,23 +85,43 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
return Vec::new();
}

let prefer_dynamic = &sess.opts.cg.prefer_dynamic;

// traverses all crates to see if any are in the prefer-dynamic set.
let prefer_dynamic_for_any_crate = || {
if prefer_dynamic.is_empty() {
// skip traversal if we know it cannot ever bear fruit.
false
} else {
tcx.crates(()).iter().any(|cnum| prefer_dynamic.contains_crate(tcx.crate_name(*cnum)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should skip non-linkable crates like proc-macros and their dependencies I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I'll look into that.

}
};

let preferred_linkage = match ty {
// Generating a dylib without `-C prefer-dynamic` means that we're going
// to try to eagerly statically link all dependencies. This is normally
// done for end-product dylibs, not intermediate products.
// If `-C prefer-dynamic` is not set for any crate, then means that we're
// going to try to eagerly statically link all dependencies into the dylib.
// This is normally done for end-product dylibs, not intermediate products.
//
// Treat cdylibs similarly. If `-C prefer-dynamic` is set, the caller may
// be code-size conscious, but without it, it makes sense to statically
// link a cdylib.
CrateType::Dylib | CrateType::Cdylib if !sess.opts.cg.prefer_dynamic => Linkage::Static,
CrateType::Dylib | CrateType::Cdylib => Linkage::Dynamic,
// Treat cdylibs similarly. If `-C prefer-dynamic` is set for any given
// crate, the caller may be code-size conscious, but without it, it
// makes sense to statically link a cdylib.
CrateType::Dylib | CrateType::Cdylib => {
if prefer_dynamic_for_any_crate() {
Linkage::Dynamic
} else {
Linkage::Static
}
}

// If the global prefer_dynamic switch is turned off, or the final
// If `prefer_dynamic` is not set for any crate, or the final
// executable will be statically linked, prefer static crate linkage.
CrateType::Executable if !sess.opts.cg.prefer_dynamic || sess.crt_static(Some(ty)) => {
Linkage::Static
CrateType::Executable => {
if !prefer_dynamic_for_any_crate() || sess.crt_static(Some(ty)) {
Linkage::Static
} else {
Linkage::Dynamic
}
}
CrateType::Executable => Linkage::Dynamic,

// proc-macro crates are mostly cdylibs, but we also need metadata.
CrateType::ProcMacro => Linkage::Static,
Expand Down Expand Up @@ -133,7 +157,7 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
if tcx.dep_kind(cnum).macros_only() {
continue;
}
let src = tcx.used_crate_source(cnum);
let src: Lrc<CrateSource> = tcx.used_crate_source(cnum);
if src.rlib.is_some() {
continue;
}
Expand All @@ -144,10 +168,16 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
));
}
return Vec::new();
} else {
tracing::info!(
"calculate_type {} attempt_static failed; falling through to dynamic linkage",
tcx.crate_name(LOCAL_CRATE)
);
}
}

let mut formats = FxHashMap::default();
let mut rev_deps: RevDeps = FxHashMap::default();

// Sweep all crates for found dylibs. Add all dylibs, as well as their
// dependencies, ensuring there are no conflicts. The only valid case for a
Expand All @@ -157,14 +187,27 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
continue;
}
let name = tcx.crate_name(cnum);
let src = tcx.used_crate_source(cnum);
if src.dylib.is_some() {
tracing::info!("adding dylib: {}", name);
add_library(tcx, cnum, RequireDynamic, &mut formats);
let src: Lrc<CrateSource> = tcx.used_crate_source(cnum);
// We should prefer the dylib, when available, if either:
// 1. the user has requested that dylib (or all dylibs) via `-C prefer-dynamic`, or
// 2. for backwards compatibility: if the prefer-dynamic subset is unset, then we *still*
// favor dylibs here. This way, if static linking fails above, we might still hope to
// succeed at linking here.
let prefer_dylib =
|name| -> bool { prefer_dynamic.is_unset() || prefer_dynamic.contains_crate(name) };
if src.dylib.is_some() && prefer_dylib(name) {
tracing::info!("calculate_type {} adding dylib: {}", tcx.crate_name(LOCAL_CRATE), name);
add_library(tcx, cnum, RequireDynamic, &mut formats, &mut rev_deps, LOCAL_CRATE);
let deps = tcx.dylib_dependency_formats(cnum);
for &(depnum, style) in deps.iter() {
tracing::info!("adding {:?}: {}", style, tcx.crate_name(depnum));
add_library(tcx, depnum, style, &mut formats);
tracing::info!(
"calculate_type {} adding dep of {}, {:?}: {}",
tcx.crate_name(LOCAL_CRATE),
name,
style,
tcx.crate_name(depnum)
);
add_library(tcx, depnum, style, &mut formats, &mut rev_deps, cnum);
}
}
}
Expand All @@ -185,14 +228,17 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
// If the crate hasn't been included yet and it's not actually required
// (e.g., it's an allocator) then we skip it here as well.
for &cnum in tcx.crates(()).iter() {
let src = tcx.used_crate_source(cnum);
if src.dylib.is_none()
&& !formats.contains_key(&cnum)
&& tcx.dep_kind(cnum) == CrateDepKind::Explicit
{
let name = tcx.crate_name(cnum);
let src: Lrc<CrateSource> = tcx.used_crate_source(cnum);
let static_available = src.rlib.is_some() || src.rmeta.is_some();
let prefer_static =
src.dylib.is_none() || (static_available && !prefer_dynamic.contains_crate(name));
let missing = !formats.contains_key(&cnum);
let actually_required = tcx.dep_kind(cnum) == CrateDepKind::Explicit;
if prefer_static && missing && actually_required {
assert!(src.rlib.is_some() || src.rmeta.is_some());
tracing::info!("adding staticlib: {}", tcx.crate_name(cnum));
add_library(tcx, cnum, RequireStatic, &mut formats);
tracing::info!("adding staticlib: {}", name);
add_library(tcx, cnum, RequireStatic, &mut formats, &mut rev_deps, LOCAL_CRATE);
ret[cnum.as_usize() - 1] = Linkage::Static;
}
}
Expand All @@ -215,7 +261,7 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
// making sure that everything is available in the requested format.
for (cnum, kind) in ret.iter().enumerate() {
let cnum = CrateNum::new(cnum + 1);
let src = tcx.used_crate_source(cnum);
let src: Lrc<CrateSource> = tcx.used_crate_source(cnum);
match *kind {
Linkage::NotLinked | Linkage::IncludedFromDylib => {}
Linkage::Static if src.rlib.is_some() => continue,
Expand Down Expand Up @@ -243,7 +289,10 @@ fn add_library(
cnum: CrateNum,
link: LinkagePreference,
m: &mut FxHashMap<CrateNum, LinkagePreference>,
rev_deps: &mut RevDeps,
parent_cnum: CrateNum,
) {
rev_deps.entry((cnum, link)).or_insert(Vec::new()).push(parent_cnum);
match m.get(&cnum) {
Some(&link2) => {
// If the linkages differ, then we'd have two copies of the library
Expand All @@ -254,11 +303,44 @@ fn add_library(
// This error is probably a little obscure, but I imagine that it
// can be refined over time.
if link2 != link || link == RequireStatic {
let parent_names = |link| {
let mut names = String::new();
let mut saw_one = false;
for name in rev_deps[&(cnum, link)].iter().map(|pcnum| tcx.crate_name(*pcnum)) {
if saw_one {
names.push_str(", ");
}
names.push_str(&format!("`{}`", name));
saw_one = true;
}
names
};
let link_parent_names = parent_names(link);
let link2_parent_names = parent_names(link2);

let details = match (link2, link) {
(RequireDynamic, RequireStatic) => format!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💖

"previously required dynamic, via [{}], \
and now also requires static, via [{}]",
link2_parent_names, link_parent_names
),
(RequireStatic, RequireDynamic) => format!(
"previously required static, via [{}], \
and now also requires dynamic, via [{}]",
link2_parent_names, link_parent_names
),
(RequireStatic, RequireStatic) => format!(
"two static copies from multiple different locations, via [{}]",
link_parent_names
),
(RequireDynamic, RequireDynamic) => unreachable!("not a problem"),
};
tcx.sess
.struct_err(&format!(
"cannot satisfy dependencies so `{}` only \
shows up once",
tcx.crate_name(cnum)
shows up once ({})",
tcx.crate_name(cnum),
details,
))
.help(
"having upstream crates all available in one format \
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/middle/cstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl CrateDepKind {
}
}

#[derive(Copy, Debug, PartialEq, Clone, Encodable, Decodable, HashStable)]
#[derive(Copy, Debug, PartialEq, Eq, Clone, Encodable, Decodable, Hash, HashStable)]
pub enum LinkagePreference {
RequireDynamic,
RequireStatic,
Expand Down
Loading