Skip to content

Commit 902cec2

Browse files
committed
Allow manually opting in and out of Linux linker overrides
1 parent 8dd97f8 commit 902cec2

File tree

6 files changed

+185
-63
lines changed

6 files changed

+185
-63
lines changed

bootstrap.example.toml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -758,12 +758,8 @@
758758
# Currently, the only standard options supported here are `"llvm"`, `"cranelift"` and `"gcc"`.
759759
#rust.codegen-backends = ["llvm"]
760760

761-
# Indicates whether LLD will be compiled and made available in the sysroot for rustc to execute, and
762-
# whether to set it as rustc's default linker on `x86_64-unknown-linux-gnu`. This will also only be
763-
# when *not* building an external LLVM (so only when using `download-ci-llvm` or building LLVM from
764-
# the in-tree source): setting `llvm-config` in the `[target.x86_64-unknown-linux-gnu]` section will
765-
# make this default to false.
766-
#rust.lld = false in all cases, except on `x86_64-unknown-linux-gnu` as described above, where it is true
761+
# Indicates whether LLD will be compiled and made available in the sysroot for rustc to execute,
762+
#rust.lld = false, except for targets that opt into LLD (see `target.default-linker-linux-override`)
767763

768764
# Indicates if we should override the linker used to link Rust crates during bootstrap to be LLD.
769765
# If set to `true` or `"external"`, a global `lld` binary that has to be in $PATH
@@ -1067,3 +1063,17 @@
10671063
# Link the compiler and LLVM against `jemalloc` instead of the default libc allocator.
10681064
# This overrides the global `rust.jemalloc` option. See that option for more info.
10691065
#jemalloc = rust.jemalloc (bool)
1066+
1067+
# The linker configuration that will *override* the default linker used for Linux
1068+
# targets in the built compiler.
1069+
#
1070+
# The following values are supported:
1071+
# - `off` => do not apply any override and use the default linker. This can be used to opt out of
1072+
# linker overrides set by bootstrap for specific targets (see below).
1073+
# - `self-contained-lld-cc` => override the default linker to be self-contained LLD (`rust-lld`)
1074+
# that is invoked through `cc`.
1075+
#
1076+
# Currently, the following targets automatically opt into the self-contained LLD linker, unless you
1077+
# pass `off`:
1078+
# - x86_64-unknown-linux-gnu
1079+
#default-linker-linux-override = "off" (for most targets)

src/bootstrap/src/core/build_steps/compile.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::core::builder;
2626
use crate::core::builder::{
2727
Builder, Cargo, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description,
2828
};
29+
use crate::core::config::toml::target::DefaultLinuxLinkerOverride;
2930
use crate::core::config::{
3031
CompilerBuiltins, DebuginfoLevel, LlvmLibunwind, RustcLto, TargetSelection,
3132
};
@@ -1355,9 +1356,14 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetS
13551356
cargo.env("CFG_DEFAULT_LINKER", s);
13561357
}
13571358

1358-
// Enable rustc's env var for `rust-lld` when requested.
1359-
if builder.config.lld_enabled {
1360-
cargo.env("CFG_DEFAULT_LINKER_SELF_CONTAINED_LLD_CC", "1");
1359+
// Enable rustc's env var to use a linker override on Linux when requested.
1360+
if let Some(linker) = target_config.map(|c| c.default_linker_linux_override) {
1361+
match linker {
1362+
DefaultLinuxLinkerOverride::Off => {}
1363+
DefaultLinuxLinkerOverride::SelfContainedLldCc => {
1364+
cargo.env("CFG_DEFAULT_LINKER_SELF_CONTAINED_LLD_CC", "1");
1365+
}
1366+
}
13611367
}
13621368

13631369
if builder.config.rust_verify_llvm_ir {

src/bootstrap/src/core/builder/tests.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,9 @@ mod snapshot {
514514
};
515515
use crate::core::builder::{Builder, Kind, StepDescription, StepMetadata};
516516
use crate::core::config::TargetSelection;
517-
use crate::core::config::toml::rust::with_lld_opt_in_targets;
517+
use crate::core::config::toml::target::{
518+
DefaultLinuxLinkerOverride, with_default_linux_linker_overrides,
519+
};
518520
use crate::utils::cache::Cache;
519521
use crate::utils::helpers::get_host_target;
520522
use crate::utils::tests::{ConfigBuilder, TestCtx};
@@ -782,17 +784,38 @@ mod snapshot {
782784

783785
#[test]
784786
fn build_compiler_lld_opt_in() {
785-
with_lld_opt_in_targets(vec![host_target()], || {
786-
let ctx = TestCtx::new();
787-
insta::assert_snapshot!(
787+
with_default_linux_linker_overrides(
788+
[(host_target(), DefaultLinuxLinkerOverride::SelfContainedLldCc)].into(),
789+
|| {
790+
let ctx = TestCtx::new();
791+
insta::assert_snapshot!(
788792
ctx.config("build")
789793
.path("compiler")
790794
.render_steps(), @r"
791795
[build] llvm <host>
792796
[build] rustc 0 <host> -> rustc 1 <host>
793797
[build] rustc 0 <host> -> LldWrapper 1 <host>
794798
");
795-
});
799+
},
800+
);
801+
}
802+
803+
#[test]
804+
fn build_compiler_lld_opt_in_lld_disabled() {
805+
with_default_linux_linker_overrides(
806+
[(host_target(), DefaultLinuxLinkerOverride::SelfContainedLldCc)].into(),
807+
|| {
808+
let ctx = TestCtx::new();
809+
insta::assert_snapshot!(
810+
ctx.config("build")
811+
.path("compiler")
812+
.args(&["--set", "rust.lld=false"])
813+
.render_steps(), @r"
814+
[build] llvm <host>
815+
[build] rustc 0 <host> -> rustc 1 <host>
816+
");
817+
},
818+
);
796819
}
797820

798821
#[test]

src/bootstrap/src/core/config/config.rs

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ use crate::core::config::toml::install::Install;
4242
use crate::core::config::toml::llvm::Llvm;
4343
use crate::core::config::toml::rust::{
4444
BootstrapOverrideLld, Rust, RustOptimize, check_incompatible_options_for_ci_rustc,
45-
default_lld_opt_in_targets, parse_codegen_backends,
45+
parse_codegen_backends,
46+
};
47+
use crate::core::config::toml::target::{
48+
DefaultLinuxLinkerOverride, Target, TomlTarget, default_linux_linker_overrides,
4649
};
47-
use crate::core::config::toml::target::{Target, TomlTarget};
4850
use crate::core::config::{
4951
CompilerBuiltins, DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt,
5052
RustcLto, SplitDebuginfo, StringOrBool, threads_from_config,
@@ -829,6 +831,11 @@ impl Config {
829831
.to_owned();
830832
}
831833

834+
let mut lld_enabled = rust_lld_enabled.unwrap_or(false);
835+
836+
// Linux targets for which the user explicitly overrode the used linker
837+
let mut targets_with_user_linker_override = HashSet::new();
838+
832839
if let Some(t) = toml.target {
833840
for (triple, cfg) in t {
834841
let TomlTarget {
@@ -837,6 +844,7 @@ impl Config {
837844
ar: target_ar,
838845
ranlib: target_ranlib,
839846
default_linker: target_default_linker,
847+
default_linker_linux: target_default_linker_linux_override,
840848
linker: target_linker,
841849
split_debuginfo: target_split_debuginfo,
842850
llvm_config: target_llvm_config,
@@ -860,6 +868,33 @@ impl Config {
860868

861869
let mut target = Target::from_triple(&triple);
862870

871+
if target_default_linker_linux_override.is_some() {
872+
targets_with_user_linker_override.insert(triple.clone());
873+
}
874+
875+
let default_linker_linux_override = match target_default_linker_linux_override {
876+
Some(DefaultLinuxLinkerOverride::SelfContainedLldCc) => {
877+
if rust_default_linker.is_some() {
878+
panic!(
879+
"cannot set both `default-linker` and `default-linker-linux` for target `{triple}`"
880+
);
881+
}
882+
if !triple.contains("linux-gnu") {
883+
panic!(
884+
"`default-linker-linux` can only be set for Linux GNU targets, not for `{triple}`"
885+
);
886+
}
887+
if !lld_enabled {
888+
panic!(
889+
"Trying to override the default Linux linker for `{triple}` to be self-contained LLD, but LLD is not being built. Enable it with rust.lld = true."
890+
);
891+
}
892+
DefaultLinuxLinkerOverride::SelfContainedLldCc
893+
}
894+
Some(DefaultLinuxLinkerOverride::Off) => DefaultLinuxLinkerOverride::Off,
895+
None => DefaultLinuxLinkerOverride::default(),
896+
};
897+
863898
if let Some(ref s) = target_llvm_config {
864899
if download_rustc_commit.is_some() && triple == *host_target.triple {
865900
panic!(
@@ -893,6 +928,7 @@ impl Config {
893928
target.linker = target_linker.map(PathBuf::from);
894929
target.crt_static = target_crt_static;
895930
target.default_linker = target_default_linker;
931+
target.default_linker_linux_override = default_linker_linux_override;
896932
target.musl_root = target_musl_root.map(PathBuf::from);
897933
target.musl_libdir = target_musl_libdir.map(PathBuf::from);
898934
target.wasi_root = target_wasi_root.map(PathBuf::from);
@@ -918,6 +954,38 @@ impl Config {
918954
}
919955
}
920956

957+
for (target, linker_override) in default_linux_linker_overrides() {
958+
// If the user overrode the default Linux linker, do not apply bootstrap defaults
959+
if targets_with_user_linker_override.contains(&target) {
960+
continue;
961+
}
962+
let default_linux_linker_override = match linker_override {
963+
DefaultLinuxLinkerOverride::Off => continue,
964+
DefaultLinuxLinkerOverride::SelfContainedLldCc => {
965+
// If we automatically default to the self-contained LLD linker,
966+
// we also need to handle the rust.lld option.
967+
match rust_lld_enabled {
968+
// If LLD was not enabled explicitly, we enable it
969+
None => {
970+
lld_enabled = true;
971+
Some(DefaultLinuxLinkerOverride::SelfContainedLldCc)
972+
}
973+
// If it was enabled already, we don't need to do anything
974+
Some(true) => Some(DefaultLinuxLinkerOverride::SelfContainedLldCc),
975+
// If it was explicitly disabled, we do not apply the
976+
// linker override
977+
Some(false) => None,
978+
}
979+
}
980+
};
981+
if let Some(linker_override) = default_linux_linker_override {
982+
target_config
983+
.entry(TargetSelection::from_user(&target))
984+
.or_default()
985+
.default_linker_linux_override = linker_override;
986+
}
987+
}
988+
921989
let llvm_from_ci = parse_download_ci_llvm(
922990
&dwn_ctx,
923991
&rust_info,
@@ -926,28 +994,6 @@ impl Config {
926994
llvm_assertions,
927995
);
928996

929-
// We make `x86_64-unknown-linux-gnu` use the self-contained linker by default, so we will
930-
// build our internal lld and use it as the default linker, by setting the `rust.lld` config
931-
// to true by default:
932-
// - on the `x86_64-unknown-linux-gnu` target
933-
// - when building our in-tree llvm (i.e. the target has not set an `llvm-config`), so that
934-
// we're also able to build the corresponding lld
935-
// - or when using an external llvm that's downloaded from CI, which also contains our prebuilt
936-
// lld
937-
// - otherwise, we'd be using an external llvm, and lld would not necessarily available and
938-
// thus, disabled
939-
// - similarly, lld will not be built nor used by default when explicitly asked not to, e.g.
940-
// when the config sets `rust.lld = false`
941-
let lld_enabled = if default_lld_opt_in_targets().contains(&host_target.triple.to_string())
942-
&& hosts == [host_target]
943-
{
944-
let no_llvm_config =
945-
target_config.get(&host_target).is_none_or(|config| config.llvm_config.is_none());
946-
rust_lld_enabled.unwrap_or(llvm_from_ci || no_llvm_config)
947-
} else {
948-
rust_lld_enabled.unwrap_or(false)
949-
};
950-
951997
if llvm_from_ci {
952998
let warn = |option: &str| {
953999
println!(

src/bootstrap/src/core/config/toml/rust.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -438,28 +438,3 @@ pub(crate) fn parse_codegen_backends(
438438
}
439439
found_backends
440440
}
441-
442-
#[cfg(not(test))]
443-
pub fn default_lld_opt_in_targets() -> Vec<String> {
444-
vec!["x86_64-unknown-linux-gnu".to_string()]
445-
}
446-
447-
#[cfg(test)]
448-
thread_local! {
449-
static TEST_LLD_OPT_IN_TARGETS: std::cell::RefCell<Option<Vec<String>>> = std::cell::RefCell::new(None);
450-
}
451-
452-
#[cfg(test)]
453-
pub fn default_lld_opt_in_targets() -> Vec<String> {
454-
TEST_LLD_OPT_IN_TARGETS.with(|cell| cell.borrow().clone()).unwrap_or_default()
455-
}
456-
457-
#[cfg(test)]
458-
pub fn with_lld_opt_in_targets<R>(targets: Vec<String>, f: impl FnOnce() -> R) -> R {
459-
TEST_LLD_OPT_IN_TARGETS.with(|cell| {
460-
let prev = cell.replace(Some(targets));
461-
let result = f();
462-
cell.replace(prev);
463-
result
464-
})
465-
}

src/bootstrap/src/core/config/toml/target.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
//! * [`Target`]: This struct represents the processed and validated configuration for a
1010
//! build target, which is is stored in the main `Config` structure.
1111
12+
use std::collections::HashMap;
13+
14+
use serde::de::Error;
1215
use serde::{Deserialize, Deserializer};
1316

1417
use crate::core::config::{
@@ -24,6 +27,7 @@ define_config! {
2427
ar: Option<String> = "ar",
2528
ranlib: Option<String> = "ranlib",
2629
default_linker: Option<PathBuf> = "default-linker",
30+
default_linker_linux: Option<DefaultLinuxLinkerOverride> = "default-linker-linux-override",
2731
linker: Option<String> = "linker",
2832
split_debuginfo: Option<String> = "split-debuginfo",
2933
llvm_config: Option<String> = "llvm-config",
@@ -60,6 +64,7 @@ pub struct Target {
6064
pub ar: Option<PathBuf>,
6165
pub ranlib: Option<PathBuf>,
6266
pub default_linker: Option<PathBuf>,
67+
pub default_linker_linux_override: DefaultLinuxLinkerOverride,
6368
pub linker: Option<PathBuf>,
6469
pub split_debuginfo: Option<SplitDebuginfo>,
6570
pub sanitizers: Option<bool>,
@@ -89,3 +94,60 @@ impl Target {
8994
target
9095
}
9196
}
97+
98+
/// Overrides the default linker used on a Linux linker.
99+
/// On Linux, the linker is usually invoked through `cc`, therefore this exists as a separate
100+
/// configuration from simply setting `default-linker`, which corresponds to `-Clinker`.
101+
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
102+
pub enum DefaultLinuxLinkerOverride {
103+
/// Do not apply any override and use the default linker for the given target.
104+
#[default]
105+
Off,
106+
/// Use the self-contained `rust-lld` linker, invoked through `cc`.
107+
/// Corresponds to `-Clinker-features=+lld -Clink-self-contained=+linker`.
108+
SelfContainedLldCc,
109+
}
110+
111+
impl<'de> Deserialize<'de> for DefaultLinuxLinkerOverride {
112+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113+
where
114+
D: Deserializer<'de>,
115+
{
116+
let name = String::deserialize(deserializer)?;
117+
match name.as_str() {
118+
"off" => Ok(Self::Off),
119+
"self-contained-lld-cc" => Ok(Self::SelfContainedLldCc),
120+
other => Err(D::Error::unknown_variant(other, &["off", "self-contained-lld-cc"])),
121+
}
122+
}
123+
}
124+
125+
/// Set of linker overrides for selected Linux targets.
126+
#[cfg(not(test))]
127+
pub fn default_linux_linker_overrides() -> HashMap<String, DefaultLinuxLinkerOverride> {
128+
[("x86_64-unknown-linux-gnu".to_string(), DefaultLinuxLinkerOverride::SelfContainedLldCc)]
129+
.into()
130+
}
131+
132+
#[cfg(test)]
133+
thread_local! {
134+
static TEST_LINUX_LINKER_OVERRIDES: std::cell::RefCell<Option<HashMap<String, DefaultLinuxLinkerOverride>>> = std::cell::RefCell::new(None);
135+
}
136+
137+
#[cfg(test)]
138+
pub fn default_linux_linker_overrides() -> HashMap<String, DefaultLinuxLinkerOverride> {
139+
TEST_LINUX_LINKER_OVERRIDES.with(|cell| cell.borrow().clone()).unwrap_or_default()
140+
}
141+
142+
#[cfg(test)]
143+
pub fn with_default_linux_linker_overrides<R>(
144+
targets: HashMap<String, DefaultLinuxLinkerOverride>,
145+
f: impl FnOnce() -> R,
146+
) -> R {
147+
TEST_LINUX_LINKER_OVERRIDES.with(|cell| {
148+
let prev = cell.replace(Some(targets));
149+
let result = f();
150+
cell.replace(prev);
151+
result
152+
})
153+
}

0 commit comments

Comments
 (0)