summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml9
-rw-r--r--bootstrap.example.toml5
-rw-r--r--compiler/rustc_abi/src/layout.rs82
-rw-r--r--compiler/rustc_abi/src/lib.rs13
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs2
-rw-r--r--compiler/rustc_borrowck/src/universal_regions.rs27
-rw-r--r--compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh1
-rwxr-xr-xcompiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh2
-rw-r--r--compiler/rustc_codegen_gcc/build_system/src/test.rs7
-rw-r--r--compiler/rustc_codegen_gcc/messages.ftl2
-rw-r--r--compiler/rustc_codegen_gcc/src/builder.rs15
-rw-r--r--compiler/rustc_codegen_gcc/src/errors.rs4
-rw-r--r--compiler/rustc_codegen_llvm/src/builder.rs25
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm/ffi.rs11
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs526
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs73
-rw-r--r--compiler/rustc_codegen_ssa/src/traits/builder.rs12
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs12
-rw-r--r--compiler/rustc_hir_typeck/src/lib.rs17
-rw-r--r--compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs7
-rw-r--r--compiler/rustc_infer/src/infer/at.rs2
-rw-r--r--compiler/rustc_infer/src/infer/context.rs4
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs67
-rw-r--r--compiler/rustc_infer/src/infer/outlives/obligations.rs1
-rw-r--r--compiler/rustc_infer/src/infer/snapshot/undo_log.rs8
-rw-r--r--compiler/rustc_lint/src/unused.rs4
-rw-r--r--compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp26
-rw-r--r--compiler/rustc_metadata/src/creader.rs25
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs8
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs29
-rw-r--r--compiler/rustc_middle/src/query/mod.rs1
-rw-r--r--compiler/rustc_middle/src/ty/layout.rs4
-rw-r--r--compiler/rustc_middle/src/ty/util.rs2
-rw-r--r--compiler/rustc_mir_transform/src/elaborate_drop.rs54
-rw-r--r--compiler/rustc_next_trait_solver/src/canonicalizer.rs72
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs11
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs32
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/mod.rs19
-rw-r--r--compiler/rustc_resolve/src/build_reduced_graph.rs11
-rw-r--r--compiler/rustc_resolve/src/lib.rs12
-rw-r--r--compiler/rustc_span/src/lib.rs82
-rw-r--r--compiler/rustc_target/src/spec/mod.rs1
-rw-r--r--compiler/rustc_trait_selection/src/solve/fulfill.rs29
-rw-r--r--compiler/rustc_type_ir/src/infer_ctxt.rs4
-rw-r--r--library/alloc/src/collections/linked_list.rs54
-rw-r--r--library/alloc/src/collections/vec_deque/mod.rs96
-rw-r--r--library/alloc/src/vec/mod.rs122
-rw-r--r--library/core/src/panic/location.rs42
-rw-r--r--library/coretests/tests/panic/location.rs43
-rw-r--r--library/coretests/tests/panic/location/file_a.rs15
-rw-r--r--library/coretests/tests/panic/location/file_b.rs15
-rw-r--r--library/coretests/tests/panic/location/file_c.rs11
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs30
-rw-r--r--src/bootstrap/src/core/config/config.rs13
-rw-r--r--src/bootstrap/src/core/config/toml/build.rs1
-rw-r--r--src/bootstrap/src/utils/change_tracker.rs5
-rw-r--r--src/ci/docker/host-x86_64/dist-various-2/Dockerfile4
-rw-r--r--src/ci/docker/host-x86_64/pr-check-1/Dockerfile1
-rw-r--r--src/ci/docker/host-x86_64/pr-check-2/Dockerfile3
-rw-r--r--src/ci/docker/host-x86_64/test-various/Dockerfile4
-rw-r--r--src/ci/github-actions/jobs.yml13
-rwxr-xr-xsrc/ci/scripts/free-disk-space-linux.sh265
-rw-r--r--src/ci/scripts/free-disk-space-windows.ps135
-rwxr-xr-xsrc/ci/scripts/free-disk-space.sh268
-rw-r--r--src/doc/rustc-dev-guide/src/tests/directives.md13
-rw-r--r--src/doc/rustdoc/src/unstable-features.md6
-rw-r--r--src/librustdoc/doctest.rs84
-rw-r--r--src/librustdoc/doctest/runner.rs11
-rw-r--r--src/librustdoc/formats/cache.rs30
-rw-r--r--src/tools/compiletest/src/directives.rs478
-rw-r--r--src/tools/compiletest/src/directives/auxiliary.rs41
-rw-r--r--src/tools/compiletest/src/directives/directive_names.rs289
-rw-r--r--src/tools/compiletest/src/runtest/debugger.rs8
-rw-r--r--src/tools/opt-dist/src/tests.rs2
m---------src/tools/rustc-perf0
-rw-r--r--src/tools/tidy/src/deps.rs51
-rw-r--r--src/tools/tidy/src/style.rs7
-rw-r--r--src/tools/tidy/src/target_specific_tests.rs61
-rw-r--r--tests/assembly-llvm/nvptx-safe-naming.rs6
-rw-r--r--tests/assembly-llvm/x86-return-float.rs6
-rw-r--r--tests/codegen-llvm/abi-efiapi.rs10
-rw-r--r--tests/codegen-llvm/become-musttail.rs18
-rw-r--r--tests/codegen-llvm/cast-target-abi.rs2
-rw-r--r--tests/codegen-llvm/cf-protection.rs2
-rw-r--r--tests/codegen-llvm/codemodels.rs2
-rw-r--r--tests/codegen-llvm/diverging-function-call-debuginfo.rs38
-rw-r--r--tests/codegen-llvm/ehcontguard_disabled.rs2
-rw-r--r--tests/codegen-llvm/enum/enum-discriminant-eq.rs14
-rw-r--r--tests/codegen-llvm/naked-fn/naked-functions.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/address-sanitizer-globals-tracking.rs6
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-generalized.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized-generalized.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-type-metadata-trait-objects.rs2
-rw-r--r--tests/codegen-llvm/sanitizer/memory-track-origins.rs2
-rw-r--r--tests/codegen-llvm/ub-checks.rs2
-rw-r--r--tests/crashes/139409.rs12
-rw-r--r--tests/debuginfo/unsized.rs1
-rw-r--r--tests/mir-opt/box_conditional_drop_allocator.main.ElaborateDrops.diff186
-rw-r--r--tests/mir-opt/box_conditional_drop_allocator.rs39
-rw-r--r--tests/run-make/link-cfg/rmake.rs1
-rw-r--r--tests/run-make/mismatching-target-triples/rmake.rs1
-rw-r--r--tests/run-make/musl-default-linking/rmake.rs1
-rw-r--r--tests/run-make/rustdoc-target-spec-json-path/rmake.rs1
-rw-r--r--tests/run-make/target-specs/rmake.rs1
-rw-r--r--tests/rustdoc-ui/2024-doctests-checks.rs2
-rw-r--r--tests/rustdoc-ui/2024-doctests-checks.stdout5
-rw-r--r--tests/rustdoc-ui/2024-doctests-crate-attribute.rs2
-rw-r--r--tests/rustdoc-ui/2024-doctests-crate-attribute.stdout5
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-2024.rs2
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-2024.stdout11
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-items.rs2
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-items.stdout83
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-module-2.rs2
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-module-2.stdout13
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-module.rs2
-rw-r--r--tests/rustdoc-ui/doctest/dead-code-module.stdout11
-rw-r--r--tests/rustdoc-ui/doctest/doctest-output-include-fail.rs2
-rw-r--r--tests/rustdoc-ui/doctest/doctest-output-include-fail.stdout1
-rw-r--r--tests/rustdoc-ui/doctest/edition-2024-error-output.rs2
-rw-r--r--tests/rustdoc-ui/doctest/edition-2024-error-output.stdout7
-rw-r--r--tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs2
-rw-r--r--tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout9
-rw-r--r--tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout8
-rw-r--r--tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout9
-rw-r--r--tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs2
-rw-r--r--tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout8
-rw-r--r--tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2024.stdout1
-rw-r--r--tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs2
-rw-r--r--tests/rustdoc-ui/doctest/stdout-and-stderr.rs2
-rw-r--r--tests/rustdoc-ui/doctest/stdout-and-stderr.stdout19
-rw-r--r--tests/rustdoc-ui/doctest/wrong-ast-2024.rs2
-rw-r--r--tests/rustdoc-ui/doctest/wrong-ast-2024.stdout15
-rw-r--r--tests/rustdoc/extern/extern-html-alias.rs9
-rw-r--r--tests/rustdoc/extern/extern-html-fallback.rs14
-rw-r--r--tests/ui/README.md4
-rw-r--r--tests/ui/async-await/async-drop/async-drop-box-allocator.rs134
-rw-r--r--tests/ui/async-await/async-drop/async-drop-box-allocator.run.stdout6
-rw-r--r--tests/ui/async-await/async-drop/async-drop-box.rs109
-rw-r--r--tests/ui/async-await/async-drop/async-drop-box.run.stdout6
-rw-r--r--tests/ui/borrowck/liberated-region-from-outer-closure.rs12
-rw-r--r--tests/ui/borrowck/liberated-region-from-outer-closure.stderr17
-rw-r--r--tests/ui/drop/box-conditional-drop-allocator.rs43
-rw-r--r--tests/ui/enum-discriminant/wrapping_niche.rs24
-rw-r--r--tests/ui/enum-discriminant/wrapping_niche.stderr238
-rw-r--r--tests/ui/errors/remap-path-prefix-sysroot.rs2
-rw-r--r--tests/ui/errors/wrong-target-spec.rs1
-rw-r--r--tests/ui/explicit-tail-calls/recursion-etc.rs17
-rw-r--r--tests/ui/linkage-attr/unstable-flavor.rs4
-rw-r--r--tests/ui/nll/closure-requirements/escape-argument-callee.stderr3
-rw-r--r--tests/ui/nll/closure-requirements/escape-argument.stderr2
-rw-r--r--tests/ui/nll/closure-requirements/propagate-approximated-fail-no-postdom.stderr2
-rw-r--r--tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr6
-rw-r--r--tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-comparing-against-free.stderr2
-rw-r--r--tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr5
-rw-r--r--tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr6
-rw-r--r--tests/ui/nll/closure-requirements/propagate-approximated-val.stderr2
-rw-r--r--tests/ui/nll/closure-requirements/propagate-despite-same-free-region.stderr2
-rw-r--r--tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr5
-rw-r--r--tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr6
-rw-r--r--tests/ui/nll/closure-requirements/return-wrong-bound-region.stderr2
-rw-r--r--tests/ui/nll/ty-outlives/ty-param-closure-approximate-lower-bound.stderr4
-rw-r--r--tests/ui/target_modifiers/defaults_check.rs2
-rw-r--r--tests/ui/target_modifiers/incompatible_fixedx18.rs2
-rw-r--r--tests/ui/target_modifiers/incompatible_regparm.rs2
-rw-r--r--tests/ui/target_modifiers/no_value_bool.rs2
-rw-r--r--tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.next.stderr19
-rw-r--r--tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.rs17
-rw-r--r--tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.next.stderr17
-rw-r--r--tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.rs20
-rw-r--r--tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.next.stderr19
-rw-r--r--tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.rs33
-rw-r--r--tests/ui/transmutability/enums/niche_optimization.rs12
-rw-r--r--tests/ui/warnings/hello-world.rs (renamed from tests/ui/hello_world/main.rs)0
-rw-r--r--triagebot.toml15
178 files changed, 3625 insertions, 1303 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e92afc14c20..e91ef4abb12 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -117,7 +117,7 @@ jobs:
         with:
           fetch-depth: 2
 
-      # Free up disk space on Linux by removing preinstalled components that
+      # Free up disk space on Linux and Windows by removing preinstalled components that
       # we do not need. We do this to enable some of the less resource
       # intensive jobs to run on free runners, which however also have
       # less disk space.
@@ -125,6 +125,13 @@ jobs:
         run: src/ci/scripts/free-disk-space.sh
         if: matrix.free_disk
 
+      # If we don't need to free up disk space then just report how much space we have
+      - name: print disk usage
+        run: |
+          echo "disk usage:"
+          df -h
+        if: matrix.free_disk == false
+
       # Rust Log Analyzer can't currently detect the PR number of a GitHub
       # Actions build on its own, so a hint in the log message is needed to
       # point it in the right direction.
diff --git a/bootstrap.example.toml b/bootstrap.example.toml
index ef49113b70f..31966af3301 100644
--- a/bootstrap.example.toml
+++ b/bootstrap.example.toml
@@ -465,6 +465,11 @@
 # What custom diff tool to use for displaying compiletest tests.
 #build.compiletest-diff-tool = <none>
 
+# Whether to allow `compiletest` self-tests and `compiletest`-managed test
+# suites to be run against the stage 0 rustc. This is only intended to be used
+# when the stage 0 compiler is actually built from in-tree sources.
+#build.compiletest-allow-stage0 = false
+
 # Whether to use the precompiled stage0 libtest with compiletest.
 #build.compiletest-use-stage0-libtest = true
 
diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs
index 716bb716cdb..c2405553756 100644
--- a/compiler/rustc_abi/src/layout.rs
+++ b/compiler/rustc_abi/src/layout.rs
@@ -1,3 +1,4 @@
+use std::collections::BTreeSet;
 use std::fmt::{self, Write};
 use std::ops::{Bound, Deref};
 use std::{cmp, iter};
@@ -5,7 +6,7 @@ use std::{cmp, iter};
 use rustc_hashes::Hash64;
 use rustc_index::Idx;
 use rustc_index::bit_set::BitMatrix;
-use tracing::debug;
+use tracing::{debug, trace};
 
 use crate::{
     AbiAlign, Align, BackendRepr, FieldsShape, HasDataLayout, IndexSlice, IndexVec, Integer,
@@ -766,30 +767,63 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
 
         let niche_filling_layout = calculate_niche_filling_layout();
 
-        let (mut min, mut max) = (i128::MAX, i128::MIN);
         let discr_type = repr.discr_type();
-        let bits = Integer::from_attr(dl, discr_type).size().bits();
-        for (i, mut val) in discriminants {
-            if !repr.c() && variants[i].iter().any(|f| f.is_uninhabited()) {
-                continue;
-            }
-            if discr_type.is_signed() {
-                // sign extend the raw representation to be an i128
-                val = (val << (128 - bits)) >> (128 - bits);
-            }
-            if val < min {
-                min = val;
-            }
-            if val > max {
-                max = val;
-            }
-        }
-        // We might have no inhabited variants, so pretend there's at least one.
-        if (min, max) == (i128::MAX, i128::MIN) {
-            min = 0;
-            max = 0;
-        }
-        assert!(min <= max, "discriminant range is {min}...{max}");
+        let discr_int = Integer::from_attr(dl, discr_type);
+        // Because we can only represent one range of valid values, we'll look for the
+        // largest range of invalid values and pick everything else as the range of valid
+        // values.
+
+        // First we need to sort the possible discriminant values so that we can look for the largest gap:
+        let valid_discriminants: BTreeSet<i128> = discriminants
+            .filter(|&(i, _)| repr.c() || variants[i].iter().all(|f| !f.is_uninhabited()))
+            .map(|(_, val)| {
+                if discr_type.is_signed() {
+                    // sign extend the raw representation to be an i128
+                    // FIXME: do this at the discriminant iterator creation sites
+                    discr_int.size().sign_extend(val as u128)
+                } else {
+                    val
+                }
+            })
+            .collect();
+        trace!(?valid_discriminants);
+        let discriminants = valid_discriminants.iter().copied();
+        //let next_discriminants = discriminants.clone().cycle().skip(1);
+        let next_discriminants =
+            discriminants.clone().chain(valid_discriminants.first().copied()).skip(1);
+        // Iterate over pairs of each discriminant together with the next one.
+        // Since they were sorted, we can now compute the niche sizes and pick the largest.
+        let discriminants = discriminants.zip(next_discriminants);
+        let largest_niche = discriminants.max_by_key(|&(start, end)| {
+            trace!(?start, ?end);
+            // If this is a wraparound range, the niche size is `MAX - abs(diff)`, as the diff between
+            // the two end points is actually the size of the valid discriminants.
+            let dist = if start > end {
+                // Overflow can happen for 128 bit discriminants if `end` is negative.
+                // But in that case casting to `u128` still gets us the right value,
+                // as the distance must be positive if the lhs of the subtraction is larger than the rhs.
+                let dist = start.wrapping_sub(end);
+                if discr_type.is_signed() {
+                    discr_int.signed_max().wrapping_sub(dist) as u128
+                } else {
+                    discr_int.size().unsigned_int_max() - dist as u128
+                }
+            } else {
+                // Overflow can happen for 128 bit discriminants if `start` is negative.
+                // But in that case casting to `u128` still gets us the right value,
+                // as the distance must be positive if the lhs of the subtraction is larger than the rhs.
+                end.wrapping_sub(start) as u128
+            };
+            trace!(?dist);
+            dist
+        });
+        trace!(?largest_niche);
+
+        // `max` is the last valid discriminant before the largest niche
+        // `min` is the first valid discriminant after the largest niche
+        let (max, min) = largest_niche
+            // We might have no inhabited variants, so pretend there's at least one.
+            .unwrap_or((0, 0));
         let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::repr_discr(tcx, ty, &repr, min, max);
 
         let mut align = dl.aggregate_align;
diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs
index 8e346706877..14e256b8045 100644
--- a/compiler/rustc_abi/src/lib.rs
+++ b/compiler/rustc_abi/src/lib.rs
@@ -1205,6 +1205,19 @@ impl Integer {
         }
     }
 
+    /// Returns the smallest signed value that can be represented by this Integer.
+    #[inline]
+    pub fn signed_min(self) -> i128 {
+        use Integer::*;
+        match self {
+            I8 => i8::MIN as i128,
+            I16 => i16::MIN as i128,
+            I32 => i32::MIN as i128,
+            I64 => i64::MIN as i128,
+            I128 => i128::MIN,
+        }
+    }
+
     /// Finds the smallest Integer type which can represent the signed value.
     #[inline]
     pub fn fit_signed(x: i128) -> Integer {
diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
index a10da08ddf3..fdca6b56540 100644
--- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
@@ -341,7 +341,7 @@ impl<'tcx> BorrowExplanation<'tcx> {
                                 }
                             }
                         } else if let LocalInfo::BlockTailTemp(info) = local_decl.local_info() {
-                            let sp = info.span.find_oldest_ancestor_in_same_ctxt();
+                            let sp = info.span.find_ancestor_not_from_macro().unwrap_or(info.span);
                             if info.tail_result_is_ignored {
                                 // #85581: If the first mutable borrow's scope contains
                                 // the second borrow, this suggestion isn't helpful.
diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs
index f138f265320..240c9a5223b 100644
--- a/compiler/rustc_borrowck/src/universal_regions.rs
+++ b/compiler/rustc_borrowck/src/universal_regions.rs
@@ -969,13 +969,28 @@ fn for_each_late_bound_region_in_item<'tcx>(
     mir_def_id: LocalDefId,
     mut f: impl FnMut(ty::Region<'tcx>),
 ) {
-    if !tcx.def_kind(mir_def_id).is_fn_like() {
-        return;
-    }
+    let bound_vars = match tcx.def_kind(mir_def_id) {
+        DefKind::Fn | DefKind::AssocFn => {
+            tcx.late_bound_vars(tcx.local_def_id_to_hir_id(mir_def_id))
+        }
+        // We extract the bound vars from the deduced closure signature, since we may have
+        // only deduced that a param in the closure signature is late-bound from a constraint
+        // that we discover during typeck.
+        DefKind::Closure => {
+            let ty = tcx.type_of(mir_def_id).instantiate_identity();
+            match *ty.kind() {
+                ty::Closure(_, args) => args.as_closure().sig().bound_vars(),
+                ty::CoroutineClosure(_, args) => {
+                    args.as_coroutine_closure().coroutine_closure_sig().bound_vars()
+                }
+                ty::Coroutine(_, _) | ty::Error(_) => return,
+                _ => unreachable!("unexpected type for closure: {ty}"),
+            }
+        }
+        _ => return,
+    };
 
-    for (idx, bound_var) in
-        tcx.late_bound_vars(tcx.local_def_id_to_hir_id(mir_def_id)).iter().enumerate()
-    {
+    for (idx, bound_var) in bound_vars.iter().enumerate() {
         if let ty::BoundVariableKind::Region(kind) = bound_var {
             let kind = ty::LateParamRegionKind::from_bound(ty::BoundVar::from_usize(idx), kind);
             let liberated_region = ty::Region::new_late_param(tcx, mir_def_id.to_def_id(), kind);
diff --git a/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh b/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh
index 532702bb1a4..492f4dc4452 100644
--- a/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh
+++ b/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh
@@ -33,6 +33,7 @@ rustc = "$(pwd)/../dist/bin/rustc-clif"
 cargo = "$(rustup which cargo)"
 full-bootstrap = true
 local-rebuild = true
+compiletest-allow-stage0 = true
 
 [rust]
 download-rustc = false
diff --git a/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh b/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh
index 7e356b4b462..52e02c857c7 100755
--- a/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh
+++ b/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh
@@ -166,5 +166,5 @@ index 073116933bd..c3e4578204d 100644
 EOF
 
 echo "[TEST] rustc test suite"
-COMPILETEST_FORCE_STAGE0=1 ./x.py test --stage 0 --test-args=--no-capture tests/{codegen-units,run-make,ui,incremental}
+./x.py test --stage 0 --test-args=--no-capture tests/{codegen-units,run-make,ui,incremental}
 popd
diff --git a/compiler/rustc_codegen_gcc/build_system/src/test.rs b/compiler/rustc_codegen_gcc/build_system/src/test.rs
index bc0fdd40b6e..2c8271c36a9 100644
--- a/compiler/rustc_codegen_gcc/build_system/src/test.rs
+++ b/compiler/rustc_codegen_gcc/build_system/src/test.rs
@@ -561,8 +561,6 @@ fn asm_tests(env: &Env, args: &TestArg) -> Result<(), String> {
     // FIXME: create a function "display_if_not_quiet" or something along the line.
     println!("[TEST] rustc asm test suite");
 
-    env.insert("COMPILETEST_FORCE_STAGE0".to_string(), "1".to_string());
-
     let codegen_backend_path = format!(
         "{pwd}/target/{channel}/librustc_codegen_gcc.{dylib_ext}",
         pwd = std::env::current_dir()
@@ -588,6 +586,8 @@ fn asm_tests(env: &Env, args: &TestArg) -> Result<(), String> {
             &"always",
             &"--stage",
             &"0",
+            &"--set",
+            &"build.compiletest-allow-stage0=true",
             &"tests/assembly-llvm/asm",
             &"--compiletest-rustc-args",
             &rustc_args,
@@ -1047,7 +1047,6 @@ where
 
     // FIXME: create a function "display_if_not_quiet" or something along the line.
     println!("[TEST] rustc {test_type} test suite");
-    env.insert("COMPILETEST_FORCE_STAGE0".to_string(), "1".to_string());
 
     let extra =
         if args.is_using_gcc_master_branch() { "" } else { " -Csymbol-mangling-version=v0" };
@@ -1070,6 +1069,8 @@ where
             &"always",
             &"--stage",
             &"0",
+            &"--set",
+            &"build.compiletest-allow-stage0=true",
             &format!("tests/{test_type}"),
             &"--compiletest-rustc-args",
             &rustc_args,
diff --git a/compiler/rustc_codegen_gcc/messages.ftl b/compiler/rustc_codegen_gcc/messages.ftl
index a70ac08f01a..b9b77b7d18c 100644
--- a/compiler/rustc_codegen_gcc/messages.ftl
+++ b/compiler/rustc_codegen_gcc/messages.ftl
@@ -4,3 +4,5 @@ codegen_gcc_unwinding_inline_asm =
 codegen_gcc_copy_bitcode = failed to copy bitcode to object file: {$err}
 
 codegen_gcc_lto_bitcode_from_rlib = failed to get bitcode from object file for LTO ({$gcc_err})
+
+codegen_gcc_explicit_tail_calls_unsupported = explicit tail calls with the 'become' keyword are not implemented in the GCC backend
diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs
index a4ec4bf8dea..4aee211e2ef 100644
--- a/compiler/rustc_codegen_gcc/src/builder.rs
+++ b/compiler/rustc_codegen_gcc/src/builder.rs
@@ -34,6 +34,7 @@ use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, X86Abi};
 
 use crate::common::{SignType, TypeReflection, type_is_pointer};
 use crate::context::CodegenCx;
+use crate::errors;
 use crate::intrinsic::llvm;
 use crate::type_of::LayoutGccExt;
 
@@ -1742,6 +1743,20 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
         call
     }
 
+    fn tail_call(
+        &mut self,
+        _llty: Self::Type,
+        _fn_attrs: Option<&CodegenFnAttrs>,
+        _fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
+        _llfn: Self::Value,
+        _args: &[Self::Value],
+        _funclet: Option<&Self::Funclet>,
+        _instance: Option<Instance<'tcx>>,
+    ) {
+        // FIXME: implement support for explicit tail calls like rustc_codegen_llvm.
+        self.tcx.dcx().emit_fatal(errors::ExplicitTailCallsUnsupported);
+    }
+
     fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> {
         // FIXME(antoyo): this does not zero-extend.
         self.gcc_int_cast(value, dest_typ)
diff --git a/compiler/rustc_codegen_gcc/src/errors.rs b/compiler/rustc_codegen_gcc/src/errors.rs
index 0aa16bd88b4..b252c39c0c0 100644
--- a/compiler/rustc_codegen_gcc/src/errors.rs
+++ b/compiler/rustc_codegen_gcc/src/errors.rs
@@ -19,3 +19,7 @@ pub(crate) struct CopyBitcode {
 pub(crate) struct LtoBitcodeFromRlib {
     pub gcc_err: String,
 }
+
+#[derive(Diagnostic)]
+#[diag(codegen_gcc_explicit_tail_calls_unsupported)]
+pub(crate) struct ExplicitTailCallsUnsupported;
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index f712b3b83fa..da2a153d819 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -15,6 +15,7 @@ use rustc_codegen_ssa::mir::place::PlaceRef;
 use rustc_codegen_ssa::traits::*;
 use rustc_data_structures::small_c_str::SmallCStr;
 use rustc_hir::def_id::DefId;
+use rustc_middle::bug;
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
 use rustc_middle::ty::layout::{
     FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers,
@@ -24,7 +25,7 @@ use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
 use rustc_sanitizers::{cfi, kcfi};
 use rustc_session::config::OptLevel;
 use rustc_span::Span;
-use rustc_target::callconv::FnAbi;
+use rustc_target::callconv::{FnAbi, PassMode};
 use rustc_target::spec::{HasTargetSpec, SanitizerSet, Target};
 use smallvec::SmallVec;
 use tracing::{debug, instrument};
@@ -1431,6 +1432,28 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
         call
     }
 
+    fn tail_call(
+        &mut self,
+        llty: Self::Type,
+        fn_attrs: Option<&CodegenFnAttrs>,
+        fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
+        llfn: Self::Value,
+        args: &[Self::Value],
+        funclet: Option<&Self::Funclet>,
+        instance: Option<Instance<'tcx>>,
+    ) {
+        let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet, instance);
+        llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail);
+
+        match &fn_abi.ret.mode {
+            PassMode::Ignore | PassMode::Indirect { .. } => self.ret_void(),
+            PassMode::Direct(_) | PassMode::Pair { .. } => self.ret(call),
+            mode @ PassMode::Cast { .. } => {
+                bug!("Encountered `PassMode::{mode:?}` during codegen")
+            }
+        }
+    }
+
     fn zext(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
         unsafe { llvm::LLVMBuildZExt(self.llbuilder, val, dest_ty, UNNAMED) }
     }
diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
index 0d0cb5f139e..2443194ff48 100644
--- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
@@ -97,6 +97,16 @@ pub(crate) enum ModuleFlagMergeBehavior {
 
 // Consts for the LLVM CallConv type, pre-cast to usize.
 
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[repr(C)]
+#[allow(dead_code)]
+pub(crate) enum TailCallKind {
+    None = 0,
+    Tail = 1,
+    MustTail = 2,
+    NoTail = 3,
+}
+
 /// LLVM CallingConv::ID. Should we wrap this?
 ///
 /// See <https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/CallingConv.h>
@@ -1186,6 +1196,7 @@ unsafe extern "C" {
     pub(crate) safe fn LLVMIsGlobalConstant(GlobalVar: &Value) -> Bool;
     pub(crate) safe fn LLVMSetGlobalConstant(GlobalVar: &Value, IsConstant: Bool);
     pub(crate) safe fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool);
+    pub(crate) safe fn LLVMRustSetTailCallKind(CallInst: &Value, Kind: TailCallKind);
 
     // Operations on attributes
     pub(crate) fn LLVMCreateStringAttribute(
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index e187331c696..5c54bce6e03 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -4,12 +4,12 @@ use rustc_abi::{Align, ExternAbi};
 use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode};
 use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr};
 use rustc_attr_data_structures::{
-    AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr, UsedBy, find_attr,
+    AttributeKind, InlineAttr, InstructionSetAttr, UsedBy, find_attr,
 };
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
 use rustc_hir::weak_lang_items::WEAK_LANG_ITEMS;
-use rustc_hir::{self as hir, LangItem, lang_items};
+use rustc_hir::{self as hir, Attribute, LangItem, lang_items};
 use rustc_middle::middle::codegen_fn_attrs::{
     CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry,
 };
@@ -53,77 +53,196 @@ fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage {
     }
 }
 
-fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
-    if cfg!(debug_assertions) {
-        let def_kind = tcx.def_kind(did);
-        assert!(
-            def_kind.has_codegen_attrs(),
-            "unexpected `def_kind` in `codegen_fn_attrs`: {def_kind:?}",
-        );
+/// In some cases, attributes are only valid on functions, but it's the `check_attr`
+/// pass that checks that they aren't used anywhere else, rather than this module.
+/// In these cases, we bail from performing further checks that are only meaningful for
+/// functions (such as calling `fn_sig`, which ICEs if given a non-function). We also
+/// report a delayed bug, just in case `check_attr` isn't doing its job.
+fn try_fn_sig<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    did: LocalDefId,
+    attr_span: Span,
+) -> Option<ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>>> {
+    use DefKind::*;
+
+    let def_kind = tcx.def_kind(did);
+    if let Fn | AssocFn | Variant | Ctor(..) = def_kind {
+        Some(tcx.fn_sig(did))
+    } else {
+        tcx.dcx().span_delayed_bug(attr_span, "this attribute can only be applied to functions");
+        None
     }
+}
 
-    let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(did));
-    let mut codegen_fn_attrs = CodegenFnAttrs::new();
-    if tcx.should_inherit_track_caller(did) {
-        codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER;
+// FIXME(jdonszelmann): remove when instruction_set becomes a parsed attr
+fn parse_instruction_set_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<InstructionSetAttr> {
+    let list = attr.meta_item_list()?;
+
+    match &list[..] {
+        [MetaItemInner::MetaItem(set)] => {
+            let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
+            match segments.as_slice() {
+                [sym::arm, sym::a32 | sym::t32] if !tcx.sess.target.has_thumb_interworking => {
+                    tcx.dcx().emit_err(errors::UnsupportedInstructionSet { span: attr.span() });
+                    None
+                }
+                [sym::arm, sym::a32] => Some(InstructionSetAttr::ArmA32),
+                [sym::arm, sym::t32] => Some(InstructionSetAttr::ArmT32),
+                _ => {
+                    tcx.dcx().emit_err(errors::InvalidInstructionSet { span: attr.span() });
+                    None
+                }
+            }
+        }
+        [] => {
+            tcx.dcx().emit_err(errors::BareInstructionSet { span: attr.span() });
+            None
+        }
+        _ => {
+            tcx.dcx().emit_err(errors::MultipleInstructionSet { span: attr.span() });
+            None
+        }
+    }
+}
+
+// FIXME(jdonszelmann): remove when linkage becomes a parsed attr
+fn parse_linkage_attr(tcx: TyCtxt<'_>, did: LocalDefId, attr: &Attribute) -> Option<Linkage> {
+    let val = attr.value_str()?;
+    let linkage = linkage_by_name(tcx, did, val.as_str());
+    Some(linkage)
+}
+
+// FIXME(jdonszelmann): remove when no_sanitize becomes a parsed attr
+fn parse_no_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<SanitizerSet> {
+    let list = attr.meta_item_list()?;
+    let mut sanitizer_set = SanitizerSet::empty();
+
+    for item in list.iter() {
+        match item.name() {
+            Some(sym::address) => {
+                sanitizer_set |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
+            }
+            Some(sym::cfi) => sanitizer_set |= SanitizerSet::CFI,
+            Some(sym::kcfi) => sanitizer_set |= SanitizerSet::KCFI,
+            Some(sym::memory) => sanitizer_set |= SanitizerSet::MEMORY,
+            Some(sym::memtag) => sanitizer_set |= SanitizerSet::MEMTAG,
+            Some(sym::shadow_call_stack) => sanitizer_set |= SanitizerSet::SHADOWCALLSTACK,
+            Some(sym::thread) => sanitizer_set |= SanitizerSet::THREAD,
+            Some(sym::hwaddress) => sanitizer_set |= SanitizerSet::HWADDRESS,
+            _ => {
+                tcx.dcx().emit_err(errors::InvalidNoSanitize { span: item.span() });
+            }
+        }
     }
 
+    Some(sanitizer_set)
+}
+
+// FIXME(jdonszelmann): remove when patchable_function_entry becomes a parsed attr
+fn parse_patchable_function_entry(
+    tcx: TyCtxt<'_>,
+    attr: &Attribute,
+) -> Option<PatchableFunctionEntry> {
+    attr.meta_item_list().and_then(|l| {
+        let mut prefix = None;
+        let mut entry = None;
+        for item in l {
+            let Some(meta_item) = item.meta_item() else {
+                tcx.dcx().emit_err(errors::ExpectedNameValuePair { span: item.span() });
+                continue;
+            };
+
+            let Some(name_value_lit) = meta_item.name_value_literal() else {
+                tcx.dcx().emit_err(errors::ExpectedNameValuePair { span: item.span() });
+                continue;
+            };
+
+            let attrib_to_write = match meta_item.name() {
+                Some(sym::prefix_nops) => &mut prefix,
+                Some(sym::entry_nops) => &mut entry,
+                _ => {
+                    tcx.dcx().emit_err(errors::UnexpectedParameterName {
+                        span: item.span(),
+                        prefix_nops: sym::prefix_nops,
+                        entry_nops: sym::entry_nops,
+                    });
+                    continue;
+                }
+            };
+
+            let rustc_ast::LitKind::Int(val, _) = name_value_lit.kind else {
+                tcx.dcx().emit_err(errors::InvalidLiteralValue { span: name_value_lit.span });
+                continue;
+            };
+
+            let Ok(val) = val.get().try_into() else {
+                tcx.dcx().emit_err(errors::OutOfRangeInteger { span: name_value_lit.span });
+                continue;
+            };
+
+            *attrib_to_write = Some(val);
+        }
+
+        if let (None, None) = (prefix, entry) {
+            tcx.dcx().span_err(attr.span(), "must specify at least one parameter");
+        }
+
+        Some(PatchableFunctionEntry::from_prefix_and_entry(prefix.unwrap_or(0), entry.unwrap_or(0)))
+    })
+}
+
+/// Spans that are collected when processing built-in attributes,
+/// that are useful for emitting diagnostics later.
+#[derive(Default)]
+struct InterestingAttributeDiagnosticSpans {
+    link_ordinal: Option<Span>,
+    no_sanitize: Option<Span>,
+    inline: Option<Span>,
+    no_mangle: Option<Span>,
+}
+
+/// Process the builtin attrs ([`hir::Attribute`]) on the item.
+/// Many of them directly translate to codegen attrs.
+fn process_builtin_attrs(
+    tcx: TyCtxt<'_>,
+    did: LocalDefId,
+    attrs: &[Attribute],
+    codegen_fn_attrs: &mut CodegenFnAttrs,
+) -> InterestingAttributeDiagnosticSpans {
+    let mut interesting_spans = InterestingAttributeDiagnosticSpans::default();
+    let rust_target_features = tcx.rust_target_features(LOCAL_CRATE);
+
     // If our rustc version supports autodiff/enzyme, then we call our handler
     // to check for any `#[rustc_autodiff(...)]` attributes.
+    // FIXME(jdonszelmann): merge with loop below
     if cfg!(llvm_enzyme) {
         let ad = autodiff_attrs(tcx, did.into());
         codegen_fn_attrs.autodiff_item = ad;
     }
 
-    // When `no_builtins` is applied at the crate level, we should add the
-    // `no-builtins` attribute to each function to ensure it takes effect in LTO.
-    let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID);
-    let no_builtins = attr::contains_name(crate_attrs, sym::no_builtins);
-    if no_builtins {
-        codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_BUILTINS;
-    }
-
-    let rust_target_features = tcx.rust_target_features(LOCAL_CRATE);
-
-    let mut link_ordinal_span = None;
-    let mut no_sanitize_span = None;
-
     for attr in attrs.iter() {
-        // In some cases, attribute are only valid on functions, but it's the `check_attr`
-        // pass that check that they aren't used anywhere else, rather this module.
-        // In these cases, we bail from performing further checks that are only meaningful for
-        // functions (such as calling `fn_sig`, which ICEs if given a non-function). We also
-        // report a delayed bug, just in case `check_attr` isn't doing its job.
-        let fn_sig = |attr_span| {
-            use DefKind::*;
-
-            let def_kind = tcx.def_kind(did);
-            if let Fn | AssocFn | Variant | Ctor(..) = def_kind {
-                Some(tcx.fn_sig(did))
-            } else {
-                tcx.dcx()
-                    .span_delayed_bug(attr_span, "this attribute can only be applied to functions");
-                None
-            }
-        };
-
         if let hir::Attribute::Parsed(p) = attr {
             match p {
                 AttributeKind::Cold(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD,
                 AttributeKind::ExportName { name, .. } => {
-                    codegen_fn_attrs.export_name = Some(*name);
+                    codegen_fn_attrs.export_name = Some(*name)
+                }
+                AttributeKind::Inline(inline, span) => {
+                    codegen_fn_attrs.inline = *inline;
+                    interesting_spans.inline = Some(*span);
                 }
                 AttributeKind::Naked(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED,
                 AttributeKind::Align { align, .. } => codegen_fn_attrs.alignment = Some(*align),
                 AttributeKind::LinkName { name, .. } => codegen_fn_attrs.link_name = Some(*name),
                 AttributeKind::LinkOrdinal { ordinal, span } => {
                     codegen_fn_attrs.link_ordinal = Some(*ordinal);
-                    link_ordinal_span = Some(*span);
+                    interesting_spans.link_ordinal = Some(*span);
                 }
                 AttributeKind::LinkSection { name, .. } => {
                     codegen_fn_attrs.link_section = Some(*name)
                 }
                 AttributeKind::NoMangle(attr_span) => {
+                    interesting_spans.no_mangle = Some(*attr_span);
                     if tcx.opt_item_name(did.to_def_id()).is_some() {
                         codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE;
                     } else {
@@ -137,6 +256,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
                         });
                     }
                 }
+                AttributeKind::Optimize(optimize, _) => codegen_fn_attrs.optimize = *optimize,
                 AttributeKind::TargetFeature(features, attr_span) => {
                     let Some(sig) = tcx.hir_node_by_def_id(did).fn_sig() else {
                         tcx.dcx().span_delayed_bug(*attr_span, "target_feature applied to non-fn");
@@ -184,7 +304,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
                     let is_closure = tcx.is_closure_like(did.to_def_id());
 
                     if !is_closure
-                        && let Some(fn_sig) = fn_sig(*attr_span)
+                        && let Some(fn_sig) = try_fn_sig(tcx, did, *attr_span)
                         && fn_sig.skip_binder().abi() != ExternAbi::Rust
                     {
                         tcx.dcx().emit_err(errors::RequiresRustAbi { span: *attr_span });
@@ -232,155 +352,49 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
             }
             sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL,
             sym::linkage => {
-                if let Some(val) = attr.value_str() {
-                    let linkage = Some(linkage_by_name(tcx, did, val.as_str()));
-                    if tcx.is_foreign_item(did) {
-                        codegen_fn_attrs.import_linkage = linkage;
-
-                        if tcx.is_mutable_static(did.into()) {
-                            let mut diag = tcx.dcx().struct_span_err(
-                                attr.span(),
-                                "extern mutable statics are not allowed with `#[linkage]`",
-                            );
-                            diag.note(
-                                "marking the extern static mutable would allow changing which \
-                                 symbol the static references rather than make the target of the \
-                                 symbol mutable",
-                            );
-                            diag.emit();
-                        }
-                    } else {
-                        codegen_fn_attrs.linkage = linkage;
+                let linkage = parse_linkage_attr(tcx, did, attr);
+
+                if tcx.is_foreign_item(did) {
+                    codegen_fn_attrs.import_linkage = linkage;
+
+                    if tcx.is_mutable_static(did.into()) {
+                        let mut diag = tcx.dcx().struct_span_err(
+                            attr.span(),
+                            "extern mutable statics are not allowed with `#[linkage]`",
+                        );
+                        diag.note(
+                            "marking the extern static mutable would allow changing which \
+                            symbol the static references rather than make the target of the \
+                            symbol mutable",
+                        );
+                        diag.emit();
                     }
+                } else {
+                    codegen_fn_attrs.linkage = linkage;
                 }
             }
             sym::no_sanitize => {
-                no_sanitize_span = Some(attr.span());
-                if let Some(list) = attr.meta_item_list() {
-                    for item in list.iter() {
-                        match item.name() {
-                            Some(sym::address) => {
-                                codegen_fn_attrs.no_sanitize |=
-                                    SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
-                            }
-                            Some(sym::cfi) => codegen_fn_attrs.no_sanitize |= SanitizerSet::CFI,
-                            Some(sym::kcfi) => codegen_fn_attrs.no_sanitize |= SanitizerSet::KCFI,
-                            Some(sym::memory) => {
-                                codegen_fn_attrs.no_sanitize |= SanitizerSet::MEMORY
-                            }
-                            Some(sym::memtag) => {
-                                codegen_fn_attrs.no_sanitize |= SanitizerSet::MEMTAG
-                            }
-                            Some(sym::shadow_call_stack) => {
-                                codegen_fn_attrs.no_sanitize |= SanitizerSet::SHADOWCALLSTACK
-                            }
-                            Some(sym::thread) => {
-                                codegen_fn_attrs.no_sanitize |= SanitizerSet::THREAD
-                            }
-                            Some(sym::hwaddress) => {
-                                codegen_fn_attrs.no_sanitize |= SanitizerSet::HWADDRESS
-                            }
-                            _ => {
-                                tcx.dcx().emit_err(errors::InvalidNoSanitize { span: item.span() });
-                            }
-                        }
-                    }
-                }
+                interesting_spans.no_sanitize = Some(attr.span());
+                codegen_fn_attrs.no_sanitize |=
+                    parse_no_sanitize_attr(tcx, attr).unwrap_or_default();
             }
             sym::instruction_set => {
-                codegen_fn_attrs.instruction_set =
-                    attr.meta_item_list().and_then(|l| match &l[..] {
-                        [MetaItemInner::MetaItem(set)] => {
-                            let segments =
-                                set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
-                            match segments.as_slice() {
-                                [sym::arm, sym::a32 | sym::t32]
-                                    if !tcx.sess.target.has_thumb_interworking =>
-                                {
-                                    tcx.dcx().emit_err(errors::UnsupportedInstructionSet {
-                                        span: attr.span(),
-                                    });
-                                    None
-                                }
-                                [sym::arm, sym::a32] => Some(InstructionSetAttr::ArmA32),
-                                [sym::arm, sym::t32] => Some(InstructionSetAttr::ArmT32),
-                                _ => {
-                                    tcx.dcx().emit_err(errors::InvalidInstructionSet {
-                                        span: attr.span(),
-                                    });
-                                    None
-                                }
-                            }
-                        }
-                        [] => {
-                            tcx.dcx().emit_err(errors::BareInstructionSet { span: attr.span() });
-                            None
-                        }
-                        _ => {
-                            tcx.dcx()
-                                .emit_err(errors::MultipleInstructionSet { span: attr.span() });
-                            None
-                        }
-                    })
+                codegen_fn_attrs.instruction_set = parse_instruction_set_attr(tcx, attr)
             }
             sym::patchable_function_entry => {
-                codegen_fn_attrs.patchable_function_entry = attr.meta_item_list().and_then(|l| {
-                    let mut prefix = None;
-                    let mut entry = None;
-                    for item in l {
-                        let Some(meta_item) = item.meta_item() else {
-                            tcx.dcx().emit_err(errors::ExpectedNameValuePair { span: item.span() });
-                            continue;
-                        };
-
-                        let Some(name_value_lit) = meta_item.name_value_literal() else {
-                            tcx.dcx().emit_err(errors::ExpectedNameValuePair { span: item.span() });
-                            continue;
-                        };
-
-                        let attrib_to_write = match meta_item.name() {
-                            Some(sym::prefix_nops) => &mut prefix,
-                            Some(sym::entry_nops) => &mut entry,
-                            _ => {
-                                tcx.dcx().emit_err(errors::UnexpectedParameterName {
-                                    span: item.span(),
-                                    prefix_nops: sym::prefix_nops,
-                                    entry_nops: sym::entry_nops,
-                                });
-                                continue;
-                            }
-                        };
-
-                        let rustc_ast::LitKind::Int(val, _) = name_value_lit.kind else {
-                            tcx.dcx().emit_err(errors::InvalidLiteralValue {
-                                span: name_value_lit.span,
-                            });
-                            continue;
-                        };
-
-                        let Ok(val) = val.get().try_into() else {
-                            tcx.dcx()
-                                .emit_err(errors::OutOfRangeInteger { span: name_value_lit.span });
-                            continue;
-                        };
-
-                        *attrib_to_write = Some(val);
-                    }
-
-                    if let (None, None) = (prefix, entry) {
-                        tcx.dcx().span_err(attr.span(), "must specify at least one parameter");
-                    }
-
-                    Some(PatchableFunctionEntry::from_prefix_and_entry(
-                        prefix.unwrap_or(0),
-                        entry.unwrap_or(0),
-                    ))
-                })
+                codegen_fn_attrs.patchable_function_entry =
+                    parse_patchable_function_entry(tcx, attr);
             }
             _ => {}
         }
     }
 
+    interesting_spans
+}
+
+/// Applies overrides for codegen fn attrs. These often have a specific reason why they're necessary.
+/// Please comment why when adding a new one!
+fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut CodegenFnAttrs) {
     // Apply the minimum function alignment here. This ensures that a function's alignment is
     // determined by the `-C` flags of the crate it is defined in, not the `-C` flags of the crate
     // it happens to be codegen'd (or const-eval'd) in.
@@ -390,15 +404,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
     // On trait methods, inherit the `#[align]` of the trait's method prototype.
     codegen_fn_attrs.alignment = Ord::max(codegen_fn_attrs.alignment, tcx.inherited_align(did));
 
-    let inline_span;
-    (codegen_fn_attrs.inline, inline_span) = if let Some((inline_attr, span)) =
-        find_attr!(attrs, AttributeKind::Inline(i, span) => (*i, *span))
-    {
-        (inline_attr, Some(span))
-    } else {
-        (InlineAttr::None, None)
-    };
-
     // naked function MUST NOT be inlined! This attribute is required for the rust compiler itself,
     // but not for the code generation backend because at that point the naked function will just be
     // a declaration, with a definition provided in global assembly.
@@ -406,9 +411,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         codegen_fn_attrs.inline = InlineAttr::Never;
     }
 
-    codegen_fn_attrs.optimize =
-        find_attr!(attrs, AttributeKind::Optimize(i, _) => *i).unwrap_or(OptimizeAttr::Default);
-
     // #73631: closures inherit `#[target_feature]` annotations
     //
     // If this closure is marked `#[inline(always)]`, simply skip adding `#[target_feature]`.
@@ -431,6 +433,26 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         }
     }
 
+    // When `no_builtins` is applied at the crate level, we should add the
+    // `no-builtins` attribute to each function to ensure it takes effect in LTO.
+    let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID);
+    let no_builtins = attr::contains_name(crate_attrs, sym::no_builtins);
+    if no_builtins {
+        codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_BUILTINS;
+    }
+
+    // inherit track-caller properly
+    if tcx.should_inherit_track_caller(did) {
+        codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER;
+    }
+}
+
+fn check_result(
+    tcx: TyCtxt<'_>,
+    did: LocalDefId,
+    interesting_spans: InterestingAttributeDiagnosticSpans,
+    codegen_fn_attrs: &CodegenFnAttrs,
+) {
     // If a function uses `#[target_feature]` it can't be inlined into general
     // purpose functions as they wouldn't have the right target features
     // enabled. For that reason we also forbid `#[inline(always)]` as it can't be
@@ -446,14 +468,16 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
     // llvm/llvm-project#70563).
     if !codegen_fn_attrs.target_features.is_empty()
         && matches!(codegen_fn_attrs.inline, InlineAttr::Always)
-        && let Some(span) = inline_span
+        && let Some(span) = interesting_spans.inline
     {
         tcx.dcx().span_err(span, "cannot use `#[inline(always)]` with `#[target_feature]`");
     }
 
+    // warn that inline has no effect when no_sanitize is present
     if !codegen_fn_attrs.no_sanitize.is_empty()
         && codegen_fn_attrs.inline.always()
-        && let (Some(no_sanitize_span), Some(inline_span)) = (no_sanitize_span, inline_span)
+        && let (Some(no_sanitize_span), Some(inline_span)) =
+            (interesting_spans.no_sanitize, interesting_spans.inline)
     {
         let hir_id = tcx.local_def_id_to_hir_id(did);
         tcx.node_span_lint(lint::builtin::INLINE_NO_SANITIZE, hir_id, no_sanitize_span, |lint| {
@@ -462,6 +486,47 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         })
     }
 
+    // error when specifying link_name together with link_ordinal
+    if let Some(_) = codegen_fn_attrs.link_name
+        && let Some(_) = codegen_fn_attrs.link_ordinal
+    {
+        let msg = "cannot use `#[link_name]` with `#[link_ordinal]`";
+        if let Some(span) = interesting_spans.link_ordinal {
+            tcx.dcx().span_err(span, msg);
+        } else {
+            tcx.dcx().err(msg);
+        }
+    }
+
+    if let Some(features) = check_tied_features(
+        tcx.sess,
+        &codegen_fn_attrs
+            .target_features
+            .iter()
+            .map(|features| (features.name.as_str(), true))
+            .collect(),
+    ) {
+        let span =
+            find_attr!(tcx.get_all_attrs(did), AttributeKind::TargetFeature(_, span) => *span)
+                .unwrap_or_else(|| tcx.def_span(did));
+
+        tcx.dcx()
+            .create_err(errors::TargetFeatureDisableOrEnable {
+                features,
+                span: Some(span),
+                missing_features: Some(errors::MissingFeatures),
+            })
+            .emit();
+    }
+}
+
+fn handle_lang_items(
+    tcx: TyCtxt<'_>,
+    did: LocalDefId,
+    interesting_spans: &InterestingAttributeDiagnosticSpans,
+    attrs: &[Attribute],
+    codegen_fn_attrs: &mut CodegenFnAttrs,
+) {
     // Weak lang items have the same semantics as "std internal" symbols in the
     // sense that they're preserved through all our LTO passes and only
     // strippable by the linker.
@@ -478,20 +543,17 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
             codegen_fn_attrs.link_name = Some(link_name);
         }
     }
-    check_link_name_xor_ordinal(tcx, &codegen_fn_attrs, link_ordinal_span);
 
+    // error when using no_mangle on a lang item item
     if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
         && codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE)
     {
-        let no_mangle_span =
-            find_attr!(attrs, AttributeKind::NoMangle(no_mangle_span) => *no_mangle_span)
-                .unwrap_or_default();
         let lang_item =
             lang_items::extract(attrs).map_or(None, |(name, _span)| LangItem::from_name(name));
         let mut err = tcx
             .dcx()
             .struct_span_err(
-                no_mangle_span,
+                interesting_spans.no_mangle.unwrap_or_default(),
                 "`#[no_mangle]` cannot be used on internal language items",
             )
             .with_note("Rustc requires this item to have a specific mangled name.")
@@ -508,28 +570,33 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         }
         err.emit();
     }
+}
 
-    if let Some(features) = check_tied_features(
-        tcx.sess,
-        &codegen_fn_attrs
-            .target_features
-            .iter()
-            .map(|features| (features.name.as_str(), true))
-            .collect(),
-    ) {
-        let span =
-            find_attr!(tcx.get_all_attrs(did), AttributeKind::TargetFeature(_, span) => *span)
-                .unwrap_or_else(|| tcx.def_span(did));
-
-        tcx.dcx()
-            .create_err(errors::TargetFeatureDisableOrEnable {
-                features,
-                span: Some(span),
-                missing_features: Some(errors::MissingFeatures),
-            })
-            .emit();
+/// Generate the [`CodegenFnAttrs`] for an item (identified by the [`LocalDefId`]).
+///
+/// This happens in 4 stages:
+/// - apply built-in attributes that directly translate to codegen attributes.
+/// - handle lang items. These have special codegen attrs applied to them.
+/// - apply overrides, like minimum requirements for alignment and other settings that don't rely directly the built-in attrs on the item.
+///   overrides come after applying built-in attributes since they may only apply when certain attributes were already set in the stage before.
+/// - check that the result is valid. There's various ways in which this may not be the case, such as certain combinations of attrs.
+fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
+    if cfg!(debug_assertions) {
+        let def_kind = tcx.def_kind(did);
+        assert!(
+            def_kind.has_codegen_attrs(),
+            "unexpected `def_kind` in `codegen_fn_attrs`: {def_kind:?}",
+        );
     }
 
+    let mut codegen_fn_attrs = CodegenFnAttrs::new();
+    let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(did));
+
+    let interesting_spans = process_builtin_attrs(tcx, did, attrs, &mut codegen_fn_attrs);
+    handle_lang_items(tcx, did, &interesting_spans, attrs, &mut codegen_fn_attrs);
+    apply_overrides(tcx, did, &mut codegen_fn_attrs);
+    check_result(tcx, did, interesting_spans, &codegen_fn_attrs);
+
     codegen_fn_attrs
 }
 
@@ -555,27 +622,12 @@ fn inherited_align<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Align> {
     tcx.codegen_fn_attrs(opt_trait_item(tcx, def_id)?).alignment
 }
 
-fn check_link_name_xor_ordinal(
-    tcx: TyCtxt<'_>,
-    codegen_fn_attrs: &CodegenFnAttrs,
-    inline_span: Option<Span>,
-) {
-    if codegen_fn_attrs.link_name.is_none() || codegen_fn_attrs.link_ordinal.is_none() {
-        return;
-    }
-    let msg = "cannot use `#[link_name]` with `#[link_ordinal]`";
-    if let Some(span) = inline_span {
-        tcx.dcx().span_err(span, msg);
-    } else {
-        tcx.dcx().err(msg);
-    }
-}
-
 /// We now check the #\[rustc_autodiff\] attributes which we generated from the #[autodiff(...)]
 /// macros. There are two forms. The pure one without args to mark primal functions (the functions
 /// being differentiated). The other form is #[rustc_autodiff(Mode, ActivityList)] on top of the
 /// placeholder functions. We wrote the rustc_autodiff attributes ourself, so this should never
 /// panic, unless we introduced a bug when parsing the autodiff macro.
+//FIXME(jdonszelmann): put in the main loop. No need to have two..... :/ Let's do that when we make autodiff parsed.
 fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option<AutoDiffAttrs> {
     let attrs = tcx.get_attrs(id, sym::rustc_autodiff);
 
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index bde63fd501a..e96590441fa 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -35,6 +35,14 @@ enum MergingSucc {
     True,
 }
 
+/// Indicates to the call terminator codegen whether a cal
+/// is a normal call or an explicit tail call.
+#[derive(Debug, PartialEq)]
+enum CallKind {
+    Normal,
+    Tail,
+}
+
 /// Used by `FunctionCx::codegen_terminator` for emitting common patterns
 /// e.g., creating a basic block, calling a function, etc.
 struct TerminatorCodegenHelper<'tcx> {
@@ -160,6 +168,7 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
         mut unwind: mir::UnwindAction,
         lifetime_ends_after_call: &[(Bx::Value, Size)],
         instance: Option<Instance<'tcx>>,
+        kind: CallKind,
         mergeable_succ: bool,
     ) -> MergingSucc {
         let tcx = bx.tcx();
@@ -221,6 +230,11 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
             }
         };
 
+        if kind == CallKind::Tail {
+            bx.tail_call(fn_ty, fn_attrs, fn_abi, fn_ptr, llargs, self.funclet(fx), instance);
+            return MergingSucc::False;
+        }
+
         if let Some(unwind_block) = unwind_block {
             let ret_llbb = if let Some((_, target)) = destination {
                 fx.llbb(target)
@@ -659,6 +673,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             unwind,
             &[],
             Some(drop_instance),
+            CallKind::Normal,
             !maybe_null && mergeable_succ,
         )
     }
@@ -747,8 +762,19 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         let (fn_abi, llfn, instance) = common::build_langcall(bx, span, lang_item);
 
         // Codegen the actual panic invoke/call.
-        let merging_succ =
-            helper.do_call(self, bx, fn_abi, llfn, &args, None, unwind, &[], Some(instance), false);
+        let merging_succ = helper.do_call(
+            self,
+            bx,
+            fn_abi,
+            llfn,
+            &args,
+            None,
+            unwind,
+            &[],
+            Some(instance),
+            CallKind::Normal,
+            false,
+        );
         assert_eq!(merging_succ, MergingSucc::False);
         MergingSucc::False
     }
@@ -777,6 +803,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             mir::UnwindAction::Unreachable,
             &[],
             Some(instance),
+            CallKind::Normal,
             false,
         );
         assert_eq!(merging_succ, MergingSucc::False);
@@ -845,6 +872,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             unwind,
             &[],
             Some(instance),
+            CallKind::Normal,
             mergeable_succ,
         ))
     }
@@ -860,6 +888,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         target: Option<mir::BasicBlock>,
         unwind: mir::UnwindAction,
         fn_span: Span,
+        kind: CallKind,
         mergeable_succ: bool,
     ) -> MergingSucc {
         let source_info = mir::SourceInfo { span: fn_span, ..terminator.source_info };
@@ -1003,8 +1032,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         // We still need to call `make_return_dest` even if there's no `target`, since
         // `fn_abi.ret` could be `PassMode::Indirect`, even if it is uninhabited,
         // and `make_return_dest` adds the return-place indirect pointer to `llargs`.
-        let return_dest = self.make_return_dest(bx, destination, &fn_abi.ret, &mut llargs);
-        let destination = target.map(|target| (return_dest, target));
+        let destination = match kind {
+            CallKind::Normal => {
+                let return_dest = self.make_return_dest(bx, destination, &fn_abi.ret, &mut llargs);
+                target.map(|target| (return_dest, target))
+            }
+            CallKind::Tail => None,
+        };
 
         // Split the rust-call tupled arguments off.
         let (first_args, untuple) = if sig.abi() == ExternAbi::RustCall
@@ -1020,6 +1054,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         // to generate `lifetime_end` when the call returns.
         let mut lifetime_ends_after_call: Vec<(Bx::Value, Size)> = Vec::new();
         'make_args: for (i, arg) in first_args.iter().enumerate() {
+            if kind == CallKind::Tail && matches!(fn_abi.args[i].mode, PassMode::Indirect { .. }) {
+                // FIXME: https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
+                span_bug!(
+                    fn_span,
+                    "arguments using PassMode::Indirect are currently not supported for tail calls"
+                );
+            }
+
             let mut op = self.codegen_operand(bx, &arg.node);
 
             if let (0, Some(ty::InstanceKind::Virtual(_, idx))) = (i, instance.map(|i| i.def)) {
@@ -1147,6 +1189,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
             unwind,
             &lifetime_ends_after_call,
             instance,
+            kind,
             mergeable_succ,
         )
     }
@@ -1388,15 +1431,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 target,
                 unwind,
                 fn_span,
+                CallKind::Normal,
                 mergeable_succ(),
             ),
-            mir::TerminatorKind::TailCall { .. } => {
-                // FIXME(explicit_tail_calls): implement tail calls in ssa backend
-                span_bug!(
-                    terminator.source_info.span,
-                    "`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
-                )
-            }
+            mir::TerminatorKind::TailCall { ref func, ref args, fn_span } => self
+                .codegen_call_terminator(
+                    helper,
+                    bx,
+                    terminator,
+                    func,
+                    args,
+                    mir::Place::from(mir::RETURN_PLACE),
+                    None,
+                    mir::UnwindAction::Unreachable,
+                    fn_span,
+                    CallKind::Tail,
+                    mergeable_succ(),
+                ),
             mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => {
                 bug!("coroutine ops in codegen")
             }
diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs
index 979456a6ba7..4b18146863b 100644
--- a/compiler/rustc_codegen_ssa/src/traits/builder.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs
@@ -595,6 +595,18 @@ pub trait BuilderMethods<'a, 'tcx>:
         funclet: Option<&Self::Funclet>,
         instance: Option<Instance<'tcx>>,
     ) -> Self::Value;
+
+    fn tail_call(
+        &mut self,
+        llty: Self::Type,
+        fn_attrs: Option<&CodegenFnAttrs>,
+        fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
+        llfn: Self::Value,
+        args: &[Self::Value],
+        funclet: Option<&Self::Funclet>,
+        instance: Option<Instance<'tcx>>,
+    );
+
     fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
 
     fn apply_attrs_to_cleanup_callsite(&mut self, llret: Self::Value);
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index 33ae4f6c45c..2345cdab208 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -1302,7 +1302,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 None => ".clone()".to_string(),
             };
 
-            let span = expr.span.find_oldest_ancestor_in_same_ctxt().shrink_to_hi();
+            let span = expr.span.find_ancestor_not_from_macro().unwrap_or(expr.span).shrink_to_hi();
 
             diag.span_suggestion_verbose(
                 span,
@@ -1395,7 +1395,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 .macro_backtrace()
                 .any(|x| matches!(x.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, ..)))
         {
-            let span = expr.span.find_oldest_ancestor_in_same_ctxt();
+            let span = expr
+                .span
+                .find_ancestor_not_from_extern_macro(&self.tcx.sess.source_map())
+                .unwrap_or(expr.span);
 
             let mut sugg = if self.precedence(expr) >= ExprPrecedence::Unambiguous {
                 vec![(span.shrink_to_hi(), ".into()".to_owned())]
@@ -2062,7 +2065,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             None => sugg.to_string(),
         };
 
-        let span = expr.span.find_oldest_ancestor_in_same_ctxt();
+        let span = expr
+            .span
+            .find_ancestor_not_from_extern_macro(&self.tcx.sess.source_map())
+            .unwrap_or(expr.span);
         err.span_suggestion_verbose(span.shrink_to_hi(), msg, sugg, Applicability::HasPlaceholders);
         true
     }
diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs
index 9e324286fa1..b395aa8162c 100644
--- a/compiler/rustc_hir_typeck/src/lib.rs
+++ b/compiler/rustc_hir_typeck/src/lib.rs
@@ -53,7 +53,7 @@ use rustc_hir_analysis::check::{check_abi, check_custom_abi};
 use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
 use rustc_infer::traits::{ObligationCauseCode, ObligationInspector, WellFormedLoc};
 use rustc_middle::query::Providers;
-use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
 use rustc_middle::{bug, span_bug};
 use rustc_session::config;
 use rustc_span::Span;
@@ -259,6 +259,21 @@ fn typeck_with_inspect<'tcx>(
 
     let typeck_results = fcx.resolve_type_vars_in_body(body);
 
+    // Handle potentially region dependent goals, see `InferCtxt::in_hir_typeck`.
+    if let None = fcx.infcx.tainted_by_errors() {
+        for obligation in fcx.take_hir_typeck_potentially_region_dependent_goals() {
+            let obligation = fcx.resolve_vars_if_possible(obligation);
+            if obligation.has_non_region_infer() {
+                bug!("unexpected inference variable after writeback: {obligation:?}");
+            }
+            fcx.register_predicate(obligation);
+        }
+        fcx.select_obligations_where_possible(|_| {});
+        if let None = fcx.infcx.tainted_by_errors() {
+            fcx.report_ambiguity_errors();
+        }
+    }
+
     fcx.detect_opaque_types_added_during_writeback();
 
     // Consistency check our TypeckResults instance can hold all ItemLocalIds
diff --git a/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs b/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs
index 6a985fc91e7..560f8ceb55f 100644
--- a/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs
+++ b/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs
@@ -85,8 +85,11 @@ impl<'tcx> TypeckRootCtxt<'tcx> {
     pub(crate) fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self {
         let hir_owner = tcx.local_def_id_to_hir_id(def_id).owner;
 
-        let infcx =
-            tcx.infer_ctxt().ignoring_regions().build(TypingMode::typeck_for_body(tcx, def_id));
+        let infcx = tcx
+            .infer_ctxt()
+            .ignoring_regions()
+            .in_hir_typeck()
+            .build(TypingMode::typeck_for_body(tcx, def_id));
         let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner));
         let fulfillment_cx = RefCell::new(<dyn TraitEngine<'_, _>>::new(&infcx));
 
diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs
index 5fe795bd23a..ad19cdef4e7 100644
--- a/compiler/rustc_infer/src/infer/at.rs
+++ b/compiler/rustc_infer/src/infer/at.rs
@@ -71,6 +71,7 @@ impl<'tcx> InferCtxt<'tcx> {
             tcx: self.tcx,
             typing_mode: self.typing_mode,
             considering_regions: self.considering_regions,
+            in_hir_typeck: self.in_hir_typeck,
             skip_leak_check: self.skip_leak_check,
             inner: self.inner.clone(),
             lexical_region_resolutions: self.lexical_region_resolutions.clone(),
@@ -95,6 +96,7 @@ impl<'tcx> InferCtxt<'tcx> {
             tcx: self.tcx,
             typing_mode,
             considering_regions: self.considering_regions,
+            in_hir_typeck: self.in_hir_typeck,
             skip_leak_check: self.skip_leak_check,
             inner: self.inner.clone(),
             lexical_region_resolutions: self.lexical_region_resolutions.clone(),
diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs
index bb9c8850093..21e999b080d 100644
--- a/compiler/rustc_infer/src/infer/context.rs
+++ b/compiler/rustc_infer/src/infer/context.rs
@@ -22,6 +22,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
         self.next_trait_solver
     }
 
+    fn in_hir_typeck(&self) -> bool {
+        self.in_hir_typeck
+    }
+
     fn typing_mode(&self) -> ty::TypingMode<'tcx> {
         self.typing_mode()
     }
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index 2d269e320b6..5bbd8a84b7f 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -37,10 +37,11 @@ use snapshot::undo_log::InferCtxtUndoLogs;
 use tracing::{debug, instrument};
 use type_variable::TypeVariableOrigin;
 
-use crate::infer::region_constraints::UndoLog;
+use crate::infer::snapshot::undo_log::UndoLog;
 use crate::infer::unify_key::{ConstVariableOrigin, ConstVariableValue, ConstVidKey};
 use crate::traits::{
-    self, ObligationCause, ObligationInspector, PredicateObligations, TraitEngine,
+    self, ObligationCause, ObligationInspector, PredicateObligation, PredicateObligations,
+    TraitEngine,
 };
 
 pub mod at;
@@ -156,6 +157,12 @@ pub struct InferCtxtInner<'tcx> {
     /// which may cause types to no longer be considered well-formed.
     region_assumptions: Vec<ty::ArgOutlivesPredicate<'tcx>>,
 
+    /// `-Znext-solver`: Successfully proven goals during HIR typeck which
+    /// reference inference variables and get reproven after writeback.
+    ///
+    /// See the documentation of `InferCtxt::in_hir_typeck` for more details.
+    hir_typeck_potentially_region_dependent_goals: Vec<PredicateObligation<'tcx>>,
+
     /// Caches for opaque type inference.
     opaque_type_storage: OpaqueTypeStorage<'tcx>,
 }
@@ -173,6 +180,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
             region_constraint_storage: Some(Default::default()),
             region_obligations: Default::default(),
             region_assumptions: Default::default(),
+            hir_typeck_potentially_region_dependent_goals: Default::default(),
             opaque_type_storage: Default::default(),
         }
     }
@@ -244,9 +252,29 @@ pub struct InferCtxt<'tcx> {
     typing_mode: TypingMode<'tcx>,
 
     /// Whether this inference context should care about region obligations in
-    /// the root universe. Most notably, this is used during hir typeck as region
+    /// the root universe. Most notably, this is used during HIR typeck as region
     /// solving is left to borrowck instead.
     pub considering_regions: bool,
+    /// `-Znext-solver`: Whether this inference context is used by HIR typeck. If so, we
+    /// need to make sure we don't rely on region identity in the trait solver or when
+    /// relating types. This is necessary as borrowck starts by replacing each occurrence of a
+    /// free region with a unique inference variable. If HIR typeck ends up depending on two
+    /// regions being equal we'd get unexpected mismatches between HIR typeck and MIR typeck,
+    /// resulting in an ICE.
+    ///
+    /// The trait solver sometimes depends on regions being identical. As a concrete example
+    /// the trait solver ignores other candidates if one candidate exists without any constraints.
+    /// The goal `&'a u32: Equals<&'a u32>` has no constraints right now. If we replace each
+    /// occurrence of `'a` with a unique region the goal now equates these regions. See
+    /// the tests in trait-system-refactor-initiative#27 for concrete examples.
+    ///
+    /// We handle this by *uniquifying* region when canonicalizing root goals during HIR typeck.
+    /// This is still insufficient as inference variables may *hide* region variables, so e.g.
+    /// `dyn TwoSuper<?x, ?x>: Super<?x>` may hold but MIR typeck could end up having to prove
+    /// `dyn TwoSuper<&'0 (), &'1 ()>: Super<&'2 ()>` which is now ambiguous. Because of this we
+    /// stash all successfully proven goals which reference inference variables and then reprove
+    /// them after writeback.
+    pub in_hir_typeck: bool,
 
     /// If set, this flag causes us to skip the 'leak check' during
     /// higher-ranked subtyping operations. This flag is a temporary one used
@@ -506,6 +534,7 @@ pub struct TypeOutlivesConstraint<'tcx> {
 pub struct InferCtxtBuilder<'tcx> {
     tcx: TyCtxt<'tcx>,
     considering_regions: bool,
+    in_hir_typeck: bool,
     skip_leak_check: bool,
     /// Whether we should use the new trait solver in the local inference context,
     /// which affects things like which solver is used in `predicate_may_hold`.
@@ -518,6 +547,7 @@ impl<'tcx> TyCtxt<'tcx> {
         InferCtxtBuilder {
             tcx: self,
             considering_regions: true,
+            in_hir_typeck: false,
             skip_leak_check: false,
             next_trait_solver: self.next_trait_solver_globally(),
         }
@@ -535,6 +565,11 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
         self
     }
 
+    pub fn in_hir_typeck(mut self) -> Self {
+        self.in_hir_typeck = true;
+        self
+    }
+
     pub fn skip_leak_check(mut self, skip_leak_check: bool) -> Self {
         self.skip_leak_check = skip_leak_check;
         self
@@ -568,12 +603,18 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
     }
 
     pub fn build(&mut self, typing_mode: TypingMode<'tcx>) -> InferCtxt<'tcx> {
-        let InferCtxtBuilder { tcx, considering_regions, skip_leak_check, next_trait_solver } =
-            *self;
+        let InferCtxtBuilder {
+            tcx,
+            considering_regions,
+            in_hir_typeck,
+            skip_leak_check,
+            next_trait_solver,
+        } = *self;
         InferCtxt {
             tcx,
             typing_mode,
             considering_regions,
+            in_hir_typeck,
             skip_leak_check,
             inner: RefCell::new(InferCtxtInner::new()),
             lexical_region_resolutions: RefCell::new(None),
@@ -978,6 +1019,22 @@ impl<'tcx> InferCtxt<'tcx> {
         }
     }
 
+    pub fn push_hir_typeck_potentially_region_dependent_goal(
+        &self,
+        goal: PredicateObligation<'tcx>,
+    ) {
+        let mut inner = self.inner.borrow_mut();
+        inner.undo_log.push(UndoLog::PushHirTypeckPotentiallyRegionDependentGoal);
+        inner.hir_typeck_potentially_region_dependent_goals.push(goal);
+    }
+
+    pub fn take_hir_typeck_potentially_region_dependent_goals(
+        &self,
+    ) -> Vec<PredicateObligation<'tcx>> {
+        assert!(!self.in_snapshot(), "cannot take goals in a snapshot");
+        std::mem::take(&mut self.inner.borrow_mut().hir_typeck_potentially_region_dependent_goals)
+    }
+
     pub fn ty_to_string(&self, t: Ty<'tcx>) -> String {
         self.resolve_vars_if_possible(t).to_string()
     }
diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs
index a8520c0e71d..bb3b51c0ab2 100644
--- a/compiler/rustc_infer/src/infer/outlives/obligations.rs
+++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs
@@ -177,6 +177,7 @@ impl<'tcx> InferCtxt<'tcx> {
     }
 
     pub fn take_registered_region_assumptions(&self) -> Vec<ty::ArgOutlivesPredicate<'tcx>> {
+        assert!(!self.in_snapshot(), "cannot take registered region assumptions in a snapshot");
         std::mem::take(&mut self.inner.borrow_mut().region_assumptions)
     }
 
diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs
index 40e4c329446..fcc0ab3af41 100644
--- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs
+++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs
@@ -29,6 +29,7 @@ pub(crate) enum UndoLog<'tcx> {
     ProjectionCache(traits::UndoLog<'tcx>),
     PushTypeOutlivesConstraint,
     PushRegionAssumption,
+    PushHirTypeckPotentiallyRegionDependentGoal,
 }
 
 macro_rules! impl_from {
@@ -79,7 +80,12 @@ impl<'tcx> Rollback<UndoLog<'tcx>> for InferCtxtInner<'tcx> {
                 assert_matches!(popped, Some(_), "pushed region constraint but could not pop it");
             }
             UndoLog::PushRegionAssumption => {
-                self.region_assumptions.pop();
+                let popped = self.region_assumptions.pop();
+                assert_matches!(popped, Some(_), "pushed region assumption but could not pop it");
+            }
+            UndoLog::PushHirTypeckPotentiallyRegionDependentGoal => {
+                let popped = self.hir_typeck_potentially_region_dependent_goals.pop();
+                assert_matches!(popped, Some(_), "pushed goal but could not pop it");
             }
         }
     }
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 11df071f068..00e40b515a3 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -185,7 +185,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
         let mut op_warned = false;
 
         if let Some(must_use_op) = must_use_op {
-            let span = expr.span.find_oldest_ancestor_in_same_ctxt();
+            let span = expr.span.find_ancestor_not_from_macro().unwrap_or(expr.span);
             cx.emit_span_lint(
                 UNUSED_MUST_USE,
                 expr.span,
@@ -511,7 +511,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
                     );
                 }
                 MustUsePath::Def(span, def_id, reason) => {
-                    let span = span.find_oldest_ancestor_in_same_ctxt();
+                    let span = span.find_ancestor_not_from_macro().unwrap_or(*span);
                     cx.emit_span_lint(
                         UNUSED_MUST_USE,
                         span,
diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
index c9814beedd6..588d867bbbf 100644
--- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
+++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
@@ -1986,3 +1986,29 @@ extern "C" void LLVMRustSetNoSanitizeHWAddress(LLVMValueRef Global) {
   MD.NoHWAddress = true;
   GV.setSanitizerMetadata(MD);
 }
+
+enum class LLVMRustTailCallKind {
+  None = 0,
+  Tail = 1,
+  MustTail = 2,
+  NoTail = 3
+};
+
+extern "C" void LLVMRustSetTailCallKind(LLVMValueRef Call,
+                                        LLVMRustTailCallKind Kind) {
+  CallInst *CI = unwrap<CallInst>(Call);
+  switch (Kind) {
+  case LLVMRustTailCallKind::None:
+    CI->setTailCallKind(CallInst::TCK_None);
+    break;
+  case LLVMRustTailCallKind::Tail:
+    CI->setTailCallKind(CallInst::TCK_Tail);
+    break;
+  case LLVMRustTailCallKind::MustTail:
+    CI->setTailCallKind(CallInst::TCK_MustTail);
+    break;
+  case LLVMRustTailCallKind::NoTail:
+    CI->setTailCallKind(CallInst::TCK_NoTail);
+    break;
+  }
+}
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index 438eff33054..6bfb3769f24 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -12,6 +12,7 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::owned_slice::OwnedSlice;
 use rustc_data_structures::svh::Svh;
 use rustc_data_structures::sync::{self, FreezeReadGuard, FreezeWriteGuard};
+use rustc_data_structures::unord::UnordMap;
 use rustc_expand::base::SyntaxExtension;
 use rustc_fs_util::try_canonicalize;
 use rustc_hir as hir;
@@ -69,6 +70,9 @@ pub struct CStore {
     /// This crate has a `#[alloc_error_handler]` item.
     has_alloc_error_handler: bool,
 
+    /// Names that were used to load the crates via `extern crate` or paths.
+    resolved_externs: UnordMap<Symbol, CrateNum>,
+
     /// Unused externs of the crate
     unused_externs: Vec<Symbol>,
 
@@ -249,6 +253,22 @@ impl CStore {
         self.metas[cnum] = Some(Box::new(data));
     }
 
+    /// Save the name used to resolve the extern crate in the local crate
+    ///
+    /// The name isn't always the crate's own name, because `sess.opts.externs` can assign it another name.
+    /// It's also not always the same as the `DefId`'s symbol due to renames `extern crate resolved_name as defid_name`.
+    pub(crate) fn set_resolved_extern_crate_name(&mut self, name: Symbol, extern_crate: CrateNum) {
+        self.resolved_externs.insert(name, extern_crate);
+    }
+
+    /// Crate resolved and loaded via the given extern name
+    /// (corresponds to names in `sess.opts.externs`)
+    ///
+    /// May be `None` if the crate wasn't used
+    pub fn resolved_extern_crate(&self, externs_name: Symbol) -> Option<CrateNum> {
+        self.resolved_externs.get(&externs_name).copied()
+    }
+
     pub(crate) fn iter_crate_data(&self) -> impl Iterator<Item = (CrateNum, &CrateMetadata)> {
         self.metas
             .iter_enumerated()
@@ -475,6 +495,7 @@ impl CStore {
             alloc_error_handler_kind: None,
             has_global_allocator: false,
             has_alloc_error_handler: false,
+            resolved_externs: UnordMap::default(),
             unused_externs: Vec::new(),
             used_extern_options: Default::default(),
         }
@@ -511,7 +532,7 @@ impl CStore {
             // We're also sure to compare *paths*, not actual byte slices. The
             // `source` stores paths which are normalized which may be different
             // from the strings on the command line.
-            let source = self.get_crate_data(cnum).cdata.source();
+            let source = data.source();
             if let Some(entry) = externs.get(name.as_str()) {
                 // Only use `--extern crate_name=path` here, not `--extern crate_name`.
                 if let Some(mut files) = entry.files() {
@@ -1308,6 +1329,7 @@ impl CStore {
                 let path_len = definitions.def_path(def_id).data.len();
                 self.update_extern_crate(
                     cnum,
+                    name,
                     ExternCrate {
                         src: ExternCrateSource::Extern(def_id.to_def_id()),
                         span: item.span,
@@ -1332,6 +1354,7 @@ impl CStore {
 
         self.update_extern_crate(
             cnum,
+            name,
             ExternCrate {
                 src: ExternCrateSource::Path,
                 span,
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index e6aedc61338..00c97a2f738 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -1937,9 +1937,13 @@ impl CrateMetadata {
         self.root.decode_target_modifiers(&self.blob).collect()
     }
 
-    pub(crate) fn update_extern_crate(&mut self, new_extern_crate: ExternCrate) -> bool {
+    /// Keep `new_extern_crate` if it looks better in diagnostics
+    pub(crate) fn update_extern_crate_diagnostics(
+        &mut self,
+        new_extern_crate: ExternCrate,
+    ) -> bool {
         let update =
-            Some(new_extern_crate.rank()) > self.extern_crate.as_ref().map(ExternCrate::rank);
+            self.extern_crate.as_ref().is_none_or(|old| old.rank() < new_extern_crate.rank());
         if update {
             self.extern_crate = Some(new_extern_crate);
         }
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index 57a672c45f7..9415e420eed 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -627,14 +627,37 @@ impl CStore {
         }
     }
 
-    pub(crate) fn update_extern_crate(&mut self, cnum: CrateNum, extern_crate: ExternCrate) {
+    /// Track how an extern crate has been loaded. Called after resolving an import in the local crate.
+    ///
+    /// * the `name` is for [`Self::set_resolved_extern_crate_name`] saving `--extern name=`
+    /// * `extern_crate` is for diagnostics
+    pub(crate) fn update_extern_crate(
+        &mut self,
+        cnum: CrateNum,
+        name: Symbol,
+        extern_crate: ExternCrate,
+    ) {
+        debug_assert_eq!(
+            extern_crate.dependency_of, LOCAL_CRATE,
+            "this function should not be called on transitive dependencies"
+        );
+        self.set_resolved_extern_crate_name(name, cnum);
+        self.update_transitive_extern_crate_diagnostics(cnum, extern_crate);
+    }
+
+    /// `CrateMetadata` uses `ExternCrate` only for diagnostics
+    fn update_transitive_extern_crate_diagnostics(
+        &mut self,
+        cnum: CrateNum,
+        extern_crate: ExternCrate,
+    ) {
         let cmeta = self.get_crate_data_mut(cnum);
-        if cmeta.update_extern_crate(extern_crate) {
+        if cmeta.update_extern_crate_diagnostics(extern_crate) {
             // Propagate the extern crate info to dependencies if it was updated.
             let extern_crate = ExternCrate { dependency_of: cnum, ..extern_crate };
             let dependencies = mem::take(&mut cmeta.dependencies);
             for &dep_cnum in &dependencies {
-                self.update_extern_crate(dep_cnum, extern_crate);
+                self.update_transitive_extern_crate_diagnostics(dep_cnum, extern_crate);
             }
             self.get_crate_data_mut(cnum).dependencies = dependencies;
         }
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index b0d579a546f..587349d4cf4 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1391,7 +1391,6 @@ rustc_queries! {
         desc { "checking effective visibilities" }
     }
     query check_private_in_public(_: ()) {
-        eval_always
         desc { "checking for private elements in public interfaces" }
     }
 
diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs
index a5123576fc6..aed94f9aa04 100644
--- a/compiler/rustc_middle/src/ty/layout.rs
+++ b/compiler/rustc_middle/src/ty/layout.rs
@@ -107,8 +107,8 @@ impl abi::Integer {
             abi::Integer::I8
         };
 
-        // If there are no negative values, we can use the unsigned fit.
-        if min >= 0 {
+        // Pick the smallest fit.
+        if unsigned_fit <= signed_fit {
             (cmp::max(unsigned_fit, at_least), false)
         } else {
             (cmp::max(signed_fit, at_least), true)
diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs
index 174892c6f4d..a7d07adf78f 100644
--- a/compiler/rustc_middle/src/ty/util.rs
+++ b/compiler/rustc_middle/src/ty/util.rs
@@ -32,7 +32,7 @@ use crate::ty::{
 
 #[derive(Copy, Clone, Debug)]
 pub struct Discr<'tcx> {
-    /// Bit representation of the discriminant (e.g., `-128i8` is `0xFF_u128`).
+    /// Bit representation of the discriminant (e.g., `-1i8` is `0xFF_u128`).
     pub val: u128,
     pub ty: Ty<'tcx>,
 }
diff --git a/compiler/rustc_mir_transform/src/elaborate_drop.rs b/compiler/rustc_mir_transform/src/elaborate_drop.rs
index de96b1f255a..df4853c1dcb 100644
--- a/compiler/rustc_mir_transform/src/elaborate_drop.rs
+++ b/compiler/rustc_mir_transform/src/elaborate_drop.rs
@@ -761,24 +761,37 @@ where
 
         let skip_contents = adt.is_union() || adt.is_manually_drop();
         let contents_drop = if skip_contents {
+            if adt.has_dtor(self.tcx()) && self.elaborator.get_drop_flag(self.path).is_some() {
+                // the top-level drop flag is usually cleared by open_drop_for_adt_contents
+                // types with destructors would still need an empty drop ladder to clear it
+
+                // however, these types are only open dropped in `DropShimElaborator`
+                // which does not have drop flags
+                // a future box-like "DerefMove" trait would allow for this case to happen
+                span_bug!(self.source_info.span, "open dropping partially moved union");
+            }
+
             (self.succ, self.unwind, self.dropline)
         } else {
             self.open_drop_for_adt_contents(adt, args)
         };
 
-        if adt.is_box() {
-            // we need to drop the inside of the box before running the destructor
-            let succ = self.destructor_call_block_sync((contents_drop.0, contents_drop.1));
-            let unwind = contents_drop
-                .1
-                .map(|unwind| self.destructor_call_block_sync((unwind, Unwind::InCleanup)));
-            let dropline = contents_drop
-                .2
-                .map(|dropline| self.destructor_call_block_sync((dropline, contents_drop.1)));
-
-            self.open_drop_for_box_contents(adt, args, succ, unwind, dropline)
-        } else if adt.has_dtor(self.tcx()) {
-            self.destructor_call_block(contents_drop)
+        if adt.has_dtor(self.tcx()) {
+            let destructor_block = if adt.is_box() {
+                // we need to drop the inside of the box before running the destructor
+                let succ = self.destructor_call_block_sync((contents_drop.0, contents_drop.1));
+                let unwind = contents_drop
+                    .1
+                    .map(|unwind| self.destructor_call_block_sync((unwind, Unwind::InCleanup)));
+                let dropline = contents_drop
+                    .2
+                    .map(|dropline| self.destructor_call_block_sync((dropline, contents_drop.1)));
+                self.open_drop_for_box_contents(adt, args, succ, unwind, dropline)
+            } else {
+                self.destructor_call_block(contents_drop)
+            };
+
+            self.drop_flag_test_block(destructor_block, contents_drop.0, contents_drop.1)
         } else {
             contents_drop.0
         }
@@ -982,12 +995,7 @@ where
             unwind.is_cleanup(),
         );
 
-        let destructor_block = self.elaborator.patch().new_block(result);
-
-        let block_start = Location { block: destructor_block, statement_index: 0 };
-        self.elaborator.clear_drop_flag(block_start, self.path, DropFlagMode::Shallow);
-
-        self.drop_flag_test_block(destructor_block, succ, unwind)
+        self.elaborator.patch().new_block(result)
     }
 
     fn destructor_call_block(
@@ -1002,13 +1010,7 @@ where
             && !unwind.is_cleanup()
             && ty.is_async_drop(self.tcx(), self.elaborator.typing_env())
         {
-            let destructor_block =
-                self.build_async_drop(self.place, ty, None, succ, unwind, dropline, true);
-
-            let block_start = Location { block: destructor_block, statement_index: 0 };
-            self.elaborator.clear_drop_flag(block_start, self.path, DropFlagMode::Shallow);
-
-            self.drop_flag_test_block(destructor_block, succ, unwind)
+            self.build_async_drop(self.place, ty, None, succ, unwind, dropline, true)
         } else {
             self.destructor_call_block_sync((succ, unwind))
         }
diff --git a/compiler/rustc_next_trait_solver/src/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonicalizer.rs
index a418aa82100..1bc35e599c7 100644
--- a/compiler/rustc_next_trait_solver/src/canonicalizer.rs
+++ b/compiler/rustc_next_trait_solver/src/canonicalizer.rs
@@ -19,6 +19,20 @@ const NEEDS_CANONICAL: TypeFlags = TypeFlags::from_bits(
 )
 .unwrap();
 
+#[derive(Debug, Clone, Copy)]
+enum CanonicalizeInputKind {
+    /// When canonicalizing the `param_env`, we keep `'static` as merging
+    /// trait candidates relies on it when deciding whether a where-bound
+    /// is trivial.
+    ParamEnv,
+    /// When canonicalizing predicates, we don't keep `'static`. If we're
+    /// currently outside of the trait solver and canonicalize the root goal
+    /// during HIR typeck, we replace each occurance of a region with a
+    /// unique region variable. See the comment on `InferCtxt::in_hir_typeck`
+    /// for more details.
+    Predicate { is_hir_typeck_root_goal: bool },
+}
+
 /// Whether we're canonicalizing a query input or the query response.
 ///
 /// When canonicalizing an input we're in the context of the caller
@@ -26,10 +40,7 @@ const NEEDS_CANONICAL: TypeFlags = TypeFlags::from_bits(
 /// query.
 #[derive(Debug, Clone, Copy)]
 enum CanonicalizeMode {
-    /// When canonicalizing the `param_env`, we keep `'static` as merging
-    /// trait candidates relies on it when deciding whether a where-bound
-    /// is trivial.
-    Input { keep_static: bool },
+    Input(CanonicalizeInputKind),
     /// FIXME: We currently return region constraints referring to
     /// placeholders and inference variables from a binder instantiated
     /// inside of the query.
@@ -122,7 +133,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
                     let mut variables = Vec::new();
                     let mut env_canonicalizer = Canonicalizer {
                         delegate,
-                        canonicalize_mode: CanonicalizeMode::Input { keep_static: true },
+                        canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv),
 
                         variables: &mut variables,
                         variable_lookup_table: Default::default(),
@@ -154,7 +165,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
         } else {
             let mut env_canonicalizer = Canonicalizer {
                 delegate,
-                canonicalize_mode: CanonicalizeMode::Input { keep_static: true },
+                canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv),
 
                 variables,
                 variable_lookup_table: Default::default(),
@@ -180,6 +191,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
     pub fn canonicalize_input<P: TypeFoldable<I>>(
         delegate: &'a D,
         variables: &'a mut Vec<I::GenericArg>,
+        is_hir_typeck_root_goal: bool,
         input: QueryInput<I, P>,
     ) -> ty::Canonical<I, QueryInput<I, P>> {
         // First canonicalize the `param_env` while keeping `'static`
@@ -189,7 +201,9 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
         // while *mostly* reusing the canonicalizer from above.
         let mut rest_canonicalizer = Canonicalizer {
             delegate,
-            canonicalize_mode: CanonicalizeMode::Input { keep_static: false },
+            canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::Predicate {
+                is_hir_typeck_root_goal,
+            }),
 
             variables,
             variable_lookup_table,
@@ -296,7 +310,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
         }
     }
 
-    fn cached_fold_ty(&mut self, t: I::Ty) -> I::Ty {
+    fn inner_fold_ty(&mut self, t: I::Ty) -> I::Ty {
         let kind = match t.kind() {
             ty::Infer(i) => match i {
                 ty::TyVar(vid) => {
@@ -413,10 +427,10 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
             // We don't canonicalize `ReStatic` in the `param_env` as we use it
             // when checking whether a `ParamEnv` candidate is global.
             ty::ReStatic => match self.canonicalize_mode {
-                CanonicalizeMode::Input { keep_static: false } => {
+                CanonicalizeMode::Input(CanonicalizeInputKind::Predicate { .. }) => {
                     CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
                 }
-                CanonicalizeMode::Input { keep_static: true }
+                CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv)
                 | CanonicalizeMode::Response { .. } => return r,
             },
 
@@ -428,12 +442,12 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
             // `ReErased`. We may be able to short-circuit registering region
             // obligations if we encounter a `ReErased` on one side, for example.
             ty::ReErased | ty::ReError(_) => match self.canonicalize_mode {
-                CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
+                CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
                 CanonicalizeMode::Response { .. } => return r,
             },
 
             ty::ReEarlyParam(_) | ty::ReLateParam(_) => match self.canonicalize_mode {
-                CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
+                CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
                 CanonicalizeMode::Response { .. } => {
                     panic!("unexpected region in response: {r:?}")
                 }
@@ -441,7 +455,7 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
 
             ty::RePlaceholder(placeholder) => match self.canonicalize_mode {
                 // We canonicalize placeholder regions as existentials in query inputs.
-                CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
+                CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
                 CanonicalizeMode::Response { max_input_universe } => {
                     // If we have a placeholder region inside of a query, it must be from
                     // a new universe.
@@ -459,9 +473,7 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
                     "region vid should have been resolved fully before canonicalization"
                 );
                 match self.canonicalize_mode {
-                    CanonicalizeMode::Input { keep_static: _ } => {
-                        CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
-                    }
+                    CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
                     CanonicalizeMode::Response { .. } => {
                         CanonicalVarKind::Region(self.delegate.universe_of_lt(vid).unwrap())
                     }
@@ -469,16 +481,34 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
             }
         };
 
-        let var = self.get_or_insert_bound_var(r, kind);
+        let var = if let CanonicalizeMode::Input(CanonicalizeInputKind::Predicate {
+            is_hir_typeck_root_goal: true,
+        }) = self.canonicalize_mode
+        {
+            let var = ty::BoundVar::from(self.variables.len());
+            self.variables.push(r.into());
+            self.var_kinds.push(kind);
+            var
+        } else {
+            self.get_or_insert_bound_var(r, kind)
+        };
 
         Region::new_anon_bound(self.cx(), self.binder_index, var)
     }
 
     fn fold_ty(&mut self, t: I::Ty) -> I::Ty {
-        if let Some(&ty) = self.cache.get(&(self.binder_index, t)) {
+        if let CanonicalizeMode::Input(CanonicalizeInputKind::Predicate {
+            is_hir_typeck_root_goal: true,
+        }) = self.canonicalize_mode
+        {
+            // If we're canonicalizing a root goal during HIR typeck, we
+            // must not use the `cache` as we want to map each occurrence
+            // of a region to a unique existential variable.
+            self.inner_fold_ty(t)
+        } else if let Some(&ty) = self.cache.get(&(self.binder_index, t)) {
             ty
         } else {
-            let res = self.cached_fold_ty(t);
+            let res = self.inner_fold_ty(t);
             let old = self.cache.insert((self.binder_index, t), res);
             assert_eq!(old, None);
             res
@@ -541,9 +571,9 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
 
     fn fold_clauses(&mut self, c: I::Clauses) -> I::Clauses {
         match self.canonicalize_mode {
-            CanonicalizeMode::Input { keep_static: true }
+            CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv)
             | CanonicalizeMode::Response { max_input_universe: _ } => {}
-            CanonicalizeMode::Input { keep_static: false } => {
+            CanonicalizeMode::Input(CanonicalizeInputKind::Predicate { .. }) => {
                 panic!("erasing 'static in env")
             }
         }
diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs
index 5ed316aa6b1..74c5b49ea92 100644
--- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs
+++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs
@@ -53,20 +53,19 @@ where
 {
     /// Canonicalizes the goal remembering the original values
     /// for each bound variable.
+    ///
+    /// This expects `goal` and `opaque_types` to be eager resolved.
     pub(super) fn canonicalize_goal(
         &self,
+        is_hir_typeck_root_goal: bool,
         goal: Goal<I, I::Predicate>,
+        opaque_types: Vec<(ty::OpaqueTypeKey<I>, I::Ty)>,
     ) -> (Vec<I::GenericArg>, CanonicalInput<I, I::Predicate>) {
-        // We only care about one entry per `OpaqueTypeKey` here,
-        // so we only canonicalize the lookup table and ignore
-        // duplicate entries.
-        let opaque_types = self.delegate.clone_opaque_types_lookup_table();
-        let (goal, opaque_types) = eager_resolve_vars(self.delegate, (goal, opaque_types));
-
         let mut orig_values = Default::default();
         let canonical = Canonicalizer::canonicalize_input(
             self.delegate,
             &mut orig_values,
+            is_hir_typeck_root_goal,
             QueryInput {
                 goal,
                 predefined_opaques_in_body: self
diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
index ce9b794d40d..8671cc7c3d3 100644
--- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
+++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
@@ -20,6 +20,7 @@ use super::has_only_region_constraints;
 use crate::coherence;
 use crate::delegate::SolverDelegate;
 use crate::placeholder::BoundVarReplacer;
+use crate::resolve::eager_resolve_vars;
 use crate::solve::inspect::{self, ProofTreeBuilder};
 use crate::solve::search_graph::SearchGraph;
 use crate::solve::ty::may_use_unstable_feature;
@@ -440,6 +441,7 @@ where
             return Ok((
                 NestedNormalizationGoals::empty(),
                 GoalEvaluation {
+                    goal,
                     certainty: Certainty::Maybe(stalled_on.stalled_cause),
                     has_changed: HasChanged::No,
                     stalled_on: Some(stalled_on),
@@ -447,7 +449,16 @@ where
             ));
         }
 
-        let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
+        // We only care about one entry per `OpaqueTypeKey` here,
+        // so we only canonicalize the lookup table and ignore
+        // duplicate entries.
+        let opaque_types = self.delegate.clone_opaque_types_lookup_table();
+        let (goal, opaque_types) = eager_resolve_vars(self.delegate, (goal, opaque_types));
+
+        let is_hir_typeck_root_goal = matches!(goal_evaluation_kind, GoalEvaluationKind::Root)
+            && self.delegate.in_hir_typeck();
+        let (orig_values, canonical_goal) =
+            self.canonicalize_goal(is_hir_typeck_root_goal, goal, opaque_types);
         let mut goal_evaluation =
             self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind);
         let canonical_result = self.search_graph.evaluate_goal(
@@ -525,7 +536,10 @@ where
             },
         };
 
-        Ok((normalization_nested_goals, GoalEvaluation { certainty, has_changed, stalled_on }))
+        Ok((
+            normalization_nested_goals,
+            GoalEvaluation { goal, certainty, has_changed, stalled_on },
+        ))
     }
 
     pub(super) fn compute_goal(&mut self, goal: Goal<I, I::Predicate>) -> QueryResult<I> {
@@ -661,7 +675,7 @@ where
 
                 let (
                     NestedNormalizationGoals(nested_goals),
-                    GoalEvaluation { certainty, stalled_on, has_changed: _ },
+                    GoalEvaluation { goal, certainty, stalled_on, has_changed: _ },
                 ) = self.evaluate_goal_raw(
                     GoalEvaluationKind::Nested,
                     source,
@@ -699,7 +713,15 @@ where
                 // FIXME: Do we need to eagerly resolve here? Or should we check
                 // if the cache key has any changed vars?
                 let with_resolved_vars = self.resolve_vars_if_possible(goal);
-                if pred.alias != goal.predicate.as_normalizes_to().unwrap().skip_binder().alias {
+                if pred.alias
+                    != with_resolved_vars
+                        .predicate
+                        .as_normalizes_to()
+                        .unwrap()
+                        .no_bound_vars()
+                        .unwrap()
+                        .alias
+                {
                     unchanged_certainty = None;
                 }
 
@@ -711,7 +733,7 @@ where
                     }
                 }
             } else {
-                let GoalEvaluation { certainty, has_changed, stalled_on } =
+                let GoalEvaluation { goal, certainty, has_changed, stalled_on } =
                     self.evaluate_goal(GoalEvaluationKind::Nested, source, goal, stalled_on)?;
                 if has_changed == HasChanged::Yes {
                     unchanged_certainty = None;
diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs
index 5ea3f0d1061..aec9594b834 100644
--- a/compiler/rustc_next_trait_solver/src/solve/mod.rs
+++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs
@@ -252,8 +252,6 @@ where
             return None;
         }
 
-        // FIXME(-Znext-solver): Add support to merge region constraints in
-        // responses to deal with trait-system-refactor-initiative#27.
         let one = responses[0];
         if responses[1..].iter().all(|&resp| resp == one) {
             return Some(one);
@@ -388,6 +386,23 @@ fn response_no_constraints_raw<I: Interner>(
 
 /// The result of evaluating a goal.
 pub struct GoalEvaluation<I: Interner> {
+    /// The goal we've evaluated. This is the input goal, but potentially with its
+    /// inference variables resolved. This never applies any inference constraints
+    /// from evaluating the goal.
+    ///
+    /// We rely on this to check whether root goals in HIR typeck had an unresolved
+    /// type inference variable in the input. We must not resolve this after evaluating
+    /// the goal as even if the inference variable has been resolved by evaluating the
+    /// goal itself, this goal may still end up failing due to region uniquification
+    /// later on.
+    ///
+    /// This is used as a minor optimization to avoid re-resolving inference variables
+    /// when reevaluating ambiguous goals. E.g. if we've got a goal `?x: Trait` with `?x`
+    /// already being constrained to `Vec<?y>`, then the first evaluation resolves it to
+    /// `Vec<?y>: Trait`. If this goal is still ambiguous and we later resolve `?y` to `u32`,
+    /// then reevaluating this goal now only needs to resolve `?y` while it would otherwise
+    /// have to resolve both `?x` and `?y`,
+    pub goal: Goal<I, I::Predicate>,
     pub certainty: Certainty,
     pub has_changed: HasChanged,
     /// If the [`Certainty`] was `Maybe`, then keep track of whether the goal has changed
diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs
index b15b478a6be..7912345ec56 100644
--- a/compiler/rustc_resolve/src/build_reduced_graph.rs
+++ b/compiler/rustc_resolve/src/build_reduced_graph.rs
@@ -984,18 +984,17 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
                 // more details: https://github.com/rust-lang/rust/pull/111761
                 return;
             }
-            let entry = self
-                .r
-                .extern_prelude
-                .entry(ident)
-                .or_insert(ExternPreludeEntry { binding: None, introduced_by_item: true });
+            let entry = self.r.extern_prelude.entry(ident).or_insert(ExternPreludeEntry {
+                binding: Cell::new(None),
+                introduced_by_item: true,
+            });
             if orig_name.is_some() {
                 entry.introduced_by_item = true;
             }
             // Binding from `extern crate` item in source code can replace
             // a binding from `--extern` on command line here.
             if !entry.is_import() {
-                entry.binding = Some(imported_binding)
+                entry.binding.set(Some(imported_binding));
             } else if ident.name != kw::Underscore {
                 self.r.dcx().span_delayed_bug(
                     item.span,
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 8115b87dcae..88dfb50b47d 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -1009,13 +1009,13 @@ impl<'ra> NameBindingData<'ra> {
 
 #[derive(Default, Clone)]
 struct ExternPreludeEntry<'ra> {
-    binding: Option<NameBinding<'ra>>,
+    binding: Cell<Option<NameBinding<'ra>>>,
     introduced_by_item: bool,
 }
 
 impl ExternPreludeEntry<'_> {
     fn is_import(&self) -> bool {
-        self.binding.is_some_and(|binding| binding.is_import())
+        self.binding.get().is_some_and(|binding| binding.is_import())
     }
 }
 
@@ -2006,7 +2006,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             // but not introduce it, as used if they are accessed from lexical scope.
             if used == Used::Scope {
                 if let Some(entry) = self.extern_prelude.get(&ident.normalize_to_macros_2_0()) {
-                    if !entry.introduced_by_item && entry.binding == Some(used_binding) {
+                    if !entry.introduced_by_item && entry.binding.get() == Some(used_binding) {
                         return;
                     }
                 }
@@ -2170,7 +2170,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
 
         let norm_ident = ident.normalize_to_macros_2_0();
         let binding = self.extern_prelude.get(&norm_ident).cloned().and_then(|entry| {
-            Some(if let Some(binding) = entry.binding {
+            Some(if let Some(binding) = entry.binding.get() {
                 if finalize {
                     if !entry.is_import() {
                         self.cstore_mut().process_path_extern(self.tcx, ident.name, ident.span);
@@ -2195,8 +2195,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             })
         });
 
-        if let Some(entry) = self.extern_prelude.get_mut(&norm_ident) {
-            entry.binding = binding;
+        if let Some(entry) = self.extern_prelude.get(&norm_ident) {
+            entry.binding.set(binding);
         }
 
         binding
diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs
index dbc67da37b5..3f72ccd9f89 100644
--- a/compiler/rustc_span/src/lib.rs
+++ b/compiler/rustc_span/src/lib.rs
@@ -716,12 +716,17 @@ impl Span {
         (!ctxt.is_root()).then(|| ctxt.outer_expn_data().call_site)
     }
 
-    /// Walk down the expansion ancestors to find a span that's contained within `outer`.
+    /// Find the first ancestor span that's contained within `outer`.
     ///
-    /// The span returned by this method may have a different [`SyntaxContext`] as `outer`.
+    /// This method traverses the macro expansion ancestors until it finds the first span
+    /// that's contained within `outer`.
+    ///
+    /// The span returned by this method may have a different [`SyntaxContext`] than `outer`.
     /// If you need to extend the span, use [`find_ancestor_inside_same_ctxt`] instead,
     /// because joining spans with different syntax contexts can create unexpected results.
     ///
+    /// This is used to find the span of the macro call when a parent expr span, i.e. `outer`, is known.
+    ///
     /// [`find_ancestor_inside_same_ctxt`]: Self::find_ancestor_inside_same_ctxt
     pub fn find_ancestor_inside(mut self, outer: Span) -> Option<Span> {
         while !outer.contains(self) {
@@ -730,8 +735,10 @@ impl Span {
         Some(self)
     }
 
-    /// Walk down the expansion ancestors to find a span with the same [`SyntaxContext`] as
-    /// `other`.
+    /// Find the first ancestor span with the same [`SyntaxContext`] as `other`.
+    ///
+    /// This method traverses the macro expansion ancestors until it finds a span
+    /// that has the same [`SyntaxContext`] as `other`.
     ///
     /// Like [`find_ancestor_inside_same_ctxt`], but specifically for when spans might not
     /// overlap. Take care when using this, and prefer [`find_ancestor_inside`] or
@@ -747,9 +754,12 @@ impl Span {
         Some(self)
     }
 
-    /// Walk down the expansion ancestors to find a span that's contained within `outer` and
+    /// Find the first ancestor span that's contained within `outer` and
     /// has the same [`SyntaxContext`] as `outer`.
     ///
+    /// This method traverses the macro expansion ancestors until it finds a span
+    /// that is both contained within `outer` and has the same [`SyntaxContext`] as `outer`.
+    ///
     /// This method is the combination of [`find_ancestor_inside`] and
     /// [`find_ancestor_in_same_ctxt`] and should be preferred when extending the returned span.
     /// If you do not need to modify the span, use [`find_ancestor_inside`] instead.
@@ -763,43 +773,43 @@ impl Span {
         Some(self)
     }
 
-    /// Recursively walk down the expansion ancestors to find the oldest ancestor span with the same
-    /// [`SyntaxContext`] the initial span.
+    /// Find the first ancestor span that does not come from an external macro.
     ///
-    /// This method is suitable for peeling through *local* macro expansions to find the "innermost"
-    /// span that is still local and shares the same [`SyntaxContext`]. For example, given
+    /// This method traverses the macro expansion ancestors until it finds a span
+    /// that is either from user-written code or from a local macro (defined in the current crate).
     ///
-    /// ```ignore (illustrative example, contains type error)
-    ///  macro_rules! outer {
-    ///      ($x: expr) => {
-    ///          inner!($x)
-    ///      }
-    ///  }
+    /// External macros are those defined in dependencies or the standard library.
+    /// This method is useful for reporting errors in user-controllable code and avoiding
+    /// diagnostics inside external macros.
     ///
-    ///  macro_rules! inner {
-    ///      ($x: expr) => {
-    ///          format!("error: {}", $x)
-    ///          //~^ ERROR mismatched types
-    ///      }
-    ///  }
+    /// # See also
     ///
-    ///  fn bar(x: &str) -> Result<(), Box<dyn std::error::Error>> {
-    ///      Err(outer!(x))
-    ///  }
-    /// ```
+    /// - [`Self::find_ancestor_not_from_macro`]
+    /// - [`Self::in_external_macro`]
+    pub fn find_ancestor_not_from_extern_macro(mut self, sm: &SourceMap) -> Option<Span> {
+        while self.in_external_macro(sm) {
+            self = self.parent_callsite()?;
+        }
+        Some(self)
+    }
+
+    /// Find the first ancestor span that does not come from any macro expansion.
     ///
-    /// if provided the initial span of `outer!(x)` inside `bar`, this method will recurse
-    /// the parent callsites until we reach `format!("error: {}", $x)`, at which point it is the
-    /// oldest ancestor span that is both still local and shares the same [`SyntaxContext`] as the
-    /// initial span.
-    pub fn find_oldest_ancestor_in_same_ctxt(self) -> Span {
-        let mut cur = self;
-        while cur.eq_ctxt(self)
-            && let Some(parent_callsite) = cur.parent_callsite()
-        {
-            cur = parent_callsite;
+    /// This method traverses the macro expansion ancestors until it finds a span
+    /// that originates from user-written code rather than any macro-generated code.
+    ///
+    /// This method is useful for reporting errors at the exact location users wrote code
+    /// and providing suggestions at directly editable locations.
+    ///
+    /// # See also
+    ///
+    /// - [`Self::find_ancestor_not_from_extern_macro`]
+    /// - [`Span::from_expansion`]
+    pub fn find_ancestor_not_from_macro(mut self) -> Option<Span> {
+        while self.from_expansion() {
+            self = self.parent_callsite()?;
         }
-        cur
+        Some(self)
     }
 
     /// Edition of the crate from which this span came.
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index c64cd9a51b7..033590e01a6 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -3598,6 +3598,7 @@ impl Target {
             ),
             "x86" => (Architecture::I386, None),
             "s390x" => (Architecture::S390x, None),
+            "m68k" => (Architecture::M68k, None),
             "mips" | "mips32r6" => (Architecture::Mips, None),
             "mips64" | "mips64r6" => (
                 // While there are currently no builtin targets
diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs
index 3ce0f025512..01bdae7435d 100644
--- a/compiler/rustc_trait_selection/src/solve/fulfill.rs
+++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs
@@ -197,6 +197,12 @@ where
                     delegate.compute_goal_fast_path(goal, obligation.cause.span)
                 {
                     match certainty {
+                        // This fast path doesn't depend on region identity so it doesn't
+                        // matter if the goal contains inference variables or not, so we
+                        // don't need to call `push_hir_typeck_potentially_region_dependent_goal`
+                        // here.
+                        //
+                        // Only goals proven via the trait solver should be region dependent.
                         Certainty::Yes => {}
                         Certainty::Maybe(_) => {
                             self.obligations.register(obligation, None);
@@ -207,7 +213,7 @@ where
 
                 let result = delegate.evaluate_root_goal(goal, obligation.cause.span, stalled_on);
                 self.inspect_evaluated_obligation(infcx, &obligation, &result);
-                let GoalEvaluation { certainty, has_changed, stalled_on } = match result {
+                let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result {
                     Ok(result) => result,
                     Err(NoSolution) => {
                         errors.push(E::from_solver_error(
@@ -218,6 +224,10 @@ where
                     }
                 };
 
+                // We've resolved the goal in `evaluate_root_goal`, avoid redoing this work
+                // in the next iteration. This does not resolve the inference variables
+                // constrained by evaluating the goal.
+                obligation.predicate = goal.predicate;
                 if has_changed == HasChanged::Yes {
                     // We increment the recursion depth here to track the number of times
                     // this goal has resulted in inference progress. This doesn't precisely
@@ -230,7 +240,22 @@ where
                 }
 
                 match certainty {
-                    Certainty::Yes => {}
+                    Certainty::Yes => {
+                        // Goals may depend on structural identity. Region uniquification at the
+                        // start of MIR borrowck may cause things to no longer be so, potentially
+                        // causing an ICE.
+                        //
+                        // While we uniquify root goals in HIR this does not handle cases where
+                        // regions are hidden inside of a type or const inference variable.
+                        //
+                        // FIXME(-Znext-solver): This does not handle inference variables hidden
+                        // inside of an opaque type, e.g. if there's `Opaque = (?x, ?x)` in the
+                        // storage, we can also rely on structural identity of `?x` even if we
+                        // later uniquify it in MIR borrowck.
+                        if infcx.in_hir_typeck && obligation.has_non_region_infer() {
+                            infcx.push_hir_typeck_potentially_region_dependent_goal(obligation);
+                        }
+                    }
                     Certainty::Maybe(_) => self.obligations.register(obligation, stalled_on),
                 }
             }
diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs
index e86a2305e23..b4873c8c71c 100644
--- a/compiler/rustc_type_ir/src/infer_ctxt.rs
+++ b/compiler/rustc_type_ir/src/infer_ctxt.rs
@@ -148,6 +148,10 @@ pub trait InferCtxtLike: Sized {
         true
     }
 
+    fn in_hir_typeck(&self) -> bool {
+        false
+    }
+
     fn typing_mode(&self) -> TypingMode<Self::Interner>;
 
     fn universe(&self) -> ty::UniverseIndex;
diff --git a/library/alloc/src/collections/linked_list.rs b/library/alloc/src/collections/linked_list.rs
index 70c344e49b7..31dfe73fc79 100644
--- a/library/alloc/src/collections/linked_list.rs
+++ b/library/alloc/src/collections/linked_list.rs
@@ -825,7 +825,7 @@ impl<T, A: Allocator> LinkedList<T, A> {
         unsafe { self.tail.as_mut().map(|node| &mut node.as_mut().element) }
     }
 
-    /// Adds an element first in the list.
+    /// Adds an element to the front of the list.
     ///
     /// This operation should compute in *O*(1) time.
     ///
@@ -844,11 +844,34 @@ impl<T, A: Allocator> LinkedList<T, A> {
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn push_front(&mut self, elt: T) {
+        let _ = self.push_front_mut(elt);
+    }
+
+    /// Adds an element to the front of the list, returning a reference to it.
+    ///
+    /// This operation should compute in *O*(1) time.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(push_mut)]
+    /// use std::collections::LinkedList;
+    ///
+    /// let mut dl = LinkedList::from([1, 2, 3]);
+    ///
+    /// let ptr = dl.push_front_mut(2);
+    /// *ptr += 4;
+    /// assert_eq!(dl.front().unwrap(), &6);
+    /// ```
+    #[unstable(feature = "push_mut", issue = "135974")]
+    #[must_use = "if you don't need a reference to the value, use `LinkedList::push_front` instead"]
+    pub fn push_front_mut(&mut self, elt: T) -> &mut T {
         let node = Box::new_in(Node::new(elt), &self.alloc);
-        let node_ptr = NonNull::from(Box::leak(node));
+        let mut node_ptr = NonNull::from(Box::leak(node));
         // SAFETY: node_ptr is a unique pointer to a node we boxed with self.alloc and leaked
         unsafe {
             self.push_front_node(node_ptr);
+            &mut node_ptr.as_mut().element
         }
     }
 
@@ -876,7 +899,7 @@ impl<T, A: Allocator> LinkedList<T, A> {
         self.pop_front_node().map(Node::into_element)
     }
 
-    /// Appends an element to the back of a list.
+    /// Adds an element to the back of the list.
     ///
     /// This operation should compute in *O*(1) time.
     ///
@@ -893,11 +916,34 @@ impl<T, A: Allocator> LinkedList<T, A> {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[rustc_confusables("push", "append")]
     pub fn push_back(&mut self, elt: T) {
+        let _ = self.push_back_mut(elt);
+    }
+
+    /// Adds an element to the back of the list, returning a reference to it.
+    ///
+    /// This operation should compute in *O*(1) time.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(push_mut)]
+    /// use std::collections::LinkedList;
+    ///
+    /// let mut dl = LinkedList::from([1, 2, 3]);
+    ///
+    /// let ptr = dl.push_back_mut(2);
+    /// *ptr += 4;
+    /// assert_eq!(dl.back().unwrap(), &6);
+    /// ```
+    #[unstable(feature = "push_mut", issue = "135974")]
+    #[must_use = "if you don't need a reference to the value, use `LinkedList::push_back` instead"]
+    pub fn push_back_mut(&mut self, elt: T) -> &mut T {
         let node = Box::new_in(Node::new(elt), &self.alloc);
-        let node_ptr = NonNull::from(Box::leak(node));
+        let mut node_ptr = NonNull::from(Box::leak(node));
         // SAFETY: node_ptr is a unique pointer to a node we boxed with self.alloc and leaked
         unsafe {
             self.push_back_node(node_ptr);
+            &mut node_ptr.as_mut().element
         }
     }
 
diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs
index 08b1828ff00..2fce5c3e737 100644
--- a/library/alloc/src/collections/vec_deque/mod.rs
+++ b/library/alloc/src/collections/vec_deque/mod.rs
@@ -182,11 +182,16 @@ impl<T, A: Allocator> VecDeque<T, A> {
         unsafe { ptr::read(self.ptr().add(off)) }
     }
 
-    /// Writes an element into the buffer, moving it.
+    /// Writes an element into the buffer, moving it and returning a pointer to it.
+    /// # Safety
+    ///
+    /// May only be called if `off < self.capacity()`.
     #[inline]
-    unsafe fn buffer_write(&mut self, off: usize, value: T) {
+    unsafe fn buffer_write(&mut self, off: usize, value: T) -> &mut T {
         unsafe {
-            ptr::write(self.ptr().add(off), value);
+            let ptr = self.ptr().add(off);
+            ptr::write(ptr, value);
+            &mut *ptr
         }
     }
 
@@ -1888,16 +1893,34 @@ impl<T, A: Allocator> VecDeque<T, A> {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[track_caller]
     pub fn push_front(&mut self, value: T) {
+        let _ = self.push_front_mut(value);
+    }
+
+    /// Prepends an element to the deque, returning a reference to it.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(push_mut)]
+    /// use std::collections::VecDeque;
+    ///
+    /// let mut d = VecDeque::from([1, 2, 3]);
+    /// let x = d.push_front_mut(8);
+    /// *x -= 1;
+    /// assert_eq!(d.front(), Some(&7));
+    /// ```
+    #[unstable(feature = "push_mut", issue = "135974")]
+    #[track_caller]
+    #[must_use = "if you don't need a reference to the value, use `VecDeque::push_front` instead"]
+    pub fn push_front_mut(&mut self, value: T) -> &mut T {
         if self.is_full() {
             self.grow();
         }
 
         self.head = self.wrap_sub(self.head, 1);
         self.len += 1;
-
-        unsafe {
-            self.buffer_write(self.head, value);
-        }
+        // SAFETY: We know that self.head is within range of the deque.
+        unsafe { self.buffer_write(self.head, value) }
     }
 
     /// Appends an element to the back of the deque.
@@ -1916,12 +1939,33 @@ impl<T, A: Allocator> VecDeque<T, A> {
     #[rustc_confusables("push", "put", "append")]
     #[track_caller]
     pub fn push_back(&mut self, value: T) {
+        let _ = self.push_back_mut(value);
+    }
+
+    /// Appends an element to the back of the deque, returning a reference to it.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(push_mut)]
+    /// use std::collections::VecDeque;
+    ///
+    /// let mut d = VecDeque::from([1, 2, 3]);
+    /// let x = d.push_back_mut(9);
+    /// *x += 1;
+    /// assert_eq!(d.back(), Some(&10));
+    /// ```
+    #[unstable(feature = "push_mut", issue = "135974")]
+    #[track_caller]
+    #[must_use = "if you don't need a reference to the value, use `VecDeque::push_back` instead"]
+    pub fn push_back_mut(&mut self, value: T) -> &mut T {
         if self.is_full() {
             self.grow();
         }
 
-        unsafe { self.buffer_write(self.to_physical_idx(self.len), value) }
+        let len = self.len;
         self.len += 1;
+        unsafe { self.buffer_write(self.to_physical_idx(len), value) }
     }
 
     #[inline]
@@ -2007,7 +2051,7 @@ impl<T, A: Allocator> VecDeque<T, A> {
     ///
     /// # Panics
     ///
-    /// Panics if `index` is strictly greater than deque's length
+    /// Panics if `index` is strictly greater than the deque's length.
     ///
     /// # Examples
     ///
@@ -2029,7 +2073,37 @@ impl<T, A: Allocator> VecDeque<T, A> {
     #[stable(feature = "deque_extras_15", since = "1.5.0")]
     #[track_caller]
     pub fn insert(&mut self, index: usize, value: T) {
+        let _ = self.insert_mut(index, value);
+    }
+
+    /// Inserts an element at `index` within the deque, shifting all elements
+    /// with indices greater than or equal to `index` towards the back, and
+    /// returning a reference to it.
+    ///
+    /// Element at index 0 is the front of the queue.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `index` is strictly greater than the deque's length.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(push_mut)]
+    /// use std::collections::VecDeque;
+    ///
+    /// let mut vec_deque = VecDeque::from([1, 2, 3]);
+    ///
+    /// let x = vec_deque.insert_mut(1, 5);
+    /// *x += 7;
+    /// assert_eq!(vec_deque, &[1, 12, 2, 3]);
+    /// ```
+    #[unstable(feature = "push_mut", issue = "135974")]
+    #[track_caller]
+    #[must_use = "if you don't need a reference to the value, use `VecDeque::insert` instead"]
+    pub fn insert_mut(&mut self, index: usize, value: T) -> &mut T {
         assert!(index <= self.len(), "index out of bounds");
+
         if self.is_full() {
             self.grow();
         }
@@ -2042,16 +2116,16 @@ impl<T, A: Allocator> VecDeque<T, A> {
             unsafe {
                 // see `remove()` for explanation why this wrap_copy() call is safe.
                 self.wrap_copy(self.to_physical_idx(index), self.to_physical_idx(index + 1), k);
-                self.buffer_write(self.to_physical_idx(index), value);
                 self.len += 1;
+                self.buffer_write(self.to_physical_idx(index), value)
             }
         } else {
             let old_head = self.head;
             self.head = self.wrap_sub(self.head, 1);
             unsafe {
                 self.wrap_copy(old_head, self.head, index);
-                self.buffer_write(self.to_physical_idx(index), value);
                 self.len += 1;
+                self.buffer_write(self.to_physical_idx(index), value)
             }
         }
     }
diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs
index 9856e9c18ec..ce74615dbcc 100644
--- a/library/alloc/src/vec/mod.rs
+++ b/library/alloc/src/vec/mod.rs
@@ -2046,6 +2046,38 @@ impl<T, A: Allocator> Vec<T, A> {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[track_caller]
     pub fn insert(&mut self, index: usize, element: T) {
+        let _ = self.insert_mut(index, element);
+    }
+
+    /// Inserts an element at position `index` within the vector, shifting all
+    /// elements after it to the right, and returning a reference to the new
+    /// element.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `index > len`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(push_mut)]
+    /// let mut vec = vec![1, 3, 5, 9];
+    /// let x = vec.insert_mut(3, 6);
+    /// *x += 1;
+    /// assert_eq!(vec, [1, 3, 5, 7, 9]);
+    /// ```
+    ///
+    /// # Time complexity
+    ///
+    /// Takes *O*([`Vec::len`]) time. All items after the insertion index must be
+    /// shifted to the right. In the worst case, all elements are shifted when
+    /// the insertion index is 0.
+    #[cfg(not(no_global_oom_handling))]
+    #[inline]
+    #[unstable(feature = "push_mut", issue = "135974")]
+    #[track_caller]
+    #[must_use = "if you don't need a reference to the value, use `Vec::insert` instead"]
+    pub fn insert_mut(&mut self, index: usize, element: T) -> &mut T {
         #[cold]
         #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))]
         #[track_caller]
@@ -2067,8 +2099,8 @@ impl<T, A: Allocator> Vec<T, A> {
         unsafe {
             // infallible
             // The spot to put the new value
+            let p = self.as_mut_ptr().add(index);
             {
-                let p = self.as_mut_ptr().add(index);
                 if index < len {
                     // Shift everything over to make space. (Duplicating the
                     // `index`th element into two consecutive places.)
@@ -2079,6 +2111,7 @@ impl<T, A: Allocator> Vec<T, A> {
                 ptr::write(p, element);
             }
             self.set_len(len + 1);
+            &mut *p
         }
     }
 
@@ -2486,18 +2519,7 @@ impl<T, A: Allocator> Vec<T, A> {
     #[rustc_confusables("push_back", "put", "append")]
     #[track_caller]
     pub fn push(&mut self, value: T) {
-        // Inform codegen that the length does not change across grow_one().
-        let len = self.len;
-        // This will panic or abort if we would allocate > isize::MAX bytes
-        // or if the length increment would overflow for zero-sized types.
-        if len == self.buf.capacity() {
-            self.buf.grow_one();
-        }
-        unsafe {
-            let end = self.as_mut_ptr().add(len);
-            ptr::write(end, value);
-            self.len = len + 1;
-        }
+        let _ = self.push_mut(value);
     }
 
     /// Appends an element if there is sufficient spare capacity, otherwise an error is returned
@@ -2538,6 +2560,77 @@ impl<T, A: Allocator> Vec<T, A> {
     #[inline]
     #[unstable(feature = "vec_push_within_capacity", issue = "100486")]
     pub fn push_within_capacity(&mut self, value: T) -> Result<(), T> {
+        self.push_mut_within_capacity(value).map(|_| ())
+    }
+
+    /// Appends an element to the back of a collection, returning a reference to it.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the new capacity exceeds `isize::MAX` _bytes_.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(push_mut)]
+    ///
+    ///
+    /// let mut vec = vec![1, 2];
+    /// let last = vec.push_mut(3);
+    /// assert_eq!(*last, 3);
+    /// assert_eq!(vec, [1, 2, 3]);
+    ///
+    /// let last = vec.push_mut(3);
+    /// *last += 1;
+    /// assert_eq!(vec, [1, 2, 3, 4]);
+    /// ```
+    ///
+    /// # Time complexity
+    ///
+    /// Takes amortized *O*(1) time. If the vector's length would exceed its
+    /// capacity after the push, *O*(*capacity*) time is taken to copy the
+    /// vector's elements to a larger allocation. This expensive operation is
+    /// offset by the *capacity* *O*(1) insertions it allows.
+    #[cfg(not(no_global_oom_handling))]
+    #[inline]
+    #[unstable(feature = "push_mut", issue = "135974")]
+    #[track_caller]
+    #[must_use = "if you don't need a reference to the value, use `Vec::push` instead"]
+    pub fn push_mut(&mut self, value: T) -> &mut T {
+        // Inform codegen that the length does not change across grow_one().
+        let len = self.len;
+        // This will panic or abort if we would allocate > isize::MAX bytes
+        // or if the length increment would overflow for zero-sized types.
+        if len == self.buf.capacity() {
+            self.buf.grow_one();
+        }
+        unsafe {
+            let end = self.as_mut_ptr().add(len);
+            ptr::write(end, value);
+            self.len = len + 1;
+            // SAFETY: We just wrote a value to the pointer that will live the lifetime of the reference.
+            &mut *end
+        }
+    }
+
+    /// Appends an element and returns a reference to it if there is sufficient spare capacity,
+    /// otherwise an error is returned with the element.
+    ///
+    /// Unlike [`push_mut`] this method will not reallocate when there's insufficient capacity.
+    /// The caller should use [`reserve`] or [`try_reserve`] to ensure that there is enough capacity.
+    ///
+    /// [`push_mut`]: Vec::push_mut
+    /// [`reserve`]: Vec::reserve
+    /// [`try_reserve`]: Vec::try_reserve
+    ///
+    /// # Time complexity
+    ///
+    /// Takes *O*(1) time.
+    #[unstable(feature = "push_mut", issue = "135974")]
+    // #[unstable(feature = "vec_push_within_capacity", issue = "100486")]
+    #[inline]
+    #[must_use = "if you don't need a reference to the value, use `Vec::push_within_capacity` instead"]
+    pub fn push_mut_within_capacity(&mut self, value: T) -> Result<&mut T, T> {
         if self.len == self.buf.capacity() {
             return Err(value);
         }
@@ -2545,8 +2638,9 @@ impl<T, A: Allocator> Vec<T, A> {
             let end = self.as_mut_ptr().add(self.len);
             ptr::write(end, value);
             self.len += 1;
+            // SAFETY: We just wrote a value to the pointer that will live the lifetime of the reference.
+            Ok(&mut *end)
         }
-        Ok(())
     }
 
     /// Removes the last element from a vector and returns it, or [`None`] if it
diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs
index 97227020885..6ef7d5a22a3 100644
--- a/library/core/src/panic/location.rs
+++ b/library/core/src/panic/location.rs
@@ -1,5 +1,7 @@
+use crate::cmp::Ordering;
 use crate::ffi::CStr;
 use crate::fmt;
+use crate::hash::{Hash, Hasher};
 use crate::marker::PhantomData;
 use crate::ptr::NonNull;
 
@@ -32,7 +34,7 @@ use crate::ptr::NonNull;
 /// Files are compared as strings, not `Path`, which could be unexpected.
 /// See [`Location::file`]'s documentation for more discussion.
 #[lang = "panic_location"]
-#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
+#[derive(Copy, Clone)]
 #[stable(feature = "panic_hooks", since = "1.10.0")]
 pub struct Location<'a> {
     // A raw pointer is used rather than a reference because the pointer is valid for one more byte
@@ -45,6 +47,44 @@ pub struct Location<'a> {
 }
 
 #[stable(feature = "panic_hooks", since = "1.10.0")]
+impl PartialEq for Location<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        // Compare col / line first as they're cheaper to compare and more likely to differ,
+        // while not impacting the result.
+        self.col == other.col && self.line == other.line && self.file() == other.file()
+    }
+}
+
+#[stable(feature = "panic_hooks", since = "1.10.0")]
+impl Eq for Location<'_> {}
+
+#[stable(feature = "panic_hooks", since = "1.10.0")]
+impl Ord for Location<'_> {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.file()
+            .cmp(other.file())
+            .then_with(|| self.line.cmp(&other.line))
+            .then_with(|| self.col.cmp(&other.col))
+    }
+}
+
+#[stable(feature = "panic_hooks", since = "1.10.0")]
+impl PartialOrd for Location<'_> {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+#[stable(feature = "panic_hooks", since = "1.10.0")]
+impl Hash for Location<'_> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.file().hash(state);
+        self.line.hash(state);
+        self.col.hash(state);
+    }
+}
+
+#[stable(feature = "panic_hooks", since = "1.10.0")]
 impl fmt::Debug for Location<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("Location")
diff --git a/library/coretests/tests/panic/location.rs b/library/coretests/tests/panic/location.rs
index 5ce0b06e90e..910001bcc1c 100644
--- a/library/coretests/tests/panic/location.rs
+++ b/library/coretests/tests/panic/location.rs
@@ -3,6 +3,23 @@ use core::panic::Location;
 // Note: Some of the following tests depend on the source location,
 // so please be careful when editing this file.
 
+mod file_a;
+mod file_b;
+mod file_c;
+
+// A small shuffled set of locations for testing, along with their true order.
+const LOCATIONS: [(usize, &'static Location<'_>); 9] = [
+    (7, file_c::two()),
+    (0, file_a::one()),
+    (3, file_b::one()),
+    (5, file_b::three()),
+    (8, file_c::three()),
+    (6, file_c::one()),
+    (2, file_a::three()),
+    (4, file_b::two()),
+    (1, file_a::two()),
+];
+
 #[test]
 fn location_const_caller() {
     const _CALLER_REFERENCE: &Location<'static> = Location::caller();
@@ -20,7 +37,7 @@ fn location_const_file() {
 fn location_const_line() {
     const CALLER: &Location<'static> = Location::caller();
     const LINE: u32 = CALLER.line();
-    assert_eq!(LINE, 21);
+    assert_eq!(LINE, 38);
 }
 
 #[test]
@@ -34,6 +51,28 @@ fn location_const_column() {
 fn location_debug() {
     let f = format!("{:?}", Location::caller());
     assert!(f.contains(&format!("{:?}", file!())));
-    assert!(f.contains("35"));
+    assert!(f.contains("52"));
     assert!(f.contains("29"));
 }
+
+#[test]
+fn location_eq() {
+    for (i, a) in LOCATIONS {
+        for (j, b) in LOCATIONS {
+            if i == j {
+                assert_eq!(a, b);
+            } else {
+                assert_ne!(a, b);
+            }
+        }
+    }
+}
+
+#[test]
+fn location_ord() {
+    let mut locations = LOCATIONS.clone();
+    locations.sort_by_key(|(_o, l)| **l);
+    for (correct, (order, _l)) in locations.iter().enumerate() {
+        assert_eq!(correct, *order);
+    }
+}
diff --git a/library/coretests/tests/panic/location/file_a.rs b/library/coretests/tests/panic/location/file_a.rs
new file mode 100644
index 00000000000..1ceb225ee9c
--- /dev/null
+++ b/library/coretests/tests/panic/location/file_a.rs
@@ -0,0 +1,15 @@
+use core::panic::Location;
+
+// Used for test super::location_{ord, eq}. Must be in a dedicated file.
+
+pub const fn one() -> &'static Location<'static> {
+    Location::caller()
+}
+
+pub const fn two() -> &'static Location<'static> {
+    Location::caller()
+}
+
+pub const fn three() -> &'static Location<'static> {
+    Location::caller()
+}
diff --git a/library/coretests/tests/panic/location/file_b.rs b/library/coretests/tests/panic/location/file_b.rs
new file mode 100644
index 00000000000..1ceb225ee9c
--- /dev/null
+++ b/library/coretests/tests/panic/location/file_b.rs
@@ -0,0 +1,15 @@
+use core::panic::Location;
+
+// Used for test super::location_{ord, eq}. Must be in a dedicated file.
+
+pub const fn one() -> &'static Location<'static> {
+    Location::caller()
+}
+
+pub const fn two() -> &'static Location<'static> {
+    Location::caller()
+}
+
+pub const fn three() -> &'static Location<'static> {
+    Location::caller()
+}
diff --git a/library/coretests/tests/panic/location/file_c.rs b/library/coretests/tests/panic/location/file_c.rs
new file mode 100644
index 00000000000..2ac416c19cf
--- /dev/null
+++ b/library/coretests/tests/panic/location/file_c.rs
@@ -0,0 +1,11 @@
+// Used for test super::location_{ord, eq}. Must be in a dedicated file.
+
+// This is used for testing column ordering of Location, hence this ugly one-liner.
+// We must fmt skip the entire containing module or else tidy will still complain.
+#[rustfmt::skip]
+mod no_fmt {
+    use core::panic::Location;
+    pub const fn one() -> &'static Location<'static> { Location::caller() }    pub const fn two() -> &'static Location<'static> { Location::caller() }    pub const fn three() -> &'static Location<'static> { Location::caller() }
+}
+
+pub use no_fmt::*;
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 0f9268097d7..951ca73fcc4 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -8,6 +8,9 @@ use std::ffi::{OsStr, OsString};
 use std::path::{Path, PathBuf};
 use std::{env, fs, iter};
 
+#[cfg(feature = "tracing")]
+use tracing::instrument;
+
 use crate::core::build_steps::compile::{Std, run_cargo};
 use crate::core::build_steps::doc::DocumentationFormat;
 use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags};
@@ -30,7 +33,7 @@ use crate::utils::helpers::{
     linker_flags, t, target_supports_cranelift_backend, up_to_date,
 };
 use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests};
-use crate::{CLang, DocTests, GitRepo, Mode, PathSet, envify};
+use crate::{CLang, DocTests, GitRepo, Mode, PathSet, debug, envify};
 
 const ADB_TEST_DIR: &str = "/data/local/tmp/work";
 
@@ -713,9 +716,23 @@ impl Step for CompiletestTest {
     }
 
     /// Runs `cargo test` for compiletest.
+    #[cfg_attr(
+        feature = "tracing",
+        instrument(level = "debug", name = "CompiletestTest::run", skip_all)
+    )]
     fn run(self, builder: &Builder<'_>) {
         let host = self.host;
+
+        if builder.top_stage == 0 && !builder.config.compiletest_allow_stage0 {
+            eprintln!("\
+ERROR: `--stage 0` runs compiletest self-tests against the stage0 (precompiled) compiler, not the in-tree compiler, and will almost always cause tests to fail
+NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `--set build.compiletest-allow-stage0=true`."
+            );
+            crate::exit!(1);
+        }
+
         let compiler = builder.compiler(builder.top_stage, host);
+        debug!(?compiler);
 
         // We need `ToolStd` for the locally-built sysroot because
         // compiletest uses unstable features of the `test` crate.
@@ -723,8 +740,8 @@ impl Step for CompiletestTest {
         let mut cargo = tool::prepare_tool_cargo(
             builder,
             compiler,
-            // compiletest uses libtest internals; make it use the in-tree std to make sure it never breaks
-            // when std sources change.
+            // compiletest uses libtest internals; make it use the in-tree std to make sure it never
+            // breaks when std sources change.
             Mode::ToolStd,
             host,
             Kind::Test,
@@ -1612,12 +1629,11 @@ impl Step for Compiletest {
             return;
         }
 
-        if builder.top_stage == 0 && env::var("COMPILETEST_FORCE_STAGE0").is_err() {
+        if builder.top_stage == 0 && !builder.config.compiletest_allow_stage0 {
             eprintln!("\
 ERROR: `--stage 0` runs compiletest on the stage0 (precompiled) compiler, not your local changes, and will almost always cause tests to fail
-HELP: to test the compiler, use `--stage 1` instead
-HELP: to test the standard library, use `--stage 0 library/std` instead
-NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `COMPILETEST_FORCE_STAGE0=1`."
+HELP: to test the compiler or standard library, omit the stage or explicitly use `--stage 1` instead
+NOTE: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `--set build.compiletest-allow-stage0=true`."
             );
             crate::exit!(1);
         }
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index 9644ade00b3..78abdd7f9b8 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -298,8 +298,16 @@ pub struct Config {
     /// Command for visual diff display, e.g. `diff-tool --color=always`.
     pub compiletest_diff_tool: Option<String>,
 
+    /// Whether to allow running both `compiletest` self-tests and `compiletest`-managed test suites
+    /// against the stage 0 (rustc, std).
+    ///
+    /// This is only intended to be used when the stage 0 compiler is actually built from in-tree
+    /// sources.
+    pub compiletest_allow_stage0: bool,
+
     /// Whether to use the precompiled stage0 libtest with compiletest.
     pub compiletest_use_stage0_libtest: bool,
+
     /// Default value for `--extra-checks`
     pub tidy_extra_checks: Option<String>,
     pub is_running_on_ci: bool,
@@ -749,6 +757,7 @@ impl Config {
             optimized_compiler_builtins,
             jobs,
             compiletest_diff_tool,
+            compiletest_allow_stage0,
             compiletest_use_stage0_libtest,
             tidy_extra_checks,
             ccache,
@@ -1020,8 +1029,12 @@ impl Config {
 
         config.optimized_compiler_builtins =
             optimized_compiler_builtins.unwrap_or(config.channel != "dev");
+
         config.compiletest_diff_tool = compiletest_diff_tool;
+
+        config.compiletest_allow_stage0 = compiletest_allow_stage0.unwrap_or(false);
         config.compiletest_use_stage0_libtest = compiletest_use_stage0_libtest.unwrap_or(true);
+
         config.tidy_extra_checks = tidy_extra_checks;
 
         let download_rustc = config.download_rustc_commit.is_some();
diff --git a/src/bootstrap/src/core/config/toml/build.rs b/src/bootstrap/src/core/config/toml/build.rs
index 4d29691f38b..728367b3972 100644
--- a/src/bootstrap/src/core/config/toml/build.rs
+++ b/src/bootstrap/src/core/config/toml/build.rs
@@ -68,6 +68,7 @@ define_config! {
         optimized_compiler_builtins: Option<bool> = "optimized-compiler-builtins",
         jobs: Option<u32> = "jobs",
         compiletest_diff_tool: Option<String> = "compiletest-diff-tool",
+        compiletest_allow_stage0: Option<bool> = "compiletest-allow-stage0",
         compiletest_use_stage0_libtest: Option<bool> = "compiletest-use-stage0-libtest",
         tidy_extra_checks: Option<String> = "tidy-extra-checks",
         ccache: Option<StringOrBool> = "ccache",
diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs
index 4b0c4821364..d3331b81587 100644
--- a/src/bootstrap/src/utils/change_tracker.rs
+++ b/src/bootstrap/src/utils/change_tracker.rs
@@ -486,4 +486,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
         severity: ChangeSeverity::Warning,
         summary: "Removed `rust.description` and `llvm.ccache` as it was deprecated in #137723 and #136941 long time ago.",
     },
+    ChangeInfo {
+        change_id: 144675,
+        severity: ChangeSeverity::Warning,
+        summary: "Added `build.compiletest-allow-stage0` flag instead of `COMPILETEST_FORCE_STAGE0` env var, and reject running `compiletest` self tests against stage 0 rustc unless explicitly allowed.",
+    },
 ];
diff --git a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
index e1d83d36087..0855ea222a3 100644
--- a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
+++ b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile
@@ -81,9 +81,9 @@ RUN /tmp/build-fuchsia-toolchain.sh
 COPY host-x86_64/dist-various-2/build-x86_64-fortanix-unknown-sgx-toolchain.sh /tmp/
 RUN /tmp/build-x86_64-fortanix-unknown-sgx-toolchain.sh
 
-RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz | \
+RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-x86_64-linux.tar.gz | \
   tar -xz
-ENV WASI_SDK_PATH=/tmp/wasi-sdk-25.0-x86_64-linux
+ENV WASI_SDK_PATH=/tmp/wasi-sdk-27.0-x86_64-linux
 
 COPY scripts/freebsd-toolchain.sh /tmp/
 RUN /tmp/freebsd-toolchain.sh i686
diff --git a/src/ci/docker/host-x86_64/pr-check-1/Dockerfile b/src/ci/docker/host-x86_64/pr-check-1/Dockerfile
index f7d51fba861..04ac0f33daf 100644
--- a/src/ci/docker/host-x86_64/pr-check-1/Dockerfile
+++ b/src/ci/docker/host-x86_64/pr-check-1/Dockerfile
@@ -43,7 +43,6 @@ ENV SCRIPT \
   python3 ../x.py check bootstrap && \
   /scripts/check-default-config-profiles.sh && \
   python3 ../x.py build src/tools/build-manifest && \
-  python3 ../x.py test --stage 0 src/tools/compiletest && \
   python3 ../x.py check compiletest --set build.compiletest-use-stage0-libtest=true && \
   python3 ../x.py check --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu && \
   python3 ../x.py check --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \
diff --git a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
index ce18a181d31..f82e19bcbb4 100644
--- a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
+++ b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile
@@ -30,6 +30,7 @@ ENV SCRIPT \
         python3 ../x.py check && \
         python3 ../x.py clippy ci && \
         python3 ../x.py test --stage 1 core alloc std test proc_macro && \
+        python3 ../x.py test --stage 1 src/tools/compiletest && \
         python3 ../x.py doc --stage 0 bootstrap && \
         # Build both public and internal documentation.
         RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 0 compiler && \
@@ -37,6 +38,6 @@ ENV SCRIPT \
         mkdir -p /checkout/obj/staging/doc && \
         cp -r build/x86_64-unknown-linux-gnu/doc /checkout/obj/staging && \
         RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library/test && \
-        # The BOOTSTRAP_TRACING flag is added to verify whether the 
+        # The BOOTSTRAP_TRACING flag is added to verify whether the
         # bootstrap process compiles successfully with this flag enabled.
         BOOTSTRAP_TRACING=1 python3 ../x.py --help
diff --git a/src/ci/docker/host-x86_64/test-various/Dockerfile b/src/ci/docker/host-x86_64/test-various/Dockerfile
index 662a26400ce..82a820c859d 100644
--- a/src/ci/docker/host-x86_64/test-various/Dockerfile
+++ b/src/ci/docker/host-x86_64/test-various/Dockerfile
@@ -40,9 +40,9 @@ WORKDIR /
 COPY scripts/sccache.sh /scripts/
 RUN sh /scripts/sccache.sh
 
-RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz | \
+RUN curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-x86_64-linux.tar.gz | \
   tar -xz
-ENV WASI_SDK_PATH=/wasi-sdk-25.0-x86_64-linux
+ENV WASI_SDK_PATH=/wasi-sdk-27.0-x86_64-linux
 
 ENV RUST_CONFIGURE_ARGS \
   --musl-root-x86_64=/usr/local/x86_64-linux-musl \
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index 0e1edfe21de..48c570bfa11 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -31,20 +31,11 @@ runners:
     <<: *base-job
 
   - &job-windows
-    os: windows-2022
-    <<: *base-job
-
-  # NOTE: windows-2025 has less disk space available than windows-2022,
-  # because the D drive is missing.
-  - &job-windows-25
     os: windows-2025
+    free_disk: true
     <<: *base-job
 
   - &job-windows-8c
-    os: windows-2022-8core-32gb
-    <<: *base-job
-
-  - &job-windows-25-8c
     os: windows-2025-8core-32gb
     <<: *base-job
 
@@ -668,7 +659,7 @@ auto:
       SCRIPT: python x.py build --set rust.debug=true opt-dist && PGO_HOST=x86_64-pc-windows-msvc ./build/x86_64-pc-windows-msvc/stage0-tools-bin/opt-dist windows-ci -- python x.py dist bootstrap --include-default-paths
       DIST_REQUIRE_ALL_TOOLS: 1
       CODEGEN_BACKENDS: llvm,cranelift
-    <<: *job-windows-25-8c
+    <<: *job-windows-8c
 
   - name: dist-i686-msvc
     env:
diff --git a/src/ci/scripts/free-disk-space-linux.sh b/src/ci/scripts/free-disk-space-linux.sh
new file mode 100755
index 00000000000..32649fe0d9b
--- /dev/null
+++ b/src/ci/scripts/free-disk-space-linux.sh
@@ -0,0 +1,265 @@
+#!/bin/bash
+set -euo pipefail
+
+# Free disk space on Linux GitHub action runners
+# Script inspired by https://github.com/jlumbroso/free-disk-space
+
+isX86() {
+    local arch
+    arch=$(uname -m)
+    if [ "$arch" = "x86_64" ]; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+# Check if we're on a GitHub hosted runner.
+# In aws codebuild, the variable RUNNER_ENVIRONMENT is "self-hosted".
+isGitHubRunner() {
+    # `:-` means "use the value of RUNNER_ENVIRONMENT if it exists, otherwise use an empty string".
+    if [[ "${RUNNER_ENVIRONMENT:-}" == "github-hosted" ]]; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+# print a line of the specified character
+printSeparationLine() {
+    for ((i = 0; i < 80; i++)); do
+        printf "%s" "$1"
+    done
+    printf "\n"
+}
+
+# compute available space
+# REF: https://unix.stackexchange.com/a/42049/60849
+# REF: https://stackoverflow.com/a/450821/408734
+getAvailableSpace() {
+    df -a | awk 'NR > 1 {avail+=$4} END {print avail}'
+}
+
+# make Kb human readable (assume the input is Kb)
+# REF: https://unix.stackexchange.com/a/44087/60849
+formatByteCount() {
+    numfmt --to=iec-i --suffix=B --padding=7 "${1}000"
+}
+
+# macro to output saved space
+printSavedSpace() {
+    # Disk space before the operation
+    local before=${1}
+    local title=${2:-}
+
+    local after
+    after=$(getAvailableSpace)
+    local saved=$((after - before))
+
+    if [ "$saved" -lt 0 ]; then
+        echo "::warning::Saved space is negative: $saved. Using '0' as saved space."
+        saved=0
+    fi
+
+    echo ""
+    printSeparationLine "*"
+    if [ -n "${title}" ]; then
+        echo "=> ${title}: Saved $(formatByteCount "$saved")"
+    else
+        echo "=> Saved $(formatByteCount "$saved")"
+    fi
+    printSeparationLine "*"
+    echo ""
+}
+
+# macro to print output of df with caption
+printDF() {
+    local caption=${1}
+
+    printSeparationLine "="
+    echo "${caption}"
+    echo ""
+    echo "$ df -h"
+    echo ""
+    df -h
+    printSeparationLine "="
+}
+
+removeUnusedFilesAndDirs() {
+    local to_remove=(
+        "/usr/share/java"
+    )
+
+    if isGitHubRunner; then
+        to_remove+=(
+            "/usr/local/aws-sam-cli"
+            "/usr/local/doc/cmake"
+            "/usr/local/julia"*
+            "/usr/local/lib/android"
+            "/usr/local/share/chromedriver-"*
+            "/usr/local/share/chromium"
+            "/usr/local/share/cmake-"*
+            "/usr/local/share/edge_driver"
+            "/usr/local/share/emacs"
+            "/usr/local/share/gecko_driver"
+            "/usr/local/share/icons"
+            "/usr/local/share/powershell"
+            "/usr/local/share/vcpkg"
+            "/usr/local/share/vim"
+            "/usr/share/apache-maven-"*
+            "/usr/share/gradle-"*
+            "/usr/share/kotlinc"
+            "/usr/share/miniconda"
+            "/usr/share/php"
+            "/usr/share/ri"
+            "/usr/share/swift"
+
+            # binaries
+            "/usr/local/bin/azcopy"
+            "/usr/local/bin/bicep"
+            "/usr/local/bin/ccmake"
+            "/usr/local/bin/cmake-"*
+            "/usr/local/bin/cmake"
+            "/usr/local/bin/cpack"
+            "/usr/local/bin/ctest"
+            "/usr/local/bin/helm"
+            "/usr/local/bin/kind"
+            "/usr/local/bin/kustomize"
+            "/usr/local/bin/minikube"
+            "/usr/local/bin/packer"
+            "/usr/local/bin/phpunit"
+            "/usr/local/bin/pulumi-"*
+            "/usr/local/bin/pulumi"
+            "/usr/local/bin/stack"
+
+            # Haskell runtime
+            "/usr/local/.ghcup"
+
+            # Azure
+            "/opt/az"
+            "/usr/share/az_"*
+        )
+
+        if [ -n "${AGENT_TOOLSDIRECTORY:-}" ]; then
+            # Environment variable set by GitHub Actions
+            to_remove+=(
+                "${AGENT_TOOLSDIRECTORY}"
+            )
+        else
+            echo "::warning::AGENT_TOOLSDIRECTORY is not set. Skipping removal."
+        fi
+    else
+        # Remove folders and files present in AWS CodeBuild
+        to_remove+=(
+            # binaries
+            "/usr/local/bin/ecs-cli"
+            "/usr/local/bin/eksctl"
+            "/usr/local/bin/kubectl"
+
+            "${HOME}/.gradle"
+            "${HOME}/.dotnet"
+            "${HOME}/.goenv"
+            "${HOME}/.phpenv"
+
+        )
+    fi
+
+    for element in "${to_remove[@]}"; do
+        if [ ! -e "$element" ]; then
+            # The file or directory doesn't exist.
+            # Maybe it was removed in a newer version of the runner or it's not present in a
+            # specific architecture (e.g. ARM).
+            echo "::warning::Directory or file $element does not exist, skipping."
+        fi
+    done
+
+    # Remove all files and directories at once to save time.
+    sudo rm -rf "${to_remove[@]}"
+}
+
+execAndMeasureSpaceChange() {
+    local operation=${1} # Function to execute
+    local title=${2}
+
+    local before
+    before=$(getAvailableSpace)
+    $operation
+
+    printSavedSpace "$before" "$title"
+}
+
+# Remove large packages
+# REF: https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh
+cleanPackages() {
+    local packages=(
+        '^aspnetcore-.*'
+        '^dotnet-.*'
+        '^llvm-.*'
+        '^mongodb-.*'
+        'firefox'
+        'libgl1-mesa-dri'
+        'mono-devel'
+        'php.*'
+    )
+
+    if isGitHubRunner; then
+        packages+=(
+            azure-cli
+        )
+
+        if isX86; then
+            packages+=(
+                'google-chrome-stable'
+                'google-cloud-cli'
+                'google-cloud-sdk'
+                'powershell'
+            )
+        fi
+    else
+        packages+=(
+            'google-chrome-stable'
+        )
+    fi
+
+    sudo apt-get -qq remove -y --fix-missing "${packages[@]}"
+
+    sudo apt-get autoremove -y || echo "::warning::The command [sudo apt-get autoremove -y] failed"
+    sudo apt-get clean || echo "::warning::The command [sudo apt-get clean] failed failed"
+}
+
+# Remove Docker images.
+# Ubuntu 22 runners have docker images already installed.
+# They aren't present in ubuntu 24 runners.
+cleanDocker() {
+    echo "=> Removing the following docker images:"
+    sudo docker image ls
+    echo "=> Removing docker images..."
+    sudo docker image prune --all --force || true
+}
+
+# Remove Swap storage
+cleanSwap() {
+    sudo swapoff -a || true
+    sudo rm -rf /mnt/swapfile || true
+    free -h
+}
+
+# Display initial disk space stats
+
+AVAILABLE_INITIAL=$(getAvailableSpace)
+
+printDF "BEFORE CLEAN-UP:"
+echo ""
+execAndMeasureSpaceChange cleanPackages "Unused packages"
+execAndMeasureSpaceChange cleanDocker "Docker images"
+execAndMeasureSpaceChange cleanSwap "Swap storage"
+execAndMeasureSpaceChange removeUnusedFilesAndDirs "Unused files and directories"
+
+# Output saved space statistic
+echo ""
+printDF "AFTER CLEAN-UP:"
+
+echo ""
+echo ""
+
+printSavedSpace "$AVAILABLE_INITIAL" "Total saved"
diff --git a/src/ci/scripts/free-disk-space-windows.ps1 b/src/ci/scripts/free-disk-space-windows.ps1
new file mode 100644
index 00000000000..8a4677bd2ab
--- /dev/null
+++ b/src/ci/scripts/free-disk-space-windows.ps1
@@ -0,0 +1,35 @@
+# Free disk space on Windows GitHub action runners.
+
+$ErrorActionPreference = 'Stop'
+
+Get-Volume | Out-String | Write-Output
+
+$available = $(Get-Volume C).SizeRemaining
+
+$dirs = 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm',
+'C:\rtools45', 'C:\ghcup', 'C:\Program Files (x86)\Android',
+'C:\Program Files\Google\Chrome', 'C:\Program Files (x86)\Microsoft\Edge',
+'C:\Program Files\Mozilla Firefox', 'C:\Program Files\MySQL', 'C:\Julia',
+'C:\Program Files\MongoDB', 'C:\Program Files\Azure Cosmos DB Emulator',
+'C:\Program Files\PostgreSQL', 'C:\Program Files\Unity Hub',
+'C:\Strawberry', 'C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk'
+
+foreach ($dir in $dirs) {
+    Start-ThreadJob -InputObject $dir {
+        Remove-Item -Recurse -Force -LiteralPath $input
+    } | Out-Null
+}
+
+foreach ($job in Get-Job) {
+    Wait-Job $job  | Out-Null
+    if ($job.Error) {
+        Write-Output "::warning file=$PSCommandPath::$($job.Error)"
+    }
+    Remove-Job $job
+}
+
+Get-Volume | Out-String | Write-Output
+
+$saved = ($(Get-Volume C).SizeRemaining - $available) / 1gb
+$savedRounded = [math]::Round($saved, 3)
+Write-Output "total space saved: $savedRounded GB"
diff --git a/src/ci/scripts/free-disk-space.sh b/src/ci/scripts/free-disk-space.sh
index 173f64858b3..062ad801cd8 100755
--- a/src/ci/scripts/free-disk-space.sh
+++ b/src/ci/scripts/free-disk-space.sh
@@ -1,266 +1,10 @@
 #!/bin/bash
 set -euo pipefail
 
-# Free disk space on Linux GitHub action runners
-# Script inspired by https://github.com/jlumbroso/free-disk-space
+script_dir=$(dirname "$0")
 
-isX86() {
-    local arch
-    arch=$(uname -m)
-    if [ "$arch" = "x86_64" ]; then
-        return 0
-    else
-        return 1
-    fi
-}
-
-# Check if we're on a GitHub hosted runner.
-# In aws codebuild, the variable RUNNER_ENVIRONMENT is "self-hosted".
-isGitHubRunner() {
-    # `:-` means "use the value of RUNNER_ENVIRONMENT if it exists, otherwise use an empty string".
-    if [[ "${RUNNER_ENVIRONMENT:-}" == "github-hosted" ]]; then
-        return 0
-    else
-        return 1
-    fi
-}
-
-# print a line of the specified character
-printSeparationLine() {
-    for ((i = 0; i < 80; i++)); do
-        printf "%s" "$1"
-    done
-    printf "\n"
-}
-
-# compute available space
-# REF: https://unix.stackexchange.com/a/42049/60849
-# REF: https://stackoverflow.com/a/450821/408734
-getAvailableSpace() {
-    df -a | awk 'NR > 1 {avail+=$4} END {print avail}'
-}
-
-# make Kb human readable (assume the input is Kb)
-# REF: https://unix.stackexchange.com/a/44087/60849
-formatByteCount() {
-    numfmt --to=iec-i --suffix=B --padding=7 "${1}000"
-}
-
-# macro to output saved space
-printSavedSpace() {
-    # Disk space before the operation
-    local before=${1}
-    local title=${2:-}
-
-    local after
-    after=$(getAvailableSpace)
-    local saved=$((after - before))
-
-    if [ "$saved" -lt 0 ]; then
-        echo "::warning::Saved space is negative: $saved. Using '0' as saved space."
-        saved=0
-    fi
-
-    echo ""
-    printSeparationLine "*"
-    if [ -n "${title}" ]; then
-        echo "=> ${title}: Saved $(formatByteCount "$saved")"
-    else
-        echo "=> Saved $(formatByteCount "$saved")"
-    fi
-    printSeparationLine "*"
-    echo ""
-}
-
-# macro to print output of df with caption
-printDF() {
-    local caption=${1}
-
-    printSeparationLine "="
-    echo "${caption}"
-    echo ""
-    echo "$ df -h"
-    echo ""
-    df -h
-    printSeparationLine "="
-}
-
-removeUnusedFilesAndDirs() {
-    local to_remove=(
-        "/usr/share/java"
-    )
-
-    if isGitHubRunner; then
-        to_remove+=(
-            "/usr/local/aws-sam-cli"
-            "/usr/local/doc/cmake"
-            "/usr/local/julia"*
-            "/usr/local/lib/android"
-            "/usr/local/share/chromedriver-"*
-            "/usr/local/share/chromium"
-            "/usr/local/share/cmake-"*
-            "/usr/local/share/edge_driver"
-            "/usr/local/share/emacs"
-            "/usr/local/share/gecko_driver"
-            "/usr/local/share/icons"
-            "/usr/local/share/powershell"
-            "/usr/local/share/vcpkg"
-            "/usr/local/share/vim"
-            "/usr/share/apache-maven-"*
-            "/usr/share/gradle-"*
-            "/usr/share/kotlinc"
-            "/usr/share/miniconda"
-            "/usr/share/php"
-            "/usr/share/ri"
-            "/usr/share/swift"
-
-            # binaries
-            "/usr/local/bin/azcopy"
-            "/usr/local/bin/bicep"
-            "/usr/local/bin/ccmake"
-            "/usr/local/bin/cmake-"*
-            "/usr/local/bin/cmake"
-            "/usr/local/bin/cpack"
-            "/usr/local/bin/ctest"
-            "/usr/local/bin/helm"
-            "/usr/local/bin/kind"
-            "/usr/local/bin/kustomize"
-            "/usr/local/bin/minikube"
-            "/usr/local/bin/packer"
-            "/usr/local/bin/phpunit"
-            "/usr/local/bin/pulumi-"*
-            "/usr/local/bin/pulumi"
-            "/usr/local/bin/stack"
-
-            # Haskell runtime
-            "/usr/local/.ghcup"
-
-            # Azure
-            "/opt/az"
-            "/usr/share/az_"*
-        )
-
-        if [ -n "${AGENT_TOOLSDIRECTORY:-}" ]; then
-            # Environment variable set by GitHub Actions
-            to_remove+=(
-                "${AGENT_TOOLSDIRECTORY}"
-            )
-        else
-            echo "::warning::AGENT_TOOLSDIRECTORY is not set. Skipping removal."
-        fi
-    else
-        # Remove folders and files present in AWS CodeBuild
-        to_remove+=(
-            # binaries
-            "/usr/local/bin/ecs-cli"
-            "/usr/local/bin/eksctl"
-            "/usr/local/bin/kubectl"
-
-            "${HOME}/.gradle"
-            "${HOME}/.dotnet"
-            "${HOME}/.goenv"
-            "${HOME}/.phpenv"
-
-        )
-    fi
-
-    for element in "${to_remove[@]}"; do
-        if [ ! -e "$element" ]; then
-            # The file or directory doesn't exist.
-            # Maybe it was removed in a newer version of the runner or it's not present in a
-            # specific architecture (e.g. ARM).
-            echo "::warning::Directory or file $element does not exist, skipping."
-        fi
-    done
-
-    # Remove all files and directories at once to save time.
-    sudo rm -rf "${to_remove[@]}"
-}
-
-execAndMeasureSpaceChange() {
-    local operation=${1} # Function to execute
-    local title=${2}
-
-    local before
-    before=$(getAvailableSpace)
-    $operation
-
-    printSavedSpace "$before" "$title"
-}
-
-# Remove large packages
-# REF: https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh
-cleanPackages() {
-    local packages=(
-        '^aspnetcore-.*'
-        '^dotnet-.*'
-        '^llvm-.*'
-        '^mongodb-.*'
-        'firefox'
-        'libgl1-mesa-dri'
-        'mono-devel'
-        'php.*'
-    )
-
-    if isGitHubRunner; then
-        packages+=(
-            azure-cli
-        )
-
-        if isX86; then
-            packages+=(
-                'google-chrome-stable'
-                'google-cloud-cli'
-                'google-cloud-sdk'
-                'powershell'
-            )
-        fi
-    else
-        packages+=(
-            'google-chrome-stable'
-        )
-    fi
-
-    sudo apt-get -qq remove -y --fix-missing "${packages[@]}"
-
-    sudo apt-get autoremove -y || echo "::warning::The command [sudo apt-get autoremove -y] failed"
-    sudo apt-get clean || echo "::warning::The command [sudo apt-get clean] failed failed"
-}
-
-# Remove Docker images.
-# Ubuntu 22 runners have docker images already installed.
-# They aren't present in ubuntu 24 runners.
-cleanDocker() {
-    echo "=> Removing the following docker images:"
-    sudo docker image ls
-    echo "=> Removing docker images..."
-    sudo docker image prune --all --force || true
-}
-
-# Remove Swap storage
-cleanSwap() {
-    sudo swapoff -a || true
-    sudo rm -rf /mnt/swapfile || true
-    free -h
-}
-
-# Display initial disk space stats
-
-AVAILABLE_INITIAL=$(getAvailableSpace)
-
-printDF "BEFORE CLEAN-UP:"
-echo ""
-
-execAndMeasureSpaceChange cleanPackages "Unused packages"
-execAndMeasureSpaceChange cleanDocker "Docker images"
-execAndMeasureSpaceChange cleanSwap "Swap storage"
-execAndMeasureSpaceChange removeUnusedFilesAndDirs "Unused files and directories"
-
-# Output saved space statistic
-echo ""
-printDF "AFTER CLEAN-UP:"
-
-echo ""
-echo ""
-
-printSavedSpace "$AVAILABLE_INITIAL" "Total saved"
+if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
+    pwsh $script_dir/free-disk-space-windows.ps1
+else
+    $script_dir/free-disk-space-linux.sh
+fi
diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md
index 5c3ae359ba0..89e4d3e9b58 100644
--- a/src/doc/rustc-dev-guide/src/tests/directives.md
+++ b/src/doc/rustc-dev-guide/src/tests/directives.md
@@ -293,8 +293,6 @@ See [Pretty-printer](compiletest.md#pretty-printer-tests).
 
 - `no-auto-check-cfg` — disable auto check-cfg (only for `--check-cfg` tests)
 - [`revisions`](compiletest.md#revisions) — compile multiple times
-- [`unused-revision-names`](compiletest.md#ignoring-unused-revision-names) -
-      suppress tidy checks for mentioning unknown revision names
 -[`forbid-output`](compiletest.md#incremental-tests) — incremental cfail rejects
       output pattern
 - [`should-ice`](compiletest.md#incremental-tests) — incremental cfail should
@@ -315,6 +313,17 @@ test suites that use those tools:
 - `llvm-cov-flags` adds extra flags when running LLVM's `llvm-cov` tool.
   - Used by [coverage tests](compiletest.md#coverage-tests) in `coverage-run` mode.
 
+### Tidy specific directives
+
+The following directives control how the [tidy script](../conventions.md#formatting)
+verifies tests.
+
+- `ignore-tidy-target-specific-tests` disables checking that the appropriate
+  LLVM component is required (via a `needs-llvm-components` directive) when a
+  test is compiled for a specific target (via the `--target` flag in a
+  `compile-flag` directive).
+- [`unused-revision-names`](compiletest.md#ignoring-unused-revision-names) -
+      suppress tidy checks for mentioning unknown revision names.
 
 ## Substitutions
 
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 27910ad0ab7..7bd2970eee7 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -395,6 +395,12 @@ flags to control that behavior. When the `--extern-html-root-url` flag is given
 one of your dependencies, rustdoc use that URL for those docs. Keep in mind that if those docs exist
 in the output directory, those local docs will still override this flag.
 
+The names in this flag are first matched against the names given in the `--extern name=` flags,
+which allows selecting between multiple crates with the same name (e.g. multiple versions of
+the same crate). For transitive dependencies that haven't been loaded via an `--extern` flag, matching
+falls backs to using crate names only, without ability to distinguish between multiple crates with
+the same name.
+
 ## `-Z force-unstable-if-unmarked`
 
 Using this flag looks like this:
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index a32c2f7fb18..0bef091468f 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -11,7 +11,8 @@ use std::path::{Path, PathBuf};
 use std::process::{self, Command, Stdio};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
-use std::{panic, str};
+use std::time::{Duration, Instant};
+use std::{fmt, panic, str};
 
 pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder};
 pub(crate) use markdown::test as test_markdown;
@@ -36,6 +37,50 @@ use crate::config::{Options as RustdocOptions, OutputFormat};
 use crate::html::markdown::{ErrorCodes, Ignore, LangString, MdRelLine};
 use crate::lint::init_lints;
 
+/// Type used to display times (compilation and total) information for merged doctests.
+struct MergedDoctestTimes {
+    total_time: Instant,
+    /// Total time spent compiling all merged doctests.
+    compilation_time: Duration,
+    /// This field is used to keep track of how many merged doctests we (tried to) compile.
+    added_compilation_times: usize,
+}
+
+impl MergedDoctestTimes {
+    fn new() -> Self {
+        Self {
+            total_time: Instant::now(),
+            compilation_time: Duration::default(),
+            added_compilation_times: 0,
+        }
+    }
+
+    fn add_compilation_time(&mut self, duration: Duration) {
+        self.compilation_time += duration;
+        self.added_compilation_times += 1;
+    }
+
+    fn display_times(&self) {
+        // If no merged doctest was compiled, then there is nothing to display since the numbers
+        // displayed by `libtest` for standalone tests are already accurate (they include both
+        // compilation and runtime).
+        if self.added_compilation_times > 0 {
+            println!("{self}");
+        }
+    }
+}
+
+impl fmt::Display for MergedDoctestTimes {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "all doctests ran in {:.2}s; merged doctests compilation took {:.2}s",
+            self.total_time.elapsed().as_secs_f64(),
+            self.compilation_time.as_secs_f64(),
+        )
+    }
+}
+
 /// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`).
 #[derive(Clone)]
 pub(crate) struct GlobalTestOptions {
@@ -295,6 +340,7 @@ pub(crate) fn run_tests(
 
     let mut nb_errors = 0;
     let mut ran_edition_tests = 0;
+    let mut times = MergedDoctestTimes::new();
     let target_str = rustdoc_options.target.to_string();
 
     for (MergeableTestKey { edition, global_crate_attrs_hash }, mut doctests) in mergeable_tests {
@@ -314,13 +360,15 @@ pub(crate) fn run_tests(
         for (doctest, scraped_test) in &doctests {
             tests_runner.add_test(doctest, scraped_test, &target_str);
         }
-        if let Ok(success) = tests_runner.run_merged_tests(
+        let (duration, ret) = tests_runner.run_merged_tests(
             rustdoc_test_options,
             edition,
             &opts,
             &test_args,
             rustdoc_options,
-        ) {
+        );
+        times.add_compilation_time(duration);
+        if let Ok(success) = ret {
             ran_edition_tests += 1;
             if !success {
                 nb_errors += 1;
@@ -354,11 +402,13 @@ pub(crate) fn run_tests(
         test::test_main_with_exit_callback(&test_args, standalone_tests, None, || {
             // We ensure temp dir destructor is called.
             std::mem::drop(temp_dir.take());
+            times.display_times();
         });
     }
     if nb_errors != 0 {
         // We ensure temp dir destructor is called.
         std::mem::drop(temp_dir);
+        times.display_times();
         // libtest::ERROR_EXIT_CODE is not public but it's the same value.
         std::process::exit(101);
     }
@@ -496,16 +546,19 @@ impl RunnableDocTest {
 ///
 /// This is the function that calculates the compiler command line, invokes the compiler, then
 /// invokes the test or tests in a separate executable (if applicable).
+///
+/// Returns a tuple containing the `Duration` of the compilation and the `Result` of the test.
 fn run_test(
     doctest: RunnableDocTest,
     rustdoc_options: &RustdocOptions,
     supports_color: bool,
     report_unused_externs: impl Fn(UnusedExterns),
-) -> Result<(), TestFailure> {
+) -> (Duration, Result<(), TestFailure>) {
     let langstr = &doctest.langstr;
     // Make sure we emit well-formed executable names for our target.
     let rust_out = add_exe_suffix("rust_out".to_owned(), &rustdoc_options.target);
     let output_file = doctest.test_opts.outdir.path().join(rust_out);
+    let instant = Instant::now();
 
     // Common arguments used for compiling the doctest runner.
     // On merged doctests, the compiler is invoked twice: once for the test code itself,
@@ -589,7 +642,7 @@ fn run_test(
         if std::fs::write(&input_file, &doctest.full_test_code).is_err() {
             // If we cannot write this file for any reason, we leave. All combined tests will be
             // tested as standalone tests.
-            return Err(TestFailure::CompileError);
+            return (Duration::default(), Err(TestFailure::CompileError));
         }
         if !rustdoc_options.nocapture {
             // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -660,7 +713,7 @@ fn run_test(
         if std::fs::write(&runner_input_file, merged_test_code).is_err() {
             // If we cannot write this file for any reason, we leave. All combined tests will be
             // tested as standalone tests.
-            return Err(TestFailure::CompileError);
+            return (instant.elapsed(), Err(TestFailure::CompileError));
         }
         if !rustdoc_options.nocapture {
             // If `nocapture` is disabled, then we don't display rustc's output when compiling
@@ -713,7 +766,7 @@ fn run_test(
     let _bomb = Bomb(&out);
     match (output.status.success(), langstr.compile_fail) {
         (true, true) => {
-            return Err(TestFailure::UnexpectedCompilePass);
+            return (instant.elapsed(), Err(TestFailure::UnexpectedCompilePass));
         }
         (true, false) => {}
         (false, true) => {
@@ -729,17 +782,18 @@ fn run_test(
                     .collect();
 
                 if !missing_codes.is_empty() {
-                    return Err(TestFailure::MissingErrorCodes(missing_codes));
+                    return (instant.elapsed(), Err(TestFailure::MissingErrorCodes(missing_codes)));
                 }
             }
         }
         (false, false) => {
-            return Err(TestFailure::CompileError);
+            return (instant.elapsed(), Err(TestFailure::CompileError));
         }
     }
 
+    let duration = instant.elapsed();
     if doctest.no_run {
-        return Ok(());
+        return (duration, Ok(()));
     }
 
     // Run the code!
@@ -771,17 +825,17 @@ fn run_test(
         cmd.output()
     };
     match result {
-        Err(e) => return Err(TestFailure::ExecutionError(e)),
+        Err(e) => return (duration, Err(TestFailure::ExecutionError(e))),
         Ok(out) => {
             if langstr.should_panic && out.status.success() {
-                return Err(TestFailure::UnexpectedRunPass);
+                return (duration, Err(TestFailure::UnexpectedRunPass));
             } else if !langstr.should_panic && !out.status.success() {
-                return Err(TestFailure::ExecutionFailure(out));
+                return (duration, Err(TestFailure::ExecutionFailure(out)));
             }
         }
     }
 
-    Ok(())
+    (duration, Ok(()))
 }
 
 /// Converts a path intended to use as a command to absolute if it is
@@ -1071,7 +1125,7 @@ fn doctest_run_fn(
         no_run: scraped_test.no_run(&rustdoc_options),
         merged_test_code: None,
     };
-    let res =
+    let (_, res) =
         run_test(runnable_test, &rustdoc_options, doctest.supports_color, report_unused_externs);
 
     if let Err(err) = res {
diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs
index f0914474c79..fcfa424968e 100644
--- a/src/librustdoc/doctest/runner.rs
+++ b/src/librustdoc/doctest/runner.rs
@@ -1,4 +1,5 @@
 use std::fmt::Write;
+use std::time::Duration;
 
 use rustc_data_structures::fx::FxIndexSet;
 use rustc_span::edition::Edition;
@@ -67,6 +68,10 @@ impl DocTestRunner {
         self.nb_tests += 1;
     }
 
+    /// Returns a tuple containing the `Duration` of the compilation and the `Result` of the test.
+    ///
+    /// If compilation failed, it will return `Err`, otherwise it will return `Ok` containing if
+    /// the test ran successfully.
     pub(crate) fn run_merged_tests(
         &mut self,
         test_options: IndividualTestOptions,
@@ -74,7 +79,7 @@ impl DocTestRunner {
         opts: &GlobalTestOptions,
         test_args: &[String],
         rustdoc_options: &RustdocOptions,
-    ) -> Result<bool, ()> {
+    ) -> (Duration, Result<bool, ()>) {
         let mut code = "\
 #![allow(unused_extern_crates)]
 #![allow(internal_features)]
@@ -204,9 +209,9 @@ std::process::Termination::report(test::test_main(test_args, tests, None))
             no_run: false,
             merged_test_code: Some(code),
         };
-        let ret =
+        let (duration, ret) =
             run_test(runnable_test, rustdoc_options, self.supports_color, |_: UnusedExterns| {});
-        if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) }
+        (duration, if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) })
     }
 }
 
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index 5191120ebdb..e28cc3a542e 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -4,6 +4,7 @@ use rustc_ast::join_path_syms;
 use rustc_attr_data_structures::StabilityLevel;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
 use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
+use rustc_metadata::creader::CStore;
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::Symbol;
 use tracing::debug;
@@ -158,18 +159,33 @@ impl Cache {
         assert!(cx.external_traits.is_empty());
         cx.cache.traits = mem::take(&mut krate.external_traits);
 
+        let render_options = &cx.render_options;
+        let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence;
+        let dst = &render_options.output;
+
+        // Make `--extern-html-root-url` support the same names as `--extern` whenever possible
+        let cstore = CStore::from_tcx(tcx);
+        for (name, extern_url) in &render_options.extern_html_root_urls {
+            if let Some(crate_num) = cstore.resolved_extern_crate(Symbol::intern(name)) {
+                let e = ExternalCrate { crate_num };
+                let location = e.location(Some(extern_url), extern_url_takes_precedence, dst, tcx);
+                cx.cache.extern_locations.insert(e.crate_num, location);
+            }
+        }
+
         // Cache where all our extern crates are located
-        // FIXME: this part is specific to HTML so it'd be nice to remove it from the common code
+        // This is also used in the JSON output.
         for &crate_num in tcx.crates(()) {
             let e = ExternalCrate { crate_num };
 
             let name = e.name(tcx);
-            let render_options = &cx.render_options;
-            let extern_url = render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u);
-            let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence;
-            let dst = &render_options.output;
-            let location = e.location(extern_url, extern_url_takes_precedence, dst, tcx);
-            cx.cache.extern_locations.insert(e.crate_num, location);
+            cx.cache.extern_locations.entry(e.crate_num).or_insert_with(|| {
+                // falls back to matching by crates' own names, because
+                // transitive dependencies and injected crates may be loaded without `--extern`
+                let extern_url =
+                    render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u);
+                e.location(extern_url, extern_url_takes_precedence, dst, tcx)
+            });
             cx.cache.external_paths.insert(e.def_id(), (vec![name], ItemType::Module));
         }
 
diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs
index a1f76a07556..54511f4fd08 100644
--- a/src/tools/compiletest/src/directives.rs
+++ b/src/tools/compiletest/src/directives.rs
@@ -12,6 +12,9 @@ use tracing::*;
 use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassMode, RunFailMode, TestMode};
 use crate::debuggers::{extract_cdb_version, extract_gdb_version};
 use crate::directives::auxiliary::{AuxProps, parse_and_update_aux};
+use crate::directives::directive_names::{
+    KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
+};
 use crate::directives::needs::CachedNeedsConditions;
 use crate::errors::ErrorKind;
 use crate::executor::{CollectedTestDesc, ShouldPanic};
@@ -20,6 +23,7 @@ use crate::util::static_regex;
 
 pub(crate) mod auxiliary;
 mod cfg;
+mod directive_names;
 mod needs;
 #[cfg(test)]
 mod tests;
@@ -59,9 +63,9 @@ impl EarlyProps {
             &mut poisoned,
             testfile,
             rdr,
-            &mut |DirectiveLine { raw_directive: ln, .. }| {
-                parse_and_update_aux(config, ln, &mut props.aux);
-                config.parse_and_update_revisions(testfile, ln, &mut props.revisions);
+            &mut |DirectiveLine { line_number, raw_directive: ln, .. }| {
+                parse_and_update_aux(config, ln, testfile, line_number, &mut props.aux);
+                config.parse_and_update_revisions(testfile, line_number, ln, &mut props.revisions);
             },
         );
 
@@ -351,7 +355,7 @@ impl TestProps {
                 &mut poisoned,
                 testfile,
                 file,
-                &mut |directive @ DirectiveLine { raw_directive: ln, .. }| {
+                &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| {
                     if !directive.applies_to_test_revision(test_revision) {
                         return;
                     }
@@ -361,17 +365,28 @@ impl TestProps {
                     config.push_name_value_directive(
                         ln,
                         ERROR_PATTERN,
+                        testfile,
+                        line_number,
                         &mut self.error_patterns,
                         |r| r,
                     );
                     config.push_name_value_directive(
                         ln,
                         REGEX_ERROR_PATTERN,
+                        testfile,
+                        line_number,
                         &mut self.regex_error_patterns,
                         |r| r,
                     );
 
-                    config.push_name_value_directive(ln, DOC_FLAGS, &mut self.doc_flags, |r| r);
+                    config.push_name_value_directive(
+                        ln,
+                        DOC_FLAGS,
+                        testfile,
+                        line_number,
+                        &mut self.doc_flags,
+                        |r| r,
+                    );
 
                     fn split_flags(flags: &str) -> Vec<String> {
                         // Individual flags can be single-quoted to preserve spaces; see
@@ -386,7 +401,9 @@ impl TestProps {
                             .collect::<Vec<_>>()
                     }
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) {
+                    if let Some(flags) =
+                        config.parse_name_value_directive(ln, COMPILE_FLAGS, testfile, line_number)
+                    {
                         let flags = split_flags(&flags);
                         for flag in &flags {
                             if flag == "--edition" || flag.starts_with("--edition=") {
@@ -395,25 +412,40 @@ impl TestProps {
                         }
                         self.compile_flags.extend(flags);
                     }
-                    if config.parse_name_value_directive(ln, INCORRECT_COMPILER_FLAGS).is_some() {
+                    if config
+                        .parse_name_value_directive(
+                            ln,
+                            INCORRECT_COMPILER_FLAGS,
+                            testfile,
+                            line_number,
+                        )
+                        .is_some()
+                    {
                         panic!("`compiler-flags` directive should be spelled `compile-flags`");
                     }
 
-                    if let Some(edition) = config.parse_edition(ln) {
+                    if let Some(edition) = config.parse_edition(ln, testfile, line_number) {
                         // The edition is added at the start, since flags from //@compile-flags must
                         // be passed to rustc last.
                         self.compile_flags.insert(0, format!("--edition={}", edition.trim()));
                         has_edition = true;
                     }
 
-                    config.parse_and_update_revisions(testfile, ln, &mut self.revisions);
+                    config.parse_and_update_revisions(
+                        testfile,
+                        line_number,
+                        ln,
+                        &mut self.revisions,
+                    );
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) {
+                    if let Some(flags) =
+                        config.parse_name_value_directive(ln, RUN_FLAGS, testfile, line_number)
+                    {
                         self.run_flags.extend(split_flags(&flags));
                     }
 
                     if self.pp_exact.is_none() {
-                        self.pp_exact = config.parse_pp_exact(ln, testfile);
+                        self.pp_exact = config.parse_pp_exact(ln, testfile, line_number);
                     }
 
                     config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice);
@@ -435,7 +467,9 @@ impl TestProps {
                     );
                     config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic);
 
-                    if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) {
+                    if let Some(m) =
+                        config.parse_name_value_directive(ln, PRETTY_MODE, testfile, line_number)
+                    {
                         self.pretty_mode = m;
                     }
 
@@ -446,35 +480,45 @@ impl TestProps {
                     );
 
                     // Call a helper method to deal with aux-related directives.
-                    parse_and_update_aux(config, ln, &mut self.aux);
+                    parse_and_update_aux(config, ln, testfile, line_number, &mut self.aux);
 
                     config.push_name_value_directive(
                         ln,
                         EXEC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.exec_env,
                         Config::parse_env,
                     );
                     config.push_name_value_directive(
                         ln,
                         UNSET_EXEC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.unset_exec_env,
                         |r| r.trim().to_owned(),
                     );
                     config.push_name_value_directive(
                         ln,
                         RUSTC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.rustc_env,
                         Config::parse_env,
                     );
                     config.push_name_value_directive(
                         ln,
                         UNSET_RUSTC_ENV,
+                        testfile,
+                        line_number,
                         &mut self.unset_rustc_env,
                         |r| r.trim().to_owned(),
                     );
                     config.push_name_value_directive(
                         ln,
                         FORBID_OUTPUT,
+                        testfile,
+                        line_number,
                         &mut self.forbid_output,
                         |r| r,
                     );
@@ -510,7 +554,7 @@ impl TestProps {
                     }
 
                     if let Some(code) = config
-                        .parse_name_value_directive(ln, FAILURE_STATUS)
+                        .parse_name_value_directive(ln, FAILURE_STATUS, testfile, line_number)
                         .and_then(|code| code.trim().parse::<i32>().ok())
                     {
                         self.failure_status = Some(code);
@@ -531,6 +575,8 @@ impl TestProps {
                     config.set_name_value_directive(
                         ln,
                         ASSEMBLY_OUTPUT,
+                        testfile,
+                        line_number,
                         &mut self.assembly_output,
                         |r| r.trim().to_string(),
                     );
@@ -543,7 +589,9 @@ impl TestProps {
 
                     // Unlike the other `name_value_directive`s this needs to be handled manually,
                     // because it sets a `bool` flag.
-                    if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) {
+                    if let Some(known_bug) =
+                        config.parse_name_value_directive(ln, KNOWN_BUG, testfile, line_number)
+                    {
                         let known_bug = known_bug.trim();
                         if known_bug == "unknown"
                             || known_bug.split(',').all(|issue_ref| {
@@ -571,16 +619,25 @@ impl TestProps {
                     config.set_name_value_directive(
                         ln,
                         TEST_MIR_PASS,
+                        testfile,
+                        line_number,
                         &mut self.mir_unit_test,
                         |s| s.trim().to_string(),
                     );
                     config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base);
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) {
+                    if let Some(flags) =
+                        config.parse_name_value_directive(ln, LLVM_COV_FLAGS, testfile, line_number)
+                    {
                         self.llvm_cov_flags.extend(split_flags(&flags));
                     }
 
-                    if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) {
+                    if let Some(flags) = config.parse_name_value_directive(
+                        ln,
+                        FILECHECK_FLAGS,
+                        testfile,
+                        line_number,
+                    ) {
                         self.filecheck_flags.extend(split_flags(&flags));
                     }
 
@@ -588,9 +645,12 @@ impl TestProps {
 
                     self.update_add_core_stubs(ln, config);
 
-                    if let Some(err_kind) =
-                        config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS)
-                    {
+                    if let Some(err_kind) = config.parse_name_value_directive(
+                        ln,
+                        DONT_REQUIRE_ANNOTATIONS,
+                        testfile,
+                        line_number,
+                    ) {
                         self.dont_require_annotations
                             .insert(ErrorKind::expect_from_user_str(err_kind.trim()));
                     }
@@ -769,296 +829,6 @@ fn line_directive<'line>(
     Some(DirectiveLine { line_number, revision, raw_directive })
 }
 
-/// This was originally generated by collecting directives from ui tests and then extracting their
-/// directive names. This is **not** an exhaustive list of all possible directives. Instead, this is
-/// a best-effort approximation for diagnostics. Add new directives to this list when needed.
-const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
-    // tidy-alphabetical-start
-    "add-core-stubs",
-    "assembly-output",
-    "aux-bin",
-    "aux-build",
-    "aux-codegen-backend",
-    "aux-crate",
-    "build-aux-docs",
-    "build-fail",
-    "build-pass",
-    "check-fail",
-    "check-pass",
-    "check-run-results",
-    "check-stdout",
-    "check-test-line-numbers-match",
-    "compile-flags",
-    "doc-flags",
-    "dont-check-compiler-stderr",
-    "dont-check-compiler-stdout",
-    "dont-check-failure-status",
-    "dont-require-annotations",
-    "edition",
-    "error-pattern",
-    "exact-llvm-major-version",
-    "exec-env",
-    "failure-status",
-    "filecheck-flags",
-    "forbid-output",
-    "force-host",
-    "ignore-16bit",
-    "ignore-32bit",
-    "ignore-64bit",
-    "ignore-aarch64",
-    "ignore-aarch64-pc-windows-msvc",
-    "ignore-aarch64-unknown-linux-gnu",
-    "ignore-aix",
-    "ignore-android",
-    "ignore-apple",
-    "ignore-arm",
-    "ignore-arm-unknown-linux-gnueabi",
-    "ignore-arm-unknown-linux-gnueabihf",
-    "ignore-arm-unknown-linux-musleabi",
-    "ignore-arm-unknown-linux-musleabihf",
-    "ignore-auxiliary",
-    "ignore-avr",
-    "ignore-backends",
-    "ignore-beta",
-    "ignore-cdb",
-    "ignore-compare-mode-next-solver",
-    "ignore-compare-mode-polonius",
-    "ignore-coverage-map",
-    "ignore-coverage-run",
-    "ignore-cross-compile",
-    "ignore-eabi",
-    "ignore-elf",
-    "ignore-emscripten",
-    "ignore-endian-big",
-    "ignore-enzyme",
-    "ignore-freebsd",
-    "ignore-fuchsia",
-    "ignore-gdb",
-    "ignore-gdb-version",
-    "ignore-gnu",
-    "ignore-haiku",
-    "ignore-horizon",
-    "ignore-i686-pc-windows-gnu",
-    "ignore-i686-pc-windows-msvc",
-    "ignore-illumos",
-    "ignore-ios",
-    "ignore-linux",
-    "ignore-lldb",
-    "ignore-llvm-version",
-    "ignore-loongarch32",
-    "ignore-loongarch64",
-    "ignore-macabi",
-    "ignore-macos",
-    "ignore-msp430",
-    "ignore-msvc",
-    "ignore-musl",
-    "ignore-netbsd",
-    "ignore-nightly",
-    "ignore-none",
-    "ignore-nto",
-    "ignore-nvptx64",
-    "ignore-nvptx64-nvidia-cuda",
-    "ignore-openbsd",
-    "ignore-pass",
-    "ignore-powerpc",
-    "ignore-powerpc64",
-    "ignore-remote",
-    "ignore-riscv64",
-    "ignore-rustc-debug-assertions",
-    "ignore-rustc_abi-x86-sse2",
-    "ignore-s390x",
-    "ignore-sgx",
-    "ignore-sparc64",
-    "ignore-spirv",
-    "ignore-stable",
-    "ignore-stage1",
-    "ignore-stage2",
-    "ignore-std-debug-assertions",
-    "ignore-test",
-    "ignore-thumb",
-    "ignore-thumbv8m.base-none-eabi",
-    "ignore-thumbv8m.main-none-eabi",
-    "ignore-tvos",
-    "ignore-unix",
-    "ignore-unknown",
-    "ignore-uwp",
-    "ignore-visionos",
-    "ignore-vxworks",
-    "ignore-wasi",
-    "ignore-wasm",
-    "ignore-wasm32",
-    "ignore-wasm32-bare",
-    "ignore-wasm64",
-    "ignore-watchos",
-    "ignore-windows",
-    "ignore-windows-gnu",
-    "ignore-windows-msvc",
-    "ignore-x32",
-    "ignore-x86",
-    "ignore-x86_64",
-    "ignore-x86_64-apple-darwin",
-    "ignore-x86_64-pc-windows-gnu",
-    "ignore-x86_64-unknown-linux-gnu",
-    "incremental",
-    "known-bug",
-    "llvm-cov-flags",
-    "max-llvm-major-version",
-    "min-cdb-version",
-    "min-gdb-version",
-    "min-lldb-version",
-    "min-llvm-version",
-    "min-system-llvm-version",
-    "needs-asm-support",
-    "needs-backends",
-    "needs-crate-type",
-    "needs-deterministic-layouts",
-    "needs-dlltool",
-    "needs-dynamic-linking",
-    "needs-enzyme",
-    "needs-force-clang-based-tests",
-    "needs-git-hash",
-    "needs-llvm-components",
-    "needs-llvm-zstd",
-    "needs-profiler-runtime",
-    "needs-relocation-model-pic",
-    "needs-run-enabled",
-    "needs-rust-lld",
-    "needs-rustc-debug-assertions",
-    "needs-sanitizer-address",
-    "needs-sanitizer-cfi",
-    "needs-sanitizer-dataflow",
-    "needs-sanitizer-hwaddress",
-    "needs-sanitizer-kcfi",
-    "needs-sanitizer-leak",
-    "needs-sanitizer-memory",
-    "needs-sanitizer-memtag",
-    "needs-sanitizer-safestack",
-    "needs-sanitizer-shadow-call-stack",
-    "needs-sanitizer-support",
-    "needs-sanitizer-thread",
-    "needs-std-debug-assertions",
-    "needs-subprocess",
-    "needs-symlink",
-    "needs-target-has-atomic",
-    "needs-target-std",
-    "needs-threads",
-    "needs-unwind",
-    "needs-wasmtime",
-    "needs-xray",
-    "no-auto-check-cfg",
-    "no-prefer-dynamic",
-    "normalize-stderr",
-    "normalize-stderr-32bit",
-    "normalize-stderr-64bit",
-    "normalize-stdout",
-    "only-16bit",
-    "only-32bit",
-    "only-64bit",
-    "only-aarch64",
-    "only-aarch64-apple-darwin",
-    "only-aarch64-unknown-linux-gnu",
-    "only-apple",
-    "only-arm",
-    "only-avr",
-    "only-beta",
-    "only-bpf",
-    "only-cdb",
-    "only-dist",
-    "only-elf",
-    "only-emscripten",
-    "only-gnu",
-    "only-i686-pc-windows-gnu",
-    "only-i686-pc-windows-msvc",
-    "only-i686-unknown-linux-gnu",
-    "only-ios",
-    "only-linux",
-    "only-loongarch32",
-    "only-loongarch64",
-    "only-loongarch64-unknown-linux-gnu",
-    "only-macos",
-    "only-mips",
-    "only-mips64",
-    "only-msp430",
-    "only-msvc",
-    "only-musl",
-    "only-nightly",
-    "only-nvptx64",
-    "only-powerpc",
-    "only-riscv64",
-    "only-rustc_abi-x86-sse2",
-    "only-s390x",
-    "only-sparc",
-    "only-sparc64",
-    "only-stable",
-    "only-thumb",
-    "only-tvos",
-    "only-uefi",
-    "only-unix",
-    "only-visionos",
-    "only-wasm32",
-    "only-wasm32-bare",
-    "only-wasm32-wasip1",
-    "only-watchos",
-    "only-windows",
-    "only-windows-gnu",
-    "only-windows-msvc",
-    "only-x86",
-    "only-x86_64",
-    "only-x86_64-apple-darwin",
-    "only-x86_64-fortanix-unknown-sgx",
-    "only-x86_64-pc-windows-gnu",
-    "only-x86_64-pc-windows-msvc",
-    "only-x86_64-unknown-linux-gnu",
-    "pp-exact",
-    "pretty-compare-only",
-    "pretty-mode",
-    "proc-macro",
-    "reference",
-    "regex-error-pattern",
-    "remap-src-base",
-    "revisions",
-    "run-crash",
-    "run-fail",
-    "run-fail-or-crash",
-    "run-flags",
-    "run-pass",
-    "run-rustfix",
-    "rustc-env",
-    "rustfix-only-machine-applicable",
-    "should-fail",
-    "should-ice",
-    "stderr-per-bitwidth",
-    "test-mir-pass",
-    "unique-doc-out-dir",
-    "unset-exec-env",
-    "unset-rustc-env",
-    // Used by the tidy check `unknown_revision`.
-    "unused-revision-names",
-    // tidy-alphabetical-end
-];
-
-const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[
-    "count",
-    "!count",
-    "files",
-    "!files",
-    "has",
-    "!has",
-    "has-dir",
-    "!has-dir",
-    "hasraw",
-    "!hasraw",
-    "matches",
-    "!matches",
-    "matchesraw",
-    "!matchesraw",
-    "snapshot",
-    "!snapshot",
-];
-
-const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] =
-    &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"];
-
 /// The (partly) broken-down contents of a line containing a test directive,
 /// which [`iter_directives`] passes to its callback function.
 ///
@@ -1206,6 +976,7 @@ impl Config {
     fn parse_and_update_revisions(
         &self,
         testfile: &Utf8Path,
+        line_number: usize,
         line: &str,
         existing: &mut Vec<String>,
     ) {
@@ -1219,7 +990,8 @@ impl Config {
         const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
             ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
 
-        if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
+        if let Some(raw) = self.parse_name_value_directive(line, "revisions", testfile, line_number)
+        {
             if self.mode == TestMode::RunMake {
                 panic!("`run-make` tests do not support revisions: {}", testfile);
             }
@@ -1264,8 +1036,13 @@ impl Config {
         (name.to_owned(), value.to_owned())
     }
 
-    fn parse_pp_exact(&self, line: &str, testfile: &Utf8Path) -> Option<Utf8PathBuf> {
-        if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
+    fn parse_pp_exact(
+        &self,
+        line: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
+    ) -> Option<Utf8PathBuf> {
+        if let Some(s) = self.parse_name_value_directive(line, "pp-exact", testfile, line_number) {
             Some(Utf8PathBuf::from(&s))
         } else if self.parse_name_directive(line, "pp-exact") {
             testfile.file_name().map(Utf8PathBuf::from)
@@ -1306,19 +1083,31 @@ impl Config {
         line.starts_with("no-") && self.parse_name_directive(&line[3..], directive)
     }
 
-    pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option<String> {
+    pub fn parse_name_value_directive(
+        &self,
+        line: &str,
+        directive: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
+    ) -> Option<String> {
         let colon = directive.len();
         if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
             let value = line[(colon + 1)..].to_owned();
             debug!("{}: {}", directive, value);
-            Some(expand_variables(value, self))
+            let value = expand_variables(value, self);
+            if value.is_empty() {
+                error!("{testfile}:{line_number}: empty value for directive `{directive}`");
+                help!("expected syntax is: `{directive}: value`");
+                panic!("empty directive value detected");
+            }
+            Some(value)
         } else {
             None
         }
     }
 
-    fn parse_edition(&self, line: &str) -> Option<String> {
-        self.parse_name_value_directive(line, "edition")
+    fn parse_edition(&self, line: &str, testfile: &Utf8Path, line_number: usize) -> Option<String> {
+        self.parse_name_value_directive(line, "edition", testfile, line_number)
     }
 
     fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) {
@@ -1340,11 +1129,14 @@ impl Config {
         &self,
         line: &str,
         directive: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
         value: &mut Option<T>,
         parse: impl FnOnce(String) -> T,
     ) {
         if value.is_none() {
-            *value = self.parse_name_value_directive(line, directive).map(parse);
+            *value =
+                self.parse_name_value_directive(line, directive, testfile, line_number).map(parse);
         }
     }
 
@@ -1352,10 +1144,14 @@ impl Config {
         &self,
         line: &str,
         directive: &str,
+        testfile: &Utf8Path,
+        line_number: usize,
         values: &mut Vec<T>,
         parse: impl FnOnce(String) -> T,
     ) {
-        if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
+        if let Some(value) =
+            self.parse_name_value_directive(line, directive, testfile, line_number).map(parse)
+        {
             values.push(value);
         }
     }
@@ -1672,9 +1468,9 @@ pub(crate) fn make_test_description<R: Read>(
             decision!(cfg::handle_ignore(config, ln));
             decision!(cfg::handle_only(config, ln));
             decision!(needs::handle_needs(&cache.needs, config, ln));
-            decision!(ignore_llvm(config, path, ln));
-            decision!(ignore_backends(config, path, ln));
-            decision!(needs_backends(config, path, ln));
+            decision!(ignore_llvm(config, path, ln, line_number));
+            decision!(ignore_backends(config, path, ln, line_number));
+            decision!(needs_backends(config, path, ln, line_number));
             decision!(ignore_cdb(config, ln));
             decision!(ignore_gdb(config, ln));
             decision!(ignore_lldb(config, ln));
@@ -1801,8 +1597,15 @@ fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision {
     IgnoreDecision::Continue
 }
 
-fn ignore_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
-    if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
+fn ignore_backends(
+    config: &Config,
+    path: &Utf8Path,
+    line: &str,
+    line_number: usize,
+) -> IgnoreDecision {
+    if let Some(backends_to_ignore) =
+        config.parse_name_value_directive(line, "ignore-backends", path, line_number)
+    {
         for backend in backends_to_ignore.split_whitespace().map(|backend| {
             match CodegenBackend::try_from(backend) {
                 Ok(backend) => backend,
@@ -1821,8 +1624,15 @@ fn ignore_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecisi
     IgnoreDecision::Continue
 }
 
-fn needs_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
-    if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
+fn needs_backends(
+    config: &Config,
+    path: &Utf8Path,
+    line: &str,
+    line_number: usize,
+) -> IgnoreDecision {
+    if let Some(needed_backends) =
+        config.parse_name_value_directive(line, "needs-backends", path, line_number)
+    {
         if !needed_backends
             .split_whitespace()
             .map(|backend| match CodegenBackend::try_from(backend) {
@@ -1844,9 +1654,9 @@ fn needs_backends(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecisio
     IgnoreDecision::Continue
 }
 
-fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
+fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str, line_number: usize) -> IgnoreDecision {
     if let Some(needed_components) =
-        config.parse_name_value_directive(line, "needs-llvm-components")
+        config.parse_name_value_directive(line, "needs-llvm-components", path, line_number)
     {
         let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
         if let Some(missing_component) = needed_components
@@ -1867,7 +1677,9 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
     if let Some(actual_version) = &config.llvm_version {
         // Note that these `min` versions will check for not just major versions.
 
-        if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
+        if let Some(version_string) =
+            config.parse_name_value_directive(line, "min-llvm-version", path, line_number)
+        {
             let min_version = extract_llvm_version(&version_string);
             // Ignore if actual version is smaller than the minimum required version.
             if *actual_version < min_version {
@@ -1878,7 +1690,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 };
             }
         } else if let Some(version_string) =
-            config.parse_name_value_directive(line, "max-llvm-major-version")
+            config.parse_name_value_directive(line, "max-llvm-major-version", path, line_number)
         {
             let max_version = extract_llvm_version(&version_string);
             // Ignore if actual major version is larger than the maximum required major version.
@@ -1892,7 +1704,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 };
             }
         } else if let Some(version_string) =
-            config.parse_name_value_directive(line, "min-system-llvm-version")
+            config.parse_name_value_directive(line, "min-system-llvm-version", path, line_number)
         {
             let min_version = extract_llvm_version(&version_string);
             // Ignore if using system LLVM and actual version
@@ -1905,7 +1717,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 };
             }
         } else if let Some(version_range) =
-            config.parse_name_value_directive(line, "ignore-llvm-version")
+            config.parse_name_value_directive(line, "ignore-llvm-version", path, line_number)
         {
             // Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
             let (v_min, v_max) =
@@ -1931,7 +1743,7 @@ fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
                 }
             }
         } else if let Some(version_string) =
-            config.parse_name_value_directive(line, "exact-llvm-major-version")
+            config.parse_name_value_directive(line, "exact-llvm-major-version", path, line_number)
         {
             // Syntax is "exact-llvm-major-version: <version>"
             let version = extract_llvm_version(&version_string);
diff --git a/src/tools/compiletest/src/directives/auxiliary.rs b/src/tools/compiletest/src/directives/auxiliary.rs
index cdb75f6ffa9..7c1ed2e7006 100644
--- a/src/tools/compiletest/src/directives/auxiliary.rs
+++ b/src/tools/compiletest/src/directives/auxiliary.rs
@@ -3,6 +3,8 @@
 
 use std::iter;
 
+use camino::Utf8Path;
+
 use super::directives::{AUX_BIN, AUX_BUILD, AUX_CODEGEN_BACKEND, AUX_CRATE, PROC_MACRO};
 use crate::common::Config;
 
@@ -41,17 +43,42 @@ impl AuxProps {
 
 /// If the given test directive line contains an `aux-*` directive, parse it
 /// and update [`AuxProps`] accordingly.
-pub(super) fn parse_and_update_aux(config: &Config, ln: &str, aux: &mut AuxProps) {
+pub(super) fn parse_and_update_aux(
+    config: &Config,
+    ln: &str,
+    testfile: &Utf8Path,
+    line_number: usize,
+    aux: &mut AuxProps,
+) {
     if !(ln.starts_with("aux-") || ln.starts_with("proc-macro")) {
         return;
     }
 
-    config.push_name_value_directive(ln, AUX_BUILD, &mut aux.builds, |r| r.trim().to_string());
-    config.push_name_value_directive(ln, AUX_BIN, &mut aux.bins, |r| r.trim().to_string());
-    config.push_name_value_directive(ln, AUX_CRATE, &mut aux.crates, parse_aux_crate);
-    config
-        .push_name_value_directive(ln, PROC_MACRO, &mut aux.proc_macros, |r| r.trim().to_string());
-    if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) {
+    config.push_name_value_directive(ln, AUX_BUILD, testfile, line_number, &mut aux.builds, |r| {
+        r.trim().to_string()
+    });
+    config.push_name_value_directive(ln, AUX_BIN, testfile, line_number, &mut aux.bins, |r| {
+        r.trim().to_string()
+    });
+    config.push_name_value_directive(
+        ln,
+        AUX_CRATE,
+        testfile,
+        line_number,
+        &mut aux.crates,
+        parse_aux_crate,
+    );
+    config.push_name_value_directive(
+        ln,
+        PROC_MACRO,
+        testfile,
+        line_number,
+        &mut aux.proc_macros,
+        |r| r.trim().to_string(),
+    );
+    if let Some(r) =
+        config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND, testfile, line_number)
+    {
         aux.codegen_backend = Some(r.trim().to_owned());
     }
 }
diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs
new file mode 100644
index 00000000000..7fc76a42e0c
--- /dev/null
+++ b/src/tools/compiletest/src/directives/directive_names.rs
@@ -0,0 +1,289 @@
+/// This was originally generated by collecting directives from ui tests and then extracting their
+/// directive names. This is **not** an exhaustive list of all possible directives. Instead, this is
+/// a best-effort approximation for diagnostics. Add new directives to this list when needed.
+pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
+    // tidy-alphabetical-start
+    "add-core-stubs",
+    "assembly-output",
+    "aux-bin",
+    "aux-build",
+    "aux-codegen-backend",
+    "aux-crate",
+    "build-aux-docs",
+    "build-fail",
+    "build-pass",
+    "check-fail",
+    "check-pass",
+    "check-run-results",
+    "check-stdout",
+    "check-test-line-numbers-match",
+    "compile-flags",
+    "doc-flags",
+    "dont-check-compiler-stderr",
+    "dont-check-compiler-stdout",
+    "dont-check-failure-status",
+    "dont-require-annotations",
+    "edition",
+    "error-pattern",
+    "exact-llvm-major-version",
+    "exec-env",
+    "failure-status",
+    "filecheck-flags",
+    "forbid-output",
+    "force-host",
+    "ignore-16bit",
+    "ignore-32bit",
+    "ignore-64bit",
+    "ignore-aarch64",
+    "ignore-aarch64-pc-windows-msvc",
+    "ignore-aarch64-unknown-linux-gnu",
+    "ignore-aix",
+    "ignore-android",
+    "ignore-apple",
+    "ignore-arm",
+    "ignore-arm-unknown-linux-gnueabi",
+    "ignore-arm-unknown-linux-gnueabihf",
+    "ignore-arm-unknown-linux-musleabi",
+    "ignore-arm-unknown-linux-musleabihf",
+    "ignore-auxiliary",
+    "ignore-avr",
+    "ignore-backends",
+    "ignore-beta",
+    "ignore-cdb",
+    "ignore-compare-mode-next-solver",
+    "ignore-compare-mode-polonius",
+    "ignore-coverage-map",
+    "ignore-coverage-run",
+    "ignore-cross-compile",
+    "ignore-eabi",
+    "ignore-elf",
+    "ignore-emscripten",
+    "ignore-endian-big",
+    "ignore-enzyme",
+    "ignore-freebsd",
+    "ignore-fuchsia",
+    "ignore-gdb",
+    "ignore-gdb-version",
+    "ignore-gnu",
+    "ignore-haiku",
+    "ignore-horizon",
+    "ignore-i686-pc-windows-gnu",
+    "ignore-i686-pc-windows-msvc",
+    "ignore-illumos",
+    "ignore-ios",
+    "ignore-linux",
+    "ignore-lldb",
+    "ignore-llvm-version",
+    "ignore-loongarch32",
+    "ignore-loongarch64",
+    "ignore-macabi",
+    "ignore-macos",
+    "ignore-msp430",
+    "ignore-msvc",
+    "ignore-musl",
+    "ignore-netbsd",
+    "ignore-nightly",
+    "ignore-none",
+    "ignore-nto",
+    "ignore-nvptx64",
+    "ignore-nvptx64-nvidia-cuda",
+    "ignore-openbsd",
+    "ignore-pass",
+    "ignore-powerpc",
+    "ignore-powerpc64",
+    "ignore-remote",
+    "ignore-riscv64",
+    "ignore-rustc-debug-assertions",
+    "ignore-rustc_abi-x86-sse2",
+    "ignore-s390x",
+    "ignore-sgx",
+    "ignore-sparc64",
+    "ignore-spirv",
+    "ignore-stable",
+    "ignore-stage1",
+    "ignore-stage2",
+    "ignore-std-debug-assertions",
+    "ignore-test",
+    "ignore-thumb",
+    "ignore-thumbv8m.base-none-eabi",
+    "ignore-thumbv8m.main-none-eabi",
+    "ignore-tvos",
+    "ignore-unix",
+    "ignore-unknown",
+    "ignore-uwp",
+    "ignore-visionos",
+    "ignore-vxworks",
+    "ignore-wasi",
+    "ignore-wasm",
+    "ignore-wasm32",
+    "ignore-wasm32-bare",
+    "ignore-wasm64",
+    "ignore-watchos",
+    "ignore-windows",
+    "ignore-windows-gnu",
+    "ignore-windows-msvc",
+    "ignore-x32",
+    "ignore-x86",
+    "ignore-x86_64",
+    "ignore-x86_64-apple-darwin",
+    "ignore-x86_64-pc-windows-gnu",
+    "ignore-x86_64-unknown-linux-gnu",
+    "incremental",
+    "known-bug",
+    "llvm-cov-flags",
+    "max-llvm-major-version",
+    "min-cdb-version",
+    "min-gdb-version",
+    "min-lldb-version",
+    "min-llvm-version",
+    "min-system-llvm-version",
+    "needs-asm-support",
+    "needs-backends",
+    "needs-crate-type",
+    "needs-deterministic-layouts",
+    "needs-dlltool",
+    "needs-dynamic-linking",
+    "needs-enzyme",
+    "needs-force-clang-based-tests",
+    "needs-git-hash",
+    "needs-llvm-components",
+    "needs-llvm-zstd",
+    "needs-profiler-runtime",
+    "needs-relocation-model-pic",
+    "needs-run-enabled",
+    "needs-rust-lld",
+    "needs-rustc-debug-assertions",
+    "needs-sanitizer-address",
+    "needs-sanitizer-cfi",
+    "needs-sanitizer-dataflow",
+    "needs-sanitizer-hwaddress",
+    "needs-sanitizer-kcfi",
+    "needs-sanitizer-leak",
+    "needs-sanitizer-memory",
+    "needs-sanitizer-memtag",
+    "needs-sanitizer-safestack",
+    "needs-sanitizer-shadow-call-stack",
+    "needs-sanitizer-support",
+    "needs-sanitizer-thread",
+    "needs-std-debug-assertions",
+    "needs-subprocess",
+    "needs-symlink",
+    "needs-target-has-atomic",
+    "needs-target-std",
+    "needs-threads",
+    "needs-unwind",
+    "needs-wasmtime",
+    "needs-xray",
+    "no-auto-check-cfg",
+    "no-prefer-dynamic",
+    "normalize-stderr",
+    "normalize-stderr-32bit",
+    "normalize-stderr-64bit",
+    "normalize-stdout",
+    "only-16bit",
+    "only-32bit",
+    "only-64bit",
+    "only-aarch64",
+    "only-aarch64-apple-darwin",
+    "only-aarch64-unknown-linux-gnu",
+    "only-apple",
+    "only-arm",
+    "only-avr",
+    "only-beta",
+    "only-bpf",
+    "only-cdb",
+    "only-dist",
+    "only-elf",
+    "only-emscripten",
+    "only-gnu",
+    "only-i686-pc-windows-gnu",
+    "only-i686-pc-windows-msvc",
+    "only-i686-unknown-linux-gnu",
+    "only-ios",
+    "only-linux",
+    "only-loongarch32",
+    "only-loongarch64",
+    "only-loongarch64-unknown-linux-gnu",
+    "only-macos",
+    "only-mips",
+    "only-mips64",
+    "only-msp430",
+    "only-msvc",
+    "only-musl",
+    "only-nightly",
+    "only-nvptx64",
+    "only-powerpc",
+    "only-riscv64",
+    "only-rustc_abi-x86-sse2",
+    "only-s390x",
+    "only-sparc",
+    "only-sparc64",
+    "only-stable",
+    "only-thumb",
+    "only-tvos",
+    "only-uefi",
+    "only-unix",
+    "only-visionos",
+    "only-wasm32",
+    "only-wasm32-bare",
+    "only-wasm32-wasip1",
+    "only-watchos",
+    "only-windows",
+    "only-windows-gnu",
+    "only-windows-msvc",
+    "only-x86",
+    "only-x86_64",
+    "only-x86_64-apple-darwin",
+    "only-x86_64-fortanix-unknown-sgx",
+    "only-x86_64-pc-windows-gnu",
+    "only-x86_64-pc-windows-msvc",
+    "only-x86_64-unknown-linux-gnu",
+    "pp-exact",
+    "pretty-compare-only",
+    "pretty-mode",
+    "proc-macro",
+    "reference",
+    "regex-error-pattern",
+    "remap-src-base",
+    "revisions",
+    "run-crash",
+    "run-fail",
+    "run-fail-or-crash",
+    "run-flags",
+    "run-pass",
+    "run-rustfix",
+    "rustc-env",
+    "rustfix-only-machine-applicable",
+    "should-fail",
+    "should-ice",
+    "stderr-per-bitwidth",
+    "test-mir-pass",
+    "unique-doc-out-dir",
+    "unset-exec-env",
+    "unset-rustc-env",
+    // Used by the tidy check `unknown_revision`.
+    "unused-revision-names",
+    // tidy-alphabetical-end
+];
+
+pub(crate) const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[
+    "count",
+    "!count",
+    "files",
+    "!files",
+    "has",
+    "!has",
+    "has-dir",
+    "!has-dir",
+    "hasraw",
+    "!hasraw",
+    "matches",
+    "!matches",
+    "matchesraw",
+    "!matchesraw",
+    "snapshot",
+    "!snapshot",
+];
+
+pub(crate) const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] =
+    &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"];
diff --git a/src/tools/compiletest/src/runtest/debugger.rs b/src/tools/compiletest/src/runtest/debugger.rs
index a4103c5b4a9..ba824124e87 100644
--- a/src/tools/compiletest/src/runtest/debugger.rs
+++ b/src/tools/compiletest/src/runtest/debugger.rs
@@ -47,10 +47,14 @@ impl DebuggerCommands {
                 continue;
             };
 
-            if let Some(command) = config.parse_name_value_directive(&line, &command_directive) {
+            if let Some(command) =
+                config.parse_name_value_directive(&line, &command_directive, file, line_no)
+            {
                 commands.push(command);
             }
-            if let Some(pattern) = config.parse_name_value_directive(&line, &check_directive) {
+            if let Some(pattern) =
+                config.parse_name_value_directive(&line, &check_directive, file, line_no)
+            {
                 check_lines.push((line_no, pattern));
             }
         }
diff --git a/src/tools/opt-dist/src/tests.rs b/src/tools/opt-dist/src/tests.rs
index c9a21fc6fb2..d5121b8c786 100644
--- a/src/tools/opt-dist/src/tests.rs
+++ b/src/tools/opt-dist/src/tests.rs
@@ -79,6 +79,7 @@ lld = false
 rustc = "{rustc}"
 cargo = "{cargo}"
 local-rebuild = true
+compiletest-allow-stage0=true
 
 [target.{host_triple}]
 llvm-config = "{llvm_config}"
@@ -117,7 +118,6 @@ llvm-config = "{llvm_config}"
             args.extend(["--skip", test_path]);
         }
         cmd(&args)
-            .env("COMPILETEST_FORCE_STAGE0", "1")
             // Also run dist-only tests
             .env("COMPILETEST_ENABLE_DIST_TESTS", "1")
             .run()
diff --git a/src/tools/rustc-perf b/src/tools/rustc-perf
-Subproject 6a70166b92a1b1560cb3cf056427b011b2a1f2b
+Subproject dde879cf1087cb34a32287bd8ccc4d545bb9fee
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index 8e2a796106f..858b058cb7d 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -167,7 +167,7 @@ const EXCEPTIONS_RUSTC_PERF: ExceptionList = &[
     ("brotli-decompressor", "BSD-3-Clause/MIT"),
     ("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"),
     ("inferno", "CDDL-1.0"),
-    ("ring", NON_STANDARD_LICENSE), // see EXCEPTIONS_NON_STANDARD_LICENSE_DEPS for more.
+    ("option-ext", "MPL-2.0"),
     ("ryu", "Apache-2.0 OR BSL-1.0"),
     ("snap", "BSD-3-Clause"),
     ("subtle", "BSD-3-Clause"),
@@ -226,20 +226,6 @@ const EXCEPTIONS_UEFI_QEMU_TEST: ExceptionList = &[
     ("r-efi", "MIT OR Apache-2.0 OR LGPL-2.1-or-later"), // LGPL is not acceptable, but we use it under MIT OR Apache-2.0
 ];
 
-/// Placeholder for non-standard license file.
-const NON_STANDARD_LICENSE: &str = "NON_STANDARD_LICENSE";
-
-/// These dependencies have non-standard licenses but are genenrally permitted.
-const EXCEPTIONS_NON_STANDARD_LICENSE_DEPS: &[&str] = &[
-    // `ring` is included because it is an optional dependency of `hyper`,
-    // which is a training data in rustc-perf for optimized build.
-    // The license of it is generally `ISC AND MIT AND OpenSSL`,
-    // though the `package.license` field is not set.
-    //
-    // See https://github.com/briansmith/ring/issues/902
-    "ring",
-];
-
 const PERMITTED_DEPS_LOCATION: &str = concat!(file!(), ":", line!());
 
 /// Crates rustc is allowed to depend on. Avoid adding to the list if possible.
@@ -599,7 +585,7 @@ pub fn check(root: &Path, cargo: &Path, bless: bool, bad: &mut bool) {
             .other_options(vec!["--locked".to_owned()]);
         let metadata = t!(cmd.exec());
 
-        check_license_exceptions(&metadata, exceptions, bad);
+        check_license_exceptions(&metadata, workspace, exceptions, bad);
         if let Some((crates, permitted_deps)) = permitted_deps {
             check_permitted_dependencies(&metadata, workspace, permitted_deps, crates, bad);
         }
@@ -730,14 +716,19 @@ fn check_runtime_license_exceptions(metadata: &Metadata, bad: &mut bool) {
 /// Check that all licenses of tool dependencies are in the valid list in `LICENSES`.
 ///
 /// Packages listed in `exceptions` are allowed for tools.
-fn check_license_exceptions(metadata: &Metadata, exceptions: &[(&str, &str)], bad: &mut bool) {
+fn check_license_exceptions(
+    metadata: &Metadata,
+    workspace: &str,
+    exceptions: &[(&str, &str)],
+    bad: &mut bool,
+) {
     // Validate the EXCEPTIONS list hasn't changed.
     for (name, license) in exceptions {
         // Check that the package actually exists.
         if !metadata.packages.iter().any(|p| *p.name == *name) {
             tidy_error!(
                 bad,
-                "could not find exception package `{}`\n\
+                "could not find exception package `{}` in workspace `{workspace}`\n\
                 Remove from EXCEPTIONS list if it is no longer used.",
                 name
             );
@@ -746,20 +737,17 @@ fn check_license_exceptions(metadata: &Metadata, exceptions: &[(&str, &str)], ba
         for pkg in metadata.packages.iter().filter(|p| *p.name == *name) {
             match &pkg.license {
                 None => {
-                    if *license == NON_STANDARD_LICENSE
-                        && EXCEPTIONS_NON_STANDARD_LICENSE_DEPS.contains(&pkg.name.as_str())
-                    {
-                        continue;
-                    }
                     tidy_error!(
                         bad,
-                        "dependency exception `{}` does not declare a license expression",
+                        "dependency exception `{}` in workspace `{workspace}` does not declare a license expression",
                         pkg.id
                     );
                 }
                 Some(pkg_license) => {
                     if pkg_license.as_str() != *license {
-                        println!("dependency exception `{name}` license has changed");
+                        println!(
+                            "dependency exception `{name}` license in workspace `{workspace}` has changed"
+                        );
                         println!("    previously `{license}` now `{pkg_license}`");
                         println!("    update EXCEPTIONS for the new license");
                         *bad = true;
@@ -783,12 +771,21 @@ fn check_license_exceptions(metadata: &Metadata, exceptions: &[(&str, &str)], ba
         let license = match &pkg.license {
             Some(license) => license,
             None => {
-                tidy_error!(bad, "dependency `{}` does not define a license expression", pkg.id);
+                tidy_error!(
+                    bad,
+                    "dependency `{}` in workspace `{workspace}` does not define a license expression",
+                    pkg.id
+                );
                 continue;
             }
         };
         if !LICENSES.contains(&license.as_str()) {
-            tidy_error!(bad, "invalid license `{}` in `{}`", license, pkg.id);
+            tidy_error!(
+                bad,
+                "invalid license `{}` for package `{}` in workspace `{workspace}`",
+                license,
+                pkg.id
+            );
         }
     }
 }
diff --git a/src/tools/tidy/src/style.rs b/src/tools/tidy/src/style.rs
index 35ed61eacc7..fca097c091b 100644
--- a/src/tools/tidy/src/style.rs
+++ b/src/tools/tidy/src/style.rs
@@ -519,8 +519,11 @@ pub fn check(path: &Path, bad: &mut bool) {
                         .any(|directive| matches!(directive, Directive::Ignore(_)));
                 let has_alphabetical_directive = line.contains("tidy-alphabetical-start")
                     || line.contains("tidy-alphabetical-end");
-                let has_recognized_directive =
-                    has_recognized_ignore_directive || has_alphabetical_directive;
+                let has_other_tidy_ignore_directive =
+                    line.contains("ignore-tidy-target-specific-tests");
+                let has_recognized_directive = has_recognized_ignore_directive
+                    || has_alphabetical_directive
+                    || has_other_tidy_ignore_directive;
                 if contains_potential_directive && (!has_recognized_directive) {
                     err("Unrecognized tidy directive")
                 }
diff --git a/src/tools/tidy/src/target_specific_tests.rs b/src/tools/tidy/src/target_specific_tests.rs
index f4a6783abb6..b2d5f259eb2 100644
--- a/src/tools/tidy/src/target_specific_tests.rs
+++ b/src/tools/tidy/src/target_specific_tests.rs
@@ -12,12 +12,16 @@ const COMPILE_FLAGS_HEADER: &str = "compile-flags:";
 
 #[derive(Default, Debug)]
 struct RevisionInfo<'a> {
-    target_arch: Option<&'a str>,
+    target_arch: Option<Option<&'a str>>,
     llvm_components: Option<Vec<&'a str>>,
 }
 
 pub fn check(tests_path: &Path, bad: &mut bool) {
     crate::walk::walk(tests_path, |path, _is_dir| filter_not_rust(path), &mut |entry, content| {
+        if content.contains("// ignore-tidy-target-specific-tests") {
+            return;
+        }
+
         let file = entry.path().display();
         let mut header_map = BTreeMap::new();
         iter_header(content, &mut |HeaderLine { revision, directive, .. }| {
@@ -34,10 +38,11 @@ pub fn check(tests_path: &Path, bad: &mut bool) {
                 && let Some((_, v)) = compile_flags.split_once("--target")
             {
                 let v = v.trim_start_matches([' ', '=']);
-                let v = if v == "{{target}}" { Some((v, v)) } else { v.split_once("-") };
-                if let Some((arch, _)) = v {
-                    let info = header_map.entry(revision).or_insert(RevisionInfo::default());
-                    info.target_arch.replace(arch);
+                let info = header_map.entry(revision).or_insert(RevisionInfo::default());
+                if v.starts_with("{{") {
+                    info.target_arch.replace(None);
+                } else if let Some((arch, _)) = v.split_once("-") {
+                    info.target_arch.replace(Some(arch));
                 } else {
                     eprintln!("{file}: seems to have a malformed --target value");
                     *bad = true;
@@ -54,9 +59,11 @@ pub fn check(tests_path: &Path, bad: &mut bool) {
             let rev = rev.unwrap_or("[unspecified]");
             match (target_arch, llvm_components) {
                 (None, None) => {}
-                (Some(_), None) => {
+                (Some(target_arch), None) => {
+                    let llvm_component =
+                        target_arch.map_or_else(|| "<arch>".to_string(), arch_to_llvm_component);
                     eprintln!(
-                        "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER}` as it has `--target` set"
+                        "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
                     );
                     *bad = true;
                 }
@@ -66,11 +73,45 @@ pub fn check(tests_path: &Path, bad: &mut bool) {
                     );
                     *bad = true;
                 }
-                (Some(_), Some(_)) => {
-                    // FIXME: check specified components against the target architectures we
-                    // gathered.
+                (Some(target_arch), Some(llvm_components)) => {
+                    if let Some(target_arch) = target_arch {
+                        let llvm_component = arch_to_llvm_component(target_arch);
+                        if !llvm_components.contains(&llvm_component.as_str()) {
+                            eprintln!(
+                                "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
+                            );
+                            *bad = true;
+                        }
+                    }
                 }
             }
         }
     });
 }
+
+fn arch_to_llvm_component(arch: &str) -> String {
+    // NOTE: This is an *approximate* mapping of Rust's `--target` architecture to LLVM component
+    // names. It is not intended to be an authoritative source, but rather a best-effort that's good
+    // enough for the purpose of this tidy check.
+    match arch {
+        "amdgcn" => "amdgpu".into(),
+        "aarch64_be" | "arm64_32" | "arm64e" | "arm64ec" => "aarch64".into(),
+        "i386" | "i586" | "i686" | "x86" | "x86_64" | "x86_64h" => "x86".into(),
+        "loongarch32" | "loongarch64" => "loongarch".into(),
+        "nvptx64" => "nvptx".into(),
+        "s390x" => "systemz".into(),
+        "sparc64" | "sparcv9" => "sparc".into(),
+        "wasm32" | "wasm32v1" | "wasm64" => "webassembly".into(),
+        _ if arch.starts_with("armeb")
+            || arch.starts_with("armv")
+            || arch.starts_with("thumbv") =>
+        {
+            "arm".into()
+        }
+        _ if arch.starts_with("bpfe") => "bpf".into(),
+        _ if arch.starts_with("mips") => "mips".into(),
+        _ if arch.starts_with("powerpc") => "powerpc".into(),
+        _ if arch.starts_with("riscv") => "riscv".into(),
+        _ => arch.to_ascii_lowercase(),
+    }
+}
diff --git a/tests/assembly-llvm/nvptx-safe-naming.rs b/tests/assembly-llvm/nvptx-safe-naming.rs
index d7b46aadd9c..6a6659a4e30 100644
--- a/tests/assembly-llvm/nvptx-safe-naming.rs
+++ b/tests/assembly-llvm/nvptx-safe-naming.rs
@@ -1,6 +1,9 @@
 //@ assembly-output: ptx-linker
 //@ compile-flags: --crate-type cdylib -Z unstable-options -Clinker-flavor=llbc
 //@ only-nvptx64
+//@ revisions: LLVM20 LLVM21
+//@ [LLVM21] min-llvm-version: 21
+//@ [LLVM20] max-llvm-major-version: 20
 
 #![feature(abi_ptx)]
 #![no_std]
@@ -15,7 +18,8 @@ extern crate breakpoint_panic_handler;
 #[no_mangle]
 pub unsafe extern "ptx-kernel" fn top_kernel(a: *const u32, b: *mut u32) {
     // CHECK:      call.uni (retval0),
-    // CHECK-NEXT: [[IMPL_FN]]
+    // LLVM20-NEXT: [[IMPL_FN]]
+    // LLVM21-SAME: [[IMPL_FN]]
     *b = deep::private::MyStruct::new(*a).square();
 }
 
diff --git a/tests/assembly-llvm/x86-return-float.rs b/tests/assembly-llvm/x86-return-float.rs
index 165c11d2280..4db93f68485 100644
--- a/tests/assembly-llvm/x86-return-float.rs
+++ b/tests/assembly-llvm/x86-return-float.rs
@@ -334,9 +334,9 @@ pub fn return_f128(x: f128) -> f128 {
     // linux-NEXT: .cfi_offset
     // CHECK-NEXT: movl %esp, %ebp
     // linux-NEXT: .cfi_def_cfa_register
-    // linux-NEXT: movaps 8(%ebp), %xmm0
-    // win-NEXT: movups 8(%ebp), %xmm0
-    // CHECK-NEXT: popl %ebp
+    // linux: movaps 8(%ebp), %xmm0
+    // win: movups 8(%ebp), %xmm0
+    // CHECK: popl %ebp
     // linux-NEXT: .cfi_def_cfa
     // CHECK-NEXT: retl
     x
diff --git a/tests/codegen-llvm/abi-efiapi.rs b/tests/codegen-llvm/abi-efiapi.rs
index 1736f0daf0f..4cd645101a8 100644
--- a/tests/codegen-llvm/abi-efiapi.rs
+++ b/tests/codegen-llvm/abi-efiapi.rs
@@ -3,15 +3,15 @@
 //@ add-core-stubs
 //@ revisions:x86_64 i686 aarch64 arm riscv
 //@[x86_64] compile-flags: --target x86_64-unknown-uefi
-//@[x86_64] needs-llvm-components: aarch64 arm riscv
+//@[x86_64] needs-llvm-components: x86
 //@[i686] compile-flags: --target i686-unknown-linux-musl
-//@[i686] needs-llvm-components: aarch64 arm riscv
+//@[i686] needs-llvm-components: x86
 //@[aarch64] compile-flags: --target aarch64-unknown-none
-//@[aarch64] needs-llvm-components: aarch64 arm riscv
+//@[aarch64] needs-llvm-components: aarch64
 //@[arm] compile-flags: --target armv7r-none-eabi
-//@[arm] needs-llvm-components: aarch64 arm riscv
+//@[arm] needs-llvm-components: arm
 //@[riscv] compile-flags: --target riscv64gc-unknown-none-elf
-//@[riscv] needs-llvm-components: aarch64 arm riscv
+//@[riscv] needs-llvm-components: riscv
 //@ compile-flags: -C no-prepopulate-passes
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/become-musttail.rs b/tests/codegen-llvm/become-musttail.rs
new file mode 100644
index 00000000000..07f33571910
--- /dev/null
+++ b/tests/codegen-llvm/become-musttail.rs
@@ -0,0 +1,18 @@
+//@ compile-flags: -C opt-level=0 -Cpanic=abort -C no-prepopulate-passes
+//@ needs-unwind
+
+#![crate_type = "lib"]
+#![feature(explicit_tail_calls)]
+
+// CHECK-LABEL: define {{.*}}@fibonacci(
+#[no_mangle]
+#[inline(never)]
+pub fn fibonacci(n: u64, a: u64, b: u64) -> u64 {
+    // CHECK: musttail call {{.*}}@fibonacci(
+    // CHECK-NEXT: ret i64
+    match n {
+        0 => a,
+        1 => b,
+        _ => become fibonacci(n - 1, b, a + b),
+    }
+}
diff --git a/tests/codegen-llvm/cast-target-abi.rs b/tests/codegen-llvm/cast-target-abi.rs
index cbd49e2f022..28d3ad5f61c 100644
--- a/tests/codegen-llvm/cast-target-abi.rs
+++ b/tests/codegen-llvm/cast-target-abi.rs
@@ -4,7 +4,7 @@
 //@ compile-flags: -Copt-level=3 -Cno-prepopulate-passes -Zlint-llvm-ir
 
 //@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu
-//@[aarch64] needs-llvm-components: arm
+//@[aarch64] needs-llvm-components: aarch64
 //@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
 //@[loongarch64] needs-llvm-components: loongarch
 //@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu
diff --git a/tests/codegen-llvm/cf-protection.rs b/tests/codegen-llvm/cf-protection.rs
index f1349a5dcb9..9efadb59932 100644
--- a/tests/codegen-llvm/cf-protection.rs
+++ b/tests/codegen-llvm/cf-protection.rs
@@ -3,7 +3,7 @@
 //@ add-core-stubs
 //@ revisions: undefined none branch return full
 //@ needs-llvm-components: x86
-//@ [undefined] compile-flags:
+// [undefined] no extra compile-flags
 //@ [none] compile-flags: -Z cf-protection=none
 //@ [branch] compile-flags: -Z cf-protection=branch
 //@ [return] compile-flags: -Z cf-protection=return
diff --git a/tests/codegen-llvm/codemodels.rs b/tests/codegen-llvm/codemodels.rs
index 06d2eade78a..e82e094aab8 100644
--- a/tests/codegen-llvm/codemodels.rs
+++ b/tests/codegen-llvm/codemodels.rs
@@ -1,7 +1,7 @@
 //@ only-x86_64
 
 //@ revisions: NOMODEL MODEL-SMALL MODEL-KERNEL MODEL-MEDIUM MODEL-LARGE
-//@[NOMODEL] compile-flags:
+// [NOMODEL] no compile-flags
 //@[MODEL-SMALL] compile-flags: -C code-model=small
 //@[MODEL-KERNEL] compile-flags: -C code-model=kernel
 //@[MODEL-MEDIUM] compile-flags: -C code-model=medium
diff --git a/tests/codegen-llvm/diverging-function-call-debuginfo.rs b/tests/codegen-llvm/diverging-function-call-debuginfo.rs
new file mode 100644
index 00000000000..1a80fe1643d
--- /dev/null
+++ b/tests/codegen-llvm/diverging-function-call-debuginfo.rs
@@ -0,0 +1,38 @@
+/// Make sure that line debuginfo is correct for diverging calls under certain
+/// conditions. In particular we want to ensure that the line number is never
+/// 0, but we check the absence of 0 by looking for the expected exact line
+/// numbers. Regression test for <https://github.com/rust-lang/rust/issues/59558>.
+
+//@ compile-flags: -g -Clto -Copt-level=0
+//@ no-prefer-dynamic
+
+// First find the scope of both diverge() calls, namely this main() function.
+// CHECK-DAG: [[MAIN_SCOPE:![0-9]+]] = distinct !DISubprogram(name: "main", linkageName: {{.*}}diverging_function_call_debuginfo{{.*}}main{{.*}}
+fn main() {
+    if True == False {
+        // unreachable
+        // Then find the DILocation with the correct line number for this call ...
+        // CHECK-DAG: [[UNREACHABLE_CALL_DBG:![0-9]+]] = !DILocation(line: [[@LINE+1]], {{.*}}scope: [[MAIN_SCOPE]]
+        diverge();
+    }
+
+    // ... and this call.
+    // CHECK-DAG: [[LAST_CALL_DBG:![0-9]+]] = !DILocation(line: [[@LINE+1]], {{.*}}scope: [[MAIN_SCOPE]]
+    diverge();
+}
+
+#[derive(PartialEq)]
+pub enum MyBool {
+    True,
+    False,
+}
+
+use MyBool::*;
+
+fn diverge() -> ! {
+    panic!();
+}
+
+// Finally make sure both DILocations belong to each the respective diverge() call.
+// CHECK-DAG: call void {{.*}}diverging_function_call_debuginfo{{.*}}diverge{{.*}} !dbg [[LAST_CALL_DBG]]
+// CHECK-DAG: call void {{.*}}diverging_function_call_debuginfo{{.*}}diverge{{.*}} !dbg [[UNREACHABLE_CALL_DBG]]
diff --git a/tests/codegen-llvm/ehcontguard_disabled.rs b/tests/codegen-llvm/ehcontguard_disabled.rs
index 9efb2721b3e..962d14e7eb9 100644
--- a/tests/codegen-llvm/ehcontguard_disabled.rs
+++ b/tests/codegen-llvm/ehcontguard_disabled.rs
@@ -1,5 +1,3 @@
-//@ compile-flags:
-
 #![crate_type = "lib"]
 
 // A basic test function.
diff --git a/tests/codegen-llvm/enum/enum-discriminant-eq.rs b/tests/codegen-llvm/enum/enum-discriminant-eq.rs
index 0494c5f551b..d599685c2e5 100644
--- a/tests/codegen-llvm/enum/enum-discriminant-eq.rs
+++ b/tests/codegen-llvm/enum/enum-discriminant-eq.rs
@@ -1,6 +1,9 @@
 //@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled
 //@ min-llvm-version: 20
 //@ only-64bit
+//@ revisions: LLVM20 LLVM21
+//@ [LLVM21] min-llvm-version: 21
+//@ [LLVM20] max-llvm-major-version: 20
 
 // The `derive(PartialEq)` on enums with field-less variants compares discriminants,
 // so make sure we emit that in some reasonable way.
@@ -137,17 +140,20 @@ pub fn mid_nz32_eq_discr(a: Mid<NonZero<u32>>, b: Mid<NonZero<u32>>) -> bool {
 pub fn mid_ac_eq_discr(a: Mid<AC>, b: Mid<AC>) -> bool {
     // CHECK-LABEL: @mid_ac_eq_discr(
 
-    // CHECK: %[[A_REL_DISCR:.+]] = xor i8 %a, -128
+    // LLVM20: %[[A_REL_DISCR:.+]] = xor i8 %a, -128
     // CHECK: %[[A_IS_NICHE:.+]] = icmp slt i8 %a, 0
     // CHECK: %[[A_NOT_HOLE:.+]] = icmp ne i8 %a, -127
     // CHECK: tail call void @llvm.assume(i1 %[[A_NOT_HOLE]])
-    // CHECK: %[[A_DISCR:.+]] = select i1 %[[A_IS_NICHE]], i8 %[[A_REL_DISCR]], i8 1
+    // LLVM20: %[[A_DISCR:.+]] = select i1 %[[A_IS_NICHE]], i8 %[[A_REL_DISCR]], i8 1
 
-    // CHECK: %[[B_REL_DISCR:.+]] = xor i8 %b, -128
+    // LLVM20: %[[B_REL_DISCR:.+]] = xor i8 %b, -128
     // CHECK: %[[B_IS_NICHE:.+]] = icmp slt i8 %b, 0
     // CHECK: %[[B_NOT_HOLE:.+]] = icmp ne i8 %b, -127
     // CHECK: tail call void @llvm.assume(i1 %[[B_NOT_HOLE]])
-    // CHECK: %[[B_DISCR:.+]] = select i1 %[[B_IS_NICHE]], i8 %[[B_REL_DISCR]], i8 1
+    // LLVM20: %[[B_DISCR:.+]] = select i1 %[[B_IS_NICHE]], i8 %[[B_REL_DISCR]], i8 1
+
+    // LLVM21: %[[A_DISCR:.+]] = select i1 %[[A_IS_NICHE]], i8 %a, i8 -127
+    // LLVM21: %[[B_DISCR:.+]] = select i1 %[[B_IS_NICHE]], i8 %b, i8 -127
 
     // CHECK: %[[R:.+]] = icmp eq i8 %[[A_DISCR]], %[[B_DISCR]]
     // CHECK: ret i1 %[[R]]
diff --git a/tests/codegen-llvm/naked-fn/naked-functions.rs b/tests/codegen-llvm/naked-fn/naked-functions.rs
index 344af6eb42f..8a7ee4b4de5 100644
--- a/tests/codegen-llvm/naked-fn/naked-functions.rs
+++ b/tests/codegen-llvm/naked-fn/naked-functions.rs
@@ -8,7 +8,7 @@
 //@[win_i686] compile-flags: --target i686-pc-windows-gnu
 //@[win_i686] needs-llvm-components: x86
 //@[macos] compile-flags: --target aarch64-apple-darwin
-//@[macos] needs-llvm-components: arm
+//@[macos] needs-llvm-components: aarch64
 //@[thumb] compile-flags: --target thumbv7em-none-eabi
 //@[thumb] needs-llvm-components: arm
 
diff --git a/tests/codegen-llvm/sanitizer/address-sanitizer-globals-tracking.rs b/tests/codegen-llvm/sanitizer/address-sanitizer-globals-tracking.rs
index f319306f93f..642bf5e7576 100644
--- a/tests/codegen-llvm/sanitizer/address-sanitizer-globals-tracking.rs
+++ b/tests/codegen-llvm/sanitizer/address-sanitizer-globals-tracking.rs
@@ -19,9 +19,9 @@
 //@ only-linux
 //
 //@ revisions:ASAN ASAN-FAT-LTO
-//@                compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static
-//@[ASAN]          compile-flags:
-//@[ASAN-FAT-LTO]  compile-flags: -Cprefer-dynamic=false -Clto=fat
+//@ compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static
+// [ASAN] no extra compile-flags
+//@[ASAN-FAT-LTO] compile-flags: -Cprefer-dynamic=false -Clto=fat
 
 #![crate_type = "staticlib"]
 
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs
index 6b40918dd3a..02c31fb8e9b 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs
@@ -5,7 +5,7 @@
 //@ [aarch64] compile-flags: --target aarch64-unknown-none
 //@ [aarch64] needs-llvm-components: aarch64
 //@ [x86_64] compile-flags: --target x86_64-unknown-none
-//@ [x86_64] needs-llvm-components:
+//@ [x86_64] needs-llvm-components: x86
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Copt-level=0
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-generalized.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-generalized.rs
index 942b50deb02..9a60d51713f 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-generalized.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-generalized.rs
@@ -5,7 +5,7 @@
 //@ [aarch64] compile-flags: --target aarch64-unknown-none
 //@ [aarch64] needs-llvm-components: aarch64
 //@ [x86_64] compile-flags: --target x86_64-unknown-none
-//@ [x86_64] needs-llvm-components:
+//@ [x86_64] needs-llvm-components: x86
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Zsanitizer-cfi-generalize-pointers
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized-generalized.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized-generalized.rs
index c89d9bdd121..134f4ff4bfd 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized-generalized.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized-generalized.rs
@@ -5,7 +5,7 @@
 //@ [aarch64] compile-flags: --target aarch64-unknown-none
 //@ [aarch64] needs-llvm-components: aarch64
 //@ [x86_64] compile-flags: --target x86_64-unknown-none
-//@ [x86_64] needs-llvm-components:
+//@ [x86_64] needs-llvm-components: x86
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Zsanitizer-cfi-normalize-integers -Zsanitizer-cfi-generalize-pointers
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized.rs
index 220cae1a4fa..4328b7fa07d 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi-normalized.rs
@@ -5,7 +5,7 @@
 //@ [aarch64] compile-flags: --target aarch64-unknown-none
 //@ [aarch64] needs-llvm-components: aarch64
 //@ [x86_64] compile-flags: --target x86_64-unknown-none
-//@ [x86_64] needs-llvm-components:
+//@ [x86_64] needs-llvm-components: x86
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Zsanitizer-cfi-normalize-integers
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi.rs
index bb9a0005903..81a9db1b97a 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-itanium-cxx-abi.rs
@@ -5,7 +5,7 @@
 //@ [aarch64] compile-flags: --target aarch64-unknown-none
 //@ [aarch64] needs-llvm-components: aarch64
 //@ [x86_64] compile-flags: --target x86_64-unknown-none
-//@ [x86_64] needs-llvm-components:
+//@ [x86_64] needs-llvm-components: x86
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Copt-level=0
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle.rs
index 8b844b99142..61056c2a54e 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle.rs
@@ -5,7 +5,7 @@
 //@ [aarch64] compile-flags: --target aarch64-unknown-none
 //@ [aarch64] needs-llvm-components: aarch64
 //@ [x86_64] compile-flags: --target x86_64-unknown-none
-//@ [x86_64] needs-llvm-components:
+//@ [x86_64] needs-llvm-components: x86
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Copt-level=0
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-type-metadata-trait-objects.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-type-metadata-trait-objects.rs
index 15c107bea15..182af162d78 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-type-metadata-trait-objects.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-type-metadata-trait-objects.rs
@@ -5,7 +5,7 @@
 //@ [aarch64] compile-flags: --target aarch64-unknown-none
 //@ [aarch64] needs-llvm-components: aarch64
 //@ [x86_64] compile-flags: --target x86_64-unknown-none
-//@ [x86_64] needs-llvm-components:
+//@ [x86_64] needs-llvm-components: x86
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Copt-level=0
 
 #![crate_type = "lib"]
diff --git a/tests/codegen-llvm/sanitizer/memory-track-origins.rs b/tests/codegen-llvm/sanitizer/memory-track-origins.rs
index 318c277e10c..5eb5b356b05 100644
--- a/tests/codegen-llvm/sanitizer/memory-track-origins.rs
+++ b/tests/codegen-llvm/sanitizer/memory-track-origins.rs
@@ -5,7 +5,7 @@
 //@ revisions:MSAN-0 MSAN-1 MSAN-2 MSAN-1-LTO MSAN-2-LTO
 //
 //@ compile-flags: -Zsanitizer=memory -Ctarget-feature=-crt-static
-//@[MSAN-0] compile-flags:
+// [MSAN-0] no extra compile-flags
 //@[MSAN-1] compile-flags: -Zsanitizer-memory-track-origins=1
 //@[MSAN-2] compile-flags: -Zsanitizer-memory-track-origins
 //@[MSAN-1-LTO] compile-flags: -Zsanitizer-memory-track-origins=1 -C lto=fat
diff --git a/tests/codegen-llvm/ub-checks.rs b/tests/codegen-llvm/ub-checks.rs
index 67f5bff08d5..c40bc9acc52 100644
--- a/tests/codegen-llvm/ub-checks.rs
+++ b/tests/codegen-llvm/ub-checks.rs
@@ -6,7 +6,7 @@
 // but ub-checks are explicitly disabled.
 
 //@ revisions: DEBUG NOCHECKS
-//@ [DEBUG] compile-flags:
+// [DEBUG] no extra compile-flags
 //@ [NOCHECKS] compile-flags: -Zub-checks=no
 //@ compile-flags: -Copt-level=3 -Cdebug-assertions=yes
 
diff --git a/tests/crashes/139409.rs b/tests/crashes/139409.rs
deleted file mode 100644
index 68cbfa153de..00000000000
--- a/tests/crashes/139409.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-//@ known-bug: #139409
-//@ compile-flags: -Znext-solver=globally
-
-fn main() {
-    trait B<C> {}
-    impl<C> B<C> for () {}
-    trait D<C, E>: B<C> + B<E> {
-        fn f(&self) {}
-    }
-    impl<C, E> D<C, E> for () {}
-    (&() as &dyn D<&(), &()>).f()
-}
diff --git a/tests/debuginfo/unsized.rs b/tests/debuginfo/unsized.rs
index 17c5e463fba..edd9f5af557 100644
--- a/tests/debuginfo/unsized.rs
+++ b/tests/debuginfo/unsized.rs
@@ -37,7 +37,6 @@
 // cdb-check:    [...] vtable           : 0x[...] [Type: unsigned [...]int[...] (*)[4]]
 
 // cdb-command:dx _box
-// cdb-check:
 // cdb-check:_box             [Type: alloc::boxed::Box<unsized::Foo<dyn$<core::fmt::Debug> >,alloc::alloc::Global>]
 // cdb-check:[+0x000] pointer          : 0x[...] [Type: unsized::Foo<dyn$<core::fmt::Debug> > *]
 // cdb-check:[...] vtable           : 0x[...] [Type: unsigned [...]int[...] (*)[4]]
diff --git a/tests/mir-opt/box_conditional_drop_allocator.main.ElaborateDrops.diff b/tests/mir-opt/box_conditional_drop_allocator.main.ElaborateDrops.diff
new file mode 100644
index 00000000000..6f6c239d7c8
--- /dev/null
+++ b/tests/mir-opt/box_conditional_drop_allocator.main.ElaborateDrops.diff
@@ -0,0 +1,186 @@
+- // MIR for `main` before ElaborateDrops
++ // MIR for `main` after ElaborateDrops
+  
+  fn main() -> () {
+      let mut _0: ();
+      let _1: std::boxed::Box<HasDrop, DropAllocator>;
+      let mut _2: HasDrop;
+      let mut _3: DropAllocator;
+      let mut _4: bool;
+      let _5: ();
+      let mut _6: HasDrop;
+      let _7: ();
+      let mut _8: std::boxed::Box<HasDrop, DropAllocator>;
++     let mut _9: bool;
++     let mut _10: &mut std::boxed::Box<HasDrop, DropAllocator>;
++     let mut _11: ();
++     let mut _12: &mut std::boxed::Box<HasDrop, DropAllocator>;
++     let mut _13: ();
++     let mut _14: *const HasDrop;
++     let mut _15: &mut std::boxed::Box<HasDrop, DropAllocator>;
++     let mut _16: ();
++     let mut _17: *const HasDrop;
+      scope 1 {
+          debug b => _1;
+      }
+  
+      bb0: {
++         _9 = const false;
+          StorageLive(_1);
+          StorageLive(_2);
+          _2 = HasDrop;
+          StorageLive(_3);
+          _3 = DropAllocator;
+          _1 = Box::<HasDrop, DropAllocator>::new_in(move _2, move _3) -> [return: bb1, unwind: bb11];
+      }
+  
+      bb1: {
++         _9 = const true;
+          StorageDead(_3);
+          StorageDead(_2);
+          StorageLive(_4);
+          _4 = const true;
+          switchInt(move _4) -> [0: bb4, otherwise: bb2];
+      }
+  
+      bb2: {
+          StorageLive(_5);
+          StorageLive(_6);
+          _6 = move (*_1);
+          _5 = std::mem::drop::<HasDrop>(move _6) -> [return: bb3, unwind: bb9];
+      }
+  
+      bb3: {
+          StorageDead(_6);
+          StorageDead(_5);
+          _0 = const ();
+          goto -> bb6;
+      }
+  
+      bb4: {
+          StorageLive(_7);
+          StorageLive(_8);
++         _9 = const false;
+          _8 = move _1;
+          _7 = std::mem::drop::<Box<HasDrop, DropAllocator>>(move _8) -> [return: bb5, unwind: bb8];
+      }
+  
+      bb5: {
+          StorageDead(_8);
+          StorageDead(_7);
+          _0 = const ();
+          goto -> bb6;
+      }
+  
+      bb6: {
+          StorageDead(_4);
+-         drop(_1) -> [return: bb7, unwind continue];
++         goto -> bb23;
+      }
+  
+      bb7: {
++         _9 = const false;
+          StorageDead(_1);
+          return;
+      }
+  
+      bb8 (cleanup): {
+-         drop(_8) -> [return: bb10, unwind terminate(cleanup)];
++         goto -> bb10;
+      }
+  
+      bb9 (cleanup): {
+-         drop(_6) -> [return: bb10, unwind terminate(cleanup)];
++         goto -> bb10;
+      }
+  
+      bb10 (cleanup): {
+-         drop(_1) -> [return: bb13, unwind terminate(cleanup)];
++         goto -> bb29;
+      }
+  
+      bb11 (cleanup): {
+-         drop(_3) -> [return: bb12, unwind terminate(cleanup)];
++         goto -> bb12;
+      }
+  
+      bb12 (cleanup): {
+-         drop(_2) -> [return: bb13, unwind terminate(cleanup)];
++         goto -> bb13;
+      }
+  
+      bb13 (cleanup): {
+          resume;
++     }
++ 
++     bb14: {
++         _9 = const false;
++         goto -> bb7;
++     }
++ 
++     bb15 (cleanup): {
++         drop((_1.1: DropAllocator)) -> [return: bb13, unwind terminate(cleanup)];
++     }
++ 
++     bb16 (cleanup): {
++         switchInt(copy _9) -> [0: bb13, otherwise: bb15];
++     }
++ 
++     bb17: {
++         drop((_1.1: DropAllocator)) -> [return: bb14, unwind: bb13];
++     }
++ 
++     bb18: {
++         switchInt(copy _9) -> [0: bb14, otherwise: bb17];
++     }
++ 
++     bb19: {
++         _10 = &mut _1;
++         _11 = <Box<HasDrop, DropAllocator> as Drop>::drop(move _10) -> [return: bb18, unwind: bb16];
++     }
++ 
++     bb20 (cleanup): {
++         _12 = &mut _1;
++         _13 = <Box<HasDrop, DropAllocator> as Drop>::drop(move _12) -> [return: bb16, unwind terminate(cleanup)];
++     }
++ 
++     bb21: {
++         goto -> bb19;
++     }
++ 
++     bb22: {
++         _14 = copy ((_1.0: std::ptr::Unique<HasDrop>).0: std::ptr::NonNull<HasDrop>) as *const HasDrop (Transmute);
++         goto -> bb21;
++     }
++ 
++     bb23: {
++         switchInt(copy _9) -> [0: bb18, otherwise: bb22];
++     }
++ 
++     bb24 (cleanup): {
++         drop((_1.1: DropAllocator)) -> [return: bb13, unwind terminate(cleanup)];
++     }
++ 
++     bb25 (cleanup): {
++         switchInt(copy _9) -> [0: bb13, otherwise: bb24];
++     }
++ 
++     bb26 (cleanup): {
++         _15 = &mut _1;
++         _16 = <Box<HasDrop, DropAllocator> as Drop>::drop(move _15) -> [return: bb25, unwind terminate(cleanup)];
++     }
++ 
++     bb27 (cleanup): {
++         goto -> bb26;
++     }
++ 
++     bb28 (cleanup): {
++         _17 = copy ((_1.0: std::ptr::Unique<HasDrop>).0: std::ptr::NonNull<HasDrop>) as *const HasDrop (Transmute);
++         goto -> bb27;
++     }
++ 
++     bb29 (cleanup): {
++         switchInt(copy _9) -> [0: bb25, otherwise: bb28];
+      }
+  }
+  
diff --git a/tests/mir-opt/box_conditional_drop_allocator.rs b/tests/mir-opt/box_conditional_drop_allocator.rs
new file mode 100644
index 00000000000..9471be14c87
--- /dev/null
+++ b/tests/mir-opt/box_conditional_drop_allocator.rs
@@ -0,0 +1,39 @@
+// skip-filecheck
+//@ test-mir-pass: ElaborateDrops
+//@ needs-unwind
+#![feature(allocator_api)]
+
+// Regression test for #131082.
+// Testing that the allocator of a Box is dropped in conditional drops
+
+use std::alloc::{AllocError, Allocator, Global, Layout};
+use std::ptr::NonNull;
+
+struct DropAllocator;
+
+unsafe impl Allocator for DropAllocator {
+    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
+        Global.allocate(layout)
+    }
+    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
+        Global.deallocate(ptr, layout);
+    }
+}
+impl Drop for DropAllocator {
+    fn drop(&mut self) {}
+}
+
+struct HasDrop;
+impl Drop for HasDrop {
+    fn drop(&mut self) {}
+}
+
+// EMIT_MIR box_conditional_drop_allocator.main.ElaborateDrops.diff
+fn main() {
+    let b = Box::new_in(HasDrop, DropAllocator);
+    if true {
+        drop(*b);
+    } else {
+        drop(b);
+    }
+}
diff --git a/tests/run-make/link-cfg/rmake.rs b/tests/run-make/link-cfg/rmake.rs
index 732de5dbd0b..18577fb836d 100644
--- a/tests/run-make/link-cfg/rmake.rs
+++ b/tests/run-make/link-cfg/rmake.rs
@@ -12,6 +12,7 @@
 
 //@ ignore-cross-compile
 // Reason: the compiled binary is executed
+//@ needs-llvm-components: x86
 
 use run_make_support::{bare_rustc, build_native_dynamic_lib, build_native_static_lib, run, rustc};
 
diff --git a/tests/run-make/mismatching-target-triples/rmake.rs b/tests/run-make/mismatching-target-triples/rmake.rs
index 6f41eac8cda..1bbe945e0da 100644
--- a/tests/run-make/mismatching-target-triples/rmake.rs
+++ b/tests/run-make/mismatching-target-triples/rmake.rs
@@ -4,6 +4,7 @@
 // now replaced by a clearer normal error message. This test checks that this aforementioned
 // error message is used.
 // See https://github.com/rust-lang/rust/issues/10814
+//@ needs-llvm-components: x86
 
 use run_make_support::rustc;
 
diff --git a/tests/run-make/musl-default-linking/rmake.rs b/tests/run-make/musl-default-linking/rmake.rs
index 017444cfcdd..7bb54e2739c 100644
--- a/tests/run-make/musl-default-linking/rmake.rs
+++ b/tests/run-make/musl-default-linking/rmake.rs
@@ -4,6 +4,7 @@ use run_make_support::{rustc, serde_json};
 // Per https://github.com/rust-lang/compiler-team/issues/422,
 // we should be trying to move these targets to dynamically link
 // musl libc by default.
+//@ needs-llvm-components: aarch64 arm mips powerpc riscv systemz x86
 static LEGACY_STATIC_LINKING_TARGETS: &[&'static str] = &[
     "aarch64-unknown-linux-musl",
     "arm-unknown-linux-musleabi",
diff --git a/tests/run-make/rustdoc-target-spec-json-path/rmake.rs b/tests/run-make/rustdoc-target-spec-json-path/rmake.rs
index fe9587f5022..d43aa9b90ac 100644
--- a/tests/run-make/rustdoc-target-spec-json-path/rmake.rs
+++ b/tests/run-make/rustdoc-target-spec-json-path/rmake.rs
@@ -1,4 +1,5 @@
 // Test that rustdoc will properly canonicalize the target spec json path just like rustc.
+//@ needs-llvm-components: x86
 
 use run_make_support::{cwd, rustc, rustdoc};
 
diff --git a/tests/run-make/target-specs/rmake.rs b/tests/run-make/target-specs/rmake.rs
index 7e565588ed6..7c30a5b21b3 100644
--- a/tests/run-make/target-specs/rmake.rs
+++ b/tests/run-make/target-specs/rmake.rs
@@ -4,6 +4,7 @@
 // with the target flag's bundle of new features to check that compilation either succeeds while
 // using them correctly, or fails with the right error message when using them improperly.
 // See https://github.com/rust-lang/rust/pull/16156
+//@ needs-llvm-components: x86
 
 use run_make_support::{diff, rfs, rustc};
 
diff --git a/tests/rustdoc-ui/2024-doctests-checks.rs b/tests/rustdoc-ui/2024-doctests-checks.rs
index 0c3a11771f3..61f90fe6231 100644
--- a/tests/rustdoc-ui/2024-doctests-checks.rs
+++ b/tests/rustdoc-ui/2024-doctests-checks.rs
@@ -3,6 +3,8 @@
 //@ compile-flags: --test --test-args=--test-threads=1
 //@ normalize-stdout: "tests/rustdoc-ui" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ normalize-stdout: ".rs:\d+:\d+" -> ".rs:$$LINE:$$COL"
 
 /// ```
diff --git a/tests/rustdoc-ui/2024-doctests-checks.stdout b/tests/rustdoc-ui/2024-doctests-checks.stdout
index 534fe466fe7..c86eafd61b9 100644
--- a/tests/rustdoc-ui/2024-doctests-checks.stdout
+++ b/tests/rustdoc-ui/2024-doctests-checks.stdout
@@ -1,12 +1,13 @@
 
 running 1 test
-test $DIR/2024-doctests-checks.rs - Foo (line 8) ... ok
+test $DIR/2024-doctests-checks.rs - Foo (line 10) ... ok
 
 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
 
 running 1 test
-test $DIR/2024-doctests-checks.rs - Foo (line 15) ... ok
+test $DIR/2024-doctests-checks.rs - Foo (line 17) ... ok
 
 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/2024-doctests-crate-attribute.rs b/tests/rustdoc-ui/2024-doctests-crate-attribute.rs
index c9887cbc63b..416d50cb070 100644
--- a/tests/rustdoc-ui/2024-doctests-crate-attribute.rs
+++ b/tests/rustdoc-ui/2024-doctests-crate-attribute.rs
@@ -4,6 +4,8 @@
 //@ normalize-stdout: "tests/rustdoc-ui" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
 //@ normalize-stdout: ".rs:\d+:\d+" -> ".rs:$$LINE:$$COL"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 
 /// This doctest is used to ensure that if a crate attribute is present,
 /// it will not be part of the merged doctests.
diff --git a/tests/rustdoc-ui/2024-doctests-crate-attribute.stdout b/tests/rustdoc-ui/2024-doctests-crate-attribute.stdout
index c084ac4522e..20618426312 100644
--- a/tests/rustdoc-ui/2024-doctests-crate-attribute.stdout
+++ b/tests/rustdoc-ui/2024-doctests-crate-attribute.stdout
@@ -1,12 +1,13 @@
 
 running 1 test
-test $DIR/2024-doctests-crate-attribute.rs - Foo (line 20) ... ok
+test $DIR/2024-doctests-crate-attribute.rs - Foo (line 22) ... ok
 
 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
 
 running 1 test
-test $DIR/2024-doctests-crate-attribute.rs - Foo (line 11) ... ok
+test $DIR/2024-doctests-crate-attribute.rs - Foo (line 13) ... ok
 
 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/dead-code-2024.rs b/tests/rustdoc-ui/doctest/dead-code-2024.rs
index 079d44570bb..e02d2601c58 100644
--- a/tests/rustdoc-ui/doctest/dead-code-2024.rs
+++ b/tests/rustdoc-ui/doctest/dead-code-2024.rs
@@ -4,6 +4,8 @@
 //@ compile-flags:--test
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 #![doc(test(attr(allow(unused_variables), deny(warnings))))]
diff --git a/tests/rustdoc-ui/doctest/dead-code-2024.stdout b/tests/rustdoc-ui/doctest/dead-code-2024.stdout
index a943a177e10..bf9cd65200b 100644
--- a/tests/rustdoc-ui/doctest/dead-code-2024.stdout
+++ b/tests/rustdoc-ui/doctest/dead-code-2024.stdout
@@ -1,18 +1,18 @@
 
 running 1 test
-test $DIR/dead-code-2024.rs - f (line 13) - compile ... FAILED
+test $DIR/dead-code-2024.rs - f (line 15) - compile ... FAILED
 
 failures:
 
----- $DIR/dead-code-2024.rs - f (line 13) stdout ----
+---- $DIR/dead-code-2024.rs - f (line 15) stdout ----
 error: trait `T` is never used
-  --> $DIR/dead-code-2024.rs:14:7
+  --> $DIR/dead-code-2024.rs:16:7
    |
 LL | trait T { fn f(); }
    |       ^
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-2024.rs:12:9
+  --> $DIR/dead-code-2024.rs:14:9
    |
 LL | #![deny(warnings)]
    |         ^^^^^^^^
@@ -23,7 +23,8 @@ error: aborting due to 1 previous error
 Couldn't compile the test.
 
 failures:
-    $DIR/dead-code-2024.rs - f (line 13)
+    $DIR/dead-code-2024.rs - f (line 15)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/dead-code-items.rs b/tests/rustdoc-ui/doctest/dead-code-items.rs
index 015504cbced..ff59bfaabc4 100644
--- a/tests/rustdoc-ui/doctest/dead-code-items.rs
+++ b/tests/rustdoc-ui/doctest/dead-code-items.rs
@@ -4,6 +4,8 @@
 //@ compile-flags:--test --test-args=--test-threads=1
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 #![doc(test(attr(deny(warnings))))]
diff --git a/tests/rustdoc-ui/doctest/dead-code-items.stdout b/tests/rustdoc-ui/doctest/dead-code-items.stdout
index 4b9d8be94dd..ecfe09f09ce 100644
--- a/tests/rustdoc-ui/doctest/dead-code-items.stdout
+++ b/tests/rustdoc-ui/doctest/dead-code-items.stdout
@@ -1,30 +1,30 @@
 
 running 13 tests
-test $DIR/dead-code-items.rs - A (line 32) - compile ... ok
-test $DIR/dead-code-items.rs - A (line 88) - compile ... ok
-test $DIR/dead-code-items.rs - A::field (line 39) - compile ... FAILED
-test $DIR/dead-code-items.rs - A::method (line 94) - compile ... ok
-test $DIR/dead-code-items.rs - C (line 22) - compile ... FAILED
-test $DIR/dead-code-items.rs - Enum (line 70) - compile ... FAILED
-test $DIR/dead-code-items.rs - Enum::Variant1 (line 77) - compile ... FAILED
-test $DIR/dead-code-items.rs - MyTrait (line 103) - compile ... FAILED
-test $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110) - compile ... FAILED
-test $DIR/dead-code-items.rs - S (line 14) - compile ... ok
-test $DIR/dead-code-items.rs - U (line 48) - compile ... ok
-test $DIR/dead-code-items.rs - U::field (line 55) - compile ... FAILED
-test $DIR/dead-code-items.rs - U::field2 (line 61) - compile ... ok
+test $DIR/dead-code-items.rs - A (line 34) - compile ... ok
+test $DIR/dead-code-items.rs - A (line 90) - compile ... ok
+test $DIR/dead-code-items.rs - A::field (line 41) - compile ... FAILED
+test $DIR/dead-code-items.rs - A::method (line 96) - compile ... ok
+test $DIR/dead-code-items.rs - C (line 24) - compile ... FAILED
+test $DIR/dead-code-items.rs - Enum (line 72) - compile ... FAILED
+test $DIR/dead-code-items.rs - Enum::Variant1 (line 79) - compile ... FAILED
+test $DIR/dead-code-items.rs - MyTrait (line 105) - compile ... FAILED
+test $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112) - compile ... FAILED
+test $DIR/dead-code-items.rs - S (line 16) - compile ... ok
+test $DIR/dead-code-items.rs - U (line 50) - compile ... ok
+test $DIR/dead-code-items.rs - U::field (line 57) - compile ... FAILED
+test $DIR/dead-code-items.rs - U::field2 (line 63) - compile ... ok
 
 failures:
 
----- $DIR/dead-code-items.rs - A::field (line 39) stdout ----
+---- $DIR/dead-code-items.rs - A::field (line 41) stdout ----
 error: trait `DeadCodeInField` is never used
-  --> $DIR/dead-code-items.rs:40:7
+  --> $DIR/dead-code-items.rs:42:7
    |
 LL | trait DeadCodeInField {}
    |       ^^^^^^^^^^^^^^^
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-items.rs:38:9
+  --> $DIR/dead-code-items.rs:40:9
    |
 LL | #![deny(dead_code)]
    |         ^^^^^^^^^
@@ -32,15 +32,15 @@ LL | #![deny(dead_code)]
 error: aborting due to 1 previous error
 
 Couldn't compile the test.
----- $DIR/dead-code-items.rs - C (line 22) stdout ----
+---- $DIR/dead-code-items.rs - C (line 24) stdout ----
 error: unused variable: `unused_error`
-  --> $DIR/dead-code-items.rs:23:5
+  --> $DIR/dead-code-items.rs:25:5
    |
 LL | let unused_error = 5;
    |     ^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_error`
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-items.rs:20:9
+  --> $DIR/dead-code-items.rs:22:9
    |
 LL | #![deny(warnings)]
    |         ^^^^^^^^
@@ -49,15 +49,15 @@ LL | #![deny(warnings)]
 error: aborting due to 1 previous error
 
 Couldn't compile the test.
----- $DIR/dead-code-items.rs - Enum (line 70) stdout ----
+---- $DIR/dead-code-items.rs - Enum (line 72) stdout ----
 error: unused variable: `not_dead_code_but_unused`
-  --> $DIR/dead-code-items.rs:71:5
+  --> $DIR/dead-code-items.rs:73:5
    |
 LL | let not_dead_code_but_unused = 5;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_not_dead_code_but_unused`
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-items.rs:68:9
+  --> $DIR/dead-code-items.rs:70:9
    |
 LL | #![deny(warnings)]
    |         ^^^^^^^^
@@ -66,15 +66,15 @@ LL | #![deny(warnings)]
 error: aborting due to 1 previous error
 
 Couldn't compile the test.
----- $DIR/dead-code-items.rs - Enum::Variant1 (line 77) stdout ----
+---- $DIR/dead-code-items.rs - Enum::Variant1 (line 79) stdout ----
 error: unused variable: `unused_in_variant`
-  --> $DIR/dead-code-items.rs:80:17
+  --> $DIR/dead-code-items.rs:82:17
    |
 LL | fn main() { let unused_in_variant = 5; }
    |                 ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_variant`
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-items.rs:75:9
+  --> $DIR/dead-code-items.rs:77:9
    |
 LL | #![deny(warnings)]
    |         ^^^^^^^^
@@ -83,15 +83,15 @@ LL | #![deny(warnings)]
 error: aborting due to 1 previous error
 
 Couldn't compile the test.
----- $DIR/dead-code-items.rs - MyTrait (line 103) stdout ----
+---- $DIR/dead-code-items.rs - MyTrait (line 105) stdout ----
 error: trait `StillDeadCodeAtMyTrait` is never used
-  --> $DIR/dead-code-items.rs:104:7
+  --> $DIR/dead-code-items.rs:106:7
    |
 LL | trait StillDeadCodeAtMyTrait { }
    |       ^^^^^^^^^^^^^^^^^^^^^^
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-items.rs:102:9
+  --> $DIR/dead-code-items.rs:104:9
    |
 LL | #![deny(dead_code)]
    |         ^^^^^^^^^
@@ -99,15 +99,15 @@ LL | #![deny(dead_code)]
 error: aborting due to 1 previous error
 
 Couldn't compile the test.
----- $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110) stdout ----
+---- $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112) stdout ----
 error: unused variable: `unused_in_impl`
-  --> $DIR/dead-code-items.rs:113:17
+  --> $DIR/dead-code-items.rs:115:17
    |
 LL | fn main() { let unused_in_impl = 5; }
    |                 ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_impl`
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-items.rs:108:9
+  --> $DIR/dead-code-items.rs:110:9
    |
 LL | #![deny(warnings)]
    |         ^^^^^^^^
@@ -116,15 +116,15 @@ LL | #![deny(warnings)]
 error: aborting due to 1 previous error
 
 Couldn't compile the test.
----- $DIR/dead-code-items.rs - U::field (line 55) stdout ----
+---- $DIR/dead-code-items.rs - U::field (line 57) stdout ----
 error: trait `DeadCodeInUnionField` is never used
-  --> $DIR/dead-code-items.rs:56:7
+  --> $DIR/dead-code-items.rs:58:7
    |
 LL | trait DeadCodeInUnionField {}
    |       ^^^^^^^^^^^^^^^^^^^^
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-items.rs:54:9
+  --> $DIR/dead-code-items.rs:56:9
    |
 LL | #![deny(dead_code)]
    |         ^^^^^^^^^
@@ -134,13 +134,14 @@ error: aborting due to 1 previous error
 Couldn't compile the test.
 
 failures:
-    $DIR/dead-code-items.rs - A::field (line 39)
-    $DIR/dead-code-items.rs - C (line 22)
-    $DIR/dead-code-items.rs - Enum (line 70)
-    $DIR/dead-code-items.rs - Enum::Variant1 (line 77)
-    $DIR/dead-code-items.rs - MyTrait (line 103)
-    $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110)
-    $DIR/dead-code-items.rs - U::field (line 55)
+    $DIR/dead-code-items.rs - A::field (line 41)
+    $DIR/dead-code-items.rs - C (line 24)
+    $DIR/dead-code-items.rs - Enum (line 72)
+    $DIR/dead-code-items.rs - Enum::Variant1 (line 79)
+    $DIR/dead-code-items.rs - MyTrait (line 105)
+    $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112)
+    $DIR/dead-code-items.rs - U::field (line 57)
 
 test result: FAILED. 6 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/dead-code-module-2.rs b/tests/rustdoc-ui/doctest/dead-code-module-2.rs
index de7b11b91ec..fd9c313ec9a 100644
--- a/tests/rustdoc-ui/doctest/dead-code-module-2.rs
+++ b/tests/rustdoc-ui/doctest/dead-code-module-2.rs
@@ -4,6 +4,8 @@
 //@ compile-flags:--test
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 #![doc(test(attr(allow(unused_variables))))]
diff --git a/tests/rustdoc-ui/doctest/dead-code-module-2.stdout b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout
index d44068dcbf5..cf737996d5c 100644
--- a/tests/rustdoc-ui/doctest/dead-code-module-2.stdout
+++ b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout
@@ -1,24 +1,24 @@
 
 running 1 test
-test $DIR/dead-code-module-2.rs - g (line 24) - compile ... ok
+test $DIR/dead-code-module-2.rs - g (line 26) - compile ... ok
 
 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
 
 running 1 test
-test $DIR/dead-code-module-2.rs - my_mod::f (line 16) - compile ... FAILED
+test $DIR/dead-code-module-2.rs - my_mod::f (line 18) - compile ... FAILED
 
 failures:
 
----- $DIR/dead-code-module-2.rs - my_mod::f (line 16) stdout ----
+---- $DIR/dead-code-module-2.rs - my_mod::f (line 18) stdout ----
 error: trait `T` is never used
-  --> $DIR/dead-code-module-2.rs:17:7
+  --> $DIR/dead-code-module-2.rs:19:7
    |
 LL | trait T { fn f(); }
    |       ^
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-module-2.rs:15:9
+  --> $DIR/dead-code-module-2.rs:17:9
    |
 LL | #![deny(warnings)]
    |         ^^^^^^^^
@@ -29,7 +29,8 @@ error: aborting due to 1 previous error
 Couldn't compile the test.
 
 failures:
-    $DIR/dead-code-module-2.rs - my_mod::f (line 16)
+    $DIR/dead-code-module-2.rs - my_mod::f (line 18)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/dead-code-module.rs b/tests/rustdoc-ui/doctest/dead-code-module.rs
index f825749a6a2..d3103ad28e9 100644
--- a/tests/rustdoc-ui/doctest/dead-code-module.rs
+++ b/tests/rustdoc-ui/doctest/dead-code-module.rs
@@ -4,6 +4,8 @@
 //@ compile-flags:--test
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 mod my_mod {
diff --git a/tests/rustdoc-ui/doctest/dead-code-module.stdout b/tests/rustdoc-ui/doctest/dead-code-module.stdout
index b5ccf225d25..83c6af3775e 100644
--- a/tests/rustdoc-ui/doctest/dead-code-module.stdout
+++ b/tests/rustdoc-ui/doctest/dead-code-module.stdout
@@ -1,18 +1,18 @@
 
 running 1 test
-test $DIR/dead-code-module.rs - my_mod::f (line 14) - compile ... FAILED
+test $DIR/dead-code-module.rs - my_mod::f (line 16) - compile ... FAILED
 
 failures:
 
----- $DIR/dead-code-module.rs - my_mod::f (line 14) stdout ----
+---- $DIR/dead-code-module.rs - my_mod::f (line 16) stdout ----
 error: trait `T` is never used
-  --> $DIR/dead-code-module.rs:15:7
+  --> $DIR/dead-code-module.rs:17:7
    |
 LL | trait T { fn f(); }
    |       ^
    |
 note: the lint level is defined here
-  --> $DIR/dead-code-module.rs:13:9
+  --> $DIR/dead-code-module.rs:15:9
    |
 LL | #![deny(warnings)]
    |         ^^^^^^^^
@@ -23,7 +23,8 @@ error: aborting due to 1 previous error
 Couldn't compile the test.
 
 failures:
-    $DIR/dead-code-module.rs - my_mod::f (line 14)
+    $DIR/dead-code-module.rs - my_mod::f (line 16)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs b/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs
index a47bac3daef..2f0d6756b27 100644
--- a/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs
+++ b/tests/rustdoc-ui/doctest/doctest-output-include-fail.rs
@@ -2,6 +2,8 @@
 //@ compile-flags:--test --test-args=--test-threads=1
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 // https://github.com/rust-lang/rust/issues/130470
diff --git a/tests/rustdoc-ui/doctest/doctest-output-include-fail.stdout b/tests/rustdoc-ui/doctest/doctest-output-include-fail.stdout
index 22d15f8743c..ceaf60b1201 100644
--- a/tests/rustdoc-ui/doctest/doctest-output-include-fail.stdout
+++ b/tests/rustdoc-ui/doctest/doctest-output-include-fail.stdout
@@ -22,3 +22,4 @@ failures:
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/edition-2024-error-output.rs b/tests/rustdoc-ui/doctest/edition-2024-error-output.rs
index 82a85debcd1..e1e57ad01cd 100644
--- a/tests/rustdoc-ui/doctest/edition-2024-error-output.rs
+++ b/tests/rustdoc-ui/doctest/edition-2024-error-output.rs
@@ -6,6 +6,8 @@
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "panicked at .+rs:" -> "panicked at $$TMP:"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ rustc-env:RUST_BACKTRACE=0
 //@ failure-status: 101
 
diff --git a/tests/rustdoc-ui/doctest/edition-2024-error-output.stdout b/tests/rustdoc-ui/doctest/edition-2024-error-output.stdout
index 273d7071237..ab6aca239af 100644
--- a/tests/rustdoc-ui/doctest/edition-2024-error-output.stdout
+++ b/tests/rustdoc-ui/doctest/edition-2024-error-output.stdout
@@ -1,10 +1,10 @@
 
 running 1 test
-test $DIR/edition-2024-error-output.rs - (line 12) ... FAILED
+test $DIR/edition-2024-error-output.rs - (line 14) ... FAILED
 
 failures:
 
----- $DIR/edition-2024-error-output.rs - (line 12) stdout ----
+---- $DIR/edition-2024-error-output.rs - (line 14) stdout ----
 Test executable failed (exit status: 101).
 
 stderr:
@@ -18,7 +18,8 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
 
 
 failures:
-    $DIR/edition-2024-error-output.rs - (line 12)
+    $DIR/edition-2024-error-output.rs - (line 14)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs
index 793f8654661..0504c3dc730 100644
--- a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs
+++ b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.rs
@@ -5,6 +5,8 @@
 //@ compile-flags:--test
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 /// ```should_panic
diff --git a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout
index 2b04b77c9dc..9047fe0dcdd 100644
--- a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout
+++ b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout
@@ -1,14 +1,15 @@
 
 running 1 test
-test $DIR/failed-doctest-should-panic.rs - Foo (line 10) - should panic ... FAILED
+test $DIR/failed-doctest-should-panic.rs - Foo (line 12) - should panic ... FAILED
 
 failures:
 
----- $DIR/failed-doctest-should-panic.rs - Foo (line 10) stdout ----
-note: test did not panic as expected at $DIR/failed-doctest-should-panic.rs:10:0
+---- $DIR/failed-doctest-should-panic.rs - Foo (line 12) stdout ----
+note: test did not panic as expected at $DIR/failed-doctest-should-panic.rs:12:0
 
 failures:
-    $DIR/failed-doctest-should-panic.rs - Foo (line 10)
+    $DIR/failed-doctest-should-panic.rs - Foo (line 12)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout
index ce767fb8443..d80c0da323d 100644
--- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout
+++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2015.stdout
@@ -1,12 +1,12 @@
 
 running 1 test
-test $DIR/failed-doctest-test-crate.rs - m (line 14) ... FAILED
+test $DIR/failed-doctest-test-crate.rs - m (line 16) ... FAILED
 
 failures:
 
----- $DIR/failed-doctest-test-crate.rs - m (line 14) stdout ----
+---- $DIR/failed-doctest-test-crate.rs - m (line 16) stdout ----
 error[E0432]: unresolved import `test`
-  --> $DIR/failed-doctest-test-crate.rs:15:5
+  --> $DIR/failed-doctest-test-crate.rs:17:5
    |
 LL | use test::*;
    |     ^^^^ use of unresolved module or unlinked crate `test`
@@ -22,7 +22,7 @@ For more information about this error, try `rustc --explain E0432`.
 Couldn't compile the test.
 
 failures:
-    $DIR/failed-doctest-test-crate.rs - m (line 14)
+    $DIR/failed-doctest-test-crate.rs - m (line 16)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout
index 80642e93bbd..724bb9bee62 100644
--- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout
+++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.edition2024.stdout
@@ -1,12 +1,12 @@
 
 running 1 test
-test $DIR/failed-doctest-test-crate.rs - m (line 14) ... FAILED
+test $DIR/failed-doctest-test-crate.rs - m (line 16) ... FAILED
 
 failures:
 
----- $DIR/failed-doctest-test-crate.rs - m (line 14) stdout ----
+---- $DIR/failed-doctest-test-crate.rs - m (line 16) stdout ----
 error[E0432]: unresolved import `test`
-  --> $DIR/failed-doctest-test-crate.rs:15:5
+  --> $DIR/failed-doctest-test-crate.rs:17:5
    |
 LL | use test::*;
    |     ^^^^ use of unresolved module or unlinked crate `test`
@@ -19,7 +19,8 @@ For more information about this error, try `rustc --explain E0432`.
 Couldn't compile the test.
 
 failures:
-    $DIR/failed-doctest-test-crate.rs - m (line 14)
+    $DIR/failed-doctest-test-crate.rs - m (line 16)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs
index 6966d3df11c..75f7ac396f5 100644
--- a/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs
+++ b/tests/rustdoc-ui/doctest/failed-doctest-test-crate.rs
@@ -7,6 +7,8 @@
 //@ compile-flags:--test
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 /// <https://github.com/rust-lang/rust/pull/137899#discussion_r1976743383>
diff --git a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout
index ff26e7e3231..0d00a9fc9c4 100644
--- a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout
+++ b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2015.stdout
@@ -1,12 +1,12 @@
 
 running 1 test
-test $DIR/relative-path-include-bytes-132203.rs - (line 18) ... FAILED
+test $DIR/relative-path-include-bytes-132203.rs - (line 20) ... FAILED
 
 failures:
 
----- $DIR/relative-path-include-bytes-132203.rs - (line 18) stdout ----
+---- $DIR/relative-path-include-bytes-132203.rs - (line 20) stdout ----
 error: couldn't read `$DIR/relative-dir-empty-file`: $FILE_NOT_FOUND_MSG (os error 2)
-  --> $DIR/relative-path-include-bytes-132203.rs:19:9
+  --> $DIR/relative-path-include-bytes-132203.rs:21:9
    |
 LL | let x = include_bytes!("relative-dir-empty-file");
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -16,7 +16,7 @@ error: aborting due to 1 previous error
 Couldn't compile the test.
 
 failures:
-    $DIR/relative-path-include-bytes-132203.rs - (line 18)
+    $DIR/relative-path-include-bytes-132203.rs - (line 20)
 
 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
diff --git a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2024.stdout b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2024.stdout
index e4c65703081..fa5bd7c93fa 100644
--- a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2024.stdout
+++ b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.edition2024.stdout
@@ -4,3 +4,4 @@ test $DIR/auxiliary/relative-dir.md - (line 1) ... ok
 
 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs
index ceacd69a5fd..321edc3ee84 100644
--- a/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs
+++ b/tests/rustdoc-ui/doctest/relative-path-include-bytes-132203.rs
@@ -9,6 +9,8 @@
 //@ normalize-stdout: "tests.rustdoc-ui.doctest." -> "$$DIR/"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
 //@ normalize-stdout: "`: .* \(os error 2\)" -> "`: $$FILE_NOT_FOUND_MSG (os error 2)"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 
 // https://github.com/rust-lang/rust/issues/132203
 // This version, because it's edition2024, passes thanks to the new
diff --git a/tests/rustdoc-ui/doctest/stdout-and-stderr.rs b/tests/rustdoc-ui/doctest/stdout-and-stderr.rs
index 9b0c69d8839..a4eda8c7f83 100644
--- a/tests/rustdoc-ui/doctest/stdout-and-stderr.rs
+++ b/tests/rustdoc-ui/doctest/stdout-and-stderr.rs
@@ -9,6 +9,8 @@
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
 //@ normalize-stdout: "panicked at .+rs:" -> "panicked at $$TMP:"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 //@ rustc-env:RUST_BACKTRACE=0
 
diff --git a/tests/rustdoc-ui/doctest/stdout-and-stderr.stdout b/tests/rustdoc-ui/doctest/stdout-and-stderr.stdout
index b2febe1344f..a35a4d7c3cb 100644
--- a/tests/rustdoc-ui/doctest/stdout-and-stderr.stdout
+++ b/tests/rustdoc-ui/doctest/stdout-and-stderr.stdout
@@ -1,12 +1,12 @@
 
 running 3 tests
-test $DIR/stdout-and-stderr.rs - (line 15) ... FAILED
-test $DIR/stdout-and-stderr.rs - (line 20) ... FAILED
-test $DIR/stdout-and-stderr.rs - (line 24) ... FAILED
+test $DIR/stdout-and-stderr.rs - (line 17) ... FAILED
+test $DIR/stdout-and-stderr.rs - (line 22) ... FAILED
+test $DIR/stdout-and-stderr.rs - (line 26) ... FAILED
 
 failures:
 
----- $DIR/stdout-and-stderr.rs - (line 15) stdout ----
+---- $DIR/stdout-and-stderr.rs - (line 17) stdout ----
 Test executable failed (exit status: 101).
 
 stdout:
@@ -21,7 +21,7 @@ assertion `left == right` failed
 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
 
 
----- $DIR/stdout-and-stderr.rs - (line 20) stdout ----
+---- $DIR/stdout-and-stderr.rs - (line 22) stdout ----
 Test executable failed (exit status: 101).
 
 stderr:
@@ -33,14 +33,15 @@ assertion `left == right` failed
 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
 
 
----- $DIR/stdout-and-stderr.rs - (line 24) stdout ----
+---- $DIR/stdout-and-stderr.rs - (line 26) stdout ----
 Test executable failed (exit status: 1).
 
 
 failures:
-    $DIR/stdout-and-stderr.rs - (line 15)
-    $DIR/stdout-and-stderr.rs - (line 20)
-    $DIR/stdout-and-stderr.rs - (line 24)
+    $DIR/stdout-and-stderr.rs - (line 17)
+    $DIR/stdout-and-stderr.rs - (line 22)
+    $DIR/stdout-and-stderr.rs - (line 26)
 
 test result: FAILED. 0 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc-ui/doctest/wrong-ast-2024.rs b/tests/rustdoc-ui/doctest/wrong-ast-2024.rs
index 3b4fb3f3443..df30e01b25e 100644
--- a/tests/rustdoc-ui/doctest/wrong-ast-2024.rs
+++ b/tests/rustdoc-ui/doctest/wrong-ast-2024.rs
@@ -3,6 +3,8 @@
 //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
 //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
 //@ normalize-stdout: ".rs:\d+:\d+" -> ".rs:$$LINE:$$COL"
+//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME"
+//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME"
 //@ failure-status: 101
 
 /// ```
diff --git a/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout
index 62e1fb10b9f..13567b41e51 100644
--- a/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout
+++ b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout
@@ -1,17 +1,17 @@
 
 running 1 test
-test $DIR/wrong-ast-2024.rs - three (line 18) - should panic ... ok
+test $DIR/wrong-ast-2024.rs - three (line 20) - should panic ... ok
 
 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
 
 running 2 tests
-test $DIR/wrong-ast-2024.rs - one (line 8) ... FAILED
-test $DIR/wrong-ast-2024.rs - two (line 13) ... FAILED
+test $DIR/wrong-ast-2024.rs - one (line 10) ... FAILED
+test $DIR/wrong-ast-2024.rs - two (line 15) ... FAILED
 
 failures:
 
----- $DIR/wrong-ast-2024.rs - one (line 8) stdout ----
+---- $DIR/wrong-ast-2024.rs - one (line 10) stdout ----
 error[E0758]: unterminated block comment
   --> $DIR/wrong-ast-2024.rs:$LINE:$COL
    |
@@ -22,7 +22,7 @@ error: aborting due to 1 previous error
 
 For more information about this error, try `rustc --explain E0758`.
 Couldn't compile the test.
----- $DIR/wrong-ast-2024.rs - two (line 13) stdout ----
+---- $DIR/wrong-ast-2024.rs - two (line 15) stdout ----
 error: unexpected closing delimiter: `}`
   --> $DIR/wrong-ast-2024.rs:$LINE:$COL
    |
@@ -34,8 +34,9 @@ error: aborting due to 1 previous error
 Couldn't compile the test.
 
 failures:
-    $DIR/wrong-ast-2024.rs - one (line 8)
-    $DIR/wrong-ast-2024.rs - two (line 13)
+    $DIR/wrong-ast-2024.rs - one (line 10)
+    $DIR/wrong-ast-2024.rs - two (line 15)
 
 test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
+all doctests ran in $TIME; merged doctests compilation took $TIME
diff --git a/tests/rustdoc/extern/extern-html-alias.rs b/tests/rustdoc/extern/extern-html-alias.rs
new file mode 100644
index 00000000000..3ff782d3963
--- /dev/null
+++ b/tests/rustdoc/extern/extern-html-alias.rs
@@ -0,0 +1,9 @@
+//@ compile-flags:-Z unstable-options --extern-html-root-url externs_name=https://renamed.example.com  --extern-html-root-url empty=https://bad.invalid
+//@ aux-crate:externs_name=empty.rs
+//@ edition: 2018
+
+extern crate externs_name as renamed;
+
+//@ has extern_html_alias/index.html
+//@ has - '//a/@href' 'https://renamed.example.com/empty/index.html'
+pub use renamed as yet_different_name;
diff --git a/tests/rustdoc/extern/extern-html-fallback.rs b/tests/rustdoc/extern/extern-html-fallback.rs
new file mode 100644
index 00000000000..ddac9bf713c
--- /dev/null
+++ b/tests/rustdoc/extern/extern-html-fallback.rs
@@ -0,0 +1,14 @@
+//@ compile-flags:-Z unstable-options --extern-html-root-url yet_another_name=https://bad.invalid --extern-html-root-url renamed_privately=https://bad.invalid --extern-html-root-url renamed_locally=https://bad.invalid --extern-html-root-url empty=https://localhost
+//@ aux-crate:externs_name=empty.rs
+//@ edition: 2018
+
+mod m {
+    pub extern crate externs_name as renamed_privately;
+}
+
+// renaming within the crate's source code is not supposed to affect CLI flags
+extern crate externs_name as renamed_locally;
+
+//@ has extern_html_fallback/index.html
+//@ has - '//a/@href' 'https://localhost/empty/index.html'
+pub use crate::renamed_locally as yet_another_name;
diff --git a/tests/ui/README.md b/tests/ui/README.md
index 86c9ad9c1ce..66c1bb905a7 100644
--- a/tests/ui/README.md
+++ b/tests/ui/README.md
@@ -654,10 +654,6 @@ Tests on range patterns where one of the bounds is not a direct value.
 
 Tests for the standard library collection [`std::collections::HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html).
 
-## `tests/ui/hello_world/`
-
-Tests that the basic hello-world program is not somehow broken.
-
 ## `tests/ui/higher-ranked/`
 
 Tests for higher-ranked trait bounds.
diff --git a/tests/ui/async-await/async-drop/async-drop-box-allocator.rs b/tests/ui/async-await/async-drop/async-drop-box-allocator.rs
new file mode 100644
index 00000000000..86ebf8a0ffd
--- /dev/null
+++ b/tests/ui/async-await/async-drop/async-drop-box-allocator.rs
@@ -0,0 +1,134 @@
+//@ run-pass
+//@ check-run-results
+// struct `Foo` has both sync and async drop.
+// It's used as the allocator of a `Box` which is conditionally moved out of.
+// Sync version is called in sync context, async version is called in async function.
+
+#![feature(async_drop, allocator_api)]
+#![allow(incomplete_features)]
+
+use std::mem::ManuallyDrop;
+
+//@ edition: 2021
+
+#[inline(never)]
+fn myprintln(msg: &str, my_resource_handle: usize) {
+    println!("{} : {}", msg, my_resource_handle);
+}
+
+use std::{
+    future::{Future, async_drop_in_place, AsyncDrop},
+    pin::{pin, Pin},
+    sync::{mpsc, Arc},
+    task::{Context, Poll, Wake, Waker},
+    alloc::{AllocError, Allocator, Global, Layout},
+    ptr::NonNull,
+};
+
+struct Foo {
+    my_resource_handle: usize,
+}
+
+impl Foo {
+    fn new(my_resource_handle: usize) -> Self {
+        let out = Foo {
+            my_resource_handle,
+        };
+        myprintln("Foo::new()", my_resource_handle);
+        out
+    }
+}
+
+impl Drop for Foo {
+    fn drop(&mut self) {
+        myprintln("Foo::drop()", self.my_resource_handle);
+    }
+}
+
+impl AsyncDrop for Foo {
+    async fn drop(self: Pin<&mut Self>) {
+        myprintln("Foo::async drop()", self.my_resource_handle);
+    }
+}
+
+unsafe impl Allocator for Foo {
+    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
+        Global.allocate(layout)
+    }
+    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
+        Global.deallocate(ptr, layout);
+    }
+}
+
+struct HasDrop;
+impl Drop for HasDrop {
+    fn drop(&mut self) {}
+}
+
+fn main() {
+    {
+        let b = Box::new_in(HasDrop, Foo::new(7));
+
+        if true {
+            let _x = *b;
+        } else {
+            let _y = b;
+        }
+    }
+    println!("Middle");
+    block_on(bar(10));
+    println!("Done")
+}
+
+async fn bar(ident_base: usize) {
+    let b = Box::new_in(HasDrop, Foo::new(ident_base));
+
+    if true {
+        let _x = *b;
+    } else {
+        let _y = b;
+    }
+}
+
+fn block_on<F>(fut_unpin: F) -> F::Output
+where
+    F: Future,
+{
+    let mut fut_pin = pin!(ManuallyDrop::new(fut_unpin));
+    let mut fut: Pin<&mut F> = unsafe {
+        Pin::map_unchecked_mut(fut_pin.as_mut(), |x| &mut **x)
+    };
+    let (waker, rx) = simple_waker();
+    let mut context = Context::from_waker(&waker);
+    let rv = loop {
+        match fut.as_mut().poll(&mut context) {
+            Poll::Ready(out) => break out,
+            // expect wake in polls
+            Poll::Pending => rx.try_recv().unwrap(),
+        }
+    };
+    let drop_fut_unpin = unsafe { async_drop_in_place(fut.get_unchecked_mut()) };
+    let mut drop_fut: Pin<&mut _> = pin!(drop_fut_unpin);
+    loop {
+        match drop_fut.as_mut().poll(&mut context) {
+            Poll::Ready(()) => break,
+            Poll::Pending => rx.try_recv().unwrap(),
+        }
+    }
+    rv
+}
+
+fn simple_waker() -> (Waker, mpsc::Receiver<()>) {
+    struct SimpleWaker {
+        tx: std::sync::mpsc::Sender<()>,
+    }
+
+    impl Wake for SimpleWaker {
+        fn wake(self: Arc<Self>) {
+            self.tx.send(()).unwrap();
+        }
+    }
+
+    let (tx, rx) = mpsc::channel();
+    (Waker::from(Arc::new(SimpleWaker { tx })), rx)
+}
diff --git a/tests/ui/async-await/async-drop/async-drop-box-allocator.run.stdout b/tests/ui/async-await/async-drop/async-drop-box-allocator.run.stdout
new file mode 100644
index 00000000000..cb7d0b0fea5
--- /dev/null
+++ b/tests/ui/async-await/async-drop/async-drop-box-allocator.run.stdout
@@ -0,0 +1,6 @@
+Foo::new() : 7
+Foo::drop() : 7
+Middle
+Foo::new() : 10
+Foo::async drop() : 10
+Done
diff --git a/tests/ui/async-await/async-drop/async-drop-box.rs b/tests/ui/async-await/async-drop/async-drop-box.rs
new file mode 100644
index 00000000000..0a6ed412863
--- /dev/null
+++ b/tests/ui/async-await/async-drop/async-drop-box.rs
@@ -0,0 +1,109 @@
+//@ run-pass
+//@ check-run-results
+// struct `Foo` has both sync and async drop.
+// `Foo` is always inside `Box`
+// Sync version is called in sync context, async version is called in async function.
+
+//@ known-bug: #143658
+// async version is never actually called
+
+#![feature(async_drop)]
+#![allow(incomplete_features)]
+
+use std::mem::ManuallyDrop;
+
+//@ edition: 2021
+
+#[inline(never)]
+fn myprintln(msg: &str, my_resource_handle: usize) {
+    println!("{} : {}", msg, my_resource_handle);
+}
+
+use std::{
+    future::{Future, async_drop_in_place, AsyncDrop},
+    pin::{pin, Pin},
+    sync::{mpsc, Arc},
+    task::{Context, Poll, Wake, Waker},
+};
+
+struct Foo {
+    my_resource_handle: usize,
+}
+
+impl Foo {
+    fn new(my_resource_handle: usize) -> Self {
+        let out = Foo {
+            my_resource_handle,
+        };
+        myprintln("Foo::new()", my_resource_handle);
+        out
+    }
+}
+
+impl Drop for Foo {
+    fn drop(&mut self) {
+        myprintln("Foo::drop()", self.my_resource_handle);
+    }
+}
+
+impl AsyncDrop for Foo {
+    async fn drop(self: Pin<&mut Self>) {
+        myprintln("Foo::async drop()", self.my_resource_handle);
+    }
+}
+
+fn main() {
+    {
+        let _ = Box::new(Foo::new(7));
+    }
+    println!("Middle");
+    block_on(bar(10));
+    println!("Done")
+}
+
+async fn bar(ident_base: usize) {
+    let _first = Box::new(Foo::new(ident_base));
+}
+
+fn block_on<F>(fut_unpin: F) -> F::Output
+where
+    F: Future,
+{
+    let mut fut_pin = pin!(ManuallyDrop::new(fut_unpin));
+    let mut fut: Pin<&mut F> = unsafe {
+        Pin::map_unchecked_mut(fut_pin.as_mut(), |x| &mut **x)
+    };
+    let (waker, rx) = simple_waker();
+    let mut context = Context::from_waker(&waker);
+    let rv = loop {
+        match fut.as_mut().poll(&mut context) {
+            Poll::Ready(out) => break out,
+            // expect wake in polls
+            Poll::Pending => rx.try_recv().unwrap(),
+        }
+    };
+    let drop_fut_unpin = unsafe { async_drop_in_place(fut.get_unchecked_mut()) };
+    let mut drop_fut: Pin<&mut _> = pin!(drop_fut_unpin);
+    loop {
+        match drop_fut.as_mut().poll(&mut context) {
+            Poll::Ready(()) => break,
+            Poll::Pending => rx.try_recv().unwrap(),
+        }
+    }
+    rv
+}
+
+fn simple_waker() -> (Waker, mpsc::Receiver<()>) {
+    struct SimpleWaker {
+        tx: std::sync::mpsc::Sender<()>,
+    }
+
+    impl Wake for SimpleWaker {
+        fn wake(self: Arc<Self>) {
+            self.tx.send(()).unwrap();
+        }
+    }
+
+    let (tx, rx) = mpsc::channel();
+    (Waker::from(Arc::new(SimpleWaker { tx })), rx)
+}
diff --git a/tests/ui/async-await/async-drop/async-drop-box.run.stdout b/tests/ui/async-await/async-drop/async-drop-box.run.stdout
new file mode 100644
index 00000000000..a2dab2ba992
--- /dev/null
+++ b/tests/ui/async-await/async-drop/async-drop-box.run.stdout
@@ -0,0 +1,6 @@
+Foo::new() : 7
+Foo::drop() : 7
+Middle
+Foo::new() : 10
+Foo::drop() : 10
+Done
diff --git a/tests/ui/borrowck/liberated-region-from-outer-closure.rs b/tests/ui/borrowck/liberated-region-from-outer-closure.rs
new file mode 100644
index 00000000000..dcc6370b4a1
--- /dev/null
+++ b/tests/ui/borrowck/liberated-region-from-outer-closure.rs
@@ -0,0 +1,12 @@
+// Regression test for <https://github.com/rust-lang/rust/issues/144608>.
+
+fn example<T: Copy>(x: T) -> impl FnMut(&mut ()) {
+    move |_: &mut ()| {
+        move || needs_static_lifetime(x);
+        //~^ ERROR the parameter type `T` may not live long enough
+    }
+}
+
+fn needs_static_lifetime<T: 'static>(obj: T) {}
+
+fn main() {}
diff --git a/tests/ui/borrowck/liberated-region-from-outer-closure.stderr b/tests/ui/borrowck/liberated-region-from-outer-closure.stderr
new file mode 100644
index 00000000000..98b45ac499d
--- /dev/null
+++ b/tests/ui/borrowck/liberated-region-from-outer-closure.stderr
@@ -0,0 +1,17 @@
+error[E0310]: the parameter type `T` may not live long enough
+  --> $DIR/liberated-region-from-outer-closure.rs:5:17
+   |
+LL |         move || needs_static_lifetime(x);
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^
+   |                 |
+   |                 the parameter type `T` must be valid for the static lifetime...
+   |                 ...so that the type `T` will meet its required lifetime bounds
+   |
+help: consider adding an explicit lifetime bound
+   |
+LL | fn example<T: Copy + 'static>(x: T) -> impl FnMut(&mut ()) {
+   |                    +++++++++
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0310`.
diff --git a/tests/ui/drop/box-conditional-drop-allocator.rs b/tests/ui/drop/box-conditional-drop-allocator.rs
new file mode 100644
index 00000000000..8f78da16473
--- /dev/null
+++ b/tests/ui/drop/box-conditional-drop-allocator.rs
@@ -0,0 +1,43 @@
+//@ run-pass
+#![feature(allocator_api)]
+
+// Regression test for #131082.
+// Testing that the allocator of a Box is dropped in conditional drops
+
+use std::alloc::{AllocError, Allocator, Global, Layout};
+use std::cell::Cell;
+use std::ptr::NonNull;
+
+struct DropCheckingAllocator<'a>(&'a Cell<bool>);
+
+unsafe impl Allocator for DropCheckingAllocator<'_> {
+    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
+        Global.allocate(layout)
+    }
+    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
+        Global.deallocate(ptr, layout);
+    }
+}
+impl Drop for DropCheckingAllocator<'_> {
+    fn drop(&mut self) {
+        self.0.set(true);
+    }
+}
+
+struct HasDrop;
+impl Drop for HasDrop {
+    fn drop(&mut self) {}
+}
+
+fn main() {
+    let dropped = Cell::new(false);
+    {
+        let b = Box::new_in(HasDrop, DropCheckingAllocator(&dropped));
+        if true {
+            drop(*b);
+        } else {
+            drop(b);
+        }
+    }
+    assert!(dropped.get());
+}
diff --git a/tests/ui/enum-discriminant/wrapping_niche.rs b/tests/ui/enum-discriminant/wrapping_niche.rs
new file mode 100644
index 00000000000..8097414be68
--- /dev/null
+++ b/tests/ui/enum-discriminant/wrapping_niche.rs
@@ -0,0 +1,24 @@
+//! Test that we produce the same niche range no
+//! matter of signendess if the discriminants are the same.
+
+#![feature(rustc_attrs)]
+
+#[repr(u16)]
+#[rustc_layout(debug)]
+enum UnsignedAroundZero {
+    //~^ ERROR: layout_of
+    A = 65535,
+    B = 0,
+    C = 1,
+}
+
+#[repr(i16)]
+#[rustc_layout(debug)]
+enum SignedAroundZero {
+    //~^ ERROR: layout_of
+    A = -1,
+    B = 0,
+    C = 1,
+}
+
+fn main() {}
diff --git a/tests/ui/enum-discriminant/wrapping_niche.stderr b/tests/ui/enum-discriminant/wrapping_niche.stderr
new file mode 100644
index 00000000000..e3e1755e14d
--- /dev/null
+++ b/tests/ui/enum-discriminant/wrapping_niche.stderr
@@ -0,0 +1,238 @@
+error: layout_of(UnsignedAroundZero) = Layout {
+           size: Size(2 bytes),
+           align: AbiAlign {
+               abi: Align(2 bytes),
+           },
+           backend_repr: Scalar(
+               Initialized {
+                   value: Int(
+                       I16,
+                       false,
+                   ),
+                   valid_range: (..=1) | (65535..),
+               },
+           ),
+           fields: Arbitrary {
+               offsets: [
+                   Size(0 bytes),
+               ],
+               memory_index: [
+                   0,
+               ],
+           },
+           largest_niche: Some(
+               Niche {
+                   offset: Size(0 bytes),
+                   value: Int(
+                       I16,
+                       false,
+                   ),
+                   valid_range: (..=1) | (65535..),
+               },
+           ),
+           uninhabited: false,
+           variants: Multiple {
+               tag: Initialized {
+                   value: Int(
+                       I16,
+                       false,
+                   ),
+                   valid_range: (..=1) | (65535..),
+               },
+               tag_encoding: Direct,
+               tag_field: 0,
+               variants: [
+                   Layout {
+                       size: Size(2 bytes),
+                       align: AbiAlign {
+                           abi: Align(2 bytes),
+                       },
+                       backend_repr: Memory {
+                           sized: true,
+                       },
+                       fields: Arbitrary {
+                           offsets: [],
+                           memory_index: [],
+                       },
+                       largest_niche: None,
+                       uninhabited: false,
+                       variants: Single {
+                           index: 0,
+                       },
+                       max_repr_align: None,
+                       unadjusted_abi_align: Align(2 bytes),
+                       randomization_seed: 9885373149222004003,
+                   },
+                   Layout {
+                       size: Size(2 bytes),
+                       align: AbiAlign {
+                           abi: Align(2 bytes),
+                       },
+                       backend_repr: Memory {
+                           sized: true,
+                       },
+                       fields: Arbitrary {
+                           offsets: [],
+                           memory_index: [],
+                       },
+                       largest_niche: None,
+                       uninhabited: false,
+                       variants: Single {
+                           index: 1,
+                       },
+                       max_repr_align: None,
+                       unadjusted_abi_align: Align(2 bytes),
+                       randomization_seed: 9885373149222004003,
+                   },
+                   Layout {
+                       size: Size(2 bytes),
+                       align: AbiAlign {
+                           abi: Align(2 bytes),
+                       },
+                       backend_repr: Memory {
+                           sized: true,
+                       },
+                       fields: Arbitrary {
+                           offsets: [],
+                           memory_index: [],
+                       },
+                       largest_niche: None,
+                       uninhabited: false,
+                       variants: Single {
+                           index: 2,
+                       },
+                       max_repr_align: None,
+                       unadjusted_abi_align: Align(2 bytes),
+                       randomization_seed: 9885373149222004003,
+                   },
+               ],
+           },
+           max_repr_align: None,
+           unadjusted_abi_align: Align(2 bytes),
+           randomization_seed: 2648004449468912780,
+       }
+  --> $DIR/wrapping_niche.rs:8:1
+   |
+LL | enum UnsignedAroundZero {
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: layout_of(SignedAroundZero) = Layout {
+           size: Size(2 bytes),
+           align: AbiAlign {
+               abi: Align(2 bytes),
+           },
+           backend_repr: Scalar(
+               Initialized {
+                   value: Int(
+                       I16,
+                       true,
+                   ),
+                   valid_range: (..=1) | (65535..),
+               },
+           ),
+           fields: Arbitrary {
+               offsets: [
+                   Size(0 bytes),
+               ],
+               memory_index: [
+                   0,
+               ],
+           },
+           largest_niche: Some(
+               Niche {
+                   offset: Size(0 bytes),
+                   value: Int(
+                       I16,
+                       true,
+                   ),
+                   valid_range: (..=1) | (65535..),
+               },
+           ),
+           uninhabited: false,
+           variants: Multiple {
+               tag: Initialized {
+                   value: Int(
+                       I16,
+                       true,
+                   ),
+                   valid_range: (..=1) | (65535..),
+               },
+               tag_encoding: Direct,
+               tag_field: 0,
+               variants: [
+                   Layout {
+                       size: Size(2 bytes),
+                       align: AbiAlign {
+                           abi: Align(2 bytes),
+                       },
+                       backend_repr: Memory {
+                           sized: true,
+                       },
+                       fields: Arbitrary {
+                           offsets: [],
+                           memory_index: [],
+                       },
+                       largest_niche: None,
+                       uninhabited: false,
+                       variants: Single {
+                           index: 0,
+                       },
+                       max_repr_align: None,
+                       unadjusted_abi_align: Align(2 bytes),
+                       randomization_seed: 2684536712112553499,
+                   },
+                   Layout {
+                       size: Size(2 bytes),
+                       align: AbiAlign {
+                           abi: Align(2 bytes),
+                       },
+                       backend_repr: Memory {
+                           sized: true,
+                       },
+                       fields: Arbitrary {
+                           offsets: [],
+                           memory_index: [],
+                       },
+                       largest_niche: None,
+                       uninhabited: false,
+                       variants: Single {
+                           index: 1,
+                       },
+                       max_repr_align: None,
+                       unadjusted_abi_align: Align(2 bytes),
+                       randomization_seed: 2684536712112553499,
+                   },
+                   Layout {
+                       size: Size(2 bytes),
+                       align: AbiAlign {
+                           abi: Align(2 bytes),
+                       },
+                       backend_repr: Memory {
+                           sized: true,
+                       },
+                       fields: Arbitrary {
+                           offsets: [],
+                           memory_index: [],
+                       },
+                       largest_niche: None,
+                       uninhabited: false,
+                       variants: Single {
+                           index: 2,
+                       },
+                       max_repr_align: None,
+                       unadjusted_abi_align: Align(2 bytes),
+                       randomization_seed: 2684536712112553499,
+                   },
+               ],
+           },
+           max_repr_align: None,
+           unadjusted_abi_align: Align(2 bytes),
+           randomization_seed: 10738146848450213996,
+       }
+  --> $DIR/wrapping_niche.rs:17:1
+   |
+LL | enum SignedAroundZero {
+   | ^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/errors/remap-path-prefix-sysroot.rs b/tests/ui/errors/remap-path-prefix-sysroot.rs
index 5e2e4fab51d..f4a2766ff4c 100644
--- a/tests/ui/errors/remap-path-prefix-sysroot.rs
+++ b/tests/ui/errors/remap-path-prefix-sysroot.rs
@@ -2,7 +2,7 @@
 //@ compile-flags: -g -Ztranslate-remapped-path-to-local-path=yes
 //@ [with-remap]compile-flags: --remap-path-prefix={{rust-src-base}}=remapped
 //@ [with-remap]compile-flags: --remap-path-prefix={{src-base}}=remapped-tests-ui
-//@ [without-remap]compile-flags:
+// [without-remap] no extra compile-flags
 
 // The $SRC_DIR*.rs:LL:COL normalisation doesn't kick in automatically
 // as the remapped revision will not begin with $SRC_DIR_REAL,
diff --git a/tests/ui/errors/wrong-target-spec.rs b/tests/ui/errors/wrong-target-spec.rs
index a3a0e05d826..1a976888112 100644
--- a/tests/ui/errors/wrong-target-spec.rs
+++ b/tests/ui/errors/wrong-target-spec.rs
@@ -2,6 +2,7 @@
 // checks that such invalid target specs are rejected by the compiler.
 // See https://github.com/rust-lang/rust/issues/33329
 
+// ignore-tidy-target-specific-tests
 //@ needs-llvm-components: x86
 //@ compile-flags: --target x86_64_unknown-linux-musl
 
diff --git a/tests/ui/explicit-tail-calls/recursion-etc.rs b/tests/ui/explicit-tail-calls/recursion-etc.rs
new file mode 100644
index 00000000000..8c89ceb7869
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/recursion-etc.rs
@@ -0,0 +1,17 @@
+//@ run-pass
+#![expect(incomplete_features)]
+#![feature(explicit_tail_calls)]
+
+use std::hint::black_box;
+
+pub fn count(curr: u64, top: u64) -> u64 {
+   if black_box(curr) >= top {
+        curr
+   } else {
+        become count(curr + 1, top)
+   }
+}
+
+fn main() {
+    println!("{}", count(0, black_box(1000000)));
+}
diff --git a/tests/ui/linkage-attr/unstable-flavor.rs b/tests/ui/linkage-attr/unstable-flavor.rs
index 6aa9efb58d1..5412e248f34 100644
--- a/tests/ui/linkage-attr/unstable-flavor.rs
+++ b/tests/ui/linkage-attr/unstable-flavor.rs
@@ -4,9 +4,9 @@
 //
 //@ revisions: bpf ptx
 //@ [bpf] compile-flags: --target=bpfel-unknown-none -C linker-flavor=bpf --crate-type=rlib
-//@ [bpf] needs-llvm-components:
+//@ [bpf] needs-llvm-components: bpf
 //@ [ptx] compile-flags: --target=nvptx64-nvidia-cuda -C linker-flavor=ptx --crate-type=rlib
-//@ [ptx] needs-llvm-components:
+//@ [ptx] needs-llvm-components: nvptx
 
 #![feature(no_core)]
 #![no_core]
diff --git a/tests/ui/nll/closure-requirements/escape-argument-callee.stderr b/tests/ui/nll/closure-requirements/escape-argument-callee.stderr
index a445534c8d8..2742162c821 100644
--- a/tests/ui/nll/closure-requirements/escape-argument-callee.stderr
+++ b/tests/ui/nll/closure-requirements/escape-argument-callee.stderr
@@ -9,6 +9,9 @@ LL |         let mut closure = expect_sig(|p, y| *p = y);
                for<Region(BrAnon), Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 mut &'^1 i32, &'^2 i32)),
                (),
            ]
+   = note: late-bound region is '?1
+   = note: late-bound region is '?2
+   = note: late-bound region is '?3
 
 error: lifetime may not live long enough
   --> $DIR/escape-argument-callee.rs:26:45
diff --git a/tests/ui/nll/closure-requirements/escape-argument.stderr b/tests/ui/nll/closure-requirements/escape-argument.stderr
index 7fd1cd8c3e4..22cb0367ad8 100644
--- a/tests/ui/nll/closure-requirements/escape-argument.stderr
+++ b/tests/ui/nll/closure-requirements/escape-argument.stderr
@@ -9,6 +9,8 @@ LL |         let mut closure = expect_sig(|p, y| *p = y);
                for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 mut &'^1 i32, &'^1 i32)),
                (),
            ]
+   = note: late-bound region is '?1
+   = note: late-bound region is '?2
 
 note: no external requirements
   --> $DIR/escape-argument.rs:20:1
diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-fail-no-postdom.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-fail-no-postdom.stderr
index 60087ec992b..134ce99014d 100644
--- a/tests/ui/nll/closure-requirements/propagate-approximated-fail-no-postdom.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-approximated-fail-no-postdom.stderr
@@ -9,6 +9,8 @@ LL |         |_outlives1, _outlives2, _outlives3, x, y| {
                for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((std::cell::Cell<&'?1 &'^0 u32>, std::cell::Cell<&'?2 &'^0 u32>, std::cell::Cell<&'^1 &'?3 u32>, std::cell::Cell<&'^0 u32>, std::cell::Cell<&'^1 u32>)),
                (),
            ]
+   = note: late-bound region is '?7
+   = note: late-bound region is '?8
    = note: late-bound region is '?4
    = note: late-bound region is '?5
    = note: late-bound region is '?6
diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr
index 7325a9de8b2..f5527eeb2cd 100644
--- a/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-approximated-ref.stderr
@@ -9,6 +9,12 @@ LL |     establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y
                for<Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 std::cell::Cell<&'?1 &'^1 u32>, &'^2 std::cell::Cell<&'^3 &'?2 u32>, &'^4 std::cell::Cell<&'^1 u32>, &'^5 std::cell::Cell<&'^3 u32>)),
                (),
            ]
+   = note: late-bound region is '?5
+   = note: late-bound region is '?6
+   = note: late-bound region is '?7
+   = note: late-bound region is '?8
+   = note: late-bound region is '?9
+   = note: late-bound region is '?10
    = note: late-bound region is '?3
    = note: late-bound region is '?4
    = note: number of external vids: 5
diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-comparing-against-free.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-comparing-against-free.stderr
index 621c1ea083b..e13653f3423 100644
--- a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-comparing-against-free.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-comparing-against-free.stderr
@@ -9,6 +9,7 @@ LL |     foo(cell, |cell_a, cell_x| {
                for<Region(BrAnon)> extern "rust-call" fn((std::cell::Cell<&'?1 u32>, std::cell::Cell<&'^0 u32>)),
                (),
            ]
+   = note: late-bound region is '?2
 
 error[E0521]: borrowed data escapes outside of closure
   --> $DIR/propagate-approximated-shorter-to-static-comparing-against-free.rs:22:9
@@ -43,6 +44,7 @@ LL |     foo(cell, |cell_a, cell_x| {
                for<Region(BrAnon)> extern "rust-call" fn((std::cell::Cell<&'?1 u32>, std::cell::Cell<&'^0 u32>)),
                (),
            ]
+   = note: late-bound region is '?2
    = note: number of external vids: 2
    = note: where '?1: '?0
 
diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
index b9365c94a1b..9e9eae98597 100644
--- a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
@@ -9,6 +9,11 @@ LL |     establish_relationships(&cell_a, &cell_b, |_outlives, x, y| {
                for<Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 std::cell::Cell<&'?1 &'^1 u32>, &'^2 std::cell::Cell<&'^1 u32>, &'^3 std::cell::Cell<&'^4 u32>)),
                (),
            ]
+   = note: late-bound region is '?4
+   = note: late-bound region is '?5
+   = note: late-bound region is '?6
+   = note: late-bound region is '?7
+   = note: late-bound region is '?8
    = note: late-bound region is '?2
    = note: late-bound region is '?3
    = note: number of external vids: 4
diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
index e5d2867103c..303fcd4cdfc 100644
--- a/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
@@ -9,6 +9,12 @@ LL |     establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y
                for<Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 std::cell::Cell<&'?1 &'^1 u32>, &'^2 std::cell::Cell<&'?2 &'^3 u32>, &'^4 std::cell::Cell<&'^1 u32>, &'^5 std::cell::Cell<&'^3 u32>)),
                (),
            ]
+   = note: late-bound region is '?5
+   = note: late-bound region is '?6
+   = note: late-bound region is '?7
+   = note: late-bound region is '?8
+   = note: late-bound region is '?9
+   = note: late-bound region is '?10
    = note: late-bound region is '?3
    = note: late-bound region is '?4
    = note: number of external vids: 5
diff --git a/tests/ui/nll/closure-requirements/propagate-approximated-val.stderr b/tests/ui/nll/closure-requirements/propagate-approximated-val.stderr
index a14bfb06e83..aa75b4c811c 100644
--- a/tests/ui/nll/closure-requirements/propagate-approximated-val.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-approximated-val.stderr
@@ -9,6 +9,8 @@ LL |     establish_relationships(cell_a, cell_b, |outlives1, outlives2, x, y| {
                for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((std::cell::Cell<&'?1 &'^0 u32>, std::cell::Cell<&'^1 &'?2 u32>, std::cell::Cell<&'^0 u32>, std::cell::Cell<&'^1 u32>)),
                (),
            ]
+   = note: late-bound region is '?5
+   = note: late-bound region is '?6
    = note: late-bound region is '?3
    = note: late-bound region is '?4
    = note: number of external vids: 5
diff --git a/tests/ui/nll/closure-requirements/propagate-despite-same-free-region.stderr b/tests/ui/nll/closure-requirements/propagate-despite-same-free-region.stderr
index 49c65d77ddd..30ee259d3dc 100644
--- a/tests/ui/nll/closure-requirements/propagate-despite-same-free-region.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-despite-same-free-region.stderr
@@ -9,6 +9,8 @@ LL |         |_outlives1, _outlives2, x, y| {
                for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((std::cell::Cell<&'?1 &'^0 u32>, std::cell::Cell<&'^1 &'?2 u32>, std::cell::Cell<&'^0 u32>, std::cell::Cell<&'^1 u32>)),
                (),
            ]
+   = note: late-bound region is '?4
+   = note: late-bound region is '?5
    = note: late-bound region is '?3
    = note: number of external vids: 4
    = note: where '?1: '?2
diff --git a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr
index f48ed2823dd..6b04e346c69 100644
--- a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-no-bounds.stderr
@@ -9,6 +9,11 @@ LL |     establish_relationships(&cell_a, &cell_b, |_outlives, x, y| {
                for<Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 std::cell::Cell<&'^1 &'?1 u32>, &'^2 std::cell::Cell<&'^3 u32>, &'^4 std::cell::Cell<&'^1 u32>)),
                (),
            ]
+   = note: late-bound region is '?4
+   = note: late-bound region is '?5
+   = note: late-bound region is '?6
+   = note: late-bound region is '?7
+   = note: late-bound region is '?8
    = note: late-bound region is '?2
    = note: late-bound region is '?3
 
diff --git a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr
index a090e94593f..ae2129c65f2 100644
--- a/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr
+++ b/tests/ui/nll/closure-requirements/propagate-fail-to-approximate-longer-wrong-bounds.stderr
@@ -9,6 +9,12 @@ LL |     establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y
                for<Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 std::cell::Cell<&'^1 &'?1 u32>, &'^2 std::cell::Cell<&'^3 &'?2 u32>, &'^4 std::cell::Cell<&'^1 u32>, &'^5 std::cell::Cell<&'^3 u32>)),
                (),
            ]
+   = note: late-bound region is '?5
+   = note: late-bound region is '?6
+   = note: late-bound region is '?7
+   = note: late-bound region is '?8
+   = note: late-bound region is '?9
+   = note: late-bound region is '?10
    = note: late-bound region is '?3
    = note: late-bound region is '?4
 
diff --git a/tests/ui/nll/closure-requirements/return-wrong-bound-region.stderr b/tests/ui/nll/closure-requirements/return-wrong-bound-region.stderr
index bc5c04a27a3..1f1cce1e885 100644
--- a/tests/ui/nll/closure-requirements/return-wrong-bound-region.stderr
+++ b/tests/ui/nll/closure-requirements/return-wrong-bound-region.stderr
@@ -9,6 +9,8 @@ LL |     expect_sig(|a, b| b); // ought to return `a`
                for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((&'^0 i32, &'^1 i32)) -> &'^0 i32,
                (),
            ]
+   = note: late-bound region is '?1
+   = note: late-bound region is '?2
 
 error: lifetime may not live long enough
   --> $DIR/return-wrong-bound-region.rs:11:23
diff --git a/tests/ui/nll/ty-outlives/ty-param-closure-approximate-lower-bound.stderr b/tests/ui/nll/ty-outlives/ty-param-closure-approximate-lower-bound.stderr
index e58764354c0..396e149554c 100644
--- a/tests/ui/nll/ty-outlives/ty-param-closure-approximate-lower-bound.stderr
+++ b/tests/ui/nll/ty-outlives/ty-param-closure-approximate-lower-bound.stderr
@@ -9,6 +9,8 @@ LL |     twice(cell, value, |a, b| invoke(a, b));
                for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((std::option::Option<std::cell::Cell<&'?1 &'^0 ()>>, &'^1 T)),
                (),
            ]
+   = note: late-bound region is '?2
+   = note: late-bound region is '?3
    = note: number of external vids: 2
    = note: where T: '?1
 
@@ -31,6 +33,8 @@ LL |     twice(cell, value, |a, b| invoke(a, b));
                for<Region(BrAnon), Region(BrAnon)> extern "rust-call" fn((std::option::Option<std::cell::Cell<&'?1 &'^0 ()>>, &'^1 T)),
                (),
            ]
+   = note: late-bound region is '?3
+   = note: late-bound region is '?4
    = note: late-bound region is '?2
    = note: number of external vids: 3
    = note: where T: '?1
diff --git a/tests/ui/target_modifiers/defaults_check.rs b/tests/ui/target_modifiers/defaults_check.rs
index de72acd32bc..ce1d534fd75 100644
--- a/tests/ui/target_modifiers/defaults_check.rs
+++ b/tests/ui/target_modifiers/defaults_check.rs
@@ -6,7 +6,7 @@
 //@ needs-llvm-components: x86
 
 //@ revisions: ok ok_explicit error
-//@[ok] compile-flags:
+// [ok] no extra compile-flags
 //@[ok_explicit] compile-flags: -Zreg-struct-return=false
 //@[error] compile-flags: -Zreg-struct-return=true
 //@[ok] check-pass
diff --git a/tests/ui/target_modifiers/incompatible_fixedx18.rs b/tests/ui/target_modifiers/incompatible_fixedx18.rs
index 6c13984f608..5ba676406ee 100644
--- a/tests/ui/target_modifiers/incompatible_fixedx18.rs
+++ b/tests/ui/target_modifiers/incompatible_fixedx18.rs
@@ -5,7 +5,7 @@
 //@ revisions:allow_match allow_mismatch error_generated
 //@[allow_match] compile-flags: -Zfixed-x18
 //@[allow_mismatch] compile-flags: -Cunsafe-allow-abi-mismatch=fixed-x18
-//@[error_generated] compile-flags:
+// [error_generated] no extra compile-flags
 //@[allow_mismatch] check-pass
 //@[allow_match] check-pass
 
diff --git a/tests/ui/target_modifiers/incompatible_regparm.rs b/tests/ui/target_modifiers/incompatible_regparm.rs
index 395c26fc2c0..e76bfc976a1 100644
--- a/tests/ui/target_modifiers/incompatible_regparm.rs
+++ b/tests/ui/target_modifiers/incompatible_regparm.rs
@@ -5,7 +5,7 @@
 //@ revisions:allow_regparm_mismatch allow_no_value error_generated
 //@[allow_regparm_mismatch] compile-flags: -Cunsafe-allow-abi-mismatch=regparm
 //@[allow_no_value] compile-flags: -Cunsafe-allow-abi-mismatch
-//@[error_generated] compile-flags:
+// [error_generated] no extra compile-flags
 //@[allow_regparm_mismatch] check-pass
 
 #![feature(no_core)]
diff --git a/tests/ui/target_modifiers/no_value_bool.rs b/tests/ui/target_modifiers/no_value_bool.rs
index ceba40afa89..72130f8737c 100644
--- a/tests/ui/target_modifiers/no_value_bool.rs
+++ b/tests/ui/target_modifiers/no_value_bool.rs
@@ -8,7 +8,7 @@
 //@ revisions: ok ok_explicit error error_explicit
 //@[ok] compile-flags: -Zreg-struct-return
 //@[ok_explicit] compile-flags: -Zreg-struct-return=true
-//@[error] compile-flags:
+// [error] no extra compile-flags
 //@[error_explicit] compile-flags: -Zreg-struct-return=false
 //@[ok] check-pass
 //@[ok_explicit] check-pass
diff --git a/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.next.stderr b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.next.stderr
new file mode 100644
index 00000000000..141a07b4be7
--- /dev/null
+++ b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.next.stderr
@@ -0,0 +1,19 @@
+error[E0283]: type annotations needed: cannot satisfy `dyn D<&(), &()>: B<&()>`
+  --> $DIR/ambiguity-due-to-uniquification-1.rs:15:31
+   |
+LL |     (&() as &dyn D<&(), &()>).f()
+   |                               ^
+   |
+   = note: cannot satisfy `dyn D<&(), &()>: B<&()>`
+   = help: the trait `B<C>` is implemented for `()`
+note: required by a bound in `D::f`
+  --> $DIR/ambiguity-due-to-uniquification-1.rs:10:16
+   |
+LL | trait D<C, E>: B<C> + B<E> {
+   |                ^^^^ required by this bound in `D::f`
+LL |     fn f(&self) {}
+   |        - required by a bound in this associated function
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0283`.
diff --git a/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.rs b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.rs
new file mode 100644
index 00000000000..cfdf74046fb
--- /dev/null
+++ b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-1.rs
@@ -0,0 +1,17 @@
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[current] check-pass
+
+// Regression test for #139409 and trait-system-refactor-initiative#27.
+
+trait B<C> {}
+impl<C> B<C> for () {}
+trait D<C, E>: B<C> + B<E> {
+    fn f(&self) {}
+}
+impl<C, E> D<C, E> for () {}
+fn main() {
+    (&() as &dyn D<&(), &()>).f()
+    //[next]~^ ERROR type annotations needed: cannot satisfy `dyn D<&(), &()>: B<&()>`
+}
diff --git a/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.next.stderr b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.next.stderr
new file mode 100644
index 00000000000..3b478889996
--- /dev/null
+++ b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.next.stderr
@@ -0,0 +1,17 @@
+error[E0283]: type annotations needed: cannot satisfy `impl Trait<'x> + Trait<'y>: Trait<'y>`
+  --> $DIR/ambiguity-due-to-uniquification-2.rs:16:23
+   |
+LL |     impls_trait::<'y, _>(foo::<'x, 'y>());
+   |                       ^
+   |
+   = note: cannot satisfy `impl Trait<'x> + Trait<'y>: Trait<'y>`
+   = help: the trait `Trait<'t>` is implemented for `()`
+note: required by a bound in `impls_trait`
+  --> $DIR/ambiguity-due-to-uniquification-2.rs:13:23
+   |
+LL | fn impls_trait<'x, T: Trait<'x>>(_: T) {}
+   |                       ^^^^^^^^^ required by this bound in `impls_trait`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0283`.
diff --git a/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.rs b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.rs
new file mode 100644
index 00000000000..2a9a8b80cc0
--- /dev/null
+++ b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-2.rs
@@ -0,0 +1,20 @@
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[current] check-pass
+
+// Regression test from trait-system-refactor-initiative#27.
+
+trait Trait<'t> {}
+impl<'t> Trait<'t> for () {}
+
+fn foo<'x, 'y>() -> impl Trait<'x> + Trait<'y> {}
+
+fn impls_trait<'x, T: Trait<'x>>(_: T) {}
+
+fn bar<'x, 'y>() {
+    impls_trait::<'y, _>(foo::<'x, 'y>());
+    //[next]~^ ERROR type annotations needed: cannot satisfy `impl Trait<'x> + Trait<'y>: Trait<'y>`
+}
+
+fn main() {}
diff --git a/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.next.stderr b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.next.stderr
new file mode 100644
index 00000000000..e25f892b365
--- /dev/null
+++ b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.next.stderr
@@ -0,0 +1,19 @@
+error[E0283]: type annotations needed: cannot satisfy `(dyn Object<&(), &()> + 'static): Trait<&()>`
+  --> $DIR/ambiguity-due-to-uniquification-3.rs:28:17
+   |
+LL |     impls_trait(obj, t);
+   |     ----------- ^^^
+   |     |
+   |     required by a bound introduced by this call
+   |
+   = note: cannot satisfy `(dyn Object<&(), &()> + 'static): Trait<&()>`
+   = help: the trait `Trait<T>` is implemented for `()`
+note: required by a bound in `impls_trait`
+  --> $DIR/ambiguity-due-to-uniquification-3.rs:24:19
+   |
+LL | fn impls_trait<T: Trait<U>, U>(_: Inv<T>, _: Inv<U>) {}
+   |                   ^^^^^^^^ required by this bound in `impls_trait`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0283`.
diff --git a/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.rs b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.rs
new file mode 100644
index 00000000000..6dcd9d5bdf4
--- /dev/null
+++ b/tests/ui/traits/next-solver/assembly/ambiguity-due-to-uniquification-3.rs
@@ -0,0 +1,33 @@
+//@ revisions: current next
+//@[next] compile-flags: -Znext-solver
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[current] check-pass
+
+// Regression test from trait-system-refactor-initiative#27.
+//
+// Unlike in the previous two tests, `dyn Object<?x, ?y>: Trait<?x>` relies
+// on structural identity of type inference variables. This inference variable
+// gets constrained to a type containing a region later on. To prevent this
+// from causing an ICE during MIR borrowck, we stash goals which depend on
+// inference variables and then reprove them at the end of HIR typeck.
+
+#![feature(rustc_attrs)]
+#![rustc_no_implicit_bounds]
+trait Trait<T> {}
+impl<T> Trait<T> for () {}
+
+trait Object<T, U>: Trait<T> + Trait<U> {}
+
+#[derive(Clone, Copy)]
+struct Inv<T>(*mut T);
+fn foo<T: Sized, U: Sized>() -> (Inv<dyn Object<T, U>>, Inv<T>) { todo!() }
+fn impls_trait<T: Trait<U>, U>(_: Inv<T>, _: Inv<U>) {}
+
+fn bar() {
+    let (obj, t) = foo();
+    impls_trait(obj, t);
+    //[next]~^ ERROR type annotations needed
+    let _: Inv<dyn Object<&(), &()>> = obj;
+}
+
+fn main() {}
diff --git a/tests/ui/transmutability/enums/niche_optimization.rs b/tests/ui/transmutability/enums/niche_optimization.rs
index 2436be50027..316a857662a 100644
--- a/tests/ui/transmutability/enums/niche_optimization.rs
+++ b/tests/ui/transmutability/enums/niche_optimization.rs
@@ -75,8 +75,8 @@ fn one_niche() {
 
     assert::is_transmutable::<OptionLike, u8>();
     assert::is_transmutable::<V0, OptionLike>();
+    assert::is_transmutable::<V1, OptionLike>();
     assert::is_transmutable::<V254, OptionLike>();
-    assert::is_transmutable::<V255, OptionLike>();
 }
 
 fn one_niche_alt() {
@@ -97,9 +97,9 @@ fn one_niche_alt() {
     };
 
     assert::is_transmutable::<OptionLike, u8>();
-    assert::is_transmutable::<V0, OptionLike>();
+    assert::is_transmutable::<V1, OptionLike>();
+    assert::is_transmutable::<V2, OptionLike>();
     assert::is_transmutable::<V254, OptionLike>();
-    assert::is_transmutable::<V255, OptionLike>();
 }
 
 fn two_niche() {
@@ -121,9 +121,9 @@ fn two_niche() {
 
     assert::is_transmutable::<OptionLike, u8>();
     assert::is_transmutable::<V0, OptionLike>();
+    assert::is_transmutable::<V1, OptionLike>();
+    assert::is_transmutable::<V2, OptionLike>();
     assert::is_transmutable::<V253, OptionLike>();
-    assert::is_transmutable::<V254, OptionLike>();
-    assert::is_transmutable::<V255, OptionLike>();
 }
 
 fn no_niche() {
@@ -142,7 +142,7 @@ fn no_niche() {
     }
 
     const _: () = {
-        assert!(std::mem::size_of::<OptionLike>() == 2);
+        assert!(std::mem::size_of::<OptionLike>() == 1);
     };
 
     #[repr(C)]
diff --git a/tests/ui/hello_world/main.rs b/tests/ui/warnings/hello-world.rs
index 1b687eb1373..1b687eb1373 100644
--- a/tests/ui/hello_world/main.rs
+++ b/tests/ui/warnings/hello-world.rs
diff --git a/triagebot.toml b/triagebot.toml
index 894f56df741..e1b4983adf7 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -50,6 +50,21 @@ remove_labels = ["S-waiting-on-author"]
 # Those labels are added when PR author requests a review from an assignee
 add_labels = ["S-waiting-on-review"]
 
+# [backport.*] sections autonominate pull requests for a backport
+# see: https://forge.rust-lang.org/triagebot/backport.html
+
+[backport.t-compiler-beta-backport]
+# The pull request MUST have one of these labels
+required-pr-labels = ["T-compiler"]
+# The regression MUST have this label
+required-issue-label = "regression-from-stable-to-beta"
+# if the above conditions matches, the PR will receive these labels
+add-labels = ["beta-nominated"]
+
+[backport.t-compiler-stable-backport]
+required-pr-labels = ["T-compiler"]
+required-issue-label = "regression-from-stable-to-stable"
+add-labels = ["stable-nominated"]
 
 # ------------------------------------------------------------------------------
 # Ping groups