Skip to content

Commit befd1eb

Browse files
committed
Auto merge of #116278 - Kobzol:bootstrap-lld-mode, r=albertlarsan68,petrochenkov
Generalize LLD usage in bootstrap The current usage of using LLD (`rust.use-lld = true`) in bootstrap is a bit messy. What it claimed: > Indicates whether LLD will be used to link Rust crates during bootstrap on > supported platforms. The LLD from the bootstrap distribution will be used > and not the LLD compiled during the bootstrap. What it did: 1) On MSVC, it did indeed use the snapshot compiler's `rust-lld`, but at the same time it was invoking a global `lld` binary (since #102101), therefore it wouldn't work if `lld` wasn't available. 2) On other targets, it was just straight up using a global `lld` linker. If it wasn't available, it would fail. This PR (hopefully) cleans up handling of LLD in bootstrap. It introduces a new enum called `LldMode`, which explicitly distinguishes between no LLD, external LLD and self-contained LLD. Since it's non-trivial to provide a custom path to LLD, if an external `lld` is used, the linker binary has to be named exactly `lld` and it has to be available in PATH. In addition, this PR also dog-foods [MCP510](rust-lang/compiler-team#510) in bootstrap. To keep backwards compatibility somewhat, I kept the original `use-lld` flag and mapped the `true` value to `"external"`, which is how it behaved before on Linux and other non-MSVC targets. Having the option to use an external `lld` on Linux should come in handy for testing on CI once MCP510 sets the default linker on Linux to `lld`. Note that thanks to MCP510, currently "self-contained" means that `lld` is used from the stage N-1 compiler (before, we always used `lld` from the snapshot/stage0 compiler). Best reviewed commit by commit. CC `@petrochenkov`
2 parents 84f6130 + 53031b2 commit befd1eb

File tree

13 files changed

+226
-109
lines changed

13 files changed

+226
-109
lines changed

config.example.toml

+5-3
Original file line numberDiff line numberDiff line change
@@ -648,10 +648,12 @@ change-id = 117813
648648
#lld = false
649649

650650
# Indicates whether LLD will be used to link Rust crates during bootstrap on
651-
# supported platforms. The LLD from the bootstrap distribution will be used
652-
# and not the LLD compiled during the bootstrap.
651+
# supported platforms.
652+
# If set to `true` or `"external"`, a global `lld` binary that has to be in $PATH
653+
# will be used.
654+
# If set to `"self-contained"`, rust-lld from the snapshot compiler will be used.
653655
#
654-
# LLD will not be used if we're cross linking.
656+
# On MSVC, LLD will not be used if we're cross linking.
655657
#
656658
# Explicitly setting the linker for a target will override this option when targeting MSVC.
657659
#use-lld = false

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ impl Step for Rustc {
870870
// is already on by default in MSVC optimized builds, which is interpreted as --icf=all:
871871
// https://github.com/llvm/llvm-project/blob/3329cec2f79185bafd678f310fafadba2a8c76d2/lld/COFF/Driver.cpp#L1746
872872
// https://github.com/rust-lang/rust/blob/f22819bcce4abaff7d1246a56eec493418f9f4ee/compiler/rustc_codegen_ssa/src/back/linker.rs#L827
873-
if builder.config.use_lld && !compiler.host.contains("msvc") {
873+
if builder.config.lld_mode.is_used() && !compiler.host.is_msvc() {
874874
cargo.rustflag("-Clink-args=-Wl,--icf=all");
875875
}
876876

@@ -1089,7 +1089,7 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
10891089
// found. This is to avoid the linker errors about undefined references to
10901090
// `__llvm_profile_instrument_memop` when linking `rustc_driver`.
10911091
let mut llvm_linker_flags = String::new();
1092-
if builder.config.llvm_profile_generate && target.contains("msvc") {
1092+
if builder.config.llvm_profile_generate && target.is_msvc() {
10931093
if let Some(ref clang_cl_path) = builder.config.llvm_clang_cl {
10941094
// Add clang's runtime library directory to the search path
10951095
let clang_rt_dir = get_clang_cl_resource_dir(clang_cl_path);
@@ -1114,7 +1114,7 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
11141114
// not for MSVC or macOS
11151115
if builder.config.llvm_static_stdcpp
11161116
&& !target.contains("freebsd")
1117-
&& !target.contains("msvc")
1117+
&& !target.is_msvc()
11181118
&& !target.contains("apple")
11191119
&& !target.contains("solaris")
11201120
{

src/bootstrap/src/core/build_steps/llvm.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub fn prebuilt_llvm_config(
9898
let out_dir = builder.llvm_out(target);
9999

100100
let mut llvm_config_ret_dir = builder.llvm_out(builder.config.build);
101-
if !builder.config.build.contains("msvc") || builder.ninja() {
101+
if !builder.config.build.is_msvc() || builder.ninja() {
102102
llvm_config_ret_dir.push("build");
103103
}
104104
llvm_config_ret_dir.push("bin");
@@ -411,7 +411,7 @@ impl Step for Llvm {
411411
ldflags.shared.push(" -latomic");
412412
}
413413

414-
if target.contains("msvc") {
414+
if target.is_msvc() {
415415
cfg.define("LLVM_USE_CRT_DEBUG", "MT");
416416
cfg.define("LLVM_USE_CRT_RELEASE", "MT");
417417
cfg.define("LLVM_USE_CRT_RELWITHDEBINFO", "MT");
@@ -644,7 +644,7 @@ fn configure_cmake(
644644
}
645645

646646
let sanitize_cc = |cc: &Path| {
647-
if target.contains("msvc") {
647+
if target.is_msvc() {
648648
OsString::from(cc.to_str().unwrap().replace("\\", "/"))
649649
} else {
650650
cc.as_os_str().to_owned()
@@ -654,7 +654,7 @@ fn configure_cmake(
654654
// MSVC with CMake uses msbuild by default which doesn't respect these
655655
// vars that we'd otherwise configure. In that case we just skip this
656656
// entirely.
657-
if target.contains("msvc") && !builder.ninja() {
657+
if target.is_msvc() && !builder.ninja() {
658658
return;
659659
}
660660

@@ -664,7 +664,7 @@ fn configure_cmake(
664664
};
665665

666666
// Handle msvc + ninja + ccache specially (this is what the bots use)
667-
if target.contains("msvc") && builder.ninja() && builder.config.ccache.is_some() {
667+
if target.is_msvc() && builder.ninja() && builder.config.ccache.is_some() {
668668
let mut wrap_cc = env::current_exe().expect("failed to get cwd");
669669
wrap_cc.set_file_name("sccache-plus-cl.exe");
670670

@@ -768,7 +768,7 @@ fn configure_cmake(
768768
// For distribution we want the LLVM tools to be *statically* linked to libstdc++.
769769
// We also do this if the user explicitly requested static libstdc++.
770770
if builder.config.llvm_static_stdcpp
771-
&& !target.contains("msvc")
771+
&& !target.is_msvc()
772772
&& !target.contains("netbsd")
773773
&& !target.contains("solaris")
774774
{
@@ -874,7 +874,7 @@ impl Step for Lld {
874874
// when doing PGO on CI, cmake or clang-cl don't automatically link clang's
875875
// profiler runtime in. In that case, we need to manually ask cmake to do it, to avoid
876876
// linking errors, much like LLVM's cmake setup does in that situation.
877-
if builder.config.llvm_profile_generate && target.contains("msvc") {
877+
if builder.config.llvm_profile_generate && target.is_msvc() {
878878
if let Some(clang_cl_path) = builder.config.llvm_clang_cl.as_ref() {
879879
// Find clang's runtime library directory and push that as a search path to the
880880
// cmake linker flags.

src/bootstrap/src/core/build_steps/test.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ use crate::utils;
2929
use crate::utils::cache::{Interned, INTERNER};
3030
use crate::utils::exec::BootstrapCommand;
3131
use crate::utils::helpers::{
32-
self, add_link_lib_path, add_rustdoc_cargo_lld_flags, add_rustdoc_lld_flags, dylib_path,
33-
dylib_path_var, output, t, target_supports_cranelift_backend, up_to_date, LldThreads,
32+
self, add_link_lib_path, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var,
33+
linker_args, linker_flags, output, t, target_supports_cranelift_backend, up_to_date,
34+
LldThreads,
3435
};
3536
use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests};
3637
use crate::{envify, CLang, DocTests, GitRepo, Mode};
@@ -277,7 +278,7 @@ impl Step for Cargotest {
277278
.args(builder.config.test_args())
278279
.env("RUSTC", builder.rustc(compiler))
279280
.env("RUSTDOC", builder.rustdoc(compiler));
280-
add_rustdoc_cargo_lld_flags(&mut cmd, builder, compiler.host, LldThreads::No);
281+
add_rustdoc_cargo_linker_args(&mut cmd, builder, compiler.host, LldThreads::No);
281282
builder.run_delaying_failure(cmd);
282283
}
283284
}
@@ -863,7 +864,7 @@ impl Step for RustdocTheme {
863864
.env("CFG_RELEASE_CHANNEL", &builder.config.channel)
864865
.env("RUSTDOC_REAL", builder.rustdoc(self.compiler))
865866
.env("RUSTC_BOOTSTRAP", "1");
866-
add_rustdoc_lld_flags(&mut cmd, builder, self.compiler.host, LldThreads::No);
867+
cmd.args(linker_args(builder, self.compiler.host, LldThreads::No));
867868

868869
builder.run_delaying_failure(&mut cmd);
869870
}
@@ -1039,7 +1040,7 @@ impl Step for RustdocGUI {
10391040
cmd.env("RUSTDOC", builder.rustdoc(self.compiler))
10401041
.env("RUSTC", builder.rustc(self.compiler));
10411042

1042-
add_rustdoc_cargo_lld_flags(&mut cmd, builder, self.compiler.host, LldThreads::No);
1043+
add_rustdoc_cargo_linker_args(&mut cmd, builder, self.compiler.host, LldThreads::No);
10431044

10441045
for path in &builder.paths {
10451046
if let Some(p) = helpers::is_valid_test_suite_arg(path, "tests/rustdoc-gui", builder) {
@@ -1746,14 +1747,14 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
17461747

17471748
let mut hostflags = flags.clone();
17481749
hostflags.push(format!("-Lnative={}", builder.test_helpers_out(compiler.host).display()));
1749-
hostflags.extend(builder.lld_flags(compiler.host));
1750+
hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No));
17501751
for flag in hostflags {
17511752
cmd.arg("--host-rustcflags").arg(flag);
17521753
}
17531754

17541755
let mut targetflags = flags;
17551756
targetflags.push(format!("-Lnative={}", builder.test_helpers_out(target).display()));
1756-
targetflags.extend(builder.lld_flags(target));
1757+
targetflags.extend(linker_flags(builder, compiler.host, LldThreads::No));
17571758
for flag in targetflags {
17581759
cmd.arg("--target-rustcflags").arg(flag);
17591760
}
@@ -1931,7 +1932,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
19311932
//
19321933
// Note that if we encounter `PATH` we make sure to append to our own `PATH`
19331934
// rather than stomp over it.
1934-
if !builder.config.dry_run() && target.contains("msvc") {
1935+
if !builder.config.dry_run() && target.is_msvc() {
19351936
for &(ref k, ref v) in builder.cc.borrow()[&target].env() {
19361937
if k != "PATH" {
19371938
cmd.env(k, v);
@@ -3070,7 +3071,7 @@ impl Step for TestHelpers {
30703071
// We may have found various cross-compilers a little differently due to our
30713072
// extra configuration, so inform cc of these compilers. Note, though, that
30723073
// on MSVC we still need cc's detection of env vars (ugh).
3073-
if !target.contains("msvc") {
3074+
if !target.is_msvc() {
30743075
if let Some(ar) = builder.ar(target) {
30753076
cfg.archiver(ar);
30763077
}

src/bootstrap/src/core/build_steps/tool.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ impl<'a> Builder<'a> {
833833
// On MSVC a tool may invoke a C compiler (e.g., compiletest in run-make
834834
// mode) and that C compiler may need some extra PATH modification. Do
835835
// so here.
836-
if compiler.host.contains("msvc") {
836+
if compiler.host.is_msvc() {
837837
let curpaths = env::var_os("PATH").unwrap_or_default();
838838
let curpaths = env::split_paths(&curpaths).collect::<Vec<_>>();
839839
for &(ref k, ref v) in self.cc.borrow()[&compiler.host].env() {

src/bootstrap/src/core/builder.rs

+14-16
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use crate::core::build_steps::{check, clean, compile, dist, doc, install, run, s
1919
use crate::core::config::flags::{Color, Subcommand};
2020
use crate::core::config::{DryRun, SplitDebuginfo, TargetSelection};
2121
use crate::utils::cache::{Cache, Interned, INTERNER};
22-
use crate::utils::helpers::{self, add_dylib_path, add_link_lib_path, add_rustdoc_lld_flags, exe};
23-
use crate::utils::helpers::{libdir, output, t, LldThreads};
22+
use crate::utils::helpers::{self, add_dylib_path, add_link_lib_path, exe, linker_args};
23+
use crate::utils::helpers::{libdir, linker_flags, output, t, LldThreads};
2424
use crate::Crate;
2525
use crate::EXTRA_CHECK_CFGS;
2626
use crate::{Build, CLang, DocTests, GitRepo, Mode};
@@ -1174,7 +1174,7 @@ impl<'a> Builder<'a> {
11741174
cmd.env_remove("MAKEFLAGS");
11751175
cmd.env_remove("MFLAGS");
11761176

1177-
add_rustdoc_lld_flags(&mut cmd, self, compiler.host, LldThreads::Yes);
1177+
cmd.args(linker_args(self, compiler.host, LldThreads::Yes));
11781178
cmd
11791179
}
11801180

@@ -1673,23 +1673,22 @@ impl<'a> Builder<'a> {
16731673
rustflags.arg(&format!("-Zstack-protector={stack_protector}"));
16741674
}
16751675

1676-
if let Some(host_linker) = self.linker(compiler.host) {
1677-
hostflags.arg(format!("-Clinker={}", host_linker.display()));
1678-
}
1679-
if self.is_fuse_ld_lld(compiler.host) {
1680-
hostflags.arg("-Clink-args=-fuse-ld=lld");
1676+
for arg in linker_args(self, compiler.host, LldThreads::Yes) {
1677+
hostflags.arg(&arg);
16811678
}
16821679

16831680
if let Some(target_linker) = self.linker(target) {
16841681
let target = crate::envify(&target.triple);
16851682
cargo.env(&format!("CARGO_TARGET_{target}_LINKER"), target_linker);
16861683
}
1687-
if self.is_fuse_ld_lld(target) {
1688-
rustflags.arg("-Clink-args=-fuse-ld=lld");
1684+
// We want to set -Clinker using Cargo, therefore we only call `linker_flags` and not
1685+
// `linker_args` here.
1686+
for flag in linker_flags(self, target, LldThreads::Yes) {
1687+
rustflags.arg(&flag);
1688+
}
1689+
for arg in linker_args(self, target, LldThreads::Yes) {
1690+
rustdocflags.arg(&arg);
16891691
}
1690-
self.lld_flags(target).for_each(|flag| {
1691-
rustdocflags.arg(&flag);
1692-
});
16931692

16941693
if !(["build", "check", "clippy", "fix", "rustc"].contains(&cmd)) && want_rustdoc {
16951694
cargo.env("RUSTDOC_LIBDIR", self.rustc_libdir(compiler));
@@ -1729,8 +1728,7 @@ impl<'a> Builder<'a> {
17291728

17301729
let split_debuginfo_is_stable = target.contains("linux")
17311730
|| target.contains("apple")
1732-
|| (target.contains("msvc")
1733-
&& self.config.rust_split_debuginfo == SplitDebuginfo::Packed)
1731+
|| (target.is_msvc() && self.config.rust_split_debuginfo == SplitDebuginfo::Packed)
17341732
|| (target.contains("windows")
17351733
&& self.config.rust_split_debuginfo == SplitDebuginfo::Off);
17361734

@@ -1909,7 +1907,7 @@ impl<'a> Builder<'a> {
19091907
// the options through environment variables that are fetched and understood by both.
19101908
//
19111909
// FIXME: the guard against msvc shouldn't need to be here
1912-
if target.contains("msvc") {
1910+
if target.is_msvc() {
19131911
if let Some(ref cl) = self.config.llvm_clang_cl {
19141912
cargo.env("CC", cl).env("CXX", cl);
19151913
}

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

+88-3
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,39 @@ impl Display for DebuginfoLevel {
105105
}
106106
}
107107

108+
/// LLD in bootstrap works like this:
109+
/// - Self-contained lld: use `rust-lld` from the compiler's sysroot
110+
/// - External: use an external `lld` binary
111+
///
112+
/// It is configured depending on the target:
113+
/// 1) Everything except MSVC
114+
/// - Self-contained: `-Clinker-flavor=gnu-lld-cc -Clink-self-contained=+linker`
115+
/// - External: `-Clinker-flavor=gnu-lld-cc`
116+
/// 2) MSVC
117+
/// - Self-contained: `-Clinker=<path to rust-lld>`
118+
/// - External: `-Clinker=lld`
119+
#[derive(Default, Copy, Clone)]
120+
pub enum LldMode {
121+
/// Do not use LLD
122+
#[default]
123+
Unused,
124+
/// Use `rust-lld` from the compiler's sysroot
125+
SelfContained,
126+
/// Use an externally provided `lld` binary.
127+
/// Note that the linker name cannot be overridden, the binary has to be named `lld` and it has
128+
/// to be in $PATH.
129+
External,
130+
}
131+
132+
impl LldMode {
133+
pub fn is_used(&self) -> bool {
134+
match self {
135+
LldMode::SelfContained | LldMode::External => true,
136+
LldMode::Unused => false,
137+
}
138+
}
139+
}
140+
108141
/// Global configuration for the entire build and/or bootstrap.
109142
///
110143
/// This structure is parsed from `config.toml`, and some of the fields are inferred from `git` or build-time parameters.
@@ -199,7 +232,7 @@ pub struct Config {
199232
pub llvm_from_ci: bool,
200233
pub llvm_build_config: HashMap<String, String>,
201234

202-
pub use_lld: bool,
235+
pub lld_mode: LldMode,
203236
pub lld_enabled: bool,
204237
pub llvm_tools_enabled: bool,
205238

@@ -489,6 +522,10 @@ impl TargetSelection {
489522
pub fn is_synthetic(&self) -> bool {
490523
self.synthetic
491524
}
525+
526+
pub fn is_msvc(&self) -> bool {
527+
self.contains("msvc")
528+
}
492529
}
493530

494531
impl fmt::Display for TargetSelection {
@@ -977,6 +1014,44 @@ enum StringOrInt<'a> {
9771014
String(&'a str),
9781015
Int(i64),
9791016
}
1017+
1018+
impl<'de> Deserialize<'de> for LldMode {
1019+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1020+
where
1021+
D: Deserializer<'de>,
1022+
{
1023+
struct LldModeVisitor;
1024+
1025+
impl<'de> serde::de::Visitor<'de> for LldModeVisitor {
1026+
type Value = LldMode;
1027+
1028+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1029+
formatter.write_str("one of true, 'self-contained' or 'external'")
1030+
}
1031+
1032+
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1033+
where
1034+
E: serde::de::Error,
1035+
{
1036+
Ok(if v { LldMode::External } else { LldMode::Unused })
1037+
}
1038+
1039+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1040+
where
1041+
E: serde::de::Error,
1042+
{
1043+
match v {
1044+
"external" => Ok(LldMode::External),
1045+
"self-contained" => Ok(LldMode::SelfContained),
1046+
_ => Err(E::custom("unknown mode {v}")),
1047+
}
1048+
}
1049+
}
1050+
1051+
deserializer.deserialize_any(LldModeVisitor)
1052+
}
1053+
}
1054+
9801055
define_config! {
9811056
/// TOML representation of how the Rust build is configured.
9821057
struct Rust {
@@ -1014,7 +1089,7 @@ define_config! {
10141089
save_toolstates: Option<String> = "save-toolstates",
10151090
codegen_backends: Option<Vec<String>> = "codegen-backends",
10161091
lld: Option<bool> = "lld",
1017-
use_lld: Option<bool> = "use-lld",
1092+
lld_mode: Option<LldMode> = "use-lld",
10181093
llvm_tools: Option<bool> = "llvm-tools",
10191094
deny_warnings: Option<bool> = "deny-warnings",
10201095
backtrace_on_ice: Option<bool> = "backtrace-on-ice",
@@ -1442,8 +1517,18 @@ impl Config {
14421517
if let Some(true) = rust.incremental {
14431518
config.incremental = true;
14441519
}
1445-
set(&mut config.use_lld, rust.use_lld);
1520+
set(&mut config.lld_mode, rust.lld_mode);
14461521
set(&mut config.lld_enabled, rust.lld);
1522+
1523+
if matches!(config.lld_mode, LldMode::SelfContained)
1524+
&& !config.lld_enabled
1525+
&& flags.stage.unwrap_or(0) > 0
1526+
{
1527+
panic!(
1528+
"Trying to use self-contained lld as a linker, but LLD is not being added to the sysroot. Enable it with rust.lld = true."
1529+
);
1530+
}
1531+
14471532
set(&mut config.llvm_tools_enabled, rust.llvm_tools);
14481533
config.rustc_parallel = rust
14491534
.parallel_compiler

0 commit comments

Comments
 (0)