Rollup merge of #147626 - Kobzol:lld-generalize2, r=jieyouxu

Generalize configuring LLD as the default linker in bootstrap

Reopen of https://github.com/rust-lang/rust/pull/147157, because apparently bors can't deal with it for some reason.

r? ``@ghost``
This commit is contained in:
Guillaume Gomez 2025-10-13 11:25:25 +02:00 committed by GitHub
commit 0e6ec72dbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 263 additions and 103 deletions

View file

@ -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)

View file

@ -1,5 +1,14 @@
use crate::spec::{TargetOptions, base};
use crate::spec::{Cc, LinkerFlavor, Lld, TargetOptions, base};
pub(crate) fn opts() -> TargetOptions {
TargetOptions { env: "gnu".into(), ..base::linux::opts() }
let mut base = TargetOptions { env: "gnu".into(), ..base::linux::opts() };
// When we're asked to use the `rust-lld` linker by default, set the appropriate lld-using
// linker flavor, and self-contained linker component.
if option_env!("CFG_DEFAULT_LINKER_SELF_CONTAINED_LLD_CC").is_some() {
base.linker_flavor = LinkerFlavor::Gnu(Cc::Yes, Lld::Yes);
base.link_self_contained = crate::spec::LinkSelfContainedDefault::with_linker();
}
base
}

View file

@ -20,13 +20,6 @@ pub(crate) fn target() -> Target {
| SanitizerSet::THREAD;
base.supports_xray = true;
// When we're asked to use the `rust-lld` linker by default, set the appropriate lld-using
// linker flavor, and self-contained linker component.
if option_env!("CFG_USE_SELF_CONTAINED_LINKER").is_some() {
base.linker_flavor = LinkerFlavor::Gnu(Cc::Yes, Lld::Yes);
base.link_self_contained = crate::spec::LinkSelfContainedDefault::with_linker();
}
Target {
llvm_target: "x86_64-unknown-linux-gnu".into(),
metadata: TargetMetadata {

View file

@ -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,
};
@ -1373,9 +1374,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_USE_SELF_CONTAINED_LINKER", "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 {

View file

@ -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 <host> -> rustc 1 <host>
[build] rustc 0 <host> -> LldWrapper 1 <host>
");
});
},
);
}
#[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 <host>
[build] rustc 0 <host> -> rustc 1 <host>
");
},
);
}
#[test]

View file

@ -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;
use crate::core::config::{
CompilerBuiltins, DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt,
RustcLto, SplitDebuginfo, StringOrBool, threads_from_config,
@ -829,11 +831,71 @@ 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 {
cc: target_cc,
cxx: target_cxx,
ar: target_ar,
ranlib: target_ranlib,
default_linker: target_default_linker,
default_linker_linux_override: target_default_linker_linux_override,
linker: target_linker,
split_debuginfo: target_split_debuginfo,
llvm_config: target_llvm_config,
llvm_has_rust_patches: target_llvm_has_rust_patches,
llvm_filecheck: target_llvm_filecheck,
llvm_libunwind: target_llvm_libunwind,
sanitizers: target_sanitizers,
profiler: target_profiler,
rpath: target_rpath,
crt_static: target_crt_static,
musl_root: target_musl_root,
musl_libdir: target_musl_libdir,
wasi_root: target_wasi_root,
qemu_rootfs: target_qemu_rootfs,
no_std: target_no_std,
codegen_backends: target_codegen_backends,
runner: target_runner,
optimized_compiler_builtins: target_optimized_compiler_builtins,
jemalloc: target_jemalloc,
} = cfg;
let mut target = Target::from_triple(&triple);
if let Some(ref s) = cfg.llvm_config {
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!(
"setting llvm_config for the host is incompatible with download-rustc"
@ -841,46 +903,48 @@ impl Config {
}
target.llvm_config = Some(src.join(s));
}
if let Some(patches) = cfg.llvm_has_rust_patches {
if let Some(patches) = target_llvm_has_rust_patches {
assert!(
build_submodules == Some(false) || cfg.llvm_config.is_some(),
build_submodules == Some(false) || target_llvm_config.is_some(),
"use of `llvm-has-rust-patches` is restricted to cases where either submodules are disabled or llvm-config been provided"
);
target.llvm_has_rust_patches = Some(patches);
}
if let Some(ref s) = cfg.llvm_filecheck {
if let Some(ref s) = target_llvm_filecheck {
target.llvm_filecheck = Some(src.join(s));
}
target.llvm_libunwind = cfg.llvm_libunwind.as_ref().map(|v| {
target.llvm_libunwind = target_llvm_libunwind.as_ref().map(|v| {
v.parse().unwrap_or_else(|_| {
panic!("failed to parse target.{triple}.llvm-libunwind")
})
});
if let Some(s) = cfg.no_std {
if let Some(s) = target_no_std {
target.no_std = s;
}
target.cc = cfg.cc.map(PathBuf::from);
target.cxx = cfg.cxx.map(PathBuf::from);
target.ar = cfg.ar.map(PathBuf::from);
target.ranlib = cfg.ranlib.map(PathBuf::from);
target.linker = cfg.linker.map(PathBuf::from);
target.crt_static = cfg.crt_static;
target.musl_root = cfg.musl_root.map(PathBuf::from);
target.musl_libdir = cfg.musl_libdir.map(PathBuf::from);
target.wasi_root = cfg.wasi_root.map(PathBuf::from);
target.qemu_rootfs = cfg.qemu_rootfs.map(PathBuf::from);
target.runner = cfg.runner;
target.sanitizers = cfg.sanitizers;
target.profiler = cfg.profiler;
target.rpath = cfg.rpath;
target.optimized_compiler_builtins = cfg.optimized_compiler_builtins;
target.jemalloc = cfg.jemalloc;
if let Some(backends) = cfg.codegen_backends {
target.cc = target_cc.map(PathBuf::from);
target.cxx = target_cxx.map(PathBuf::from);
target.ar = target_ar.map(PathBuf::from);
target.ranlib = target_ranlib.map(PathBuf::from);
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);
target.qemu_rootfs = target_qemu_rootfs.map(PathBuf::from);
target.runner = target_runner;
target.sanitizers = target_sanitizers;
target.profiler = target_profiler;
target.rpath = target_rpath;
target.optimized_compiler_builtins = target_optimized_compiler_builtins;
target.jemalloc = target_jemalloc;
if let Some(backends) = target_codegen_backends {
target.codegen_backends =
Some(parse_codegen_backends(backends, &format!("target.{triple}")))
}
target.split_debuginfo = cfg.split_debuginfo.as_ref().map(|v| {
target.split_debuginfo = target_split_debuginfo.as_ref().map(|v| {
v.parse().unwrap_or_else(|_| {
panic!("invalid value for target.{triple}.split-debuginfo")
})
@ -897,28 +961,8 @@ impl Config {
llvm_download_ci_llvm,
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)
};
let is_host_system_llvm =
is_system_llvm(&target_config, llvm_from_ci, host_target, host_target);
if llvm_from_ci {
let warn = |option: &str| {
@ -966,6 +1010,41 @@ impl Config {
build_target.llvm_filecheck = Some(ci_llvm_bin.join(exe("FileCheck", host_target)));
}
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, unless LLVM config has
// been set
None if !is_host_system_llvm => {
lld_enabled = true;
Some(DefaultLinuxLinkerOverride::SelfContainedLldCc)
}
None => None,
// 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 initial_rustfmt = build_rustfmt.or_else(|| maybe_download_rustfmt(&dwn_ctx, &out));
if matches!(bootstrap_override_lld, BootstrapOverrideLld::SelfContained)
@ -977,7 +1056,7 @@ impl Config {
);
}
if lld_enabled && is_system_llvm(&dwn_ctx, &target_config, llvm_from_ci, host_target) {
if lld_enabled && is_host_system_llvm {
panic!("Cannot enable LLD with `rust.lld = true` when using external llvm-config.");
}
@ -1771,8 +1850,7 @@ impl Config {
///
/// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
pub fn is_system_llvm(&self, target: TargetSelection) -> bool {
let dwn_ctx = DownloadContext::from(self);
is_system_llvm(dwn_ctx, &self.target_config, self.llvm_from_ci, target)
is_system_llvm(&self.target_config, self.llvm_from_ci, self.host_target, target)
}
/// Returns `true` if this is our custom, patched, version of LLVM.
@ -2375,16 +2453,15 @@ pub fn submodules_(submodules: &Option<bool>, rust_info: &channel::GitInfo) -> b
/// In particular, we expect llvm sources to be available when this is false.
///
/// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
pub fn is_system_llvm<'a>(
dwn_ctx: impl AsRef<DownloadContext<'a>>,
pub fn is_system_llvm(
target_config: &HashMap<TargetSelection, Target>,
llvm_from_ci: bool,
host_target: TargetSelection,
target: TargetSelection,
) -> bool {
let dwn_ctx = dwn_ctx.as_ref();
match target_config.get(&target) {
Some(Target { llvm_config: Some(_), .. }) => {
let ci_llvm = llvm_from_ci && is_host_target(&dwn_ctx.host_target, &target);
let ci_llvm = llvm_from_ci && is_host_target(&host_target, &target);
!ci_llvm
}
// We're building from the in-tree src/llvm-project sources.

View file

@ -438,28 +438,3 @@ pub(crate) fn parse_codegen_backends(
}
found_backends
}
#[cfg(not(test))]
pub fn default_lld_opt_in_targets() -> Vec<String> {
vec!["x86_64-unknown-linux-gnu".to_string()]
}
#[cfg(test)]
thread_local! {
static TEST_LLD_OPT_IN_TARGETS: std::cell::RefCell<Option<Vec<String>>> = std::cell::RefCell::new(None);
}
#[cfg(test)]
pub fn default_lld_opt_in_targets() -> Vec<String> {
TEST_LLD_OPT_IN_TARGETS.with(|cell| cell.borrow().clone()).unwrap_or_default()
}
#[cfg(test)]
pub fn with_lld_opt_in_targets<R>(targets: Vec<String>, 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
})
}

View file

@ -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<String> = "ar",
ranlib: Option<String> = "ranlib",
default_linker: Option<PathBuf> = "default-linker",
default_linker_linux_override: Option<DefaultLinuxLinkerOverride> = "default-linker-linux-override",
linker: Option<String> = "linker",
split_debuginfo: Option<String> = "split-debuginfo",
llvm_config: Option<String> = "llvm-config",
@ -60,6 +64,7 @@ pub struct Target {
pub ar: Option<PathBuf>,
pub ranlib: Option<PathBuf>,
pub default_linker: Option<PathBuf>,
pub default_linker_linux_override: DefaultLinuxLinkerOverride,
pub linker: Option<PathBuf>,
pub split_debuginfo: Option<SplitDebuginfo>,
pub sanitizers: Option<bool>,
@ -89,3 +94,60 @@ impl Target {
target
}
}
/// Overrides the default linker used on a Linux target.
/// 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<D>(deserializer: D) -> Result<Self, D::Error>
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<String, DefaultLinuxLinkerOverride> {
[("x86_64-unknown-linux-gnu".to_string(), DefaultLinuxLinkerOverride::SelfContainedLldCc)]
.into()
}
#[cfg(test)]
thread_local! {
static TEST_LINUX_LINKER_OVERRIDES: std::cell::RefCell<Option<HashMap<String, DefaultLinuxLinkerOverride>>> = std::cell::RefCell::new(None);
}
#[cfg(test)]
pub fn default_linux_linker_overrides() -> HashMap<String, DefaultLinuxLinkerOverride> {
TEST_LINUX_LINKER_OVERRIDES.with(|cell| cell.borrow().clone()).unwrap_or_default()
}
#[cfg(test)]
pub fn with_default_linux_linker_overrides<R>(
targets: HashMap<String, DefaultLinuxLinkerOverride>,
f: impl FnOnce() -> R,
) -> R {
TEST_LINUX_LINKER_OVERRIDES.with(|cell| {
let prev = cell.replace(Some(targets));
let result = f();
cell.replace(prev);
result
})
}

View file

@ -566,4 +566,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info,
summary: "`compiletest` is now always built with the stage 0 compiler, so `build.compiletest-use-stage0-libtest` has no effect.",
},
ChangeInfo {
change_id: 147157,
severity: ChangeSeverity::Warning,
summary: "`rust.lld = true` no longer automatically causes the `x86_64-unknown-linux-gnu` target to default into using the self-contained LLD linker. This target now uses the LLD linker by default. To opt out, set `target.x86_64-unknown-linux-gnu.default-linker-linux-override = 'off'`.",
},
];