about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-03-08 03:19:34 +0000
committerbors <bors@rust-lang.org>2025-03-08 03:19:34 +0000
commit20f0108ada90e37c2350ad68fdc6afe56f51539d (patch)
treec842cd31aaf30aff8cbf5bf16f97013bad4e789e
parentf5a1ef7121ad661b5a21a1d02941c8064d54ee0b (diff)
parentdad4c8b6a97e872b22732009f59afce5b5c631a3 (diff)
downloadrust-20f0108ada90e37c2350ad68fdc6afe56f51539d.tar.gz
rust-20f0108ada90e37c2350ad68fdc6afe56f51539d.zip
Auto merge of #138202 - jhpratt:rollup-kqrl5xn, r=jhpratt
Rollup of 12 pull requests

Successful merges:

 - #137337 (Add verbatim linker to AIXLinker)
 - #137363 (compiler: factor Windows x86-32 ABI impl into its own file)
 - #137537 (Prevent `rmake.rs` from using unstable features, and fix 3 run-make tests that currently do)
 - #137606 (add a "future" edition)
 - #137957 (Remove i586-pc-windows-msvc)
 - #138000 (atomic: clarify that failing conditional RMW operations are not 'writes')
 - #138013 (Add post-merge analysis CI workflow)
 - #138033 (rustdoc: Add attribute-related tests for rustdoc JSON.)
 - #138137 (setTargetTriple now accepts Triple rather than string)
 - #138173 (Delay bug for negative auto trait rather than ICEing)
 - #138184 (Allow anyone to relabel `CI-spurious-*`)
 - #138187 (remove clones)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--.github/workflows/post-merge.yml36
-rw-r--r--Cargo.lock11
-rw-r--r--compiler/rustc_builtin_macros/src/autodiff.rs29
-rw-r--r--compiler/rustc_builtin_macros/src/standard_library_imports.rs1
-rw-r--r--compiler/rustc_codegen_ssa/src/back/linker.rs6
-rw-r--r--compiler/rustc_hir_analysis/src/check/always_applicable.rs5
-rw-r--r--compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp8
-rw-r--r--compiler/rustc_span/src/edition.rs29
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_target/src/callconv/mod.rs7
-rw-r--r--compiler/rustc_target/src/callconv/x86.rs24
-rw-r--r--compiler/rustc_target/src/callconv/x86_win32.rs81
-rw-r--r--compiler/rustc_target/src/spec/mod.rs11
-rw-r--r--compiler/rustc_target/src/spec/targets/i586_pc_windows_msvc.rs9
-rw-r--r--library/core/src/prelude/mod.rs23
-rw-r--r--library/core/src/sync/atomic.rs5
-rw-r--r--library/std/src/prelude/mod.rs15
-rw-r--r--src/bootstrap/src/core/builder/cargo.rs2
-rw-r--r--src/build_helper/src/metrics.rs2
-rw-r--r--src/ci/citool/src/main.rs12
-rw-r--r--src/ci/citool/src/merge_report.rs257
-rw-r--r--src/ci/citool/src/metrics.rs22
-rw-r--r--src/ci/github-actions/jobs.yml2
-rw-r--r--src/doc/rustc-dev-guide/src/tests/compiletest.md4
-rw-r--r--src/doc/rustc/src/platform-support.md1
-rw-r--r--src/tools/build-manifest/src/main.rs1
-rw-r--r--src/tools/compiletest/src/runtest/run_make.rs5
-rw-r--r--src/tools/run-make-support/Cargo.toml4
-rw-r--r--src/tools/run-make-support/src/lib.rs2
-rw-r--r--tests/assembly/stack-protector/stack-protector-target-support.rs6
-rw-r--r--tests/assembly/targets/targets-pe.rs3
-rw-r--r--tests/run-make/broken-pipe-no-ice/rmake.rs8
-rw-r--r--tests/run-make/cross-lang-lto/rmake.rs15
-rw-r--r--tests/run-make/issue-107495-archive-permissions/rmake.rs7
-rw-r--r--tests/rustdoc-json/attrs/export_name_2021.rs6
-rw-r--r--tests/rustdoc-json/attrs/export_name_2024.rs9
-rw-r--r--tests/rustdoc-json/attrs/must_use.rs9
-rw-r--r--tests/rustdoc-json/attrs/no_mangle_2021.rs6
-rw-r--r--tests/rustdoc-json/attrs/no_mangle_2024.rs9
-rw-r--r--tests/rustdoc-json/attrs/non_exhaustive.rs19
-rw-r--r--tests/ui/auto-traits/ungated-impl.rs7
-rw-r--r--tests/ui/auto-traits/ungated-impl.stderr23
-rw-r--r--triagebot.toml1
43 files changed, 644 insertions, 99 deletions
diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml
new file mode 100644
index 00000000000..d3f42c5a905
--- /dev/null
+++ b/.github/workflows/post-merge.yml
@@ -0,0 +1,36 @@
+# Workflow that runs after a merge to master, analyses changes in test executions
+# and posts the result to the merged PR.
+
+name: Post merge analysis
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  analysis:
+    runs-on: ubuntu-24.04
+    if: github.repository == 'rust-lang/rust'
+    permissions:
+      pull-requests: write
+    steps:
+      - uses: actions/checkout@v4
+      - name: Perform analysis and send PR
+        run: |
+          # Get closest bors merge commit
+          PARENT_COMMIT=`git rev-list --author='bors <bors@rust-lang.org>' -n1 --first-parent HEAD^1`
+
+          # Find PR for the current commit
+          HEAD_PR=`gh pr list --search "${{ github.sha }}" --state merged --json number --jq '.[0].number'`
+
+          echo "Parent: ${PARENT_COMMIT}"
+          echo "HEAD: ${{ github.sha }} (#${HEAD_PR})"
+
+          cd src/ci/citool
+
+          echo "Post-merge analysis result" > output.log
+          cargo run --release post-merge-analysis ${PARENT_COMMIT} ${{ github.sha }} >> output.log
+          cat output.log
+
+          gh pr comment ${HEAD_PR} -F output.log
diff --git a/Cargo.lock b/Cargo.lock
index 72f2d4f6cd3..5886b96b728 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2527,6 +2527,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
+name = "os_pipe"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "overload"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3050,6 +3060,7 @@ dependencies = [
  "gimli 0.31.1",
  "libc",
  "object 0.36.7",
+ "os_pipe",
  "regex",
  "serde_json",
  "similar",
diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs
index 6d9c3575657..7b5c4a159b0 100644
--- a/compiler/rustc_builtin_macros/src/autodiff.rs
+++ b/compiler/rustc_builtin_macros/src/autodiff.rs
@@ -286,7 +286,7 @@ mod llvm_enzyme {
         let orig_annotatable: Annotatable = match item {
             Annotatable::Item(ref mut iitem) => {
                 if !iitem.attrs.iter().any(|a| a.id == attr.id) {
-                    iitem.attrs.push(attr.clone());
+                    iitem.attrs.push(attr);
                 }
                 if !iitem.attrs.iter().any(|a| a.id == inline_never.id) {
                     iitem.attrs.push(inline_never.clone());
@@ -295,7 +295,7 @@ mod llvm_enzyme {
             }
             Annotatable::AssocItem(ref mut assoc_item, i @ Impl) => {
                 if !assoc_item.attrs.iter().any(|a| a.id == attr.id) {
-                    assoc_item.attrs.push(attr.clone());
+                    assoc_item.attrs.push(attr);
                 }
                 if !assoc_item.attrs.iter().any(|a| a.id == inline_never.id) {
                     assoc_item.attrs.push(inline_never.clone());
@@ -322,7 +322,7 @@ mod llvm_enzyme {
         let d_annotatable = if is_impl {
             let assoc_item: AssocItemKind = ast::AssocItemKind::Fn(asdf);
             let d_fn = P(ast::AssocItem {
-                attrs: thin_vec![d_attr.clone(), inline_never],
+                attrs: thin_vec![d_attr, inline_never],
                 id: ast::DUMMY_NODE_ID,
                 span,
                 vis,
@@ -332,12 +332,8 @@ mod llvm_enzyme {
             });
             Annotatable::AssocItem(d_fn, Impl)
         } else {
-            let mut d_fn = ecx.item(
-                span,
-                d_ident,
-                thin_vec![d_attr.clone(), inline_never],
-                ItemKind::Fn(asdf),
-            );
+            let mut d_fn =
+                ecx.item(span, d_ident, thin_vec![d_attr, inline_never], ItemKind::Fn(asdf));
             d_fn.vis = vis;
             Annotatable::Item(d_fn)
         };
@@ -446,7 +442,7 @@ mod llvm_enzyme {
 
         if primal_ret && n_active == 0 && x.mode.is_rev() {
             // We only have the primal ret.
-            body.stmts.push(ecx.stmt_expr(black_box_primal_call.clone()));
+            body.stmts.push(ecx.stmt_expr(black_box_primal_call));
             return body;
         }
 
@@ -471,7 +467,7 @@ mod llvm_enzyme {
         if primal_ret {
             // We have both primal ret and active floats.
             // primal ret is first, by construction.
-            exprs.push(primal_call.clone());
+            exprs.push(primal_call);
         }
 
         // Now construct default placeholder for each active float.
@@ -538,16 +534,11 @@ mod llvm_enzyme {
                 return body;
             }
             [arg] => {
-                ret = ecx.expr_call(
-                    new_decl_span,
-                    blackbox_call_expr.clone(),
-                    thin_vec![arg.clone()],
-                );
+                ret = ecx.expr_call(new_decl_span, blackbox_call_expr, thin_vec![arg.clone()]);
             }
             args => {
                 let ret_tuple: P<ast::Expr> = ecx.expr_tuple(span, args.into());
-                ret =
-                    ecx.expr_call(new_decl_span, blackbox_call_expr.clone(), thin_vec![ret_tuple]);
+                ret = ecx.expr_call(new_decl_span, blackbox_call_expr, thin_vec![ret_tuple]);
             }
         }
         assert!(has_ret(&d_sig.decl.output));
@@ -567,7 +558,7 @@ mod llvm_enzyme {
             let args: ThinVec<_> =
                 idents[1..].iter().map(|arg| ecx.expr_path(ecx.path_ident(span, *arg))).collect();
             let self_expr = ecx.expr_self(span);
-            ecx.expr_method_call(span, self_expr, primal, args.clone())
+            ecx.expr_method_call(span, self_expr, primal, args)
         } else {
             let args: ThinVec<_> =
                 idents.iter().map(|arg| ecx.expr_path(ecx.path_ident(span, *arg))).collect();
diff --git a/compiler/rustc_builtin_macros/src/standard_library_imports.rs b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
index 6933ca09349..ba63b185e09 100644
--- a/compiler/rustc_builtin_macros/src/standard_library_imports.rs
+++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
@@ -60,6 +60,7 @@ pub fn inject(
             Edition2018 => sym::rust_2018,
             Edition2021 => sym::rust_2021,
             Edition2024 => sym::rust_2024,
+            EditionFuture => sym::rust_future,
         }])
         .map(|&symbol| Ident::new(symbol, span))
         .collect();
diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs
index 818edaf6603..a8405a2aec9 100644
--- a/compiler/rustc_codegen_ssa/src/back/linker.rs
+++ b/compiler/rustc_codegen_ssa/src/back/linker.rs
@@ -1655,9 +1655,9 @@ impl<'a> Linker for AixLinker<'a> {
         }
     }
 
-    fn link_dylib_by_name(&mut self, name: &str, _verbatim: bool, _as_needed: bool) {
+    fn link_dylib_by_name(&mut self, name: &str, verbatim: bool, _as_needed: bool) {
         self.hint_dynamic();
-        self.link_or_cc_arg(format!("-l{name}"));
+        self.link_or_cc_arg(if verbatim { String::from(name) } else { format!("-l{name}") });
     }
 
     fn link_dylib_by_path(&mut self, path: &Path, _as_needed: bool) {
@@ -1668,7 +1668,7 @@ impl<'a> Linker for AixLinker<'a> {
     fn link_staticlib_by_name(&mut self, name: &str, verbatim: bool, whole_archive: bool) {
         self.hint_static();
         if !whole_archive {
-            self.link_or_cc_arg(format!("-l{name}"));
+            self.link_or_cc_arg(if verbatim { String::from(name) } else { format!("-l{name}") });
         } else {
             let mut arg = OsString::from("-bkeepfile:");
             arg.push(find_native_static_library(name, verbatim, self.sess));
diff --git a/compiler/rustc_hir_analysis/src/check/always_applicable.rs b/compiler/rustc_hir_analysis/src/check/always_applicable.rs
index ba5b61d3fce..8a841a11556 100644
--- a/compiler/rustc_hir_analysis/src/check/always_applicable.rs
+++ b/compiler/rustc_hir_analysis/src/check/always_applicable.rs
@@ -124,7 +124,10 @@ pub(crate) fn check_negative_auto_trait_impl<'tcx>(
                 // be implemented here to handle non-ADT rigid types.
                 Ok(())
             } else {
-                span_bug!(tcx.def_span(impl_def_id), "incoherent impl of negative auto trait");
+                Err(tcx.dcx().span_delayed_bug(
+                    tcx.def_span(impl_def_id),
+                    "incoherent impl of negative auto trait",
+                ))
             }
         }
     }
diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
index aea2a8dd097..53df59930f4 100644
--- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
@@ -152,8 +152,12 @@ extern "C" LLVMContextRef LLVMRustContextCreate(bool shouldDiscardNames) {
 }
 
 extern "C" void LLVMRustSetNormalizedTarget(LLVMModuleRef M,
-                                            const char *Triple) {
-  unwrap(M)->setTargetTriple(Triple::normalize(Triple));
+                                            const char *Target) {
+#if LLVM_VERSION_GE(21, 0)
+  unwrap(M)->setTargetTriple(Triple(Triple::normalize(Target)));
+#else
+  unwrap(M)->setTargetTriple(Triple::normalize(Target));
+#endif
 }
 
 extern "C" void LLVMRustPrintPassTimings(RustStringRef OutBuf) {
diff --git a/compiler/rustc_span/src/edition.rs b/compiler/rustc_span/src/edition.rs
index 36f9b4cff60..da298080ed2 100644
--- a/compiler/rustc_span/src/edition.rs
+++ b/compiler/rustc_span/src/edition.rs
@@ -23,11 +23,27 @@ pub enum Edition {
     Edition2021,
     /// The 2024 edition
     Edition2024,
+    /// The future edition - this variant will always exist and features associated with this
+    /// edition can be moved to the next 20XX edition when it is established and it is confirmed
+    /// that those features will be part of that edition.
+    ///
+    /// This variant allows edition changes to be implemented before being assigned to a concrete
+    /// edition - primarily when there are two different unstable behaviours that need tested across
+    /// an edition boundary.
+    ///
+    /// This edition will be permanently unstable and any features associated with this edition
+    /// must also be behind a feature gate.
+    EditionFuture,
 }
 
 // Must be in order from oldest to newest.
-pub const ALL_EDITIONS: &[Edition] =
-    &[Edition::Edition2015, Edition::Edition2018, Edition::Edition2021, Edition::Edition2024];
+pub const ALL_EDITIONS: &[Edition] = &[
+    Edition::Edition2015,
+    Edition::Edition2018,
+    Edition::Edition2021,
+    Edition::Edition2024,
+    Edition::EditionFuture,
+];
 
 pub const EDITION_NAME_LIST: &str = "2015|2018|2021|2024";
 
@@ -42,6 +58,7 @@ impl fmt::Display for Edition {
             Edition::Edition2018 => "2018",
             Edition::Edition2021 => "2021",
             Edition::Edition2024 => "2024",
+            Edition::EditionFuture => "future",
         };
         write!(f, "{s}")
     }
@@ -54,6 +71,7 @@ impl Edition {
             Edition::Edition2018 => "rust_2018_compatibility",
             Edition::Edition2021 => "rust_2021_compatibility",
             Edition::Edition2024 => "rust_2024_compatibility",
+            Edition::EditionFuture => "edition_future_compatibility",
         }
     }
 
@@ -63,6 +81,7 @@ impl Edition {
             Edition::Edition2018 => true,
             Edition::Edition2021 => true,
             Edition::Edition2024 => true,
+            Edition::EditionFuture => false,
         }
     }
 
@@ -85,6 +104,11 @@ impl Edition {
     pub fn at_least_rust_2024(self) -> bool {
         self >= Edition::Edition2024
     }
+
+    /// Are we allowed to use features from the future edition?
+    pub fn at_least_edition_future(self) -> bool {
+        self >= Edition::EditionFuture
+    }
 }
 
 impl FromStr for Edition {
@@ -95,6 +119,7 @@ impl FromStr for Edition {
             "2018" => Ok(Edition::Edition2018),
             "2021" => Ok(Edition::Edition2021),
             "2024" => Ok(Edition::Edition2024),
+            "future" => Ok(Edition::EditionFuture),
             _ => Err(()),
         }
     }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 9045fc95ec7..9e7f5047eb3 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1718,6 +1718,7 @@ symbols! {
         rust_cold_cc,
         rust_eh_catch_typeinfo,
         rust_eh_personality,
+        rust_future,
         rust_logo,
         rust_out,
         rustc,
diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs
index 198d08864be..6d0ee3c7ee5 100644
--- a/compiler/rustc_target/src/callconv/mod.rs
+++ b/compiler/rustc_target/src/callconv/mod.rs
@@ -31,6 +31,7 @@ mod sparc64;
 mod wasm;
 mod x86;
 mod x86_64;
+mod x86_win32;
 mod x86_win64;
 mod xtensa;
 
@@ -649,7 +650,11 @@ impl<'a, Ty> FnAbi<'a, Ty> {
                 };
                 let reg_struct_return = cx.x86_abi_opt().reg_struct_return;
                 let opts = x86::X86Options { flavor, regparm, reg_struct_return };
-                x86::compute_abi_info(cx, self, opts);
+                if spec.is_like_msvc {
+                    x86_win32::compute_abi_info(cx, self, opts);
+                } else {
+                    x86::compute_abi_info(cx, self, opts);
+                }
             }
             "x86_64" => match abi {
                 ExternAbi::SysV64 { .. } => x86_64::compute_abi_info(cx, self),
diff --git a/compiler/rustc_target/src/callconv/x86.rs b/compiler/rustc_target/src/callconv/x86.rs
index 73aff85a0ad..6f112b49400 100644
--- a/compiler/rustc_target/src/callconv/x86.rs
+++ b/compiler/rustc_target/src/callconv/x86.rs
@@ -36,7 +36,7 @@ where
             if t.abi_return_struct_as_int || opts.reg_struct_return {
                 // According to Clang, everyone but MSVC returns single-element
                 // float aggregates directly in a floating-point register.
-                if !t.is_like_msvc && fn_abi.ret.layout.is_single_fp_element(cx) {
+                if fn_abi.ret.layout.is_single_fp_element(cx) {
                     match fn_abi.ret.layout.size.bytes() {
                         4 => fn_abi.ret.cast_to(Reg::f32()),
                         8 => fn_abi.ret.cast_to(Reg::f64()),
@@ -64,31 +64,11 @@ where
             continue;
         }
 
-        // FIXME: MSVC 2015+ will pass the first 3 vector arguments in [XYZ]MM0-2
-        // See https://reviews.llvm.org/D72114 for Clang behavior
-
         let t = cx.target_spec();
         let align_4 = Align::from_bytes(4).unwrap();
         let align_16 = Align::from_bytes(16).unwrap();
 
-        if t.is_like_msvc
-            && arg.layout.is_adt()
-            && let Some(max_repr_align) = arg.layout.max_repr_align
-            && max_repr_align > align_4
-        {
-            // MSVC has special rules for overaligned arguments: https://reviews.llvm.org/D72114.
-            // Summarized here:
-            // - Arguments with _requested_ alignment > 4 are passed indirectly.
-            // - For backwards compatibility, arguments with natural alignment > 4 are still passed
-            //   on stack (via `byval`). For example, this includes `double`, `int64_t`,
-            //   and structs containing them, provided they lack an explicit alignment attribute.
-            assert!(
-                arg.layout.align.abi >= max_repr_align,
-                "abi alignment {:?} less than requested alignment {max_repr_align:?}",
-                arg.layout.align.abi,
-            );
-            arg.make_indirect();
-        } else if arg.layout.is_aggregate() {
+        if arg.layout.is_aggregate() {
             // We need to compute the alignment of the `byval` argument. The rules can be found in
             // `X86_32ABIInfo::getTypeStackAlignInBytes` in Clang's `TargetInfo.cpp`. Summarized
             // here, they are:
diff --git a/compiler/rustc_target/src/callconv/x86_win32.rs b/compiler/rustc_target/src/callconv/x86_win32.rs
new file mode 100644
index 00000000000..554a7368848
--- /dev/null
+++ b/compiler/rustc_target/src/callconv/x86_win32.rs
@@ -0,0 +1,81 @@
+use rustc_abi::{Align, HasDataLayout, Reg, TyAbiInterface};
+
+use crate::callconv::FnAbi;
+use crate::spec::HasTargetSpec;
+
+pub(crate) fn compute_abi_info<'a, Ty, C>(
+    cx: &C,
+    fn_abi: &mut FnAbi<'a, Ty>,
+    opts: super::x86::X86Options,
+) where
+    Ty: TyAbiInterface<'a, C> + Copy,
+    C: HasDataLayout + HasTargetSpec,
+{
+    if !fn_abi.ret.is_ignore() {
+        if fn_abi.ret.layout.is_aggregate() && fn_abi.ret.layout.is_sized() {
+            // Returning a structure. Most often, this will use
+            // a hidden first argument. On some platforms, though,
+            // small structs are returned as integers.
+            //
+            // Some links:
+            // https://www.angelcode.com/dev/callconv/callconv.html
+            // Clang's ABI handling is in lib/CodeGen/TargetInfo.cpp
+            let t = cx.target_spec();
+            // MSVC does not special-case 1-element float aggregates, unlike others.
+            // GCC used to apply the SysV rule here, breaking windows-gnu's ABI, but was fixed:
+            // - reported in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82028
+            // - fixed in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85667
+            if t.abi_return_struct_as_int || opts.reg_struct_return {
+                match fn_abi.ret.layout.size.bytes() {
+                    1 => fn_abi.ret.cast_to(Reg::i8()),
+                    2 => fn_abi.ret.cast_to(Reg::i16()),
+                    4 => fn_abi.ret.cast_to(Reg::i32()),
+                    8 => fn_abi.ret.cast_to(Reg::i64()),
+                    _ => fn_abi.ret.make_indirect(),
+                }
+            } else {
+                fn_abi.ret.make_indirect();
+            }
+        } else {
+            fn_abi.ret.extend_integer_width_to(32);
+        }
+    }
+
+    for arg in fn_abi.args.iter_mut() {
+        if arg.is_ignore() || !arg.layout.is_sized() {
+            continue;
+        }
+
+        // FIXME: MSVC 2015+ will pass the first 3 vector arguments in [XYZ]MM0-2
+        // See https://reviews.llvm.org/D72114 for Clang behavior
+
+        let align_4 = Align::from_bytes(4).unwrap();
+
+        if arg.layout.is_adt()
+            && let Some(max_repr_align) = arg.layout.max_repr_align
+            && max_repr_align > align_4
+        {
+            // MSVC has special rules for overaligned arguments: https://reviews.llvm.org/D72114.
+            // Summarized here:
+            // - Arguments with _requested_ alignment > 4 are passed indirectly.
+            // - For backwards compatibility, arguments with natural alignment > 4 are still passed
+            //   on stack (via `byval`). For example, this includes `double`, `int64_t`,
+            //   and structs containing them, provided they lack an explicit alignment attribute.
+            assert!(
+                arg.layout.align.abi >= max_repr_align,
+                "abi alignment {:?} less than requested alignment {max_repr_align:?}",
+                arg.layout.align.abi,
+            );
+            arg.make_indirect();
+        } else if arg.layout.is_aggregate() {
+            // Alignment of the `byval` argument.
+            // The rules can be found in `X86_32ABIInfo::getTypeStackAlignInBytes` in Clang's `TargetInfo.cpp`.
+            let byval_align = align_4;
+            arg.pass_by_stack_offset(Some(byval_align));
+        } else {
+            arg.extend_integer_width_to(32);
+        }
+    }
+
+    super::x86::fill_inregs(cx, fn_abi, opts, false);
+}
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 72202129f5b..15936c731ea 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -1915,7 +1915,6 @@ supported_targets! {
     ("i686-pc-windows-msvc", i686_pc_windows_msvc),
     ("i686-uwp-windows-msvc", i686_uwp_windows_msvc),
     ("i686-win7-windows-msvc", i686_win7_windows_msvc),
-    ("i586-pc-windows-msvc", i586_pc_windows_msvc),
     ("thumbv7a-pc-windows-msvc", thumbv7a_pc_windows_msvc),
     ("thumbv7a-uwp-windows-msvc", thumbv7a_uwp_windows_msvc),
 
@@ -3503,7 +3502,15 @@ impl Target {
                     return load_file(&p);
                 }
 
-                Err(format!("Could not find specification for target {target_tuple:?}"))
+                // Leave in a specialized error message for the removed target.
+                // FIXME: If you see this and it's been a few months after this has been released,
+                // you can probably remove it.
+                if target_tuple == "i586-pc-windows-msvc" {
+                    Err("the `i586-pc-windows-msvc` target has been removed. Use the `i686-pc-windows-msvc` target instead.\n\
+                        Windows 10 (the minimum required OS version) requires a CPU baseline of at least i686 so you can safely switch".into())
+                } else {
+                    Err(format!("Could not find specification for target {target_tuple:?}"))
+                }
             }
             TargetTuple::TargetJson { ref contents, .. } => {
                 let obj = serde_json::from_str(contents).map_err(|e| e.to_string())?;
diff --git a/compiler/rustc_target/src/spec/targets/i586_pc_windows_msvc.rs b/compiler/rustc_target/src/spec/targets/i586_pc_windows_msvc.rs
deleted file mode 100644
index 394e6f9e6bf..00000000000
--- a/compiler/rustc_target/src/spec/targets/i586_pc_windows_msvc.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use crate::spec::Target;
-
-pub(crate) fn target() -> Target {
-    let mut base = super::i686_pc_windows_msvc::target();
-    base.rustc_abi = None; // overwrite the SSE2 ABI set by the base target
-    base.cpu = "pentium".into();
-    base.llvm_target = "i586-pc-windows-msvc".into();
-    base
-}
diff --git a/library/core/src/prelude/mod.rs b/library/core/src/prelude/mod.rs
index 590ffd64b5b..8d867a269a2 100644
--- a/library/core/src/prelude/mod.rs
+++ b/library/core/src/prelude/mod.rs
@@ -70,3 +70,26 @@ pub mod rust_2024 {
     #[doc(no_inline)]
     pub use crate::future::{Future, IntoFuture};
 }
+
+/// The Future version of the core prelude.
+///
+/// See the [module-level documentation](self) for more.
+#[doc(hidden)]
+#[unstable(feature = "prelude_future", issue = "none")]
+pub mod rust_future {
+    #[stable(feature = "rust1", since = "1.0.0")]
+    #[doc(no_inline)]
+    pub use super::v1::*;
+
+    #[stable(feature = "prelude_2021", since = "1.55.0")]
+    #[doc(no_inline)]
+    pub use crate::iter::FromIterator;
+
+    #[stable(feature = "prelude_2021", since = "1.55.0")]
+    #[doc(no_inline)]
+    pub use crate::convert::{TryFrom, TryInto};
+
+    #[stable(feature = "prelude_2024", since = "1.85.0")]
+    #[doc(no_inline)]
+    pub use crate::future::{Future, IntoFuture};
+}
diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs
index bac92ef94e7..ae28ec4baa0 100644
--- a/library/core/src/sync/atomic.rs
+++ b/library/core/src/sync/atomic.rs
@@ -44,8 +44,9 @@
 //! The most important aspect of this model is that *data races* are undefined behavior. A data race
 //! is defined as conflicting non-synchronized accesses where at least one of the accesses is
 //! non-atomic. Here, accesses are *conflicting* if they affect overlapping regions of memory and at
-//! least one of them is a write. They are *non-synchronized* if neither of them *happens-before*
-//! the other, according to the happens-before order of the memory model.
+//! least one of them is a write. (A `compare_exchange` or `compare_exchange_weak` that does not
+//! succeed is not considered a write.) They are *non-synchronized* if neither of them
+//! *happens-before* the other, according to the happens-before order of the memory model.
 //!
 //! The other possible cause of undefined behavior in the memory model are mixed-size accesses: Rust
 //! inherits the C++ limitation that non-synchronized conflicting atomic accesses may not partially
diff --git a/library/std/src/prelude/mod.rs b/library/std/src/prelude/mod.rs
index 992a9207a72..5f7097c26e2 100644
--- a/library/std/src/prelude/mod.rs
+++ b/library/std/src/prelude/mod.rs
@@ -160,3 +160,18 @@ pub mod rust_2024 {
     #[doc(no_inline)]
     pub use core::prelude::rust_2024::*;
 }
+
+/// The Future version of the prelude of The Rust Standard Library.
+///
+/// See the [module-level documentation](self) for more.
+#[doc(hidden)]
+#[unstable(feature = "prelude_future", issue = "none")]
+pub mod rust_future {
+    #[stable(feature = "rust1", since = "1.0.0")]
+    #[doc(no_inline)]
+    pub use super::v1::*;
+
+    #[unstable(feature = "prelude_next", issue = "none")]
+    #[doc(no_inline)]
+    pub use core::prelude::rust_future::*;
+}
diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs
index d1d52d82eaa..12dd40d14e9 100644
--- a/src/bootstrap/src/core/builder/cargo.rs
+++ b/src/bootstrap/src/core/builder/cargo.rs
@@ -597,7 +597,7 @@ impl Builder<'_> {
         // sysroot. Passing this cfg enables raw-dylib support instead, which makes the native
         // library unnecessary. This can be removed when windows-rs enables raw-dylib
         // unconditionally.
-        if let Mode::Rustc | Mode::ToolRustc = mode {
+        if let Mode::Rustc | Mode::ToolRustc | Mode::ToolBootstrap = mode {
             rustflags.arg("--cfg=windows_raw_dylib");
         }
 
diff --git a/src/build_helper/src/metrics.rs b/src/build_helper/src/metrics.rs
index eb306550fc4..b6daac32a44 100644
--- a/src/build_helper/src/metrics.rs
+++ b/src/build_helper/src/metrics.rs
@@ -74,7 +74,7 @@ pub struct Test {
     pub outcome: TestOutcome,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
 #[serde(tag = "outcome", rename_all = "snake_case")]
 pub enum TestOutcome {
     Passed,
diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs
index 16edd681886..52e7638d98b 100644
--- a/src/ci/citool/src/main.rs
+++ b/src/ci/citool/src/main.rs
@@ -1,5 +1,6 @@
 mod cpu_usage;
 mod datadog;
+mod merge_report;
 mod metrics;
 mod utils;
 
@@ -13,6 +14,7 @@ use serde_yaml::Value;
 
 use crate::cpu_usage::load_cpu_usage;
 use crate::datadog::upload_datadog_metric;
+use crate::merge_report::post_merge_report;
 use crate::metrics::postprocess_metrics;
 use crate::utils::load_env_var;
 
@@ -373,6 +375,13 @@ enum Args {
         /// Path to a CSV containing the CI job CPU usage.
         cpu_usage_csv: PathBuf,
     },
+    /// Generate a report of test execution changes between two rustc commits.
+    PostMergeReport {
+        /// Parent commit to use as a base of the comparison.
+        parent: String,
+        /// Current commit that will be compared to `parent`.
+        current: String,
+    },
 }
 
 #[derive(clap::ValueEnum, Clone)]
@@ -410,6 +419,9 @@ fn main() -> anyhow::Result<()> {
         Args::PostprocessMetrics { metrics_path, summary_path } => {
             postprocess_metrics(&metrics_path, &summary_path)?;
         }
+        Args::PostMergeReport { current: commit, parent } => {
+            post_merge_report(load_db(default_jobs_file)?, parent, commit)?;
+        }
     }
 
     Ok(())
diff --git a/src/ci/citool/src/merge_report.rs b/src/ci/citool/src/merge_report.rs
new file mode 100644
index 00000000000..5dd662280f0
--- /dev/null
+++ b/src/ci/citool/src/merge_report.rs
@@ -0,0 +1,257 @@
+use std::cmp::Reverse;
+use std::collections::HashMap;
+
+use anyhow::Context;
+use build_helper::metrics::{JsonRoot, TestOutcome};
+
+use crate::JobDatabase;
+use crate::metrics::get_test_suites;
+
+type Sha = String;
+type JobName = String;
+
+/// Computes a post merge CI analysis report between the `parent` and `current` commits.
+pub fn post_merge_report(job_db: JobDatabase, parent: Sha, current: Sha) -> anyhow::Result<()> {
+    let jobs = download_all_metrics(&job_db, &parent, &current)?;
+    let diffs = aggregate_test_diffs(&jobs)?;
+    report_test_changes(diffs);
+
+    Ok(())
+}
+
+struct JobMetrics {
+    parent: Option<JsonRoot>,
+    current: JsonRoot,
+}
+
+/// Download before/after metrics for all auto jobs in the job database.
+fn download_all_metrics(
+    job_db: &JobDatabase,
+    parent: &str,
+    current: &str,
+) -> anyhow::Result<HashMap<JobName, JobMetrics>> {
+    let mut jobs = HashMap::default();
+
+    for job in &job_db.auto_jobs {
+        eprintln!("Downloading metrics of job {}", job.name);
+        let metrics_parent = match download_job_metrics(&job.name, parent) {
+            Ok(metrics) => Some(metrics),
+            Err(error) => {
+                eprintln!(
+                    r#"Did not find metrics for job `{}` at `{}`: {error:?}.
+Maybe it was newly added?"#,
+                    job.name, parent
+                );
+                None
+            }
+        };
+        let metrics_current = download_job_metrics(&job.name, current)?;
+        jobs.insert(
+            job.name.clone(),
+            JobMetrics { parent: metrics_parent, current: metrics_current },
+        );
+    }
+    Ok(jobs)
+}
+
+fn download_job_metrics(job_name: &str, sha: &str) -> anyhow::Result<JsonRoot> {
+    let url = get_metrics_url(job_name, sha);
+    let mut response = ureq::get(&url).call()?;
+    if !response.status().is_success() {
+        return Err(anyhow::anyhow!(
+            "Cannot fetch metrics from {url}: {}\n{}",
+            response.status(),
+            response.body_mut().read_to_string()?
+        ));
+    }
+    let data: JsonRoot = response
+        .body_mut()
+        .read_json()
+        .with_context(|| anyhow::anyhow!("cannot deserialize metrics from {url}"))?;
+    Ok(data)
+}
+
+fn get_metrics_url(job_name: &str, sha: &str) -> String {
+    let suffix = if job_name.ends_with("-alt") { "-alt" } else { "" };
+    format!("https://ci-artifacts.rust-lang.org/rustc-builds{suffix}/{sha}/metrics-{job_name}.json")
+}
+
+fn aggregate_test_diffs(
+    jobs: &HashMap<JobName, JobMetrics>,
+) -> anyhow::Result<Vec<AggregatedTestDiffs>> {
+    let mut job_diffs = vec![];
+
+    // Aggregate test suites
+    for (name, metrics) in jobs {
+        if let Some(parent) = &metrics.parent {
+            let tests_parent = aggregate_tests(parent);
+            let tests_current = aggregate_tests(&metrics.current);
+            let test_diffs = calculate_test_diffs(tests_parent, tests_current);
+            if !test_diffs.is_empty() {
+                job_diffs.push((name.clone(), test_diffs));
+            }
+        }
+    }
+
+    // Aggregate jobs with the same diff, as often the same diff will appear in many jobs
+    let job_diffs: HashMap<Vec<(Test, TestOutcomeDiff)>, Vec<String>> =
+        job_diffs.into_iter().fold(HashMap::new(), |mut acc, (job, diffs)| {
+            acc.entry(diffs).or_default().push(job);
+            acc
+        });
+
+    Ok(job_diffs
+        .into_iter()
+        .map(|(test_diffs, jobs)| AggregatedTestDiffs { jobs, test_diffs })
+        .collect())
+}
+
+fn calculate_test_diffs(
+    reference: TestSuiteData,
+    current: TestSuiteData,
+) -> Vec<(Test, TestOutcomeDiff)> {
+    let mut diffs = vec![];
+    for (test, outcome) in &current.tests {
+        match reference.tests.get(test) {
+            Some(before) => {
+                if before != outcome {
+                    diffs.push((
+                        test.clone(),
+                        TestOutcomeDiff::ChangeOutcome {
+                            before: before.clone(),
+                            after: outcome.clone(),
+                        },
+                    ));
+                }
+            }
+            None => diffs.push((test.clone(), TestOutcomeDiff::Added(outcome.clone()))),
+        }
+    }
+    for (test, outcome) in &reference.tests {
+        if !current.tests.contains_key(test) {
+            diffs.push((test.clone(), TestOutcomeDiff::Missing { before: outcome.clone() }));
+        }
+    }
+
+    diffs
+}
+
+/// Represents a difference in the outcome of tests between a base and a current commit.
+#[derive(Debug)]
+struct AggregatedTestDiffs {
+    /// All jobs that had the exact same test diffs.
+    jobs: Vec<String>,
+    test_diffs: Vec<(Test, TestOutcomeDiff)>,
+}
+
+#[derive(Eq, PartialEq, Hash, Debug)]
+enum TestOutcomeDiff {
+    ChangeOutcome { before: TestOutcome, after: TestOutcome },
+    Missing { before: TestOutcome },
+    Added(TestOutcome),
+}
+
+/// Aggregates test suite executions from all bootstrap invocations in a given CI job.
+#[derive(Default)]
+struct TestSuiteData {
+    tests: HashMap<Test, TestOutcome>,
+}
+
+#[derive(Hash, PartialEq, Eq, Debug, Clone)]
+struct Test {
+    name: String,
+}
+
+/// Extracts all tests from the passed metrics and map them to their outcomes.
+fn aggregate_tests(metrics: &JsonRoot) -> TestSuiteData {
+    let mut tests = HashMap::new();
+    let test_suites = get_test_suites(&metrics);
+    for suite in test_suites {
+        for test in &suite.tests {
+            let test_entry = Test { name: normalize_test_name(&test.name) };
+            tests.insert(test_entry, test.outcome.clone());
+        }
+    }
+    TestSuiteData { tests }
+}
+
+/// Normalizes Windows-style path delimiters to Unix-style paths.
+fn normalize_test_name(name: &str) -> String {
+    name.replace('\\', "/")
+}
+
+/// Prints test changes in Markdown format to stdout.
+fn report_test_changes(mut diffs: Vec<AggregatedTestDiffs>) {
+    println!("## Test differences");
+    if diffs.is_empty() {
+        println!("No test diffs found");
+        return;
+    }
+
+    // Sort diffs in decreasing order by diff count
+    diffs.sort_by_key(|entry| Reverse(entry.test_diffs.len()));
+
+    fn format_outcome(outcome: &TestOutcome) -> String {
+        match outcome {
+            TestOutcome::Passed => "pass".to_string(),
+            TestOutcome::Failed => "fail".to_string(),
+            TestOutcome::Ignored { ignore_reason } => {
+                let reason = match ignore_reason {
+                    Some(reason) => format!(" ({reason})"),
+                    None => String::new(),
+                };
+                format!("ignore{reason}")
+            }
+        }
+    }
+
+    fn format_diff(diff: &TestOutcomeDiff) -> String {
+        match diff {
+            TestOutcomeDiff::ChangeOutcome { before, after } => {
+                format!("{} -> {}", format_outcome(before), format_outcome(after))
+            }
+            TestOutcomeDiff::Missing { before } => {
+                format!("{} -> [missing]", format_outcome(before))
+            }
+            TestOutcomeDiff::Added(outcome) => {
+                format!("[missing] -> {}", format_outcome(outcome))
+            }
+        }
+    }
+
+    let max_diff_count = 10;
+    let max_job_count = 5;
+    let max_test_count = 10;
+
+    for diff in diffs.iter().take(max_diff_count) {
+        let mut jobs = diff.jobs.clone();
+        jobs.sort();
+
+        let jobs = jobs.iter().take(max_job_count).map(|j| format!("`{j}`")).collect::<Vec<_>>();
+
+        let extra_jobs = diff.jobs.len().saturating_sub(max_job_count);
+        let suffix = if extra_jobs > 0 {
+            format!(" (and {extra_jobs} {})", pluralize("other", extra_jobs))
+        } else {
+            String::new()
+        };
+        println!("- {}{suffix}", jobs.join(","));
+
+        let extra_tests = diff.test_diffs.len().saturating_sub(max_test_count);
+        for (test, outcome_diff) in diff.test_diffs.iter().take(max_test_count) {
+            println!("  - {}: {}", test.name, format_diff(&outcome_diff));
+        }
+        if extra_tests > 0 {
+            println!("  - (and {extra_tests} additional {})", pluralize("tests", extra_tests));
+        }
+    }
+
+    let extra_diffs = diffs.len().saturating_sub(max_diff_count);
+    if extra_diffs > 0 {
+        println!("\n(and {extra_diffs} additional {})", pluralize("diff", extra_diffs));
+    }
+}
+
+fn pluralize(text: &str, count: usize) -> String {
+    if count == 1 { text.to_string() } else { format!("{text}s") }
+}
diff --git a/src/ci/citool/src/metrics.rs b/src/ci/citool/src/metrics.rs
index 2386413ed6b..8548602b31c 100644
--- a/src/ci/citool/src/metrics.rs
+++ b/src/ci/citool/src/metrics.rs
@@ -105,17 +105,21 @@ struct TestSuiteRecord {
     failed: u64,
 }
 
+fn test_metadata_name(metadata: &TestSuiteMetadata) -> String {
+    match metadata {
+        TestSuiteMetadata::CargoPackage { crates, stage, .. } => {
+            format!("{} (stage {stage})", crates.join(", "))
+        }
+        TestSuiteMetadata::Compiletest { suite, stage, .. } => {
+            format!("{suite} (stage {stage})")
+        }
+    }
+}
+
 fn aggregate_test_suites(suites: &[&TestSuite]) -> BTreeMap<String, TestSuiteRecord> {
     let mut records: BTreeMap<String, TestSuiteRecord> = BTreeMap::new();
     for suite in suites {
-        let name = match &suite.metadata {
-            TestSuiteMetadata::CargoPackage { crates, stage, .. } => {
-                format!("{} (stage {stage})", crates.join(", "))
-            }
-            TestSuiteMetadata::Compiletest { suite, stage, .. } => {
-                format!("{suite} (stage {stage})")
-            }
-        };
+        let name = test_metadata_name(&suite.metadata);
         let record = records.entry(name).or_default();
         for test in &suite.tests {
             match test.outcome {
@@ -134,7 +138,7 @@ fn aggregate_test_suites(suites: &[&TestSuite]) -> BTreeMap<String, TestSuiteRec
     records
 }
 
-fn get_test_suites(metrics: &JsonRoot) -> Vec<&TestSuite> {
+pub fn get_test_suites(metrics: &JsonRoot) -> Vec<&TestSuite> {
     fn visit_test_suites<'a>(nodes: &'a [JsonNode], suites: &mut Vec<&'a TestSuite>) {
         for node in nodes {
             match node {
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index 6b88c26e3a2..eba55338ff8 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -591,7 +591,7 @@ auto:
       RUST_CONFIGURE_ARGS: >-
         --build=i686-pc-windows-msvc
         --host=i686-pc-windows-msvc
-        --target=i686-pc-windows-msvc,i586-pc-windows-msvc
+        --target=i686-pc-windows-msvc
         --enable-full-tools
         --enable-profiler
       SCRIPT: python x.py dist bootstrap --include-default-paths
diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md
index 2905e470fab..a6996e39822 100644
--- a/src/doc/rustc-dev-guide/src/tests/compiletest.md
+++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md
@@ -415,6 +415,10 @@ Compiletest directives like `//@ only-<target>` or `//@ ignore-<target>` are
 supported in `rmake.rs`, like in UI tests. However, revisions or building
 auxiliary via directives are not currently supported.
 
+`rmake.rs` and `run-make-support` may *not* use any nightly/unstable features,
+as they must be compilable by a stage 0 rustc that may be a beta or even stable
+rustc.
+
 #### Quickly check if `rmake.rs` tests can be compiled
 
 You can quickly check if `rmake.rs` tests can be compiled without having to
diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md
index 84440a5ab78..f78ab151b9c 100644
--- a/src/doc/rustc/src/platform-support.md
+++ b/src/doc/rustc/src/platform-support.md
@@ -162,7 +162,6 @@ target | std | notes
 [`armv7a-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare Armv7-A
 [`armv7r-none-eabi`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R
 [`armv7r-none-eabihf`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R, hardfloat
-`i586-pc-windows-msvc` | * | 32-bit Windows (original Pentium) [^x86_32-floats-x87]
 `i586-unknown-linux-gnu` | ✓ | 32-bit Linux (kernel 3.2, glibc 2.17, original Pentium) [^x86_32-floats-x87]
 `i586-unknown-linux-musl` | ✓ | 32-bit Linux (musl 1.2.3, original Pentium) [^x86_32-floats-x87]
 [`i686-linux-android`](platform-support/android.md) | ✓ | 32-bit x86 Android ([Pentium 4 plus various extensions](https://developer.android.com/ndk/guides/abis.html#x86)) [^x86_32-floats-return-ABI]
diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs
index cd5ca74f8ad..dfc8f3bc7e0 100644
--- a/src/tools/build-manifest/src/main.rs
+++ b/src/tools/build-manifest/src/main.rs
@@ -97,7 +97,6 @@ static TARGETS: &[&str] = &[
     "bpfeb-unknown-none",
     "bpfel-unknown-none",
     "i386-apple-ios",
-    "i586-pc-windows-msvc",
     "i586-unknown-linux-gnu",
     "i586-unknown-linux-musl",
     "i586-unknown-redox",
diff --git a/src/tools/compiletest/src/runtest/run_make.rs b/src/tools/compiletest/src/runtest/run_make.rs
index 8900752bd4b..073116933bd 100644
--- a/src/tools/compiletest/src/runtest/run_make.rs
+++ b/src/tools/compiletest/src/runtest/run_make.rs
@@ -105,6 +105,11 @@ impl TestCx<'_> {
             .expect("stage0 rustc is required to run run-make tests");
         let mut rustc = Command::new(&stage0_rustc);
         rustc
+            // `rmake.rs` **must** be buildable by a stable compiler, it may not use *any* unstable
+            // library or compiler features. Here, we force the stage 0 rustc to consider itself as
+            // a stable-channel compiler via `RUSTC_BOOTSTRAP=-1` to prevent *any* unstable
+            // library/compiler usages, even if stage 0 rustc is *actually* a nightly rustc.
+            .env("RUSTC_BOOTSTRAP", "-1")
             .arg("-o")
             .arg(&recipe_bin)
             // Specify library search paths for `run_make_support`.
diff --git a/src/tools/run-make-support/Cargo.toml b/src/tools/run-make-support/Cargo.toml
index 15ed03ad5c2..f9beffec750 100644
--- a/src/tools/run-make-support/Cargo.toml
+++ b/src/tools/run-make-support/Cargo.toml
@@ -14,5 +14,9 @@ build_helper = { path = "../../build_helper" }
 serde_json = "1.0"
 libc = "0.2"
 
+# FIXME(#137532): replace `os_pipe` with `anonymous_pipe` once it stabilizes and
+# reaches beta.
+os_pipe = "1.2.1"
+
 [lib]
 crate-type = ["lib", "dylib"]
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index d40ec9c4116..c846a2d53e5 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -40,6 +40,8 @@ pub use bstr;
 pub use gimli;
 pub use libc;
 pub use object;
+// FIXME(#137532): replace with std `anonymous_pipe` once it stabilizes and reaches beta.
+pub use os_pipe;
 pub use regex;
 pub use serde_json;
 pub use similar;
diff --git a/tests/assembly/stack-protector/stack-protector-target-support.rs b/tests/assembly/stack-protector/stack-protector-target-support.rs
index e9ba0f9ba89..a937256a60f 100644
--- a/tests/assembly/stack-protector/stack-protector-target-support.rs
+++ b/tests/assembly/stack-protector/stack-protector-target-support.rs
@@ -3,7 +3,7 @@
 //
 //@ add-core-stubs
 //@ revisions: r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23
-//@ revisions: r24 r25 r26 r27 r28 r29 r30 r31 r32 r33     r35 r36 r37 r38 r39 r40 r41 r42 r43 r44
+//@ revisions: r24 r25 r26 r27 r28 r29 r30 r31 r32 r33         r36 r37 r38 r39 r40 r41 r42 r43 r44
 //@ revisions: r45 r46 r47 r48 r49 r50 r51 r52 r53 r54 r55 r56 r57 r58 r59 r60 r61 r62 r63 r64 r65
 //@ revisions: r66 r67 r68 r69 r70 r71 r72 r73 r74 r75 r76 r77 r78 r79 r80 r81 r82 r83 r84 r85
 //@ assembly-output: emit-asm
@@ -73,9 +73,6 @@
 //@ [r32] needs-llvm-components: arm
 //@ [r33] compile-flags: --target armv7-unknown-linux-musleabihf
 //@ [r33] needs-llvm-components: arm
-
-//@ [r35] compile-flags: --target i586-pc-windows-msvc
-//@ [r35] needs-llvm-components: x86
 //@ [r36] compile-flags: --target i586-unknown-linux-gnu
 //@ [r36] needs-llvm-components: x86
 //@ [r37] compile-flags: --target i586-unknown-linux-musl
@@ -195,7 +192,6 @@ pub fn foo() {
     // r3: calll @__security_check_cookie
     // r7: callq __security_check_cookie
     // r13: bl __security_check_cookie
-    // r35: calll @__security_check_cookie
 
     // cuda doesn't support stack-smash protection
     // r49-NOT: __security_check_cookie
diff --git a/tests/assembly/targets/targets-pe.rs b/tests/assembly/targets/targets-pe.rs
index f895b79ba50..de29b9af502 100644
--- a/tests/assembly/targets/targets-pe.rs
+++ b/tests/assembly/targets/targets-pe.rs
@@ -25,9 +25,6 @@
 //@ revisions: bpfel_unknown_none
 //@ [bpfel_unknown_none] compile-flags: --target bpfel-unknown-none
 //@ [bpfel_unknown_none] needs-llvm-components: bpf
-//@ revisions: i586_pc_windows_msvc
-//@ [i586_pc_windows_msvc] compile-flags: --target i586-pc-windows-msvc
-//@ [i586_pc_windows_msvc] needs-llvm-components: x86
 //@ revisions: i686_pc_windows_gnu
 //@ [i686_pc_windows_gnu] compile-flags: --target i686-pc-windows-gnu
 //@ [i686_pc_windows_gnu] needs-llvm-components: x86
diff --git a/tests/run-make/broken-pipe-no-ice/rmake.rs b/tests/run-make/broken-pipe-no-ice/rmake.rs
index 54d13b62f4a..3e54b576fd4 100644
--- a/tests/run-make/broken-pipe-no-ice/rmake.rs
+++ b/tests/run-make/broken-pipe-no-ice/rmake.rs
@@ -11,12 +11,12 @@
 // Internal Compiler Error strangely, but it doesn't even go through normal diagnostic infra. Very
 // strange.
 
-#![feature(anonymous_pipe)]
-
 use std::io::Read;
 use std::process::{Command, Stdio};
 
-use run_make_support::env_var;
+// FIXME(#137532): replace `os_pipe` dependency with std `anonymous_pipe` once that stabilizes and
+// reaches beta.
+use run_make_support::{env_var, os_pipe};
 
 #[derive(Debug, PartialEq)]
 enum Binary {
@@ -25,7 +25,7 @@ enum Binary {
 }
 
 fn check_broken_pipe_handled_gracefully(bin: Binary, mut cmd: Command) {
-    let (reader, writer) = std::io::pipe().unwrap();
+    let (reader, writer) = os_pipe::pipe().unwrap();
     drop(reader); // close read-end
     cmd.stdout(writer).stderr(Stdio::piped());
 
diff --git a/tests/run-make/cross-lang-lto/rmake.rs b/tests/run-make/cross-lang-lto/rmake.rs
index dc376b561e4..50d37460d8d 100644
--- a/tests/run-make/cross-lang-lto/rmake.rs
+++ b/tests/run-make/cross-lang-lto/rmake.rs
@@ -3,8 +3,6 @@
 // -Clinker-plugin-lto.
 // See https://github.com/rust-lang/rust/pull/50000
 
-#![feature(path_file_prefix)]
-
 use std::path::PathBuf;
 
 use run_make_support::{
@@ -92,10 +90,17 @@ fn check_bitcode(instructions: LibBuild) {
         llvm_ar().extract().arg(&instructions.output).run();
     }
 
-    for object in shallow_find_files(cwd(), |path| {
-        has_prefix(path, instructions.output.file_prefix().unwrap().to_str().unwrap())
+    let objects = shallow_find_files(cwd(), |path| {
+        let mut output_path = instructions.output.clone();
+        output_path.set_extension("");
+        has_prefix(path, output_path.file_name().unwrap().to_str().unwrap())
             && has_extension(path, "o")
-    }) {
+    });
+    assert!(!objects.is_empty());
+    println!("objects: {:#?}", objects);
+
+    for object in objects {
+        println!("reading bitcode: {}", object.display());
         // All generated object files should be LLVM bitcode files - this will fail otherwise.
         llvm_bcanalyzer().input(object).run();
     }
diff --git a/tests/run-make/issue-107495-archive-permissions/rmake.rs b/tests/run-make/issue-107495-archive-permissions/rmake.rs
index f210b7c737b..228cfb0864e 100644
--- a/tests/run-make/issue-107495-archive-permissions/rmake.rs
+++ b/tests/run-make/issue-107495-archive-permissions/rmake.rs
@@ -1,12 +1,9 @@
-#![feature(rustc_private)]
-
-#[cfg(unix)]
-extern crate libc;
-
 #[cfg(unix)]
 use std::os::unix::fs::PermissionsExt;
 use std::path::Path;
 
+#[cfg(unix)]
+use run_make_support::libc;
 use run_make_support::{aux_build, rfs};
 
 fn main() {
diff --git a/tests/rustdoc-json/attrs/export_name_2021.rs b/tests/rustdoc-json/attrs/export_name_2021.rs
new file mode 100644
index 00000000000..badf124bdde
--- /dev/null
+++ b/tests/rustdoc-json/attrs/export_name_2021.rs
@@ -0,0 +1,6 @@
+//@ edition: 2021
+#![no_std]
+
+//@ is "$.index[*][?(@.name=='example')].attrs" '["#[export_name = \"altered\"]"]'
+#[export_name = "altered"]
+pub extern "C" fn example() {}
diff --git a/tests/rustdoc-json/attrs/export_name_2024.rs b/tests/rustdoc-json/attrs/export_name_2024.rs
new file mode 100644
index 00000000000..c5bb9dcc8f6
--- /dev/null
+++ b/tests/rustdoc-json/attrs/export_name_2024.rs
@@ -0,0 +1,9 @@
+//@ edition: 2024
+#![no_std]
+
+// The representation of `#[unsafe(export_name = ..)]` in rustdoc in edition 2024
+// is still `#[export_name = ..]` without the `unsafe` attribute wrapper.
+
+//@ is "$.index[*][?(@.name=='example')].attrs" '["#[export_name = \"altered\"]"]'
+#[unsafe(export_name = "altered")]
+pub extern "C" fn example() {}
diff --git a/tests/rustdoc-json/attrs/must_use.rs b/tests/rustdoc-json/attrs/must_use.rs
new file mode 100644
index 00000000000..dca73abc76a
--- /dev/null
+++ b/tests/rustdoc-json/attrs/must_use.rs
@@ -0,0 +1,9 @@
+#![no_std]
+
+//@ is "$.index[*][?(@.name=='example')].attrs" '["#[must_use]"]'
+#[must_use]
+pub fn example() -> impl Iterator<Item = i64> {}
+
+//@ is "$.index[*][?(@.name=='explicit_message')].attrs" '["#[must_use = \"does nothing if you do not use it\"]"]'
+#[must_use = "does nothing if you do not use it"]
+pub fn explicit_message() -> impl Iterator<Item = i64> {}
diff --git a/tests/rustdoc-json/attrs/no_mangle_2021.rs b/tests/rustdoc-json/attrs/no_mangle_2021.rs
new file mode 100644
index 00000000000..258542086ec
--- /dev/null
+++ b/tests/rustdoc-json/attrs/no_mangle_2021.rs
@@ -0,0 +1,6 @@
+//@ edition: 2021
+#![no_std]
+
+//@ is "$.index[*][?(@.name=='example')].attrs" '["#[no_mangle]"]'
+#[no_mangle]
+pub extern "C" fn example() {}
diff --git a/tests/rustdoc-json/attrs/no_mangle_2024.rs b/tests/rustdoc-json/attrs/no_mangle_2024.rs
new file mode 100644
index 00000000000..4c01082d045
--- /dev/null
+++ b/tests/rustdoc-json/attrs/no_mangle_2024.rs
@@ -0,0 +1,9 @@
+//@ edition: 2024
+#![no_std]
+
+// The representation of `#[unsafe(no_mangle)]` in rustdoc in edition 2024
+// is still `#[no_mangle]` without the `unsafe` attribute wrapper.
+
+//@ is "$.index[*][?(@.name=='example')].attrs" '["#[no_mangle]"]'
+#[unsafe(no_mangle)]
+pub extern "C" fn example() {}
diff --git a/tests/rustdoc-json/attrs/non_exhaustive.rs b/tests/rustdoc-json/attrs/non_exhaustive.rs
new file mode 100644
index 00000000000..5d738fc0560
--- /dev/null
+++ b/tests/rustdoc-json/attrs/non_exhaustive.rs
@@ -0,0 +1,19 @@
+#![no_std]
+
+//@ is "$.index[*][?(@.name=='MyEnum')].attrs" '["#[non_exhaustive]"]'
+#[non_exhaustive]
+pub enum MyEnum {
+    First,
+}
+
+pub enum NonExhaustiveVariant {
+    //@ is "$.index[*][?(@.name=='Variant')].attrs" '["#[non_exhaustive]"]'
+    #[non_exhaustive]
+    Variant(i64),
+}
+
+//@ is "$.index[*][?(@.name=='MyStruct')].attrs" '["#[non_exhaustive]"]'
+#[non_exhaustive]
+pub struct MyStruct {
+    pub x: i64,
+}
diff --git a/tests/ui/auto-traits/ungated-impl.rs b/tests/ui/auto-traits/ungated-impl.rs
new file mode 100644
index 00000000000..d46b4b01af9
--- /dev/null
+++ b/tests/ui/auto-traits/ungated-impl.rs
@@ -0,0 +1,7 @@
+auto trait MyTrait {}
+//~^ ERROR auto traits are experimental and possibly buggy
+
+impl<T> !MyTrait for *mut T {}
+//~^ ERROR negative trait bounds are not fully implemented
+
+fn main() {}
diff --git a/tests/ui/auto-traits/ungated-impl.stderr b/tests/ui/auto-traits/ungated-impl.stderr
new file mode 100644
index 00000000000..9d10d46a902
--- /dev/null
+++ b/tests/ui/auto-traits/ungated-impl.stderr
@@ -0,0 +1,23 @@
+error[E0658]: auto traits are experimental and possibly buggy
+  --> $DIR/ungated-impl.rs:1:1
+   |
+LL | auto trait MyTrait {}
+   | ^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #13231 <https://github.com/rust-lang/rust/issues/13231> for more information
+   = help: add `#![feature(auto_traits)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error[E0658]: negative trait bounds are not fully implemented; use marker types for now
+  --> $DIR/ungated-impl.rs:4:9
+   |
+LL | impl<T> !MyTrait for *mut T {}
+   |         ^^^^^^^^
+   |
+   = note: see issue #68318 <https://github.com/rust-lang/rust/issues/68318> for more information
+   = help: add `#![feature(negative_impls)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/triagebot.toml b/triagebot.toml
index 7114c7ab27b..305b99ad5ec 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -18,6 +18,7 @@ allow-unauthenticated = [
     "WG-*",
     "-Z*",
     "beta-nominated",
+    "CI-spurious-*",
     "const-hack",
     "llvm-*",
     "needs-fcp",