From 902cec2bc6997748e2ca47c11dba8acc673806d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 6 Oct 2025 10:30:31 +0200 Subject: [PATCH] Allow manually opting in and out of Linux linker overrides --- bootstrap.example.toml | 22 +++-- src/bootstrap/src/core/build_steps/compile.rs | 12 ++- src/bootstrap/src/core/builder/tests.rs | 33 ++++++- src/bootstrap/src/core/config/config.rs | 94 ++++++++++++++----- src/bootstrap/src/core/config/toml/rust.rs | 25 ----- src/bootstrap/src/core/config/toml/target.rs | 62 ++++++++++++ 6 files changed, 185 insertions(+), 63 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index f623a3db0029..6f37e51a47de 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -758,12 +758,8 @@ # Currently, the only standard options supported here are `"llvm"`, `"cranelift"` and `"gcc"`. #rust.codegen-backends = ["llvm"] -# Indicates whether LLD will be compiled and made available in the sysroot for rustc to execute, and -# whether to set it as rustc's default linker on `x86_64-unknown-linux-gnu`. This will also only be -# when *not* building an external LLVM (so only when using `download-ci-llvm` or building LLVM from -# the in-tree source): setting `llvm-config` in the `[target.x86_64-unknown-linux-gnu]` section will -# make this default to false. -#rust.lld = false in all cases, except on `x86_64-unknown-linux-gnu` as described above, where it is true +# Indicates whether LLD will be compiled and made available in the sysroot for rustc to execute, +#rust.lld = false, except for targets that opt into LLD (see `target.default-linker-linux-override`) # Indicates if we should override the linker used to link Rust crates during bootstrap to be LLD. # If set to `true` or `"external"`, a global `lld` binary that has to be in $PATH @@ -1067,3 +1063,17 @@ # Link the compiler and LLVM against `jemalloc` instead of the default libc allocator. # This overrides the global `rust.jemalloc` option. See that option for more info. #jemalloc = rust.jemalloc (bool) + +# The linker configuration that will *override* the default linker used for Linux +# targets in the built compiler. +# +# The following values are supported: +# - `off` => do not apply any override and use the default linker. This can be used to opt out of +# linker overrides set by bootstrap for specific targets (see below). +# - `self-contained-lld-cc` => override the default linker to be self-contained LLD (`rust-lld`) +# that is invoked through `cc`. +# +# Currently, the following targets automatically opt into the self-contained LLD linker, unless you +# pass `off`: +# - x86_64-unknown-linux-gnu +#default-linker-linux-override = "off" (for most targets) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 6c34b6249196..dd90ac7cdda3 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -26,6 +26,7 @@ use crate::core::builder; use crate::core::builder::{ Builder, Cargo, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description, }; +use crate::core::config::toml::target::DefaultLinuxLinkerOverride; use crate::core::config::{ CompilerBuiltins, DebuginfoLevel, LlvmLibunwind, RustcLto, TargetSelection, }; @@ -1355,9 +1356,14 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetS cargo.env("CFG_DEFAULT_LINKER", s); } - // Enable rustc's env var for `rust-lld` when requested. - if builder.config.lld_enabled { - cargo.env("CFG_DEFAULT_LINKER_SELF_CONTAINED_LLD_CC", "1"); + // Enable rustc's env var to use a linker override on Linux when requested. + if let Some(linker) = target_config.map(|c| c.default_linker_linux_override) { + match linker { + DefaultLinuxLinkerOverride::Off => {} + DefaultLinuxLinkerOverride::SelfContainedLldCc => { + cargo.env("CFG_DEFAULT_LINKER_SELF_CONTAINED_LLD_CC", "1"); + } + } } if builder.config.rust_verify_llvm_ir { diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index d0429387f824..e0eb38d04aad 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -514,7 +514,9 @@ mod snapshot { }; use crate::core::builder::{Builder, Kind, StepDescription, StepMetadata}; use crate::core::config::TargetSelection; - use crate::core::config::toml::rust::with_lld_opt_in_targets; + use crate::core::config::toml::target::{ + DefaultLinuxLinkerOverride, with_default_linux_linker_overrides, + }; use crate::utils::cache::Cache; use crate::utils::helpers::get_host_target; use crate::utils::tests::{ConfigBuilder, TestCtx}; @@ -782,9 +784,11 @@ mod snapshot { #[test] fn build_compiler_lld_opt_in() { - with_lld_opt_in_targets(vec![host_target()], || { - let ctx = TestCtx::new(); - insta::assert_snapshot!( + with_default_linux_linker_overrides( + [(host_target(), DefaultLinuxLinkerOverride::SelfContainedLldCc)].into(), + || { + let ctx = TestCtx::new(); + insta::assert_snapshot!( ctx.config("build") .path("compiler") .render_steps(), @r" @@ -792,7 +796,26 @@ mod snapshot { [build] rustc 0 -> rustc 1 [build] rustc 0 -> LldWrapper 1 "); - }); + }, + ); + } + + #[test] + fn build_compiler_lld_opt_in_lld_disabled() { + with_default_linux_linker_overrides( + [(host_target(), DefaultLinuxLinkerOverride::SelfContainedLldCc)].into(), + || { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("compiler") + .args(&["--set", "rust.lld=false"]) + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + "); + }, + ); } #[test] diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 84457e7c3345..0a5c34283502 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -42,9 +42,11 @@ use crate::core::config::toml::install::Install; use crate::core::config::toml::llvm::Llvm; use crate::core::config::toml::rust::{ BootstrapOverrideLld, Rust, RustOptimize, check_incompatible_options_for_ci_rustc, - default_lld_opt_in_targets, parse_codegen_backends, + parse_codegen_backends, +}; +use crate::core::config::toml::target::{ + DefaultLinuxLinkerOverride, Target, TomlTarget, default_linux_linker_overrides, }; -use crate::core::config::toml::target::{Target, TomlTarget}; use crate::core::config::{ CompilerBuiltins, DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt, RustcLto, SplitDebuginfo, StringOrBool, threads_from_config, @@ -829,6 +831,11 @@ impl Config { .to_owned(); } + let mut lld_enabled = rust_lld_enabled.unwrap_or(false); + + // Linux targets for which the user explicitly overrode the used linker + let mut targets_with_user_linker_override = HashSet::new(); + if let Some(t) = toml.target { for (triple, cfg) in t { let TomlTarget { @@ -837,6 +844,7 @@ impl Config { ar: target_ar, ranlib: target_ranlib, default_linker: target_default_linker, + default_linker_linux: target_default_linker_linux_override, linker: target_linker, split_debuginfo: target_split_debuginfo, llvm_config: target_llvm_config, @@ -860,6 +868,33 @@ impl Config { let mut target = Target::from_triple(&triple); + if target_default_linker_linux_override.is_some() { + targets_with_user_linker_override.insert(triple.clone()); + } + + let default_linker_linux_override = match target_default_linker_linux_override { + Some(DefaultLinuxLinkerOverride::SelfContainedLldCc) => { + if rust_default_linker.is_some() { + panic!( + "cannot set both `default-linker` and `default-linker-linux` for target `{triple}`" + ); + } + if !triple.contains("linux-gnu") { + panic!( + "`default-linker-linux` can only be set for Linux GNU targets, not for `{triple}`" + ); + } + if !lld_enabled { + panic!( + "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." + ); + } + DefaultLinuxLinkerOverride::SelfContainedLldCc + } + Some(DefaultLinuxLinkerOverride::Off) => DefaultLinuxLinkerOverride::Off, + None => DefaultLinuxLinkerOverride::default(), + }; + if let Some(ref s) = target_llvm_config { if download_rustc_commit.is_some() && triple == *host_target.triple { panic!( @@ -893,6 +928,7 @@ impl Config { target.linker = target_linker.map(PathBuf::from); target.crt_static = target_crt_static; target.default_linker = target_default_linker; + target.default_linker_linux_override = default_linker_linux_override; target.musl_root = target_musl_root.map(PathBuf::from); target.musl_libdir = target_musl_libdir.map(PathBuf::from); target.wasi_root = target_wasi_root.map(PathBuf::from); @@ -918,6 +954,38 @@ impl Config { } } + for (target, linker_override) in default_linux_linker_overrides() { + // If the user overrode the default Linux linker, do not apply bootstrap defaults + if targets_with_user_linker_override.contains(&target) { + continue; + } + let default_linux_linker_override = match linker_override { + DefaultLinuxLinkerOverride::Off => continue, + DefaultLinuxLinkerOverride::SelfContainedLldCc => { + // If we automatically default to the self-contained LLD linker, + // we also need to handle the rust.lld option. + match rust_lld_enabled { + // If LLD was not enabled explicitly, we enable it + None => { + lld_enabled = true; + Some(DefaultLinuxLinkerOverride::SelfContainedLldCc) + } + // If it was enabled already, we don't need to do anything + Some(true) => Some(DefaultLinuxLinkerOverride::SelfContainedLldCc), + // If it was explicitly disabled, we do not apply the + // linker override + Some(false) => None, + } + } + }; + if let Some(linker_override) = default_linux_linker_override { + target_config + .entry(TargetSelection::from_user(&target)) + .or_default() + .default_linker_linux_override = linker_override; + } + } + let llvm_from_ci = parse_download_ci_llvm( &dwn_ctx, &rust_info, @@ -926,28 +994,6 @@ impl Config { llvm_assertions, ); - // We make `x86_64-unknown-linux-gnu` use the self-contained linker by default, so we will - // build our internal lld and use it as the default linker, by setting the `rust.lld` config - // to true by default: - // - on the `x86_64-unknown-linux-gnu` target - // - when building our in-tree llvm (i.e. the target has not set an `llvm-config`), so that - // we're also able to build the corresponding lld - // - or when using an external llvm that's downloaded from CI, which also contains our prebuilt - // lld - // - otherwise, we'd be using an external llvm, and lld would not necessarily available and - // thus, disabled - // - similarly, lld will not be built nor used by default when explicitly asked not to, e.g. - // when the config sets `rust.lld = false` - let lld_enabled = if default_lld_opt_in_targets().contains(&host_target.triple.to_string()) - && hosts == [host_target] - { - let no_llvm_config = - target_config.get(&host_target).is_none_or(|config| config.llvm_config.is_none()); - rust_lld_enabled.unwrap_or(llvm_from_ci || no_llvm_config) - } else { - rust_lld_enabled.unwrap_or(false) - }; - if llvm_from_ci { let warn = |option: &str| { println!( diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index ca9e0d0bc98e..5a2c6e169869 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -438,28 +438,3 @@ pub(crate) fn parse_codegen_backends( } found_backends } - -#[cfg(not(test))] -pub fn default_lld_opt_in_targets() -> Vec { - vec!["x86_64-unknown-linux-gnu".to_string()] -} - -#[cfg(test)] -thread_local! { - static TEST_LLD_OPT_IN_TARGETS: std::cell::RefCell>> = std::cell::RefCell::new(None); -} - -#[cfg(test)] -pub fn default_lld_opt_in_targets() -> Vec { - TEST_LLD_OPT_IN_TARGETS.with(|cell| cell.borrow().clone()).unwrap_or_default() -} - -#[cfg(test)] -pub fn with_lld_opt_in_targets(targets: Vec, f: impl FnOnce() -> R) -> R { - TEST_LLD_OPT_IN_TARGETS.with(|cell| { - let prev = cell.replace(Some(targets)); - let result = f(); - cell.replace(prev); - result - }) -} diff --git a/src/bootstrap/src/core/config/toml/target.rs b/src/bootstrap/src/core/config/toml/target.rs index 020602e6a199..7b8d4f9601b5 100644 --- a/src/bootstrap/src/core/config/toml/target.rs +++ b/src/bootstrap/src/core/config/toml/target.rs @@ -9,6 +9,9 @@ //! * [`Target`]: This struct represents the processed and validated configuration for a //! build target, which is is stored in the main `Config` structure. +use std::collections::HashMap; + +use serde::de::Error; use serde::{Deserialize, Deserializer}; use crate::core::config::{ @@ -24,6 +27,7 @@ define_config! { ar: Option = "ar", ranlib: Option = "ranlib", default_linker: Option = "default-linker", + default_linker_linux: Option = "default-linker-linux-override", linker: Option = "linker", split_debuginfo: Option = "split-debuginfo", llvm_config: Option = "llvm-config", @@ -60,6 +64,7 @@ pub struct Target { pub ar: Option, pub ranlib: Option, pub default_linker: Option, + pub default_linker_linux_override: DefaultLinuxLinkerOverride, pub linker: Option, pub split_debuginfo: Option, pub sanitizers: Option, @@ -89,3 +94,60 @@ impl Target { target } } + +/// Overrides the default linker used on a Linux linker. +/// On Linux, the linker is usually invoked through `cc`, therefore this exists as a separate +/// configuration from simply setting `default-linker`, which corresponds to `-Clinker`. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub enum DefaultLinuxLinkerOverride { + /// Do not apply any override and use the default linker for the given target. + #[default] + Off, + /// Use the self-contained `rust-lld` linker, invoked through `cc`. + /// Corresponds to `-Clinker-features=+lld -Clink-self-contained=+linker`. + SelfContainedLldCc, +} + +impl<'de> Deserialize<'de> for DefaultLinuxLinkerOverride { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = String::deserialize(deserializer)?; + match name.as_str() { + "off" => Ok(Self::Off), + "self-contained-lld-cc" => Ok(Self::SelfContainedLldCc), + other => Err(D::Error::unknown_variant(other, &["off", "self-contained-lld-cc"])), + } + } +} + +/// Set of linker overrides for selected Linux targets. +#[cfg(not(test))] +pub fn default_linux_linker_overrides() -> HashMap { + [("x86_64-unknown-linux-gnu".to_string(), DefaultLinuxLinkerOverride::SelfContainedLldCc)] + .into() +} + +#[cfg(test)] +thread_local! { + static TEST_LINUX_LINKER_OVERRIDES: std::cell::RefCell>> = std::cell::RefCell::new(None); +} + +#[cfg(test)] +pub fn default_linux_linker_overrides() -> HashMap { + TEST_LINUX_LINKER_OVERRIDES.with(|cell| cell.borrow().clone()).unwrap_or_default() +} + +#[cfg(test)] +pub fn with_default_linux_linker_overrides( + targets: HashMap, + f: impl FnOnce() -> R, +) -> R { + TEST_LINUX_LINKER_OVERRIDES.with(|cell| { + let prev = cell.replace(Some(targets)); + let result = f(); + cell.replace(prev); + result + }) +}