about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-08-15 15:44:03 +0000
committerbors <bors@rust-lang.org>2025-08-15 15:44:03 +0000
commit8b1889cc22439e02cf03cd8cff9764a307b21193 (patch)
tree1f3e2701b5207d409be0c0b7b8415ba1c66c394b
parentc018ae5389c49cc4bcb8343d80dd8e7323325410 (diff)
parent9bf76aba76ecdc3c1524e005e13d8319d5ae4f7b (diff)
downloadrust-8b1889cc22439e02cf03cd8cff9764a307b21193.tar.gz
rust-8b1889cc22439e02cf03cd8cff9764a307b21193.zip
Auto merge of #145450 - Kobzol:rollup-cqclix0, r=Kobzol
Rollup of 11 pull requests

Successful merges:

 - rust-lang/rust#144210 (std: thread: Return error if setting thread stack size fails)
 - rust-lang/rust#145310 (Reduce usage of `compiler_for` in bootstrap)
 - rust-lang/rust#145311 (ci: clean windows disk space in background)
 - rust-lang/rust#145340 (Split codegen backend check step into two and don't run it with `x check compiler`)
 - rust-lang/rust#145408 (Deduplicate -L search paths)
 - rust-lang/rust#145412 (Windows: Replace `GetThreadId`+`GetCurrentThread` with `GetCurrentThreadId`)
 - rust-lang/rust#145413 (bootstrap: Reduce dependencies)
 - rust-lang/rust#145426 (Fix typos in bootstrap.example.toml)
 - rust-lang/rust#145430 (Fix wrong spans with external macros in the `dropping_copy_types` lint)
 - rust-lang/rust#145431 (Enhance UI test output handling for runtime errors)
 - rust-lang/rust#145448 (Autolabel `src/tools/{rustfmt,rust-analyzer}` changes with `T-{rustfmt,rust-analyzer}`)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--.github/workflows/ci.yml5
-rw-r--r--bootstrap.example.toml10
-rw-r--r--compiler/rustc_lint/src/drop_forget_useless.rs2
-rw-r--r--compiler/rustc_session/src/config.rs31
-rw-r--r--library/std/src/sys/pal/unix/thread.rs13
-rw-r--r--library/std/src/sys/pal/windows/c/bindings.txt2
-rw-r--r--library/std/src/sys/pal/windows/c/windows_sys.rs2
-rw-r--r--library/std/src/sys/pal/windows/thread.rs2
-rw-r--r--src/bootstrap/Cargo.lock27
-rw-r--r--src/bootstrap/Cargo.toml3
-rw-r--r--src/bootstrap/src/bin/main.rs46
-rw-r--r--src/bootstrap/src/core/build_steps/check.rs109
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs173
-rw-r--r--src/bootstrap/src/core/builder/mod.rs3
-rw-r--r--src/bootstrap/src/core/builder/tests.rs149
-rw-r--r--src/ci/scripts/free-disk-space-windows-start.py72
-rw-r--r--src/ci/scripts/free-disk-space-windows-wait.py77
-rwxr-xr-xsrc/ci/scripts/free-disk-space.sh2
-rw-r--r--src/ci/scripts/free_disk_space_windows_util.py29
-rw-r--r--src/librustdoc/doctest.rs10
-rw-r--r--src/tools/compiletest/src/runtest/ui.rs18
-rw-r--r--tests/ui/lint/dropping_copy_types-macros.fixed3
-rw-r--r--tests/ui/lint/dropping_copy_types-macros.rs3
-rw-r--r--tests/ui/lint/dropping_copy_types-macros.stderr16
-rw-r--r--triagebot.toml10
25 files changed, 478 insertions, 339 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index df5dda76eb7..8266c03eaa7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -223,6 +223,11 @@ jobs:
           cd src/ci/citool
           CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=../../../build/citool cargo build
 
+      - name: wait for Windows disk cleanup to finish
+        if: ${{ matrix.free_disk && startsWith(matrix.os, 'windows-') }}
+        run: |
+          python3 src/ci/scripts/free-disk-space-windows-wait.py
+
       - name: run the build
         run: |
           set +e
diff --git a/bootstrap.example.toml b/bootstrap.example.toml
index 89da6eeb531..4c18d5f8675 100644
--- a/bootstrap.example.toml
+++ b/bootstrap.example.toml
@@ -9,7 +9,7 @@
 # a custom configuration file can also be specified with `--config` to the build
 # system.
 #
-# Note that the following are equivelent, for more details see <https://toml.io/en/v1.0.0>.
+# Note that the following are equivalent, for more details see <https://toml.io/en/v1.0.0>.
 #
 #     build.verbose = 1
 #
@@ -345,9 +345,9 @@
 # want to use vendoring. See https://forge.rust-lang.org/infra/other-installation-methods.html#source-code.
 #build.vendor = if "is a tarball source" && "vendor" dir exists && ".cargo/config.toml" file exists { true } else { false }
 
-# Typically the build system will build the Rust compiler twice. The second
-# compiler, however, will simply use its own libraries to link against. If you
-# would rather to perform a full bootstrap, compiling the compiler three times,
+# If you build the compiler more than twice (stage3+) or the standard library more than once
+# (stage 2+), the third compiler and second library will get uplifted from stage2 and stage1,
+# respectively. If you would like to disable this uplifting, and rather perform a full bootstrap,
 # then you can set this option to true.
 #
 # This is only useful for verifying that rustc generates reproducible builds.
@@ -482,7 +482,7 @@
 # Use `--extra-checks=''` to temporarily disable all extra checks.
 #
 # Automatically enabled in the "tools" profile.
-# Set to the empty string to force disable (recommeded for hdd systems).
+# Set to the empty string to force disable (recommended for hdd systems).
 #build.tidy-extra-checks = ""
 
 # Indicates whether ccache is used when building certain artifacts (e.g. LLVM).
diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs
index 7f098893f7d..c2d137986ce 100644
--- a/compiler/rustc_lint/src/drop_forget_useless.rs
+++ b/compiler/rustc_lint/src/drop_forget_useless.rs
@@ -151,7 +151,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless {
                     && let Node::Stmt(stmt) = node
                     && let StmtKind::Semi(e) = stmt.kind
                     && e.hir_id == expr.hir_id
-                    && let Some(arg_span) = arg.span.find_ancestor_inside(expr.span)
+                    && let Some(arg_span) = arg.span.find_ancestor_inside_same_ctxt(expr.span)
                 {
                     UseLetUnderscoreIgnoreSuggestion::Suggestion {
                         start_span: expr.span.shrink_to_lo().until(arg_span),
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 57c797fa153..9793d8091e2 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -2847,16 +2847,27 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
         // This is the location used by the `rustc-dev` `rustup` component.
         real_source_base_dir("lib/rustlib/rustc-src/rust", "compiler/rustc/src/main.rs");
 
-    let mut search_paths = vec![];
-    for s in &matches.opt_strs("L") {
-        search_paths.push(SearchPath::from_cli_opt(
-            sysroot.path(),
-            &target_triple,
-            early_dcx,
-            s,
-            unstable_opts.unstable_options,
-        ));
-    }
+    // We eagerly scan all files in each passed -L path. If the same directory is passed multiple
+    // times, and the directory contains a lot of files, this can take a lot of time.
+    // So we remove -L paths that were passed multiple times, and keep only the first occurrence.
+    // We still have to keep the original order of the -L arguments.
+    let search_paths: Vec<SearchPath> = {
+        let mut seen_search_paths = FxHashSet::default();
+        let search_path_matches: Vec<String> = matches.opt_strs("L");
+        search_path_matches
+            .iter()
+            .filter(|p| seen_search_paths.insert(*p))
+            .map(|path| {
+                SearchPath::from_cli_opt(
+                    sysroot.path(),
+                    &target_triple,
+                    early_dcx,
+                    &path,
+                    unstable_opts.unstable_options,
+                )
+            })
+            .collect()
+    };
 
     let working_dir = std::env::current_dir().unwrap_or_else(|e| {
         early_dcx.early_fatal(format!("Current directory is invalid: {e}"));
diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs
index 36e53e7cadc..24b65c11fd2 100644
--- a/library/std/src/sys/pal/unix/thread.rs
+++ b/library/std/src/sys/pal/unix/thread.rs
@@ -77,7 +77,18 @@ impl Thread {
                     let page_size = os::page_size();
                     let stack_size =
                         (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1);
-                    assert_eq!(libc::pthread_attr_setstacksize(attr.as_mut_ptr(), stack_size), 0);
+
+                    // Some libc implementations, e.g. musl, place an upper bound
+                    // on the stack size, in which case we can only gracefully return
+                    // an error here.
+                    if libc::pthread_attr_setstacksize(attr.as_mut_ptr(), stack_size) != 0 {
+                        assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
+                        drop(Box::from_raw(data));
+                        return Err(io::const_error!(
+                            io::ErrorKind::InvalidInput,
+                            "invalid stack size"
+                        ));
+                    }
                 }
             };
         }
diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt
index c8e4dca4781..abc1c19827f 100644
--- a/library/std/src/sys/pal/windows/c/bindings.txt
+++ b/library/std/src/sys/pal/windows/c/bindings.txt
@@ -2158,6 +2158,7 @@ GetCurrentDirectoryW
 GetCurrentProcess
 GetCurrentProcessId
 GetCurrentThread
+GetCurrentThreadId
 GetEnvironmentStringsW
 GetEnvironmentVariableW
 GetExitCodeProcess
@@ -2185,7 +2186,6 @@ GetSystemInfo
 GetSystemTimeAsFileTime
 GetSystemTimePreciseAsFileTime
 GetTempPathW
-GetThreadId
 GetUserProfileDirectoryW
 GetWindowsDirectoryW
 HANDLE
diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs
index 45a273d241a..989a1246650 100644
--- a/library/std/src/sys/pal/windows/c/windows_sys.rs
+++ b/library/std/src/sys/pal/windows/c/windows_sys.rs
@@ -38,6 +38,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetCurrentDirectoryW(nbufferle
 windows_targets::link!("kernel32.dll" "system" fn GetCurrentProcess() -> HANDLE);
 windows_targets::link!("kernel32.dll" "system" fn GetCurrentProcessId() -> u32);
 windows_targets::link!("kernel32.dll" "system" fn GetCurrentThread() -> HANDLE);
+windows_targets::link!("kernel32.dll" "system" fn GetCurrentThreadId() -> u32);
 windows_targets::link!("kernel32.dll" "system" fn GetEnvironmentStringsW() -> PWSTR);
 windows_targets::link!("kernel32.dll" "system" fn GetEnvironmentVariableW(lpname : PCWSTR, lpbuffer : PWSTR, nsize : u32) -> u32);
 windows_targets::link!("kernel32.dll" "system" fn GetExitCodeProcess(hprocess : HANDLE, lpexitcode : *mut u32) -> BOOL);
@@ -61,7 +62,6 @@ windows_targets::link!("kernel32.dll" "system" fn GetSystemInfo(lpsysteminfo : *
 windows_targets::link!("kernel32.dll" "system" fn GetSystemTimeAsFileTime(lpsystemtimeasfiletime : *mut FILETIME));
 windows_targets::link!("kernel32.dll" "system" fn GetSystemTimePreciseAsFileTime(lpsystemtimeasfiletime : *mut FILETIME));
 windows_targets::link!("kernel32.dll" "system" fn GetTempPathW(nbufferlength : u32, lpbuffer : PWSTR) -> u32);
-windows_targets::link!("kernel32.dll" "system" fn GetThreadId(thread : HANDLE) -> u32);
 windows_targets::link!("userenv.dll" "system" fn GetUserProfileDirectoryW(htoken : HANDLE, lpprofiledir : PWSTR, lpcchsize : *mut u32) -> BOOL);
 windows_targets::link!("kernel32.dll" "system" fn GetWindowsDirectoryW(lpbuffer : PWSTR, usize : u32) -> u32);
 windows_targets::link!("kernel32.dll" "system" fn InitOnceBeginInitialize(lpinitonce : *mut INIT_ONCE, dwflags : u32, fpending : *mut BOOL, lpcontext : *mut *mut core::ffi::c_void) -> BOOL);
diff --git a/library/std/src/sys/pal/windows/thread.rs b/library/std/src/sys/pal/windows/thread.rs
index c708da5af12..b0e38220a2d 100644
--- a/library/std/src/sys/pal/windows/thread.rs
+++ b/library/std/src/sys/pal/windows/thread.rs
@@ -129,7 +129,7 @@ impl Thread {
 
 pub(crate) fn current_os_id() -> Option<u64> {
     // SAFETY: FFI call with no preconditions.
-    let id: u32 = unsafe { c::GetThreadId(c::GetCurrentThread()) };
+    let id: u32 = unsafe { c::GetCurrentThreadId() };
 
     // A return value of 0 indicates failed lookup.
     if id == 0 { None } else { Some(id.into()) }
diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock
index 537f4b6184f..be29e77e572 100644
--- a/src/bootstrap/Cargo.lock
+++ b/src/bootstrap/Cargo.lock
@@ -48,7 +48,6 @@ dependencies = [
  "clap",
  "clap_complete",
  "cmake",
- "fd-lock",
  "home",
  "ignore",
  "insta",
@@ -269,17 +268,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
 [[package]]
-name = "fd-lock"
-version = "4.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
-dependencies = [
- "cfg-if",
- "rustix",
- "windows-sys 0.59.0",
-]
-
-[[package]]
 name = "filetime"
 version = "0.2.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -759,13 +747,12 @@ dependencies = [
 
 [[package]]
 name = "tar"
-version = "0.4.43"
+version = "0.4.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6"
+checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
 dependencies = [
  "filetime",
  "libc",
- "xattr",
 ]
 
 [[package]]
@@ -1148,16 +1135,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "xattr"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
-dependencies = [
- "libc",
- "rustix",
-]
-
-[[package]]
 name = "xz2"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml
index bdf0b42255e..cd5a60187e5 100644
--- a/src/bootstrap/Cargo.toml
+++ b/src/bootstrap/Cargo.toml
@@ -38,7 +38,6 @@ cmake = "=0.1.54"
 build_helper = { path = "../build_helper" }
 clap = { version = "4.4", default-features = false, features = ["std", "usage", "help", "derive", "error-context"] }
 clap_complete = "4.4"
-fd-lock = "4.0"
 home = "0.5"
 ignore = "0.4"
 libc = "0.2"
@@ -51,7 +50,7 @@ serde = "1.0"
 serde_derive = "1.0"
 serde_json = "1.0"
 sha2 = "0.10"
-tar = "0.4"
+tar = { version = "0.4.44", default-features = false }
 termcolor = "1.4"
 toml = "0.5"
 walkdir = "2.4"
diff --git a/src/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs
index 8b2d67266a7..93c7faf4f01 100644
--- a/src/bootstrap/src/bin/main.rs
+++ b/src/bootstrap/src/bin/main.rs
@@ -5,8 +5,8 @@
 //! parent directory, and otherwise documentation can be found throughout the `build`
 //! directory in each respective module.
 
-use std::fs::{self, OpenOptions};
-use std::io::{self, BufRead, BufReader, IsTerminal, Write};
+use std::fs::{self, OpenOptions, TryLockError};
+use std::io::{self, BufRead, BufReader, IsTerminal, Read, Write};
 use std::path::Path;
 use std::str::FromStr;
 use std::time::Instant;
@@ -39,38 +39,34 @@ fn main() {
     let config = Config::parse(flags);
 
     let mut build_lock;
-    let _build_lock_guard;
 
     if !config.bypass_bootstrap_lock {
         // Display PID of process holding the lock
         // PID will be stored in a lock file
         let lock_path = config.out.join("lock");
-        let pid = fs::read_to_string(&lock_path);
-
-        build_lock = fd_lock::RwLock::new(t!(fs::OpenOptions::new()
+        build_lock = t!(fs::OpenOptions::new()
+            .read(true)
             .write(true)
-            .truncate(true)
             .create(true)
-            .open(&lock_path)));
-        _build_lock_guard = match build_lock.try_write() {
-            Ok(mut lock) => {
-                t!(lock.write(process::id().to_string().as_ref()));
-                lock
+            .truncate(false)
+            .open(&lock_path));
+        t!(build_lock.try_lock().or_else(|e| {
+            if let TryLockError::Error(e) = e {
+                return Err(e);
             }
-            err => {
-                drop(err);
-                // #135972: We can reach this point when the lock has been taken,
-                // but the locker has not yet written its PID to the file
-                if let Some(pid) = pid.ok().filter(|pid| !pid.is_empty()) {
-                    println!("WARNING: build directory locked by process {pid}, waiting for lock");
-                } else {
-                    println!("WARNING: build directory locked, waiting for lock");
-                }
-                let mut lock = t!(build_lock.write());
-                t!(lock.write(process::id().to_string().as_ref()));
-                lock
+            let mut pid = String::new();
+            t!(build_lock.read_to_string(&mut pid));
+            // #135972: We can reach this point when the lock has been taken,
+            // but the locker has not yet written its PID to the file
+            if !pid.is_empty() {
+                println!("WARNING: build directory locked by process {pid}, waiting for lock");
+            } else {
+                println!("WARNING: build directory locked, waiting for lock");
             }
-        };
+            build_lock.lock()
+        }));
+        t!(build_lock.set_len(0));
+        t!(build_lock.write_all(process::id().to_string().as_bytes()));
     }
 
     // check_version warnings are not printed during setup, or during CI
diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs
index 1e08e8547dc..4a110b733e1 100644
--- a/src/bootstrap/src/core/build_steps/check.rs
+++ b/src/bootstrap/src/core/build_steps/check.rs
@@ -314,41 +314,31 @@ pub fn prepare_compiler_for_check(
     }
 }
 
-/// Checks a single codegen backend.
+/// Check the Cranelift codegen backend.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct CodegenBackend {
-    pub build_compiler: Compiler,
-    pub target: TargetSelection,
-    pub backend: CodegenBackendKind,
+pub struct CraneliftCodegenBackend {
+    build_compiler: Compiler,
+    target: TargetSelection,
 }
 
-impl Step for CodegenBackend {
+impl Step for CraneliftCodegenBackend {
     type Output = ();
+
     const IS_HOST: bool = true;
     const DEFAULT: bool = true;
 
     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
-        run.paths(&["compiler/rustc_codegen_cranelift", "compiler/rustc_codegen_gcc"])
+        run.alias("rustc_codegen_cranelift").alias("cg_clif")
     }
 
     fn make_run(run: RunConfig<'_>) {
-        // FIXME: only check the backend(s) that were actually selected in run.paths
         let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::Codegen);
-        for backend in [CodegenBackendKind::Cranelift, CodegenBackendKind::Gcc] {
-            run.builder.ensure(CodegenBackend { build_compiler, target: run.target, backend });
-        }
+        run.builder.ensure(CraneliftCodegenBackend { build_compiler, target: run.target });
     }
 
     fn run(self, builder: &Builder<'_>) {
-        // FIXME: remove once https://github.com/rust-lang/rust/issues/112393 is resolved
-        if builder.build.config.vendor && self.backend.is_gcc() {
-            println!("Skipping checking of `rustc_codegen_gcc` with vendoring enabled.");
-            return;
-        }
-
         let build_compiler = self.build_compiler;
         let target = self.target;
-        let backend = self.backend;
 
         let mut cargo = builder::Cargo::new(
             builder,
@@ -361,31 +351,104 @@ impl Step for CodegenBackend {
 
         cargo
             .arg("--manifest-path")
-            .arg(builder.src.join(format!("compiler/{}/Cargo.toml", backend.crate_name())));
+            .arg(builder.src.join("compiler/rustc_codegen_cranelift/Cargo.toml"));
         rustc_cargo_env(builder, &mut cargo, target);
 
         let _guard = builder.msg(
             Kind::Check,
-            backend.crate_name(),
+            "rustc_codegen_cranelift",
             Mode::Codegen,
             self.build_compiler,
             target,
         );
 
-        let stamp = build_stamp::codegen_backend_stamp(builder, build_compiler, target, &backend)
-            .with_prefix("check");
+        let stamp = build_stamp::codegen_backend_stamp(
+            builder,
+            build_compiler,
+            target,
+            &CodegenBackendKind::Cranelift,
+        )
+        .with_prefix("check");
 
         run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
         Some(
-            StepMetadata::check(&self.backend.crate_name(), self.target)
+            StepMetadata::check("rustc_codegen_cranelift", self.target)
                 .built_by(self.build_compiler),
         )
     }
 }
 
+/// Check the GCC codegen backend.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct GccCodegenBackend {
+    build_compiler: Compiler,
+    target: TargetSelection,
+}
+
+impl Step for GccCodegenBackend {
+    type Output = ();
+
+    const IS_HOST: bool = true;
+    const DEFAULT: bool = true;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.alias("rustc_codegen_gcc").alias("cg_gcc")
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::Codegen);
+        run.builder.ensure(GccCodegenBackend { build_compiler, target: run.target });
+    }
+
+    fn run(self, builder: &Builder<'_>) {
+        // FIXME: remove once https://github.com/rust-lang/rust/issues/112393 is resolved
+        if builder.build.config.vendor {
+            println!("Skipping checking of `rustc_codegen_gcc` with vendoring enabled.");
+            return;
+        }
+
+        let build_compiler = self.build_compiler;
+        let target = self.target;
+
+        let mut cargo = builder::Cargo::new(
+            builder,
+            build_compiler,
+            Mode::Codegen,
+            SourceType::InTree,
+            target,
+            builder.kind,
+        );
+
+        cargo.arg("--manifest-path").arg(builder.src.join("compiler/rustc_codegen_gcc/Cargo.toml"));
+        rustc_cargo_env(builder, &mut cargo, target);
+
+        let _guard = builder.msg(
+            Kind::Check,
+            "rustc_codegen_gcc",
+            Mode::Codegen,
+            self.build_compiler,
+            target,
+        );
+
+        let stamp = build_stamp::codegen_backend_stamp(
+            builder,
+            build_compiler,
+            target,
+            &CodegenBackendKind::Gcc,
+        )
+        .with_prefix("check");
+
+        run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
+    }
+
+    fn metadata(&self) -> Option<StepMetadata> {
+        Some(StepMetadata::check("rustc_codegen_gcc", self.target).built_by(self.build_compiler))
+    }
+}
+
 macro_rules! tool_check_step {
     (
         $name:ident {
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index d860cafa1c0..da828937861 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -37,11 +37,12 @@ use crate::{
     debug, trace,
 };
 
-/// Build a standard library for the given `target` using the given `compiler`.
+/// Build a standard library for the given `target` using the given `build_compiler`.
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Std {
     pub target: TargetSelection,
-    pub compiler: Compiler,
+    /// Compiler that builds the standard library.
+    pub build_compiler: Compiler,
     /// Whether to build only a subset of crates in the standard library.
     ///
     /// This shouldn't be used from other steps; see the comment on [`Rustc`].
@@ -54,10 +55,10 @@ pub struct Std {
 }
 
 impl Std {
-    pub fn new(compiler: Compiler, target: TargetSelection) -> Self {
+    pub fn new(build_compiler: Compiler, target: TargetSelection) -> Self {
         Self {
             target,
-            compiler,
+            build_compiler,
             crates: Default::default(),
             force_recompile: false,
             extra_rust_args: &[],
@@ -120,7 +121,7 @@ impl Step for Std {
         trace!(force_recompile);
 
         run.builder.ensure(Std {
-            compiler: run.builder.compiler(run.builder.top_stage, run.build_triple()),
+            build_compiler: run.builder.compiler(run.builder.top_stage, run.build_triple()),
             target: run.target,
             crates,
             force_recompile,
@@ -138,8 +139,8 @@ impl Step for Std {
         let target = self.target;
 
         // We already have std ready to be used for stage 0.
-        if self.compiler.stage == 0 {
-            let compiler = self.compiler;
+        if self.build_compiler.stage == 0 {
+            let compiler = self.build_compiler;
             builder.ensure(StdLink::from_std(self, compiler));
 
             return;
@@ -148,9 +149,10 @@ impl Step for Std {
         let build_compiler = if builder.download_rustc() && self.force_recompile {
             // When there are changes in the library tree with CI-rustc, we want to build
             // the stageN library and that requires using stageN-1 compiler.
-            builder.compiler(self.compiler.stage.saturating_sub(1), builder.config.host_target)
+            builder
+                .compiler(self.build_compiler.stage.saturating_sub(1), builder.config.host_target)
         } else {
-            self.compiler
+            self.build_compiler
         };
 
         // When using `download-rustc`, we already have artifacts for the host available. Don't
@@ -187,51 +189,50 @@ impl Step for Std {
 
         let mut target_deps = builder.ensure(StartupObjects { compiler: build_compiler, target });
 
-        let compiler_to_use =
-            builder.compiler_for(build_compiler.stage, build_compiler.host, target);
-        trace!(?compiler_to_use);
-
-        if compiler_to_use != build_compiler
-            // Never uplift std unless we have compiled stage 1; if stage 1 is compiled,
-            // uplift it from there.
-            //
-            // FIXME: improve `fn compiler_for` to avoid adding stage condition here.
-            && build_compiler.stage > 1
+        // Stage of the stdlib that we're building
+        let stage = build_compiler.stage;
+
+        // If we're building a stage2+ libstd, full bootstrap is
+        // disabled and we have a stage1 libstd already compiled for the given target,
+        // then simply uplift a previously built stage1 library.
+        if build_compiler.stage > 1
+            && !builder.config.full_bootstrap
+            // This estimates if a stage1 libstd exists for the given target. If we're not
+            // cross-compiling, it should definitely exist by the time we're building a stage2
+            // libstd.
+            // Or if we are cross-compiling, and we are building a cross-compiled rustc, then that
+            // rustc needs to link to a cross-compiled libstd, so again we should have a stage1
+            // libstd for the given target prepared.
+            // Even if we guess wrong in the cross-compiled case, the worst that should happen is
+            // that we build a fresh stage1 libstd below, and then we immediately uplift it, so we
+            // don't pay the libstd build cost twice.
+            && (target == builder.host_target || builder.config.hosts.contains(&target))
         {
-            trace!(
-                ?compiler_to_use,
-                ?build_compiler,
-                "build_compiler != compiler_to_use, uplifting library"
-            );
+            let build_compiler_for_std_to_uplift = builder.compiler(1, builder.host_target);
+            builder.std(build_compiler_for_std_to_uplift, target);
 
-            builder.std(compiler_to_use, target);
-            let msg = if compiler_to_use.host == target {
+            let msg = if build_compiler_for_std_to_uplift.host == target {
                 format!(
-                    "Uplifting library (stage{} -> stage{})",
-                    compiler_to_use.stage, build_compiler.stage
+                    "Uplifting library (stage{} -> stage{stage})",
+                    build_compiler_for_std_to_uplift.stage
                 )
             } else {
                 format!(
-                    "Uplifting library (stage{}:{} -> stage{}:{})",
-                    compiler_to_use.stage, compiler_to_use.host, build_compiler.stage, target
+                    "Uplifting library (stage{}:{} -> stage{stage}:{target})",
+                    build_compiler_for_std_to_uplift.stage, build_compiler_for_std_to_uplift.host,
                 )
             };
+
             builder.info(&msg);
 
             // Even if we're not building std this stage, the new sysroot must
             // still contain the third party objects needed by various targets.
             self.copy_extra_objects(builder, &build_compiler, target);
 
-            builder.ensure(StdLink::from_std(self, compiler_to_use));
+            builder.ensure(StdLink::from_std(self, build_compiler_for_std_to_uplift));
             return;
         }
 
-        trace!(
-            ?compiler_to_use,
-            ?build_compiler,
-            "compiler == compiler_to_use, handling not-cross-compile scenario"
-        );
-
         target_deps.extend(self.copy_extra_objects(builder, &build_compiler, target));
 
         // We build a sysroot for mir-opt tests using the same trick that Miri does: A check build
@@ -299,7 +300,7 @@ impl Step for Std {
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
-        Some(StepMetadata::build("std", self.target).built_by(self.compiler))
+        Some(StepMetadata::build("std", self.target).built_by(self.build_compiler))
     }
 }
 
@@ -665,6 +666,14 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, cargo: &mut Car
     cargo.rustdocflag("-Zcrate-attr=warn(rust_2018_idioms)");
 }
 
+/// Link all libstd rlibs/dylibs into a sysroot of `target_compiler`.
+///
+/// Links those artifacts generated by `compiler` to the `stage` compiler's
+/// sysroot for the specified `host` and `target`.
+///
+/// Note that this assumes that `compiler` has already generated the libstd
+/// libraries for `target`, and this method will find them in the relevant
+/// output directory.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct StdLink {
     pub compiler: Compiler,
@@ -680,7 +689,7 @@ impl StdLink {
     pub fn from_std(std: Std, host_compiler: Compiler) -> Self {
         Self {
             compiler: host_compiler,
-            target_compiler: std.compiler,
+            target_compiler: std.build_compiler,
             target: std.target,
             crates: std.crates,
             force_recompile: std.force_recompile,
@@ -951,14 +960,8 @@ impl Rustc {
 }
 
 impl Step for Rustc {
-    /// We return the stage of the "actual" compiler (not the uplifted one).
-    ///
-    /// By "actual" we refer to the uplifting logic where we may not compile the requested stage;
-    /// instead, we uplift it from the previous stages. Which can lead to bootstrap failures in
-    /// specific situations where we request stage X from other steps. However we may end up
-    /// uplifting it from stage Y, causing the other stage to fail when attempting to link with
-    /// stage X which was never actually built.
-    type Output = u32;
+    type Output = ();
+
     const IS_HOST: bool = true;
     const DEFAULT: bool = false;
 
@@ -997,7 +1000,7 @@ impl Step for Rustc {
     /// This will build the compiler for a particular stage of the build using
     /// the `build_compiler` targeting the `target` architecture. The artifacts
     /// created will also be linked into the sysroot directory.
-    fn run(self, builder: &Builder<'_>) -> u32 {
+    fn run(self, builder: &Builder<'_>) {
         let build_compiler = self.build_compiler;
         let target = self.target;
 
@@ -1013,7 +1016,7 @@ impl Step for Rustc {
                 &sysroot,
                 builder.config.ci_rustc_dev_contents(),
             );
-            return build_compiler.stage;
+            return;
         }
 
         // Build a standard library for `target` using the `build_compiler`.
@@ -1027,31 +1030,33 @@ impl Step for Rustc {
             builder.info("WARNING: Use `--keep-stage-std` if you want to rebuild the compiler when it changes");
             builder.ensure(RustcLink::from_rustc(self, build_compiler));
 
-            return build_compiler.stage;
+            return;
         }
 
-        let compiler_to_use =
-            builder.compiler_for(build_compiler.stage, build_compiler.host, target);
-        if compiler_to_use != build_compiler {
-            builder.ensure(Rustc::new(compiler_to_use, target));
-            let msg = if compiler_to_use.host == target {
-                format!(
-                    "Uplifting rustc (stage{} -> stage{})",
-                    compiler_to_use.stage,
-                    build_compiler.stage + 1
-                )
+        // The stage of the compiler that we're building
+        let stage = build_compiler.stage + 1;
+
+        // If we are building a stage3+ compiler, and full bootstrap is disabled, and we have a
+        // previous rustc available, we will uplift a compiler from a previous stage.
+        if build_compiler.stage >= 2
+            && !builder.config.full_bootstrap
+            && (target == builder.host_target || builder.hosts.contains(&target))
+        {
+            // If we're cross-compiling, the earliest rustc that we could have is stage 2.
+            // If we're not cross-compiling, then we should have rustc stage 1.
+            let stage_to_uplift = if target == builder.host_target { 1 } else { 2 };
+            let rustc_to_uplift = builder.compiler(stage_to_uplift, target);
+            let msg = if rustc_to_uplift.host == target {
+                format!("Uplifting rustc (stage{} -> stage{stage})", rustc_to_uplift.stage,)
             } else {
                 format!(
-                    "Uplifting rustc (stage{}:{} -> stage{}:{})",
-                    compiler_to_use.stage,
-                    compiler_to_use.host,
-                    build_compiler.stage + 1,
-                    target
+                    "Uplifting rustc (stage{}:{} -> stage{stage}:{target})",
+                    rustc_to_uplift.stage, rustc_to_uplift.host,
                 )
             };
             builder.info(&msg);
-            builder.ensure(RustcLink::from_rustc(self, compiler_to_use));
-            return compiler_to_use.stage;
+            builder.ensure(RustcLink::from_rustc(self, rustc_to_uplift));
+            return;
         }
 
         // Build a standard library for the current host target using the `build_compiler`.
@@ -1128,8 +1133,6 @@ impl Step for Rustc {
             self,
             builder.compiler(build_compiler.stage, builder.config.host_target),
         ));
-
-        build_compiler.stage
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
@@ -1909,12 +1912,18 @@ impl Step for Sysroot {
     }
 }
 
+/// Prepare a compiler sysroot.
+///
+/// The sysroot may contain various things useful for running the compiler, like linkers and
+/// linker wrappers (LLD, LLVM bitcode linker, etc.).
+///
+/// This will assemble a compiler in `build/$target/stage$stage`.
 #[derive(Debug, PartialOrd, Ord, Clone, PartialEq, Eq, Hash)]
 pub struct Assemble {
     /// The compiler which we will produce in this step. Assemble itself will
     /// take care of ensuring that the necessary prerequisites to do so exist,
-    /// that is, this target can be a stage2 compiler and Assemble will build
-    /// previous stages for you.
+    /// that is, this can be e.g. a stage2 compiler and Assemble will build
+    /// the previous stages for you.
     pub target_compiler: Compiler,
 }
 
@@ -1932,11 +1941,6 @@ impl Step for Assemble {
         });
     }
 
-    /// Prepare a new compiler from the artifacts in `stage`
-    ///
-    /// This will assemble a compiler in `build/$host/stage$stage`. The compiler
-    /// must have been previously produced by the `stage - 1` builder.build
-    /// compiler.
     fn run(self, builder: &Builder<'_>) -> Compiler {
         let target_compiler = self.target_compiler;
 
@@ -2065,7 +2069,7 @@ impl Step for Assemble {
             target_compiler.stage - 1,
             builder.config.host_target,
         );
-        let mut build_compiler =
+        let build_compiler =
             builder.compiler(target_compiler.stage - 1, builder.config.host_target);
 
         // Build enzyme
@@ -2089,24 +2093,13 @@ impl Step for Assemble {
         }
 
         // Build the libraries for this compiler to link to (i.e., the libraries
-        // it uses at runtime). NOTE: Crates the target compiler compiles don't
-        // link to these. (FIXME: Is that correct? It seems to be correct most
-        // of the time but I think we do link to these for stage2/bin compilers
-        // when not performing a full bootstrap).
+        // it uses at runtime).
         debug!(
             ?build_compiler,
             "target_compiler.host" = ?target_compiler.host,
             "building compiler libraries to link to"
         );
-        let actual_stage = builder.ensure(Rustc::new(build_compiler, target_compiler.host));
-        // Current build_compiler.stage might be uplifted instead of being built; so update it
-        // to not fail while linking the artifacts.
-        debug!(
-            "(old) build_compiler.stage" = build_compiler.stage,
-            "(adjusted) build_compiler.stage" = actual_stage,
-            "temporarily adjusting `build_compiler.stage` to account for uplifted libraries"
-        );
-        build_compiler.stage = actual_stage;
+        builder.ensure(Rustc::new(build_compiler, target_compiler.host));
 
         let stage = target_compiler.stage;
         let host = target_compiler.host;
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index 6226c81a3fd..2b521debd84 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -1042,7 +1042,8 @@ impl<'a> Builder<'a> {
             Kind::Check | Kind::Fix => describe!(
                 check::Rustc,
                 check::Rustdoc,
-                check::CodegenBackend,
+                check::CraneliftCodegenBackend,
+                check::GccCodegenBackend,
                 check::Clippy,
                 check::Miri,
                 check::CargoMiri,
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index 9ba57542549..a9398a654e9 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -1514,12 +1514,7 @@ mod snapshot {
         insta::assert_snapshot!(
             ctx.config("check")
                 .path("compiler")
-                .render_steps(), @r"
-        [check] rustc 0 <host> -> rustc 1 <host> (73 crates)
-        [check] rustc 0 <host> -> rustc 1 <host>
-        [check] rustc 0 <host> -> rustc_codegen_cranelift 1 <host>
-        [check] rustc 0 <host> -> rustc_codegen_gcc 1 <host>
-        ");
+                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (73 crates)");
     }
 
     #[test]
@@ -1545,12 +1540,7 @@ mod snapshot {
             ctx.config("check")
                 .path("compiler")
                 .stage(1)
-                .render_steps(), @r"
-        [check] rustc 0 <host> -> rustc 1 <host> (73 crates)
-        [check] rustc 0 <host> -> rustc 1 <host>
-        [check] rustc 0 <host> -> rustc_codegen_cranelift 1 <host>
-        [check] rustc 0 <host> -> rustc_codegen_gcc 1 <host>
-        ");
+                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (73 crates)");
     }
 
     #[test]
@@ -1565,9 +1555,6 @@ mod snapshot {
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustc 1 <host> -> std 1 <host>
         [check] rustc 1 <host> -> rustc 2 <host> (73 crates)
-        [check] rustc 1 <host> -> rustc 2 <host>
-        [check] rustc 1 <host> -> rustc_codegen_cranelift 2 <host>
-        [check] rustc 1 <host> -> rustc_codegen_gcc 2 <host>
         ");
     }
 
@@ -1679,12 +1666,7 @@ mod snapshot {
             ctx.config("check")
                 .paths(&["library", "compiler"])
                 .args(&args)
-                .render_steps(), @r"
-        [check] rustc 0 <host> -> rustc 1 <host> (73 crates)
-        [check] rustc 0 <host> -> rustc 1 <host>
-        [check] rustc 0 <host> -> rustc_codegen_cranelift 1 <host>
-        [check] rustc 0 <host> -> rustc_codegen_gcc 1 <host>
-        ");
+                .render_steps(), @"[check] rustc 0 <host> -> rustc 1 <host> (73 crates)");
     }
 
     #[test]
@@ -1768,7 +1750,6 @@ mod snapshot {
                 .render_steps(), @r"
         [check] rustc 0 <host> -> rustc 1 <host>
         [check] rustc 0 <host> -> rustc_codegen_cranelift 1 <host>
-        [check] rustc 0 <host> -> rustc_codegen_gcc 1 <host>
         ");
     }
 
@@ -2068,130 +2049,6 @@ mod snapshot {
         [doc] rustc 1 <host> -> reference (book) 2 <host>
         ");
     }
-
-    #[test]
-    fn clippy_ci() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("ci")
-                .stage(2)
-                .render_steps(), @r"
-        [build] llvm <host>
-        [build] rustc 0 <host> -> rustc 1 <host>
-        [build] rustc 1 <host> -> std 1 <host>
-        [build] rustc 0 <host> -> clippy-driver 1 <host>
-        [build] rustc 0 <host> -> cargo-clippy 1 <host>
-        [clippy] rustc 1 <host> -> bootstrap 2 <host>
-        [clippy] rustc 1 <host> -> std 1 <host>
-        [clippy] rustc 1 <host> -> rustc 2 <host>
-        [check] rustc 1 <host> -> rustc 2 <host>
-        [clippy] rustc 1 <host> -> rustc_codegen_gcc 2 <host>
-        ");
-    }
-
-    #[test]
-    fn clippy_compiler_stage1() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("compiler")
-                .render_steps(), @r"
-        [build] llvm <host>
-        [clippy] rustc 0 <host> -> rustc 1 <host>
-        ");
-    }
-
-    #[test]
-    fn clippy_compiler_stage2() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("compiler")
-                .stage(2)
-                .render_steps(), @r"
-        [build] llvm <host>
-        [build] rustc 0 <host> -> rustc 1 <host>
-        [build] rustc 1 <host> -> std 1 <host>
-        [build] rustc 0 <host> -> clippy-driver 1 <host>
-        [build] rustc 0 <host> -> cargo-clippy 1 <host>
-        [clippy] rustc 1 <host> -> rustc 2 <host>
-        ");
-    }
-
-    #[test]
-    fn clippy_std_stage1() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("std")
-                .render_steps(), @r"
-        [build] llvm <host>
-        [build] rustc 0 <host> -> rustc 1 <host>
-        [build] rustc 0 <host> -> clippy-driver 1 <host>
-        [build] rustc 0 <host> -> cargo-clippy 1 <host>
-        [clippy] rustc 1 <host> -> std 1 <host>
-        ");
-    }
-
-    #[test]
-    fn clippy_std_stage2() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("std")
-                .stage(2)
-                .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 1 <host> -> clippy-driver 2 <host>
-        [build] rustc 1 <host> -> cargo-clippy 2 <host>
-        [clippy] rustc 2 <host> -> std 2 <host>
-        ");
-    }
-
-    #[test]
-    fn clippy_miri_stage1() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("miri")
-                .stage(1)
-                .render_steps(), @r"
-        [build] llvm <host>
-        [check] rustc 0 <host> -> rustc 1 <host>
-        [clippy] rustc 0 <host> -> miri 1 <host>
-        ");
-    }
-
-    #[test]
-    fn clippy_miri_stage2() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("miri")
-                .stage(2)
-                .render_steps(), @r"
-        [build] llvm <host>
-        [build] rustc 0 <host> -> rustc 1 <host>
-        [build] rustc 1 <host> -> std 1 <host>
-        [check] rustc 1 <host> -> rustc 2 <host>
-        [build] rustc 0 <host> -> clippy-driver 1 <host>
-        [build] rustc 0 <host> -> cargo-clippy 1 <host>
-        [clippy] rustc 1 <host> -> miri 2 <host>
-        ");
-    }
-
-    #[test]
-    fn clippy_bootstrap() {
-        let ctx = TestCtx::new();
-        insta::assert_snapshot!(
-            ctx.config("clippy")
-                .path("bootstrap")
-                .render_steps(), @"[clippy] rustc 0 <host> -> bootstrap 1 <host>");
-    }
 }
 
 struct ExecutedSteps {
diff --git a/src/ci/scripts/free-disk-space-windows-start.py b/src/ci/scripts/free-disk-space-windows-start.py
new file mode 100644
index 00000000000..fbaad722bff
--- /dev/null
+++ b/src/ci/scripts/free-disk-space-windows-start.py
@@ -0,0 +1,72 @@
+"""
+Start freeing disk space on Windows in the background by launching
+the PowerShell cleanup script, and recording the PID in a file,
+so later steps can wait for completion.
+"""
+
+import subprocess
+from pathlib import Path
+from free_disk_space_windows_util import get_pid_file, get_log_file, run_main
+
+
+def get_cleanup_script() -> Path:
+    script_dir = Path(__file__).resolve().parent
+    cleanup_script = script_dir / "free-disk-space-windows.ps1"
+    if not cleanup_script.exists():
+        raise Exception(f"Cleanup script '{cleanup_script}' not found")
+    return cleanup_script
+
+
+def write_pid(pid: int):
+    pid_file = get_pid_file()
+    if pid_file.exists():
+        raise Exception(f"Pid file '{pid_file}' already exists")
+    pid_file.write_text(str(pid))
+    print(f"wrote pid {pid} in file {pid_file}")
+
+
+def launch_cleanup_process():
+    cleanup_script = get_cleanup_script()
+    log_file_path = get_log_file()
+    # Launch the PowerShell cleanup in the background and redirect logs.
+    try:
+        with open(log_file_path, "w", encoding="utf-8") as log_file:
+            proc = subprocess.Popen(
+                [
+                    "pwsh",
+                    # Suppress PowerShell startup banner/logo for cleaner logs.
+                    "-NoLogo",
+                    # Don't load user/system profiles. Ensures a clean, predictable environment.
+                    "-NoProfile",
+                    # Disable interactive prompts. Required for CI to avoid hangs.
+                    "-NonInteractive",
+                    # Execute the specified script file (next argument).
+                    "-File",
+                    str(cleanup_script),
+                ],
+                # Write child stdout to the log file.
+                stdout=log_file,
+                # Merge stderr into stdout for a single, ordered log stream.
+                stderr=subprocess.STDOUT,
+            )
+            print(
+                f"Started free-disk-space cleanup in background. "
+                f"pid={proc.pid}; log_file={log_file_path}"
+            )
+            return proc
+    except FileNotFoundError as e:
+        raise Exception("pwsh not found on PATH; cannot start disk cleanup.") from e
+
+
+def main() -> int:
+    proc = launch_cleanup_process()
+
+    # Write pid of the process to a file, so that later steps can read it and wait
+    # until the process completes.
+    write_pid(proc.pid)
+
+    return 0
+
+
+if __name__ == "__main__":
+    run_main(main)
diff --git a/src/ci/scripts/free-disk-space-windows-wait.py b/src/ci/scripts/free-disk-space-windows-wait.py
new file mode 100644
index 00000000000..b8612bb71c2
--- /dev/null
+++ b/src/ci/scripts/free-disk-space-windows-wait.py
@@ -0,0 +1,77 @@
+"""
+Wait for the background Windows disk cleanup process.
+"""
+
+import ctypes
+import time
+from free_disk_space_windows_util import get_pid_file, get_log_file, run_main
+
+
+def is_process_running(pid: int) -> bool:
+    PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
+    processHandle = ctypes.windll.kernel32.OpenProcess(
+        PROCESS_QUERY_LIMITED_INFORMATION, 0, pid
+    )
+    if processHandle == 0:
+        # The process is not running.
+        # If you don't have the sufficient rights to check if a process is running,
+        # zero is also returned. But in GitHub Actions we have these rights.
+        return False
+    else:
+        ctypes.windll.kernel32.CloseHandle(processHandle)
+        return True
+
+
+def print_logs():
+    """Print the logs from the cleanup script."""
+    log_file = get_log_file()
+    if log_file.exists():
+        print("free-disk-space logs:")
+        # Print entire log; replace undecodable bytes to avoid exceptions.
+        try:
+            with open(log_file, "r", encoding="utf-8", errors="replace") as f:
+                print(f.read())
+        except Exception as e:
+            raise Exception(f"Failed to read log file '{log_file}'") from e
+    else:
+        print(f"::warning::Log file '{log_file}' not found")
+
+
+def read_pid_from_file() -> int:
+    """Read the PID from the pid file."""
+
+    pid_file = get_pid_file()
+    if not pid_file.exists():
+        raise Exception(
+            f"No background free-disk-space process to wait for: pid file {pid_file} not found"
+        )
+
+    pid_file_content = pid_file.read_text().strip()
+
+    # Delete the file if it exists
+    pid_file.unlink(missing_ok=True)
+
+    try:
+        # Read the first line and convert to int.
+        pid = int(pid_file_content.splitlines()[0])
+        return pid
+    except Exception as e:
+        raise Exception(
+            f"Error while parsing the pid file with content '{pid_file_content!r}'"
+        ) from e
+
+
+def main() -> int:
+    pid = read_pid_from_file()
+
+    # Poll until process exits
+    while is_process_running(pid):
+        time.sleep(3)
+
+    print_logs()
+
+    return 0
+
+
+if __name__ == "__main__":
+    run_main(main)
diff --git a/src/ci/scripts/free-disk-space.sh b/src/ci/scripts/free-disk-space.sh
index 062ad801cd8..9264fe4de6d 100755
--- a/src/ci/scripts/free-disk-space.sh
+++ b/src/ci/scripts/free-disk-space.sh
@@ -4,7 +4,7 @@ set -euo pipefail
 script_dir=$(dirname "$0")
 
 if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
-    pwsh $script_dir/free-disk-space-windows.ps1
+    python3 "$script_dir/free-disk-space-windows-start.py"
 else
     $script_dir/free-disk-space-linux.sh
 fi
diff --git a/src/ci/scripts/free_disk_space_windows_util.py b/src/ci/scripts/free_disk_space_windows_util.py
new file mode 100644
index 00000000000..488187864c2
--- /dev/null
+++ b/src/ci/scripts/free_disk_space_windows_util.py
@@ -0,0 +1,29 @@
+"""
+Utilities for Windows disk space cleanup scripts.
+"""
+
+import os
+from pathlib import Path
+import sys
+
+
+def get_temp_dir() -> Path:
+    """Get the temporary directory set by GitHub Actions."""
+    return Path(os.environ.get("RUNNER_TEMP"))
+
+
+def get_pid_file() -> Path:
+    return get_temp_dir() / "free-disk-space.pid"
+
+
+def get_log_file() -> Path:
+    return get_temp_dir() / "free-disk-space.log"
+
+
+def run_main(main_fn):
+    exit_code = 1
+    try:
+        exit_code = main_fn()
+    except Exception as e:
+        print(f"::error::{e}")
+    sys.exit(exit_code)
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 73ce62cdcde..95bd31729de 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -16,7 +16,7 @@ use std::{fmt, panic, str};
 
 pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder};
 pub(crate) use markdown::test as test_markdown;
-use rustc_data_structures::fx::{FxHashMap, FxHasher, FxIndexMap, FxIndexSet};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxHasher, FxIndexMap, FxIndexSet};
 use rustc_errors::emitter::HumanReadableErrorType;
 use rustc_errors::{ColorConfig, DiagCtxtHandle};
 use rustc_hir as hir;
@@ -689,6 +689,10 @@ fn run_test(
             "--extern=doctest_bundle_{edition}=",
             edition = doctest.edition
         ));
+
+        // Deduplicate passed -L directory paths, since usually all dependencies will be in the
+        // same directory (e.g. target/debug/deps from Cargo).
+        let mut seen_search_dirs = FxHashSet::default();
         for extern_str in &rustdoc_options.extern_strs {
             if let Some((_cratename, path)) = extern_str.split_once('=') {
                 // Direct dependencies of the tests themselves are
@@ -698,7 +702,9 @@ fn run_test(
                     .parent()
                     .filter(|x| x.components().count() > 0)
                     .unwrap_or(Path::new("."));
-                runner_compiler.arg("-L").arg(dir);
+                if seen_search_dirs.insert(dir) {
+                    runner_compiler.arg("-L").arg(dir);
+                }
             }
         }
         let output_bundle_file = doctest
diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs
index 0507c2600ae..40b0ee0a399 100644
--- a/src/tools/compiletest/src/runtest/ui.rs
+++ b/src/tools/compiletest/src/runtest/ui.rs
@@ -10,6 +10,7 @@ use super::{
     TestCx, TestOutput, Truncated, UI_FIXED, WillExecute,
 };
 use crate::json;
+use crate::runtest::ProcRes;
 
 impl TestCx<'_> {
     pub(super) fn run_ui_test(&self) {
@@ -127,6 +128,9 @@ impl TestCx<'_> {
             );
         }
 
+        // If the test is executed, capture its ProcRes separately so that
+        // pattern/forbid checks can report the *runtime* stdout/stderr when they fail.
+        let mut run_proc_res: Option<ProcRes> = None;
         let output_to_check = if let WillExecute::Yes = should_run {
             let proc_res = self.exec_compiled_test();
             let run_output_errors = if self.props.check_run_results {
@@ -189,7 +193,10 @@ impl TestCx<'_> {
                 unreachable!("run_ui_test() must not be called if the test should not run");
             }
 
-            self.get_output(&proc_res)
+            let output = self.get_output(&proc_res);
+            // Move the proc_res into our option after we've extracted output.
+            run_proc_res = Some(proc_res);
+            output
         } else {
             self.get_output(&proc_res)
         };
@@ -200,9 +207,14 @@ impl TestCx<'_> {
             explicit, self.config.compare_mode, proc_res.status, self.props.error_patterns
         );
 
+        // Compiler diagnostics (expected errors) are always tied to the compile-time ProcRes.
         self.check_expected_errors(&proc_res);
-        self.check_all_error_patterns(&output_to_check, &proc_res);
-        self.check_forbid_output(&output_to_check, &proc_res);
+
+        // For runtime pattern/forbid checks prefer the executed program's ProcRes if available
+        // so that missing pattern failures include the program's stdout/stderr.
+        let pattern_proc_res = run_proc_res.as_ref().unwrap_or(&proc_res);
+        self.check_all_error_patterns(&output_to_check, pattern_proc_res);
+        self.check_forbid_output(&output_to_check, pattern_proc_res);
 
         if self.props.run_rustfix && self.config.compare_mode.is_none() {
             // And finally, compile the fixed code and make sure it both
diff --git a/tests/ui/lint/dropping_copy_types-macros.fixed b/tests/ui/lint/dropping_copy_types-macros.fixed
index a8ceedadc80..2a0c3988157 100644
--- a/tests/ui/lint/dropping_copy_types-macros.fixed
+++ b/tests/ui/lint/dropping_copy_types-macros.fixed
@@ -9,4 +9,7 @@ fn main() {
     let mut msg = String::new();
     let _ = writeln!(&mut msg, "test");
     //~^ ERROR calls to `std::mem::drop`
+
+    let _ = format_args!("a");
+    //~^ ERROR calls to `std::mem::drop`
 }
diff --git a/tests/ui/lint/dropping_copy_types-macros.rs b/tests/ui/lint/dropping_copy_types-macros.rs
index b249b0c868f..2272e0e8560 100644
--- a/tests/ui/lint/dropping_copy_types-macros.rs
+++ b/tests/ui/lint/dropping_copy_types-macros.rs
@@ -9,4 +9,7 @@ fn main() {
     let mut msg = String::new();
     drop(writeln!(&mut msg, "test"));
     //~^ ERROR calls to `std::mem::drop`
+
+    drop(format_args!("a"));
+    //~^ ERROR calls to `std::mem::drop`
 }
diff --git a/tests/ui/lint/dropping_copy_types-macros.stderr b/tests/ui/lint/dropping_copy_types-macros.stderr
index 117e9f4fe09..5048f6e3f18 100644
--- a/tests/ui/lint/dropping_copy_types-macros.stderr
+++ b/tests/ui/lint/dropping_copy_types-macros.stderr
@@ -17,5 +17,19 @@ LL -     drop(writeln!(&mut msg, "test"));
 LL +     let _ = writeln!(&mut msg, "test");
    |
 
-error: aborting due to 1 previous error
+error: calls to `std::mem::drop` with a value that implements `Copy` does nothing
+  --> $DIR/dropping_copy_types-macros.rs:13:5
+   |
+LL |     drop(format_args!("a"));
+   |     ^^^^^-----------------^
+   |          |
+   |          argument has type `Arguments<'_>`
+   |
+help: use `let _ = ...` to ignore the expression or result
+   |
+LL -     drop(format_args!("a"));
+LL +     let _ = format_args!("a");
+   |
+
+error: aborting due to 2 previous errors
 
diff --git a/triagebot.toml b/triagebot.toml
index df81bb71160..de89a4b4e72 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -627,6 +627,16 @@ trigger_files = [
     "src/ci",
 ]
 
+[autolabel."T-rust-analyzer"]
+trigger_files = [
+    "src/tools/rust-analyzer",
+]
+
+[autolabel."T-rustfmt"]
+trigger_files = [
+    "src/tools/rustfmt",
+]
+
 # ------------------------------------------------------------------------------
 # Prioritization and team nominations
 # ------------------------------------------------------------------------------