about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-10-14 05:17:07 +0000
committerbors <bors@rust-lang.org>2023-10-14 05:17:07 +0000
commit99d6cd4a04e57dffb8e7e6a4962ff3cf93449cad (patch)
tree6fed90869af4aee08821df88dfce245697938ab8
parenteba1f5bbdcab86b9fb0a08833c5d2d1cf4c89388 (diff)
parent2383a00d4f91e332c1129ecbf1ec28e96c35dcc9 (diff)
downloadrust-99d6cd4a04e57dffb8e7e6a4962ff3cf93449cad.tar.gz
rust-99d6cd4a04e57dffb8e7e6a4962ff3cf93449cad.zip
Auto merge of #3121 - rust-lang:rustup-2023-10-14, r=saethlin
Automatic sync from rustc
-rw-r--r--Cargo.lock2
-rw-r--r--compiler/rustc_attr/src/builtin.rs8
-rw-r--r--compiler/rustc_codegen_cranelift/src/driver/aot.rs98
-rw-r--r--compiler/rustc_codegen_llvm/src/back/lto.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/type_of.rs7
-rw-r--r--compiler/rustc_codegen_ssa/Cargo.toml1
-rw-r--r--compiler/rustc_codegen_ssa/messages.ftl24
-rw-r--r--compiler/rustc_codegen_ssa/src/assert_module_sources.rs (renamed from compiler/rustc_incremental/src/assert_module_sources.rs)138
-rw-r--r--compiler/rustc_codegen_ssa/src/back/write.rs6
-rw-r--r--compiler/rustc_codegen_ssa/src/base.rs12
-rw-r--r--compiler/rustc_codegen_ssa/src/errors.rs69
-rw-r--r--compiler/rustc_codegen_ssa/src/lib.rs1
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs14
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs55
-rw-r--r--compiler/rustc_const_eval/src/interpret/machine.rs13
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs49
-rw-r--r--compiler/rustc_const_eval/src/interpret/place.rs3
-rw-r--r--compiler/rustc_driver_impl/src/lib.rs22
-rw-r--r--compiler/rustc_driver_impl/src/pretty.rs408
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs1
-rw-r--r--compiler/rustc_hir_pretty/src/lib.rs191
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs3
-rw-r--r--compiler/rustc_hir_typeck/src/pat.rs12
-rw-r--r--compiler/rustc_incremental/messages.ftl14
-rw-r--r--compiler/rustc_incremental/src/errors.rs50
-rw-r--r--compiler/rustc_incremental/src/lib.rs1
-rw-r--r--compiler/rustc_interface/src/passes.rs3
-rw-r--r--compiler/rustc_lint/src/context.rs17
-rw-r--r--compiler/rustc_lint/src/lib.rs5
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs40
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs1117
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/usefulness.rs129
-rw-r--r--compiler/rustc_mir_transform/src/const_prop.rs21
-rw-r--r--compiler/rustc_mir_transform/src/coverage/counters.rs131
-rw-r--r--compiler/rustc_mir_transform/src/coverage/graph.rs161
-rw-r--r--compiler/rustc_mir_transform/src/coverage/tests.rs2
-rw-r--r--compiler/rustc_mir_transform/src/dataflow_const_prop.rs17
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs59
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs18
-rw-r--r--compiler/rustc_session/messages.ftl9
-rw-r--r--compiler/rustc_session/src/cgu_reuse_tracker.rs136
-rw-r--r--compiler/rustc_session/src/config.rs25
-rw-r--r--compiler/rustc_session/src/errors.rs19
-rw-r--r--compiler/rustc_session/src/lib.rs1
-rw-r--r--compiler/rustc_session/src/session.rs11
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs46
-rw-r--r--library/core/src/mem/maybe_uninit.rs5
-rw-r--r--library/core/src/sync/atomic.rs61
-rw-r--r--library/core/tests/lib.rs1
-rw-r--r--library/std/Cargo.toml4
-rw-r--r--library/std/build.rs8
-rw-r--r--library/std/src/sys/unix/process/process_common/tests.rs33
-rw-r--r--library/std/src/sys/unix/process/process_unix.rs5
-rw-r--r--library/std/src/sys/unix/process/process_unsupported.rs52
-rw-r--r--library/std/src/sys/unix/process/process_unsupported/wait_status.rs68
-rw-r--r--library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs36
-rw-r--r--src/doc/rustc/src/platform-support.md4
-rw-r--r--src/doc/unstable-book/src/compiler-flags/no-jump-tables.md19
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/mut_reference.rs2
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/machine.rs18
-rw-r--r--tests/assembly/x86_64-array-pair-load-store-merge.rs20
-rw-r--r--tests/codegen/array-codegen.rs22
-rw-r--r--tests/codegen/array-optimized.rs33
-rw-r--r--tests/rustdoc-ui/check-cfg/check-cfg.stderr2
-rw-r--r--tests/rustdoc-ui/doctest/check-cfg-test.stderr2
-rw-r--r--tests/ui/check-cfg/allow-same-level.stderr3
-rw-r--r--tests/ui/check-cfg/compact-names.stderr3
-rw-r--r--tests/ui/check-cfg/compact-values.stderr2
-rw-r--r--tests/ui/check-cfg/diagnotics.stderr13
-rw-r--r--tests/ui/check-cfg/empty-names.stderr3
-rw-r--r--tests/ui/check-cfg/empty-values.stderr2
-rw-r--r--tests/ui/check-cfg/invalid-cfg-name.stderr2
-rw-r--r--tests/ui/check-cfg/invalid-cfg-value.stderr4
-rw-r--r--tests/ui/check-cfg/mix.stderr54
-rw-r--r--tests/ui/check-cfg/no-values.stderr4
-rw-r--r--tests/ui/check-cfg/order-independant.names_after.stderr4
-rw-r--r--tests/ui/check-cfg/order-independant.names_before.stderr4
-rw-r--r--tests/ui/check-cfg/stmt-no-ice.stderr3
-rw-r--r--tests/ui/check-cfg/values-target-json.stderr2
-rw-r--r--tests/ui/check-cfg/well-known-names.stderr6
-rw-r--r--tests/ui/check-cfg/well-known-values.stderr10
-rw-r--r--tests/ui/consts/const-eval/raw-pointer-ub.rs36
-rw-r--r--tests/ui/consts/const-eval/raw-pointer-ub.stderr36
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs22
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr52
-rw-r--r--tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed10
-rw-r--r--tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs10
-rw-r--r--tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr38
-rw-r--r--tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs6
-rw-r--r--tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr16
-rw-r--r--tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed9
-rw-r--r--tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs9
-rw-r--r--tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr36
-rw-r--r--tests/ui/or-patterns/or-patterns-syntactic-fail.stderr8
-rw-r--r--tests/ui/parser/issues/issue-32505.rs1
-rw-r--r--tests/ui/parser/issues/issue-32505.stderr18
-rw-r--r--tests/ui/pattern/usefulness/slice_of_empty.rs22
-rw-r--r--tests/ui/pattern/usefulness/slice_of_empty.stderr39
-rw-r--r--tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs23
-rw-r--r--tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr44
102 files changed, 2262 insertions, 1862 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 48d13e17d62..8bd9b605ddf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3650,6 +3650,7 @@ dependencies = [
  "serde_json",
  "smallvec",
  "tempfile",
+ "thin-vec",
  "thorin-dwp",
  "tracing",
  "windows",
@@ -5081,7 +5082,6 @@ version = "0.0.0"
 dependencies = [
  "addr2line",
  "alloc",
- "cc",
  "cfg-if",
  "compiler_builtins",
  "core",
diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index f013ff45a4f..76659458dad 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -548,7 +548,11 @@ pub fn cfg_matches(
                     UNEXPECTED_CFGS,
                     cfg.span,
                     lint_node_id,
-                    "unexpected `cfg` condition value",
+                    if let Some(value) = cfg.value {
+                        format!("unexpected `cfg` condition value: `{value}`")
+                    } else {
+                        format!("unexpected `cfg` condition value: (none)")
+                    },
                     BuiltinLintDiagnostics::UnexpectedCfgValue(
                         (cfg.name, cfg.name_span),
                         cfg.value.map(|v| (v, cfg.value_span.unwrap())),
@@ -560,7 +564,7 @@ pub fn cfg_matches(
                     UNEXPECTED_CFGS,
                     cfg.span,
                     lint_node_id,
-                    "unexpected `cfg` condition name",
+                    format!("unexpected `cfg` condition name: `{}`", cfg.name),
                     BuiltinLintDiagnostics::UnexpectedCfgName(
                         (cfg.name, cfg.name_span),
                         cfg.value.map(|v| (v, cfg.value_span.unwrap())),
diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs
index cc2f5d72714..49f51f9f956 100644
--- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs
+++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs
@@ -7,14 +7,15 @@ use std::sync::Arc;
 use std::thread::JoinHandle;
 
 use cranelift_object::{ObjectBuilder, ObjectModule};
+use rustc_codegen_ssa::assert_module_sources::CguReuse;
 use rustc_codegen_ssa::back::metadata::create_compressed_metadata_file;
+use rustc_codegen_ssa::base::determine_cgu_reuse;
 use rustc_codegen_ssa::{CodegenResults, CompiledModule, CrateInfo, ModuleKind};
 use rustc_data_structures::profiling::SelfProfilerRef;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_metadata::EncodedMetadata;
 use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
 use rustc_middle::mir::mono::{CodegenUnit, MonoItem};
-use rustc_session::cgu_reuse_tracker::CguReuse;
 use rustc_session::config::{DebugInfo, OutputFilenames, OutputType};
 use rustc_session::Session;
 
@@ -374,43 +375,47 @@ pub(crate) fn run_aot(
         }
     }
 
+    // Calculate the CGU reuse
+    let cgu_reuse = tcx.sess.time("find_cgu_reuse", || {
+        cgus.iter().map(|cgu| determine_cgu_reuse(tcx, &cgu)).collect::<Vec<_>>()
+    });
+
+    rustc_codegen_ssa::assert_module_sources::assert_module_sources(tcx, &|cgu_reuse_tracker| {
+        for (i, cgu) in cgus.iter().enumerate() {
+            let cgu_reuse = cgu_reuse[i];
+            cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse);
+        }
+    });
+
     let global_asm_config = Arc::new(crate::global_asm::GlobalAsmConfig::new(tcx));
 
     let mut concurrency_limiter = ConcurrencyLimiter::new(tcx.sess, cgus.len());
 
     let modules = tcx.sess.time("codegen mono items", || {
         cgus.iter()
-            .map(|cgu| {
-                let cgu_reuse = if backend_config.disable_incr_cache {
-                    CguReuse::No
-                } else {
-                    determine_cgu_reuse(tcx, cgu)
-                };
-                tcx.sess.cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse);
-
-                match cgu_reuse {
-                    CguReuse::No => {
-                        let dep_node = cgu.codegen_dep_node(tcx);
-                        tcx.dep_graph
-                            .with_task(
-                                dep_node,
-                                tcx,
-                                (
-                                    backend_config.clone(),
-                                    global_asm_config.clone(),
-                                    cgu.name(),
-                                    concurrency_limiter.acquire(tcx.sess.diagnostic()),
-                                ),
-                                module_codegen,
-                                Some(rustc_middle::dep_graph::hash_result),
-                            )
-                            .0
-                    }
-                    CguReuse::PreLto => unreachable!(),
-                    CguReuse::PostLto => {
-                        concurrency_limiter.job_already_done();
-                        OngoingModuleCodegen::Sync(reuse_workproduct_for_cgu(tcx, cgu))
-                    }
+            .enumerate()
+            .map(|(i, cgu)| match cgu_reuse[i] {
+                CguReuse::No => {
+                    let dep_node = cgu.codegen_dep_node(tcx);
+                    tcx.dep_graph
+                        .with_task(
+                            dep_node,
+                            tcx,
+                            (
+                                backend_config.clone(),
+                                global_asm_config.clone(),
+                                cgu.name(),
+                                concurrency_limiter.acquire(tcx.sess.diagnostic()),
+                            ),
+                            module_codegen,
+                            Some(rustc_middle::dep_graph::hash_result),
+                        )
+                        .0
+                }
+                CguReuse::PreLto => unreachable!("LTO not yet supported"),
+                CguReuse::PostLto => {
+                    concurrency_limiter.job_already_done();
+                    OngoingModuleCodegen::Sync(reuse_workproduct_for_cgu(tcx, cgu))
                 }
             })
             .collect::<Vec<_>>()
@@ -489,32 +494,3 @@ pub(crate) fn run_aot(
         concurrency_limiter,
     })
 }
-
-// Adapted from https://github.com/rust-lang/rust/blob/303d8aff6092709edd4dbd35b1c88e9aa40bf6d8/src/librustc_codegen_ssa/base.rs#L922-L953
-fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguReuse {
-    if !tcx.dep_graph.is_fully_enabled() {
-        return CguReuse::No;
-    }
-
-    let work_product_id = &cgu.work_product_id();
-    if tcx.dep_graph.previous_work_product(work_product_id).is_none() {
-        // We don't have anything cached for this CGU. This can happen
-        // if the CGU did not exist in the previous session.
-        return CguReuse::No;
-    }
-
-    // Try to mark the CGU as green. If it we can do so, it means that nothing
-    // affecting the LLVM module has changed and we can re-use a cached version.
-    // If we compile with any kind of LTO, this means we can re-use the bitcode
-    // of the Pre-LTO stage (possibly also the Post-LTO version but we'll only
-    // know that later). If we are not doing LTO, there is only one optimized
-    // version of each module, so we re-use that.
-    let dep_node = cgu.codegen_dep_node(tcx);
-    assert!(
-        !tcx.dep_graph.dep_node_exists(&dep_node),
-        "CompileCodegenUnit dep-node for CGU `{}` already exists before marking.",
-        cgu.name()
-    );
-
-    if tcx.try_mark_green(&dep_node) { CguReuse::PostLto } else { CguReuse::No }
-}
diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs
index cb5acf79135..8655aeec13d 100644
--- a/compiler/rustc_codegen_llvm/src/back/lto.rs
+++ b/compiler/rustc_codegen_llvm/src/back/lto.rs
@@ -19,7 +19,6 @@ use rustc_hir::def_id::LOCAL_CRATE;
 use rustc_middle::bug;
 use rustc_middle::dep_graph::WorkProduct;
 use rustc_middle::middle::exported_symbols::{SymbolExportInfo, SymbolExportLevel};
-use rustc_session::cgu_reuse_tracker::CguReuse;
 use rustc_session::config::{self, CrateType, Lto};
 
 use std::ffi::{CStr, CString};
@@ -584,7 +583,6 @@ fn thin_lto(
                     copy_jobs.push(work_product);
                     info!(" - {}: re-used", module_name);
                     assert!(cgcx.incr_comp_session_dir.is_some());
-                    cgcx.cgu_reuse_tracker.set_actual_reuse(module_name, CguReuse::PostLto);
                     continue;
                 }
             }
diff --git a/compiler/rustc_codegen_llvm/src/type_of.rs b/compiler/rustc_codegen_llvm/src/type_of.rs
index dcc62d314ff..fd4c9572af2 100644
--- a/compiler/rustc_codegen_llvm/src/type_of.rs
+++ b/compiler/rustc_codegen_llvm/src/type_of.rs
@@ -397,7 +397,12 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> {
             // extracts all the individual values.
 
             let ety = element.llvm_type(cx);
-            return Some(cx.type_vector(ety, *count));
+            if *count == 1 {
+                // Emitting `<1 x T>` would be silly; just use the scalar.
+                return Some(ety);
+            } else {
+                return Some(cx.type_vector(ety, *count));
+            }
         }
 
         // FIXME: The above only handled integer arrays; surely more things
diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml
index 34d0e2d1df6..454e2f80676 100644
--- a/compiler/rustc_codegen_ssa/Cargo.toml
+++ b/compiler/rustc_codegen_ssa/Cargo.toml
@@ -16,6 +16,7 @@ pathdiff = "0.2.0"
 serde_json = "1.0.59"
 smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
 regex = "1.4"
+thin-vec = "0.2.12"
 
 rustc_serialize = { path = "../rustc_serialize" }
 rustc_arena = { path = "../rustc_arena" }
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 66482667336..5881c6236ec 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -11,6 +11,9 @@ codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing
 
 codegen_ssa_binary_output_to_tty = option `-o` or `--emit` is used to write binary output type `{$shorthand}` to stdout, but stdout is a tty
 
+codegen_ssa_cgu_not_recorded =
+    CGU-reuse for `{$cgu_user_name}` is (mangled: `{$cgu_name}`) was not recorded
+
 codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option.
 
 codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error}
@@ -39,6 +42,8 @@ codegen_ssa_failed_to_get_layout = failed to get layout for {$ty}: {$err}
 
 codegen_ssa_failed_to_write = failed to write {$path}: {$error}
 
+codegen_ssa_field_associated_value_expected = associated value expected for `{$name}`
+
 codegen_ssa_ignoring_emit_path = ignoring emit path because multiple .{$extension} files were produced
 
 codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files were produced
@@ -46,6 +51,12 @@ codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files w
 codegen_ssa_illegal_link_ordinal_format = illegal ordinal format in `link_ordinal`
     .note = an unsuffixed integer value, e.g., `1`, is expected
 
+codegen_ssa_incorrect_cgu_reuse_type =
+    CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least ->
+    [one] {"at least "}
+    *[other] {""}
+    }`{$expected_reuse}`
+
 codegen_ssa_insufficient_vs_code_product = VS Code is a different product, and is not sufficient.
 
 codegen_ssa_invalid_link_ordinal_nargs = incorrect number of arguments to `#[link_ordinal]`
@@ -153,12 +164,18 @@ codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for
 
 codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status}
 
+codegen_ssa_malformed_cgu_name =
+    found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case).
+
 codegen_ssa_metadata_object_file_write = error writing metadata object file: {$error}
 
 codegen_ssa_missing_cpp_build_tool_component = or a necessary component may be missing from the "C++ build tools" workload
 
 codegen_ssa_missing_memory_ordering = Atomic intrinsic missing memory ordering
 
+codegen_ssa_missing_query_depgraph =
+    found CGU-reuse attribute but `-Zquery-dep-graph` was not specified
+
 codegen_ssa_msvc_missing_linker = the msvc targets depend on the msvc linker but `link.exe` was not found
 
 codegen_ssa_multiple_external_func_decl = multiple declarations of external function `{$function}` from library `{$library_name}` have different calling conventions
@@ -166,6 +183,11 @@ codegen_ssa_multiple_external_func_decl = multiple declarations of external func
 codegen_ssa_multiple_main_functions = entry symbol `main` declared multiple times
     .help = did you use `#[no_mangle]` on `fn main`? Use `#[start]` instead
 
+codegen_ssa_no_field = no field `{$name}`
+
+codegen_ssa_no_module_named =
+    no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names}
+
 codegen_ssa_no_natvis_directory = error enumerating natvis directory: {$error}
 
 codegen_ssa_processing_dymutil_failed = processing debug info with `dsymutil` failed: {$status}
@@ -297,6 +319,8 @@ codegen_ssa_unknown_atomic_operation = unknown atomic operation
 
 codegen_ssa_unknown_atomic_ordering = unknown ordering in atomic intrinsic
 
+codegen_ssa_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified
+
 codegen_ssa_unsupported_arch = unsupported arch `{$arch}` for os `{$os}`
 
 codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target
diff --git a/compiler/rustc_incremental/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs
index 8e22ab4083e..16bb7b12bd3 100644
--- a/compiler/rustc_incremental/src/assert_module_sources.rs
+++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs
@@ -25,16 +25,21 @@
 
 use crate::errors;
 use rustc_ast as ast;
+use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::unord::UnordSet;
+use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg};
 use rustc_hir::def_id::LOCAL_CRATE;
 use rustc_middle::mir::mono::CodegenUnitNameBuilder;
 use rustc_middle::ty::TyCtxt;
-use rustc_session::cgu_reuse_tracker::*;
-use rustc_span::symbol::{sym, Symbol};
+use rustc_session::Session;
+use rustc_span::symbol::sym;
+use rustc_span::{Span, Symbol};
+use std::borrow::Cow;
+use std::fmt;
 use thin_vec::ThinVec;
 
 #[allow(missing_docs)]
-pub fn assert_module_sources(tcx: TyCtxt<'_>) {
+pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&mut CguReuseTracker)) {
     tcx.dep_graph.with_ignore(|| {
         if tcx.sess.opts.incremental.is_none() {
             return;
@@ -43,21 +48,34 @@ pub fn assert_module_sources(tcx: TyCtxt<'_>) {
         let available_cgus =
             tcx.collect_and_partition_mono_items(()).1.iter().map(|cgu| cgu.name()).collect();
 
-        let ams = AssertModuleSource { tcx, available_cgus };
+        let mut ams = AssertModuleSource {
+            tcx,
+            available_cgus,
+            cgu_reuse_tracker: if tcx.sess.opts.unstable_opts.query_dep_graph {
+                CguReuseTracker::new()
+            } else {
+                CguReuseTracker::new_disabled()
+            },
+        };
 
         for attr in tcx.hir().attrs(rustc_hir::CRATE_HIR_ID) {
             ams.check_attr(attr);
         }
-    })
+
+        set_reuse(&mut ams.cgu_reuse_tracker);
+
+        ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess);
+    });
 }
 
 struct AssertModuleSource<'tcx> {
     tcx: TyCtxt<'tcx>,
     available_cgus: UnordSet<Symbol>,
+    cgu_reuse_tracker: CguReuseTracker,
 }
 
 impl<'tcx> AssertModuleSource<'tcx> {
-    fn check_attr(&self, attr: &ast::Attribute) {
+    fn check_attr(&mut self, attr: &ast::Attribute) {
         let (expected_reuse, comp_kind) = if attr.has_name(sym::rustc_partition_reused) {
             (CguReuse::PreLto, ComparisonKind::AtLeast)
         } else if attr.has_name(sym::rustc_partition_codegened) {
@@ -129,7 +147,7 @@ impl<'tcx> AssertModuleSource<'tcx> {
             });
         }
 
-        self.tcx.sess.cgu_reuse_tracker.set_expectation(
+        self.cgu_reuse_tracker.set_expectation(
             cgu_name,
             &user_path,
             attr.span,
@@ -169,3 +187,109 @@ impl<'tcx> AssertModuleSource<'tcx> {
         false
     }
 }
+
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
+pub enum CguReuse {
+    No,
+    PreLto,
+    PostLto,
+}
+
+impl fmt::Display for CguReuse {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match *self {
+            CguReuse::No => write!(f, "No"),
+            CguReuse::PreLto => write!(f, "PreLto "),
+            CguReuse::PostLto => write!(f, "PostLto "),
+        }
+    }
+}
+
+impl IntoDiagnosticArg for CguReuse {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ComparisonKind {
+    Exact,
+    AtLeast,
+}
+
+struct TrackerData {
+    actual_reuse: FxHashMap<String, CguReuse>,
+    expected_reuse: FxHashMap<String, (String, Span, CguReuse, ComparisonKind)>,
+}
+
+pub struct CguReuseTracker {
+    data: Option<TrackerData>,
+}
+
+impl CguReuseTracker {
+    fn new() -> CguReuseTracker {
+        let data =
+            TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() };
+
+        CguReuseTracker { data: Some(data) }
+    }
+
+    fn new_disabled() -> CguReuseTracker {
+        CguReuseTracker { data: None }
+    }
+
+    pub fn set_actual_reuse(&mut self, cgu_name: &str, kind: CguReuse) {
+        if let Some(data) = &mut self.data {
+            debug!("set_actual_reuse({cgu_name:?}, {kind:?})");
+
+            let prev_reuse = data.actual_reuse.insert(cgu_name.to_string(), kind);
+            assert!(prev_reuse.is_none());
+        }
+    }
+
+    fn set_expectation(
+        &mut self,
+        cgu_name: Symbol,
+        cgu_user_name: &str,
+        error_span: Span,
+        expected_reuse: CguReuse,
+        comparison_kind: ComparisonKind,
+    ) {
+        if let Some(data) = &mut self.data {
+            debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})");
+
+            data.expected_reuse.insert(
+                cgu_name.to_string(),
+                (cgu_user_name.to_string(), error_span, expected_reuse, comparison_kind),
+            );
+        }
+    }
+
+    fn check_expected_reuse(&self, sess: &Session) {
+        if let Some(ref data) = self.data {
+            for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in
+                &data.expected_reuse
+            {
+                if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) {
+                    let (error, at_least) = match comparison_kind {
+                        ComparisonKind::Exact => (expected_reuse != actual_reuse, false),
+                        ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true),
+                    };
+
+                    if error {
+                        let at_least = if at_least { 1 } else { 0 };
+                        errors::IncorrectCguReuseType {
+                            span: *error_span,
+                            cgu_user_name,
+                            actual_reuse,
+                            expected_reuse,
+                            at_least,
+                        };
+                    }
+                } else {
+                    sess.emit_fatal(errors::CguNotRecorded { cgu_user_name, cgu_name });
+                }
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs
index f192747c8ab..3d6a2124334 100644
--- a/compiler/rustc_codegen_ssa/src/back/write.rs
+++ b/compiler/rustc_codegen_ssa/src/back/write.rs
@@ -26,7 +26,6 @@ use rustc_metadata::EncodedMetadata;
 use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
 use rustc_middle::middle::exported_symbols::SymbolExportInfo;
 use rustc_middle::ty::TyCtxt;
-use rustc_session::cgu_reuse_tracker::CguReuseTracker;
 use rustc_session::config::{self, CrateType, Lto, OutFileName, OutputFilenames, OutputType};
 use rustc_session::config::{Passes, SwitchWithOptPath};
 use rustc_session::Session;
@@ -366,8 +365,6 @@ pub struct CodegenContext<B: WriteBackendMethods> {
     /// The incremental compilation session directory, or None if we are not
     /// compiling incrementally
     pub incr_comp_session_dir: Option<PathBuf>,
-    /// Used to update CGU re-use information during the thinlto phase.
-    pub cgu_reuse_tracker: CguReuseTracker,
     /// Channel back to the main control thread to send messages to
     pub coordinator_send: Sender<Box<dyn Any + Send>>,
 }
@@ -1119,7 +1116,6 @@ fn start_executing_work<B: ExtraBackendMethods>(
         remark: sess.opts.cg.remark.clone(),
         remark_dir,
         incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()),
-        cgu_reuse_tracker: sess.cgu_reuse_tracker.clone(),
         coordinator_send,
         expanded_args: tcx.sess.expanded_args.clone(),
         diag_emitter: shared_emitter.clone(),
@@ -1969,8 +1965,6 @@ impl<B: ExtraBackendMethods> OngoingCodegen<B> {
             }
         });
 
-        sess.cgu_reuse_tracker.check_expected_reuse(sess);
-
         sess.abort_if_errors();
 
         let work_products =
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index 1e4ea73a172..198e5696357 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -1,3 +1,4 @@
+use crate::assert_module_sources::CguReuse;
 use crate::back::link::are_upstream_rust_objects_already_included;
 use crate::back::metadata::create_compressed_metadata_file;
 use crate::back::write::{
@@ -31,7 +32,6 @@ use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder, MonoItem};
 use rustc_middle::query::Providers;
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout};
 use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
-use rustc_session::cgu_reuse_tracker::CguReuse;
 use rustc_session::config::{self, CrateType, EntryFnType, OutputType};
 use rustc_session::Session;
 use rustc_span::symbol::sym;
@@ -683,6 +683,13 @@ pub fn codegen_crate<B: ExtraBackendMethods>(
         codegen_units.iter().map(|cgu| determine_cgu_reuse(tcx, &cgu)).collect::<Vec<_>>()
     });
 
+    crate::assert_module_sources::assert_module_sources(tcx, &|cgu_reuse_tracker| {
+        for (i, cgu) in codegen_units.iter().enumerate() {
+            let cgu_reuse = cgu_reuse[i];
+            cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse);
+        }
+    });
+
     let mut total_codegen_time = Duration::new(0, 0);
     let start_rss = tcx.sess.opts.unstable_opts.time_passes.then(|| get_resident_set_size());
 
@@ -727,7 +734,6 @@ pub fn codegen_crate<B: ExtraBackendMethods>(
         ongoing_codegen.check_for_errors(tcx.sess);
 
         let cgu_reuse = cgu_reuse[i];
-        tcx.sess.cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse);
 
         match cgu_reuse {
             CguReuse::No => {
@@ -994,7 +1000,7 @@ pub fn provide(providers: &mut Providers) {
     };
 }
 
-fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguReuse {
+pub fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguReuse {
     if !tcx.dep_graph.is_fully_enabled() {
         return CguReuse::No;
     }
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index 39b2fa37fbf..ed6ac9f9c5d 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -1,5 +1,6 @@
 //! Errors emitted by codegen_ssa
 
+use crate::assert_module_sources::CguReuse;
 use crate::back::command::Command;
 use crate::fluent_generated as fluent;
 use rustc_errors::{
@@ -17,6 +18,74 @@ use std::path::{Path, PathBuf};
 use std::process::ExitStatus;
 
 #[derive(Diagnostic)]
+#[diag(codegen_ssa_incorrect_cgu_reuse_type)]
+pub struct IncorrectCguReuseType<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub cgu_user_name: &'a str,
+    pub actual_reuse: CguReuse,
+    pub expected_reuse: CguReuse,
+    pub at_least: u8,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_cgu_not_recorded)]
+pub struct CguNotRecorded<'a> {
+    pub cgu_user_name: &'a str,
+    pub cgu_name: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_unknown_reuse_kind)]
+pub struct UnknownReuseKind {
+    #[primary_span]
+    pub span: Span,
+    pub kind: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_missing_query_depgraph)]
+pub struct MissingQueryDepGraph {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_malformed_cgu_name)]
+pub struct MalformedCguName {
+    #[primary_span]
+    pub span: Span,
+    pub user_path: String,
+    pub crate_name: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_no_module_named)]
+pub struct NoModuleNamed<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub user_path: &'a str,
+    pub cgu_name: Symbol,
+    pub cgu_names: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_field_associated_value_expected)]
+pub struct FieldAssociatedValueExpected {
+    #[primary_span]
+    pub span: Span,
+    pub name: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_no_field)]
+pub struct NoField {
+    #[primary_span]
+    pub span: Span,
+    pub name: Symbol,
+}
+
+#[derive(Diagnostic)]
 #[diag(codegen_ssa_lib_def_write_failure)]
 pub struct LibDefWriteFailure {
     pub error: Error,
diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs
index b4728ac2aa6..156c2904f40 100644
--- a/compiler/rustc_codegen_ssa/src/lib.rs
+++ b/compiler/rustc_codegen_ssa/src/lib.rs
@@ -46,6 +46,7 @@ use std::collections::BTreeSet;
 use std::io;
 use std::path::{Path, PathBuf};
 
+pub mod assert_module_sources;
 pub mod back;
 pub mod base;
 pub mod codegen_attrs;
diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
index 3d758cd01d3..4adfbe336af 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -1,6 +1,3 @@
-use crate::const_eval::CheckAlignment;
-use crate::errors::ConstEvalError;
-
 use either::{Left, Right};
 
 use rustc_hir::def::DefKind;
@@ -15,7 +12,9 @@ use rustc_span::source_map::Span;
 use rustc_target::abi::{self, Abi};
 
 use super::{CanAccessStatics, CompileTimeEvalContext, CompileTimeInterpreter};
+use crate::const_eval::CheckAlignment;
 use crate::errors;
+use crate::errors::ConstEvalError;
 use crate::interpret::eval_nullary_intrinsic;
 use crate::interpret::{
     intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate, InternKind, InterpCx,
@@ -290,14 +289,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
         key.param_env,
         // Statics (and promoteds inside statics) may access other statics, because unlike consts
         // they do not have to behave "as if" they were evaluated at runtime.
-        CompileTimeInterpreter::new(
-            CanAccessStatics::from(is_static),
-            if tcx.sess.opts.unstable_opts.extra_const_ub_checks {
-                CheckAlignment::Error
-            } else {
-                CheckAlignment::FutureIncompat
-            },
-        ),
+        CompileTimeInterpreter::new(CanAccessStatics::from(is_static), CheckAlignment::Error),
     );
 
     let res = ecx.load_mir(cid.instance.def, cid.promoted);
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 865e01d0aee..166d3d45e79 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -1,10 +1,9 @@
 use rustc_hir::def::DefKind;
-use rustc_hir::{LangItem, CRATE_HIR_ID};
+use rustc_hir::LangItem;
 use rustc_middle::mir;
 use rustc_middle::mir::interpret::PointerArithmetic;
 use rustc_middle::ty::layout::{FnAbiOf, TyAndLayout};
 use rustc_middle::ty::{self, TyCtxt};
-use rustc_session::lint::builtin::INVALID_ALIGNMENT;
 use std::borrow::Borrow;
 use std::hash::Hash;
 use std::ops::ControlFlow;
@@ -21,11 +20,11 @@ use rustc_target::abi::{Align, Size};
 use rustc_target::spec::abi::Abi as CallAbi;
 
 use crate::errors::{LongRunning, LongRunningWarn};
+use crate::fluent_generated as fluent;
 use crate::interpret::{
     self, compile_time_machine, AllocId, ConstAllocation, FnArg, FnVal, Frame, ImmTy, InterpCx,
     InterpResult, OpTy, PlaceTy, Pointer, Scalar,
 };
-use crate::{errors, fluent_generated as fluent};
 
 use super::error::*;
 
@@ -65,22 +64,11 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
 
 #[derive(Copy, Clone)]
 pub enum CheckAlignment {
-    /// Ignore alignment when following relocations.
+    /// Ignore all alignment requirements.
     /// This is mainly used in interning.
     No,
     /// Hard error when dereferencing a misaligned pointer.
     Error,
-    /// Emit a future incompat lint when dereferencing a misaligned pointer.
-    FutureIncompat,
-}
-
-impl CheckAlignment {
-    pub fn should_check(&self) -> bool {
-        match self {
-            CheckAlignment::No => false,
-            CheckAlignment::Error | CheckAlignment::FutureIncompat => true,
-        }
-    }
 }
 
 #[derive(Copy, Clone, PartialEq)]
@@ -358,8 +346,8 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
     const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
 
     #[inline(always)]
-    fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment {
-        ecx.machine.check_alignment
+    fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
+        matches!(ecx.machine.check_alignment, CheckAlignment::Error)
     }
 
     #[inline(always)]
@@ -367,39 +355,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
         ecx.tcx.sess.opts.unstable_opts.extra_const_ub_checks || layout.abi.is_uninhabited()
     }
 
-    fn alignment_check_failed(
-        ecx: &InterpCx<'mir, 'tcx, Self>,
-        has: Align,
-        required: Align,
-        check: CheckAlignment,
-    ) -> InterpResult<'tcx, ()> {
-        let err = err_ub!(AlignmentCheckFailed { has, required }).into();
-        match check {
-            CheckAlignment::Error => Err(err),
-            CheckAlignment::No => span_bug!(
-                ecx.cur_span(),
-                "`alignment_check_failed` called when no alignment check requested"
-            ),
-            CheckAlignment::FutureIncompat => {
-                let (_, backtrace) = err.into_parts();
-                backtrace.print_backtrace();
-                let (span, frames) = super::get_span_and_frames(&ecx);
-
-                ecx.tcx.emit_spanned_lint(
-                    INVALID_ALIGNMENT,
-                    ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
-                    span,
-                    errors::AlignmentCheckFailed {
-                        has: has.bytes(),
-                        required: required.bytes(),
-                        frames,
-                    },
-                );
-                Ok(())
-            }
-        }
-    }
-
     fn load_mir(
         ecx: &InterpCx<'mir, 'tcx, Self>,
         instance: ty::InstanceDef<'tcx>,
diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs
index b615ced6c76..9b4f9906599 100644
--- a/compiler/rustc_const_eval/src/interpret/machine.rs
+++ b/compiler/rustc_const_eval/src/interpret/machine.rs
@@ -12,11 +12,9 @@ use rustc_middle::mir;
 use rustc_middle::ty::layout::TyAndLayout;
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::def_id::DefId;
-use rustc_target::abi::{Align, Size};
+use rustc_target::abi::Size;
 use rustc_target::spec::abi::Abi as CallAbi;
 
-use crate::const_eval::CheckAlignment;
-
 use super::{
     AllocBytes, AllocId, AllocRange, Allocation, ConstAllocation, FnArg, Frame, ImmTy, InterpCx,
     InterpResult, MPlaceTy, MemoryKind, OpTy, PlaceTy, Pointer, Provenance,
@@ -135,7 +133,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
     const POST_MONO_CHECKS: bool = true;
 
     /// Whether memory accesses should be alignment-checked.
-    fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment;
+    fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
 
     /// Whether, when checking alignment, we should look at the actual address and thus support
     /// custom alignment logic based on whatever the integer address happens to be.
@@ -143,13 +141,6 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
     /// If this returns true, Provenance::OFFSET_IS_ADDR must be true.
     fn use_addr_for_alignment_check(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
 
-    fn alignment_check_failed(
-        ecx: &InterpCx<'mir, 'tcx, Self>,
-        has: Align,
-        required: Align,
-        check: CheckAlignment,
-    ) -> InterpResult<'tcx, ()>;
-
     /// Whether to enforce the validity invariant for a specific layout.
     fn enforce_validity(ecx: &InterpCx<'mir, 'tcx, Self>, layout: TyAndLayout<'tcx>) -> bool;
 
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index ce666e6af3b..edd8c540226 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -18,7 +18,6 @@ use rustc_middle::mir::display_allocation;
 use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt};
 use rustc_target::abi::{Align, HasDataLayout, Size};
 
-use crate::const_eval::CheckAlignment;
 use crate::fluent_generated as fluent;
 
 use super::{
@@ -373,8 +372,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         self.check_and_deref_ptr(
             ptr,
             size,
-            align,
-            M::enforce_alignment(self),
+            M::enforce_alignment(self).then_some(align),
             CheckInAllocMsg::MemoryAccessTest,
             |alloc_id, offset, prov| {
                 let (size, align) = self
@@ -395,17 +393,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         align: Align,
         msg: CheckInAllocMsg,
     ) -> InterpResult<'tcx> {
-        self.check_and_deref_ptr(
-            ptr,
-            size,
-            align,
-            CheckAlignment::Error,
-            msg,
-            |alloc_id, _, _| {
-                let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?;
-                Ok((size, align, ()))
-            },
-        )?;
+        self.check_and_deref_ptr(ptr, size, Some(align), msg, |alloc_id, _, _| {
+            let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?;
+            Ok((size, align, ()))
+        })?;
         Ok(())
     }
 
@@ -419,8 +410,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         &self,
         ptr: Pointer<Option<M::Provenance>>,
         size: Size,
-        align: Align,
-        check: CheckAlignment,
+        align: Option<Align>,
         msg: CheckInAllocMsg,
         alloc_size: impl FnOnce(
             AllocId,
@@ -436,8 +426,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     throw_ub!(DanglingIntPointer(addr, msg));
                 }
                 // Must be aligned.
-                if check.should_check() {
-                    self.check_offset_align(addr, align, check)?;
+                if let Some(align) = align {
+                    self.check_offset_align(addr, align)?;
                 }
                 None
             }
@@ -460,16 +450,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 }
                 // Test align. Check this last; if both bounds and alignment are violated
                 // we want the error to be about the bounds.
-                if check.should_check() {
+                if let Some(align) = align {
                     if M::use_addr_for_alignment_check(self) {
                         // `use_addr_for_alignment_check` can only be true if `OFFSET_IS_ADDR` is true.
-                        self.check_offset_align(ptr.addr().bytes(), align, check)?;
+                        self.check_offset_align(ptr.addr().bytes(), align)?;
                     } else {
                         // Check allocation alignment and offset alignment.
                         if alloc_align.bytes() < align.bytes() {
-                            M::alignment_check_failed(self, alloc_align, align, check)?;
+                            throw_ub!(AlignmentCheckFailed { has: alloc_align, required: align });
                         }
-                        self.check_offset_align(offset.bytes(), align, check)?;
+                        self.check_offset_align(offset.bytes(), align)?;
                     }
                 }
 
@@ -480,18 +470,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         })
     }
 
-    fn check_offset_align(
-        &self,
-        offset: u64,
-        align: Align,
-        check: CheckAlignment,
-    ) -> InterpResult<'tcx> {
+    fn check_offset_align(&self, offset: u64, align: Align) -> InterpResult<'tcx> {
         if offset % align.bytes() == 0 {
             Ok(())
         } else {
             // The biggest power of two through which `offset` is divisible.
             let offset_pow2 = 1 << offset.trailing_zeros();
-            M::alignment_check_failed(self, Align::from_bytes(offset_pow2).unwrap(), align, check)
+            throw_ub!(AlignmentCheckFailed {
+                has: Align::from_bytes(offset_pow2).unwrap(),
+                required: align
+            });
         }
     }
 }
@@ -609,8 +597,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         let ptr_and_alloc = self.check_and_deref_ptr(
             ptr,
             size,
-            align,
-            M::enforce_alignment(self),
+            M::enforce_alignment(self).then_some(align),
             CheckInAllocMsg::MemoryAccessTest,
             |alloc_id, offset, prov| {
                 let alloc = self.get_alloc_raw(alloc_id)?;
diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs
index 79448f07cae..9f95d2a3246 100644
--- a/compiler/rustc_const_eval/src/interpret/place.rs
+++ b/compiler/rustc_const_eval/src/interpret/place.rs
@@ -500,8 +500,7 @@ where
             .size_and_align_of_mplace(&mplace)?
             .unwrap_or((mplace.layout.size, mplace.layout.align.abi));
         // Due to packed places, only `mplace.align` matters.
-        let align =
-            if M::enforce_alignment(self).should_check() { mplace.align } else { Align::ONE };
+        let align = if M::enforce_alignment(self) { mplace.align } else { Align::ONE };
         self.check_ptr_access_align(mplace.ptr(), size, align, CheckInAllocMsg::DerefTest)?;
         Ok(())
     }
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index 17556c45296..7a45ac10f0b 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -8,10 +8,10 @@
 #![cfg_attr(not(bootstrap), doc(rust_logo))]
 #![cfg_attr(not(bootstrap), feature(rustdoc_internals))]
 #![cfg_attr(not(bootstrap), allow(internal_features))]
-#![feature(lazy_cell)]
 #![feature(decl_macro)]
-#![feature(panic_update_hook)]
+#![feature(lazy_cell)]
 #![feature(let_chains)]
+#![feature(panic_update_hook)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 #![deny(rustc::untranslatable_diagnostic)]
@@ -395,7 +395,7 @@ fn run_compiler(
                 if ppm.needs_ast_map() {
                     queries.global_ctxt()?.enter(|tcx| {
                         tcx.ensure().early_lint_checks(());
-                        pretty::print_after_hir_lowering(tcx, *ppm);
+                        pretty::print(sess, *ppm, pretty::PrintExtra::NeedsAstMap { tcx });
                         Ok(())
                     })?;
 
@@ -404,7 +404,7 @@ fn run_compiler(
                     queries.global_ctxt()?.enter(|tcx| tcx.output_filenames(()));
                 } else {
                     let krate = queries.parse()?.steal();
-                    pretty::print_after_parsing(sess, &krate, *ppm);
+                    pretty::print(sess, *ppm, pretty::PrintExtra::AfterParsing { krate });
                 }
                 trace!("finished pretty-printing");
                 return early_exit();
@@ -545,7 +545,7 @@ pub enum Compilation {
 }
 
 impl Compilation {
-    pub fn and_then<F: FnOnce() -> Compilation>(self, next: F) -> Compilation {
+    fn and_then<F: FnOnce() -> Compilation>(self, next: F) -> Compilation {
         match self {
             Compilation::Stop => Compilation::Stop,
             Compilation::Continue => next(),
@@ -657,7 +657,7 @@ fn show_md_content_with_pager(content: &str, color: ColorConfig) {
     }
 }
 
-pub fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Compilation {
+fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Compilation {
     if sess.opts.unstable_opts.link_only {
         if let Input::File(file) = &sess.io.input {
             let outputs = compiler.build_output_filenames(sess, &[]);
@@ -698,7 +698,7 @@ pub fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Comp
     }
 }
 
-pub fn list_metadata(
+fn list_metadata(
     handler: &EarlyErrorHandler,
     sess: &Session,
     metadata_loader: &dyn MetadataLoader,
@@ -1184,7 +1184,7 @@ fn print_flag_list<T>(
 ///
 /// So with all that in mind, the comments below have some more detail about the
 /// contortions done here to get things to work out correctly.
-pub fn handle_options(handler: &EarlyErrorHandler, args: &[String]) -> Option<getopts::Matches> {
+fn handle_options(handler: &EarlyErrorHandler, args: &[String]) -> Option<getopts::Matches> {
     if args.is_empty() {
         // user did not write `-v` nor `-Z unstable-options`, so do not
         // include that extra information.
@@ -1283,9 +1283,9 @@ pub fn catch_with_exit_code(f: impl FnOnce() -> interface::Result<()>) -> i32 {
     }
 }
 
-pub static ICE_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
+static ICE_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
 
-pub fn ice_path() -> &'static Option<PathBuf> {
+fn ice_path() -> &'static Option<PathBuf> {
     ICE_PATH.get_or_init(|| {
         if !rustc_feature::UnstableFeatures::from_environment(None).is_nightly_build() {
             return None;
@@ -1394,7 +1394,7 @@ pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler))
 ///
 /// When `install_ice_hook` is called, this function will be called as the panic
 /// hook.
-pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: fn(&Handler)) {
+fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: fn(&Handler)) {
     let fallback_bundle =
         rustc_errors::fallback_fluent_bundle(crate::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
     let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs
index 222c7b5d6a7..8c6fee83013 100644
--- a/compiler/rustc_driver_impl/src/pretty.rs
+++ b/compiler/rustc_driver_impl/src/pretty.rs
@@ -1,14 +1,13 @@
 //! The various pretty-printing routines.
 
 use rustc_ast as ast;
-use rustc_ast_pretty::pprust;
-use rustc_errors::ErrorGuaranteed;
+use rustc_ast_pretty::pprust as pprust_ast;
 use rustc_hir as hir;
 use rustc_hir_pretty as pprust_hir;
-use rustc_middle::hir::map as hir_map;
+use rustc_middle::bug;
 use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty};
 use rustc_middle::ty::{self, TyCtxt};
-use rustc_session::config::{OutFileName, PpAstTreeMode, PpHirMode, PpMode, PpSourceMode};
+use rustc_session::config::{OutFileName, PpHirMode, PpMode, PpSourceMode};
 use rustc_session::Session;
 use rustc_span::symbol::Ident;
 use rustc_span::FileName;
@@ -20,174 +19,57 @@ pub use self::PpMode::*;
 pub use self::PpSourceMode::*;
 use crate::abort_on_err;
 
-// This slightly awkward construction is to allow for each PpMode to
-// choose whether it needs to do analyses (which can consume the
-// Session) and then pass through the session (now attached to the
-// analysis results) on to the chosen pretty-printer, along with the
-// `&PpAnn` object.
-//
-// Note that since the `&PrinterSupport` is freshly constructed on each
-// call, it would not make sense to try to attach the lifetime of `self`
-// to the lifetime of the `&PrinterObject`.
-
-/// Constructs a `PrinterSupport` object and passes it to `f`.
-fn call_with_pp_support<'tcx, A, F>(
-    ppmode: &PpSourceMode,
-    sess: &'tcx Session,
-    tcx: Option<TyCtxt<'tcx>>,
-    f: F,
-) -> A
-where
-    F: FnOnce(&dyn PrinterSupport) -> A,
-{
-    match *ppmode {
-        Normal | Expanded => {
-            let annotation = NoAnn { sess, tcx };
-            f(&annotation)
-        }
-
-        Identified | ExpandedIdentified => {
-            let annotation = IdentifiedAnnotation { sess, tcx };
-            f(&annotation)
-        }
-        ExpandedHygiene => {
-            let annotation = HygieneAnnotation { sess };
-            f(&annotation)
-        }
-    }
-}
-fn call_with_pp_support_hir<A, F>(ppmode: &PpHirMode, tcx: TyCtxt<'_>, f: F) -> A
-where
-    F: FnOnce(&dyn HirPrinterSupport<'_>, hir_map::Map<'_>) -> A,
-{
-    match *ppmode {
-        PpHirMode::Normal => {
-            let annotation = NoAnn { sess: tcx.sess, tcx: Some(tcx) };
-            f(&annotation, tcx.hir())
-        }
-
-        PpHirMode::Identified => {
-            let annotation = IdentifiedAnnotation { sess: tcx.sess, tcx: Some(tcx) };
-            f(&annotation, tcx.hir())
-        }
-        PpHirMode::Typed => {
-            abort_on_err(tcx.analysis(()), tcx.sess);
-
-            let annotation = TypedAnnotation { tcx, maybe_typeck_results: Cell::new(None) };
-            tcx.dep_graph.with_ignore(|| f(&annotation, tcx.hir()))
-        }
-    }
-}
-
-trait PrinterSupport: pprust::PpAnn {
-    /// Provides a uniform interface for re-extracting a reference to a
-    /// `Session` from a value that now owns it.
-    fn sess(&self) -> &Session;
-
-    /// Produces the pretty-print annotation object.
-    ///
-    /// (Rust does not yet support upcasting from a trait object to
-    /// an object for one of its supertraits.)
-    fn pp_ann(&self) -> &dyn pprust::PpAnn;
-}
-
-trait HirPrinterSupport<'hir>: pprust_hir::PpAnn {
-    /// Provides a uniform interface for re-extracting a reference to a
-    /// `Session` from a value that now owns it.
-    fn sess(&self) -> &Session;
-
-    /// Provides a uniform interface for re-extracting a reference to an
-    /// `hir_map::Map` from a value that now owns it.
-    fn hir_map(&self) -> Option<hir_map::Map<'hir>>;
-
-    /// Produces the pretty-print annotation object.
-    ///
-    /// (Rust does not yet support upcasting from a trait object to
-    /// an object for one of its supertraits.)
-    fn pp_ann(&self) -> &dyn pprust_hir::PpAnn;
-}
-
-struct NoAnn<'hir> {
-    sess: &'hir Session,
-    tcx: Option<TyCtxt<'hir>>,
-}
-
-impl<'hir> PrinterSupport for NoAnn<'hir> {
-    fn sess(&self) -> &Session {
-        self.sess
-    }
-
-    fn pp_ann(&self) -> &dyn pprust::PpAnn {
-        self
-    }
-}
+struct AstNoAnn;
 
-impl<'hir> HirPrinterSupport<'hir> for NoAnn<'hir> {
-    fn sess(&self) -> &Session {
-        self.sess
-    }
-
-    fn hir_map(&self) -> Option<hir_map::Map<'hir>> {
-        self.tcx.map(|tcx| tcx.hir())
-    }
+impl pprust_ast::PpAnn for AstNoAnn {}
 
-    fn pp_ann(&self) -> &dyn pprust_hir::PpAnn {
-        self
-    }
+struct HirNoAnn<'tcx> {
+    tcx: TyCtxt<'tcx>,
 }
 
-impl<'hir> pprust::PpAnn for NoAnn<'hir> {}
-impl<'hir> pprust_hir::PpAnn for NoAnn<'hir> {
+impl<'tcx> pprust_hir::PpAnn for HirNoAnn<'tcx> {
     fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) {
-        if let Some(tcx) = self.tcx {
-            pprust_hir::PpAnn::nested(&(&tcx.hir() as &dyn hir::intravisit::Map<'_>), state, nested)
-        }
+        pprust_hir::PpAnn::nested(
+            &(&self.tcx.hir() as &dyn hir::intravisit::Map<'_>),
+            state,
+            nested,
+        )
     }
 }
 
-struct IdentifiedAnnotation<'hir> {
-    sess: &'hir Session,
-    tcx: Option<TyCtxt<'hir>>,
-}
-
-impl<'hir> PrinterSupport for IdentifiedAnnotation<'hir> {
-    fn sess(&self) -> &Session {
-        self.sess
-    }
+struct AstIdentifiedAnn;
 
-    fn pp_ann(&self) -> &dyn pprust::PpAnn {
-        self
-    }
-}
-
-impl<'hir> pprust::PpAnn for IdentifiedAnnotation<'hir> {
-    fn pre(&self, s: &mut pprust::State<'_>, node: pprust::AnnNode<'_>) {
-        if let pprust::AnnNode::Expr(_) = node {
+impl pprust_ast::PpAnn for AstIdentifiedAnn {
+    fn pre(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) {
+        if let pprust_ast::AnnNode::Expr(_) = node {
             s.popen();
         }
     }
-    fn post(&self, s: &mut pprust::State<'_>, node: pprust::AnnNode<'_>) {
+
+    fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) {
         match node {
-            pprust::AnnNode::Crate(_) | pprust::AnnNode::Ident(_) | pprust::AnnNode::Name(_) => {}
+            pprust_ast::AnnNode::Crate(_)
+            | pprust_ast::AnnNode::Ident(_)
+            | pprust_ast::AnnNode::Name(_) => {}
 
-            pprust::AnnNode::Item(item) => {
+            pprust_ast::AnnNode::Item(item) => {
                 s.s.space();
                 s.synth_comment(item.id.to_string())
             }
-            pprust::AnnNode::SubItem(id) => {
+            pprust_ast::AnnNode::SubItem(id) => {
                 s.s.space();
                 s.synth_comment(id.to_string())
             }
-            pprust::AnnNode::Block(blk) => {
+            pprust_ast::AnnNode::Block(blk) => {
                 s.s.space();
                 s.synth_comment(format!("block {}", blk.id))
             }
-            pprust::AnnNode::Expr(expr) => {
+            pprust_ast::AnnNode::Expr(expr) => {
                 s.s.space();
                 s.synth_comment(expr.id.to_string());
                 s.pclose()
             }
-            pprust::AnnNode::Pat(pat) => {
+            pprust_ast::AnnNode::Pat(pat) => {
                 s.s.space();
                 s.synth_comment(format!("pat {}", pat.id));
             }
@@ -195,31 +77,25 @@ impl<'hir> pprust::PpAnn for IdentifiedAnnotation<'hir> {
     }
 }
 
-impl<'hir> HirPrinterSupport<'hir> for IdentifiedAnnotation<'hir> {
-    fn sess(&self) -> &Session {
-        self.sess
-    }
-
-    fn hir_map(&self) -> Option<hir_map::Map<'hir>> {
-        self.tcx.map(|tcx| tcx.hir())
-    }
-
-    fn pp_ann(&self) -> &dyn pprust_hir::PpAnn {
-        self
-    }
+struct HirIdentifiedAnn<'tcx> {
+    tcx: TyCtxt<'tcx>,
 }
 
-impl<'hir> pprust_hir::PpAnn for IdentifiedAnnotation<'hir> {
+impl<'tcx> pprust_hir::PpAnn for HirIdentifiedAnn<'tcx> {
     fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) {
-        if let Some(ref tcx) = self.tcx {
-            pprust_hir::PpAnn::nested(&(&tcx.hir() as &dyn hir::intravisit::Map<'_>), state, nested)
-        }
+        pprust_hir::PpAnn::nested(
+            &(&self.tcx.hir() as &dyn hir::intravisit::Map<'_>),
+            state,
+            nested,
+        )
     }
+
     fn pre(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) {
         if let pprust_hir::AnnNode::Expr(_) = node {
             s.popen();
         }
     }
+
     fn post(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) {
         match node {
             pprust_hir::AnnNode::Name(_) => {}
@@ -252,32 +128,22 @@ impl<'hir> pprust_hir::PpAnn for IdentifiedAnnotation<'hir> {
     }
 }
 
-struct HygieneAnnotation<'a> {
+struct AstHygieneAnn<'a> {
     sess: &'a Session,
 }
 
-impl<'a> PrinterSupport for HygieneAnnotation<'a> {
-    fn sess(&self) -> &Session {
-        self.sess
-    }
-
-    fn pp_ann(&self) -> &dyn pprust::PpAnn {
-        self
-    }
-}
-
-impl<'a> pprust::PpAnn for HygieneAnnotation<'a> {
-    fn post(&self, s: &mut pprust::State<'_>, node: pprust::AnnNode<'_>) {
+impl<'a> pprust_ast::PpAnn for AstHygieneAnn<'a> {
+    fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) {
         match node {
-            pprust::AnnNode::Ident(&Ident { name, span }) => {
+            pprust_ast::AnnNode::Ident(&Ident { name, span }) => {
                 s.s.space();
                 s.synth_comment(format!("{}{:?}", name.as_u32(), span.ctxt()))
             }
-            pprust::AnnNode::Name(&name) => {
+            pprust_ast::AnnNode::Name(&name) => {
                 s.s.space();
                 s.synth_comment(name.as_u32().to_string())
             }
-            pprust::AnnNode::Crate(_) => {
+            pprust_ast::AnnNode::Crate(_) => {
                 s.s.hardbreak();
                 let verbose = self.sess.verbose();
                 s.synth_comment(rustc_span::hygiene::debug_hygiene_data(verbose));
@@ -288,26 +154,12 @@ impl<'a> pprust::PpAnn for HygieneAnnotation<'a> {
     }
 }
 
-struct TypedAnnotation<'tcx> {
+struct HirTypedAnn<'tcx> {
     tcx: TyCtxt<'tcx>,
     maybe_typeck_results: Cell<Option<&'tcx ty::TypeckResults<'tcx>>>,
 }
 
-impl<'tcx> HirPrinterSupport<'tcx> for TypedAnnotation<'tcx> {
-    fn sess(&self) -> &Session {
-        self.tcx.sess
-    }
-
-    fn hir_map(&self) -> Option<hir_map::Map<'tcx>> {
-        Some(self.tcx.hir())
-    }
-
-    fn pp_ann(&self) -> &dyn pprust_hir::PpAnn {
-        self
-    }
-}
-
-impl<'tcx> pprust_hir::PpAnn for TypedAnnotation<'tcx> {
+impl<'tcx> pprust_hir::PpAnn for HirTypedAnn<'tcx> {
     fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) {
         let old_maybe_typeck_results = self.maybe_typeck_results.get();
         if let pprust_hir::Nested::Body(id) = nested {
@@ -317,11 +169,13 @@ impl<'tcx> pprust_hir::PpAnn for TypedAnnotation<'tcx> {
         pprust_hir::PpAnn::nested(pp_ann, state, nested);
         self.maybe_typeck_results.set(old_maybe_typeck_results);
     }
+
     fn pre(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) {
         if let pprust_hir::AnnNode::Expr(_) = node {
             s.popen();
         }
     }
+
     fn post(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) {
         if let pprust_hir::AnnNode::Expr(expr) = node {
             let typeck_results = self.maybe_typeck_results.get().or_else(|| {
@@ -360,119 +214,119 @@ fn write_or_print(out: &str, sess: &Session) {
     sess.io.output_file.as_ref().unwrap_or(&OutFileName::Stdout).overwrite(out, sess);
 }
 
-pub fn print_after_parsing(sess: &Session, krate: &ast::Crate, ppm: PpMode) {
-    let (src, src_name) = get_source(sess);
+// Extra data for pretty-printing, the form of which depends on what kind of
+// pretty-printing we are doing.
+pub enum PrintExtra<'tcx> {
+    AfterParsing { krate: ast::Crate },
+    NeedsAstMap { tcx: TyCtxt<'tcx> },
+}
 
-    let out = match ppm {
-        Source(s) => {
-            // Silently ignores an identified node.
-            call_with_pp_support(&s, sess, None, move |annotation| {
-                debug!("pretty printing source code {:?}", s);
-                let sess = annotation.sess();
-                let parse = &sess.parse_sess;
-                pprust::print_crate(
-                    sess.source_map(),
-                    krate,
-                    src_name,
-                    src,
-                    annotation.pp_ann(),
-                    false,
-                    parse.edition,
-                    &sess.parse_sess.attr_id_generator,
-                )
-            })
+impl<'tcx> PrintExtra<'tcx> {
+    fn with_krate<F, R>(&self, f: F) -> R
+    where
+        F: FnOnce(&ast::Crate) -> R,
+    {
+        match self {
+            PrintExtra::AfterParsing { krate, .. } => f(krate),
+            PrintExtra::NeedsAstMap { tcx } => f(&tcx.resolver_for_lowering(()).borrow().1),
         }
-        AstTree(PpAstTreeMode::Normal) => {
-            debug!("pretty printing AST tree");
-            format!("{krate:#?}")
-        }
-        _ => unreachable!(),
-    };
+    }
 
-    write_or_print(&out, sess);
+    fn tcx(&self) -> TyCtxt<'tcx> {
+        match self {
+            PrintExtra::AfterParsing { .. } => bug!("PrintExtra::tcx"),
+            PrintExtra::NeedsAstMap { tcx } => *tcx,
+        }
+    }
 }
 
-pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) {
+pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) {
     if ppm.needs_analysis() {
-        abort_on_err(print_with_analysis(tcx, ppm), tcx.sess);
-        return;
+        abort_on_err(ex.tcx().analysis(()), sess);
     }
 
-    let (src, src_name) = get_source(tcx.sess);
+    let (src, src_name) = get_source(sess);
 
     let out = match ppm {
         Source(s) => {
-            // Silently ignores an identified node.
-            call_with_pp_support(&s, tcx.sess, Some(tcx), move |annotation| {
-                debug!("pretty printing source code {:?}", s);
-                let sess = annotation.sess();
-                let parse = &sess.parse_sess;
-                pprust::print_crate(
+            debug!("pretty printing source code {:?}", s);
+            let annotation: Box<dyn pprust_ast::PpAnn> = match s {
+                Normal => Box::new(AstNoAnn),
+                Expanded => Box::new(AstNoAnn),
+                Identified => Box::new(AstIdentifiedAnn),
+                ExpandedIdentified => Box::new(AstIdentifiedAnn),
+                ExpandedHygiene => Box::new(AstHygieneAnn { sess }),
+            };
+            let parse = &sess.parse_sess;
+            let is_expanded = ppm.needs_ast_map();
+            ex.with_krate(|krate| {
+                pprust_ast::print_crate(
                     sess.source_map(),
-                    &tcx.resolver_for_lowering(()).borrow().1,
+                    krate,
                     src_name,
                     src,
-                    annotation.pp_ann(),
-                    true,
+                    &*annotation,
+                    is_expanded,
                     parse.edition,
                     &sess.parse_sess.attr_id_generator,
                 )
             })
         }
-
-        AstTree(PpAstTreeMode::Expanded) => {
+        AstTree => {
+            debug!("pretty printing AST tree");
+            ex.with_krate(|krate| format!("{krate:#?}"))
+        }
+        AstTreeExpanded => {
             debug!("pretty-printing expanded AST");
-            format!("{:#?}", tcx.resolver_for_lowering(()).borrow().1)
+            format!("{:#?}", ex.tcx().resolver_for_lowering(()).borrow().1)
         }
-
-        Hir(s) => call_with_pp_support_hir(&s, tcx, move |annotation, hir_map| {
+        Hir(s) => {
             debug!("pretty printing HIR {:?}", s);
-            let sess = annotation.sess();
-            let sm = sess.source_map();
-            let attrs = |id| hir_map.attrs(id);
-            pprust_hir::print_crate(
-                sm,
-                hir_map.root_module(),
-                src_name,
-                src,
-                &attrs,
-                annotation.pp_ann(),
-            )
-        }),
-
+            let tcx = ex.tcx();
+            let f = |annotation: &dyn pprust_hir::PpAnn| {
+                let sm = sess.source_map();
+                let hir_map = tcx.hir();
+                let attrs = |id| hir_map.attrs(id);
+                pprust_hir::print_crate(
+                    sm,
+                    hir_map.root_module(),
+                    src_name,
+                    src,
+                    &attrs,
+                    annotation,
+                )
+            };
+            match s {
+                PpHirMode::Normal => {
+                    let annotation = HirNoAnn { tcx };
+                    f(&annotation)
+                }
+                PpHirMode::Identified => {
+                    let annotation = HirIdentifiedAnn { tcx };
+                    f(&annotation)
+                }
+                PpHirMode::Typed => {
+                    let annotation = HirTypedAnn { tcx, maybe_typeck_results: Cell::new(None) };
+                    tcx.dep_graph.with_ignore(|| f(&annotation))
+                }
+            }
+        }
         HirTree => {
-            call_with_pp_support_hir(&PpHirMode::Normal, tcx, move |_annotation, hir_map| {
-                debug!("pretty printing HIR tree");
-                format!("{:#?}", hir_map.krate())
-            })
+            debug!("pretty printing HIR tree");
+            format!("{:#?}", ex.tcx().hir().krate())
         }
-
-        _ => unreachable!(),
-    };
-
-    write_or_print(&out, tcx.sess);
-}
-
-// In an ideal world, this would be a public function called by the driver after
-// analysis is performed. However, we want to call `phase_3_run_analysis_passes`
-// with a different callback than the standard driver, so that isn't easy.
-// Instead, we call that function ourselves.
-fn print_with_analysis(tcx: TyCtxt<'_>, ppm: PpMode) -> Result<(), ErrorGuaranteed> {
-    tcx.analysis(())?;
-    let out = match ppm {
         Mir => {
             let mut out = Vec::new();
-            write_mir_pretty(tcx, None, &mut out).unwrap();
+            write_mir_pretty(ex.tcx(), None, &mut out).unwrap();
             String::from_utf8(out).unwrap()
         }
-
         MirCFG => {
             let mut out = Vec::new();
-            write_mir_graphviz(tcx, None, &mut out).unwrap();
+            write_mir_graphviz(ex.tcx(), None, &mut out).unwrap();
             String::from_utf8(out).unwrap()
         }
-
         ThirTree => {
+            let tcx = ex.tcx();
             let mut out = String::new();
             abort_on_err(rustc_hir_analysis::check_crate(tcx), tcx.sess);
             debug!("pretty printing THIR tree");
@@ -481,8 +335,8 @@ fn print_with_analysis(tcx: TyCtxt<'_>, ppm: PpMode) -> Result<(), ErrorGuarante
             }
             out
         }
-
         ThirFlat => {
+            let tcx = ex.tcx();
             let mut out = String::new();
             abort_on_err(rustc_hir_analysis::check_crate(tcx), tcx.sess);
             debug!("pretty printing THIR flat");
@@ -491,11 +345,7 @@ fn print_with_analysis(tcx: TyCtxt<'_>, ppm: PpMode) -> Result<(), ErrorGuarante
             }
             out
         }
-
-        _ => unreachable!(),
     };
 
-    write_or_print(&out, tcx.sess);
-
-    Ok(())
+    write_or_print(&out, sess);
 }
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index 5e23ae655fe..85acf8ab5aa 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -659,6 +659,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
         msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self);
     forward!(pub fn help(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
+    forward!(pub fn help_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
     forward!(pub fn span_help(
         &mut self,
         sp: impl Into<MultiSpan>,
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index 8587b009f25..ab6ab391484 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -52,8 +52,6 @@ pub struct NoAnn;
 impl PpAnn for NoAnn {}
 pub const NO_ANN: &dyn PpAnn = &NoAnn;
 
-/// Identical to the `PpAnn` implementation for `hir::Crate`,
-/// except it avoids creating a dependency on the whole crate.
 impl PpAnn for &dyn rustc_hir::intravisit::Map<'_> {
     fn nested(&self, state: &mut State<'_>, nested: Nested) {
         match nested {
@@ -75,7 +73,11 @@ pub struct State<'a> {
 }
 
 impl<'a> State<'a> {
-    pub fn print_node(&mut self, node: Node<'_>) {
+    fn attrs(&self, id: hir::HirId) -> &'a [ast::Attribute] {
+        (self.attrs)(id)
+    }
+
+    fn print_node(&mut self, node: Node<'_>) {
         match node {
             Node::Param(a) => self.print_param(a),
             Node::Item(a) => self.print_item(a),
@@ -144,7 +146,7 @@ impl<'a> PrintState<'a> for State<'a> {
     }
 }
 
-pub const INDENT_UNIT: isize = 4;
+const INDENT_UNIT: isize = 4;
 
 /// Requires you to pass an input filename and reader so that
 /// it can scan the input text for comments to copy forward.
@@ -156,7 +158,12 @@ pub fn print_crate<'a>(
     attrs: &'a dyn Fn(hir::HirId) -> &'a [ast::Attribute],
     ann: &'a dyn PpAnn,
 ) -> String {
-    let mut s = State::new_from_input(sm, filename, input, attrs, ann);
+    let mut s = State {
+        s: pp::Printer::new(),
+        comments: Some(Comments::new(sm, filename, input)),
+        attrs,
+        ann,
+    };
 
     // When printing the AST, we sometimes need to inject `#[no_std]` here.
     // Since you can't compile the HIR, it's not necessary.
@@ -166,28 +173,7 @@ pub fn print_crate<'a>(
     s.s.eof()
 }
 
-impl<'a> State<'a> {
-    pub fn new_from_input(
-        sm: &'a SourceMap,
-        filename: FileName,
-        input: String,
-        attrs: &'a dyn Fn(hir::HirId) -> &'a [ast::Attribute],
-        ann: &'a dyn PpAnn,
-    ) -> State<'a> {
-        State {
-            s: pp::Printer::new(),
-            comments: Some(Comments::new(sm, filename, input)),
-            attrs,
-            ann,
-        }
-    }
-
-    fn attrs(&self, id: hir::HirId) -> &'a [ast::Attribute] {
-        (self.attrs)(id)
-    }
-}
-
-pub fn to_string<F>(ann: &dyn PpAnn, f: F) -> String
+fn to_string<F>(ann: &dyn PpAnn, f: F) -> String
 where
     F: FnOnce(&mut State<'_>),
 {
@@ -196,52 +182,20 @@ where
     printer.s.eof()
 }
 
-pub fn generic_params_to_string(generic_params: &[GenericParam<'_>]) -> String {
-    to_string(NO_ANN, |s| s.print_generic_params(generic_params))
-}
-
-pub fn bounds_to_string<'b>(bounds: impl IntoIterator<Item = &'b hir::GenericBound<'b>>) -> String {
-    to_string(NO_ANN, |s| s.print_bounds("", bounds))
-}
-
 pub fn ty_to_string(ty: &hir::Ty<'_>) -> String {
     to_string(NO_ANN, |s| s.print_type(ty))
 }
 
-pub fn path_segment_to_string(segment: &hir::PathSegment<'_>) -> String {
-    to_string(NO_ANN, |s| s.print_path_segment(segment))
-}
-
-pub fn path_to_string(segment: &hir::Path<'_>) -> String {
-    to_string(NO_ANN, |s| s.print_path(segment, false))
-}
-
 pub fn qpath_to_string(segment: &hir::QPath<'_>) -> String {
     to_string(NO_ANN, |s| s.print_qpath(segment, false))
 }
 
-pub fn fn_to_string(
-    decl: &hir::FnDecl<'_>,
-    header: hir::FnHeader,
-    name: Option<Symbol>,
-    generics: &hir::Generics<'_>,
-    arg_names: &[Ident],
-    body_id: Option<hir::BodyId>,
-) -> String {
-    to_string(NO_ANN, |s| s.print_fn(decl, header, name, generics, arg_names, body_id))
-}
-
-pub fn enum_def_to_string(
-    enum_definition: &hir::EnumDef<'_>,
-    generics: &hir::Generics<'_>,
-    name: Symbol,
-    span: rustc_span::Span,
-) -> String {
-    to_string(NO_ANN, |s| s.print_enum_def(enum_definition, generics, name, span))
+pub fn pat_to_string(pat: &hir::Pat<'_>) -> String {
+    to_string(NO_ANN, |s| s.print_pat(pat))
 }
 
 impl<'a> State<'a> {
-    pub fn bclose_maybe_open(&mut self, span: rustc_span::Span, close_box: bool) {
+    fn bclose_maybe_open(&mut self, span: rustc_span::Span, close_box: bool) {
         self.maybe_print_comment(span.hi());
         self.break_offset_if_not_bol(1, -INDENT_UNIT);
         self.word("}");
@@ -250,11 +204,11 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn bclose(&mut self, span: rustc_span::Span) {
+    fn bclose(&mut self, span: rustc_span::Span) {
         self.bclose_maybe_open(span, true)
     }
 
-    pub fn commasep_cmnt<T, F, G>(&mut self, b: Breaks, elts: &[T], mut op: F, mut get_span: G)
+    fn commasep_cmnt<T, F, G>(&mut self, b: Breaks, elts: &[T], mut op: F, mut get_span: G)
     where
         F: FnMut(&mut State<'_>, &T),
         G: FnMut(&T) -> rustc_span::Span,
@@ -275,25 +229,25 @@ impl<'a> State<'a> {
         self.end();
     }
 
-    pub fn commasep_exprs(&mut self, b: Breaks, exprs: &[hir::Expr<'_>]) {
+    fn commasep_exprs(&mut self, b: Breaks, exprs: &[hir::Expr<'_>]) {
         self.commasep_cmnt(b, exprs, |s, e| s.print_expr(e), |e| e.span);
     }
 
-    pub fn print_mod(&mut self, _mod: &hir::Mod<'_>, attrs: &[ast::Attribute]) {
+    fn print_mod(&mut self, _mod: &hir::Mod<'_>, attrs: &[ast::Attribute]) {
         self.print_inner_attributes(attrs);
         for &item_id in _mod.item_ids {
             self.ann.nested(self, Nested::Item(item_id));
         }
     }
 
-    pub fn print_opt_lifetime(&mut self, lifetime: &hir::Lifetime) {
+    fn print_opt_lifetime(&mut self, lifetime: &hir::Lifetime) {
         if !lifetime.is_elided() {
             self.print_lifetime(lifetime);
             self.nbsp();
         }
     }
 
-    pub fn print_type(&mut self, ty: &hir::Ty<'_>) {
+    fn print_type(&mut self, ty: &hir::Ty<'_>) {
         self.maybe_print_comment(ty.span.lo());
         self.ibox(0);
         match ty.kind {
@@ -371,7 +325,7 @@ impl<'a> State<'a> {
         self.end()
     }
 
-    pub fn print_foreign_item(&mut self, item: &hir::ForeignItem<'_>) {
+    fn print_foreign_item(&mut self, item: &hir::ForeignItem<'_>) {
         self.hardbreak_if_not_bol();
         self.maybe_print_comment(item.span.lo());
         self.print_outer_attributes(self.attrs(item.hir_id()));
@@ -478,8 +432,7 @@ impl<'a> State<'a> {
         self.end(); // end the outer ibox
     }
 
-    /// Pretty-print an item
-    pub fn print_item(&mut self, item: &hir::Item<'_>) {
+    fn print_item(&mut self, item: &hir::Item<'_>) {
         self.hardbreak_if_not_bol();
         self.maybe_print_comment(item.span.lo());
         let attrs = self.attrs(item.hir_id());
@@ -704,7 +657,7 @@ impl<'a> State<'a> {
         self.ann.post(self, AnnNode::Item(item))
     }
 
-    pub fn print_trait_ref(&mut self, t: &hir::TraitRef<'_>) {
+    fn print_trait_ref(&mut self, t: &hir::TraitRef<'_>) {
         self.print_path(t.path, false);
     }
 
@@ -721,7 +674,7 @@ impl<'a> State<'a> {
         self.print_trait_ref(&t.trait_ref);
     }
 
-    pub fn print_enum_def(
+    fn print_enum_def(
         &mut self,
         enum_definition: &hir::EnumDef<'_>,
         generics: &hir::Generics<'_>,
@@ -736,7 +689,7 @@ impl<'a> State<'a> {
         self.print_variants(enum_definition.variants, span);
     }
 
-    pub fn print_variants(&mut self, variants: &[hir::Variant<'_>], span: rustc_span::Span) {
+    fn print_variants(&mut self, variants: &[hir::Variant<'_>], span: rustc_span::Span) {
         self.bopen();
         for v in variants {
             self.space_if_not_bol();
@@ -751,14 +704,14 @@ impl<'a> State<'a> {
         self.bclose(span)
     }
 
-    pub fn print_defaultness(&mut self, defaultness: hir::Defaultness) {
+    fn print_defaultness(&mut self, defaultness: hir::Defaultness) {
         match defaultness {
             hir::Defaultness::Default { .. } => self.word_nbsp("default"),
             hir::Defaultness::Final => (),
         }
     }
 
-    pub fn print_struct(
+    fn print_struct(
         &mut self,
         struct_def: &hir::VariantData<'_>,
         generics: &hir::Generics<'_>,
@@ -807,7 +760,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_variant(&mut self, v: &hir::Variant<'_>) {
+    fn print_variant(&mut self, v: &hir::Variant<'_>) {
         self.head("");
         let generics = hir::Generics::empty();
         self.print_struct(&v.data, generics, v.ident.name, v.span, false);
@@ -817,7 +770,8 @@ impl<'a> State<'a> {
             self.print_anon_const(d);
         }
     }
-    pub fn print_method_sig(
+
+    fn print_method_sig(
         &mut self,
         ident: Ident,
         m: &hir::FnSig<'_>,
@@ -828,7 +782,7 @@ impl<'a> State<'a> {
         self.print_fn(m.decl, m.header, Some(ident.name), generics, arg_names, body_id);
     }
 
-    pub fn print_trait_item(&mut self, ti: &hir::TraitItem<'_>) {
+    fn print_trait_item(&mut self, ti: &hir::TraitItem<'_>) {
         self.ann.pre(self, AnnNode::SubItem(ti.hir_id()));
         self.hardbreak_if_not_bol();
         self.maybe_print_comment(ti.span.lo());
@@ -856,7 +810,7 @@ impl<'a> State<'a> {
         self.ann.post(self, AnnNode::SubItem(ti.hir_id()))
     }
 
-    pub fn print_impl_item(&mut self, ii: &hir::ImplItem<'_>) {
+    fn print_impl_item(&mut self, ii: &hir::ImplItem<'_>) {
         self.ann.pre(self, AnnNode::SubItem(ii.hir_id()));
         self.hardbreak_if_not_bol();
         self.maybe_print_comment(ii.span.lo());
@@ -881,7 +835,7 @@ impl<'a> State<'a> {
         self.ann.post(self, AnnNode::SubItem(ii.hir_id()))
     }
 
-    pub fn print_local(
+    fn print_local(
         &mut self,
         init: Option<&hir::Expr<'_>>,
         els: Option<&hir::Block<'_>>,
@@ -914,7 +868,7 @@ impl<'a> State<'a> {
         self.end()
     }
 
-    pub fn print_stmt(&mut self, st: &hir::Stmt<'_>) {
+    fn print_stmt(&mut self, st: &hir::Stmt<'_>) {
         self.maybe_print_comment(st.span.lo());
         match st.kind {
             hir::StmtKind::Local(loc) => {
@@ -937,19 +891,19 @@ impl<'a> State<'a> {
         self.maybe_print_trailing_comment(st.span, None)
     }
 
-    pub fn print_block(&mut self, blk: &hir::Block<'_>) {
+    fn print_block(&mut self, blk: &hir::Block<'_>) {
         self.print_block_with_attrs(blk, &[])
     }
 
-    pub fn print_block_unclosed(&mut self, blk: &hir::Block<'_>) {
+    fn print_block_unclosed(&mut self, blk: &hir::Block<'_>) {
         self.print_block_maybe_unclosed(blk, &[], false)
     }
 
-    pub fn print_block_with_attrs(&mut self, blk: &hir::Block<'_>, attrs: &[ast::Attribute]) {
+    fn print_block_with_attrs(&mut self, blk: &hir::Block<'_>, attrs: &[ast::Attribute]) {
         self.print_block_maybe_unclosed(blk, attrs, true)
     }
 
-    pub fn print_block_maybe_unclosed(
+    fn print_block_maybe_unclosed(
         &mut self,
         blk: &hir::Block<'_>,
         attrs: &[ast::Attribute],
@@ -1005,7 +959,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_if(
+    fn print_if(
         &mut self,
         test: &hir::Expr<'_>,
         blk: &hir::Expr<'_>,
@@ -1018,14 +972,14 @@ impl<'a> State<'a> {
         self.print_else(elseopt)
     }
 
-    pub fn print_array_length(&mut self, len: &hir::ArrayLen) {
+    fn print_array_length(&mut self, len: &hir::ArrayLen) {
         match len {
             hir::ArrayLen::Infer(_, _) => self.word("_"),
             hir::ArrayLen::Body(ct) => self.print_anon_const(ct),
         }
     }
 
-    pub fn print_anon_const(&mut self, constant: &hir::AnonConst) {
+    fn print_anon_const(&mut self, constant: &hir::AnonConst) {
         self.ann.nested(self, Nested::Body(constant.body))
     }
 
@@ -1041,7 +995,7 @@ impl<'a> State<'a> {
 
     /// Prints an expr using syntax that's acceptable in a condition position, such as the `cond` in
     /// `if cond { ... }`.
-    pub fn print_expr_as_cond(&mut self, expr: &hir::Expr<'_>) {
+    fn print_expr_as_cond(&mut self, expr: &hir::Expr<'_>) {
         self.print_expr_cond_paren(expr, Self::cond_needs_par(expr))
     }
 
@@ -1360,7 +1314,7 @@ impl<'a> State<'a> {
         self.pclose();
     }
 
-    pub fn print_expr(&mut self, expr: &hir::Expr<'_>) {
+    fn print_expr(&mut self, expr: &hir::Expr<'_>) {
         self.maybe_print_comment(expr.span.lo());
         self.print_outer_attributes(self.attrs(expr.hir_id));
         self.ibox(INDENT_UNIT);
@@ -1593,7 +1547,7 @@ impl<'a> State<'a> {
         self.end()
     }
 
-    pub fn print_local_decl(&mut self, loc: &hir::Local<'_>) {
+    fn print_local_decl(&mut self, loc: &hir::Local<'_>) {
         self.print_pat(loc.pat);
         if let Some(ty) = loc.ty {
             self.word_space(":");
@@ -1601,11 +1555,11 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_name(&mut self, name: Symbol) {
+    fn print_name(&mut self, name: Symbol) {
         self.print_ident(Ident::with_dummy_span(name))
     }
 
-    pub fn print_path<R>(&mut self, path: &hir::Path<'_, R>, colons_before_params: bool) {
+    fn print_path<R>(&mut self, path: &hir::Path<'_, R>, colons_before_params: bool) {
         self.maybe_print_comment(path.span.lo());
 
         for (i, segment) in path.segments.iter().enumerate() {
@@ -1619,14 +1573,14 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_path_segment(&mut self, segment: &hir::PathSegment<'_>) {
+    fn print_path_segment(&mut self, segment: &hir::PathSegment<'_>) {
         if segment.ident.name != kw::PathRoot {
             self.print_ident(segment.ident);
             self.print_generic_args(segment.args(), false);
         }
     }
 
-    pub fn print_qpath(&mut self, qpath: &hir::QPath<'_>, colons_before_params: bool) {
+    fn print_qpath(&mut self, qpath: &hir::QPath<'_>, colons_before_params: bool) {
         match *qpath {
             hir::QPath::Resolved(None, path) => self.print_path(path, colons_before_params),
             hir::QPath::Resolved(Some(qself), path) => {
@@ -1743,7 +1697,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_type_binding(&mut self, binding: &hir::TypeBinding<'_>) {
+    fn print_type_binding(&mut self, binding: &hir::TypeBinding<'_>) {
         self.print_ident(binding.ident);
         self.print_generic_args(binding.gen_args, false);
         self.space();
@@ -1761,7 +1715,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_pat(&mut self, pat: &hir::Pat<'_>) {
+    fn print_pat(&mut self, pat: &hir::Pat<'_>) {
         self.maybe_print_comment(pat.span.lo());
         self.ann.pre(self, AnnNode::Pat(pat));
         // Pat isn't normalized, but the beauty of it
@@ -1905,7 +1859,7 @@ impl<'a> State<'a> {
         self.ann.post(self, AnnNode::Pat(pat))
     }
 
-    pub fn print_patfield(&mut self, field: &hir::PatField<'_>) {
+    fn print_patfield(&mut self, field: &hir::PatField<'_>) {
         if self.attrs(field.hir_id).is_empty() {
             self.space();
         }
@@ -1919,12 +1873,12 @@ impl<'a> State<'a> {
         self.end();
     }
 
-    pub fn print_param(&mut self, arg: &hir::Param<'_>) {
+    fn print_param(&mut self, arg: &hir::Param<'_>) {
         self.print_outer_attributes(self.attrs(arg.hir_id));
         self.print_pat(arg.pat);
     }
 
-    pub fn print_arm(&mut self, arm: &hir::Arm<'_>) {
+    fn print_arm(&mut self, arm: &hir::Arm<'_>) {
         // I have no idea why this check is necessary, but here it
         // is :(
         if self.attrs(arm.hir_id).is_empty() {
@@ -1976,7 +1930,7 @@ impl<'a> State<'a> {
         self.end() // close enclosing cbox
     }
 
-    pub fn print_fn(
+    fn print_fn(
         &mut self,
         decl: &hir::FnDecl<'_>,
         header: hir::FnHeader,
@@ -2056,14 +2010,14 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_capture_clause(&mut self, capture_clause: hir::CaptureBy) {
+    fn print_capture_clause(&mut self, capture_clause: hir::CaptureBy) {
         match capture_clause {
             hir::CaptureBy::Value => self.word_space("move"),
             hir::CaptureBy::Ref => {}
         }
     }
 
-    pub fn print_closure_binder(
+    fn print_closure_binder(
         &mut self,
         binder: hir::ClosureBinder,
         generic_params: &[GenericParam<'_>],
@@ -2083,7 +2037,8 @@ impl<'a> State<'a> {
 
         match binder {
             hir::ClosureBinder::Default => {}
-            // we need to distinguish `|...| {}` from `for<> |...| {}` as `for<>` adds additional restrictions
+            // We need to distinguish `|...| {}` from `for<> |...| {}` as `for<>` adds additional
+            // restrictions.
             hir::ClosureBinder::For { .. } if generic_params.is_empty() => self.word("for<>"),
             hir::ClosureBinder::For { .. } => {
                 self.word("for");
@@ -2099,7 +2054,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_bounds<'b>(
+    fn print_bounds<'b>(
         &mut self,
         prefix: &'static str,
         bounds: impl IntoIterator<Item = &'b hir::GenericBound<'b>>,
@@ -2137,7 +2092,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_generic_params(&mut self, generic_params: &[GenericParam<'_>]) {
+    fn print_generic_params(&mut self, generic_params: &[GenericParam<'_>]) {
         if !generic_params.is_empty() {
             self.word("<");
 
@@ -2147,7 +2102,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_generic_param(&mut self, param: &GenericParam<'_>) {
+    fn print_generic_param(&mut self, param: &GenericParam<'_>) {
         if let GenericParamKind::Const { .. } = param.kind {
             self.word_space("const");
         }
@@ -2175,11 +2130,11 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_lifetime(&mut self, lifetime: &hir::Lifetime) {
+    fn print_lifetime(&mut self, lifetime: &hir::Lifetime) {
         self.print_ident(lifetime.ident)
     }
 
-    pub fn print_where_clause(&mut self, generics: &hir::Generics<'_>) {
+    fn print_where_clause(&mut self, generics: &hir::Generics<'_>) {
         if generics.predicates.is_empty() {
             return;
         }
@@ -2236,7 +2191,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_mutability(&mut self, mutbl: hir::Mutability, print_const: bool) {
+    fn print_mutability(&mut self, mutbl: hir::Mutability, print_const: bool) {
         match mutbl {
             hir::Mutability::Mut => self.word_nbsp("mut"),
             hir::Mutability::Not => {
@@ -2247,12 +2202,12 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_mt(&mut self, mt: &hir::MutTy<'_>, print_const: bool) {
+    fn print_mt(&mut self, mt: &hir::MutTy<'_>, print_const: bool) {
         self.print_mutability(mt.mutbl, print_const);
         self.print_type(mt.ty);
     }
 
-    pub fn print_fn_output(&mut self, decl: &hir::FnDecl<'_>) {
+    fn print_fn_output(&mut self, decl: &hir::FnDecl<'_>) {
         if let hir::FnRetTy::DefaultReturn(..) = decl.output {
             return;
         }
@@ -2271,7 +2226,7 @@ impl<'a> State<'a> {
         }
     }
 
-    pub fn print_ty_fn(
+    fn print_ty_fn(
         &mut self,
         abi: Abi,
         unsafety: hir::Unsafety,
@@ -2299,7 +2254,7 @@ impl<'a> State<'a> {
         self.end();
     }
 
-    pub fn print_fn_header_info(&mut self, header: hir::FnHeader) {
+    fn print_fn_header_info(&mut self, header: hir::FnHeader) {
         self.print_constness(header.constness);
 
         match header.asyncness {
@@ -2317,21 +2272,21 @@ impl<'a> State<'a> {
         self.word("fn")
     }
 
-    pub fn print_constness(&mut self, s: hir::Constness) {
+    fn print_constness(&mut self, s: hir::Constness) {
         match s {
             hir::Constness::NotConst => {}
             hir::Constness::Const => self.word_nbsp("const"),
         }
     }
 
-    pub fn print_unsafety(&mut self, s: hir::Unsafety) {
+    fn print_unsafety(&mut self, s: hir::Unsafety) {
         match s {
             hir::Unsafety::Normal => {}
             hir::Unsafety::Unsafe => self.word_nbsp("unsafe"),
         }
     }
 
-    pub fn print_is_auto(&mut self, s: hir::IsAuto) {
+    fn print_is_auto(&mut self, s: hir::IsAuto) {
         match s {
             hir::IsAuto::Yes => self.word_nbsp("auto"),
             hir::IsAuto::No => {}
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
index 9b7f8f80310..c762e684480 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
@@ -2036,7 +2036,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 }
                 let typeck = self.typeck_results.borrow();
                 for (rcvr, args) in call_finder.calls {
-                    if let Some(rcvr_ty) = typeck.node_type_opt(rcvr.hir_id)
+                    if rcvr.hir_id.owner == typeck.hir_owner
+                        && let Some(rcvr_ty) = typeck.node_type_opt(rcvr.hir_id)
                         && let ty::Closure(call_def_id, _) = rcvr_ty.kind()
                         && def_id == *call_def_id
                         && let Some(idx) = expected_idx
diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs
index 3f9c9b3381b..072c7a9f36a 100644
--- a/compiler/rustc_hir_typeck/src/pat.rs
+++ b/compiler/rustc_hir_typeck/src/pat.rs
@@ -1504,9 +1504,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         {
             let has_shorthand_field_name = field_patterns.iter().any(|field| field.is_shorthand);
             if has_shorthand_field_name {
-                let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
-                    s.print_qpath(qpath, false)
-                });
+                let path = rustc_hir_pretty::qpath_to_string(qpath);
                 let mut err = struct_span_err!(
                     self.tcx.sess,
                     pat.span,
@@ -1688,9 +1686,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 return None;
             }
 
-            let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
-                s.print_qpath(qpath, false)
-            });
+            let path = rustc_hir_pretty::qpath_to_string(qpath);
             let mut err = struct_span_err!(
                 self.tcx.sess,
                 pat.span,
@@ -1740,9 +1736,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                             f
                         }
                     }
-                    Err(_) => rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
-                        s.print_pat(field.pat)
-                    }),
+                    Err(_) => rustc_hir_pretty::pat_to_string(field.pat),
                 }
             })
             .collect::<Vec<String>>()
diff --git a/compiler/rustc_incremental/messages.ftl b/compiler/rustc_incremental/messages.ftl
index 9fa4e0fb27c..5d885e07192 100644
--- a/compiler/rustc_incremental/messages.ftl
+++ b/compiler/rustc_incremental/messages.ftl
@@ -46,8 +46,6 @@ incremental_delete_partial = failed to delete partly initialized session dir `{$
 
 incremental_delete_workproduct = file-system error deleting outdated file `{$path}`: {$err}
 
-incremental_field_associated_value_expected = associated value expected for `{$name}`
-
 incremental_finalize = error finalizing incremental compilation session directory `{$path}`: {$err}
 
 incremental_finalized_gc_failed =
@@ -63,25 +61,15 @@ incremental_load_dep_graph = could not load dep-graph from `{$path}`: {$err}
 
 incremental_lock_unsupported =
     the filesystem for the incremental path at {$session_dir} does not appear to support locking, consider changing the incremental path to a filesystem that supports locking or disable incremental compilation
-incremental_malformed_cgu_name =
-    found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case).
 
 incremental_missing_depnode = missing `DepNode` variant
 
 incremental_missing_if_this_changed = no `#[rustc_if_this_changed]` annotation detected
 
-incremental_missing_query_depgraph =
-    found CGU-reuse attribute but `-Zquery-dep-graph` was not specified
-
 incremental_move_dep_graph = failed to move dependency graph from `{$from}` to `{$to}`: {$err}
 
 incremental_no_cfg = no cfg attribute
 
-incremental_no_field = no field `{$name}`
-
-incremental_no_module_named =
-    no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names}
-
 incremental_no_path = no path from `{$source}` to `{$target}`
 
 incremental_not_clean = `{$dep_node_str}` should be clean but is not
@@ -107,8 +95,6 @@ incremental_undefined_clean_dirty_assertions_item =
 
 incremental_unknown_item = unknown item `{$name}`
 
-incremental_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified
-
 incremental_unrecognized_depnode = unrecognized `DepNode` variant: {$name}
 
 incremental_unrecognized_depnode_label = dep-node label `{$label}` not recognized
diff --git a/compiler/rustc_incremental/src/errors.rs b/compiler/rustc_incremental/src/errors.rs
index deb87678365..05ed4f7598d 100644
--- a/compiler/rustc_incremental/src/errors.rs
+++ b/compiler/rustc_incremental/src/errors.rs
@@ -41,56 +41,6 @@ pub struct NoPath {
 }
 
 #[derive(Diagnostic)]
-#[diag(incremental_unknown_reuse_kind)]
-pub struct UnknownReuseKind {
-    #[primary_span]
-    pub span: Span,
-    pub kind: Symbol,
-}
-
-#[derive(Diagnostic)]
-#[diag(incremental_missing_query_depgraph)]
-pub struct MissingQueryDepGraph {
-    #[primary_span]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(incremental_malformed_cgu_name)]
-pub struct MalformedCguName {
-    #[primary_span]
-    pub span: Span,
-    pub user_path: String,
-    pub crate_name: String,
-}
-
-#[derive(Diagnostic)]
-#[diag(incremental_no_module_named)]
-pub struct NoModuleNamed<'a> {
-    #[primary_span]
-    pub span: Span,
-    pub user_path: &'a str,
-    pub cgu_name: Symbol,
-    pub cgu_names: String,
-}
-
-#[derive(Diagnostic)]
-#[diag(incremental_field_associated_value_expected)]
-pub struct FieldAssociatedValueExpected {
-    #[primary_span]
-    pub span: Span,
-    pub name: Symbol,
-}
-
-#[derive(Diagnostic)]
-#[diag(incremental_no_field)]
-pub struct NoField {
-    #[primary_span]
-    pub span: Span,
-    pub name: Symbol,
-}
-
-#[derive(Diagnostic)]
 #[diag(incremental_assertion_auto)]
 pub struct AssertionAuto<'a> {
     #[primary_span]
diff --git a/compiler/rustc_incremental/src/lib.rs b/compiler/rustc_incremental/src/lib.rs
index 472dfe3dc4a..bdae07a3946 100644
--- a/compiler/rustc_incremental/src/lib.rs
+++ b/compiler/rustc_incremental/src/lib.rs
@@ -16,7 +16,6 @@ extern crate rustc_middle;
 extern crate tracing;
 
 mod assert_dep_graph;
-pub mod assert_module_sources;
 mod errors;
 mod persist;
 
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 0e8f93cef17..718dbaaafcc 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -957,10 +957,9 @@ pub fn start_codegen<'tcx>(
         codegen_backend.codegen_crate(tcx, metadata, need_metadata_module)
     });
 
-    // Don't run these test assertions when not doing codegen. Compiletest tries to build
+    // Don't run this test assertions when not doing codegen. Compiletest tries to build
     // build-fail tests in check mode first and expects it to not give an error in that case.
     if tcx.sess.opts.output_types.should_codegen() {
-        rustc_incremental::assert_module_sources::assert_module_sources(tcx);
         rustc_symbol_mangling::test::report_symbol_names(tcx);
     }
 
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 3c5cde4309b..1ed9b86d66c 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -727,11 +727,14 @@ pub trait LintContext: Sized {
                                 .collect::<Vec<_>>();
                             possibilities.sort();
 
+                            let mut should_print_possibilities = true;
                             if let Some((value, value_span)) = value {
                                 if best_match_values.contains(&Some(value)) {
                                     db.span_suggestion(name_span, "there is a config with a similar name and value", best_match, Applicability::MaybeIncorrect);
+                                    should_print_possibilities = false;
                                 } else if best_match_values.contains(&None) {
                                     db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and no value", best_match, Applicability::MaybeIncorrect);
+                                    should_print_possibilities = false;
                                 } else if let Some(first_value) = possibilities.first() {
                                     db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", format!("{best_match} = \"{first_value}\""), Applicability::MaybeIncorrect);
                                 } else {
@@ -741,13 +744,25 @@ pub trait LintContext: Sized {
                                 db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect);
                             }
 
-                            if !possibilities.is_empty() {
+                            if !possibilities.is_empty() && should_print_possibilities {
                                 let possibilities = possibilities.join("`, `");
                                 db.help(format!("expected values for `{best_match}` are: `{possibilities}`"));
                             }
                         } else {
                             db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect);
                         }
+                    } else if !possibilities.is_empty() {
+                        let mut possibilities = possibilities.iter()
+                            .map(Symbol::as_str)
+                            .collect::<Vec<_>>();
+                        possibilities.sort();
+                        let possibilities = possibilities.join("`, `");
+
+                        // The list of expected names can be long (even by default) and
+                        // so the diagnostic produced can take a lot of space. To avoid
+                        // cloging the user output we only want to print that diagnostic
+                        // once.
+                        db.help_once(format!("expected names are: `{possibilities}`"));
                     }
                 },
                 BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => {
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index d7a666aa72b..b9e455e6c2a 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -506,6 +506,11 @@ fn register_builtins(store: &mut LintStore) {
         "replaced with another group of lints, see RFC \
          <https://rust-lang.github.io/rfcs/2145-type-privacy.html> for more information",
     );
+    store.register_removed(
+        "invalid_alignment",
+        "converted into hard error, see PR #104616 \
+         <https://github.com/rust-lang/rust/pull/104616> for more information",
+    );
 }
 
 fn register_internals(store: &mut LintStore) {
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 69b462d32bd..0e242c1febd 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -987,45 +987,6 @@ declare_lint! {
 }
 
 declare_lint! {
-    /// The `invalid_alignment` lint detects dereferences of misaligned pointers during
-    /// constant evaluation.
-    ///
-    /// ### Example
-    ///
-    /// ```rust,compile_fail
-    /// #![feature(const_mut_refs)]
-    /// const FOO: () = unsafe {
-    ///     let x = &[0_u8; 4];
-    ///     let y = x.as_ptr().cast::<u32>();
-    ///     let mut z = 123;
-    ///     y.copy_to_nonoverlapping(&mut z, 1); // the address of a `u8` array is unknown
-    ///     // and thus we don't know if it is aligned enough for copying a `u32`.
-    /// };
-    /// ```
-    ///
-    /// {{produces}}
-    ///
-    /// ### Explanation
-    ///
-    /// The compiler allowed dereferencing raw pointers irrespective of alignment
-    /// during const eval due to the const evaluator at the time not making it easy
-    /// or cheap to check. Now that it is both, this is not accepted anymore.
-    ///
-    /// Since it was undefined behaviour to begin with, this breakage does not violate
-    /// Rust's stability guarantees. Using undefined behaviour can cause arbitrary
-    /// behaviour, including failure to build.
-    ///
-    /// [future-incompatible]: ../index.md#future-incompatible-lints
-    pub INVALID_ALIGNMENT,
-    Deny,
-    "raw pointers must be aligned before dereferencing",
-    @future_incompatible = FutureIncompatibleInfo {
-        reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps,
-        reference: "issue #68585 <https://github.com/rust-lang/rust/issues/104616>",
-    };
-}
-
-declare_lint! {
     /// The `exported_private_dependencies` lint detects private dependencies
     /// that are exposed in a public interface.
     ///
@@ -3430,7 +3391,6 @@ declare_lint_pass! {
         INDIRECT_STRUCTURAL_MATCH,
         INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
         INLINE_NO_SANITIZE,
-        INVALID_ALIGNMENT,
         INVALID_DOC_ATTRIBUTES,
         INVALID_MACRO_EXPORT_ARGUMENTS,
         INVALID_TYPE_PARAM_DEFAULT,
diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
index 3ee4befa121..a7a000ba31c 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
@@ -39,8 +39,8 @@
 //!
 //! Splitting is implemented in the [`Constructor::split`] function. We don't do splitting for
 //! or-patterns; instead we just try the alternatives one-by-one. For details on splitting
-//! wildcards, see [`SplitWildcard`]; for integer ranges, see [`SplitIntRange`]; for slices, see
-//! [`SplitVarLenSlice`].
+//! wildcards, see [`Constructor::split`]; for integer ranges, see
+//! [`IntRange::split`]; for slices, see [`Slice::split`].
 
 use std::cell::Cell;
 use std::cmp::{self, max, min, Ordering};
@@ -52,6 +52,7 @@ use smallvec::{smallvec, SmallVec};
 
 use rustc_apfloat::ieee::{DoubleS, IeeeFloat, SingleS};
 use rustc_data_structures::captures::Captures;
+use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::{HirId, RangeEnd};
 use rustc_index::Idx;
 use rustc_middle::middle::stability::EvalResult;
@@ -86,6 +87,13 @@ fn expand_or_pat<'p, 'tcx>(pat: &'p Pat<'tcx>) -> Vec<&'p Pat<'tcx>> {
     pats
 }
 
+/// Whether we have seen a constructor in the column or not.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+enum Presence {
+    Unseen,
+    Seen,
+}
+
 /// An inclusive interval, used for precise integer exhaustiveness checking.
 /// `IntRange`s always store a contiguous range. This means that values are
 /// encoded such that `0` encodes the minimum value for the integer,
@@ -186,7 +194,105 @@ impl IntRange {
         (lo == other_hi || hi == other_lo) && !self.is_singleton() && !other.is_singleton()
     }
 
-    /// Only used for displaying the range properly.
+    /// Partition a range of integers into disjoint subranges. This does constructor splitting for
+    /// integer ranges as explained at the top of the file.
+    ///
+    /// This returns an output that covers `self`. The output is split so that the only
+    /// intersections between an output range and a column range are inclusions. No output range
+    /// straddles the boundary of one of the inputs.
+    ///
+    /// Additionally, we track for each output range whether it is covered by one of the column ranges or not.
+    ///
+    /// The following input:
+    /// ```text
+    ///   (--------------------------) // `self`
+    /// (------) (----------)    (-)
+    ///     (------) (--------)
+    /// ```
+    /// is first intersected with `self`:
+    /// ```text
+    ///   (--------------------------) // `self`
+    ///   (----) (----------)    (-)
+    ///     (------) (--------)
+    /// ```
+    /// and then iterated over as follows:
+    /// ```text
+    ///   (-(--)-(-)-(------)-)--(-)-
+    /// ```
+    /// where each sequence of dashes is an output range, and dashes outside parentheses are marked
+    /// as `Presence::Missing`.
+    fn split(
+        &self,
+        column_ranges: impl Iterator<Item = IntRange>,
+    ) -> impl Iterator<Item = (Presence, IntRange)> {
+        /// Represents a boundary between 2 integers. Because the intervals spanning boundaries must be
+        /// able to cover every integer, we need to be able to represent 2^128 + 1 such boundaries.
+        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+        enum IntBoundary {
+            JustBefore(u128),
+            AfterMax,
+        }
+
+        fn unpack_intrange(range: IntRange) -> [IntBoundary; 2] {
+            use IntBoundary::*;
+            let (lo, hi) = range.boundaries();
+            let lo = JustBefore(lo);
+            let hi = match hi.checked_add(1) {
+                Some(m) => JustBefore(m),
+                None => AfterMax,
+            };
+            [lo, hi]
+        }
+
+        // The boundaries of ranges in `column_ranges` intersected with `self`.
+        // We do parenthesis matching for input ranges. A boundary counts as +1 if it starts
+        // a range and -1 if it ends it. When the count is > 0 between two boundaries, we
+        // are within an input range.
+        let mut boundaries: Vec<(IntBoundary, isize)> = column_ranges
+            .filter_map(|r| self.intersection(&r))
+            .map(unpack_intrange)
+            .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)])
+            .collect();
+        // We sort by boundary, and for each boundary we sort the "closing parentheses" first. The
+        // order of +1/-1 for a same boundary value is actually irrelevant, because we only look at
+        // the accumulated count between distinct boundary values.
+        boundaries.sort_unstable();
+
+        let [self_start, self_end] = unpack_intrange(self.clone());
+        // Accumulate parenthesis counts.
+        let mut paren_counter = 0isize;
+        // Gather pairs of adjacent boundaries.
+        let mut prev_bdy = self_start;
+        boundaries
+            .into_iter()
+            // End with the end of the range. The count is ignored.
+            .chain(once((self_end, 0)))
+            // List pairs of adjacent boundaries and the count between them.
+            .map(move |(bdy, delta)| {
+                // `delta` affects the count as we cross `bdy`, so the relevant count between
+                // `prev_bdy` and `bdy` is untouched by `delta`.
+                let ret = (prev_bdy, paren_counter, bdy);
+                prev_bdy = bdy;
+                paren_counter += delta;
+                ret
+            })
+            // Skip empty ranges.
+            .filter(|&(prev_bdy, _, bdy)| prev_bdy != bdy)
+            // Convert back to ranges.
+            .map(move |(prev_bdy, paren_count, bdy)| {
+                use IntBoundary::*;
+                use Presence::*;
+                let presence = if paren_count > 0 { Seen } else { Unseen };
+                let range = match (prev_bdy, bdy) {
+                    (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1),
+                    (JustBefore(n), AfterMax) => n..=u128::MAX,
+                    _ => unreachable!(), // Ruled out by the sorting and filtering we did
+                };
+                (presence, IntRange { range })
+            })
+    }
+
+    /// Only used for displaying the range.
     fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
         let (lo, hi) = self.boundaries();
 
@@ -254,18 +360,6 @@ impl IntRange {
             );
         }
     }
-
-    /// See `Constructor::is_covered_by`
-    fn is_covered_by(&self, other: &Self) -> bool {
-        if self.intersection(other).is_some() {
-            // Constructor splitting should ensure that all intersections we encounter are actually
-            // inclusions.
-            assert!(self.is_subrange(other));
-            true
-        } else {
-            false
-        }
-    }
 }
 
 /// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and
@@ -279,101 +373,6 @@ impl fmt::Debug for IntRange {
     }
 }
 
-/// Represents a border between 2 integers. Because the intervals spanning borders must be able to
-/// cover every integer, we need to be able to represent 2^128 + 1 such borders.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-enum IntBorder {
-    JustBefore(u128),
-    AfterMax,
-}
-
-/// A range of integers that is partitioned into disjoint subranges. This does constructor
-/// splitting for integer ranges as explained at the top of the file.
-///
-/// This is fed multiple ranges, and returns an output that covers the input, but is split so that
-/// the only intersections between an output range and a seen range are inclusions. No output range
-/// straddles the boundary of one of the inputs.
-///
-/// The following input:
-/// ```text
-///   |-------------------------| // `self`
-/// |------|  |----------|   |----|
-///    |-------| |-------|
-/// ```
-/// would be iterated over as follows:
-/// ```text
-///   ||---|--||-|---|---|---|--|
-/// ```
-#[derive(Debug, Clone)]
-struct SplitIntRange {
-    /// The range we are splitting
-    range: IntRange,
-    /// The borders of ranges we have seen. They are all contained within `range`. This is kept
-    /// sorted.
-    borders: Vec<IntBorder>,
-}
-
-impl SplitIntRange {
-    fn new(range: IntRange) -> Self {
-        SplitIntRange { range, borders: Vec::new() }
-    }
-
-    /// Internal use
-    fn to_borders(r: IntRange) -> [IntBorder; 2] {
-        use IntBorder::*;
-        let (lo, hi) = r.boundaries();
-        let lo = JustBefore(lo);
-        let hi = match hi.checked_add(1) {
-            Some(m) => JustBefore(m),
-            None => AfterMax,
-        };
-        [lo, hi]
-    }
-
-    /// Add ranges relative to which we split.
-    fn split(&mut self, ranges: impl Iterator<Item = IntRange>) {
-        let this_range = &self.range;
-        let included_ranges = ranges.filter_map(|r| this_range.intersection(&r));
-        let included_borders = included_ranges.flat_map(|r| {
-            let borders = Self::to_borders(r);
-            once(borders[0]).chain(once(borders[1]))
-        });
-        self.borders.extend(included_borders);
-        self.borders.sort_unstable();
-    }
-
-    /// Iterate over the contained ranges.
-    fn iter(&self) -> impl Iterator<Item = IntRange> + Captures<'_> {
-        use IntBorder::*;
-
-        let self_range = Self::to_borders(self.range.clone());
-        // Start with the start of the range.
-        let mut prev_border = self_range[0];
-        self.borders
-            .iter()
-            .copied()
-            // End with the end of the range.
-            .chain(once(self_range[1]))
-            // List pairs of adjacent borders.
-            .map(move |border| {
-                let ret = (prev_border, border);
-                prev_border = border;
-                ret
-            })
-            // Skip duplicates.
-            .filter(|(prev_border, border)| prev_border != border)
-            // Finally, convert to ranges.
-            .map(move |(prev_border, border)| {
-                let range = match (prev_border, border) {
-                    (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1),
-                    (JustBefore(n), AfterMax) => n..=u128::MAX,
-                    _ => unreachable!(), // Ruled out by the sorting and filtering we did
-                };
-                IntRange { range }
-            })
-    }
-}
-
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 enum SliceKind {
     /// Patterns of length `n` (`[x, y]`).
@@ -430,142 +429,164 @@ impl Slice {
     fn is_covered_by(self, other: Self) -> bool {
         other.kind.covers_length(self.arity())
     }
-}
 
-/// This computes constructor splitting for variable-length slices, as explained at the top of the
-/// file.
-///
-/// A slice pattern `[x, .., y]` behaves like the infinite or-pattern `[x, y] | [x, _, y] | [x, _,
-/// _, y] | ...`. The corresponding value constructors are fixed-length array constructors above a
-/// given minimum length. We obviously can't list this infinitude of constructors. Thankfully,
-/// it turns out that for each finite set of slice patterns, all sufficiently large array lengths
-/// are equivalent.
-///
-/// Let's look at an example, where we are trying to split the last pattern:
-/// ```
-/// # fn foo(x: &[bool]) {
-/// match x {
-///     [true, true, ..] => {}
-///     [.., false, false] => {}
-///     [..] => {}
-/// }
-/// # }
-/// ```
-/// Here are the results of specialization for the first few lengths:
-/// ```
-/// # fn foo(x: &[bool]) { match x {
-/// // length 0
-/// [] => {}
-/// // length 1
-/// [_] => {}
-/// // length 2
-/// [true, true] => {}
-/// [false, false] => {}
-/// [_, _] => {}
-/// // length 3
-/// [true, true,  _    ] => {}
-/// [_,    false, false] => {}
-/// [_,    _,     _    ] => {}
-/// // length 4
-/// [true, true, _,     _    ] => {}
-/// [_,    _,    false, false] => {}
-/// [_,    _,    _,     _    ] => {}
-/// // length 5
-/// [true, true, _, _,     _    ] => {}
-/// [_,    _,    _, false, false] => {}
-/// [_,    _,    _, _,     _    ] => {}
-/// # _ => {}
-/// # }}
-/// ```
-///
-/// If we went above length 5, we would simply be inserting more columns full of wildcards in the
-/// middle. This means that the set of witnesses for length `l >= 5` if equivalent to the set for
-/// any other `l' >= 5`: simply add or remove wildcards in the middle to convert between them.
-///
-/// This applies to any set of slice patterns: there will be a length `L` above which all lengths
-/// behave the same. This is exactly what we need for constructor splitting. Therefore a
-/// variable-length slice can be split into a variable-length slice of minimal length `L`, and many
-/// fixed-length slices of lengths `< L`.
-///
-/// For each variable-length pattern `p` with a prefix of length `plₚ` and suffix of length `slₚ`,
-/// only the first `plₚ` and the last `slₚ` elements are examined. Therefore, as long as `L` is
-/// positive (to avoid concerns about empty types), all elements after the maximum prefix length
-/// and before the maximum suffix length are not examined by any variable-length pattern, and
-/// therefore can be added/removed without affecting them - creating equivalent patterns from any
-/// sufficiently-large length.
-///
-/// Of course, if fixed-length patterns exist, we must be sure that our length is large enough to
-/// miss them all, so we can pick `L = max(max(FIXED_LEN)+1, max(PREFIX_LEN) + max(SUFFIX_LEN))`
-///
-/// `max_slice` below will be made to have arity `L`.
-#[derive(Debug)]
-struct SplitVarLenSlice {
-    /// If the type is an array, this is its size.
-    array_len: Option<usize>,
-    /// The arity of the input slice.
-    arity: usize,
-    /// The smallest slice bigger than any slice seen. `max_slice.arity()` is the length `L`
-    /// described above.
-    max_slice: SliceKind,
-}
-
-impl SplitVarLenSlice {
-    fn new(prefix: usize, suffix: usize, array_len: Option<usize>) -> Self {
-        SplitVarLenSlice { array_len, arity: prefix + suffix, max_slice: VarLen(prefix, suffix) }
-    }
-
-    /// Pass a set of slices relative to which to split this one.
-    fn split(&mut self, slices: impl Iterator<Item = SliceKind>) {
-        let VarLen(max_prefix_len, max_suffix_len) = &mut self.max_slice else {
-            // No need to split
-            return;
-        };
-        // We grow `self.max_slice` to be larger than all slices encountered, as described above.
-        // For diagnostics, we keep the prefix and suffix lengths separate, but grow them so that
-        // `L = max_prefix_len + max_suffix_len`.
-        let mut max_fixed_len = 0;
-        for slice in slices {
-            match slice {
-                FixedLen(len) => {
-                    max_fixed_len = cmp::max(max_fixed_len, len);
+    /// This computes constructor splitting for variable-length slices, as explained at the top of
+    /// the file.
+    ///
+    /// A slice pattern `[x, .., y]` behaves like the infinite or-pattern `[x, y] | [x, _, y] | [x,
+    /// _, _, y] | etc`. The corresponding value constructors are fixed-length array constructors of
+    /// corresponding lengths. We obviously can't list this infinitude of constructors.
+    /// Thankfully, it turns out that for each finite set of slice patterns, all sufficiently large
+    /// array lengths are equivalent.
+    ///
+    /// Let's look at an example, where we are trying to split the last pattern:
+    /// ```
+    /// # fn foo(x: &[bool]) {
+    /// match x {
+    ///     [true, true, ..] => {}
+    ///     [.., false, false] => {}
+    ///     [..] => {}
+    /// }
+    /// # }
+    /// ```
+    /// Here are the results of specialization for the first few lengths:
+    /// ```
+    /// # fn foo(x: &[bool]) { match x {
+    /// // length 0
+    /// [] => {}
+    /// // length 1
+    /// [_] => {}
+    /// // length 2
+    /// [true, true] => {}
+    /// [false, false] => {}
+    /// [_, _] => {}
+    /// // length 3
+    /// [true, true,  _    ] => {}
+    /// [_,    false, false] => {}
+    /// [_,    _,     _    ] => {}
+    /// // length 4
+    /// [true, true, _,     _    ] => {}
+    /// [_,    _,    false, false] => {}
+    /// [_,    _,    _,     _    ] => {}
+    /// // length 5
+    /// [true, true, _, _,     _    ] => {}
+    /// [_,    _,    _, false, false] => {}
+    /// [_,    _,    _, _,     _    ] => {}
+    /// # _ => {}
+    /// # }}
+    /// ```
+    ///
+    /// We see that above length 4, we are simply inserting columns full of wildcards in the middle.
+    /// This means that specialization and witness computation with slices of length `l >= 4` will
+    /// give equivalent results regardless of `l`. This applies to any set of slice patterns: there
+    /// will be a length `L` above which all lengths behave the same. This is exactly what we need
+    /// for constructor splitting.
+    ///
+    /// A variable-length slice pattern covers all lengths from its arity up to infinity. As we just
+    /// saw, we can split this in two: lengths below `L` are treated individually with a
+    /// fixed-length slice each; lengths above `L` are grouped into a single variable-length slice
+    /// constructor.
+    ///
+    /// For each variable-length slice pattern `p` with a prefix of length `plₚ` and suffix of
+    /// length `slₚ`, only the first `plₚ` and the last `slₚ` elements are examined. Therefore, as
+    /// long as `L` is positive (to avoid concerns about empty types), all elements after the
+    /// maximum prefix length and before the maximum suffix length are not examined by any
+    /// variable-length pattern, and therefore can be ignored. This gives us a way to compute `L`.
+    ///
+    /// Additionally, if fixed-length patterns exist, we must pick an `L` large enough to miss them,
+    /// so we can pick `L = max(max(FIXED_LEN)+1, max(PREFIX_LEN) + max(SUFFIX_LEN))`.
+    /// `max_slice` below will be made to have this arity `L`.
+    ///
+    /// If `self` is fixed-length, it is returned as-is.
+    ///
+    /// Additionally, we track for each output slice whether it is covered by one of the column slices or not.
+    fn split(
+        self,
+        column_slices: impl Iterator<Item = Slice>,
+    ) -> impl Iterator<Item = (Presence, Slice)> {
+        // Range of lengths below `L`.
+        let smaller_lengths;
+        let arity = self.arity();
+        let mut max_slice = self.kind;
+        // Tracks the smallest variable-length slice we've seen. Any slice arity above it is
+        // therefore `Presence::Seen` in the column.
+        let mut min_var_len = usize::MAX;
+        // Tracks the fixed-length slices we've seen, to mark them as `Presence::Seen`.
+        let mut seen_fixed_lens = FxHashSet::default();
+        match &mut max_slice {
+            VarLen(max_prefix_len, max_suffix_len) => {
+                // We grow `max_slice` to be larger than all slices encountered, as described above.
+                // For diagnostics, we keep the prefix and suffix lengths separate, but grow them so that
+                // `L = max_prefix_len + max_suffix_len`.
+                let mut max_fixed_len = 0;
+                for slice in column_slices {
+                    match slice.kind {
+                        FixedLen(len) => {
+                            max_fixed_len = cmp::max(max_fixed_len, len);
+                            if arity <= len {
+                                seen_fixed_lens.insert(len);
+                            }
+                        }
+                        VarLen(prefix, suffix) => {
+                            *max_prefix_len = cmp::max(*max_prefix_len, prefix);
+                            *max_suffix_len = cmp::max(*max_suffix_len, suffix);
+                            min_var_len = cmp::min(min_var_len, prefix + suffix);
+                        }
+                    }
                 }
-                VarLen(prefix, suffix) => {
-                    *max_prefix_len = cmp::max(*max_prefix_len, prefix);
-                    *max_suffix_len = cmp::max(*max_suffix_len, suffix);
+                // We want `L = max(L, max_fixed_len + 1)`, modulo the fact that we keep prefix and
+                // suffix separate.
+                if max_fixed_len + 1 >= *max_prefix_len + *max_suffix_len {
+                    // The subtraction can't overflow thanks to the above check.
+                    // The new `max_prefix_len` is larger than its previous value.
+                    *max_prefix_len = max_fixed_len + 1 - *max_suffix_len;
                 }
-            }
-        }
-        // We want `L = max(L, max_fixed_len + 1)`, modulo the fact that we keep prefix and
-        // suffix separate.
-        if max_fixed_len + 1 >= *max_prefix_len + *max_suffix_len {
-            // The subtraction can't overflow thanks to the above check.
-            // The new `max_prefix_len` is larger than its previous value.
-            *max_prefix_len = max_fixed_len + 1 - *max_suffix_len;
-        }
 
-        // We cap the arity of `max_slice` at the array size.
-        match self.array_len {
-            Some(len) if self.max_slice.arity() >= len => self.max_slice = FixedLen(len),
-            _ => {}
-        }
-    }
+                // We cap the arity of `max_slice` at the array size.
+                match self.array_len {
+                    Some(len) if max_slice.arity() >= len => max_slice = FixedLen(len),
+                    _ => {}
+                }
 
-    /// Iterate over the partition of this slice.
-    fn iter(&self) -> impl Iterator<Item = Slice> + Captures<'_> {
-        let smaller_lengths = match self.array_len {
-            // The only admissible fixed-length slice is one of the array size. Whether `max_slice`
-            // is fixed-length or variable-length, it will be the only relevant slice to output
-            // here.
-            Some(_) => 0..0, // empty range
-            // We cover all arities in the range `(self.arity..infinity)`. We split that range into
-            // two: lengths smaller than `max_slice.arity()` are treated independently as
-            // fixed-lengths slices, and lengths above are captured by `max_slice`.
-            None => self.arity..self.max_slice.arity(),
+                smaller_lengths = match self.array_len {
+                    // The only admissible fixed-length slice is one of the array size. Whether `max_slice`
+                    // is fixed-length or variable-length, it will be the only relevant slice to output
+                    // here.
+                    Some(_) => 0..0, // empty range
+                    // We need to cover all arities in the range `(arity..infinity)`. We split that
+                    // range into two: lengths smaller than `max_slice.arity()` are treated
+                    // independently as fixed-lengths slices, and lengths above are captured by
+                    // `max_slice`.
+                    None => self.arity()..max_slice.arity(),
+                };
+            }
+            FixedLen(_) => {
+                // No need to split here. We only track presence.
+                for slice in column_slices {
+                    match slice.kind {
+                        FixedLen(len) => {
+                            if len == arity {
+                                seen_fixed_lens.insert(len);
+                            }
+                        }
+                        VarLen(prefix, suffix) => {
+                            min_var_len = cmp::min(min_var_len, prefix + suffix);
+                        }
+                    }
+                }
+                smaller_lengths = 0..0;
+            }
         };
-        smaller_lengths
-            .map(FixedLen)
-            .chain(once(self.max_slice))
-            .map(move |kind| Slice::new(self.array_len, kind))
+
+        smaller_lengths.map(FixedLen).chain(once(max_slice)).map(move |kind| {
+            let arity = kind.arity();
+            let seen = if min_var_len <= arity || seen_fixed_lens.contains(&arity) {
+                Presence::Seen
+            } else {
+                Presence::Unseen
+            };
+            (seen, Slice::new(self.array_len, kind))
+        })
     }
 }
 
@@ -596,19 +617,23 @@ pub(super) enum Constructor<'tcx> {
     /// boxes for the purposes of exhaustiveness: we must not inspect them, and they
     /// don't count towards making a match exhaustive.
     Opaque,
+    /// Or-pattern.
+    Or,
+    /// Wildcard pattern.
+    Wildcard,
     /// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used
     /// for those types for which we cannot list constructors explicitly, like `f64` and `str`.
     NonExhaustive,
-    /// Stands for constructors that are not seen in the matrix, as explained in the documentation
-    /// for [`SplitWildcard`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns`
-    /// lint.
+    /// Fake extra constructor for variants that should not be mentioned in diagnostics.
+    /// We use this for variants behind an unstable gate as well as
+    /// `#[doc(hidden)]` ones.
+    Hidden,
+    /// Fake extra constructor for constructors that are not seen in the matrix, as explained in the
+    /// code for [`Constructor::split`]. The carried `bool` is used for the
+    /// `non_exhaustive_omitted_patterns` lint.
     Missing {
-        nonexhaustive_enum_missing_real_variants: bool,
+        nonexhaustive_enum_missing_visible_variants: bool,
     },
-    /// Wildcard pattern.
-    Wildcard,
-    /// Or-pattern.
-    Or,
 }
 
 impl<'tcx> Constructor<'tcx> {
@@ -620,13 +645,18 @@ impl<'tcx> Constructor<'tcx> {
         matches!(self, NonExhaustive)
     }
 
+    pub(super) fn as_variant(&self) -> Option<VariantIdx> {
+        match self {
+            Variant(i) => Some(*i),
+            _ => None,
+        }
+    }
     fn as_int_range(&self) -> Option<&IntRange> {
         match self {
             IntRange(range) => Some(range),
             _ => None,
         }
     }
-
     fn as_slice(&self) -> Option<Slice> {
         match self {
             Slice(slice) => Some(*slice),
@@ -634,32 +664,6 @@ impl<'tcx> Constructor<'tcx> {
         }
     }
 
-    /// Checks if the `Constructor` is a variant and `TyCtxt::eval_stability` returns
-    /// `EvalResult::Deny { .. }`.
-    ///
-    /// This means that the variant has a stdlib unstable feature marking it.
-    pub(super) fn is_unstable_variant(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool {
-        if let Constructor::Variant(idx) = self && let ty::Adt(adt, _) = pcx.ty.kind() {
-            let variant_def_id = adt.variant(*idx).def_id;
-            // Filter variants that depend on a disabled unstable feature.
-            return matches!(
-                pcx.cx.tcx.eval_stability(variant_def_id, None, DUMMY_SP, None),
-                EvalResult::Deny { .. }
-            );
-        }
-        false
-    }
-
-    /// Checks if the `Constructor` is a `Constructor::Variant` with a `#[doc(hidden)]`
-    /// attribute from a type not local to the current crate.
-    pub(super) fn is_doc_hidden_variant(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool {
-        if let Constructor::Variant(idx) = self && let ty::Adt(adt, _) = pcx.ty.kind() {
-            let variant_def_id = adt.variants()[*idx].def_id;
-            return pcx.cx.tcx.is_doc_hidden(variant_def_id) && !variant_def_id.is_local();
-        }
-        false
-    }
-
     fn variant_index_for_adt(&self, adt: ty::AdtDef<'tcx>) -> VariantIdx {
         match *self {
             Variant(idx) => idx,
@@ -695,27 +699,28 @@ impl<'tcx> Constructor<'tcx> {
             | F32Range(..)
             | F64Range(..)
             | IntRange(..)
-            | NonExhaustive
             | Opaque
+            | NonExhaustive
+            | Hidden
             | Missing { .. }
             | Wildcard => 0,
             Or => bug!("The `Or` constructor doesn't have a fixed arity"),
         }
     }
 
-    /// Some constructors (namely `Wildcard`, `IntRange` and `Slice`) actually stand for a set of actual
-    /// constructors (like variants, integers or fixed-sized slices). When specializing for these
-    /// constructors, we want to be specialising for the actual underlying constructors.
+    /// Some constructors (namely `Wildcard`, `IntRange` and `Slice`) actually stand for a set of
+    /// actual constructors (like variants, integers or fixed-sized slices). When specializing for
+    /// these constructors, we want to be specialising for the actual underlying constructors.
     /// Naively, we would simply return the list of constructors they correspond to. We instead are
-    /// more clever: if there are constructors that we know will behave the same wrt the current
-    /// matrix, we keep them grouped. For example, all slices of a sufficiently large length
-    /// will either be all useful or all non-useful with a given matrix.
+    /// more clever: if there are constructors that we know will behave the same w.r.t. the current
+    /// matrix, we keep them grouped. For example, all slices of a sufficiently large length will
+    /// either be all useful or all non-useful with a given matrix.
     ///
     /// See the branches for details on how the splitting is done.
     ///
-    /// This function may discard some irrelevant constructors if this preserves behavior and
-    /// diagnostics. Eg. for the `_` case, we ignore the constructors already present in the
-    /// matrix, unless all of them are.
+    /// This function may discard some irrelevant constructors if this preserves behavior. Eg. for
+    /// the `_` case, we ignore the constructors already present in the column, unless all of them
+    /// are.
     pub(super) fn split<'a>(
         &self,
         pcx: &PatCtxt<'_, '_, 'tcx>,
@@ -726,23 +731,74 @@ impl<'tcx> Constructor<'tcx> {
     {
         match self {
             Wildcard => {
-                let mut split_wildcard = SplitWildcard::new(pcx);
-                split_wildcard.split(pcx, ctors);
-                split_wildcard.into_ctors(pcx)
+                let split_set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, ctors);
+                if !split_set.missing.is_empty() {
+                    // We are splitting a wildcard in order to compute its usefulness. Some constructors are
+                    // not present in the column. The first thing we note is that specializing with any of
+                    // the missing constructors would select exactly the rows with wildcards. Moreover, they
+                    // would all return equivalent results. We can therefore group them all into a
+                    // fictitious `Missing` constructor.
+                    //
+                    // As an important optimization, this function will skip all the present constructors.
+                    // This is correct because specializing with any of the present constructors would
+                    // select a strict superset of the wildcard rows, and thus would only find witnesses
+                    // already found with the `Missing` constructor.
+                    // This does mean that diagnostics are incomplete: in
+                    // ```
+                    // match x {
+                    //   Some(true) => {}
+                    // }
+                    // ```
+                    // we report `None` as missing but not `Some(false)`.
+                    //
+                    // When all the constructors are missing we can equivalently return the `Wildcard`
+                    // constructor on its own. The difference between `Wildcard` and `Missing` will then
+                    // only be in diagnostics.
+
+                    // If some constructors are missing, we typically want to report those constructors,
+                    // e.g.:
+                    // ```
+                    //     enum Direction { N, S, E, W }
+                    //     let Direction::N = ...;
+                    // ```
+                    // we can report 3 witnesses: `S`, `E`, and `W`.
+                    //
+                    // However, if the user didn't actually specify a constructor
+                    // in this arm, e.g., in
+                    // ```
+                    //     let x: (Direction, Direction, bool) = ...;
+                    //     let (_, _, false) = x;
+                    // ```
+                    // we don't want to show all 16 possible witnesses `(<direction-1>, <direction-2>,
+                    // true)` - we are satisfied with `(_, _, true)`. So if all constructors are missing we
+                    // prefer to report just a wildcard `_`.
+                    //
+                    // The exception is: if we are at the top-level, for example in an empty match, we
+                    // usually prefer to report the full list of constructors.
+                    let all_missing = split_set.present.is_empty();
+                    let report_when_all_missing =
+                        pcx.is_top_level && !IntRange::is_integral(pcx.ty);
+                    let ctor = if all_missing && !report_when_all_missing {
+                        Wildcard
+                    } else {
+                        Missing {
+                            nonexhaustive_enum_missing_visible_variants: split_set
+                                .nonexhaustive_enum_missing_visible_variants,
+                        }
+                    };
+                    smallvec![ctor]
+                } else {
+                    split_set.present
+                }
             }
-            // Fast-track if the range is trivial. In particular, we don't do the overlapping
-            // ranges check.
-            IntRange(ctor_range) if !ctor_range.is_singleton() => {
-                let mut split_range = SplitIntRange::new(ctor_range.clone());
-                let int_ranges = ctors.filter_map(|ctor| ctor.as_int_range());
-                split_range.split(int_ranges.cloned());
-                split_range.iter().map(IntRange).collect()
+            // Fast-track if the range is trivial.
+            IntRange(this_range) if !this_range.is_singleton() => {
+                let column_ranges = ctors.filter_map(|ctor| ctor.as_int_range()).cloned();
+                this_range.split(column_ranges).map(|(_, range)| IntRange(range)).collect()
             }
-            &Slice(Slice { kind: VarLen(self_prefix, self_suffix), array_len }) => {
-                let mut split_self = SplitVarLenSlice::new(self_prefix, self_suffix, array_len);
-                let slices = ctors.filter_map(|c| c.as_slice()).map(|s| s.kind);
-                split_self.split(slices);
-                split_self.iter().map(Slice).collect()
+            Slice(this_slice @ Slice { kind: VarLen(..), .. }) => {
+                let column_slices = ctors.filter_map(|c| c.as_slice());
+                this_slice.split(column_slices).map(|(_, slice)| Slice(slice)).collect()
             }
             // Any other constructor can be used unchanged.
             _ => smallvec![self.clone()],
@@ -759,13 +815,13 @@ impl<'tcx> Constructor<'tcx> {
         match (self, other) {
             // Wildcards cover anything
             (_, Wildcard) => true,
-            // The missing ctors are not covered by anything in the matrix except wildcards.
-            (Missing { .. } | Wildcard, _) => false,
+            // Only a wildcard pattern can match these special constructors.
+            (Wildcard | Missing { .. } | NonExhaustive | Hidden, _) => false,
 
             (Single, Single) => true,
             (Variant(self_id), Variant(other_id)) => self_id == other_id,
 
-            (IntRange(self_range), IntRange(other_range)) => self_range.is_covered_by(other_range),
+            (IntRange(self_range), IntRange(other_range)) => self_range.is_subrange(other_range),
             (F32Range(self_from, self_to, self_end), F32Range(other_from, other_to, other_end)) => {
                 self_from.ge(other_from)
                     && match self_to.partial_cmp(other_to) {
@@ -791,8 +847,6 @@ impl<'tcx> Constructor<'tcx> {
 
             // We are trying to inspect an opaque constant. Thus we skip the row.
             (Opaque, _) | (_, Opaque) => false,
-            // Only a wildcard pattern can match the special extra constructor.
-            (NonExhaustive, _) => false,
 
             _ => span_bug!(
                 pcx.span,
@@ -802,96 +856,121 @@ impl<'tcx> Constructor<'tcx> {
             ),
         }
     }
+}
 
-    /// Faster version of `is_covered_by` when applied to many constructors. `used_ctors` is
-    /// assumed to be built from `matrix.head_ctors()` with wildcards and opaques filtered out,
-    /// and `self` is assumed to have been split from a wildcard.
-    fn is_covered_by_any<'p>(
-        &self,
-        pcx: &PatCtxt<'_, 'p, 'tcx>,
-        used_ctors: &[Constructor<'tcx>],
-    ) -> bool {
-        if used_ctors.is_empty() {
-            return false;
-        }
-
-        // This must be kept in sync with `is_covered_by`.
-        match self {
-            // If `self` is `Single`, `used_ctors` cannot contain anything else than `Single`s.
-            Single => !used_ctors.is_empty(),
-            Variant(vid) => used_ctors.iter().any(|c| matches!(c, Variant(i) if i == vid)),
-            IntRange(range) => used_ctors
-                .iter()
-                .filter_map(|c| c.as_int_range())
-                .any(|other| range.is_covered_by(other)),
-            Slice(slice) => used_ctors
-                .iter()
-                .filter_map(|c| c.as_slice())
-                .any(|other| slice.is_covered_by(other)),
-            // This constructor is never covered by anything else
-            NonExhaustive => false,
-            Str(..) | F32Range(..) | F64Range(..) | Opaque | Missing { .. } | Wildcard | Or => {
-                span_bug!(pcx.span, "found unexpected ctor in all_ctors: {:?}", self)
-            }
-        }
-    }
+/// Describes the set of all constructors for a type.
+#[derive(Debug)]
+pub(super) enum ConstructorSet {
+    /// The type has a single constructor, e.g. `&T` or a struct.
+    Single,
+    /// This type has the following list of constructors.
+    /// Some variants are hidden, which means they won't be mentioned in diagnostics unless the user
+    /// mentioned them first. We use this for variants behind an unstable gate as well as
+    /// `#[doc(hidden)]` ones.
+    Variants {
+        visible_variants: Vec<VariantIdx>,
+        hidden_variants: Vec<VariantIdx>,
+        non_exhaustive: bool,
+    },
+    /// The type is spanned by integer values. The range or ranges give the set of allowed values.
+    /// The second range is only useful for `char`.
+    /// This is reused for bool. FIXME: don't.
+    /// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's
+    /// for usize/isize).
+    Integers { range_1: IntRange, range_2: Option<IntRange>, non_exhaustive: bool },
+    /// The type is matched by slices. The usize is the compile-time length of the array, if known.
+    Slice(Option<usize>),
+    /// The type is matched by slices whose elements are uninhabited.
+    SliceOfEmpty,
+    /// The constructors cannot be listed, and the type cannot be matched exhaustively. E.g. `str`,
+    /// floats.
+    Unlistable,
+    /// The type has no inhabitants.
+    Uninhabited,
 }
 
-/// A wildcard constructor that we split relative to the constructors in the matrix, as explained
-/// at the top of the file.
+/// Describes the result of analyzing the constructors in a column of a match.
 ///
-/// A constructor that is not present in the matrix rows will only be covered by the rows that have
-/// wildcards. Thus we can group all of those constructors together; we call them "missing
-/// constructors". Splitting a wildcard would therefore list all present constructors individually
-/// (or grouped if they are integers or slices), and then all missing constructors together as a
-/// group.
+/// `present` is morally the set of constructors present in the column, and `missing` is the set of
+/// constructors that exist in the type but are not present in the column.
 ///
-/// However we can go further: since any constructor will match the wildcard rows, and having more
-/// rows can only reduce the amount of usefulness witnesses, we can skip the present constructors
-/// and only try the missing ones.
-/// This will not preserve the whole list of witnesses, but will preserve whether the list is empty
-/// or not. In fact this is quite natural from the point of view of diagnostics too. This is done
-/// in `to_ctors`: in some cases we only return `Missing`.
+/// More formally, they respect the following constraints:
+/// - the union of `present` and `missing` covers the whole type
+/// - `present` and `missing` are disjoint
+/// - neither contains wildcards
+/// - each constructor in `present` is covered by some non-wildcard constructor in the column
+/// - together, the constructors in `present` cover all the non-wildcard constructor in the column
+/// - non-wildcards in the column do no cover anything in `missing`
+/// - constructors in `present` and `missing` are split for the column; in other words, they are
+///     either fully included in or disjoint from each constructor in the column. This avoids
+///     non-trivial intersections like between `0..10` and `5..15`.
 #[derive(Debug)]
-pub(super) struct SplitWildcard<'tcx> {
-    /// Constructors (other than wildcards and opaques) seen in the matrix.
-    matrix_ctors: Vec<Constructor<'tcx>>,
-    /// All the constructors for this type
-    all_ctors: SmallVec<[Constructor<'tcx>; 1]>,
+struct SplitConstructorSet<'tcx> {
+    present: SmallVec<[Constructor<'tcx>; 1]>,
+    missing: Vec<Constructor<'tcx>>,
+    /// For the `non_exhaustive_omitted_patterns` lint.
+    nonexhaustive_enum_missing_visible_variants: bool,
 }
 
-impl<'tcx> SplitWildcard<'tcx> {
-    pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self {
-        debug!("SplitWildcard::new({:?})", pcx.ty);
-        let cx = pcx.cx;
-        let make_range = |start, end| {
-            IntRange(
-                // `unwrap()` is ok because we know the type is an integer.
-                IntRange::from_range(cx.tcx, start, end, pcx.ty, RangeEnd::Included),
-            )
-        };
-        // This determines the set of all possible constructors for the type `pcx.ty`. For numbers,
+impl ConstructorSet {
+    #[instrument(level = "debug", skip(cx), ret)]
+    pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self {
+        let make_range =
+            |start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included);
+        // This determines the set of all possible constructors for the type `ty`. For numbers,
         // arrays and slices we use ranges and variable-length slices when appropriate.
         //
         // If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that
         // are statically impossible. E.g., for `Option<!>`, we do not include `Some(_)` in the
         // returned list of constructors.
-        // Invariant: this is empty if and only if the type is uninhabited (as determined by
+        // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by
         // `cx.is_uninhabited()`).
-        let all_ctors = match pcx.ty.kind() {
-            ty::Bool => smallvec![make_range(0, 1)],
+        match ty.kind() {
+            ty::Bool => {
+                Self::Integers { range_1: make_range(0, 1), range_2: None, non_exhaustive: false }
+            }
+            ty::Char => {
+                // The valid Unicode Scalar Value ranges.
+                Self::Integers {
+                    range_1: make_range('\u{0000}' as u128, '\u{D7FF}' as u128),
+                    range_2: Some(make_range('\u{E000}' as u128, '\u{10FFFF}' as u128)),
+                    non_exhaustive: false,
+                }
+            }
+            &ty::Int(ity) => {
+                // `usize`/`isize` are not allowed to be matched exhaustively unless the
+                // `precise_pointer_size_matching` feature is enabled.
+                let non_exhaustive =
+                    ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching;
+                let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128;
+                let min = 1u128 << (bits - 1);
+                let max = min - 1;
+                Self::Integers { range_1: make_range(min, max), non_exhaustive, range_2: None }
+            }
+            &ty::Uint(uty) => {
+                // `usize`/`isize` are not allowed to be matched exhaustively unless the
+                // `precise_pointer_size_matching` feature is enabled.
+                let non_exhaustive =
+                    ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching;
+                let size = Integer::from_uint_ty(&cx.tcx, uty).size();
+                let max = size.truncate(u128::MAX);
+                Self::Integers { range_1: make_range(0, max), non_exhaustive, range_2: None }
+            }
             ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => {
                 let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize;
                 if len != 0 && cx.is_uninhabited(*sub_ty) {
-                    smallvec![]
+                    Self::Uninhabited
                 } else {
-                    smallvec![Slice(Slice::new(Some(len), VarLen(0, 0)))]
+                    Self::Slice(Some(len))
                 }
             }
             // Treat arrays of a constant but unknown length like slices.
             ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
-                let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) };
-                smallvec![Slice(Slice::new(None, kind))]
+                if cx.is_uninhabited(*sub_ty) {
+                    Self::SliceOfEmpty
+                } else {
+                    Self::Slice(None)
+                }
             }
             ty::Adt(def, args) if def.is_enum() => {
                 // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an
@@ -910,19 +989,14 @@ impl<'tcx> SplitWildcard<'tcx> {
                 //
                 // we don't want to show every possible IO error, but instead have only `_` as the
                 // witness.
-                let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty);
-
-                let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns;
+                let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(ty);
 
-                // If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it
-                // as though it had an "unknown" constructor to avoid exposing its emptiness. The
-                // exception is if the pattern is at the top level, because we want empty matches to be
-                // considered exhaustive.
-                let is_secretly_empty =
-                    def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level;
-
-                let mut ctors: SmallVec<[_; 1]> =
-                    def.variants()
+                if def.variants().is_empty() && !is_declared_nonexhaustive {
+                    Self::Uninhabited
+                } else {
+                    let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns;
+                    let (hidden_variants, visible_variants) = def
+                        .variants()
                         .iter_enumerated()
                         .filter(|(_, v)| {
                             // If `exhaustive_patterns` is enabled, we exclude variants known to be
@@ -932,135 +1006,173 @@ impl<'tcx> SplitWildcard<'tcx> {
                                     .instantiate(cx.tcx, args)
                                     .apply(cx.tcx, cx.param_env, cx.module)
                         })
-                        .map(|(idx, _)| Variant(idx))
-                        .collect();
+                        .map(|(idx, _)| idx)
+                        .partition(|idx| {
+                            let variant_def_id = def.variant(*idx).def_id;
+                            // Filter variants that depend on a disabled unstable feature.
+                            let is_unstable = matches!(
+                                cx.tcx.eval_stability(variant_def_id, None, DUMMY_SP, None),
+                                EvalResult::Deny { .. }
+                            );
+                            // Filter foreign `#[doc(hidden)]` variants.
+                            let is_doc_hidden =
+                                cx.tcx.is_doc_hidden(variant_def_id) && !variant_def_id.is_local();
+                            is_unstable || is_doc_hidden
+                        });
+
+                    Self::Variants {
+                        visible_variants,
+                        hidden_variants,
+                        non_exhaustive: is_declared_nonexhaustive,
+                    }
+                }
+            }
+            ty::Never => Self::Uninhabited,
+            _ if cx.is_uninhabited(ty) => Self::Uninhabited,
+            ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Single,
+            // This type is one for which we cannot list constructors, like `str` or `f64`.
+            _ => Self::Unlistable,
+        }
+    }
 
-                if is_secretly_empty || is_declared_nonexhaustive {
-                    ctors.push(NonExhaustive);
+    /// This is the core logical operation of exhaustiveness checking. This analyzes a column a
+    /// constructors to 1/ determine which constructors of the type (if any) are missing; 2/ split
+    /// constructors to handle non-trivial intersections e.g. on ranges or slices.
+    #[instrument(level = "debug", skip(self, pcx, ctors), ret)]
+    fn split<'a, 'tcx>(
+        &self,
+        pcx: &PatCtxt<'_, '_, 'tcx>,
+        ctors: impl Iterator<Item = &'a Constructor<'tcx>> + Clone,
+    ) -> SplitConstructorSet<'tcx>
+    where
+        'tcx: 'a,
+    {
+        let mut present: SmallVec<[_; 1]> = SmallVec::new();
+        let mut missing = Vec::new();
+        // Constructors in `ctors`, except wildcards.
+        let mut seen = ctors.filter(|c| !(matches!(c, Opaque | Wildcard)));
+        let mut nonexhaustive_enum_missing_visible_variants = false;
+        match self {
+            ConstructorSet::Single => {
+                if seen.next().is_none() {
+                    missing.push(Single);
+                } else {
+                    present.push(Single);
                 }
-                ctors
             }
-            ty::Char => {
-                smallvec![
-                    // The valid Unicode Scalar Value ranges.
-                    make_range('\u{0000}' as u128, '\u{D7FF}' as u128),
-                    make_range('\u{E000}' as u128, '\u{10FFFF}' as u128),
-                ]
+            ConstructorSet::Variants { visible_variants, hidden_variants, non_exhaustive } => {
+                let seen_set: FxHashSet<_> = seen.map(|c| c.as_variant().unwrap()).collect();
+                let mut skipped_a_hidden_variant = false;
+                for variant in visible_variants {
+                    let ctor = Variant(*variant);
+                    if seen_set.contains(&variant) {
+                        present.push(ctor);
+                    } else {
+                        missing.push(ctor);
+                    }
+                }
+                nonexhaustive_enum_missing_visible_variants =
+                    *non_exhaustive && !missing.is_empty();
+
+                for variant in hidden_variants {
+                    let ctor = Variant(*variant);
+                    if seen_set.contains(&variant) {
+                        present.push(ctor);
+                    } else {
+                        skipped_a_hidden_variant = true;
+                    }
+                }
+                if skipped_a_hidden_variant {
+                    missing.push(Hidden);
+                }
+
+                if *non_exhaustive {
+                    missing.push(NonExhaustive);
+                }
             }
-            ty::Int(_) | ty::Uint(_)
-                if pcx.ty.is_ptr_sized_integral()
-                    && !cx.tcx.features().precise_pointer_size_matching =>
-            {
-                // `usize`/`isize` are not allowed to be matched exhaustively unless the
-                // `precise_pointer_size_matching` feature is enabled. So we treat those types like
-                // `#[non_exhaustive]` enums by returning a special unmatchable constructor.
-                smallvec![NonExhaustive]
+            ConstructorSet::Integers { range_1, range_2, non_exhaustive } => {
+                let seen_ranges: Vec<_> =
+                    seen.map(|ctor| ctor.as_int_range().unwrap().clone()).collect();
+                for (seen, splitted_range) in range_1.split(seen_ranges.iter().cloned()) {
+                    match seen {
+                        Presence::Unseen => missing.push(IntRange(splitted_range)),
+                        Presence::Seen => present.push(IntRange(splitted_range)),
+                    }
+                }
+                if let Some(range_2) = range_2 {
+                    for (seen, splitted_range) in range_2.split(seen_ranges.into_iter()) {
+                        match seen {
+                            Presence::Unseen => missing.push(IntRange(splitted_range)),
+                            Presence::Seen => present.push(IntRange(splitted_range)),
+                        }
+                    }
+                }
+
+                if *non_exhaustive {
+                    missing.push(NonExhaustive);
+                }
             }
-            &ty::Int(ity) => {
-                let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128;
-                let min = 1u128 << (bits - 1);
-                let max = min - 1;
-                smallvec![make_range(min, max)]
+            &ConstructorSet::Slice(array_len) => {
+                let seen_slices = seen.map(|c| c.as_slice().unwrap());
+                let base_slice = Slice::new(array_len, VarLen(0, 0));
+                for (seen, splitted_slice) in base_slice.split(seen_slices) {
+                    let ctor = Slice(splitted_slice);
+                    match seen {
+                        Presence::Unseen => missing.push(ctor),
+                        Presence::Seen => present.push(ctor),
+                    }
+                }
             }
-            &ty::Uint(uty) => {
-                let size = Integer::from_uint_ty(&cx.tcx, uty).size();
-                let max = size.truncate(u128::MAX);
-                smallvec![make_range(0, max)]
+            ConstructorSet::SliceOfEmpty => {
+                // This one is tricky because even though there's only one possible value of this
+                // type (namely `[]`), slice patterns of all lengths are allowed, they're just
+                // unreachable if length != 0.
+                // We still gather the seen constructors in `present`, but the only slice that can
+                // go in `missing` is `[]`.
+                let seen_slices = seen.map(|c| c.as_slice().unwrap());
+                let base_slice = Slice::new(None, VarLen(0, 0));
+                for (seen, splitted_slice) in base_slice.split(seen_slices) {
+                    let ctor = Slice(splitted_slice);
+                    match seen {
+                        Presence::Seen => present.push(ctor),
+                        Presence::Unseen if splitted_slice.arity() == 0 => {
+                            missing.push(Slice(Slice::new(None, FixedLen(0))))
+                        }
+                        Presence::Unseen => {}
+                    }
+                }
+            }
+            ConstructorSet::Unlistable => {
+                // Since we can't list constructors, we take the ones in the column. This might list
+                // some constructors several times but there's not much we can do.
+                present.extend(seen.cloned());
+                missing.push(NonExhaustive);
             }
-            // If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot
+            // If `exhaustive_patterns` is disabled and our scrutinee is an empty type, we cannot
             // expose its emptiness. The exception is if the pattern is at the top level, because we
             // want empty matches to be considered exhaustive.
-            ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => {
-                smallvec![NonExhaustive]
+            ConstructorSet::Uninhabited
+                if !pcx.cx.tcx.features().exhaustive_patterns && !pcx.is_top_level =>
+            {
+                missing.push(NonExhaustive);
             }
-            ty::Never => smallvec![],
-            _ if cx.is_uninhabited(pcx.ty) => smallvec![],
-            ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => smallvec![Single],
-            // This type is one for which we cannot list constructors, like `str` or `f64`.
-            _ => smallvec![NonExhaustive],
-        };
+            ConstructorSet::Uninhabited => {}
+        }
 
-        SplitWildcard { matrix_ctors: Vec::new(), all_ctors }
+        SplitConstructorSet { present, missing, nonexhaustive_enum_missing_visible_variants }
     }
 
-    /// Pass a set of constructors relative to which to split this one. Don't call twice, it won't
-    /// do what you want.
-    pub(super) fn split<'a>(
-        &mut self,
+    /// Compute the set of constructors missing from this column.
+    /// This is only used for reporting to the user.
+    pub(super) fn compute_missing<'a, 'tcx>(
+        &self,
         pcx: &PatCtxt<'_, '_, 'tcx>,
         ctors: impl Iterator<Item = &'a Constructor<'tcx>> + Clone,
-    ) where
+    ) -> Vec<Constructor<'tcx>>
+    where
         'tcx: 'a,
     {
-        // Since `all_ctors` never contains wildcards, this won't recurse further.
-        self.all_ctors =
-            self.all_ctors.iter().flat_map(|ctor| ctor.split(pcx, ctors.clone())).collect();
-        self.matrix_ctors = ctors.filter(|c| !matches!(c, Wildcard | Opaque)).cloned().collect();
-    }
-
-    /// Whether there are any value constructors for this type that are not present in the matrix.
-    fn any_missing(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool {
-        self.iter_missing(pcx).next().is_some()
-    }
-
-    /// Iterate over the constructors for this type that are not present in the matrix.
-    pub(super) fn iter_missing<'a, 'p>(
-        &'a self,
-        pcx: &'a PatCtxt<'a, 'p, 'tcx>,
-    ) -> impl Iterator<Item = &'a Constructor<'tcx>> + Captures<'p> {
-        self.all_ctors.iter().filter(move |ctor| !ctor.is_covered_by_any(pcx, &self.matrix_ctors))
-    }
-
-    /// Return the set of constructors resulting from splitting the wildcard. As explained at the
-    /// top of the file, if any constructors are missing we can ignore the present ones.
-    fn into_ctors(self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> {
-        if self.any_missing(pcx) {
-            // Some constructors are missing, thus we can specialize with the special `Missing`
-            // constructor, which stands for those constructors that are not seen in the matrix,
-            // and matches the same rows as any of them (namely the wildcard rows). See the top of
-            // the file for details.
-            // However, when all constructors are missing we can also specialize with the full
-            // `Wildcard` constructor. The difference will depend on what we want in diagnostics.
-
-            // If some constructors are missing, we typically want to report those constructors,
-            // e.g.:
-            // ```
-            //     enum Direction { N, S, E, W }
-            //     let Direction::N = ...;
-            // ```
-            // we can report 3 witnesses: `S`, `E`, and `W`.
-            //
-            // However, if the user didn't actually specify a constructor
-            // in this arm, e.g., in
-            // ```
-            //     let x: (Direction, Direction, bool) = ...;
-            //     let (_, _, false) = x;
-            // ```
-            // we don't want to show all 16 possible witnesses `(<direction-1>, <direction-2>,
-            // true)` - we are satisfied with `(_, _, true)`. So if all constructors are missing we
-            // prefer to report just a wildcard `_`.
-            //
-            // The exception is: if we are at the top-level, for example in an empty match, we
-            // sometimes prefer reporting the list of constructors instead of just `_`.
-            let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty);
-            let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing {
-                if pcx.is_non_exhaustive {
-                    Missing {
-                        nonexhaustive_enum_missing_real_variants: self
-                            .iter_missing(pcx)
-                            .any(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))),
-                    }
-                } else {
-                    Missing { nonexhaustive_enum_missing_real_variants: false }
-                }
-            } else {
-                Wildcard
-            };
-            return smallvec![ctor];
-        }
-
-        // All the constructors are present in the matrix, so we just go through them all.
-        self.all_ctors
+        self.split(pcx, ctors).missing
     }
 }
 
@@ -1177,8 +1289,9 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
             | F32Range(..)
             | F64Range(..)
             | IntRange(..)
-            | NonExhaustive
             | Opaque
+            | NonExhaustive
+            | Hidden
             | Missing { .. }
             | Wildcard => Fields::empty(),
             Or => {
@@ -1492,7 +1605,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
             }
             &Str(value) => PatKind::Constant { value },
             IntRange(range) => return range.to_pat(cx.tcx, self.ty),
-            Wildcard | NonExhaustive => PatKind::Wild,
+            Wildcard | NonExhaustive | Hidden => PatKind::Wild,
             Missing { .. } => bug!(
                 "trying to convert a `Missing` constructor into a `Pat`; this is probably a bug,
                 `Missing` should have been processed in `apply_constructors`"
@@ -1675,15 +1788,15 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> {
             F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
             F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
             IntRange(range) => write!(f, "{range:?}"), // Best-effort, will render e.g. `false` as `0..=0`
-            Wildcard | Missing { .. } | NonExhaustive => write!(f, "_ : {:?}", self.ty),
+            Str(value) => write!(f, "{value}"),
+            Opaque => write!(f, "<constant pattern>"),
             Or => {
                 for pat in self.iter_fields() {
                     write!(f, "{}{:?}", start_or_continue(" | "), pat)?;
                 }
                 Ok(())
             }
-            Str(value) => write!(f, "{value}"),
-            Opaque => write!(f, "<constant pattern>"),
+            Wildcard | Missing { .. } | NonExhaustive | Hidden => write!(f, "_ : {:?}", self.ty),
         }
     }
 }
diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
index 21031e8ba9d..6cd73c7eaa9 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
@@ -307,7 +307,7 @@
 
 use self::ArmType::*;
 use self::Usefulness::*;
-use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard};
+use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, Fields};
 use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
 
 use rustc_data_structures::captures::Captures;
@@ -368,8 +368,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> {
     /// Whether the current pattern is the whole pattern as found in a match arm, or if it's a
     /// subpattern.
     pub(super) is_top_level: bool,
-    /// Whether the current pattern is from a `non_exhaustive` enum.
-    pub(super) is_non_exhaustive: bool,
 }
 
 impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> {
@@ -616,62 +614,41 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> {
             WithWitnesses(ref witnesses) if witnesses.is_empty() => self,
             WithWitnesses(witnesses) => {
                 let new_witnesses = if let Constructor::Missing { .. } = ctor {
+                    let mut missing = ConstructorSet::for_ty(pcx.cx, pcx.ty)
+                        .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor));
+                    if missing.iter().any(|c| c.is_non_exhaustive()) {
+                        // We only report `_` here; listing other constructors would be redundant.
+                        missing = vec![Constructor::NonExhaustive];
+                    }
+
                     // We got the special `Missing` constructor, so each of the missing constructors
-                    // gives a new pattern that is not caught by the match. We list those patterns.
-                    if pcx.is_non_exhaustive {
-                        witnesses
-                            .into_iter()
-                            // Here we don't want the user to try to list all variants, we want them to add
-                            // a wildcard, so we only suggest that.
-                            .map(|witness| {
-                                witness.apply_constructor(pcx, &Constructor::NonExhaustive)
-                            })
-                            .collect()
-                    } else {
-                        let mut split_wildcard = SplitWildcard::new(pcx);
-                        split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
-
-                        // This lets us know if we skipped any variants because they are marked
-                        // `doc(hidden)` or they are unstable feature gate (only stdlib types).
-                        let mut hide_variant_show_wild = false;
-                        // Construct for each missing constructor a "wild" version of this
-                        // constructor, that matches everything that can be built with
-                        // it. For example, if `ctor` is a `Constructor::Variant` for
-                        // `Option::Some`, we get the pattern `Some(_)`.
-                        let mut new_patterns: Vec<DeconstructedPat<'_, '_>> = split_wildcard
-                            .iter_missing(pcx)
-                            .filter_map(|missing_ctor| {
-                                // Check if this variant is marked `doc(hidden)`
-                                if missing_ctor.is_doc_hidden_variant(pcx)
-                                    || missing_ctor.is_unstable_variant(pcx)
-                                {
-                                    hide_variant_show_wild = true;
-                                    return None;
-                                }
-                                Some(DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone()))
-                            })
-                            .collect();
-
-                        if hide_variant_show_wild {
-                            new_patterns.push(DeconstructedPat::wildcard(pcx.ty, pcx.span));
-                        }
-
-                        witnesses
-                            .into_iter()
-                            .flat_map(|witness| {
-                                new_patterns.iter().map(move |pat| {
-                                    Witness(
-                                        witness
-                                            .0
-                                            .iter()
-                                            .chain(once(pat))
-                                            .map(DeconstructedPat::clone_and_forget_reachability)
-                                            .collect(),
-                                    )
-                                })
+                    // gives a new pattern that is not caught by the match.
+                    // We construct for each missing constructor a version of this constructor with
+                    // wildcards for fields, i.e. that matches everything that can be built with it.
+                    // For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get
+                    // the pattern `Some(_)`.
+                    let new_patterns: Vec<DeconstructedPat<'_, '_>> = missing
+                        .into_iter()
+                        .map(|missing_ctor| {
+                            DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone())
+                        })
+                        .collect();
+
+                    witnesses
+                        .into_iter()
+                        .flat_map(|witness| {
+                            new_patterns.iter().map(move |pat| {
+                                Witness(
+                                    witness
+                                        .0
+                                        .iter()
+                                        .chain(once(pat))
+                                        .map(DeconstructedPat::clone_and_forget_reachability)
+                                        .collect(),
+                                )
                             })
-                            .collect()
-                    }
+                        })
+                        .collect()
                 } else {
                     witnesses
                         .into_iter()
@@ -844,9 +821,8 @@ fn is_useful<'p, 'tcx>(
                 ty = row.head().ty();
             }
         }
-        let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty);
         debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span());
-        let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive };
+        let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level };
 
         let v_ctor = v.head().ctor();
         debug!(?v_ctor);
@@ -861,7 +837,8 @@ fn is_useful<'p, 'tcx>(
         }
         // We split the head constructor of `v`.
         let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
-        let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard();
+        let is_non_exhaustive_and_wild =
+            cx.is_foreign_non_exhaustive_enum(ty) && v_ctor.is_wildcard();
         // For each constructor, we compute whether there's a value that starts with it that would
         // witness the usefulness of `v`.
         let start_matrix = &matrix;
@@ -895,27 +872,21 @@ fn is_useful<'p, 'tcx>(
                 && usefulness.is_useful() && matches!(witness_preference, RealArm)
                 && matches!(
                     &ctor,
-                    Constructor::Missing { nonexhaustive_enum_missing_real_variants: true }
+                    Constructor::Missing { nonexhaustive_enum_missing_visible_variants: true }
                 )
             {
-                let patterns = {
-                    let mut split_wildcard = SplitWildcard::new(pcx);
-                    split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
-                    // Construct for each missing constructor a "wild" version of this
-                    // constructor, that matches everything that can be built with
-                    // it. For example, if `ctor` is a `Constructor::Variant` for
-                    // `Option::Some`, we get the pattern `Some(_)`.
-                    split_wildcard
-                        .iter_missing(pcx)
-                        // Filter out the `NonExhaustive` because we want to list only real
-                        // variants. Also remove any unstable feature gated variants.
-                        // Because of how we computed `nonexhaustive_enum_missing_real_variants`,
-                        // this will not return an empty `Vec`.
-                        .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx)))
-                        .cloned()
-                        .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
-                        .collect::<Vec<_>>()
-                };
+                let missing = ConstructorSet::for_ty(pcx.cx, pcx.ty)
+                    .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor));
+                // Construct for each missing constructor a "wild" version of this constructor, that
+                // matches everything that can be built with it. For example, if `ctor` is a
+                // `Constructor::Variant` for `Option::Some`, we get the pattern `Some(_)`.
+                let patterns = missing
+                    .into_iter()
+                    // Because of how we computed `nonexhaustive_enum_missing_visible_variants`,
+                    // this will not return an empty `Vec`.
+                    .filter(|c| !(matches!(c, Constructor::NonExhaustive | Constructor::Hidden)))
+                    .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
+                    .collect::<Vec<_>>();
 
                 // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
                 // is not exhaustive enough.
diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs
index 4fc78b28580..d379db5e07d 100644
--- a/compiler/rustc_mir_transform/src/const_prop.rs
+++ b/compiler/rustc_mir_transform/src/const_prop.rs
@@ -2,8 +2,6 @@
 //! assertion failures
 
 use either::Right;
-
-use rustc_const_eval::const_eval::CheckAlignment;
 use rustc_const_eval::ReportErrorExt;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def::DefKind;
@@ -16,7 +14,7 @@ use rustc_middle::mir::*;
 use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
 use rustc_middle::ty::{self, GenericArgs, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt};
 use rustc_span::{def_id::DefId, Span};
-use rustc_target::abi::{self, Align, HasDataLayout, Size, TargetDataLayout};
+use rustc_target::abi::{self, HasDataLayout, Size, TargetDataLayout};
 use rustc_target::spec::abi::Abi as CallAbi;
 
 use crate::dataflow_const_prop::Patch;
@@ -141,27 +139,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
     type MemoryKind = !;
 
     #[inline(always)]
-    fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment {
-        // We do not check for alignment to avoid having to carry an `Align`
-        // in `ConstValue::Indirect`.
-        CheckAlignment::No
+    fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
+        false // no reason to enforce alignment
     }
 
     #[inline(always)]
     fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>, _layout: TyAndLayout<'tcx>) -> bool {
         false // for now, we don't enforce validity
     }
-    fn alignment_check_failed(
-        ecx: &InterpCx<'mir, 'tcx, Self>,
-        _has: Align,
-        _required: Align,
-        _check: CheckAlignment,
-    ) -> InterpResult<'tcx, ()> {
-        span_bug!(
-            ecx.cur_span(),
-            "`alignment_check_failed` called when no alignment check requested"
-        )
-    }
 
     fn load_mir(
         _ecx: &InterpCx<'mir, 'tcx, Self>,
diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs
index 78845af0162..77e3dee1fef 100644
--- a/compiler/rustc_mir_transform/src/coverage/counters.rs
+++ b/compiler/rustc_mir_transform/src/coverage/counters.rs
@@ -245,13 +245,13 @@ impl<'a> MakeBcbCounters<'a> {
         // the loop. The `traversal` state includes a `context_stack`, providing a way to know if
         // the current BCB is in one or more nested loops or not.
         let mut traversal = TraverseCoverageGraphWithLoops::new(&self.basic_coverage_blocks);
-        while let Some(bcb) = traversal.next(self.basic_coverage_blocks) {
+        while let Some(bcb) = traversal.next() {
             if bcb_has_coverage_spans(bcb) {
                 debug!("{:?} has at least one coverage span. Get or make its counter", bcb);
                 let branching_counter_operand = self.get_or_make_counter_operand(bcb)?;
 
                 if self.bcb_needs_branch_counters(bcb) {
-                    self.make_branch_counters(&mut traversal, bcb, branching_counter_operand)?;
+                    self.make_branch_counters(&traversal, bcb, branching_counter_operand)?;
                 }
             } else {
                 debug!(
@@ -274,7 +274,7 @@ impl<'a> MakeBcbCounters<'a> {
 
     fn make_branch_counters(
         &mut self,
-        traversal: &mut TraverseCoverageGraphWithLoops,
+        traversal: &TraverseCoverageGraphWithLoops<'_>,
         branching_bcb: BasicCoverageBlock,
         branching_counter_operand: Operand,
     ) -> Result<(), Error> {
@@ -507,21 +507,14 @@ impl<'a> MakeBcbCounters<'a> {
     /// found, select any branch.
     fn choose_preferred_expression_branch(
         &self,
-        traversal: &TraverseCoverageGraphWithLoops,
+        traversal: &TraverseCoverageGraphWithLoops<'_>,
         branches: &[BcbBranch],
     ) -> BcbBranch {
-        let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch);
-
-        let some_reloop_branch = self.find_some_reloop_branch(traversal, &branches);
-        if let Some(reloop_branch_without_counter) =
-            some_reloop_branch.filter(branch_needs_a_counter)
-        {
-            debug!(
-                "Selecting reloop_branch={:?} that still needs a counter, to get the \
-                `Expression`",
-                reloop_branch_without_counter
-            );
-            reloop_branch_without_counter
+        let good_reloop_branch = self.find_good_reloop_branch(traversal, &branches);
+        if let Some(reloop_branch) = good_reloop_branch {
+            assert!(self.branch_has_no_counter(&reloop_branch));
+            debug!("Selecting reloop branch {reloop_branch:?} to get an expression");
+            reloop_branch
         } else {
             let &branch_without_counter =
                 branches.iter().find(|&branch| self.branch_has_no_counter(branch)).expect(
@@ -538,75 +531,52 @@ impl<'a> MakeBcbCounters<'a> {
         }
     }
 
-    /// At most, one of the branches (or its edge, from the branching_bcb, if the branch has
-    /// multiple incoming edges) can have a counter computed by expression.
-    ///
-    /// If at least one of the branches leads outside of a loop (`found_loop_exit` is
-    /// true), and at least one other branch does not exit the loop (the first of which
-    /// is captured in `some_reloop_branch`), it's likely any reloop branch will be
-    /// executed far more often than loop exit branch, making the reloop branch a better
-    /// candidate for an expression.
-    fn find_some_reloop_branch(
+    /// Tries to find a branch that leads back to the top of a loop, and that
+    /// doesn't already have a counter. Such branches are good candidates to
+    /// be given an expression (instead of a physical counter), because they
+    /// will tend to be executed more times than a loop-exit branch.
+    fn find_good_reloop_branch(
         &self,
-        traversal: &TraverseCoverageGraphWithLoops,
+        traversal: &TraverseCoverageGraphWithLoops<'_>,
         branches: &[BcbBranch],
     ) -> Option<BcbBranch> {
-        let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch);
-
-        let mut some_reloop_branch: Option<BcbBranch> = None;
-        for context in traversal.context_stack.iter().rev() {
-            if let Some((backedge_from_bcbs, _)) = &context.loop_backedges {
-                let mut found_loop_exit = false;
-                for &branch in branches.iter() {
-                    if backedge_from_bcbs.iter().any(|&backedge_from_bcb| {
-                        self.bcb_dominates(branch.target_bcb, backedge_from_bcb)
-                    }) {
-                        if let Some(reloop_branch) = some_reloop_branch {
-                            if self.branch_has_no_counter(&reloop_branch) {
-                                // we already found a candidate reloop_branch that still
-                                // needs a counter
-                                continue;
-                            }
-                        }
-                        // The path from branch leads back to the top of the loop. Set this
-                        // branch as the `reloop_branch`. If this branch already has a
-                        // counter, and we find another reloop branch that doesn't have a
-                        // counter yet, that branch will be selected as the `reloop_branch`
-                        // instead.
-                        some_reloop_branch = Some(branch);
-                    } else {
-                        // The path from branch leads outside this loop
-                        found_loop_exit = true;
-                    }
-                    if found_loop_exit
-                        && some_reloop_branch.filter(branch_needs_a_counter).is_some()
-                    {
-                        // Found both a branch that exits the loop and a branch that returns
-                        // to the top of the loop (`reloop_branch`), and the `reloop_branch`
-                        // doesn't already have a counter.
-                        break;
+        // Consider each loop on the current traversal context stack, top-down.
+        for reloop_bcbs in traversal.reloop_bcbs_per_loop() {
+            let mut all_branches_exit_this_loop = true;
+
+            // Try to find a branch that doesn't exit this loop and doesn't
+            // already have a counter.
+            for &branch in branches {
+                // A branch is a reloop branch if it dominates any BCB that has
+                // an edge back to the loop header. (Other branches are exits.)
+                let is_reloop_branch = reloop_bcbs.iter().any(|&reloop_bcb| {
+                    self.basic_coverage_blocks.dominates(branch.target_bcb, reloop_bcb)
+                });
+
+                if is_reloop_branch {
+                    all_branches_exit_this_loop = false;
+                    if self.branch_has_no_counter(&branch) {
+                        // We found a good branch to be given an expression.
+                        return Some(branch);
                     }
+                    // Keep looking for another reloop branch without a counter.
+                } else {
+                    // This branch exits the loop.
                 }
-                if !found_loop_exit {
-                    debug!(
-                        "No branches exit the loop, so any branch without an existing \
-                        counter can have the `Expression`."
-                    );
-                    break;
-                }
-                if some_reloop_branch.is_some() {
-                    debug!(
-                        "Found a branch that exits the loop and a branch the loops back to \
-                        the top of the loop (`reloop_branch`). The `reloop_branch` will \
-                        get the `Expression`, as long as it still needs a counter."
-                    );
-                    break;
-                }
-                // else all branches exited this loop context, so run the same checks with
-                // the outer loop(s)
             }
+
+            if !all_branches_exit_this_loop {
+                // We found one or more reloop branches, but all of them already
+                // have counters. Let the caller choose one of the exit branches.
+                debug!("All reloop branches had counters; skip checking the other loops");
+                return None;
+            }
+
+            // All of the branches exit this loop, so keep looking for a good
+            // reloop branch for one of the outer loops.
         }
-        some_reloop_branch
+
+        None
     }
 
     #[inline]
@@ -652,9 +622,4 @@ impl<'a> MakeBcbCounters<'a> {
     fn bcb_has_one_path_to_target(&self, bcb: BasicCoverageBlock) -> bool {
         self.bcb_predecessors(bcb).len() <= 1
     }
-
-    #[inline]
-    fn bcb_dominates(&self, dom: BasicCoverageBlock, node: BasicCoverageBlock) -> bool {
-        self.basic_coverage_blocks.dominates(dom, node)
-    }
 }
diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs
index 39027d21f06..9a7adaada09 100644
--- a/compiler/rustc_mir_transform/src/coverage/graph.rs
+++ b/compiler/rustc_mir_transform/src/coverage/graph.rs
@@ -6,6 +6,7 @@ use rustc_index::{IndexSlice, IndexVec};
 use rustc_middle::mir::{self, BasicBlock, TerminatorKind};
 
 use std::cmp::Ordering;
+use std::collections::VecDeque;
 use std::ops::{Index, IndexMut};
 
 /// A coverage-specific simplification of the MIR control flow graph (CFG). The `CoverageGraph`s
@@ -385,57 +386,72 @@ fn bcb_filtered_successors<'a, 'tcx>(
 /// ensures a loop is completely traversed before processing Blocks after the end of the loop.
 #[derive(Debug)]
 pub(super) struct TraversalContext {
-    /// From one or more backedges returning to a loop header.
-    pub loop_backedges: Option<(Vec<BasicCoverageBlock>, BasicCoverageBlock)>,
-
-    /// worklist, to be traversed, of CoverageGraph in the loop with the given loop
-    /// backedges, such that the loop is the inner inner-most loop containing these
-    /// CoverageGraph
-    pub worklist: Vec<BasicCoverageBlock>,
+    /// BCB with one or more incoming loop backedges, indicating which loop
+    /// this context is for.
+    ///
+    /// If `None`, this is the non-loop context for the function as a whole.
+    loop_header: Option<BasicCoverageBlock>,
+
+    /// Worklist of BCBs to be processed in this context.
+    worklist: VecDeque<BasicCoverageBlock>,
 }
 
-pub(super) struct TraverseCoverageGraphWithLoops {
-    pub backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
-    pub context_stack: Vec<TraversalContext>,
+pub(super) struct TraverseCoverageGraphWithLoops<'a> {
+    basic_coverage_blocks: &'a CoverageGraph,
+
+    backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
+    context_stack: Vec<TraversalContext>,
     visited: BitSet<BasicCoverageBlock>,
 }
 
-impl TraverseCoverageGraphWithLoops {
-    pub fn new(basic_coverage_blocks: &CoverageGraph) -> Self {
-        let start_bcb = basic_coverage_blocks.start_node();
+impl<'a> TraverseCoverageGraphWithLoops<'a> {
+    pub(super) fn new(basic_coverage_blocks: &'a CoverageGraph) -> Self {
         let backedges = find_loop_backedges(basic_coverage_blocks);
-        let context_stack =
-            vec![TraversalContext { loop_backedges: None, worklist: vec![start_bcb] }];
+
+        let worklist = VecDeque::from([basic_coverage_blocks.start_node()]);
+        let context_stack = vec![TraversalContext { loop_header: None, worklist }];
+
         // `context_stack` starts with a `TraversalContext` for the main function context (beginning
         // with the `start` BasicCoverageBlock of the function). New worklists are pushed to the top
         // of the stack as loops are entered, and popped off of the stack when a loop's worklist is
         // exhausted.
         let visited = BitSet::new_empty(basic_coverage_blocks.num_nodes());
-        Self { backedges, context_stack, visited }
+        Self { basic_coverage_blocks, backedges, context_stack, visited }
     }
 
-    pub fn next(&mut self, basic_coverage_blocks: &CoverageGraph) -> Option<BasicCoverageBlock> {
+    /// For each loop on the loop context stack (top-down), yields a list of BCBs
+    /// within that loop that have an outgoing edge back to the loop header.
+    pub(super) fn reloop_bcbs_per_loop(&self) -> impl Iterator<Item = &[BasicCoverageBlock]> {
+        self.context_stack
+            .iter()
+            .rev()
+            .filter_map(|context| context.loop_header)
+            .map(|header_bcb| self.backedges[header_bcb].as_slice())
+    }
+
+    pub(super) fn next(&mut self) -> Option<BasicCoverageBlock> {
         debug!(
             "TraverseCoverageGraphWithLoops::next - context_stack: {:?}",
             self.context_stack.iter().rev().collect::<Vec<_>>()
         );
 
         while let Some(context) = self.context_stack.last_mut() {
-            if let Some(next_bcb) = context.worklist.pop() {
-                if !self.visited.insert(next_bcb) {
-                    debug!("Already visited: {:?}", next_bcb);
+            if let Some(bcb) = context.worklist.pop_front() {
+                if !self.visited.insert(bcb) {
+                    debug!("Already visited: {bcb:?}");
                     continue;
                 }
-                debug!("Visiting {:?}", next_bcb);
-                if self.backedges[next_bcb].len() > 0 {
-                    debug!("{:?} is a loop header! Start a new TraversalContext...", next_bcb);
+                debug!("Visiting {bcb:?}");
+
+                if self.backedges[bcb].len() > 0 {
+                    debug!("{bcb:?} is a loop header! Start a new TraversalContext...");
                     self.context_stack.push(TraversalContext {
-                        loop_backedges: Some((self.backedges[next_bcb].clone(), next_bcb)),
-                        worklist: Vec::new(),
+                        loop_header: Some(bcb),
+                        worklist: VecDeque::new(),
                     });
                 }
-                self.extend_worklist(basic_coverage_blocks, next_bcb);
-                return Some(next_bcb);
+                self.add_successors_to_worklists(bcb);
+                return Some(bcb);
             } else {
                 // Strip contexts with empty worklists from the top of the stack
                 self.context_stack.pop();
@@ -445,13 +461,10 @@ impl TraverseCoverageGraphWithLoops {
         None
     }
 
-    pub fn extend_worklist(
-        &mut self,
-        basic_coverage_blocks: &CoverageGraph,
-        bcb: BasicCoverageBlock,
-    ) {
-        let successors = &basic_coverage_blocks.successors[bcb];
+    pub fn add_successors_to_worklists(&mut self, bcb: BasicCoverageBlock) {
+        let successors = &self.basic_coverage_blocks.successors[bcb];
         debug!("{:?} has {} successors:", bcb, successors.len());
+
         for &successor in successors {
             if successor == bcb {
                 debug!(
@@ -460,56 +473,44 @@ impl TraverseCoverageGraphWithLoops {
                     bcb
                 );
                 // Don't re-add this successor to the worklist. We are already processing it.
+                // FIXME: This claims to skip just the self-successor, but it actually skips
+                // all other successors as well. Does that matter?
                 break;
             }
-            for context in self.context_stack.iter_mut().rev() {
-                // Add successors of the current BCB to the appropriate context. Successors that
-                // stay within a loop are added to the BCBs context worklist. Successors that
-                // exit the loop (they are not dominated by the loop header) must be reachable
-                // from other BCBs outside the loop, and they will be added to a different
-                // worklist.
-                //
-                // Branching blocks (with more than one successor) must be processed before
-                // blocks with only one successor, to prevent unnecessarily complicating
-                // `Expression`s by creating a Counter in a `BasicCoverageBlock` that the
-                // branching block would have given an `Expression` (or vice versa).
-                let (some_successor_to_add, some_loop_header) =
-                    if let Some((_, loop_header)) = context.loop_backedges {
-                        if basic_coverage_blocks.dominates(loop_header, successor) {
-                            (Some(successor), Some(loop_header))
-                        } else {
-                            (None, None)
-                        }
-                    } else {
-                        (Some(successor), None)
-                    };
-                if let Some(successor_to_add) = some_successor_to_add {
-                    if basic_coverage_blocks.successors[successor_to_add].len() > 1 {
-                        debug!(
-                            "{:?} successor is branching. Prioritize it at the beginning of \
-                            the {}",
-                            successor_to_add,
-                            if let Some(loop_header) = some_loop_header {
-                                format!("worklist for the loop headed by {loop_header:?}")
-                            } else {
-                                String::from("non-loop worklist")
-                            },
-                        );
-                        context.worklist.insert(0, successor_to_add);
-                    } else {
-                        debug!(
-                            "{:?} successor is non-branching. Defer it to the end of the {}",
-                            successor_to_add,
-                            if let Some(loop_header) = some_loop_header {
-                                format!("worklist for the loop headed by {loop_header:?}")
-                            } else {
-                                String::from("non-loop worklist")
-                            },
-                        );
-                        context.worklist.push(successor_to_add);
+
+            // Add successors of the current BCB to the appropriate context. Successors that
+            // stay within a loop are added to the BCBs context worklist. Successors that
+            // exit the loop (they are not dominated by the loop header) must be reachable
+            // from other BCBs outside the loop, and they will be added to a different
+            // worklist.
+            //
+            // Branching blocks (with more than one successor) must be processed before
+            // blocks with only one successor, to prevent unnecessarily complicating
+            // `Expression`s by creating a Counter in a `BasicCoverageBlock` that the
+            // branching block would have given an `Expression` (or vice versa).
+
+            let context = self
+                .context_stack
+                .iter_mut()
+                .rev()
+                .find(|context| match context.loop_header {
+                    Some(loop_header) => {
+                        self.basic_coverage_blocks.dominates(loop_header, successor)
                     }
-                    break;
-                }
+                    None => true,
+                })
+                .unwrap_or_else(|| bug!("should always fall back to the root non-loop context"));
+            debug!("adding to worklist for {:?}", context.loop_header);
+
+            // FIXME: The code below had debug messages claiming to add items to a
+            // particular end of the worklist, but was confused about which end was
+            // which. The existing behaviour has been preserved for now, but it's
+            // unclear what the intended behaviour was.
+
+            if self.basic_coverage_blocks.successors[successor].len() > 1 {
+                context.worklist.push_back(successor);
+            } else {
+                context.worklist.push_front(successor);
             }
         }
     }
diff --git a/compiler/rustc_mir_transform/src/coverage/tests.rs b/compiler/rustc_mir_transform/src/coverage/tests.rs
index ee7cb791c81..487d2282364 100644
--- a/compiler/rustc_mir_transform/src/coverage/tests.rs
+++ b/compiler/rustc_mir_transform/src/coverage/tests.rs
@@ -628,7 +628,7 @@ fn test_traverse_coverage_with_loops() {
     let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
     let mut traversed_in_order = Vec::new();
     let mut traversal = graph::TraverseCoverageGraphWithLoops::new(&basic_coverage_blocks);
-    while let Some(bcb) = traversal.next(&basic_coverage_blocks) {
+    while let Some(bcb) = traversal.next() {
         traversed_in_order.push(bcb);
     }
 
diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
index 7b14fef6153..85a0be8a44c 100644
--- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
+++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
@@ -2,7 +2,6 @@
 //!
 //! Currently, this pass only propagates scalar values.
 
-use rustc_const_eval::const_eval::CheckAlignment;
 use rustc_const_eval::interpret::{ImmTy, Immediate, InterpCx, OpTy, Projectable};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def::DefKind;
@@ -17,7 +16,7 @@ use rustc_mir_dataflow::value_analysis::{
 use rustc_mir_dataflow::{lattice::FlatSet, Analysis, Results, ResultsVisitor};
 use rustc_span::def_id::DefId;
 use rustc_span::DUMMY_SP;
-use rustc_target::abi::{Align, FieldIdx, VariantIdx};
+use rustc_target::abi::{FieldIdx, VariantIdx};
 
 use crate::MirPass;
 
@@ -709,23 +708,13 @@ impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for Dumm
     const PANIC_ON_ALLOC_FAIL: bool = true;
 
     #[inline(always)]
-    fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment {
-        // We do not check for alignment to avoid having to carry an `Align`
-        // in `ConstValue::ByRef`.
-        CheckAlignment::No
+    fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
+        false // no reason to enforce alignment
     }
 
     fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>, _layout: TyAndLayout<'tcx>) -> bool {
         unimplemented!()
     }
-    fn alignment_check_failed(
-        _ecx: &InterpCx<'mir, 'tcx, Self>,
-        _has: Align,
-        _required: Align,
-        _check: CheckAlignment,
-    ) -> interpret::InterpResult<'tcx, ()> {
-        unimplemented!()
-    }
 
     fn before_access_global(
         _tcx: TyCtxt<'tcx>,
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 06b1b1523ed..59498978948 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -827,6 +827,65 @@ impl<'a> Parser<'a> {
         None
     }
 
+    pub(super) fn recover_closure_body(
+        &mut self,
+        mut err: DiagnosticBuilder<'a, ErrorGuaranteed>,
+        before: token::Token,
+        prev: token::Token,
+        token: token::Token,
+        lo: Span,
+        decl_hi: Span,
+    ) -> PResult<'a, P<Expr>> {
+        err.span_label(lo.to(decl_hi), "while parsing the body of this closure");
+        match before.kind {
+            token::OpenDelim(Delimiter::Brace)
+                if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
+            {
+                // `{ || () }` should have been `|| { () }`
+                err.multipart_suggestion(
+                    "you might have meant to open the body of the closure, instead of enclosing \
+                     the closure in a block",
+                    vec![
+                        (before.span, String::new()),
+                        (prev.span.shrink_to_hi(), " {".to_string()),
+                    ],
+                    Applicability::MaybeIncorrect,
+                );
+                err.emit();
+                self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]);
+            }
+            token::OpenDelim(Delimiter::Parenthesis)
+                if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) =>
+            {
+                // We are within a function call or tuple, we can emit the error
+                // and recover.
+                self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis), &token::Comma]);
+
+                err.multipart_suggestion_verbose(
+                    "you might have meant to open the body of the closure",
+                    vec![
+                        (prev.span.shrink_to_hi(), " {".to_string()),
+                        (self.token.span.shrink_to_lo(), "}".to_string()),
+                    ],
+                    Applicability::MaybeIncorrect,
+                );
+                err.emit();
+            }
+            _ if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) => {
+                // We don't have a heuristic to correctly identify where the block
+                // should be closed.
+                err.multipart_suggestion_verbose(
+                    "you might have meant to open the body of the closure",
+                    vec![(prev.span.shrink_to_hi(), " {".to_string())],
+                    Applicability::HasPlaceholders,
+                );
+                return Err(err);
+            }
+            _ => return Err(err),
+        }
+        Ok(self.mk_expr_err(lo.to(self.token.span)))
+    }
+
     /// Eats and discards tokens until one of `kets` is encountered. Respects token trees,
     /// passes through any errors encountered. Used for error recovery.
     pub(super) fn eat_to_tokens(&mut self, kets: &[&TokenKind]) {
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 1d883e16f9d..a9f497e55e6 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -2209,6 +2209,7 @@ impl<'a> Parser<'a> {
     fn parse_expr_closure(&mut self) -> PResult<'a, P<Expr>> {
         let lo = self.token.span;
 
+        let before = self.prev_token.clone();
         let binder = if self.check_keyword(kw::For) {
             let lo = self.token.span;
             let lifetime_defs = self.parse_late_bound_lifetime_defs()?;
@@ -2239,7 +2240,12 @@ impl<'a> Parser<'a> {
             FnRetTy::Default(_) => {
                 let restrictions =
                     self.restrictions - Restrictions::STMT_EXPR - Restrictions::ALLOW_LET;
-                self.parse_expr_res(restrictions, None)?
+                let prev = self.prev_token.clone();
+                let token = self.token.clone();
+                match self.parse_expr_res(restrictions, None) {
+                    Ok(expr) => expr,
+                    Err(err) => self.recover_closure_body(err, before, prev, token, lo, decl_hi)?,
+                }
             }
             _ => {
                 // If an explicit return type is given, require a block to appear (RFC 968).
@@ -2459,10 +2465,16 @@ impl<'a> Parser<'a> {
     /// Parses a `let $pat = $expr` pseudo-expression.
     fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, P<Expr>> {
         let is_recovered = if !restrictions.contains(Restrictions::ALLOW_LET) {
-            Some(self.sess.emit_err(errors::ExpectedExpressionFoundLet {
+            let err = errors::ExpectedExpressionFoundLet {
                 span: self.token.span,
                 reason: ForbiddenLetReason::OtherForbidden,
-            }))
+            };
+            if self.prev_token.kind == token::BinOp(token::Or) {
+                // This was part of a closure, the that part of the parser recover.
+                return Err(err.into_diagnostic(&self.sess.span_diagnostic));
+            } else {
+                Some(self.sess.emit_err(err))
+            }
         } else {
             None
         };
diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl
index b356b503aa5..fa1b6f9f13d 100644
--- a/compiler/rustc_session/messages.ftl
+++ b/compiler/rustc_session/messages.ftl
@@ -5,9 +5,6 @@ session_cannot_enable_crt_static_linux = sanitizer is incompatible with statical
 
 session_cannot_mix_and_match_sanitizers = `-Zsanitizer={$first}` is incompatible with `-Zsanitizer={$second}`
 
-session_cgu_not_recorded =
-    CGU-reuse for `{$cgu_user_name}` is (mangled: `{$cgu_name}`) was not recorded
-
 session_cli_feature_diagnostic_help =
     add `-Zcrate-attr="feature({$feature})"` to the command-line options to enable
 
@@ -34,12 +31,6 @@ session_hexadecimal_float_literal_not_supported = hexadecimal float literal is n
 session_incompatible_linker_flavor = linker flavor `{$flavor}` is incompatible with the current target
     .note = compatible flavors are: {$compatible_list}
 
-session_incorrect_cgu_reuse_type =
-    CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least ->
-    [one] {"at least "}
-    *[other] {""}
-    }`{$expected_reuse}`
-
 session_instrumentation_not_supported = {$us} instrumentation is not supported for this target
 
 session_int_literal_too_large = integer literal is too large
diff --git a/compiler/rustc_session/src/cgu_reuse_tracker.rs b/compiler/rustc_session/src/cgu_reuse_tracker.rs
deleted file mode 100644
index 8703e575465..00000000000
--- a/compiler/rustc_session/src/cgu_reuse_tracker.rs
+++ /dev/null
@@ -1,136 +0,0 @@
-//! Some facilities for tracking how codegen-units are reused during incremental
-//! compilation. This is used for incremental compilation tests and debug
-//! output.
-
-use crate::errors::{CguNotRecorded, IncorrectCguReuseType};
-use crate::Session;
-use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg};
-use rustc_span::{Span, Symbol};
-use std::borrow::Cow;
-use std::fmt::{self};
-use std::sync::{Arc, Mutex};
-
-#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
-pub enum CguReuse {
-    No,
-    PreLto,
-    PostLto,
-}
-
-impl fmt::Display for CguReuse {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match *self {
-            CguReuse::No => write!(f, "No"),
-            CguReuse::PreLto => write!(f, "PreLto "),
-            CguReuse::PostLto => write!(f, "PostLto "),
-        }
-    }
-}
-
-impl IntoDiagnosticArg for CguReuse {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum ComparisonKind {
-    Exact,
-    AtLeast,
-}
-
-struct TrackerData {
-    actual_reuse: FxHashMap<String, CguReuse>,
-    expected_reuse: FxHashMap<String, (String, SendSpan, CguReuse, ComparisonKind)>,
-}
-
-// Span does not implement `Send`, so we can't just store it in the shared
-// `TrackerData` object. Instead of splitting up `TrackerData` into shared and
-// non-shared parts (which would be complicated), we just mark the `Span` here
-// explicitly as `Send`. That's safe because the span data here is only ever
-// accessed from the main thread.
-struct SendSpan(Span);
-unsafe impl Send for SendSpan {}
-
-#[derive(Clone)]
-pub struct CguReuseTracker {
-    data: Option<Arc<Mutex<TrackerData>>>,
-}
-
-impl CguReuseTracker {
-    pub fn new() -> CguReuseTracker {
-        let data =
-            TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() };
-
-        CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) }
-    }
-
-    pub fn new_disabled() -> CguReuseTracker {
-        CguReuseTracker { data: None }
-    }
-
-    pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) {
-        if let Some(ref data) = self.data {
-            debug!("set_actual_reuse({cgu_name:?}, {kind:?})");
-
-            let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind);
-
-            if let Some(prev_reuse) = prev_reuse {
-                // The only time it is legal to overwrite reuse state is when
-                // we discover during ThinLTO that we can actually reuse the
-                // post-LTO version of a CGU.
-                assert_eq!(prev_reuse, CguReuse::PreLto);
-            }
-        }
-    }
-
-    pub fn set_expectation(
-        &self,
-        cgu_name: Symbol,
-        cgu_user_name: &str,
-        error_span: Span,
-        expected_reuse: CguReuse,
-        comparison_kind: ComparisonKind,
-    ) {
-        if let Some(ref data) = self.data {
-            debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})");
-            let mut data = data.lock().unwrap();
-
-            data.expected_reuse.insert(
-                cgu_name.to_string(),
-                (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind),
-            );
-        }
-    }
-
-    pub fn check_expected_reuse(&self, sess: &Session) {
-        if let Some(ref data) = self.data {
-            let data = data.lock().unwrap();
-
-            for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in
-                &data.expected_reuse
-            {
-                if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) {
-                    let (error, at_least) = match comparison_kind {
-                        ComparisonKind::Exact => (expected_reuse != actual_reuse, false),
-                        ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true),
-                    };
-
-                    if error {
-                        let at_least = if at_least { 1 } else { 0 };
-                        IncorrectCguReuseType {
-                            span: error_span.0,
-                            cgu_user_name,
-                            actual_reuse,
-                            expected_reuse,
-                            at_least,
-                        };
-                    }
-                } else {
-                    sess.emit_fatal(CguNotRecorded { cgu_user_name, cgu_name });
-                }
-            }
-        }
-    }
-}
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 58461856eb1..2a6f5994c49 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -2925,8 +2925,8 @@ fn parse_pretty(handler: &EarlyErrorHandler, unstable_opts: &UnstableOptions) ->
         "expanded" => Source(PpSourceMode::Expanded),
         "expanded,identified" => Source(PpSourceMode::ExpandedIdentified),
         "expanded,hygiene" => Source(PpSourceMode::ExpandedHygiene),
-        "ast-tree" => AstTree(PpAstTreeMode::Normal),
-        "ast-tree,expanded" => AstTree(PpAstTreeMode::Expanded),
+        "ast-tree" => AstTree,
+        "ast-tree,expanded" => AstTreeExpanded,
         "hir" => Hir(PpHirMode::Normal),
         "hir,identified" => Hir(PpHirMode::Identified),
         "hir,typed" => Hir(PpHirMode::Typed),
@@ -3084,14 +3084,6 @@ pub enum PpSourceMode {
 }
 
 #[derive(Copy, Clone, PartialEq, Debug)]
-pub enum PpAstTreeMode {
-    /// `-Zunpretty=ast`
-    Normal,
-    /// `-Zunpretty=ast,expanded`
-    Expanded,
-}
-
-#[derive(Copy, Clone, PartialEq, Debug)]
 pub enum PpHirMode {
     /// `-Zunpretty=hir`
     Normal,
@@ -3106,7 +3098,10 @@ pub enum PpMode {
     /// Options that print the source code, i.e.
     /// `-Zunpretty=normal` and `-Zunpretty=expanded`
     Source(PpSourceMode),
-    AstTree(PpAstTreeMode),
+    /// `-Zunpretty=ast-tree`
+    AstTree,
+    /// `-Zunpretty=ast-tree,expanded`
+    AstTreeExpanded,
     /// Options that print the HIR, i.e. `-Zunpretty=hir`
     Hir(PpHirMode),
     /// `-Zunpretty=hir-tree`
@@ -3126,10 +3121,10 @@ impl PpMode {
         use PpMode::*;
         use PpSourceMode::*;
         match *self {
-            Source(Normal | Identified) | AstTree(PpAstTreeMode::Normal) => false,
+            Source(Normal | Identified) | AstTree => false,
 
             Source(Expanded | ExpandedIdentified | ExpandedHygiene)
-            | AstTree(PpAstTreeMode::Expanded)
+            | AstTreeExpanded
             | Hir(_)
             | HirTree
             | ThirTree
@@ -3141,7 +3136,7 @@ impl PpMode {
     pub fn needs_hir(&self) -> bool {
         use PpMode::*;
         match *self {
-            Source(_) | AstTree(_) => false,
+            Source(_) | AstTree | AstTreeExpanded => false,
 
             Hir(_) | HirTree | ThirTree | ThirFlat | Mir | MirCFG => true,
         }
@@ -3149,7 +3144,7 @@ impl PpMode {
 
     pub fn needs_analysis(&self) -> bool {
         use PpMode::*;
-        matches!(*self, Mir | MirCFG | ThirTree | ThirFlat)
+        matches!(*self, Hir(PpHirMode::Typed) | Mir | MirCFG | ThirTree | ThirFlat)
     }
 }
 
diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs
index 5f8bbfca890..31094e0d266 100644
--- a/compiler/rustc_session/src/errors.rs
+++ b/compiler/rustc_session/src/errors.rs
@@ -1,6 +1,5 @@
 use std::num::NonZeroU32;
 
-use crate::cgu_reuse_tracker::CguReuse;
 use crate::parse::ParseSess;
 use rustc_ast::token;
 use rustc_ast::util::literal::LitError;
@@ -9,24 +8,6 @@ use rustc_macros::Diagnostic;
 use rustc_span::{BytePos, Span, Symbol};
 use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTriple};
 
-#[derive(Diagnostic)]
-#[diag(session_incorrect_cgu_reuse_type)]
-pub struct IncorrectCguReuseType<'a> {
-    #[primary_span]
-    pub span: Span,
-    pub cgu_user_name: &'a str,
-    pub actual_reuse: CguReuse,
-    pub expected_reuse: CguReuse,
-    pub at_least: u8,
-}
-
-#[derive(Diagnostic)]
-#[diag(session_cgu_not_recorded)]
-pub struct CguNotRecorded<'a> {
-    pub cgu_user_name: &'a str,
-    pub cgu_name: &'a str,
-}
-
 pub struct FeatureGateError {
     pub span: MultiSpan,
     pub explain: DiagnosticMessage,
diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs
index ed6705ec239..7da0bcf01bf 100644
--- a/compiler/rustc_session/src/lib.rs
+++ b/compiler/rustc_session/src/lib.rs
@@ -23,7 +23,6 @@ extern crate tracing;
 use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
 use rustc_fluent_macro::fluent_messages;
 
-pub mod cgu_reuse_tracker;
 pub mod utils;
 pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pass};
 pub use rustc_lint_defs as lint;
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index b484978eed2..5cac11cc8f7 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -1,4 +1,3 @@
-use crate::cgu_reuse_tracker::CguReuseTracker;
 use crate::code_stats::CodeStats;
 pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
 use crate::config::{
@@ -153,9 +152,6 @@ pub struct Session {
     pub io: CompilerIO,
 
     incr_comp_session: OneThread<RefCell<IncrCompSession>>,
-    /// Used for incremental compilation tests. Will only be populated if
-    /// `-Zquery-dep-graph` is specified.
-    pub cgu_reuse_tracker: CguReuseTracker,
 
     /// Used by `-Z self-profile`.
     pub prof: SelfProfilerRef,
@@ -1431,12 +1427,6 @@ pub fn build_session(
     });
     let print_fuel = AtomicU64::new(0);
 
-    let cgu_reuse_tracker = if sopts.unstable_opts.query_dep_graph {
-        CguReuseTracker::new()
-    } else {
-        CguReuseTracker::new_disabled()
-    };
-
     let prof = SelfProfilerRef::new(
         self_profiler,
         sopts.unstable_opts.time_passes.then(|| sopts.unstable_opts.time_passes_format),
@@ -1461,7 +1451,6 @@ pub fn build_session(
         sysroot,
         io,
         incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)),
-        cgu_reuse_tracker,
         prof,
         perf_stats: PerfStats {
             symbol_hash_time: Lock::new(Duration::from_secs(0)),
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
index d9059e46a8c..cc1f13a2f0f 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
@@ -1,6 +1,6 @@
 use super::{ObligationCauseCode, PredicateObligation};
 use crate::infer::error_reporting::TypeErrCtxt;
-use rustc_ast::{MetaItem, NestedMetaItem};
+use rustc_ast::{Attribute, MetaItem, NestedMetaItem};
 use rustc_attr as attr;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{struct_span_err, ErrorGuaranteed};
@@ -474,18 +474,40 @@ impl<'tcx> OnUnimplementedDirective {
     }
 
     pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
-        let mut is_diagnostic_namespace_variant = false;
-        let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented).or_else(|| {
-            if tcx.features().diagnostic_namespace {
-                is_diagnostic_namespace_variant = true;
-                tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented]).next()
-            } else {
-                None
-            }
-        }) else {
-            return Ok(None);
-        };
+        if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) {
+            return Self::parse_attribute(attr, false, tcx, item_def_id);
+        } else if tcx.features().diagnostic_namespace {
+            tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented])
+                .filter_map(|attr| Self::parse_attribute(attr, true, tcx, item_def_id).transpose())
+                .try_fold(None, |aggr: Option<Self>, directive| {
+                    let directive = directive?;
+                    if let Some(aggr) = aggr {
+                        let mut subcommands = aggr.subcommands;
+                        subcommands.extend(directive.subcommands);
+                        Ok(Some(Self {
+                            condition: aggr.condition.or(directive.condition),
+                            subcommands,
+                            message: aggr.message.or(directive.message),
+                            label: aggr.label.or(directive.label),
+                            note: aggr.note.or(directive.note),
+                            parent_label: aggr.parent_label.or(directive.parent_label),
+                            append_const_msg: aggr.append_const_msg.or(directive.append_const_msg),
+                        }))
+                    } else {
+                        Ok(Some(directive))
+                    }
+                })
+        } else {
+            Ok(None)
+        }
+    }
 
+    fn parse_attribute(
+        attr: &Attribute,
+        is_diagnostic_namespace_variant: bool,
+        tcx: TyCtxt<'tcx>,
+        item_def_id: DefId,
+    ) -> Result<Option<Self>, ErrorGuaranteed> {
         let result = if let Some(items) = attr.meta_item_list() {
             Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant)
         } else if let Some(value) = attr.value_str() {
diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs
index d09a24b4b1d..6274a916f3e 100644
--- a/library/core/src/mem/maybe_uninit.rs
+++ b/library/core/src/mem/maybe_uninit.rs
@@ -686,7 +686,10 @@ impl<T> MaybeUninit<T> {
     /// // they both get dropped!
     /// ```
     #[stable(feature = "maybe_uninit_extra", since = "1.60.0")]
-    #[rustc_const_unstable(feature = "const_maybe_uninit_assume_init_read", issue = "63567")]
+    #[rustc_const_stable(
+        feature = "const_maybe_uninit_assume_init_read",
+        since = "CURRENT_RUSTC_VERSION"
+    )]
     #[inline(always)]
     #[track_caller]
     pub const unsafe fn assume_init_read(&self) -> T {
diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs
index cf1fbe2d389..1271dcc78e0 100644
--- a/library/core/src/sync/atomic.rs
+++ b/library/core/src/sync/atomic.rs
@@ -319,7 +319,7 @@ impl AtomicBool {
     /// # Examples
     ///
     /// ```
-    /// #![feature(atomic_from_ptr, pointer_is_aligned)]
+    /// #![feature(pointer_is_aligned)]
     /// use std::sync::atomic::{self, AtomicBool};
     /// use std::mem::align_of;
     ///
@@ -346,13 +346,21 @@ impl AtomicBool {
     ///
     /// # Safety
     ///
-    /// * `ptr` must be aligned to `align_of::<AtomicBool>()` (note that on some platforms this can be bigger than `align_of::<bool>()`).
+    /// * `ptr` must be aligned to `align_of::<AtomicBool>()` (note that on some platforms this can
+    ///   be bigger than `align_of::<bool>()`).
     /// * `ptr` must be [valid] for both reads and writes for the whole lifetime `'a`.
-    /// * The value behind `ptr` must not be accessed through non-atomic operations for the whole lifetime `'a`.
+    /// * Non-atomic accesses to the value behind `ptr` must have a happens-before relationship
+    ///   with atomic accesses via the returned value (or vice-versa).
+    ///   * In other words, time periods where the value is accessed atomically may not overlap
+    ///     with periods where the value is accessed non-atomically.
+    ///   * This requirement is trivially satisfied if `ptr` is never used non-atomically for the
+    ///     duration of lifetime `'a`. Most use cases should be able to follow this guideline.
+    ///   * This requirement is also trivially satisfied if all accesses (atomic or not) are done
+    ///     from the same thread.
     ///
     /// [valid]: crate::ptr#safety
-    #[unstable(feature = "atomic_from_ptr", issue = "108652")]
-    #[rustc_const_unstable(feature = "atomic_from_ptr", issue = "108652")]
+    #[stable(feature = "atomic_from_ptr", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_unstable(feature = "const_atomic_from_ptr", issue = "108652")]
     pub const unsafe fn from_ptr<'a>(ptr: *mut bool) -> &'a AtomicBool {
         // SAFETY: guaranteed by the caller
         unsafe { &*ptr.cast() }
@@ -1113,7 +1121,7 @@ impl<T> AtomicPtr<T> {
     /// # Examples
     ///
     /// ```
-    /// #![feature(atomic_from_ptr, pointer_is_aligned)]
+    /// #![feature(pointer_is_aligned)]
     /// use std::sync::atomic::{self, AtomicPtr};
     /// use std::mem::align_of;
     ///
@@ -1140,13 +1148,23 @@ impl<T> AtomicPtr<T> {
     ///
     /// # Safety
     ///
-    /// * `ptr` must be aligned to `align_of::<AtomicPtr<T>>()` (note that on some platforms this can be bigger than `align_of::<*mut T>()`).
+    /// * `ptr` must be aligned to `align_of::<AtomicPtr<T>>()` (note that on some platforms this
+    ///   can be bigger than `align_of::<*mut T>()`).
     /// * `ptr` must be [valid] for both reads and writes for the whole lifetime `'a`.
-    /// * The value behind `ptr` must not be accessed through non-atomic operations for the whole lifetime `'a`.
+    /// * Non-atomic accesses to the value behind `ptr` must have a happens-before relationship
+    ///   with atomic accesses via the returned value (or vice-versa).
+    ///   * In other words, time periods where the value is accessed atomically may not overlap
+    ///     with periods where the value is accessed non-atomically.
+    ///   * This requirement is trivially satisfied if `ptr` is never used non-atomically for the
+    ///     duration of lifetime `'a`. Most use cases should be able to follow this guideline.
+    ///   * This requirement is also trivially satisfied if all accesses (atomic or not) are done
+    ///     from the same thread.
+    /// * This method should not be used to create overlapping or mixed-size atomic accesses, as
+    ///   these are not supported by the memory model.
     ///
     /// [valid]: crate::ptr#safety
-    #[unstable(feature = "atomic_from_ptr", issue = "108652")]
-    #[rustc_const_unstable(feature = "atomic_from_ptr", issue = "108652")]
+    #[stable(feature = "atomic_from_ptr", since = "CURRENT_RUSTC_VERSION")]
+    #[rustc_const_unstable(feature = "const_atomic_from_ptr", issue = "108652")]
     pub const unsafe fn from_ptr<'a>(ptr: *mut *mut T) -> &'a AtomicPtr<T> {
         // SAFETY: guaranteed by the caller
         unsafe { &*ptr.cast() }
@@ -2083,7 +2101,7 @@ macro_rules! atomic_int {
             /// # Examples
             ///
             /// ```
-            /// #![feature(atomic_from_ptr, pointer_is_aligned)]
+            /// #![feature(pointer_is_aligned)]
             #[doc = concat!($extra_feature, "use std::sync::atomic::{self, ", stringify!($atomic_type), "};")]
             /// use std::mem::align_of;
             ///
@@ -2111,14 +2129,25 @@ macro_rules! atomic_int {
             ///
             /// # Safety
             ///
-            /// * `ptr` must be aligned to `align_of::<AtomicBool>()` (note that on some platforms this can be bigger than `align_of::<bool>()`).
-            #[doc = concat!(" * `ptr` must be aligned to `align_of::<", stringify!($atomic_type), ">()` (note that on some platforms this can be bigger than `align_of::<", stringify!($int_type), ">()`).")]
+            #[doc = concat!(" * `ptr` must be aligned to \
+                `align_of::<", stringify!($atomic_type), ">()` (note that on some platforms this \
+                can be bigger than `align_of::<", stringify!($int_type), ">()`).")]
             /// * `ptr` must be [valid] for both reads and writes for the whole lifetime `'a`.
-            /// * The value behind `ptr` must not be accessed through non-atomic operations for the whole lifetime `'a`.
+            /// * Non-atomic accesses to the value behind `ptr` must have a happens-before
+            ///   relationship with atomic accesses via the returned value (or vice-versa).
+            ///   * In other words, time periods where the value is accessed atomically may not
+            ///     overlap with periods where the value is accessed non-atomically.
+            ///   * This requirement is trivially satisfied if `ptr` is never used non-atomically
+            ///     for the duration of lifetime `'a`. Most use cases should be able to follow
+            ///     this guideline.
+            ///   * This requirement is also trivially satisfied if all accesses (atomic or not) are
+            ///     done from the same thread.
+            /// * This method should not be used to create overlapping or mixed-size atomic
+            ///   accesses, as these are not supported by the memory model.
             ///
             /// [valid]: crate::ptr#safety
-            #[unstable(feature = "atomic_from_ptr", issue = "108652")]
-            #[rustc_const_unstable(feature = "atomic_from_ptr", issue = "108652")]
+            #[stable(feature = "atomic_from_ptr", since = "CURRENT_RUSTC_VERSION")]
+            #[rustc_const_unstable(feature = "const_atomic_from_ptr", issue = "108652")]
             pub const unsafe fn from_ptr<'a>(ptr: *mut $int_type) -> &'a $atomic_type {
                 // SAFETY: guaranteed by the caller
                 unsafe { &*ptr.cast() }
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index 1a01efa5ba9..79601c8c19d 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -15,7 +15,6 @@
 #![feature(const_hash)]
 #![feature(const_heap)]
 #![feature(const_maybe_uninit_as_mut_ptr)]
-#![feature(const_maybe_uninit_assume_init_read)]
 #![feature(const_nonnull_new)]
 #![feature(const_pointer_byte_offsets)]
 #![feature(const_pointer_is_aligned)]
diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml
index feb6274d029..965132bdedb 100644
--- a/library/std/Cargo.toml
+++ b/library/std/Cargo.toml
@@ -36,10 +36,6 @@ object = { version = "0.32.0", default-features = false, optional = true, featur
 rand = { version = "0.8.5", default-features = false, features = ["alloc"] }
 rand_xorshift = "0.3.0"
 
-[build-dependencies]
-# Dependency of the `backtrace` crate's build script
-cc = "1.0.67"
-
 [target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies]
 dlmalloc = { version = "0.2.4", features = ['rustc-dep-of-std'] }
 
diff --git a/library/std/build.rs b/library/std/build.rs
index 49354d5a58c..046ac488b1f 100644
--- a/library/std/build.rs
+++ b/library/std/build.rs
@@ -1,11 +1,5 @@
 use std::env;
 
-// backtrace-rs requires a feature check on Android targets, so
-// we need to run its build.rs as well.
-#[allow(unused_extern_crates)]
-#[path = "../backtrace/build.rs"]
-mod backtrace_build_rs;
-
 fn main() {
     println!("cargo:rerun-if-changed=build.rs");
     let target = env::var("TARGET").expect("TARGET was not set");
@@ -65,6 +59,4 @@ fn main() {
     }
     println!("cargo:rustc-env=STD_ENV_ARCH={}", env::var("CARGO_CFG_TARGET_ARCH").unwrap());
     println!("cargo:rustc-cfg=backtrace_in_libstd");
-
-    backtrace_build_rs::main();
 }
diff --git a/library/std/src/sys/unix/process/process_common/tests.rs b/library/std/src/sys/unix/process/process_common/tests.rs
index 03631e4e33b..4e41efc9096 100644
--- a/library/std/src/sys/unix/process/process_common/tests.rs
+++ b/library/std/src/sys/unix/process/process_common/tests.rs
@@ -159,3 +159,36 @@ fn test_program_kind() {
         );
     }
 }
+
+// Test that Rust std handles wait status values (`ExitStatus`) the way that Unix does,
+// at least for the values which represent a Unix exit status (`ExitCode`).
+// Should work on every #[cfg(unix)] platform.  However:
+#[cfg(not(any(
+    // Fuchsia is not Unix and has totally broken std::os::unix.
+    // https://github.com/rust-lang/rust/issues/58590#issuecomment-836535609
+    target_os = "fuchsia",
+)))]
+#[test]
+fn unix_exit_statuses() {
+    use crate::num::NonZeroI32;
+    use crate::os::unix::process::ExitStatusExt;
+    use crate::process::*;
+
+    for exit_code in 0..=0xff {
+        // FIXME impl From<ExitCode> for ExitStatus and then test that here too;
+        // the two ExitStatus values should be the same
+        let raw_wait_status = exit_code << 8;
+        let exit_status = ExitStatus::from_raw(raw_wait_status);
+
+        assert_eq!(exit_status.code(), Some(exit_code));
+
+        if let Ok(nz) = NonZeroI32::try_from(exit_code) {
+            assert!(!exit_status.success());
+            let es_error = exit_status.exit_ok().unwrap_err();
+            assert_eq!(es_error.code().unwrap(), i32::from(nz));
+        } else {
+            assert!(exit_status.success());
+            assert_eq!(exit_status.exit_ok(), Ok(()));
+        }
+    }
+}
diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs
index 564f8c48290..72aca4e6659 100644
--- a/library/std/src/sys/unix/process/process_unix.rs
+++ b/library/std/src/sys/unix/process/process_unix.rs
@@ -1074,3 +1074,8 @@ impl crate::os::linux::process::ChildExt for crate::process::Child {
 #[cfg(test)]
 #[path = "process_unix/tests.rs"]
 mod tests;
+
+// See [`process_unsupported_wait_status::compare_with_linux`];
+#[cfg(all(test, target_os = "linux"))]
+#[path = "process_unsupported/wait_status.rs"]
+mod process_unsupported_wait_status;
diff --git a/library/std/src/sys/unix/process/process_unsupported.rs b/library/std/src/sys/unix/process/process_unsupported.rs
index 8e0b971af73..325d3f23be7 100644
--- a/library/std/src/sys/unix/process/process_unsupported.rs
+++ b/library/std/src/sys/unix/process/process_unsupported.rs
@@ -55,56 +55,8 @@ impl Process {
     }
 }
 
-#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
-pub struct ExitStatus(c_int);
-
-impl ExitStatus {
-    #[cfg_attr(target_os = "horizon", allow(unused))]
-    pub fn success(&self) -> bool {
-        self.code() == Some(0)
-    }
-
-    pub fn exit_ok(&self) -> Result<(), ExitStatusError> {
-        Err(ExitStatusError(1.try_into().unwrap()))
-    }
-
-    pub fn code(&self) -> Option<i32> {
-        None
-    }
-
-    pub fn signal(&self) -> Option<i32> {
-        None
-    }
-
-    pub fn core_dumped(&self) -> bool {
-        false
-    }
-
-    pub fn stopped_signal(&self) -> Option<i32> {
-        None
-    }
-
-    pub fn continued(&self) -> bool {
-        false
-    }
-
-    pub fn into_raw(&self) -> c_int {
-        0
-    }
-}
-
-/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying.
-impl From<c_int> for ExitStatus {
-    fn from(a: c_int) -> ExitStatus {
-        ExitStatus(a as i32)
-    }
-}
-
-impl fmt::Display for ExitStatus {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "exit code: {}", self.0)
-    }
-}
+mod wait_status;
+pub use wait_status::ExitStatus;
 
 #[derive(PartialEq, Eq, Clone, Copy, Debug)]
 pub struct ExitStatusError(NonZero_c_int);
diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs
new file mode 100644
index 00000000000..3c649db1a35
--- /dev/null
+++ b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs
@@ -0,0 +1,68 @@
+//! Emulated wait status for non-Unix #[cfg(unix) platforms
+//!
+//! Separate module to facilitate testing against a real Unix implementation.
+
+use crate::ffi::c_int;
+use crate::fmt;
+
+/// Emulated wait status for use by `process_unsupported.rs`
+///
+/// Uses the "traditional unix" encoding.  For use on platfors which are `#[cfg(unix)]`
+/// but do not actually support subprocesses at all.
+///
+/// These platforms aren't Unix, but are simply pretending to be for porting convenience.
+/// So, we provide a faithful pretence here.
+#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
+pub struct ExitStatus {
+    wait_status: c_int,
+}
+
+/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it
+impl From<c_int> for ExitStatus {
+    fn from(wait_status: c_int) -> ExitStatus {
+        ExitStatus { wait_status }
+    }
+}
+
+impl fmt::Display for ExitStatus {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "emulated wait status: {}", self.wait_status)
+    }
+}
+
+impl ExitStatus {
+    pub fn code(&self) -> Option<i32> {
+        // Linux and FreeBSD both agree that values linux 0x80
+        // count as "WIFEXITED" even though this is quite mad.
+        // Likewise the macros disregard all the high bits, so are happy to declare
+        // out-of-range values to be WIFEXITED, WIFSTOPPED, etc.
+        let w = self.wait_status;
+        if (w & 0x7f) == 0 { Some((w & 0xff00) >> 8) } else { None }
+    }
+
+    pub fn signal(&self) -> Option<i32> {
+        let signal = self.wait_status & 0x007f;
+        if signal > 0 && signal < 0x7f { Some(signal) } else { None }
+    }
+
+    pub fn core_dumped(&self) -> bool {
+        self.signal().is_some() && (self.wait_status & 0x80) != 0
+    }
+
+    pub fn stopped_signal(&self) -> Option<i32> {
+        let w = self.wait_status;
+        if (w & 0xff) == 0x7f { Some((w & 0xff00) >> 8) } else { None }
+    }
+
+    pub fn continued(&self) -> bool {
+        self.wait_status == 0xffff
+    }
+
+    pub fn into_raw(&self) -> c_int {
+        self.wait_status
+    }
+}
+
+#[cfg(test)]
+#[path = "wait_status/tests.rs"] // needed because of strange layout of process_unsupported
+mod tests;
diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs
new file mode 100644
index 00000000000..5132eab10a1
--- /dev/null
+++ b/library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs
@@ -0,0 +1,36 @@
+// Note that tests in this file are run on Linux as well as on platforms using process_unsupported
+
+// Test that our emulation exactly matches Linux
+//
+// This test runs *on Linux* but it tests
+// the implementation used on non-Unix `#[cfg(unix)]` platforms.
+//
+// I.e. we're using Linux as a proxy for "trad unix".
+#[cfg(target_os = "linux")]
+#[test]
+fn compare_with_linux() {
+    use super::ExitStatus as Emulated;
+    use crate::os::unix::process::ExitStatusExt as _;
+    use crate::process::ExitStatus as Real;
+
+    // Check that we handle out-of-range values similarly, too.
+    for wstatus in -0xf_ffff..0xf_ffff {
+        let emulated = Emulated::from(wstatus);
+        let real = Real::from_raw(wstatus);
+
+        macro_rules! compare { { $method:ident } => {
+            assert_eq!(
+                emulated.$method(),
+                real.$method(),
+                "{wstatus:#x}.{}()",
+                stringify!($method),
+            );
+        } }
+        compare!(code);
+        compare!(signal);
+        compare!(core_dumped);
+        compare!(stopped_signal);
+        compare!(continued);
+        compare!(into_raw);
+    }
+}
diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md
index d25fa661c3f..1fb5e56db5d 100644
--- a/src/doc/rustc/src/platform-support.md
+++ b/src/doc/rustc/src/platform-support.md
@@ -286,10 +286,6 @@ target | std | host | notes
 `mipsel-unknown-linux-musl` | ✓ |  | MIPS (little endian) Linux with musl libc
 `mipsel-sony-psp` | * |  | MIPS (LE) Sony PlayStation Portable (PSP)
 [`mipsel-sony-psx`](platform-support/mipsel-sony-psx.md) | * |  | MIPS (LE) Sony PlayStation 1 (PSX)
-`mips-unknown-linux-gnu` | MIPS Linux (kernel 4.4, glibc 2.23)
-`mips64-unknown-linux-gnuabi64` | MIPS64 Linux, n64 ABI (kernel 4.4, glibc 2.23)
-`mips64el-unknown-linux-gnuabi64` | MIPS64 (LE) Linux, n64 ABI (kernel 4.4, glibc 2.23)
-`mipsel-unknown-linux-gnu` | MIPS (LE) Linux (kernel 4.4, glibc 2.23)
 `mipsel-unknown-linux-uclibc` | ✓ |  | MIPS (LE) Linux with uClibc
 `mipsel-unknown-none` | * |  | Bare MIPS (LE) softfloat
 [`mipsisa32r6-unknown-linux-gnu`](platform-support/mips-release-6.md) | ? |  | 32-bit MIPS Release 6 Big Endian
diff --git a/src/doc/unstable-book/src/compiler-flags/no-jump-tables.md b/src/doc/unstable-book/src/compiler-flags/no-jump-tables.md
new file mode 100644
index 00000000000..f096c20f4bd
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/no-jump-tables.md
@@ -0,0 +1,19 @@
+# `no-jump-tables`
+
+The tracking issue for this feature is [#116592](https://github.com/rust-lang/rust/issues/116592)
+
+---
+
+This option enables the `-fno-jump-tables` flag for LLVM, which makes the
+codegen backend avoid generating jump tables when lowering switches.
+
+This option adds the LLVM `no-jump-tables=true` attribute to every function.
+
+The option can be used to help provide protection against
+jump-oriented-programming (JOP) attacks, such as with the linux kernel's [IBT].
+
+```sh
+RUSTFLAGS="-Zno-jump-tables" cargo +nightly build -Z build-std
+```
+
+[IBT]: https://www.phoronix.com/news/Linux-IBT-By-Default-Tip
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs b/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs
index de911f7a028..a2903e52ae0 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs
@@ -19,7 +19,7 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'
     if is_type_diagnostic_item(cx, ex_ty, sym::Result) {
         for arm in arms {
             if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind {
-                let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false));
+                let path_str = rustc_hir_pretty::qpath_to_string(path);
                 if path_str == "Err" {
                     let mut matching_wild = inner.iter().any(is_wild);
                     let mut ident_bind_name = kw::Underscore;
diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs
index e53e146ec5d..01b850cdb11 100644
--- a/src/tools/clippy/clippy_lints/src/mut_reference.rs
+++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs
@@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
                         cx,
                         arguments.iter().collect(),
                         cx.typeck_results().expr_ty(fn_expr),
-                        &rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)),
+                        &rustc_hir_pretty::qpath_to_string(path),
                         "function",
                     );
                 }
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index 5e10427d171..474e7da0000 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-3d575a2f2ef8a6eb99064bb31c16feb8d508f1ee
+2a7c2df506fcd5611967a203cc994da5f21abd1e
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 4cb1e7a826b..439cff84bd4 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -12,7 +12,6 @@ use rand::rngs::StdRng;
 use rand::SeedableRng;
 
 use rustc_ast::ast::Mutability;
-use rustc_const_eval::const_eval::CheckAlignment;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 #[allow(unused)]
 use rustc_data_structures::static_assert_size;
@@ -885,12 +884,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
     const PANIC_ON_ALLOC_FAIL: bool = false;
 
     #[inline(always)]
-    fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> CheckAlignment {
-        if ecx.machine.check_alignment == AlignmentCheck::None {
-            CheckAlignment::No
-        } else {
-            CheckAlignment::Error
-        }
+    fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
+        ecx.machine.check_alignment != AlignmentCheck::None
     }
 
     #[inline(always)]
@@ -898,15 +893,6 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
         ecx.machine.check_alignment == AlignmentCheck::Int
     }
 
-    fn alignment_check_failed(
-        _ecx: &InterpCx<'mir, 'tcx, Self>,
-        has: Align,
-        required: Align,
-        _check: CheckAlignment,
-    ) -> InterpResult<'tcx, ()> {
-        throw_ub!(AlignmentCheckFailed { has, required })
-    }
-
     #[inline(always)]
     fn enforce_validity(ecx: &MiriInterpCx<'mir, 'tcx>, _layout: TyAndLayout<'tcx>) -> bool {
         ecx.machine.validate
diff --git a/tests/assembly/x86_64-array-pair-load-store-merge.rs b/tests/assembly/x86_64-array-pair-load-store-merge.rs
new file mode 100644
index 00000000000..55e317e91bf
--- /dev/null
+++ b/tests/assembly/x86_64-array-pair-load-store-merge.rs
@@ -0,0 +1,20 @@
+// assembly-output: emit-asm
+// compile-flags: --crate-type=lib -O -C llvm-args=-x86-asm-syntax=intel
+// only-x86_64
+// ignore-sgx
+// ignore-macos (manipulates rsp too)
+
+// Depending on various codegen choices, this might end up copying
+// a `<2 x i8>`, an `i16`, or two `i8`s.
+// Regardless of those choices, make sure the instructions use (2-byte) words.
+
+// CHECK-LABEL: array_copy_2_elements:
+#[no_mangle]
+pub fn array_copy_2_elements(a: &[u8; 2], p: &mut [u8; 2]) {
+    // CHECK-NOT: byte
+    // CHECK-NOT: mov
+    // CHECK: mov{{.+}}, word ptr
+    // CHECK-NEXT: mov word ptr
+    // CHECK-NEXT: ret
+    *p = *a;
+}
diff --git a/tests/codegen/array-codegen.rs b/tests/codegen/array-codegen.rs
index ba0d444f97e..bf5ae74679b 100644
--- a/tests/codegen/array-codegen.rs
+++ b/tests/codegen/array-codegen.rs
@@ -32,3 +32,25 @@ pub fn array_copy(a: &[u8; 4], p: &mut [u8; 4]) {
     // CHECK: store <4 x i8> %[[TEMP2]], ptr %p, align 1
     *p = *a;
 }
+
+// CHECK-LABEL: @array_copy_1_element
+#[no_mangle]
+pub fn array_copy_1_element(a: &[u8; 1], p: &mut [u8; 1]) {
+    // CHECK: %[[LOCAL:.+]] = alloca [1 x i8], align 1
+    // CHECK: %[[TEMP1:.+]] = load i8, ptr %a, align 1
+    // CHECK: store i8 %[[TEMP1]], ptr %[[LOCAL]], align 1
+    // CHECK: %[[TEMP2:.+]] = load i8, ptr %[[LOCAL]], align 1
+    // CHECK: store i8 %[[TEMP2]], ptr %p, align 1
+    *p = *a;
+}
+
+// CHECK-LABEL: @array_copy_2_elements
+#[no_mangle]
+pub fn array_copy_2_elements(a: &[u8; 2], p: &mut [u8; 2]) {
+    // CHECK: %[[LOCAL:.+]] = alloca [2 x i8], align 1
+    // CHECK: %[[TEMP1:.+]] = load <2 x i8>, ptr %a, align 1
+    // CHECK: store <2 x i8> %[[TEMP1]], ptr %[[LOCAL]], align 1
+    // CHECK: %[[TEMP2:.+]] = load <2 x i8>, ptr %[[LOCAL]], align 1
+    // CHECK: store <2 x i8> %[[TEMP2]], ptr %p, align 1
+    *p = *a;
+}
diff --git a/tests/codegen/array-optimized.rs b/tests/codegen/array-optimized.rs
new file mode 100644
index 00000000000..27448fdcfad
--- /dev/null
+++ b/tests/codegen/array-optimized.rs
@@ -0,0 +1,33 @@
+// compile-flags: -O
+
+#![crate_type = "lib"]
+
+// CHECK-LABEL: @array_copy_1_element
+#[no_mangle]
+pub fn array_copy_1_element(a: &[u8; 1], p: &mut [u8; 1]) {
+    // CHECK-NOT: alloca
+    // CHECK: %[[TEMP:.+]] = load i8, ptr %a, align 1
+    // CHECK: store i8 %[[TEMP]], ptr %p, align 1
+    // CHECK: ret
+    *p = *a;
+}
+
+// CHECK-LABEL: @array_copy_2_elements
+#[no_mangle]
+pub fn array_copy_2_elements(a: &[u8; 2], p: &mut [u8; 2]) {
+    // CHECK-NOT: alloca
+    // CHECK: %[[TEMP:.+]] = load <2 x i8>, ptr %a, align 1
+    // CHECK: store <2 x i8> %[[TEMP]], ptr %p, align 1
+    // CHECK: ret
+    *p = *a;
+}
+
+// CHECK-LABEL: @array_copy_4_elements
+#[no_mangle]
+pub fn array_copy_4_elements(a: &[u8; 4], p: &mut [u8; 4]) {
+    // CHECK-NOT: alloca
+    // CHECK: %[[TEMP:.+]] = load <4 x i8>, ptr %a, align 1
+    // CHECK: store <4 x i8> %[[TEMP]], ptr %p, align 1
+    // CHECK: ret
+    *p = *a;
+}
diff --git a/tests/rustdoc-ui/check-cfg/check-cfg.stderr b/tests/rustdoc-ui/check-cfg/check-cfg.stderr
index 03fb6f96fb5..d010c1f7ec6 100644
--- a/tests/rustdoc-ui/check-cfg/check-cfg.stderr
+++ b/tests/rustdoc-ui/check-cfg/check-cfg.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `uniz`
   --> $DIR/check-cfg.rs:5:7
    |
 LL | #[cfg(uniz)]
diff --git a/tests/rustdoc-ui/doctest/check-cfg-test.stderr b/tests/rustdoc-ui/doctest/check-cfg-test.stderr
index f84543c2072..0bfd569e381 100644
--- a/tests/rustdoc-ui/doctest/check-cfg-test.stderr
+++ b/tests/rustdoc-ui/doctest/check-cfg-test.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `invalid`
   --> $DIR/check-cfg-test.rs:9:7
    |
 LL | #[cfg(feature = "invalid")]
diff --git a/tests/ui/check-cfg/allow-same-level.stderr b/tests/ui/check-cfg/allow-same-level.stderr
index 7797de584b9..b0c459fabf8 100644
--- a/tests/ui/check-cfg/allow-same-level.stderr
+++ b/tests/ui/check-cfg/allow-same-level.stderr
@@ -1,9 +1,10 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `FALSE`
   --> $DIR/allow-same-level.rs:7:7
    |
 LL | #[cfg(FALSE)]
    |       ^^^^^
    |
+   = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
 warning: 1 warning emitted
diff --git a/tests/ui/check-cfg/compact-names.stderr b/tests/ui/check-cfg/compact-names.stderr
index f1fc4285a71..b0228774b75 100644
--- a/tests/ui/check-cfg/compact-names.stderr
+++ b/tests/ui/check-cfg/compact-names.stderr
@@ -1,9 +1,10 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `target_architecture`
   --> $DIR/compact-names.rs:11:28
    |
 LL | #[cfg(target(os = "linux", architecture = "arm"))]
    |                            ^^^^^^^^^^^^^^^^^^^^
    |
+   = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
 warning: 1 warning emitted
diff --git a/tests/ui/check-cfg/compact-values.stderr b/tests/ui/check-cfg/compact-values.stderr
index b7269a652ea..bb2f4915b5e 100644
--- a/tests/ui/check-cfg/compact-values.stderr
+++ b/tests/ui/check-cfg/compact-values.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `X`
   --> $DIR/compact-values.rs:11:28
    |
 LL | #[cfg(target(os = "linux", arch = "X"))]
diff --git a/tests/ui/check-cfg/diagnotics.stderr b/tests/ui/check-cfg/diagnotics.stderr
index 8b9fef09d09..31c0db03a7e 100644
--- a/tests/ui/check-cfg/diagnotics.stderr
+++ b/tests/ui/check-cfg/diagnotics.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `featur`
   --> $DIR/diagnotics.rs:4:7
    |
 LL | #[cfg(featur)]
@@ -7,19 +7,18 @@ LL | #[cfg(featur)]
    = help: expected values for `feature` are: `foo`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `featur`
   --> $DIR/diagnotics.rs:8:7
    |
 LL | #[cfg(featur = "foo")]
    |       ^^^^^^^^^^^^^^
    |
-   = help: expected values for `feature` are: `foo`
 help: there is a config with a similar name and value
    |
 LL | #[cfg(feature = "foo")]
    |       ~~~~~~~
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `featur`
   --> $DIR/diagnotics.rs:12:7
    |
 LL | #[cfg(featur = "fo")]
@@ -31,13 +30,13 @@ help: there is a config with a similar name and different values
 LL | #[cfg(feature = "foo")]
    |       ~~~~~~~~~~~~~~~
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `no_value`
   --> $DIR/diagnotics.rs:19:7
    |
 LL | #[cfg(no_value)]
    |       ^^^^^^^^ help: there is a config with a similar name: `no_values`
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `no_value`
   --> $DIR/diagnotics.rs:23:7
    |
 LL | #[cfg(no_value = "foo")]
@@ -48,7 +47,7 @@ help: there is a config with a similar name and no value
 LL | #[cfg(no_values)]
    |       ~~~~~~~~~
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `bar`
   --> $DIR/diagnotics.rs:27:7
    |
 LL | #[cfg(no_values = "bar")]
diff --git a/tests/ui/check-cfg/empty-names.stderr b/tests/ui/check-cfg/empty-names.stderr
index f926d1133cc..9bba852c6de 100644
--- a/tests/ui/check-cfg/empty-names.stderr
+++ b/tests/ui/check-cfg/empty-names.stderr
@@ -1,9 +1,10 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `unknown_key`
   --> $DIR/empty-names.rs:6:7
    |
 LL | #[cfg(unknown_key = "value")]
    |       ^^^^^^^^^^^^^^^^^^^^^
    |
+   = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
 warning: 1 warning emitted
diff --git a/tests/ui/check-cfg/empty-values.stderr b/tests/ui/check-cfg/empty-values.stderr
index a0168b2caa8..932651c5bfe 100644
--- a/tests/ui/check-cfg/empty-values.stderr
+++ b/tests/ui/check-cfg/empty-values.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `value`
   --> $DIR/empty-values.rs:6:7
    |
 LL | #[cfg(test = "value")]
diff --git a/tests/ui/check-cfg/invalid-cfg-name.stderr b/tests/ui/check-cfg/invalid-cfg-name.stderr
index ed09f8cb66d..8c3c72c9667 100644
--- a/tests/ui/check-cfg/invalid-cfg-name.stderr
+++ b/tests/ui/check-cfg/invalid-cfg-name.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `widnows`
   --> $DIR/invalid-cfg-name.rs:7:7
    |
 LL | #[cfg(widnows)]
diff --git a/tests/ui/check-cfg/invalid-cfg-value.stderr b/tests/ui/check-cfg/invalid-cfg-value.stderr
index 776d264a7ad..947beac6ffd 100644
--- a/tests/ui/check-cfg/invalid-cfg-value.stderr
+++ b/tests/ui/check-cfg/invalid-cfg-value.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `sedre`
   --> $DIR/invalid-cfg-value.rs:7:7
    |
 LL | #[cfg(feature = "sedre")]
@@ -9,7 +9,7 @@ LL | #[cfg(feature = "sedre")]
    = note: expected values for `feature` are: `full`, `serde`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `rand`
   --> $DIR/invalid-cfg-value.rs:14:7
    |
 LL | #[cfg(feature = "rand")]
diff --git a/tests/ui/check-cfg/mix.stderr b/tests/ui/check-cfg/mix.stderr
index 07c514aed52..23da9f22a72 100644
--- a/tests/ui/check-cfg/mix.stderr
+++ b/tests/ui/check-cfg/mix.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `widnows`
   --> $DIR/mix.rs:11:7
    |
 LL | #[cfg(widnows)]
@@ -6,7 +6,7 @@ LL | #[cfg(widnows)]
    |
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: (none)
   --> $DIR/mix.rs:15:7
    |
 LL | #[cfg(feature)]
@@ -14,7 +14,7 @@ LL | #[cfg(feature)]
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `bar`
   --> $DIR/mix.rs:22:7
    |
 LL | #[cfg(feature = "bar")]
@@ -22,7 +22,7 @@ LL | #[cfg(feature = "bar")]
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `zebra`
   --> $DIR/mix.rs:26:7
    |
 LL | #[cfg(feature = "zebra")]
@@ -30,11 +30,13 @@ LL | #[cfg(feature = "zebra")]
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `uu`
   --> $DIR/mix.rs:30:12
    |
 LL | #[cfg_attr(uu, test)]
    |            ^^
+   |
+   = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
 
 warning: unexpected condition value `bar` for condition name `feature`
    |
@@ -44,13 +46,13 @@ warning: unexpected `unknown_name` as condition name
    |
    = help: was set with `--cfg` but isn't in the `--check-cfg` expected names
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `widnows`
   --> $DIR/mix.rs:39:10
    |
 LL |     cfg!(widnows);
    |          ^^^^^^^ help: there is a config with a similar name: `windows`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `bar`
   --> $DIR/mix.rs:42:10
    |
 LL |     cfg!(feature = "bar");
@@ -58,7 +60,7 @@ LL |     cfg!(feature = "bar");
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `zebra`
   --> $DIR/mix.rs:44:10
    |
 LL |     cfg!(feature = "zebra");
@@ -66,25 +68,25 @@ LL |     cfg!(feature = "zebra");
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:46:10
    |
 LL |     cfg!(xxx = "foo");
    |          ^^^^^^^^^^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:48:10
    |
 LL |     cfg!(xxx);
    |          ^^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:50:14
    |
 LL |     cfg!(any(xxx, windows));
    |              ^^^
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `bad`
   --> $DIR/mix.rs:52:14
    |
 LL |     cfg!(any(feature = "bad", windows));
@@ -92,43 +94,43 @@ LL |     cfg!(any(feature = "bad", windows));
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:54:23
    |
 LL |     cfg!(any(windows, xxx));
    |                       ^^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:56:20
    |
 LL |     cfg!(all(unix, xxx));
    |                    ^^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `aa`
   --> $DIR/mix.rs:58:14
    |
 LL |     cfg!(all(aa, bb));
    |              ^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `bb`
   --> $DIR/mix.rs:58:18
    |
 LL |     cfg!(all(aa, bb));
    |                  ^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `aa`
   --> $DIR/mix.rs:61:14
    |
 LL |     cfg!(any(aa, bb));
    |              ^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `bb`
   --> $DIR/mix.rs:61:18
    |
 LL |     cfg!(any(aa, bb));
    |                  ^^
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `zebra`
   --> $DIR/mix.rs:64:20
    |
 LL |     cfg!(any(unix, feature = "zebra"));
@@ -136,13 +138,13 @@ LL |     cfg!(any(unix, feature = "zebra"));
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:66:14
    |
 LL |     cfg!(any(xxx, feature = "zebra"));
    |              ^^^
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `zebra`
   --> $DIR/mix.rs:66:19
    |
 LL |     cfg!(any(xxx, feature = "zebra"));
@@ -150,19 +152,19 @@ LL |     cfg!(any(xxx, feature = "zebra"));
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:69:14
    |
 LL |     cfg!(any(xxx, unix, xxx));
    |              ^^^
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `xxx`
   --> $DIR/mix.rs:69:25
    |
 LL |     cfg!(any(xxx, unix, xxx));
    |                         ^^^
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `zebra`
   --> $DIR/mix.rs:72:14
    |
 LL |     cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra"));
@@ -170,7 +172,7 @@ LL |     cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra"));
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `zebra`
   --> $DIR/mix.rs:72:33
    |
 LL |     cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra"));
@@ -178,7 +180,7 @@ LL |     cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra"));
    |
    = note: expected values for `feature` are: `foo`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `zebra`
   --> $DIR/mix.rs:72:52
    |
 LL |     cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra"));
diff --git a/tests/ui/check-cfg/no-values.stderr b/tests/ui/check-cfg/no-values.stderr
index ffa87dc58f2..b05a569dd01 100644
--- a/tests/ui/check-cfg/no-values.stderr
+++ b/tests/ui/check-cfg/no-values.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `foo`
   --> $DIR/no-values.rs:6:7
    |
 LL | #[cfg(feature = "foo")]
@@ -9,7 +9,7 @@ LL | #[cfg(feature = "foo")]
    = note: no expected value for `feature`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `foo`
   --> $DIR/no-values.rs:10:7
    |
 LL | #[cfg(test = "foo")]
diff --git a/tests/ui/check-cfg/order-independant.names_after.stderr b/tests/ui/check-cfg/order-independant.names_after.stderr
index 91b81428b38..a308358e485 100644
--- a/tests/ui/check-cfg/order-independant.names_after.stderr
+++ b/tests/ui/check-cfg/order-independant.names_after.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: (none)
   --> $DIR/order-independant.rs:8:7
    |
 LL | #[cfg(a)]
@@ -7,7 +7,7 @@ LL | #[cfg(a)]
    = note: expected values for `a` are: `b`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `unk`
   --> $DIR/order-independant.rs:12:7
    |
 LL | #[cfg(a = "unk")]
diff --git a/tests/ui/check-cfg/order-independant.names_before.stderr b/tests/ui/check-cfg/order-independant.names_before.stderr
index 91b81428b38..a308358e485 100644
--- a/tests/ui/check-cfg/order-independant.names_before.stderr
+++ b/tests/ui/check-cfg/order-independant.names_before.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: (none)
   --> $DIR/order-independant.rs:8:7
    |
 LL | #[cfg(a)]
@@ -7,7 +7,7 @@ LL | #[cfg(a)]
    = note: expected values for `a` are: `b`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `unk`
   --> $DIR/order-independant.rs:12:7
    |
 LL | #[cfg(a = "unk")]
diff --git a/tests/ui/check-cfg/stmt-no-ice.stderr b/tests/ui/check-cfg/stmt-no-ice.stderr
index da65b596911..900ea4e4da0 100644
--- a/tests/ui/check-cfg/stmt-no-ice.stderr
+++ b/tests/ui/check-cfg/stmt-no-ice.stderr
@@ -1,9 +1,10 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `crossbeam_loom`
   --> $DIR/stmt-no-ice.rs:7:11
    |
 LL |     #[cfg(crossbeam_loom)]
    |           ^^^^^^^^^^^^^^
    |
+   = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
 warning: 1 warning emitted
diff --git a/tests/ui/check-cfg/values-target-json.stderr b/tests/ui/check-cfg/values-target-json.stderr
index c705152d9fc..e71149f337f 100644
--- a/tests/ui/check-cfg/values-target-json.stderr
+++ b/tests/ui/check-cfg/values-target-json.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `linuz`
   --> $DIR/values-target-json.rs:13:7
    |
 LL | #[cfg(target_os = "linuz")]
diff --git a/tests/ui/check-cfg/well-known-names.stderr b/tests/ui/check-cfg/well-known-names.stderr
index 34c5d6172d9..a5d38a99eee 100644
--- a/tests/ui/check-cfg/well-known-names.stderr
+++ b/tests/ui/check-cfg/well-known-names.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `target_oz`
   --> $DIR/well-known-names.rs:6:7
    |
 LL | #[cfg(target_oz = "linux")]
@@ -8,7 +8,7 @@ LL | #[cfg(target_oz = "linux")]
    |
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `features`
   --> $DIR/well-known-names.rs:13:7
    |
 LL | #[cfg(features = "foo")]
@@ -16,7 +16,7 @@ LL | #[cfg(features = "foo")]
    |       |
    |       help: there is a config with a similar name: `feature`
 
-warning: unexpected `cfg` condition name
+warning: unexpected `cfg` condition name: `uniw`
   --> $DIR/well-known-names.rs:20:7
    |
 LL | #[cfg(uniw)]
diff --git a/tests/ui/check-cfg/well-known-values.stderr b/tests/ui/check-cfg/well-known-values.stderr
index b381f5a4a0a..6877d8f5bb7 100644
--- a/tests/ui/check-cfg/well-known-values.stderr
+++ b/tests/ui/check-cfg/well-known-values.stderr
@@ -1,4 +1,4 @@
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `linuz`
   --> $DIR/well-known-values.rs:7:7
    |
 LL | #[cfg(target_os = "linuz")]
@@ -9,7 +9,7 @@ LL | #[cfg(target_os = "linuz")]
    = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`
    = note: `#[warn(unexpected_cfgs)]` on by default
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `0`
   --> $DIR/well-known-values.rs:14:7
    |
 LL | #[cfg(target_has_atomic = "0")]
@@ -19,7 +19,7 @@ LL | #[cfg(target_has_atomic = "0")]
    |
    = note: expected values for `target_has_atomic` are: (none), `128`, `16`, `32`, `64`, `8`, `ptr`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `aa`
   --> $DIR/well-known-values.rs:21:7
    |
 LL | #[cfg(unix = "aa")]
@@ -29,7 +29,7 @@ LL | #[cfg(unix = "aa")]
    |
    = note: no expected value for `unix`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `miri`
   --> $DIR/well-known-values.rs:28:7
    |
 LL | #[cfg(miri = "miri")]
@@ -39,7 +39,7 @@ LL | #[cfg(miri = "miri")]
    |
    = note: no expected value for `miri`
 
-warning: unexpected `cfg` condition value
+warning: unexpected `cfg` condition value: `linux`
   --> $DIR/well-known-values.rs:35:7
    |
 LL | #[cfg(doc = "linux")]
diff --git a/tests/ui/consts/const-eval/raw-pointer-ub.rs b/tests/ui/consts/const-eval/raw-pointer-ub.rs
new file mode 100644
index 00000000000..e53865309eb
--- /dev/null
+++ b/tests/ui/consts/const-eval/raw-pointer-ub.rs
@@ -0,0 +1,36 @@
+// normalize-stderr-test "alloc\d+" -> "allocN"
+#![feature(const_pointer_byte_offsets)]
+#![feature(pointer_byte_offsets)]
+#![feature(const_mut_refs)]
+
+const MISALIGNED_LOAD: () = unsafe {
+    let mem = [0u32; 8];
+    let ptr = mem.as_ptr().byte_add(1);
+    let _val = *ptr; //~ERROR: evaluation of constant value failed
+    //~^NOTE: accessing memory with alignment 1, but alignment 4 is required
+};
+
+const MISALIGNED_STORE: () = unsafe {
+    let mut mem = [0u32; 8];
+    let ptr = mem.as_mut_ptr().byte_add(1);
+    *ptr = 0; //~ERROR: evaluation of constant value failed
+    //~^NOTE: accessing memory with alignment 1, but alignment 4 is required
+};
+
+const MISALIGNED_COPY: () = unsafe {
+    let x = &[0_u8; 4];
+    let y = x.as_ptr().cast::<u32>();
+    let mut z = 123;
+    y.copy_to_nonoverlapping(&mut z, 1);
+    //~^NOTE
+    // The actual error points into the implementation of `copy_to_nonoverlapping`.
+};
+
+const OOB: () = unsafe {
+    let mem = [0u32; 1];
+    let ptr = mem.as_ptr().cast::<u64>();
+    let _val = *ptr; //~ERROR: evaluation of constant value failed
+    //~^NOTE: size 4, so pointer to 8 bytes starting at offset 0 is out-of-bounds
+};
+
+fn main() {}
diff --git a/tests/ui/consts/const-eval/raw-pointer-ub.stderr b/tests/ui/consts/const-eval/raw-pointer-ub.stderr
new file mode 100644
index 00000000000..96b7f4f58f9
--- /dev/null
+++ b/tests/ui/consts/const-eval/raw-pointer-ub.stderr
@@ -0,0 +1,36 @@
+error[E0080]: evaluation of constant value failed
+  --> $DIR/raw-pointer-ub.rs:9:16
+   |
+LL |     let _val = *ptr;
+   |                ^^^^ accessing memory with alignment 1, but alignment 4 is required
+
+error[E0080]: evaluation of constant value failed
+  --> $DIR/raw-pointer-ub.rs:16:5
+   |
+LL |     *ptr = 0;
+   |     ^^^^^^^^ accessing memory with alignment 1, but alignment 4 is required
+
+error[E0080]: evaluation of constant value failed
+  --> $SRC_DIR/core/src/intrinsics.rs:LL:COL
+   |
+   = note: accessing memory with alignment 1, but alignment 4 is required
+   |
+note: inside `copy_nonoverlapping::<u32>`
+  --> $SRC_DIR/core/src/intrinsics.rs:LL:COL
+note: inside `ptr::const_ptr::<impl *const u32>::copy_to_nonoverlapping`
+  --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
+note: inside `MISALIGNED_COPY`
+  --> $DIR/raw-pointer-ub.rs:24:5
+   |
+LL |     y.copy_to_nonoverlapping(&mut z, 1);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0080]: evaluation of constant value failed
+  --> $DIR/raw-pointer-ub.rs:32:16
+   |
+LL |     let _val = *ptr;
+   |                ^^^^ dereferencing pointer failed: allocN has size 4, so pointer to 8 bytes starting at offset 0 is out-of-bounds
+
+error: aborting due to 4 previous errors
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs
new file mode 100644
index 00000000000..35307586391
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs
@@ -0,0 +1,22 @@
+#![feature(diagnostic_namespace)]
+
+#[diagnostic::on_unimplemented(
+    //~^WARN malformed `on_unimplemented` attribute
+    //~|WARN malformed `on_unimplemented` attribute
+    if(Self = ()),
+    message = "not used yet",
+    label = "not used yet",
+    note = "not used yet"
+)]
+#[diagnostic::on_unimplemented(message = "fallback!!")]
+#[diagnostic::on_unimplemented(label = "fallback label")]
+#[diagnostic::on_unimplemented(note = "fallback note")]
+#[diagnostic::on_unimplemented(message = "fallback2!!")]
+trait Foo {}
+
+fn takes_foo(_: impl Foo) {}
+
+fn main() {
+    takes_foo(());
+    //~^ERROR fallback!!
+}
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr
new file mode 100644
index 00000000000..6a83d8e39c6
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr
@@ -0,0 +1,52 @@
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:3:1
+   |
+LL | / #[diagnostic::on_unimplemented(
+LL | |
+LL | |
+LL | |     if(Self = ()),
+...  |
+LL | |     note = "not used yet"
+LL | | )]
+   | |__^
+   |
+   = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:3:1
+   |
+LL | / #[diagnostic::on_unimplemented(
+LL | |
+LL | |
+LL | |     if(Self = ()),
+...  |
+LL | |     note = "not used yet"
+LL | | )]
+   | |__^
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error[E0277]: fallback!!
+  --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:20:15
+   |
+LL |     takes_foo(());
+   |     --------- ^^ fallback label
+   |     |
+   |     required by a bound introduced by this call
+   |
+   = help: the trait `Foo` is not implemented for `()`
+   = note: fallback note
+help: this trait has no implementations, consider adding one
+  --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:15:1
+   |
+LL | trait Foo {}
+   | ^^^^^^^^^
+note: required by a bound in `takes_foo`
+  --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:17:22
+   |
+LL | fn takes_foo(_: impl Foo) {}
+   |                      ^^^ required by this bound in `takes_foo`
+
+error: aborting due to previous error; 2 warnings emitted
+
+For more information about this error, try `rustc --explain E0277`.
diff --git a/tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed
new file mode 100644
index 00000000000..b81515cda9a
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed
@@ -0,0 +1,10 @@
+// run-rustfix
+fn main() {
+    let _ = vec![1, 2, 3].into_iter().map(|x| {
+        let y = x; //~ ERROR expected expression, found `let` statement
+        y
+    });
+    let _: () = foo(); //~ ERROR mismatched types
+}
+
+fn foo() {}
diff --git a/tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs
new file mode 100644
index 00000000000..e47ad562fb0
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs
@@ -0,0 +1,10 @@
+// run-rustfix
+fn main() {
+    let _ = vec![1, 2, 3].into_iter().map(|x|
+        let y = x; //~ ERROR expected expression, found `let` statement
+        y
+    );
+    let _: () = foo; //~ ERROR mismatched types
+}
+
+fn foo() {}
diff --git a/tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr
new file mode 100644
index 00000000000..fbb7e0e4df5
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr
@@ -0,0 +1,38 @@
+error: expected expression, found `let` statement
+  --> $DIR/missing_block_in_fn_call.rs:4:9
+   |
+LL |     let _ = vec![1, 2, 3].into_iter().map(|x|
+   |                                           --- while parsing the body of this closure
+LL |         let y = x;
+   |         ^^^
+   |
+   = note: only supported directly in conditions of `if` and `while` expressions
+help: you might have meant to open the body of the closure
+   |
+LL ~     let _ = vec![1, 2, 3].into_iter().map(|x| {
+LL |         let y = x;
+LL |         y
+LL ~     });
+   |
+
+error[E0308]: mismatched types
+  --> $DIR/missing_block_in_fn_call.rs:7:17
+   |
+LL |     let _: () = foo;
+   |            --   ^^^ expected `()`, found fn item
+   |            |
+   |            expected due to this
+...
+LL | fn foo() {}
+   | -------- function `foo` defined here
+   |
+   = note: expected unit type `()`
+                found fn item `fn() {foo}`
+help: use parentheses to call this function
+   |
+LL |     let _: () = foo();
+   |                    ++
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs
new file mode 100644
index 00000000000..1ee215d150d
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs
@@ -0,0 +1,6 @@
+fn main() {
+    let x = |x|
+        let y = x; //~ ERROR expected expression, found `let` statement
+        let _ = () + ();
+        y
+}
diff --git a/tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr
new file mode 100644
index 00000000000..d4640fba96c
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr
@@ -0,0 +1,16 @@
+error: expected expression, found `let` statement
+  --> $DIR/missing_block_in_let_binding.rs:3:9
+   |
+LL |     let x = |x|
+   |             --- while parsing the body of this closure
+LL |         let y = x;
+   |         ^^^
+   |
+   = note: only supported directly in conditions of `if` and `while` expressions
+help: you might have meant to open the body of the closure
+   |
+LL |     let x = |x| {
+   |                 +
+
+error: aborting due to previous error
+
diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed
new file mode 100644
index 00000000000..8014dc87c08
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed
@@ -0,0 +1,9 @@
+// run-rustfix
+fn main() {
+    let _ = vec![1, 2, 3].into_iter().map(|x| {
+        let y = x; //~ ERROR expected expression, found `let` statement
+        y
+    });
+    let _: () = foo(); //~ ERROR mismatched types
+}
+fn foo() {}
diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs
new file mode 100644
index 00000000000..9e4aca888ad
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs
@@ -0,0 +1,9 @@
+// run-rustfix
+fn main() {
+    let _ = vec![1, 2, 3].into_iter().map({|x|
+        let y = x; //~ ERROR expected expression, found `let` statement
+        y
+    });
+    let _: () = foo; //~ ERROR mismatched types
+}
+fn foo() {}
diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr
new file mode 100644
index 00000000000..5fc48d73b14
--- /dev/null
+++ b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr
@@ -0,0 +1,36 @@
+error: expected expression, found `let` statement
+  --> $DIR/ruby_style_closure_parse_error.rs:4:9
+   |
+LL |     let _ = vec![1, 2, 3].into_iter().map({|x|
+   |                                            --- while parsing the body of this closure
+LL |         let y = x;
+   |         ^^^
+   |
+   = note: only supported directly in conditions of `if` and `while` expressions
+help: you might have meant to open the body of the closure, instead of enclosing the closure in a block
+   |
+LL -     let _ = vec![1, 2, 3].into_iter().map({|x|
+LL +     let _ = vec![1, 2, 3].into_iter().map(|x| {
+   |
+
+error[E0308]: mismatched types
+  --> $DIR/ruby_style_closure_parse_error.rs:7:17
+   |
+LL |     let _: () = foo;
+   |            --   ^^^ expected `()`, found fn item
+   |            |
+   |            expected due to this
+LL | }
+LL | fn foo() {}
+   | -------- function `foo` defined here
+   |
+   = note: expected unit type `()`
+                found fn item `fn() {foo}`
+help: use parentheses to call this function
+   |
+LL |     let _: () = foo();
+   |                    ++
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr b/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr
index c16a90368e1..e09194d5d39 100644
--- a/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr
+++ b/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr
@@ -2,9 +2,15 @@ error: expected identifier, found `:`
   --> $DIR/or-patterns-syntactic-fail.rs:11:19
    |
 LL |     let _ = |A | B: E| ();
-   |                   ^ expected identifier
+   |             ----  ^ expected identifier
+   |             |
+   |             while parsing the body of this closure
    |
    = note: type ascription syntax has been removed, see issue #101728 <https://github.com/rust-lang/rust/issues/101728>
+help: you might have meant to open the body of the closure
+   |
+LL |     let _ = |A | { B: E| ();
+   |                  +
 
 error: top-level or-patterns are not allowed in function parameters
   --> $DIR/or-patterns-syntactic-fail.rs:18:13
diff --git a/tests/ui/parser/issues/issue-32505.rs b/tests/ui/parser/issues/issue-32505.rs
index f31c00e5cc3..d95e7dc7d9e 100644
--- a/tests/ui/parser/issues/issue-32505.rs
+++ b/tests/ui/parser/issues/issue-32505.rs
@@ -1,5 +1,6 @@
 pub fn test() {
     foo(|_|) //~ ERROR expected expression, found `)`
+    //~^ ERROR cannot find function `foo` in this scope
 }
 
 fn main() { }
diff --git a/tests/ui/parser/issues/issue-32505.stderr b/tests/ui/parser/issues/issue-32505.stderr
index cdd779a93ef..27ad2c3e5be 100644
--- a/tests/ui/parser/issues/issue-32505.stderr
+++ b/tests/ui/parser/issues/issue-32505.stderr
@@ -2,7 +2,21 @@ error: expected expression, found `)`
   --> $DIR/issue-32505.rs:2:12
    |
 LL |     foo(|_|)
-   |            ^ expected expression
+   |         ---^ expected expression
+   |         |
+   |         while parsing the body of this closure
+   |
+help: you might have meant to open the body of the closure
+   |
+LL |     foo(|_| {})
+   |            ++
+
+error[E0425]: cannot find function `foo` in this scope
+  --> $DIR/issue-32505.rs:2:5
+   |
+LL |     foo(|_|)
+   |     ^^^ not found in this scope
 
-error: aborting due to previous error
+error: aborting due to 2 previous errors
 
+For more information about this error, try `rustc --explain E0425`.
diff --git a/tests/ui/pattern/usefulness/slice_of_empty.rs b/tests/ui/pattern/usefulness/slice_of_empty.rs
new file mode 100644
index 00000000000..fe068871195
--- /dev/null
+++ b/tests/ui/pattern/usefulness/slice_of_empty.rs
@@ -0,0 +1,22 @@
+#![feature(never_type)]
+#![feature(exhaustive_patterns)]
+#![deny(unreachable_patterns)]
+
+fn main() {}
+
+fn foo(nevers: &[!]) {
+    match nevers {
+        &[] => (),
+    };
+
+    match nevers {
+        &[] => (),
+        &[_] => (),        //~ ERROR unreachable pattern
+        &[_, _, ..] => (), //~ ERROR unreachable pattern
+    };
+
+    match nevers {
+        //~^ ERROR non-exhaustive patterns: `&[]` not covered
+        &[_] => (), //~ ERROR unreachable pattern
+    };
+}
diff --git a/tests/ui/pattern/usefulness/slice_of_empty.stderr b/tests/ui/pattern/usefulness/slice_of_empty.stderr
new file mode 100644
index 00000000000..07bb6b3a67d
--- /dev/null
+++ b/tests/ui/pattern/usefulness/slice_of_empty.stderr
@@ -0,0 +1,39 @@
+error: unreachable pattern
+  --> $DIR/slice_of_empty.rs:14:9
+   |
+LL |         &[_] => (),
+   |         ^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/slice_of_empty.rs:3:9
+   |
+LL | #![deny(unreachable_patterns)]
+   |         ^^^^^^^^^^^^^^^^^^^^
+
+error: unreachable pattern
+  --> $DIR/slice_of_empty.rs:15:9
+   |
+LL |         &[_, _, ..] => (),
+   |         ^^^^^^^^^^^
+
+error: unreachable pattern
+  --> $DIR/slice_of_empty.rs:20:9
+   |
+LL |         &[_] => (),
+   |         ^^^^
+
+error[E0004]: non-exhaustive patterns: `&[]` not covered
+  --> $DIR/slice_of_empty.rs:18:11
+   |
+LL |     match nevers {
+   |           ^^^^^^ pattern `&[]` not covered
+   |
+   = note: the matched value is of type `&[!]`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
+   |
+LL |         &[_] => (), &[] => todo!(),
+   |                   ++++++++++++++++
+
+error: aborting due to 4 previous errors
+
+For more information about this error, try `rustc --explain E0004`.
diff --git a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs
new file mode 100644
index 00000000000..157383fede1
--- /dev/null
+++ b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs
@@ -0,0 +1,23 @@
+fn test() {
+    let x = match **x { //~ ERROR
+        Some(&a) if { panic!() } => {}
+    };
+    let mut p = &x;
+
+    {
+        let mut closure = expect_sig(|p, y| *p = y);
+        closure(&mut p, &y); //~ ERROR
+        //~^ ERROR
+    }
+
+    deref(p); //~ ERROR
+}
+
+fn expect_sig<F>(f: F) -> F
+where
+    F: FnMut(&mut &i32, &i32),
+{
+    f
+}
+
+fn main() {}
diff --git a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr
new file mode 100644
index 00000000000..1470c32d7de
--- /dev/null
+++ b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr
@@ -0,0 +1,44 @@
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:2:21
+   |
+LL |     let x = match **x {
+   |                     ^ not found in this scope
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:9:26
+   |
+LL |         closure(&mut p, &y);
+   |                          ^ help: a local variable with a similar name exists: `p`
+
+error[E0308]: mismatched types
+  --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:9:17
+   |
+LL |         closure(&mut p, &y);
+   |         ------- ^^^^^^ expected `&mut &i32`, found `&mut &()`
+   |         |
+   |         arguments to this function are incorrect
+   |
+   = note: expected mutable reference `&mut &i32`
+              found mutable reference `&mut &()`
+note: closure parameter defined here
+  --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:8:39
+   |
+LL |         let mut closure = expect_sig(|p, y| *p = y);
+   |                                       ^
+
+error[E0425]: cannot find function `deref` in this scope
+  --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:13:5
+   |
+LL |     deref(p);
+   |     ^^^^^ not found in this scope
+   |
+help: use the `.` operator to call the method `Deref::deref` on `&&()`
+   |
+LL -     deref(p);
+LL +     p.deref();
+   |
+
+error: aborting due to 4 previous errors
+
+Some errors have detailed explanations: E0308, E0425.
+For more information about an error, try `rustc --explain E0308`.