diff options
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_const_eval/messages.ftl | 6 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/const_eval/eval_queries.rs | 3 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/const_eval/machine.rs | 82 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/errors.rs | 20 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/interpret/eval_context.rs | 10 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/util/check_validity_requirement.rs | 8 | ||||
| -rw-r--r-- | compiler/rustc_feature/src/active.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_feature/src/builtin_attrs.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_feature/src/removed.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_lint_defs/src/builtin.rs | 38 | ||||
| -rw-r--r-- | compiler/rustc_middle/src/middle/limits.rs | 11 | ||||
| -rw-r--r-- | compiler/rustc_middle/src/mir/interpret/error.rs | 7 | ||||
| -rw-r--r-- | compiler/rustc_middle/src/ty/context.rs | 10 | ||||
| -rw-r--r-- | compiler/rustc_session/src/session.rs | 2 |
14 files changed, 146 insertions, 61 deletions
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 7d56cf0aa07..917bd829572 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -10,6 +10,12 @@ const_eval_interior_mutable_data_refer = This would make multiple uses of a constant to be able to see different values and allow circumventing the `Send` and `Sync` requirements for shared mutable data, which is unsound. +const_eval_long_running = + constant evaluation is taking a long time + .note = this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval. + If your compilation actually takes a long time, you can safely allow the lint. + .label = the const evaluator is currently interpreting this expression + .help = the constant being evaluated const_eval_max_num_nodes_in_const = maximum number of nodes exceeded in constant {$global_const_id} const_eval_mut_deref = 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..e4d34b90018 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -103,7 +103,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), ) } @@ -306,7 +306,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 diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 58b5755af07..a8b6b98c96c 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -16,11 +16,11 @@ 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, @@ -28,13 +28,24 @@ use crate::interpret::{ 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 +83,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, @@ -569,13 +576,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/errors.rs b/compiler/rustc_const_eval/src/errors.rs index f8b7cc6d7e1..ad2e68e752d 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -1,5 +1,5 @@ use rustc_hir::ConstContext; -use rustc_macros::Diagnostic; +use rustc_macros::{Diagnostic, LintDiagnostic}; use rustc_span::Span; #[derive(Diagnostic)] @@ -194,3 +194,21 @@ pub(crate) struct InteriorMutabilityBorrow { #[primary_span] pub span: Span, } + +#[derive(LintDiagnostic)] +#[diag(const_eval_long_running)] +#[note] +pub struct LongRunning { + #[help] + pub item_span: Span, +} + +#[derive(Diagnostic)] +#[diag(const_eval_long_running)] +pub struct LongRunningWarn { + #[primary_span] + #[label] + pub span: Span, + #[help] + pub item_span: Span, +} diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 91ccdef7215..cd43a53b1b3 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -4,6 +4,7 @@ use std::mem; use either::{Either, Left, Right}; +use hir::CRATE_HIR_ID; use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData}; use rustc_index::IndexVec; use rustc_middle::mir; @@ -406,6 +407,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } #[inline(always)] + /// Find the first stack frame that is within the current crate, if any, otherwise return the crate's HirId + pub fn best_lint_scope(&self) -> hir::HirId { + self.stack() + .iter() + .find_map(|frame| frame.body.source.def_id().as_local()) + .map_or(CRATE_HIR_ID, |def_id| self.tcx.hir().local_def_id_to_hir_id(def_id)) + } + + #[inline(always)] pub(crate) fn stack(&self) -> &[Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>] { M::stack(self) } diff --git a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs index 23fcd22c52b..29063261ada 100644 --- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs +++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs @@ -1,6 +1,5 @@ use rustc_middle::ty::layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement}; use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt}; -use rustc_session::Limit; use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants}; use crate::const_eval::{CheckAlignment, CompileTimeInterpreter}; @@ -45,11 +44,8 @@ fn might_permit_raw_init_strict<'tcx>( tcx: TyCtxt<'tcx>, kind: ValidityRequirement, ) -> Result<bool, LayoutError<'tcx>> { - let machine = CompileTimeInterpreter::new( - Limit::new(0), - /*can_access_statics:*/ false, - CheckAlignment::Error, - ); + let machine = + CompileTimeInterpreter::new(/*can_access_statics:*/ false, CheckAlignment::Error); let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index dde9890dfa5..4c53f9d8369 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -351,8 +351,6 @@ declare_features! ( (active, const_async_blocks, "1.53.0", Some(85368), None), /// Allows `const || {}` closures in const contexts. (incomplete, const_closures, "1.68.0", Some(106003), None), - /// Allows limiting the evaluation steps of const expressions - (active, const_eval_limit, "1.43.0", Some(67217), None), /// Allows the definition of `const extern fn` and `const unsafe extern fn`. (active, const_extern_fn, "1.40.0", Some(64926), None), /// Allows basic arithmetic on floating point types in a `const fn`. diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 06f4a0b5eef..9be28c338f6 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -356,10 +356,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), gated!( - const_eval_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, - const_eval_limit, experimental!(const_eval_limit) - ), - gated!( move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, large_assignments, experimental!(move_size_limit) ), diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 8bca24b2bf0..ed5d76b861a 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -59,8 +59,10 @@ declare_features! ( /// Allows comparing raw pointers during const eval. (removed, const_compare_raw_pointers, "1.46.0", Some(53020), None, Some("cannot be allowed in const eval in any meaningful way")), + /// Allows limiting the evaluation steps of const expressions + (removed, const_eval_limit, "1.43.0", Some(67217), None, Some("removed the limit entirely")), /// Allows non-trivial generic constants which have to be manually propagated upwards. - (removed, const_evaluatable_checked, "1.48.0", Some(76560), None, Some("renamed to `generic_const_exprs`")), + (removed, const_evaluatable_checked, "1.48.0", Some(76560), None, Some("renamed to `generic_const_exprs`")), /// Allows the definition of `const` functions with some advanced features. (removed, const_fn, "1.54.0", Some(57563), None, Some("split into finer-grained feature gates")), diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 1507087bdd4..eb246c3f93e 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3357,6 +3357,7 @@ declare_lint_pass! { LARGE_ASSIGNMENTS, LATE_BOUND_LIFETIME_ARGUMENTS, LEGACY_DERIVE_HELPERS, + LONG_RUNNING_CONST_EVAL, LOSSY_PROVENANCE_CASTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, MACRO_USE_EXTERN_CRATE, @@ -3427,6 +3428,43 @@ declare_lint_pass! { } declare_lint! { + /// The `long_running_const_eval` lint is emitted when const + /// eval is running for a long time to ensure rustc terminates + /// even if you accidentally wrote an infinite loop. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// const FOO: () = loop {}; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Loops allow const evaluation to compute arbitrary code, but may also + /// cause infinite loops or just very long running computations. + /// Users can enable long running computations by allowing the lint + /// on individual constants or for entire crates. + /// + /// ### Unconditional warnings + /// + /// Note that regardless of whether the lint is allowed or set to warn, + /// the compiler will issue warnings if constant evaluation runs significantly + /// longer than this lint's limit. These warnings are also shown to downstream + /// users from crates.io or similar registries. If you are above the lint's limit, + /// both you and downstream users might be exposed to these warnings. + /// They might also appear on compiler updates, as the compiler makes minor changes + /// about how complexity is measured: staying below the limit ensures that there + /// is enough room, and given that the lint is disabled for people who use your + /// dependency it means you will be the only one to get the warning and can put + /// out an update in your own time. + pub LONG_RUNNING_CONST_EVAL, + Deny, + "detects long const eval operations" +} + +declare_lint! { /// The `unused_doc_comments` lint detects doc comments that aren't used /// by `rustdoc`. /// diff --git a/compiler/rustc_middle/src/middle/limits.rs b/compiler/rustc_middle/src/middle/limits.rs index bd859d4d61b..d4f023958d6 100644 --- a/compiler/rustc_middle/src/middle/limits.rs +++ b/compiler/rustc_middle/src/middle/limits.rs @@ -1,8 +1,7 @@ //! Registering limits: //! * recursion_limit, -//! * move_size_limit, -//! * type_length_limit, and -//! * const_eval_limit +//! * move_size_limit, and +//! * type_length_limit //! //! There are various parts of the compiler that must impose arbitrary limits //! on how deeply they recurse to prevent stack overflow. Users can override @@ -34,12 +33,6 @@ pub fn provide(providers: &mut Providers) { sym::type_length_limit, 1048576, ), - const_eval_limit: get_limit( - tcx.hir().krate_attrs(), - tcx.sess, - sym::const_eval_limit, - 2_000_000, - ), } } diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 055d8e9a352..357bcca4419 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -465,10 +465,6 @@ impl fmt::Display for UnsupportedOpInfo { pub enum ResourceExhaustionInfo { /// The stack grew too big. StackFrameLimitReached, - /// The program ran for too long. - /// - /// The exact limit is set by the `const_eval_limit` attribute. - StepLimitReached, /// There is not enough memory (on the host) to perform an allocation. MemoryExhausted, /// The address space (of the target) is full. @@ -482,9 +478,6 @@ impl fmt::Display for ResourceExhaustionInfo { StackFrameLimitReached => { write!(f, "reached the configured maximum number of stack frames") } - StepLimitReached => { - write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)") - } MemoryExhausted => { write!(f, "tried to allocate more memory than available to compiler") } diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 77725f0b3b6..b05e791211d 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -82,8 +82,6 @@ use std::iter; use std::mem; use std::ops::{Bound, Deref}; -const TINY_CONST_EVAL_LIMIT: Limit = Limit(20); - #[allow(rustc::usage_of_ty_tykind)] impl<'tcx> Interner for TyCtxt<'tcx> { type AdtDef = ty::AdtDef<'tcx>; @@ -1178,14 +1176,6 @@ impl<'tcx> TyCtxt<'tcx> { self.limits(()).move_size_limit } - pub fn const_eval_limit(self) -> Limit { - if self.sess.opts.unstable_opts.tiny_const_eval_limit { - TINY_CONST_EVAL_LIMIT - } else { - self.limits(()).const_eval_limit - } - } - pub fn all_traits(self) -> impl Iterator<Item = DefId> + 'tcx { iter::once(LOCAL_CRATE) .chain(self.crates(()).iter().copied()) diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 1eb54cee5a1..2ebd83540ee 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -128,8 +128,6 @@ pub struct Limits { pub move_size_limit: Limit, /// The maximum length of types during monomorphization. pub type_length_limit: Limit, - /// The maximum blocks a const expression can evaluate. - pub const_eval_limit: Limit, } pub struct CompilerIO { |
