about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_abi/src/lib.rs49
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs10
-rw-r--r--compiler/rustc_ast_lowering/src/index.rs8
-rw-r--r--compiler/rustc_borrowck/src/region_infer/opaque_types.rs14
-rw-r--r--compiler/rustc_borrowck/src/type_check/mod.rs8
-rw-r--r--compiler/rustc_codegen_cranelift/src/common.rs16
-rw-r--r--compiler/rustc_codegen_gcc/src/builder.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/consts.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/context.rs15
-rw-r--r--compiler/rustc_codegen_gcc/src/type_of.rs3
-rw-r--r--compiler/rustc_codegen_llvm/Cargo.toml1
-rw-r--r--compiler/rustc_codegen_llvm/messages.ftl8
-rw-r--r--compiler/rustc_codegen_llvm/src/allocator.rs4
-rw-r--r--compiler/rustc_codegen_llvm/src/back/lto.rs2
-rw-r--r--compiler/rustc_codegen_llvm/src/back/write.rs8
-rw-r--r--compiler/rustc_codegen_llvm/src/base.rs6
-rw-r--r--compiler/rustc_codegen_llvm/src/builder.rs18
-rw-r--r--compiler/rustc_codegen_llvm/src/consts.rs26
-rw-r--r--compiler/rustc_codegen_llvm/src/context.rs61
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs3
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs9
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/mod.rs7
-rw-r--r--compiler/rustc_codegen_llvm/src/errors.rs12
-rw-r--r--compiler/rustc_codegen_llvm/src/lib.rs1
-rw-r--r--compiler/rustc_codegen_llvm/src/type_of.rs3
-rw-r--r--compiler/rustc_codegen_ssa/src/base.rs2
-rw-r--r--compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs3
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs13
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/debuginfo.rs3
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/mod.rs13
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/operand.rs49
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/rvalue.rs68
-rw-r--r--compiler/rustc_const_eval/messages.ftl397
-rw-r--r--compiler/rustc_const_eval/src/const_eval/error.rs280
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs100
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs119
-rw-r--r--compiler/rustc_const_eval/src/const_eval/mod.rs13
-rw-r--r--compiler/rustc_const_eval/src/errors.rs676
-rw-r--r--compiler/rustc_const_eval/src/interpret/cast.rs20
-rw-r--r--compiler/rustc_const_eval/src/interpret/eval_context.rs45
-rw-r--r--compiler/rustc_const_eval/src/interpret/intern.rs27
-rw-r--r--compiler/rustc_const_eval/src/interpret/intrinsics.rs75
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs63
-rw-r--r--compiler/rustc_const_eval/src/interpret/terminator.rs51
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs217
-rw-r--r--compiler/rustc_const_eval/src/lib.rs3
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/ops.rs133
-rw-r--r--compiler/rustc_const_eval/src/util/check_validity_requirement.rs8
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0741.md12
-rw-r--r--compiler/rustc_error_messages/src/lib.rs4
-rw-r--r--compiler/rustc_errors/messages.ftl6
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs4
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs32
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs23
-rw-r--r--compiler/rustc_errors/src/lib.rs3
-rw-r--r--compiler/rustc_feature/src/active.rs2
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs4
-rw-r--r--compiler/rustc_feature/src/removed.rs4
-rw-r--r--compiler/rustc_hir/src/hir.rs21
-rw-r--r--compiler/rustc_hir/src/intravisit.rs10
-rw-r--r--compiler/rustc_hir_analysis/src/check/check.rs6
-rw-r--r--compiler/rustc_hir_analysis/src/check/region.rs2
-rw-r--r--compiler/rustc_hir_analysis/src/check/wfcheck.rs91
-rw-r--r--compiler/rustc_hir_analysis/src/collect.rs2
-rw-r--r--compiler/rustc_hir_analysis/src/collect/generics_of.rs25
-rw-r--r--compiler/rustc_hir_analysis/src/collect/type_of.rs13
-rw-r--r--compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs111
-rw-r--r--compiler/rustc_hir_pretty/src/lib.rs7
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs31
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs10
-rw-r--r--compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs1
-rw-r--r--compiler/rustc_hir_typeck/src/writeback.rs12
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs2
-rw-r--r--compiler/rustc_lint/messages.ftl2
-rw-r--r--compiler/rustc_lint/src/builtin.rs12
-rw-r--r--compiler/rustc_lint/src/cast_ref_to_mut.rs72
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/lints.rs5
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs38
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs16
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs23
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs27
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs2
-rw-r--r--compiler/rustc_metadata/src/rmeta/encoder.rs13
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs2
-rw-r--r--compiler/rustc_middle/messages.ftl35
-rw-r--r--compiler/rustc_middle/src/error.rs55
-rw-r--r--compiler/rustc_middle/src/hir/map/mod.rs19
-rw-r--r--compiler/rustc_middle/src/lib.rs3
-rw-r--r--compiler/rustc_middle/src/middle/limits.rs11
-rw-r--r--compiler/rustc_middle/src/mir/interpret/allocation.rs48
-rw-r--r--compiler/rustc_middle/src/mir/interpret/error.rs444
-rw-r--r--compiler/rustc_middle/src/mir/interpret/mod.rs31
-rw-r--r--compiler/rustc_middle/src/mir/interpret/value.rs3
-rw-r--r--compiler/rustc_middle/src/mir/mod.rs90
-rw-r--r--compiler/rustc_middle/src/mir/mono.rs2
-rw-r--r--compiler/rustc_middle/src/mir/pretty.rs2
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs3
-rw-r--r--compiler/rustc_middle/src/query/mod.rs5
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs3
-rw-r--r--compiler/rustc_middle/src/traits/specialization_graph.rs2
-rw-r--r--compiler/rustc_middle/src/ty/assoc.rs2
-rw-r--r--compiler/rustc_middle/src/ty/consts/int.rs9
-rw-r--r--compiler/rustc_middle/src/ty/context.rs40
-rw-r--r--compiler/rustc_middle/src/ty/layout.rs75
-rw-r--r--compiler/rustc_middle/src/ty/typeck_results.rs6
-rw-r--r--compiler/rustc_middle/src/ty/vtable.rs2
-rw-r--r--compiler/rustc_mir_build/src/build/mod.rs2
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/mod.rs15
-rw-r--r--compiler/rustc_mir_transform/src/check_unsafety.rs6
-rw-r--r--compiler/rustc_mir_transform/src/const_goto.rs2
-rw-r--r--compiler/rustc_mir_transform/src/const_prop.rs54
-rw-r--r--compiler/rustc_mir_transform/src/const_prop_lint.rs6
-rw-r--r--compiler/rustc_mir_transform/src/errors.rs11
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs5
-rw-r--r--compiler/rustc_mir_transform/src/separate_const_switch.rs2
-rw-r--r--compiler/rustc_mir_transform/src/sroa.rs50
-rw-r--r--compiler/rustc_monomorphize/src/collector.rs232
-rw-r--r--compiler/rustc_monomorphize/src/lib.rs1
-rw-r--r--compiler/rustc_monomorphize/src/partitioning.rs175
-rw-r--r--compiler/rustc_passes/src/check_const.rs5
-rw-r--r--compiler/rustc_passes/src/dead.rs11
-rw-r--r--compiler/rustc_passes/src/layout_test.rs9
-rw-r--r--compiler/rustc_passes/src/loops.rs10
-rw-r--r--compiler/rustc_privacy/src/lib.rs4
-rw-r--r--compiler/rustc_session/src/session.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs2
-rw-r--r--compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs59
-rw-r--r--compiler/rustc_target/src/abi/call/mod.rs11
-rw-r--r--compiler/rustc_target/src/abi/call/x86_64.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_ios.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_tvos.rs2
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs110
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs3
-rw-r--r--compiler/rustc_trait_selection/src/traits/misc.rs3
-rw-r--r--compiler/rustc_trait_selection/src/traits/mod.rs4
-rw-r--r--compiler/rustc_trait_selection/src/traits/object_safety.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/structural_match.rs70
-rw-r--r--compiler/rustc_trait_selection/src/traits/util.rs2
-rw-r--r--compiler/rustc_ty_utils/src/assoc.rs8
-rw-r--r--compiler/rustc_ty_utils/src/instance.rs2
-rw-r--r--compiler/rustc_ty_utils/src/ty.rs7
144 files changed, 3307 insertions, 1805 deletions
diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs
index 43db66a3c28..2ee63c286ba 100644
--- a/compiler/rustc_abi/src/lib.rs
+++ b/compiler/rustc_abi/src/lib.rs
@@ -209,7 +209,7 @@ pub enum TargetDataLayoutErrors<'a> {
     InvalidAddressSpace { addr_space: &'a str, cause: &'a str, err: ParseIntError },
     InvalidBits { kind: &'a str, bit: &'a str, cause: &'a str, err: ParseIntError },
     MissingAlignment { cause: &'a str },
-    InvalidAlignment { cause: &'a str, err: String },
+    InvalidAlignment { cause: &'a str, err: AlignFromBytesError },
     InconsistentTargetArchitecture { dl: &'a str, target: &'a str },
     InconsistentTargetPointerWidth { pointer_size: u64, target: u32 },
     InvalidBitsSize { err: String },
@@ -640,30 +640,65 @@ impl fmt::Debug for Align {
     }
 }
 
+#[derive(Clone, Copy)]
+pub enum AlignFromBytesError {
+    NotPowerOfTwo(u64),
+    TooLarge(u64),
+}
+
+impl AlignFromBytesError {
+    pub fn diag_ident(self) -> &'static str {
+        match self {
+            Self::NotPowerOfTwo(_) => "not_power_of_two",
+            Self::TooLarge(_) => "too_large",
+        }
+    }
+
+    pub fn align(self) -> u64 {
+        let (Self::NotPowerOfTwo(align) | Self::TooLarge(align)) = self;
+        align
+    }
+}
+
+impl fmt::Debug for AlignFromBytesError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
+
+impl fmt::Display for AlignFromBytesError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            AlignFromBytesError::NotPowerOfTwo(align) => write!(f, "`{align}` is not a power of 2"),
+            AlignFromBytesError::TooLarge(align) => write!(f, "`{align}` is too large"),
+        }
+    }
+}
+
 impl Align {
     pub const ONE: Align = Align { pow2: 0 };
     pub const MAX: Align = Align { pow2: 29 };
 
     #[inline]
-    pub fn from_bits(bits: u64) -> Result<Align, String> {
+    pub fn from_bits(bits: u64) -> Result<Align, AlignFromBytesError> {
         Align::from_bytes(Size::from_bits(bits).bytes())
     }
 
     #[inline]
-    pub fn from_bytes(align: u64) -> Result<Align, String> {
+    pub fn from_bytes(align: u64) -> Result<Align, AlignFromBytesError> {
         // Treat an alignment of 0 bytes like 1-byte alignment.
         if align == 0 {
             return Ok(Align::ONE);
         }
 
         #[cold]
-        fn not_power_of_2(align: u64) -> String {
-            format!("`{}` is not a power of 2", align)
+        fn not_power_of_2(align: u64) -> AlignFromBytesError {
+            AlignFromBytesError::NotPowerOfTwo(align)
         }
 
         #[cold]
-        fn too_large(align: u64) -> String {
-            format!("`{}` is too large", align)
+        fn too_large(align: u64) -> AlignFromBytesError {
+            AlignFromBytesError::TooLarge(align)
         }
 
         let tz = align.trailing_zeros();
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 5e0ab80c6ac..f52797c4f3f 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -71,9 +71,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
 
             let kind = match &e.kind {
                 ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)),
-                ExprKind::ConstBlock(anon_const) => {
-                    let anon_const = self.lower_anon_const(anon_const);
-                    hir::ExprKind::ConstBlock(anon_const)
+                ExprKind::ConstBlock(c) => {
+                    let c = self.with_new_scopes(|this| hir::ConstBlock {
+                        def_id: this.local_def_id(c.id),
+                        hir_id: this.lower_node_id(c.id),
+                        body: this.lower_const_body(c.value.span, Some(&c.value)),
+                    });
+                    hir::ExprKind::ConstBlock(c)
                 }
                 ExprKind::Repeat(expr, count) => {
                     let expr = self.lower_expr(expr);
diff --git a/compiler/rustc_ast_lowering/src/index.rs b/compiler/rustc_ast_lowering/src/index.rs
index 2e66c81eb0d..ce847906fb9 100644
--- a/compiler/rustc_ast_lowering/src/index.rs
+++ b/compiler/rustc_ast_lowering/src/index.rs
@@ -223,6 +223,14 @@ impl<'a, 'hir> Visitor<'hir> for NodeCollector<'a, 'hir> {
         });
     }
 
+    fn visit_inline_const(&mut self, constant: &'hir ConstBlock) {
+        self.insert(DUMMY_SP, constant.hir_id, Node::ConstBlock(constant));
+
+        self.with_parent(constant.hir_id, |this| {
+            intravisit::walk_inline_const(this, constant);
+        });
+    }
+
     fn visit_expr(&mut self, expr: &'hir Expr<'hir>) {
         self.insert(expr.span, expr.hir_id, Node::Expr(expr));
 
diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
index 33f07c1d8fc..13e346b86bc 100644
--- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
+++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
@@ -279,8 +279,18 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
         // HACK This bubble is required for this tests to pass:
         // nested-return-type2-tait2.rs
         // nested-return-type2-tait3.rs
-        let infcx =
-            self.tcx.infer_ctxt().with_opaque_type_inference(DefiningAnchor::Bubble).build();
+        // FIXME(-Ztrait-solver=next): We probably should use `DefiningAnchor::Error`
+        // and prepopulate this `InferCtxt` with known opaque values, rather than
+        // using the `Bind` anchor here. For now it's fine.
+        let infcx = self
+            .tcx
+            .infer_ctxt()
+            .with_opaque_type_inference(if self.tcx.trait_solver_next() {
+                DefiningAnchor::Bind(def_id)
+            } else {
+                DefiningAnchor::Bubble
+            })
+            .build();
         let ocx = ObligationCtxt::new(&infcx);
         // Require the hidden type to be well-formed with only the generics of the opaque type.
         // Defining use functions may have more bounds than the opaque type, which is ok, as long as the
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index 3b896f6540c..908ff3da5ca 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -188,9 +188,6 @@ pub(crate) fn type_check<'mir, 'tcx>(
 
     // FIXME(-Ztrait-solver=next): A bit dubious that we're only registering
     // predefined opaques in the typeck root.
-    // FIXME(-Ztrait-solver=next): This is also totally wrong for TAITs, since
-    // the HIR typeck map defining usages back to their definition params,
-    // they won't actually match up with the usages in this body...
     if infcx.tcx.trait_solver_next() && !infcx.tcx.is_typeck_child(body.source.def_id()) {
         checker.register_predefined_opaques_in_new_solver();
     }
@@ -1042,10 +1039,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
             .typeck(self.body.source.def_id().expect_local())
             .concrete_opaque_types
             .iter()
-            .map(|(&def_id, &hidden_ty)| {
-                let substs = ty::InternalSubsts::identity_for_item(self.infcx.tcx, def_id);
-                (ty::OpaqueTypeKey { def_id, substs }, hidden_ty)
-            })
+            .map(|(k, v)| (*k, *v))
             .collect();
 
         let renumbered_opaques = self.infcx.tcx.fold_regions(opaques, |_, _| {
diff --git a/compiler/rustc_codegen_cranelift/src/common.rs b/compiler/rustc_codegen_cranelift/src/common.rs
index 67fd6d793e0..7243cf6da23 100644
--- a/compiler/rustc_codegen_cranelift/src/common.rs
+++ b/compiler/rustc_codegen_cranelift/src/common.rs
@@ -6,6 +6,7 @@ use rustc_index::IndexVec;
 use rustc_middle::ty::layout::{
     FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers,
 };
+use rustc_span::source_map::Spanned;
 use rustc_span::SourceFile;
 use rustc_target::abi::call::FnAbi;
 use rustc_target::abi::{Integer, Primitive};
@@ -495,25 +496,16 @@ impl<'tcx> FnAbiOfHelpers<'tcx> for RevealAllLayoutCx<'tcx> {
         fn_abi_request: FnAbiRequest<'tcx>,
     ) -> ! {
         if let FnAbiError::Layout(LayoutError::SizeOverflow(_)) = err {
-            self.0.sess.span_fatal(span, err.to_string())
+            self.0.sess.emit_fatal(Spanned { span, node: err })
         } else {
             match fn_abi_request {
                 FnAbiRequest::OfFnPtr { sig, extra_args } => {
-                    span_bug!(
-                        span,
-                        "`fn_abi_of_fn_ptr({}, {:?})` failed: {}",
-                        sig,
-                        extra_args,
-                        err
-                    );
+                    span_bug!(span, "`fn_abi_of_fn_ptr({sig}, {extra_args:?})` failed: {err:?}");
                 }
                 FnAbiRequest::OfInstance { instance, extra_args } => {
                     span_bug!(
                         span,
-                        "`fn_abi_of_instance({}, {:?})` failed: {}",
-                        instance,
-                        extra_args,
-                        err
+                        "`fn_abi_of_instance({instance}, {extra_args:?})` failed: {err:?}"
                     );
                 }
             }
diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs
index 869344ce92d..f9ea0f00456 100644
--- a/compiler/rustc_codegen_gcc/src/builder.rs
+++ b/compiler/rustc_codegen_gcc/src/builder.rs
@@ -758,7 +758,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
         assert_eq!(place.llextra.is_some(), place.layout.is_unsized());
 
         if place.layout.is_zst() {
-            return OperandRef::new_zst(self, place.layout);
+            return OperandRef::zero_sized(place.layout);
         }
 
         fn scalar_load_metadata<'a, 'gcc, 'tcx>(bx: &mut Builder<'a, 'gcc, 'tcx>, load: RValue<'gcc>, scalar: &abi::Scalar) {
diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs
index 873f652e6f1..33e3b0baa92 100644
--- a/compiler/rustc_codegen_gcc/src/consts.rs
+++ b/compiler/rustc_codegen_gcc/src/consts.rs
@@ -24,7 +24,7 @@ fn set_global_alignment<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, gv: LValue<'gcc>
         match Align::from_bits(min) {
             Ok(min) => align = align.max(min),
             Err(err) => {
-                cx.sess().emit_err(InvalidMinimumAlignment { err });
+                cx.sess().emit_err(InvalidMinimumAlignment { err: err.to_string() });
             }
         }
     }
diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs
index 661681bdb50..08507e19652 100644
--- a/compiler/rustc_codegen_gcc/src/context.rs
+++ b/compiler/rustc_codegen_gcc/src/context.rs
@@ -477,7 +477,7 @@ impl<'gcc, 'tcx> LayoutOfHelpers<'tcx> for CodegenCx<'gcc, 'tcx> {
     #[inline]
     fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! {
         if let LayoutError::SizeOverflow(_) = err {
-            self.sess().emit_fatal(respan(span, err))
+            self.sess().emit_fatal(respan(span, err.into_diagnostic()))
         } else {
             span_bug!(span, "failed to get layout for `{}`: {}", ty, err)
         }
@@ -499,21 +499,12 @@ impl<'gcc, 'tcx> FnAbiOfHelpers<'tcx> for CodegenCx<'gcc, 'tcx> {
         } else {
             match fn_abi_request {
                 FnAbiRequest::OfFnPtr { sig, extra_args } => {
-                    span_bug!(
-                        span,
-                        "`fn_abi_of_fn_ptr({}, {:?})` failed: {}",
-                        sig,
-                        extra_args,
-                        err
-                    );
+                    span_bug!(span, "`fn_abi_of_fn_ptr({sig}, {extra_args:?})` failed: {err:?}");
                 }
                 FnAbiRequest::OfInstance { instance, extra_args } => {
                     span_bug!(
                         span,
-                        "`fn_abi_of_instance({}, {:?})` failed: {}",
-                        instance,
-                        extra_args,
-                        err
+                        "`fn_abi_of_instance({instance}, {extra_args:?})` failed: {err:?}"
                     );
                 }
             }
diff --git a/compiler/rustc_codegen_gcc/src/type_of.rs b/compiler/rustc_codegen_gcc/src/type_of.rs
index 5df8c1a209d..30a3fe67b85 100644
--- a/compiler/rustc_codegen_gcc/src/type_of.rs
+++ b/compiler/rustc_codegen_gcc/src/type_of.rs
@@ -159,8 +159,7 @@ impl<'tcx> LayoutGccExt<'tcx> for TyAndLayout<'tcx> {
     fn is_gcc_immediate(&self) -> bool {
         match self.abi {
             Abi::Scalar(_) | Abi::Vector { .. } => true,
-            Abi::ScalarPair(..) => false,
-            Abi::Uninhabited | Abi::Aggregate { .. } => self.is_zst(),
+            Abi::ScalarPair(..) | Abi::Uninhabited | Abi::Aggregate { .. } => false,
         }
     }
 
diff --git a/compiler/rustc_codegen_llvm/Cargo.toml b/compiler/rustc_codegen_llvm/Cargo.toml
index ad51f2d0958..39ff3a0ba2d 100644
--- a/compiler/rustc_codegen_llvm/Cargo.toml
+++ b/compiler/rustc_codegen_llvm/Cargo.toml
@@ -8,7 +8,6 @@ test = false
 
 [dependencies]
 bitflags = "1.0"
-cstr = "0.2"
 libc = "0.2"
 measureme = "10.0.0"
 object = { version = "0.31.1", default-features = false, features = [
diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl
index 55622fdb20a..de1622951fe 100644
--- a/compiler/rustc_codegen_llvm/messages.ftl
+++ b/compiler/rustc_codegen_llvm/messages.ftl
@@ -20,8 +20,12 @@ codegen_llvm_error_writing_def_file =
 codegen_llvm_from_llvm_diag = {$message}
 
 codegen_llvm_from_llvm_optimization_diag = {$filename}:{$line}:{$column} {$pass_name} ({$kind}): {$message}
-codegen_llvm_invalid_minimum_alignment =
-    invalid minimum global alignment: {$err}
+
+codegen_llvm_invalid_minimum_alignment_not_power_of_two =
+    invalid minimum global alignment: {$align} is not power of 2
+
+codegen_llvm_invalid_minimum_alignment_too_large =
+    invalid minimum global alignment: {$align} is too large
 
 codegen_llvm_load_bitcode = failed to load bitcode of module "{$name}"
 codegen_llvm_load_bitcode_with_llvm_err = failed to load bitcode of module "{$name}": {$llvm_err}
diff --git a/compiler/rustc_codegen_llvm/src/allocator.rs b/compiler/rustc_codegen_llvm/src/allocator.rs
index a57508815d6..ad0636894b7 100644
--- a/compiler/rustc_codegen_llvm/src/allocator.rs
+++ b/compiler/rustc_codegen_llvm/src/allocator.rs
@@ -77,7 +77,7 @@ pub(crate) unsafe fn codegen(
                 llvm::LLVMRustGetOrInsertFunction(llmod, callee.as_ptr().cast(), callee.len(), ty);
             llvm::LLVMRustSetVisibility(callee, llvm::Visibility::Hidden);
 
-            let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, "entry\0".as_ptr().cast());
+            let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, c"entry".as_ptr().cast());
 
             let llbuilder = llvm::LLVMCreateBuilderInContext(llcx);
             llvm::LLVMPositionBuilderAtEnd(llbuilder, llbb);
@@ -129,7 +129,7 @@ pub(crate) unsafe fn codegen(
     attributes::apply_to_llfn(callee, llvm::AttributePlace::Function, &[no_return]);
     llvm::LLVMRustSetVisibility(callee, llvm::Visibility::Hidden);
 
-    let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, "entry\0".as_ptr().cast());
+    let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, c"entry".as_ptr().cast());
 
     let llbuilder = llvm::LLVMCreateBuilderInContext(llcx);
     llvm::LLVMPositionBuilderAtEnd(llbuilder, llbb);
diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs
index 604f68eb6a4..8b05af7bed9 100644
--- a/compiler/rustc_codegen_llvm/src/back/lto.rs
+++ b/compiler/rustc_codegen_llvm/src/back/lto.rs
@@ -595,7 +595,7 @@ pub(crate) fn run_pass_manager(
             llvm::LLVMRustAddModuleFlag(
                 module.module_llvm.llmod(),
                 llvm::LLVMModFlagBehavior::Error,
-                "LTOPostLink\0".as_ptr().cast(),
+                c"LTOPostLink".as_ptr().cast(),
                 1,
             );
         }
diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs
index ca2eab28f87..53b4296802e 100644
--- a/compiler/rustc_codegen_llvm/src/back/write.rs
+++ b/compiler/rustc_codegen_llvm/src/back/write.rs
@@ -891,11 +891,11 @@ unsafe fn embed_bitcode(
         let llglobal = llvm::LLVMAddGlobal(
             llmod,
             common::val_ty(llconst),
-            "rustc.embedded.module\0".as_ptr().cast(),
+            c"rustc.embedded.module".as_ptr().cast(),
         );
         llvm::LLVMSetInitializer(llglobal, llconst);
 
-        let section = if is_apple { "__LLVM,__bitcode\0" } else { ".llvmbc\0" };
+        let section = if is_apple { c"__LLVM,__bitcode" } else { c".llvmbc" };
         llvm::LLVMSetSection(llglobal, section.as_ptr().cast());
         llvm::LLVMRustSetLinkage(llglobal, llvm::Linkage::PrivateLinkage);
         llvm::LLVMSetGlobalConstant(llglobal, llvm::True);
@@ -904,10 +904,10 @@ unsafe fn embed_bitcode(
         let llglobal = llvm::LLVMAddGlobal(
             llmod,
             common::val_ty(llconst),
-            "rustc.embedded.cmdline\0".as_ptr().cast(),
+            c"rustc.embedded.cmdline".as_ptr().cast(),
         );
         llvm::LLVMSetInitializer(llglobal, llconst);
-        let section = if is_apple { "__LLVM,__cmdline\0" } else { ".llvmcmd\0" };
+        let section = if is_apple { c"__LLVM,__cmdline" } else { c".llvmcmd" };
         llvm::LLVMSetSection(llglobal, section.as_ptr().cast());
         llvm::LLVMRustSetLinkage(llglobal, llvm::Linkage::PrivateLinkage);
     } else {
diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs
index 5b2bbdb4bde..2f7eb08ad3d 100644
--- a/compiler/rustc_codegen_llvm/src/base.rs
+++ b/compiler/rustc_codegen_llvm/src/base.rs
@@ -19,8 +19,6 @@ use crate::context::CodegenCx;
 use crate::llvm;
 use crate::value::Value;
 
-use cstr::cstr;
-
 use rustc_codegen_ssa::base::maybe_create_entry_wrapper;
 use rustc_codegen_ssa::mono_item::MonoItemExt;
 use rustc_codegen_ssa::traits::*;
@@ -110,11 +108,11 @@ pub fn compile_codegen_unit(tcx: TyCtxt<'_>, cgu_name: Symbol) -> (ModuleCodegen
 
             // Create the llvm.used and llvm.compiler.used variables.
             if !cx.used_statics.borrow().is_empty() {
-                cx.create_used_variable_impl(cstr!("llvm.used"), &*cx.used_statics.borrow());
+                cx.create_used_variable_impl(c"llvm.used", &*cx.used_statics.borrow());
             }
             if !cx.compiler_used_statics.borrow().is_empty() {
                 cx.create_used_variable_impl(
-                    cstr!("llvm.compiler.used"),
+                    c"llvm.compiler.used",
                     &*cx.compiler_used_statics.borrow(),
                 );
             }
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index 4d0bcd53d15..b4aa001547c 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -6,7 +6,6 @@ use crate::llvm::{self, AtomicOrdering, AtomicRmwBinOp, BasicBlock, False, True}
 use crate::type_::Type;
 use crate::type_of::LayoutLlvmExt;
 use crate::value::Value;
-use cstr::cstr;
 use libc::{c_char, c_uint};
 use rustc_codegen_ssa::common::{IntPredicate, RealPredicate, SynchronizationScope, TypeKind};
 use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
@@ -25,7 +24,6 @@ use rustc_symbol_mangling::typeid::{kcfi_typeid_for_fnabi, typeid_for_fnabi, Typ
 use rustc_target::abi::{self, call::FnAbi, Align, Size, WrappingRange};
 use rustc_target::spec::{HasTargetSpec, SanitizerSet, Target};
 use std::borrow::Cow;
-use std::ffi::CStr;
 use std::iter;
 use std::ops::Deref;
 use std::ptr;
@@ -45,13 +43,10 @@ impl Drop for Builder<'_, '_, '_> {
     }
 }
 
-// FIXME(eddyb) use a checked constructor when they become `const fn`.
-const EMPTY_C_STR: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
-
 /// Empty string, to be used where LLVM expects an instruction name, indicating
 /// that the instruction is to be left unnamed (i.e. numbered, in textual IR).
 // FIXME(eddyb) pass `&CStr` directly to FFI once it's a thin pointer.
-const UNNAMED: *const c_char = EMPTY_C_STR.as_ptr();
+const UNNAMED: *const c_char = c"".as_ptr();
 
 impl<'ll, 'tcx> BackendTypes for Builder<'_, 'll, 'tcx> {
     type Value = <CodegenCx<'ll, 'tcx> as BackendTypes>::Value;
@@ -486,7 +481,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
         assert_eq!(place.llextra.is_some(), place.layout.is_unsized());
 
         if place.layout.is_zst() {
-            return OperandRef::new_zst(self, place.layout);
+            return OperandRef::zero_sized(place.layout);
         }
 
         #[instrument(level = "trace", skip(bx))]
@@ -1010,14 +1005,13 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
     }
 
     fn cleanup_pad(&mut self, parent: Option<&'ll Value>, args: &[&'ll Value]) -> Funclet<'ll> {
-        let name = cstr!("cleanuppad");
         let ret = unsafe {
             llvm::LLVMBuildCleanupPad(
                 self.llbuilder,
                 parent,
                 args.as_ptr(),
                 args.len() as c_uint,
-                name.as_ptr(),
+                c"cleanuppad".as_ptr(),
             )
         };
         Funclet::new(ret.expect("LLVM does not have support for cleanuppad"))
@@ -1031,14 +1025,13 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
     }
 
     fn catch_pad(&mut self, parent: &'ll Value, args: &[&'ll Value]) -> Funclet<'ll> {
-        let name = cstr!("catchpad");
         let ret = unsafe {
             llvm::LLVMBuildCatchPad(
                 self.llbuilder,
                 parent,
                 args.as_ptr(),
                 args.len() as c_uint,
-                name.as_ptr(),
+                c"catchpad".as_ptr(),
             )
         };
         Funclet::new(ret.expect("LLVM does not have support for catchpad"))
@@ -1050,14 +1043,13 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
         unwind: Option<&'ll BasicBlock>,
         handlers: &[&'ll BasicBlock],
     ) -> &'ll Value {
-        let name = cstr!("catchswitch");
         let ret = unsafe {
             llvm::LLVMBuildCatchSwitch(
                 self.llbuilder,
                 parent,
                 unwind,
                 handlers.len() as c_uint,
-                name.as_ptr(),
+                c"catchswitch".as_ptr(),
             )
         };
         let ret = ret.expect("LLVM does not have support for catchswitch");
diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs
index 940358acde9..2087754c66b 100644
--- a/compiler/rustc_codegen_llvm/src/consts.rs
+++ b/compiler/rustc_codegen_llvm/src/consts.rs
@@ -1,12 +1,13 @@
 use crate::base;
 use crate::common::{self, CodegenCx};
 use crate::debuginfo;
-use crate::errors::{InvalidMinimumAlignment, SymbolAlreadyDefined};
+use crate::errors::{
+    InvalidMinimumAlignmentNotPowerOfTwo, InvalidMinimumAlignmentTooLarge, SymbolAlreadyDefined,
+};
 use crate::llvm::{self, True};
 use crate::type_::Type;
 use crate::type_of::LayoutLlvmExt;
 use crate::value::Value;
-use cstr::cstr;
 use rustc_codegen_ssa::traits::*;
 use rustc_hir::def_id::DefId;
 use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
@@ -19,7 +20,9 @@ use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::{self, Instance, Ty};
 use rustc_middle::{bug, span_bug};
 use rustc_session::config::Lto;
-use rustc_target::abi::{Align, HasDataLayout, Primitive, Scalar, Size, WrappingRange};
+use rustc_target::abi::{
+    Align, AlignFromBytesError, HasDataLayout, Primitive, Scalar, Size, WrappingRange,
+};
 use std::ops::Range;
 
 pub fn const_alloc_to_llvm<'ll>(cx: &CodegenCx<'ll, '_>, alloc: ConstAllocation<'_>) -> &'ll Value {
@@ -129,9 +132,14 @@ fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align:
     if let Some(min) = cx.sess().target.min_global_align {
         match Align::from_bits(min) {
             Ok(min) => align = align.max(min),
-            Err(err) => {
-                cx.sess().emit_err(InvalidMinimumAlignment { err });
-            }
+            Err(err) => match err {
+                AlignFromBytesError::NotPowerOfTwo(align) => {
+                    cx.sess().emit_err(InvalidMinimumAlignmentNotPowerOfTwo { align });
+                }
+                AlignFromBytesError::TooLarge(align) => {
+                    cx.sess().emit_err(InvalidMinimumAlignmentTooLarge { align });
+                }
+            },
         }
     }
     unsafe {
@@ -473,9 +481,9 @@ impl<'ll> StaticMethods for CodegenCx<'ll, '_> {
                             .all(|&byte| byte == 0);
 
                     let sect_name = if all_bytes_are_zero {
-                        cstr!("__DATA,__thread_bss")
+                        c"__DATA,__thread_bss"
                     } else {
-                        cstr!("__DATA,__thread_data")
+                        c"__DATA,__thread_data"
                     };
                     llvm::LLVMSetSection(g, sect_name.as_ptr());
                 }
@@ -504,7 +512,7 @@ impl<'ll> StaticMethods for CodegenCx<'ll, '_> {
                     let val = llvm::LLVMMetadataAsValue(self.llcx, meta);
                     llvm::LLVMAddNamedMetadataOperand(
                         self.llmod,
-                        "wasm.custom_sections\0".as_ptr().cast(),
+                        c"wasm.custom_sections".as_ptr().cast(),
                         val,
                     );
                 }
diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs
index 83101a85435..e8a7afcc632 100644
--- a/compiler/rustc_codegen_llvm/src/context.rs
+++ b/compiler/rustc_codegen_llvm/src/context.rs
@@ -8,7 +8,6 @@ use crate::llvm_util;
 use crate::type_::Type;
 use crate::value::Value;
 
-use cstr::cstr;
 use rustc_codegen_ssa::base::wants_msvc_seh;
 use rustc_codegen_ssa::traits::*;
 use rustc_data_structures::base_n;
@@ -224,36 +223,42 @@ pub unsafe fn create_module<'ll>(
     // If skipping the PLT is enabled, we need to add some module metadata
     // to ensure intrinsic calls don't use it.
     if !sess.needs_plt() {
-        let avoid_plt = "RtLibUseGOT\0".as_ptr().cast();
-        llvm::LLVMRustAddModuleFlag(llmod, llvm::LLVMModFlagBehavior::Warning, avoid_plt, 1);
+        llvm::LLVMRustAddModuleFlag(
+            llmod,
+            llvm::LLVMModFlagBehavior::Warning,
+            c"RtLibUseGOT".as_ptr().cast(),
+            1,
+        );
     }
 
     // Enable canonical jump tables if CFI is enabled. (See https://reviews.llvm.org/D65629.)
     if sess.is_sanitizer_cfi_canonical_jump_tables_enabled() && sess.is_sanitizer_cfi_enabled() {
-        let canonical_jump_tables = "CFI Canonical Jump Tables\0".as_ptr().cast();
         llvm::LLVMRustAddModuleFlag(
             llmod,
             llvm::LLVMModFlagBehavior::Override,
-            canonical_jump_tables,
+            c"CFI Canonical Jump Tables".as_ptr().cast(),
             1,
         );
     }
 
     // Enable LTO unit splitting if specified or if CFI is enabled. (See https://reviews.llvm.org/D53891.)
     if sess.is_split_lto_unit_enabled() || sess.is_sanitizer_cfi_enabled() {
-        let enable_split_lto_unit = "EnableSplitLTOUnit\0".as_ptr().cast();
         llvm::LLVMRustAddModuleFlag(
             llmod,
             llvm::LLVMModFlagBehavior::Override,
-            enable_split_lto_unit,
+            c"EnableSplitLTOUnit".as_ptr().cast(),
             1,
         );
     }
 
     // Add "kcfi" module flag if KCFI is enabled. (See https://reviews.llvm.org/D119296.)
     if sess.is_sanitizer_kcfi_enabled() {
-        let kcfi = "kcfi\0".as_ptr().cast();
-        llvm::LLVMRustAddModuleFlag(llmod, llvm::LLVMModFlagBehavior::Override, kcfi, 1);
+        llvm::LLVMRustAddModuleFlag(
+            llmod,
+            llvm::LLVMModFlagBehavior::Override,
+            c"kcfi".as_ptr().cast(),
+            1,
+        );
     }
 
     // Control Flow Guard is currently only supported by the MSVC linker on Windows.
@@ -265,7 +270,7 @@ pub unsafe fn create_module<'ll>(
                 llvm::LLVMRustAddModuleFlag(
                     llmod,
                     llvm::LLVMModFlagBehavior::Warning,
-                    "cfguard\0".as_ptr() as *const _,
+                    c"cfguard".as_ptr() as *const _,
                     1,
                 )
             }
@@ -274,7 +279,7 @@ pub unsafe fn create_module<'ll>(
                 llvm::LLVMRustAddModuleFlag(
                     llmod,
                     llvm::LLVMModFlagBehavior::Warning,
-                    "cfguard\0".as_ptr() as *const _,
+                    c"cfguard".as_ptr() as *const _,
                     2,
                 )
             }
@@ -292,26 +297,26 @@ pub unsafe fn create_module<'ll>(
             llvm::LLVMRustAddModuleFlag(
                 llmod,
                 behavior,
-                "branch-target-enforcement\0".as_ptr().cast(),
+                c"branch-target-enforcement".as_ptr().cast(),
                 bti.into(),
             );
             llvm::LLVMRustAddModuleFlag(
                 llmod,
                 behavior,
-                "sign-return-address\0".as_ptr().cast(),
+                c"sign-return-address".as_ptr().cast(),
                 pac_ret.is_some().into(),
             );
             let pac_opts = pac_ret.unwrap_or(PacRet { leaf: false, key: PAuthKey::A });
             llvm::LLVMRustAddModuleFlag(
                 llmod,
                 behavior,
-                "sign-return-address-all\0".as_ptr().cast(),
+                c"sign-return-address-all".as_ptr().cast(),
                 pac_opts.leaf.into(),
             );
             llvm::LLVMRustAddModuleFlag(
                 llmod,
                 behavior,
-                "sign-return-address-with-bkey\0".as_ptr().cast(),
+                c"sign-return-address-with-bkey".as_ptr().cast(),
                 u32::from(pac_opts.key == PAuthKey::B),
             );
         } else {
@@ -327,7 +332,7 @@ pub unsafe fn create_module<'ll>(
         llvm::LLVMRustAddModuleFlag(
             llmod,
             llvm::LLVMModFlagBehavior::Override,
-            "cf-protection-branch\0".as_ptr().cast(),
+            c"cf-protection-branch".as_ptr().cast(),
             1,
         )
     }
@@ -335,7 +340,7 @@ pub unsafe fn create_module<'ll>(
         llvm::LLVMRustAddModuleFlag(
             llmod,
             llvm::LLVMModFlagBehavior::Override,
-            "cf-protection-return\0".as_ptr().cast(),
+            c"cf-protection-return".as_ptr().cast(),
             1,
         )
     }
@@ -344,7 +349,7 @@ pub unsafe fn create_module<'ll>(
         llvm::LLVMRustAddModuleFlag(
             llmod,
             llvm::LLVMModFlagBehavior::Error,
-            "Virtual Function Elim\0".as_ptr().cast(),
+            c"Virtual Function Elim".as_ptr().cast(),
             1,
         );
     }
@@ -476,14 +481,13 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
     }
 
     pub(crate) fn create_used_variable_impl(&self, name: &'static CStr, values: &[&'ll Value]) {
-        let section = cstr!("llvm.metadata");
         let array = self.const_array(self.type_ptr_to(self.type_i8()), values);
 
         unsafe {
             let g = llvm::LLVMAddGlobal(self.llmod, self.val_ty(array), name.as_ptr());
             llvm::LLVMSetInitializer(g, array);
             llvm::LLVMRustSetLinkage(g, llvm::Linkage::AppendingLinkage);
-            llvm::LLVMSetSection(g, section.as_ptr());
+            llvm::LLVMSetSection(g, c"llvm.metadata".as_ptr());
         }
     }
 }
@@ -969,9 +973,9 @@ impl<'tcx> LayoutOfHelpers<'tcx> for CodegenCx<'_, 'tcx> {
     #[inline]
     fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! {
         if let LayoutError::SizeOverflow(_) = err {
-            self.sess().emit_fatal(Spanned { span, node: err })
+            self.sess().emit_fatal(Spanned { span, node: err.into_diagnostic() })
         } else {
-            span_bug!(span, "failed to get layout for `{}`: {}", ty, err)
+            span_bug!(span, "failed to get layout for `{ty}`: {err:?}")
         }
     }
 }
@@ -991,21 +995,12 @@ impl<'tcx> FnAbiOfHelpers<'tcx> for CodegenCx<'_, 'tcx> {
         } else {
             match fn_abi_request {
                 FnAbiRequest::OfFnPtr { sig, extra_args } => {
-                    span_bug!(
-                        span,
-                        "`fn_abi_of_fn_ptr({}, {:?})` failed: {}",
-                        sig,
-                        extra_args,
-                        err
-                    );
+                    span_bug!(span, "`fn_abi_of_fn_ptr({sig}, {extra_args:?})` failed: {err:?}",);
                 }
                 FnAbiRequest::OfInstance { instance, extra_args } => {
                     span_bug!(
                         span,
-                        "`fn_abi_of_instance({}, {:?})` failed: {}",
-                        instance,
-                        extra_args,
-                        err
+                        "`fn_abi_of_instance({instance}, {extra_args:?})` failed: {err:?}",
                     );
                 }
             }
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs
index 37f30917609..8be54b7eb71 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs
@@ -38,7 +38,6 @@ pub fn get_or_insert_gdb_debug_scripts_section_global<'ll>(cx: &CodegenCx<'ll, '
         unsafe { llvm::LLVMGetNamedGlobal(cx.llmod, c_section_var_name.as_ptr().cast()) };
 
     section_var.unwrap_or_else(|| {
-        let section_name = b".debug_gdb_scripts\0";
         let mut section_contents = Vec::new();
 
         // Add the pretty printers for the standard library first.
@@ -71,7 +70,7 @@ pub fn get_or_insert_gdb_debug_scripts_section_global<'ll>(cx: &CodegenCx<'ll, '
             let section_var = cx
                 .define_global(section_var_name, llvm_type)
                 .unwrap_or_else(|| bug!("symbol `{}` is already defined", section_var_name));
-            llvm::LLVMSetSection(section_var, section_name.as_ptr().cast());
+            llvm::LLVMSetSection(section_var, c".debug_gdb_scripts".as_ptr().cast());
             llvm::LLVMSetInitializer(section_var, cx.const_bytes(section_contents));
             llvm::LLVMSetGlobalConstant(section_var, llvm::True);
             llvm::LLVMSetUnnamedAddress(section_var, llvm::UnnamedAddr::Global);
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
index bd2fba12602..166454d3ae7 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
@@ -20,7 +20,6 @@ use crate::llvm::debuginfo::{
 };
 use crate::value::Value;
 
-use cstr::cstr;
 use rustc_codegen_ssa::debuginfo::type_names::cpp_like_debuginfo;
 use rustc_codegen_ssa::debuginfo::type_names::VTableNameKind;
 use rustc_codegen_ssa::traits::*;
@@ -812,7 +811,6 @@ pub fn build_compile_unit_di_node<'ll, 'tcx>(
 
     let name_in_debuginfo = name_in_debuginfo.to_string_lossy();
     let work_dir = tcx.sess.opts.working_dir.to_string_lossy(FileNameDisplayPreference::Remapped);
-    let flags = "\0";
     let output_filenames = tcx.output_filenames(());
     let split_name = if tcx.sess.target_can_use_split_dwarf() {
         output_filenames
@@ -849,7 +847,7 @@ pub fn build_compile_unit_di_node<'ll, 'tcx>(
             producer.as_ptr().cast(),
             producer.len(),
             tcx.sess.opts.optimize != config::OptLevel::No,
-            flags.as_ptr().cast(),
+            c"".as_ptr().cast(),
             0,
             // NB: this doesn't actually have any perceptible effect, it seems. LLVM will instead
             // put the path supplied to `MCSplitDwarfFile` into the debug info of the final
@@ -878,8 +876,7 @@ pub fn build_compile_unit_di_node<'ll, 'tcx>(
             );
             let val = llvm::LLVMMetadataAsValue(debug_context.llcontext, gcov_metadata);
 
-            let llvm_gcov_ident = cstr!("llvm.gcov");
-            llvm::LLVMAddNamedMetadataOperand(debug_context.llmod, llvm_gcov_ident.as_ptr(), val);
+            llvm::LLVMAddNamedMetadataOperand(debug_context.llmod, c"llvm.gcov".as_ptr(), val);
         }
 
         // Insert `llvm.ident` metadata on the wasm targets since that will
@@ -892,7 +889,7 @@ pub fn build_compile_unit_di_node<'ll, 'tcx>(
             );
             llvm::LLVMAddNamedMetadataOperand(
                 debug_context.llmod,
-                cstr!("llvm.ident").as_ptr(),
+                c"llvm.ident".as_ptr(),
                 llvm::LLVMMDNodeInContext(debug_context.llcontext, &name_metadata, 1),
             );
         }
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
index c3f0a0033b0..aa7ae9355bc 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs
@@ -113,7 +113,7 @@ impl<'ll, 'tcx> CodegenUnitDebugContext<'ll, 'tcx> {
                 llvm::LLVMRustAddModuleFlag(
                     self.llmod,
                     llvm::LLVMModFlagBehavior::Warning,
-                    "Dwarf Version\0".as_ptr().cast(),
+                    c"Dwarf Version".as_ptr().cast(),
                     dwarf_version,
                 );
             } else {
@@ -121,17 +121,16 @@ impl<'ll, 'tcx> CodegenUnitDebugContext<'ll, 'tcx> {
                 llvm::LLVMRustAddModuleFlag(
                     self.llmod,
                     llvm::LLVMModFlagBehavior::Warning,
-                    "CodeView\0".as_ptr().cast(),
+                    c"CodeView".as_ptr().cast(),
                     1,
                 )
             }
 
             // Prevent bitcode readers from deleting the debug info.
-            let ptr = "Debug Info Version\0".as_ptr();
             llvm::LLVMRustAddModuleFlag(
                 self.llmod,
                 llvm::LLVMModFlagBehavior::Warning,
-                ptr.cast(),
+                c"Debug Info Version".as_ptr().cast(),
                 llvm::LLVMRustDebugMetadataVersion(),
             );
         }
diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs
index 6a9173ab450..44869ced1ae 100644
--- a/compiler/rustc_codegen_llvm/src/errors.rs
+++ b/compiler/rustc_codegen_llvm/src/errors.rs
@@ -50,9 +50,15 @@ pub(crate) struct SymbolAlreadyDefined<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(codegen_llvm_invalid_minimum_alignment)]
-pub(crate) struct InvalidMinimumAlignment {
-    pub err: String,
+#[diag(codegen_llvm_invalid_minimum_alignment_not_power_of_two)]
+pub(crate) struct InvalidMinimumAlignmentNotPowerOfTwo {
+    pub align: u64,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_invalid_minimum_alignment_too_large)]
+pub(crate) struct InvalidMinimumAlignmentTooLarge {
+    pub align: u64,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs
index 805843e5863..ff9909c720e 100644
--- a/compiler/rustc_codegen_llvm/src/lib.rs
+++ b/compiler/rustc_codegen_llvm/src/lib.rs
@@ -11,6 +11,7 @@
 #![feature(let_chains)]
 #![feature(never_type)]
 #![feature(impl_trait_in_assoc_type)]
+#![feature(c_str_literals)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 #![deny(rustc::untranslatable_diagnostic)]
diff --git a/compiler/rustc_codegen_llvm/src/type_of.rs b/compiler/rustc_codegen_llvm/src/type_of.rs
index e264ce78f0d..a493c9c0548 100644
--- a/compiler/rustc_codegen_llvm/src/type_of.rs
+++ b/compiler/rustc_codegen_llvm/src/type_of.rs
@@ -198,8 +198,7 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> {
     fn is_llvm_immediate(&self) -> bool {
         match self.abi {
             Abi::Scalar(_) | Abi::Vector { .. } => true,
-            Abi::ScalarPair(..) => false,
-            Abi::Uninhabited | Abi::Aggregate { .. } => self.is_zst(),
+            Abi::ScalarPair(..) | Abi::Uninhabited | Abi::Aggregate { .. } => false,
         }
     }
 
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index 15c7847155d..242d209b684 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -295,7 +295,7 @@ pub fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
             let (base, info) = match bx.load_operand(src).val {
                 OperandValue::Pair(base, info) => unsize_ptr(bx, base, src_ty, dst_ty, Some(info)),
                 OperandValue::Immediate(base) => unsize_ptr(bx, base, src_ty, dst_ty, None),
-                OperandValue::Ref(..) => bug!(),
+                OperandValue::Ref(..) | OperandValue::ZeroSized => bug!(),
             };
             OperandValue::Pair(base, info).store(bx, dst);
         }
diff --git a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs
index 6297f91341d..e91f7b86e5e 100644
--- a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs
+++ b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs
@@ -93,8 +93,7 @@ fn push_debuginfo_type_name<'tcx>(
                     Err(e) => {
                         // Computing the layout can still fail here, e.g. if the target architecture
                         // cannot represent the type. See https://github.com/rust-lang/rust/issues/94961.
-                        // FIXME: migrate once `rustc_middle::mir::interpret::InterpError` is translatable.
-                        tcx.sess.fatal(format!("{}", e));
+                        tcx.sess.emit_fatal(e.into_diagnostic());
                     }
                 }
             } else {
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index 3f0b64b1103..e0cb26d3ba8 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -1,5 +1,5 @@
 use super::operand::OperandRef;
-use super::operand::OperandValue::{Immediate, Pair, Ref};
+use super::operand::OperandValue::{Immediate, Pair, Ref, ZeroSized};
 use super::place::PlaceRef;
 use super::{CachedLlbb, FunctionCx, LocalRef};
 
@@ -427,6 +427,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                         assert_eq!(align, op.layout.align.abi, "return place is unaligned!");
                         llval
                     }
+                    ZeroSized => bug!("ZST return value shouldn't be in PassMode::Cast"),
                 };
                 let ty = bx.cast_backend_type(cast_ty);
                 let addr = bx.pointercast(llslot, bx.type_ptr_to(ty));
@@ -1386,6 +1387,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     (llval, align, true)
                 }
             }
+            ZeroSized => match arg.mode {
+                PassMode::Indirect { .. } => {
+                    // Though `extern "Rust"` doesn't pass ZSTs, some ABIs pass
+                    // a pointer for `repr(C)` structs even when empty, so get
+                    // one from an `alloca` (which can be left uninitialized).
+                    let scratch = PlaceRef::alloca(bx, arg.layout);
+                    (scratch.llval, scratch.align, true)
+                }
+                _ => bug!("ZST {op:?} wasn't ignored, but was passed with abi {arg:?}"),
+            },
         };
 
         if by_ref && !arg.is_indirect() {
diff --git a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs
index bba2800fb05..4f79c6a3d82 100644
--- a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs
@@ -352,6 +352,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                         bx.set_var_name(a, &(name.clone() + ".0"));
                         bx.set_var_name(b, &(name.clone() + ".1"));
                     }
+                    OperandValue::ZeroSized => {
+                        // These never have a value to talk about
+                    }
                 },
                 LocalRef::PendingOperand => {}
             }
diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs
index 00414003202..2809ec2deb5 100644
--- a/compiler/rustc_codegen_ssa/src/mir/mod.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs
@@ -129,16 +129,13 @@ enum LocalRef<'tcx, V> {
     PendingOperand,
 }
 
-impl<'a, 'tcx, V: CodegenObject> LocalRef<'tcx, V> {
-    fn new_operand<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
-        bx: &mut Bx,
-        layout: TyAndLayout<'tcx>,
-    ) -> LocalRef<'tcx, V> {
+impl<'tcx, V: CodegenObject> LocalRef<'tcx, V> {
+    fn new_operand(layout: TyAndLayout<'tcx>) -> LocalRef<'tcx, V> {
         if layout.is_zst() {
             // Zero-size temporaries aren't always initialized, which
             // doesn't matter because they don't contain data, but
             // we need something in the operand.
-            LocalRef::Operand(OperandRef::new_zst(bx, layout))
+            LocalRef::Operand(OperandRef::zero_sized(layout))
         } else {
             LocalRef::PendingOperand
         }
@@ -249,7 +246,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
                 }
             } else {
                 debug!("alloc: {:?} -> operand", local);
-                LocalRef::new_operand(&mut start_bx, layout)
+                LocalRef::new_operand(layout)
             }
         };
 
@@ -355,7 +352,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
                 let local = |op| LocalRef::Operand(op);
                 match arg.mode {
                     PassMode::Ignore => {
-                        return local(OperandRef::new_zst(bx, arg.layout));
+                        return local(OperandRef::zero_sized(arg.layout));
                     }
                     PassMode::Direct(_) => {
                         let llarg = bx.get_param(llarg_idx);
diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs
index 4000c9540ce..31c293d7c29 100644
--- a/compiler/rustc_codegen_ssa/src/mir/operand.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs
@@ -45,6 +45,14 @@ pub enum OperandValue<V> {
     /// as returned by [`LayoutTypeMethods::scalar_pair_element_backend_type`]
     /// with `immediate: true`.
     Pair(V, V),
+    /// A value taking no bytes, and which therefore needs no LLVM value at all.
+    ///
+    /// If you ever need a `V` to pass to something, get a fresh poison value
+    /// from [`ConstMethods::const_poison`].
+    ///
+    /// An `OperandValue` *must* be this variant for any type for which
+    /// `is_zst` on its `Layout` returns `true`.
+    ZeroSized,
 }
 
 /// An `OperandRef` is an "SSA" reference to a Rust value, along with
@@ -71,15 +79,9 @@ impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
 }
 
 impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
-    pub fn new_zst<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
-        bx: &mut Bx,
-        layout: TyAndLayout<'tcx>,
-    ) -> OperandRef<'tcx, V> {
+    pub fn zero_sized(layout: TyAndLayout<'tcx>) -> OperandRef<'tcx, V> {
         assert!(layout.is_zst());
-        OperandRef {
-            val: OperandValue::Immediate(bx.const_poison(bx.immediate_backend_type(layout))),
-            layout,
-        }
+        OperandRef { val: OperandValue::ZeroSized, layout }
     }
 
     pub fn from_const<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
@@ -97,7 +99,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
                 let llval = bx.scalar_to_backend(x, scalar, bx.immediate_backend_type(layout));
                 OperandValue::Immediate(llval)
             }
-            ConstValue::ZeroSized => return OperandRef::new_zst(bx, layout),
+            ConstValue::ZeroSized => return OperandRef::zero_sized(layout),
             ConstValue::Slice { data, start, end } => {
                 let Abi::ScalarPair(a_scalar, _) = layout.abi else {
                     bug!("from_const: invalid ScalarPair layout: {:#?}", layout);
@@ -178,7 +180,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
                 );
                 OperandRef { val: OperandValue::Pair(a_val, b_val), layout }
             }
-            _ if layout.is_zst() => OperandRef::new_zst(bx, layout),
+            _ if layout.is_zst() => OperandRef::zero_sized(layout),
             _ => {
                 // Neither a scalar nor scalar pair. Load from a place
                 let init = bx.const_data_from_alloc(alloc);
@@ -216,6 +218,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
             OperandValue::Immediate(llptr) => (llptr, None),
             OperandValue::Pair(llptr, llextra) => (llptr, Some(llextra)),
             OperandValue::Ref(..) => bug!("Deref of by-Ref operand {:?}", self),
+            OperandValue::ZeroSized => bug!("Deref of ZST operand {:?}", self),
         };
         let layout = cx.layout_of(projected_ty);
         PlaceRef { llval: llptr, llextra, layout, align: layout.align.abi }
@@ -273,9 +276,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
 
         let mut val = match (self.val, self.layout.abi) {
             // If the field is ZST, it has no data.
-            _ if field.is_zst() => {
-                return OperandRef::new_zst(bx, field);
-            }
+            _ if field.is_zst() => OperandValue::ZeroSized,
 
             // Newtype of a scalar, scalar pair or vector.
             (OperandValue::Immediate(_) | OperandValue::Pair(..), _)
@@ -306,6 +307,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
         };
 
         match (&mut val, field.abi) {
+            (OperandValue::ZeroSized, _) => {}
             (
                 OperandValue::Immediate(llval),
                 Abi::Scalar(_) | Abi::ScalarPair(..) | Abi::Vector { .. },
@@ -359,8 +361,8 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
 impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
     /// Returns an `OperandValue` that's generally UB to use in any way.
     ///
-    /// Depending on the `layout`, returns an `Immediate` or `Pair` containing
-    /// poison value(s), or a `Ref` containing a poison pointer.
+    /// Depending on the `layout`, returns `ZeroSized` for ZSTs, an `Immediate` or
+    /// `Pair` containing poison value(s), or a `Ref` containing a poison pointer.
     ///
     /// Supports sized types only.
     pub fn poison<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
@@ -368,7 +370,9 @@ impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
         layout: TyAndLayout<'tcx>,
     ) -> OperandValue<V> {
         assert!(layout.is_sized());
-        if bx.cx().is_backend_immediate(layout) {
+        if layout.is_zst() {
+            OperandValue::ZeroSized
+        } else if bx.cx().is_backend_immediate(layout) {
             let ibty = bx.cx().immediate_backend_type(layout);
             OperandValue::Immediate(bx.const_poison(ibty))
         } else if bx.cx().is_backend_scalar_pair(layout) {
@@ -421,12 +425,11 @@ impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
         flags: MemFlags,
     ) {
         debug!("OperandRef::store: operand={:?}, dest={:?}", self, dest);
-        // Avoid generating stores of zero-sized values, because the only way to have a zero-sized
-        // value is through `undef`, and store itself is useless.
-        if dest.layout.is_zst() {
-            return;
-        }
         match self {
+            OperandValue::ZeroSized => {
+                // Avoid generating stores of zero-sized values, because the only way to have a zero-sized
+                // value is through `undef`/`poison`, and the store itself is useless.
+            }
             OperandValue::Ref(r, None, source_align) => {
                 if flags.contains(MemFlags::NONTEMPORAL) {
                     // HACK(nox): This is inefficient but there is no nontemporal memcpy.
@@ -527,7 +530,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                             // checks in `codegen_consume` and `extract_field`.
                             let elem = o.layout.field(bx.cx(), 0);
                             if elem.is_zst() {
-                                o = OperandRef::new_zst(bx, elem);
+                                o = OperandRef::zero_sized(elem);
                             } else {
                                 return None;
                             }
@@ -561,7 +564,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
 
         // ZSTs don't require any actual memory access.
         if layout.is_zst() {
-            return OperandRef::new_zst(bx, layout);
+            return OperandRef::zero_sized(layout);
         }
 
         if let Some(o) = self.maybe_codegen_consume_direct(bx, place_ref) {
diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
index 0255b660380..5241a5aee00 100644
--- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
@@ -70,6 +70,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     OperandValue::Ref(_, Some(_), _) => {
                         bug!("unsized coercion on an unsized rvalue");
                     }
+                    OperandValue::ZeroSized => {
+                        bug!("unsized coercion on a ZST rvalue");
+                    }
                 }
             }
 
@@ -165,11 +168,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         }
 
         match src.val {
-            OperandValue::Ref(..) => {
+            OperandValue::Ref(..) | OperandValue::ZeroSized => {
                 span_bug!(
                     self.mir.span,
                     "Operand path should have handled transmute \
-                    from `Ref` {src:?} to place {dst:?}"
+                    from {src:?} to place {dst:?}"
                 );
             }
             OperandValue::Immediate(..) | OperandValue::Pair(..) => {
@@ -220,17 +223,22 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 let fake_place = PlaceRef::new_sized_aligned(cast_ptr, cast, align);
                 Some(bx.load_operand(fake_place).val)
             }
+            OperandValue::ZeroSized => {
+                let OperandValueKind::ZeroSized = operand_kind else {
+                    bug!("Found {operand_kind:?} for operand {operand:?}");
+                };
+                if let OperandValueKind::ZeroSized = cast_kind {
+                    Some(OperandValue::ZeroSized)
+                } else {
+                    None
+                }
+            }
             OperandValue::Immediate(imm) => {
                 let OperandValueKind::Immediate(in_scalar) = operand_kind else {
                     bug!("Found {operand_kind:?} for operand {operand:?}");
                 };
-                if let OperandValueKind::Immediate(out_scalar) = cast_kind {
-                    match (in_scalar, out_scalar) {
-                        (ScalarOrZst::Zst, ScalarOrZst::Zst) => {
-                            Some(OperandRef::new_zst(bx, cast).val)
-                        }
-                        (ScalarOrZst::Scalar(in_scalar), ScalarOrZst::Scalar(out_scalar))
-                            if in_scalar.size(self.cx) == out_scalar.size(self.cx) =>
+                if let OperandValueKind::Immediate(out_scalar) = cast_kind
+                    && in_scalar.size(self.cx) == out_scalar.size(self.cx)
                         {
                             let operand_bty = bx.backend_type(operand.layout);
                             let cast_bty = bx.backend_type(cast);
@@ -242,9 +250,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 out_scalar,
                                 cast_bty,
                             )))
-                        }
-                        _ => None,
-                    }
                 } else {
                     None
                 }
@@ -457,6 +462,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                             OperandValue::Ref(..) => {
                                 bug!("by-ref operand {:?} in `codegen_rvalue_operand`", operand);
                             }
+                            OperandValue::ZeroSized => {
+                                bug!("zero-sized operand {:?} in `codegen_rvalue_operand`", operand);
+                            }
                         };
                         let (lldata, llextra) =
                             base::unsize_ptr(bx, lldata, operand.layout.ty, cast.ty, llextra);
@@ -490,6 +498,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                             OperandValue::Ref(_, _, _) => todo!(),
                             OperandValue::Immediate(v) => (v, None),
                             OperandValue::Pair(v, l) => (v, Some(l)),
+                            OperandValue::ZeroSized => bug!("ZST -- which is not PointerLike -- in DynStar"),
                         };
                         let (lldata, llextra) =
                             base::cast_to_dyn_star(bx, lldata, operand.layout, cast.ty, llextra);
@@ -718,7 +727,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 // According to `rvalue_creates_operand`, only ZST
                 // aggregate rvalues are allowed to be operands.
                 let ty = rvalue.ty(self.mir, self.cx.tcx());
-                OperandRef::new_zst(bx, self.cx.layout_of(self.monomorphize(ty)))
+                OperandRef::zero_sized(self.cx.layout_of(self.monomorphize(ty)))
             }
             mir::Rvalue::ShallowInitBox(ref operand, content_ty) => {
                 let operand = self.codegen_operand(bx, operand);
@@ -936,6 +945,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     // Can always load from a pointer as needed
                     (OperandValueKind::Ref, _) => true,
 
+                    // ZST-to-ZST is the easiest thing ever
+                    (OperandValueKind::ZeroSized, OperandValueKind::ZeroSized) => true,
+
+                    // But if only one of them is a ZST the sizes can't match
+                    (OperandValueKind::ZeroSized, _) | (_, OperandValueKind::ZeroSized) => false,
+
                     // Need to generate an `alloc` to get a pointer from an immediate
                     (OperandValueKind::Immediate(..) | OperandValueKind::Pair(..), OperandValueKind::Ref) => false,
 
@@ -979,12 +994,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
 
     /// Gets which variant of [`OperandValue`] is expected for a particular type.
     fn value_kind(&self, layout: TyAndLayout<'tcx>) -> OperandValueKind {
-        if self.cx.is_backend_immediate(layout) {
+        if layout.is_zst() {
+            OperandValueKind::ZeroSized
+        } else if self.cx.is_backend_immediate(layout) {
             debug_assert!(!self.cx.is_backend_scalar_pair(layout));
             OperandValueKind::Immediate(match layout.abi {
-                abi::Abi::Scalar(s) => ScalarOrZst::Scalar(s),
-                abi::Abi::Vector { element, .. } => ScalarOrZst::Scalar(element),
-                _ if layout.is_zst() => ScalarOrZst::Zst,
+                abi::Abi::Scalar(s) => s,
+                abi::Abi::Vector { element, .. } => element,
                 x => span_bug!(self.mir.span, "Couldn't translate {x:?} as backend immediate"),
             })
         } else if self.cx.is_backend_scalar_pair(layout) {
@@ -1007,21 +1023,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
 #[derive(Debug, Copy, Clone)]
 enum OperandValueKind {
     Ref,
-    Immediate(ScalarOrZst),
+    Immediate(abi::Scalar),
     Pair(abi::Scalar, abi::Scalar),
-}
-
-#[derive(Debug, Copy, Clone)]
-enum ScalarOrZst {
-    Zst,
-    Scalar(abi::Scalar),
-}
-
-impl ScalarOrZst {
-    pub fn size(self, cx: &impl abi::HasDataLayout) -> abi::Size {
-        match self {
-            ScalarOrZst::Zst => abi::Size::ZERO,
-            ScalarOrZst::Scalar(s) => s.size(cx),
-        }
-    }
+    ZeroSized,
 }
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl
index 7d56cf0aa07..bf660c59cab 100644
--- a/compiler/rustc_const_eval/messages.ftl
+++ b/compiler/rustc_const_eval/messages.ftl
@@ -1,8 +1,142 @@
+const_eval_address_space_full =
+    there are no more free addresses in the address space
+const_eval_align_check_failed = accessing memory with alignment {$has}, but alignment {$required} is required
+const_eval_align_offset_invalid_align =
+    `align_offset` called with non-power-of-two align: {$target_align}
+
+const_eval_alignment_check_failed =
+    accessing memory with alignment {$has}, but alignment {$required} is required
+const_eval_already_reported =
+    an error has already been reported elsewhere (this should not usually be printed)
+const_eval_assume_false =
+    `assume` called with `false`
+
+const_eval_await_non_const =
+    cannot convert `{$ty}` into a future in {const_eval_const_context}s
+const_eval_bounds_check_failed =
+    indexing out of bounds: the len is {$len} but the index is {$index}
+const_eval_box_to_mut = {$front_matter}: encountered a box pointing to mutable memory in a constant
+const_eval_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant
+const_eval_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty}
+const_eval_call_nonzero_intrinsic =
+    `{$name}` called on 0
+
+const_eval_closure_call =
+    closures need an RFC before allowed to be called in {const_eval_const_context}s
+const_eval_closure_fndef_not_const =
+    function defined here, but it is not `const`
+const_eval_closure_non_const =
+    cannot call non-const closure in {const_eval_const_context}s
+const_eval_consider_dereferencing =
+    consider dereferencing here
+const_eval_const_accesses_static = constant accesses static
+
+const_eval_const_context = {$kind ->
+    [const] constant
+    [static] static
+    [const_fn] constant function
+    *[other] {""}
+}
+
+const_eval_copy_nonoverlapping_overlapping =
+    `copy_nonoverlapping` called on overlapping ranges
+
+const_eval_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance)
+const_eval_dangling_box_out_of_bounds = {$front_matter}: encountered a dangling box (going beyond the bounds of its allocation)
+const_eval_dangling_box_use_after_free = {$front_matter}: encountered a dangling box (use-after-free)
+const_eval_dangling_int_pointer =
+    {$bad_pointer_message}: {$pointer} is a dangling pointer (it has no provenance)
+const_eval_dangling_null_pointer =
+    {$bad_pointer_message}: null pointer is a dangling pointer (it has no provenance)
+const_eval_dangling_ptr_in_final = encountered dangling pointer in final constant
+
+const_eval_dangling_ref_no_provenance = {$front_matter}: encountered a dangling reference ({$pointer} has no provenance)
+const_eval_dangling_ref_out_of_bounds = {$front_matter}: encountered a dangling reference (going beyond the bounds of its allocation)
+const_eval_dangling_ref_use_after_free = {$front_matter}: encountered a dangling reference (use-after-free)
+const_eval_dead_local =
+    accessing a dead local variable
+const_eval_dealloc_immutable =
+    deallocating immutable allocation {$alloc}
+
+const_eval_dealloc_incorrect_layout =
+    incorrect layout on deallocation: {$alloc} has size {$size} and alignment {$align}, but gave size {$size_found} and alignment {$align_found}
+
+const_eval_dealloc_kind_mismatch =
+    deallocating {$alloc}, which is {$alloc_kind} memory, using {$kind} deallocation operation
+
+const_eval_deref_coercion_non_const =
+    cannot perform deref coercion on `{$ty}` in {const_eval_const_context}s
+    .note = attempting to deref into `{$target_ty}`
+    .target_note = deref defined here
+const_eval_deref_function_pointer =
+    accessing {$allocation} which contains a function
+const_eval_deref_test = dereferencing pointer failed
+const_eval_deref_vtable_pointer =
+    accessing {$allocation} which contains a vtable
+const_eval_different_allocations =
+    `{$name}` called on pointers into different allocations
+
+const_eval_division_by_zero =
+    dividing by zero
+const_eval_division_overflow =
+    overflow in signed division (dividing MIN by -1)
+const_eval_double_storage_live =
+    StorageLive on a local that was already live
+
+const_eval_dyn_call_not_a_method =
+    `dyn` call trying to call something that is not a method
+
+const_eval_dyn_call_vtable_mismatch =
+    `dyn` call on a pointer whose vtable does not match its type
+
+const_eval_dyn_star_call_vtable_mismatch =
+    `dyn*` call on a pointer whose vtable does not match its type
+
+const_eval_erroneous_constant =
+    erroneous constant used
+
+const_eval_error = {$error_kind ->
+    [static] could not evaluate static initializer
+    [const] evaluation of constant value failed
+    [const_with_path] evaluation of `{$instance}` failed
+    *[other] {""}
+}
+
+const_eval_exact_div_has_remainder =
+    exact_div: {$a} cannot be divided by {$b} without remainder
+
+const_eval_expected_non_ptr = {$front_matter}: encountered `{$value}`, but expected plain (non-pointer) bytes
+const_eval_fn_ptr_call =
+    function pointers need an RFC before allowed to be called in {const_eval_const_context}s
+const_eval_for_loop_into_iter_non_const =
+    cannot convert `{$ty}` into an iterator in {const_eval_const_context}s
+
+const_eval_frame_note = {$times ->
+    [0] {const_eval_frame_note_inner}
+    *[other] [... {$times} additional calls {const_eval_frame_note_inner} ...]
+}
+
+const_eval_frame_note_inner = inside {$where_ ->
+    [closure] closure
+    [instance] `{$instance}`
+    *[other] {""}
+}
+
+const_eval_in_bounds_test = out-of-bounds pointer use
+const_eval_incompatible_calling_conventions =
+    calling a function with calling convention {$callee_conv} using calling convention {$caller_conv}
+
+const_eval_incompatible_return_types =
+    calling a function with return type {$callee_ty} passing return place of type {$caller_ty}
+
+const_eval_incompatible_types =
+    calling a function with argument of type {$callee_ty} passing data of type {$caller_ty}
+
 const_eval_interior_mutability_borrow =
     cannot borrow here, since the borrowed element may contain interior mutability
 
 const_eval_interior_mutable_data_refer =
-    {$kind}s cannot refer to interior mutable data
+    {const_eval_const_context}s cannot refer to interior mutable data
     .label = this borrow of an interior mutable value may end up in the final value
     .help = to fix this, the value can be extracted to a separate `static` item and then referenced
     .teach_note =
@@ -10,19 +144,163 @@ const_eval_interior_mutable_data_refer =
         This would make multiple uses of a constant to be able to see different values and allow circumventing
         the `Send` and `Sync` requirements for shared mutable data, which is unsound.
 
+const_eval_invalid_align =
+    align has to be a power of 2
+
+const_eval_invalid_align_details =
+    invalid align passed to `{$name}`: {$align} is {$err_kind ->
+        [not_power_of_two] not a power of 2
+        [too_large] too large
+        *[other] {""}
+    }
+
+const_eval_invalid_bool =
+    interpreting an invalid 8-bit value as a bool: 0x{$value}
+const_eval_invalid_box_meta = {$front_matter}: encountered invalid box metadata: total size is bigger than largest supported object
+const_eval_invalid_box_slice_meta = {$front_matter}: encountered invalid box metadata: slice is bigger than largest supported object
+const_eval_invalid_char =
+    interpreting an invalid 32-bit value as a char: 0x{$value}
+const_eval_invalid_dealloc =
+    deallocating {$alloc_id}, which is {$kind ->
+        [fn] a function
+        [vtable] a vtable
+        [static_mem] static memory
+        *[other] {""}
+    }
+
+const_eval_invalid_enum_tag = {$front_matter}: encountered {$value}, but expected a valid enum tag
+const_eval_invalid_fn_ptr = {$front_matter}: encountered {$value}, but expected a function pointer
+const_eval_invalid_function_pointer =
+    using {$pointer} as function pointer but it does not point to a function
+const_eval_invalid_meta =
+    invalid metadata in wide pointer: total size is bigger than largest supported object
+const_eval_invalid_meta_slice =
+    invalid metadata in wide pointer: slice is bigger than largest supported object
+const_eval_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object
+const_eval_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
+const_eval_invalid_str =
+    this string is not valid UTF-8: {$err}
+const_eval_invalid_tag =
+    enum value has invalid tag: {$tag}
+const_eval_invalid_transmute =
+    transmuting from {$src_bytes}-byte type to {$dest_bytes}-byte type: `{$src}` -> `{$dest}`
+
+const_eval_invalid_uninit_bytes =
+    reading memory at {$alloc}{$access}, but memory is uninitialized at {$uninit}, and this operation requires initialized memory
+const_eval_invalid_uninit_bytes_unknown =
+    using uninitialized data, but this operation requires initialized memory
+const_eval_invalid_value = constructing invalid value
+const_eval_invalid_value_with_path = constructing invalid value at {$path}
+## The `front_matter`s here refer to either `middle_invalid_value` or `middle_invalid_value_with_path`.
+
+const_eval_invalid_vtable_pointer =
+    using {$pointer} as vtable pointer but it does not point to a vtable
+
+const_eval_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
+
+const_eval_live_drop =
+    destructor of `{$dropped_ty}` cannot be evaluated at compile-time
+    .label = the destructor for this type cannot be evaluated in {const_eval_const_context}s
+    .dropped_at_label = value is dropped here
+
+const_eval_long_running =
+    constant evaluation is taking a long time
+    .note = this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval.
+        If your compilation actually takes a long time, you can safely allow the lint.
+    .label = the const evaluator is currently interpreting this expression
+    .help = the constant being evaluated
+
 const_eval_max_num_nodes_in_const = maximum number of nodes exceeded in constant {$global_const_id}
 
+const_eval_memory_access_test = memory access failed
+const_eval_memory_exhausted =
+    tried to allocate more memory than available to compiler
+const_eval_modified_global =
+    modifying a static's initial value from another static's initializer
+
 const_eval_mut_deref =
-    mutation through a reference is not allowed in {$kind}s
+    mutation through a reference is not allowed in {const_eval_const_context}s
 
+const_eval_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const`
+const_eval_never_val = {$front_matter}: encountered a value of the never type `!`
 const_eval_non_const_fmt_macro_call =
-    cannot call non-const formatting macro in {$kind}s
+    cannot call non-const formatting macro in {const_eval_const_context}s
 
 const_eval_non_const_fn_call =
-    cannot call non-const fn `{$def_path_str}` in {$kind}s
+    cannot call non-const fn `{$def_path_str}` in {const_eval_const_context}s
+
+const_eval_non_const_impl =
+    impl defined here, but it is not `const`
+
+const_eval_noreturn_asm_returned =
+    returned from noreturn inline assembly
+
+const_eval_not_enough_caller_args =
+    calling a function with fewer arguments than it requires
+
+const_eval_null_box = {$front_matter}: encountered a null box
+const_eval_null_fn_ptr = {$front_matter}: encountered a null function pointer
+const_eval_null_ref = {$front_matter}: encountered a null reference
+const_eval_nullable_ptr_out_of_range = {$front_matter}: encountered a potentially null pointer, but expected something that cannot possibly fail to be {$in_range}
+const_eval_nullary_intrinsic_fail =
+    could not evaluate nullary intrinsic
+
+const_eval_offset_from_overflow =
+    `{$name}` called when first pointer is too far ahead of second
+
+const_eval_offset_from_test = out-of-bounds `offset_from`
+const_eval_offset_from_underflow =
+    `{$name}` called when first pointer is too far before second
+
+const_eval_operator_non_const =
+    cannot call non-const operator in {const_eval_const_context}s
+const_eval_out_of_range = {$front_matter}: encountered {$value}, but expected something {$in_range}
+const_eval_overflow =
+    overflow executing `{$name}`
+
+const_eval_overflow_shift =
+    overflowing shift by {$val} in `{$name}`
+
+const_eval_panic =
+    the evaluated program panicked at '{$msg}', {$file}:{$line}:{$col}
 
 const_eval_panic_non_str = argument to `panic!()` in a const context must have type `&str`
 
+const_eval_partial_pointer_copy =
+    unable to copy parts of a pointer from memory at {$ptr}
+const_eval_partial_pointer_overwrite =
+    unable to overwrite parts of a pointer in memory at {$ptr}
+const_eval_pointer_arithmetic_overflow =
+    overflowing in-bounds pointer arithmetic
+const_eval_pointer_arithmetic_test = out-of-bounds pointer arithmetic
+const_eval_pointer_out_of_bounds =
+    {$bad_pointer_message}: {$alloc_id} has size {$alloc_size}, so pointer to {$ptr_size} {$ptr_size ->
+        [1] byte
+        *[many] bytes
+    } starting at offset {$ptr_offset} is out-of-bounds
+const_eval_pointer_use_after_free =
+    pointer to {$allocation} was dereferenced after this allocation got freed
+const_eval_ptr_as_bytes_1 =
+    this code performed an operation that depends on the underlying bytes representing a pointer
+const_eval_ptr_as_bytes_2 =
+    the absolute address of a pointer is not known at compile-time, so such operations are not supported
+const_eval_ptr_out_of_range = {$front_matter}: encountered a pointer, but expected something that cannot possibly fail to be {$in_range}
+const_eval_question_branch_non_const =
+    `?` cannot determine the branch of `{$ty}` in {const_eval_const_context}s
+
+const_eval_question_from_residual_non_const =
+    `?` cannot convert from residual of `{$ty}` in {const_eval_const_context}s
+
+const_eval_range = in the range {$lo}..={$hi}
+const_eval_range_lower = greater or equal to {$lo}
+const_eval_range_singular = equal to {$lo}
+const_eval_range_upper = less or equal to {$hi}
+const_eval_range_wrapping = less or equal to {$hi}, or greater or equal to {$lo}
+const_eval_raw_bytes = the raw bytes of the constant (size: {$size}, align: {$align}) {"{"}{$bytes}{"}"}
+
+const_eval_raw_eq_with_provenance =
+    `raw_eq` on bytes with provenance
+
 const_eval_raw_ptr_comparison =
     pointers cannot be reliably compared during const eval
     .note = see issue #53020 <https://github.com/rust-lang/rust/issues/53020> for more information
@@ -32,8 +310,36 @@ const_eval_raw_ptr_to_int =
     .note = at compile-time, pointers do not have an integer value
     .note2 = avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior
 
+const_eval_read_extern_static =
+    cannot read from extern static ({$did})
+const_eval_read_pointer_as_bytes =
+    unable to turn pointer into raw bytes
+const_eval_realloc_or_alloc_with_offset =
+    {$kind ->
+        [dealloc] deallocating
+        [realloc] reallocating
+        *[other] {""}
+    } {$ptr} which does not point to the beginning of an object
+
+const_eval_ref_to_mut = {$front_matter}: encountered a reference pointing to mutable memory in a constant
+const_eval_ref_to_static = {$front_matter}: encountered a reference pointing to a static variable in a constant
+const_eval_ref_to_uninhabited = {$front_matter}: encountered a reference pointing to uninhabited type {$ty}
+const_eval_remainder_by_zero =
+    calculating the remainder with a divisor of zero
+const_eval_remainder_overflow =
+    overflow in signed remainder (dividing MIN by -1)
+const_eval_scalar_size_mismatch =
+    scalar size mismatch: expected {$target_size} bytes but got {$data_size} bytes instead
+const_eval_size_of_unsized =
+    size_of called on unsized type `{$ty}`
+const_eval_size_overflow =
+    overflow computing total size of `{$name}`
+
+const_eval_stack_frame_limit_reached =
+    reached the configured maximum number of stack frames
+
 const_eval_static_access =
-    {$kind}s cannot refer to statics
+    {const_eval_const_context}s cannot refer to statics
     .help = consider extracting the value of the `static` to a `const`, and referring to that
     .teach_note = `static` and `const` variables can refer to other `const` variables. A `const` variable, however, cannot refer to a `static` variable.
     .teach_help = To fix this, the value can be extracted to a `const` and then used.
@@ -41,27 +347,34 @@ const_eval_static_access =
 const_eval_thread_local_access =
     thread-local statics cannot be accessed at compile-time
 
-const_eval_transient_mut_borrow = mutable references are not allowed in {$kind}s
+const_eval_thread_local_static =
+    cannot access thread local static ({$did})
+const_eval_too_generic =
+    encountered overly generic constant
+const_eval_too_many_caller_args =
+    calling a function with more arguments than it expected
 
-const_eval_transient_mut_borrow_raw = raw mutable references are not allowed in {$kind}s
+const_eval_transient_mut_borrow = mutable references are not allowed in {const_eval_const_context}s
 
-const_eval_unallowed_fn_pointer_call = function pointer calls are not allowed in {$kind}s
+const_eval_transient_mut_borrow_raw = raw mutable references are not allowed in {const_eval_const_context}s
+
+const_eval_try_block_from_output_non_const =
+    `try` block cannot convert `{$ty}` to the result in {const_eval_const_context}s
+const_eval_unaligned_box = {$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes})
+const_eval_unaligned_ref = {$front_matter}: encountered an unaligned reference (required {$required_bytes} byte alignment but found {$found_bytes})
+const_eval_unallowed_fn_pointer_call = function pointer calls are not allowed in {const_eval_const_context}s
 
 const_eval_unallowed_heap_allocations =
-    allocations are not allowed in {$kind}s
-    .label = allocation not allowed in {$kind}s
+    allocations are not allowed in {const_eval_const_context}s
+    .label = allocation not allowed in {const_eval_const_context}s
     .teach_note =
         The value of statics and constants must be known at compile time, and they live for the entire lifetime of a program. Creating a boxed value allocates memory on the heap at runtime, and therefore cannot be done at compile time.
 
 const_eval_unallowed_inline_asm =
-    inline assembly is not allowed in {$kind}s
-
+    inline assembly is not allowed in {const_eval_const_context}s
 const_eval_unallowed_mutable_refs =
-    mutable references are not allowed in the final value of {$kind}s
+    mutable references are not allowed in the final value of {const_eval_const_context}s
     .teach_note =
-        References in statics and constants may only refer to immutable values.
-
-
         Statics are shared everywhere, and if they refer to mutable data one might violate memory
         safety since holding multiple mutable references to shared data is not allowed.
 
@@ -69,7 +382,7 @@ const_eval_unallowed_mutable_refs =
         If you really want global mutable state, try using static mut or a global UnsafeCell.
 
 const_eval_unallowed_mutable_refs_raw =
-    raw mutable references are not allowed in the final value of {$kind}s
+    raw mutable references are not allowed in the final value of {const_eval_const_context}s
     .teach_note =
         References in statics and constants may only refer to immutable values.
 
@@ -83,9 +396,59 @@ const_eval_unallowed_mutable_refs_raw =
 const_eval_unallowed_op_in_const_context =
     {$msg}
 
+const_eval_undefined_behavior =
+    it is undefined behavior to use this value
+
+const_eval_undefined_behavior_note =
+    The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
+
+const_eval_uninhabited_enum_variant_written =
+    writing discriminant of an uninhabited enum
+const_eval_uninhabited_val = {$front_matter}: encountered a value of uninhabited type `{$ty}`
+const_eval_uninit = {$front_matter}: encountered uninitialized bytes
+const_eval_uninit_bool = {$front_matter}: encountered uninitialized memory, but expected a boolean
+const_eval_uninit_box = {$front_matter}: encountered uninitialized memory, but expected a box
+const_eval_uninit_char = {$front_matter}: encountered uninitialized memory, but expected a unicode scalar value
+const_eval_uninit_enum_tag = {$front_matter}: encountered uninitialized bytes, but expected a valid enum tag
+const_eval_uninit_float = {$front_matter}: encountered uninitialized memory, but expected a floating point number
+const_eval_uninit_fn_ptr = {$front_matter}: encountered uninitialized memory, but expected a function pointer
+const_eval_uninit_init_scalar = {$front_matter}: encountered uninitialized memory, but expected initialized scalar value
+const_eval_uninit_int = {$front_matter}: encountered uninitialized memory, but expected an integer
+const_eval_uninit_raw_ptr = {$front_matter}: encountered uninitialized memory, but expected a raw pointer
+const_eval_uninit_ref = {$front_matter}: encountered uninitialized memory, but expected a reference
+const_eval_uninit_str = {$front_matter}: encountered uninitialized data in `str`
+const_eval_uninit_unsized_local =
+    unsized local is used while uninitialized
+const_eval_unreachable = entering unreachable code
+const_eval_unreachable_unwind =
+    unwinding past a stack frame that does not allow unwinding
+
+const_eval_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in a `const`
+const_eval_unsigned_offset_from_overflow =
+    `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: {$a_offset} < {$b_offset}
+
 const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
 
 const_eval_unstable_in_stable =
     const-stable function cannot use `#[feature({$gate})]`
     .unstable_sugg = if it is not part of the public API, make this function unstably const
     .bypass_sugg = otherwise `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks
+
+const_eval_unsupported_untyped_pointer = unsupported untyped pointer in constant
+    .note = memory only reachable via raw pointers is not supported
+
+const_eval_unterminated_c_string =
+    reading a null-terminated string starting at {$pointer} with no null found before end of allocation
+
+const_eval_unwind_past_top =
+    unwinding past the topmost frame of the stack
+
+const_eval_upcast_mismatch =
+    upcast on a pointer whose vtable does not match its type
+
+const_eval_validation_invalid_bool = {$front_matter}: encountered {$value}, but expected a boolean
+const_eval_validation_invalid_char = {$front_matter}: encountered {$value}, but expected a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)
+const_eval_write_to_read_only =
+    writing to {$allocation} which is read-only
+const_eval_zst_pointer_out_of_bounds =
+    {$bad_pointer_message}: {$alloc_id} has size {$alloc_size}, so pointer at offset {$ptr_offset} is out-of-bounds
diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs
index c591ff75ab8..7890d878d08 100644
--- a/compiler/rustc_const_eval/src/const_eval/error.rs
+++ b/compiler/rustc_const_eval/src/const_eval/error.rs
@@ -1,17 +1,15 @@
-use std::error::Error;
-use std::fmt;
+use std::mem;
 
-use rustc_errors::Diagnostic;
+use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, IntoDiagnostic, IntoDiagnosticArg};
 use rustc_middle::mir::AssertKind;
-use rustc_middle::query::TyCtxtAt;
+use rustc_middle::ty::TyCtxt;
 use rustc_middle::ty::{layout::LayoutError, ConstInt};
-use rustc_span::{Span, Symbol};
+use rustc_span::source_map::Spanned;
+use rustc_span::{ErrorGuaranteed, Span, Symbol};
 
 use super::InterpCx;
-use crate::interpret::{
-    struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
-    UnsupportedOpInfo,
-};
+use crate::errors::{self, FrameNote, ReportErrorExt};
+use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, Machine, MachineStopType};
 
 /// The CTFE machine has some custom error kinds.
 #[derive(Clone, Debug)]
@@ -23,7 +21,35 @@ pub enum ConstEvalErrKind {
     Abort(String),
 }
 
-impl MachineStopType for ConstEvalErrKind {}
+impl MachineStopType for ConstEvalErrKind {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use ConstEvalErrKind::*;
+        match self {
+            ConstAccessesStatic => const_eval_const_accesses_static,
+            ModifiedGlobal => const_eval_modified_global,
+            Panic { .. } => const_eval_panic,
+            AssertFailure(x) => x.diagnostic_message(),
+            Abort(msg) => msg.to_string().into(),
+        }
+    }
+    fn add_args(
+        self: Box<Self>,
+        adder: &mut dyn FnMut(std::borrow::Cow<'static, str>, DiagnosticArgValue<'static>),
+    ) {
+        use ConstEvalErrKind::*;
+        match *self {
+            ConstAccessesStatic | ModifiedGlobal | Abort(_) => {}
+            AssertFailure(kind) => kind.add_args(adder),
+            Panic { msg, line, col, file } => {
+                adder("msg".into(), msg.into_diagnostic_arg());
+                adder("file".into(), file.into_diagnostic_arg());
+                adder("line".into(), line.into_diagnostic_arg());
+                adder("col".into(), col.into_diagnostic_arg());
+            }
+        }
+    }
+}
 
 // The errors become `MachineStop` with plain strings when being raised.
 // `ConstEvalErr` (in `librustc_middle/mir/interpret/error.rs`) knows to
@@ -34,151 +60,117 @@ impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
     }
 }
 
-impl fmt::Display for ConstEvalErrKind {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use self::ConstEvalErrKind::*;
-        match self {
-            ConstAccessesStatic => write!(f, "constant accesses static"),
-            ModifiedGlobal => {
-                write!(f, "modifying a static's initial value from another static's initializer")
-            }
-            AssertFailure(msg) => write!(f, "{:?}", msg),
-            Panic { msg, line, col, file } => {
-                write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col)
-            }
-            Abort(msg) => write!(f, "{}", msg),
-        }
-    }
-}
-
-impl Error for ConstEvalErrKind {}
+pub fn get_span_and_frames<'tcx, 'mir, M: Machine<'mir, 'tcx>>(
+    ecx: &InterpCx<'mir, 'tcx, M>,
+) -> (Span, Vec<errors::FrameNote>)
+where
+    'tcx: 'mir,
+{
+    let mut stacktrace = ecx.generate_stacktrace();
+    // Filter out `requires_caller_location` frames.
+    stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
+    let span = stacktrace.first().map(|f| f.span).unwrap_or(ecx.tcx.span);
 
-/// When const-evaluation errors, this type is constructed with the resulting information,
-/// and then used to emit the error as a lint or hard error.
-#[derive(Debug)]
-pub(super) struct ConstEvalErr<'tcx> {
-    pub span: Span,
-    pub error: InterpError<'tcx>,
-    pub stacktrace: Vec<FrameInfo<'tcx>>,
-}
+    let mut frames = Vec::new();
 
-impl<'tcx> ConstEvalErr<'tcx> {
-    /// Turn an interpreter error into something to report to the user.
-    /// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace.
-    /// Should be called only if the error is actually going to be reported!
-    pub fn new<'mir, M: Machine<'mir, 'tcx>>(
-        ecx: &InterpCx<'mir, 'tcx, M>,
-        error: InterpErrorInfo<'tcx>,
-        span: Option<Span>,
-    ) -> ConstEvalErr<'tcx>
-    where
-        'tcx: 'mir,
-    {
-        error.print_backtrace();
-        let mut stacktrace = ecx.generate_stacktrace();
-        // Filter out `requires_caller_location` frames.
-        stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
-        // If `span` is missing, use topmost remaining frame, or else the "root" span from `ecx.tcx`.
-        let span = span.or_else(|| stacktrace.first().map(|f| f.span)).unwrap_or(ecx.tcx.span);
-        ConstEvalErr { error: error.into_kind(), stacktrace, span }
-    }
-
-    pub(super) fn report(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled {
-        self.report_decorated(tcx, message, |_| {})
-    }
-
-    #[instrument(level = "trace", skip(self, decorate))]
-    pub(super) fn decorate(&self, err: &mut Diagnostic, decorate: impl FnOnce(&mut Diagnostic)) {
-        trace!("reporting const eval failure at {:?}", self.span);
-        // Add some more context for select error types.
-        match self.error {
-            InterpError::Unsupported(
-                UnsupportedOpInfo::ReadPointerAsBytes
-                | UnsupportedOpInfo::PartialPointerOverwrite(_)
-                | UnsupportedOpInfo::PartialPointerCopy(_),
-            ) => {
-                err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
-                err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
+    // Add notes to the backtrace. Don't print a single-line backtrace though.
+    if stacktrace.len() > 1 {
+        // Helper closure to print duplicated lines.
+        let mut add_frame = |mut frame: errors::FrameNote| {
+            frames.push(errors::FrameNote { times: 0, ..frame.clone() });
+            // Don't print [... additional calls ...] if the number of lines is small
+            if frame.times < 3 {
+                let times = frame.times;
+                frame.times = 0;
+                frames.extend(std::iter::repeat(frame).take(times as usize));
+            } else {
+                frames.push(frame);
             }
-            _ => {}
-        }
-        // Add spans for the stacktrace. Don't print a single-line backtrace though.
-        if self.stacktrace.len() > 1 {
-            // Helper closure to print duplicated lines.
-            let mut flush_last_line = |last_frame: Option<(String, _)>, times| {
-                if let Some((line, span)) = last_frame {
-                    err.span_note(span, line.clone());
-                    // Don't print [... additional calls ...] if the number of lines is small
-                    if times < 3 {
-                        for _ in 0..times {
-                            err.span_note(span, line.clone());
-                        }
-                    } else {
-                        err.span_note(
-                            span,
-                            format!("[... {} additional calls {} ...]", times, &line),
-                        );
-                    }
-                }
-            };
+        };
 
-            let mut last_frame = None;
-            let mut times = 0;
-            for frame_info in &self.stacktrace {
-                let frame = (frame_info.to_string(), frame_info.span);
-                if last_frame.as_ref() == Some(&frame) {
-                    times += 1;
-                } else {
-                    flush_last_line(last_frame, times);
+        let mut last_frame: Option<errors::FrameNote> = None;
+        for frame_info in &stacktrace {
+            let frame = frame_info.as_note(*ecx.tcx);
+            match last_frame.as_mut() {
+                Some(last_frame)
+                    if last_frame.span == frame.span
+                        && last_frame.where_ == frame.where_
+                        && last_frame.instance == frame.instance =>
+                {
+                    last_frame.times += 1;
+                }
+                Some(last_frame) => {
+                    add_frame(mem::replace(last_frame, frame));
+                }
+                None => {
                     last_frame = Some(frame);
-                    times = 0;
                 }
             }
-            flush_last_line(last_frame, times);
         }
-        // Let the caller attach any additional information it wants.
-        decorate(err);
+        if let Some(frame) = last_frame {
+            add_frame(frame);
+        }
     }
 
-    /// Create a diagnostic for this const eval error.
-    ///
-    /// Sets the message passed in via `message` and adds span labels with detailed error
-    /// information before handing control back to `decorate` to do any final annotations,
-    /// after which the diagnostic is emitted.
-    ///
-    /// If `lint_root.is_some()` report it as a lint, else report it as a hard error.
-    /// (Except that for some errors, we ignore all that -- see `must_error` below.)
-    #[instrument(skip(self, tcx, decorate), level = "debug")]
-    pub(super) fn report_decorated(
-        &self,
-        tcx: TyCtxtAt<'tcx>,
-        message: &str,
-        decorate: impl FnOnce(&mut Diagnostic),
-    ) -> ErrorHandled {
-        debug!("self.error: {:?}", self.error);
-        // Special handling for certain errors
-        match &self.error {
-            // Don't emit a new diagnostic for these errors
-            err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
-                ErrorHandled::TooGeneric
-            }
-            err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(*error_reported),
-            err_inval!(Layout(LayoutError::SizeOverflow(_))) => {
-                // We must *always* hard error on these, even if the caller wants just a lint.
-                // The `message` makes little sense here, this is a more serious error than the
-                // caller thinks anyway.
-                // See <https://github.com/rust-lang/rust/pull/63152>.
-                let mut err = struct_error(tcx, &self.error.to_string());
-                self.decorate(&mut err, decorate);
-                ErrorHandled::Reported(err.emit().into())
-            }
-            _ => {
-                // Report as hard error.
-                let mut err = struct_error(tcx, message);
-                err.span_label(self.span, self.error.to_string());
-                self.decorate(&mut err, decorate);
-                ErrorHandled::Reported(err.emit().into())
+    (span, frames)
+}
+
+/// Create a diagnostic for a const eval error.
+///
+/// This will use the `mk` function for creating the error which will get passed labels according to
+/// the `InterpError` and the span and a stacktrace of current execution according to
+/// `get_span_and_frames`.
+pub(super) fn report<'tcx, C, F, E>(
+    tcx: TyCtxt<'tcx>,
+    error: InterpError<'tcx>,
+    span: Option<Span>,
+    get_span_and_frames: C,
+    mk: F,
+) -> ErrorHandled
+where
+    C: FnOnce() -> (Span, Vec<FrameNote>),
+    F: FnOnce(Span, Vec<FrameNote>) -> E,
+    E: IntoDiagnostic<'tcx, ErrorGuaranteed>,
+{
+    // Special handling for certain errors
+    match error {
+        // Don't emit a new diagnostic for these errors
+        err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
+            ErrorHandled::TooGeneric
+        }
+        err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(error_reported),
+        err_inval!(Layout(layout_error @ LayoutError::SizeOverflow(_))) => {
+            // We must *always* hard error on these, even if the caller wants just a lint.
+            // The `message` makes little sense here, this is a more serious error than the
+            // caller thinks anyway.
+            // See <https://github.com/rust-lang/rust/pull/63152>.
+            let (our_span, frames) = get_span_and_frames();
+            let span = span.unwrap_or(our_span);
+            let mut err =
+                tcx.sess.create_err(Spanned { span, node: layout_error.into_diagnostic() });
+            err.code(rustc_errors::error_code!(E0080));
+            let Some((mut err, handler)) = err.into_diagnostic() else {
+                    panic!("did not emit diag");
+                };
+            for frame in frames {
+                err.eager_subdiagnostic(handler, frame);
             }
+
+            ErrorHandled::Reported(handler.emit_diagnostic(&mut err).unwrap().into())
+        }
+        _ => {
+            // Report as hard error.
+            let (our_span, frames) = get_span_and_frames();
+            let span = span.unwrap_or(our_span);
+            let err = mk(span, frames);
+            let mut err = tcx.sess.create_err(err);
+
+            let msg = error.diagnostic_message();
+            error.add_args(&tcx.sess.parse_sess.span_diagnostic, &mut err);
+
+            // Use *our* span to label the interp error
+            err.span_label(our_span, msg);
+            ErrorHandled::Reported(err.emit().into())
         }
     }
 }
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 046d2052968..8b8e8ff58e9 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -1,12 +1,12 @@
 use crate::const_eval::CheckAlignment;
-use std::borrow::Cow;
+use crate::errors::ConstEvalError;
 
 use either::{Left, Right};
 
 use rustc_hir::def::DefKind;
 use rustc_middle::mir;
-use rustc_middle::mir::interpret::ErrorHandled;
-use rustc_middle::mir::pretty::display_allocation;
+use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo};
+use rustc_middle::mir::pretty::write_allocation_bytes;
 use rustc_middle::traits::Reveal;
 use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::print::with_no_trimmed_paths;
@@ -14,7 +14,8 @@ use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::source_map::Span;
 use rustc_target::abi::{self, Abi};
 
-use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr};
+use super::{CompileTimeEvalContext, CompileTimeInterpreter};
+use crate::errors;
 use crate::interpret::eval_nullary_intrinsic;
 use crate::interpret::{
     intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
@@ -22,10 +23,6 @@ use crate::interpret::{
     RefTracking, StackPopCleanup,
 };
 
-const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \
-     so this check might be overzealous. Please open an issue on the rustc \
-     repository if you believe it should not be considered undefined behavior.";
-
 // Returns a pointer to where the result lives
 fn eval_body_using_ecx<'mir, 'tcx>(
     ecx: &mut CompileTimeEvalContext<'mir, 'tcx>,
@@ -103,7 +100,7 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>(
         tcx,
         root_span,
         param_env,
-        CompileTimeInterpreter::new(tcx.const_eval_limit(), can_access_statics, CheckAlignment::No),
+        CompileTimeInterpreter::new(can_access_statics, CheckAlignment::No),
     )
 }
 
@@ -253,8 +250,14 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
         };
         return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| {
             let span = tcx.def_span(def_id);
-            let error = ConstEvalErr { error: error.into_kind(), stacktrace: vec![], span };
-            error.report(tcx.at(span), "could not evaluate nullary intrinsic")
+
+            super::report(
+                tcx,
+                error.into_kind(),
+                Some(span),
+                || (span, vec![]),
+                |span, _| errors::NullaryIntrinsicError { span },
+            )
         });
     }
 
@@ -306,7 +309,6 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
         // 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(
-            tcx.const_eval_limit(),
             /*can_access_statics:*/ is_static,
             if tcx.sess.opts.unstable_opts.extra_const_ub_checks {
                 CheckAlignment::Error
@@ -319,9 +321,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
     let res = ecx.load_mir(cid.instance.def, cid.promoted);
     match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) {
         Err(error) => {
-            let err = ConstEvalErr::new(&ecx, error, None);
-            let msg = if is_static {
-                Cow::from("could not evaluate static initializer")
+            let (error, backtrace) = error.into_parts();
+            backtrace.print_backtrace();
+
+            let (kind, instance) = if is_static {
+                ("static", String::new())
             } else {
                 // If the current item has generics, we'd like to enrich the message with the
                 // instance and its substs: to show the actual compile-time values, in addition to
@@ -329,19 +333,29 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
                 let instance = &key.value.instance;
                 if !instance.substs.is_empty() {
                     let instance = with_no_trimmed_paths!(instance.to_string());
-                    let msg = format!("evaluation of `{}` failed", instance);
-                    Cow::from(msg)
+                    ("const_with_path", instance)
                 } else {
-                    Cow::from("evaluation of constant value failed")
+                    ("const", String::new())
                 }
             };
 
-            Err(err.report(ecx.tcx.at(err.span), &msg))
+            Err(super::report(
+                *ecx.tcx,
+                error,
+                None,
+                || super::get_span_and_frames(&ecx),
+                |span, frames| ConstEvalError {
+                    span,
+                    error_kind: kind,
+                    instance,
+                    frame_notes: frames,
+                },
+            ))
         }
         Ok(mplace) => {
             // Since evaluation had no errors, validate the resulting constant.
             // This is a separate `try` block to provide more targeted error reporting.
-            let validation = try {
+            let validation: Result<_, InterpErrorInfo<'_>> = try {
                 let mut ref_tracking = RefTracking::new(mplace);
                 let mut inner = false;
                 while let Some((mplace, path)) = ref_tracking.todo.pop() {
@@ -358,23 +372,37 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
                 }
             };
             let alloc_id = mplace.ptr.provenance.unwrap();
+
+            // Validation failed, report an error. This is always a hard error.
             if let Err(error) = validation {
-                // Validation failed, report an error. This is always a hard error.
-                let err = ConstEvalErr::new(&ecx, error, None);
-                Err(err.report_decorated(
-                    ecx.tcx,
-                    "it is undefined behavior to use this value",
-                    |diag| {
-                        if matches!(err.error, InterpError::UndefinedBehavior(_)) {
-                            diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
-                        }
-                        diag.note(format!(
-                            "the raw bytes of the constant ({}",
-                            display_allocation(
-                                *ecx.tcx,
-                                ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner()
-                            )
-                        ));
+                let (error, backtrace) = error.into_parts();
+                backtrace.print_backtrace();
+
+                let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
+
+                let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
+                let mut bytes = String::new();
+                if alloc.size() != abi::Size::ZERO {
+                    bytes = "\n".into();
+                    // FIXME(translation) there might be pieces that are translatable.
+                    write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, "    ").unwrap();
+                }
+                let raw_bytes = errors::RawBytesNote {
+                    size: alloc.size().bytes(),
+                    align: alloc.align.bytes(),
+                    bytes,
+                };
+
+                Err(super::report(
+                    *ecx.tcx,
+                    error,
+                    None,
+                    || super::get_span_and_frames(&ecx),
+                    move |span, frames| errors::UndefinedBehavior {
+                        span,
+                        ub_note,
+                        frames,
+                        raw_bytes,
                     },
                 ))
             } else {
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 58b5755af07..7391f567040 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -16,25 +16,37 @@ use std::fmt;
 use rustc_ast::Mutability;
 use rustc_hir::def_id::DefId;
 use rustc_middle::mir::AssertMessage;
-use rustc_session::Limit;
 use rustc_span::symbol::{sym, Symbol};
 use rustc_target::abi::{Align, Size};
 use rustc_target::spec::abi::Abi as CallAbi;
 
+use crate::errors::{LongRunning, LongRunningWarn};
 use crate::interpret::{
     self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
     InterpResult, OpTy, PlaceTy, Pointer, Scalar,
 };
+use crate::{errors, fluent_generated as fluent};
 
 use super::error::*;
 
+/// When hitting this many interpreted terminators we emit a deny by default lint
+/// that notfies the user that their constant takes a long time to evaluate. If that's
+/// what they intended, they can just allow the lint.
+const LINT_TERMINATOR_LIMIT: usize = 2_000_000;
+/// The limit used by `-Z tiny-const-eval-limit`. This smaller limit is useful for internal
+/// tests not needing to run 30s or more to show some behaviour.
+const TINY_LINT_TERMINATOR_LIMIT: usize = 20;
+/// After this many interpreted terminators, we start emitting progress indicators at every
+/// power of two of interpreted terminators.
+const PROGRESS_INDICATOR_START: usize = 4_000_000;
+
 /// Extra machine state for CTFE, and the Machine instance
 pub struct CompileTimeInterpreter<'mir, 'tcx> {
-    /// For now, the number of terminators that can be evaluated before we throw a resource
-    /// exhaustion error.
+    /// The number of terminators that have been evaluated.
     ///
-    /// Setting this to `0` disables the limit and allows the interpreter to run forever.
-    pub(super) steps_remaining: usize,
+    /// This is used to produce lints informing the user that the compiler is not stuck.
+    /// Set to `usize::MAX` to never report anything.
+    pub(super) num_evaluated_steps: usize,
 
     /// The virtual call stack.
     pub(super) stack: Vec<Frame<'mir, 'tcx, AllocId, ()>>,
@@ -72,13 +84,9 @@ impl CheckAlignment {
 }
 
 impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
-    pub(crate) fn new(
-        const_eval_limit: Limit,
-        can_access_statics: bool,
-        check_alignment: CheckAlignment,
-    ) -> Self {
+    pub(crate) fn new(can_access_statics: bool, check_alignment: CheckAlignment) -> Self {
         CompileTimeInterpreter {
-            steps_remaining: const_eval_limit.0,
+            num_evaluated_steps: 0,
             stack: Vec::new(),
             can_access_statics,
             check_alignment,
@@ -247,7 +255,10 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
         let target_align = self.read_scalar(&args[1])?.to_target_usize(self)?;
 
         if !target_align.is_power_of_two() {
-            throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
+            throw_ub_custom!(
+                fluent::const_eval_align_offset_invalid_align,
+                target_align = target_align,
+            );
         }
 
         match self.ptr_try_get_alloc_id(ptr) {
@@ -353,15 +364,18 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
                 "`alignment_check_failed` called when no alignment check requested"
             ),
             CheckAlignment::FutureIncompat => {
-                let err = ConstEvalErr::new(ecx, err, None);
-                ecx.tcx.struct_span_lint_hir(
+                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),
-                    err.span,
-                    err.error.to_string(),
-                    |db| {
-                        err.decorate(db, |_| {});
-                        db
+                    span,
+                    errors::AlignmentCheckFailed {
+                        has: has.bytes(),
+                        required: required.bytes(),
+                        frames,
                     },
                 );
                 Ok(())
@@ -475,7 +489,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
 
                 let align = match Align::from_bytes(align) {
                     Ok(a) => a,
-                    Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+                    Err(err) => throw_ub_custom!(
+                        fluent::const_eval_invalid_align_details,
+                        name = "const_allocate",
+                        err_kind = err.diag_ident(),
+                        align = err.align()
+                    ),
                 };
 
                 let ptr = ecx.allocate_ptr(
@@ -493,7 +512,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
                 let size = Size::from_bytes(size);
                 let align = match Align::from_bytes(align) {
                     Ok(a) => a,
-                    Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+                    Err(err) => throw_ub_custom!(
+                        fluent::const_eval_invalid_align_details,
+                        name = "const_deallocate",
+                        err_kind = err.diag_ident(),
+                        align = err.align()
+                    ),
                 };
 
                 // If an allocation is created in an another const,
@@ -569,13 +593,54 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
 
     fn increment_const_eval_counter(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
         // The step limit has already been hit in a previous call to `increment_const_eval_counter`.
-        if ecx.machine.steps_remaining == 0 {
-            return Ok(());
-        }
 
-        ecx.machine.steps_remaining -= 1;
-        if ecx.machine.steps_remaining == 0 {
-            throw_exhaust!(StepLimitReached)
+        if let Some(new_steps) = ecx.machine.num_evaluated_steps.checked_add(1) {
+            let (limit, start) = if ecx.tcx.sess.opts.unstable_opts.tiny_const_eval_limit {
+                (TINY_LINT_TERMINATOR_LIMIT, TINY_LINT_TERMINATOR_LIMIT)
+            } else {
+                (LINT_TERMINATOR_LIMIT, PROGRESS_INDICATOR_START)
+            };
+
+            ecx.machine.num_evaluated_steps = new_steps;
+            // By default, we have a *deny* lint kicking in after some time
+            // to ensure `loop {}` doesn't just go forever.
+            // In case that lint got reduced, in particular for `--cap-lint` situations, we also
+            // have a hard warning shown every now and then for really long executions.
+            if new_steps == limit {
+                // By default, we stop after a million steps, but the user can disable this lint
+                // to be able to run until the heat death of the universe or power loss, whichever
+                // comes first.
+                let hir_id = ecx.best_lint_scope();
+                let is_error = ecx
+                    .tcx
+                    .lint_level_at_node(
+                        rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
+                        hir_id,
+                    )
+                    .0
+                    .is_error();
+                let span = ecx.cur_span();
+                ecx.tcx.emit_spanned_lint(
+                    rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
+                    hir_id,
+                    span,
+                    LongRunning { item_span: ecx.tcx.span },
+                );
+                // If this was a hard error, don't bother continuing evaluation.
+                if is_error {
+                    let guard = ecx
+                        .tcx
+                        .sess
+                        .delay_span_bug(span, "The deny lint should have already errored");
+                    throw_inval!(AlreadyReported(guard.into()));
+                }
+            } else if new_steps > start && new_steps.is_power_of_two() {
+                // Only report after a certain number of terminators have been evaluated and the
+                // current number of evaluated terminators is a power of 2. The latter gives us a cheap
+                // way to implement exponential backoff.
+                let span = ecx.cur_span();
+                ecx.tcx.sess.emit_warning(LongRunningWarn { span, item_span: ecx.tcx.span });
+            }
         }
 
         Ok(())
diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs
index b59ca8e2070..6e462d3a1e9 100644
--- a/compiler/rustc_const_eval/src/const_eval/mod.rs
+++ b/compiler/rustc_const_eval/src/const_eval/mod.rs
@@ -73,17 +73,8 @@ pub(crate) fn eval_to_valtree<'tcx>(
             let global_const_id = cid.display(tcx);
             match err {
                 ValTreeCreationError::NodesOverflow => {
-                    let msg = format!(
-                        "maximum number of nodes exceeded in constant {}",
-                        &global_const_id
-                    );
-                    let mut diag = match tcx.hir().span_if_local(did) {
-                        Some(span) => {
-                            tcx.sess.create_err(MaxNumNodesInConstErr { span, global_const_id })
-                        }
-                        None => tcx.sess.struct_err(msg),
-                    };
-                    diag.emit();
+                    let span = tcx.hir().span_if_local(did);
+                    tcx.sess.emit_err(MaxNumNodesInConstErr { span, global_const_id });
 
                     Ok(None)
                 }
diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs
index f8b7cc6d7e1..eed3091d481 100644
--- a/compiler/rustc_const_eval/src/errors.rs
+++ b/compiler/rustc_const_eval/src/errors.rs
@@ -1,6 +1,24 @@
+use rustc_errors::{
+    DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler,
+    IntoDiagnostic,
+};
 use rustc_hir::ConstContext;
-use rustc_macros::Diagnostic;
+use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
+use rustc_middle::mir::interpret::{
+    CheckInAllocMsg, ExpectedKind, InterpError, InvalidMetaKind, InvalidProgramInfo, PointerKind,
+    ResourceExhaustionInfo, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
+};
+use rustc_middle::ty::{self, Ty};
 use rustc_span::Span;
+use rustc_target::abi::call::AdjustForForeignAbiError;
+use rustc_target::abi::{Size, WrappingRange};
+
+#[derive(Diagnostic)]
+#[diag(const_eval_dangling_ptr_in_final)]
+pub(crate) struct DanglingPtrInFinal {
+    #[primary_span]
+    pub span: Span,
+}
 
 #[derive(Diagnostic)]
 #[diag(const_eval_unstable_in_stable)]
@@ -92,7 +110,7 @@ pub(crate) struct TransientMutBorrowErrRaw {
 #[diag(const_eval_max_num_nodes_in_const)]
 pub(crate) struct MaxNumNodesInConstErr {
     #[primary_span]
-    pub span: Span,
+    pub span: Option<Span>,
     pub global_const_id: String,
 }
 
@@ -176,6 +194,14 @@ pub(crate) struct UnallowedInlineAsm {
 }
 
 #[derive(Diagnostic)]
+#[diag(const_eval_unsupported_untyped_pointer)]
+#[note]
+pub(crate) struct UnsupportedUntypedPointer {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
 #[diag(const_eval_interior_mutable_data_refer, code = "E0492")]
 pub(crate) struct InteriorMutableDataRefer {
     #[primary_span]
@@ -194,3 +220,649 @@ pub(crate) struct InteriorMutabilityBorrow {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(LintDiagnostic)]
+#[diag(const_eval_long_running)]
+#[note]
+pub struct LongRunning {
+    #[help]
+    pub item_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_long_running)]
+pub struct LongRunningWarn {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    #[help]
+    pub item_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_erroneous_constant)]
+pub(crate) struct ErroneousConstUsed {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[note(const_eval_non_const_impl)]
+pub(crate) struct NonConstImplNote {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Subdiagnostic, PartialEq, Eq, Clone)]
+#[note(const_eval_frame_note)]
+pub struct FrameNote {
+    #[primary_span]
+    pub span: Span,
+    pub times: i32,
+    pub where_: &'static str,
+    pub instance: String,
+}
+
+#[derive(Subdiagnostic)]
+#[note(const_eval_raw_bytes)]
+pub struct RawBytesNote {
+    pub size: u64,
+    pub align: u64,
+    pub bytes: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_for_loop_into_iter_non_const, code = "E0015")]
+pub struct NonConstForLoopIntoIter<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_question_branch_non_const, code = "E0015")]
+pub struct NonConstQuestionBranch<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_question_from_residual_non_const, code = "E0015")]
+pub struct NonConstQuestionFromResidual<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_try_block_from_output_non_const, code = "E0015")]
+pub struct NonConstTryBlockFromOutput<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_await_non_const, code = "E0015")]
+pub struct NonConstAwait<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_closure_non_const, code = "E0015")]
+pub struct NonConstClosure {
+    #[primary_span]
+    pub span: Span,
+    pub kind: ConstContext,
+    #[subdiagnostic]
+    pub note: Option<NonConstClosureNote>,
+}
+
+#[derive(Subdiagnostic)]
+pub enum NonConstClosureNote {
+    #[note(const_eval_closure_fndef_not_const)]
+    FnDef {
+        #[primary_span]
+        span: Span,
+    },
+    #[note(const_eval_fn_ptr_call)]
+    FnPtr,
+    #[note(const_eval_closure_call)]
+    Closure,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(const_eval_consider_dereferencing, applicability = "machine-applicable")]
+pub struct ConsiderDereferencing {
+    pub deref: String,
+    #[suggestion_part(code = "{deref}")]
+    pub span: Span,
+    #[suggestion_part(code = "{deref}")]
+    pub rhs_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_operator_non_const, code = "E0015")]
+pub struct NonConstOperator {
+    #[primary_span]
+    pub span: Span,
+    pub kind: ConstContext,
+    #[subdiagnostic]
+    pub sugg: Option<ConsiderDereferencing>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_deref_coercion_non_const, code = "E0015")]
+#[note]
+pub struct NonConstDerefCoercion<'tcx> {
+    #[primary_span]
+    pub span: Span,
+    pub ty: Ty<'tcx>,
+    pub kind: ConstContext,
+    pub target_ty: Ty<'tcx>,
+    #[note(const_eval_target_note)]
+    pub deref_target: Option<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_live_drop, code = "E0493")]
+pub struct LiveDrop<'tcx> {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub kind: ConstContext,
+    pub dropped_ty: Ty<'tcx>,
+    #[label(const_eval_dropped_at_label)]
+    pub dropped_at: Option<Span>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(const_eval_align_check_failed)]
+pub struct AlignmentCheckFailed {
+    pub has: u64,
+    pub required: u64,
+    #[subdiagnostic]
+    pub frames: Vec<FrameNote>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_error, code = "E0080")]
+pub struct ConstEvalError {
+    #[primary_span]
+    pub span: Span,
+    /// One of "const", "const_with_path", and "static"
+    pub error_kind: &'static str,
+    pub instance: String,
+    #[subdiagnostic]
+    pub frame_notes: Vec<FrameNote>,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_nullary_intrinsic_fail)]
+pub struct NullaryIntrinsicError {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(const_eval_undefined_behavior, code = "E0080")]
+pub struct UndefinedBehavior {
+    #[primary_span]
+    pub span: Span,
+    #[note(const_eval_undefined_behavior_note)]
+    pub ub_note: Option<()>,
+    #[subdiagnostic]
+    pub frames: Vec<FrameNote>,
+    #[subdiagnostic]
+    pub raw_bytes: RawBytesNote,
+}
+
+pub trait ReportErrorExt {
+    /// Returns the diagnostic message for this error.
+    fn diagnostic_message(&self) -> DiagnosticMessage;
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    );
+
+    fn debug(self) -> String
+    where
+        Self: Sized,
+    {
+        ty::tls::with(move |tcx| {
+            let mut builder = tcx.sess.struct_allow(DiagnosticMessage::Str(String::new().into()));
+            let handler = &tcx.sess.parse_sess.span_diagnostic;
+            let message = self.diagnostic_message();
+            self.add_args(handler, &mut builder);
+            let s = handler.eagerly_translate_to_string(message, builder.args());
+            builder.cancel();
+            s
+        })
+    }
+}
+
+fn bad_pointer_message(msg: CheckInAllocMsg, handler: &Handler) -> String {
+    use crate::fluent_generated::*;
+
+    let msg = match msg {
+        CheckInAllocMsg::DerefTest => const_eval_deref_test,
+        CheckInAllocMsg::MemoryAccessTest => const_eval_memory_access_test,
+        CheckInAllocMsg::PointerArithmeticTest => const_eval_pointer_arithmetic_test,
+        CheckInAllocMsg::OffsetFromTest => const_eval_offset_from_test,
+        CheckInAllocMsg::InboundsTest => const_eval_in_bounds_test,
+    };
+
+    handler.eagerly_translate_to_string(msg, [].into_iter())
+}
+
+impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use UndefinedBehaviorInfo::*;
+        match self {
+            Ub(msg) => msg.clone().into(),
+            Unreachable => const_eval_unreachable,
+            BoundsCheckFailed { .. } => const_eval_bounds_check_failed,
+            DivisionByZero => const_eval_division_by_zero,
+            RemainderByZero => const_eval_remainder_by_zero,
+            DivisionOverflow => const_eval_division_overflow,
+            RemainderOverflow => const_eval_remainder_overflow,
+            PointerArithOverflow => const_eval_pointer_arithmetic_overflow,
+            InvalidMeta(InvalidMetaKind::SliceTooBig) => const_eval_invalid_meta_slice,
+            InvalidMeta(InvalidMetaKind::TooBig) => const_eval_invalid_meta,
+            UnterminatedCString(_) => const_eval_unterminated_c_string,
+            PointerUseAfterFree(_) => const_eval_pointer_use_after_free,
+            PointerOutOfBounds { ptr_size: Size::ZERO, .. } => const_eval_zst_pointer_out_of_bounds,
+            PointerOutOfBounds { .. } => const_eval_pointer_out_of_bounds,
+            DanglingIntPointer(0, _) => const_eval_dangling_null_pointer,
+            DanglingIntPointer(_, _) => const_eval_dangling_int_pointer,
+            AlignmentCheckFailed { .. } => const_eval_alignment_check_failed,
+            WriteToReadOnly(_) => const_eval_write_to_read_only,
+            DerefFunctionPointer(_) => const_eval_deref_function_pointer,
+            DerefVTablePointer(_) => const_eval_deref_vtable_pointer,
+            InvalidBool(_) => const_eval_invalid_bool,
+            InvalidChar(_) => const_eval_invalid_char,
+            InvalidTag(_) => const_eval_invalid_tag,
+            InvalidFunctionPointer(_) => const_eval_invalid_function_pointer,
+            InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer,
+            InvalidStr(_) => const_eval_invalid_str,
+            InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown,
+            InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes,
+            DeadLocal => const_eval_dead_local,
+            ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch,
+            UninhabitedEnumVariantWritten => const_eval_uninhabited_enum_variant_written,
+            Validation(e) => e.diagnostic_message(),
+            Custom(x) => (x.msg)(),
+        }
+    }
+
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    ) {
+        use UndefinedBehaviorInfo::*;
+        match self {
+            Ub(_)
+            | Unreachable
+            | DivisionByZero
+            | RemainderByZero
+            | DivisionOverflow
+            | RemainderOverflow
+            | PointerArithOverflow
+            | InvalidMeta(InvalidMetaKind::SliceTooBig)
+            | InvalidMeta(InvalidMetaKind::TooBig)
+            | InvalidUninitBytes(None)
+            | DeadLocal
+            | UninhabitedEnumVariantWritten => {}
+            BoundsCheckFailed { len, index } => {
+                builder.set_arg("len", len);
+                builder.set_arg("index", index);
+            }
+            UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
+                builder.set_arg("pointer", ptr);
+            }
+            PointerUseAfterFree(allocation) => {
+                builder.set_arg("allocation", allocation);
+            }
+            PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => {
+                builder
+                    .set_arg("alloc_id", alloc_id)
+                    .set_arg("alloc_size", alloc_size.bytes())
+                    .set_arg("ptr_offset", ptr_offset)
+                    .set_arg("ptr_size", ptr_size.bytes())
+                    .set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
+            }
+            DanglingIntPointer(ptr, msg) => {
+                if ptr != 0 {
+                    builder.set_arg("pointer", format!("{ptr:#x}[noalloc]"));
+                }
+
+                builder.set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
+            }
+            AlignmentCheckFailed { required, has } => {
+                builder.set_arg("required", required.bytes());
+                builder.set_arg("has", has.bytes());
+            }
+            WriteToReadOnly(alloc) | DerefFunctionPointer(alloc) | DerefVTablePointer(alloc) => {
+                builder.set_arg("allocation", alloc);
+            }
+            InvalidBool(b) => {
+                builder.set_arg("value", format!("{b:02x}"));
+            }
+            InvalidChar(c) => {
+                builder.set_arg("value", format!("{c:08x}"));
+            }
+            InvalidTag(tag) => {
+                builder.set_arg("tag", format!("{tag:x}"));
+            }
+            InvalidStr(err) => {
+                builder.set_arg("err", format!("{err}"));
+            }
+            InvalidUninitBytes(Some((alloc, info))) => {
+                builder.set_arg("alloc", alloc);
+                builder.set_arg("access", info.access);
+                builder.set_arg("uninit", info.uninit);
+            }
+            ScalarSizeMismatch(info) => {
+                builder.set_arg("target_size", info.target_size);
+                builder.set_arg("data_size", info.data_size);
+            }
+            Validation(e) => e.add_args(handler, builder),
+            Custom(custom) => {
+                (custom.add_args)(&mut |name, value| {
+                    builder.set_arg(name, value);
+                });
+            }
+        }
+    }
+}
+
+impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use rustc_middle::mir::interpret::ValidationErrorKind::*;
+        match self.kind {
+            PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => const_eval_box_to_uninhabited,
+            PtrToUninhabited { ptr_kind: PointerKind::Ref, .. } => const_eval_ref_to_uninhabited,
+
+            PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_box_to_static,
+            PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_ref_to_static,
+
+            PtrToMut { ptr_kind: PointerKind::Box } => const_eval_box_to_mut,
+            PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_ref_to_mut,
+
+            ExpectedNonPtr { .. } => const_eval_expected_non_ptr,
+            MutableRefInConst => const_eval_mutable_ref_in_const,
+            NullFnPtr => const_eval_null_fn_ptr,
+            NeverVal => const_eval_never_val,
+            NullablePtrOutOfRange { .. } => const_eval_nullable_ptr_out_of_range,
+            PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
+            OutOfRange { .. } => const_eval_out_of_range,
+            UnsafeCell => const_eval_unsafe_cell,
+            UninhabitedVal { .. } => const_eval_uninhabited_val,
+            InvalidEnumTag { .. } => const_eval_invalid_enum_tag,
+            UninitEnumTag => const_eval_uninit_enum_tag,
+            UninitStr => const_eval_uninit_str,
+            Uninit { expected: ExpectedKind::Bool } => const_eval_uninit_bool,
+            Uninit { expected: ExpectedKind::Reference } => const_eval_uninit_ref,
+            Uninit { expected: ExpectedKind::Box } => const_eval_uninit_box,
+            Uninit { expected: ExpectedKind::RawPtr } => const_eval_uninit_raw_ptr,
+            Uninit { expected: ExpectedKind::InitScalar } => const_eval_uninit_init_scalar,
+            Uninit { expected: ExpectedKind::Char } => const_eval_uninit_char,
+            Uninit { expected: ExpectedKind::Float } => const_eval_uninit_float,
+            Uninit { expected: ExpectedKind::Int } => const_eval_uninit_int,
+            Uninit { expected: ExpectedKind::FnPtr } => const_eval_uninit_fn_ptr,
+            UninitVal => const_eval_uninit,
+            InvalidVTablePtr { .. } => const_eval_invalid_vtable_ptr,
+            InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => {
+                const_eval_invalid_box_slice_meta
+            }
+            InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref } => {
+                const_eval_invalid_ref_slice_meta
+            }
+
+            InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => const_eval_invalid_box_meta,
+            InvalidMetaTooLarge { ptr_kind: PointerKind::Ref } => const_eval_invalid_ref_meta,
+            UnalignedPtr { ptr_kind: PointerKind::Ref, .. } => const_eval_unaligned_ref,
+            UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_unaligned_box,
+
+            NullPtr { ptr_kind: PointerKind::Box } => const_eval_null_box,
+            NullPtr { ptr_kind: PointerKind::Ref } => const_eval_null_ref,
+            DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => {
+                const_eval_dangling_box_no_provenance
+            }
+            DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref, .. } => {
+                const_eval_dangling_ref_no_provenance
+            }
+            DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => {
+                const_eval_dangling_box_out_of_bounds
+            }
+            DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref } => {
+                const_eval_dangling_ref_out_of_bounds
+            }
+            DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => {
+                const_eval_dangling_box_use_after_free
+            }
+            DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref } => {
+                const_eval_dangling_ref_use_after_free
+            }
+            InvalidBool { .. } => const_eval_validation_invalid_bool,
+            InvalidChar { .. } => const_eval_validation_invalid_char,
+            InvalidFnPtr { .. } => const_eval_invalid_fn_ptr,
+        }
+    }
+
+    fn add_args<G: EmissionGuarantee>(self, handler: &Handler, err: &mut DiagnosticBuilder<'_, G>) {
+        use crate::fluent_generated as fluent;
+        use rustc_middle::mir::interpret::ValidationErrorKind::*;
+
+        let message = if let Some(path) = self.path {
+            handler.eagerly_translate_to_string(
+                fluent::const_eval_invalid_value_with_path,
+                [("path".into(), DiagnosticArgValue::Str(path.into()))].iter().map(|(a, b)| (a, b)),
+            )
+        } else {
+            handler.eagerly_translate_to_string(fluent::const_eval_invalid_value, [].into_iter())
+        };
+
+        err.set_arg("front_matter", message);
+
+        fn add_range_arg<G: EmissionGuarantee>(
+            r: WrappingRange,
+            max_hi: u128,
+            handler: &Handler,
+            err: &mut DiagnosticBuilder<'_, G>,
+        ) {
+            let WrappingRange { start: lo, end: hi } = r;
+            assert!(hi <= max_hi);
+            let msg = if lo > hi {
+                fluent::const_eval_range_wrapping
+            } else if lo == hi {
+                fluent::const_eval_range_singular
+            } else if lo == 0 {
+                assert!(hi < max_hi, "should not be printing if the range covers everything");
+                fluent::const_eval_range_upper
+            } else if hi == max_hi {
+                assert!(lo > 0, "should not be printing if the range covers everything");
+                fluent::const_eval_range_lower
+            } else {
+                fluent::const_eval_range
+            };
+
+            let args = [
+                ("lo".into(), DiagnosticArgValue::Str(lo.to_string().into())),
+                ("hi".into(), DiagnosticArgValue::Str(hi.to_string().into())),
+            ];
+            let args = args.iter().map(|(a, b)| (a, b));
+            let message = handler.eagerly_translate_to_string(msg, args);
+            err.set_arg("in_range", message);
+        }
+
+        match self.kind {
+            PtrToUninhabited { ty, .. } | UninhabitedVal { ty } => {
+                err.set_arg("ty", ty);
+            }
+            ExpectedNonPtr { value }
+            | InvalidEnumTag { value }
+            | InvalidVTablePtr { value }
+            | InvalidBool { value }
+            | InvalidChar { value }
+            | InvalidFnPtr { value } => {
+                err.set_arg("value", value);
+            }
+            NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => {
+                add_range_arg(range, max_value, handler, err)
+            }
+            OutOfRange { range, max_value, value } => {
+                err.set_arg("value", value);
+                add_range_arg(range, max_value, handler, err);
+            }
+            UnalignedPtr { required_bytes, found_bytes, .. } => {
+                err.set_arg("required_bytes", required_bytes);
+                err.set_arg("found_bytes", found_bytes);
+            }
+            DanglingPtrNoProvenance { pointer, .. } => {
+                err.set_arg("pointer", pointer);
+            }
+            NullPtr { .. }
+            | PtrToStatic { .. }
+            | PtrToMut { .. }
+            | MutableRefInConst
+            | NullFnPtr
+            | NeverVal
+            | UnsafeCell
+            | UninitEnumTag
+            | UninitStr
+            | Uninit { .. }
+            | UninitVal
+            | InvalidMetaSliceTooLarge { .. }
+            | InvalidMetaTooLarge { .. }
+            | DanglingPtrUseAfterFree { .. }
+            | DanglingPtrOutOfBounds { .. } => {}
+        }
+    }
+}
+
+impl ReportErrorExt for UnsupportedOpInfo {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        match self {
+            UnsupportedOpInfo::Unsupported(s) => s.clone().into(),
+            UnsupportedOpInfo::PartialPointerOverwrite(_) => const_eval_partial_pointer_overwrite,
+            UnsupportedOpInfo::PartialPointerCopy(_) => const_eval_partial_pointer_copy,
+            UnsupportedOpInfo::ReadPointerAsBytes => const_eval_read_pointer_as_bytes,
+            UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static,
+            UnsupportedOpInfo::ReadExternStatic(_) => const_eval_read_extern_static,
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(self, _: &Handler, builder: &mut DiagnosticBuilder<'_, G>) {
+        use crate::fluent_generated::*;
+
+        use UnsupportedOpInfo::*;
+        if let ReadPointerAsBytes | PartialPointerOverwrite(_) | PartialPointerCopy(_) = self {
+            builder.help(const_eval_ptr_as_bytes_1);
+            builder.help(const_eval_ptr_as_bytes_2);
+        }
+        match self {
+            Unsupported(_) | ReadPointerAsBytes => {}
+            PartialPointerOverwrite(ptr) | PartialPointerCopy(ptr) => {
+                builder.set_arg("ptr", ptr);
+            }
+            ThreadLocalStatic(did) | ReadExternStatic(did) => {
+                builder.set_arg("did", format!("{did:?}"));
+            }
+        }
+    }
+}
+
+impl<'tcx> ReportErrorExt for InterpError<'tcx> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        match self {
+            InterpError::UndefinedBehavior(ub) => ub.diagnostic_message(),
+            InterpError::Unsupported(e) => e.diagnostic_message(),
+            InterpError::InvalidProgram(e) => e.diagnostic_message(),
+            InterpError::ResourceExhaustion(e) => e.diagnostic_message(),
+            InterpError::MachineStop(e) => e.diagnostic_message(),
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    ) {
+        match self {
+            InterpError::UndefinedBehavior(ub) => ub.add_args(handler, builder),
+            InterpError::Unsupported(e) => e.add_args(handler, builder),
+            InterpError::InvalidProgram(e) => e.add_args(handler, builder),
+            InterpError::ResourceExhaustion(e) => e.add_args(handler, builder),
+            InterpError::MachineStop(e) => e.add_args(&mut |name, value| {
+                builder.set_arg(name, value);
+            }),
+        }
+    }
+}
+
+impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        match self {
+            InvalidProgramInfo::TooGeneric => const_eval_too_generic,
+            InvalidProgramInfo::AlreadyReported(_) => const_eval_already_reported,
+            InvalidProgramInfo::Layout(e) => e.diagnostic_message(),
+            InvalidProgramInfo::FnAbiAdjustForForeignAbi(_) => {
+                rustc_middle::error::middle_adjust_for_foreign_abi_error
+            }
+            InvalidProgramInfo::SizeOfUnsizedType(_) => const_eval_size_of_unsized,
+            InvalidProgramInfo::UninitUnsizedLocal => const_eval_uninit_unsized_local,
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(
+        self,
+        handler: &Handler,
+        builder: &mut DiagnosticBuilder<'_, G>,
+    ) {
+        match self {
+            InvalidProgramInfo::TooGeneric
+            | InvalidProgramInfo::AlreadyReported(_)
+            | InvalidProgramInfo::UninitUnsizedLocal => {}
+            InvalidProgramInfo::Layout(e) => {
+                let diag: DiagnosticBuilder<'_, ()> = e.into_diagnostic().into_diagnostic(handler);
+                for (name, val) in diag.args() {
+                    builder.set_arg(name.clone(), val.clone());
+                }
+                diag.cancel();
+            }
+            InvalidProgramInfo::FnAbiAdjustForForeignAbi(
+                AdjustForForeignAbiError::Unsupported { arch, abi },
+            ) => {
+                builder.set_arg("arch", arch);
+                builder.set_arg("abi", abi.name());
+            }
+            InvalidProgramInfo::SizeOfUnsizedType(ty) => {
+                builder.set_arg("ty", ty);
+            }
+        }
+    }
+}
+
+impl ReportErrorExt for ResourceExhaustionInfo {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        match self {
+            ResourceExhaustionInfo::StackFrameLimitReached => const_eval_stack_frame_limit_reached,
+            ResourceExhaustionInfo::MemoryExhausted => const_eval_memory_exhausted,
+            ResourceExhaustionInfo::AddressSpaceFull => const_eval_address_space_full,
+        }
+    }
+    fn add_args<G: EmissionGuarantee>(self, _: &Handler, _: &mut DiagnosticBuilder<'_, G>) {}
+}
diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs
index 163e3f86993..b4db3dff3ff 100644
--- a/compiler/rustc_const_eval/src/interpret/cast.rs
+++ b/compiler/rustc_const_eval/src/interpret/cast.rs
@@ -14,6 +14,8 @@ use super::{
     util::ensure_monomorphic_enough, FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy,
 };
 
+use crate::fluent_generated as fluent;
+
 impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     pub fn cast(
         &mut self,
@@ -138,12 +140,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 assert!(src.layout.is_sized());
                 assert!(dest.layout.is_sized());
                 if src.layout.size != dest.layout.size {
-                    throw_ub_format!(
-                        "transmuting from {}-byte type to {}-byte type: `{}` -> `{}`",
-                        src.layout.size.bytes(),
-                        dest.layout.size.bytes(),
-                        src.layout.ty,
-                        dest.layout.ty,
+                    let src_bytes = src.layout.size.bytes();
+                    let dest_bytes = dest.layout.size.bytes();
+                    let src_ty = format!("{}", src.layout.ty);
+                    let dest_ty = format!("{}", dest.layout.ty);
+                    throw_ub_custom!(
+                        fluent::const_eval_invalid_transmute,
+                        src_bytes = src_bytes,
+                        dest_bytes = dest_bytes,
+                        src = src_ty,
+                        dest = dest_ty,
                     );
                 }
 
@@ -363,7 +369,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let old_vptr = old_vptr.to_pointer(self)?;
                 let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
                 if old_trait != data_a.principal() {
-                    throw_ub_format!("upcast on a pointer whose vtable does not match its type");
+                    throw_ub_custom!(fluent::const_eval_upcast_mismatch);
                 }
                 let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
                 self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs
index 91ccdef7215..36606ff69a6 100644
--- a/compiler/rustc_const_eval/src/interpret/eval_context.rs
+++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs
@@ -1,13 +1,13 @@
 use std::cell::Cell;
-use std::fmt;
-use std::mem;
+use std::{fmt, mem};
 
 use either::{Either, Left, Right};
 
+use hir::CRATE_HIR_ID;
 use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData};
 use rustc_index::IndexVec;
 use rustc_middle::mir;
-use rustc_middle::mir::interpret::{ErrorHandled, InterpError, ReportedErrorInfo};
+use rustc_middle::mir::interpret::{ErrorHandled, InterpError, InvalidMetaKind, ReportedErrorInfo};
 use rustc_middle::query::TyCtxtAt;
 use rustc_middle::ty::layout::{
     self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, LayoutOfHelpers,
@@ -24,6 +24,8 @@ use super::{
     MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, PointerArithmetic, Provenance,
     Scalar, StackPopJump,
 };
+use crate::errors::{self, ErroneousConstUsed};
+use crate::fluent_generated as fluent;
 use crate::util;
 
 pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
@@ -246,6 +248,7 @@ impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> {
     }
 }
 
+// FIXME: only used by miri, should be removed once translatable.
 impl<'tcx> fmt::Display for FrameInfo<'tcx> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         ty::tls::with(|tcx| {
@@ -263,6 +266,21 @@ impl<'tcx> fmt::Display for FrameInfo<'tcx> {
     }
 }
 
+impl<'tcx> FrameInfo<'tcx> {
+    pub fn as_note(&self, tcx: TyCtxt<'tcx>) -> errors::FrameNote {
+        let span = self.span;
+        if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
+            errors::FrameNote { where_: "closure", span, instance: String::new(), times: 0 }
+        } else {
+            let instance = format!("{}", self.instance);
+            // Note: this triggers a `good_path_bug` state, which means that if we ever get here
+            // we must emit a diagnostic. We should never display a `FrameInfo` unless we
+            // actually want to emit a warning or error to the user.
+            errors::FrameNote { where_: "instance", span, instance, times: 0 }
+        }
+    }
+}
+
 impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for InterpCx<'mir, 'tcx, M> {
     #[inline]
     fn data_layout(&self) -> &TargetDataLayout {
@@ -406,6 +424,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     }
 
     #[inline(always)]
+    /// Find the first stack frame that is within the current crate, if any, otherwise return the crate's HirId
+    pub fn best_lint_scope(&self) -> hir::HirId {
+        self.stack()
+            .iter()
+            .find_map(|frame| frame.body.source.def_id().as_local())
+            .map_or(CRATE_HIR_ID, |def_id| self.tcx.hir().local_def_id_to_hir_id(def_id))
+    }
+
+    #[inline(always)]
     pub(crate) fn stack(&self) -> &[Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>] {
         M::stack(self)
     }
@@ -610,7 +637,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
                 // Check if this brought us over the size limit.
                 if size > self.max_size_of_val() {
-                    throw_ub!(InvalidMeta("total size is bigger than largest supported object"));
+                    throw_ub!(InvalidMeta(InvalidMetaKind::TooBig));
                 }
                 Ok(Some((size, align)))
             }
@@ -628,7 +655,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let size = elem.size.bytes().saturating_mul(len); // we rely on `max_size_of_val` being smaller than `u64::MAX`.
                 let size = Size::from_bytes(size);
                 if size > self.max_size_of_val() {
-                    throw_ub!(InvalidMeta("slice is bigger than largest supported object"));
+                    throw_ub!(InvalidMeta(InvalidMetaKind::SliceTooBig));
                 }
                 Ok(Some((size, elem.align.abi)))
             }
@@ -736,7 +763,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             mir::UnwindAction::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }),
             mir::UnwindAction::Continue => Right(self.frame_mut().body.span),
             mir::UnwindAction::Unreachable => {
-                throw_ub_format!("unwinding past a stack frame that does not allow unwinding")
+                throw_ub_custom!(fluent::const_eval_unreachable_unwind);
             }
             mir::UnwindAction::Terminate => {
                 self.frame_mut().loc = Right(self.frame_mut().body.span);
@@ -775,7 +802,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             }
         );
         if unwinding && self.frame_idx() == 0 {
-            throw_ub_format!("unwinding past the topmost frame of the stack");
+            throw_ub_custom!(fluent::const_eval_unwind_past_top);
         }
 
         // Copy return value. Must of course happen *before* we deallocate the locals.
@@ -863,7 +890,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // StorageLive expects the local to be dead, and marks it live.
         let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
         if !matches!(old, LocalValue::Dead) {
-            throw_ub_format!("StorageLive on a local that was already live");
+            throw_ub_custom!(fluent::const_eval_double_storage_live);
         }
         Ok(())
     }
@@ -906,7 +933,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 ErrorHandled::Reported(err) => {
                     if !err.is_tainted_by_errors() && let Some(span) = span {
                         // To make it easier to figure out where this error comes from, also add a note at the current location.
-                        self.tcx.sess.span_note_without_error(span, "erroneous constant used");
+                        self.tcx.sess.emit_note(ErroneousConstUsed { span });
                     }
                     err_inval!(AlreadyReported(err))
                 }
diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs
index c2b82ba9b07..7b11ad33091 100644
--- a/compiler/rustc_const_eval/src/interpret/intern.rs
+++ b/compiler/rustc_const_eval/src/interpret/intern.rs
@@ -28,6 +28,7 @@ use super::{
     ValueVisitor,
 };
 use crate::const_eval;
+use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer};
 
 pub trait CompileTimeMachine<'mir, 'tcx, T> = Machine<
         'mir,
@@ -320,10 +321,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
     }
 }
 
+/// How a constant value should be interned.
 #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
 pub enum InternKind {
     /// The `mutability` of the static, ignoring the type which may have interior mutability.
     Static(hir::Mutability),
+    /// A `const` item
     Constant,
     Promoted,
 }
@@ -388,8 +391,7 @@ pub fn intern_const_alloc_recursive<
                 ecx.tcx.sess.delay_span_bug(
                     ecx.tcx.span,
                     format!(
-                        "error during interning should later cause validation failure: {}",
-                        error
+                        "error during interning should later cause validation failure: {error:?}"
                     ),
                 );
             }
@@ -425,14 +427,16 @@ pub fn intern_const_alloc_recursive<
                     // immutability is so important.
                     alloc.mutability = Mutability::Not;
                 }
+                // If it's a constant, we should not have any "leftovers" as everything
+                // is tracked by const-checking.
+                // FIXME: downgrade this to a warning? It rejects some legitimate consts,
+                // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
+                //
+                // NOTE: it looks likes this code path is only reachable when we try to intern
+                // something that cannot be promoted, which in constants means values that have
+                // drop glue, such as the example above.
                 InternKind::Constant => {
-                    // If it's a constant, we should not have any "leftovers" as everything
-                    // is tracked by const-checking.
-                    // FIXME: downgrade this to a warning? It rejects some legitimate consts,
-                    // such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
-                    ecx.tcx
-                        .sess
-                        .span_err(ecx.tcx.span, "untyped pointers are not allowed in constant");
+                    ecx.tcx.sess.emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span });
                     // For better errors later, mark the allocation as immutable.
                     alloc.mutability = Mutability::Not;
                 }
@@ -447,10 +451,7 @@ pub fn intern_const_alloc_recursive<
         } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
             // Codegen does not like dangling pointers, and generally `tcx` assumes that
             // all allocations referenced anywhere actually exist. So, make sure we error here.
-            let reported = ecx
-                .tcx
-                .sess
-                .span_err(ecx.tcx.span, "encountered dangling pointer in final constant");
+            let reported = ecx.tcx.sess.emit_err(DanglingPtrInFinal { span: ecx.tcx.span });
             return Err(reported);
         } else if ecx.tcx.try_get_global_alloc(alloc_id).is_none() {
             // We have hit an `AllocId` that is neither in local or global memory and isn't
diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
index a4b58c9849b..fffb9a7f264 100644
--- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs
+++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
@@ -22,6 +22,8 @@ use super::{
     Pointer,
 };
 
+use crate::fluent_generated as fluent;
+
 mod caller_location;
 
 fn numeric_intrinsic<Prov>(name: Symbol, bits: u128, kind: Primitive) -> Scalar<Prov> {
@@ -198,15 +200,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         ty
                     ),
                 };
-                let (nonzero, intrinsic_name) = match intrinsic_name {
+                let (nonzero, actual_intrinsic_name) = match intrinsic_name {
                     sym::cttz_nonzero => (true, sym::cttz),
                     sym::ctlz_nonzero => (true, sym::ctlz),
                     other => (false, other),
                 };
                 if nonzero && bits == 0 {
-                    throw_ub_format!("`{}_nonzero` called on 0", intrinsic_name);
+                    throw_ub_custom!(
+                        fluent::const_eval_call_nonzero_intrinsic,
+                        name = intrinsic_name,
+                    );
                 }
-                let out_val = numeric_intrinsic(intrinsic_name, bits, kind);
+                let out_val = numeric_intrinsic(actual_intrinsic_name, bits, kind);
                 self.write_scalar(out_val, dest)?;
             }
             sym::saturating_add | sym::saturating_sub => {
@@ -249,9 +254,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let layout = self.layout_of(substs.type_at(0))?;
                     let r_val = r.to_scalar().to_bits(layout.size)?;
                     if let sym::unchecked_shl | sym::unchecked_shr = intrinsic_name {
-                        throw_ub_format!("overflowing shift by {} in `{}`", r_val, intrinsic_name);
+                        throw_ub_custom!(
+                            fluent::const_eval_overflow_shift,
+                            val = r_val,
+                            name = intrinsic_name
+                        );
                     } else {
-                        throw_ub_format!("overflow executing `{}`", intrinsic_name);
+                        throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
                     }
                 }
                 self.write_scalar(val, dest)?;
@@ -310,17 +319,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         (Err(_), _) | (_, Err(_)) => {
                             // We managed to find a valid allocation for one pointer, but not the other.
                             // That means they are definitely not pointing to the same allocation.
-                            throw_ub_format!(
-                                "`{}` called on pointers into different allocations",
-                                intrinsic_name
+                            throw_ub_custom!(
+                                fluent::const_eval_different_allocations,
+                                name = intrinsic_name,
                             );
                         }
                         (Ok((a_alloc_id, a_offset, _)), Ok((b_alloc_id, b_offset, _))) => {
                             // Found allocation for both. They must be into the same allocation.
                             if a_alloc_id != b_alloc_id {
-                                throw_ub_format!(
-                                    "`{}` called on pointers into different allocations",
-                                    intrinsic_name
+                                throw_ub_custom!(
+                                    fluent::const_eval_different_allocations,
+                                    name = intrinsic_name,
                                 );
                             }
                             // Use these offsets for distance calculation.
@@ -340,11 +349,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     if overflowed {
                         // a < b
                         if intrinsic_name == sym::ptr_offset_from_unsigned {
-                            throw_ub_format!(
-                                "`{}` called when first pointer has smaller offset than second: {} < {}",
-                                intrinsic_name,
-                                a_offset,
-                                b_offset,
+                            throw_ub_custom!(
+                                fluent::const_eval_unsigned_offset_from_overflow,
+                                a_offset = a_offset,
+                                b_offset = b_offset,
                             );
                         }
                         // The signed form of the intrinsic allows this. If we interpret the
@@ -352,9 +360,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         // seems *positive*, they were more than isize::MAX apart.
                         let dist = val.to_target_isize(self)?;
                         if dist >= 0 {
-                            throw_ub_format!(
-                                "`{}` called when first pointer is too far before second",
-                                intrinsic_name
+                            throw_ub_custom!(
+                                fluent::const_eval_offset_from_underflow,
+                                name = intrinsic_name,
                             );
                         }
                         dist
@@ -364,9 +372,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         // If converting to isize produced a *negative* result, we had an overflow
                         // because they were more than isize::MAX apart.
                         if dist < 0 {
-                            throw_ub_format!(
-                                "`{}` called when first pointer is too far ahead of second",
-                                intrinsic_name
+                            throw_ub_custom!(
+                                fluent::const_eval_offset_from_overflow,
+                                name = intrinsic_name,
                             );
                         }
                         dist
@@ -509,7 +517,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let op = self.eval_operand(op, None)?;
                 let cond = self.read_scalar(&op)?.to_bool()?;
                 if !cond {
-                    throw_ub_format!("`assume` called with `false`");
+                    throw_ub_custom!(fluent::const_eval_assume_false);
                 }
                 Ok(())
             }
@@ -538,7 +546,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         let (res, overflow, _ty) = self.overflowing_binary_op(BinOp::Rem, &a, &b)?;
         assert!(!overflow); // All overflow is UB, so this should never return on overflow.
         if res.assert_bits(a.layout.size) != 0 {
-            throw_ub_format!("exact_div: {} cannot be divided by {} without remainder", a, b)
+            throw_ub_custom!(
+                fluent::const_eval_exact_div_has_remainder,
+                a = format!("{a}"),
+                b = format!("{b}")
+            )
         }
         // `Rem` says this is all right, so we can let `Div` do its job.
         self.binop_ignore_overflow(BinOp::Div, &a, &b, dest)
@@ -634,9 +646,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
         // but no actual allocation can be big enough for the difference to be noticeable.
         let size = size.checked_mul(count, self).ok_or_else(|| {
-            err_ub_format!(
-                "overflow computing total size of `{}`",
-                if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
+            err_ub_custom!(
+                fluent::const_eval_size_overflow,
+                name = if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
             )
         })?;
 
@@ -660,10 +672,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
         // `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
         // but no actual allocation can be big enough for the difference to be noticeable.
-        let len = layout
-            .size
-            .checked_mul(count, self)
-            .ok_or_else(|| err_ub_format!("overflow computing total size of `write_bytes`"))?;
+        let len = layout.size.checked_mul(count, self).ok_or_else(|| {
+            err_ub_custom!(fluent::const_eval_size_overflow, name = "write_bytes")
+        })?;
 
         let bytes = std::iter::repeat(byte).take(len.bytes_usize());
         self.write_bytes_ptr(dst, bytes)
@@ -687,7 +698,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 return Ok(&[]);
             };
             if alloc_ref.has_provenance() {
-                throw_ub_format!("`raw_eq` on bytes with provenance");
+                throw_ub_custom!(fluent::const_eval_raw_eq_with_provenance);
             }
             alloc_ref.get_bytes_strip_provenance()
         };
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index d5b6a581a79..1125d8d1f0e 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -19,6 +19,7 @@ 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::{
     alloc_range, AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg,
@@ -200,7 +201,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         align: Align,
         kind: MemoryKind<M::MemoryKind>,
     ) -> InterpResult<'tcx, Pointer<M::Provenance>> {
-        let alloc = Allocation::uninit(size, align, M::PANIC_ON_ALLOC_FAIL)?;
+        let alloc = if M::PANIC_ON_ALLOC_FAIL {
+            Allocation::uninit(size, align)
+        } else {
+            Allocation::try_uninit(size, align)?
+        };
         self.allocate_raw_ptr(alloc, kind)
     }
 
@@ -242,9 +247,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     ) -> InterpResult<'tcx, Pointer<M::Provenance>> {
         let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr)?;
         if offset.bytes() != 0 {
-            throw_ub_format!(
-                "reallocating {:?} which does not point to the beginning of an object",
-                ptr
+            throw_ub_custom!(
+                fluent::const_eval_realloc_or_alloc_with_offset,
+                ptr = format!("{ptr:?}"),
+                kind = "realloc"
             );
         }
 
@@ -280,9 +286,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         trace!("deallocating: {alloc_id:?}");
 
         if offset.bytes() != 0 {
-            throw_ub_format!(
-                "deallocating {:?} which does not point to the beginning of an object",
-                ptr
+            throw_ub_custom!(
+                fluent::const_eval_realloc_or_alloc_with_offset,
+                ptr = format!("{ptr:?}"),
+                kind = "dealloc",
             );
         }
 
@@ -290,13 +297,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             // Deallocating global memory -- always an error
             return Err(match self.tcx.try_get_global_alloc(alloc_id) {
                 Some(GlobalAlloc::Function(..)) => {
-                    err_ub_format!("deallocating {alloc_id:?}, which is a function")
+                    err_ub_custom!(
+                        fluent::const_eval_invalid_dealloc,
+                        alloc_id = alloc_id,
+                        kind = "fn",
+                    )
                 }
                 Some(GlobalAlloc::VTable(..)) => {
-                    err_ub_format!("deallocating {alloc_id:?}, which is a vtable")
+                    err_ub_custom!(
+                        fluent::const_eval_invalid_dealloc,
+                        alloc_id = alloc_id,
+                        kind = "vtable",
+                    )
                 }
                 Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
-                    err_ub_format!("deallocating {alloc_id:?}, which is static memory")
+                    err_ub_custom!(
+                        fluent::const_eval_invalid_dealloc,
+                        alloc_id = alloc_id,
+                        kind = "static_mem"
+                    )
                 }
                 None => err_ub!(PointerUseAfterFree(alloc_id)),
             }
@@ -304,21 +323,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         };
 
         if alloc.mutability.is_not() {
-            throw_ub_format!("deallocating immutable allocation {alloc_id:?}");
+            throw_ub_custom!(fluent::const_eval_dealloc_immutable, alloc = alloc_id,);
         }
         if alloc_kind != kind {
-            throw_ub_format!(
-                "deallocating {alloc_id:?}, which is {alloc_kind} memory, using {kind} deallocation operation"
+            throw_ub_custom!(
+                fluent::const_eval_dealloc_kind_mismatch,
+                alloc = alloc_id,
+                alloc_kind = format!("{alloc_kind}"),
+                kind = format!("{kind}"),
             );
         }
         if let Some((size, align)) = old_size_and_align {
             if size != alloc.size() || align != alloc.align {
-                throw_ub_format!(
-                    "incorrect layout on deallocation: {alloc_id:?} has size {} and alignment {}, but gave size {} and alignment {}",
-                    alloc.size().bytes(),
-                    alloc.align.bytes(),
-                    size.bytes(),
-                    align.bytes(),
+                throw_ub_custom!(
+                    fluent::const_eval_dealloc_incorrect_layout,
+                    alloc = alloc_id,
+                    size = alloc.size().bytes(),
+                    align = alloc.align.bytes(),
+                    size_found = size.bytes(),
+                    align_found = align.bytes(),
                 )
             }
         }
@@ -1166,7 +1189,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     if (src_offset <= dest_offset && src_offset + size > dest_offset)
                         || (dest_offset <= src_offset && dest_offset + size > src_offset)
                     {
-                        throw_ub_format!("copy_nonoverlapping called on overlapping ranges")
+                        throw_ub_custom!(fluent::const_eval_copy_nonoverlapping_overlapping);
                     }
                 }
 
diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs
index 586e8f063ee..7269ff8d53c 100644
--- a/compiler/rustc_const_eval/src/interpret/terminator.rs
+++ b/compiler/rustc_const_eval/src/interpret/terminator.rs
@@ -15,6 +15,7 @@ use super::{
     FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand,
     PlaceTy, Scalar, StackPopCleanup,
 };
+use crate::fluent_generated as fluent;
 
 impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
     pub(super) fn eval_terminator(
@@ -172,7 +173,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             InlineAsm { template, ref operands, options, destination, .. } => {
                 M::eval_inline_asm(self, template, operands, options)?;
                 if options.contains(InlineAsmOptions::NORETURN) {
-                    throw_ub_format!("returned from noreturn inline assembly");
+                    throw_ub_custom!(fluent::const_eval_noreturn_asm_returned);
                 }
                 self.go_to_block(
                     destination
@@ -288,15 +289,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             return Ok(());
         }
         // Find next caller arg.
-        let (caller_arg, caller_abi) = caller_args.next().ok_or_else(|| {
-            err_ub_format!("calling a function with fewer arguments than it requires")
-        })?;
+        let Some((caller_arg, caller_abi)) = caller_args.next() else {
+            throw_ub_custom!(fluent::const_eval_not_enough_caller_args);
+        };
         // Now, check
         if !Self::check_argument_compat(caller_abi, callee_abi) {
-            throw_ub_format!(
-                "calling a function with argument of type {:?} passing data of type {:?}",
-                callee_arg.layout.ty,
-                caller_arg.layout.ty
+            let callee_ty = format!("{}", callee_arg.layout.ty);
+            let caller_ty = format!("{}", caller_arg.layout.ty);
+            throw_ub_custom!(
+                fluent::const_eval_incompatible_types,
+                callee_ty = callee_ty,
+                caller_ty = caller_ty,
             )
         }
         // Special handling for unsized parameters.
@@ -398,10 +401,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
                 if M::enforce_abi(self) {
                     if caller_fn_abi.conv != callee_fn_abi.conv {
-                        throw_ub_format!(
-                            "calling a function with calling convention {:?} using calling convention {:?}",
-                            callee_fn_abi.conv,
-                            caller_fn_abi.conv
+                        throw_ub_custom!(
+                            fluent::const_eval_incompatible_calling_conventions,
+                            callee_conv = format!("{:?}", callee_fn_abi.conv),
+                            caller_conv = format!("{:?}", caller_fn_abi.conv),
                         )
                     }
                 }
@@ -508,15 +511,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                         "mismatch between callee ABI and callee body arguments"
                     );
                     if caller_args.next().is_some() {
-                        throw_ub_format!("calling a function with more arguments than it expected")
+                        throw_ub_custom!(fluent::const_eval_too_many_caller_args);
                     }
                     // Don't forget to check the return type!
                     if !Self::check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret) {
-                        throw_ub_format!(
-                            "calling a function with return type {:?} passing \
-                                    return place of type {:?}",
-                            callee_fn_abi.ret.layout.ty,
-                            caller_fn_abi.ret.layout.ty,
+                        let callee_ty = format!("{}", callee_fn_abi.ret.layout.ty);
+                        let caller_ty = format!("{}", caller_fn_abi.ret.layout.ty);
+                        throw_ub_custom!(
+                            fluent::const_eval_incompatible_return_types,
+                            callee_ty = callee_ty,
+                            caller_ty = caller_ty,
                         )
                     }
                 };
@@ -587,9 +591,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let (recv, vptr) = self.unpack_dyn_star(&receiver_place.into())?;
                     let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
                     if dyn_trait != data.principal() {
-                        throw_ub_format!(
-                            "`dyn*` call on a pointer whose vtable does not match its type"
-                        );
+                        throw_ub_custom!(fluent::const_eval_dyn_star_call_vtable_mismatch);
                     }
                     let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one
 
@@ -609,9 +611,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
                     let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
                     if dyn_trait != data.principal() {
-                        throw_ub_format!(
-                            "`dyn` call on a pointer whose vtable does not match its type"
-                        );
+                        throw_ub_custom!(fluent::const_eval_dyn_call_vtable_mismatch);
                     }
 
                     // It might be surprising that we use a pointer as the receiver even if this
@@ -623,7 +623,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 // Now determine the actual method to call. We can do that in two different ways and
                 // compare them to ensure everything fits.
                 let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vptr)?.get(idx).copied() else {
-                    throw_ub_format!("`dyn` call trying to call something that is not a method")
+                    // FIXME(fee1-dead) these could be variants of the UB info enum instead of this
+                    throw_ub_custom!(fluent::const_eval_dyn_call_not_a_method);
                 };
                 trace!("Virtual call dispatches to {fn_inst:#?}");
                 if cfg!(debug_assertions) {
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 01b77289937..21c655988a0 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -4,7 +4,7 @@
 //! That's useful because it means other passes (e.g. promotion) can rely on `const`s
 //! to be const-safe.
 
-use std::fmt::{Display, Write};
+use std::fmt::Write;
 use std::num::NonZeroUsize;
 
 use either::{Left, Right};
@@ -12,7 +12,10 @@ use either::{Left, Right};
 use rustc_ast::Mutability;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir as hir;
-use rustc_middle::mir::interpret::InterpError;
+use rustc_middle::mir::interpret::{
+    ExpectedKind, InterpError, InvalidMetaKind, PointerKind, ValidationErrorInfo,
+    ValidationErrorKind, ValidationErrorKind::*,
+};
 use rustc_middle::ty;
 use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
 use rustc_span::symbol::{sym, Symbol};
@@ -30,14 +33,7 @@ use super::{
 };
 
 macro_rules! throw_validation_failure {
-    ($where:expr, { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )?) => {{
-        let mut msg = String::new();
-        msg.push_str("encountered ");
-        write!(&mut msg, $($what_fmt)*).unwrap();
-        $(
-            msg.push_str(", but expected ");
-            write!(&mut msg, $($expected_fmt)*).unwrap();
-        )?
+    ($where:expr, $kind: expr) => {{
         let where_ = &$where;
         let path = if !where_.is_empty() {
             let mut path = String::new();
@@ -46,7 +42,8 @@ macro_rules! throw_validation_failure {
         } else {
             None
         };
-        throw_ub!(ValidationFailure { path, msg })
+
+        throw_ub!(Validation(ValidationErrorInfo { path, kind: $kind }))
     }};
 }
 
@@ -82,22 +79,22 @@ macro_rules! throw_validation_failure {
 ///
 macro_rules! try_validation {
     ($e:expr, $where:expr,
-    $( $( $p:pat_param )|+ => { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )? ),+ $(,)?
+    $( $( $p:pat_param )|+ => $kind: expr ),+ $(,)?
     ) => {{
         match $e {
             Ok(x) => x,
             // We catch the error and turn it into a validation failure. We are okay with
             // allocation here as this can only slow down builds that fail anyway.
-            Err(e) => match e.kind() {
+            Err(e) => match e.into_parts() {
                 $(
-                    InterpError::UndefinedBehavior($($p)|+) =>
+                    (InterpError::UndefinedBehavior($($p)|+), _) =>
                        throw_validation_failure!(
                             $where,
-                            { $( $what_fmt )* } $( expected { $( $expected_fmt )* } )?
+                            $kind
                         )
                 ),+,
                 #[allow(unreachable_patterns)]
-                _ => Err::<!, _>(e)?,
+                (e, rest) => Err::<!, _>($crate::interpret::InterpErrorInfo::from_parts(e, rest))?,
             }
         }
     }};
@@ -160,6 +157,7 @@ impl<T: Copy + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH>
     }
 }
 
+// FIXME make this translatable as well?
 /// Format a path
 fn write_path(out: &mut String, path: &[PathElem]) {
     use self::PathElem::*;
@@ -185,26 +183,6 @@ fn write_path(out: &mut String, path: &[PathElem]) {
     }
 }
 
-// Formats such that a sentence like "expected something {}" to mean
-// "expected something <in the given range>" makes sense.
-fn wrapping_range_format(r: WrappingRange, max_hi: u128) -> String {
-    let WrappingRange { start: lo, end: hi } = r;
-    assert!(hi <= max_hi);
-    if lo > hi {
-        format!("less or equal to {}, or greater or equal to {}", hi, lo)
-    } else if lo == hi {
-        format!("equal to {}", lo)
-    } else if lo == 0 {
-        assert!(hi < max_hi, "should not be printing if the range covers everything");
-        format!("less or equal to {}", hi)
-    } else if hi == max_hi {
-        assert!(lo > 0, "should not be printing if the range covers everything");
-        format!("greater or equal to {}", lo)
-    } else {
-        format!("in the range {:?}", r)
-    }
-}
-
 struct ValidityVisitor<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
     /// The `path` may be pushed to, but the part that is present when a function
     /// starts must not be changed!  `visit_fields` and `visit_array` rely on
@@ -311,19 +289,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
     fn read_immediate(
         &self,
         op: &OpTy<'tcx, M::Provenance>,
-        expected: impl Display,
+        expected: ExpectedKind,
     ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
         Ok(try_validation!(
             self.ecx.read_immediate(op),
             self.path,
-            InvalidUninitBytes(None) => { "uninitialized memory" } expected { "{expected}" }
+            InvalidUninitBytes(None) => Uninit { expected }
         ))
     }
 
     fn read_scalar(
         &self,
         op: &OpTy<'tcx, M::Provenance>,
-        expected: impl Display,
+        expected: ExpectedKind,
     ) -> InterpResult<'tcx, Scalar<M::Provenance>> {
         Ok(self.read_immediate(op, expected)?.to_scalar())
     }
@@ -342,8 +320,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                     self.ecx.get_ptr_vtable(vtable),
                     self.path,
                     DanglingIntPointer(..) |
-                    InvalidVTablePointer(..) =>
-                        { "{vtable}" } expected { "a vtable pointer" },
+                    InvalidVTablePointer(..) => InvalidVTablePtr { value: format!("{vtable}") }
                 );
                 // FIXME: check if the type/trait match what ty::Dynamic says?
             }
@@ -366,10 +343,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
     fn check_safe_pointer(
         &mut self,
         value: &OpTy<'tcx, M::Provenance>,
-        kind: &str,
+        ptr_kind: PointerKind,
     ) -> InterpResult<'tcx> {
-        let place =
-            self.ecx.ref_to_mplace(&self.read_immediate(value, format_args!("a {kind}"))?)?;
+        let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?;
         // Handle wide pointers.
         // Check metadata early, for better diagnostics
         if place.layout.is_unsized() {
@@ -379,7 +355,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
         let size_and_align = try_validation!(
             self.ecx.size_and_align_of_mplace(&place),
             self.path,
-            InvalidMeta(msg) => { "invalid {} metadata: {}", kind, msg },
+            InvalidMeta(msg) => match msg {
+                InvalidMetaKind::SliceTooBig => InvalidMetaSliceTooLarge { ptr_kind },
+                InvalidMetaKind::TooBig => InvalidMetaTooLarge { ptr_kind },
+            }
         );
         let (size, align) = size_and_align
             // for the purpose of validity, consider foreign types to have
@@ -395,31 +374,30 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
             ),
             self.path,
-            AlignmentCheckFailed { required, has } =>
-                {
-                    "an unaligned {kind} (required {} byte alignment but found {})",
-                    required.bytes(),
-                    has.bytes(),
-                },
-            DanglingIntPointer(0, _) =>
-                { "a null {kind}" },
-            DanglingIntPointer(i, _) =>
-                {
-                    "a dangling {kind} ({pointer} has no provenance)",
-                    pointer = Pointer::<Option<AllocId>>::from_addr_invalid(*i),
-                },
-            PointerOutOfBounds { .. } =>
-                { "a dangling {kind} (going beyond the bounds of its allocation)" },
+            AlignmentCheckFailed { required, has } => UnalignedPtr {
+                ptr_kind,
+                required_bytes: required.bytes(),
+                found_bytes: has.bytes()
+            },
+            DanglingIntPointer(0, _) => NullPtr { ptr_kind },
+            DanglingIntPointer(i, _) => DanglingPtrNoProvenance {
+                ptr_kind,
+                // FIXME this says "null pointer" when null but we need translate
+                pointer: format!("{}", Pointer::<Option<AllocId>>::from_addr_invalid(i))
+            },
+            PointerOutOfBounds { .. } => DanglingPtrOutOfBounds {
+                ptr_kind
+            },
             // This cannot happen during const-eval (because interning already detects
             // dangling pointers), but it can happen in Miri.
-            PointerUseAfterFree(..) =>
-                { "a dangling {kind} (use-after-free)" },
+            PointerUseAfterFree(..) => DanglingPtrUseAfterFree {
+                ptr_kind,
+            },
         );
         // Do not allow pointers to uninhabited types.
         if place.layout.abi.is_uninhabited() {
-            throw_validation_failure!(self.path,
-                { "a {kind} pointing to uninhabited type {}", place.layout.ty }
-            )
+            let ty = place.layout.ty;
+            throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
         }
         // Recursive checking
         if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
@@ -441,9 +419,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                             // this check is so important.
                             // This check is reachable when the const just referenced the static,
                             // but never read it (so we never entered `before_access_global`).
-                            throw_validation_failure!(self.path,
-                                { "a {} pointing to a static variable in a constant", kind }
-                            );
+                            throw_validation_failure!(self.path, PtrToStatic { ptr_kind });
                         }
                         // We skip recursively checking other statics. These statics must be sound by
                         // themselves, and the only way to get broken statics here is by using
@@ -464,9 +440,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                             // This should be unreachable, but if someone manages to copy a pointer
                             // out of a `static`, then that pointer might point to mutable memory,
                             // and we would catch that here.
-                            throw_validation_failure!(self.path,
-                                { "a {} pointing to mutable memory in a constant", kind }
-                            );
+                            throw_validation_failure!(self.path, PtrToMut { ptr_kind });
                         }
                     }
                     // Nothing to check for these.
@@ -496,22 +470,24 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
         let ty = value.layout.ty;
         match ty.kind() {
             ty::Bool => {
-                let value = self.read_scalar(value, "a boolean")?;
+                let value = self.read_scalar(value, ExpectedKind::Bool)?;
                 try_validation!(
                     value.to_bool(),
                     self.path,
-                    InvalidBool(..) =>
-                        { "{:x}", value } expected { "a boolean" },
+                    InvalidBool(..) => ValidationErrorKind::InvalidBool {
+                        value: format!("{value:x}"),
+                    }
                 );
                 Ok(true)
             }
             ty::Char => {
-                let value = self.read_scalar(value, "a unicode scalar value")?;
+                let value = self.read_scalar(value, ExpectedKind::Char)?;
                 try_validation!(
                     value.to_char(),
                     self.path,
-                    InvalidChar(..) =>
-                        { "{:x}", value } expected { "a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)" },
+                    InvalidChar(..) => ValidationErrorKind::InvalidChar {
+                        value: format!("{value:x}"),
+                    }
                 );
                 Ok(true)
             }
@@ -521,16 +497,17 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 let value = self.read_scalar(
                     value,
                     if matches!(ty.kind(), ty::Float(..)) {
-                        "a floating point number"
+                        ExpectedKind::Float
                     } else {
-                        "an integer"
+                        ExpectedKind::Int
                     },
                 )?;
                 // As a special exception we *do* match on a `Scalar` here, since we truly want
                 // to know its underlying representation (and *not* cast it to an integer).
                 if matches!(value, Scalar::Ptr(..)) {
-                    throw_validation_failure!(self.path,
-                        { "{:x}", value } expected { "plain (non-pointer) bytes" }
+                    throw_validation_failure!(
+                        self.path,
+                        ExpectedNonPtr { value: format!("{value:x}") }
                     )
                 }
                 Ok(true)
@@ -540,7 +517,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 // actually enforce the strict rules for raw pointers (mostly because
                 // that lets us re-use `ref_to_mplace`).
                 let place =
-                    self.ecx.ref_to_mplace(&self.read_immediate(value, "a raw pointer")?)?;
+                    self.ecx.ref_to_mplace(&self.read_immediate(value, ExpectedKind::RawPtr)?)?;
                 if place.layout.is_unsized() {
                     self.check_wide_ptr_meta(place.meta, place.layout)?;
                 }
@@ -554,14 +531,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                     // a ZST).
                     let layout = self.ecx.layout_of(*ty)?;
                     if !layout.is_zst() {
-                        throw_validation_failure!(self.path, { "mutable reference in a `const`" });
+                        throw_validation_failure!(self.path, MutableRefInConst);
                     }
                 }
-                self.check_safe_pointer(value, "reference")?;
+                self.check_safe_pointer(value, PointerKind::Ref)?;
                 Ok(true)
             }
             ty::FnPtr(_sig) => {
-                let value = self.read_scalar(value, "a function pointer")?;
+                let value = self.read_scalar(value, ExpectedKind::FnPtr)?;
 
                 // If we check references recursively, also check that this points to a function.
                 if let Some(_) = self.ref_tracking {
@@ -570,19 +547,20 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                         self.ecx.get_ptr_fn(ptr),
                         self.path,
                         DanglingIntPointer(..) |
-                        InvalidFunctionPointer(..) =>
-                            { "{ptr}" } expected { "a function pointer" },
+                        InvalidFunctionPointer(..) => InvalidFnPtr {
+                            value: format!("{ptr}"),
+                        },
                     );
                     // FIXME: Check if the signature matches
                 } else {
                     // Otherwise (for standalone Miri), we have to still check it to be non-null.
                     if self.ecx.scalar_may_be_null(value)? {
-                        throw_validation_failure!(self.path, { "a null function pointer" });
+                        throw_validation_failure!(self.path, NullFnPtr);
                     }
                 }
                 Ok(true)
             }
-            ty::Never => throw_validation_failure!(self.path, { "a value of the never type `!`" }),
+            ty::Never => throw_validation_failure!(self.path, NeverVal),
             ty::Foreign(..) | ty::FnDef(..) => {
                 // Nothing to check.
                 Ok(true)
@@ -629,12 +607,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 if start == 1 && end == max_value {
                     // Only null is the niche. So make sure the ptr is NOT null.
                     if self.ecx.scalar_may_be_null(scalar)? {
-                        throw_validation_failure!(self.path,
-                            { "a potentially null pointer" }
-                            expected {
-                                "something that cannot possibly fail to be {}",
-                                wrapping_range_format(valid_range, max_value)
-                            }
+                        throw_validation_failure!(
+                            self.path,
+                            NullablePtrOutOfRange { range: valid_range, max_value }
                         )
                     } else {
                         return Ok(());
@@ -645,12 +620,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
                 } else {
                     // Conservatively, we reject, because the pointer *could* have a bad
                     // value.
-                    throw_validation_failure!(self.path,
-                        { "a pointer" }
-                        expected {
-                            "something that cannot possibly fail to be {}",
-                            wrapping_range_format(valid_range, max_value)
-                        }
+                    throw_validation_failure!(
+                        self.path,
+                        PtrOutOfRange { range: valid_range, max_value }
                     )
                 }
             }
@@ -659,9 +631,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
         if valid_range.contains(bits) {
             Ok(())
         } else {
-            throw_validation_failure!(self.path,
-                { "{}", bits }
-                expected { "something {}", wrapping_range_format(valid_range, max_value) }
+            throw_validation_failure!(
+                self.path,
+                OutOfRange { value: format!("{bits}"), range: valid_range, max_value }
             )
         }
     }
@@ -685,10 +657,11 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
             Ok(try_validation!(
                 this.ecx.read_discriminant(op),
                 this.path,
-                InvalidTag(val) =>
-                    { "{:x}", val } expected { "a valid enum tag" },
-                InvalidUninitBytes(None) =>
-                    { "uninitialized bytes" } expected { "a valid enum tag" },
+                InvalidTag(val) => InvalidEnumTag {
+                    value: format!("{val:x}"),
+                },
+
+                InvalidUninitBytes(None) => UninitEnumTag,
             )
             .1)
         })
@@ -730,7 +703,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
         // Special check preventing `UnsafeCell` inside unions in the inner part of constants.
         if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) {
             if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) {
-                throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
+                throw_validation_failure!(self.path, UnsafeCell);
             }
         }
         Ok(())
@@ -738,7 +711,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
 
     #[inline]
     fn visit_box(&mut self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
-        self.check_safe_pointer(op, "box")?;
+        self.check_safe_pointer(op, PointerKind::Box)?;
         Ok(())
     }
 
@@ -756,7 +729,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
             if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. }))
                 && def.is_unsafe_cell()
             {
-                throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
+                throw_validation_failure!(self.path, UnsafeCell);
             }
         }
 
@@ -775,14 +748,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
         // MyNewtype and then the scalar in there).
         match op.layout.abi {
             Abi::Uninhabited => {
-                throw_validation_failure!(self.path,
-                    { "a value of uninhabited type {:?}", op.layout.ty }
-                );
+                let ty = op.layout.ty;
+                throw_validation_failure!(self.path, UninhabitedVal { ty });
             }
             Abi::Scalar(scalar_layout) => {
                 if !scalar_layout.is_uninit_valid() {
                     // There is something to check here.
-                    let scalar = self.read_scalar(op, "initialized scalar value")?;
+                    let scalar = self.read_scalar(op, ExpectedKind::InitScalar)?;
                     self.visit_scalar(scalar, scalar_layout)?;
                 }
             }
@@ -792,7 +764,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                 // the other must be init.
                 if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() {
                     let (a, b) =
-                        self.read_immediate(op, "initialized scalar value")?.to_scalar_pair();
+                        self.read_immediate(op, ExpectedKind::InitScalar)?.to_scalar_pair();
                     self.visit_scalar(a, a_layout)?;
                     self.visit_scalar(b, b_layout)?;
                 }
@@ -822,7 +794,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                 try_validation!(
                     self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len)),
                     self.path,
-                    InvalidUninitBytes(..) => { "uninitialized data in `str`" },
+                    InvalidUninitBytes(..) => { UninitStr },
                 );
             }
             ty::Array(tys, ..) | ty::Slice(tys)
@@ -852,7 +824,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                     Left(mplace) => mplace,
                     Right(imm) => match *imm {
                         Immediate::Uninit =>
-                            throw_validation_failure!(self.path, { "uninitialized bytes" }),
+                            throw_validation_failure!(self.path, UninitVal),
                         Immediate::Scalar(..) | Immediate::ScalarPair(..) =>
                             bug!("arrays/slices can never have Scalar/ScalarPair layout"),
                     }
@@ -888,7 +860,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                                 .unwrap();
                                 self.path.push(PathElem::ArrayElem(i));
 
-                                throw_validation_failure!(self.path, { "uninitialized bytes" })
+                                throw_validation_failure!(self.path, UninitVal)
                             }
 
                             // Propagate upwards (that will also check for unexpected errors).
@@ -929,12 +901,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         match visitor.visit_value(&op) {
             Ok(()) => Ok(()),
             // Pass through validation failures.
-            Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
+            Err(err) if matches!(err.kind(), err_ub!(Validation { .. })) => Err(err),
             // Complain about any other kind of UB error -- those are bad because we'd like to
             // report them in a way that shows *where* in the value the issue lies.
             Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => {
-                err.print_backtrace();
-                bug!("Unexpected Undefined Behavior error during validation: {}", err);
+                let (err, backtrace) = err.into_parts();
+                backtrace.print_backtrace();
+                bug!("Unexpected Undefined Behavior error during validation: {err:?}");
             }
             // Pass through everything else.
             Err(err) => Err(err),
diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs
index 0c48d99915a..8314f53ba57 100644
--- a/compiler/rustc_const_eval/src/lib.rs
+++ b/compiler/rustc_const_eval/src/lib.rs
@@ -4,6 +4,7 @@ Rust MIR: a lowered representation of Rust.
 
 */
 
+#![deny(rustc::untranslatable_diagnostic)]
 #![feature(assert_matches)]
 #![feature(box_patterns)]
 #![feature(decl_macro)]
@@ -33,6 +34,8 @@ pub mod interpret;
 pub mod transform;
 pub mod util;
 
+pub use errors::ReportErrorExt;
+
 use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
 use rustc_fluent_macro::fluent_messages;
 use rustc_middle::query::Providers;
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
index 21f3c2c8917..236e43bdfcc 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs
@@ -2,9 +2,7 @@
 
 use hir::def_id::LocalDefId;
 use hir::{ConstContext, LangItem};
-use rustc_errors::{
-    error_code, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed,
-};
+use rustc_errors::{error_code, DiagnosticBuilder, ErrorGuaranteed};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_infer::infer::TyCtxtInferExt;
@@ -152,7 +150,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
 
                     if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
                         let span = tcx.def_span(data.impl_def_id);
-                        err.span_note(span, "impl defined here, but it is not `const`");
+                        err.subdiagnostic(errors::NonConstImplNote { span });
                     }
                 }
                 _ => {}
@@ -166,26 +164,30 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
         let mut err = match call_kind {
             CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => {
                 macro_rules! error {
-                    ($fmt:literal) => {
-                        struct_span_err!(tcx.sess, span, E0015, $fmt, self_ty, ccx.const_kind())
+                    ($err:ident) => {
+                        tcx.sess.create_err(errors::$err {
+                            span,
+                            ty: self_ty,
+                            kind: ccx.const_kind(),
+                        })
                     };
                 }
 
                 let mut err = match kind {
                     CallDesugaringKind::ForLoopIntoIter => {
-                        error!("cannot convert `{}` into an iterator in {}s")
+                        error!(NonConstForLoopIntoIter)
                     }
                     CallDesugaringKind::QuestionBranch => {
-                        error!("`?` cannot determine the branch of `{}` in {}s")
+                        error!(NonConstQuestionBranch)
                     }
                     CallDesugaringKind::QuestionFromResidual => {
-                        error!("`?` cannot convert from residual of `{}` in {}s")
+                        error!(NonConstQuestionFromResidual)
                     }
                     CallDesugaringKind::TryBlockFromOutput => {
-                        error!("`try` block cannot convert `{}` to the result in {}s")
+                        error!(NonConstTryBlockFromOutput)
                     }
                     CallDesugaringKind::Await => {
-                        error!("cannot convert `{}` into a future in {}s")
+                        error!(NonConstAwait)
                     }
                 };
 
@@ -193,49 +195,31 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
                 err
             }
             CallKind::FnCall { fn_trait_id, self_ty } => {
-                let mut err = struct_span_err!(
-                    tcx.sess,
-                    span,
-                    E0015,
-                    "cannot call non-const closure in {}s",
-                    ccx.const_kind(),
-                );
-
-                match self_ty.kind() {
+                let note = match self_ty.kind() {
                     FnDef(def_id, ..) => {
                         let span = tcx.def_span(*def_id);
                         if ccx.tcx.is_const_fn_raw(*def_id) {
                             span_bug!(span, "calling const FnDef errored when it shouldn't");
                         }
 
-                        err.span_note(span, "function defined here, but it is not `const`");
-                    }
-                    FnPtr(..) => {
-                        err.note(format!(
-                            "function pointers need an RFC before allowed to be called in {}s",
-                            ccx.const_kind()
-                        ));
+                        Some(errors::NonConstClosureNote::FnDef { span })
                     }
-                    Closure(..) => {
-                        err.note(format!(
-                            "closures need an RFC before allowed to be called in {}s",
-                            ccx.const_kind()
-                        ));
-                    }
-                    _ => {}
-                }
+                    FnPtr(..) => Some(errors::NonConstClosureNote::FnPtr),
+                    Closure(..) => Some(errors::NonConstClosureNote::Closure),
+                    _ => None,
+                };
+
+                let mut err = tcx.sess.create_err(errors::NonConstClosure {
+                    span,
+                    kind: ccx.const_kind(),
+                    note,
+                });
 
                 diag_trait(&mut err, self_ty, fn_trait_id);
                 err
             }
             CallKind::Operator { trait_id, self_ty, .. } => {
-                let mut err = struct_span_err!(
-                    tcx.sess,
-                    span,
-                    E0015,
-                    "cannot call non-const operator in {}s",
-                    ccx.const_kind()
-                );
+                let mut sugg = None;
 
                 if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
                     match (substs[0].unpack(), substs[1].unpack()) {
@@ -260,14 +244,11 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
                                         let rhs_pos =
                                             span.lo() + BytePos::from_usize(eq_idx + 2 + rhs_idx);
                                         let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos);
-                                        err.multipart_suggestion(
-                                            "consider dereferencing here",
-                                            vec![
-                                                (span.shrink_to_lo(), deref.clone()),
-                                                (rhs_span, deref),
-                                            ],
-                                            Applicability::MachineApplicable,
-                                        );
+                                        sugg = Some(errors::ConsiderDereferencing {
+                                            deref,
+                                            span: span.shrink_to_lo(),
+                                            rhs_span,
+                                        });
                                     }
                                 }
                             }
@@ -275,26 +256,29 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
                         _ => {}
                     }
                 }
-
+                let mut err = tcx.sess.create_err(errors::NonConstOperator {
+                    span,
+                    kind: ccx.const_kind(),
+                    sugg,
+                });
                 diag_trait(&mut err, self_ty, trait_id);
                 err
             }
             CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => {
-                let mut err = struct_span_err!(
-                    tcx.sess,
-                    span,
-                    E0015,
-                    "cannot perform deref coercion on `{}` in {}s",
-                    self_ty,
-                    ccx.const_kind()
-                );
-
-                err.note(format!("attempting to deref into `{}`", deref_target_ty));
-
                 // Check first whether the source is accessible (issue #87060)
-                if tcx.sess.source_map().is_span_accessible(deref_target) {
-                    err.span_note(deref_target, "deref defined here");
-                }
+                let target = if tcx.sess.source_map().is_span_accessible(deref_target) {
+                    Some(deref_target)
+                } else {
+                    None
+                };
+
+                let mut err = tcx.sess.create_err(errors::NonConstDerefCoercion {
+                    span,
+                    ty: self_ty,
+                    kind: ccx.const_kind(),
+                    target_ty: deref_target_ty,
+                    deref_target: target,
+                });
 
                 diag_trait(&mut err, self_ty, tcx.require_lang_item(LangItem::Deref, Some(span)));
                 err
@@ -432,21 +416,12 @@ impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> {
         ccx: &ConstCx<'_, 'tcx>,
         span: Span,
     ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
-        let mut err = struct_span_err!(
-            ccx.tcx.sess,
-            span,
-            E0493,
-            "destructor of `{}` cannot be evaluated at compile-time",
-            self.dropped_ty,
-        );
-        err.span_label(
+        ccx.tcx.sess.create_err(errors::LiveDrop {
             span,
-            format!("the destructor for this type cannot be evaluated in {}s", ccx.const_kind()),
-        );
-        if let Some(span) = self.dropped_at {
-            err.span_label(span, "value is dropped here");
-        }
-        err
+            dropped_ty: self.dropped_ty,
+            kind: ccx.const_kind(),
+            dropped_at: self.dropped_at,
+        })
     }
 }
 
diff --git a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
index 23fcd22c52b..29063261ada 100644
--- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
+++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
@@ -1,6 +1,5 @@
 use rustc_middle::ty::layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement};
 use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt};
-use rustc_session::Limit;
 use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants};
 
 use crate::const_eval::{CheckAlignment, CompileTimeInterpreter};
@@ -45,11 +44,8 @@ fn might_permit_raw_init_strict<'tcx>(
     tcx: TyCtxt<'tcx>,
     kind: ValidityRequirement,
 ) -> Result<bool, LayoutError<'tcx>> {
-    let machine = CompileTimeInterpreter::new(
-        Limit::new(0),
-        /*can_access_statics:*/ false,
-        CheckAlignment::Error,
-    );
+    let machine =
+        CompileTimeInterpreter::new(/*can_access_statics:*/ false, CheckAlignment::Error);
 
     let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine);
 
diff --git a/compiler/rustc_error_codes/src/error_codes/E0741.md b/compiler/rustc_error_codes/src/error_codes/E0741.md
index 70d963cd41f..0c701052665 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0741.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0741.md
@@ -10,15 +10,19 @@ struct A;
 struct B<const X: A>; // error!
 ```
 
-Only structural-match types (that is, types that derive `PartialEq` and `Eq`)
-may be used as the types of const generic parameters.
+Only structural-match types, which are types that derive `PartialEq` and `Eq`
+and implement `ConstParamTy`, may be used as the types of const generic
+parameters.
 
-To fix the previous code example, we derive `PartialEq` and `Eq`:
+To fix the previous code example, we derive `PartialEq`, `Eq`, and
+`ConstParamTy`:
 
 ```
 #![feature(adt_const_params)]
 
-#[derive(PartialEq, Eq)] // We derive both traits here.
+use std::marker::ConstParamTy;
+
+#[derive(PartialEq, Eq, ConstParamTy)] // We derive both traits here.
 struct A;
 
 struct B<const X: A>; // ok!
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index 2a97c4ff7ae..f3ee83fd4d2 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -492,6 +492,10 @@ impl MultiSpan {
         replacements_occurred
     }
 
+    pub fn pop_span_label(&mut self) -> Option<(Span, DiagnosticMessage)> {
+        self.span_labels.pop()
+    }
+
     /// Returns the strings to highlight. We always ensure that there
     /// is an entry for each of the primary spans -- for each primary
     /// span `P`, if there is at least one label with span `P`, we return
diff --git a/compiler/rustc_errors/messages.ftl b/compiler/rustc_errors/messages.ftl
index 33709734322..8e8223c3cf8 100644
--- a/compiler/rustc_errors/messages.ftl
+++ b/compiler/rustc_errors/messages.ftl
@@ -8,7 +8,11 @@ errors_target_invalid_address_space =
     invalid address space `{$addr_space}` for `{$cause}` in "data-layout": {$err}
 
 errors_target_invalid_alignment =
-    invalid alignment for `{$cause}` in "data-layout": {$err}
+    invalid alignment for `{$cause}` in "data-layout": `{$align}` is {$err_kind ->
+        [not_power_of_two] not a power of 2
+        [too_large] too large
+        *[other] {""}
+    }
 
 errors_target_invalid_bits =
     invalid {$kind} `{$bit}` for `{$cause}` in "data-layout": {$err}
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 488f2d67ee5..ed0d06ed0ff 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -10,7 +10,7 @@ use rustc_lint_defs::{Applicability, LintExpectationId};
 use rustc_span::symbol::Symbol;
 use rustc_span::{Span, DUMMY_SP};
 use std::borrow::Cow;
-use std::fmt;
+use std::fmt::{self, Debug};
 use std::hash::{Hash, Hasher};
 use std::panic::Location;
 
@@ -33,7 +33,7 @@ pub type DiagnosticArgName<'source> = Cow<'source, str>;
 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
 pub enum DiagnosticArgValue<'source> {
     Str(Cow<'source, str>),
-    Number(usize),
+    Number(i128),
     StrListSepByAnd(Vec<Cow<'source, str>>),
 }
 
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index 7d9d0c76450..08ff2cfba5c 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -115,36 +115,22 @@ pub trait EmissionGuarantee: Sized {
     ) -> DiagnosticBuilder<'_, Self>;
 }
 
-/// Private module for sealing the `IsError` helper trait.
-mod sealed_level_is_error {
-    use crate::Level;
-
-    /// Sealed helper trait for statically checking that a `Level` is an error.
-    pub(crate) trait IsError<const L: Level> {}
-
-    impl IsError<{ Level::Bug }> for () {}
-    impl IsError<{ Level::DelayedBug }> for () {}
-    impl IsError<{ Level::Fatal }> for () {}
-    // NOTE(eddyb) `Level::Error { lint: true }` is also an error, but lints
-    // don't need error guarantees, as their levels are always dynamic.
-    impl IsError<{ Level::Error { lint: false } }> for () {}
-}
-
 impl<'a> DiagnosticBuilder<'a, ErrorGuaranteed> {
     /// Convenience function for internal use, clients should use one of the
     /// `struct_*` methods on [`Handler`].
     #[track_caller]
-    pub(crate) fn new_guaranteeing_error<M: Into<DiagnosticMessage>, const L: Level>(
+    pub(crate) fn new_guaranteeing_error<M: Into<DiagnosticMessage>>(
         handler: &'a Handler,
         message: M,
-    ) -> Self
-    where
-        (): sealed_level_is_error::IsError<L>,
-    {
+    ) -> Self {
         Self {
             inner: DiagnosticBuilderInner {
                 state: DiagnosticBuilderState::Emittable(handler),
-                diagnostic: Box::new(Diagnostic::new_with_code(L, None, message)),
+                diagnostic: Box::new(Diagnostic::new_with_code(
+                    Level::Error { lint: false },
+                    None,
+                    message,
+                )),
             },
             _marker: PhantomData,
         }
@@ -203,9 +189,7 @@ impl EmissionGuarantee for ErrorGuaranteed {
         handler: &Handler,
         msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, Self> {
-        DiagnosticBuilder::new_guaranteeing_error::<_, { Level::Error { lint: false } }>(
-            handler, msg,
-        )
+        DiagnosticBuilder::new_guaranteeing_error(handler, msg)
     }
 }
 
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index 65f8a61a30a..10fe7fc74a8 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -60,10 +60,8 @@ into_diagnostic_arg_using_display!(
     u8,
     i16,
     u16,
-    i32,
     u32,
     i64,
-    u64,
     i128,
     u128,
     std::io::Error,
@@ -80,6 +78,18 @@ into_diagnostic_arg_using_display!(
     ExitStatus,
 );
 
+impl IntoDiagnosticArg for i32 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Number(self.into())
+    }
+}
+
+impl IntoDiagnosticArg for u64 {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Number(self.into())
+    }
+}
+
 impl IntoDiagnosticArg for bool {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         if self {
@@ -134,7 +144,7 @@ impl IntoDiagnosticArg for PathBuf {
 
 impl IntoDiagnosticArg for usize {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Number(self)
+        DiagnosticArgValue::Number(self as i128)
     }
 }
 
@@ -147,9 +157,9 @@ impl IntoDiagnosticArg for PanicStrategy {
 impl IntoDiagnosticArg for hir::ConstContext {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         DiagnosticArgValue::Str(Cow::Borrowed(match self {
-            hir::ConstContext::ConstFn => "constant function",
+            hir::ConstContext::ConstFn => "const_fn",
             hir::ConstContext::Static(_) => "static",
-            hir::ConstContext::Const => "constant",
+            hir::ConstContext::Const => "const",
         }))
     }
 }
@@ -254,7 +264,8 @@ impl IntoDiagnostic<'_, !> for TargetDataLayoutErrors<'_> {
             TargetDataLayoutErrors::InvalidAlignment { cause, err } => {
                 diag = handler.struct_fatal(fluent::errors_target_invalid_alignment);
                 diag.set_arg("cause", cause);
-                diag.set_arg("err", err);
+                diag.set_arg("err_kind", err.diag_ident());
+                diag.set_arg("align", err.align());
                 diag
             }
             TargetDataLayoutErrors::InconsistentTargetArchitecture { dl, target } => {
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 6c5f3e62454..bf77ed81f9b 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -6,7 +6,6 @@
 #![feature(array_windows)]
 #![feature(drain_filter)]
 #![feature(if_let_guard)]
-#![feature(adt_const_params)]
 #![feature(let_chains)]
 #![feature(never_type)]
 #![feature(result_option_inspect)]
@@ -845,7 +844,7 @@ impl Handler {
         &self,
         msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
-        DiagnosticBuilder::new_guaranteeing_error::<_, { Level::Error { lint: false } }>(self, msg)
+        DiagnosticBuilder::new_guaranteeing_error(self, msg)
     }
 
     /// This should only be used by `rustc_middle::lint::struct_lint_level`. Do not use it for hard errors.
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index dde9890dfa5..4c53f9d8369 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -351,8 +351,6 @@ declare_features! (
     (active, const_async_blocks, "1.53.0", Some(85368), None),
     /// Allows `const || {}` closures in const contexts.
     (incomplete, const_closures, "1.68.0", Some(106003), None),
-    /// Allows limiting the evaluation steps of const expressions
-    (active, const_eval_limit, "1.43.0", Some(67217), None),
     /// Allows the definition of `const extern fn` and `const unsafe extern fn`.
     (active, const_extern_fn, "1.40.0", Some(64926), None),
     /// Allows basic arithmetic on floating point types in a `const fn`.
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 06f4a0b5eef..9be28c338f6 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -356,10 +356,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing),
     ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing),
     gated!(
-        const_eval_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing,
-        const_eval_limit, experimental!(const_eval_limit)
-    ),
-    gated!(
         move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing,
         large_assignments, experimental!(move_size_limit)
     ),
diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs
index 8bca24b2bf0..ed5d76b861a 100644
--- a/compiler/rustc_feature/src/removed.rs
+++ b/compiler/rustc_feature/src/removed.rs
@@ -59,8 +59,10 @@ declare_features! (
     /// Allows comparing raw pointers during const eval.
     (removed, const_compare_raw_pointers, "1.46.0", Some(53020), None,
      Some("cannot be allowed in const eval in any meaningful way")),
+    /// Allows limiting the evaluation steps of const expressions
+    (removed, const_eval_limit, "1.43.0", Some(67217), None, Some("removed the limit entirely")),
     /// Allows non-trivial generic constants which have to be manually propagated upwards.
-     (removed, const_evaluatable_checked, "1.48.0", Some(76560), None, Some("renamed to `generic_const_exprs`")),
+    (removed, const_evaluatable_checked, "1.48.0", Some(76560), None, Some("renamed to `generic_const_exprs`")),
     /// Allows the definition of `const` functions with some advanced features.
     (removed, const_fn, "1.54.0", Some(57563), None,
      Some("split into finer-grained feature gates")),
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index e8447310917..70fc66947df 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1675,6 +1675,14 @@ pub struct AnonConst {
     pub body: BodyId,
 }
 
+/// An inline constant expression `const { something }`.
+#[derive(Copy, Clone, Debug, HashStable_Generic)]
+pub struct ConstBlock {
+    pub hir_id: HirId,
+    pub def_id: LocalDefId,
+    pub body: BodyId,
+}
+
 /// An expression.
 #[derive(Debug, Clone, Copy, HashStable_Generic)]
 pub struct Expr<'hir> {
@@ -1922,7 +1930,7 @@ pub fn is_range_literal(expr: &Expr<'_>) -> bool {
 #[derive(Debug, Clone, Copy, HashStable_Generic)]
 pub enum ExprKind<'hir> {
     /// Allow anonymous constants from an inline `const` block
-    ConstBlock(AnonConst),
+    ConstBlock(ConstBlock),
     /// An array (e.g., `[a, b, c, d]`).
     Array(&'hir [Expr<'hir>]),
     /// A function call.
@@ -3641,6 +3649,7 @@ pub enum Node<'hir> {
     Variant(&'hir Variant<'hir>),
     Field(&'hir FieldDef<'hir>),
     AnonConst(&'hir AnonConst),
+    ConstBlock(&'hir ConstBlock),
     Expr(&'hir Expr<'hir>),
     ExprField(&'hir ExprField<'hir>),
     Stmt(&'hir Stmt<'hir>),
@@ -3695,6 +3704,7 @@ impl<'hir> Node<'hir> {
             Node::TypeBinding(b) => Some(b.ident),
             Node::Param(..)
             | Node::AnonConst(..)
+            | Node::ConstBlock(..)
             | Node::Expr(..)
             | Node::Stmt(..)
             | Node::Block(..)
@@ -3758,7 +3768,7 @@ impl<'hir> Node<'hir> {
             })
             | Node::Expr(Expr {
                 kind:
-                    ExprKind::ConstBlock(AnonConst { body, .. })
+                    ExprKind::ConstBlock(ConstBlock { body, .. })
                     | ExprKind::Closure(Closure { body, .. })
                     | ExprKind::Repeat(_, ArrayLen::Body(AnonConst { body, .. })),
                 ..
@@ -3878,6 +3888,13 @@ impl<'hir> Node<'hir> {
         this
     }
 
+    /// Expect a [`Node::ConstBlock`] or panic.
+    #[track_caller]
+    pub fn expect_inline_const(self) -> &'hir ConstBlock {
+        let Node::ConstBlock(this) = self else { self.expect_failed("an inline constant") };
+        this
+    }
+
     /// Expect a [`Node::Expr`] or panic.
     #[track_caller]
     pub fn expect_expr(self) -> &'hir Expr<'hir> {
diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs
index df0047d82e1..f84c814bd92 100644
--- a/compiler/rustc_hir/src/intravisit.rs
+++ b/compiler/rustc_hir/src/intravisit.rs
@@ -335,6 +335,9 @@ pub trait Visitor<'v>: Sized {
     fn visit_anon_const(&mut self, c: &'v AnonConst) {
         walk_anon_const(self, c)
     }
+    fn visit_inline_const(&mut self, c: &'v ConstBlock) {
+        walk_inline_const(self, c)
+    }
     fn visit_expr(&mut self, ex: &'v Expr<'v>) {
         walk_expr(self, ex)
     }
@@ -679,13 +682,18 @@ pub fn walk_anon_const<'v, V: Visitor<'v>>(visitor: &mut V, constant: &'v AnonCo
     visitor.visit_nested_body(constant.body);
 }
 
+pub fn walk_inline_const<'v, V: Visitor<'v>>(visitor: &mut V, constant: &'v ConstBlock) {
+    visitor.visit_id(constant.hir_id);
+    visitor.visit_nested_body(constant.body);
+}
+
 pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) {
     visitor.visit_id(expression.hir_id);
     match expression.kind {
         ExprKind::Array(subexpressions) => {
             walk_list!(visitor, visit_expr, subexpressions);
         }
-        ExprKind::ConstBlock(ref anon_const) => visitor.visit_anon_const(anon_const),
+        ExprKind::ConstBlock(ref const_block) => visitor.visit_inline_const(const_block),
         ExprKind::Repeat(ref element, ref count) => {
             visitor.visit_expr(element);
             visitor.visit_array_length(count)
diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs
index 3b2c052e8f4..4a1f8ece7b2 100644
--- a/compiler/rustc_hir_analysis/src/check/check.rs
+++ b/compiler/rustc_hir_analysis/src/check/check.rs
@@ -177,7 +177,7 @@ fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) {
         }
         // Generic statics are rejected, but we still reach this case.
         Err(e) => {
-            tcx.sess.delay_span_bug(span, e.to_string());
+            tcx.sess.delay_span_bug(span, format!("{e:?}"));
             return;
         }
     };
@@ -704,7 +704,7 @@ pub(super) fn check_specialization_validity<'tcx>(
             // grandparent. In that case, if parent is a `default impl`, inherited items use the
             // "defaultness" from the grandparent, else they are final.
             None => {
-                if tcx.impl_defaultness(parent_impl.def_id()).is_default() {
+                if tcx.defaultness(parent_impl.def_id()).is_default() {
                     None
                 } else {
                     Some(Err(parent_impl.def_id()))
@@ -803,7 +803,7 @@ fn check_impl_items_against_trait<'tcx>(
                 .as_ref()
                 .is_some_and(|node_item| node_item.item.defaultness(tcx).has_value());
 
-            if !is_implemented && tcx.impl_defaultness(impl_id).is_final() {
+            if !is_implemented && tcx.defaultness(impl_id).is_final() {
                 missing_items.push(tcx.associated_item(trait_item_id));
             }
 
diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs
index 6ab5556e951..5bd6fcb9612 100644
--- a/compiler/rustc_hir_analysis/src/check/region.rs
+++ b/compiler/rustc_hir_analysis/src/check/region.rs
@@ -392,7 +392,7 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
         // Manually recurse over closures and inline consts, because they are the only
         // case of nested bodies that share the parent environment.
         hir::ExprKind::Closure(&hir::Closure { body, .. })
-        | hir::ExprKind::ConstBlock(hir::AnonConst { body, .. }) => {
+        | hir::ExprKind::ConstBlock(hir::ConstBlock { body, .. }) => {
             let body = visitor.tcx.hir().body(body);
             visitor.visit_body(body);
         }
diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs
index fff417fcb29..69e32c35ed8 100644
--- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs
+++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs
@@ -829,83 +829,20 @@ fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) {
             let ty = tcx.type_of(param.def_id).subst_identity();
 
             if tcx.features().adt_const_params {
-                if let Some(non_structural_match_ty) =
-                    traits::search_for_adt_const_param_violation(param.span, tcx, ty)
-                {
-                    // We use the same error code in both branches, because this is really the same
-                    // issue: we just special-case the message for type parameters to make it
-                    // clearer.
-                    match non_structural_match_ty.kind() {
-                        ty::Param(_) => {
-                            // Const parameters may not have type parameters as their types,
-                            // because we cannot be sure that the type parameter derives `PartialEq`
-                            // and `Eq` (just implementing them is not enough for `structural_match`).
-                            struct_span_err!(
-                                tcx.sess,
-                                hir_ty.span,
-                                E0741,
-                                "`{ty}` is not guaranteed to `#[derive(PartialEq, Eq)]`, so may not be \
-                                used as the type of a const parameter",
-                            )
-                            .span_label(
-                                hir_ty.span,
-                                format!("`{ty}` may not derive both `PartialEq` and `Eq`"),
-                            )
-                            .note(
-                                "it is not currently possible to use a type parameter as the type of a \
-                                const parameter",
-                            )
-                            .emit();
-                        }
-                        ty::Float(_) => {
-                            struct_span_err!(
-                                tcx.sess,
-                                hir_ty.span,
-                                E0741,
-                                "`{ty}` is forbidden as the type of a const generic parameter",
-                            )
-                            .note("floats do not derive `Eq` or `Ord`, which are required for const parameters")
-                            .emit();
-                        }
-                        ty::FnPtr(_) => {
-                            struct_span_err!(
-                                tcx.sess,
-                                hir_ty.span,
-                                E0741,
-                                "using function pointers as const generic parameters is forbidden",
-                            )
-                            .emit();
-                        }
-                        ty::RawPtr(_) => {
-                            struct_span_err!(
-                                tcx.sess,
-                                hir_ty.span,
-                                E0741,
-                                "using raw pointers as const generic parameters is forbidden",
-                            )
-                            .emit();
-                        }
-                        _ => {
-                            let mut diag = struct_span_err!(
-                                tcx.sess,
-                                hir_ty.span,
-                                E0741,
-                                "`{}` must be annotated with `#[derive(PartialEq, Eq)]` to be used as \
-                                the type of a const parameter",
-                                non_structural_match_ty,
-                            );
-
-                            if ty == non_structural_match_ty {
-                                diag.span_label(
-                                    hir_ty.span,
-                                    format!("`{ty}` doesn't derive both `PartialEq` and `Eq`"),
-                                );
-                            }
-
-                            diag.emit();
-                        }
-                    }
-                }
+                enter_wf_checking_ctxt(tcx, hir_ty.span, param.def_id, |wfcx| {
+                    let trait_def_id =
+                        tcx.require_lang_item(LangItem::ConstParamTy, Some(hir_ty.span));
+                    wfcx.register_bound(
+                        ObligationCause::new(
+                            hir_ty.span,
+                            param.def_id,
+                            ObligationCauseCode::ConstParam(ty),
+                        ),
+                        wfcx.param_env,
+                        ty,
+                        trait_def_id,
+                    );
+                });
             } else {
                 let err_ty_str;
                 let mut is_ptr = true;
diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs
index 2f7d465839c..c7b9fc9a697 100644
--- a/compiler/rustc_hir_analysis/src/collect.rs
+++ b/compiler/rustc_hir_analysis/src/collect.rs
@@ -941,7 +941,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
 
                 match item {
                     Some(item) if matches!(item.kind, hir::AssocItemKind::Fn { .. }) => {
-                        if !tcx.impl_defaultness(item.id.owner_id).has_value() {
+                        if !tcx.defaultness(item.id.owner_id).has_value() {
                             tcx.sess.emit_err(errors::FunctionNotHaveDefaultImplementation {
                                 span: item.span,
                                 note_span: attr_span,
diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs
index ed60998ec8d..32a7e8af862 100644
--- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs
+++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs
@@ -123,9 +123,6 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
                     {
                         Some(parent_def_id.to_def_id())
                     }
-                    Node::Expr(&Expr { kind: ExprKind::ConstBlock(_), .. }) => {
-                        Some(tcx.typeck_root_def_id(def_id.to_def_id()))
-                    }
                     // Exclude `GlobalAsm` here which cannot have generics.
                     Node::Expr(&Expr { kind: ExprKind::InlineAsm(asm), .. })
                         if asm.operands.iter().any(|(op, _op_sp)| match op {
@@ -142,7 +139,8 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
                 }
             }
         }
-        Node::Expr(&hir::Expr { kind: hir::ExprKind::Closure { .. }, .. }) => {
+        Node::ConstBlock(_)
+        | Node::Expr(&hir::Expr { kind: hir::ExprKind::Closure { .. }, .. }) => {
             Some(tcx.typeck_root_def_id(def_id.to_def_id()))
         }
         Node::Item(item) => match item.kind {
@@ -339,17 +337,14 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics {
     }
 
     // provide junk type parameter defs for const blocks.
-    if let Node::AnonConst(_) = node {
-        let parent_node = tcx.hir().get_parent(hir_id);
-        if let Node::Expr(&Expr { kind: ExprKind::ConstBlock(_), .. }) = parent_node {
-            params.push(ty::GenericParamDef {
-                index: next_index(),
-                name: Symbol::intern("<const_ty>"),
-                def_id: def_id.to_def_id(),
-                pure_wrt_drop: false,
-                kind: ty::GenericParamDefKind::Type { has_default: false, synthetic: false },
-            });
-        }
+    if let Node::ConstBlock(_) = node {
+        params.push(ty::GenericParamDef {
+            index: next_index(),
+            name: Symbol::intern("<const_ty>"),
+            def_id: def_id.to_def_id(),
+            pure_wrt_drop: false,
+            kind: ty::GenericParamDefKind::Type { has_default: false, synthetic: false },
+        });
     }
 
     let param_def_id_to_index = params.iter().map(|param| (param.def_id, param.index)).collect();
diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs
index 65ab00fda81..c2b837fcfa6 100644
--- a/compiler/rustc_hir_analysis/src/collect/type_of.rs
+++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs
@@ -34,12 +34,6 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> {
         Node::Ty(&Ty { kind: TyKind::Typeof(ref e), .. }) if e.hir_id == hir_id => {
             return tcx.typeck(def_id).node_type(e.hir_id)
         }
-        Node::Expr(&Expr { kind: ExprKind::ConstBlock(ref anon_const), .. })
-            if anon_const.hir_id == hir_id =>
-        {
-            let substs = InternalSubsts::identity_for_item(tcx, def_id.to_def_id());
-            return substs.as_inline_const().ty()
-        }
         Node::Expr(&Expr { kind: ExprKind::InlineAsm(asm), .. })
         | Node::Item(&Item { kind: ItemKind::GlobalAsm(asm), .. })
             if asm.operands.iter().any(|(op, _op_sp)| match op {
@@ -435,7 +429,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<Ty
                     in_trait,
                     ..
                 }) => {
-                    if in_trait && !tcx.impl_defaultness(owner).has_value() {
+                    if in_trait && !tcx.defaultness(owner).has_value() {
                         span_bug!(
                             tcx.def_span(def_id),
                             "tried to get type of this RPITIT with no definition"
@@ -487,6 +481,11 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<Ty
 
         Node::AnonConst(_) => anon_const_type_of(tcx, def_id),
 
+        Node::ConstBlock(_) => {
+            let substs = InternalSubsts::identity_for_item(tcx, def_id.to_def_id());
+            substs.as_inline_const().ty()
+        }
+
         Node::GenericParam(param) => match &param.kind {
             GenericParamKind::Type { default: Some(ty), .. }
             | GenericParamKind::Const { ty, .. } => icx.to_ty(ty),
diff --git a/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs
index f7c5b44678f..4d96a7ff4c3 100644
--- a/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs
+++ b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs
@@ -1,3 +1,4 @@
+use rustc_errors::StashKey;
 use rustc_hir::def_id::LocalDefId;
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{self as hir, Expr, ImplItem, Item, Node, TraitItem};
@@ -59,7 +60,20 @@ pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: Local
         }
     }
 
-    let Some(hidden) = locator.found else {
+    if let Some(hidden) = locator.found {
+        // Only check against typeck if we didn't already error
+        if !hidden.ty.references_error() {
+            for concrete_type in locator.typeck_types {
+                if concrete_type.ty != tcx.erase_regions(hidden.ty)
+                    && !(concrete_type, hidden).references_error()
+                {
+                    hidden.report_mismatch(&concrete_type, def_id, tcx).emit();
+                }
+            }
+        }
+
+        hidden.ty
+    } else {
         let reported = tcx.sess.emit_err(UnconstrainedOpaqueType {
             span: tcx.def_span(def_id),
             name: tcx.item_name(tcx.local_parent(def_id).to_def_id()),
@@ -70,21 +84,8 @@ pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: Local
                 _ => "item",
             },
         });
-        return tcx.ty_error(reported);
-    };
-
-    // Only check against typeck if we didn't already error
-    if !hidden.ty.references_error() {
-        for concrete_type in locator.typeck_types {
-            if concrete_type.ty != tcx.erase_regions(hidden.ty)
-                && !(concrete_type, hidden).references_error()
-            {
-                hidden.report_mismatch(&concrete_type, def_id, tcx).emit();
-            }
-        }
+        tcx.ty_error(reported)
     }
-
-    hidden.ty
 }
 
 struct TaitConstraintLocator<'tcx> {
@@ -130,13 +131,28 @@ impl TaitConstraintLocator<'_> {
             self.found = Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: self.tcx.ty_error(guar) });
             return;
         }
-        let Some(&typeck_hidden_ty) = tables.concrete_opaque_types.get(&self.def_id) else {
+
+        let mut constrained = false;
+        for (&opaque_type_key, &hidden_type) in &tables.concrete_opaque_types {
+            if opaque_type_key.def_id != self.def_id {
+                continue;
+            }
+            constrained = true;
+            let concrete_type =
+                self.tcx.erase_regions(hidden_type.remap_generic_params_to_declaration_params(
+                    opaque_type_key,
+                    self.tcx,
+                    true,
+                ));
+            if self.typeck_types.iter().all(|prev| prev.ty != concrete_type.ty) {
+                self.typeck_types.push(concrete_type);
+            }
+        }
+
+        if !constrained {
             debug!("no constraints in typeck results");
             return;
         };
-        if self.typeck_types.iter().all(|prev| prev.ty != typeck_hidden_ty.ty) {
-            self.typeck_types.push(typeck_hidden_ty);
-        }
 
         // Use borrowck to get the type with unerased regions.
         let concrete_opaque_types = &self.tcx.mir_borrowck(item_def_id).concrete_opaque_types;
@@ -190,17 +206,45 @@ impl<'tcx> intravisit::Visitor<'tcx> for TaitConstraintLocator<'tcx> {
     }
 }
 
-pub(super) fn find_opaque_ty_constraints_for_rpit(
-    tcx: TyCtxt<'_>,
+pub(super) fn find_opaque_ty_constraints_for_rpit<'tcx>(
+    tcx: TyCtxt<'tcx>,
     def_id: LocalDefId,
     owner_def_id: LocalDefId,
 ) -> Ty<'_> {
-    let concrete = tcx.mir_borrowck(owner_def_id).concrete_opaque_types.get(&def_id).copied();
+    let tables = tcx.typeck(owner_def_id);
+
+    // Check that all of the opaques we inferred during HIR are compatible.
+    // FIXME: We explicitly don't check that the types inferred during HIR
+    // typeck are compatible with the one that we infer during borrowck,
+    // because that one actually sometimes has consts evaluated eagerly so
+    // using strict type equality will fail.
+    let mut hir_opaque_ty: Option<ty::OpaqueHiddenType<'tcx>> = None;
+    if tables.tainted_by_errors.is_none() {
+        for (&opaque_type_key, &hidden_type) in &tables.concrete_opaque_types {
+            if opaque_type_key.def_id != def_id {
+                continue;
+            }
+            let concrete_type = tcx.erase_regions(
+                hidden_type.remap_generic_params_to_declaration_params(opaque_type_key, tcx, true),
+            );
+            if let Some(prev) = &mut hir_opaque_ty {
+                if concrete_type.ty != prev.ty && !(concrete_type, prev.ty).references_error() {
+                    prev.report_mismatch(&concrete_type, def_id, tcx).stash(
+                        tcx.def_span(opaque_type_key.def_id),
+                        StashKey::OpaqueHiddenTypeMismatch,
+                    );
+                }
+            } else {
+                hir_opaque_ty = Some(concrete_type);
+            }
+        }
+    }
 
-    if let Some(concrete) = concrete {
+    let mir_opaque_ty = tcx.mir_borrowck(owner_def_id).concrete_opaque_types.get(&def_id).copied();
+    if let Some(mir_opaque_ty) = mir_opaque_ty {
         let scope = tcx.hir().local_def_id_to_hir_id(owner_def_id);
         debug!(?scope);
-        let mut locator = RpitConstraintChecker { def_id, tcx, found: concrete };
+        let mut locator = RpitConstraintChecker { def_id, tcx, found: mir_opaque_ty };
 
         match tcx.hir().get(scope) {
             Node::Item(it) => intravisit::walk_item(&mut locator, it),
@@ -208,17 +252,18 @@ pub(super) fn find_opaque_ty_constraints_for_rpit(
             Node::TraitItem(it) => intravisit::walk_trait_item(&mut locator, it),
             other => bug!("{:?} is not a valid scope for an opaque type item", other),
         }
-    }
 
-    concrete.map(|concrete| concrete.ty).unwrap_or_else(|| {
-        let table = tcx.typeck(owner_def_id);
-        if let Some(guar) = table.tainted_by_errors {
-            // Some error in the
-            // owner fn prevented us from populating
+        mir_opaque_ty.ty
+    } else {
+        if let Some(guar) = tables.tainted_by_errors {
+            // Some error in the owner fn prevented us from populating
             // the `concrete_opaque_types` table.
             tcx.ty_error(guar)
         } else {
-            table.concrete_opaque_types.get(&def_id).map(|ty| ty.ty).unwrap_or_else(|| {
+            // Fall back to the RPIT we inferred during HIR typeck
+            if let Some(hir_opaque_ty) = hir_opaque_ty {
+                hir_opaque_ty.ty
+            } else {
                 // We failed to resolve the opaque type or it
                 // resolves to itself. We interpret this as the
                 // no values of the hidden type ever being constructed,
@@ -226,9 +271,9 @@ pub(super) fn find_opaque_ty_constraints_for_rpit(
                 // For backwards compatibility reasons, we fall back to
                 // `()` until we the diverging default is changed.
                 tcx.mk_diverging_default()
-            })
+            }
         }
-    })
+    }
 }
 
 struct RpitConstraintChecker<'tcx> {
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index d93e8efc1b5..ced46fe426c 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -84,6 +84,7 @@ impl<'a> State<'a> {
             Node::ImplItem(a) => self.print_impl_item(a),
             Node::Variant(a) => self.print_variant(a),
             Node::AnonConst(a) => self.print_anon_const(a),
+            Node::ConstBlock(a) => self.print_inline_const(a),
             Node::Expr(a) => self.print_expr(a),
             Node::ExprField(a) => self.print_expr_field(&a),
             Node::Stmt(a) => self.print_stmt(a),
@@ -1095,10 +1096,10 @@ impl<'a> State<'a> {
         self.end()
     }
 
-    fn print_expr_anon_const(&mut self, anon_const: &hir::AnonConst) {
+    fn print_inline_const(&mut self, constant: &hir::ConstBlock) {
         self.ibox(INDENT_UNIT);
         self.word_space("const");
-        self.print_anon_const(anon_const);
+        self.ann.nested(self, Nested::Body(constant.body));
         self.end()
     }
 
@@ -1370,7 +1371,7 @@ impl<'a> State<'a> {
                 self.print_expr_vec(exprs);
             }
             hir::ExprKind::ConstBlock(ref anon_const) => {
-                self.print_expr_anon_const(anon_const);
+                self.print_inline_const(anon_const);
             }
             hir::ExprKind::Repeat(element, ref count) => {
                 self.print_expr_repeat(element, count);
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 19ff77d8349..3c5feb1ba51 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -348,9 +348,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             }
             ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected),
             ExprKind::Array(args) => self.check_expr_array(args, expected, expr),
-            ExprKind::ConstBlock(ref anon_const) => {
-                self.check_expr_const_block(anon_const, expected, expr)
-            }
+            ExprKind::ConstBlock(ref block) => self.check_expr_const_block(block, expected, expr),
             ExprKind::Repeat(element, ref count) => {
                 self.check_expr_repeat(element, count, expected, expr)
             }
@@ -1368,20 +1366,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
     fn check_expr_const_block(
         &self,
-        anon_const: &'tcx hir::AnonConst,
+        block: &'tcx hir::ConstBlock,
         expected: Expectation<'tcx>,
         _expr: &'tcx hir::Expr<'tcx>,
     ) -> Ty<'tcx> {
-        let body = self.tcx.hir().body(anon_const.body);
+        let body = self.tcx.hir().body(block.body);
 
         // Create a new function context.
-        let def_id = anon_const.def_id;
+        let def_id = block.def_id;
         let fcx = FnCtxt::new(self, self.param_env.with_const(), def_id);
         crate::GatherLocalsVisitor::new(&fcx).visit_body(body);
 
         let ty = fcx.check_expr_with_expectation(&body.value, expected);
         fcx.require_type_is_sized(ty, body.value.span, traits::ConstSized);
-        fcx.write_ty(anon_const.hir_id, ty);
+        fcx.write_ty(block.hir_id, ty);
         ty
     }
 
@@ -3117,16 +3115,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     }
                 }
                 ty::Tuple(tys) => {
-                    let fstr = field.as_str();
-
-                    if let Ok(index) = fstr.parse::<usize>() {
-                        if fstr == index.to_string() {
-                            if let Some(&field_ty) = tys.get(index) {
-                                field_indices.push(index.into());
-                                current_container = field_ty;
+                    if let Ok(index) = field.as_str().parse::<usize>()
+                        && field.name == sym::integer(index)
+                    {
+                        for ty in tys.iter().take(index + 1) {
+                            self.require_type_is_sized(ty, expr.span, traits::MiscObligation);
+                        }
+                        if let Some(&field_ty) = tys.get(index) {
+                            field_indices.push(index.into());
+                            current_container = field_ty;
 
-                                continue;
-                            }
+                            continue;
                         }
                     }
                 }
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index c4add4dbdfb..e19b0664461 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -874,7 +874,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let found = self.resolve_vars_with_obligations(found);
 
         let in_loop = self.is_loop(id)
-            || self.tcx.hir().parent_iter(id).any(|(parent_id, _)| self.is_loop(parent_id));
+            || self
+                .tcx
+                .hir()
+                .parent_iter(id)
+                .take_while(|(_, node)| {
+                    // look at parents until we find the first body owner
+                    node.body_id().is_none()
+                })
+                .any(|(parent_id, _)| self.is_loop(parent_id));
 
         let in_local_statement = self.is_local_statement(id)
             || self
diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
index e4a62ec05ae..786a8c28f99 100644
--- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
+++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
@@ -270,6 +270,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
                 | hir::Node::Variant(..)
                 | hir::Node::Field(..)
                 | hir::Node::AnonConst(..)
+                | hir::Node::ConstBlock(..)
                 | hir::Node::Stmt(..)
                 | hir::Node::PathSegment(..)
                 | hir::Node::Ty(..)
diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs
index 0f21fc1e662..964acc4eb77 100644
--- a/compiler/rustc_hir_typeck/src/writeback.rs
+++ b/compiler/rustc_hir_typeck/src/writeback.rs
@@ -583,19 +583,15 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
                 continue;
             }
 
-            let hidden_type =
-                self.tcx().erase_regions(hidden_type.remap_generic_params_to_declaration_params(
-                    opaque_type_key,
-                    self.tcx(),
-                    true,
-                ));
-
+            // Here we only detect impl trait definition conflicts when they
+            // are equal modulo regions.
             if let Some(last_opaque_ty) = self
                 .typeck_results
                 .concrete_opaque_types
-                .insert(opaque_type_key.def_id, hidden_type)
+                .insert(opaque_type_key, hidden_type)
                 && last_opaque_ty.ty != hidden_type.ty
             {
+                assert!(!self.tcx().trait_solver_next());
                 hidden_type
                     .report_mismatch(&last_opaque_ty, opaque_type_key.def_id, self.tcx())
                     .stash(
diff --git a/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs b/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs
index d1f110472c9..9a8fac0fc1b 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs
@@ -586,7 +586,7 @@ fn foo(&self) -> Self::T { String::new() }
                             // FIXME: account for returning some type in a trait fn impl that has
                             // an assoc type as a return type (#72076).
                             if let hir::Defaultness::Default { has_value: true } =
-                                tcx.impl_defaultness(item.id.owner_id)
+                                tcx.defaultness(item.id.owner_id)
                             {
                                 let assoc_ty = tcx.type_of(item.id.owner_id).subst_identity();
                                 if self.infcx.can_eq(param_env, assoc_ty, found) {
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index e707ac41a05..98fe3821947 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -155,6 +155,8 @@ lint_builtin_unused_doc_comment = unused doc comment
 lint_builtin_while_true = denote infinite loops with `loop {"{"} ... {"}"}`
     .suggestion = use `loop`
 
+lint_cast_ref_to_mut = casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
+
 lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name}
 
 lint_check_name_unknown = unknown lint: `{$lint_name}`
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 358d412a4d8..213e8db66a0 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -1317,10 +1317,14 @@ declare_lint! {
     ///
     /// ### Explanation
     ///
-    /// A bare `pub` visibility may be misleading if the item is not actually
-    /// publicly exported from the crate. The `pub(crate)` visibility is
-    /// recommended to be used instead, which more clearly expresses the intent
-    /// that the item is only visible within its own crate.
+    /// The `pub` keyword both expresses an intent for an item to be publicly available, and also
+    /// signals to the compiler to make the item publicly accessible. The intent can only be
+    /// satisfied, however, if all items which contain this item are *also* publicly accessible.
+    /// Thus, this lint serves to identify situations where the intent does not match the reality.
+    ///
+    /// If you wish the item to be accessible elsewhere within the crate, but not outside it, the
+    /// `pub(crate)` visibility is recommended to be used instead. This more clearly expresses the
+    /// intent that the item is only visible within its own crate.
     ///
     /// This lint is "allow" by default because it will trigger for a large
     /// amount existing Rust code, and has some false-positives. Eventually it
diff --git a/compiler/rustc_lint/src/cast_ref_to_mut.rs b/compiler/rustc_lint/src/cast_ref_to_mut.rs
new file mode 100644
index 00000000000..84308d48c10
--- /dev/null
+++ b/compiler/rustc_lint/src/cast_ref_to_mut.rs
@@ -0,0 +1,72 @@
+use rustc_ast::Mutability;
+use rustc_hir::{Expr, ExprKind, MutTy, TyKind, UnOp};
+use rustc_middle::ty;
+use rustc_span::sym;
+
+use crate::{lints::CastRefToMutDiag, LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+    /// The `cast_ref_to_mut` lint checks for casts of `&T` to `&mut T`
+    /// without using interior mutability.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// fn x(r: &i32) {
+    ///     unsafe {
+    ///         *(r as *const i32 as *mut i32) += 1;
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Casting `&T` to `&mut T` without using interior mutability is undefined behavior,
+    /// as it's a violation of Rust reference aliasing requirements.
+    ///
+    /// `UnsafeCell` is the only way to obtain aliasable data that is considered
+    /// mutable.
+    CAST_REF_TO_MUT,
+    Deny,
+    "casts of `&T` to `&mut T` without interior mutability"
+}
+
+declare_lint_pass!(CastRefToMut => [CAST_REF_TO_MUT]);
+
+impl<'tcx> LateLintPass<'tcx> for CastRefToMut {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+        let ExprKind::Unary(UnOp::Deref, e) = &expr.kind else { return; };
+
+        let e = e.peel_blocks();
+        let e = if let ExprKind::Cast(e, t) = e.kind
+            && let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind {
+            e
+        } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
+            && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+            && cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) {
+            expr
+        } else {
+            return;
+        };
+
+        let e = e.peel_blocks();
+        let e = if let ExprKind::Cast(e, t) = e.kind
+            && let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind {
+            e
+        } else if let ExprKind::Call(path, [arg]) = e.kind
+            && let ExprKind::Path(ref qpath) = path.kind
+            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+            && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) {
+            arg
+        } else {
+            return;
+        };
+
+        let e = e.peel_blocks();
+        if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind() {
+            cx.emit_spanned_lint(CAST_REF_TO_MUT, expr.span, CastRefToMutDiag);
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index c62109b2986..5e3f057d428 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -50,6 +50,7 @@ extern crate tracing;
 
 mod array_into_iter;
 pub mod builtin;
+mod cast_ref_to_mut;
 mod context;
 mod deref_into_dyn_supertrait;
 mod drop_forget_useless;
@@ -97,6 +98,7 @@ use rustc_span::Span;
 
 use array_into_iter::ArrayIntoIter;
 use builtin::*;
+use cast_ref_to_mut::*;
 use deref_into_dyn_supertrait::*;
 use drop_forget_useless::*;
 use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
@@ -214,6 +216,7 @@ late_lint_methods!(
             BoxPointers: BoxPointers,
             PathStatements: PathStatements,
             LetUnderscore: LetUnderscore,
+            CastRefToMut: CastRefToMut,
             // Depends on referenced function signatures in expressions
             UnusedResults: UnusedResults,
             NonUpperCaseGlobals: NonUpperCaseGlobals,
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 746abebeb37..fd15f795202 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -718,6 +718,11 @@ pub enum InvalidFromUtf8Diag {
     },
 }
 
+// cast_ref_to_mut.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_cast_ref_to_mut)]
+pub struct CastRefToMutDiag;
+
 // hidden_unicode_codepoints.rs
 #[derive(LintDiagnostic)]
 #[diag(lint_hidden_unicode_codepoints)]
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 1507087bdd4..eb246c3f93e 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -3357,6 +3357,7 @@ declare_lint_pass! {
         LARGE_ASSIGNMENTS,
         LATE_BOUND_LIFETIME_ARGUMENTS,
         LEGACY_DERIVE_HELPERS,
+        LONG_RUNNING_CONST_EVAL,
         LOSSY_PROVENANCE_CASTS,
         MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS,
         MACRO_USE_EXTERN_CRATE,
@@ -3427,6 +3428,43 @@ declare_lint_pass! {
 }
 
 declare_lint! {
+    /// The `long_running_const_eval` lint is emitted when const
+    /// eval is running for a long time to ensure rustc terminates
+    /// even if you accidentally wrote an infinite loop.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// const FOO: () = loop {};
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Loops allow const evaluation to compute arbitrary code, but may also
+    /// cause infinite loops or just very long running computations.
+    /// Users can enable long running computations by allowing the lint
+    /// on individual constants or for entire crates.
+    ///
+    /// ### Unconditional warnings
+    ///
+    /// Note that regardless of whether the lint is allowed or set to warn,
+    /// the compiler will issue warnings if constant evaluation runs significantly
+    /// longer than this lint's limit. These warnings are also shown to downstream
+    /// users from crates.io or similar registries. If you are above the lint's limit,
+    /// both you and downstream users might be exposed to these warnings.
+    /// They might also appear on compiler updates, as the compiler makes minor changes
+    /// about how complexity is measured: staying below the limit ensures that there
+    /// is enough room, and given that the lint is disabled for people who use your
+    /// dependency it means you will be the only one to get the warning and can put
+    /// out an update in your own time.
+    pub LONG_RUNNING_CONST_EVAL,
+    Deny,
+    "detects long const eval operations"
+}
+
+declare_lint! {
     /// The `unused_doc_comments` lint detects doc comments that aren't used
     /// by `rustdoc`.
     ///
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index cd6e3687460..2e6e84ad80e 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -14,6 +14,8 @@ use syn::Token;
 use syn::{parse_quote, spanned::Spanned, Attribute, Meta, Path, Type};
 use synstructure::{BindingInfo, Structure, VariantInfo};
 
+use super::utils::SubdiagnosticVariant;
+
 /// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
 #[derive(Clone, PartialEq, Eq)]
 pub(crate) enum DiagnosticDeriveKind {
@@ -150,19 +152,19 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
     fn parse_subdiag_attribute(
         &self,
         attr: &Attribute,
-    ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
-        let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
+    ) -> Result<Option<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
+        let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, self)? else {
             // Some attributes aren't errors - like documentation comments - but also aren't
             // subdiagnostics.
             return Ok(None);
         };
 
-        if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
+        if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag.kind {
             throw_invalid_attr!(attr, |diag| diag
                 .help("consider creating a `Subdiagnostic` instead"));
         }
 
-        let slug = slug.unwrap_or_else(|| match subdiag {
+        let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind {
             SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
             SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
             SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
@@ -171,7 +173,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
             SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
         });
 
-        Ok(Some((subdiag, slug)))
+        Ok(Some((subdiag.kind, slug, subdiag.no_span)))
     }
 
     /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
@@ -229,7 +231,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
             return Ok(tokens);
         }
 
-        let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
+        let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
             // Some attributes aren't errors - like documentation comments - but also aren't
             // subdiagnostics.
             return Ok(quote! {});
@@ -380,7 +382,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
             _ => (),
         }
 
-        let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
+        let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
             // Some attributes aren't errors - like documentation comments - but also aren't
             // subdiagnostics.
             return Ok(quote! {});
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 374ba1a45c0..e3d9eb96574 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -14,6 +14,8 @@ use quote::{format_ident, quote};
 use syn::{spanned::Spanned, Attribute, Meta, MetaList, Path};
 use synstructure::{BindingInfo, Structure, VariantInfo};
 
+use super::utils::SubdiagnosticVariant;
+
 /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
 pub(crate) struct SubdiagnosticDeriveBuilder {
     diag: syn::Ident,
@@ -180,11 +182,13 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
 }
 
 impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
-    fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
+    fn identify_kind(
+        &mut self,
+    ) -> Result<Vec<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
         let mut kind_slugs = vec![];
 
         for attr in self.variant.ast().attrs {
-            let Some((kind, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
+            let Some(SubdiagnosticVariant { kind, slug, no_span }) = SubdiagnosticVariant::from_attr(attr, self)? else {
                 // Some attributes aren't errors - like documentation comments - but also aren't
                 // subdiagnostics.
                 continue;
@@ -202,7 +206,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
                 );
             };
 
-            kind_slugs.push((kind, slug));
+            kind_slugs.push((kind, slug, no_span));
         }
 
         Ok(kind_slugs)
@@ -487,7 +491,8 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
             }
         };
 
-        let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect();
+        let kind_stats: KindsStatistics =
+            kind_slugs.iter().map(|(kind, _slug, _no_span)| kind).collect();
 
         let init = if kind_stats.has_multipart_suggestion {
             quote! { let mut suggestions = Vec::new(); }
@@ -508,13 +513,17 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
         let diag = &self.parent.diag;
         let f = &self.parent.f;
         let mut calls = TokenStream::new();
-        for (kind, slug) in kind_slugs {
+        for (kind, slug, no_span) in kind_slugs {
             let message = format_ident!("__message");
             calls.extend(
                 quote! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); },
             );
 
-            let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
+            let name = format_ident!(
+                "{}{}",
+                if span_field.is_some() && !no_span { "span_" } else { "" },
+                kind
+            );
             let call = match kind {
                 SubdiagnosticKind::Suggestion {
                     suggestion_kind,
@@ -566,7 +575,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
                     }
                 }
                 _ => {
-                    if let Some(span) = span_field {
+                    if let Some(span) = span_field && !no_span {
                         quote! { #diag.#name(#span, #message); }
                     } else {
                         quote! { #diag.#name(#message); }
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index e2434981f8d..85dd9f6a3ce 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -597,14 +597,20 @@ pub(super) enum SubdiagnosticKind {
     },
 }
 
-impl SubdiagnosticKind {
-    /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
-    /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
+pub(super) struct SubdiagnosticVariant {
+    pub(super) kind: SubdiagnosticKind,
+    pub(super) slug: Option<Path>,
+    pub(super) no_span: bool,
+}
+
+impl SubdiagnosticVariant {
+    /// Constructs a `SubdiagnosticVariant` from a field or type attribute such as `#[note]`,
+    /// `#[error(parser::add_paren, no_span)]` or `#[suggestion(code = "...")]`. Returns the
     /// `SubdiagnosticKind` and the diagnostic slug, if specified.
     pub(super) fn from_attr(
         attr: &Attribute,
         fields: &impl HasFieldMap,
-    ) -> Result<Option<(SubdiagnosticKind, Option<Path>)>, DiagnosticDeriveError> {
+    ) -> Result<Option<SubdiagnosticVariant>, DiagnosticDeriveError> {
         // Always allow documentation comments.
         if is_doc_comment(attr) {
             return Ok(None);
@@ -679,7 +685,7 @@ impl SubdiagnosticKind {
                     | SubdiagnosticKind::Help
                     | SubdiagnosticKind::Warn
                     | SubdiagnosticKind::MultipartSuggestion { .. } => {
-                        return Ok(Some((kind, None)));
+                        return Ok(Some(SubdiagnosticVariant { kind, slug: None, no_span: false }));
                     }
                     SubdiagnosticKind::Suggestion { .. } => {
                         throw_span_err!(span, "suggestion without `code = \"...\"`")
@@ -696,11 +702,14 @@ impl SubdiagnosticKind {
 
         let mut first = true;
         let mut slug = None;
+        let mut no_span = false;
 
         list.parse_nested_meta(|nested| {
             if nested.input.is_empty() || nested.input.peek(Token![,]) {
                 if first {
                     slug = Some(nested.path);
+                } else if nested.path.is_ident("no_span") {
+                    no_span = true;
                 } else {
                     span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit();
                 }
@@ -775,19 +784,19 @@ impl SubdiagnosticKind {
                 (_, SubdiagnosticKind::Suggestion { .. }) => {
                     span_err(path_span, "invalid nested attribute")
                         .help(
-                            "only `style`, `code` and `applicability` are valid nested attributes",
+                            "only `no_span`, `style`, `code` and `applicability` are valid nested attributes",
                         )
                         .emit();
                     has_errors = true;
                 }
                 (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
                     span_err(path_span, "invalid nested attribute")
-                        .help("only `style` and `applicability` are valid nested attributes")
+                        .help("only `no_span`, `style` and `applicability` are valid nested attributes")
                         .emit();
                     has_errors = true;
                 }
                 _ => {
-                    span_err(path_span, "invalid nested attribute").emit();
+                    span_err(path_span, "only `no_span` is a valid nested attribute").emit();
                     has_errors = true;
                 }
             }
@@ -831,7 +840,7 @@ impl SubdiagnosticKind {
             | SubdiagnosticKind::Warn => {}
         }
 
-        Ok(Some((kind, slug)))
+        Ok(Some(SubdiagnosticVariant { kind, slug, no_span }))
     }
 }
 
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index a15307e4345..243e4f5b659 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -231,7 +231,7 @@ provide! { tcx, def_id, other, cdata,
     opt_def_kind => { table_direct }
     impl_parent => { table }
     impl_polarity => { table_direct }
-    impl_defaultness => { table_direct }
+    defaultness => { table_direct }
     constness => { table_direct }
     coerce_unsized_info => { table }
     mir_const_qualif => { table }
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 6ceb61e793e..cbc012c55c3 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -1191,7 +1191,7 @@ fn should_encode_type(tcx: TyCtxt<'_>, def_id: LocalDefId, def_kind: DefKind) ->
 
 fn should_encode_const(def_kind: DefKind) -> bool {
     match def_kind {
-        DefKind::Const | DefKind::AssocConst | DefKind::AnonConst => true,
+        DefKind::Const | DefKind::AssocConst | DefKind::AnonConst | DefKind::InlineConst => true,
 
         DefKind::Struct
         | DefKind::Union
@@ -1210,7 +1210,6 @@ fn should_encode_const(def_kind: DefKind) -> bool {
         | DefKind::Closure
         | DefKind::Generator
         | DefKind::ConstParam
-        | DefKind::InlineConst
         | DefKind::AssocTy
         | DefKind::TyParam
         | DefKind::Trait
@@ -1437,8 +1436,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         debug!("EncodeContext::encode_info_for_trait_item({:?})", def_id);
         let tcx = self.tcx;
 
-        let impl_defaultness = tcx.impl_defaultness(def_id.expect_local());
-        self.tables.impl_defaultness.set_some(def_id.index, impl_defaultness);
+        let defaultness = tcx.defaultness(def_id.expect_local());
+        self.tables.defaultness.set_some(def_id.index, defaultness);
         let trait_item = tcx.associated_item(def_id);
         self.tables.assoc_container.set_some(def_id.index, trait_item.container);
 
@@ -1466,8 +1465,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         debug!("EncodeContext::encode_info_for_impl_item({:?})", def_id);
         let tcx = self.tcx;
 
-        let defaultness = self.tcx.impl_defaultness(def_id.expect_local());
-        self.tables.impl_defaultness.set_some(def_id.index, defaultness);
+        let defaultness = self.tcx.defaultness(def_id.expect_local());
+        self.tables.defaultness.set_some(def_id.index, defaultness);
         let impl_item = self.tcx.associated_item(def_id);
         self.tables.assoc_container.set_some(def_id.index, impl_item.container);
 
@@ -1653,7 +1652,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
                 );
             }
             hir::ItemKind::Impl(hir::Impl { defaultness, constness, .. }) => {
-                self.tables.impl_defaultness.set_some(def_id.index, *defaultness);
+                self.tables.defaultness.set_some(def_id.index, *defaultness);
                 self.tables.constness.set_some(def_id.index, *constness);
                 self.tables.impl_polarity.set_some(def_id.index, self.tcx.impl_polarity(def_id));
 
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 2da888f4468..e9b67c70c47 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -420,7 +420,7 @@ define_tables! {
     impl_parent: Table<DefIndex, RawDefId>,
     impl_polarity: Table<DefIndex, ty::ImplPolarity>,
     constness: Table<DefIndex, hir::Constness>,
-    impl_defaultness: Table<DefIndex, hir::Defaultness>,
+    defaultness: Table<DefIndex, hir::Defaultness>,
     // FIXME(eddyb) perhaps compute this on the fly if cheap enough?
     coerce_unsized_info: Table<DefIndex, LazyValue<ty::adjustment::CoerceUnsizedInfo>>,
     mir_const_qualif: Table<DefIndex, LazyValue<mir::ConstQualifs>>,
diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl
index 3d581daa925..bb7147ac80f 100644
--- a/compiler/rustc_middle/messages.ftl
+++ b/compiler/rustc_middle/messages.ftl
@@ -1,3 +1,38 @@
+middle_adjust_for_foreign_abi_error =
+    target architecture {$arch} does not support `extern {$abi}` ABI
+
+middle_assert_async_resume_after_panic = `async fn` resumed after panicking
+
+middle_assert_async_resume_after_return = `async fn` resumed after completion
+
+middle_assert_divide_by_zero =
+    attempt to divide `{$val}` by zero
+
+middle_assert_generator_resume_after_panic = generator resumed after panicking
+
+middle_assert_generator_resume_after_return = generator resumed after completion
+
+middle_assert_misaligned_ptr_deref =
+    misaligned pointer dereference: address must be a multiple of {$required} but is {$found}
+
+middle_assert_op_overflow =
+    attempt to compute `{$left} {$op} {$right}`, which would overflow
+
+middle_assert_overflow_neg =
+    attempt to negate `{$val}`, which would overflow
+
+middle_assert_remainder_by_zero =
+    attempt to calculate the remainder of `{$val}` with a divisor of zero
+
+middle_assert_shl_overflow =
+    attempt to shift left by `{$val}`, which would overflow
+
+middle_assert_shr_overflow =
+    attempt to shift right by `{$val}`, which would overflow
+
+middle_bounds_check =
+    index out of bounds: the length is {$len} but the index is {$index}
+
 middle_cannot_be_normalized =
     unable to determine layout for `{$ty}` because `{$failure_ty}` cannot be normalized
 
diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs
index 046186d274c..57b2de84b47 100644
--- a/compiler/rustc_middle/src/error.rs
+++ b/compiler/rustc_middle/src/error.rs
@@ -1,3 +1,7 @@
+use std::borrow::Cow;
+use std::fmt;
+
+use rustc_errors::{DiagnosticArgValue, DiagnosticMessage};
 use rustc_macros::Diagnostic;
 use rustc_span::{Span, Symbol};
 
@@ -88,3 +92,54 @@ pub(super) struct ConstNotUsedTraitAlias {
     #[primary_span]
     pub span: Span,
 }
+
+pub struct CustomSubdiagnostic<'a> {
+    pub msg: fn() -> DiagnosticMessage,
+    pub add_args:
+        Box<dyn FnOnce(&mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) + 'a>,
+}
+
+impl<'a> CustomSubdiagnostic<'a> {
+    pub fn label(x: fn() -> DiagnosticMessage) -> Self {
+        Self::label_and_then(x, |_| {})
+    }
+    pub fn label_and_then<
+        F: FnOnce(&mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) + 'a,
+    >(
+        msg: fn() -> DiagnosticMessage,
+        f: F,
+    ) -> Self {
+        Self { msg, add_args: Box::new(move |x| f(x)) }
+    }
+}
+
+impl fmt::Debug for CustomSubdiagnostic<'_> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("CustomSubdiagnostic").finish_non_exhaustive()
+    }
+}
+
+#[derive(Diagnostic)]
+pub enum LayoutError<'tcx> {
+    #[diag(middle_unknown_layout)]
+    Unknown { ty: Ty<'tcx> },
+
+    #[diag(middle_values_too_big)]
+    Overflow { ty: Ty<'tcx> },
+
+    #[diag(middle_cannot_be_normalized)]
+    NormalizationFailure { ty: Ty<'tcx>, failure_ty: String },
+
+    #[diag(middle_cycle)]
+    Cycle,
+}
+
+#[derive(Diagnostic)]
+#[diag(middle_adjust_for_foreign_abi_error)]
+pub struct UnsupportedFnAbi {
+    pub arch: Symbol,
+    pub abi: &'static str,
+}
+
+/// Used by `rustc_const_eval`
+pub use crate::fluent_generated::middle_adjust_for_foreign_abi_error;
diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs
index d1ddc8fc1fd..5f2eb890c4e 100644
--- a/compiler/rustc_middle/src/hir/map/mod.rs
+++ b/compiler/rustc_middle/src/hir/map/mod.rs
@@ -44,6 +44,7 @@ pub fn associated_body(node: Node<'_>) -> Option<(LocalDefId, BodyId)> {
         }
 
         Node::AnonConst(constant) => Some((constant.def_id, constant.body)),
+        Node::ConstBlock(constant) => Some((constant.def_id, constant.body)),
 
         _ => None,
     }
@@ -240,15 +241,8 @@ impl<'hir> Map<'hir> {
                     None => bug!("constructor node without a constructor"),
                 }
             }
-            Node::AnonConst(_) => {
-                let inline = match self.find_parent(hir_id) {
-                    Some(Node::Expr(&Expr {
-                        kind: ExprKind::ConstBlock(ref anon_const), ..
-                    })) if anon_const.hir_id == hir_id => true,
-                    _ => false,
-                };
-                if inline { DefKind::InlineConst } else { DefKind::AnonConst }
-            }
+            Node::AnonConst(_) => DefKind::AnonConst,
+            Node::ConstBlock(_) => DefKind::InlineConst,
             Node::Field(_) => DefKind::Field,
             Node::Expr(expr) => match expr.kind {
                 ExprKind::Closure(Closure { movability: None, .. }) => DefKind::Closure,
@@ -1060,6 +1054,7 @@ impl<'hir> Map<'hir> {
             Node::Variant(variant) => variant.span,
             Node::Field(field) => field.span,
             Node::AnonConst(constant) => self.body(constant.body).value.span,
+            Node::ConstBlock(constant) => self.body(constant.body).value.span,
             Node::Expr(expr) => expr.span,
             Node::ExprField(field) => field.span,
             Node::Stmt(stmt) => stmt.span,
@@ -1289,6 +1284,7 @@ fn hir_id_to_string(map: Map<'_>, id: HirId) -> String {
             format!("{id} (field `{}` in {})", field.ident, path_str(field.def_id))
         }
         Some(Node::AnonConst(_)) => node_str("const"),
+        Some(Node::ConstBlock(_)) => node_str("const"),
         Some(Node::Expr(_)) => node_str("expr"),
         Some(Node::ExprField(_)) => node_str("expr field"),
         Some(Node::Stmt(_)) => node_str("stmt"),
@@ -1434,6 +1430,11 @@ impl<'hir> Visitor<'hir> for ItemCollector<'hir> {
         intravisit::walk_anon_const(self, c)
     }
 
+    fn visit_inline_const(&mut self, c: &'hir ConstBlock) {
+        self.body_owners.push(c.def_id);
+        intravisit::walk_inline_const(self, c)
+    }
+
     fn visit_expr(&mut self, ex: &'hir Expr<'hir>) {
         if let ExprKind::Closure(closure) = ex.kind {
             self.body_owners.push(closure.def_id);
diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs
index 22ee2a8c5e5..0d6c2eba06c 100644
--- a/compiler/rustc_middle/src/lib.rs
+++ b/compiler/rustc_middle/src/lib.rs
@@ -48,6 +48,7 @@
 #![feature(associated_type_bounds)]
 #![feature(rustc_attrs)]
 #![feature(control_flow_enum)]
+#![feature(trait_upcasting)]
 #![feature(trusted_step)]
 #![feature(try_blocks)]
 #![feature(try_reserve_kind)]
@@ -86,7 +87,7 @@ mod macros;
 
 #[macro_use]
 pub mod arena;
-pub(crate) mod error;
+pub mod error;
 pub mod hir;
 pub mod infer;
 pub mod lint;
diff --git a/compiler/rustc_middle/src/middle/limits.rs b/compiler/rustc_middle/src/middle/limits.rs
index bd859d4d61b..d4f023958d6 100644
--- a/compiler/rustc_middle/src/middle/limits.rs
+++ b/compiler/rustc_middle/src/middle/limits.rs
@@ -1,8 +1,7 @@
 //! Registering limits:
 //! * recursion_limit,
-//! * move_size_limit,
-//! * type_length_limit, and
-//! * const_eval_limit
+//! * move_size_limit, and
+//! * type_length_limit
 //!
 //! There are various parts of the compiler that must impose arbitrary limits
 //! on how deeply they recurse to prevent stack overflow. Users can override
@@ -34,12 +33,6 @@ pub fn provide(providers: &mut Providers) {
             sym::type_length_limit,
             1048576,
         ),
-        const_eval_limit: get_limit(
-            tcx.hir().krate_attrs(),
-            tcx.sess,
-            sym::const_eval_limit,
-            2_000_000,
-        ),
     }
 }
 
diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs
index 1a8e4826447..b8030d9db13 100644
--- a/compiler/rustc_middle/src/mir/interpret/allocation.rs
+++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs
@@ -296,25 +296,13 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
         Allocation::from_bytes(slice, Align::ONE, Mutability::Not)
     }
 
-    /// Try to create an Allocation of `size` bytes, failing if there is not enough memory
-    /// available to the compiler to do so.
-    ///
-    /// If `panic_on_fail` is true, this will never return `Err`.
-    pub fn uninit<'tcx>(size: Size, align: Align, panic_on_fail: bool) -> InterpResult<'tcx, Self> {
-        let bytes = Bytes::zeroed(size, align).ok_or_else(|| {
-            // This results in an error that can happen non-deterministically, since the memory
-            // available to the compiler can change between runs. Normally queries are always
-            // deterministic. However, we can be non-deterministic here because all uses of const
-            // evaluation (including ConstProp!) will make compilation fail (via hard error
-            // or ICE) upon encountering a `MemoryExhausted` error.
-            if panic_on_fail {
-                panic!("Allocation::uninit called with panic_on_fail had allocation failure")
-            }
-            ty::tls::with(|tcx| {
-                tcx.sess.delay_span_bug(DUMMY_SP, "exhausted memory during interpretation")
-            });
-            InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted)
-        })?;
+    fn uninit_inner<R>(size: Size, align: Align, fail: impl FnOnce() -> R) -> Result<Self, R> {
+        // This results in an error that can happen non-deterministically, since the memory
+        // available to the compiler can change between runs. Normally queries are always
+        // deterministic. However, we can be non-deterministic here because all uses of const
+        // evaluation (including ConstProp!) will make compilation fail (via hard error
+        // or ICE) upon encountering a `MemoryExhausted` error.
+        let bytes = Bytes::zeroed(size, align).ok_or_else(fail)?;
 
         Ok(Allocation {
             bytes,
@@ -325,6 +313,28 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
             extra: (),
         })
     }
+
+    /// Try to create an Allocation of `size` bytes, failing if there is not enough memory
+    /// available to the compiler to do so.
+    pub fn try_uninit<'tcx>(size: Size, align: Align) -> InterpResult<'tcx, Self> {
+        Self::uninit_inner(size, align, || {
+            ty::tls::with(|tcx| {
+                tcx.sess.delay_span_bug(DUMMY_SP, "exhausted memory during interpretation")
+            });
+            InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted).into()
+        })
+    }
+
+    /// Try to create an Allocation of `size` bytes, panics if there is not enough memory
+    /// available to the compiler to do so.
+    pub fn uninit(size: Size, align: Align) -> Self {
+        match Self::uninit_inner(size, align, || {
+            panic!("Allocation::uninit called with panic_on_fail had allocation failure");
+        }) {
+            Ok(x) => x,
+            Err(x) => x,
+        }
+    }
 }
 
 impl<Bytes: AllocBytes> Allocation<AllocId, (), Bytes> {
diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs
index 055d8e9a352..2435bc59ec0 100644
--- a/compiler/rustc_middle/src/mir/interpret/error.rs
+++ b/compiler/rustc_middle/src/mir/interpret/error.rs
@@ -5,11 +5,15 @@ use crate::query::TyCtxtAt;
 use crate::ty::{layout, tls, Ty, ValTree};
 
 use rustc_data_structures::sync::Lock;
-use rustc_errors::{pluralize, struct_span_err, DiagnosticBuilder, ErrorGuaranteed};
+use rustc_errors::{
+    struct_span_err, DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed,
+    IntoDiagnosticArg,
+};
 use rustc_macros::HashStable;
 use rustc_session::CtfeBacktrace;
 use rustc_span::def_id::DefId;
-use rustc_target::abi::{call, Align, Size};
+use rustc_target::abi::{call, Align, Size, WrappingRange};
+use std::borrow::Cow;
 use std::{any::Any, backtrace::Backtrace, fmt};
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
@@ -91,21 +95,54 @@ pub struct InterpErrorInfo<'tcx>(Box<InterpErrorInfoInner<'tcx>>);
 #[derive(Debug)]
 struct InterpErrorInfoInner<'tcx> {
     kind: InterpError<'tcx>,
+    backtrace: InterpErrorBacktrace,
+}
+
+#[derive(Debug)]
+pub struct InterpErrorBacktrace {
     backtrace: Option<Box<Backtrace>>,
 }
 
-impl fmt::Display for InterpErrorInfo<'_> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}", self.0.kind)
+impl InterpErrorBacktrace {
+    pub fn new() -> InterpErrorBacktrace {
+        let capture_backtrace = tls::with_opt(|tcx| {
+            if let Some(tcx) = tcx {
+                *Lock::borrow(&tcx.sess.ctfe_backtrace)
+            } else {
+                CtfeBacktrace::Disabled
+            }
+        });
+
+        let backtrace = match capture_backtrace {
+            CtfeBacktrace::Disabled => None,
+            CtfeBacktrace::Capture => Some(Box::new(Backtrace::force_capture())),
+            CtfeBacktrace::Immediate => {
+                // Print it now.
+                let backtrace = Backtrace::force_capture();
+                print_backtrace(&backtrace);
+                None
+            }
+        };
+
+        InterpErrorBacktrace { backtrace }
     }
-}
 
-impl<'tcx> InterpErrorInfo<'tcx> {
     pub fn print_backtrace(&self) {
-        if let Some(backtrace) = self.0.backtrace.as_ref() {
+        if let Some(backtrace) = self.backtrace.as_ref() {
             print_backtrace(backtrace);
         }
     }
+}
+
+impl<'tcx> InterpErrorInfo<'tcx> {
+    pub fn from_parts(kind: InterpError<'tcx>, backtrace: InterpErrorBacktrace) -> Self {
+        Self(Box::new(InterpErrorInfoInner { kind, backtrace }))
+    }
+
+    pub fn into_parts(self) -> (InterpError<'tcx>, InterpErrorBacktrace) {
+        let InterpErrorInfo(box InterpErrorInfoInner { kind, backtrace }) = self;
+        (kind, backtrace)
+    }
 
     pub fn into_kind(self) -> InterpError<'tcx> {
         let InterpErrorInfo(box InterpErrorInfoInner { kind, .. }) = self;
@@ -130,32 +167,17 @@ impl From<ErrorGuaranteed> for InterpErrorInfo<'_> {
 
 impl<'tcx> From<InterpError<'tcx>> for InterpErrorInfo<'tcx> {
     fn from(kind: InterpError<'tcx>) -> Self {
-        let capture_backtrace = tls::with_opt(|tcx| {
-            if let Some(tcx) = tcx {
-                *Lock::borrow(&tcx.sess.ctfe_backtrace)
-            } else {
-                CtfeBacktrace::Disabled
-            }
-        });
-
-        let backtrace = match capture_backtrace {
-            CtfeBacktrace::Disabled => None,
-            CtfeBacktrace::Capture => Some(Box::new(Backtrace::force_capture())),
-            CtfeBacktrace::Immediate => {
-                // Print it now.
-                let backtrace = Backtrace::force_capture();
-                print_backtrace(&backtrace);
-                None
-            }
-        };
-
-        InterpErrorInfo(Box::new(InterpErrorInfoInner { kind, backtrace }))
+        InterpErrorInfo(Box::new(InterpErrorInfoInner {
+            kind,
+            backtrace: InterpErrorBacktrace::new(),
+        }))
     }
 }
 
 /// Error information for when the program we executed turned out not to actually be a valid
 /// program. This cannot happen in stand-alone Miri, but it can happen during CTFE/ConstProp
 /// where we work on generic code or execution does not have all information available.
+#[derive(Debug)]
 pub enum InvalidProgramInfo<'tcx> {
     /// Resolution can fail if we are in a too generic context.
     TooGeneric,
@@ -174,25 +196,6 @@ pub enum InvalidProgramInfo<'tcx> {
     UninitUnsizedLocal,
 }
 
-impl fmt::Display for InvalidProgramInfo<'_> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use InvalidProgramInfo::*;
-        match self {
-            TooGeneric => write!(f, "encountered overly generic constant"),
-            AlreadyReported(_) => {
-                write!(
-                    f,
-                    "an error has already been reported elsewhere (this should not usually be printed)"
-                )
-            }
-            Layout(ref err) => write!(f, "{err}"),
-            FnAbiAdjustForForeignAbi(ref err) => write!(f, "{err}"),
-            SizeOfUnsizedType(ty) => write!(f, "size_of called on unsized type `{ty}`"),
-            UninitUnsizedLocal => write!(f, "unsized local is used while uninitialized"),
-        }
-    }
-}
-
 /// Details of why a pointer had to be in-bounds.
 #[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)]
 pub enum CheckInAllocMsg {
@@ -208,26 +211,25 @@ pub enum CheckInAllocMsg {
     InboundsTest,
 }
 
-impl fmt::Display for CheckInAllocMsg {
-    /// When this is printed as an error the context looks like this:
-    /// "{msg}{pointer} is a dangling pointer".
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(
-            f,
-            "{}",
-            match *self {
-                CheckInAllocMsg::DerefTest => "dereferencing pointer failed: ",
-                CheckInAllocMsg::MemoryAccessTest => "memory access failed: ",
-                CheckInAllocMsg::PointerArithmeticTest => "out-of-bounds pointer arithmetic: ",
-                CheckInAllocMsg::OffsetFromTest => "out-of-bounds offset_from: ",
-                CheckInAllocMsg::InboundsTest => "out-of-bounds pointer use: ",
-            }
-        )
+#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)]
+pub enum InvalidMetaKind {
+    /// Size of a `[T]` is too big
+    SliceTooBig,
+    /// Size of a DST is too big
+    TooBig,
+}
+
+impl IntoDiagnosticArg for InvalidMetaKind {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Borrowed(match self {
+            InvalidMetaKind::SliceTooBig => "slice_too_big",
+            InvalidMetaKind::TooBig => "too_big",
+        }))
     }
 }
 
 /// Details of an access to uninitialized bytes where it is not allowed.
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
 pub struct UninitBytesAccess {
     /// Range of the original memory access.
     pub access: AllocRange,
@@ -242,17 +244,32 @@ pub struct ScalarSizeMismatch {
     pub data_size: u64,
 }
 
+macro_rules! impl_into_diagnostic_arg_through_debug {
+    ($($ty:ty),*$(,)?) => {$(
+        impl IntoDiagnosticArg for $ty {
+            fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+                DiagnosticArgValue::Str(Cow::Owned(format!("{self:?}")))
+            }
+        }
+    )*}
+}
+
+// These types have nice `Debug` output so we can just use them in diagnostics.
+impl_into_diagnostic_arg_through_debug! {
+    AllocId,
+    Pointer,
+    AllocRange,
+}
+
 /// Error information for when the program caused Undefined Behavior.
-pub enum UndefinedBehaviorInfo {
-    /// Free-form case. Only for errors that are never caught!
+#[derive(Debug)]
+pub enum UndefinedBehaviorInfo<'a> {
+    /// Free-form case. Only for errors that are never caught! Used by miri
     Ub(String),
     /// Unreachable code was executed.
     Unreachable,
     /// A slice/array index projection went out-of-bounds.
-    BoundsCheckFailed {
-        len: u64,
-        index: u64,
-    },
+    BoundsCheckFailed { len: u64, index: u64 },
     /// Something was divided by 0 (x / 0).
     DivisionByZero,
     /// Something was "remainded" by 0 (x % 0).
@@ -263,8 +280,8 @@ pub enum UndefinedBehaviorInfo {
     RemainderOverflow,
     /// Overflowing inbounds pointer arithmetic.
     PointerArithOverflow,
-    /// Invalid metadata in a wide pointer (using `str` to avoid allocations).
-    InvalidMeta(&'static str),
+    /// Invalid metadata in a wide pointer
+    InvalidMeta(InvalidMetaKind),
     /// Reading a C string that does not end within its allocation.
     UnterminatedCString(Pointer),
     /// Dereferencing a dangling pointer after it got freed.
@@ -281,25 +298,13 @@ pub enum UndefinedBehaviorInfo {
     /// Using an integer as a pointer in the wrong way.
     DanglingIntPointer(u64, CheckInAllocMsg),
     /// Used a pointer with bad alignment.
-    AlignmentCheckFailed {
-        required: Align,
-        has: Align,
-    },
+    AlignmentCheckFailed { required: Align, has: Align },
     /// Writing to read-only memory.
     WriteToReadOnly(AllocId),
-    // Trying to access the data behind a function pointer.
+    /// Trying to access the data behind a function pointer.
     DerefFunctionPointer(AllocId),
-    // Trying to access the data behind a vtable pointer.
+    /// Trying to access the data behind a vtable pointer.
     DerefVTablePointer(AllocId),
-    /// The value validity check found a problem.
-    /// Should only be thrown by `validity.rs` and always point out which part of the value
-    /// is the problem.
-    ValidationFailure {
-        /// The "path" to the value in question, e.g. `.0[5].field` for a struct
-        /// field in the 6th element of an array that is the first element of a tuple.
-        path: Option<String>,
-        msg: String,
-    },
     /// Using a non-boolean `u8` as bool.
     InvalidBool(u8),
     /// Using a non-character `u32` as character.
@@ -320,110 +325,100 @@ pub enum UndefinedBehaviorInfo {
     ScalarSizeMismatch(ScalarSizeMismatch),
     /// A discriminant of an uninhabited enum variant is written.
     UninhabitedEnumVariantWritten,
+    /// Validation error.
+    Validation(ValidationErrorInfo<'a>),
+    // FIXME(fee1-dead) these should all be actual variants of the enum instead of dynamically
+    // dispatched
+    /// A custom (free-form) error, created by `err_ub_custom!`.
+    Custom(crate::error::CustomSubdiagnostic<'a>),
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum PointerKind {
+    Ref,
+    Box,
+}
+
+impl IntoDiagnosticArg for PointerKind {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(
+            match self {
+                Self::Ref => "ref",
+                Self::Box => "box",
+            }
+            .into(),
+        )
+    }
 }
 
-impl fmt::Display for UndefinedBehaviorInfo {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use UndefinedBehaviorInfo::*;
-        match self {
-            Ub(msg) => write!(f, "{msg}"),
-            Unreachable => write!(f, "entering unreachable code"),
-            BoundsCheckFailed { ref len, ref index } => {
-                write!(f, "indexing out of bounds: the len is {len} but the index is {index}")
-            }
-            DivisionByZero => write!(f, "dividing by zero"),
-            RemainderByZero => write!(f, "calculating the remainder with a divisor of zero"),
-            DivisionOverflow => write!(f, "overflow in signed division (dividing MIN by -1)"),
-            RemainderOverflow => write!(f, "overflow in signed remainder (dividing MIN by -1)"),
-            PointerArithOverflow => write!(f, "overflowing in-bounds pointer arithmetic"),
-            InvalidMeta(msg) => write!(f, "invalid metadata in wide pointer: {msg}"),
-            UnterminatedCString(p) => write!(
-                f,
-                "reading a null-terminated string starting at {p:?} with no null found before end of allocation",
-            ),
-            PointerUseAfterFree(a) => {
-                write!(f, "pointer to {a:?} was dereferenced after this allocation got freed")
-            }
-            PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size: Size::ZERO, msg } => {
-                write!(
-                    f,
-                    "{msg}{alloc_id:?} has size {alloc_size}, so pointer at offset {ptr_offset} is out-of-bounds",
-                    alloc_size = alloc_size.bytes(),
-                )
-            }
-            PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => write!(
-                f,
-                "{msg}{alloc_id:?} has size {alloc_size}, so pointer to {ptr_size} byte{ptr_size_p} starting at offset {ptr_offset} is out-of-bounds",
-                alloc_size = alloc_size.bytes(),
-                ptr_size = ptr_size.bytes(),
-                ptr_size_p = pluralize!(ptr_size.bytes()),
-            ),
-            DanglingIntPointer(i, msg) => {
-                write!(
-                    f,
-                    "{msg}{pointer} is a dangling pointer (it has no provenance)",
-                    pointer = Pointer::<Option<AllocId>>::from_addr_invalid(*i),
-                )
-            }
-            AlignmentCheckFailed { required, has } => write!(
-                f,
-                "accessing memory with alignment {has}, but alignment {required} is required",
-                has = has.bytes(),
-                required = required.bytes()
-            ),
-            WriteToReadOnly(a) => write!(f, "writing to {a:?} which is read-only"),
-            DerefFunctionPointer(a) => write!(f, "accessing {a:?} which contains a function"),
-            DerefVTablePointer(a) => write!(f, "accessing {a:?} which contains a vtable"),
-            ValidationFailure { path: None, msg } => {
-                write!(f, "constructing invalid value: {msg}")
-            }
-            ValidationFailure { path: Some(path), msg } => {
-                write!(f, "constructing invalid value at {path}: {msg}")
-            }
-            InvalidBool(b) => {
-                write!(f, "interpreting an invalid 8-bit value as a bool: 0x{b:02x}")
-            }
-            InvalidChar(c) => {
-                write!(f, "interpreting an invalid 32-bit value as a char: 0x{c:08x}")
-            }
-            InvalidTag(val) => write!(f, "enum value has invalid tag: {val:x}"),
-            InvalidFunctionPointer(p) => {
-                write!(f, "using {p:?} as function pointer but it does not point to a function")
-            }
-            InvalidVTablePointer(p) => {
-                write!(f, "using {p:?} as vtable pointer but it does not point to a vtable")
-            }
-            InvalidStr(err) => write!(f, "this string is not valid UTF-8: {err}"),
-            InvalidUninitBytes(Some((alloc, info))) => write!(
-                f,
-                "reading memory at {alloc:?}{access:?}, \
-                 but memory is uninitialized at {uninit:?}, \
-                 and this operation requires initialized memory",
-                access = info.access,
-                uninit = info.uninit,
-            ),
-            InvalidUninitBytes(None) => write!(
-                f,
-                "using uninitialized data, but this operation requires initialized memory"
-            ),
-            DeadLocal => write!(f, "accessing a dead local variable"),
-            ScalarSizeMismatch(self::ScalarSizeMismatch { target_size, data_size }) => write!(
-                f,
-                "scalar size mismatch: expected {target_size} bytes but got {data_size} bytes instead",
-            ),
-            UninhabitedEnumVariantWritten => {
-                write!(f, "writing discriminant of an uninhabited enum")
-            }
+#[derive(Debug)]
+pub struct ValidationErrorInfo<'tcx> {
+    pub path: Option<String>,
+    pub kind: ValidationErrorKind<'tcx>,
+}
+
+#[derive(Debug)]
+pub enum ExpectedKind {
+    Reference,
+    Box,
+    RawPtr,
+    InitScalar,
+    Bool,
+    Char,
+    Float,
+    Int,
+    FnPtr,
+}
+
+impl From<PointerKind> for ExpectedKind {
+    fn from(x: PointerKind) -> ExpectedKind {
+        match x {
+            PointerKind::Box => ExpectedKind::Box,
+            PointerKind::Ref => ExpectedKind::Reference,
         }
     }
 }
 
+#[derive(Debug)]
+pub enum ValidationErrorKind<'tcx> {
+    PtrToUninhabited { ptr_kind: PointerKind, ty: Ty<'tcx> },
+    PtrToStatic { ptr_kind: PointerKind },
+    PtrToMut { ptr_kind: PointerKind },
+    ExpectedNonPtr { value: String },
+    MutableRefInConst,
+    NullFnPtr,
+    NeverVal,
+    NullablePtrOutOfRange { range: WrappingRange, max_value: u128 },
+    PtrOutOfRange { range: WrappingRange, max_value: u128 },
+    OutOfRange { value: String, range: WrappingRange, max_value: u128 },
+    UnsafeCell,
+    UninhabitedVal { ty: Ty<'tcx> },
+    InvalidEnumTag { value: String },
+    UninitEnumTag,
+    UninitStr,
+    Uninit { expected: ExpectedKind },
+    UninitVal,
+    InvalidVTablePtr { value: String },
+    InvalidMetaSliceTooLarge { ptr_kind: PointerKind },
+    InvalidMetaTooLarge { ptr_kind: PointerKind },
+    UnalignedPtr { ptr_kind: PointerKind, required_bytes: u64, found_bytes: u64 },
+    NullPtr { ptr_kind: PointerKind },
+    DanglingPtrNoProvenance { ptr_kind: PointerKind, pointer: String },
+    DanglingPtrOutOfBounds { ptr_kind: PointerKind },
+    DanglingPtrUseAfterFree { ptr_kind: PointerKind },
+    InvalidBool { value: String },
+    InvalidChar { value: String },
+    InvalidFnPtr { value: String },
+}
+
 /// Error information for when the program did something that might (or might not) be correct
 /// to do according to the Rust spec, but due to limitations in the interpreter, the
 /// operation could not be carried out. These limitations can differ between CTFE and the
 /// Miri engine, e.g., CTFE does not support dereferencing pointers at integral addresses.
+#[derive(Debug)]
 pub enum UnsupportedOpInfo {
     /// Free-form case. Only for errors that are never caught!
+    // FIXME still use translatable diagnostics
     Unsupported(String),
     //
     // The variants below are only reachable from CTFE/const prop, miri will never emit them.
@@ -442,83 +437,42 @@ pub enum UnsupportedOpInfo {
     ReadExternStatic(DefId),
 }
 
-impl fmt::Display for UnsupportedOpInfo {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use UnsupportedOpInfo::*;
-        match self {
-            Unsupported(ref msg) => write!(f, "{msg}"),
-            PartialPointerOverwrite(ptr) => {
-                write!(f, "unable to overwrite parts of a pointer in memory at {ptr:?}")
-            }
-            PartialPointerCopy(ptr) => {
-                write!(f, "unable to copy parts of a pointer from memory at {ptr:?}")
-            }
-            ReadPointerAsBytes => write!(f, "unable to turn pointer into raw bytes"),
-            ThreadLocalStatic(did) => write!(f, "cannot access thread local static ({did:?})"),
-            ReadExternStatic(did) => write!(f, "cannot read from extern static ({did:?})"),
-        }
-    }
-}
-
 /// Error information for when the program exhausted the resources granted to it
 /// by the interpreter.
+#[derive(Debug)]
 pub enum ResourceExhaustionInfo {
     /// The stack grew too big.
     StackFrameLimitReached,
-    /// The program ran for too long.
-    ///
-    /// The exact limit is set by the `const_eval_limit` attribute.
-    StepLimitReached,
     /// There is not enough memory (on the host) to perform an allocation.
     MemoryExhausted,
     /// The address space (of the target) is full.
     AddressSpaceFull,
 }
 
-impl fmt::Display for ResourceExhaustionInfo {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use ResourceExhaustionInfo::*;
-        match self {
-            StackFrameLimitReached => {
-                write!(f, "reached the configured maximum number of stack frames")
-            }
-            StepLimitReached => {
-                write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)")
-            }
-            MemoryExhausted => {
-                write!(f, "tried to allocate more memory than available to compiler")
-            }
-            AddressSpaceFull => {
-                write!(f, "there are no more free addresses in the address space")
-            }
-        }
-    }
-}
-
-/// A trait to work around not having trait object upcasting.
-pub trait AsAny: Any {
-    fn as_any(&self) -> &dyn Any;
-}
-impl<T: Any> AsAny for T {
-    #[inline(always)]
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-}
-
 /// A trait for machine-specific errors (or other "machine stop" conditions).
-pub trait MachineStopType: AsAny + fmt::Display + Send {}
+pub trait MachineStopType: Any + fmt::Debug + Send {
+    /// The diagnostic message for this error
+    fn diagnostic_message(&self) -> DiagnosticMessage;
+    /// Add diagnostic arguments by passing name and value pairs to `adder`, which are passed to
+    /// fluent for formatting the translated diagnostic message.
+    fn add_args(
+        self: Box<Self>,
+        adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>),
+    );
+}
 
 impl dyn MachineStopType {
     #[inline(always)]
     pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
-        self.as_any().downcast_ref()
+        let x: &dyn Any = self;
+        x.downcast_ref()
     }
 }
 
+#[derive(Debug)]
 pub enum InterpError<'tcx> {
     /// The program caused undefined behavior.
-    UndefinedBehavior(UndefinedBehaviorInfo),
+    UndefinedBehavior(UndefinedBehaviorInfo<'tcx>),
     /// The program did something the interpreter does not support (some of these *might* be UB
     /// but the interpreter is not sure).
     Unsupported(UnsupportedOpInfo),
@@ -534,26 +488,6 @@ pub enum InterpError<'tcx> {
 
 pub type InterpResult<'tcx, T = ()> = Result<T, InterpErrorInfo<'tcx>>;
 
-impl fmt::Display for InterpError<'_> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use InterpError::*;
-        match *self {
-            Unsupported(ref msg) => write!(f, "{msg}"),
-            InvalidProgram(ref msg) => write!(f, "{msg}"),
-            UndefinedBehavior(ref msg) => write!(f, "{msg}"),
-            ResourceExhaustion(ref msg) => write!(f, "{msg}"),
-            MachineStop(ref msg) => write!(f, "{msg}"),
-        }
-    }
-}
-
-// Forward `Debug` to `Display`, so it does not look awful.
-impl fmt::Debug for InterpError<'_> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        fmt::Display::fmt(self, f)
-    }
-}
-
 impl InterpError<'_> {
     /// Some errors do string formatting even if the error is never printed.
     /// To avoid performance issues, there are places where we want to be sure to never raise these formatting errors,
@@ -562,7 +496,7 @@ impl InterpError<'_> {
         matches!(
             self,
             InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_))
-                | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationFailure { .. })
+                | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Validation { .. })
                 | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
         )
     }
diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs
index 3620385fab1..2d2cfee1b21 100644
--- a/compiler/rustc_middle/src/mir/interpret/mod.rs
+++ b/compiler/rustc_middle/src/mir/interpret/mod.rs
@@ -89,6 +89,30 @@ macro_rules! throw_machine_stop {
     ($($tt:tt)*) => { do yeet err_machine_stop!($($tt)*) };
 }
 
+#[macro_export]
+macro_rules! err_ub_custom {
+    ($msg:expr $(, $($name:ident = $value:expr),* $(,)?)?) => {{
+        $(
+            let ($($name,)*) = ($($value,)*);
+        )?
+        err_ub!(Custom(
+            rustc_middle::error::CustomSubdiagnostic {
+                msg: || $msg,
+                add_args: Box::new(move |mut set_arg| {
+                    $($(
+                        set_arg(stringify!($name).into(), rustc_errors::IntoDiagnosticArg::into_diagnostic_arg($name));
+                    )*)?
+                })
+            }
+        ))
+    }};
+}
+
+#[macro_export]
+macro_rules! throw_ub_custom {
+    ($($tt:tt)*) => { do yeet err_ub_custom!($($tt)*) };
+}
+
 mod allocation;
 mod error;
 mod pointer;
@@ -119,9 +143,10 @@ use crate::ty::{self, Instance, Ty, TyCtxt};
 
 pub use self::error::{
     struct_error, CheckInAllocMsg, ErrorHandled, EvalToAllocationRawResult, EvalToConstValueResult,
-    EvalToValTreeResult, InterpError, InterpErrorInfo, InterpResult, InvalidProgramInfo,
-    MachineStopType, ReportedErrorInfo, ResourceExhaustionInfo, ScalarSizeMismatch,
-    UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo,
+    EvalToValTreeResult, ExpectedKind, InterpError, InterpErrorInfo, InterpResult, InvalidMetaKind,
+    InvalidProgramInfo, MachineStopType, PointerKind, ReportedErrorInfo, ResourceExhaustionInfo,
+    ScalarSizeMismatch, UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo,
+    ValidationErrorInfo, ValidationErrorKind,
 };
 
 pub use self::value::{get_slice_bytes, ConstAlloc, ConstValue, Scalar};
diff --git a/compiler/rustc_middle/src/mir/interpret/value.rs b/compiler/rustc_middle/src/mir/interpret/value.rs
index 36dbbe4bf77..91caf9db336 100644
--- a/compiler/rustc_middle/src/mir/interpret/value.rs
+++ b/compiler/rustc_middle/src/mir/interpret/value.rs
@@ -375,7 +375,8 @@ impl<'tcx, Prov: Provenance> Scalar<Prov> {
     #[inline(always)]
     #[cfg_attr(debug_assertions, track_caller)] // only in debug builds due to perf (see #98980)
     pub fn assert_bits(self, target_size: Size) -> u128 {
-        self.to_bits(target_size).unwrap()
+        self.to_bits(target_size)
+            .unwrap_or_else(|_| panic!("assertion failed: {self:?} fits {target_size:?}"))
     }
 
     pub fn to_bool(self) -> InterpResult<'tcx, bool> {
diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs
index 5c27bdec575..e929240bf30 100644
--- a/compiler/rustc_middle/src/mir/mod.rs
+++ b/compiler/rustc_middle/src/mir/mod.rs
@@ -15,7 +15,7 @@ use crate::ty::{AdtDef, InstanceDef, ScalarInt, UserTypeAnnotationIndex};
 use crate::ty::{GenericArg, InternalSubsts, SubstsRef};
 
 use rustc_data_structures::captures::Captures;
-use rustc_errors::ErrorGuaranteed;
+use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, ErrorGuaranteed, IntoDiagnosticArg};
 use rustc_hir::def::{CtorKind, Namespace};
 use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
 use rustc_hir::{self, GeneratorKind, ImplicitSelfKind};
@@ -1371,55 +1371,61 @@ impl<O> AssertKind<O> {
             _ => write!(f, "\"{}\"", self.description()),
         }
     }
-}
 
-impl<O: fmt::Debug> fmt::Debug for AssertKind<O> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    pub fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
         use AssertKind::*;
+
         match self {
-            BoundsCheck { ref len, ref index } => write!(
-                f,
-                "index out of bounds: the length is {:?} but the index is {:?}",
-                len, index
-            ),
-            OverflowNeg(op) => write!(f, "attempt to negate `{:#?}`, which would overflow", op),
-            DivisionByZero(op) => write!(f, "attempt to divide `{:#?}` by zero", op),
-            RemainderByZero(op) => write!(
-                f,
-                "attempt to calculate the remainder of `{:#?}` with a divisor of zero",
-                op
-            ),
-            Overflow(BinOp::Add, l, r) => {
-                write!(f, "attempt to compute `{:#?} + {:#?}`, which would overflow", l, r)
-            }
-            Overflow(BinOp::Sub, l, r) => {
-                write!(f, "attempt to compute `{:#?} - {:#?}`, which would overflow", l, r)
-            }
-            Overflow(BinOp::Mul, l, r) => {
-                write!(f, "attempt to compute `{:#?} * {:#?}`, which would overflow", l, r)
-            }
-            Overflow(BinOp::Div, l, r) => {
-                write!(f, "attempt to compute `{:#?} / {:#?}`, which would overflow", l, r)
+            BoundsCheck { .. } => middle_bounds_check,
+            Overflow(BinOp::Shl, _, _) => middle_assert_shl_overflow,
+            Overflow(BinOp::Shr, _, _) => middle_assert_shr_overflow,
+            Overflow(_, _, _) => middle_assert_op_overflow,
+            OverflowNeg(_) => middle_assert_overflow_neg,
+            DivisionByZero(_) => middle_assert_divide_by_zero,
+            RemainderByZero(_) => middle_assert_remainder_by_zero,
+            ResumedAfterReturn(GeneratorKind::Async(_)) => middle_assert_async_resume_after_return,
+            ResumedAfterReturn(GeneratorKind::Gen) => middle_assert_generator_resume_after_return,
+            ResumedAfterPanic(GeneratorKind::Async(_)) => middle_assert_async_resume_after_panic,
+            ResumedAfterPanic(GeneratorKind::Gen) => middle_assert_generator_resume_after_panic,
+
+            MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref,
+        }
+    }
+
+    pub fn add_args(self, adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>))
+    where
+        O: fmt::Debug,
+    {
+        use AssertKind::*;
+
+        macro_rules! add {
+            ($name: expr, $value: expr) => {
+                adder($name.into(), $value.into_diagnostic_arg());
+            };
+        }
+
+        match self {
+            BoundsCheck { len, index } => {
+                add!("len", format!("{len:?}"));
+                add!("index", format!("{index:?}"));
             }
-            Overflow(BinOp::Rem, l, r) => write!(
-                f,
-                "attempt to compute the remainder of `{:#?} % {:#?}`, which would overflow",
-                l, r
-            ),
-            Overflow(BinOp::Shr, _, r) => {
-                write!(f, "attempt to shift right by `{:#?}`, which would overflow", r)
+            Overflow(BinOp::Shl | BinOp::Shr, _, val)
+            | DivisionByZero(val)
+            | RemainderByZero(val)
+            | OverflowNeg(val) => {
+                add!("val", format!("{val:#?}"));
             }
-            Overflow(BinOp::Shl, _, r) => {
-                write!(f, "attempt to shift left by `{:#?}`, which would overflow", r)
+            Overflow(binop, left, right) => {
+                add!("op", binop.to_hir_binop().as_str());
+                add!("left", format!("{left:#?}"));
+                add!("right", format!("{right:#?}"));
             }
+            ResumedAfterReturn(_) | ResumedAfterPanic(_) => {}
             MisalignedPointerDereference { required, found } => {
-                write!(
-                    f,
-                    "misaligned pointer dereference: address must be a multiple of {:?} but is {:?}",
-                    required, found
-                )
+                add!("required", format!("{required:#?}"));
+                add!("found", format!("{found:#?}"));
             }
-            _ => write!(f, "{}", self.description()),
         }
     }
 }
diff --git a/compiler/rustc_middle/src/mir/mono.rs b/compiler/rustc_middle/src/mir/mono.rs
index f31b343c947..1511e255235 100644
--- a/compiler/rustc_middle/src/mir/mono.rs
+++ b/compiler/rustc_middle/src/mir/mono.rs
@@ -291,10 +291,12 @@ impl<'tcx> CodegenUnit<'tcx> {
         self.primary = true;
     }
 
+    /// The order of these items is non-determinstic.
     pub fn items(&self) -> &FxHashMap<MonoItem<'tcx>, (Linkage, Visibility)> {
         &self.items
     }
 
+    /// The order of these items is non-determinstic.
     pub fn items_mut(&mut self) -> &mut FxHashMap<MonoItem<'tcx>, (Linkage, Visibility)> {
         &mut self.items
     }
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs
index 62c3d8cf239..8477722ea39 100644
--- a/compiler/rustc_middle/src/mir/pretty.rs
+++ b/compiler/rustc_middle/src/mir/pretty.rs
@@ -846,7 +846,7 @@ fn write_allocation_newline(
 /// The `prefix` argument allows callers to add an arbitrary prefix before each line (even if there
 /// is only one line). Note that your prefix should contain a trailing space as the lines are
 /// printed directly after it.
-fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>(
+pub fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>(
     tcx: TyCtxt<'tcx>,
     alloc: &Allocation<Prov, Extra, Bytes>,
     w: &mut dyn std::fmt::Write,
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index 94a1e9ced77..1a65f74f4fe 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -801,7 +801,8 @@ pub enum UnwindAction {
 }
 
 /// Information about an assertion failure.
-#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, TypeFoldable, TypeVisitable)]
+#[derive(Clone, Hash, HashStable, PartialEq, Debug)]
+#[derive(TyEncodable, TyDecodable, TypeFoldable, TypeVisitable)]
 pub enum AssertKind<O> {
     BoundsCheck { len: O, index: O },
     Overflow(BinOp, O, O),
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 0b31c9bbf81..45e0b498fac 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1475,8 +1475,9 @@ rustc_queries! {
         desc { "getting traits in scope at a block" }
     }
 
-    query impl_defaultness(def_id: DefId) -> hir::Defaultness {
-        desc { |tcx| "looking up whether `{}` is a default impl", tcx.def_path_str(def_id) }
+    /// Returns whether the impl or associated function has the `default` keyword.
+    query defaultness(def_id: DefId) -> hir::Defaultness {
+        desc { |tcx| "looking up whether `{}` has `default`", tcx.def_path_str(def_id) }
         separate_provide_extern
         feedable
     }
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index 0a903a76974..bf3872e81d4 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -445,6 +445,9 @@ pub enum ObligationCauseCode<'tcx> {
     /// Obligations to prove that a `std::ops::Drop` impl is not stronger than
     /// the ADT it's being implemented for.
     DropImpl,
+
+    /// Requirement for a `const N: Ty` to implement `Ty: ConstParamTy`
+    ConstParam(Ty<'tcx>),
 }
 
 /// The 'location' at which we try to perform HIR-based wf checking.
diff --git a/compiler/rustc_middle/src/traits/specialization_graph.rs b/compiler/rustc_middle/src/traits/specialization_graph.rs
index c016f722750..dc2cd203560 100644
--- a/compiler/rustc_middle/src/traits/specialization_graph.rs
+++ b/compiler/rustc_middle/src/traits/specialization_graph.rs
@@ -228,7 +228,7 @@ impl<'tcx> Ancestors<'tcx> {
             if let Some(item) = node.item(tcx, trait_item_def_id) {
                 if finalizing_node.is_none() {
                     let is_specializable = item.defaultness(tcx).is_default()
-                        || tcx.impl_defaultness(node.def_id()).is_default();
+                        || tcx.defaultness(node.def_id()).is_default();
 
                     if !is_specializable {
                         finalizing_node = Some(node);
diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs
index 090b769323a..cce609c261e 100644
--- a/compiler/rustc_middle/src/ty/assoc.rs
+++ b/compiler/rustc_middle/src/ty/assoc.rs
@@ -48,7 +48,7 @@ impl AssocItem {
     ///
     /// [`type_of`]: crate::ty::TyCtxt::type_of
     pub fn defaultness(&self, tcx: TyCtxt<'_>) -> hir::Defaultness {
-        tcx.impl_defaultness(self.def_id)
+        tcx.defaultness(self.def_id)
     }
 
     #[inline]
diff --git a/compiler/rustc_middle/src/ty/consts/int.rs b/compiler/rustc_middle/src/ty/consts/int.rs
index d1dbc531edf..1e43fab457e 100644
--- a/compiler/rustc_middle/src/ty/consts/int.rs
+++ b/compiler/rustc_middle/src/ty/consts/int.rs
@@ -1,5 +1,6 @@
 use rustc_apfloat::ieee::{Double, Single};
 use rustc_apfloat::Float;
+use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg};
 use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
 use rustc_target::abi::Size;
 use std::fmt;
@@ -113,6 +114,14 @@ impl std::fmt::Debug for ConstInt {
     }
 }
 
+impl IntoDiagnosticArg for ConstInt {
+    // FIXME this simply uses the Debug impl, but we could probably do better by converting both
+    // to an inherent method that returns `Cow`.
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(format!("{self:?}").into())
+    }
+}
+
 /// The raw bytes of a simple value.
 ///
 /// This is a packed struct in order to allow this type to be optimally embedded in enums
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 77725f0b3b6..e7335104e61 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -82,8 +82,6 @@ use std::iter;
 use std::mem;
 use std::ops::{Bound, Deref};
 
-const TINY_CONST_EVAL_LIMIT: Limit = Limit(20);
-
 #[allow(rustc::usage_of_ty_tykind)]
 impl<'tcx> Interner for TyCtxt<'tcx> {
     type AdtDef = ty::AdtDef<'tcx>;
@@ -480,6 +478,17 @@ impl<'tcx> TyCtxtFeed<'tcx, LocalDefId> {
 /// [rustc dev guide] for more details.
 ///
 /// [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/ty.html
+///
+/// An implementation detail: `TyCtxt` is a wrapper type for [GlobalCtxt],
+/// which is the struct that actually holds all the data. `TyCtxt` derefs to
+/// `GlobalCtxt`, and in practice `TyCtxt` is passed around everywhere, and all
+/// operations are done via `TyCtxt`. A `TyCtxt` is obtained for a `GlobalCtxt`
+/// by calling `enter` with a closure `f`. That function creates both the
+/// `TyCtxt`, and an `ImplicitCtxt` around it that is put into TLS. Within `f`:
+/// - The `ImplicitCtxt` is available implicitly via TLS.
+/// - The `TyCtxt` is available explicitly via the `tcx` parameter, and also
+///   implicitly within the `ImplicitCtxt`. Explicit access is preferred when
+///   possible.
 #[derive(Copy, Clone)]
 #[rustc_diagnostic_item = "TyCtxt"]
 #[rustc_pass_by_value]
@@ -495,6 +504,7 @@ impl<'tcx> Deref for TyCtxt<'tcx> {
     }
 }
 
+/// See [TyCtxt] for details about this type.
 pub struct GlobalCtxt<'tcx> {
     pub arena: &'tcx WorkerLocal<Arena<'tcx>>,
     pub hir_arena: &'tcx WorkerLocal<hir::Arena<'tcx>>,
@@ -1178,14 +1188,6 @@ impl<'tcx> TyCtxt<'tcx> {
         self.limits(()).move_size_limit
     }
 
-    pub fn const_eval_limit(self) -> Limit {
-        if self.sess.opts.unstable_opts.tiny_const_eval_limit {
-            TINY_CONST_EVAL_LIMIT
-        } else {
-            self.limits(()).const_eval_limit
-        }
-    }
-
     pub fn all_traits(self) -> impl Iterator<Item = DefId> + 'tcx {
         iter::once(LOCAL_CRATE)
             .chain(self.crates(()).iter().copied())
@@ -1882,18 +1884,28 @@ impl<'tcx> TyCtxt<'tcx> {
     }
 
     #[inline]
-    pub fn mk_closure(self, closure_id: DefId, closure_substs: SubstsRef<'tcx>) -> Ty<'tcx> {
-        self.mk_ty_from_kind(Closure(closure_id, closure_substs))
+    pub fn mk_closure(self, def_id: DefId, closure_substs: SubstsRef<'tcx>) -> Ty<'tcx> {
+        debug_assert_eq!(
+            closure_substs.len(),
+            self.generics_of(self.typeck_root_def_id(def_id)).count() + 3,
+            "closure constructed with incorrect substitutions"
+        );
+        self.mk_ty_from_kind(Closure(def_id, closure_substs))
     }
 
     #[inline]
     pub fn mk_generator(
         self,
-        id: DefId,
+        def_id: DefId,
         generator_substs: SubstsRef<'tcx>,
         movability: hir::Movability,
     ) -> Ty<'tcx> {
-        self.mk_ty_from_kind(Generator(id, generator_substs, movability))
+        debug_assert_eq!(
+            generator_substs.len(),
+            self.generics_of(self.typeck_root_def_id(def_id)).count() + 5,
+            "generator constructed with incorrect number of substitutions"
+        );
+        self.mk_ty_from_kind(Generator(def_id, generator_substs, movability))
     }
 
     #[inline]
diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs
index b5a743cfe34..c5a306fdf1f 100644
--- a/compiler/rustc_middle/src/ty/layout.rs
+++ b/compiler/rustc_middle/src/ty/layout.rs
@@ -1,8 +1,9 @@
-use crate::fluent_generated as fluent;
+use crate::error::UnsupportedFnAbi;
 use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use crate::query::TyCtxtAt;
 use crate::ty::normalize_erasing_regions::NormalizationError;
 use crate::ty::{self, ReprOptions, Ty, TyCtxt, TypeVisitableExt};
+use rustc_error_messages::DiagnosticMessage;
 use rustc_errors::{DiagnosticBuilder, Handler, IntoDiagnostic};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
@@ -14,7 +15,7 @@ use rustc_target::abi::call::FnAbi;
 use rustc_target::abi::*;
 use rustc_target::spec::{abi::Abi as SpecAbi, HasTargetSpec, PanicStrategy, Target};
 
-use std::cmp::{self};
+use std::cmp;
 use std::fmt;
 use std::num::NonZeroUsize;
 use std::ops::Bound;
@@ -214,29 +215,29 @@ pub enum LayoutError<'tcx> {
     Cycle,
 }
 
-impl IntoDiagnostic<'_, !> for LayoutError<'_> {
-    fn into_diagnostic(self, handler: &Handler) -> DiagnosticBuilder<'_, !> {
-        let mut diag = handler.struct_fatal("");
+impl<'tcx> LayoutError<'tcx> {
+    pub fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use LayoutError::*;
+        match self {
+            Unknown(_) => middle_unknown_layout,
+            SizeOverflow(_) => middle_values_too_big,
+            NormalizationFailure(_, _) => middle_cannot_be_normalized,
+            Cycle => middle_cycle,
+        }
+    }
 
+    pub fn into_diagnostic(self) -> crate::error::LayoutError<'tcx> {
+        use crate::error::LayoutError as E;
+        use LayoutError::*;
         match self {
-            LayoutError::Unknown(ty) => {
-                diag.set_arg("ty", ty);
-                diag.set_primary_message(fluent::middle_unknown_layout);
-            }
-            LayoutError::SizeOverflow(ty) => {
-                diag.set_arg("ty", ty);
-                diag.set_primary_message(fluent::middle_values_too_big);
-            }
-            LayoutError::NormalizationFailure(ty, e) => {
-                diag.set_arg("ty", ty);
-                diag.set_arg("failure_ty", e.get_type_for_failure());
-                diag.set_primary_message(fluent::middle_cannot_be_normalized);
-            }
-            LayoutError::Cycle => {
-                diag.set_primary_message(fluent::middle_cycle);
+            Unknown(ty) => E::Unknown { ty },
+            SizeOverflow(ty) => E::Overflow { ty },
+            NormalizationFailure(ty, e) => {
+                E::NormalizationFailure { ty, failure_ty: e.get_type_for_failure() }
             }
+            Cycle => E::Cycle,
         }
-        diag
     }
 }
 
@@ -330,11 +331,8 @@ impl<'tcx> SizeSkeleton<'tcx> {
                         Ok(SizeSkeleton::Pointer { non_zero, tail: tcx.erase_regions(tail) })
                     }
                     _ => bug!(
-                        "SizeSkeleton::compute({}): layout errored ({}), yet \
-                              tail `{}` is not a type parameter or a projection",
-                        ty,
-                        err,
-                        tail
+                        "SizeSkeleton::compute({ty}): layout errored ({err:?}), yet \
+                              tail `{tail}` is not a type parameter or a projection",
                     ),
                 }
             }
@@ -940,12 +938,8 @@ where
             TyMaybeWithLayout::Ty(field_ty) => {
                 cx.tcx().layout_of(cx.param_env().and(field_ty)).unwrap_or_else(|e| {
                     bug!(
-                        "failed to get layout for `{}`: {},\n\
-                         despite it being a field (#{}) of an existing layout: {:#?}",
-                        field_ty,
-                        e,
-                        i,
-                        this
+                        "failed to get layout for `{field_ty}`: {e:?},\n\
+                         despite it being a field (#{i}) of an existing layout: {this:#?}",
                     )
                 })
             }
@@ -1262,21 +1256,18 @@ impl From<call::AdjustForForeignAbiError> for FnAbiError<'_> {
     }
 }
 
-impl<'tcx> fmt::Display for FnAbiError<'tcx> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+impl<'a, 'b> IntoDiagnostic<'a, !> for FnAbiError<'b> {
+    fn into_diagnostic(self, handler: &'a Handler) -> DiagnosticBuilder<'a, !> {
         match self {
-            Self::Layout(err) => err.fmt(f),
-            Self::AdjustForForeignAbi(err) => err.fmt(f),
+            Self::Layout(e) => e.into_diagnostic().into_diagnostic(handler),
+            Self::AdjustForForeignAbi(call::AdjustForForeignAbiError::Unsupported {
+                arch,
+                abi,
+            }) => UnsupportedFnAbi { arch, abi: abi.name() }.into_diagnostic(handler),
         }
     }
 }
 
-impl IntoDiagnostic<'_, !> for FnAbiError<'_> {
-    fn into_diagnostic(self, handler: &Handler) -> DiagnosticBuilder<'_, !> {
-        handler.struct_fatal(self.to_string())
-    }
-}
-
 // FIXME(eddyb) maybe use something like this for an unified `fn_abi_of`, not
 // just for error handling.
 #[derive(Debug)]
diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs
index e04dbbff9a7..8cbffa14850 100644
--- a/compiler/rustc_middle/src/ty/typeck_results.rs
+++ b/compiler/rustc_middle/src/ty/typeck_results.rs
@@ -155,11 +155,7 @@ pub struct TypeckResults<'tcx> {
     /// We also store the type here, so that the compiler can use it as a hint
     /// for figuring out hidden types, even if they are only set in dead code
     /// (which doesn't show up in MIR).
-    ///
-    /// These types are mapped back to the opaque's identity substitutions
-    /// (with erased regions), which is why we don't associated substs with any
-    /// of these usages.
-    pub concrete_opaque_types: FxIndexMap<LocalDefId, ty::OpaqueHiddenType<'tcx>>,
+    pub concrete_opaque_types: FxIndexMap<ty::OpaqueTypeKey<'tcx>, ty::OpaqueHiddenType<'tcx>>,
 
     /// Tracks the minimum captures required for a closure;
     /// see `MinCaptureInformationMap` for more details.
diff --git a/compiler/rustc_middle/src/ty/vtable.rs b/compiler/rustc_middle/src/ty/vtable.rs
index b9b1cd73a8b..443791d0af4 100644
--- a/compiler/rustc_middle/src/ty/vtable.rs
+++ b/compiler/rustc_middle/src/ty/vtable.rs
@@ -73,7 +73,7 @@ pub(super) fn vtable_allocation_provider<'tcx>(
     let ptr_align = tcx.data_layout.pointer_align.abi;
 
     let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap();
-    let mut vtable = Allocation::uninit(vtable_size, ptr_align, /* panic_on_fail */ true).unwrap();
+    let mut vtable = Allocation::uninit(vtable_size, ptr_align);
 
     // No need to do any alignment checks on the memory accesses below, because we know the
     // allocation is correctly aligned as we created it above. Also we're only offsetting by
diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs
index 8f6a069a7db..7f0c1d53f72 100644
--- a/compiler/rustc_mir_build/src/build/mod.rs
+++ b/compiler/rustc_mir_build/src/build/mod.rs
@@ -556,7 +556,7 @@ fn construct_const<'a, 'tcx>(
             span,
             ..
         }) => (*span, ty.span),
-        Node::AnonConst(_) => {
+        Node::AnonConst(_) | Node::ConstBlock(_) => {
             let span = tcx.def_span(def);
             (span, span)
         }
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index 1bbe7b45c1e..8eeb055ed82 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -582,22 +582,15 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
     /// Converts inline const patterns.
     fn lower_inline_const(
         &mut self,
-        anon_const: &'tcx hir::AnonConst,
+        block: &'tcx hir::ConstBlock,
         id: hir::HirId,
         span: Span,
     ) -> PatKind<'tcx> {
         let tcx = self.tcx;
-        let def_id = anon_const.def_id;
-        let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
-        let body_id = match tcx.hir().get(hir_id) {
-            hir::Node::AnonConst(ac) => ac.body,
-            _ => span_bug!(
-                tcx.def_span(def_id.to_def_id()),
-                "from_inline_const can only process anonymous constants"
-            ),
-        };
+        let def_id = block.def_id;
+        let body_id = block.body;
         let expr = &tcx.hir().body(body_id).value;
-        let ty = tcx.typeck(def_id).node_type(hir_id);
+        let ty = tcx.typeck(def_id).node_type(block.hir_id);
 
         // Special case inline consts that are just literals. This is solely
         // a performance optimization, as we could also just go through the regular
diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs
index 069514d8a3b..2f851cd1eb5 100644
--- a/compiler/rustc_mir_transform/src/check_unsafety.rs
+++ b/compiler/rustc_mir_transform/src/check_unsafety.rs
@@ -421,10 +421,8 @@ impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_, 'tcx> {
         intravisit::walk_block(self, block);
     }
 
-    fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
-        if matches!(self.tcx.def_kind(c.def_id), DefKind::InlineConst) {
-            self.visit_body(self.tcx.hir().body(c.body))
-        }
+    fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
+        self.visit_body(self.tcx.hir().body(c.body))
     }
 
     fn visit_fn(
diff --git a/compiler/rustc_mir_transform/src/const_goto.rs b/compiler/rustc_mir_transform/src/const_goto.rs
index da101ca7ad2..024bea62098 100644
--- a/compiler/rustc_mir_transform/src/const_goto.rs
+++ b/compiler/rustc_mir_transform/src/const_goto.rs
@@ -28,7 +28,7 @@ pub struct ConstGoto;
 
 impl<'tcx> MirPass<'tcx> for ConstGoto {
     fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
-        sess.mir_opt_level() >= 4
+        sess.mir_opt_level() >= 2
     }
 
     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs
index 1ba1951afde..1d43dbda0aa 100644
--- a/compiler/rustc_mir_transform/src/const_prop.rs
+++ b/compiler/rustc_mir_transform/src/const_prop.rs
@@ -4,6 +4,7 @@
 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;
 use rustc_index::bit_set::BitSet;
@@ -37,6 +38,7 @@ macro_rules! throw_machine_stop_str {
     ($($tt:tt)*) => {{
         // We make a new local type for it. The type itself does not carry any information,
         // but its vtable (for the `MachineStopType` trait) does.
+        #[derive(Debug)]
         struct Zst;
         // Printing this type shows the desired string.
         impl std::fmt::Display for Zst {
@@ -44,7 +46,17 @@ macro_rules! throw_machine_stop_str {
                 write!(f, $($tt)*)
             }
         }
-        impl rustc_middle::mir::interpret::MachineStopType for Zst {}
+
+        impl rustc_middle::mir::interpret::MachineStopType for Zst {
+            fn diagnostic_message(&self) -> rustc_errors::DiagnosticMessage {
+                self.to_string().into()
+            }
+
+            fn add_args(
+                self: Box<Self>,
+                _: &mut dyn FnMut(std::borrow::Cow<'static, str>, rustc_errors::DiagnosticArgValue<'static>),
+            ) {}
+        }
         throw_machine_stop!(Zst)
     }};
 }
@@ -103,7 +115,14 @@ impl<'tcx> MirPass<'tcx> for ConstProp {
         // That would require a uniform one-def no-mutation analysis
         // and RPO (or recursing when needing the value of a local).
         let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx);
-        optimization_finder.visit_body(body);
+
+        // Traverse the body in reverse post-order, to ensure that `FullConstProp` locals are
+        // assigned before being read.
+        let postorder = body.basic_blocks.postorder().to_vec();
+        for bb in postorder.into_iter().rev() {
+            let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb];
+            optimization_finder.visit_basic_block_data(bb, data);
+        }
 
         trace!("ConstProp done for {:?}", def_id);
     }
@@ -367,7 +386,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
                 op
             }
             Err(e) => {
-                trace!("get_const failed: {}", e);
+                trace!("get_const failed: {:?}", e.into_kind().debug());
                 return None;
             }
         };
@@ -789,12 +808,6 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
         self.tcx
     }
 
-    fn visit_body(&mut self, body: &mut Body<'tcx>) {
-        for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
-            self.visit_basic_block_data(bb, data);
-        }
-    }
-
     fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
         self.super_operand(operand, location);
 
@@ -885,14 +898,23 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
                 }
             }
             StatementKind::StorageLive(local) => {
-                let frame = self.ecx.frame_mut();
-                frame.locals[local].value =
-                    LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit));
-            }
-            StatementKind::StorageDead(local) => {
-                let frame = self.ecx.frame_mut();
-                frame.locals[local].value = LocalValue::Dead;
+                Self::remove_const(&mut self.ecx, local);
             }
+            // We do not need to mark dead locals as such. For `FullConstProp` locals,
+            // this allows to propagate the single assigned value in this case:
+            // ```
+            // let x = SOME_CONST;
+            // if a {
+            //   f(copy x);
+            //   StorageDead(x);
+            // } else {
+            //   g(copy x);
+            //   StorageDead(x);
+            // }
+            // ```
+            //
+            // This may propagate a constant where the local would be uninit or dead.
+            // In both cases, this does not matter, as those reads would be UB anyway.
             _ => {}
         }
     }
diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs
index 0fe49b8a1bb..759650fe4db 100644
--- a/compiler/rustc_mir_transform/src/const_prop_lint.rs
+++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs
@@ -9,6 +9,7 @@ use rustc_const_eval::interpret::Immediate;
 use rustc_const_eval::interpret::{
     self, InterpCx, InterpResult, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
 };
+use rustc_const_eval::ReportErrorExt;
 use rustc_hir::def::DefKind;
 use rustc_hir::HirId;
 use rustc_index::bit_set::BitSet;
@@ -232,7 +233,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
                 op
             }
             Err(e) => {
-                trace!("get_const failed: {}", e);
+                trace!("get_const failed: {:?}", e.into_kind().debug());
                 return None;
             }
         };
@@ -272,8 +273,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
                 // dedicated error variants should be introduced instead.
                 assert!(
                     !error.kind().formatted_string(),
-                    "const-prop encountered formatting error: {}",
-                    error
+                    "const-prop encountered formatting error: {error:?}",
                 );
                 None
             }
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 602e40d5131..22f71bb0851 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -163,7 +163,14 @@ impl<'a, P: std::fmt::Debug> DecorateLint<'a, ()> for AssertLint<P> {
         self,
         diag: &'b mut DiagnosticBuilder<'a, ()>,
     ) -> &'b mut DiagnosticBuilder<'a, ()> {
-        diag.span_label(self.span(), format!("{:?}", self.panic()));
+        let span = self.span();
+        let assert_kind = self.panic();
+        let message = assert_kind.diagnostic_message();
+        assert_kind.add_args(&mut |name, value| {
+            diag.set_arg(name, value);
+        });
+        diag.span_label(span, message);
+
         diag
     }
 
@@ -191,7 +198,7 @@ impl<P> AssertLint<P> {
             AssertLint::ArithmeticOverflow(sp, _) | AssertLint::UnconditionalPanic(sp, _) => *sp,
         }
     }
-    pub fn panic(&self) -> &AssertKind<P> {
+    pub fn panic(self) -> AssertKind<P> {
         match self {
             AssertLint::ArithmeticOverflow(_, p) | AssertLint::UnconditionalPanic(_, p) => p,
         }
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 54c138b6fbd..7d9f6c38e36 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -559,10 +559,13 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
             // inst combine is after MatchBranchSimplification to clean up Ne(_1, false)
             &multiple_return_terminators::MultipleReturnTerminators,
             &instsimplify::InstSimplify,
-            &separate_const_switch::SeparateConstSwitch,
             &simplify::SimplifyLocals::BeforeConstProp,
             &copy_prop::CopyProp,
             &ref_prop::ReferencePropagation,
+            // Perform `SeparateConstSwitch` after SSA-based analyses, as cloning blocks may
+            // destroy the SSA property. It should still happen before const-propagation, so the
+            // latter pass will leverage the created opportunities.
+            &separate_const_switch::SeparateConstSwitch,
             &const_prop::ConstProp,
             &dataflow_const_prop::DataflowConstProp,
             //
diff --git a/compiler/rustc_mir_transform/src/separate_const_switch.rs b/compiler/rustc_mir_transform/src/separate_const_switch.rs
index 2479856b727..f35a5fb4276 100644
--- a/compiler/rustc_mir_transform/src/separate_const_switch.rs
+++ b/compiler/rustc_mir_transform/src/separate_const_switch.rs
@@ -46,7 +46,7 @@ pub struct SeparateConstSwitch;
 
 impl<'tcx> MirPass<'tcx> for SeparateConstSwitch {
     fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
-        sess.mir_opt_level() >= 4
+        sess.mir_opt_level() >= 2
     }
 
     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
diff --git a/compiler/rustc_mir_transform/src/sroa.rs b/compiler/rustc_mir_transform/src/sroa.rs
index 2d77291293d..e4b3b8b9262 100644
--- a/compiler/rustc_mir_transform/src/sroa.rs
+++ b/compiler/rustc_mir_transform/src/sroa.rs
@@ -6,23 +6,29 @@ use rustc_middle::mir::visit::*;
 use rustc_middle::mir::*;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_mir_dataflow::value_analysis::{excluded_locals, iter_fields};
-use rustc_target::abi::FieldIdx;
+use rustc_target::abi::{FieldIdx, ReprFlags, FIRST_VARIANT};
 
 pub struct ScalarReplacementOfAggregates;
 
 impl<'tcx> MirPass<'tcx> for ScalarReplacementOfAggregates {
     fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
-        sess.mir_opt_level() >= 3
+        sess.mir_opt_level() >= 2
     }
 
     #[instrument(level = "debug", skip(self, tcx, body))]
     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
         debug!(def_id = ?body.source.def_id());
+
+        // Avoid query cycles (generators require optimized MIR for layout).
+        if tcx.type_of(body.source.def_id()).subst_identity().is_generator() {
+            return;
+        }
+
         let mut excluded = excluded_locals(body);
         let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
         loop {
             debug!(?excluded);
-            let escaping = escaping_locals(&excluded, body);
+            let escaping = escaping_locals(tcx, param_env, &excluded, body);
             debug!(?escaping);
             let replacements = compute_flattening(tcx, param_env, body, escaping);
             debug!(?replacements);
@@ -48,11 +54,45 @@ impl<'tcx> MirPass<'tcx> for ScalarReplacementOfAggregates {
 /// - the locals is a union or an enum;
 /// - the local's address is taken, and thus the relative addresses of the fields are observable to
 ///   client code.
-fn escaping_locals(excluded: &BitSet<Local>, body: &Body<'_>) -> BitSet<Local> {
+fn escaping_locals<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    excluded: &BitSet<Local>,
+    body: &Body<'tcx>,
+) -> BitSet<Local> {
+    let is_excluded_ty = |ty: Ty<'tcx>| {
+        if ty.is_union() || ty.is_enum() {
+            return true;
+        }
+        if let ty::Adt(def, _substs) = ty.kind() {
+            if def.repr().flags.contains(ReprFlags::IS_SIMD) {
+                // Exclude #[repr(simd)] types so that they are not de-optimized into an array
+                return true;
+            }
+            // We already excluded unions and enums, so this ADT must have one variant
+            let variant = def.variant(FIRST_VARIANT);
+            if variant.fields.len() > 1 {
+                // If this has more than one field, it cannot be a wrapper that only provides a
+                // niche, so we do not want to automatically exclude it.
+                return false;
+            }
+            let Ok(layout) = tcx.layout_of(param_env.and(ty)) else {
+                // We can't get the layout
+                return true;
+            };
+            if layout.layout.largest_niche().is_some() {
+                // This type has a niche
+                return true;
+            }
+        }
+        // Default for non-ADTs
+        false
+    };
+
     let mut set = BitSet::new_empty(body.local_decls.len());
     set.insert_range(RETURN_PLACE..=Local::from_usize(body.arg_count));
     for (local, decl) in body.local_decls().iter_enumerated() {
-        if decl.ty.is_union() || decl.ty.is_enum() || excluded.contains(local) {
+        if excluded.contains(local) || is_excluded_ty(decl.ty) {
             set.insert(local);
         }
     }
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index cefa64d27ac..f4ee7b75875 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -35,15 +35,15 @@
 //!
 //! - A "mono item" is something that results in a function or global in
 //!   the LLVM IR of a codegen unit. Mono items do not stand on their
-//!   own, they can reference other mono items. For example, if function
+//!   own, they can use other mono items. For example, if function
 //!   `foo()` calls function `bar()` then the mono item for `foo()`
-//!   references the mono item for function `bar()`. In general, the
-//!   definition for mono item A referencing a mono item B is that
-//!   the LLVM artifact produced for A references the LLVM artifact produced
+//!   uses the mono item for function `bar()`. In general, the
+//!   definition for mono item A using a mono item B is that
+//!   the LLVM artifact produced for A uses the LLVM artifact produced
 //!   for B.
 //!
-//! - Mono items and the references between them form a directed graph,
-//!   where the mono items are the nodes and references form the edges.
+//! - Mono items and the uses between them form a directed graph,
+//!   where the mono items are the nodes and uses form the edges.
 //!   Let's call this graph the "mono item graph".
 //!
 //! - The mono item graph for a program contains all mono items
@@ -53,12 +53,11 @@
 //! mono item graph for the current crate. It runs in two phases:
 //!
 //! 1. Discover the roots of the graph by traversing the HIR of the crate.
-//! 2. Starting from the roots, find neighboring nodes by inspecting the MIR
+//! 2. Starting from the roots, find uses by inspecting the MIR
 //!    representation of the item corresponding to a given node, until no more
 //!    new nodes are found.
 //!
 //! ### Discovering roots
-//!
 //! The roots of the mono item graph correspond to the public non-generic
 //! syntactic items in the source code. We find them by walking the HIR of the
 //! crate, and whenever we hit upon a public function, method, or static item,
@@ -69,25 +68,23 @@
 //! specified. Functions marked `#[no_mangle]` and functions called by inlinable
 //! functions also always act as roots.)
 //!
-//! ### Finding neighbor nodes
-//! Given a mono item node, we can discover neighbors by inspecting its
-//! MIR. We walk the MIR and any time we hit upon something that signifies a
-//! reference to another mono item, we have found a neighbor. Since the
-//! mono item we are currently at is always monomorphic, we also know the
-//! concrete type arguments of its neighbors, and so all neighbors again will be
-//! monomorphic. The specific forms a reference to a neighboring node can take
-//! in MIR are quite diverse. Here is an overview:
+//! ### Finding uses
+//! Given a mono item node, we can discover uses by inspecting its MIR. We walk
+//! the MIR to find other mono items used by each mono item. Since the mono
+//! item we are currently at is always monomorphic, we also know the concrete
+//! type arguments of its used mono items. The specific forms a use can take in
+//! MIR are quite diverse. Here is an overview:
 //!
 //! #### Calling Functions/Methods
-//! The most obvious form of one mono item referencing another is a
+//! The most obvious way for one mono item to use another is a
 //! function or method call (represented by a CALL terminator in MIR). But
-//! calls are not the only thing that might introduce a reference between two
+//! calls are not the only thing that might introduce a use between two
 //! function mono items, and as we will see below, they are just a
 //! specialization of the form described next, and consequently will not get any
 //! special treatment in the algorithm.
 //!
 //! #### Taking a reference to a function or method
-//! A function does not need to actually be called in order to be a neighbor of
+//! A function does not need to actually be called in order to be used by
 //! another function. It suffices to just take a reference in order to introduce
 //! an edge. Consider the following example:
 //!
@@ -109,18 +106,18 @@
 //! The MIR of none of these functions will contain an explicit call to
 //! `print_val::<i32>`. Nonetheless, in order to mono this program, we need
 //! an instance of this function. Thus, whenever we encounter a function or
-//! method in operand position, we treat it as a neighbor of the current
+//! method in operand position, we treat it as a use of the current
 //! mono item. Calls are just a special case of that.
 //!
 //! #### Drop glue
 //! Drop glue mono items are introduced by MIR drop-statements. The
-//! generated mono item will again have drop-glue item neighbors if the
+//! generated mono item will have additional drop-glue item uses if the
 //! type to be dropped contains nested values that also need to be dropped. It
-//! might also have a function item neighbor for the explicit `Drop::drop`
+//! might also have a function item use for the explicit `Drop::drop`
 //! implementation of its type.
 //!
 //! #### Unsizing Casts
-//! A subtle way of introducing neighbor edges is by casting to a trait object.
+//! A subtle way of introducing use edges is by casting to a trait object.
 //! Since the resulting fat-pointer contains a reference to a vtable, we need to
 //! instantiate all object-safe methods of the trait, as we need to store
 //! pointers to these functions even if they never get called anywhere. This can
@@ -151,7 +148,7 @@
 //! Mono item collection can be performed in one of two modes:
 //!
 //! - Lazy mode means that items will only be instantiated when actually
-//!   referenced. The goal is to produce the least amount of machine code
+//!   used. The goal is to produce the least amount of machine code
 //!   possible.
 //!
 //! - Eager mode is meant to be used in conjunction with incremental compilation
@@ -179,7 +176,6 @@ use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId};
 use rustc_hir::lang_items::LangItem;
-use rustc_index::bit_set::GrowableBitSet;
 use rustc_middle::mir::interpret::{AllocId, ConstValue};
 use rustc_middle::mir::interpret::{ErrorHandled, GlobalAlloc, Scalar};
 use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
@@ -199,7 +195,6 @@ use rustc_session::lint::builtin::LARGE_ASSIGNMENTS;
 use rustc_session::Limit;
 use rustc_span::source_map::{dummy_spanned, respan, Span, Spanned, DUMMY_SP};
 use rustc_target::abi::Size;
-use std::ops::Range;
 use std::path::PathBuf;
 
 use crate::errors::{
@@ -212,114 +207,51 @@ pub enum MonoItemCollectionMode {
     Lazy,
 }
 
-/// Maps every mono item to all mono items it references in its
-/// body.
-pub struct InliningMap<'tcx> {
-    // Maps a source mono item to the range of mono items
-    // accessed by it.
-    // The range selects elements within the `targets` vecs.
-    index: FxHashMap<MonoItem<'tcx>, Range<usize>>,
-    targets: Vec<MonoItem<'tcx>>,
-
-    // Contains one bit per mono item in the `targets` field. That bit
-    // is true if that mono item needs to be inlined into every CGU.
-    inlines: GrowableBitSet<usize>,
-}
-
-/// Struct to store mono items in each collecting and if they should
-/// be inlined. We call `instantiation_mode` to get their inlining
-/// status when inserting new elements, which avoids calling it in
-/// `inlining_map.lock_mut()`. See the `collect_items_rec` implementation
-/// below.
-struct MonoItems<'tcx> {
-    // If this is false, we do not need to compute whether items
-    // will need to be inlined.
-    compute_inlining: bool,
-
-    // The TyCtxt used to determine whether the a item should
-    // be inlined.
-    tcx: TyCtxt<'tcx>,
+pub struct UsageMap<'tcx> {
+    // Maps every mono item to the mono items used by it.
+    used_map: FxHashMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
 
-    // The collected mono items. The bool field in each element
-    // indicates whether this element should be inlined.
-    items: Vec<(Spanned<MonoItem<'tcx>>, bool /*inlined*/)>,
+    // Maps every mono item to the mono items that use it.
+    user_map: FxHashMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
 }
 
-impl<'tcx> MonoItems<'tcx> {
-    #[inline]
-    fn push(&mut self, item: Spanned<MonoItem<'tcx>>) {
-        self.extend([item]);
-    }
+type MonoItems<'tcx> = Vec<Spanned<MonoItem<'tcx>>>;
 
-    #[inline]
-    fn extend<T: IntoIterator<Item = Spanned<MonoItem<'tcx>>>>(&mut self, iter: T) {
-        self.items.extend(iter.into_iter().map(|mono_item| {
-            let inlined = if !self.compute_inlining {
-                false
-            } else {
-                mono_item.node.instantiation_mode(self.tcx) == InstantiationMode::LocalCopy
-            };
-            (mono_item, inlined)
-        }))
+impl<'tcx> UsageMap<'tcx> {
+    fn new() -> UsageMap<'tcx> {
+        UsageMap { used_map: FxHashMap::default(), user_map: FxHashMap::default() }
     }
-}
 
-impl<'tcx> InliningMap<'tcx> {
-    fn new() -> InliningMap<'tcx> {
-        InliningMap {
-            index: FxHashMap::default(),
-            targets: Vec::new(),
-            inlines: GrowableBitSet::with_capacity(1024),
-        }
-    }
-
-    fn record_accesses<'a>(
+    fn record_used<'a>(
         &mut self,
-        source: MonoItem<'tcx>,
-        new_targets: &'a [(Spanned<MonoItem<'tcx>>, bool)],
+        user_item: MonoItem<'tcx>,
+        used_items: &'a [Spanned<MonoItem<'tcx>>],
     ) where
         'tcx: 'a,
     {
-        let start_index = self.targets.len();
-        let new_items_count = new_targets.len();
-        let new_items_count_total = new_items_count + self.targets.len();
-
-        self.targets.reserve(new_items_count);
-        self.inlines.ensure(new_items_count_total);
-
-        for (i, (Spanned { node: mono_item, .. }, inlined)) in new_targets.into_iter().enumerate() {
-            self.targets.push(*mono_item);
-            if *inlined {
-                self.inlines.insert(i + start_index);
-            }
+        let used_items: Vec<_> = used_items.iter().map(|item| item.node).collect();
+        for &used_item in used_items.iter() {
+            self.user_map.entry(used_item).or_default().push(user_item);
         }
 
-        let end_index = self.targets.len();
-        assert!(self.index.insert(source, start_index..end_index).is_none());
+        assert!(self.used_map.insert(user_item, used_items).is_none());
     }
 
-    /// Internally iterate over all items referenced by `source` which will be
-    /// made available for inlining.
-    pub fn with_inlining_candidates<F>(&self, source: MonoItem<'tcx>, mut f: F)
-    where
-        F: FnMut(MonoItem<'tcx>),
-    {
-        if let Some(range) = self.index.get(&source) {
-            for (i, candidate) in self.targets[range.clone()].iter().enumerate() {
-                if self.inlines.contains(range.start + i) {
-                    f(*candidate);
-                }
-            }
-        }
+    pub fn get_user_items(&self, item: MonoItem<'tcx>) -> Option<&[MonoItem<'tcx>]> {
+        self.user_map.get(&item).map(|items| items.as_slice())
     }
 
-    /// Internally iterate over all items and the things each accesses.
-    pub fn iter_accesses<F>(&self, mut f: F)
+    /// Internally iterate over all inlined items used by `item`.
+    pub fn for_each_inlined_used_item<F>(&self, tcx: TyCtxt<'tcx>, item: MonoItem<'tcx>, mut f: F)
     where
-        F: FnMut(MonoItem<'tcx>, &[MonoItem<'tcx>]),
+        F: FnMut(MonoItem<'tcx>),
     {
-        for (&accessor, range) in &self.index {
-            f(accessor, &self.targets[range.clone()])
+        let used_items = self.used_map.get(&item).unwrap();
+        for used_item in used_items.iter() {
+            let is_inlined = used_item.instantiation_mode(tcx) == InstantiationMode::LocalCopy;
+            if is_inlined {
+                f(*used_item);
+            }
         }
     }
 }
@@ -328,7 +260,7 @@ impl<'tcx> InliningMap<'tcx> {
 pub fn collect_crate_mono_items(
     tcx: TyCtxt<'_>,
     mode: MonoItemCollectionMode,
-) -> (FxHashSet<MonoItem<'_>>, InliningMap<'_>) {
+) -> (FxHashSet<MonoItem<'_>>, UsageMap<'_>) {
     let _prof_timer = tcx.prof.generic_activity("monomorphization_collector");
 
     let roots =
@@ -337,12 +269,12 @@ pub fn collect_crate_mono_items(
     debug!("building mono item graph, beginning at roots");
 
     let mut visited = MTLock::new(FxHashSet::default());
-    let mut inlining_map = MTLock::new(InliningMap::new());
+    let mut usage_map = MTLock::new(UsageMap::new());
     let recursion_limit = tcx.recursion_limit();
 
     {
         let visited: MTLockRef<'_, _> = &mut visited;
-        let inlining_map: MTLockRef<'_, _> = &mut inlining_map;
+        let usage_map: MTLockRef<'_, _> = &mut usage_map;
 
         tcx.sess.time("monomorphization_collector_graph_walk", || {
             par_for_each_in(roots, |root| {
@@ -353,13 +285,13 @@ pub fn collect_crate_mono_items(
                     visited,
                     &mut recursion_depths,
                     recursion_limit,
-                    inlining_map,
+                    usage_map,
                 );
             });
         });
     }
 
-    (visited.into_inner(), inlining_map.into_inner())
+    (visited.into_inner(), usage_map.into_inner())
 }
 
 // Find all non-generic items by walking the HIR. These items serve as roots to
@@ -367,7 +299,7 @@ pub fn collect_crate_mono_items(
 #[instrument(skip(tcx, mode), level = "debug")]
 fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionMode) -> Vec<MonoItem<'_>> {
     debug!("collecting roots");
-    let mut roots = MonoItems { compute_inlining: false, tcx, items: Vec::new() };
+    let mut roots = Vec::new();
 
     {
         let entry_fn = tcx.entry_fn(());
@@ -393,9 +325,8 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionMode) -> Vec<MonoItem<
     // whose predicates hold. Luckily, items that aren't instantiable
     // can't actually be used, so we can just skip codegenning them.
     roots
-        .items
         .into_iter()
-        .filter_map(|(Spanned { node: mono_item, .. }, _)| {
+        .filter_map(|Spanned { node: mono_item, .. }| {
             mono_item.is_instantiable(tcx).then_some(mono_item)
         })
         .collect()
@@ -403,24 +334,23 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionMode) -> Vec<MonoItem<
 
 /// Collect all monomorphized items reachable from `starting_point`, and emit a note diagnostic if a
 /// post-monomorphization error is encountered during a collection step.
-#[instrument(skip(tcx, visited, recursion_depths, recursion_limit, inlining_map), level = "debug")]
+#[instrument(skip(tcx, visited, recursion_depths, recursion_limit, usage_map), level = "debug")]
 fn collect_items_rec<'tcx>(
     tcx: TyCtxt<'tcx>,
-    starting_point: Spanned<MonoItem<'tcx>>,
+    starting_item: Spanned<MonoItem<'tcx>>,
     visited: MTLockRef<'_, FxHashSet<MonoItem<'tcx>>>,
     recursion_depths: &mut DefIdMap<usize>,
     recursion_limit: Limit,
-    inlining_map: MTLockRef<'_, InliningMap<'tcx>>,
+    usage_map: MTLockRef<'_, UsageMap<'tcx>>,
 ) {
-    if !visited.lock_mut().insert(starting_point.node) {
+    if !visited.lock_mut().insert(starting_item.node) {
         // We've been here already, no need to search again.
         return;
     }
 
-    let mut neighbors = MonoItems { compute_inlining: true, tcx, items: Vec::new() };
+    let mut used_items = Vec::new();
     let recursion_depth_reset;
 
-    //
     // Post-monomorphization errors MVP
     //
     // We can encounter errors while monomorphizing an item, but we don't have a good way of
@@ -446,7 +376,7 @@ fn collect_items_rec<'tcx>(
     // FIXME: don't rely on global state, instead bubble up errors. Note: this is very hard to do.
     let error_count = tcx.sess.diagnostic().err_count();
 
-    match starting_point.node {
+    match starting_item.node {
         MonoItem::Static(def_id) => {
             let instance = Instance::mono(tcx, def_id);
 
@@ -454,19 +384,19 @@ fn collect_items_rec<'tcx>(
             debug_assert!(should_codegen_locally(tcx, &instance));
 
             let ty = instance.ty(tcx, ty::ParamEnv::reveal_all());
-            visit_drop_use(tcx, ty, true, starting_point.span, &mut neighbors);
+            visit_drop_use(tcx, ty, true, starting_item.span, &mut used_items);
 
             recursion_depth_reset = None;
 
             if let Ok(alloc) = tcx.eval_static_initializer(def_id) {
                 for &id in alloc.inner().provenance().ptrs().values() {
-                    collect_miri(tcx, id, &mut neighbors);
+                    collect_miri(tcx, id, &mut used_items);
                 }
             }
 
             if tcx.needs_thread_local_shim(def_id) {
-                neighbors.push(respan(
-                    starting_point.span,
+                used_items.push(respan(
+                    starting_item.span,
                     MonoItem::Fn(Instance {
                         def: InstanceDef::ThreadLocalShim(def_id),
                         substs: InternalSubsts::empty(),
@@ -482,14 +412,14 @@ fn collect_items_rec<'tcx>(
             recursion_depth_reset = Some(check_recursion_limit(
                 tcx,
                 instance,
-                starting_point.span,
+                starting_item.span,
                 recursion_depths,
                 recursion_limit,
             ));
             check_type_length_limit(tcx, instance);
 
             rustc_data_structures::stack::ensure_sufficient_stack(|| {
-                collect_neighbours(tcx, instance, &mut neighbors);
+                collect_used_items(tcx, instance, &mut used_items);
             });
         }
         MonoItem::GlobalAsm(item_id) => {
@@ -507,13 +437,13 @@ fn collect_items_rec<'tcx>(
                         hir::InlineAsmOperand::SymFn { anon_const } => {
                             let fn_ty =
                                 tcx.typeck_body(anon_const.body).node_type(anon_const.hir_id);
-                            visit_fn_use(tcx, fn_ty, false, *op_sp, &mut neighbors);
+                            visit_fn_use(tcx, fn_ty, false, *op_sp, &mut used_items);
                         }
                         hir::InlineAsmOperand::SymStatic { path: _, def_id } => {
                             let instance = Instance::mono(tcx, *def_id);
                             if should_codegen_locally(tcx, &instance) {
                                 trace!("collecting static {:?}", def_id);
-                                neighbors.push(dummy_spanned(MonoItem::Static(*def_id)));
+                                used_items.push(dummy_spanned(MonoItem::Static(*def_id)));
                             }
                         }
                         hir::InlineAsmOperand::In { .. }
@@ -533,19 +463,19 @@ fn collect_items_rec<'tcx>(
     // Check for PMEs and emit a diagnostic if one happened. To try to show relevant edges of the
     // mono item graph.
     if tcx.sess.diagnostic().err_count() > error_count
-        && starting_point.node.is_generic_fn()
-        && starting_point.node.is_user_defined()
+        && starting_item.node.is_generic_fn()
+        && starting_item.node.is_user_defined()
     {
-        let formatted_item = with_no_trimmed_paths!(starting_point.node.to_string());
+        let formatted_item = with_no_trimmed_paths!(starting_item.node.to_string());
         tcx.sess.emit_note(EncounteredErrorWhileInstantiating {
-            span: starting_point.span,
+            span: starting_item.span,
             formatted_item,
         });
     }
-    inlining_map.lock_mut().record_accesses(starting_point.node, &neighbors.items);
+    usage_map.lock_mut().record_used(starting_item.node, &used_items);
 
-    for (neighbour, _) in neighbors.items {
-        collect_items_rec(tcx, neighbour, visited, recursion_depths, recursion_limit, inlining_map);
+    for used_item in used_items {
+        collect_items_rec(tcx, used_item, visited, recursion_depths, recursion_limit, usage_map);
     }
 
     if let Some((def_id, depth)) = recursion_depth_reset {
@@ -661,14 +591,14 @@ fn check_type_length_limit<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
     }
 }
 
-struct MirNeighborCollector<'a, 'tcx> {
+struct MirUsedCollector<'a, 'tcx> {
     tcx: TyCtxt<'tcx>,
     body: &'a mir::Body<'tcx>,
     output: &'a mut MonoItems<'tcx>,
     instance: Instance<'tcx>,
 }
 
-impl<'a, 'tcx> MirNeighborCollector<'a, 'tcx> {
+impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> {
     pub fn monomorphize<T>(&self, value: T) -> T
     where
         T: TypeFoldable<TyCtxt<'tcx>>,
@@ -682,7 +612,7 @@ impl<'a, 'tcx> MirNeighborCollector<'a, 'tcx> {
     }
 }
 
-impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> {
+impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
     fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
         debug!("visiting rvalue {:?}", *rvalue);
 
@@ -1442,13 +1372,13 @@ fn collect_miri<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIte
 
 /// Scans the MIR in order to find function calls, closures, and drop-glue.
 #[instrument(skip(tcx, output), level = "debug")]
-fn collect_neighbours<'tcx>(
+fn collect_used_items<'tcx>(
     tcx: TyCtxt<'tcx>,
     instance: Instance<'tcx>,
     output: &mut MonoItems<'tcx>,
 ) {
     let body = tcx.instance_mir(instance.def);
-    MirNeighborCollector { tcx, body: &body, output, instance }.visit_body(&body);
+    MirUsedCollector { tcx, body: &body, output, instance }.visit_body(&body);
 }
 
 #[instrument(skip(tcx, output), level = "debug")]
diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs
index ecc50c3f664..89dadc782f2 100644
--- a/compiler/rustc_monomorphize/src/lib.rs
+++ b/compiler/rustc_monomorphize/src/lib.rs
@@ -1,4 +1,5 @@
 #![feature(array_windows)]
+#![feature(is_sorted)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 #![deny(rustc::untranslatable_diagnostic)]
diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs
index be9c349c384..79fcd62bc62 100644
--- a/compiler/rustc_monomorphize/src/partitioning.rs
+++ b/compiler/rustc_monomorphize/src/partitioning.rs
@@ -115,34 +115,38 @@ use rustc_middle::ty::{self, visit::TypeVisitableExt, InstanceDef, TyCtxt};
 use rustc_session::config::{DumpMonoStatsFormat, SwitchWithOptPath};
 use rustc_span::symbol::Symbol;
 
-use crate::collector::InliningMap;
+use crate::collector::UsageMap;
 use crate::collector::{self, MonoItemCollectionMode};
 use crate::errors::{CouldntDumpMonoStats, SymbolAlreadyDefined, UnknownCguCollectionMode};
 
 struct PartitioningCx<'a, 'tcx> {
     tcx: TyCtxt<'tcx>,
     target_cgu_count: usize,
-    inlining_map: &'a InliningMap<'tcx>,
+    usage_map: &'a UsageMap<'tcx>,
 }
 
 struct PlacedRootMonoItems<'tcx> {
+    /// The codegen units, sorted by name to make things deterministic.
     codegen_units: Vec<CodegenUnit<'tcx>>,
+
     roots: FxHashSet<MonoItem<'tcx>>,
     internalization_candidates: FxHashSet<MonoItem<'tcx>>,
 }
 
+// The output CGUs are sorted by name.
 fn partition<'tcx, I>(
     tcx: TyCtxt<'tcx>,
     mono_items: &mut I,
     max_cgu_count: usize,
-    inlining_map: &InliningMap<'tcx>,
+    usage_map: &UsageMap<'tcx>,
 ) -> Vec<CodegenUnit<'tcx>>
 where
     I: Iterator<Item = MonoItem<'tcx>>,
 {
     let _prof_timer = tcx.prof.generic_activity("cgu_partitioning");
 
-    let cx = &PartitioningCx { tcx, target_cgu_count: max_cgu_count, inlining_map };
+    let cx = &PartitioningCx { tcx, target_cgu_count: max_cgu_count, usage_map };
+
     // In the first step, we place all regular monomorphizations into their
     // respective 'home' codegen unit. Regular monomorphizations are all
     // functions and statics defined in the local crate.
@@ -225,8 +229,8 @@ where
         dead_code_cgu.make_code_coverage_dead_code_cgu();
     }
 
-    // Finally, sort by codegen unit name, so that we get deterministic results.
-    codegen_units.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str()));
+    // Ensure CGUs are sorted by name, so that we get deterministic results.
+    assert!(codegen_units.is_sorted_by(|a, b| Some(a.name().as_str().cmp(b.name().as_str()))));
 
     debug_dump(tcx, "FINAL", &codegen_units);
 
@@ -301,27 +305,22 @@ where
         codegen_units.insert(codegen_unit_name, CodegenUnit::new(codegen_unit_name));
     }
 
-    let codegen_units = codegen_units.into_values().collect();
+    let mut codegen_units: Vec<_> = codegen_units.into_values().collect();
+    codegen_units.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str()));
+
     PlacedRootMonoItems { codegen_units, roots, internalization_candidates }
 }
 
+// This function requires the CGUs to be sorted by name on input, and ensures
+// they are sorted by name on return, for deterministic behaviour.
 fn merge_codegen_units<'tcx>(
     cx: &PartitioningCx<'_, 'tcx>,
     codegen_units: &mut Vec<CodegenUnit<'tcx>>,
 ) {
     assert!(cx.target_cgu_count >= 1);
 
-    // Note that at this point in time the `codegen_units` here may not be
-    // in a deterministic order (but we know they're deterministically the
-    // same set). We want this merging to produce a deterministic ordering
-    // of codegen units from the input.
-    //
-    // Due to basically how we've implemented the merging below (merge the
-    // two smallest into each other) we're sure to start off with a
-    // deterministic order (sorted by name). This'll mean that if two cgus
-    // have the same size the stable sort below will keep everything nice
-    // and deterministic.
-    codegen_units.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str()));
+    // A sorted order here ensures merging is deterministic.
+    assert!(codegen_units.is_sorted_by(|a, b| Some(a.name().as_str().cmp(b.name().as_str()))));
 
     // This map keeps track of what got merged into what.
     let mut cgu_contents: FxHashMap<Symbol, Vec<Symbol>> =
@@ -400,10 +399,13 @@ fn merge_codegen_units<'tcx>(
             cgu.set_name(numbered_codegen_unit_name);
         }
     }
+
+    // A sorted order here ensures what follows can be deterministic.
+    codegen_units.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str()));
 }
 
 /// For symbol internalization, we need to know whether a symbol/mono-item is
-/// accessed from outside the codegen unit it is defined in. This type is used
+/// used from outside the codegen unit it is defined in. This type is used
 /// to keep track of that.
 #[derive(Clone, PartialEq, Eq, Debug)]
 enum MonoItemPlacement {
@@ -420,33 +422,25 @@ fn place_inlined_mono_items<'tcx>(
 
     let single_codegen_unit = codegen_units.len() == 1;
 
-    for old_codegen_unit in codegen_units.iter_mut() {
+    for cgu in codegen_units.iter_mut() {
         // Collect all items that need to be available in this codegen unit.
         let mut reachable = FxHashSet::default();
-        for root in old_codegen_unit.items().keys() {
-            follow_inlining(*root, cx.inlining_map, &mut reachable);
+        for root in cgu.items().keys() {
+            // Insert the root item itself, plus all inlined items that are
+            // reachable from it without going via another root item.
+            reachable.insert(*root);
+            get_reachable_inlined_items(cx.tcx, *root, cx.usage_map, &mut reachable);
         }
 
-        let mut new_codegen_unit = CodegenUnit::new(old_codegen_unit.name());
-
         // Add all monomorphizations that are not already there.
         for mono_item in reachable {
-            if let Some(linkage) = old_codegen_unit.items().get(&mono_item) {
-                // This is a root, just copy it over.
-                new_codegen_unit.items_mut().insert(mono_item, *linkage);
-            } else {
+            if !cgu.items().contains_key(&mono_item) {
                 if roots.contains(&mono_item) {
-                    bug!(
-                        "GloballyShared mono-item inlined into other CGU: \
-                          {:?}",
-                        mono_item
-                    );
+                    bug!("GloballyShared mono-item inlined into other CGU: {:?}", mono_item);
                 }
 
                 // This is a CGU-private copy.
-                new_codegen_unit
-                    .items_mut()
-                    .insert(mono_item, (Linkage::Internal, Visibility::Default));
+                cgu.items_mut().insert(mono_item, (Linkage::Internal, Visibility::Default));
             }
 
             if !single_codegen_unit {
@@ -456,38 +450,32 @@ fn place_inlined_mono_items<'tcx>(
                     Entry::Occupied(e) => {
                         let placement = e.into_mut();
                         debug_assert!(match *placement {
-                            MonoItemPlacement::SingleCgu { cgu_name } => {
-                                cgu_name != new_codegen_unit.name()
-                            }
+                            MonoItemPlacement::SingleCgu { cgu_name } => cgu_name != cgu.name(),
                             MonoItemPlacement::MultipleCgus => true,
                         });
                         *placement = MonoItemPlacement::MultipleCgus;
                     }
                     Entry::Vacant(e) => {
-                        e.insert(MonoItemPlacement::SingleCgu {
-                            cgu_name: new_codegen_unit.name(),
-                        });
+                        e.insert(MonoItemPlacement::SingleCgu { cgu_name: cgu.name() });
                     }
                 }
             }
         }
-
-        *old_codegen_unit = new_codegen_unit;
     }
 
     return mono_item_placements;
 
-    fn follow_inlining<'tcx>(
-        mono_item: MonoItem<'tcx>,
-        inlining_map: &InliningMap<'tcx>,
+    fn get_reachable_inlined_items<'tcx>(
+        tcx: TyCtxt<'tcx>,
+        item: MonoItem<'tcx>,
+        usage_map: &UsageMap<'tcx>,
         visited: &mut FxHashSet<MonoItem<'tcx>>,
     ) {
-        if !visited.insert(mono_item) {
-            return;
-        }
-
-        inlining_map.with_inlining_candidates(mono_item, |target| {
-            follow_inlining(target, inlining_map, visited);
+        usage_map.for_each_inlined_used_item(tcx, item, |inlined_item| {
+            let is_new = visited.insert(inlined_item);
+            if is_new {
+                get_reachable_inlined_items(tcx, inlined_item, usage_map, visited);
+            }
         });
     }
 }
@@ -501,7 +489,7 @@ fn internalize_symbols<'tcx>(
     if codegen_units.len() == 1 {
         // Fast path for when there is only one codegen unit. In this case we
         // can internalize all candidates, since there is nowhere else they
-        // could be accessed from.
+        // could be used from.
         for cgu in codegen_units {
             for candidate in &internalization_candidates {
                 cgu.items_mut().insert(*candidate, (Linkage::Internal, Visibility::Default));
@@ -511,45 +499,36 @@ fn internalize_symbols<'tcx>(
         return;
     }
 
-    // Build a map from every monomorphization to all the monomorphizations that
-    // reference it.
-    let mut accessor_map: FxHashMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>> = Default::default();
-    cx.inlining_map.iter_accesses(|accessor, accessees| {
-        for accessee in accessees {
-            accessor_map.entry(*accessee).or_default().push(accessor);
-        }
-    });
-
     // For each internalization candidates in each codegen unit, check if it is
-    // accessed from outside its defining codegen unit.
+    // used from outside its defining codegen unit.
     for cgu in codegen_units {
         let home_cgu = MonoItemPlacement::SingleCgu { cgu_name: cgu.name() };
 
-        for (accessee, linkage_and_visibility) in cgu.items_mut() {
-            if !internalization_candidates.contains(accessee) {
+        for (item, linkage_and_visibility) in cgu.items_mut() {
+            if !internalization_candidates.contains(item) {
                 // This item is no candidate for internalizing, so skip it.
                 continue;
             }
-            debug_assert_eq!(mono_item_placements[accessee], home_cgu);
+            debug_assert_eq!(mono_item_placements[item], home_cgu);
 
-            if let Some(accessors) = accessor_map.get(accessee) {
-                if accessors
+            if let Some(user_items) = cx.usage_map.get_user_items(*item) {
+                if user_items
                     .iter()
-                    .filter_map(|accessor| {
-                        // Some accessors might not have been
+                    .filter_map(|user_item| {
+                        // Some user mono items might not have been
                         // instantiated. We can safely ignore those.
-                        mono_item_placements.get(accessor)
+                        mono_item_placements.get(user_item)
                     })
                     .any(|placement| *placement != home_cgu)
                 {
-                    // Found an accessor from another CGU, so skip to the next
-                    // item without marking this one as internal.
+                    // Found a user from another CGU, so skip to the next item
+                    // without marking this one as internal.
                     continue;
                 }
             }
 
-            // If we got here, we did not find any accesses from other CGUs,
-            // so it's fine to make this monomorphization internal.
+            // If we got here, we did not find any uses from other CGUs, so
+            // it's fine to make this monomorphization internal.
             *linkage_and_visibility = (Linkage::Internal, Visibility::Default);
         }
     }
@@ -785,7 +764,7 @@ fn mono_item_visibility<'tcx>(
     } else {
         // If this isn't a generic function then we mark this a `Default` if
         // this is a reachable item, meaning that it's a symbol other crates may
-        // access when they link to us.
+        // use when they link to us.
         if tcx.is_reachable_non_generic(def_id.to_def_id()) {
             *can_be_internalized = false;
             debug_assert!(!is_generic);
@@ -858,36 +837,46 @@ fn default_visibility(tcx: TyCtxt<'_>, id: DefId, is_generic: bool) -> Visibilit
         _ => Visibility::Hidden,
     }
 }
+
 fn debug_dump<'a, 'tcx: 'a>(tcx: TyCtxt<'tcx>, label: &str, cgus: &[CodegenUnit<'tcx>]) {
     let dump = move || {
         use std::fmt::Write;
 
         let num_cgus = cgus.len();
-        let max = cgus.iter().map(|cgu| cgu.size_estimate()).max().unwrap();
-        let min = cgus.iter().map(|cgu| cgu.size_estimate()).min().unwrap();
-        let ratio = max as f64 / min as f64;
+        let num_items: usize = cgus.iter().map(|cgu| cgu.items().len()).sum();
+        let total_size: usize = cgus.iter().map(|cgu| cgu.size_estimate()).sum();
+        let max_size = cgus.iter().map(|cgu| cgu.size_estimate()).max().unwrap();
+        let min_size = cgus.iter().map(|cgu| cgu.size_estimate()).min().unwrap();
+        let max_min_size_ratio = max_size as f64 / min_size as f64;
 
         let s = &mut String::new();
         let _ = writeln!(
             s,
-            "{label} ({num_cgus} CodegenUnits, max={max}, min={min}, max/min={ratio:.1}):"
+            "{label} ({num_items} items, total_size={total_size}; {num_cgus} CGUs, \
+             max_size={max_size}, min_size={min_size}, max_size/min_size={max_min_size_ratio:.1}):"
         );
-        for cgu in cgus {
-            let _ =
-                writeln!(s, "CodegenUnit {} estimated size {}:", cgu.name(), cgu.size_estimate());
+        for (i, cgu) in cgus.iter().enumerate() {
+            let num_items = cgu.items().len();
+            let _ = writeln!(
+                s,
+                "- CGU[{i}] {} ({num_items} items, size={}):",
+                cgu.name(),
+                cgu.size_estimate()
+            );
 
-            for (mono_item, linkage) in cgu.items() {
-                let symbol_name = mono_item.symbol_name(tcx).name;
+            // The order of `cgu.items()` is non-deterministic; sort it by name
+            // to give deterministic output.
+            let mut items: Vec<_> = cgu.items().iter().collect();
+            items.sort_by_key(|(item, _)| item.symbol_name(tcx).name);
+            for (item, linkage) in items {
+                let symbol_name = item.symbol_name(tcx).name;
                 let symbol_hash_start = symbol_name.rfind('h');
                 let symbol_hash = symbol_hash_start.map_or("<no hash>", |i| &symbol_name[i..]);
 
+                let size = item.size_estimate(tcx);
                 let _ = with_no_trimmed_paths!(writeln!(
                     s,
-                    " - {} [{:?}] [{}] estimated size {}",
-                    mono_item,
-                    linkage,
-                    symbol_hash,
-                    mono_item.size_estimate(tcx)
+                    "  - {item} [{linkage:?}] [{symbol_hash}] (size={size})"
                 ));
             }
 
@@ -955,7 +944,7 @@ fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> (&DefIdSet, &[Co
         }
     };
 
-    let (items, inlining_map) = collector::collect_crate_mono_items(tcx, collection_mode);
+    let (items, usage_map) = collector::collect_crate_mono_items(tcx, collection_mode);
 
     tcx.sess.abort_if_errors();
 
@@ -966,7 +955,7 @@ fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> (&DefIdSet, &[Co
                     tcx,
                     &mut items.iter().copied(),
                     tcx.sess.codegen_units(),
-                    &inlining_map,
+                    &usage_map,
                 );
                 codegen_units[0].make_primary();
                 &*tcx.arena.alloc_from_iter(codegen_units)
diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs
index 2357b0aadef..fc437c429fb 100644
--- a/compiler/rustc_passes/src/check_const.rs
+++ b/compiler/rustc_passes/src/check_const.rs
@@ -199,6 +199,11 @@ impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
         self.recurse_into(kind, None, |this| intravisit::walk_anon_const(this, anon));
     }
 
+    fn visit_inline_const(&mut self, block: &'tcx hir::ConstBlock) {
+        let kind = Some(hir::ConstContext::Const);
+        self.recurse_into(kind, None, |this| intravisit::walk_inline_const(this, block));
+    }
+
     fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
         let owner = self.tcx.hir().body_owner_def_id(body.id());
         let kind = self.tcx.hir().body_const_context(owner);
diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs
index 7812dcde44c..d5ac1cd9ce3 100644
--- a/compiler/rustc_passes/src/dead.rs
+++ b/compiler/rustc_passes/src/dead.rs
@@ -500,6 +500,17 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
 
         self.in_pat = in_pat;
     }
+
+    fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
+        // When inline const blocks are used in pattern position, paths
+        // referenced by it should be considered as used.
+        let in_pat = mem::replace(&mut self.in_pat, false);
+
+        self.live_symbols.insert(c.def_id);
+        intravisit::walk_inline_const(self, c);
+
+        self.in_pat = in_pat;
+    }
 }
 
 fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
diff --git a/compiler/rustc_passes/src/layout_test.rs b/compiler/rustc_passes/src/layout_test.rs
index 5a1ae808e66..9971bdf45bb 100644
--- a/compiler/rustc_passes/src/layout_test.rs
+++ b/compiler/rustc_passes/src/layout_test.rs
@@ -93,7 +93,7 @@ fn dump_layout_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &Attribute) {
 
         Err(layout_error) => {
             tcx.sess.emit_fatal(Spanned {
-                node: layout_error,
+                node: layout_error.into_diagnostic(),
                 span: tcx.def_span(item_def_id.to_def_id()),
             });
         }
@@ -109,12 +109,7 @@ impl<'tcx> LayoutOfHelpers<'tcx> for UnwrapLayoutCx<'tcx> {
     type LayoutOfResult = TyAndLayout<'tcx>;
 
     fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! {
-        span_bug!(
-            span,
-            "`#[rustc_layout(..)]` test resulted in `layout_of({}) = Err({})`",
-            ty,
-            err
-        );
+        span_bug!(span, "`#[rustc_layout(..)]` test resulted in `layout_of({ty}) = Err({err})`",);
     }
 }
 
diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs
index 73cfe68e7f2..7c64df6a50e 100644
--- a/compiler/rustc_passes/src/loops.rs
+++ b/compiler/rustc_passes/src/loops.rs
@@ -24,7 +24,7 @@ enum Context {
     Closure(Span),
     AsyncClosure(Span),
     LabeledBlock,
-    AnonConst,
+    Constant,
 }
 
 #[derive(Copy, Clone)]
@@ -53,7 +53,11 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
     }
 
     fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) {
-        self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c));
+        self.with_context(Constant, |v| intravisit::walk_anon_const(v, c));
+    }
+
+    fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) {
+        self.with_context(Constant, |v| intravisit::walk_inline_const(v, c));
     }
 
     fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
@@ -192,7 +196,7 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
             AsyncClosure(closure_span) => {
                 self.sess.emit_err(BreakInsideAsyncBlock { span, closure_span, name });
             }
-            Normal | AnonConst => {
+            Normal | Constant => {
                 self.sess.emit_err(OutsideLoop { span, name, is_break: name == "break" });
             }
         }
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index 65dfdf31e54..0197b99c7db 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -751,7 +751,7 @@ impl<'tcx> Visitor<'tcx> for EmbargoVisitor<'tcx> {
                         reach.generics().predicates();
 
                         if trait_item_ref.kind == AssocItemKind::Type
-                            && !tcx.impl_defaultness(trait_item_ref.id.owner_id).has_value()
+                            && !tcx.defaultness(trait_item_ref.id.owner_id).has_value()
                         {
                             // No type to visit.
                         } else {
@@ -1927,7 +1927,7 @@ impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> {
 
         let (check_ty, is_assoc_ty) = match assoc_item_kind {
             AssocItemKind::Const | AssocItemKind::Fn { .. } => (true, false),
-            AssocItemKind::Type => (self.tcx.impl_defaultness(def_id).has_value(), true),
+            AssocItemKind::Type => (self.tcx.defaultness(def_id).has_value(), true),
         };
         check.in_assoc_ty = is_assoc_ty;
         check.generics().predicates();
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 1eb54cee5a1..2ebd83540ee 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -128,8 +128,6 @@ pub struct Limits {
     pub move_size_limit: Limit,
     /// The maximum length of types during monomorphization.
     pub type_length_limit: Limit,
-    /// The maximum blocks a const expression can evaluate.
-    pub const_eval_limit: Limit,
 }
 
 pub struct CompilerIO {
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 1185563ea80..2f002e42427 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1146,6 +1146,8 @@ symbols! {
         profiler_builtins,
         profiler_runtime,
         ptr,
+        ptr_cast_mut,
+        ptr_from_ref,
         ptr_guaranteed_cmp,
         ptr_mask,
         ptr_null,
diff --git a/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs b/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs
index b245742e533..85825513ce9 100644
--- a/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs
+++ b/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs
@@ -501,7 +501,15 @@ fn encode_ty<'tcx>(
         ty::Array(ty0, len) => {
             // A<array-length><element-type>
             let mut s = String::from("A");
-            let _ = write!(s, "{}", &len.kind().try_to_scalar().unwrap().to_u64().unwrap());
+            let _ = write!(
+                s,
+                "{}",
+                &len.kind()
+                    .try_to_scalar()
+                    .unwrap()
+                    .to_u64()
+                    .unwrap_or_else(|_| panic!("failed to convert length to u64"))
+            );
             s.push_str(&encode_ty(tcx, *ty0, dict, options));
             compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
             typeid.push_str(&s);
@@ -601,9 +609,7 @@ fn encode_ty<'tcx>(
         }
 
         // Function types
-        ty::FnDef(def_id, substs)
-        | ty::Closure(def_id, substs)
-        | ty::Generator(def_id, substs, ..) => {
+        ty::FnDef(def_id, substs) | ty::Closure(def_id, substs) => {
             // u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>,
             // as vendor extended type.
             let mut s = String::new();
@@ -614,6 +620,23 @@ fn encode_ty<'tcx>(
             typeid.push_str(&s);
         }
 
+        ty::Generator(def_id, substs, ..) => {
+            // u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>,
+            // as vendor extended type.
+            let mut s = String::new();
+            let name = encode_ty_name(tcx, *def_id);
+            let _ = write!(s, "u{}{}", name.len(), &name);
+            // Encode parent substs only
+            s.push_str(&encode_substs(
+                tcx,
+                tcx.mk_substs(substs.as_generator().parent_substs()),
+                dict,
+                options,
+            ));
+            compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
+            typeid.push_str(&s);
+        }
+
         // Pointer types
         ty::Ref(region, ty0, ..) => {
             // [U3mut]u3refI<element-type>E as vendor extended type qualifier and type
@@ -674,12 +697,12 @@ fn encode_ty<'tcx>(
         }
 
         // Unexpected types
-        ty::Bound(..)
+        ty::Alias(..)
+        | ty::Bound(..)
         | ty::Error(..)
         | ty::GeneratorWitness(..)
         | ty::GeneratorWitnessMIR(..)
         | ty::Infer(..)
-        | ty::Alias(..)
         | ty::Placeholder(..) => {
             bug!("encode_ty: unexpected `{:?}`", ty.kind());
         }
@@ -732,7 +755,12 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio
     let mut ty = ty;
 
     match ty.kind() {
-        ty::Float(..) | ty::Char | ty::Str | ty::Never | ty::Foreign(..) => {}
+        ty::Float(..)
+        | ty::Char
+        | ty::Str
+        | ty::Never
+        | ty::Foreign(..)
+        | ty::GeneratorWitness(..) => {}
 
         ty::Bool => {
             if options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) {
@@ -786,7 +814,12 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio
         }
 
         ty::Array(ty0, len) => {
-            let len = len.kind().try_to_scalar().unwrap().to_u64().unwrap();
+            let len = len
+                .kind()
+                .try_to_scalar()
+                .unwrap()
+                .to_u64()
+                .unwrap_or_else(|_| panic!("failed to convert length to u64"));
             ty = tcx.mk_array(transform_ty(tcx, *ty0, options), len);
         }
 
@@ -913,12 +946,18 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio
             );
         }
 
+        ty::Alias(..) => {
+            ty = transform_ty(
+                tcx,
+                tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), ty),
+                options,
+            );
+        }
+
         ty::Bound(..)
         | ty::Error(..)
-        | ty::GeneratorWitness(..)
         | ty::GeneratorWitnessMIR(..)
         | ty::Infer(..)
-        | ty::Alias(..)
         | ty::Param(..)
         | ty::Placeholder(..) => {
             bug!("transform_ty: unexpected `{:?}`", ty.kind());
diff --git a/compiler/rustc_target/src/abi/call/mod.rs b/compiler/rustc_target/src/abi/call/mod.rs
index 1ae11f5671c..c4abf6f4b5e 100644
--- a/compiler/rustc_target/src/abi/call/mod.rs
+++ b/compiler/rustc_target/src/abi/call/mod.rs
@@ -2,7 +2,6 @@ use crate::abi::{self, Abi, Align, FieldsShape, Size};
 use crate::abi::{HasDataLayout, TyAbiInterface, TyAndLayout};
 use crate::spec::{self, HasTargetSpec};
 use rustc_span::Symbol;
-use std::fmt;
 use std::str::FromStr;
 
 mod aarch64;
@@ -633,16 +632,6 @@ pub enum AdjustForForeignAbiError {
     Unsupported { arch: Symbol, abi: spec::abi::Abi },
 }
 
-impl fmt::Display for AdjustForForeignAbiError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::Unsupported { arch, abi } => {
-                write!(f, "target architecture {arch:?} does not support `extern {abi}` ABI")
-            }
-        }
-    }
-}
-
 impl<'a, Ty> FnAbi<'a, Ty> {
     pub fn adjust_for_foreign_abi<C>(
         &mut self,
diff --git a/compiler/rustc_target/src/abi/call/x86_64.rs b/compiler/rustc_target/src/abi/call/x86_64.rs
index 9427f27d1b7..74ef53915c9 100644
--- a/compiler/rustc_target/src/abi/call/x86_64.rs
+++ b/compiler/rustc_target/src/abi/call/x86_64.rs
@@ -1,5 +1,5 @@
 // The classification code for the x86_64 ABI is taken from the clay language
-// https://github.com/jckarter/clay/blob/master/compiler/src/externals.cpp
+// https://github.com/jckarter/clay/blob/db0bd2702ab0b6e48965cd85f8859bbd5f60e48e/compiler/externals.cpp
 
 use crate::abi::call::{ArgAbi, CastTarget, FnAbi, Reg, RegKind};
 use crate::abi::{self, Abi, HasDataLayout, Size, TyAbiInterface, TyAndLayout};
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_ios.rs b/compiler/rustc_target/src/spec/x86_64_apple_ios.rs
index 1dcb47056a4..061b6a96fc8 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_ios.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_ios.rs
@@ -13,7 +13,7 @@ pub fn target() -> Target {
             .into(),
         arch: arch.target_arch(),
         options: TargetOptions {
-            max_atomic_width: Some(64),
+            max_atomic_width: Some(128),
             stack_probes: StackProbeType::X86,
             ..base
         },
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
index 9f3b0fab697..50f359c357b 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs
@@ -15,7 +15,7 @@ pub fn target() -> Target {
             .into(),
         arch: arch.target_arch(),
         options: TargetOptions {
-            max_atomic_width: Some(64),
+            max_atomic_width: Some(128),
             stack_probes: StackProbeType::X86,
             ..base
         },
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs b/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs
index 550ce0b9ce5..76de7d20c4c 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs
@@ -9,7 +9,7 @@ pub fn target() -> Target {
         data_layout: "e-m:o-i64:64-f80:128-n8:16:32:64-S128".into(),
         arch: arch.target_arch(),
         options: TargetOptions {
-            max_atomic_width: Some(64),
+            max_atomic_width: Some(128),
             stack_probes: StackProbeType::X86,
             ..opts("tvos", arch)
         },
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs b/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs
index 75ce02cba1d..5fcc00a86ff 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs
@@ -10,7 +10,7 @@ pub fn target() -> Target {
             .into(),
         arch: arch.target_arch(),
         options: TargetOptions {
-            max_atomic_width: Some(64),
+            max_atomic_width: Some(128),
             stack_probes: StackProbeType::X86,
             forces_embed_bitcode: true,
             // Taken from a clang build on Xcode 11.4.1.
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
index 1470dc452a1..01c74be7057 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -149,6 +149,12 @@ pub trait TypeErrCtxtExt<'tcx> {
         root_obligation: &PredicateObligation<'tcx>,
         error: &SelectionError<'tcx>,
     );
+
+    fn report_const_param_not_wf(
+        &self,
+        ty: Ty<'tcx>,
+        obligation: &PredicateObligation<'tcx>,
+    ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>;
 }
 
 impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
@@ -641,6 +647,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
                         span = obligation.cause.span;
                     }
                 }
+
                 if let ObligationCauseCode::CompareImplItemObligation {
                     impl_item_def_id,
                     trait_item_def_id,
@@ -657,6 +664,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
                     return;
                 }
 
+                // Report a const-param specific error
+                if let ObligationCauseCode::ConstParam(ty) = *obligation.cause.code().peel_derives()
+                {
+                    self.report_const_param_not_wf(ty, &obligation).emit();
+                    return;
+                }
+
                 let bound_predicate = obligation.predicate.kind();
                 match bound_predicate.skip_binder() {
                     ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) => {
@@ -1163,6 +1177,102 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
         self.point_at_returns_when_relevant(&mut err, &obligation);
         err.emit();
     }
+
+    fn report_const_param_not_wf(
+        &self,
+        ty: Ty<'tcx>,
+        obligation: &PredicateObligation<'tcx>,
+    ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
+        let span = obligation.cause.span;
+
+        let mut diag = match ty.kind() {
+            _ if ty.has_param() => {
+                span_bug!(span, "const param tys cannot mention other generic parameters");
+            }
+            ty::Float(_) => {
+                struct_span_err!(
+                    self.tcx.sess,
+                    span,
+                    E0741,
+                    "`{ty}` is forbidden as the type of a const generic parameter",
+                )
+            }
+            ty::FnPtr(_) => {
+                struct_span_err!(
+                    self.tcx.sess,
+                    span,
+                    E0741,
+                    "using function pointers as const generic parameters is forbidden",
+                )
+            }
+            ty::RawPtr(_) => {
+                struct_span_err!(
+                    self.tcx.sess,
+                    span,
+                    E0741,
+                    "using raw pointers as const generic parameters is forbidden",
+                )
+            }
+            ty::Adt(def, _) => {
+                // We should probably see if we're *allowed* to derive `ConstParamTy` on the type...
+                let mut diag = struct_span_err!(
+                    self.tcx.sess,
+                    span,
+                    E0741,
+                    "`{ty}` must implement `ConstParamTy` to be used as the type of a const generic parameter",
+                );
+                // Only suggest derive if this isn't a derived obligation,
+                // and the struct is local.
+                if let Some(span) = self.tcx.hir().span_if_local(def.did())
+                    && obligation.cause.code().parent().is_none()
+                {
+                    if ty.is_structural_eq_shallow(self.tcx) {
+                        diag.span_suggestion(
+                            span,
+                            "add `#[derive(ConstParamTy)]` to the struct",
+                            "#[derive(ConstParamTy)]\n",
+                            Applicability::MachineApplicable,
+                        );
+                    } else {
+                        // FIXME(adt_const_params): We should check there's not already an
+                        // overlapping `Eq`/`PartialEq` impl.
+                        diag.span_suggestion(
+                            span,
+                            "add `#[derive(ConstParamTy, PartialEq, Eq)]` to the struct",
+                            "#[derive(ConstParamTy, PartialEq, Eq)]\n",
+                            Applicability::MachineApplicable,
+                        );
+                    }
+                }
+                diag
+            }
+            _ => {
+                struct_span_err!(
+                    self.tcx.sess,
+                    span,
+                    E0741,
+                    "`{ty}` can't be used as a const parameter type",
+                )
+            }
+        };
+
+        let mut code = obligation.cause.code();
+        let mut pred = obligation.predicate.to_opt_poly_trait_pred();
+        while let Some((next_code, next_pred)) = code.parent() {
+            if let Some(pred) = pred {
+                let pred = self.instantiate_binder_with_placeholders(pred);
+                diag.note(format!(
+                    "`{}` must implement `{}`, but it does not",
+                    pred.self_ty(),
+                    pred.print_modifiers_and_trait_path()
+                ));
+            }
+            code = next_code;
+            pred = next_pred;
+        }
+
+        diag
+    }
 }
 
 trait InferCtxtPrivExt<'tcx> {
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index b5b8c7fe3ac..204d6fd043b 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -2655,7 +2655,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
             | ObligationCauseCode::BinOp { .. }
             | ObligationCauseCode::AscribeUserTypeProvePredicate(..)
             | ObligationCauseCode::RustCall
-            | ObligationCauseCode::DropImpl => {}
+            | ObligationCauseCode::DropImpl
+            | ObligationCauseCode::ConstParam(_) => {}
             ObligationCauseCode::SliceOrArrayElem => {
                 err.note("slice and array elements must have `Sized` type");
             }
diff --git a/compiler/rustc_trait_selection/src/traits/misc.rs b/compiler/rustc_trait_selection/src/traits/misc.rs
index 2210ef975e6..e9cfd63e2ed 100644
--- a/compiler/rustc_trait_selection/src/traits/misc.rs
+++ b/compiler/rustc_trait_selection/src/traits/misc.rs
@@ -100,7 +100,8 @@ pub fn type_allowed_to_implement_const_param_ty<'tcx>(
         | ty::Str
         | ty::Array(..)
         | ty::Slice(_)
-        | ty::Ref(.., hir::Mutability::Not) => return Ok(()),
+        | ty::Ref(.., hir::Mutability::Not)
+        | ty::Tuple(_) => return Ok(()),
 
         &ty::Adt(adt, substs) => (adt, substs),
 
diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs
index f7389bda159..c2f94cb6385 100644
--- a/compiler/rustc_trait_selection/src/traits/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/mod.rs
@@ -62,9 +62,7 @@ pub use self::specialize::specialization_graph::FutureCompatOverlapErrorKind;
 pub use self::specialize::{
     specialization_graph, translate_substs, translate_substs_with_cause, OverlapError,
 };
-pub use self::structural_match::{
-    search_for_adt_const_param_violation, search_for_structural_match_violation,
-};
+pub use self::structural_match::search_for_structural_match_violation;
 pub use self::structural_normalize::StructurallyNormalizeExt;
 pub use self::util::elaborate;
 pub use self::util::{expand_trait_aliases, TraitAliasExpander};
diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs
index 9582479941b..048302187cf 100644
--- a/compiler/rustc_trait_selection/src/traits/object_safety.rs
+++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs
@@ -526,7 +526,7 @@ fn virtual_call_violation_for_method<'tcx>(
                         // #78372
                         tcx.sess.delay_span_bug(
                             tcx.def_span(method.def_id),
-                            format!("error: {}\n while computing layout for type {:?}", err, ty),
+                            format!("error: {err}\n while computing layout for type {ty:?}"),
                         );
                         None
                     }
diff --git a/compiler/rustc_trait_selection/src/traits/structural_match.rs b/compiler/rustc_trait_selection/src/traits/structural_match.rs
index e38ae9381c1..420f8c5dceb 100644
--- a/compiler/rustc_trait_selection/src/traits/structural_match.rs
+++ b/compiler/rustc_trait_selection/src/traits/structural_match.rs
@@ -34,24 +34,7 @@ pub fn search_for_structural_match_violation<'tcx>(
     tcx: TyCtxt<'tcx>,
     ty: Ty<'tcx>,
 ) -> Option<Ty<'tcx>> {
-    ty.visit_with(&mut Search { tcx, span, seen: FxHashSet::default(), adt_const_param: false })
-        .break_value()
-}
-
-/// This method traverses the structure of `ty`, trying to find any
-/// types that are not allowed to be used in a const generic.
-///
-/// This is either because the type does not implement `StructuralEq`
-/// and `StructuralPartialEq`, or because the type is intentionally
-/// not supported in const generics (such as floats and raw pointers,
-/// which are allowed in match blocks).
-pub fn search_for_adt_const_param_violation<'tcx>(
-    span: Span,
-    tcx: TyCtxt<'tcx>,
-    ty: Ty<'tcx>,
-) -> Option<Ty<'tcx>> {
-    ty.visit_with(&mut Search { tcx, span, seen: FxHashSet::default(), adt_const_param: true })
-        .break_value()
+    ty.visit_with(&mut Search { tcx, span, seen: FxHashSet::default() }).break_value()
 }
 
 /// This implements the traversal over the structure of a given type to try to
@@ -65,11 +48,6 @@ struct Search<'tcx> {
     /// Tracks ADTs previously encountered during search, so that
     /// we will not recur on them again.
     seen: FxHashSet<hir::def_id::DefId>,
-
-    // Additionally deny things that have been allowed in patterns,
-    // but are not allowed in adt const params, such as floats and
-    // fn ptrs.
-    adt_const_param: bool,
 }
 
 impl<'tcx> Search<'tcx> {
@@ -124,41 +102,29 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for Search<'tcx> {
             }
 
             ty::FnPtr(..) => {
-                if !self.adt_const_param {
-                    return ControlFlow::Continue(());
-                } else {
-                    return ControlFlow::Break(ty);
-                }
+                return ControlFlow::Continue(());
             }
 
             ty::RawPtr(..) => {
-                if !self.adt_const_param {
-                    // structural-match ignores substructure of
-                    // `*const _`/`*mut _`, so skip `super_visit_with`.
-                    //
-                    // For example, if you have:
-                    // ```
-                    // struct NonStructural;
-                    // #[derive(PartialEq, Eq)]
-                    // struct T(*const NonStructural);
-                    // const C: T = T(std::ptr::null());
-                    // ```
-                    //
-                    // Even though `NonStructural` does not implement `PartialEq`,
-                    // structural equality on `T` does not recur into the raw
-                    // pointer. Therefore, one can still use `C` in a pattern.
-                    return ControlFlow::Continue(());
-                } else {
-                    return ControlFlow::Break(ty);
-                }
+                // structural-match ignores substructure of
+                // `*const _`/`*mut _`, so skip `super_visit_with`.
+                //
+                // For example, if you have:
+                // ```
+                // struct NonStructural;
+                // #[derive(PartialEq, Eq)]
+                // struct T(*const NonStructural);
+                // const C: T = T(std::ptr::null());
+                // ```
+                //
+                // Even though `NonStructural` does not implement `PartialEq`,
+                // structural equality on `T` does not recur into the raw
+                // pointer. Therefore, one can still use `C` in a pattern.
+                return ControlFlow::Continue(());
             }
 
             ty::Float(_) => {
-                if !self.adt_const_param {
-                    return ControlFlow::Continue(());
-                } else {
-                    return ControlFlow::Break(ty);
-                }
+                return ControlFlow::Continue(());
             }
 
             ty::Array(..) | ty::Slice(_) | ty::Ref(..) | ty::Tuple(..) => {
diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs
index e2c9c62512e..537405b7eb9 100644
--- a/compiler/rustc_trait_selection/src/traits/util.rs
+++ b/compiler/rustc_trait_selection/src/traits/util.rs
@@ -295,7 +295,7 @@ pub fn future_trait_ref_and_outputs<'tcx>(
 
 pub fn impl_item_is_final(tcx: TyCtxt<'_>, assoc_item: &ty::AssocItem) -> bool {
     assoc_item.defaultness(tcx).is_final()
-        && tcx.impl_defaultness(assoc_item.container_id(tcx)).is_final()
+        && tcx.defaultness(assoc_item.container_id(tcx)).is_final()
 }
 
 pub enum TupleArgumentsFlag {
diff --git a/compiler/rustc_ty_utils/src/assoc.rs b/compiler/rustc_ty_utils/src/assoc.rs
index 57b4183d336..658ab03c0f4 100644
--- a/compiler/rustc_ty_utils/src/assoc.rs
+++ b/compiler/rustc_ty_utils/src/assoc.rs
@@ -297,8 +297,8 @@ fn associated_type_for_impl_trait_in_trait(
     // Copy visility of the containing function.
     trait_assoc_ty.visibility(tcx.visibility(fn_def_id));
 
-    // Copy impl_defaultness of the containing function.
-    trait_assoc_ty.impl_defaultness(tcx.impl_defaultness(fn_def_id));
+    // Copy defaultness of the containing function.
+    trait_assoc_ty.defaultness(tcx.defaultness(fn_def_id));
 
     // Copy type_of of the opaque.
     trait_assoc_ty.type_of(ty::EarlyBinder::bind(tcx.mk_opaque(
@@ -393,8 +393,8 @@ fn associated_type_for_impl_trait_in_impl(
     // Copy visility of the containing function.
     impl_assoc_ty.visibility(tcx.visibility(impl_fn_def_id));
 
-    // Copy impl_defaultness of the containing function.
-    impl_assoc_ty.impl_defaultness(tcx.impl_defaultness(impl_fn_def_id));
+    // Copy defaultness of the containing function.
+    impl_assoc_ty.defaultness(tcx.defaultness(impl_fn_def_id));
 
     // Copy generics_of the trait's associated item but the impl as the parent.
     // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty) resolves to the trait instead of the impl
diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs
index 36a20c78fcc..dd911239ac4 100644
--- a/compiler/rustc_ty_utils/src/instance.rs
+++ b/compiler/rustc_ty_utils/src/instance.rs
@@ -204,7 +204,7 @@ fn resolve_associated_item<'tcx>(
             } else {
                 // All other methods are default methods of the `Future` trait.
                 // (this assumes that `ImplSource::Future` is only used for methods on `Future`)
-                debug_assert!(tcx.impl_defaultness(trait_item_id).has_value());
+                debug_assert!(tcx.defaultness(trait_item_id).has_value());
                 Some(Instance::new(trait_item_id, rcvr_substs))
             }
         }
diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs
index 12ad8b0842f..fe2d1fba7fe 100644
--- a/compiler/rustc_ty_utils/src/ty.rs
+++ b/compiler/rustc_ty_utils/src/ty.rs
@@ -75,13 +75,13 @@ fn sized_constraint_for_ty<'tcx>(
     result
 }
 
-fn impl_defaultness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Defaultness {
+fn defaultness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Defaultness {
     match tcx.hir().get_by_def_id(def_id) {
         hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(impl_), .. }) => impl_.defaultness,
         hir::Node::ImplItem(hir::ImplItem { defaultness, .. })
         | hir::Node::TraitItem(hir::TraitItem { defaultness, .. }) => *defaultness,
         node => {
-            bug!("`impl_defaultness` called on {:?}", node);
+            bug!("`defaultness` called on {:?}", node);
         }
     }
 }
@@ -186,6 +186,7 @@ fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> {
                 kind: hir::TraitItemKind::Const(..), ..
             })
             | hir::Node::AnonConst(_)
+            | hir::Node::ConstBlock(_)
             | hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Const(..), .. })
             | hir::Node::ImplItem(hir::ImplItem {
                 kind:
@@ -574,7 +575,7 @@ pub fn provide(providers: &mut Providers) {
         param_env_reveal_all_normalized,
         instance_def_size_estimate,
         issue33140_self_ty,
-        impl_defaultness,
+        defaultness,
         unsizing_params_for_adt,
         ..*providers
     };