Rollup merge of #70087 - ecstatic-morse:remove-const-eval-loop-detector, r=RalfJung

Remove const eval loop detector

Now that there is a configurable instruction limit for CTFE (see #67260), we can replace the loop detector with something much simpler. See #66946 for more discussion about this. Although the instruction limit is nightly-only, the only practical way to reach the default limit uses nightly-only features as well (although CTFE will still execute code using such features inside an array initializer on stable).

This will at the very least require a crater run, since it will result in an error wherever the "long running const eval" warning appeared before. We may need to increase the default for `const_eval_limit` to work around this.

Resolves #54384 cc #49980
r? @oli-obk cc @RalfJung
This commit is contained in:
Mazdak Farrokhzad 2020-03-24 00:49:41 +01:00 committed by GitHub
commit 72c99f2cf0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 76 additions and 567 deletions

View file

@ -494,8 +494,10 @@ impl fmt::Debug for UnsupportedOpInfo {
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.
StepLimitReached,
}
impl fmt::Debug for ResourceExhaustionInfo {
@ -505,11 +507,9 @@ 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"
),
StepLimitReached => {
write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)")
}
}
}
}

View file

@ -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<K: Hash + Eq, V> interpret::AllocMap<K, V> for FxHashMap<K, V> {
}
}
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 = !;
@ -345,26 +327,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!(StepLimitReached)
}
let span = ecx.frame().span;
ecx.machine.loop_detector.observe_and_analyze(*ecx.tcx, span, &ecx.memory, &ecx.stack[..])
Ok(())
}
#[inline(always)]

View file

@ -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<AllocId, (MemoryKind<M::MemoryKinds>, 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 {

View file

@ -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;

View file

@ -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<u64>,
/// 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<InterpSnapshot<'mir, 'tcx>>,
}
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::<u64>();
// 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<T>
where
Ctx: SnapshotContext<'a>,
T: Snapshot<'a, Ctx>,
{
type Item = Option<<T as Snapshot<'a, Ctx>>::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<AllocationSnapshot<'a>>);
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<Place<(), AllocIdSnapshot<'a>>>,
locals: IndexVec<mir::Local, LocalValue<(), AllocIdSnapshot<'a>>>,
block: Option<mir::BasicBlock>,
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<Frame<'mir, 'tcx>>,
}
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<FrameSnapshot<'b, 'tcx>> {
// 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<H: Hasher>(&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::<u64>().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()
}
}

View file

@ -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`

View file

@ -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 <https://github.com/rust-lang/rust/issues/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 step limit (see `#[const_eval_limit]`)
error: aborting due to 3 previous errors

View file

@ -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 {

View file

@ -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 step limit (see `#[const_eval_limit]`)
error: aborting due to 2 previous errors

View file

@ -1,15 +1,21 @@
// check-pass
#![feature(const_eval_limit)]
#![const_eval_limit="1000"]
const CONSTANT: usize = limit();
#![feature(const_eval_limit)]
#![feature(const_loop, const_if_match)]
// 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"]
const X: usize = {
let mut x = 0;
while x != 1000 {
x += 1;
}
x
};
fn main() {
assert_eq!(CONSTANT, 1764);
}
const fn limit() -> usize {
let x = 42;
x * 42
assert_eq!(X, 1000);
}

View file

@ -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"]
const X: usize = {
let mut x = 0;
while x != 1000 {
//~^ ERROR any use of this value will cause an error
x += 1;
}
x
};
fn main() {
assert_eq!(CONSTANT, 1764);
}
const fn limit() -> usize { //~ WARNING Constant evaluating a complex constant, this might take some time
let x = 42;
x * 42
assert_eq!(X, 1000);
}

View file

@ -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 step limit (see `#[const_eval_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