about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/src/core/build_steps/dist.rs40
-rw-r--r--src/bootstrap/src/core/build_steps/doc.rs337
-rw-r--r--src/bootstrap/src/core/build_steps/llvm.rs9
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs15
-rw-r--r--src/bootstrap/src/core/builder/cargo.rs4
-rw-r--r--src/bootstrap/src/core/builder/tests.rs354
-rw-r--r--src/bootstrap/src/core/config/config.rs707
-rw-r--r--src/bootstrap/src/core/download.rs37
-rw-r--r--src/bootstrap/src/utils/change_tracker.rs5
-rw-r--r--src/ci/docker/host-x86_64/pr-check-2/Dockerfile8
-rwxr-xr-xsrc/ci/run.sh3
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs9
12 files changed, 973 insertions, 555 deletions
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index 8f0de4c87d8..f9cb300b68e 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -93,7 +93,8 @@ impl Step for Docs {
 
 #[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)]
 pub struct JsonDocs {
-    pub host: TargetSelection,
+    build_compiler: Compiler,
+    target: TargetSelection,
 }
 
 impl Step for JsonDocs {
@@ -106,24 +107,27 @@ impl Step for JsonDocs {
     }
 
     fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(JsonDocs { host: run.target });
+        run.builder.ensure(JsonDocs {
+            build_compiler: run.builder.compiler(run.builder.top_stage, run.builder.host_target),
+            target: run.target,
+        });
     }
 
     /// Builds the `rust-docs-json` installer component.
     fn run(self, builder: &Builder<'_>) -> Option<GeneratedTarball> {
-        let host = self.host;
-        builder.ensure(crate::core::build_steps::doc::Std::new(
-            builder.top_stage,
-            host,
+        let target = self.target;
+        let directory = builder.ensure(crate::core::build_steps::doc::Std::from_build_compiler(
+            self.build_compiler,
+            target,
             DocumentationFormat::Json,
         ));
 
         let dest = "share/doc/rust/json";
 
-        let mut tarball = Tarball::new(builder, "rust-docs-json", &host.triple);
+        let mut tarball = Tarball::new(builder, "rust-docs-json", &target.triple);
         tarball.set_product_name("Rust Documentation In JSON Format");
         tarball.is_preview(true);
-        tarball.add_bulk_dir(builder.json_doc_out(host), dest);
+        tarball.add_bulk_dir(directory, dest);
         Some(tarball.generate())
     }
 }
@@ -926,6 +930,8 @@ fn copy_src_dirs(
             "llvm-project\\cmake",
             "llvm-project/runtimes",
             "llvm-project\\runtimes",
+            "llvm-project/third-party",
+            "llvm-project\\third-party",
         ];
         if spath.contains("llvm-project")
             && !spath.ends_with("llvm-project")
@@ -934,6 +940,18 @@ fn copy_src_dirs(
             return false;
         }
 
+        // Keep only these third party libraries
+        const LLVM_THIRD_PARTY: &[&str] =
+            &["llvm-project/third-party/siphash", "llvm-project\\third-party\\siphash"];
+        if (spath.starts_with("llvm-project/third-party")
+            || spath.starts_with("llvm-project\\third-party"))
+            && !(spath.ends_with("llvm-project/third-party")
+                || spath.ends_with("llvm-project\\third-party"))
+            && !LLVM_THIRD_PARTY.iter().any(|path| spath.contains(path))
+        {
+            return false;
+        }
+
         const LLVM_TEST: &[&str] = &["llvm-project/llvm/test", "llvm-project\\llvm\\test"];
         if LLVM_TEST.iter().any(|path| spath.contains(path))
             && (spath.ends_with(".ll") || spath.ends_with(".td") || spath.ends_with(".s"))
@@ -1566,11 +1584,12 @@ impl Step for Extended {
             };
         }
 
+        let target_compiler = builder.compiler(stage, target);
         // When rust-std package split from rustc, we needed to ensure that during
         // upgrades rustc was upgraded before rust-std. To avoid rustc clobbering
         // the std files during uninstall. To do this ensure that rustc comes
         // before rust-std in the list below.
-        tarballs.push(builder.ensure(Rustc { compiler: builder.compiler(stage, target) }));
+        tarballs.push(builder.ensure(Rustc { compiler: target_compiler }));
         tarballs.push(builder.ensure(Std { compiler, target }).expect("missing std"));
 
         if target.is_windows_gnu() {
@@ -1578,7 +1597,8 @@ impl Step for Extended {
         }
 
         add_component!("rust-docs" => Docs { host: target });
-        add_component!("rust-json-docs" => JsonDocs { host: target });
+        // Std stage N is documented with compiler stage N
+        add_component!("rust-json-docs" => JsonDocs { build_compiler: target_compiler, target });
         add_component!("cargo" => Cargo { build_compiler: compiler, target });
         add_component!("rustfmt" => Rustfmt { build_compiler: compiler, target });
         add_component!("rust-analyzer" => RustAnalyzer { build_compiler: compiler, target });
diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs
index 434e9c0977d..ca7a6dc8e07 100644
--- a/src/bootstrap/src/core/build_steps/doc.rs
+++ b/src/bootstrap/src/core/build_steps/doc.rs
@@ -16,8 +16,7 @@ use crate::core::build_steps::tool::{
     self, RustcPrivateCompilers, SourceType, Tool, prepare_tool_cargo,
 };
 use crate::core::builder::{
-    self, Alias, Builder, Compiler, Kind, RunConfig, ShouldRun, Step, StepMetadata,
-    crate_description,
+    self, Builder, Compiler, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description,
 };
 use crate::core::config::{Config, TargetSelection};
 use crate::helpers::{submodule_path_of, symlink_dir, t, up_to_date};
@@ -26,7 +25,7 @@ use crate::{FileType, Mode};
 macro_rules! book {
     ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => {
         $(
-            #[derive(Debug, Clone, Hash, PartialEq, Eq)]
+        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
             target: TargetSelection,
         }
@@ -57,7 +56,7 @@ macro_rules! book {
                     src: builder.src.join($path),
                     parent: Some(self),
                     languages: $lang.into(),
-                    rustdoc_compiler: None,
+                    build_compiler: None,
                 })
             }
         }
@@ -105,7 +104,7 @@ impl Step for UnstableBook {
             src: builder.md_doc_out(self.target).join("unstable-book"),
             parent: Some(self),
             languages: vec![],
-            rustdoc_compiler: None,
+            build_compiler: None,
         })
     }
 }
@@ -117,7 +116,8 @@ struct RustbookSrc<P: Step> {
     src: PathBuf,
     parent: Option<P>,
     languages: Vec<&'static str>,
-    rustdoc_compiler: Option<Compiler>,
+    /// Compiler whose rustdoc should be used to document things using `mdbook-spec`.
+    build_compiler: Option<Compiler>,
 }
 
 impl<P: Step> Step for RustbookSrc<P> {
@@ -150,7 +150,7 @@ impl<P: Step> Step for RustbookSrc<P> {
 
             let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
 
-            if let Some(compiler) = self.rustdoc_compiler {
+            if let Some(compiler) = self.build_compiler {
                 let mut rustdoc = builder.rustdoc_for_compiler(compiler);
                 rustdoc.pop();
                 let old_path = env::var_os("PATH").unwrap_or_default();
@@ -193,11 +193,21 @@ impl<P: Step> Step for RustbookSrc<P> {
             builder.maybe_open_in_browser::<P>(index)
         }
     }
+
+    fn metadata(&self) -> Option<StepMetadata> {
+        let mut metadata = StepMetadata::doc(&format!("{} (book)", self.name), self.target);
+        if let Some(compiler) = self.build_compiler {
+            metadata = metadata.built_by(compiler);
+        }
+
+        Some(metadata)
+    }
 }
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct TheBook {
-    compiler: Compiler,
+    /// Compiler whose rustdoc will be used to generated documentation.
+    build_compiler: Compiler,
     target: TargetSelection,
 }
 
@@ -212,7 +222,7 @@ impl Step for TheBook {
 
     fn make_run(run: RunConfig<'_>) {
         run.builder.ensure(TheBook {
-            compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target),
+            build_compiler: prepare_doc_compiler(run.builder, run.target, run.builder.top_stage),
             target: run.target,
         });
     }
@@ -229,7 +239,7 @@ impl Step for TheBook {
     fn run(self, builder: &Builder<'_>) {
         builder.require_submodule("src/doc/book", None);
 
-        let compiler = self.compiler;
+        let compiler = self.build_compiler;
         let target = self.target;
 
         let absolute_path = builder.src.join("src/doc/book");
@@ -242,7 +252,7 @@ impl Step for TheBook {
             src: absolute_path.clone(),
             parent: Some(self),
             languages: vec![],
-            rustdoc_compiler: None,
+            build_compiler: None,
         });
 
         // building older edition redirects
@@ -255,7 +265,7 @@ impl Step for TheBook {
                 // treat the other editions as not having a parent.
                 parent: Option::<Self>::None,
                 languages: vec![],
-                rustdoc_compiler: None,
+                build_compiler: None,
             });
         }
 
@@ -317,7 +327,7 @@ fn invoke_rustdoc(
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Standalone {
-    compiler: Compiler,
+    build_compiler: Compiler,
     target: TargetSelection,
 }
 
@@ -332,7 +342,11 @@ impl Step for Standalone {
 
     fn make_run(run: RunConfig<'_>) {
         run.builder.ensure(Standalone {
-            compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target),
+            build_compiler: prepare_doc_compiler(
+                run.builder,
+                run.builder.host_target,
+                run.builder.top_stage,
+            ),
             target: run.target,
         });
     }
@@ -347,8 +361,8 @@ impl Step for Standalone {
     /// In the end, this is just a glorified wrapper around rustdoc!
     fn run(self, builder: &Builder<'_>) {
         let target = self.target;
-        let compiler = self.compiler;
-        let _guard = builder.msg_doc(compiler, "standalone", target);
+        let build_compiler = self.build_compiler;
+        let _guard = builder.msg_doc(build_compiler, "standalone", target);
         let out = builder.doc_out(target);
         t!(fs::create_dir_all(&out));
 
@@ -367,7 +381,7 @@ impl Step for Standalone {
             }
 
             let html = out.join(filename).with_extension("html");
-            let rustdoc = builder.rustdoc_for_compiler(compiler);
+            let rustdoc = builder.rustdoc_for_compiler(build_compiler);
             if up_to_date(&path, &html)
                 && up_to_date(&footer, &html)
                 && up_to_date(&favicon, &html)
@@ -378,7 +392,7 @@ impl Step for Standalone {
                 continue;
             }
 
-            let mut cmd = builder.rustdoc_cmd(compiler);
+            let mut cmd = builder.rustdoc_cmd(build_compiler);
 
             cmd.arg("--html-after-content")
                 .arg(&footer)
@@ -415,11 +429,15 @@ impl Step for Standalone {
             builder.open_in_browser(index);
         }
     }
+
+    fn metadata(&self) -> Option<StepMetadata> {
+        Some(StepMetadata::doc("standalone", self.target).built_by(self.build_compiler))
+    }
 }
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Releases {
-    compiler: Compiler,
+    build_compiler: Compiler,
     target: TargetSelection,
 }
 
@@ -434,7 +452,11 @@ impl Step for Releases {
 
     fn make_run(run: RunConfig<'_>) {
         run.builder.ensure(Releases {
-            compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target),
+            build_compiler: prepare_doc_compiler(
+                run.builder,
+                run.builder.host_target,
+                run.builder.top_stage,
+            ),
             target: run.target,
         });
     }
@@ -446,15 +468,12 @@ impl Step for Releases {
     /// the headline added. In the end, the conversion is done by Rustdoc.
     fn run(self, builder: &Builder<'_>) {
         let target = self.target;
-        let compiler = self.compiler;
-        let _guard = builder.msg_doc(compiler, "releases", target);
+        let build_compiler = self.build_compiler;
+        let _guard = builder.msg_doc(build_compiler, "releases", target);
         let out = builder.doc_out(target);
         t!(fs::create_dir_all(&out));
 
-        builder.ensure(Standalone {
-            compiler: builder.compiler(builder.top_stage, builder.config.host_target),
-            target,
-        });
+        builder.ensure(Standalone { build_compiler, target });
 
         let version_info = builder.ensure(SharedAssets { target: self.target }).version_info;
 
@@ -465,7 +484,7 @@ impl Step for Releases {
         let html = out.join("releases.html");
         let tmppath = out.join("releases.md");
         let inpath = builder.src.join("RELEASES.md");
-        let rustdoc = builder.rustdoc_for_compiler(compiler);
+        let rustdoc = builder.rustdoc_for_compiler(build_compiler);
         if !up_to_date(&inpath, &html)
             || !up_to_date(&footer, &html)
             || !up_to_date(&favicon, &html)
@@ -478,7 +497,7 @@ impl Step for Releases {
             t!(tmpfile.write_all(b"% Rust Release Notes\n\n"));
             t!(io::copy(&mut t!(fs::File::open(&inpath)), &mut tmpfile));
             mem::drop(tmpfile);
-            let mut cmd = builder.rustdoc_cmd(compiler);
+            let mut cmd = builder.rustdoc_cmd(build_compiler);
 
             cmd.arg("--html-after-content")
                 .arg(&footer)
@@ -511,6 +530,10 @@ impl Step for Releases {
             builder.open_in_browser(&html);
         }
     }
+
+    fn metadata(&self) -> Option<StepMetadata> {
+        Some(StepMetadata::doc("releases", self.target).built_by(self.build_compiler))
+    }
 }
 
 #[derive(Debug, Clone)]
@@ -556,22 +579,29 @@ impl Step for SharedAssets {
     }
 }
 
+/// Document the standard library using `build_compiler`.
 #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Std {
-    pub stage: u32,
-    pub target: TargetSelection,
-    pub format: DocumentationFormat,
+    build_compiler: Compiler,
+    target: TargetSelection,
+    format: DocumentationFormat,
     crates: Vec<String>,
 }
 
 impl Std {
-    pub(crate) fn new(stage: u32, target: TargetSelection, format: DocumentationFormat) -> Self {
-        Std { stage, target, format, crates: vec![] }
+    pub(crate) fn from_build_compiler(
+        build_compiler: Compiler,
+        target: TargetSelection,
+        format: DocumentationFormat,
+    ) -> Self {
+        Std { build_compiler, target, format, crates: vec![] }
     }
 }
 
 impl Step for Std {
-    type Output = ();
+    /// Path to a directory with the built documentation.
+    type Output = PathBuf;
+
     const DEFAULT: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
@@ -586,7 +616,7 @@ impl Step for Std {
             return;
         }
         run.builder.ensure(Std {
-            stage: run.builder.top_stage,
+            build_compiler: run.builder.compiler(run.builder.top_stage, run.builder.host_target),
             target: run.target,
             format: if run.builder.config.cmd.json() {
                 DocumentationFormat::Json
@@ -601,8 +631,7 @@ impl Step for Std {
     ///
     /// This will generate all documentation for the standard library and its
     /// dependencies. This is largely just a wrapper around `cargo doc`.
-    fn run(self, builder: &Builder<'_>) {
-        let stage = self.stage;
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
         let target = self.target;
         let crates = if self.crates.is_empty() {
             builder
@@ -644,32 +673,32 @@ impl Step for Std {
         // For `--index-page` and `--output-format=json`.
         extra_args.push("-Zunstable-options");
 
-        doc_std(builder, self.format, stage, target, &out, &extra_args, &crates);
+        doc_std(builder, self.format, self.build_compiler, target, &out, &extra_args, &crates);
 
-        // Don't open if the format is json
-        if let DocumentationFormat::Json = self.format {
-            return;
-        }
-
-        if builder.paths.iter().any(|path| path.ends_with("library")) {
-            // For `x.py doc library --open`, open `std` by default.
-            let index = out.join("std").join("index.html");
-            builder.open_in_browser(index);
-        } else {
-            for requested_crate in crates {
-                if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) {
-                    let index = out.join(requested_crate).join("index.html");
-                    builder.open_in_browser(index);
-                    break;
+        // Open if the format is HTML
+        if let DocumentationFormat::Html = self.format {
+            if builder.paths.iter().any(|path| path.ends_with("library")) {
+                // For `x.py doc library --open`, open `std` by default.
+                let index = out.join("std").join("index.html");
+                builder.open_in_browser(index);
+            } else {
+                for requested_crate in crates {
+                    if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) {
+                        let index = out.join(requested_crate).join("index.html");
+                        builder.open_in_browser(index);
+                        break;
+                    }
                 }
             }
         }
+
+        out
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
         Some(
             StepMetadata::doc("std", self.target)
-                .stage(self.stage)
+                .built_by(self.build_compiler)
                 .with_metadata(format!("crates=[{}]", self.crates.join(","))),
         )
     }
@@ -705,24 +734,29 @@ impl DocumentationFormat {
 fn doc_std(
     builder: &Builder<'_>,
     format: DocumentationFormat,
-    stage: u32,
+    build_compiler: Compiler,
     target: TargetSelection,
     out: &Path,
     extra_args: &[&str],
     requested_crates: &[String],
 ) {
-    let compiler = builder.compiler(stage, builder.config.host_target);
-
     let target_doc_dir_name = if format == DocumentationFormat::Json { "json-doc" } else { "doc" };
-    let target_dir = builder.stage_out(compiler, Mode::Std).join(target).join(target_doc_dir_name);
+    let target_dir =
+        builder.stage_out(build_compiler, Mode::Std).join(target).join(target_doc_dir_name);
 
     // This is directory where the compiler will place the output of the command.
     // We will then copy the files from this directory into the final `out` directory, the specified
     // as a function parameter.
     let out_dir = target_dir.join(target).join("doc");
 
-    let mut cargo =
-        builder::Cargo::new(builder, compiler, Mode::Std, SourceType::InTree, target, Kind::Doc);
+    let mut cargo = builder::Cargo::new(
+        builder,
+        build_compiler,
+        Mode::Std,
+        SourceType::InTree,
+        target,
+        Kind::Doc,
+    );
 
     compile::std_cargo(builder, target, &mut cargo);
     cargo
@@ -750,27 +784,46 @@ fn doc_std(
 
     let description =
         format!("library{} in {} format", crate_description(requested_crates), format.as_str());
-    let _guard = builder.msg_doc(compiler, description, target);
+    let _guard = builder.msg_doc(build_compiler, description, target);
 
     cargo.into_cmd().run(builder);
     builder.cp_link_r(&out_dir, out);
 }
 
+/// Prepare a compiler that will be able to document something for `target` at `stage`.
+fn prepare_doc_compiler(builder: &Builder<'_>, target: TargetSelection, stage: u32) -> Compiler {
+    assert!(stage > 0, "Cannot document anything in stage 0");
+    let build_compiler = builder.compiler(stage - 1, builder.host_target);
+    builder.std(build_compiler, target);
+    build_compiler
+}
+
+/// Document the compiler for the given `target` using rustdoc from `build_compiler`.
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Rustc {
-    pub stage: u32,
-    pub target: TargetSelection,
+    build_compiler: Compiler,
+    target: TargetSelection,
     crates: Vec<String>,
 }
 
 impl Rustc {
-    pub(crate) fn new(stage: u32, target: TargetSelection, builder: &Builder<'_>) -> Self {
+    /// Document `stage` compiler for the given `target`.
+    pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self {
+        let build_compiler = prepare_doc_compiler(builder, target, stage);
+        Self::from_build_compiler(builder, build_compiler, target)
+    }
+
+    fn from_build_compiler(
+        builder: &Builder<'_>,
+        build_compiler: Compiler,
+        target: TargetSelection,
+    ) -> Self {
         let crates = builder
             .in_tree_crates("rustc-main", Some(target))
             .into_iter()
             .map(|krate| krate.name.to_string())
             .collect();
-        Self { stage, target, crates }
+        Self { build_compiler, target, crates }
     }
 }
 
@@ -787,11 +840,7 @@ impl Step for Rustc {
     }
 
     fn make_run(run: RunConfig<'_>) {
-        run.builder.ensure(Rustc {
-            stage: run.builder.top_stage,
-            target: run.target,
-            crates: run.make_run_crates(Alias::Compiler),
-        });
+        run.builder.ensure(Rustc::for_stage(run.builder, run.builder.top_stage, run.target));
     }
 
     /// Generates compiler documentation.
@@ -801,7 +850,6 @@ impl Step for Rustc {
     /// we do not merge it with the other documentation from std, test and
     /// proc_macros. This is largely just a wrapper around `cargo doc`.
     fn run(self, builder: &Builder<'_>) {
-        let stage = self.stage;
         let target = self.target;
 
         // This is the intended out directory for compiler documentation.
@@ -810,21 +858,21 @@ impl Step for Rustc {
 
         // Build the standard library, so that proc-macros can use it.
         // (Normally, only the metadata would be necessary, but proc-macros are special since they run at compile-time.)
-        let compiler = builder.compiler(stage, builder.config.host_target);
-        builder.std(compiler, builder.config.host_target);
+        let build_compiler = self.build_compiler;
+        builder.std(build_compiler, builder.config.host_target);
 
         let _guard = builder.msg_rustc_tool(
             Kind::Doc,
-            stage,
+            build_compiler.stage,
             format!("compiler{}", crate_description(&self.crates)),
-            compiler.host,
+            build_compiler.host,
             target,
         );
 
         // Build cargo command.
         let mut cargo = builder::Cargo::new(
             builder,
-            compiler,
+            build_compiler,
             Mode::Rustc,
             SourceType::InTree,
             target,
@@ -842,7 +890,7 @@ impl Step for Rustc {
         // If there is any bug, please comment out the next line.
         cargo.rustdocflag("--generate-link-to-definition");
 
-        compile::rustc_cargo(builder, &mut cargo, target, &compiler, &self.crates);
+        compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
         cargo.arg("-Zskip-rustdoc-fingerprint");
 
         // Only include compiler crates, no dependencies of those, such as `libc`.
@@ -857,7 +905,7 @@ impl Step for Rustc {
 
         let mut to_open = None;
 
-        let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target).join("doc");
+        let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc");
         for krate in &*self.crates {
             // Create all crate output directories first to make sure rustdoc uses
             // relative links.
@@ -879,7 +927,7 @@ impl Step for Rustc {
         symlink_dir_force(&builder.config, &out, &out_dir);
         // Cargo puts proc macros in `target/doc` even if you pass `--target`
         // explicitly (https://github.com/rust-lang/cargo/issues/7677).
-        let proc_macro_out_dir = builder.stage_out(compiler, Mode::Rustc).join("doc");
+        let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc");
         symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);
 
         cargo.into_cmd().run(builder);
@@ -905,7 +953,7 @@ impl Step for Rustc {
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
-        Some(StepMetadata::doc("rustc", self.target).stage(self.stage))
+        Some(StepMetadata::doc("rustc", self.target).built_by(self.build_compiler))
     }
 }
 
@@ -913,12 +961,14 @@ macro_rules! tool_doc {
     (
         $tool: ident,
         $path: literal,
-        $(rustc_tool = $rustc_tool:literal, )?
+        $(rustc_private_tool = $rustc_private_tool:literal, )?
         $(is_library = $is_library:expr,)?
         $(crates = $crates:expr)?
        ) => {
         #[derive(Debug, Clone, Hash, PartialEq, Eq)]
         pub struct $tool {
+            build_compiler: Compiler,
+            mode: Mode,
             target: TargetSelection,
         }
 
@@ -933,15 +983,26 @@ macro_rules! tool_doc {
             }
 
             fn make_run(run: RunConfig<'_>) {
-                run.builder.ensure($tool { target: run.target });
+                let target = run.target;
+                let (build_compiler, mode) = if true $(&& $rustc_private_tool)? {
+                    // Rustdoc needs the rustc sysroot available to build.
+                    let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, target);
+
+                    // Build rustc docs so that we generate relative links.
+                    run.builder.ensure(Rustc::from_build_compiler(run.builder, compilers.build_compiler(), target));
+
+                    (compilers.build_compiler(), Mode::ToolRustc)
+                } else {
+                    // bootstrap/host tools have to be documented with the stage 0 compiler
+                    (prepare_doc_compiler(run.builder, target, 1), Mode::ToolBootstrap)
+                };
+
+                run.builder.ensure($tool { build_compiler, mode, target });
             }
 
-            /// Generates compiler documentation.
+            /// Generates documentation for a tool.
             ///
-            /// This will generate all documentation for compiler and dependencies.
-            /// Compiler documentation is distributed separately, so we make sure
-            /// we do not merge it with the other documentation from std, test and
-            /// proc_macros. This is largely just a wrapper around `cargo doc`.
+            /// This is largely just a wrapper around `cargo doc`.
             fn run(self, builder: &Builder<'_>) {
                 let mut source_type = SourceType::InTree;
 
@@ -950,31 +1011,17 @@ macro_rules! tool_doc {
                     builder.require_submodule(&submodule_path, None);
                 }
 
-                let stage = builder.top_stage;
-                let target = self.target;
+                let $tool { build_compiler, mode, target } = self;
 
                 // This is the intended out directory for compiler documentation.
                 let out = builder.compiler_doc_out(target);
                 t!(fs::create_dir_all(&out));
 
-                let compiler = builder.compiler(stage, builder.config.host_target);
-                builder.std(compiler, target);
-
-                if true $(&& $rustc_tool)? {
-                    // Build rustc docs so that we generate relative links.
-                    builder.ensure(Rustc::new(stage, target, builder));
-
-                    // Rustdoc needs the rustc sysroot available to build.
-                    // FIXME: is there a way to only ensure `check::Rustc` here? Last time I tried it failed
-                    // with strange errors, but only on a full bors test ...
-                    builder.ensure(compile::Rustc::new(compiler, target));
-                }
-
                 // Build cargo command.
                 let mut cargo = prepare_tool_cargo(
                     builder,
-                    compiler,
-                    Mode::ToolRustc,
+                    build_compiler,
+                    mode,
                     target,
                     Kind::Doc,
                     $path,
@@ -1001,7 +1048,7 @@ macro_rules! tool_doc {
                 cargo.rustdocflag("--show-type-layout");
                 cargo.rustdocflag("--generate-link-to-definition");
 
-                let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target).join("doc");
+                let out_dir = builder.stage_out(build_compiler, mode).join(target).join("doc");
                 $(for krate in $crates {
                     let dir_name = krate.replace("-", "_");
                     t!(fs::create_dir_all(out_dir.join(&*dir_name)));
@@ -1009,10 +1056,10 @@ macro_rules! tool_doc {
 
                 // Symlink compiler docs to the output directory of rustdoc documentation.
                 symlink_dir_force(&builder.config, &out, &out_dir);
-                let proc_macro_out_dir = builder.stage_out(compiler, Mode::ToolRustc).join("doc");
+                let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc");
                 symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);
 
-                let _guard = builder.msg_doc(compiler, stringify!($tool).to_lowercase(), target);
+                let _guard = builder.msg_doc(build_compiler, stringify!($tool).to_lowercase(), target);
                 cargo.into_cmd().run(builder);
 
                 if !builder.config.dry_run() {
@@ -1026,7 +1073,7 @@ macro_rules! tool_doc {
             }
 
             fn metadata(&self) -> Option<StepMetadata> {
-                Some(StepMetadata::doc(stringify!($tool), self.target))
+                Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler))
             }
         }
     }
@@ -1036,7 +1083,7 @@ macro_rules! tool_doc {
 tool_doc!(
     BuildHelper,
     "src/build_helper",
-    rustc_tool = false,
+    rustc_private_tool = false,
     is_library = true,
     crates = ["build_helper"]
 );
@@ -1047,7 +1094,7 @@ tool_doc!(Miri, "src/tools/miri", crates = ["miri"]);
 tool_doc!(
     Cargo,
     "src/tools/cargo",
-    rustc_tool = false,
+    rustc_private_tool = false,
     crates = [
         "cargo",
         "cargo-credential",
@@ -1061,25 +1108,25 @@ tool_doc!(
         "rustfix",
     ]
 );
-tool_doc!(Tidy, "src/tools/tidy", rustc_tool = false, crates = ["tidy"]);
+tool_doc!(Tidy, "src/tools/tidy", rustc_private_tool = false, crates = ["tidy"]);
 tool_doc!(
     Bootstrap,
     "src/bootstrap",
-    rustc_tool = false,
+    rustc_private_tool = false,
     is_library = true,
     crates = ["bootstrap"]
 );
 tool_doc!(
     RunMakeSupport,
     "src/tools/run-make-support",
-    rustc_tool = false,
+    rustc_private_tool = false,
     is_library = true,
     crates = ["run_make_support"]
 );
 tool_doc!(
     Compiletest,
     "src/tools/compiletest",
-    rustc_tool = false,
+    rustc_private_tool = false,
     is_library = true,
     crates = ["compiletest"]
 );
@@ -1182,11 +1229,20 @@ fn symlink_dir_force(config: &Config, original: &Path, link: &Path) {
     );
 }
 
+/// Builds the Rust compiler book.
 #[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)]
 pub struct RustcBook {
-    pub compiler: Compiler,
-    pub target: TargetSelection,
-    pub validate: bool,
+    build_compiler: Compiler,
+    target: TargetSelection,
+    /// Test that the examples of lints in the book produce the correct lints in the expected
+    /// format.
+    validate: bool,
+}
+
+impl RustcBook {
+    pub fn validate(build_compiler: Compiler, target: TargetSelection) -> Self {
+        Self { build_compiler, target, validate: true }
+    }
 }
 
 impl Step for RustcBook {
@@ -1200,8 +1256,17 @@ impl Step for RustcBook {
     }
 
     fn make_run(run: RunConfig<'_>) {
+        // Bump the stage to 2, because the rustc book requires an in-tree compiler.
+        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
+        // in stage 1.
+        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
+            run.builder.top_stage
+        } else {
+            2
+        };
+
         run.builder.ensure(RustcBook {
-            compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target),
+            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
             target: run.target,
             validate: false,
         });
@@ -1219,10 +1284,10 @@ impl Step for RustcBook {
         builder.cp_link_r(&builder.src.join("src/doc/rustc"), &out_base);
         builder.info(&format!("Generating lint docs ({})", self.target));
 
-        let rustc = builder.rustc(self.compiler);
+        let rustc = builder.rustc(self.build_compiler);
         // The tool runs `rustc` for extracting output examples, so it needs a
         // functional sysroot.
-        builder.std(self.compiler, self.target);
+        builder.std(self.build_compiler, self.target);
         let mut cmd = builder.tool_cmd(Tool::LintDocs);
         cmd.arg("--src");
         cmd.arg(builder.src.join("compiler"));
@@ -1248,12 +1313,12 @@ impl Step for RustcBook {
         // If the lib directories are in an unusual location (changed in
         // bootstrap.toml), then this needs to explicitly update the dylib search
         // path.
-        builder.add_rustc_lib_path(self.compiler, &mut cmd);
+        builder.add_rustc_lib_path(self.build_compiler, &mut cmd);
         let doc_generator_guard = builder.msg(
             Kind::Run,
-            self.compiler.stage,
+            self.build_compiler.stage,
             "lint-docs",
-            self.compiler.host,
+            self.build_compiler.host,
             self.target,
         );
         cmd.run(builder);
@@ -1266,15 +1331,18 @@ impl Step for RustcBook {
             src: out_base,
             parent: Some(self),
             languages: vec![],
-            rustdoc_compiler: None,
+            build_compiler: None,
         });
     }
 }
 
+/// Documents the reference.
+/// It has to always be done using a stage 1+ compiler, because it references in-tree
+/// compiler/stdlib concepts.
 #[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Reference {
-    pub compiler: Compiler,
-    pub target: TargetSelection,
+    build_compiler: Compiler,
+    target: TargetSelection,
 }
 
 impl Step for Reference {
@@ -1287,8 +1355,19 @@ impl Step for Reference {
     }
 
     fn make_run(run: RunConfig<'_>) {
+        // Bump the stage to 2, because the reference requires an in-tree compiler.
+        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
+        // in stage 1.
+        // FIXME: create a shared method on builder for auto-bumping, and print some warning when
+        // it happens.
+        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
+            run.builder.top_stage
+        } else {
+            2
+        };
+
         run.builder.ensure(Reference {
-            compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target),
+            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
             target: run.target,
         });
     }
@@ -1299,14 +1378,14 @@ impl Step for Reference {
 
         // This is needed for generating links to the standard library using
         // the mdbook-spec plugin.
-        builder.std(self.compiler, builder.config.host_target);
+        builder.std(self.build_compiler, builder.config.host_target);
 
         // Run rustbook/mdbook to generate the HTML pages.
         builder.ensure(RustbookSrc {
             target: self.target,
             name: "reference".to_owned(),
             src: builder.src.join("src/doc/reference"),
-            rustdoc_compiler: Some(self.compiler),
+            build_compiler: Some(self.build_compiler),
             parent: Some(self),
             languages: vec![],
         });
diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs
index 79244827059..16941a32bb1 100644
--- a/src/bootstrap/src/core/build_steps/llvm.rs
+++ b/src/bootstrap/src/core/build_steps/llvm.rs
@@ -196,7 +196,10 @@ pub(crate) fn detect_llvm_freshness(config: &Config, is_git: bool) -> PathFreshn
 ///
 /// This checks the build triple platform to confirm we're usable at all, and if LLVM
 /// with/without assertions is available.
-pub(crate) fn is_ci_llvm_available_for_target(config: &Config, asserts: bool) -> bool {
+pub(crate) fn is_ci_llvm_available_for_target(
+    host_target: &TargetSelection,
+    asserts: bool,
+) -> bool {
     // This is currently all tier 1 targets and tier 2 targets with host tools
     // (since others may not have CI artifacts)
     // https://doc.rust-lang.org/rustc/platform-support.html#tier-1
@@ -235,8 +238,8 @@ pub(crate) fn is_ci_llvm_available_for_target(config: &Config, asserts: bool) ->
         ("x86_64-unknown-netbsd", false),
     ];
 
-    if !supported_platforms.contains(&(&*config.host_target.triple, asserts))
-        && (asserts || !supported_platforms.contains(&(&*config.host_target.triple, true)))
+    if !supported_platforms.contains(&(&*host_target.triple, asserts))
+        && (asserts || !supported_platforms.contains(&(&*host_target.triple, true)))
     {
         return false;
     }
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 32c3ef53e12..2f933baa4a4 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -212,10 +212,10 @@ impl Step for HtmlCheck {
         }
         // Ensure that a few different kinds of documentation are available.
         builder.default_doc(&[]);
-        builder.ensure(crate::core::build_steps::doc::Rustc::new(
+        builder.ensure(crate::core::build_steps::doc::Rustc::for_stage(
+            builder,
             builder.top_stage,
             self.target,
-            builder,
         ));
 
         builder
@@ -983,8 +983,8 @@ impl Step for RustdocJSStd {
                 command.arg("--test-file").arg(path);
             }
         }
-        builder.ensure(crate::core::build_steps::doc::Std::new(
-            builder.top_stage,
+        builder.ensure(crate::core::build_steps::doc::Std::from_build_compiler(
+            builder.compiler(builder.top_stage, builder.host_target),
             self.target,
             DocumentationFormat::Html,
         ));
@@ -3310,11 +3310,8 @@ impl Step for LintDocs {
     /// Tests that the lint examples in the rustc book generate the correct
     /// lints and have the expected format.
     fn run(self, builder: &Builder<'_>) {
-        builder.ensure(crate::core::build_steps::doc::RustcBook {
-            compiler: self.compiler,
-            target: self.target,
-            validate: true,
-        });
+        builder
+            .ensure(crate::core::build_steps::doc::RustcBook::validate(self.compiler, self.target));
     }
 }
 
diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs
index 39f46bf43af..757b9277ec6 100644
--- a/src/bootstrap/src/core/builder/cargo.rs
+++ b/src/bootstrap/src/core/builder/cargo.rs
@@ -502,7 +502,9 @@ impl Builder<'_> {
         if cmd_kind == Kind::Doc {
             let my_out = match mode {
                 // This is the intended out directory for compiler documentation.
-                Mode::Rustc | Mode::ToolRustc => self.compiler_doc_out(target),
+                Mode::Rustc | Mode::ToolRustc | Mode::ToolBootstrap => {
+                    self.compiler_doc_out(target)
+                }
                 Mode::Std => {
                     if self.config.cmd.json() {
                         out_dir.join(target).join("json-doc")
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index 4c7766e58c1..32d191c4265 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -742,6 +742,21 @@ mod snapshot {
     }
 
     #[test]
+    fn build_compiler_lld_opt_in() {
+        with_lld_opt_in_targets(vec![host_target()], || {
+            let ctx = TestCtx::new();
+            insta::assert_snapshot!(
+                ctx.config("build")
+                    .path("compiler")
+                    .render_steps(), @r"
+            [build] llvm <host>
+            [build] rustc 0 <host> -> rustc 1 <host>
+            [build] rustc 0 <host> -> LldWrapper 1 <host>
+            ");
+        });
+    }
+
+    #[test]
     fn build_library_no_explicit_stage() {
         let ctx = TestCtx::new();
         insta::assert_snapshot!(
@@ -939,7 +954,7 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 1 <host>
-        [doc] std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 1 <host> -> std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         ");
     }
 
@@ -1008,19 +1023,36 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <host>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] book (book) <host>
+        [doc] book/first-edition (book) <host>
+        [doc] book/second-edition (book) <host>
+        [doc] book/2018-edition (book) <host>
+        [build] rustdoc 1 <host>
+        [doc] rustc 1 <host> -> standalone 2 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] rustc 1 <host> -> error-index 2 <host>
         [doc] rustc 1 <host> -> error-index 2 <host>
-        [build] rustc 2 <host> -> std 2 <host>
+        [doc] nomicon (book) <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
+        [doc] rustdoc (book) <host>
+        [doc] rust-by-example (book) <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
+        [doc] rustc (book) <host>
+        [doc] cargo (book) <host>
+        [doc] clippy (book) <host>
+        [doc] embedded-book (book) <host>
+        [doc] edition-guide (book) <host>
+        [doc] style-guide (book) <host>
+        [doc] rustc 1 <host> -> releases 2 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
-        [doc] std 2 <host> crates=[]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[]
         [dist] mingw <host>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
         [dist] rustc <host>
@@ -1046,25 +1078,42 @@ mod snapshot {
             .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <host>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 0 <host> -> LldWrapper 1 <host>
         [build] rustc 0 <host> -> WasmComponentLd 1 <host>
         [build] rustc 0 <host> -> LlvmBitcodeLinker 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] book (book) <host>
+        [doc] book/first-edition (book) <host>
+        [doc] book/second-edition (book) <host>
+        [doc] book/2018-edition (book) <host>
+        [build] rustdoc 1 <host>
+        [doc] rustc 1 <host> -> standalone 2 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustc 1 <host> -> LldWrapper 2 <host>
         [build] rustc 1 <host> -> WasmComponentLd 2 <host>
         [build] rustc 1 <host> -> LlvmBitcodeLinker 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] rustc 1 <host> -> error-index 2 <host>
         [doc] rustc 1 <host> -> error-index 2 <host>
-        [build] rustc 2 <host> -> std 2 <host>
+        [doc] nomicon (book) <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
+        [doc] rustdoc (book) <host>
+        [doc] rust-by-example (book) <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
+        [doc] rustc (book) <host>
+        [doc] cargo (book) <host>
+        [doc] clippy (book) <host>
+        [doc] embedded-book (book) <host>
+        [doc] edition-guide (book) <host>
+        [doc] style-guide (book) <host>
+        [doc] rustc 1 <host> -> releases 2 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
-        [doc] std 2 <host> crates=[]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[]
         [dist] mingw <host>
         [build] rustc 1 <host> -> rust-analyzer-proc-macro-srv 2 <host>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
@@ -1093,22 +1142,56 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <host>
+        [doc] unstable-book (book) <target1>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] book (book) <host>
+        [doc] book/first-edition (book) <host>
+        [doc] book/second-edition (book) <host>
+        [doc] book/2018-edition (book) <host>
+        [build] rustdoc 1 <host>
+        [build] rustc 1 <host> -> std 1 <target1>
+        [doc] book (book) <target1>
+        [doc] book/first-edition (book) <target1>
+        [doc] book/second-edition (book) <target1>
+        [doc] book/2018-edition (book) <target1>
+        [doc] rustc 1 <host> -> standalone 2 <host>
+        [doc] rustc 1 <host> -> standalone 2 <target1>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
-        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] rustc 1 <host> -> error-index 2 <host>
         [doc] rustc 1 <host> -> error-index 2 <host>
-        [build] rustc 2 <host> -> std 2 <host>
+        [doc] nomicon (book) <host>
+        [doc] nomicon (book) <target1>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <target1>
+        [doc] rustdoc (book) <host>
+        [doc] rustdoc (book) <target1>
+        [doc] rust-by-example (book) <host>
+        [doc] rust-by-example (book) <target1>
         [build] rustc 0 <host> -> LintDocs 1 <host>
+        [doc] rustc (book) <host>
+        [doc] cargo (book) <host>
+        [doc] cargo (book) <target1>
+        [doc] clippy (book) <host>
+        [doc] clippy (book) <target1>
+        [doc] embedded-book (book) <host>
+        [doc] embedded-book (book) <target1>
+        [doc] edition-guide (book) <host>
+        [doc] edition-guide (book) <target1>
+        [doc] style-guide (book) <host>
+        [doc] style-guide (book) <target1>
+        [doc] rustc 1 <host> -> releases 2 <host>
+        [doc] rustc 1 <host> -> releases 2 <target1>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
         [dist] docs <target1>
-        [doc] std 2 <host> crates=[]
-        [doc] std 2 <target1> crates=[]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[]
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[]
         [dist] mingw <host>
         [dist] mingw <target1>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
@@ -1132,12 +1215,19 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <host>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] book (book) <host>
+        [doc] book/first-edition (book) <host>
+        [doc] book/second-edition (book) <host>
+        [doc] book/2018-edition (book) <host>
+        [build] rustdoc 1 <host>
+        [doc] rustc 1 <host> -> standalone 2 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] rustc 1 <host> -> error-index 2 <host>
         [doc] rustc 1 <host> -> error-index 2 <host>
         [build] llvm <target1>
@@ -1145,12 +1235,22 @@ mod snapshot {
         [build] rustc 1 <host> -> rustc 2 <target1>
         [build] rustc 1 <host> -> error-index 2 <target1>
         [doc] rustc 1 <host> -> error-index 2 <target1>
-        [build] rustc 2 <host> -> std 2 <host>
+        [doc] nomicon (book) <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
+        [doc] rustdoc (book) <host>
+        [doc] rust-by-example (book) <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
-        [build] rustc 2 <host> -> std 2 <target1>
+        [doc] rustc (book) <host>
+        [doc] rustc (book) <target1>
+        [doc] cargo (book) <host>
+        [doc] clippy (book) <host>
+        [doc] embedded-book (book) <host>
+        [doc] edition-guide (book) <host>
+        [doc] style-guide (book) <host>
+        [doc] rustc 1 <host> -> releases 2 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
-        [doc] std 2 <host> crates=[]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[]
         [dist] mingw <host>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
         [dist] rustc <host>
@@ -1173,28 +1273,61 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <host>
+        [doc] unstable-book (book) <target1>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] book (book) <host>
+        [doc] book/first-edition (book) <host>
+        [doc] book/second-edition (book) <host>
+        [doc] book/2018-edition (book) <host>
+        [build] rustdoc 1 <host>
+        [build] rustc 1 <host> -> std 1 <target1>
+        [doc] book (book) <target1>
+        [doc] book/first-edition (book) <target1>
+        [doc] book/second-edition (book) <target1>
+        [doc] book/2018-edition (book) <target1>
+        [doc] rustc 1 <host> -> standalone 2 <host>
+        [doc] rustc 1 <host> -> standalone 2 <target1>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
-        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] rustc 1 <host> -> error-index 2 <host>
         [doc] rustc 1 <host> -> error-index 2 <host>
         [build] llvm <target1>
-        [build] rustc 1 <host> -> std 1 <target1>
         [build] rustc 1 <host> -> rustc 2 <target1>
         [build] rustc 1 <host> -> error-index 2 <target1>
         [doc] rustc 1 <host> -> error-index 2 <target1>
-        [build] rustc 2 <host> -> std 2 <host>
+        [doc] nomicon (book) <host>
+        [doc] nomicon (book) <target1>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <target1>
+        [doc] rustdoc (book) <host>
+        [doc] rustdoc (book) <target1>
+        [doc] rust-by-example (book) <host>
+        [doc] rust-by-example (book) <target1>
         [build] rustc 0 <host> -> LintDocs 1 <host>
-        [build] rustc 2 <host> -> std 2 <target1>
+        [doc] rustc (book) <host>
+        [doc] rustc (book) <target1>
+        [doc] cargo (book) <host>
+        [doc] cargo (book) <target1>
+        [doc] clippy (book) <host>
+        [doc] clippy (book) <target1>
+        [doc] embedded-book (book) <host>
+        [doc] embedded-book (book) <target1>
+        [doc] edition-guide (book) <host>
+        [doc] edition-guide (book) <target1>
+        [doc] style-guide (book) <host>
+        [doc] style-guide (book) <target1>
+        [doc] rustc 1 <host> -> releases 2 <host>
+        [doc] rustc 1 <host> -> releases 2 <target1>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
         [dist] docs <target1>
-        [doc] std 2 <host> crates=[]
-        [doc] std 2 <target1> crates=[]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[]
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[]
         [dist] mingw <host>
         [dist] mingw <target1>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
@@ -1219,16 +1352,33 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <target1>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustc 1 <host> -> std 1 <target1>
+        [doc] book (book) <target1>
+        [doc] book/first-edition (book) <target1>
+        [doc] book/second-edition (book) <target1>
+        [doc] book/2018-edition (book) <target1>
+        [build] rustdoc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] rustc 1 <host> -> standalone 2 <target1>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
-        [build] rustc 2 <host> -> std 2 <host>
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] nomicon (book) <target1>
+        [doc] rustc 1 <host> -> reference (book) 2 <target1>
+        [doc] rustdoc (book) <target1>
+        [doc] rust-by-example (book) <target1>
+        [doc] cargo (book) <target1>
+        [doc] clippy (book) <target1>
+        [doc] embedded-book (book) <target1>
+        [doc] edition-guide (book) <target1>
+        [doc] style-guide (book) <target1>
+        [doc] rustc 1 <host> -> releases 2 <target1>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <target1>
-        [doc] std 2 <target1> crates=[]
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[]
         [dist] mingw <target1>
         [build] rustc 2 <host> -> std 2 <target1>
         [dist] rustc 2 <host> -> std 2 <target1>
@@ -1249,26 +1399,42 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <target1>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 0 <host> -> WasmComponentLd 1 <host>
+        [build] rustc 1 <host> -> std 1 <target1>
+        [doc] book (book) <target1>
+        [doc] book/first-edition (book) <target1>
+        [doc] book/second-edition (book) <target1>
+        [doc] book/2018-edition (book) <target1>
+        [build] rustdoc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] rustc 1 <host> -> standalone 2 <target1>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustc 1 <host> -> WasmComponentLd 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] llvm <target1>
-        [build] rustc 1 <host> -> std 1 <target1>
         [build] rustc 1 <host> -> rustc 2 <target1>
         [build] rustc 1 <host> -> WasmComponentLd 2 <target1>
         [build] rustc 1 <host> -> error-index 2 <target1>
         [doc] rustc 1 <host> -> error-index 2 <target1>
-        [build] rustc 2 <host> -> std 2 <host>
-        [build] rustc 2 <host> -> std 2 <target1>
+        [doc] nomicon (book) <target1>
+        [doc] rustc 1 <host> -> reference (book) 2 <target1>
+        [doc] rustdoc (book) <target1>
+        [doc] rust-by-example (book) <target1>
         [build] rustc 0 <host> -> LintDocs 1 <host>
+        [doc] rustc (book) <target1>
+        [doc] cargo (book) <target1>
+        [doc] clippy (book) <target1>
+        [doc] embedded-book (book) <target1>
+        [doc] edition-guide (book) <target1>
+        [doc] style-guide (book) <target1>
+        [doc] rustc 1 <host> -> releases 2 <target1>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <target1>
-        [doc] std 2 <target1> crates=[]
+        [doc] rustc 2 <host> -> std 2 <target1> crates=[]
         [dist] mingw <target1>
         [build] rustdoc 2 <target1>
         [build] rustc 1 <host> -> rust-analyzer-proc-macro-srv 2 <target1>
@@ -1285,6 +1451,7 @@ mod snapshot {
         [build] rustc 1 <host> -> miri 2 <target1>
         [build] rustc 1 <host> -> cargo-miri 2 <target1>
         [build] rustc 1 <host> -> LlvmBitcodeLinker 2 <target1>
+        [doc] rustc 2 <target1> -> std 2 <target1> crates=[]
         ");
     }
 
@@ -1300,21 +1467,38 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <host>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 0 <host> -> rustc_codegen_cranelift 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] book (book) <host>
+        [doc] book/first-edition (book) <host>
+        [doc] book/second-edition (book) <host>
+        [doc] book/2018-edition (book) <host>
+        [build] rustdoc 1 <host>
+        [doc] rustc 1 <host> -> standalone 2 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustc 1 <host> -> rustc_codegen_cranelift 2 <host>
         [build] rustdoc 2 <host>
-        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] rustc 1 <host> -> error-index 2 <host>
         [doc] rustc 1 <host> -> error-index 2 <host>
-        [build] rustc 2 <host> -> std 2 <host>
+        [doc] nomicon (book) <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
+        [doc] rustdoc (book) <host>
+        [doc] rust-by-example (book) <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
+        [doc] rustc (book) <host>
+        [doc] cargo (book) <host>
+        [doc] clippy (book) <host>
+        [doc] embedded-book (book) <host>
+        [doc] edition-guide (book) <host>
+        [doc] style-guide (book) <host>
+        [doc] rustc 1 <host> -> releases 2 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
-        [doc] std 2 <host> crates=[]
+        [doc] rustc 2 <host> -> std 2 <host> crates=[]
         [dist] mingw <host>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
         [dist] rustc <host>
@@ -1693,14 +1877,32 @@ mod snapshot {
                 .render_steps(), @r"
         [build] rustc 0 <host> -> UnstableBookGen 1 <host>
         [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] unstable-book (book) <host>
+        [doc] book (book) <host>
+        [doc] book/first-edition (book) <host>
+        [doc] book/second-edition (book) <host>
+        [doc] book/2018-edition (book) <host>
+        [build] rustdoc 0 <host>
+        [doc] rustc 0 <host> -> standalone 1 <host>
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 1 <host>
-        [doc] std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 1 <host> -> std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         [build] rustc 0 <host> -> error-index 1 <host>
         [doc] rustc 0 <host> -> error-index 1 <host>
+        [doc] nomicon (book) <host>
         [build] rustc 1 <host> -> std 1 <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
+        [doc] rustdoc (book) <host>
+        [doc] rust-by-example (book) <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
+        [doc] rustc (book) <host>
+        [doc] cargo (book) <host>
+        [doc] clippy (book) <host>
+        [doc] embedded-book (book) <host>
+        [doc] edition-guide (book) <host>
+        [doc] style-guide (book) <host>
+        [doc] rustc 0 <host> -> releases 1 <host>
         ");
     }
 
@@ -1714,7 +1916,7 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 1 <host>
-        [doc] std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
+        [doc] rustc 1 <host> -> std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind]
         ");
     }
 
@@ -1728,7 +1930,7 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 1 <host>
-        [doc] std 1 <host> crates=[core]
+        [doc] rustc 1 <host> -> std 1 <host> crates=[core]
         ");
     }
 
@@ -1743,26 +1945,11 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 1 <host>
-        [doc] std 1 <host> crates=[core]
+        [doc] rustc 1 <host> -> std 1 <host> crates=[core]
         ");
     }
 
     #[test]
-    fn test_lld_opt_in() {
-        with_lld_opt_in_targets(vec![host_target()], || {
-            let ctx = TestCtx::new();
-            insta::assert_snapshot!(
-                ctx.config("build")
-                    .path("compiler")
-                    .render_steps(), @r"
-            [build] llvm <host>
-            [build] rustc 0 <host> -> rustc 1 <host>
-            [build] rustc 0 <host> -> LldWrapper 1 <host>
-            ");
-        });
-    }
-
-    #[test]
     fn doc_library_no_std_target() {
         let ctx = TestCtx::new();
         insta::assert_snapshot!(
@@ -1773,7 +1960,7 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 1 <host>
-        [doc] std 1 <host> crates=[alloc,core]
+        [doc] rustc 1 <host> -> std 1 <host> crates=[alloc,core]
         ");
     }
 
@@ -1789,22 +1976,15 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 1 <host>
-        [doc] std 1 <target1> crates=[alloc,core]
+        [doc] rustc 1 <host> -> std 1 <target1> crates=[alloc,core]
         ");
     }
 
     #[test]
+    #[should_panic]
     fn doc_compiler_stage_0() {
         let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("doc")
-                .path("compiler")
-                .stage(0)
-                .render_steps(), @r"
-        [build] rustdoc 0 <host>
-        [build] llvm <host>
-        [doc] rustc 0 <host>
-        ");
+        ctx.config("doc").path("compiler").stage(0).run();
     }
 
     #[test]
@@ -1815,11 +1995,9 @@ mod snapshot {
                 .path("compiler")
                 .stage(1)
                 .render_steps(), @r"
+        [build] rustdoc 0 <host>
         [build] llvm <host>
-        [build] rustc 0 <host> -> rustc 1 <host>
-        [build] rustc 1 <host> -> std 1 <host>
-        [build] rustdoc 1 <host>
-        [doc] rustc 1 <host>
+        [doc] rustc 0 <host> -> rustc 1 <host>
         ");
     }
 
@@ -1834,57 +2012,57 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
-        [build] rustc 1 <host> -> rustc 2 <host>
-        [build] rustc 2 <host> -> std 2 <host>
-        [build] rustdoc 2 <host>
-        [doc] rustc 2 <host>
+        [build] rustdoc 1 <host>
+        [doc] rustc 1 <host> -> rustc 2 <host>
         ");
     }
 
     #[test]
+    #[should_panic]
     fn doc_compiletest_stage_0() {
         let ctx = TestCtx::new();
+        ctx.config("doc").path("src/tools/compiletest").stage(0).run();
+    }
+
+    #[test]
+    fn doc_compiletest_stage_1() {
+        let ctx = TestCtx::new();
         insta::assert_snapshot!(
             ctx.config("doc")
                 .path("src/tools/compiletest")
-                .stage(0)
+                .stage(1)
                 .render_steps(), @r"
         [build] rustdoc 0 <host>
-        [doc] Compiletest <host>
+        [doc] rustc 0 <host> -> Compiletest 1 <host>
         ");
     }
 
     #[test]
-    fn doc_compiletest_stage_1() {
+    fn doc_compiletest_stage_2() {
         let ctx = TestCtx::new();
         insta::assert_snapshot!(
             ctx.config("doc")
                 .path("src/tools/compiletest")
-                .stage(1)
+                .stage(2)
                 .render_steps(), @r"
-        [build] llvm <host>
-        [build] rustc 0 <host> -> rustc 1 <host>
-        [build] rustc 1 <host> -> std 1 <host>
-        [build] rustdoc 1 <host>
-        [doc] Compiletest <host>
+        [build] rustdoc 0 <host>
+        [doc] rustc 0 <host> -> Compiletest 1 <host>
         ");
     }
 
+    // Reference should be auto-bumped to stage 2.
     #[test]
-    fn doc_compiletest_stage_2() {
+    fn doc_reference() {
         let ctx = TestCtx::new();
         insta::assert_snapshot!(
             ctx.config("doc")
-                .path("src/tools/compiletest")
-                .stage(2)
+                .path("reference")
                 .render_steps(), @r"
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
-        [build] rustc 1 <host> -> rustc 2 <host>
-        [build] rustc 2 <host> -> std 2 <host>
-        [build] rustdoc 2 <host>
-        [doc] Compiletest <host>
+        [build] rustc 0 <host> -> Rustbook 1 <host>
+        [doc] rustc 1 <host> -> reference (book) 2 <host>
         ");
     }
 }
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index eb1ac8c637f..c505cacadb5 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -893,21 +893,25 @@ impl Config {
         let default = config.channel == "dev";
         config.omit_git_hash = rust_omit_git_hash.unwrap_or(default);
 
-        config.rust_info = config.git_info(config.omit_git_hash, &config.src);
+        config.rust_info = git_info(&config.exec_ctx, config.omit_git_hash, &config.src);
         config.cargo_info =
-            config.git_info(config.omit_git_hash, &config.src.join("src/tools/cargo"));
-        config.rust_analyzer_info =
-            config.git_info(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer"));
+            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/cargo"));
+        config.rust_analyzer_info = git_info(
+            &config.exec_ctx,
+            config.omit_git_hash,
+            &config.src.join("src/tools/rust-analyzer"),
+        );
         config.clippy_info =
-            config.git_info(config.omit_git_hash, &config.src.join("src/tools/clippy"));
+            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/clippy"));
         config.miri_info =
-            config.git_info(config.omit_git_hash, &config.src.join("src/tools/miri"));
+            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/miri"));
         config.rustfmt_info =
-            config.git_info(config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
+            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/rustfmt"));
         config.enzyme_info =
-            config.git_info(config.omit_git_hash, &config.src.join("src/tools/enzyme"));
-        config.in_tree_llvm_info = config.git_info(false, &config.src.join("src/llvm-project"));
-        config.in_tree_gcc_info = config.git_info(false, &config.src.join("src/gcc"));
+            git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/enzyme"));
+        config.in_tree_llvm_info =
+            git_info(&config.exec_ctx, false, &config.src.join("src/llvm-project"));
+        config.in_tree_gcc_info = git_info(&config.exec_ctx, false, &config.src.join("src/gcc"));
 
         config.vendor = build_vendor.unwrap_or(
             config.rust_info.is_from_tarball()
@@ -947,11 +951,18 @@ impl Config {
             );
         }
 
-        config.download_rustc_commit = config.download_ci_rustc_commit(
-            rust_download_rustc,
-            debug_assertions_requested,
-            config.llvm_assertions,
-        );
+        let dwn_ctx = DownloadContext::from(&config);
+        config.download_rustc_commit =
+            download_ci_rustc_commit(dwn_ctx, rust_download_rustc, config.llvm_assertions);
+
+        if debug_assertions_requested {
+            eprintln!(
+                "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \
+                rustc is not currently built with debug assertions."
+            );
+            // We need to put this later down_ci_rustc_commit.
+            config.download_rustc_commit = None;
+        }
 
         if let Some(t) = toml.target {
             for (triple, cfg) in t {
@@ -1157,8 +1168,9 @@ impl Config {
                 "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel."
             );
 
+            let dwn_ctx = DownloadContext::from(&config);
             let channel =
-                config.read_file_by_commit(Path::new("src/ci/channel"), commit).trim().to_owned();
+                read_file_by_commit(dwn_ctx, Path::new("src/ci/channel"), commit).trim().to_owned();
 
             config.channel = channel;
         }
@@ -1193,8 +1205,9 @@ impl Config {
         config.llvm_enable_warnings = llvm_enable_warnings.unwrap_or(false);
         config.llvm_build_config = llvm_build_config.clone().unwrap_or(Default::default());
 
+        let dwn_ctx = DownloadContext::from(&config);
         config.llvm_from_ci =
-            config.parse_download_ci_llvm(llvm_download_ci_llvm, config.llvm_assertions);
+            parse_download_ci_llvm(dwn_ctx, llvm_download_ci_llvm, config.llvm_assertions);
 
         if config.llvm_from_ci {
             let warn = |option: &str| {
@@ -1256,7 +1269,8 @@ impl Config {
 
         if config.llvm_from_ci {
             let triple = &config.host_target.triple;
-            let ci_llvm_bin = config.ci_llvm_root().join("bin");
+            let dwn_ctx = DownloadContext::from(&config);
+            let ci_llvm_bin = ci_llvm_root(dwn_ctx).join("bin");
             let build_target = config
                 .target_config
                 .entry(config.host_target)
@@ -1297,7 +1311,8 @@ impl Config {
             );
         }
 
-        if config.lld_enabled && config.is_system_llvm(config.host_target) {
+        let dwn_ctx = DownloadContext::from(&config);
+        if config.lld_enabled && is_system_llvm(dwn_ctx, config.host_target) {
             panic!("Cannot enable LLD with `rust.lld = true` when using external llvm-config.");
         }
 
@@ -1349,11 +1364,15 @@ impl Config {
         // Now check that the selected stage makes sense, and if not, print a warning and end
         match (config.stage, &config.cmd) {
             (0, Subcommand::Build) => {
-                eprintln!("WARNING: cannot build anything on stage 0. Use at least stage 1.");
+                eprintln!("ERROR: cannot build anything on stage 0. Use at least stage 1.");
                 exit!(1);
             }
             (0, Subcommand::Check { .. }) => {
-                eprintln!("WARNING: cannot check anything on stage 0. Use at least stage 1.");
+                eprintln!("ERROR: cannot check anything on stage 0. Use at least stage 1.");
+                exit!(1);
+            }
+            (0, Subcommand::Doc { .. }) => {
+                eprintln!("ERROR: cannot document anything on stage 0. Use at least stage 1.");
                 exit!(1);
             }
             _ => {}
@@ -1432,14 +1451,8 @@ impl Config {
 
     /// Returns the content of the given file at a specific commit.
     pub(crate) fn read_file_by_commit(&self, file: &Path, commit: &str) -> String {
-        assert!(
-            self.rust_info.is_managed_git_subrepository(),
-            "`Config::read_file_by_commit` is not supported in non-git sources."
-        );
-
-        let mut git = helpers::git(Some(&self.src));
-        git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
-        git.run_capture_stdout(self).stdout()
+        let dwn_ctx = DownloadContext::from(self);
+        read_file_by_commit(dwn_ctx, file, commit)
     }
 
     /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
@@ -1510,8 +1523,8 @@ impl Config {
 
     /// The absolute path to the downloaded LLVM artifacts.
     pub(crate) fn ci_llvm_root(&self) -> PathBuf {
-        assert!(self.llvm_from_ci);
-        self.out.join(self.host_target).join("ci-llvm")
+        let dwn_ctx = DownloadContext::from(self);
+        ci_llvm_root(dwn_ctx)
     }
 
     /// Directory where the extracted `rustc-dev` component is stored.
@@ -1674,261 +1687,14 @@ impl Config {
         ),
     )]
     pub(crate) fn update_submodule(&self, relative_path: &str) {
-        if self.rust_info.is_from_tarball() || !self.submodules() {
-            return;
-        }
-
-        let absolute_path = self.src.join(relative_path);
-
-        // NOTE: This check is required because `jj git clone` doesn't create directories for
-        // submodules, they are completely ignored. The code below assumes this directory exists,
-        // so create it here.
-        if !absolute_path.exists() {
-            t!(fs::create_dir_all(&absolute_path));
-        }
-
-        // NOTE: The check for the empty directory is here because when running x.py the first time,
-        // the submodule won't be checked out. Check it out now so we can build it.
-        if !self.git_info(false, &absolute_path).is_managed_git_subrepository()
-            && !helpers::dir_is_empty(&absolute_path)
-        {
-            return;
-        }
-
-        // Submodule updating actually happens during in the dry run mode. We need to make sure that
-        // all the git commands below are actually executed, because some follow-up code
-        // in bootstrap might depend on the submodules being checked out. Furthermore, not all
-        // the command executions below work with an empty output (produced during dry run).
-        // Therefore, all commands below are marked with `run_in_dry_run()`, so that they also run in
-        // dry run mode.
-        let submodule_git = || {
-            let mut cmd = helpers::git(Some(&absolute_path));
-            cmd.run_in_dry_run();
-            cmd
-        };
-
-        // Determine commit checked out in submodule.
-        let checked_out_hash =
-            submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(self).stdout();
-        let checked_out_hash = checked_out_hash.trim_end();
-        // Determine commit that the submodule *should* have.
-        let recorded = helpers::git(Some(&self.src))
-            .run_in_dry_run()
-            .args(["ls-tree", "HEAD"])
-            .arg(relative_path)
-            .run_capture_stdout(self)
-            .stdout();
-
-        let actual_hash = recorded
-            .split_whitespace()
-            .nth(2)
-            .unwrap_or_else(|| panic!("unexpected output `{recorded}`"));
-
-        if actual_hash == checked_out_hash {
-            // already checked out
-            return;
-        }
-
-        println!("Updating submodule {relative_path}");
-
-        helpers::git(Some(&self.src))
-            .allow_failure()
-            .run_in_dry_run()
-            .args(["submodule", "-q", "sync"])
-            .arg(relative_path)
-            .run(self);
-
-        // Try passing `--progress` to start, then run git again without if that fails.
-        let update = |progress: bool| {
-            // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
-            // even though that has no relation to the upstream for the submodule.
-            let current_branch = helpers::git(Some(&self.src))
-                .allow_failure()
-                .run_in_dry_run()
-                .args(["symbolic-ref", "--short", "HEAD"])
-                .run_capture(self);
-
-            let mut git = helpers::git(Some(&self.src)).allow_failure();
-            git.run_in_dry_run();
-            if current_branch.is_success() {
-                // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
-                // This syntax isn't accepted by `branch.{branch}`. Strip it.
-                let branch = current_branch.stdout();
-                let branch = branch.trim();
-                let branch = branch.strip_prefix("heads/").unwrap_or(branch);
-                git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
-            }
-            git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
-            if progress {
-                git.arg("--progress");
-            }
-            git.arg(relative_path);
-            git
-        };
-        if !update(true).allow_failure().run(self) {
-            update(false).allow_failure().run(self);
-        }
-
-        // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
-        // diff-index reports the modifications through the exit status
-        let has_local_modifications =
-            !submodule_git().allow_failure().args(["diff-index", "--quiet", "HEAD"]).run(self);
-        if has_local_modifications {
-            submodule_git().allow_failure().args(["stash", "push"]).run(self);
-        }
-
-        submodule_git().allow_failure().args(["reset", "-q", "--hard"]).run(self);
-        submodule_git().allow_failure().args(["clean", "-qdfx"]).run(self);
-
-        if has_local_modifications {
-            submodule_git().allow_failure().args(["stash", "pop"]).run(self);
-        }
-    }
-
-    /// Returns the commit to download, or `None` if we shouldn't download CI artifacts.
-    pub fn download_ci_rustc_commit(
-        &self,
-        download_rustc: Option<StringOrBool>,
-        debug_assertions_requested: bool,
-        llvm_assertions: bool,
-    ) -> Option<String> {
-        if !is_download_ci_available(&self.host_target.triple, llvm_assertions) {
-            return None;
-        }
-
-        // If `download-rustc` is not set, default to rebuilding.
-        let if_unchanged = match download_rustc {
-            // Globally default `download-rustc` to `false`, because some contributors don't use
-            // profiles for reasons such as:
-            // - They need to seamlessly switch between compiler/library work.
-            // - They don't want to use compiler profile because they need to override too many
-            //   things and it's easier to not use a profile.
-            None | Some(StringOrBool::Bool(false)) => return None,
-            Some(StringOrBool::Bool(true)) => false,
-            Some(StringOrBool::String(s)) if s == "if-unchanged" => {
-                if !self.rust_info.is_managed_git_subrepository() {
-                    println!(
-                        "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources."
-                    );
-                    crate::exit!(1);
-                }
-
-                true
-            }
-            Some(StringOrBool::String(other)) => {
-                panic!("unrecognized option for download-rustc: {other}")
-            }
-        };
-
-        let commit = if self.rust_info.is_managed_git_subrepository() {
-            // Look for a version to compare to based on the current commit.
-            // Only commits merged by bors will have CI artifacts.
-            let freshness = self.check_path_modifications(RUSTC_IF_UNCHANGED_ALLOWED_PATHS);
-            self.verbose(|| {
-                eprintln!("rustc freshness: {freshness:?}");
-            });
-            match freshness {
-                PathFreshness::LastModifiedUpstream { upstream } => upstream,
-                PathFreshness::HasLocalModifications { upstream } => {
-                    if if_unchanged {
-                        return None;
-                    }
-
-                    if self.is_running_on_ci {
-                        eprintln!("CI rustc commit matches with HEAD and we are in CI.");
-                        eprintln!(
-                            "`rustc.download-ci` functionality will be skipped as artifacts are not available."
-                        );
-                        return None;
-                    }
-
-                    upstream
-                }
-                PathFreshness::MissingUpstream => {
-                    eprintln!("No upstream commit found");
-                    return None;
-                }
-            }
-        } else {
-            channel::read_commit_info_file(&self.src)
-                .map(|info| info.sha.trim().to_owned())
-                .expect("git-commit-info is missing in the project root")
-        };
-
-        if debug_assertions_requested {
-            eprintln!(
-                "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \
-                rustc is not currently built with debug assertions."
-            );
-            return None;
-        }
-
-        Some(commit)
-    }
-
-    pub fn parse_download_ci_llvm(
-        &self,
-        download_ci_llvm: Option<StringOrBool>,
-        asserts: bool,
-    ) -> bool {
-        // We don't ever want to use `true` on CI, as we should not
-        // download upstream artifacts if there are any local modifications.
-        let default = if self.is_running_on_ci {
-            StringOrBool::String("if-unchanged".to_string())
-        } else {
-            StringOrBool::Bool(true)
-        };
-        let download_ci_llvm = download_ci_llvm.unwrap_or(default);
-
-        let if_unchanged = || {
-            if self.rust_info.is_from_tarball() {
-                // Git is needed for running "if-unchanged" logic.
-                println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
-                crate::exit!(1);
-            }
-
-            // Fetching the LLVM submodule is unnecessary for self-tests.
-            #[cfg(not(test))]
-            self.update_submodule("src/llvm-project");
-
-            // Check for untracked changes in `src/llvm-project` and other important places.
-            let has_changes = self.has_changes_from_upstream(LLVM_INVALIDATION_PATHS);
-
-            // Return false if there are untracked changes, otherwise check if CI LLVM is available.
-            if has_changes { false } else { llvm::is_ci_llvm_available_for_target(self, asserts) }
-        };
-
-        match download_ci_llvm {
-            StringOrBool::Bool(b) => {
-                if !b && self.download_rustc_commit.is_some() {
-                    panic!(
-                        "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`."
-                    );
-                }
-
-                if b && self.is_running_on_ci {
-                    // On CI, we must always rebuild LLVM if there were any modifications to it
-                    panic!(
-                        "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead."
-                    );
-                }
-
-                // If download-ci-llvm=true we also want to check that CI llvm is available
-                b && llvm::is_ci_llvm_available_for_target(self, asserts)
-            }
-            StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(),
-            StringOrBool::String(other) => {
-                panic!("unrecognized option for download-ci-llvm: {other:?}")
-            }
-        }
+        let dwn_ctx = DownloadContext::from(self);
+        update_submodule(dwn_ctx, relative_path);
     }
 
     /// Returns true if any of the `paths` have been modified locally.
     pub fn has_changes_from_upstream(&self, paths: &[&'static str]) -> bool {
-        match self.check_path_modifications(paths) {
-            PathFreshness::LastModifiedUpstream { .. } => false,
-            PathFreshness::HasLocalModifications { .. } | PathFreshness::MissingUpstream => true,
-        }
+        let dwn_ctx = DownloadContext::from(self);
+        has_changes_from_upstream(dwn_ctx, paths)
     }
 
     /// Checks whether any of the given paths have been modified w.r.t. upstream.
@@ -1949,10 +1715,6 @@ impl Config {
             .clone()
     }
 
-    pub fn ci_env(&self) -> CiEnv {
-        if self.is_running_on_ci { CiEnv::GitHubActions } else { CiEnv::None }
-    }
-
     pub fn sanitizers_enabled(&self, target: TargetSelection) -> bool {
         self.target_config.get(&target).and_then(|t| t.sanitizers).unwrap_or(self.sanitizers)
     }
@@ -2041,15 +1803,8 @@ 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 {
-        match self.target_config.get(&target) {
-            Some(Target { llvm_config: Some(_), .. }) => {
-                let ci_llvm = self.llvm_from_ci && self.is_host_target(target);
-                !ci_llvm
-            }
-            // We're building from the in-tree src/llvm-project sources.
-            Some(Target { llvm_config: None, .. }) => false,
-            None => false,
-        }
+        let dwn_ctx = DownloadContext::from(self);
+        is_system_llvm(dwn_ctx, target)
     }
 
     /// Returns `true` if this is our custom, patched, version of LLVM.
@@ -2340,3 +2095,365 @@ pub fn check_stage0_version(
         ));
     }
 }
+
+pub fn download_ci_rustc_commit<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    download_rustc: Option<StringOrBool>,
+    llvm_assertions: bool,
+) -> Option<String> {
+    let dwn_ctx = dwn_ctx.as_ref();
+
+    if !is_download_ci_available(&dwn_ctx.host_target.triple, llvm_assertions) {
+        return None;
+    }
+
+    // If `download-rustc` is not set, default to rebuilding.
+    let if_unchanged = match download_rustc {
+        // Globally default `download-rustc` to `false`, because some contributors don't use
+        // profiles for reasons such as:
+        // - They need to seamlessly switch between compiler/library work.
+        // - They don't want to use compiler profile because they need to override too many
+        //   things and it's easier to not use a profile.
+        None | Some(StringOrBool::Bool(false)) => return None,
+        Some(StringOrBool::Bool(true)) => false,
+        Some(StringOrBool::String(s)) if s == "if-unchanged" => {
+            if !dwn_ctx.rust_info.is_managed_git_subrepository() {
+                println!(
+                    "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources."
+                );
+                crate::exit!(1);
+            }
+
+            true
+        }
+        Some(StringOrBool::String(other)) => {
+            panic!("unrecognized option for download-rustc: {other}")
+        }
+    };
+
+    let commit = if dwn_ctx.rust_info.is_managed_git_subrepository() {
+        // Look for a version to compare to based on the current commit.
+        // Only commits merged by bors will have CI artifacts.
+        let freshness = check_path_modifications_(dwn_ctx, RUSTC_IF_UNCHANGED_ALLOWED_PATHS);
+        dwn_ctx.exec_ctx.verbose(|| {
+            eprintln!("rustc freshness: {freshness:?}");
+        });
+        match freshness {
+            PathFreshness::LastModifiedUpstream { upstream } => upstream,
+            PathFreshness::HasLocalModifications { upstream } => {
+                if if_unchanged {
+                    return None;
+                }
+
+                if dwn_ctx.is_running_on_ci {
+                    eprintln!("CI rustc commit matches with HEAD and we are in CI.");
+                    eprintln!(
+                        "`rustc.download-ci` functionality will be skipped as artifacts are not available."
+                    );
+                    return None;
+                }
+
+                upstream
+            }
+            PathFreshness::MissingUpstream => {
+                eprintln!("No upstream commit found");
+                return None;
+            }
+        }
+    } else {
+        channel::read_commit_info_file(dwn_ctx.src)
+            .map(|info| info.sha.trim().to_owned())
+            .expect("git-commit-info is missing in the project root")
+    };
+
+    Some(commit)
+}
+
+pub fn check_path_modifications_<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    paths: &[&'static str],
+) -> PathFreshness {
+    let dwn_ctx = dwn_ctx.as_ref();
+    // Checking path modifications through git can be relatively expensive (>100ms).
+    // We do not assume that the sources would change during bootstrap's execution,
+    // so we can cache the results here.
+    // Note that we do not use a static variable for the cache, because it would cause problems
+    // in tests that create separate `Config` instsances.
+    dwn_ctx
+        .path_modification_cache
+        .lock()
+        .unwrap()
+        .entry(paths.to_vec())
+        .or_insert_with(|| {
+            check_path_modifications(
+                dwn_ctx.src,
+                &git_config(dwn_ctx.stage0_metadata),
+                paths,
+                CiEnv::current(),
+            )
+            .unwrap()
+        })
+        .clone()
+}
+
+pub fn git_config(stage0_metadata: &build_helper::stage0_parser::Stage0) -> GitConfig<'_> {
+    GitConfig {
+        nightly_branch: &stage0_metadata.config.nightly_branch,
+        git_merge_commit_email: &stage0_metadata.config.git_merge_commit_email,
+    }
+}
+
+pub fn parse_download_ci_llvm<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    download_ci_llvm: Option<StringOrBool>,
+    asserts: bool,
+) -> bool {
+    let dwn_ctx = dwn_ctx.as_ref();
+
+    // We don't ever want to use `true` on CI, as we should not
+    // download upstream artifacts if there are any local modifications.
+    let default = if dwn_ctx.is_running_on_ci {
+        StringOrBool::String("if-unchanged".to_string())
+    } else {
+        StringOrBool::Bool(true)
+    };
+    let download_ci_llvm = download_ci_llvm.unwrap_or(default);
+
+    let if_unchanged = || {
+        if dwn_ctx.rust_info.is_from_tarball() {
+            // Git is needed for running "if-unchanged" logic.
+            println!("ERROR: 'if-unchanged' is only compatible with Git managed sources.");
+            crate::exit!(1);
+        }
+
+        // Fetching the LLVM submodule is unnecessary for self-tests.
+        #[cfg(not(test))]
+        update_submodule(dwn_ctx, "src/llvm-project");
+
+        // Check for untracked changes in `src/llvm-project` and other important places.
+        let has_changes = has_changes_from_upstream(dwn_ctx, LLVM_INVALIDATION_PATHS);
+
+        // Return false if there are untracked changes, otherwise check if CI LLVM is available.
+        if has_changes {
+            false
+        } else {
+            llvm::is_ci_llvm_available_for_target(&dwn_ctx.host_target, asserts)
+        }
+    };
+
+    match download_ci_llvm {
+        StringOrBool::Bool(b) => {
+            if !b && dwn_ctx.download_rustc_commit.is_some() {
+                panic!(
+                    "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`."
+                );
+            }
+
+            if b && dwn_ctx.is_running_on_ci {
+                // On CI, we must always rebuild LLVM if there were any modifications to it
+                panic!(
+                    "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead."
+                );
+            }
+
+            // If download-ci-llvm=true we also want to check that CI llvm is available
+            b && llvm::is_ci_llvm_available_for_target(&dwn_ctx.host_target, asserts)
+        }
+        StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(),
+        StringOrBool::String(other) => {
+            panic!("unrecognized option for download-ci-llvm: {other:?}")
+        }
+    }
+}
+
+pub fn has_changes_from_upstream<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    paths: &[&'static str],
+) -> bool {
+    let dwn_ctx = dwn_ctx.as_ref();
+    match check_path_modifications_(dwn_ctx, paths) {
+        PathFreshness::LastModifiedUpstream { .. } => false,
+        PathFreshness::HasLocalModifications { .. } | PathFreshness::MissingUpstream => true,
+    }
+}
+
+#[cfg_attr(
+    feature = "tracing",
+    instrument(
+        level = "trace",
+        name = "Config::update_submodule",
+        skip_all,
+        fields(relative_path = ?relative_path),
+    ),
+)]
+pub(crate) fn update_submodule<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>, relative_path: &str) {
+    let dwn_ctx = dwn_ctx.as_ref();
+    if dwn_ctx.rust_info.is_from_tarball() || !submodules_(dwn_ctx.submodules, dwn_ctx.rust_info) {
+        return;
+    }
+
+    let absolute_path = dwn_ctx.src.join(relative_path);
+
+    // NOTE: This check is required because `jj git clone` doesn't create directories for
+    // submodules, they are completely ignored. The code below assumes this directory exists,
+    // so create it here.
+    if !absolute_path.exists() {
+        t!(fs::create_dir_all(&absolute_path));
+    }
+
+    // NOTE: The check for the empty directory is here because when running x.py the first time,
+    // the submodule won't be checked out. Check it out now so we can build it.
+    if !git_info(dwn_ctx.exec_ctx, false, &absolute_path).is_managed_git_subrepository()
+        && !helpers::dir_is_empty(&absolute_path)
+    {
+        return;
+    }
+
+    // Submodule updating actually happens during in the dry run mode. We need to make sure that
+    // all the git commands below are actually executed, because some follow-up code
+    // in bootstrap might depend on the submodules being checked out. Furthermore, not all
+    // the command executions below work with an empty output (produced during dry run).
+    // Therefore, all commands below are marked with `run_in_dry_run()`, so that they also run in
+    // dry run mode.
+    let submodule_git = || {
+        let mut cmd = helpers::git(Some(&absolute_path));
+        cmd.run_in_dry_run();
+        cmd
+    };
+
+    // Determine commit checked out in submodule.
+    let checked_out_hash =
+        submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(dwn_ctx.exec_ctx).stdout();
+    let checked_out_hash = checked_out_hash.trim_end();
+    // Determine commit that the submodule *should* have.
+    let recorded = helpers::git(Some(dwn_ctx.src))
+        .run_in_dry_run()
+        .args(["ls-tree", "HEAD"])
+        .arg(relative_path)
+        .run_capture_stdout(dwn_ctx.exec_ctx)
+        .stdout();
+
+    let actual_hash = recorded
+        .split_whitespace()
+        .nth(2)
+        .unwrap_or_else(|| panic!("unexpected output `{recorded}`"));
+
+    if actual_hash == checked_out_hash {
+        // already checked out
+        return;
+    }
+
+    println!("Updating submodule {relative_path}");
+
+    helpers::git(Some(dwn_ctx.src))
+        .allow_failure()
+        .run_in_dry_run()
+        .args(["submodule", "-q", "sync"])
+        .arg(relative_path)
+        .run(dwn_ctx.exec_ctx);
+
+    // Try passing `--progress` to start, then run git again without if that fails.
+    let update = |progress: bool| {
+        // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository,
+        // even though that has no relation to the upstream for the submodule.
+        let current_branch = helpers::git(Some(dwn_ctx.src))
+            .allow_failure()
+            .run_in_dry_run()
+            .args(["symbolic-ref", "--short", "HEAD"])
+            .run_capture(dwn_ctx.exec_ctx);
+
+        let mut git = helpers::git(Some(dwn_ctx.src)).allow_failure();
+        git.run_in_dry_run();
+        if current_branch.is_success() {
+            // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name.
+            // This syntax isn't accepted by `branch.{branch}`. Strip it.
+            let branch = current_branch.stdout();
+            let branch = branch.trim();
+            let branch = branch.strip_prefix("heads/").unwrap_or(branch);
+            git.arg("-c").arg(format!("branch.{branch}.remote=origin"));
+        }
+        git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]);
+        if progress {
+            git.arg("--progress");
+        }
+        git.arg(relative_path);
+        git
+    };
+    if !update(true).allow_failure().run(dwn_ctx.exec_ctx) {
+        update(false).allow_failure().run(dwn_ctx.exec_ctx);
+    }
+
+    // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
+    // diff-index reports the modifications through the exit status
+    let has_local_modifications = !submodule_git()
+        .allow_failure()
+        .args(["diff-index", "--quiet", "HEAD"])
+        .run(dwn_ctx.exec_ctx);
+    if has_local_modifications {
+        submodule_git().allow_failure().args(["stash", "push"]).run(dwn_ctx.exec_ctx);
+    }
+
+    submodule_git().allow_failure().args(["reset", "-q", "--hard"]).run(dwn_ctx.exec_ctx);
+    submodule_git().allow_failure().args(["clean", "-qdfx"]).run(dwn_ctx.exec_ctx);
+
+    if has_local_modifications {
+        submodule_git().allow_failure().args(["stash", "pop"]).run(dwn_ctx.exec_ctx);
+    }
+}
+
+pub fn git_info(exec_ctx: &ExecutionContext, omit_git_hash: bool, dir: &Path) -> GitInfo {
+    GitInfo::new(omit_git_hash, dir, exec_ctx)
+}
+
+pub fn submodules_(submodules: &Option<bool>, rust_info: &channel::GitInfo) -> bool {
+    // If not specified in config, the default is to only manage
+    // submodules if we're currently inside a git repository.
+    submodules.unwrap_or(rust_info.is_managed_git_subrepository())
+}
+
+/// Returns `true` if this is an external version of LLVM not managed by bootstrap.
+/// 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>>,
+    target: TargetSelection,
+) -> bool {
+    let dwn_ctx = dwn_ctx.as_ref();
+    match dwn_ctx.target_config.get(&target) {
+        Some(Target { llvm_config: Some(_), .. }) => {
+            let ci_llvm = dwn_ctx.llvm_from_ci && is_host_target(&dwn_ctx.host_target, &target);
+            !ci_llvm
+        }
+        // We're building from the in-tree src/llvm-project sources.
+        Some(Target { llvm_config: None, .. }) => false,
+        None => false,
+    }
+}
+
+pub fn is_host_target(host_target: &TargetSelection, target: &TargetSelection) -> bool {
+    host_target == target
+}
+
+pub(crate) fn ci_llvm_root<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>) -> PathBuf {
+    let dwn_ctx = dwn_ctx.as_ref();
+    assert!(dwn_ctx.llvm_from_ci);
+    dwn_ctx.out.join(dwn_ctx.host_target).join("ci-llvm")
+}
+
+/// Returns the content of the given file at a specific commit.
+pub(crate) fn read_file_by_commit<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    file: &Path,
+    commit: &str,
+) -> String {
+    let dwn_ctx = dwn_ctx.as_ref();
+    assert!(
+        dwn_ctx.rust_info.is_managed_git_subrepository(),
+        "`Config::read_file_by_commit` is not supported in non-git sources."
+    );
+
+    let mut git = helpers::git(Some(dwn_ctx.src));
+    git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
+    git.run_capture_stdout(dwn_ctx.exec_ctx).stdout()
+}
diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs
index 7ec6c62a07d..5ded44cef14 100644
--- a/src/bootstrap/src/core/download.rs
+++ b/src/bootstrap/src/core/download.rs
@@ -1,14 +1,17 @@
+use std::collections::HashMap;
 use std::env;
 use std::ffi::OsString;
 use std::fs::{self, File};
 use std::io::{BufRead, BufReader, BufWriter, ErrorKind, Write};
 use std::path::{Path, PathBuf};
-use std::sync::OnceLock;
+use std::sync::{Arc, Mutex, OnceLock};
 
+use build_helper::git::PathFreshness;
 use xz2::bufread::XzDecoder;
 
-use crate::core::config::{BUILDER_CONFIG_FILENAME, TargetSelection};
+use crate::core::config::{BUILDER_CONFIG_FILENAME, Target, TargetSelection};
 use crate::utils::build_stamp::BuildStamp;
+use crate::utils::channel;
 use crate::utils::exec::{ExecutionContext, command};
 use crate::utils::helpers::{exe, hex_encode, move_file};
 use crate::{Config, t};
@@ -398,14 +401,21 @@ impl Config {
 
 /// Only should be used for pre config initialization downloads.
 pub(crate) struct DownloadContext<'a> {
-    host_target: TargetSelection,
-    out: &'a Path,
-    patch_binaries_for_nix: Option<bool>,
-    exec_ctx: &'a ExecutionContext,
-    stage0_metadata: &'a build_helper::stage0_parser::Stage0,
-    llvm_assertions: bool,
-    bootstrap_cache_path: &'a Option<PathBuf>,
-    is_running_on_ci: bool,
+    pub path_modification_cache: Arc<Mutex<HashMap<Vec<&'static str>, PathFreshness>>>,
+    pub src: &'a Path,
+    pub rust_info: &'a channel::GitInfo,
+    pub submodules: &'a Option<bool>,
+    pub download_rustc_commit: &'a Option<String>,
+    pub host_target: TargetSelection,
+    pub llvm_from_ci: bool,
+    pub target_config: &'a HashMap<TargetSelection, Target>,
+    pub out: &'a Path,
+    pub patch_binaries_for_nix: Option<bool>,
+    pub exec_ctx: &'a ExecutionContext,
+    pub stage0_metadata: &'a build_helper::stage0_parser::Stage0,
+    pub llvm_assertions: bool,
+    pub bootstrap_cache_path: &'a Option<PathBuf>,
+    pub is_running_on_ci: bool,
 }
 
 impl<'a> AsRef<DownloadContext<'a>> for DownloadContext<'a> {
@@ -417,7 +427,14 @@ impl<'a> AsRef<DownloadContext<'a>> for DownloadContext<'a> {
 impl<'a> From<&'a Config> for DownloadContext<'a> {
     fn from(value: &'a Config) -> Self {
         DownloadContext {
+            path_modification_cache: value.path_modification_cache.clone(),
+            src: &value.src,
             host_target: value.host_target,
+            rust_info: &value.rust_info,
+            download_rustc_commit: &value.download_rustc_commit,
+            submodules: &value.submodules,
+            llvm_from_ci: value.llvm_from_ci,
+            target_config: &value.target_config,
             out: &value.out,
             patch_binaries_for_nix: value.patch_binaries_for_nix,
             exec_ctx: &value.exec_ctx,
diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs
index d3331b81587..091956e7e5f 100644
--- a/src/bootstrap/src/utils/change_tracker.rs
+++ b/src/bootstrap/src/utils/change_tracker.rs
@@ -491,4 +491,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
         severity: ChangeSeverity::Warning,
         summary: "Added `build.compiletest-allow-stage0` flag instead of `COMPILETEST_FORCE_STAGE0` env var, and reject running `compiletest` self tests against stage 0 rustc unless explicitly allowed.",
     },
+    ChangeInfo {
+        change_id: 145011,
+        severity: ChangeSeverity::Warning,
+        summary: "It is no longer possible to `x doc` with stage 0. All doc commands have to be on stage 1+.",
+    },
 ];
diff --git a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
index f82e19bcbb4..1a219125593 100644
--- a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
+++ b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
@@ -31,13 +31,13 @@ ENV SCRIPT \
         python3 ../x.py clippy ci && \
         python3 ../x.py test --stage 1 core alloc std test proc_macro && \
         python3 ../x.py test --stage 1 src/tools/compiletest && \
-        python3 ../x.py doc --stage 0 bootstrap && \
+        python3 ../x.py doc bootstrap && \
         # Build both public and internal documentation.
-        RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 0 compiler && \
-        RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library && \
+        RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc compiler --stage 2 && \
+        RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc library --stage 2 && \
         mkdir -p /checkout/obj/staging/doc && \
         cp -r build/x86_64-unknown-linux-gnu/doc /checkout/obj/staging && \
-        RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library/test && \
+        RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc library/test --stage 2 && \
         # The BOOTSTRAP_TRACING flag is added to verify whether the
         # bootstrap process compiles successfully with this flag enabled.
         BOOTSTRAP_TRACING=1 python3 ../x.py --help
diff --git a/src/ci/run.sh b/src/ci/run.sh
index f58a067041d..c9d81f1ff51 100755
--- a/src/ci/run.sh
+++ b/src/ci/run.sh
@@ -205,6 +205,9 @@ if [ "$ENABLE_GCC_CODEGEN" = "1" ]; then
   RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-new-symbol-mangling"
 fi
 
+# If bootstrap fails, we want to see its backtrace
+export RUST_BACKTRACE=1
+
 # Print the date from the local machine and the date from an external source to
 # check for clock drifts. An HTTP URL is used instead of HTTPS since on Azure
 # Pipelines it happened that the certificates were marked as expired.
diff --git a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs
index 9c6141d8222..7317c62df7f 100644
--- a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs
@@ -1,6 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::msrvs::Msrv;
-use clippy_utils::qualify_min_const_fn::is_stable_const_fn;
 use clippy_utils::source::SpanRangeExt;
 use clippy_utils::ty::implements_trait;
 use clippy_utils::visitors::for_each_expr_without_closures;
@@ -21,7 +20,7 @@ pub(super) fn check<'tcx>(
     expr: &'tcx hir::Expr<'_>,
     assignee: &'tcx hir::Expr<'_>,
     e: &'tcx hir::Expr<'_>,
-    msrv: Msrv,
+    _msrv: Msrv,
 ) {
     if let hir::ExprKind::Binary(op, l, r) = &e.kind {
         let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
@@ -45,10 +44,8 @@ pub(super) fn check<'tcx>(
                 }
 
                 // Skip if the trait is not stable in const contexts
-                if is_in_const_context(cx)
-                    && let Some(binop_id) = cx.tcx.associated_item_def_ids(trait_id).first()
-                    && !is_stable_const_fn(cx, *binop_id, msrv)
-                {
+                // FIXME: reintroduce a better check after this is merged back into Clippy
+                if is_in_const_context(cx) {
                     return;
                 }