about summary refs log tree commit diff
path: root/compiler/rustc_const_eval/src/const_eval
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_const_eval/src/const_eval')
-rw-r--r--compiler/rustc_const_eval/src/const_eval/error.rs280
-rw-r--r--compiler/rustc_const_eval/src/const_eval/eval_queries.rs100
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs119
-rw-r--r--compiler/rustc_const_eval/src/const_eval/mod.rs52
4 files changed, 295 insertions, 256 deletions
diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs
index c591ff75ab8..7890d878d08 100644
--- a/compiler/rustc_const_eval/src/const_eval/error.rs
+++ b/compiler/rustc_const_eval/src/const_eval/error.rs
@@ -1,17 +1,15 @@
-use std::error::Error;
-use std::fmt;
+use std::mem;
 
-use rustc_errors::Diagnostic;
+use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, IntoDiagnostic, IntoDiagnosticArg};
 use rustc_middle::mir::AssertKind;
-use rustc_middle::query::TyCtxtAt;
+use rustc_middle::ty::TyCtxt;
 use rustc_middle::ty::{layout::LayoutError, ConstInt};
-use rustc_span::{Span, Symbol};
+use rustc_span::source_map::Spanned;
+use rustc_span::{ErrorGuaranteed, Span, Symbol};
 
 use super::InterpCx;
-use crate::interpret::{
-    struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
-    UnsupportedOpInfo,
-};
+use crate::errors::{self, FrameNote, ReportErrorExt};
+use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, Machine, MachineStopType};
 
 /// The CTFE machine has some custom error kinds.
 #[derive(Clone, Debug)]
@@ -23,7 +21,35 @@ pub enum ConstEvalErrKind {
     Abort(String),
 }
 
-impl MachineStopType for ConstEvalErrKind {}
+impl MachineStopType for ConstEvalErrKind {
+    fn diagnostic_message(&self) -> DiagnosticMessage {
+        use crate::fluent_generated::*;
+        use ConstEvalErrKind::*;
+        match self {
+            ConstAccessesStatic => const_eval_const_accesses_static,
+            ModifiedGlobal => const_eval_modified_global,
+            Panic { .. } => const_eval_panic,
+            AssertFailure(x) => x.diagnostic_message(),
+            Abort(msg) => msg.to_string().into(),
+        }
+    }
+    fn add_args(
+        self: Box<Self>,
+        adder: &mut dyn FnMut(std::borrow::Cow<'static, str>, DiagnosticArgValue<'static>),
+    ) {
+        use ConstEvalErrKind::*;
+        match *self {
+            ConstAccessesStatic | ModifiedGlobal | Abort(_) => {}
+            AssertFailure(kind) => kind.add_args(adder),
+            Panic { msg, line, col, file } => {
+                adder("msg".into(), msg.into_diagnostic_arg());
+                adder("file".into(), file.into_diagnostic_arg());
+                adder("line".into(), line.into_diagnostic_arg());
+                adder("col".into(), col.into_diagnostic_arg());
+            }
+        }
+    }
+}
 
 // The errors become `MachineStop` with plain strings when being raised.
 // `ConstEvalErr` (in `librustc_middle/mir/interpret/error.rs`) knows to
@@ -34,151 +60,117 @@ impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
     }
 }
 
-impl fmt::Display for ConstEvalErrKind {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        use self::ConstEvalErrKind::*;
-        match self {
-            ConstAccessesStatic => write!(f, "constant accesses static"),
-            ModifiedGlobal => {
-                write!(f, "modifying a static's initial value from another static's initializer")
-            }
-            AssertFailure(msg) => write!(f, "{:?}", msg),
-            Panic { msg, line, col, file } => {
-                write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col)
-            }
-            Abort(msg) => write!(f, "{}", msg),
-        }
-    }
-}
-
-impl Error for ConstEvalErrKind {}
+pub fn get_span_and_frames<'tcx, 'mir, M: Machine<'mir, 'tcx>>(
+    ecx: &InterpCx<'mir, 'tcx, M>,
+) -> (Span, Vec<errors::FrameNote>)
+where
+    'tcx: 'mir,
+{
+    let mut stacktrace = ecx.generate_stacktrace();
+    // Filter out `requires_caller_location` frames.
+    stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
+    let span = stacktrace.first().map(|f| f.span).unwrap_or(ecx.tcx.span);
 
-/// When const-evaluation errors, this type is constructed with the resulting information,
-/// and then used to emit the error as a lint or hard error.
-#[derive(Debug)]
-pub(super) struct ConstEvalErr<'tcx> {
-    pub span: Span,
-    pub error: InterpError<'tcx>,
-    pub stacktrace: Vec<FrameInfo<'tcx>>,
-}
+    let mut frames = Vec::new();
 
-impl<'tcx> ConstEvalErr<'tcx> {
-    /// Turn an interpreter error into something to report to the user.
-    /// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace.
-    /// Should be called only if the error is actually going to be reported!
-    pub fn new<'mir, M: Machine<'mir, 'tcx>>(
-        ecx: &InterpCx<'mir, 'tcx, M>,
-        error: InterpErrorInfo<'tcx>,
-        span: Option<Span>,
-    ) -> ConstEvalErr<'tcx>
-    where
-        'tcx: 'mir,
-    {
-        error.print_backtrace();
-        let mut stacktrace = ecx.generate_stacktrace();
-        // Filter out `requires_caller_location` frames.
-        stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
-        // If `span` is missing, use topmost remaining frame, or else the "root" span from `ecx.tcx`.
-        let span = span.or_else(|| stacktrace.first().map(|f| f.span)).unwrap_or(ecx.tcx.span);
-        ConstEvalErr { error: error.into_kind(), stacktrace, span }
-    }
-
-    pub(super) fn report(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled {
-        self.report_decorated(tcx, message, |_| {})
-    }
-
-    #[instrument(level = "trace", skip(self, decorate))]
-    pub(super) fn decorate(&self, err: &mut Diagnostic, decorate: impl FnOnce(&mut Diagnostic)) {
-        trace!("reporting const eval failure at {:?}", self.span);
-        // Add some more context for select error types.
-        match self.error {
-            InterpError::Unsupported(
-                UnsupportedOpInfo::ReadPointerAsBytes
-                | UnsupportedOpInfo::PartialPointerOverwrite(_)
-                | UnsupportedOpInfo::PartialPointerCopy(_),
-            ) => {
-                err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
-                err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
+    // Add notes to the backtrace. Don't print a single-line backtrace though.
+    if stacktrace.len() > 1 {
+        // Helper closure to print duplicated lines.
+        let mut add_frame = |mut frame: errors::FrameNote| {
+            frames.push(errors::FrameNote { times: 0, ..frame.clone() });
+            // Don't print [... additional calls ...] if the number of lines is small
+            if frame.times < 3 {
+                let times = frame.times;
+                frame.times = 0;
+                frames.extend(std::iter::repeat(frame).take(times as usize));
+            } else {
+                frames.push(frame);
             }
-            _ => {}
-        }
-        // Add spans for the stacktrace. Don't print a single-line backtrace though.
-        if self.stacktrace.len() > 1 {
-            // Helper closure to print duplicated lines.
-            let mut flush_last_line = |last_frame: Option<(String, _)>, times| {
-                if let Some((line, span)) = last_frame {
-                    err.span_note(span, line.clone());
-                    // Don't print [... additional calls ...] if the number of lines is small
-                    if times < 3 {
-                        for _ in 0..times {
-                            err.span_note(span, line.clone());
-                        }
-                    } else {
-                        err.span_note(
-                            span,
-                            format!("[... {} additional calls {} ...]", times, &line),
-                        );
-                    }
-                }
-            };
+        };
 
-            let mut last_frame = None;
-            let mut times = 0;
-            for frame_info in &self.stacktrace {
-                let frame = (frame_info.to_string(), frame_info.span);
-                if last_frame.as_ref() == Some(&frame) {
-                    times += 1;
-                } else {
-                    flush_last_line(last_frame, times);
+        let mut last_frame: Option<errors::FrameNote> = None;
+        for frame_info in &stacktrace {
+            let frame = frame_info.as_note(*ecx.tcx);
+            match last_frame.as_mut() {
+                Some(last_frame)
+                    if last_frame.span == frame.span
+                        && last_frame.where_ == frame.where_
+                        && last_frame.instance == frame.instance =>
+                {
+                    last_frame.times += 1;
+                }
+                Some(last_frame) => {
+                    add_frame(mem::replace(last_frame, frame));
+                }
+                None => {
                     last_frame = Some(frame);
-                    times = 0;
                 }
             }
-            flush_last_line(last_frame, times);
         }
-        // Let the caller attach any additional information it wants.
-        decorate(err);
+        if let Some(frame) = last_frame {
+            add_frame(frame);
+        }
     }
 
-    /// Create a diagnostic for this const eval error.
-    ///
-    /// Sets the message passed in via `message` and adds span labels with detailed error
-    /// information before handing control back to `decorate` to do any final annotations,
-    /// after which the diagnostic is emitted.
-    ///
-    /// If `lint_root.is_some()` report it as a lint, else report it as a hard error.
-    /// (Except that for some errors, we ignore all that -- see `must_error` below.)
-    #[instrument(skip(self, tcx, decorate), level = "debug")]
-    pub(super) fn report_decorated(
-        &self,
-        tcx: TyCtxtAt<'tcx>,
-        message: &str,
-        decorate: impl FnOnce(&mut Diagnostic),
-    ) -> ErrorHandled {
-        debug!("self.error: {:?}", self.error);
-        // Special handling for certain errors
-        match &self.error {
-            // Don't emit a new diagnostic for these errors
-            err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
-                ErrorHandled::TooGeneric
-            }
-            err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(*error_reported),
-            err_inval!(Layout(LayoutError::SizeOverflow(_))) => {
-                // We must *always* hard error on these, even if the caller wants just a lint.
-                // The `message` makes little sense here, this is a more serious error than the
-                // caller thinks anyway.
-                // See <https://github.com/rust-lang/rust/pull/63152>.
-                let mut err = struct_error(tcx, &self.error.to_string());
-                self.decorate(&mut err, decorate);
-                ErrorHandled::Reported(err.emit().into())
-            }
-            _ => {
-                // Report as hard error.
-                let mut err = struct_error(tcx, message);
-                err.span_label(self.span, self.error.to_string());
-                self.decorate(&mut err, decorate);
-                ErrorHandled::Reported(err.emit().into())
+    (span, frames)
+}
+
+/// Create a diagnostic for a const eval error.
+///
+/// This will use the `mk` function for creating the error which will get passed labels according to
+/// the `InterpError` and the span and a stacktrace of current execution according to
+/// `get_span_and_frames`.
+pub(super) fn report<'tcx, C, F, E>(
+    tcx: TyCtxt<'tcx>,
+    error: InterpError<'tcx>,
+    span: Option<Span>,
+    get_span_and_frames: C,
+    mk: F,
+) -> ErrorHandled
+where
+    C: FnOnce() -> (Span, Vec<FrameNote>),
+    F: FnOnce(Span, Vec<FrameNote>) -> E,
+    E: IntoDiagnostic<'tcx, ErrorGuaranteed>,
+{
+    // Special handling for certain errors
+    match error {
+        // Don't emit a new diagnostic for these errors
+        err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
+            ErrorHandled::TooGeneric
+        }
+        err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(error_reported),
+        err_inval!(Layout(layout_error @ LayoutError::SizeOverflow(_))) => {
+            // We must *always* hard error on these, even if the caller wants just a lint.
+            // The `message` makes little sense here, this is a more serious error than the
+            // caller thinks anyway.
+            // See <https://github.com/rust-lang/rust/pull/63152>.
+            let (our_span, frames) = get_span_and_frames();
+            let span = span.unwrap_or(our_span);
+            let mut err =
+                tcx.sess.create_err(Spanned { span, node: layout_error.into_diagnostic() });
+            err.code(rustc_errors::error_code!(E0080));
+            let Some((mut err, handler)) = err.into_diagnostic() else {
+                    panic!("did not emit diag");
+                };
+            for frame in frames {
+                err.eager_subdiagnostic(handler, frame);
             }
+
+            ErrorHandled::Reported(handler.emit_diagnostic(&mut err).unwrap().into())
+        }
+        _ => {
+            // Report as hard error.
+            let (our_span, frames) = get_span_and_frames();
+            let span = span.unwrap_or(our_span);
+            let err = mk(span, frames);
+            let mut err = tcx.sess.create_err(err);
+
+            let msg = error.diagnostic_message();
+            error.add_args(&tcx.sess.parse_sess.span_diagnostic, &mut err);
+
+            // Use *our* span to label the interp error
+            err.span_label(our_span, msg);
+            ErrorHandled::Reported(err.emit().into())
         }
     }
 }
diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
index 046d2052968..8b8e8ff58e9 100644
--- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
+++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs
@@ -1,12 +1,12 @@
 use crate::const_eval::CheckAlignment;
-use std::borrow::Cow;
+use crate::errors::ConstEvalError;
 
 use either::{Left, Right};
 
 use rustc_hir::def::DefKind;
 use rustc_middle::mir;
-use rustc_middle::mir::interpret::ErrorHandled;
-use rustc_middle::mir::pretty::display_allocation;
+use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo};
+use rustc_middle::mir::pretty::write_allocation_bytes;
 use rustc_middle::traits::Reveal;
 use rustc_middle::ty::layout::LayoutOf;
 use rustc_middle::ty::print::with_no_trimmed_paths;
@@ -14,7 +14,8 @@ use rustc_middle::ty::{self, TyCtxt};
 use rustc_span::source_map::Span;
 use rustc_target::abi::{self, Abi};
 
-use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr};
+use super::{CompileTimeEvalContext, CompileTimeInterpreter};
+use crate::errors;
 use crate::interpret::eval_nullary_intrinsic;
 use crate::interpret::{
     intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
@@ -22,10 +23,6 @@ use crate::interpret::{
     RefTracking, StackPopCleanup,
 };
 
-const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \
-     so this check might be overzealous. Please open an issue on the rustc \
-     repository if you believe it should not be considered undefined behavior.";
-
 // Returns a pointer to where the result lives
 fn eval_body_using_ecx<'mir, 'tcx>(
     ecx: &mut CompileTimeEvalContext<'mir, 'tcx>,
@@ -103,7 +100,7 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>(
         tcx,
         root_span,
         param_env,
-        CompileTimeInterpreter::new(tcx.const_eval_limit(), can_access_statics, CheckAlignment::No),
+        CompileTimeInterpreter::new(can_access_statics, CheckAlignment::No),
     )
 }
 
@@ -253,8 +250,14 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
         };
         return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| {
             let span = tcx.def_span(def_id);
-            let error = ConstEvalErr { error: error.into_kind(), stacktrace: vec![], span };
-            error.report(tcx.at(span), "could not evaluate nullary intrinsic")
+
+            super::report(
+                tcx,
+                error.into_kind(),
+                Some(span),
+                || (span, vec![]),
+                |span, _| errors::NullaryIntrinsicError { span },
+            )
         });
     }
 
@@ -306,7 +309,6 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
         // Statics (and promoteds inside statics) may access other statics, because unlike consts
         // they do not have to behave "as if" they were evaluated at runtime.
         CompileTimeInterpreter::new(
-            tcx.const_eval_limit(),
             /*can_access_statics:*/ is_static,
             if tcx.sess.opts.unstable_opts.extra_const_ub_checks {
                 CheckAlignment::Error
@@ -319,9 +321,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
     let res = ecx.load_mir(cid.instance.def, cid.promoted);
     match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) {
         Err(error) => {
-            let err = ConstEvalErr::new(&ecx, error, None);
-            let msg = if is_static {
-                Cow::from("could not evaluate static initializer")
+            let (error, backtrace) = error.into_parts();
+            backtrace.print_backtrace();
+
+            let (kind, instance) = if is_static {
+                ("static", String::new())
             } else {
                 // If the current item has generics, we'd like to enrich the message with the
                 // instance and its substs: to show the actual compile-time values, in addition to
@@ -329,19 +333,29 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
                 let instance = &key.value.instance;
                 if !instance.substs.is_empty() {
                     let instance = with_no_trimmed_paths!(instance.to_string());
-                    let msg = format!("evaluation of `{}` failed", instance);
-                    Cow::from(msg)
+                    ("const_with_path", instance)
                 } else {
-                    Cow::from("evaluation of constant value failed")
+                    ("const", String::new())
                 }
             };
 
-            Err(err.report(ecx.tcx.at(err.span), &msg))
+            Err(super::report(
+                *ecx.tcx,
+                error,
+                None,
+                || super::get_span_and_frames(&ecx),
+                |span, frames| ConstEvalError {
+                    span,
+                    error_kind: kind,
+                    instance,
+                    frame_notes: frames,
+                },
+            ))
         }
         Ok(mplace) => {
             // Since evaluation had no errors, validate the resulting constant.
             // This is a separate `try` block to provide more targeted error reporting.
-            let validation = try {
+            let validation: Result<_, InterpErrorInfo<'_>> = try {
                 let mut ref_tracking = RefTracking::new(mplace);
                 let mut inner = false;
                 while let Some((mplace, path)) = ref_tracking.todo.pop() {
@@ -358,23 +372,37 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
                 }
             };
             let alloc_id = mplace.ptr.provenance.unwrap();
+
+            // Validation failed, report an error. This is always a hard error.
             if let Err(error) = validation {
-                // Validation failed, report an error. This is always a hard error.
-                let err = ConstEvalErr::new(&ecx, error, None);
-                Err(err.report_decorated(
-                    ecx.tcx,
-                    "it is undefined behavior to use this value",
-                    |diag| {
-                        if matches!(err.error, InterpError::UndefinedBehavior(_)) {
-                            diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
-                        }
-                        diag.note(format!(
-                            "the raw bytes of the constant ({}",
-                            display_allocation(
-                                *ecx.tcx,
-                                ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner()
-                            )
-                        ));
+                let (error, backtrace) = error.into_parts();
+                backtrace.print_backtrace();
+
+                let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
+
+                let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
+                let mut bytes = String::new();
+                if alloc.size() != abi::Size::ZERO {
+                    bytes = "\n".into();
+                    // FIXME(translation) there might be pieces that are translatable.
+                    write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, "    ").unwrap();
+                }
+                let raw_bytes = errors::RawBytesNote {
+                    size: alloc.size().bytes(),
+                    align: alloc.align.bytes(),
+                    bytes,
+                };
+
+                Err(super::report(
+                    *ecx.tcx,
+                    error,
+                    None,
+                    || super::get_span_and_frames(&ecx),
+                    move |span, frames| errors::UndefinedBehavior {
+                        span,
+                        ub_note,
+                        frames,
+                        raw_bytes,
                     },
                 ))
             } else {
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 58b5755af07..7391f567040 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -16,25 +16,37 @@ use std::fmt;
 use rustc_ast::Mutability;
 use rustc_hir::def_id::DefId;
 use rustc_middle::mir::AssertMessage;
-use rustc_session::Limit;
 use rustc_span::symbol::{sym, Symbol};
 use rustc_target::abi::{Align, Size};
 use rustc_target::spec::abi::Abi as CallAbi;
 
+use crate::errors::{LongRunning, LongRunningWarn};
 use crate::interpret::{
     self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
     InterpResult, OpTy, PlaceTy, Pointer, Scalar,
 };
+use crate::{errors, fluent_generated as fluent};
 
 use super::error::*;
 
+/// When hitting this many interpreted terminators we emit a deny by default lint
+/// that notfies the user that their constant takes a long time to evaluate. If that's
+/// what they intended, they can just allow the lint.
+const LINT_TERMINATOR_LIMIT: usize = 2_000_000;
+/// The limit used by `-Z tiny-const-eval-limit`. This smaller limit is useful for internal
+/// tests not needing to run 30s or more to show some behaviour.
+const TINY_LINT_TERMINATOR_LIMIT: usize = 20;
+/// After this many interpreted terminators, we start emitting progress indicators at every
+/// power of two of interpreted terminators.
+const PROGRESS_INDICATOR_START: usize = 4_000_000;
+
 /// Extra machine state for CTFE, and the Machine instance
 pub struct CompileTimeInterpreter<'mir, 'tcx> {
-    /// For now, the number of terminators that can be evaluated before we throw a resource
-    /// exhaustion error.
+    /// The number of terminators that have been evaluated.
     ///
-    /// Setting this to `0` disables the limit and allows the interpreter to run forever.
-    pub(super) steps_remaining: usize,
+    /// This is used to produce lints informing the user that the compiler is not stuck.
+    /// Set to `usize::MAX` to never report anything.
+    pub(super) num_evaluated_steps: usize,
 
     /// The virtual call stack.
     pub(super) stack: Vec<Frame<'mir, 'tcx, AllocId, ()>>,
@@ -72,13 +84,9 @@ impl CheckAlignment {
 }
 
 impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
-    pub(crate) fn new(
-        const_eval_limit: Limit,
-        can_access_statics: bool,
-        check_alignment: CheckAlignment,
-    ) -> Self {
+    pub(crate) fn new(can_access_statics: bool, check_alignment: CheckAlignment) -> Self {
         CompileTimeInterpreter {
-            steps_remaining: const_eval_limit.0,
+            num_evaluated_steps: 0,
             stack: Vec::new(),
             can_access_statics,
             check_alignment,
@@ -247,7 +255,10 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
         let target_align = self.read_scalar(&args[1])?.to_target_usize(self)?;
 
         if !target_align.is_power_of_two() {
-            throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
+            throw_ub_custom!(
+                fluent::const_eval_align_offset_invalid_align,
+                target_align = target_align,
+            );
         }
 
         match self.ptr_try_get_alloc_id(ptr) {
@@ -353,15 +364,18 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
                 "`alignment_check_failed` called when no alignment check requested"
             ),
             CheckAlignment::FutureIncompat => {
-                let err = ConstEvalErr::new(ecx, err, None);
-                ecx.tcx.struct_span_lint_hir(
+                let (_, backtrace) = err.into_parts();
+                backtrace.print_backtrace();
+                let (span, frames) = super::get_span_and_frames(&ecx);
+
+                ecx.tcx.emit_spanned_lint(
                     INVALID_ALIGNMENT,
                     ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
-                    err.span,
-                    err.error.to_string(),
-                    |db| {
-                        err.decorate(db, |_| {});
-                        db
+                    span,
+                    errors::AlignmentCheckFailed {
+                        has: has.bytes(),
+                        required: required.bytes(),
+                        frames,
                     },
                 );
                 Ok(())
@@ -475,7 +489,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
 
                 let align = match Align::from_bytes(align) {
                     Ok(a) => a,
-                    Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+                    Err(err) => throw_ub_custom!(
+                        fluent::const_eval_invalid_align_details,
+                        name = "const_allocate",
+                        err_kind = err.diag_ident(),
+                        align = err.align()
+                    ),
                 };
 
                 let ptr = ecx.allocate_ptr(
@@ -493,7 +512,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
                 let size = Size::from_bytes(size);
                 let align = match Align::from_bytes(align) {
                     Ok(a) => a,
-                    Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
+                    Err(err) => throw_ub_custom!(
+                        fluent::const_eval_invalid_align_details,
+                        name = "const_deallocate",
+                        err_kind = err.diag_ident(),
+                        align = err.align()
+                    ),
                 };
 
                 // If an allocation is created in an another const,
@@ -569,13 +593,54 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
 
     fn increment_const_eval_counter(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
         // The step limit has already been hit in a previous call to `increment_const_eval_counter`.
-        if ecx.machine.steps_remaining == 0 {
-            return Ok(());
-        }
 
-        ecx.machine.steps_remaining -= 1;
-        if ecx.machine.steps_remaining == 0 {
-            throw_exhaust!(StepLimitReached)
+        if let Some(new_steps) = ecx.machine.num_evaluated_steps.checked_add(1) {
+            let (limit, start) = if ecx.tcx.sess.opts.unstable_opts.tiny_const_eval_limit {
+                (TINY_LINT_TERMINATOR_LIMIT, TINY_LINT_TERMINATOR_LIMIT)
+            } else {
+                (LINT_TERMINATOR_LIMIT, PROGRESS_INDICATOR_START)
+            };
+
+            ecx.machine.num_evaluated_steps = new_steps;
+            // By default, we have a *deny* lint kicking in after some time
+            // to ensure `loop {}` doesn't just go forever.
+            // In case that lint got reduced, in particular for `--cap-lint` situations, we also
+            // have a hard warning shown every now and then for really long executions.
+            if new_steps == limit {
+                // By default, we stop after a million steps, but the user can disable this lint
+                // to be able to run until the heat death of the universe or power loss, whichever
+                // comes first.
+                let hir_id = ecx.best_lint_scope();
+                let is_error = ecx
+                    .tcx
+                    .lint_level_at_node(
+                        rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
+                        hir_id,
+                    )
+                    .0
+                    .is_error();
+                let span = ecx.cur_span();
+                ecx.tcx.emit_spanned_lint(
+                    rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
+                    hir_id,
+                    span,
+                    LongRunning { item_span: ecx.tcx.span },
+                );
+                // If this was a hard error, don't bother continuing evaluation.
+                if is_error {
+                    let guard = ecx
+                        .tcx
+                        .sess
+                        .delay_span_bug(span, "The deny lint should have already errored");
+                    throw_inval!(AlreadyReported(guard.into()));
+                }
+            } else if new_steps > start && new_steps.is_power_of_two() {
+                // Only report after a certain number of terminators have been evaluated and the
+                // current number of evaluated terminators is a power of 2. The latter gives us a cheap
+                // way to implement exponential backoff.
+                let span = ecx.cur_span();
+                ecx.tcx.sess.emit_warning(LongRunningWarn { span, item_span: ecx.tcx.span });
+            }
         }
 
         Ok(())
diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs
index 05be45fef13..6e462d3a1e9 100644
--- a/compiler/rustc_const_eval/src/const_eval/mod.rs
+++ b/compiler/rustc_const_eval/src/const_eval/mod.rs
@@ -2,10 +2,8 @@
 
 use crate::errors::MaxNumNodesInConstErr;
 use crate::interpret::{
-    intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, MemPlaceMeta,
-    Scalar,
+    intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, Scalar,
 };
-use rustc_hir::Mutability;
 use rustc_middle::mir;
 use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId};
 use rustc_middle::ty::{self, TyCtxt};
@@ -75,17 +73,8 @@ pub(crate) fn eval_to_valtree<'tcx>(
             let global_const_id = cid.display(tcx);
             match err {
                 ValTreeCreationError::NodesOverflow => {
-                    let msg = format!(
-                        "maximum number of nodes exceeded in constant {}",
-                        &global_const_id
-                    );
-                    let mut diag = match tcx.hir().span_if_local(did) {
-                        Some(span) => {
-                            tcx.sess.create_err(MaxNumNodesInConstErr { span, global_const_id })
-                        }
-                        None => tcx.sess.struct_err(msg),
-                    };
-                    diag.emit();
+                    let span = tcx.hir().span_if_local(did);
+                    tcx.sess.emit_err(MaxNumNodesInConstErr { span, global_const_id });
 
                     Ok(None)
                 }
@@ -131,38 +120,3 @@ pub(crate) fn try_destructure_mir_constant<'tcx>(
 
     Ok(mir::DestructuredConstant { variant, fields })
 }
-
-#[instrument(skip(tcx), level = "debug")]
-pub(crate) fn deref_mir_constant<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    param_env: ty::ParamEnv<'tcx>,
-    val: mir::ConstantKind<'tcx>,
-) -> mir::ConstantKind<'tcx> {
-    let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
-    let op = ecx.eval_mir_constant(&val, None, None).unwrap();
-    let mplace = ecx.deref_operand(&op).unwrap();
-    if let Some(alloc_id) = mplace.ptr.provenance {
-        assert_eq!(
-            tcx.global_alloc(alloc_id).unwrap_memory().0.0.mutability,
-            Mutability::Not,
-            "deref_mir_constant cannot be used with mutable allocations as \
-            that could allow pattern matching to observe mutable statics",
-        );
-    }
-
-    let ty = match mplace.meta {
-        MemPlaceMeta::None => mplace.layout.ty,
-        // In case of unsized types, figure out the real type behind.
-        MemPlaceMeta::Meta(scalar) => match mplace.layout.ty.kind() {
-            ty::Str => bug!("there's no sized equivalent of a `str`"),
-            ty::Slice(elem_ty) => tcx.mk_array(*elem_ty, scalar.to_target_usize(&tcx).unwrap()),
-            _ => bug!(
-                "type {} should not have metadata, but had {:?}",
-                mplace.layout.ty,
-                mplace.meta
-            ),
-        },
-    };
-
-    mir::ConstantKind::Val(op_to_const(&ecx, &mplace.into()), ty)
-}