From 5b66f27ed34849a849bd93ce662bffeec2e56947 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Tue, 17 Mar 2020 16:07:29 -0700 Subject: [PATCH 1/4] Use `const_eval_limit` instead of infinite loop detector --- src/librustc/mir/interpret/error.rs | 12 ++--- src/librustc_mir/const_eval/machine.rs | 67 ++++++++------------------ src/librustc_mir/interpret/memory.rs | 19 -------- 3 files changed, 25 insertions(+), 73 deletions(-) diff --git a/src/librustc/mir/interpret/error.rs b/src/librustc/mir/interpret/error.rs index 0b33408edf02d..4b1fad5d0b3ee 100644 --- a/src/librustc/mir/interpret/error.rs +++ b/src/librustc/mir/interpret/error.rs @@ -565,8 +565,10 @@ impl fmt::Debug for UnsupportedOpInfo<'tcx> { pub enum ResourceExhaustionInfo { /// The stack grew too big. StackFrameLimitReached, - /// The program ran into an infinite loop. - InfiniteLoop, + /// The program ran for too long. + /// + /// The exact limit is set by the `const_eval_limit` attribute. + TimeLimitReached, } impl fmt::Debug for ResourceExhaustionInfo { @@ -576,11 +578,7 @@ impl fmt::Debug for ResourceExhaustionInfo { StackFrameLimitReached => { write!(f, "reached the configured maximum number of stack frames") } - InfiniteLoop => write!( - f, - "duplicate interpreter state observed here, const evaluation will never \ - terminate" - ), + TimeLimitReached => write!(f, "exceeded interpreter time limit"), } } } diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs index bb661d3d2a30a..22b01be299b1c 100644 --- a/src/librustc_mir/const_eval/machine.rs +++ b/src/librustc_mir/const_eval/machine.rs @@ -3,7 +3,6 @@ use rustc::ty::layout::HasTyCtxt; use rustc::ty::{self, Ty}; use std::borrow::{Borrow, Cow}; use std::collections::hash_map::Entry; -use std::convert::TryFrom; use std::hash::Hash; use rustc_data_structures::fx::FxHashMap; @@ -13,13 +12,13 @@ use rustc_span::source_map::Span; use rustc_span::symbol::Symbol; use crate::interpret::{ - self, snapshot, AllocId, Allocation, GlobalId, ImmTy, InterpCx, InterpResult, Memory, - MemoryKind, OpTy, PlaceTy, Pointer, Scalar, + self, AllocId, Allocation, GlobalId, ImmTy, InterpCx, InterpResult, Memory, MemoryKind, OpTy, + PlaceTy, Pointer, Scalar, }; use super::error::*; -impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { +impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter> { /// Evaluate a const function where all arguments (if any) are zero-sized types. /// The evaluation is memoized thanks to the query system. /// @@ -86,22 +85,13 @@ impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { } } -/// The number of steps between loop detector snapshots. -/// Should be a power of two for performance reasons. -const DETECTOR_SNAPSHOT_PERIOD: isize = 256; - -// Extra machine state for CTFE, and the Machine instance -pub struct CompileTimeInterpreter<'mir, 'tcx> { - /// When this value is negative, it indicates the number of interpreter - /// steps *until* the loop detector is enabled. When it is positive, it is - /// the number of steps after the detector has been enabled modulo the loop - /// detector period. - pub(super) steps_since_detector_enabled: isize, - - pub(super) is_detector_enabled: bool, - - /// Extra state to detect loops. - pub(super) loop_detector: snapshot::InfiniteLoopDetector<'mir, 'tcx>, +/// Extra machine state for CTFE, and the Machine instance +pub struct CompileTimeInterpreter { + /// For now, the number of terminators that can be evaluated before we throw a resource + /// exhuastion error. + /// + /// Setting this to `0` disables the limit and allows the interpreter to run forever. + pub steps_remaining: usize, } #[derive(Copy, Clone, Debug)] @@ -110,16 +100,9 @@ pub struct MemoryExtra { pub(super) can_access_statics: bool, } -impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { +impl CompileTimeInterpreter { pub(super) fn new(const_eval_limit: usize) -> Self { - let steps_until_detector_enabled = - isize::try_from(const_eval_limit).unwrap_or(std::isize::MAX); - - CompileTimeInterpreter { - loop_detector: Default::default(), - steps_since_detector_enabled: -steps_until_detector_enabled, - is_detector_enabled: const_eval_limit != 0, - } + CompileTimeInterpreter { steps_remaining: const_eval_limit } } } @@ -173,8 +156,7 @@ impl interpret::AllocMap for FxHashMap { } } -crate type CompileTimeEvalContext<'mir, 'tcx> = - InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>; +crate type CompileTimeEvalContext<'mir, 'tcx> = InterpCx<'mir, 'tcx, CompileTimeInterpreter>; impl interpret::MayLeak for ! { #[inline(always)] @@ -184,7 +166,7 @@ impl interpret::MayLeak for ! { } } -impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, 'tcx> { +impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter { type MemoryKinds = !; type PointerTag = (); type ExtraFnVal = !; @@ -346,26 +328,17 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, } fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> { - if !ecx.machine.is_detector_enabled { + // The step limit has already been hit in a previous call to `before_terminator`. + if ecx.machine.steps_remaining == 0 { return Ok(()); } - { - let steps = &mut ecx.machine.steps_since_detector_enabled; - - *steps += 1; - if *steps < 0 { - return Ok(()); - } - - *steps %= DETECTOR_SNAPSHOT_PERIOD; - if *steps != 0 { - return Ok(()); - } + ecx.machine.steps_remaining -= 1; + if ecx.machine.steps_remaining == 0 { + throw_exhaust!(TimeLimitReached) } - let span = ecx.frame().span; - ecx.machine.loop_detector.observe_and_analyze(*ecx.tcx, span, &ecx.memory, &ecx.stack[..]) + Ok(()) } #[inline(always)] diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs index 82a467c7ba92c..03be71f6174a8 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -112,25 +112,6 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for Memory<'mir, 'tcx, M> } } -// FIXME: Really we shouldn't clone memory, ever. Snapshot machinery should instead -// carefully copy only the reachable parts. -impl<'mir, 'tcx, M> Clone for Memory<'mir, 'tcx, M> -where - M: Machine<'mir, 'tcx, PointerTag = (), AllocExtra = ()>, - M::MemoryExtra: Copy, - M::MemoryMap: AllocMap, Allocation)>, -{ - fn clone(&self) -> Self { - Memory { - alloc_map: self.alloc_map.clone(), - extra_fn_ptr_map: self.extra_fn_ptr_map.clone(), - dead_alloc_map: self.dead_alloc_map.clone(), - extra: self.extra, - tcx: self.tcx, - } - } -} - impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> { pub fn new(tcx: TyCtxtAt<'tcx>, extra: M::MemoryExtra) -> Self { Memory { From e15c48672850c0a0b7936df3dfa861bfa34db4bd Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Tue, 17 Mar 2020 16:08:13 -0700 Subject: [PATCH 2/4] Remove infinite loop detector --- src/librustc_mir/interpret/mod.rs | 1 - src/librustc_mir/interpret/snapshot.rs | 420 ------------------------- 2 files changed, 421 deletions(-) delete mode 100644 src/librustc_mir/interpret/snapshot.rs diff --git a/src/librustc_mir/interpret/mod.rs b/src/librustc_mir/interpret/mod.rs index c3fd968276577..3063a99886b7b 100644 --- a/src/librustc_mir/interpret/mod.rs +++ b/src/librustc_mir/interpret/mod.rs @@ -9,7 +9,6 @@ mod memory; mod operand; mod operator; mod place; -pub(crate) mod snapshot; // for const_eval mod step; mod terminator; mod traits; diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs deleted file mode 100644 index ee45179fd8b31..0000000000000 --- a/src/librustc_mir/interpret/snapshot.rs +++ /dev/null @@ -1,420 +0,0 @@ -//! This module contains the machinery necessary to detect infinite loops -//! during const-evaluation by taking snapshots of the state of the interpreter -//! at regular intervals. - -// This lives in `interpret` because it needs access to all sots of private state. However, -// it is not used by the general miri engine, just by CTFE. - -use std::hash::{Hash, Hasher}; - -use rustc::ich::StableHashingContextProvider; -use rustc::mir; -use rustc::mir::interpret::{ - AllocId, Allocation, InterpResult, Pointer, Relocations, Scalar, UndefMask, -}; - -use rustc::ty::layout::{Align, Size}; -use rustc::ty::{self, TyCtxt}; -use rustc_ast::ast::Mutability; -use rustc_data_structures::fx::FxHashSet; -use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_index::vec::IndexVec; -use rustc_macros::HashStable; -use rustc_span::source_map::Span; - -use super::eval_context::{LocalState, StackPopCleanup}; -use super::{ - Frame, Immediate, LocalValue, MemPlace, MemPlaceMeta, Memory, Operand, Place, ScalarMaybeUndef, -}; -use crate::const_eval::CompileTimeInterpreter; - -#[derive(Default)] -pub(crate) struct InfiniteLoopDetector<'mir, 'tcx> { - /// The set of all `InterpSnapshot` *hashes* observed by this detector. - /// - /// When a collision occurs in this table, we store the full snapshot in - /// `snapshots`. - hashes: FxHashSet, - - /// The set of all `InterpSnapshot`s observed by this detector. - /// - /// An `InterpSnapshot` will only be fully cloned once it has caused a - /// collision in `hashes`. As a result, the detector must observe at least - /// *two* full cycles of an infinite loop before it triggers. - snapshots: FxHashSet>, -} - -impl<'mir, 'tcx> InfiniteLoopDetector<'mir, 'tcx> { - pub fn observe_and_analyze( - &mut self, - tcx: TyCtxt<'tcx>, - span: Span, - memory: &Memory<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>, - stack: &[Frame<'mir, 'tcx>], - ) -> InterpResult<'tcx, ()> { - // Compute stack's hash before copying anything - let mut hcx = tcx.get_stable_hashing_context(); - let mut hasher = StableHasher::new(); - stack.hash_stable(&mut hcx, &mut hasher); - let hash = hasher.finish::(); - - // Check if we know that hash already - if self.hashes.is_empty() { - // FIXME(#49980): make this warning a lint - tcx.sess.span_warn( - span, - "Constant evaluating a complex constant, this might take some time", - ); - } - if self.hashes.insert(hash) { - // No collision - return Ok(()); - } - - // We need to make a full copy. NOW things that to get really expensive. - info!("snapshotting the state of the interpreter"); - - if self.snapshots.insert(InterpSnapshot::new(memory, stack)) { - // Spurious collision or first cycle - return Ok(()); - } - - // Second cycle - throw_exhaust!(InfiniteLoop) - } -} - -trait SnapshotContext<'a> { - fn resolve(&'a self, id: &AllocId) -> Option<&'a Allocation>; -} - -/// Taking a snapshot of the evaluation context produces a view of -/// the state of the interpreter that is invariant to `AllocId`s. -trait Snapshot<'a, Ctx: SnapshotContext<'a>> { - type Item; - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item; -} - -macro_rules! __impl_snapshot_field { - ($field:ident, $ctx:expr) => { - $field.snapshot($ctx) - }; - ($field:ident, $ctx:expr, $delegate:expr) => { - $delegate - }; -} - -// This assumes the type has two type parameters, first for the tag (set to `()`), -// then for the id -macro_rules! impl_snapshot_for { - (enum $enum_name:ident { - $( $variant:ident $( ( $($field:ident $(-> $delegate:expr)?),* ) )? ),* $(,)? - }) => { - - impl<'a, Ctx> self::Snapshot<'a, Ctx> for $enum_name - where Ctx: self::SnapshotContext<'a>, - { - type Item = $enum_name<(), AllocIdSnapshot<'a>>; - - #[inline] - fn snapshot(&self, __ctx: &'a Ctx) -> Self::Item { - match *self { - $( - $enum_name::$variant $( ( $(ref $field),* ) )? => { - $enum_name::$variant $( - ( $( __impl_snapshot_field!($field, __ctx $(, $delegate)?) ),* ) - )? - } - )* - } - } - } - }; - - (struct $struct_name:ident { $($field:ident $(-> $delegate:expr)?),* $(,)? }) => { - impl<'a, Ctx> self::Snapshot<'a, Ctx> for $struct_name - where Ctx: self::SnapshotContext<'a>, - { - type Item = $struct_name<(), AllocIdSnapshot<'a>>; - - #[inline] - fn snapshot(&self, __ctx: &'a Ctx) -> Self::Item { - let $struct_name { - $(ref $field),* - } = *self; - - $struct_name { - $( $field: __impl_snapshot_field!($field, __ctx $(, $delegate)?) ),* - } - } - } - }; -} - -impl<'a, Ctx, T> Snapshot<'a, Ctx> for Option -where - Ctx: SnapshotContext<'a>, - T: Snapshot<'a, Ctx>, -{ - type Item = Option<>::Item>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - match self { - Some(x) => Some(x.snapshot(ctx)), - None => None, - } - } -} - -#[derive(Eq, PartialEq)] -struct AllocIdSnapshot<'a>(Option>); - -impl<'a, Ctx> Snapshot<'a, Ctx> for AllocId -where - Ctx: SnapshotContext<'a>, -{ - type Item = AllocIdSnapshot<'a>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - AllocIdSnapshot(ctx.resolve(self).map(|alloc| alloc.snapshot(ctx))) - } -} - -impl_snapshot_for!(struct Pointer { - alloc_id, - offset -> *offset, // just copy offset verbatim - tag -> *tag, // just copy tag -}); - -impl<'a, Ctx> Snapshot<'a, Ctx> for Scalar -where - Ctx: SnapshotContext<'a>, -{ - type Item = Scalar<(), AllocIdSnapshot<'a>>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - match self { - Scalar::Ptr(p) => Scalar::Ptr(p.snapshot(ctx)), - Scalar::Raw { size, data } => Scalar::Raw { data: *data, size: *size }, - } - } -} - -impl_snapshot_for!( - enum ScalarMaybeUndef { - Scalar(s), - Undef, - } -); - -impl_snapshot_for!( - enum MemPlaceMeta { - Meta(s), - None, - Poison, - } -); - -impl_snapshot_for!(struct MemPlace { - ptr, - meta, - align -> *align, // just copy alignment verbatim -}); - -impl<'a, Ctx> Snapshot<'a, Ctx> for Place -where - Ctx: SnapshotContext<'a>, -{ - type Item = Place<(), AllocIdSnapshot<'a>>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - match self { - Place::Ptr(p) => Place::Ptr(p.snapshot(ctx)), - - Place::Local { frame, local } => Place::Local { frame: *frame, local: *local }, - } - } -} - -impl_snapshot_for!( - enum Immediate { - Scalar(s), - ScalarPair(s, t), - } -); - -impl_snapshot_for!( - enum Operand { - Immediate(v), - Indirect(m), - } -); - -impl_snapshot_for!( - enum LocalValue { - Dead, - Uninitialized, - Live(v), - } -); - -impl<'a, Ctx> Snapshot<'a, Ctx> for Relocations -where - Ctx: SnapshotContext<'a>, -{ - type Item = Relocations<(), AllocIdSnapshot<'a>>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - Relocations::from_presorted( - self.iter().map(|(size, ((), id))| (*size, ((), id.snapshot(ctx)))).collect(), - ) - } -} - -#[derive(Eq, PartialEq)] -struct AllocationSnapshot<'a> { - bytes: &'a [u8], - relocations: Relocations<(), AllocIdSnapshot<'a>>, - undef_mask: &'a UndefMask, - align: &'a Align, - size: &'a Size, - mutability: &'a Mutability, -} - -impl<'a, Ctx> Snapshot<'a, Ctx> for &'a Allocation -where - Ctx: SnapshotContext<'a>, -{ - type Item = AllocationSnapshot<'a>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - let Allocation { size, align, mutability, extra: (), .. } = self; - - let all_bytes = 0..self.len(); - // This 'inspect' is okay since following access respects undef and relocations. This does - // influence interpreter exeuction, but only to detect the error of cycles in evaluation - // dependencies. - let bytes = self.inspect_with_undef_and_ptr_outside_interpreter(all_bytes); - - let undef_mask = self.undef_mask(); - let relocations = self.relocations(); - - AllocationSnapshot { - bytes, - undef_mask, - align, - size, - mutability, - relocations: relocations.snapshot(ctx), - } - } -} - -#[derive(Eq, PartialEq)] -struct FrameSnapshot<'a, 'tcx> { - instance: ty::Instance<'tcx>, - span: Span, - return_to_block: &'a StackPopCleanup, - return_place: Option>>, - locals: IndexVec>>, - block: Option, - stmt: usize, -} - -impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx> -where - Ctx: SnapshotContext<'a>, -{ - type Item = FrameSnapshot<'a, 'tcx>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - let Frame { - body: _, - instance, - span, - return_to_block, - return_place, - locals, - block, - stmt, - extra: _, - } = self; - - FrameSnapshot { - instance: *instance, - span: *span, - return_to_block, - block: *block, - stmt: *stmt, - return_place: return_place.map(|r| r.snapshot(ctx)), - locals: locals.iter().map(|local| local.snapshot(ctx)).collect(), - } - } -} - -impl<'a, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a LocalState<'tcx> -where - Ctx: SnapshotContext<'a>, -{ - type Item = LocalValue<(), AllocIdSnapshot<'a>>; - - fn snapshot(&self, ctx: &'a Ctx) -> Self::Item { - let LocalState { value, layout: _ } = self; - value.snapshot(ctx) - } -} - -impl<'b, 'mir, 'tcx> SnapshotContext<'b> - for Memory<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> -{ - fn resolve(&'b self, id: &AllocId) -> Option<&'b Allocation> { - self.get_raw(*id).ok() - } -} - -/// The virtual machine state during const-evaluation at a given point in time. -/// We assume the `CompileTimeInterpreter` has no interesting extra state that -/// is worth considering here. -#[derive(HashStable)] -struct InterpSnapshot<'mir, 'tcx> { - // Not hashing memory: Avoid hashing memory all the time during execution - #[stable_hasher(ignore)] - memory: Memory<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>, - stack: Vec>, -} - -impl InterpSnapshot<'mir, 'tcx> { - fn new( - memory: &Memory<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>, - stack: &[Frame<'mir, 'tcx>], - ) -> Self { - InterpSnapshot { memory: memory.clone(), stack: stack.into() } - } - - // Used to compare two snapshots - fn snapshot(&'b self) -> Vec> { - // Start with the stack, iterate and recursively snapshot - self.stack.iter().map(|frame| frame.snapshot(&self.memory)).collect() - } -} - -impl<'mir, 'tcx> Hash for InterpSnapshot<'mir, 'tcx> { - fn hash(&self, state: &mut H) { - // Implement in terms of hash stable, so that k1 == k2 -> hash(k1) == hash(k2) - let mut hcx = self.memory.tcx.get_stable_hashing_context(); - let mut hasher = StableHasher::new(); - self.hash_stable(&mut hcx, &mut hasher); - hasher.finish::().hash(state) - } -} - -impl<'mir, 'tcx> Eq for InterpSnapshot<'mir, 'tcx> {} - -impl<'mir, 'tcx> PartialEq for InterpSnapshot<'mir, 'tcx> { - fn eq(&self, other: &Self) -> bool { - // FIXME: This looks to be a *ridiculously expensive* comparison operation. - // Doesn't this make tons of copies? Either `snapshot` is very badly named, - // or it does! - self.snapshot() == other.snapshot() - } -} From 044dc6e8dd435eb9c3b6f1dc0c85d0a6e70dae82 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Tue, 17 Mar 2020 16:08:28 -0700 Subject: [PATCH 3/4] Update const-eval tests --- .../ui/consts/const-eval/infinite_loop.rs | 1 - .../ui/consts/const-eval/infinite_loop.stderr | 21 +++------------ src/test/ui/consts/const-eval/issue-52475.rs | 1 - .../ui/consts/const-eval/issue-52475.stderr | 19 +++---------- .../const_eval_limit_not_reached.rs | 22 +++++++++------ .../const_limit/const_eval_limit_reached.rs | 27 +++++++++---------- .../const_eval_limit_reached.stderr | 25 ++++++++--------- 7 files changed, 46 insertions(+), 70 deletions(-) diff --git a/src/test/ui/consts/const-eval/infinite_loop.rs b/src/test/ui/consts/const-eval/infinite_loop.rs index af5e7658d48d2..c8de259354eac 100644 --- a/src/test/ui/consts/const-eval/infinite_loop.rs +++ b/src/test/ui/consts/const-eval/infinite_loop.rs @@ -2,7 +2,6 @@ fn main() { // Tests the Collatz conjecture with an incorrect base case (0 instead of 1). // The value of `n` will loop indefinitely (4 - 2 - 1 - 4). let _ = [(); { - //~^ WARNING Constant evaluating a complex constant, this might take some time let mut n = 113383; // #20 in https://oeis.org/A006884 while n != 0 { //~^ ERROR `while` is not allowed in a `const` diff --git a/src/test/ui/consts/const-eval/infinite_loop.stderr b/src/test/ui/consts/const-eval/infinite_loop.stderr index e04c31cf39782..9b56457ea5f57 100644 --- a/src/test/ui/consts/const-eval/infinite_loop.stderr +++ b/src/test/ui/consts/const-eval/infinite_loop.stderr @@ -1,5 +1,5 @@ error[E0658]: `while` is not allowed in a `const` - --> $DIR/infinite_loop.rs:7:9 + --> $DIR/infinite_loop.rs:6:9 | LL | / while n != 0 { LL | | @@ -14,7 +14,7 @@ LL | | } = help: add `#![feature(const_if_match)]` to the crate attributes to enable error[E0658]: `if` is not allowed in a `const` - --> $DIR/infinite_loop.rs:9:17 + --> $DIR/infinite_loop.rs:8:17 | LL | n = if n % 2 == 0 { n/2 } else { 3*n + 1 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -22,24 +22,11 @@ LL | n = if n % 2 == 0 { n/2 } else { 3*n + 1 }; = note: see issue #49146 for more information = help: add `#![feature(const_if_match)]` to the crate attributes to enable -warning: Constant evaluating a complex constant, this might take some time - --> $DIR/infinite_loop.rs:4:18 - | -LL | let _ = [(); { - | __________________^ -LL | | -LL | | let mut n = 113383; // #20 in https://oeis.org/A006884 -LL | | while n != 0 { -... | -LL | | n -LL | | }]; - | |_____^ - error[E0080]: evaluation of constant value failed - --> $DIR/infinite_loop.rs:9:20 + --> $DIR/infinite_loop.rs:8:20 | LL | n = if n % 2 == 0 { n/2 } else { 3*n + 1 }; - | ^^^^^^^^^^ duplicate interpreter state observed here, const evaluation will never terminate + | ^^^^^^^^^^ exceeded interpreter time limit error: aborting due to 3 previous errors diff --git a/src/test/ui/consts/const-eval/issue-52475.rs b/src/test/ui/consts/const-eval/issue-52475.rs index 3788167f44902..869f0b981af7d 100644 --- a/src/test/ui/consts/const-eval/issue-52475.rs +++ b/src/test/ui/consts/const-eval/issue-52475.rs @@ -1,6 +1,5 @@ fn main() { let _ = [(); { - //~^ WARNING Constant evaluating a complex constant, this might take some time let mut x = &0; let mut n = 0; while n < 5 { diff --git a/src/test/ui/consts/const-eval/issue-52475.stderr b/src/test/ui/consts/const-eval/issue-52475.stderr index 31d87925b2cfc..8d6168a7f8693 100644 --- a/src/test/ui/consts/const-eval/issue-52475.stderr +++ b/src/test/ui/consts/const-eval/issue-52475.stderr @@ -1,5 +1,5 @@ error[E0658]: `while` is not allowed in a `const` - --> $DIR/issue-52475.rs:6:9 + --> $DIR/issue-52475.rs:5:9 | LL | / while n < 5 { LL | | @@ -12,24 +12,11 @@ LL | | } = help: add `#![feature(const_loop)]` to the crate attributes to enable = help: add `#![feature(const_if_match)]` to the crate attributes to enable -warning: Constant evaluating a complex constant, this might take some time - --> $DIR/issue-52475.rs:2:18 - | -LL | let _ = [(); { - | __________________^ -LL | | -LL | | let mut x = &0; -LL | | let mut n = 0; -... | -LL | | 0 -LL | | }]; - | |_____^ - error[E0080]: evaluation of constant value failed - --> $DIR/issue-52475.rs:8:17 + --> $DIR/issue-52475.rs:7:17 | LL | n = (n + 1) % 5; - | ^^^^^^^^^^^ duplicate interpreter state observed here, const evaluation will never terminate + | ^^^^^^^^^^^ exceeded interpreter time limit error: aborting due to 2 previous errors diff --git a/src/test/ui/consts/const_limit/const_eval_limit_not_reached.rs b/src/test/ui/consts/const_limit/const_eval_limit_not_reached.rs index 4ed908312fb6d..34abcdf08dae0 100644 --- a/src/test/ui/consts/const_limit/const_eval_limit_not_reached.rs +++ b/src/test/ui/consts/const_limit/const_eval_limit_not_reached.rs @@ -1,15 +1,21 @@ // check-pass + #![feature(const_eval_limit)] -#![const_eval_limit="1000"] +#![feature(const_loop, const_if_match)] -const CONSTANT: usize = limit(); +// This needs to be higher than the number of loop iterations since each pass through the loop may +// hit more than one terminator. +#![const_eval_limit="4000"] -fn main() { - assert_eq!(CONSTANT, 1764); -} +const X: usize = { + let mut x = 0; + while x != 1000 { + x += 1; + } -const fn limit() -> usize { - let x = 42; + x +}; - x * 42 +fn main() { + assert_eq!(X, 1000); } diff --git a/src/test/ui/consts/const_limit/const_eval_limit_reached.rs b/src/test/ui/consts/const_limit/const_eval_limit_reached.rs index d962398d4136e..b45aca0b13e42 100644 --- a/src/test/ui/consts/const_limit/const_eval_limit_reached.rs +++ b/src/test/ui/consts/const_limit/const_eval_limit_reached.rs @@ -1,21 +1,18 @@ -// ignore-tidy-linelength -// only-x86_64 -// check-pass -// NOTE: We always compile this test with -Copt-level=0 because higher opt-levels -// optimize away the const function -// compile-flags:-Copt-level=0 #![feature(const_eval_limit)] -#![const_eval_limit="2"] +#![feature(const_loop, const_if_match)] -const CONSTANT: usize = limit(); -//~^ WARNING Constant evaluating a complex constant, this might take some time +#![const_eval_limit="500"] -fn main() { - assert_eq!(CONSTANT, 1764); -} +const X: usize = { + let mut x = 0; + while x != 1000 { + //~^ ERROR any use of this value will cause an error + x += 1; + } -const fn limit() -> usize { //~ WARNING Constant evaluating a complex constant, this might take some time - let x = 42; + x +}; - x * 42 +fn main() { + assert_eq!(X, 1000); } diff --git a/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr b/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr index e0871ff718561..ca8fbef9072d8 100644 --- a/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr +++ b/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr @@ -1,16 +1,17 @@ -warning: Constant evaluating a complex constant, this might take some time - --> $DIR/const_eval_limit_reached.rs:17:1 +error: any use of this value will cause an error + --> $DIR/const_eval_limit_reached.rs:8:11 | -LL | / const fn limit() -> usize { -LL | | let x = 42; +LL | / const X: usize = { +LL | | let mut x = 0; +LL | | while x != 1000 { + | | ^^^^^^^^^ exceeded interpreter time limit LL | | -LL | | x * 42 -LL | | } - | |_^ - -warning: Constant evaluating a complex constant, this might take some time - --> $DIR/const_eval_limit_reached.rs:10:1 +... | +LL | | x +LL | | }; + | |__- | -LL | const CONSTANT: usize = limit(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(const_err)]` on by default + +error: aborting due to previous error From b5636b8437feecc3474e607f60891f3ebc098ded Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Sun, 22 Mar 2020 12:49:58 -0700 Subject: [PATCH 4/4] Rename `TimeLimitReached` -> `StepLimitReached` --- src/librustc/mir/interpret/error.rs | 6 ++++-- src/librustc_mir/const_eval/machine.rs | 2 +- src/test/ui/consts/const-eval/infinite_loop.stderr | 2 +- src/test/ui/consts/const-eval/issue-52475.stderr | 2 +- .../ui/consts/const_limit/const_eval_limit_reached.stderr | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/librustc/mir/interpret/error.rs b/src/librustc/mir/interpret/error.rs index 4b1fad5d0b3ee..54d48e83d0336 100644 --- a/src/librustc/mir/interpret/error.rs +++ b/src/librustc/mir/interpret/error.rs @@ -568,7 +568,7 @@ pub enum ResourceExhaustionInfo { /// The program ran for too long. /// /// The exact limit is set by the `const_eval_limit` attribute. - TimeLimitReached, + StepLimitReached, } impl fmt::Debug for ResourceExhaustionInfo { @@ -578,7 +578,9 @@ impl fmt::Debug for ResourceExhaustionInfo { StackFrameLimitReached => { write!(f, "reached the configured maximum number of stack frames") } - TimeLimitReached => write!(f, "exceeded interpreter time limit"), + StepLimitReached => { + write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)") + } } } } diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs index 22b01be299b1c..123fc80021f3b 100644 --- a/src/librustc_mir/const_eval/machine.rs +++ b/src/librustc_mir/const_eval/machine.rs @@ -335,7 +335,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter { ecx.machine.steps_remaining -= 1; if ecx.machine.steps_remaining == 0 { - throw_exhaust!(TimeLimitReached) + throw_exhaust!(StepLimitReached) } Ok(()) diff --git a/src/test/ui/consts/const-eval/infinite_loop.stderr b/src/test/ui/consts/const-eval/infinite_loop.stderr index 9b56457ea5f57..ebdb73c446791 100644 --- a/src/test/ui/consts/const-eval/infinite_loop.stderr +++ b/src/test/ui/consts/const-eval/infinite_loop.stderr @@ -26,7 +26,7 @@ error[E0080]: evaluation of constant value failed --> $DIR/infinite_loop.rs:8:20 | LL | n = if n % 2 == 0 { n/2 } else { 3*n + 1 }; - | ^^^^^^^^^^ exceeded interpreter time limit + | ^^^^^^^^^^ exceeded interpreter step limit (see `#[const_eval_limit]`) error: aborting due to 3 previous errors diff --git a/src/test/ui/consts/const-eval/issue-52475.stderr b/src/test/ui/consts/const-eval/issue-52475.stderr index 8d6168a7f8693..b069537ead963 100644 --- a/src/test/ui/consts/const-eval/issue-52475.stderr +++ b/src/test/ui/consts/const-eval/issue-52475.stderr @@ -16,7 +16,7 @@ error[E0080]: evaluation of constant value failed --> $DIR/issue-52475.rs:7:17 | LL | n = (n + 1) % 5; - | ^^^^^^^^^^^ exceeded interpreter time limit + | ^^^^^^^^^^^ exceeded interpreter step limit (see `#[const_eval_limit]`) error: aborting due to 2 previous errors diff --git a/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr b/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr index ca8fbef9072d8..be522dd6d5d5a 100644 --- a/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr +++ b/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr @@ -4,7 +4,7 @@ error: any use of this value will cause an error LL | / const X: usize = { LL | | let mut x = 0; LL | | while x != 1000 { - | | ^^^^^^^^^ exceeded interpreter time limit + | | ^^^^^^^^^ exceeded interpreter step limit (see `#[const_eval_limit]`) LL | | ... | LL | | x