Skip to content

Commit 72c99f2

Browse files
authored
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
2 parents a73ed5a + b5636b8 commit 72c99f2

File tree

12 files changed

+73
-564
lines changed

12 files changed

+73
-564
lines changed

src/librustc/mir/interpret/error.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,10 @@ impl fmt::Debug for UnsupportedOpInfo {
494494
pub enum ResourceExhaustionInfo {
495495
/// The stack grew too big.
496496
StackFrameLimitReached,
497-
/// The program ran into an infinite loop.
498-
InfiniteLoop,
497+
/// The program ran for too long.
498+
///
499+
/// The exact limit is set by the `const_eval_limit` attribute.
500+
StepLimitReached,
499501
}
500502

501503
impl fmt::Debug for ResourceExhaustionInfo {
@@ -505,11 +507,9 @@ impl fmt::Debug for ResourceExhaustionInfo {
505507
StackFrameLimitReached => {
506508
write!(f, "reached the configured maximum number of stack frames")
507509
}
508-
InfiniteLoop => write!(
509-
f,
510-
"duplicate interpreter state observed here, const evaluation will never \
511-
terminate"
512-
),
510+
StepLimitReached => {
511+
write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)")
512+
}
513513
}
514514
}
515515
}

src/librustc_mir/const_eval/machine.rs

+20-47
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use rustc::ty::layout::HasTyCtxt;
33
use rustc::ty::{self, Ty};
44
use std::borrow::{Borrow, Cow};
55
use std::collections::hash_map::Entry;
6-
use std::convert::TryFrom;
76
use std::hash::Hash;
87

98
use rustc_data_structures::fx::FxHashMap;
@@ -13,13 +12,13 @@ use rustc_span::source_map::Span;
1312
use rustc_span::symbol::Symbol;
1413

1514
use crate::interpret::{
16-
self, snapshot, AllocId, Allocation, GlobalId, ImmTy, InterpCx, InterpResult, Memory,
17-
MemoryKind, OpTy, PlaceTy, Pointer, Scalar,
15+
self, AllocId, Allocation, GlobalId, ImmTy, InterpCx, InterpResult, Memory, MemoryKind, OpTy,
16+
PlaceTy, Pointer, Scalar,
1817
};
1918

2019
use super::error::*;
2120

22-
impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> {
21+
impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter> {
2322
/// Evaluate a const function where all arguments (if any) are zero-sized types.
2423
/// The evaluation is memoized thanks to the query system.
2524
///
@@ -86,22 +85,13 @@ impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> {
8685
}
8786
}
8887

89-
/// The number of steps between loop detector snapshots.
90-
/// Should be a power of two for performance reasons.
91-
const DETECTOR_SNAPSHOT_PERIOD: isize = 256;
92-
93-
// Extra machine state for CTFE, and the Machine instance
94-
pub struct CompileTimeInterpreter<'mir, 'tcx> {
95-
/// When this value is negative, it indicates the number of interpreter
96-
/// steps *until* the loop detector is enabled. When it is positive, it is
97-
/// the number of steps after the detector has been enabled modulo the loop
98-
/// detector period.
99-
pub(super) steps_since_detector_enabled: isize,
100-
101-
pub(super) is_detector_enabled: bool,
102-
103-
/// Extra state to detect loops.
104-
pub(super) loop_detector: snapshot::InfiniteLoopDetector<'mir, 'tcx>,
88+
/// Extra machine state for CTFE, and the Machine instance
89+
pub struct CompileTimeInterpreter {
90+
/// For now, the number of terminators that can be evaluated before we throw a resource
91+
/// exhuastion error.
92+
///
93+
/// Setting this to `0` disables the limit and allows the interpreter to run forever.
94+
pub steps_remaining: usize,
10595
}
10696

10797
#[derive(Copy, Clone, Debug)]
@@ -110,16 +100,9 @@ pub struct MemoryExtra {
110100
pub(super) can_access_statics: bool,
111101
}
112102

113-
impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
103+
impl CompileTimeInterpreter {
114104
pub(super) fn new(const_eval_limit: usize) -> Self {
115-
let steps_until_detector_enabled =
116-
isize::try_from(const_eval_limit).unwrap_or(std::isize::MAX);
117-
118-
CompileTimeInterpreter {
119-
loop_detector: Default::default(),
120-
steps_since_detector_enabled: -steps_until_detector_enabled,
121-
is_detector_enabled: const_eval_limit != 0,
122-
}
105+
CompileTimeInterpreter { steps_remaining: const_eval_limit }
123106
}
124107
}
125108

@@ -173,8 +156,7 @@ impl<K: Hash + Eq, V> interpret::AllocMap<K, V> for FxHashMap<K, V> {
173156
}
174157
}
175158

176-
crate type CompileTimeEvalContext<'mir, 'tcx> =
177-
InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>;
159+
crate type CompileTimeEvalContext<'mir, 'tcx> = InterpCx<'mir, 'tcx, CompileTimeInterpreter>;
178160

179161
impl interpret::MayLeak for ! {
180162
#[inline(always)]
@@ -184,7 +166,7 @@ impl interpret::MayLeak for ! {
184166
}
185167
}
186168

187-
impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, 'tcx> {
169+
impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter {
188170
type MemoryKinds = !;
189171
type PointerTag = ();
190172
type ExtraFnVal = !;
@@ -345,26 +327,17 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
345327
}
346328

347329
fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
348-
if !ecx.machine.is_detector_enabled {
330+
// The step limit has already been hit in a previous call to `before_terminator`.
331+
if ecx.machine.steps_remaining == 0 {
349332
return Ok(());
350333
}
351334

352-
{
353-
let steps = &mut ecx.machine.steps_since_detector_enabled;
354-
355-
*steps += 1;
356-
if *steps < 0 {
357-
return Ok(());
358-
}
359-
360-
*steps %= DETECTOR_SNAPSHOT_PERIOD;
361-
if *steps != 0 {
362-
return Ok(());
363-
}
335+
ecx.machine.steps_remaining -= 1;
336+
if ecx.machine.steps_remaining == 0 {
337+
throw_exhaust!(StepLimitReached)
364338
}
365339

366-
let span = ecx.frame().span;
367-
ecx.machine.loop_detector.observe_and_analyze(*ecx.tcx, span, &ecx.memory, &ecx.stack[..])
340+
Ok(())
368341
}
369342

370343
#[inline(always)]

src/librustc_mir/interpret/memory.rs

-19
Original file line numberDiff line numberDiff line change
@@ -112,25 +112,6 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for Memory<'mir, 'tcx, M>
112112
}
113113
}
114114

115-
// FIXME: Really we shouldn't clone memory, ever. Snapshot machinery should instead
116-
// carefully copy only the reachable parts.
117-
impl<'mir, 'tcx, M> Clone for Memory<'mir, 'tcx, M>
118-
where
119-
M: Machine<'mir, 'tcx, PointerTag = (), AllocExtra = ()>,
120-
M::MemoryExtra: Copy,
121-
M::MemoryMap: AllocMap<AllocId, (MemoryKind<M::MemoryKinds>, Allocation)>,
122-
{
123-
fn clone(&self) -> Self {
124-
Memory {
125-
alloc_map: self.alloc_map.clone(),
126-
extra_fn_ptr_map: self.extra_fn_ptr_map.clone(),
127-
dead_alloc_map: self.dead_alloc_map.clone(),
128-
extra: self.extra,
129-
tcx: self.tcx,
130-
}
131-
}
132-
}
133-
134115
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
135116
pub fn new(tcx: TyCtxtAt<'tcx>, extra: M::MemoryExtra) -> Self {
136117
Memory {

src/librustc_mir/interpret/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ mod memory;
99
mod operand;
1010
mod operator;
1111
mod place;
12-
pub(crate) mod snapshot; // for const_eval
1312
mod step;
1413
mod terminator;
1514
mod traits;

0 commit comments

Comments
 (0)