Skip to content

Commit bf817f6

Browse files
committed
Generalize -C prefer-dyanmic to carry an optional subset of the crates to prefer dynamic linkage on.
Note: carrying an empty set here is *not* the same as `-C prefer-dyanmic=no` (and the latter is the default behavior for `rustc`). As discussed in detail in the comments for `enum PreferDynamicSet` (and also in the comments of `rustc_metadata::dependency_format`), `-C prefer-dynamic=no` asks the compiler to guess what linkage to use, and it currently guesses all static first, and if that fails, then it prefers dynamic linkage for all crates that provide both dynamic and static libraries. Using an empty set for `-C prefer-dynamic`, on the other hand, means the compiler should never prefer dynamic linkage over static linkage, even if that means it won't be able to successfully link the build product.
1 parent dbe4d27 commit bf817f6

File tree

8 files changed

+164
-25
lines changed

8 files changed

+164
-25
lines changed

compiler/rustc_codegen_llvm/src/back/lto.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn prepare_lto(
7777
// with either fat or thin LTO
7878
let mut upstream_modules = Vec::new();
7979
if cgcx.lto != Lto::ThinLocal {
80-
if cgcx.opts.cg.prefer_dynamic {
80+
if cgcx.opts.cg.prefer_dynamic.is_non_empty() {
8181
diag_handler
8282
.struct_err("cannot prefer dynamic linking when performing LTO")
8383
.note(

compiler/rustc_codegen_llvm/src/consts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ impl CodegenCx<'ll, 'tcx> {
256256
debug_assert!(
257257
!(self.tcx.sess.opts.cg.linker_plugin_lto.enabled()
258258
&& self.tcx.sess.target.is_like_windows
259-
&& self.tcx.sess.opts.cg.prefer_dynamic)
259+
&& self.tcx.sess.opts.cg.prefer_dynamic.is_non_empty())
260260
);
261261

262262
if needs_dll_storage_attr {

compiler/rustc_codegen_ssa/src/back/write.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1950,7 +1950,7 @@ fn msvc_imps_needed(tcx: TyCtxt<'_>) -> bool {
19501950
assert!(
19511951
!(tcx.sess.opts.cg.linker_plugin_lto.enabled()
19521952
&& tcx.sess.target.is_like_windows
1953-
&& tcx.sess.opts.cg.prefer_dynamic)
1953+
&& tcx.sess.opts.cg.prefer_dynamic.is_non_empty())
19541954
);
19551955

19561956
tcx.sess.target.is_like_windows &&

compiler/rustc_metadata/src/dependency_format.rs

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,43 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
8585
return Vec::new();
8686
}
8787

88+
let prefer_dynamic = &sess.opts.cg.prefer_dynamic;
89+
90+
// traverses all crates to see if any are in the prefer-dynamic set.
91+
let prefer_dynamic_for_any_crate = || {
92+
if prefer_dynamic.is_empty() {
93+
// skip traversal if we know it cannot ever bear fruit.
94+
false
95+
} else {
96+
tcx.crates(()).iter().any(|cnum| prefer_dynamic.contains_crate(tcx.crate_name(*cnum)))
97+
}
98+
};
99+
88100
let preferred_linkage = match ty {
89-
// Generating a dylib without `-C prefer-dynamic` means that we're going
90-
// to try to eagerly statically link all dependencies. This is normally
91-
// done for end-product dylibs, not intermediate products.
101+
// If `-C prefer-dynamic` is not set for any crate, then means that we're
102+
// going to try to eagerly statically link all dependencies into the dylib.
103+
// This is normally done for end-product dylibs, not intermediate products.
92104
//
93-
// Treat cdylibs similarly. If `-C prefer-dynamic` is set, the caller may
94-
// be code-size conscious, but without it, it makes sense to statically
95-
// link a cdylib.
96-
CrateType::Dylib | CrateType::Cdylib if !sess.opts.cg.prefer_dynamic => Linkage::Static,
97-
CrateType::Dylib | CrateType::Cdylib => Linkage::Dynamic,
105+
// Treat cdylibs similarly. If `-C prefer-dynamic` is set for any given
106+
// crate, the caller may be code-size conscious, but without it, it
107+
// makes sense to statically link a cdylib.
108+
CrateType::Dylib | CrateType::Cdylib => {
109+
if prefer_dynamic_for_any_crate() {
110+
Linkage::Dynamic
111+
} else {
112+
Linkage::Static
113+
}
114+
}
98115

99-
// If the global prefer_dynamic switch is turned off, or the final
116+
// If `prefer_dynamic` is not set for any crate, or the final
100117
// executable will be statically linked, prefer static crate linkage.
101-
CrateType::Executable if !sess.opts.cg.prefer_dynamic || sess.crt_static(Some(ty)) => {
102-
Linkage::Static
118+
CrateType::Executable => {
119+
if !prefer_dynamic_for_any_crate() || sess.crt_static(Some(ty)) {
120+
Linkage::Static
121+
} else {
122+
Linkage::Dynamic
123+
}
103124
}
104-
CrateType::Executable => Linkage::Dynamic,
105125

106126
// proc-macro crates are mostly cdylibs, but we also need metadata.
107127
CrateType::ProcMacro => Linkage::Static,
@@ -148,6 +168,11 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
148168
));
149169
}
150170
return Vec::new();
171+
} else {
172+
tracing::info!(
173+
"calculate_type {} attempt_static failed; falling through to dynamic linkage",
174+
tcx.crate_name(LOCAL_CRATE)
175+
);
151176
}
152177
}
153178

@@ -163,7 +188,14 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
163188
}
164189
let name = tcx.crate_name(cnum);
165190
let src: Lrc<CrateSource> = tcx.used_crate_source(cnum);
166-
if src.dylib.is_some() {
191+
// We should prefer the dylib, when available, if either:
192+
// 1. the user has requested that dylib (or all dylibs) via `-C prefer-dynamic`, or
193+
// 2. for backwards compatibility: if the prefer-dynamic subset is unset, then we *still*
194+
// favor dylibs here. This way, if static linking fails above, we might still hope to
195+
// succeed at linking here.
196+
let prefer_dylib =
197+
|name| -> bool { prefer_dynamic.is_unset() || prefer_dynamic.contains_crate(name) };
198+
if src.dylib.is_some() && prefer_dylib(name) {
167199
tracing::info!("calculate_type {} adding dylib: {}", tcx.crate_name(LOCAL_CRATE), name);
168200
add_library(tcx, cnum, RequireDynamic, &mut formats, &mut rev_deps, LOCAL_CRATE);
169201
let deps = tcx.dylib_dependency_formats(cnum);
@@ -196,13 +228,16 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
196228
// If the crate hasn't been included yet and it's not actually required
197229
// (e.g., it's an allocator) then we skip it here as well.
198230
for &cnum in tcx.crates(()).iter() {
231+
let name = tcx.crate_name(cnum);
199232
let src: Lrc<CrateSource> = tcx.used_crate_source(cnum);
200-
if src.dylib.is_none()
201-
&& !formats.contains_key(&cnum)
202-
&& tcx.dep_kind(cnum) == CrateDepKind::Explicit
203-
{
233+
let static_available = src.rlib.is_some() || src.rmeta.is_some();
234+
let prefer_static =
235+
src.dylib.is_none() || (static_available && !prefer_dynamic.contains_crate(name));
236+
let missing = !formats.contains_key(&cnum);
237+
let actually_required = tcx.dep_kind(cnum) == CrateDepKind::Explicit;
238+
if prefer_static && missing && actually_required {
204239
assert!(src.rlib.is_some() || src.rmeta.is_some());
205-
tracing::info!("adding staticlib: {}", tcx.crate_name(cnum));
240+
tracing::info!("adding staticlib: {}", name);
206241
add_library(tcx, cnum, RequireStatic, &mut formats, &mut rev_deps, LOCAL_CRATE);
207242
ret[cnum.as_usize() - 1] = Linkage::Static;
208243
}

compiler/rustc_session/src/config.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,68 @@ pub enum CFGuard {
6363
Checks,
6464
}
6565

66+
/// Handle `-C prefer-dynamic` flag: either its all, or its some explicit
67+
/// subset (potentially the empty set).
68+
#[derive(Clone, PartialEq, Hash, Debug)]
69+
pub enum PreferDynamicSet {
70+
/// Specified by the absence of `-C prefer-dynamic`, or via an explicit `-C prefer-dynamic=...`
71+
/// with value `n`, `no`, or `off`.
72+
///
73+
/// Under this (default) behavior, the compiler first optimistically attempts to statically
74+
/// link everything, but if that fails, then attempts to dynamically link *everything* with a
75+
/// dylib path available.
76+
Unset,
77+
78+
/// Specified via `-C prefer-dynamic` with no value. For backwards-compatibility, also
79+
/// specified via `-C prefer-dynamic=...` with value `y`, `yes`, or `on`.
80+
All,
81+
82+
/// Specified via `-C prefer-dynamic=crate,...`.
83+
Set(BTreeSet<String>),
84+
}
85+
86+
impl PreferDynamicSet {
87+
pub fn unset() -> Self {
88+
PreferDynamicSet::Unset
89+
}
90+
91+
pub fn every_crate() -> Self {
92+
PreferDynamicSet::All
93+
}
94+
95+
pub fn subset(crates: impl Iterator<Item = impl ToString>) -> Self {
96+
PreferDynamicSet::Set(crates.map(|x| x.to_string()).collect())
97+
}
98+
99+
pub fn is_unset(&self) -> bool {
100+
if let PreferDynamicSet::Unset = *self { true } else { false }
101+
}
102+
103+
pub fn is_empty(&self) -> bool {
104+
match self {
105+
PreferDynamicSet::Unset => true,
106+
PreferDynamicSet::All => false,
107+
PreferDynamicSet::Set(s) => s.len() == 0,
108+
}
109+
}
110+
111+
pub fn is_non_empty(&self) -> bool {
112+
match self {
113+
PreferDynamicSet::Unset => false,
114+
PreferDynamicSet::All => true,
115+
PreferDynamicSet::Set(s) => s.len() > 0,
116+
}
117+
}
118+
119+
pub fn contains_crate(&self, crate_name: Symbol) -> bool {
120+
match self {
121+
PreferDynamicSet::Unset => false,
122+
PreferDynamicSet::All => true,
123+
PreferDynamicSet::Set(s) => s.iter().any(|s| s == &*crate_name.as_str()),
124+
}
125+
}
126+
}
127+
66128
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
67129
pub enum OptLevel {
68130
No, // -O0
@@ -2420,8 +2482,8 @@ crate mod dep_tracking {
24202482
use super::LdImpl;
24212483
use super::{
24222484
CFGuard, CrateType, DebugInfo, ErrorOutputType, InstrumentCoverage, LinkerPluginLto,
2423-
LtoCli, OptLevel, OutputType, OutputTypes, Passes, SourceFileHashAlgorithm,
2424-
SwitchWithOptPath, SymbolManglingVersion, TrimmedDefPaths,
2485+
LtoCli, OptLevel, OutputType, OutputTypes, Passes, PreferDynamicSet,
2486+
SourceFileHashAlgorithm, SwitchWithOptPath, SymbolManglingVersion, TrimmedDefPaths,
24252487
};
24262488
use crate::lint;
24272489
use crate::options::WasiExecModel;
@@ -2510,6 +2572,7 @@ crate mod dep_tracking {
25102572
TrimmedDefPaths,
25112573
Option<LdImpl>,
25122574
OutputType,
2575+
PreferDynamicSet,
25132576
RealFileName,
25142577
);
25152578

compiler/rustc_session/src/options.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,8 @@ mod desc {
376376
"one of supported relocation models (`rustc --print relocation-models`)";
377377
pub const parse_code_model: &str = "one of supported code models (`rustc --print code-models`)";
378378
pub const parse_tls_model: &str = "one of supported TLS models (`rustc --print tls-models`)";
379+
pub const parse_prefer_dynamic: &str =
380+
"one of `y`, `yes`, `on`, `n`, `no`, `off`, or a comma separated list of crates";
379381
pub const parse_target_feature: &str = parse_string;
380382
pub const parse_wasi_exec_model: &str = "either `command` or `reactor`";
381383
pub const parse_split_debuginfo: &str =
@@ -835,6 +837,38 @@ mod parse {
835837
true
836838
}
837839

840+
crate fn parse_prefer_dynamic(slot: &mut PreferDynamicSet, v: Option<&str>) -> bool {
841+
let s = match v {
842+
// Note: n/no/off do *not* map to an empty-set of crates.
843+
//
844+
// This is to continue supporting rustc's historical behavior where it attempts to
845+
// link everything statically, but failing that, then greedily link as many crates
846+
// dynamically as it can.
847+
//
848+
// If these options mapped to an empty set, then that would denote that no dynamic
849+
// linkage should be given preference over static, which would not correspond to
850+
// historical meaning of `-C prefer-dynamic=no`.
851+
//
852+
// (One requests an empty set by writing `-C prefer-dynamic=`, with an empty string
853+
// as the value.)
854+
Some("n") | Some("no") | Some("off") => PreferDynamicSet::unset(),
855+
856+
// `-C prefer-dynamic` gives all crates preferred dynamic linkage.
857+
// `-C prefer-dynamic=...` with `y`/`yes`/`on` is a synonym, for backwards
858+
// compatibility.
859+
Some("y") | Some("yes") | Some("on") | None => PreferDynamicSet::every_crate(),
860+
861+
// `-C prefer-dynamic=crate1,crate2,...` gives *just* crate1, crate2, ... preferred
862+
// dynamic linkage.
863+
Some(s) => {
864+
let v = s.split(',').map(|s| s.to_string()).collect();
865+
PreferDynamicSet::Set(v)
866+
}
867+
};
868+
*slot = s;
869+
return true;
870+
}
871+
838872
crate fn parse_src_file_hash(
839873
slot: &mut Option<SourceFileHashAlgorithm>,
840874
v: Option<&str>,
@@ -962,7 +996,7 @@ options! {
962996
"panic strategy to compile crate with"),
963997
passes: Vec<String> = (Vec::new(), parse_list, [TRACKED],
964998
"a list of extra LLVM passes to run (space separated)"),
965-
prefer_dynamic: bool = (false, parse_bool, [TRACKED],
999+
prefer_dynamic: PreferDynamicSet = (PreferDynamicSet::unset(), parse_prefer_dynamic, [TRACKED],
9661000
"prefer dynamic linking to static linking (default: no)"),
9671001
profile_generate: SwitchWithOptPath = (SwitchWithOptPath::Disabled,
9681002
parse_switch_with_opt_path, [TRACKED],

compiler/rustc_session/src/session.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1379,7 +1379,7 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
13791379
// when compiling for LLD ThinLTO. This way we can validly just not generate
13801380
// the `dllimport` attributes and `__imp_` symbols in that case.
13811381
if sess.opts.cg.linker_plugin_lto.enabled()
1382-
&& sess.opts.cg.prefer_dynamic
1382+
&& sess.opts.cg.prefer_dynamic.is_non_empty()
13831383
&& sess.target.is_like_windows
13841384
{
13851385
sess.err(

src/doc/rustc/src/codegen-options/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,13 @@ linkage. This flag takes one of the following values:
402402

403403
* `y`, `yes`, `on`, or no value: use dynamic linking.
404404
* `n`, `no`, or `off`: use static linking (the default).
405+
* A comma-separated list of crate names `crate1,crate2,...`: prefer dynamic
406+
linking, but solely for the indicated crates. For example, to statically link
407+
everything except `std`, use `-C prefer-dynamic=std`.
408+
409+
Note that the explicit list of crate names variant of this flag is gated behind
410+
`-Zprefer-dynamic-subset`; as a special case, one can enable the handling of
411+
the single crate `std` as shown in the example above via `-Zprefer-dynamic-std`.
405412

406413
## profile-generate
407414

0 commit comments

Comments
 (0)