about summary refs log tree commit diff
path: root/compiler/rustc_const_eval
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2024-09-29 11:53:23 +0200
committerRalf Jung <post@ralfj.de>2024-10-01 21:45:35 +0200
commitc4ce8c114b06840c3521a189ee44958b713fb33a (patch)
tree8c7b85df780bfb8751c75ad3e7240bc805a40dfc /compiler/rustc_const_eval
parent4b8a5bd511814ed97640c46ab2232acc0e0ade41 (diff)
downloadrust-c4ce8c114b06840c3521a189ee44958b713fb33a.tar.gz
rust-c4ce8c114b06840c3521a189ee44958b713fb33a.zip
make InterpResult a dedicated type to avoid accidentally discarding the error
Diffstat (limited to 'compiler/rustc_const_eval')
-rw-r--r--compiler/rustc_const_eval/src/const_eval/dummy_machine.rs7
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs53
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs68
-rw-r--r--compiler/rustc_const_eval/src/const_eval/mod.rs20
-rw-r--r--compiler/rustc_const_eval/src/const_eval/valtrees.rs8
-rw-r--r--compiler/rustc_const_eval/src/interpret/call.rs58
-rw-r--r--compiler/rustc_const_eval/src/interpret/cast.rs24
-rw-r--r--compiler/rustc_const_eval/src/interpret/discriminant.rs19
-rw-r--r--compiler/rustc_const_eval/src/interpret/eval_context.rs49
-rw-r--r--compiler/rustc_const_eval/src/interpret/intern.rs10
-rw-r--r--compiler/rustc_const_eval/src/interpret/intrinsics.rs27
-rw-r--r--compiler/rustc_const_eval/src/interpret/machine.rs49
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs148
-rw-r--r--compiler/rustc_const_eval/src/interpret/operand.rs51
-rw-r--r--compiler/rustc_const_eval/src/interpret/operator.rs37
-rw-r--r--compiler/rustc_const_eval/src/interpret/place.rs76
-rw-r--r--compiler/rustc_const_eval/src/interpret/projection.rs20
-rw-r--r--compiler/rustc_const_eval/src/interpret/stack.rs26
-rw-r--r--compiler/rustc_const_eval/src/interpret/step.rs28
-rw-r--r--compiler/rustc_const_eval/src/interpret/traits.rs14
-rw-r--r--compiler/rustc_const_eval/src/interpret/util.rs8
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs182
-rw-r--r--compiler/rustc_const_eval/src/interpret/visitor.rs10
-rw-r--r--compiler/rustc_const_eval/src/util/check_validity_requirement.rs3
24 files changed, 501 insertions, 494 deletions
diff --git a/compiler/rustc_const_eval/src/const_eval/dummy_machine.rs b/compiler/rustc_const_eval/src/const_eval/dummy_machine.rs
index ed1c912e6cb..743924faa21 100644
--- a/compiler/rustc_const_eval/src/const_eval/dummy_machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/dummy_machine.rs
@@ -6,7 +6,8 @@ use rustc_middle::{bug, span_bug, ty};
 use rustc_span::def_id::DefId;
 
 use crate::interpret::{
-    self, HasStaticRootDefId, ImmTy, Immediate, InterpCx, PointerArithmetic, throw_machine_stop,
+    self, HasStaticRootDefId, ImmTy, Immediate, InterpCx, PointerArithmetic, interp_ok,
+    throw_machine_stop,
 };
 
 /// Macro for machine-specific `InterpError` without allocation.
@@ -79,7 +80,7 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
             throw_machine_stop_str!("can't access mutable globals in ConstProp");
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     fn find_mir_or_eval_fn(
@@ -127,7 +128,7 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
         right: &interpret::ImmTy<'tcx, Self::Provenance>,
     ) -> interpret::InterpResult<'tcx, ImmTy<'tcx, Self::Provenance>> {
         use rustc_middle::mir::BinOp::*;
-        Ok(match bin_op {
+        interp_ok(match bin_op {
             Eq | Ne | Lt | Le | Gt | Ge => {
                 // Types can differ, e.g. fn ptrs with different `for`.
                 assert_eq!(left.layout.abi, right.layout.abi);
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 a241edbd77a..672353e629d 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -20,7 +20,7 @@ use crate::const_eval::CheckAlignment;
 use crate::interpret::{
     CtfeValidationMode, GlobalId, Immediate, InternKind, InternResult, InterpCx, InterpError,
     InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, StackPopCleanup, create_static_alloc,
-    eval_nullary_intrinsic, intern_const_alloc_recursive, throw_exhaust,
+    eval_nullary_intrinsic, intern_const_alloc_recursive, interp_ok, throw_exhaust,
 };
 use crate::{CTRL_C_RECEIVED, errors};
 
@@ -98,19 +98,19 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
             return Err(ecx
                 .tcx
                 .dcx()
-                .emit_err(errors::DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
-                .into());
+                .emit_err(errors::DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind }))
+            .into();
         }
         Err(InternResult::FoundBadMutablePointer) => {
             return Err(ecx
                 .tcx
                 .dcx()
-                .emit_err(errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind })
-                .into());
+                .emit_err(errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind }))
+            .into();
         }
     }
 
-    Ok(R::make_result(ret, ecx))
+    interp_ok(R::make_result(ret, ecx))
 }
 
 /// The `InterpCx` is only meant to be used to do field and index projections into constants for
@@ -147,7 +147,8 @@ pub fn mk_eval_cx_for_const_val<'tcx>(
     ty: Ty<'tcx>,
 ) -> Option<(CompileTimeInterpCx<'tcx>, OpTy<'tcx>)> {
     let ecx = mk_eval_cx_to_read_const_val(tcx.tcx, tcx.span, param_env, CanAccessMutGlobal::No);
-    let op = ecx.const_val_to_op(val, ty, None).ok()?;
+    // FIXME: is it a problem to discard the error here?
+    let op = ecx.const_val_to_op(val, ty, None).discard_err()?;
     Some((ecx, op))
 }
 
@@ -185,12 +186,16 @@ pub(super) fn op_to_const<'tcx>(
         _ => false,
     };
     let immediate = if force_as_immediate {
-        match ecx.read_immediate(op) {
+        match ecx.read_immediate(op).report_err() {
             Ok(imm) => Right(imm),
-            Err(err) if !for_diagnostics => {
-                panic!("normalization works on validated constants: {err:?}")
+            Err(err) => {
+                if for_diagnostics {
+                    // This discard the error, but for diagnostics that's okay.
+                    op.as_mplace_or_imm()
+                } else {
+                    panic!("normalization works on validated constants: {err:?}")
+                }
             }
-            _ => op.as_mplace_or_imm(),
         }
     } else {
         op.as_mplace_or_imm()
@@ -283,17 +288,19 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
         let ty::FnDef(_, args) = ty.kind() else {
             bug!("intrinsic with type {:?}", ty);
         };
-        return eval_nullary_intrinsic(tcx, key.param_env, def_id, args).map_err(|error| {
-            let span = tcx.def_span(def_id);
-
-            super::report(
-                tcx,
-                error.into_kind(),
-                span,
-                || (span, vec![]),
-                |span, _| errors::NullaryIntrinsicError { span },
-            )
-        });
+        return eval_nullary_intrinsic(tcx, key.param_env, def_id, args).report_err().map_err(
+            |error| {
+                let span = tcx.def_span(def_id);
+
+                super::report(
+                    tcx,
+                    error.into_kind(),
+                    span,
+                    || (span, vec![]),
+                    |span, _| errors::NullaryIntrinsicError { span },
+                )
+            },
+        );
     }
 
     tcx.eval_to_allocation_raw(key).map(|val| turn_into_const_value(tcx, val, key))
@@ -376,6 +383,7 @@ fn eval_in_interpreter<'tcx, R: InterpretationResult<'tcx>>(
     );
     let res = ecx.load_mir(cid.instance.def, cid.promoted);
     res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body))
+        .report_err()
         .map_err(|error| report_eval_error(&ecx, cid, error))
 }
 
@@ -400,6 +408,7 @@ fn const_validate_mplace<'tcx>(
             }
         };
         ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)
+            .report_err()
             // Instead of just reporting the `InterpError` via the usual machinery, we give a more targeted
             // error about the validation failure.
             .map_err(|error| report_validation_error(&ecx, cid, error, alloc_id))?;
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 6d74998b7d8..4aec74595bc 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -24,8 +24,8 @@ use crate::fluent_generated as fluent;
 use crate::interpret::{
     self, AllocId, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy,
     InterpCx, InterpResult, MPlaceTy, OpTy, Pointer, PointerArithmetic, RangeSet, Scalar,
-    StackPopCleanup, compile_time_machine, err_ub, throw_exhaust, throw_inval, throw_ub_custom,
-    throw_unsup, throw_unsup_format,
+    StackPopCleanup, compile_time_machine, interp_ok, throw_exhaust, throw_inval, throw_ub,
+    throw_ub_custom, throw_unsup, throw_unsup_format,
 };
 
 /// When hitting this many interpreted terminators we emit a deny by default lint
@@ -247,7 +247,7 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
             let msg = Symbol::intern(self.read_str(&msg_place)?);
             let span = self.find_closest_untracked_caller_location();
             let (file, line, col) = self.location_triple_for_span(span);
-            return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
+            return Err(ConstEvalErrKind::Panic { msg, file, line, col }).into();
         } else if self.tcx.is_lang_item(def_id, LangItem::PanicFmt) {
             // For panic_fmt, call const_panic_fmt instead.
             let const_def_id = self.tcx.require_lang_item(LangItem::ConstPanicFmt, None);
@@ -259,16 +259,16 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
                 self.cur_span(),
             );
 
-            return Ok(Some(new_instance));
+            return interp_ok(Some(new_instance));
         } else if self.tcx.is_lang_item(def_id, LangItem::AlignOffset) {
             let args = self.copy_fn_args(args);
             // For align_offset, we replace the function call if the pointer has no address.
             match self.align_offset(instance, &args, dest, ret)? {
-                ControlFlow::Continue(()) => return Ok(Some(instance)),
-                ControlFlow::Break(()) => return Ok(None),
+                ControlFlow::Continue(()) => return interp_ok(Some(instance)),
+                ControlFlow::Break(()) => return interp_ok(None),
             }
         }
-        Ok(Some(instance))
+        interp_ok(Some(instance))
     }
 
     /// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
@@ -323,25 +323,25 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
                         dest,
                         StackPopCleanup::Goto { ret, unwind: mir::UnwindAction::Unreachable },
                     )?;
-                    Ok(ControlFlow::Break(()))
+                    interp_ok(ControlFlow::Break(()))
                 } else {
                     // Not alignable in const, return `usize::MAX`.
                     let usize_max = Scalar::from_target_usize(self.target_usize_max(), self);
                     self.write_scalar(usize_max, dest)?;
                     self.return_to_block(ret)?;
-                    Ok(ControlFlow::Break(()))
+                    interp_ok(ControlFlow::Break(()))
                 }
             }
             Err(_addr) => {
                 // The pointer has an address, continue with function call.
-                Ok(ControlFlow::Continue(()))
+                interp_ok(ControlFlow::Continue(()))
             }
         }
     }
 
     /// See documentation on the `ptr_guaranteed_cmp` intrinsic.
     fn guaranteed_cmp(&mut self, a: Scalar, b: Scalar) -> InterpResult<'tcx, u8> {
-        Ok(match (a, b) {
+        interp_ok(match (a, b) {
             // Comparisons between integers are always known.
             (Scalar::Int { .. }, Scalar::Int { .. }) => {
                 if a == b {
@@ -403,8 +403,8 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
         instance: ty::InstanceKind<'tcx>,
     ) -> InterpResult<'tcx, &'tcx mir::Body<'tcx>> {
         match instance {
-            ty::InstanceKind::Item(def) => Ok(ecx.tcx.mir_for_ctfe(def)),
-            _ => Ok(ecx.tcx.instance_mir(instance)),
+            ty::InstanceKind::Item(def) => interp_ok(ecx.tcx.mir_for_ctfe(def)),
+            _ => interp_ok(ecx.tcx.instance_mir(instance)),
         }
     }
 
@@ -422,7 +422,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
         // Replace some functions.
         let Some(instance) = ecx.hook_special_const_fn(orig_instance, args, dest, ret)? else {
             // Call has already been handled.
-            return Ok(None);
+            return interp_ok(None);
         };
 
         // Only check non-glue functions
@@ -444,14 +444,14 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
         // This is a const fn. Call it.
         // In case of replacement, we return the *original* instance to make backtraces work out
         // (and we hope this does not confuse the FnAbi checks too much).
-        Ok(Some((ecx.load_mir(instance.def, None)?, orig_instance)))
+        interp_ok(Some((ecx.load_mir(instance.def, None)?, orig_instance)))
     }
 
     fn panic_nounwind(ecx: &mut InterpCx<'tcx, Self>, msg: &str) -> InterpResult<'tcx> {
         let msg = Symbol::intern(msg);
         let span = ecx.find_closest_untracked_caller_location();
         let (file, line, col) = ecx.location_triple_for_span(span);
-        Err(ConstEvalErrKind::Panic { msg, file, line, col }.into())
+        Err(ConstEvalErrKind::Panic { msg, file, line, col }).into()
     }
 
     fn call_intrinsic(
@@ -464,7 +464,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
     ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
         // Shared intrinsics.
         if ecx.eval_intrinsic(instance, args, dest, target)? {
-            return Ok(None);
+            return interp_ok(None);
         }
         let intrinsic_name = ecx.tcx.item_name(instance.def_id());
 
@@ -541,7 +541,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
                         "intrinsic `{intrinsic_name}` is not supported at compile-time"
                     );
                 }
-                return Ok(Some(ty::Instance {
+                return interp_ok(Some(ty::Instance {
                     def: ty::InstanceKind::Item(instance.def_id()),
                     args: instance.args,
                 }));
@@ -550,7 +550,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
 
         // Intrinsic is done, jump to next block.
         ecx.return_to_block(target)?;
-        Ok(None)
+        interp_ok(None)
     }
 
     fn assert_panic(
@@ -581,7 +581,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
                 }
             }
         };
-        Err(ConstEvalErrKind::AssertFailure(err).into())
+        Err(ConstEvalErrKind::AssertFailure(err)).into()
     }
 
     fn binary_ptr_op(
@@ -652,7 +652,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
             }
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     #[inline(always)]
@@ -670,7 +670,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
         if !ecx.recursion_limit.value_within_limit(ecx.stack().len() + 1) {
             throw_exhaust!(StackFrameLimitReached)
         } else {
-            Ok(frame)
+            interp_ok(frame)
         }
     }
 
@@ -700,22 +700,22 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
         if is_write {
             // Write access. These are never allowed, but we give a targeted error message.
             match alloc.mutability {
-                Mutability::Not => Err(err_ub!(WriteToReadOnly(alloc_id)).into()),
-                Mutability::Mut => Err(ConstEvalErrKind::ModifiedGlobal.into()),
+                Mutability::Not => throw_ub!(WriteToReadOnly(alloc_id)),
+                Mutability::Mut => Err(ConstEvalErrKind::ModifiedGlobal).into(),
             }
         } else {
             // Read access. These are usually allowed, with some exceptions.
             if machine.can_access_mut_global == CanAccessMutGlobal::Yes {
                 // Machine configuration allows us read from anything (e.g., `static` initializer).
-                Ok(())
+                interp_ok(())
             } else if alloc.mutability == Mutability::Mut {
                 // Machine configuration does not allow us to read statics (e.g., `const`
                 // initializer).
-                Err(ConstEvalErrKind::ConstAccessesMutGlobal.into())
+                Err(ConstEvalErrKind::ConstAccessesMutGlobal).into()
             } else {
                 // Immutable global, this read is fine.
                 assert_eq!(alloc.mutability, Mutability::Not);
-                Ok(())
+                interp_ok(())
             }
         }
     }
@@ -748,9 +748,9 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
                 // even when there is interior mutability.)
                 place.map_provenance(CtfeProvenance::as_shared_ref)
             };
-            Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout))
+            interp_ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout))
         } else {
-            Ok(val.clone())
+            interp_ok(val.clone())
         }
     }
 
@@ -763,20 +763,20 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
     ) -> InterpResult<'tcx> {
         if range.size == Size::ZERO {
             // Nothing to check.
-            return Ok(());
+            return interp_ok(());
         }
         // Reject writes through immutable pointers.
         if immutable {
-            return Err(ConstEvalErrKind::WriteThroughImmutablePointer.into());
+            return Err(ConstEvalErrKind::WriteThroughImmutablePointer).into();
         }
         // Everything else is fine.
-        Ok(())
+        interp_ok(())
     }
 
     fn before_alloc_read(ecx: &InterpCx<'tcx, Self>, alloc_id: AllocId) -> InterpResult<'tcx> {
         // Check if this is the currently evaluated static.
         if Some(alloc_id) == ecx.machine.static_root_ids.map(|(id, _)| id) {
-            return Err(ConstEvalErrKind::RecursiveStatic.into());
+            return Err(ConstEvalErrKind::RecursiveStatic).into();
         }
         // If this is another static, make sure we fire off the query to detect cycles.
         // But only do that when checks for static recursion are enabled.
@@ -788,7 +788,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
                 ecx.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
             }
         }
-        Ok(())
+        interp_ok(())
     }
 
     fn cached_union_data_range<'e>(
diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs
index 48aeee2e6f0..ba19f642795 100644
--- a/compiler/rustc_const_eval/src/const_eval/mod.rs
+++ b/compiler/rustc_const_eval/src/const_eval/mod.rs
@@ -1,13 +1,12 @@
 // Not in interpret to make sure we do not use private implementation details
 
-use rustc_middle::mir::interpret::InterpErrorInfo;
 use rustc_middle::query::{Key, TyCtxtAt};
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_middle::{bug, mir};
 use rustc_target::abi::VariantIdx;
 use tracing::instrument;
 
-use crate::interpret::{InterpCx, format_interp_error};
+use crate::interpret::InterpCx;
 
 mod dummy_machine;
 mod error;
@@ -33,17 +32,6 @@ pub(crate) enum ValTreeCreationError<'tcx> {
 }
 pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError<'tcx>>;
 
-impl<'tcx> From<InterpErrorInfo<'tcx>> for ValTreeCreationError<'tcx> {
-    fn from(err: InterpErrorInfo<'tcx>) -> Self {
-        ty::tls::with(|tcx| {
-            bug!(
-                "Unexpected Undefined Behavior error during valtree construction: {}",
-                format_interp_error(tcx.dcx(), err),
-            )
-        })
-    }
-}
-
 #[instrument(skip(tcx), level = "debug")]
 pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
     tcx: TyCtxtAt<'tcx>,
@@ -60,8 +48,8 @@ pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
             return None;
         }
         ty::Adt(def, _) => {
-            let variant = ecx.read_discriminant(&op).ok()?;
-            let down = ecx.project_downcast(&op, variant).ok()?;
+            let variant = ecx.read_discriminant(&op).discard_err()?;
+            let down = ecx.project_downcast(&op, variant).discard_err()?;
             (def.variants()[variant].fields.len(), Some(variant), down)
         }
         ty::Tuple(args) => (args.len(), None, op),
@@ -70,7 +58,7 @@ pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
 
     let fields_iter = (0..field_count)
         .map(|i| {
-            let field_op = ecx.project_field(&down, i).ok()?;
+            let field_op = ecx.project_field(&down, i).discard_err()?;
             let val = op_to_const(&ecx, &field_op, /* for diagnostics */ true);
             Some((val, field_op.layout.ty))
         })
diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs
index a11d491bdd2..9e80e666ba9 100644
--- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs
+++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs
@@ -92,7 +92,7 @@ fn const_to_valtree_inner<'tcx>(
             Ok(ty::ValTree::zst())
         }
         ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => {
-            let val = ecx.read_immediate(place)?;
+            let val = ecx.read_immediate(place).unwrap();
             let val = val.to_scalar_int().unwrap();
             *num_nodes += 1;
 
@@ -114,7 +114,7 @@ fn const_to_valtree_inner<'tcx>(
             // equality at compile-time (see `ptr_guaranteed_cmp`).
             // However we allow those that are just integers in disguise.
             // First, get the pointer. Remember it might be wide!
-            let val = ecx.read_immediate(place)?;
+            let val = ecx.read_immediate(place).unwrap();
             // We could allow wide raw pointers where both sides are integers in the future,
             // but for now we reject them.
             if matches!(val.layout.abi, Abi::ScalarPair(..)) {
@@ -135,7 +135,7 @@ fn const_to_valtree_inner<'tcx>(
         ty::FnPtr(..) => Err(ValTreeCreationError::NonSupportedType(ty)),
 
         ty::Ref(_, _, _)  => {
-            let derefd_place = ecx.deref_pointer(place)?;
+            let derefd_place = ecx.deref_pointer(place).unwrap();
             const_to_valtree_inner(ecx, &derefd_place, num_nodes)
         }
 
@@ -159,7 +159,7 @@ fn const_to_valtree_inner<'tcx>(
                 bug!("uninhabited types should have errored and never gotten converted to valtree")
             }
 
-            let variant = ecx.read_discriminant(place)?;
+            let variant = ecx.read_discriminant(place).unwrap();
             branches(ecx, place, def.variant(variant).fields.len(), def.is_enum().then_some(variant), num_nodes)
         }
 
diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs
index 19aa76bf1ea..4945563f4a4 100644
--- a/compiler/rustc_const_eval/src/interpret/call.rs
+++ b/compiler/rustc_const_eval/src/interpret/call.rs
@@ -15,8 +15,8 @@ use tracing::{info, instrument, trace};
 
 use super::{
     CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy,
-    Projectable, Provenance, ReturnAction, Scalar, StackPopCleanup, StackPopInfo, throw_ub,
-    throw_ub_custom, throw_unsup_format,
+    Projectable, Provenance, ReturnAction, Scalar, StackPopCleanup, StackPopInfo, interp_ok,
+    throw_ub, throw_ub_custom, throw_unsup_format,
 };
 use crate::fluent_generated as fluent;
 
@@ -64,7 +64,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         arg: &FnArg<'tcx, M::Provenance>,
         field: usize,
     ) -> InterpResult<'tcx, FnArg<'tcx, M::Provenance>> {
-        Ok(match arg {
+        interp_ok(match arg {
             FnArg::Copy(op) => FnArg::Copy(self.project_field(op, field)?),
             FnArg::InPlace(mplace) => FnArg::InPlace(self.project_field(mplace, field)?),
         })
@@ -97,7 +97,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // another type.
         let ty::Adt(def, args) = layout.ty.kind() else {
             // Not an ADT, so definitely no NPO.
-            return Ok(layout);
+            return interp_ok(layout);
         };
         let inner = if self.tcx.is_diagnostic_item(sym::Option, def.did()) {
             // The wrapped type is the only arg.
@@ -111,10 +111,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             } else if rhs.is_1zst() {
                 lhs
             } else {
-                return Ok(layout); // no NPO
+                return interp_ok(layout); // no NPO
             }
         } else {
-            return Ok(layout); // no NPO
+            return interp_ok(layout); // no NPO
         };
 
         // Check if the inner type is one of the NPO-guaranteed ones.
@@ -126,7 +126,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             // Stop at NPO types so that we don't miss that attribute in the check below!
             def.is_struct() && !is_npo(def)
         });
-        Ok(match inner.ty.kind() {
+        interp_ok(match inner.ty.kind() {
             ty::Ref(..) | ty::FnPtr(..) => {
                 // Option<&T> behaves like &T, and same for fn()
                 inner
@@ -153,11 +153,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     ) -> InterpResult<'tcx, bool> {
         // Fast path: equal types are definitely compatible.
         if caller.ty == callee.ty {
-            return Ok(true);
+            return interp_ok(true);
         }
         // 1-ZST are compatible with all 1-ZST (and with nothing else).
         if caller.is_1zst() || callee.is_1zst() {
-            return Ok(caller.is_1zst() && callee.is_1zst());
+            return interp_ok(caller.is_1zst() && callee.is_1zst());
         }
         // Unfold newtypes and NPO optimizations.
         let unfold = |layout: TyAndLayout<'tcx>| {
@@ -180,17 +180,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             _ => None,
         };
         if let (Some(caller), Some(callee)) = (thin_pointer(caller), thin_pointer(callee)) {
-            return Ok(caller == callee);
+            return interp_ok(caller == callee);
         }
         // For wide pointers we have to get the pointee type.
         let pointee_ty = |ty: Ty<'tcx>| -> InterpResult<'tcx, Option<Ty<'tcx>>> {
             // We cannot use `builtin_deref` here since we need to reject `Box<T, MyAlloc>`.
-            Ok(Some(match ty.kind() {
+            interp_ok(Some(match ty.kind() {
                 ty::Ref(_, ty, _) => *ty,
                 ty::RawPtr(ty, _) => *ty,
                 // We only accept `Box` with the default allocator.
                 _ if ty.is_box_global(*self.tcx) => ty.expect_boxed_ty(),
-                _ => return Ok(None),
+                _ => return interp_ok(None),
             }))
         };
         if let (Some(caller), Some(callee)) = (pointee_ty(caller.ty)?, pointee_ty(callee.ty)?) {
@@ -202,7 +202,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 let normalize = |ty| self.tcx.normalize_erasing_regions(self.param_env, ty);
                 ty.ptr_metadata_ty(*self.tcx, normalize)
             };
-            return Ok(meta_ty(caller) == meta_ty(callee));
+            return interp_ok(meta_ty(caller) == meta_ty(callee));
         }
 
         // Compatible integer types (in particular, usize vs ptr-sized-u32/u64).
@@ -217,11 +217,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         };
         if let (Some(caller), Some(callee)) = (int_ty(caller.ty), int_ty(callee.ty)) {
             // This is okay if they are the same integer type.
-            return Ok(caller == callee);
+            return interp_ok(caller == callee);
         }
 
         // Fall back to exact equality.
-        Ok(caller == callee)
+        interp_ok(caller == callee)
     }
 
     fn check_argument_compat(
@@ -235,13 +235,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             // Ensure that our checks imply actual ABI compatibility for this concrete call.
             // (This can fail e.g. if `#[rustc_nonnull_optimization_guaranteed]` is used incorrectly.)
             assert!(caller_abi.eq_abi(callee_abi));
-            Ok(true)
+            interp_ok(true)
         } else {
             trace!(
                 "check_argument_compat: incompatible ABIs:\ncaller: {:?}\ncallee: {:?}",
                 caller_abi, callee_abi
             );
-            Ok(false)
+            interp_ok(false)
         }
     }
 
@@ -266,7 +266,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             if !already_live {
                 self.storage_live(callee_arg.as_local().unwrap())?;
             }
-            return Ok(());
+            return interp_ok(());
         }
         // Find next caller arg.
         let Some((caller_arg, caller_abi)) = caller_args.next() else {
@@ -308,7 +308,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if let FnArg::InPlace(mplace) = caller_arg {
             M::protect_in_place_function_argument(self, mplace)?;
         }
-        Ok(())
+        interp_ok(())
     }
 
     /// The main entry point for creating a new stack frame: performs ABI checks and initializes
@@ -536,7 +536,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                         unwind,
                     );
                 } else {
-                    Ok(())
+                    interp_ok(())
                 }
             }
             ty::InstanceKind::VTableShim(..)
@@ -561,7 +561,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     unwind,
                 )?
                 else {
-                    return Ok(());
+                    return interp_ok(());
                 };
 
                 // Special handling for the closure ABI: untuple the last argument.
@@ -572,7 +572,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                         trace!("init_fn_call: Will pass last argument by untupling");
                         Cow::from(
                             args.iter()
-                                .map(|a| Ok(a.clone()))
+                                .map(|a| interp_ok(a.clone()))
                                 .chain(
                                     (0..untuple_arg.layout().fields.count())
                                         .map(|i| self.fn_arg_field(untuple_arg, i)),
@@ -886,27 +886,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             // this transmute.
             res
         } else {
-            Ok(())
+            interp_ok(())
         };
 
         // All right, now it is time to actually pop the frame.
-        let stack_pop_info = self.pop_stack_frame_raw(unwinding)?;
-
-        // Report error from return value copy, if any.
-        copy_ret_result?;
+        // An error here takes precedence over the copy error.
+        let (stack_pop_info, ()) = self.pop_stack_frame_raw(unwinding).and(copy_ret_result)?;
 
         match stack_pop_info.return_action {
             ReturnAction::Normal => {}
             ReturnAction::NoJump => {
                 // The hook already did everything.
-                return Ok(());
+                return interp_ok(());
             }
             ReturnAction::NoCleanup => {
                 // If we are not doing cleanup, also skip everything else.
                 assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
                 assert!(!unwinding, "tried to skip cleanup during unwinding");
                 // Skip machine hook.
-                return Ok(());
+                return interp_ok(());
             }
         }
 
@@ -931,7 +929,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                         self.stack().is_empty(),
                         "only the bottommost frame can have StackPopCleanup::Root"
                     );
-                    Ok(())
+                    interp_ok(())
                 }
             }
         }
diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs
index 85e7d91c2ad..565a7d16242 100644
--- a/compiler/rustc_const_eval/src/interpret/cast.rs
+++ b/compiler/rustc_const_eval/src/interpret/cast.rs
@@ -14,7 +14,8 @@ use tracing::trace;
 
 use super::util::ensure_monomorphic_enough;
 use super::{
-    FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy, err_inval, throw_ub, throw_ub_custom,
+    FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy, err_inval, interp_ok, throw_ub,
+    throw_ub_custom,
 };
 use crate::fluent_generated as fluent;
 
@@ -157,7 +158,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 self.copy_op_allow_transmute(src, dest)?;
             }
         }
-        Ok(())
+        interp_ok(())
     }
 
     /// Handles 'IntToInt' and 'IntToFloat' casts.
@@ -169,7 +170,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         assert!(src.layout.ty.is_integral() || src.layout.ty.is_char() || src.layout.ty.is_bool());
         assert!(cast_to.ty.is_floating_point() || cast_to.ty.is_integral() || cast_to.ty.is_char());
 
-        Ok(ImmTy::from_scalar(
+        interp_ok(ImmTy::from_scalar(
             self.cast_from_int_like(src.to_scalar(), src.layout, cast_to.ty)?,
             cast_to,
         ))
@@ -192,7 +193,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             FloatTy::F64 => self.cast_from_float(src.to_scalar().to_f64()?, cast_to.ty),
             FloatTy::F128 => self.cast_from_float(src.to_scalar().to_f128()?, cast_to.ty),
         };
-        Ok(ImmTy::from_scalar(val, cast_to))
+        interp_ok(ImmTy::from_scalar(val, cast_to))
     }
 
     /// Handles 'FnPtrToPtr' and 'PtrToPtr' casts.
@@ -206,14 +207,14 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // Handle casting any ptr to raw ptr (might be a fat ptr).
         if cast_to.size == src.layout.size {
             // Thin or fat pointer that just has the ptr kind of target type changed.
-            return Ok(ImmTy::from_immediate(**src, cast_to));
+            return interp_ok(ImmTy::from_immediate(**src, cast_to));
         } else {
             // Casting the metadata away from a fat ptr.
             assert_eq!(src.layout.size, 2 * self.pointer_size());
             assert_eq!(cast_to.size, self.pointer_size());
             assert!(src.layout.ty.is_unsafe_ptr());
             return match **src {
-                Immediate::ScalarPair(data, _) => Ok(ImmTy::from_scalar(data, cast_to)),
+                Immediate::ScalarPair(data, _) => interp_ok(ImmTy::from_scalar(data, cast_to)),
                 Immediate::Scalar(..) => span_bug!(
                     self.cur_span(),
                     "{:?} input to a fat-to-thin cast ({} -> {})",
@@ -240,7 +241,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             Ok(ptr) => M::expose_ptr(self, ptr)?,
             Err(_) => {} // Do nothing, exposing an invalid pointer (`None` provenance) is a NOP.
         };
-        Ok(ImmTy::from_scalar(self.cast_from_int_like(scalar, src.layout, cast_to.ty)?, cast_to))
+        interp_ok(ImmTy::from_scalar(
+            self.cast_from_int_like(scalar, src.layout, cast_to.ty)?,
+            cast_to,
+        ))
     }
 
     pub fn pointer_with_exposed_provenance_cast(
@@ -258,7 +262,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
 
         // Then turn address into pointer.
         let ptr = M::ptr_from_addr_cast(self, addr)?;
-        Ok(ImmTy::from_scalar(Scalar::from_maybe_pointer(ptr, self), cast_to))
+        interp_ok(ImmTy::from_scalar(Scalar::from_maybe_pointer(ptr, self), cast_to))
     }
 
     /// Low-level cast helper function. This works directly on scalars and can take 'int-like' input
@@ -280,7 +284,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             _ => span_bug!(self.cur_span(), "invalid int-like cast from {}", src_layout.ty),
         };
 
-        Ok(match *cast_ty.kind() {
+        interp_ok(match *cast_ty.kind() {
             // int -> int
             Int(_) | Uint(_) => {
                 let size = match *cast_ty.kind() {
@@ -505,7 +509,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                         self.unsize_into(&src_field, cast_ty_field, &dst_field)?;
                     }
                 }
-                Ok(())
+                interp_ok(())
             }
             _ => {
                 // Do not ICE if we are not monomorphic enough.
diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs
index 3bab2b3261e..81e0b1e12ca 100644
--- a/compiler/rustc_const_eval/src/interpret/discriminant.rs
+++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs
@@ -7,7 +7,8 @@ use rustc_target::abi::{self, TagEncoding, VariantIdx, Variants};
 use tracing::{instrument, trace};
 
 use super::{
-    ImmTy, InterpCx, InterpResult, Machine, Projectable, Scalar, Writeable, err_ub, throw_ub,
+    ImmTy, InterpCx, InterpResult, Machine, Projectable, Scalar, Writeable, err_ub, interp_ok,
+    throw_ub,
 };
 
 impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
@@ -48,7 +49,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 if actual_variant != variant_index {
                     throw_ub!(InvalidNichedEnumVariantWritten { enum_ty: dest.layout().ty });
                 }
-                Ok(())
+                interp_ok(())
             }
         }
     }
@@ -89,7 +90,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                         throw_ub!(UninhabitedEnumVariantRead(index))
                     }
                 }
-                return Ok(index);
+                return interp_ok(index);
             }
             Variants::Multiple { tag, ref tag_encoding, tag_field, .. } => {
                 (tag, tag_encoding, tag_field)
@@ -205,7 +206,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if op.layout().for_variant(self, index).abi.is_uninhabited() {
             throw_ub!(UninhabitedEnumVariantRead(index))
         }
-        Ok(index)
+        interp_ok(index)
     }
 
     pub fn discriminant_for_variant(
@@ -226,7 +227,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 Scalar::from_uint(variant.as_u32(), discr_layout.size)
             }
         };
-        Ok(ImmTy::from_scalar(discr_value, discr_layout))
+        interp_ok(ImmTy::from_scalar(discr_value, discr_layout))
     }
 
     /// Computes how to write the tag of a given variant of enum `ty`:
@@ -247,7 +248,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 // discriminant is encoded implicitly, so any attempt to write
                 // the wrong discriminant for a `Single` enum will reliably
                 // result in UB.
-                Ok(None)
+                interp_ok(None)
             }
 
             abi::Variants::Multiple {
@@ -265,7 +266,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 let tag_size = tag_layout.size(self);
                 let tag_val = tag_size.truncate(discr_val);
                 let tag = ScalarInt::try_from_uint(tag_val, tag_size).unwrap();
-                Ok(Some((tag, tag_field)))
+                interp_ok(Some((tag, tag_field)))
             }
 
             abi::Variants::Multiple {
@@ -274,7 +275,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             } if untagged_variant == variant_index => {
                 // The untagged variant is implicitly encoded simply by having a
                 // value that is outside the niche variants.
-                Ok(None)
+                interp_ok(None)
             }
 
             abi::Variants::Multiple {
@@ -299,7 +300,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 let tag = self
                     .binary_op(mir::BinOp::Add, &variant_index_relative_val, &niche_start_val)?
                     .to_scalar_int()?;
-                Ok(Some((tag, tag_field)))
+                interp_ok(Some((tag, tag_field)))
             }
         }
     }
diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs
index f4c4a6fb0bf..5165f95afd5 100644
--- a/compiler/rustc_const_eval/src/interpret/eval_context.rs
+++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs
@@ -19,9 +19,9 @@ use rustc_trait_selection::traits::ObligationCtxt;
 use tracing::{debug, instrument, trace};
 
 use super::{
-    Frame, FrameInfo, GlobalId, InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlaceMeta,
-    Memory, OpTy, Place, PlaceTy, PointerArithmetic, Projectable, Provenance, err_inval,
-    throw_inval, throw_ub, throw_ub_custom,
+    Frame, FrameInfo, GlobalId, InterpError, InterpErrorInfo, InterpResult, MPlaceTy, Machine,
+    MemPlaceMeta, Memory, OpTy, Place, PlaceTy, PointerArithmetic, Projectable, Provenance,
+    err_inval, interp_ok, throw_inval, throw_ub, throw_ub_custom,
 };
 use crate::{ReportErrorExt, fluent_generated as fluent, util};
 
@@ -73,7 +73,7 @@ where
 }
 
 impl<'tcx, M: Machine<'tcx>> LayoutOfHelpers<'tcx> for InterpCx<'tcx, M> {
-    type LayoutOfResult = InterpResult<'tcx, TyAndLayout<'tcx>>;
+    type LayoutOfResult = Result<TyAndLayout<'tcx>, InterpError<'tcx>>;
 
     #[inline]
     fn layout_tcx_at_span(&self) -> Span {
@@ -82,29 +82,24 @@ impl<'tcx, M: Machine<'tcx>> LayoutOfHelpers<'tcx> for InterpCx<'tcx, M> {
     }
 
     #[inline]
-    fn handle_layout_err(
-        &self,
-        err: LayoutError<'tcx>,
-        _: Span,
-        _: Ty<'tcx>,
-    ) -> InterpErrorInfo<'tcx> {
-        err_inval!(Layout(err)).into()
+    fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> InterpError<'tcx> {
+        err_inval!(Layout(err))
     }
 }
 
 impl<'tcx, M: Machine<'tcx>> FnAbiOfHelpers<'tcx> for InterpCx<'tcx, M> {
-    type FnAbiOfResult = InterpResult<'tcx, &'tcx FnAbi<'tcx, Ty<'tcx>>>;
+    type FnAbiOfResult = Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, InterpError<'tcx>>;
 
     fn handle_fn_abi_err(
         &self,
         err: FnAbiError<'tcx>,
         _span: Span,
         _fn_abi_request: FnAbiRequest<'tcx>,
-    ) -> InterpErrorInfo<'tcx> {
+    ) -> InterpError<'tcx> {
         match err {
-            FnAbiError::Layout(err) => err_inval!(Layout(err)).into(),
+            FnAbiError::Layout(err) => err_inval!(Layout(err)),
             FnAbiError::AdjustForForeignAbi(err) => {
-                err_inval!(FnAbiAdjustForForeignAbi(err)).into()
+                err_inval!(FnAbiAdjustForForeignAbi(err))
             }
         }
     }
@@ -160,7 +155,7 @@ pub(super) fn from_known_layout<'tcx>(
                     );
                 }
             }
-            Ok(known_layout)
+            interp_ok(known_layout)
         }
     }
 }
@@ -262,7 +257,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if let Some(err) = body.tainted_by_errors {
             throw_inval!(AlreadyReported(ReportedErrorInfo::tainted_by_errors(err)));
         }
-        Ok(body)
+        interp_ok(body)
     }
 
     /// Call this on things you got out of the MIR (so it is as generic as the current
@@ -305,7 +300,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         trace!("param_env: {:#?}", self.param_env);
         trace!("args: {:#?}", args);
         match ty::Instance::try_resolve(*self.tcx, self.param_env, def, args) {
-            Ok(Some(instance)) => Ok(instance),
+            Ok(Some(instance)) => interp_ok(instance),
             Ok(None) => throw_inval!(TooGeneric),
 
             // FIXME(eddyb) this could be a bit more specific than `AlreadyReported`.
@@ -401,7 +396,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         layout: &TyAndLayout<'tcx>,
     ) -> InterpResult<'tcx, Option<(Size, Align)>> {
         if layout.is_sized() {
-            return Ok(Some((layout.size, layout.align.abi)));
+            return interp_ok(Some((layout.size, layout.align.abi)));
         }
         match layout.ty.kind() {
             ty::Adt(..) | ty::Tuple(..) => {
@@ -425,7 +420,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 else {
                     // A field with an extern type. We don't know the actual dynamic size
                     // or the alignment.
-                    return Ok(None);
+                    return interp_ok(None);
                 };
 
                 // # First compute the dynamic alignment
@@ -456,12 +451,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 if full_size > self.max_size_of_val() {
                     throw_ub!(InvalidMeta(InvalidMetaKind::TooBig));
                 }
-                Ok(Some((full_size, full_align)))
+                interp_ok(Some((full_size, full_align)))
             }
             ty::Dynamic(expected_trait, _, ty::Dyn) => {
                 let vtable = metadata.unwrap_meta().to_pointer(self)?;
                 // Read size and align from vtable (already checks size).
-                Ok(Some(self.get_vtable_size_and_align(vtable, Some(expected_trait))?))
+                interp_ok(Some(self.get_vtable_size_and_align(vtable, Some(expected_trait))?))
             }
 
             ty::Slice(_) | ty::Str => {
@@ -474,10 +469,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 if size > self.max_size_of_val() {
                     throw_ub!(InvalidMeta(InvalidMetaKind::SliceTooBig));
                 }
-                Ok(Some((size, elem.align.abi)))
+                interp_ok(Some((size, elem.align.abi)))
             }
 
-            ty::Foreign(_) => Ok(None),
+            ty::Foreign(_) => interp_ok(None),
 
             _ => span_bug!(self.cur_span(), "size_and_align_of::<{}> not supported", layout.ty),
         }
@@ -503,7 +498,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     pub fn return_to_block(&mut self, target: Option<mir::BasicBlock>) -> InterpResult<'tcx> {
         if let Some(target) = target {
             self.go_to_block(target);
-            Ok(())
+            interp_ok(())
         } else {
             throw_ub!(Unreachable)
         }
@@ -530,10 +525,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 M::unwind_terminate(self, reason)?;
                 // This might have pushed a new stack frame, or it terminated execution.
                 // Either way, `loc` will not be updated.
-                return Ok(());
+                return interp_ok(());
             }
         };
-        Ok(())
+        interp_ok(())
     }
 
     /// Call a query that can return `ErrorHandled`. Should be used for statics and other globals.
diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs
index 81659df7c53..7a1b92601a4 100644
--- a/compiler/rustc_const_eval/src/interpret/intern.rs
+++ b/compiler/rustc_const_eval/src/interpret/intern.rs
@@ -26,7 +26,9 @@ use rustc_span::def_id::LocalDefId;
 use rustc_span::sym;
 use tracing::{instrument, trace};
 
-use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, err_ub};
+use super::{
+    AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, err_ub, interp_ok,
+};
 use crate::const_eval;
 use crate::errors::NestedStaticInThreadLocal;
 
@@ -307,7 +309,7 @@ pub fn intern_const_alloc_for_constprop<'tcx, T, M: CompileTimeMachine<'tcx, T>>
 ) -> InterpResult<'tcx, ()> {
     if ecx.tcx.try_get_global_alloc(alloc_id).is_some() {
         // The constant is already in global memory. Do nothing.
-        return Ok(());
+        return interp_ok(());
     }
     // Move allocation to `tcx`.
     if let Some(_) =
@@ -318,7 +320,7 @@ pub fn intern_const_alloc_for_constprop<'tcx, T, M: CompileTimeMachine<'tcx, T>>
         // proper recursive interning loop -- or just call `intern_const_alloc_recursive`.
         panic!("`intern_const_alloc_for_constprop` called on allocation with nested provenance")
     }
-    Ok(())
+    interp_ok(())
 }
 
 impl<'tcx, M: super::intern::CompileTimeMachine<'tcx, !>> InterpCx<'tcx, M> {
@@ -342,6 +344,6 @@ impl<'tcx, M: super::intern::CompileTimeMachine<'tcx, !>> InterpCx<'tcx, M> {
                 panic!("`intern_with_temp_alloc` with nested allocations");
             }
         }
-        Ok(alloc_id)
+        interp_ok(alloc_id)
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
index 4c34ffc5d4e..52780cc6a3a 100644
--- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs
+++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
@@ -18,7 +18,7 @@ use super::util::ensure_monomorphic_enough;
 use super::{
     Allocation, CheckInAllocMsg, ConstAllocation, GlobalId, ImmTy, InterpCx, InterpResult,
     MPlaceTy, Machine, OpTy, Pointer, PointerArithmetic, Provenance, Scalar, err_inval,
-    err_ub_custom, err_unsup_format, throw_inval, throw_ub_custom, throw_ub_format,
+    err_ub_custom, err_unsup_format, interp_ok, throw_inval, throw_ub_custom, throw_ub_format,
 };
 use crate::fluent_generated as fluent;
 
@@ -39,7 +39,7 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
 ) -> InterpResult<'tcx, ConstValue<'tcx>> {
     let tp_ty = args.type_at(0);
     let name = tcx.item_name(def_id);
-    Ok(match name {
+    interp_ok(match name {
         sym::type_name => {
             ensure_monomorphic_enough(tcx, tp_ty)?;
             let alloc = alloc_type_name(tcx, tp_ty);
@@ -329,6 +329,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                         fluent::const_eval_offset_from_different_allocations,
                         name = intrinsic_name,
                     )
+                    .into()
                 })?;
 
                 // Perform division by size to compute return value.
@@ -378,7 +379,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
 
                     M::panic_nounwind(self, &msg)?;
                     // Skip the `return_to_block` at the end (we panicked, we do not return).
-                    return Ok(true);
+                    return interp_ok(true);
                 }
             }
             sym::simd_insert => {
@@ -438,12 +439,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             }
 
             // Unsupported intrinsic: skip the return_to_block below.
-            _ => return Ok(false),
+            _ => return interp_ok(false),
         }
 
         trace!("{:?}", self.dump_place(&dest.clone().into()));
         self.return_to_block(ret)?;
-        Ok(true)
+        interp_ok(true)
     }
 
     pub(super) fn eval_nondiverging_intrinsic(
@@ -457,7 +458,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 if !cond {
                     throw_ub_custom!(fluent::const_eval_assume_false);
                 }
-                Ok(())
+                interp_ok(())
             }
             NonDivergingIntrinsic::CopyNonOverlapping(mir::CopyNonOverlapping {
                 count,
@@ -499,7 +500,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             }
             _ => bug!("not a numeric intrinsic: {}", name),
         };
-        Ok(Scalar::from_uint(bits_out, ret_layout.size))
+        interp_ok(Scalar::from_uint(bits_out, ret_layout.size))
     }
 
     pub fn exact_div(
@@ -540,7 +541,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
 
         let (val, overflowed) =
             self.binary_op(mir_op.wrapping_to_overflowing().unwrap(), l, r)?.to_scalar_pair();
-        Ok(if overflowed.to_bool()? {
+        interp_ok(if overflowed.to_bool()? {
             let size = l.layout.size;
             if l.layout.abi.is_signed() {
                 // For signed ints the saturated value depends on the sign of the first
@@ -582,7 +583,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // The offset must be in bounds starting from `ptr`.
         self.check_ptr_access_signed(ptr, offset_bytes, CheckInAllocMsg::PointerArithmeticTest)?;
         // This also implies that there is no overflow, so we are done.
-        Ok(ptr.wrapping_signed_offset(offset_bytes, self))
+        interp_ok(ptr.wrapping_signed_offset(offset_bytes, self))
     }
 
     /// Copy `count*size_of::<T>()` many bytes from `*src` to `*dst`.
@@ -628,7 +629,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         self.copy_op(&right, &left)?;
         self.copy_op(&temp, &right)?;
         self.deallocate_ptr(temp.ptr(), None, kind)?;
-        Ok(())
+        interp_ok(())
     }
 
     pub fn write_bytes_intrinsic(
@@ -669,7 +670,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
 
         // `Ordering`'s discriminants are -1/0/+1, so casting does the right thing.
         let result = Ord::cmp(left_bytes, right_bytes) as i32;
-        Ok(Scalar::from_i32(result))
+        interp_ok(Scalar::from_i32(result))
     }
 
     pub(crate) fn raw_eq_intrinsic(
@@ -687,13 +688,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             this.check_ptr_align(ptr, layout.align.abi)?;
             let Some(alloc_ref) = self.get_ptr_alloc(ptr, layout.size)? else {
                 // zero-sized access
-                return Ok(&[]);
+                return interp_ok(&[]);
             };
             alloc_ref.get_bytes_strip_provenance()
         };
 
         let lhs_bytes = get_bytes(self, lhs)?;
         let rhs_bytes = get_bytes(self, rhs)?;
-        Ok(Scalar::from_bool(lhs_bytes == rhs_bytes))
+        interp_ok(Scalar::from_bool(lhs_bytes == rhs_bytes))
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs
index a809f74e8eb..0f796c31222 100644
--- a/compiler/rustc_const_eval/src/interpret/machine.rs
+++ b/compiler/rustc_const_eval/src/interpret/machine.rs
@@ -20,7 +20,8 @@ use rustc_target::spec::abi::Abi as CallAbi;
 use super::{
     AllocBytes, AllocId, AllocKind, AllocRange, Allocation, CTFE_ALLOC_SALT, ConstAllocation,
     CtfeProvenance, FnArg, Frame, ImmTy, InterpCx, InterpResult, MPlaceTy, MemoryKind,
-    Misalignment, OpTy, PlaceTy, Pointer, Provenance, RangeSet, throw_unsup, throw_unsup_format,
+    Misalignment, OpTy, PlaceTy, Pointer, Provenance, RangeSet, interp_ok, throw_unsup,
+    throw_unsup_format,
 };
 
 /// Data returned by [`Machine::after_stack_pop`], and consumed by
@@ -185,7 +186,7 @@ pub trait Machine<'tcx>: Sized {
         ecx: &InterpCx<'tcx, Self>,
         instance: ty::InstanceKind<'tcx>,
     ) -> InterpResult<'tcx, &'tcx mir::Body<'tcx>> {
-        Ok(ecx.tcx.instance_mir(instance))
+        interp_ok(ecx.tcx.instance_mir(instance))
     }
 
     /// Entry point to all function calls.
@@ -280,7 +281,7 @@ pub trait Machine<'tcx>: Sized {
     /// Called before a basic block terminator is executed.
     #[inline]
     fn before_terminator(_ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Determines the result of a `NullaryOp::UbChecks` invocation.
@@ -290,7 +291,7 @@ pub trait Machine<'tcx>: Sized {
     /// You can use this to detect long or endlessly running programs.
     #[inline]
     fn increment_const_eval_counter(_ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Called before a global allocation is accessed.
@@ -304,7 +305,7 @@ pub trait Machine<'tcx>: Sized {
         _static_def_id: Option<DefId>,
         _is_write: bool,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Return the `AllocId` for the given thread-local static in the current thread.
@@ -422,7 +423,7 @@ pub trait Machine<'tcx>: Sized {
         _prov: (AllocId, Self::ProvenanceExtra),
         _range: AllocRange,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Hook for performing extra checks on any memory read access,
@@ -433,7 +434,7 @@ pub trait Machine<'tcx>: Sized {
     /// Used to prevent statics from self-initializing by reading from their own memory
     /// as it is being initialized.
     fn before_alloc_read(_ecx: &InterpCx<'tcx, Self>, _alloc_id: AllocId) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Hook for performing extra checks on a memory write access.
@@ -446,7 +447,7 @@ pub trait Machine<'tcx>: Sized {
         _prov: (AllocId, Self::ProvenanceExtra),
         _range: AllocRange,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Hook for performing extra operations on a memory deallocation.
@@ -460,7 +461,7 @@ pub trait Machine<'tcx>: Sized {
         _align: Align,
         _kind: MemoryKind<Self::MemoryKind>,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Executes a retagging operation for a single pointer.
@@ -471,7 +472,7 @@ pub trait Machine<'tcx>: Sized {
         _kind: mir::RetagKind,
         val: &ImmTy<'tcx, Self::Provenance>,
     ) -> InterpResult<'tcx, ImmTy<'tcx, Self::Provenance>> {
-        Ok(val.clone())
+        interp_ok(val.clone())
     }
 
     /// Executes a retagging operation on a compound value.
@@ -482,7 +483,7 @@ pub trait Machine<'tcx>: Sized {
         _kind: mir::RetagKind,
         _place: &PlaceTy<'tcx, Self::Provenance>,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Called on places used for in-place function argument and return value handling.
@@ -516,7 +517,7 @@ pub trait Machine<'tcx>: Sized {
 
     /// Called immediately after a stack frame got pushed and its locals got initialized.
     fn after_stack_push(_ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Called just before the return value is copied to the caller-provided return place.
@@ -524,7 +525,7 @@ pub trait Machine<'tcx>: Sized {
         _ecx: &InterpCx<'tcx, Self>,
         _frame: &Frame<'tcx, Self::Provenance, Self::FrameExtra>,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Called immediately after a stack frame got popped, but before jumping back to the caller.
@@ -537,14 +538,14 @@ pub trait Machine<'tcx>: Sized {
     ) -> InterpResult<'tcx, ReturnAction> {
         // By default, we do not support unwinding from panics
         assert!(!unwinding);
-        Ok(ReturnAction::Normal)
+        interp_ok(ReturnAction::Normal)
     }
 
     /// Called immediately after an "immediate" local variable is read
     /// (i.e., this is called for reads that do not end up accessing addressable memory).
     #[inline(always)]
     fn after_local_read(_ecx: &InterpCx<'tcx, Self>, _local: mir::Local) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Called immediately after an "immediate" local variable is assigned a new value
@@ -556,7 +557,7 @@ pub trait Machine<'tcx>: Sized {
         _local: mir::Local,
         _storage_live: bool,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Called immediately after actual memory was allocated for a local
@@ -567,7 +568,7 @@ pub trait Machine<'tcx>: Sized {
         _local: mir::Local,
         _mplace: &MPlaceTy<'tcx, Self::Provenance>,
     ) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Evaluate the given constant. The `eval` function will do all the required evaluation,
@@ -645,7 +646,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
     ) -> InterpResult<$tcx> {
         // For now we don't do any checking here. We can't use `tcx.sess` because that can differ
         // between crates, and we need to ensure that const-eval always behaves the same.
-        Ok(())
+        interp_ok(())
     }
 
     #[inline(always)]
@@ -665,7 +666,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
     fn ub_checks(_ecx: &InterpCx<$tcx, Self>) -> InterpResult<$tcx, bool> {
         // We can't look at `tcx.sess` here as that can differ across crates, which can lead to
         // unsound differences in evaluating the same constant at different instantiation sites.
-        Ok(true)
+        interp_ok(true)
     }
 
     #[inline(always)]
@@ -675,7 +676,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
         alloc: &'b Allocation,
     ) -> InterpResult<$tcx, Cow<'b, Allocation<Self::Provenance>>> {
         // Overwrite default implementation: no need to adjust anything.
-        Ok(Cow::Borrowed(alloc))
+        interp_ok(Cow::Borrowed(alloc))
     }
 
     fn init_alloc_extra(
@@ -685,7 +686,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
         _size: Size,
         _align: Align,
     ) -> InterpResult<$tcx, Self::AllocExtra> {
-        Ok(())
+        interp_ok(())
     }
 
     fn extern_static_pointer(
@@ -693,7 +694,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
         def_id: DefId,
     ) -> InterpResult<$tcx, Pointer> {
         // Use the `AllocId` associated with the `DefId`. Any actual *access* will fail.
-        Ok(Pointer::new(ecx.tcx.reserve_and_set_static_alloc(def_id).into(), Size::ZERO))
+        interp_ok(Pointer::new(ecx.tcx.reserve_and_set_static_alloc(def_id).into(), Size::ZERO))
     }
 
     #[inline(always)]
@@ -702,7 +703,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
         ptr: Pointer<CtfeProvenance>,
         _kind: Option<MemoryKind<Self::MemoryKind>>,
     ) -> InterpResult<$tcx, Pointer<CtfeProvenance>> {
-        Ok(ptr)
+        interp_ok(ptr)
     }
 
     #[inline(always)]
@@ -713,7 +714,7 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
         // Allow these casts, but make the pointer not dereferenceable.
         // (I.e., they behave like transmutation.)
         // This is correct because no pointers can ever be exposed in compile-time evaluation.
-        Ok(Pointer::from_addr_invalid(addr))
+        interp_ok(Pointer::from_addr_invalid(addr))
     }
 
     #[inline(always)]
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index c3b506d848c..e6ab8ca12a8 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -23,7 +23,7 @@ use tracing::{debug, instrument, trace};
 use super::{
     AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckAlignMsg, CheckInAllocMsg,
     CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak, Misalignment, Pointer,
-    PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, err_ub_custom, throw_ub,
+    PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, err_ub_custom, interp_ok, throw_ub,
     throw_ub_custom, throw_unsup, throw_unsup_format,
 };
 use crate::fluent_generated as fluent;
@@ -82,7 +82,7 @@ pub enum FnVal<'tcx, Other> {
 impl<'tcx, Other> FnVal<'tcx, Other> {
     pub fn as_instance(self) -> InterpResult<'tcx, Instance<'tcx>> {
         match self {
-            FnVal::Instance(instance) => Ok(instance),
+            FnVal::Instance(instance) => interp_ok(instance),
             FnVal::Other(_) => {
                 throw_unsup_format!("'foreign' function pointers are not supported in this context")
             }
@@ -284,7 +284,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         self.mem_copy(ptr, new_ptr.into(), old_size.min(new_size), /*nonoverlapping*/ true)?;
         self.deallocate_ptr(ptr, old_size_and_align, kind)?;
 
-        Ok(new_ptr)
+        interp_ok(new_ptr)
     }
 
     #[instrument(skip(self), level = "debug")]
@@ -330,8 +330,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     )
                 }
                 None => err_ub!(PointerUseAfterFree(alloc_id, CheckInAllocMsg::MemoryAccessTest)),
-            }
-            .into());
+            })
+            .into();
         };
 
         if alloc.mutability.is_not() {
@@ -376,7 +376,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             bug!("Nothing can be deallocated twice");
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Internal helper function to determine the allocation and offset of a pointer (if any).
@@ -395,7 +395,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             |this, alloc_id, offset, prov| {
                 let (size, align) = this
                     .get_live_alloc_size_and_align(alloc_id, CheckInAllocMsg::MemoryAccessTest)?;
-                Ok((size, align, (alloc_id, offset, prov)))
+                interp_ok((size, align, (alloc_id, offset, prov)))
             },
         )
     }
@@ -412,9 +412,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let size = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes
         Self::check_and_deref_ptr(self, ptr, size, msg, |this, alloc_id, _, _| {
             let (size, align) = this.get_live_alloc_size_and_align(alloc_id, msg)?;
-            Ok((size, align, ()))
+            interp_ok((size, align, ()))
         })?;
-        Ok(())
+        interp_ok(())
     }
 
     /// Check whether the given pointer points to live memory for a signed amount of bytes.
@@ -428,9 +428,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     ) -> InterpResult<'tcx> {
         Self::check_and_deref_ptr(self, ptr, size, msg, |this, alloc_id, _, _| {
             let (size, align) = this.get_live_alloc_size_and_align(alloc_id, msg)?;
-            Ok((size, align, ()))
+            interp_ok((size, align, ()))
         })?;
-        Ok(())
+        interp_ok(())
     }
 
     /// Low-level helper function to check if a ptr is in-bounds and potentially return a reference
@@ -455,10 +455,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     ) -> InterpResult<'tcx, Option<T>> {
         // Everything is okay with size 0.
         if size == 0 {
-            return Ok(None);
+            return interp_ok(None);
         }
 
-        Ok(match this.borrow().ptr_try_get_alloc_id(ptr, size) {
+        interp_ok(match this.borrow().ptr_try_get_alloc_id(ptr, size) {
             Err(addr) => {
                 // We couldn't get a proper allocation.
                 throw_ub!(DanglingIntPointer { addr, inbounds_size: size, msg });
@@ -498,7 +498,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if let Some(misaligned) = misaligned {
             throw_ub!(AlignmentCheckFailed(misaligned, msg))
         }
-        Ok(())
+        interp_ok(())
     }
 
     pub(super) fn is_ptr_misaligned(
@@ -634,7 +634,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // `get_global_alloc` that we can actually use directly without inserting anything anywhere.
         // So the error type is `InterpResult<'tcx, &Allocation<M::Provenance>>`.
         let a = self.memory.alloc_map.get_or(id, || {
-            let alloc = self.get_global_alloc(id, /*is_write*/ false).map_err(Err)?;
+            // We have to funnel the `InterpErrorInfo` through a `Result` to match the `get_or` API,
+            // so we use `report_err` for that.
+            let alloc = self.get_global_alloc(id, /*is_write*/ false).report_err().map_err(Err)?;
             match alloc {
                 Cow::Borrowed(alloc) => {
                     // We got a ref, cheaply return that as an "error" so that the
@@ -653,8 +655,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         });
         // Now unpack that funny error type
         match a {
-            Ok(a) => Ok(&a.1),
-            Err(a) => a,
+            Ok(a) => interp_ok(&a.1),
+            Err(a) => a.into(),
         }
     }
 
@@ -662,7 +664,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     /// The caller is responsible for calling the access hooks!
     pub fn get_alloc_bytes_unchecked_raw(&self, id: AllocId) -> InterpResult<'tcx, *const u8> {
         let alloc = self.get_alloc_raw(id)?;
-        Ok(alloc.get_bytes_unchecked_raw())
+        interp_ok(alloc.get_bytes_unchecked_raw())
     }
 
     /// Bounds-checked *but not align-checked* allocation access.
@@ -680,7 +682,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             CheckInAllocMsg::MemoryAccessTest,
             |this, alloc_id, offset, prov| {
                 let alloc = this.get_alloc_raw(alloc_id)?;
-                Ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc)))
+                interp_ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc)))
             },
         )?;
         // We want to call the hook on *all* accesses that involve an AllocId, including zero-sized
@@ -703,20 +705,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     range,
                 )?;
             }
-            Ok(Some(AllocRef { alloc, range, tcx: *self.tcx, alloc_id }))
+            interp_ok(Some(AllocRef { alloc, range, tcx: *self.tcx, alloc_id }))
         } else {
-            Ok(None)
+            interp_ok(None)
         }
     }
 
     /// Return the `extra` field of the given allocation.
     pub fn get_alloc_extra<'a>(&'a self, id: AllocId) -> InterpResult<'tcx, &'a M::AllocExtra> {
-        Ok(&self.get_alloc_raw(id)?.extra)
+        interp_ok(&self.get_alloc_raw(id)?.extra)
     }
 
     /// Return the `mutability` field of the given allocation.
     pub fn get_alloc_mutability<'a>(&'a self, id: AllocId) -> InterpResult<'tcx, Mutability> {
-        Ok(self.get_alloc_raw(id)?.mutability)
+        interp_ok(self.get_alloc_raw(id)?.mutability)
     }
 
     /// Gives raw mutable access to the `Allocation`, without bounds or alignment checks.
@@ -750,7 +752,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if alloc.mutability.is_not() {
             throw_ub!(WriteToReadOnly(id))
         }
-        Ok((alloc, &mut self.machine))
+        interp_ok((alloc, &mut self.machine))
     }
 
     /// Gives raw, mutable access to the `Allocation` address, without bounds or alignment checks.
@@ -760,7 +762,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         id: AllocId,
     ) -> InterpResult<'tcx, *mut u8> {
         let alloc = self.get_alloc_raw_mut(id)?.0;
-        Ok(alloc.get_bytes_unchecked_raw_mut())
+        interp_ok(alloc.get_bytes_unchecked_raw_mut())
     }
 
     /// Bounds-checked *but not align-checked* allocation access.
@@ -781,7 +783,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             CheckInAllocMsg::MemoryAccessTest,
             |this, alloc_id, offset, prov| {
                 let (alloc, machine) = this.get_alloc_raw_mut(alloc_id)?;
-                Ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc, machine)))
+                interp_ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc, machine)))
             },
         )?;
 
@@ -790,9 +792,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             if !validation_in_progress {
                 M::before_memory_write(tcx, machine, &mut alloc.extra, (alloc_id, prov), range)?;
             }
-            Ok(Some(AllocRefMut { alloc, range, tcx: *tcx, alloc_id }))
+            interp_ok(Some(AllocRefMut { alloc, range, tcx: *tcx, alloc_id }))
         } else {
-            Ok(None)
+            interp_ok(None)
         }
     }
 
@@ -802,7 +804,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         id: AllocId,
     ) -> InterpResult<'tcx, (&'a mut M::AllocExtra, &'a mut M)> {
         let (alloc, machine) = self.get_alloc_raw_mut(id)?;
-        Ok((&mut alloc.extra, machine))
+        interp_ok((&mut alloc.extra, machine))
     }
 
     /// Check whether an allocation is live. This is faster than calling
@@ -904,7 +906,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if matches!(kind, AllocKind::Dead) {
             throw_ub!(PointerUseAfterFree(id, msg))
         }
-        Ok((size, align))
+        interp_ok((size, align))
     }
 
     fn get_fn_alloc(&self, id: AllocId) -> Option<FnVal<'tcx, M::ExtraFnVal>> {
@@ -928,7 +930,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             throw_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset)))
         }
         self.get_fn_alloc(alloc_id)
-            .ok_or_else(|| err_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))).into())
+            .ok_or_else(|| err_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))))
+            .into()
     }
 
     /// Get the dynamic type of the given vtable pointer.
@@ -951,12 +954,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if let Some(expected_dyn_type) = expected_trait {
             self.check_vtable_for_type(vtable_dyn_type, expected_dyn_type)?;
         }
-        Ok(ty)
+        interp_ok(ty)
     }
 
     pub fn alloc_mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {
         self.get_alloc_raw_mut(id)?.0.mutability = Mutability::Not;
-        Ok(())
+        interp_ok(())
     }
 
     /// Create a lazy debug printer that prints the given allocation and all allocations it points
@@ -1144,10 +1147,11 @@ impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>
     pub fn write_scalar(&mut self, range: AllocRange, val: Scalar<Prov>) -> InterpResult<'tcx> {
         let range = self.range.subrange(range);
         debug!("write_scalar at {:?}{range:?}: {val:?}", self.alloc_id);
-        Ok(self
-            .alloc
+
+        self.alloc
             .write_scalar(&self.tcx, range, val)
-            .map_err(|e| e.to_interp_error(self.alloc_id))?)
+            .map_err(|e| e.to_interp_error(self.alloc_id))
+            .into()
     }
 
     /// `offset` is relative to this allocation reference, not the base of the allocation.
@@ -1158,26 +1162,27 @@ impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>
     /// Mark the given sub-range (relative to this allocation reference) as uninitialized.
     pub fn write_uninit(&mut self, range: AllocRange) -> InterpResult<'tcx> {
         let range = self.range.subrange(range);
-        Ok(self
-            .alloc
+
+        self.alloc
             .write_uninit(&self.tcx, range)
-            .map_err(|e| e.to_interp_error(self.alloc_id))?)
+            .map_err(|e| e.to_interp_error(self.alloc_id))
+            .into()
     }
 
     /// Mark the entire referenced range as uninitialized
     pub fn write_uninit_full(&mut self) -> InterpResult<'tcx> {
-        Ok(self
-            .alloc
+        self.alloc
             .write_uninit(&self.tcx, self.range)
-            .map_err(|e| e.to_interp_error(self.alloc_id))?)
+            .map_err(|e| e.to_interp_error(self.alloc_id))
+            .into()
     }
 
     /// Remove all provenance in the reference range.
     pub fn clear_provenance(&mut self) -> InterpResult<'tcx> {
-        Ok(self
-            .alloc
+        self.alloc
             .clear_provenance(&self.tcx, self.range)
-            .map_err(|e| e.to_interp_error(self.alloc_id))?)
+            .map_err(|e| e.to_interp_error(self.alloc_id))
+            .into()
     }
 }
 
@@ -1189,12 +1194,10 @@ impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes> AllocRef<'a, 'tcx, Pr
         read_provenance: bool,
     ) -> InterpResult<'tcx, Scalar<Prov>> {
         let range = self.range.subrange(range);
-        let res = self
-            .alloc
+        self.alloc
             .read_scalar(&self.tcx, range, read_provenance)
-            .map_err(|e| e.to_interp_error(self.alloc_id))?;
-        debug!("read_scalar at {:?}{range:?}: {res:?}", self.alloc_id);
-        Ok(res)
+            .map_err(|e| e.to_interp_error(self.alloc_id))
+            .into()
     }
 
     /// `range` is relative to this allocation reference, not the base of the allocation.
@@ -1212,10 +1215,10 @@ impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes> AllocRef<'a, 'tcx, Pr
 
     /// `range` is relative to this allocation reference, not the base of the allocation.
     pub fn get_bytes_strip_provenance<'b>(&'b self) -> InterpResult<'tcx, &'a [u8]> {
-        Ok(self
-            .alloc
+        self.alloc
             .get_bytes_strip_provenance(&self.tcx, self.range)
-            .map_err(|e| e.to_interp_error(self.alloc_id))?)
+            .map_err(|e| e.to_interp_error(self.alloc_id))
+            .into()
     }
 
     /// Returns whether the allocation has provenance anywhere in the range of the `AllocRef`.
@@ -1236,14 +1239,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     ) -> InterpResult<'tcx, &[u8]> {
         let Some(alloc_ref) = self.get_ptr_alloc(ptr, size)? else {
             // zero-sized access
-            return Ok(&[]);
+            return interp_ok(&[]);
         };
         // Side-step AllocRef and directly access the underlying bytes more efficiently.
         // (We are staying inside the bounds here so all is good.)
-        Ok(alloc_ref
-            .alloc
-            .get_bytes_strip_provenance(&alloc_ref.tcx, alloc_ref.range)
-            .map_err(|e| e.to_interp_error(alloc_ref.alloc_id))?)
+        interp_ok(
+            alloc_ref
+                .alloc
+                .get_bytes_strip_provenance(&alloc_ref.tcx, alloc_ref.range)
+                .map_err(|e| e.to_interp_error(alloc_ref.alloc_id))?,
+        )
     }
 
     /// Writes the given stream of bytes into memory.
@@ -1263,7 +1268,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let Some(alloc_ref) = self.get_ptr_alloc_mut(ptr, size)? else {
             // zero-sized access
             assert_matches!(src.next(), None, "iterator said it was empty but returned an element");
-            return Ok(());
+            return interp_ok(());
         };
 
         // Side-step AllocRef and directly access the underlying bytes more efficiently.
@@ -1279,7 +1284,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             *dest = src.next().expect("iterator was shorter than it said it would be");
         }
         assert_matches!(src.next(), None, "iterator was longer than it said it would be");
-        Ok(())
+        interp_ok(())
     }
 
     pub fn mem_copy(
@@ -1316,7 +1321,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // Source alloc preparations and access hooks.
         let Some((src_alloc_id, src_offset, src_prov)) = src_parts else {
             // Zero-sized *source*, that means dest is also zero-sized and we have nothing to do.
-            return Ok(());
+            return interp_ok(());
         };
         let src_alloc = self.get_alloc_raw(src_alloc_id)?;
         let src_range = alloc_range(src_offset, size);
@@ -1332,7 +1337,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // We already did the source checks and called the hooks so we are good to return early.
         let Some((dest_alloc_id, dest_offset, dest_prov)) = dest_parts else {
             // Zero-sized *destination*.
-            return Ok(());
+            return interp_ok(());
         };
 
         // Prepare getting source provenance.
@@ -1375,7 +1380,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 .write_uninit(&tcx, dest_range)
                 .map_err(|e| e.to_interp_error(dest_alloc_id))?;
             // We can forget about the provenance, this is all not initialized anyway.
-            return Ok(());
+            return interp_ok(());
         }
 
         // SAFE: The above indexing would have panicked if there weren't at least `size` bytes
@@ -1432,7 +1437,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // copy the provenance to the destination
         dest_alloc.provenance_apply_copy(provenance);
 
-        Ok(())
+        interp_ok(())
     }
 }
 
@@ -1441,7 +1446,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     /// Test if this value might be null.
     /// If the machine does not support ptr-to-int casts, this is conservative.
     pub fn scalar_may_be_null(&self, scalar: Scalar<M::Provenance>) -> InterpResult<'tcx, bool> {
-        Ok(match scalar.try_to_scalar_int() {
+        interp_ok(match scalar.try_to_scalar_int() {
             Ok(int) => int.is_null(),
             Err(_) => {
                 // Can only happen during CTFE.
@@ -1508,13 +1513,14 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         ptr: Pointer<Option<M::Provenance>>,
         size: i64,
     ) -> InterpResult<'tcx, (AllocId, Size, M::ProvenanceExtra)> {
-        self.ptr_try_get_alloc_id(ptr, size).map_err(|offset| {
-            err_ub!(DanglingIntPointer {
-                addr: offset,
-                inbounds_size: size,
-                msg: CheckInAllocMsg::InboundsTest
+        self.ptr_try_get_alloc_id(ptr, size)
+            .map_err(|offset| {
+                err_ub!(DanglingIntPointer {
+                    addr: offset,
+                    inbounds_size: size,
+                    msg: CheckInAllocMsg::InboundsTest
+                })
             })
             .into()
-        })
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs
index 31ee3e6519a..7e6600060b4 100644
--- a/compiler/rustc_const_eval/src/interpret/operand.rs
+++ b/compiler/rustc_const_eval/src/interpret/operand.rs
@@ -16,7 +16,7 @@ use tracing::trace;
 use super::{
     CtfeProvenance, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, OffsetMode,
     PlaceTy, Pointer, Projectable, Provenance, Scalar, alloc_range, err_ub, from_known_layout,
-    mir_assign_valid_types, throw_ub,
+    interp_ok, mir_assign_valid_types, throw_ub,
 };
 
 /// An `Immediate` represents a single immediate self-contained Rust value.
@@ -149,7 +149,7 @@ impl<Prov: Provenance> Immediate<Prov> {
             }
             Immediate::Uninit => {}
         }
-        Ok(())
+        interp_ok(())
     }
 }
 
@@ -307,7 +307,7 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
                 data_size: s.size().bytes(),
             }));
         }
-        Ok(s)
+        interp_ok(s)
     }
 
     #[inline]
@@ -430,7 +430,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for ImmTy<'tcx, Prov> {
         ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, Self> {
         assert_matches!(meta, MemPlaceMeta::None); // we can't store this anywhere anyway
-        Ok(self.offset_(offset, layout, ecx))
+        interp_ok(self.offset_(offset, layout, ecx))
     }
 
     #[inline(always)]
@@ -438,7 +438,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for ImmTy<'tcx, Prov> {
         &self,
         _ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
-        Ok(self.clone().into())
+        interp_ok(self.clone().into())
     }
 }
 
@@ -514,11 +514,13 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> {
         ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, Self> {
         match self.as_mplace_or_imm() {
-            Left(mplace) => Ok(mplace.offset_with_meta(offset, mode, meta, layout, ecx)?.into()),
+            Left(mplace) => {
+                interp_ok(mplace.offset_with_meta(offset, mode, meta, layout, ecx)?.into())
+            }
             Right(imm) => {
                 assert_matches!(meta, MemPlaceMeta::None); // no place to store metadata here
                 // Every part of an uninit is uninit.
-                Ok(imm.offset_(offset, layout, ecx).into())
+                interp_ok(imm.offset_(offset, layout, ecx).into())
             }
         }
     }
@@ -528,7 +530,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> {
         &self,
         _ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
-        Ok(self.clone())
+        interp_ok(self.clone())
     }
 }
 
@@ -543,12 +545,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     ) -> InterpResult<'tcx, Option<ImmTy<'tcx, M::Provenance>>> {
         if mplace.layout.is_unsized() {
             // Don't touch unsized
-            return Ok(None);
+            return interp_ok(None);
         }
 
         let Some(alloc) = self.get_place_alloc(mplace)? else {
             // zero-sized type can be left uninit
-            return Ok(Some(ImmTy::uninit(mplace.layout)));
+            return interp_ok(Some(ImmTy::uninit(mplace.layout)));
         };
 
         // It may seem like all types with `Scalar` or `ScalarPair` ABI are fair game at this point.
@@ -557,7 +559,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // case where some of the bytes are initialized and others are not. So, we need an extra
         // check that walks over the type of `mplace` to make sure it is truly correct to treat this
         // like a `Scalar` (or `ScalarPair`).
-        Ok(match mplace.layout.abi {
+        interp_ok(match mplace.layout.abi {
             Abi::Scalar(abi::Scalar::Initialized { value: s, .. }) => {
                 let size = s.size(self);
                 assert_eq!(size, mplace.layout.size, "abi::Scalar size does not match layout size");
@@ -606,7 +608,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         &self,
         src: &impl Projectable<'tcx, M::Provenance>,
     ) -> InterpResult<'tcx, Either<MPlaceTy<'tcx, M::Provenance>, ImmTy<'tcx, M::Provenance>>> {
-        Ok(match src.to_op(self)?.as_mplace_or_imm() {
+        interp_ok(match src.to_op(self)?.as_mplace_or_imm() {
             Left(ref mplace) => {
                 if let Some(val) = self.read_immediate_from_mplace_raw(mplace)? {
                     Right(val)
@@ -637,7 +639,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if matches!(*imm, Immediate::Uninit) {
             throw_ub!(InvalidUninitBytes(None));
         }
-        Ok(imm)
+        interp_ok(imm)
     }
 
     /// Read a scalar from a place
@@ -645,7 +647,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         &self,
         op: &impl Projectable<'tcx, M::Provenance>,
     ) -> InterpResult<'tcx, Scalar<M::Provenance>> {
-        Ok(self.read_immediate(op)?.to_scalar())
+        interp_ok(self.read_immediate(op)?.to_scalar())
     }
 
     // Pointer-sized reads are fairly common and need target layout access, so we wrap them in
@@ -678,7 +680,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let len = mplace.len(self)?;
         let bytes = self.read_bytes_ptr_strip_provenance(mplace.ptr(), Size::from_bytes(len))?;
         let str = std::str::from_utf8(bytes).map_err(|err| err_ub!(InvalidStr(err)))?;
-        Ok(str)
+        interp_ok(str)
     }
 
     /// Read from a local of the current frame.
@@ -698,7 +700,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             assert!(!layout.is_unsized());
         }
         M::after_local_read(self, local)?;
-        Ok(OpTy { op, layout })
+        interp_ok(OpTy { op, layout })
     }
 
     /// Every place can be read from, so we can turn them into an operand.
@@ -709,12 +711,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         place: &PlaceTy<'tcx, M::Provenance>,
     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
         match place.as_mplace_or_local() {
-            Left(mplace) => Ok(mplace.into()),
+            Left(mplace) => interp_ok(mplace.into()),
             Right((local, offset, locals_addr, _)) => {
                 debug_assert!(place.layout.is_sized()); // only sized locals can ever be `Place::Local`.
                 debug_assert_eq!(locals_addr, self.frame().locals_addr());
                 let base = self.local_to_op(local, None)?;
-                Ok(match offset {
+                interp_ok(match offset {
                     Some(offset) => base.offset(offset, place.layout, self)?,
                     None => {
                         // In the common case this hasn't been projected.
@@ -764,7 +766,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 )
             }
         }
-        Ok(op)
+        interp_ok(op)
     }
 
     /// Evaluate the operand, returning a place where you can then find the data.
@@ -794,7 +796,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             }
         };
         trace!("{:?}: {:?}", mir_op, op);
-        Ok(op)
+        interp_ok(op)
     }
 
     pub(crate) fn const_val_to_op(
@@ -805,12 +807,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
         // Other cases need layout.
         let adjust_scalar = |scalar| -> InterpResult<'tcx, _> {
-            Ok(match scalar {
+            interp_ok(match scalar {
                 Scalar::Ptr(ptr, size) => Scalar::Ptr(self.global_root_pointer(ptr)?, size),
                 Scalar::Int(int) => Scalar::Int(int),
             })
         };
-        let layout = from_known_layout(self.tcx, self.param_env, layout, || self.layout_of(ty))?;
+        let layout =
+            from_known_layout(self.tcx, self.param_env, layout, || self.layout_of(ty).into())?;
         let imm = match val_val {
             mir::ConstValue::Indirect { alloc_id, offset } => {
                 // This is const data, no mutation allowed.
@@ -818,7 +821,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     CtfeProvenance::from(alloc_id).as_immutable(),
                     offset,
                 ))?;
-                return Ok(self.ptr_to_mplace(ptr.into(), layout).into());
+                return interp_ok(self.ptr_to_mplace(ptr.into(), layout).into());
             }
             mir::ConstValue::Scalar(x) => adjust_scalar(x)?.into(),
             mir::ConstValue::ZeroSized => Immediate::Uninit,
@@ -829,7 +832,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 Immediate::new_slice(self.global_root_pointer(ptr)?.into(), meta, self)
             }
         };
-        Ok(OpTy { op: Operand::Immediate(imm), layout })
+        interp_ok(OpTy { op: Operand::Immediate(imm), layout })
     }
 }
 
diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs
index 863b330a74c..52cd9b898bb 100644
--- a/compiler/rustc_const_eval/src/interpret/operator.rs
+++ b/compiler/rustc_const_eval/src/interpret/operator.rs
@@ -9,7 +9,7 @@ use rustc_span::symbol::sym;
 use rustc_target::abi::Size;
 use tracing::trace;
 
-use super::{ImmTy, InterpCx, Machine, MemPlaceMeta, throw_ub};
+use super::{ImmTy, InterpCx, Machine, MemPlaceMeta, interp_ok, throw_ub};
 
 impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     fn three_way_compare<T: Ord>(&self, lhs: T, rhs: T) -> ImmTy<'tcx, M::Provenance> {
@@ -156,7 +156,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 });
             }
 
-            return Ok(ImmTy::from_scalar_int(result, left.layout));
+            return interp_ok(ImmTy::from_scalar_int(result, left.layout));
         }
 
         // For the remaining ops, the types must be the same on both sides
@@ -181,10 +181,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 _ => None,
             };
             if let Some(op) = op {
-                return Ok(ImmTy::from_bool(op(&l_signed(), &r_signed()), *self.tcx));
+                return interp_ok(ImmTy::from_bool(op(&l_signed(), &r_signed()), *self.tcx));
             }
             if bin_op == Cmp {
-                return Ok(self.three_way_compare(l_signed(), r_signed()));
+                return interp_ok(self.three_way_compare(l_signed(), r_signed()));
             }
             let op: Option<fn(i128, i128) -> (i128, bool)> = match bin_op {
                 Div if r.is_null() => throw_ub!(DivisionByZero),
@@ -221,7 +221,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     throw_ub!(ArithOverflow { intrinsic });
                 }
                 let res = ImmTy::from_scalar_int(result, left.layout);
-                return Ok(if with_overflow {
+                return interp_ok(if with_overflow {
                     let overflow = ImmTy::from_bool(overflow, *self.tcx);
                     ImmTy::from_pair(res, overflow, *self.tcx)
                 } else {
@@ -234,10 +234,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let r = r_unsigned();
 
         if bin_op == Cmp {
-            return Ok(self.three_way_compare(l, r));
+            return interp_ok(self.three_way_compare(l, r));
         }
 
-        Ok(match bin_op {
+        interp_ok(match bin_op {
             Eq => ImmTy::from_bool(l == r, *self.tcx),
             Ne => ImmTy::from_bool(l != r, *self.tcx),
 
@@ -339,7 +339,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     throw_ub!(PointerArithOverflow)
                 }
                 let offset_ptr = self.ptr_offset_inbounds(ptr, offset_bytes)?;
-                Ok(ImmTy::from_scalar(Scalar::from_maybe_pointer(offset_ptr, self), left.layout))
+                interp_ok(ImmTy::from_scalar(
+                    Scalar::from_maybe_pointer(offset_ptr, self),
+                    left.layout,
+                ))
             }
 
             // Fall back to machine hook so Miri can support more pointer ops.
@@ -366,20 +369,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 assert_eq!(left.layout.ty, right.layout.ty);
                 let left = left.to_scalar();
                 let right = right.to_scalar();
-                Ok(self.binary_char_op(bin_op, left.to_char()?, right.to_char()?))
+                interp_ok(self.binary_char_op(bin_op, left.to_char()?, right.to_char()?))
             }
             ty::Bool => {
                 assert_eq!(left.layout.ty, right.layout.ty);
                 let left = left.to_scalar();
                 let right = right.to_scalar();
-                Ok(self.binary_bool_op(bin_op, left.to_bool()?, right.to_bool()?))
+                interp_ok(self.binary_bool_op(bin_op, left.to_bool()?, right.to_bool()?))
             }
             ty::Float(fty) => {
                 assert_eq!(left.layout.ty, right.layout.ty);
                 let layout = left.layout;
                 let left = left.to_scalar();
                 let right = right.to_scalar();
-                Ok(match fty {
+                interp_ok(match fty {
                     FloatTy::F16 => {
                         self.binary_float_op(bin_op, layout, left.to_f16()?, right.to_f16()?)
                     }
@@ -447,7 +450,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     Not => !val,
                     _ => span_bug!(self.cur_span(), "Invalid bool op {:?}", un_op),
                 };
-                Ok(ImmTy::from_bool(res, *self.tcx))
+                interp_ok(ImmTy::from_bool(res, *self.tcx))
             }
             ty::Float(fty) => {
                 let val = val.to_scalar();
@@ -462,7 +465,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     FloatTy::F64 => Scalar::from_f64(-val.to_f64()?),
                     FloatTy::F128 => Scalar::from_f128(-val.to_f128()?),
                 };
-                Ok(ImmTy::from_scalar(res, layout))
+                interp_ok(ImmTy::from_scalar(res, layout))
             }
             ty::Int(..) => {
                 let val = val.to_scalar().to_int(layout.size)?;
@@ -472,7 +475,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     _ => span_bug!(self.cur_span(), "Invalid integer op {:?}", un_op),
                 };
                 let res = ScalarInt::truncate_from_int(res, layout.size).0;
-                Ok(ImmTy::from_scalar(res.into(), layout))
+                interp_ok(ImmTy::from_scalar(res.into(), layout))
             }
             ty::Uint(..) => {
                 let val = val.to_scalar().to_uint(layout.size)?;
@@ -481,12 +484,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     _ => span_bug!(self.cur_span(), "Invalid unsigned integer op {:?}", un_op),
                 };
                 let res = ScalarInt::truncate_from_uint(res, layout.size).0;
-                Ok(ImmTy::from_scalar(res.into(), layout))
+                interp_ok(ImmTy::from_scalar(res.into(), layout))
             }
             ty::RawPtr(..) | ty::Ref(..) => {
                 assert_eq!(un_op, PtrMetadata);
                 let (_, meta) = val.to_scalar_and_meta();
-                Ok(match meta {
+                interp_ok(match meta {
                     MemPlaceMeta::Meta(scalar) => {
                         let ty = un_op.ty(*self.tcx, val.layout.ty);
                         let layout = self.layout_of(ty)?;
@@ -514,7 +517,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let layout = self.layout_of(arg_ty)?;
         let usize_layout = || self.layout_of(self.tcx.types.usize).unwrap();
 
-        Ok(match null_op {
+        interp_ok(match null_op {
             SizeOf => {
                 if !layout.abi.is_sized() {
                     span_bug!(self.cur_span(), "unsized type for `NullaryOp::SizeOf`");
diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs
index 05264d32c6b..449d4c6bd7d 100644
--- a/compiler/rustc_const_eval/src/interpret/place.rs
+++ b/compiler/rustc_const_eval/src/interpret/place.rs
@@ -13,9 +13,9 @@ use rustc_target::abi::{Abi, Align, HasDataLayout, Size};
 use tracing::{instrument, trace};
 
 use super::{
-    AllocRef, AllocRefMut, CheckAlignMsg, CtfeProvenance, DiscardInterpError, ImmTy, Immediate,
-    InterpCx, InterpResult, Machine, MemoryKind, Misalignment, OffsetMode, OpTy, Operand, Pointer,
-    Projectable, Provenance, Scalar, alloc_range, mir_assign_valid_types,
+    AllocRef, AllocRefMut, CheckAlignMsg, CtfeProvenance, ImmTy, Immediate, InterpCx, InterpResult,
+    Machine, MemoryKind, Misalignment, OffsetMode, OpTy, Operand, Pointer, Projectable, Provenance,
+    Scalar, alloc_range, interp_ok, mir_assign_valid_types,
 };
 
 #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
@@ -90,7 +90,7 @@ impl<Prov: Provenance> MemPlace<Prov> {
             }
             OffsetMode::Wrapping => self.ptr.wrapping_offset(offset, ecx),
         };
-        Ok(MemPlace { ptr, meta, misaligned: self.misaligned })
+        interp_ok(MemPlace { ptr, meta, misaligned: self.misaligned })
     }
 }
 
@@ -163,7 +163,10 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for MPlaceTy<'tcx, Prov> {
         layout: TyAndLayout<'tcx>,
         ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, Self> {
-        Ok(MPlaceTy { mplace: self.mplace.offset_with_meta_(offset, mode, meta, ecx)?, layout })
+        interp_ok(MPlaceTy {
+            mplace: self.mplace.offset_with_meta_(offset, mode, meta, ecx)?,
+            layout,
+        })
     }
 
     #[inline(always)]
@@ -171,7 +174,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for MPlaceTy<'tcx, Prov> {
         &self,
         _ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
-        Ok(self.clone().into())
+        interp_ok(self.clone().into())
     }
 }
 
@@ -279,7 +282,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for PlaceTy<'tcx, Prov> {
         layout: TyAndLayout<'tcx>,
         ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, Self> {
-        Ok(match self.as_mplace_or_local() {
+        interp_ok(match self.as_mplace_or_local() {
             Left(mplace) => mplace.offset_with_meta(offset, mode, meta, layout, ecx)?.into(),
             Right((local, old_offset, locals_addr, _)) => {
                 debug_assert!(layout.is_sized(), "unsized locals should live in memory");
@@ -367,7 +370,7 @@ impl<'tcx, Prov: Provenance> Writeable<'tcx, Prov> for MPlaceTy<'tcx, Prov> {
         &self,
         _ecx: &mut InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, MPlaceTy<'tcx, Prov>> {
-        Ok(self.clone())
+        interp_ok(self.clone())
     }
 }
 
@@ -425,7 +428,7 @@ where
         // `ref_to_mplace` is called on raw pointers even if they don't actually get dereferenced;
         // we hence can't call `size_and_align_of` since that asserts more validity than we want.
         let ptr = ptr.to_pointer(self)?;
-        Ok(self.ptr_with_meta_to_mplace(ptr, meta, layout, /*unaligned*/ false))
+        interp_ok(self.ptr_with_meta_to_mplace(ptr, meta, layout, /*unaligned*/ false))
     }
 
     /// Turn a mplace into a (thin or wide) mutable raw pointer, pointing to the same space.
@@ -437,7 +440,7 @@ where
     ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
         let imm = mplace.mplace.to_ref(self);
         let layout = self.layout_of(Ty::new_mut_ptr(self.tcx.tcx, mplace.layout.ty))?;
-        Ok(ImmTy::from_immediate(imm, layout))
+        interp_ok(ImmTy::from_immediate(imm, layout))
     }
 
     /// Take an operand, representing a pointer, and dereference it to a place.
@@ -458,7 +461,7 @@ where
         trace!("deref to {} on {:?}", val.layout.ty, *val);
 
         let mplace = self.ref_to_mplace(&val)?;
-        Ok(mplace)
+        interp_ok(mplace)
     }
 
     #[inline]
@@ -474,7 +477,7 @@ where
         // If an access is both OOB and misaligned, we want to see the bounds error.
         let a = self.get_ptr_alloc(mplace.ptr(), size)?;
         self.check_misalign(mplace.mplace.misaligned, CheckAlignMsg::BasedOn)?;
-        Ok(a)
+        interp_ok(a)
     }
 
     #[inline]
@@ -489,17 +492,10 @@ where
         // We check alignment separately, and raise that error *after* checking everything else.
         // If an access is both OOB and misaligned, we want to see the bounds error.
         // However we have to call `check_misalign` first to make the borrow checker happy.
-        let misalign_err = self.check_misalign(mplace.mplace.misaligned, CheckAlignMsg::BasedOn);
-        match self.get_ptr_alloc_mut(mplace.ptr(), size) {
-            Ok(a) => {
-                misalign_err?;
-                Ok(a)
-            }
-            Err(e) => {
-                misalign_err.discard_interp_err();
-                Err(e)
-            }
-        }
+        let misalign_res = self.check_misalign(mplace.mplace.misaligned, CheckAlignMsg::BasedOn);
+        // An error from get_ptr_alloc_mut takes precedence.
+        let (a, ()) = self.get_ptr_alloc_mut(mplace.ptr(), size).and(misalign_res)?;
+        interp_ok(a)
     }
 
     /// Turn a local in the current frame into a place.
@@ -519,7 +515,7 @@ where
                 Operand::Indirect(mplace) => Place::Ptr(*mplace),
             }
         };
-        Ok(PlaceTy { place, layout })
+        interp_ok(PlaceTy { place, layout })
     }
 
     /// Computes a place. You should only use this if you intend to write into this
@@ -556,7 +552,7 @@ where
                 )
             }
         }
-        Ok(place)
+        interp_ok(place)
     }
 
     /// Given a place, returns either the underlying mplace or a reference to where the value of
@@ -572,7 +568,7 @@ where
             (&mut Immediate<M::Provenance>, TyAndLayout<'tcx>, mir::Local),
         >,
     > {
-        Ok(match place.to_place().as_mplace_or_local() {
+        interp_ok(match place.to_place().as_mplace_or_local() {
             Left(mplace) => Left(mplace),
             Right((local, offset, locals_addr, layout)) => {
                 if offset.is_some() {
@@ -617,7 +613,7 @@ where
             )?;
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Write a scalar to a place
@@ -667,7 +663,7 @@ where
                 self.write_immediate_to_mplace_no_validate(src, mplace.layout, mplace.mplace)?;
             }
         }
-        Ok(())
+        interp_ok(())
     }
 
     /// Write an immediate to memory.
@@ -690,7 +686,7 @@ where
         let tcx = *self.tcx;
         let Some(mut alloc) = self.get_place_alloc_mut(&MPlaceTy { mplace: dest, layout })? else {
             // zero-sized access
-            return Ok(());
+            return interp_ok(());
         };
 
         match value {
@@ -715,7 +711,7 @@ where
                 alloc.write_scalar(alloc_range(Size::ZERO, a_val.size()), a_val)?;
                 alloc.write_scalar(alloc_range(b_offset, b_val.size()), b_val)?;
                 // We don't have to reset padding here, `write_immediate` will anyway do a validation run.
-                Ok(())
+                interp_ok(())
             }
             Immediate::Uninit => alloc.write_uninit_full(),
         }
@@ -736,12 +732,12 @@ where
             Left(mplace) => {
                 let Some(mut alloc) = self.get_place_alloc_mut(&mplace)? else {
                     // Zero-sized access
-                    return Ok(());
+                    return interp_ok(());
                 };
                 alloc.write_uninit_full()?;
             }
         }
-        Ok(())
+        interp_ok(())
     }
 
     /// Remove all provenance in the given place.
@@ -760,12 +756,12 @@ where
             Left(mplace) => {
                 let Some(mut alloc) = self.get_place_alloc_mut(&mplace)? else {
                     // Zero-sized access
-                    return Ok(());
+                    return interp_ok(());
                 };
                 alloc.clear_provenance()?;
             }
         }
-        Ok(())
+        interp_ok(())
     }
 
     /// Copies the data from an operand to a place.
@@ -848,7 +844,7 @@ where
             )?;
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Copies the data from an operand to a place.
@@ -925,7 +921,7 @@ where
         self.mem_copy(src.ptr(), dest.ptr(), dest_size, /*nonoverlapping*/ true)?;
         self.check_misalign(src.mplace.misaligned, CheckAlignMsg::BasedOn)?;
         self.check_misalign(dest.mplace.misaligned, CheckAlignMsg::BasedOn)?;
-        Ok(())
+        interp_ok(())
     }
 
     /// Ensures that a place is in memory, and returns where it is.
@@ -987,7 +983,7 @@ where
             Place::Ptr(mplace) => mplace,
         };
         // Return with the original layout and align, so that the caller can go on
-        Ok(MPlaceTy { mplace, layout: place.layout })
+        interp_ok(MPlaceTy { mplace, layout: place.layout })
     }
 
     pub fn allocate_dyn(
@@ -1000,7 +996,7 @@ where
             span_bug!(self.cur_span(), "cannot allocate space for `extern` type, size is not known")
         };
         let ptr = self.allocate_ptr(size, align, kind)?;
-        Ok(self.ptr_with_meta_to_mplace(ptr.into(), meta, layout, /*unaligned*/ false))
+        interp_ok(self.ptr_with_meta_to_mplace(ptr.into(), meta, layout, /*unaligned*/ false))
     }
 
     pub fn allocate(
@@ -1035,7 +1031,7 @@ where
         };
         let meta = Scalar::from_target_usize(u64::try_from(str.len()).unwrap(), self);
         let layout = self.layout_of(self.tcx.types.str_).unwrap();
-        Ok(self.ptr_with_meta_to_mplace(
+        interp_ok(self.ptr_with_meta_to_mplace(
             ptr.into(),
             MemPlaceMeta::Meta(meta),
             layout,
@@ -1051,7 +1047,7 @@ where
         let _ = self.tcx.global_alloc(raw.alloc_id);
         let ptr = self.global_root_pointer(Pointer::from(raw.alloc_id))?;
         let layout = self.layout_of(raw.ty)?;
-        Ok(self.ptr_to_mplace(ptr.into(), layout))
+        interp_ok(self.ptr_to_mplace(ptr.into(), layout))
     }
 }
 
diff --git a/compiler/rustc_const_eval/src/interpret/projection.rs b/compiler/rustc_const_eval/src/interpret/projection.rs
index 621afdb1050..cc49d7cebf4 100644
--- a/compiler/rustc_const_eval/src/interpret/projection.rs
+++ b/compiler/rustc_const_eval/src/interpret/projection.rs
@@ -18,7 +18,7 @@ use tracing::{debug, instrument};
 
 use super::{
     InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Provenance, Scalar, err_ub,
-    throw_ub, throw_unsup,
+    interp_ok, throw_ub, throw_unsup,
 };
 
 /// Describes the constraints placed on offset-projections.
@@ -54,7 +54,7 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
             // Go through the layout. There are lots of types that support a length,
             // e.g., SIMD types. (But not all repr(simd) types even have FieldsShape::Array!)
             match layout.fields {
-                abi::FieldsShape::Array { count, .. } => Ok(count),
+                abi::FieldsShape::Array { count, .. } => interp_ok(count),
                 _ => bug!("len not supported on sized type {:?}", layout.ty),
             }
         }
@@ -115,9 +115,9 @@ impl<'a, 'tcx, Prov: Provenance, P: Projectable<'tcx, Prov>> ArrayIterator<'a, '
         &mut self,
         ecx: &InterpCx<'tcx, M>,
     ) -> InterpResult<'tcx, Option<(u64, P)>> {
-        let Some(idx) = self.range.next() else { return Ok(None) };
+        let Some(idx) = self.range.next() else { return interp_ok(None) };
         // We use `Wrapping` here since the offset has already been checked when the iterator was created.
-        Ok(Some((
+        interp_ok(Some((
             idx,
             self.base.offset_with_meta(
                 self.stride * idx,
@@ -258,7 +258,7 @@ where
         // SIMD types must be newtypes around arrays, so all we have to do is project to their only field.
         let array = self.project_field(base, 0)?;
         let len = array.len(self)?;
-        Ok((array, len))
+        interp_ok((array, len))
     }
 
     fn project_constant_index<P: Projectable<'tcx, M::Provenance>>(
@@ -300,7 +300,13 @@ where
         debug!("project_array_fields: {base:?} {len}");
         base.offset(len * stride, self.layout_of(self.tcx.types.unit).unwrap(), self)?;
         // Create the iterator.
-        Ok(ArrayIterator { base, range: 0..len, stride, field_layout, _phantom: PhantomData })
+        interp_ok(ArrayIterator {
+            base,
+            range: 0..len,
+            stride,
+            field_layout,
+            _phantom: PhantomData,
+        })
     }
 
     /// Subslicing
@@ -367,7 +373,7 @@ where
         P: Projectable<'tcx, M::Provenance> + From<MPlaceTy<'tcx, M::Provenance>> + std::fmt::Debug,
     {
         use rustc_middle::mir::ProjectionElem::*;
-        Ok(match proj_elem {
+        interp_ok(match proj_elem {
             OpaqueCast(ty) => {
                 span_bug!(self.cur_span(), "OpaqueCast({ty}) encountered after borrowck")
             }
diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs
index 15868f1b02d..3bc9f46aea0 100644
--- a/compiler/rustc_const_eval/src/interpret/stack.rs
+++ b/compiler/rustc_const_eval/src/interpret/stack.rs
@@ -17,7 +17,7 @@ use tracing::{info_span, instrument, trace};
 use super::{
     AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace,
     MemPlaceMeta, MemoryKind, Operand, Pointer, Provenance, ReturnAction, Scalar,
-    from_known_layout, throw_ub, throw_unsup,
+    from_known_layout, interp_ok, throw_ub, throw_unsup,
 };
 use crate::errors;
 
@@ -189,7 +189,7 @@ impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> {
     pub(super) fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
         match &self.value {
             LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
-            LocalValue::Live(val) => Ok(val),
+            LocalValue::Live(val) => interp_ok(val),
         }
     }
 
@@ -199,7 +199,7 @@ impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> {
     pub(super) fn access_mut(&mut self) -> InterpResult<'tcx, &mut Operand<Prov>> {
         match &mut self.value {
             LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
-            LocalValue::Live(val) => Ok(val),
+            LocalValue::Live(val) => interp_ok(val),
         }
     }
 }
@@ -391,7 +391,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let span = info_span!("frame", "{}", instance);
         self.frame_mut().tracing_span.enter(span);
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Low-level helper that pops a stack frame from the stack and returns some information about
@@ -426,7 +426,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             return_action = ReturnAction::NoCleanup;
         };
 
-        Ok(StackPopInfo { return_action, return_to_block, return_place })
+        interp_ok(StackPopInfo { return_action, return_to_block, return_place })
     }
 
     /// A private helper for [`pop_stack_frame_raw`](InterpCx::pop_stack_frame_raw).
@@ -449,7 +449,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             }
         }
 
-        Ok(cleanup)
+        interp_ok(cleanup)
     }
 
     /// In the current stack frame, mark all locals as live that are not arguments and don't have
@@ -464,7 +464,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 self.storage_live(local)?;
             }
         }
-        Ok(())
+        interp_ok(())
     }
 
     pub fn storage_live_dyn(
@@ -550,7 +550,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // If the local is already live, deallocate its old memory.
         let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
         self.deallocate_local(old)?;
-        Ok(())
+        interp_ok(())
     }
 
     /// Mark a storage as live, killing the previous content.
@@ -566,7 +566,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // If the local is already dead, this is a NOP.
         let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead);
         self.deallocate_local(old)?;
-        Ok(())
+        interp_ok(())
     }
 
     fn deallocate_local(&mut self, local: LocalValue<M::Provenance>) -> InterpResult<'tcx> {
@@ -581,7 +581,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             );
             self.deallocate_ptr(ptr, None, MemoryKind::Stack)?;
         };
-        Ok(())
+        interp_ok(())
     }
 
     #[inline(always)]
@@ -593,19 +593,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     ) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
         let state = &frame.locals[local];
         if let Some(layout) = state.layout.get() {
-            return Ok(layout);
+            return interp_ok(layout);
         }
 
         let layout = from_known_layout(self.tcx, self.param_env, layout, || {
             let local_ty = frame.body.local_decls[local].ty;
             let local_ty =
                 self.instantiate_from_frame_and_normalize_erasing_regions(frame, local_ty)?;
-            self.layout_of(local_ty)
+            self.layout_of(local_ty).into()
         })?;
 
         // Layouts of locals are requested a lot, so we cache them.
         state.layout.set(Some(layout));
-        Ok(layout)
+        interp_ok(layout)
     }
 }
 
diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs
index 8e2367e0d21..aa752955675 100644
--- a/compiler/rustc_const_eval/src/interpret/step.rs
+++ b/compiler/rustc_const_eval/src/interpret/step.rs
@@ -14,7 +14,7 @@ use tracing::{info, instrument, trace};
 
 use super::{
     FnArg, FnVal, ImmTy, Immediate, InterpCx, InterpResult, Machine, MemPlaceMeta, PlaceTy,
-    Projectable, Scalar, throw_ub,
+    Projectable, Scalar, interp_ok, throw_ub,
 };
 use crate::util;
 
@@ -36,7 +36,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     #[inline(always)]
     pub fn step(&mut self) -> InterpResult<'tcx, bool> {
         if self.stack().is_empty() {
-            return Ok(false);
+            return interp_ok(false);
         }
 
         let Either::Left(loc) = self.frame().loc else {
@@ -44,7 +44,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             // Just go on unwinding.
             trace!("unwinding: skipping frame");
             self.return_from_current_stack_frame(/* unwinding */ true)?;
-            return Ok(true);
+            return interp_ok(true);
         };
         let basic_block = &self.body().basic_blocks[loc.block];
 
@@ -55,7 +55,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             assert_eq!(old_frames, self.frame_idx());
             // Advance the program counter.
             self.frame_mut().loc.as_mut().left().unwrap().statement_index += 1;
-            return Ok(true);
+            return interp_ok(true);
         }
 
         M::before_terminator(self)?;
@@ -67,7 +67,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 info!("// executing {:?}", loc.block);
             }
         }
-        Ok(true)
+        interp_ok(true)
     }
 
     /// Runs the interpretation logic for the given `mir::Statement` at the current frame and
@@ -145,7 +145,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             Nop => {}
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Evaluate an assignment statement.
@@ -277,7 +277,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
 
         trace!("{:?}", self.dump_place(&dest));
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Writes the aggregate to the destination.
@@ -313,7 +313,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 let ptr_imm = Immediate::new_pointer_with_meta(data, meta, self);
                 let ptr = ImmTy::from_immediate(ptr_imm, dest.layout);
                 self.copy_op(&ptr, dest)?;
-                return Ok(());
+                return interp_ok(());
             }
             _ => (FIRST_VARIANT, dest.clone(), None),
         };
@@ -365,7 +365,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             )?;
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Evaluate the arguments of a function call
@@ -373,7 +373,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         &self,
         op: &mir::Operand<'tcx>,
     ) -> InterpResult<'tcx, FnArg<'tcx, M::Provenance>> {
-        Ok(match op {
+        interp_ok(match op {
             mir::Operand::Copy(_) | mir::Operand::Constant(_) => {
                 // Make a regular copy.
                 let op = self.eval_operand(op, None)?;
@@ -442,7 +442,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             }
         };
 
-        Ok(EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location })
+        interp_ok(EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location })
     }
 
     fn eval_terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> InterpResult<'tcx> {
@@ -537,7 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                     // generic. In order to make sure that generic and non-generic code behaves
                     // roughly the same (and in keeping with Mir semantics) we do nothing here.
                     self.go_to_block(target);
-                    return Ok(());
+                    return interp_ok(());
                 }
                 trace!("TerminatorKind::drop: {:?}, type {}", place, place.layout.ty);
                 self.init_drop_in_place_call(&place, instance, target, unwind)?;
@@ -566,7 +566,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 // By definition, a Resume terminator means
                 // that we're unwinding
                 self.return_from_current_stack_frame(/* unwinding */ true)?;
-                return Ok(());
+                return interp_ok(());
             }
 
             // It is UB to ever encounter this.
@@ -584,6 +584,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             }
         }
 
-        Ok(())
+        interp_ok(())
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/traits.rs b/compiler/rustc_const_eval/src/interpret/traits.rs
index 8eead6018ac..da7d6853c0e 100644
--- a/compiler/rustc_const_eval/src/interpret/traits.rs
+++ b/compiler/rustc_const_eval/src/interpret/traits.rs
@@ -5,7 +5,9 @@ use rustc_target::abi::{Align, Size};
 use tracing::trace;
 
 use super::util::ensure_monomorphic_enough;
-use super::{InterpCx, MPlaceTy, Machine, MemPlaceMeta, OffsetMode, Projectable, throw_ub};
+use super::{
+    InterpCx, MPlaceTy, Machine, MemPlaceMeta, OffsetMode, Projectable, interp_ok, throw_ub,
+};
 
 impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
     /// Creates a dynamic vtable for the given type and vtable origin. This is used only for
@@ -31,7 +33,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let salt = M::get_global_alloc_salt(self, None);
         let vtable_symbolic_allocation = self.tcx.reserve_and_set_vtable_alloc(ty, dyn_ty, salt);
         let vtable_ptr = self.global_root_pointer(Pointer::from(vtable_symbolic_allocation))?;
-        Ok(vtable_ptr.into())
+        interp_ok(vtable_ptr.into())
     }
 
     pub fn get_vtable_size_and_align(
@@ -42,7 +44,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let ty = self.get_ptr_vtable_ty(vtable, expected_trait)?;
         let layout = self.layout_of(ty)?;
         assert!(layout.is_sized(), "there are no vtables for unsized types");
-        Ok((layout.size, layout.align.abi))
+        interp_ok((layout.size, layout.align.abi))
     }
 
     pub(super) fn vtable_entries(
@@ -102,7 +104,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             }
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Turn a place with a `dyn Trait` type into a place with the actual dynamic type.
@@ -127,7 +129,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             layout,
             self,
         )?;
-        Ok(mplace)
+        interp_ok(mplace)
     }
 
     /// Turn a `dyn* Trait` type into an value with the actual dynamic type.
@@ -147,6 +149,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // `data` is already the right thing but has the wrong type. So we transmute it.
         let layout = self.layout_of(ty)?;
         let data = data.transmute(layout, self)?;
-        Ok(data)
+        interp_ok(data)
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs
index 2d53badf0f9..8bb5f173a56 100644
--- a/compiler/rustc_const_eval/src/interpret/util.rs
+++ b/compiler/rustc_const_eval/src/interpret/util.rs
@@ -9,7 +9,7 @@ use rustc_middle::ty::{
 };
 use tracing::debug;
 
-use super::{InterpCx, MPlaceTy, MemoryKind, throw_inval};
+use super::{InterpCx, MPlaceTy, MemoryKind, interp_ok, throw_inval};
 use crate::const_eval::{CompileTimeInterpCx, CompileTimeMachine, InterpretationResult};
 
 /// Checks whether a type contains generic parameters which must be instantiated.
@@ -23,7 +23,7 @@ where
 {
     debug!("ensure_monomorphic_enough: ty={:?}", ty);
     if !ty.has_param() {
-        return Ok(());
+        return interp_ok(());
     }
 
     struct FoundParam;
@@ -78,7 +78,7 @@ where
     if matches!(ty.visit_with(&mut vis), ControlFlow::Break(FoundParam)) {
         throw_inval!(TooGeneric);
     } else {
-        Ok(())
+        interp_ok(())
     }
 }
 
@@ -103,5 +103,5 @@ pub(crate) fn create_static_alloc<'tcx>(
     assert_eq!(ecx.machine.static_root_ids, None);
     ecx.machine.static_root_ids = Some((alloc_id, static_def_id));
     assert!(ecx.memory.alloc_map.insert(alloc_id, (MemoryKind::Stack, alloc)).is_none());
-    Ok(ecx.ptr_to_mplace(Pointer::from(alloc_id).into(), layout))
+    interp_ok(ecx.ptr_to_mplace(Pointer::from(alloc_id).into(), layout))
 }
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 3c9fdd73100..13641ef2bd3 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -18,7 +18,7 @@ use rustc_middle::bug;
 use rustc_middle::mir::interpret::ValidationErrorKind::{self, *};
 use rustc_middle::mir::interpret::{
     ExpectedKind, InterpError, InterpErrorInfo, InvalidMetaKind, Misalignment, PointerKind,
-    Provenance, UnsupportedOpInfo, ValidationErrorInfo, alloc_range,
+    Provenance, UnsupportedOpInfo, ValidationErrorInfo, alloc_range, interp_ok,
 };
 use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
 use rustc_middle::ty::{self, Ty};
@@ -32,7 +32,7 @@ use super::machine::AllocMap;
 use super::{
     AllocId, AllocKind, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult,
     MPlaceTy, Machine, MemPlaceMeta, PlaceTy, Pointer, Projectable, Scalar, ValueVisitor, err_ub,
-    format_interp_error, throw_ub,
+    format_interp_error,
 };
 
 // for the validation errors
@@ -42,7 +42,7 @@ use super::InterpError::Unsupported as Unsup;
 use super::UndefinedBehaviorInfo::*;
 use super::UnsupportedOpInfo::*;
 
-macro_rules! throw_validation_failure {
+macro_rules! err_validation_failure {
     ($where:expr, $kind: expr) => {{
         let where_ = &$where;
         let path = if !where_.is_empty() {
@@ -53,10 +53,16 @@ macro_rules! throw_validation_failure {
             None
         };
 
-        throw_ub!(ValidationError(ValidationErrorInfo { path, kind: $kind }))
+        err_ub!(ValidationError(ValidationErrorInfo { path, kind: $kind }))
     }};
 }
 
+macro_rules! throw_validation_failure {
+    ($where:expr, $kind: expr) => {
+        do yeet err_validation_failure!($where, $kind)
+    };
+}
+
 /// If $e throws an error matching the pattern, throw a validation failure.
 /// Other errors are passed back to the caller, unchanged -- and if they reach the root of
 /// the visitor, we make sure only validation errors and `InvalidProgram` errors are left.
@@ -91,25 +97,22 @@ macro_rules! try_validation {
     ($e:expr, $where:expr,
     $( $( $p:pat_param )|+ => $kind: expr ),+ $(,)?
     ) => {{
-        match $e {
-            Ok(x) => x,
+        $e.map_err(|e| {
             // 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) => {
-                let (kind, backtrace) = e.into_parts();
-                match kind {
-                    $(
-                        $($p)|+ => {
-                            throw_validation_failure!(
-                                $where,
-                                $kind
-                            )
-                        }
-                    ),+,
-                    _ => Err::<!, _>(InterpErrorInfo::from_parts(kind, backtrace))?,
-                }
+            let (kind, backtrace) = e.into_parts();
+            match kind {
+                $(
+                    $($p)|+ => {
+                        err_validation_failure!(
+                            $where,
+                            $kind
+                        ).into()
+                    }
+                ),+,
+                _ => InterpErrorInfo::from_parts(kind, backtrace),
             }
-        }
+        })?
     }};
 }
 
@@ -381,7 +384,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
         // Undo changes
         self.path.truncate(path_len);
         // Done
-        Ok(r)
+        interp_ok(r)
     }
 
     fn read_immediate(
@@ -389,7 +392,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
         val: &PlaceTy<'tcx, M::Provenance>,
         expected: ExpectedKind,
     ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
-        Ok(try_validation!(
+        interp_ok(try_validation!(
             self.ecx.read_immediate(val),
             self.path,
             Ub(InvalidUninitBytes(None)) =>
@@ -407,7 +410,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
         val: &PlaceTy<'tcx, M::Provenance>,
         expected: ExpectedKind,
     ) -> InterpResult<'tcx, Scalar<M::Provenance>> {
-        Ok(self.read_immediate(val, expected)?.to_scalar())
+        interp_ok(self.read_immediate(val, expected)?.to_scalar())
     }
 
     fn deref_pointer(
@@ -472,7 +475,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
             _ => bug!("Unexpected unsized type tail: {:?}", tail),
         }
 
-        Ok(())
+        interp_ok(())
     }
 
     /// Check a reference or `Box`.
@@ -635,7 +638,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                 }
                 // Potentially skip recursive check.
                 if skip_recursive_check {
-                    return Ok(());
+                    return interp_ok(());
                 }
             } else {
                 // This is not CTFE, so it's Miri with recursive checking.
@@ -644,7 +647,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                 // FIXME: should we also skip `UnsafeCell` behind shared references? Currently that is not
                 // needed since validation reads bypass Stacked Borrows and data race checks.
                 if matches!(ptr_kind, PointerKind::Box) {
-                    return Ok(());
+                    return interp_ok(());
                 }
             }
             let path = &self.path;
@@ -657,7 +660,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                 new_path
             });
         }
-        Ok(())
+        interp_ok(())
     }
 
     /// Check if this is a value of primitive type, and if yes check the validity of the value
@@ -684,7 +687,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                     self.ecx.clear_provenance(value)?;
                     self.add_data_range_place(value);
                 }
-                Ok(true)
+                interp_ok(true)
             }
             ty::Char => {
                 let scalar = self.read_scalar(value, ExpectedKind::Char)?;
@@ -699,7 +702,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                     self.ecx.clear_provenance(value)?;
                     self.add_data_range_place(value);
                 }
-                Ok(true)
+                interp_ok(true)
             }
             ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
                 // NOTE: Keep this in sync with the array optimization for int/float
@@ -716,18 +719,18 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                     self.ecx.clear_provenance(value)?;
                     self.add_data_range_place(value);
                 }
-                Ok(true)
+                interp_ok(true)
             }
             ty::RawPtr(..) => {
                 let place = self.deref_pointer(value, ExpectedKind::RawPtr)?;
                 if place.layout.is_unsized() {
                     self.check_wide_ptr_meta(place.meta(), place.layout)?;
                 }
-                Ok(true)
+                interp_ok(true)
             }
             ty::Ref(_, _ty, mutbl) => {
                 self.check_safe_pointer(value, PointerKind::Ref(*mutbl))?;
-                Ok(true)
+                interp_ok(true)
             }
             ty::FnPtr(..) => {
                 let scalar = self.read_scalar(value, ExpectedKind::FnPtr)?;
@@ -756,12 +759,12 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                     }
                     self.add_data_range_place(value);
                 }
-                Ok(true)
+                interp_ok(true)
             }
             ty::Never => throw_validation_failure!(self.path, NeverVal),
             ty::Foreign(..) | ty::FnDef(..) => {
                 // Nothing to check.
-                Ok(true)
+                interp_ok(true)
             }
             // The above should be all the primitive types. The rest is compound, we
             // check them by visiting their fields/variants.
@@ -774,7 +777,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
             | ty::Closure(..)
             | ty::Pat(..)
             | ty::CoroutineClosure(..)
-            | ty::Coroutine(..) => Ok(false),
+            | ty::Coroutine(..) => interp_ok(false),
             // Some types only occur during typechecking, they have no layout.
             // We should not see them here and we could not check them anyway.
             ty::Error(_)
@@ -811,11 +814,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
                             max_value
                         })
                     } else {
-                        return Ok(());
+                        return interp_ok(());
                     }
                 } else if scalar_layout.is_always_valid(self.ecx) {
                     // Easy. (This is reachable if `enforce_number_validity` is set.)
-                    return Ok(());
+                    return interp_ok(());
                 } else {
                     // Conservatively, we reject, because the pointer *could* have a bad
                     // value.
@@ -828,7 +831,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
         };
         // Now compare.
         if valid_range.contains(bits) {
-            Ok(())
+            interp_ok(())
         } else {
             throw_validation_failure!(self.path, OutOfRange {
                 value: format!("{bits}"),
@@ -887,7 +890,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
     }
 
     fn reset_padding(&mut self, place: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
-        let Some(data_bytes) = self.data_bytes.as_mut() else { return Ok(()) };
+        let Some(data_bytes) = self.data_bytes.as_mut() else { return interp_ok(()) };
         // Our value must be in memory, otherwise we would not have set up `data_bytes`.
         let mplace = self.ecx.force_allocation(place)?;
         // Determine starting offset and size.
@@ -899,14 +902,14 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
         // If there is no padding at all, we can skip the rest: check for
         // a single data range covering the entire value.
         if data_bytes.0 == &[(start_offset, size)] {
-            return Ok(());
+            return interp_ok(());
         }
         // Get a handle for the allocation. Do this only once, to avoid looking up the same
         // allocation over and over again. (Though to be fair, iterating the value already does
         // exactly that.)
         let Some(mut alloc) = self.ecx.get_ptr_alloc_mut(mplace.ptr(), size)? else {
             // A ZST, no padding to clear.
-            return Ok(());
+            return interp_ok(());
         };
         // Add a "finalizer" data range at the end, so that the iteration below finds all gaps
         // between ranges.
@@ -933,7 +936,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
             padding_cleared_until = offset + size;
         }
         assert!(padding_cleared_until == start_offset + size);
-        Ok(())
+        interp_ok(())
     }
 
     /// Computes the data range of this union type:
@@ -1073,7 +1076,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
         val: &PlaceTy<'tcx, M::Provenance>,
     ) -> InterpResult<'tcx, VariantIdx> {
         self.with_elem(PathElem::EnumTag, move |this| {
-            Ok(try_validation!(
+            interp_ok(try_validation!(
                 this.ecx.read_discriminant(val),
                 this.path,
                 Ub(InvalidTag(val)) => InvalidEnumTag {
@@ -1137,7 +1140,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
                 data_bytes.add_range(base_offset + offset, size);
             }
         }
-        Ok(())
+        interp_ok(())
     }
 
     #[inline]
@@ -1147,7 +1150,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
         val: &PlaceTy<'tcx, M::Provenance>,
     ) -> InterpResult<'tcx> {
         self.check_safe_pointer(val, PointerKind::Box)?;
-        Ok(())
+        interp_ok(())
     }
 
     #[inline]
@@ -1160,7 +1163,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
         // We assume that the Scalar validity range does not restrict these values
         // any further than `try_visit_primitive` does!
         if self.try_visit_primitive(val)? {
-            return Ok(());
+            return interp_ok(());
         }
 
         // Special check preventing `UnsafeCell` in the inner part of constants
@@ -1207,7 +1210,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
                 // If the size is 0, there is nothing to check.
                 // (`size` can only be 0 if `len` is 0, and empty arrays are always valid.)
                 if size == Size::ZERO {
-                    return Ok(());
+                    return interp_ok(());
                 }
                 // Now that we definitely have a non-ZST array, we know it lives in memory -- except it may
                 // be an uninitialized local variable, those are also "immediate".
@@ -1227,37 +1230,33 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
                 // No need for an alignment check here, this is not an actual memory access.
                 let alloc = self.ecx.get_ptr_alloc(mplace.ptr(), size)?.expect("we already excluded size 0");
 
-                match alloc.get_bytes_strip_provenance() {
-                    // In the happy case, we needn't check anything else.
-                    Ok(_) => {}
+                alloc.get_bytes_strip_provenance().map_err(|err| {
                     // Some error happened, try to provide a more detailed description.
-                    Err(err) => {
-                        // For some errors we might be able to provide extra information.
-                        // (This custom logic does not fit the `try_validation!` macro.)
-                        let (kind, backtrace) = err.into_parts();
-                        match kind {
-                            Ub(InvalidUninitBytes(Some((_alloc_id, access)))) | Unsup(ReadPointerAsInt(Some((_alloc_id, access)))) => {
-                                // Some byte was uninitialized, determine which
-                                // element that byte belongs to so we can
-                                // provide an index.
-                                let i = usize::try_from(
-                                    access.bad.start.bytes() / layout.size.bytes(),
-                                )
-                                .unwrap();
-                                self.path.push(PathElem::ArrayElem(i));
-
-                                if matches!(kind, Ub(InvalidUninitBytes(_))) {
-                                    throw_validation_failure!(self.path, Uninit { expected })
-                                } else {
-                                    throw_validation_failure!(self.path, PointerAsInt { expected })
-                                }
-                            }
+                    // For some errors we might be able to provide extra information.
+                    // (This custom logic does not fit the `try_validation!` macro.)
+                    let (kind, backtrace) = err.into_parts();
+                    match kind {
+                        Ub(InvalidUninitBytes(Some((_alloc_id, access)))) | Unsup(ReadPointerAsInt(Some((_alloc_id, access)))) => {
+                            // Some byte was uninitialized, determine which
+                            // element that byte belongs to so we can
+                            // provide an index.
+                            let i = usize::try_from(
+                                access.bad.start.bytes() / layout.size.bytes(),
+                            )
+                            .unwrap();
+                            self.path.push(PathElem::ArrayElem(i));
 
-                            // Propagate upwards (that will also check for unexpected errors).
-                            _ => return Err(InterpErrorInfo::from_parts(kind, backtrace)),
+                            if matches!(kind, Ub(InvalidUninitBytes(_))) {
+                                err_validation_failure!(self.path, Uninit { expected }).into()
+                            } else {
+                                err_validation_failure!(self.path, PointerAsInt { expected }).into()
+                            }
                         }
+
+                        // Propagate upwards (that will also check for unexpected errors).
+                        _ => return InterpErrorInfo::from_parts(kind, backtrace),
                     }
-                }
+                })?;
 
                 // Don't forget that these are all non-pointer types, and thus do not preserve
                 // provenance.
@@ -1335,7 +1334,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
             }
         }
 
-        Ok(())
+        interp_ok(())
     }
 }
 
@@ -1351,7 +1350,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty);
 
         // Run the visitor.
-        match self.run_for_validation(|ecx| {
+        self.run_for_validation(|ecx| {
             let reset_padding = reset_provenance_and_padding && {
                 // Check if `val` is actually stored in memory. If not, padding is not even
                 // represented and we need not reset it.
@@ -1367,29 +1366,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
             };
             v.visit_value(val)?;
             v.reset_padding(val)?;
-            InterpResult::Ok(())
-        }) {
-            Ok(()) => Ok(()),
-            // Pass through validation failures and "invalid program" issues.
-            Err(err)
-                if matches!(
-                    err.kind(),
-                    err_ub!(ValidationError { .. })
-                        | InterpError::InvalidProgram(_)
-                        | InterpError::Unsupported(UnsupportedOpInfo::ExternTypeField)
-                ) =>
-            {
-                Err(err)
-            }
-            // Complain about any other kind of 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) => {
+            interp_ok(())
+        })
+        .map_err(|err| {
+            if !matches!(
+                err.kind(),
+                err_ub!(ValidationError { .. })
+                    | InterpError::InvalidProgram(_)
+                    | InterpError::Unsupported(UnsupportedOpInfo::ExternTypeField)
+            ) {
                 bug!(
                     "Unexpected error during validation: {}",
                     format_interp_error(self.tcx.dcx(), err)
                 );
             }
-        }
+            err
+        })
     }
 
     /// This function checks the data at `op` to be const-valid.
@@ -1460,6 +1452,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
                 /*reset_provenance_and_padding*/ false,
             )?;
         }
-        Ok(())
+        interp_ok(())
     }
 }
diff --git a/compiler/rustc_const_eval/src/interpret/visitor.rs b/compiler/rustc_const_eval/src/interpret/visitor.rs
index d004a3f0892..647917dbb67 100644
--- a/compiler/rustc_const_eval/src/interpret/visitor.rs
+++ b/compiler/rustc_const_eval/src/interpret/visitor.rs
@@ -10,7 +10,7 @@ use rustc_middle::ty::{self, Ty};
 use rustc_target::abi::{FieldIdx, FieldsShape, VariantIdx, Variants};
 use tracing::trace;
 
-use super::{InterpCx, MPlaceTy, Machine, Projectable, throw_inval};
+use super::{InterpCx, MPlaceTy, Machine, Projectable, interp_ok, throw_inval};
 
 /// How to traverse a value and what to do when we are at the leaves.
 pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
@@ -46,14 +46,14 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
     /// Visits the given value as a union. No automatic recursion can happen here.
     #[inline(always)]
     fn visit_union(&mut self, _v: &Self::V, _fields: NonZero<usize>) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
     /// Visits the given value as the pointer of a `Box`. There is nothing to recurse into.
     /// The type of `v` will be a raw pointer to `T`, but this is a field of `Box<T>` and the
     /// pointee type is the actual `T`. `box_ty` provides the full type of the `Box` itself.
     #[inline(always)]
     fn visit_box(&mut self, _box_ty: Ty<'tcx>, _v: &Self::V) -> InterpResult<'tcx> {
-        Ok(())
+        interp_ok(())
     }
 
     /// Called each time we recurse down to a field of a "product-like" aggregate
@@ -165,7 +165,7 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
                 self.visit_field(v, 1, &alloc)?;
 
                 // We visited all parts of this one.
-                return Ok(());
+                return interp_ok(());
             }
 
             // Non-normalized types should never show up here.
@@ -222,6 +222,6 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
             Variants::Single { .. } => {}
         }
 
-        Ok(())
+        interp_ok(())
     }
 }
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 421648d9e7b..649179d5b1e 100644
--- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
+++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs
@@ -1,5 +1,4 @@
 use rustc_middle::bug;
-use rustc_middle::mir::interpret::DiscardInterpError;
 use rustc_middle::ty::layout::{
     HasTyCtxt, LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement,
 };
@@ -76,7 +75,7 @@ fn check_validity_requirement_strict<'tcx>(
             /*recursive*/ false,
             /*reset_provenance_and_padding*/ false,
         )
-        .discard_interp_err()
+        .discard_err()
         .is_some())
 }