about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2018-09-06 17:35:40 +0000
committerbors <bors@rust-lang.org>2018-09-06 17:35:40 +0000
commitc3186914071b71af2c771ec0c0d044a1a8c215c2 (patch)
tree55da58e5a752fd1fd33d4f0e1fb5a6c5837ac4c2
parent35a5541fd90c96564d483eee248daabc6b133de3 (diff)
parent05cdf8dc3d490fbec2519de90b254f47ec7373e8 (diff)
Auto merge of #52626 - brunocodutra:issue-52475, r=oli-obk
Fix issue #52475: Make loop detector only consider reachable memory

As [suggested](https://github.com/rust-lang/rust/pull/51702#discussion_r197585664) by @oli-obk `alloc_id`s should be ignored by traversing all `Allocation`s in interpreter memory at a given moment in time, beginning by `ByRef` locals in the stack.

- [x] Generalize the implementation of `Hash` for `EvalSnapshot` to traverse `Allocation`s
- [x] Generalize the implementation of `PartialEq` for `EvalSnapshot` to traverse `Allocation`s
- [x] Commit regression tests

Fixes #52626
Fixes https://github.com/rust-lang/rust/issues/52849
-rw-r--r--src/librustc/ich/hcx.rs4
-rw-r--r--src/librustc/ich/impls_ty.rs2
-rw-r--r--src/librustc/mir/interpret/mod.rs17
-rw-r--r--src/librustc/mir/interpret/value.rs8
-rw-r--r--src/librustc_data_structures/stable_hasher.rs17
-rw-r--r--src/librustc_mir/const_eval.rs2
-rw-r--r--src/librustc_mir/interpret/eval_context.rs140
-rw-r--r--src/librustc_mir/interpret/machine.rs6
-rw-r--r--src/librustc_mir/interpret/memory.rs55
-rw-r--r--src/librustc_mir/interpret/mod.rs1
-rw-r--r--src/librustc_mir/interpret/operand.rs27
-rw-r--r--src/librustc_mir/interpret/place.rs35
-rw-r--r--src/librustc_mir/interpret/snapshot.rs411
-rw-r--r--src/librustc_mir/interpret/step.rs7
-rw-r--r--src/test/ui/consts/const-eval/issue-52475.rs25
-rw-r--r--src/test/ui/consts/const-eval/issue-52475.stderr42
16 files changed, 606 insertions, 193 deletions
diff --git a/src/librustc/ich/hcx.rs b/src/librustc/ich/hcx.rs
index 371f631737c..1acc58948f4 100644
--- a/src/librustc/ich/hcx.rs
+++ b/src/librustc/ich/hcx.rs
@@ -15,7 +15,6 @@ use hir::map::definitions::Definitions;
 use ich::{self, CachingSourceMapView, Fingerprint};
 use middle::cstore::CrateStore;
 use ty::{TyCtxt, fast_reject};
-use mir::interpret::AllocId;
 use session::Session;
 
 use std::cmp::Ord;
@@ -60,8 +59,6 @@ pub struct StableHashingContext<'a> {
     // CachingSourceMapView, so we initialize it lazily.
     raw_source_map: &'a SourceMap,
     caching_source_map: Option<CachingSourceMapView<'a>>,
-
-    pub(super) alloc_id_recursion_tracker: FxHashSet<AllocId>,
 }
 
 #[derive(PartialEq, Eq, Clone, Copy)]
@@ -105,7 +102,6 @@ impl<'a> StableHashingContext<'a> {
             hash_spans: hash_spans_initial,
             hash_bodies: true,
             node_id_hashing_mode: NodeIdHashingMode::HashDefPath,
-            alloc_id_recursion_tracker: Default::default(),
         }
     }
 
diff --git a/src/librustc/ich/impls_ty.rs b/src/librustc/ich/impls_ty.rs
index bb8498cb94d..68320dfddef 100644
--- a/src/librustc/ich/impls_ty.rs
+++ b/src/librustc/ich/impls_ty.rs
@@ -412,7 +412,7 @@ impl<'a> HashStable<StableHashingContext<'a>> for mir::interpret::AllocId {
         ty::tls::with_opt(|tcx| {
             trace!("hashing {:?}", *self);
             let tcx = tcx.expect("can't hash AllocIds during hir lowering");
-            let alloc_kind = tcx.alloc_map.lock().get(*self).expect("no value for AllocId");
+            let alloc_kind = tcx.alloc_map.lock().get(*self);
             alloc_kind.hash_stable(hcx, hasher);
         });
     }
diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs
index d40dbae09d2..ccc5bba1ad6 100644
--- a/src/librustc/mir/interpret/mod.rs
+++ b/src/librustc/mir/interpret/mod.rs
@@ -133,9 +133,14 @@ pub trait PointerArithmetic: layout::HasDataLayout {
 impl<T: layout::HasDataLayout> PointerArithmetic for T {}
 
 
+/// Pointer is generic over the type that represents a reference to Allocations,
+/// thus making it possible for the most convenient representation to be used in
+/// each context.
+///
+/// Defaults to the index based and loosely coupled AllocId.
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)]
-pub struct Pointer {
-    pub alloc_id: AllocId,
+pub struct Pointer<Id=AllocId> {
+    pub alloc_id: Id,
     pub offset: Size,
 }
 
@@ -543,16 +548,16 @@ impl Allocation {
 impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {}
 
 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, RustcEncodable, RustcDecodable)]
-pub struct Relocations(SortedMap<Size, AllocId>);
+pub struct Relocations<Id=AllocId>(SortedMap<Size, Id>);
 
-impl Relocations {
-    pub fn new() -> Relocations {
+impl<Id> Relocations<Id> {
+    pub fn new() -> Self {
         Relocations(SortedMap::new())
     }
 
     // The caller must guarantee that the given relocations are already sorted
     // by address and contain no duplicates.
-    pub fn from_presorted(r: Vec<(Size, AllocId)>) -> Relocations {
+    pub fn from_presorted(r: Vec<(Size, Id)>) -> Self {
         Relocations(SortedMap::from_presorted_elements(r))
     }
 }
diff --git a/src/librustc/mir/interpret/value.rs b/src/librustc/mir/interpret/value.rs
index 11a4f8b884e..9982da483ce 100644
--- a/src/librustc/mir/interpret/value.rs
+++ b/src/librustc/mir/interpret/value.rs
@@ -326,7 +326,7 @@ impl From<Pointer> for Scalar {
 /// size. Like a range of bytes in an `Allocation`, a `Scalar` can either represent the raw bytes
 /// of a simple value or a pointer into another `Allocation`
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)]
-pub enum Scalar {
+pub enum Scalar<Id=AllocId> {
     /// The raw bytes of a simple value.
     Bits {
         /// The first `size` bytes are the value.
@@ -338,12 +338,12 @@ pub enum Scalar {
     /// A pointer into an `Allocation`. An `Allocation` in the `memory` module has a list of
     /// relocations, but a `Scalar` is only large enough to contain one, so we just represent the
     /// relocation and its associated offset together as a `Pointer` here.
-    Ptr(Pointer),
+    Ptr(Pointer<Id>),
 }
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable, Hash)]
-pub enum ScalarMaybeUndef {
-    Scalar(Scalar),
+pub enum ScalarMaybeUndef<Id=AllocId> {
+    Scalar(Scalar<Id>),
     Undef,
 }
 
diff --git a/src/librustc_data_structures/stable_hasher.rs b/src/librustc_data_structures/stable_hasher.rs
index 1024e69cc2b..c70a0abe8c7 100644
--- a/src/librustc_data_structures/stable_hasher.rs
+++ b/src/librustc_data_structures/stable_hasher.rs
@@ -281,6 +281,23 @@ impl<T1, T2, T3, CTX> HashStable<CTX> for (T1, T2, T3)
     }
 }
 
+impl<T1, T2, T3, T4, CTX> HashStable<CTX> for (T1, T2, T3, T4)
+     where T1: HashStable<CTX>,
+           T2: HashStable<CTX>,
+           T3: HashStable<CTX>,
+           T4: HashStable<CTX>,
+{
+    fn hash_stable<W: StableHasherResult>(&self,
+                                          ctx: &mut CTX,
+                                          hasher: &mut StableHasher<W>) {
+        let (ref _0, ref _1, ref _2, ref _3) = *self;
+        _0.hash_stable(ctx, hasher);
+        _1.hash_stable(ctx, hasher);
+        _2.hash_stable(ctx, hasher);
+        _3.hash_stable(ctx, hasher);
+    }
+}
+
 impl<T: HashStable<CTX>, CTX> HashStable<CTX> for [T] {
     default fn hash_stable<W: StableHasherResult>(&self,
                                                   ctx: &mut CTX,
diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs
index 70addf2c907..92ddd8777f7 100644
--- a/src/librustc_mir/const_eval.rs
+++ b/src/librustc_mir/const_eval.rs
@@ -196,6 +196,8 @@ impl<'tcx> Into<EvalError<'tcx>> for ConstEvalError {
     }
 }
 
+impl_stable_hash_for!(struct CompileTimeEvaluator {});
+
 #[derive(Clone, Debug)]
 enum ConstEvalError {
     NeedsRfc(String),
diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs
index 6e144ba7ed2..f7277f8d276 100644
--- a/src/librustc_mir/interpret/eval_context.rs
+++ b/src/librustc_mir/interpret/eval_context.rs
@@ -9,12 +9,12 @@
 // except according to those terms.
 
 use std::fmt::Write;
-use std::hash::{Hash, Hasher};
 use std::mem;
 
 use rustc::hir::def_id::DefId;
 use rustc::hir::def::Def;
 use rustc::hir::map::definitions::DefPathData;
+use rustc::ich::StableHashingContext;
 use rustc::mir;
 use rustc::ty::layout::{
     self, Size, Align, HasDataLayout, LayoutOf, TyLayout
@@ -22,10 +22,10 @@ use rustc::ty::layout::{
 use rustc::ty::subst::{Subst, Substs};
 use rustc::ty::{self, Ty, TyCtxt, TypeFoldable};
 use rustc::ty::query::TyCtxtAt;
-use rustc_data_structures::fx::{FxHashSet, FxHasher};
 use rustc_data_structures::indexed_vec::IndexVec;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult};
 use rustc::mir::interpret::{
-    GlobalId, Scalar, FrameInfo,
+    GlobalId, Scalar, FrameInfo, AllocId,
     EvalResult, EvalErrorKind,
     ScalarMaybeUndef,
     truncate, sign_extend,
@@ -38,6 +38,8 @@ use super::{
     Memory, Machine
 };
 
+use super::snapshot::InfiniteLoopDetector;
+
 pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
     /// Stores the `Machine` instance.
     pub machine: M,
@@ -95,7 +97,7 @@ pub struct Frame<'mir, 'tcx: 'mir> {
     /// The locals are stored as `Option<Value>`s.
     /// `None` represents a local that is currently dead, while a live local
     /// can either directly contain `Scalar` or refer to some part of an `Allocation`.
-    pub locals: IndexVec<mir::Local, LocalValue>,
+    pub locals: IndexVec<mir::Local, LocalValue<AllocId>>,
 
     ////////////////////////////////////////////////////////////////////////////////
     // Current position within the function
@@ -108,38 +110,16 @@ pub struct Frame<'mir, 'tcx: 'mir> {
     pub stmt: usize,
 }
 
-impl<'mir, 'tcx: 'mir> Eq for Frame<'mir, 'tcx> {}
-
-impl<'mir, 'tcx: 'mir> PartialEq for Frame<'mir, 'tcx> {
-    fn eq(&self, other: &Self) -> bool {
-        let Frame {
-            mir: _,
-            instance,
-            span: _,
-            return_to_block,
-            return_place,
-            locals,
-            block,
-            stmt,
-        } = self;
-
-        // Some of these are constant during evaluation, but are included
-        // anyways for correctness.
-        *instance == other.instance
-            && *return_to_block == other.return_to_block
-            && *return_place == other.return_place
-            && *locals == other.locals
-            && *block == other.block
-            && *stmt == other.stmt
-    }
-}
+impl<'a, 'mir, 'tcx: 'mir> HashStable<StableHashingContext<'a>> for Frame<'mir, 'tcx> {
+    fn hash_stable<W: StableHasherResult>(
+        &self,
+        hcx: &mut StableHashingContext<'a>,
+        hasher: &mut StableHasher<W>) {
 
-impl<'mir, 'tcx: 'mir> Hash for Frame<'mir, 'tcx> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
         let Frame {
-            mir: _,
+            mir,
             instance,
-            span: _,
+            span,
             return_to_block,
             return_place,
             locals,
@@ -147,12 +127,8 @@ impl<'mir, 'tcx: 'mir> Hash for Frame<'mir, 'tcx> {
             stmt,
         } = self;
 
-        instance.hash(state);
-        return_to_block.hash(state);
-        return_place.hash(state);
-        locals.hash(state);
-        block.hash(state);
-        stmt.hash(state);
+        (mir, instance, span, return_to_block).hash_stable(hcx, hasher);
+        (return_place, locals, block, stmt).hash_stable(hcx, hasher);
     }
 }
 
@@ -168,15 +144,27 @@ pub enum StackPopCleanup {
     None { cleanup: bool },
 }
 
+impl<'a> HashStable<StableHashingContext<'a>> for StackPopCleanup {
+    fn hash_stable<W: StableHasherResult>(
+        &self,
+        hcx: &mut StableHashingContext<'a>,
+        hasher: &mut StableHasher<W>) {
+        match self {
+            StackPopCleanup::Goto(ref block) => block.hash_stable(hcx, hasher),
+            StackPopCleanup::None { cleanup } => cleanup.hash_stable(hcx, hasher),
+        }
+    }
+}
+
 // State of a local variable
 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
-pub enum LocalValue {
+pub enum LocalValue<Id=AllocId> {
     Dead,
     // Mostly for convenience, we re-use the `Operand` type here.
     // This is an optimization over just always having a pointer here;
     // we can thus avoid doing an allocation when the local just stores
     // immediate values *and* never has its address taken.
-    Live(Operand),
+    Live(Operand<Id>),
 }
 
 impl<'tcx> LocalValue {
@@ -195,72 +183,10 @@ impl<'tcx> LocalValue {
     }
 }
 
-/// The virtual machine state during const-evaluation at a given point in time.
-type EvalSnapshot<'a, 'mir, 'tcx, M>
-    = (M, Vec<Frame<'mir, 'tcx>>, Memory<'a, 'mir, 'tcx, M>);
-
-pub(super) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
-    /// The set of all `EvalSnapshot` *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 `EvalSnapshot`s observed by this detector.
-    ///
-    /// An `EvalSnapshot` 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<EvalSnapshot<'a, 'mir, 'tcx, M>>,
-}
-
-impl<'a, 'mir, 'tcx, M> Default for InfiniteLoopDetector<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
-          'tcx: 'a + 'mir,
-{
-    fn default() -> Self {
-        InfiniteLoopDetector {
-            hashes: FxHashSet::default(),
-            snapshots: FxHashSet::default(),
-        }
-    }
-}
-
-impl<'a, 'mir, 'tcx, M> InfiniteLoopDetector<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
-          'tcx: 'a + 'mir,
-{
-    /// Returns `true` if the loop detector has not yet observed a snapshot.
-    pub fn is_empty(&self) -> bool {
-        self.hashes.is_empty()
-    }
-
-    pub fn observe_and_analyze(
-        &mut self,
-        machine: &M,
-        stack: &Vec<Frame<'mir, 'tcx>>,
-        memory: &Memory<'a, 'mir, 'tcx, M>,
-    ) -> EvalResult<'tcx, ()> {
-        let snapshot = (machine, stack, memory);
-
-        let mut fx = FxHasher::default();
-        snapshot.hash(&mut fx);
-        let hash = fx.finish();
-
-        if self.hashes.insert(hash) {
-            // No collision
-            return Ok(())
-        }
-
-        if self.snapshots.insert((machine.clone(), stack.clone(), memory.clone())) {
-            // Spurious collision or first cycle
-            return Ok(())
-        }
-
-        // Second cycle
-        Err(EvalErrorKind::InfiniteLoop.into())
-    }
-}
+impl_stable_hash_for!(enum self::LocalValue {
+    Dead,
+    Live(x),
+});
 
 impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for &'a EvalContext<'a, 'mir, 'tcx, M> {
     #[inline]
diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs
index 57af63d63d9..61963f6d3d3 100644
--- a/src/librustc_mir/interpret/machine.rs
+++ b/src/librustc_mir/interpret/machine.rs
@@ -15,17 +15,19 @@
 use std::hash::Hash;
 
 use rustc::hir::def_id::DefId;
+use rustc::ich::StableHashingContext;
 use rustc::mir::interpret::{Allocation, EvalResult, Scalar};
 use rustc::mir;
 use rustc::ty::{self, layout::TyLayout, query::TyCtxtAt};
+use rustc_data_structures::stable_hasher::HashStable;
 
 use super::{EvalContext, PlaceTy, OpTy};
 
 /// Methods of this trait signifies a point where CTFE evaluation would fail
 /// and some use case dependent behaviour can instead be applied
-pub trait Machine<'mir, 'tcx>: Clone + Eq + Hash {
+pub trait Machine<'mir, 'tcx>: Clone + Eq + Hash + for<'a> HashStable<StableHashingContext<'a>> {
     /// Additional data that can be accessed via the Memory
-    type MemoryData: Clone + Eq + Hash;
+    type MemoryData: Clone + Eq + Hash + for<'a> HashStable<StableHashingContext<'a>>;
 
     /// Additional memory kinds a machine wishes to distinguish from the builtin ones
     type MemoryKinds: ::std::fmt::Debug + Copy + Clone + Eq + Hash;
diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs
index 59bebbb87a7..9e61de92936 100644
--- a/src/librustc_mir/interpret/memory.rs
+++ b/src/librustc_mir/interpret/memory.rs
@@ -17,7 +17,6 @@
 //! short-circuiting the empty case!
 
 use std::collections::VecDeque;
-use std::hash::{Hash, Hasher};
 use std::ptr;
 
 use rustc::ty::{self, Instance, query::TyCtxtAt};
@@ -26,7 +25,7 @@ use rustc::mir::interpret::{Pointer, AllocId, Allocation, ConstValue, ScalarMayb
                             EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic,
                             truncate};
 pub use rustc::mir::interpret::{write_target_uint, read_target_uint};
-use rustc_data_structures::fx::{FxHashSet, FxHashMap, FxHasher};
+use rustc_data_structures::fx::{FxHashSet, FxHashMap};
 
 use syntax::ast::Mutability;
 
@@ -70,58 +69,6 @@ impl<'a, 'b, 'c, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout
     }
 }
 
-impl<'a, 'mir, 'tcx, M> Eq for Memory<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
-          'tcx: 'a + 'mir,
-{}
-
-impl<'a, 'mir, 'tcx, M> PartialEq for Memory<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
-          'tcx: 'a + 'mir,
-{
-    fn eq(&self, other: &Self) -> bool {
-        let Memory {
-            data,
-            alloc_map,
-            tcx: _,
-        } = self;
-
-        *data == other.data
-            && *alloc_map == other.alloc_map
-    }
-}
-
-impl<'a, 'mir, 'tcx, M> Hash for Memory<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
-          'tcx: 'a + 'mir,
-{
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        let Memory {
-            data,
-            alloc_map: _,
-            tcx: _,
-        } = self;
-
-        data.hash(state);
-
-        // We ignore some fields which don't change between evaluation steps.
-
-        // Since HashMaps which contain the same items may have different
-        // iteration orders, we use a commutative operation (in this case
-        // addition, but XOR would also work), to combine the hash of each
-        // `Allocation`.
-        self.alloc_map.iter()
-            .map(|(&id, alloc)| {
-                let mut h = FxHasher::default();
-                id.hash(&mut h);
-                alloc.hash(&mut h);
-                h.finish()
-            })
-            .fold(0u64, |hash, x| hash.wrapping_add(x))
-            .hash(state);
-    }
-}
-
 impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
     pub fn new(tcx: TyCtxtAt<'a, 'tcx, 'tcx>, data: M::MemoryData) -> Self {
         Memory {
diff --git a/src/librustc_mir/interpret/mod.rs b/src/librustc_mir/interpret/mod.rs
index 462c4b8889d..1e8de029232 100644
--- a/src/librustc_mir/interpret/mod.rs
+++ b/src/librustc_mir/interpret/mod.rs
@@ -17,6 +17,7 @@ mod operand;
 mod machine;
 mod memory;
 mod operator;
+mod snapshot;
 mod step;
 mod terminator;
 mod traits;
diff --git a/src/librustc_mir/interpret/operand.rs b/src/librustc_mir/interpret/operand.rs
index 6f9e0cf3e37..4093a6304b3 100644
--- a/src/librustc_mir/interpret/operand.rs
+++ b/src/librustc_mir/interpret/operand.rs
@@ -17,9 +17,10 @@ use std::convert::TryInto;
 use rustc::{mir, ty};
 use rustc::ty::layout::{self, Size, LayoutOf, TyLayout, HasDataLayout, IntegerExt};
 use rustc_data_structures::indexed_vec::Idx;
-
 use rustc::mir::interpret::{
-    GlobalId, ConstValue, Scalar, EvalResult, Pointer, ScalarMaybeUndef, EvalErrorKind
+    GlobalId, AllocId,
+    ConstValue, Pointer, Scalar, ScalarMaybeUndef,
+    EvalResult, EvalErrorKind
 };
 use super::{EvalContext, Machine, MemPlace, MPlaceTy, MemoryKind};
 
@@ -31,9 +32,9 @@ use super::{EvalContext, Machine, MemPlace, MPlaceTy, MemoryKind};
 /// In particular, thanks to `ScalarPair`, arithmetic operations and casts can be entirely
 /// defined on `Value`, and do not have to work with a `Place`.
 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
-pub enum Value {
-    Scalar(ScalarMaybeUndef),
-    ScalarPair(ScalarMaybeUndef, ScalarMaybeUndef),
+pub enum Value<Id=AllocId> {
+    Scalar(ScalarMaybeUndef<Id>),
+    ScalarPair(ScalarMaybeUndef<Id>, ScalarMaybeUndef<Id>),
 }
 
 impl<'tcx> Value {
@@ -81,6 +82,11 @@ impl<'tcx> Value {
     }
 }
 
+impl_stable_hash_for!(enum ::interpret::Value {
+    Scalar(x),
+    ScalarPair(x, y),
+});
+
 // ScalarPair needs a type to interpret, so we often have a value and a type together
 // as input for binary and cast operations.
 #[derive(Copy, Clone, Debug)]
@@ -101,9 +107,9 @@ impl<'tcx> ::std::ops::Deref for ValTy<'tcx> {
 /// or still in memory.  The latter is an optimization, to delay reading that chunk of
 /// memory and to avoid having to store arbitrary-sized data here.
 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
-pub enum Operand {
-    Immediate(Value),
-    Indirect(MemPlace),
+pub enum Operand<Id=AllocId> {
+    Immediate(Value<Id>),
+    Indirect(MemPlace<Id>),
 }
 
 impl Operand {
@@ -126,6 +132,11 @@ impl Operand {
     }
 }
 
+impl_stable_hash_for!(enum ::interpret::Operand {
+    Immediate(x),
+    Indirect(x),
+});
+
 #[derive(Copy, Clone, Debug)]
 pub struct OpTy<'tcx> {
     crate op: Operand, // ideally we'd make this private, but const_prop needs this
diff --git a/src/librustc_mir/interpret/place.rs b/src/librustc_mir/interpret/place.rs
index 6ee768fde5f..51a42944527 100644
--- a/src/librustc_mir/interpret/place.rs
+++ b/src/librustc_mir/interpret/place.rs
@@ -14,33 +14,41 @@
 
 use std::convert::TryFrom;
 
+use rustc::ich::StableHashingContext;
 use rustc::mir;
 use rustc::ty::{self, Ty};
 use rustc::ty::layout::{self, Size, Align, LayoutOf, TyLayout, HasDataLayout};
 use rustc_data_structures::indexed_vec::Idx;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult};
 
 use rustc::mir::interpret::{
-    GlobalId, Scalar, EvalResult, Pointer, ScalarMaybeUndef, PointerArithmetic
+    GlobalId, AllocId, Scalar, EvalResult, Pointer, ScalarMaybeUndef, PointerArithmetic
 };
 use super::{EvalContext, Machine, Value, ValTy, Operand, OpTy, MemoryKind};
 
 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
-pub struct MemPlace {
+pub struct MemPlace<Id=AllocId> {
     /// A place may have an integral pointer for ZSTs, and since it might
     /// be turned back into a reference before ever being dereferenced.
     /// However, it may never be undef.
-    pub ptr: Scalar,
+    pub ptr: Scalar<Id>,
     pub align: Align,
     /// Metadata for unsized places.  Interpretation is up to the type.
     /// Must not be present for sized types, but can be missing for unsized types
     /// (e.g. `extern type`).
-    pub extra: Option<Scalar>,
+    pub extra: Option<Scalar<Id>>,
 }
 
+impl_stable_hash_for!(struct ::interpret::MemPlace {
+    ptr,
+    align,
+    extra,
+});
+
 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
-pub enum Place {
+pub enum Place<Id=AllocId> {
     /// A place referring to a value allocated in the `Memory` system.
-    Ptr(MemPlace),
+    Ptr(MemPlace<Id>),
 
     /// To support alloc-free locals, we are able to write directly to a local.
     /// (Without that optimization, we'd just always be a `MemPlace`.)
@@ -50,6 +58,21 @@ pub enum Place {
     },
 }
 
+impl<'a> HashStable<StableHashingContext<'a>> for Place {
+    fn hash_stable<W: StableHasherResult>(
+        &self, hcx: &mut StableHashingContext<'a>,
+        hasher: &mut StableHasher<W>) {
+
+        match self {
+            Place::Ptr(mem_place) => mem_place.hash_stable(hcx, hasher),
+
+            Place::Local { frame, local } => {
+                frame.hash_stable(hcx, hasher);
+                local.hash_stable(hcx, hasher);
+            },
+        }
+    }
+}
 #[derive(Copy, Clone, Debug)]
 pub struct PlaceTy<'tcx> {
     place: Place,
diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs
new file mode 100644
index 00000000000..8aa053baae9
--- /dev/null
+++ b/src/librustc_mir/interpret/snapshot.rs
@@ -0,0 +1,411 @@
+//! 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.
+
+use std::hash::{Hash, Hasher};
+
+use rustc::ich::{StableHashingContext, StableHashingContextProvider};
+use rustc::mir;
+use rustc::mir::interpret::{
+    AllocId, Pointer, Scalar, ScalarMaybeUndef,
+    Relocations, Allocation, UndefMask,
+    EvalResult, EvalErrorKind,
+};
+
+use rustc::ty::{self, TyCtxt};
+use rustc::ty::layout::Align;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::indexed_vec::IndexVec;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult};
+use syntax::ast::Mutability;
+use syntax::source_map::Span;
+
+use super::eval_context::{LocalValue, StackPopCleanup};
+use super::{Frame, Memory, Machine, Operand, MemPlace, Place, Value};
+
+pub(super) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
+    /// The set of all `EvalSnapshot` *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 `EvalSnapshot`s observed by this detector.
+    ///
+    /// An `EvalSnapshot` 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<EvalSnapshot<'a, 'mir, 'tcx, M>>,
+}
+
+impl<'a, 'mir, 'tcx, M> Default for InfiniteLoopDetector<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+          'tcx: 'a + 'mir,
+{
+    fn default() -> Self {
+        InfiniteLoopDetector {
+            hashes: FxHashSet::default(),
+            snapshots: FxHashSet::default(),
+        }
+    }
+}
+
+impl<'a, 'mir, 'tcx, M> InfiniteLoopDetector<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+          'tcx: 'a + 'mir,
+{
+    /// Returns `true` if the loop detector has not yet observed a snapshot.
+    pub fn is_empty(&self) -> bool {
+        self.hashes.is_empty()
+    }
+
+    pub fn observe_and_analyze(
+        &mut self,
+        tcx: &TyCtxt<'b, 'tcx, 'tcx>,
+        machine: &M,
+        memory: &Memory<'a, 'mir, 'tcx, M>,
+        stack: &[Frame<'mir, 'tcx>],
+    ) -> EvalResult<'tcx, ()> {
+
+        let mut hcx = tcx.get_stable_hashing_context();
+        let mut hasher = StableHasher::<u64>::new();
+        (machine, stack).hash_stable(&mut hcx, &mut hasher);
+        let hash = hasher.finish();
+
+        if self.hashes.insert(hash) {
+            // No collision
+            return Ok(())
+        }
+
+        info!("snapshotting the state of the interpreter");
+
+        if self.snapshots.insert(EvalSnapshot::new(machine, memory, stack)) {
+            // Spurious collision or first cycle
+            return Ok(())
+        }
+
+        // Second cycle
+        Err(EvalErrorKind::InfiniteLoop.into())
+    }
+}
+
+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);
+}
+
+macro_rules! impl_snapshot_for {
+    // FIXME(mark-i-m): Some of these should be `?` rather than `*`.
+    (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)*) ),* ),
+                            )*
+                    )*
+                }
+            }
+        }
+    };
+
+    // FIXME(mark-i-m): same here.
+    (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,
+});
+
+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::Bits{ size, bits } => Scalar::Bits {
+                size: *size,
+                bits: *bits,
+            },
+        }
+    }
+}
+
+impl_snapshot_for!(enum ScalarMaybeUndef {
+    Scalar(s),
+    Undef,
+});
+
+impl_snapshot_for!(struct MemPlace {
+    ptr,
+    extra,
+    align -> *align,
+});
+
+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 Value {
+    Scalar(s),
+    ScalarPair(s, t),
+});
+
+impl_snapshot_for!(enum Operand {
+    Immediate(v),
+    Indirect(m),
+});
+
+impl_snapshot_for!(enum LocalValue {
+    Live(v),
+    Dead,
+});
+
+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,
+    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 { bytes, relocations, undef_mask, align, mutability } = self;
+
+        AllocationSnapshot {
+            bytes,
+            undef_mask,
+            align,
+            mutability,
+            relocations: relocations.snapshot(ctx),
+        }
+    }
+}
+
+#[derive(Eq, PartialEq)]
+struct FrameSnapshot<'a, 'tcx: 'a> {
+    instance: &'a ty::Instance<'tcx>,
+    span: &'a Span,
+    return_to_block: &'a StackPopCleanup,
+    return_place: Place<AllocIdSnapshot<'a>>,
+    locals: IndexVec<mir::Local, LocalValue<AllocIdSnapshot<'a>>>,
+    block: &'a 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 {
+            mir: _,
+            instance,
+            span,
+            return_to_block,
+            return_place,
+            locals,
+            block,
+            stmt,
+        } = self;
+
+        FrameSnapshot {
+            instance,
+            span,
+            return_to_block,
+            block,
+            stmt: *stmt,
+            return_place: return_place.snapshot(ctx),
+            locals: locals.iter().map(|local| local.snapshot(ctx)).collect(),
+        }
+    }
+}
+
+#[derive(Eq, PartialEq)]
+struct MemorySnapshot<'a, 'mir: 'a, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx> + 'a> {
+    data: &'a M::MemoryData,
+}
+
+impl<'a, 'mir, 'tcx, M> Memory<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+{
+    fn snapshot<'b: 'a>(&'b self) -> MemorySnapshot<'b, 'mir, 'tcx, M> {
+        let Memory { data, .. } = self;
+        MemorySnapshot { data }
+    }
+}
+
+impl<'a, 'b, 'mir, 'tcx, M> SnapshotContext<'b> for Memory<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+{
+    fn resolve(&'b self, id: &AllocId) -> Option<&'b Allocation> {
+        self.get(*id).ok()
+    }
+}
+
+/// The virtual machine state during const-evaluation at a given point in time.
+struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
+    machine: M,
+    memory: Memory<'a, 'mir, 'tcx, M>,
+    stack: Vec<Frame<'mir, 'tcx>>,
+}
+
+impl<'a, 'mir, 'tcx, M> EvalSnapshot<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+{
+    fn new(
+        machine: &M,
+        memory: &Memory<'a, 'mir, 'tcx, M>,
+        stack: &[Frame<'mir, 'tcx>]) -> Self {
+
+        EvalSnapshot {
+            machine: machine.clone(),
+            memory: memory.clone(),
+            stack: stack.into(),
+        }
+    }
+
+    fn snapshot<'b: 'a>(&'b self)
+        -> (&'b M, MemorySnapshot<'b, 'mir, 'tcx, M>, Vec<FrameSnapshot<'a, 'tcx>>) {
+        let EvalSnapshot{ machine, memory, stack } = self;
+        (&machine, memory.snapshot(), stack.iter().map(|frame| frame.snapshot(memory)).collect())
+    }
+}
+
+impl<'a, 'mir, 'tcx, M> Hash for EvalSnapshot<'a, 'mir, 'tcx, M>
+    where M: Machine<'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::<u64>::new();
+        self.hash_stable(&mut hcx, &mut hasher);
+        hasher.finish().hash(state)
+    }
+}
+
+impl<'a, 'b, 'mir, 'tcx, M> HashStable<StableHashingContext<'b>>
+    for EvalSnapshot<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+{
+    fn hash_stable<W: StableHasherResult>(
+        &self,
+        hcx: &mut StableHashingContext<'b>,
+        hasher: &mut StableHasher<W>) {
+
+        let EvalSnapshot{ machine, memory, stack } = self;
+        (machine, &memory.data, stack).hash_stable(hcx, hasher);
+    }
+}
+
+impl<'a, 'mir, 'tcx, M> Eq for EvalSnapshot<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+{}
+
+impl<'a, 'mir, 'tcx, M> PartialEq for EvalSnapshot<'a, 'mir, 'tcx, M>
+    where M: Machine<'mir, 'tcx>,
+{
+    fn eq(&self, other: &Self) -> bool {
+        self.snapshot() == other.snapshot()
+    }
+}
diff --git a/src/librustc_mir/interpret/step.rs b/src/librustc_mir/interpret/step.rs
index 114ef093ec2..545333e8791 100644
--- a/src/librustc_mir/interpret/step.rs
+++ b/src/librustc_mir/interpret/step.rs
@@ -73,7 +73,12 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
                 "Constant evaluating a complex constant, this might take some time");
         }
 
-        self.loop_detector.observe_and_analyze(&self.machine, &self.stack, &self.memory)
+        self.loop_detector.observe_and_analyze(
+            &self.tcx,
+            &self.machine,
+            &self.memory,
+            &self.stack[..],
+        )
     }
 
     pub fn run(&mut self) -> EvalResult<'tcx> {
diff --git a/src/test/ui/consts/const-eval/issue-52475.rs b/src/test/ui/consts/const-eval/issue-52475.rs
new file mode 100644
index 00000000000..1c1e6535e69
--- /dev/null
+++ b/src/test/ui/consts/const-eval/issue-52475.rs
@@ -0,0 +1,25 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(const_let)]
+
+fn main() {
+    let _ = [(); {
+        //~^ WARNING Constant evaluating a complex constant, this might take some time
+        //~| ERROR could not evaluate repeat length
+        let mut x = &0;
+        let mut n = 0;
+        while n < 5 { //~ ERROR constant contains unimplemented expression type
+            n = (n + 1) % 5;
+            x = &0; // Materialize a new AllocId
+        }
+        0
+    }];
+}
diff --git a/src/test/ui/consts/const-eval/issue-52475.stderr b/src/test/ui/consts/const-eval/issue-52475.stderr
new file mode 100644
index 00000000000..f45587f3f75
--- /dev/null
+++ b/src/test/ui/consts/const-eval/issue-52475.stderr
@@ -0,0 +1,42 @@
+error[E0019]: constant contains unimplemented expression type
+  --> $DIR/issue-52475.rs:19:9
+   |
+LL | /         while n < 5 { //~ ERROR constant contains unimplemented expression type
+LL | |             n = (n + 1) % 5;
+LL | |             x = &0; // Materialize a new AllocId
+LL | |         }
+   | |_________^
+
+warning: Constant evaluating a complex constant, this might take some time
+  --> $DIR/issue-52475.rs:14:18
+   |
+LL |       let _ = [(); {
+   |  __________________^
+LL | |         //~^ WARNING Constant evaluating a complex constant, this might take some time
+LL | |         //~| ERROR could not evaluate repeat length
+LL | |         let mut x = &0;
+...  |
+LL | |         0
+LL | |     }];
+   | |_____^
+
+error[E0080]: could not evaluate repeat length
+  --> $DIR/issue-52475.rs:14:18
+   |
+LL |       let _ = [(); {
+   |  __________________^
+LL | |         //~^ WARNING Constant evaluating a complex constant, this might take some time
+LL | |         //~| ERROR could not evaluate repeat length
+LL | |         let mut x = &0;
+...  |
+LL | |             n = (n + 1) % 5;
+   | |                 ----------- duplicate interpreter state observed here, const evaluation will never terminate
+...  |
+LL | |         0
+LL | |     }];
+   | |_____^
+
+error: aborting due to 2 previous errors
+
+Some errors occurred: E0019, E0080.
+For more information about an error, try `rustc --explain E0019`.