about summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2022-11-05 12:35:40 +0100
committerRalf Jung <post@ralfj.de>2022-11-05 15:21:50 +0100
commit83239c2c1ebed372dd34016bdd75d02e4429589d (patch)
treef76015608b2a3fcdbdc5dc7f50049e089c6666d6 /src/bootstrap
parent4492c029eff7b42e91e759576ab0c7ca88f873dc (diff)
parent452cf4f7109f58433ac38be7d3da527408571054 (diff)
downloadrust-83239c2c1ebed372dd34016bdd75d02e4429589d.tar.gz
rust-83239c2c1ebed372dd34016bdd75d02e4429589d.zip
Merge from rustc
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/bin/rustdoc.rs8
-rw-r--r--src/bootstrap/builder.rs2
-rw-r--r--src/bootstrap/check.rs8
-rw-r--r--src/bootstrap/compile.rs15
-rw-r--r--src/bootstrap/config.rs59
-rw-r--r--src/bootstrap/dist.rs32
-rw-r--r--src/bootstrap/doc.rs35
-rw-r--r--src/bootstrap/lib.rs4
-rw-r--r--src/bootstrap/native.rs4
-rw-r--r--src/bootstrap/test.rs15
-rw-r--r--src/bootstrap/tool.rs30
-rw-r--r--src/bootstrap/util.rs14
12 files changed, 118 insertions, 108 deletions
diff --git a/src/bootstrap/bin/rustdoc.rs b/src/bootstrap/bin/rustdoc.rs
index e69cab956c5..23828f4758d 100644
--- a/src/bootstrap/bin/rustdoc.rs
+++ b/src/bootstrap/bin/rustdoc.rs
@@ -55,13 +55,9 @@ fn main() {
         arg.push(&linker);
         cmd.arg(arg);
     }
-    if env::var_os("RUSTDOC_FUSE_LD_LLD").is_some() {
+    if let Ok(no_threads) = env::var("RUSTDOC_LLD_NO_THREADS") {
         cmd.arg("-Clink-arg=-fuse-ld=lld");
-        if cfg!(windows) {
-            cmd.arg("-Clink-arg=-Wl,/threads:1");
-        } else {
-            cmd.arg("-Clink-arg=-Wl,--threads=1");
-        }
+        cmd.arg(format!("-Clink-arg=-Wl,{}", no_threads));
     }
     // Cargo doesn't pass RUSTDOCFLAGS to proc_macros:
     // https://github.com/rust-lang/cargo/issues/4423
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 8b144f14635..6de37463633 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -621,6 +621,8 @@ impl<'a> Builder<'a> {
                 check::CodegenBackend,
                 check::Clippy,
                 check::Miri,
+                check::CargoMiri,
+                check::MiroptTestTools,
                 check::Rls,
                 check::RustAnalyzer,
                 check::Rustfmt,
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index 229851238f1..2e1bd8d6d1f 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -451,16 +451,16 @@ macro_rules! tool_check_step {
 }
 
 tool_check_step!(Rustdoc, "src/tools/rustdoc", "src/librustdoc", SourceType::InTree);
-// Clippy and Rustfmt are hybrids. They are external tools, but use a git subtree instead
+// Clippy, miri and Rustfmt are hybrids. They are external tools, but use a git subtree instead
 // of a submodule. Since the SourceType only drives the deny-warnings
 // behavior, treat it as in-tree so that any new warnings in clippy will be
 // rejected.
 tool_check_step!(Clippy, "src/tools/clippy", SourceType::InTree);
-// Miri on the other hand is treated as out of tree, since InTree also causes it to
-// be run as part of `check`, which can fail on platforms which libffi-sys has no support for.
-tool_check_step!(Miri, "src/tools/miri", SourceType::Submodule);
+tool_check_step!(Miri, "src/tools/miri", SourceType::InTree);
+tool_check_step!(CargoMiri, "src/tools/miri/cargo-miri", SourceType::InTree);
 tool_check_step!(Rls, "src/tools/rls", SourceType::InTree);
 tool_check_step!(Rustfmt, "src/tools/rustfmt", SourceType::InTree);
+tool_check_step!(MiroptTestTools, "src/tools/miropt-test-tools", SourceType::InTree);
 
 tool_check_step!(Bootstrap, "src/bootstrap", SourceType::InTree, false);
 
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index e02a10b8164..18e780a108d 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -299,7 +299,9 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, car
 
     // Determine if we're going to compile in optimized C intrinsics to
     // the `compiler-builtins` crate. These intrinsics live in LLVM's
-    // `compiler-rt` repository.
+    // `compiler-rt` repository, but our `src/llvm-project` submodule isn't
+    // always checked out, so we need to conditionally look for this. (e.g. if
+    // an external LLVM is used we skip the LLVM submodule checkout).
     //
     // Note that this shouldn't affect the correctness of `compiler-builtins`,
     // but only its speed. Some intrinsics in C haven't been translated to Rust
@@ -310,15 +312,8 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, car
     // If `compiler-rt` is available ensure that the `c` feature of the
     // `compiler-builtins` crate is enabled and it's configured to learn where
     // `compiler-rt` is located.
-    let compiler_builtins_c_feature = if builder.config.optimized_compiler_builtins {
-        if !builder.is_rust_llvm(target) {
-            panic!(
-                "need a managed LLVM submodule for optimized intrinsics support; unset `llvm-config` or `optimized-compiler-builtins`"
-            );
-        }
-
-        builder.update_submodule(&Path::new("src").join("llvm-project"));
-        let compiler_builtins_root = builder.src.join("src/llvm-project/compiler-rt");
+    let compiler_builtins_root = builder.src.join("src/llvm-project/compiler-rt");
+    let compiler_builtins_c_feature = if compiler_builtins_root.exists() {
         // Note that `libprofiler_builtins/build.rs` also computes this so if
         // you're changing something here please also change that.
         cargo.env("RUST_COMPILER_RT_ROOT", &compiler_builtins_root);
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index a8c403675d8..21dc11c4808 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -73,8 +73,6 @@ pub struct Config {
     pub color: Color,
     pub patch_binaries_for_nix: bool,
     pub stage0_metadata: Stage0Metadata,
-    /// Whether to use the `c` feature of the `compiler_builtins` crate.
-    pub optimized_compiler_builtins: bool,
 
     pub on_fail: Option<String>,
     pub stage: u32,
@@ -624,7 +622,6 @@ define_config! {
         bench_stage: Option<u32> = "bench-stage",
         patch_binaries_for_nix: Option<bool> = "patch-binaries-for-nix",
         metrics: Option<bool> = "metrics",
-        optimized_compiler_builtins: Option<bool> = "optimized-compiler-builtins",
     }
 }
 
@@ -1013,7 +1010,6 @@ impl Config {
         set(&mut config.print_step_timings, build.print_step_timings);
         set(&mut config.print_step_rusage, build.print_step_rusage);
         set(&mut config.patch_binaries_for_nix, build.patch_binaries_for_nix);
-        set(&mut config.optimized_compiler_builtins, build.optimized_compiler_builtins);
 
         config.verbose = cmp::max(config.verbose, flags.verbose);
 
@@ -1384,21 +1380,46 @@ impl Config {
         git
     }
 
-    pub(crate) fn artifact_channel(&self, builder: &Builder<'_>, commit: &str) -> String {
-        if builder.rust_info.is_managed_git_subrepository() {
+    /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
+    /// Return the version it would have used for the given commit.
+    pub(crate) fn artifact_version_part(&self, builder: &Builder<'_>, commit: &str) -> String {
+        let (channel, version) = if builder.rust_info.is_managed_git_subrepository() {
             let mut channel = self.git();
             channel.arg("show").arg(format!("{}:src/ci/channel", commit));
             let channel = output(&mut channel);
-            channel.trim().to_owned()
-        } else if let Ok(channel) = fs::read_to_string(builder.src.join("src/ci/channel")) {
-            channel.trim().to_owned()
+            let mut version = self.git();
+            version.arg("show").arg(format!("{}:src/version", commit));
+            let version = output(&mut version);
+            (channel.trim().to_owned(), version.trim().to_owned())
         } else {
-            let src = builder.src.display();
-            eprintln!("error: failed to determine artifact channel");
-            eprintln!(
-                "help: either use git or ensure that {src}/src/ci/channel contains the name of the channel to use"
-            );
-            panic!();
+            let channel = fs::read_to_string(builder.src.join("src/ci/channel"));
+            let version = fs::read_to_string(builder.src.join("src/version"));
+            match (channel, version) {
+                (Ok(channel), Ok(version)) => {
+                    (channel.trim().to_owned(), version.trim().to_owned())
+                }
+                (channel, version) => {
+                    let src = builder.src.display();
+                    eprintln!("error: failed to determine artifact channel and/or version");
+                    eprintln!(
+                        "help: consider using a git checkout or ensure these files are readable"
+                    );
+                    if let Err(channel) = channel {
+                        eprintln!("reading {}/src/ci/channel failed: {:?}", src, channel);
+                    }
+                    if let Err(version) = version {
+                        eprintln!("reading {}/src/version failed: {:?}", src, version);
+                    }
+                    panic!();
+                }
+            }
+        };
+
+        match channel.as_str() {
+            "stable" => version,
+            "beta" => channel,
+            "nightly" => channel,
+            other => unreachable!("{:?} is not recognized as a valid channel", other),
         }
     }
 
@@ -1641,7 +1662,7 @@ fn maybe_download_rustfmt(builder: &Builder<'_>) -> Option<PathBuf> {
 
 fn download_ci_rustc(builder: &Builder<'_>, commit: &str) {
     builder.verbose(&format!("using downloaded stage2 artifacts from CI (commit {commit})"));
-    let channel = builder.config.artifact_channel(builder, commit);
+    let version = builder.config.artifact_version_part(builder, commit);
     let host = builder.config.build.triple;
     let bin_root = builder.out.join(host).join("ci-rustc");
     let rustc_stamp = bin_root.join(".rustc-stamp");
@@ -1650,13 +1671,13 @@ fn download_ci_rustc(builder: &Builder<'_>, commit: &str) {
         if bin_root.exists() {
             t!(fs::remove_dir_all(&bin_root));
         }
-        let filename = format!("rust-std-{channel}-{host}.tar.xz");
+        let filename = format!("rust-std-{version}-{host}.tar.xz");
         let pattern = format!("rust-std-{host}");
         download_ci_component(builder, filename, &pattern, commit);
-        let filename = format!("rustc-{channel}-{host}.tar.xz");
+        let filename = format!("rustc-{version}-{host}.tar.xz");
         download_ci_component(builder, filename, "rustc", commit);
         // download-rustc doesn't need its own cargo, it can just use beta's.
-        let filename = format!("rustc-dev-{channel}-{host}.tar.xz");
+        let filename = format!("rustc-dev-{version}-{host}.tar.xz");
         download_ci_component(builder, filename, "rustc-dev", commit);
 
         builder.fix_bin_or_dylib(&bin_root.join("bin").join("rustc"));
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index c636d4c2b47..805633c926c 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -1847,21 +1847,23 @@ fn add_env(builder: &Builder<'_>, cmd: &mut Command, target: TargetSelection) {
 ///
 /// Returns whether the files were actually copied.
 fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir: &Path) -> bool {
-    if !builder.is_rust_llvm(target) {
-        // If the LLVM was externally provided, then we don't currently copy
-        // artifacts into the sysroot. This is not necessarily the right
-        // choice (in particular, it will require the LLVM dylib to be in
-        // the linker's load path at runtime), but the common use case for
-        // external LLVMs is distribution provided LLVMs, and in that case
-        // they're usually in the standard search path (e.g., /usr/lib) and
-        // copying them here is going to cause problems as we may end up
-        // with the wrong files and isn't what distributions want.
-        //
-        // This behavior may be revisited in the future though.
-        //
-        // If the LLVM is coming from ourselves (just from CI) though, we
-        // still want to install it, as it otherwise won't be available.
-        return false;
+    if let Some(config) = builder.config.target_config.get(&target) {
+        if config.llvm_config.is_some() && !builder.config.llvm_from_ci {
+            // If the LLVM was externally provided, then we don't currently copy
+            // artifacts into the sysroot. This is not necessarily the right
+            // choice (in particular, it will require the LLVM dylib to be in
+            // the linker's load path at runtime), but the common use case for
+            // external LLVMs is distribution provided LLVMs, and in that case
+            // they're usually in the standard search path (e.g., /usr/lib) and
+            // copying them here is going to cause problems as we may end up
+            // with the wrong files and isn't what distributions want.
+            //
+            // This behavior may be revisited in the future though.
+            //
+            // If the LLVM is coming from ourselves (just from CI) though, we
+            // still want to install it, as it otherwise won't be available.
+            return false;
+        }
     }
 
     // On macOS, rustc (and LLVM tools) link to an unversioned libLLVM.dylib
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index ea06caf9c33..5d265b9ad0c 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -765,7 +765,7 @@ impl Step for Rustc {
 }
 
 macro_rules! tool_doc {
-    ($tool: ident, $should_run: literal, $path: literal, [$($krate: literal),+ $(,)?], in_tree = $in_tree:expr $(,)?) => {
+    ($tool: ident, $should_run: literal, $path: literal, [$($krate: literal),+ $(,)?] $(,)?) => {
         #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
         pub struct $tool {
             target: TargetSelection,
@@ -821,12 +821,6 @@ macro_rules! tool_doc {
                 t!(fs::create_dir_all(&out_dir));
                 t!(symlink_dir_force(&builder.config, &out, &out_dir));
 
-                let source_type = if $in_tree == true {
-                    SourceType::InTree
-                } else {
-                    SourceType::Submodule
-                };
-
                 // Build cargo command.
                 let mut cargo = prepare_tool_cargo(
                     builder,
@@ -835,7 +829,7 @@ macro_rules! tool_doc {
                     target,
                     "doc",
                     $path,
-                    source_type,
+                    SourceType::InTree,
                     &[],
                 );
 
@@ -851,38 +845,21 @@ macro_rules! tool_doc {
                 cargo.rustdocflag("--show-type-layout");
                 cargo.rustdocflag("--generate-link-to-definition");
                 cargo.rustdocflag("-Zunstable-options");
-                if $in_tree == true {
-                    builder.run(&mut cargo.into());
-                } else {
-                    // Allow out-of-tree docs to fail (since the tool might be in a broken state).
-                    if !builder.try_run(&mut cargo.into()) {
-                        builder.info(&format!(
-                            "WARNING: tool {} failed to document; ignoring failure because it is an out-of-tree tool",
-                            stringify!($tool).to_lowercase(),
-                        ));
-                    }
-                }
+                builder.run(&mut cargo.into());
             }
         }
     }
 }
 
-tool_doc!(
-    Rustdoc,
-    "rustdoc-tool",
-    "src/tools/rustdoc",
-    ["rustdoc", "rustdoc-json-types"],
-    in_tree = true
-);
+tool_doc!(Rustdoc, "rustdoc-tool", "src/tools/rustdoc", ["rustdoc", "rustdoc-json-types"],);
 tool_doc!(
     Rustfmt,
     "rustfmt-nightly",
     "src/tools/rustfmt",
     ["rustfmt-nightly", "rustfmt-config_proc_macro"],
-    in_tree = true
 );
-tool_doc!(Clippy, "clippy", "src/tools/clippy", ["clippy_utils"], in_tree = true);
-tool_doc!(Miri, "miri", "src/tools/miri", ["miri"], in_tree = false);
+tool_doc!(Clippy, "clippy", "src/tools/clippy", ["clippy_utils"]);
+tool_doc!(Miri, "miri", "src/tools/miri", ["miri"]);
 
 #[derive(Ord, PartialOrd, Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct ErrorIndex {
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 7e70e99bb8c..f5def8ba834 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -1152,8 +1152,8 @@ impl Build {
                 options[0] = Some("-Clink-arg=-fuse-ld=lld".to_string());
             }
 
-            let threads = if target.contains("windows") { "/threads:1" } else { "--threads=1" };
-            options[1] = Some(format!("-Clink-arg=-Wl,{}", threads));
+            let no_threads = util::lld_flag_no_threads(target.contains("windows"));
+            options[1] = Some(format!("-Clink-arg=-Wl,{}", no_threads));
         }
 
         IntoIterator::into_iter(options).flatten()
diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs
index 2f856c2761c..94a61b727a3 100644
--- a/src/bootstrap/native.rs
+++ b/src/bootstrap/native.rs
@@ -269,8 +269,8 @@ fn download_ci_llvm(builder: &Builder<'_>, llvm_sha: &str) {
     } else {
         &builder.config.stage0_metadata.config.artifacts_server
     };
-    let channel = builder.config.artifact_channel(builder, llvm_sha);
-    let filename = format!("rust-dev-{}-{}.tar.xz", channel, builder.build.build.triple);
+    let version = builder.config.artifact_version_part(builder, llvm_sha);
+    let filename = format!("rust-dev-{}-{}.tar.xz", version, builder.build.build.triple);
     let tarball = rustc_cache.join(&filename);
     if !tarball.exists() {
         let help_on_error = "error: failed to download llvm from ci
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 7edd3c12041..935ce5e7f84 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -509,7 +509,7 @@ impl Step for Miri {
             host,
             "run",
             "src/tools/miri/cargo-miri",
-            SourceType::Submodule,
+            SourceType::InTree,
             &[],
         );
         cargo.add_rustc_lib_path(builder, compiler);
@@ -557,7 +557,7 @@ impl Step for Miri {
             host,
             "test",
             "src/tools/miri",
-            SourceType::Submodule,
+            SourceType::InTree,
             &[],
         );
         cargo.add_rustc_lib_path(builder, compiler);
@@ -771,7 +771,10 @@ impl Step for RustdocTheme {
             cmd.env("RUSTDOC_LINKER", linker);
         }
         if builder.is_fuse_ld_lld(self.compiler.host) {
-            cmd.env("RUSTDOC_FUSE_LD_LLD", "1");
+            cmd.env(
+                "RUSTDOC_LLD_NO_THREADS",
+                util::lld_flag_no_threads(self.compiler.host.contains("windows")),
+            );
         }
         try_run(builder, &mut cmd);
     }
@@ -983,6 +986,7 @@ impl Step for RustdocGUI {
                     .arg("doc")
                     .arg("--target-dir")
                     .arg(&out_dir)
+                    .env("RUSTC_BOOTSTRAP", "1")
                     .env("RUSTDOC", builder.rustdoc(self.compiler))
                     .env("RUSTC", builder.rustc(self.compiler))
                     .current_dir(path);
@@ -1048,6 +1052,9 @@ impl Step for Tidy {
         if builder.is_verbose() {
             cmd.arg("--verbose");
         }
+        if builder.config.cmd.bless() {
+            cmd.arg("--bless");
+        }
 
         builder.info("tidy check");
         try_run(builder, &mut cmd);
@@ -1722,6 +1729,8 @@ impl BookTest {
 
         let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
         let path = builder.src.join(&self.path);
+        // Books often have feature-gated example text.
+        rustbook_cmd.env("RUSTC_BOOTSTRAP", "1");
         rustbook_cmd.env("PATH", new_path).arg("test").arg(path);
         builder.add_rust_test_threads(&mut rustbook_cmd);
         builder.info(&format!("Testing rustbook {}", self.path.display()));
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index eec74b2675a..d3952206947 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -794,10 +794,9 @@ macro_rules! tool_extended {
        $($name:ident,
        $path:expr,
        $tool_name:expr,
-       stable = $stable:expr,
-       $(in_tree = $in_tree:expr,)?
-       $(tool_std = $tool_std:literal,)?
-       $extra_deps:block;)+) => {
+       stable = $stable:expr
+       $(,tool_std = $tool_std:literal)?
+       ;)+) => {
         $(
             #[derive(Debug, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
@@ -839,7 +838,6 @@ macro_rules! tool_extended {
 
             #[allow(unused_mut)]
             fn run(mut $sel, $builder: &Builder<'_>) -> Option<PathBuf> {
-                $extra_deps
                 $builder.ensure(ToolBuild {
                     compiler: $sel.compiler,
                     target: $sel.target,
@@ -848,11 +846,7 @@ macro_rules! tool_extended {
                     path: $path,
                     extra_features: $sel.extra_features,
                     is_optional_tool: true,
-                    source_type: if false $(|| $in_tree)* {
-                        SourceType::InTree
-                    } else {
-                        SourceType::Submodule
-                    },
+                    source_type: SourceType::InTree,
                 })
             }
         }
@@ -865,17 +859,17 @@ macro_rules! tool_extended {
 // Note: Most submodule updates for tools are handled by bootstrap.py, since they're needed just to
 // invoke Cargo to build bootstrap. See the comment there for more details.
 tool_extended!((self, builder),
-    Cargofmt, "src/tools/rustfmt", "cargo-fmt", stable=true, in_tree=true, {};
-    CargoClippy, "src/tools/clippy", "cargo-clippy", stable=true, in_tree=true, {};
-    Clippy, "src/tools/clippy", "clippy-driver", stable=true, in_tree=true, {};
-    Miri, "src/tools/miri", "miri", stable=false, in_tree=true, {};
-    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", stable=false, in_tree=true, {};
+    Cargofmt, "src/tools/rustfmt", "cargo-fmt", stable=true;
+    CargoClippy, "src/tools/clippy", "cargo-clippy", stable=true;
+    Clippy, "src/tools/clippy", "clippy-driver", stable=true;
+    Miri, "src/tools/miri", "miri", stable=false;
+    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", stable=true;
     // FIXME: tool_std is not quite right, we shouldn't allow nightly features.
     // But `builder.cargo` doesn't know how to handle ToolBootstrap in stages other than 0,
     // and this is close enough for now.
-    Rls, "src/tools/rls", "rls", stable=true, in_tree=true, tool_std=true, {};
-    RustDemangler, "src/tools/rust-demangler", "rust-demangler", stable=false, in_tree=true, tool_std=true, {};
-    Rustfmt, "src/tools/rustfmt", "rustfmt", stable=true, in_tree=true, {};
+    Rls, "src/tools/rls", "rls", stable=true, tool_std=true;
+    RustDemangler, "src/tools/rust-demangler", "rust-demangler", stable=false, tool_std=true;
+    Rustfmt, "src/tools/rustfmt", "rustfmt", stable=true;
 );
 
 impl<'a> Builder<'a> {
diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs
index 0ebabbd5ca5..20c3801f0a5 100644
--- a/src/bootstrap/util.rs
+++ b/src/bootstrap/util.rs
@@ -13,6 +13,7 @@ use std::time::{Instant, SystemTime, UNIX_EPOCH};
 
 use crate::builder::Builder;
 use crate::config::{Config, TargetSelection};
+use crate::OnceCell;
 
 /// A helper macro to `unwrap` a result except also print out details like:
 ///
@@ -607,3 +608,16 @@ pub fn get_clang_cl_resource_dir(clang_cl_path: &str) -> PathBuf {
     let clang_rt_dir = clang_rt_builtins.parent().expect("The clang lib folder should exist");
     clang_rt_dir.to_path_buf()
 }
+
+pub fn lld_flag_no_threads(is_windows: bool) -> &'static str {
+    static LLD_NO_THREADS: OnceCell<(&'static str, &'static str)> = OnceCell::new();
+    let (windows, other) = LLD_NO_THREADS.get_or_init(|| {
+        let out = output(Command::new("lld").arg("-flavor").arg("ld").arg("--version"));
+        let newer = match (out.find(char::is_numeric), out.find('.')) {
+            (Some(b), Some(e)) => out.as_str()[b..e].parse::<i32>().ok().unwrap_or(14) > 10,
+            _ => true,
+        };
+        if newer { ("/threads:1", "--threads=1") } else { ("/no-threads", "--no-threads") }
+    });
+    if is_windows { windows } else { other }
+}