about summary refs log tree commit diff
path: root/compiler/rustc_middle/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_middle/src')
-rw-r--r--compiler/rustc_middle/src/error.rs55
-rw-r--r--compiler/rustc_middle/src/lib.rs3
-rw-r--r--compiler/rustc_middle/src/mir/interpret/allocation.rs48
-rw-r--r--compiler/rustc_middle/src/mir/interpret/error.rs437
-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/pretty.rs2
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs3
-rw-r--r--compiler/rustc_middle/src/ty/consts/int.rs9
-rw-r--r--compiler/rustc_middle/src/ty/layout.rs75
-rw-r--r--compiler/rustc_middle/src/ty/vtable.rs2
12 files changed, 405 insertions, 353 deletions
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/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/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 357bcca4419..ca6f58adbb1 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,26 +437,9 @@ 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,
@@ -471,47 +449,29 @@ pub enum ResourceExhaustionInfo {
     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")
-            }
-            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()
     }
 }
 
 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),
@@ -527,26 +487,19 @@ pub enum InterpError<'tcx> {
 
 pub type InterpResult<'tcx, T = ()> = Result<T, InterpErrorInfo<'tcx>>;
 
-impl fmt::Display for InterpError<'_> {
+impl fmt::Debug 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}"),
+        match self {
+            Unsupported(msg) => msg.fmt(f),
+            InvalidProgram(msg) => msg.fmt(f),
+            UndefinedBehavior(msg) => msg.fmt(f),
+            ResourceExhaustion(msg) => msg.fmt(f),
+            MachineStop(msg) => msg.fmt(f),
         }
     }
 }
 
-// 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,
@@ -555,7 +508,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/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 3e474c1d377..83f8f00d72c 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/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/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/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