diff --git a/src/librustc_codegen_llvm/mir/constant.rs b/src/librustc_codegen_llvm/mir/constant.rs
index b6c9658dd6fc3..ce18f31da6907 100644
--- a/src/librustc_codegen_llvm/mir/constant.rs
+++ b/src/librustc_codegen_llvm/mir/constant.rs
@@ -10,7 +10,7 @@
 
 use llvm;
 use rustc::mir::interpret::{ConstEvalErr, read_target_uint};
-use rustc_mir::interpret::{const_field};
+use rustc_mir::const_eval::const_field;
 use rustc::hir::def_id::DefId;
 use rustc::mir;
 use rustc_data_structures::indexed_vec::Idx;
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index f9e717f8d456e..e9a81ee465168 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -1612,7 +1612,7 @@ fn validate_const<'a, 'tcx>(
     gid: ::rustc::mir::interpret::GlobalId<'tcx>,
     what: &str,
 ) {
-    let ecx = ::rustc_mir::interpret::mk_eval_cx(tcx, gid.instance, param_env).unwrap();
+    let ecx = ::rustc_mir::const_eval::mk_eval_cx(tcx, gid.instance, param_env).unwrap();
     let result = (|| {
         let op = ecx.const_to_op(constant)?;
         let mut todo = vec![(op, Vec::new())];
diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs
index 82cc1b7f66166..99a49122ef7e6 100644
--- a/src/librustc_mir/const_eval.rs
+++ b/src/librustc_mir/const_eval.rs
@@ -22,7 +22,7 @@ use rustc::ty::subst::Subst;
 use rustc_data_structures::indexed_vec::IndexVec;
 
 use syntax::ast::Mutability;
-use syntax::source_map::Span;
+use syntax::source_map::{Span, DUMMY_SP};
 
 use rustc::mir::interpret::{
     EvalResult, EvalError, EvalErrorKind, GlobalId,
@@ -31,17 +31,25 @@ use rustc::mir::interpret::{
 use interpret::{self,
     Place, PlaceTy, MemPlace, OpTy, Operand, Value,
     EvalContext, StackPopCleanup, MemoryKind,
+    snapshot,
 };
 
+/// Number of steps until the detector even starts doing anything.
+/// Also, a warning is shown to the user when this number is reached.
+const STEPS_UNTIL_DETECTOR_ENABLED: isize = 1_000_000;
+/// The number of steps between loop detector snapshots.
+/// Should be a power of two for performance reasons.
+const DETECTOR_SNAPSHOT_PERIOD: isize = 256;
+
 pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>(
     tcx: TyCtxt<'a, 'tcx, 'tcx>,
     instance: Instance<'tcx>,
     mir: &'mir mir::Mir<'tcx>,
     span: Span,
-) -> EvalResult<'tcx, EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>> {
+) -> EvalResult<'tcx, CompileTimeEvalContext<'a, 'mir, 'tcx>> {
     debug!("mk_borrowck_eval_cx: {:?}", instance);
     let param_env = tcx.param_env(instance.def_id());
-    let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeEvaluator, ());
+    let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new(), ());
     // insert a stack frame so any queries have the correct substs
     ecx.stack.push(interpret::Frame {
         block: mir::START_BLOCK,
@@ -60,10 +68,10 @@ pub fn mk_eval_cx<'a, 'tcx>(
     tcx: TyCtxt<'a, 'tcx, 'tcx>,
     instance: Instance<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
-) -> EvalResult<'tcx, EvalContext<'a, 'tcx, 'tcx, CompileTimeEvaluator>> {
+) -> EvalResult<'tcx, CompileTimeEvalContext<'a, 'tcx, 'tcx>> {
     debug!("mk_eval_cx: {:?}, {:?}", instance, param_env);
     let span = tcx.def_span(instance.def_id());
-    let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeEvaluator, ());
+    let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new(), ());
     let mir = ecx.load_mir(instance.def)?;
     // insert a stack frame so any queries have the correct substs
     ecx.push_stack_frame(
@@ -76,19 +84,18 @@ pub fn mk_eval_cx<'a, 'tcx>(
     Ok(ecx)
 }
 
-pub fn eval_promoted<'a, 'mir, 'tcx>(
-    ecx: &mut EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>,
+pub(crate) fn eval_promoted<'a, 'mir, 'tcx>(
+    tcx: TyCtxt<'a, 'tcx, 'tcx>,
     cid: GlobalId<'tcx>,
     mir: &'mir mir::Mir<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
 ) -> EvalResult<'tcx, OpTy<'tcx>> {
-    ecx.with_fresh_body(|ecx| {
-        eval_body_using_ecx(ecx, cid, Some(mir), param_env)
-    })
+    let mut ecx = mk_borrowck_eval_cx(tcx, cid.instance, mir, DUMMY_SP).unwrap();
+    eval_body_using_ecx(&mut ecx, cid, Some(mir), param_env)
 }
 
 pub fn op_to_const<'tcx>(
-    ecx: &EvalContext<'_, '_, 'tcx, CompileTimeEvaluator>,
+    ecx: &CompileTimeEvalContext<'_, '_, 'tcx>,
     op: OpTy<'tcx>,
     normalize: bool,
 ) -> EvalResult<'tcx, &'tcx ty::Const<'tcx>> {
@@ -128,19 +135,19 @@ fn eval_body_and_ecx<'a, 'mir, 'tcx>(
     cid: GlobalId<'tcx>,
     mir: Option<&'mir mir::Mir<'tcx>>,
     param_env: ty::ParamEnv<'tcx>,
-) -> (EvalResult<'tcx, OpTy<'tcx>>, EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>) {
+) -> (EvalResult<'tcx, OpTy<'tcx>>, CompileTimeEvalContext<'a, 'mir, 'tcx>) {
     // we start out with the best span we have
     // and try improving it down the road when more information is available
     let span = tcx.def_span(cid.instance.def_id());
     let span = mir.map(|mir| mir.span).unwrap_or(span);
-    let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeEvaluator, ());
+    let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeInterpreter::new(), ());
     let r = eval_body_using_ecx(&mut ecx, cid, mir, param_env);
     (r, ecx)
 }
 
 // Returns a pointer to where the result lives
-fn eval_body_using_ecx<'a, 'mir, 'tcx>(
-    ecx: &mut EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>,
+fn eval_body_using_ecx<'mir, 'tcx>(
+    ecx: &mut CompileTimeEvalContext<'_, 'mir, 'tcx>,
     cid: GlobalId<'tcx>,
     mir: Option<&'mir mir::Mir<'tcx>>,
     param_env: ty::ParamEnv<'tcx>,
@@ -187,17 +194,12 @@ fn eval_body_using_ecx<'a, 'mir, 'tcx>(
     Ok(ret.into())
 }
 
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct CompileTimeEvaluator;
-
 impl<'tcx> Into<EvalError<'tcx>> for ConstEvalError {
     fn into(self) -> EvalError<'tcx> {
         EvalErrorKind::MachineError(self.to_string()).into()
     }
 }
 
-impl_stable_hash_for!(struct CompileTimeEvaluator {});
-
 #[derive(Clone, Debug)]
 enum ConstEvalError {
     NeedsRfc(String),
@@ -234,14 +236,39 @@ impl Error for ConstEvalError {
     }
 }
 
-impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeEvaluator {
+// Extra machine state for CTFE, and the Machine instance
+pub struct CompileTimeInterpreter<'a, 'mir, 'tcx: 'a+'mir> {
+    /// 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,
+
+    /// Extra state to detect loops.
+    pub(super) loop_detector: snapshot::InfiniteLoopDetector<'a, 'mir, 'tcx>,
+}
+
+impl<'a, 'mir, 'tcx> CompileTimeInterpreter<'a, 'mir, 'tcx> {
+    fn new() -> Self {
+        CompileTimeInterpreter {
+            loop_detector: Default::default(),
+            steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED,
+        }
+    }
+}
+
+type CompileTimeEvalContext<'a, 'mir, 'tcx> =
+    EvalContext<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>;
+
+impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx>
+    for CompileTimeInterpreter<'a, 'mir, 'tcx>
+{
     type MemoryData = ();
     type MemoryKinds = !;
 
     const MUT_STATIC_KIND: Option<!> = None; // no mutating of statics allowed
-    const DETECT_LOOPS: bool = true;
 
-    fn find_fn<'a>(
+    fn find_fn(
         ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
         instance: ty::Instance<'tcx>,
         args: &[OpTy<'tcx>],
@@ -275,7 +302,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeEvaluator {
         }))
     }
 
-    fn call_intrinsic<'a>(
+    fn call_intrinsic(
         ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
         instance: ty::Instance<'tcx>,
         args: &[OpTy<'tcx>],
@@ -291,7 +318,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeEvaluator {
         )
     }
 
-    fn ptr_op<'a>(
+    fn ptr_op(
         _ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
         _bin_op: mir::BinOp,
         _left: Scalar,
@@ -304,14 +331,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeEvaluator {
         )
     }
 
-    fn find_foreign_static<'a>(
+    fn find_foreign_static(
         _tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
         _def_id: DefId,
     ) -> EvalResult<'tcx, &'tcx Allocation> {
         err!(ReadForeignStatic)
     }
 
-    fn box_alloc<'a>(
+    fn box_alloc(
         _ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
         _dest: PlaceTy<'tcx>,
     ) -> EvalResult<'tcx> {
@@ -319,6 +346,30 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeEvaluator {
             ConstEvalError::NeedsRfc("heap allocations via `box` keyword".to_string()).into(),
         )
     }
+
+    fn before_terminator(ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>) -> EvalResult<'tcx> {
+        {
+            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(());
+            }
+        }
+
+        let span = ecx.frame().span;
+        ecx.machine.loop_detector.observe_and_analyze(
+            &ecx.tcx,
+            span,
+            &ecx.memory,
+            &ecx.stack[..],
+        )
+    }
 }
 
 /// Project to a field of a (variant of a) const
diff --git a/src/librustc_mir/hair/pattern/mod.rs b/src/librustc_mir/hair/pattern/mod.rs
index 57519d6ad7d70..c72f878368550 100644
--- a/src/librustc_mir/hair/pattern/mod.rs
+++ b/src/librustc_mir/hair/pattern/mod.rs
@@ -16,7 +16,7 @@ mod check_match;
 pub use self::check_match::check_crate;
 pub(crate) use self::check_match::check_match;
 
-use interpret::{const_field, const_variant_index};
+use const_eval::{const_field, const_variant_index};
 
 use rustc::mir::{fmt_const_val, Field, BorrowKind, Mutability};
 use rustc::mir::interpret::{Scalar, GlobalId, ConstValue, sign_extend};
diff --git a/src/librustc_mir/interpret/cast.rs b/src/librustc_mir/interpret/cast.rs
index 83264acf76a31..c6b527f42294c 100644
--- a/src/librustc_mir/interpret/cast.rs
+++ b/src/librustc_mir/interpret/cast.rs
@@ -21,7 +21,7 @@ use rustc_apfloat::Float;
 
 use super::{EvalContext, Machine, PlaceTy, OpTy, Value};
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     fn type_is_fat_ptr(&self, ty: Ty<'tcx>) -> bool {
         match ty.sty {
             ty::RawPtr(ty::TypeAndMut { ty, .. }) |
diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs
index 740d4e038df7e..6cbe8065c7e24 100644
--- a/src/librustc_mir/interpret/eval_context.rs
+++ b/src/librustc_mir/interpret/eval_context.rs
@@ -14,7 +14,6 @@ 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
@@ -23,7 +22,6 @@ use rustc::ty::subst::{Subst, Substs};
 use rustc::ty::{self, Ty, TyCtxt, TypeFoldable};
 use rustc::ty::query::TyCtxtAt;
 use rustc_data_structures::indexed_vec::IndexVec;
-use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult};
 use rustc::mir::interpret::{
     GlobalId, Scalar, FrameInfo, AllocId,
     EvalResult, EvalErrorKind,
@@ -38,9 +36,7 @@ use super::{
     Memory, Machine
 };
 
-use super::snapshot::InfiniteLoopDetector;
-
-pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
+pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>> {
     /// Stores the `Machine` instance.
     pub machine: M,
 
@@ -48,26 +44,13 @@ pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
     pub tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
 
     /// Bounds in scope for polymorphic evaluations.
-    pub param_env: ty::ParamEnv<'tcx>,
+    pub(crate) param_env: ty::ParamEnv<'tcx>,
 
     /// The virtual memory system.
     pub memory: Memory<'a, 'mir, 'tcx, M>,
 
     /// The virtual call stack.
     pub(crate) stack: Vec<Frame<'mir, 'tcx>>,
-
-    /// The maximum number of stack frames allowed
-    pub(super) stack_limit: usize,
-
-    /// 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,
-
-    /// Extra state to detect loops.
-    /// FIXME: Move this to the CTFE machine's state, out of the general miri engine.
-    pub(super) loop_detector: InfiniteLoopDetector<'a, 'mir, 'tcx, M>,
 }
 
 /// A stack frame.
@@ -112,29 +95,6 @@ pub struct Frame<'mir, 'tcx: 'mir> {
     pub stmt: usize,
 }
 
-// Not using the macro because that does not support types depending on 'tcx
-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>) {
-
-        let Frame {
-            mir,
-            instance,
-            span,
-            return_to_block,
-            return_place,
-            locals,
-            block,
-            stmt,
-        } = self;
-
-        (mir, instance, span, return_to_block).hash_stable(hcx, hasher);
-        (return_place, locals, block, stmt).hash_stable(hcx, hasher);
-    }
-}
-
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
 pub enum StackPopCleanup {
     /// Jump to the next block in the caller, or cause UB if None (that's a function
@@ -147,21 +107,6 @@ pub enum StackPopCleanup {
     None { cleanup: bool },
 }
 
-// Can't use the macro here because that does not support named enum fields.
-impl<'a> HashStable<StableHashingContext<'a>> for StackPopCleanup {
-    fn hash_stable<W: StableHasherResult>(
-        &self,
-        hcx: &mut StableHashingContext<'a>,
-        hasher: &mut StableHasher<W>)
-    {
-        mem::discriminant(self).hash_stable(hcx, hasher);
-        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<Id=AllocId> {
@@ -189,19 +134,16 @@ impl<'tcx> LocalValue {
     }
 }
 
-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> {
+impl<'b, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> HasDataLayout
+    for &'b EvalContext<'a, 'mir, 'tcx, M>
+{
     #[inline]
     fn data_layout(&self) -> &layout::TargetDataLayout {
         &self.tcx.data_layout
     }
 }
 
-impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout
+impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> HasDataLayout
     for &'c &'b mut EvalContext<'a, 'mir, 'tcx, M>
 {
     #[inline]
@@ -210,24 +152,27 @@ impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout
     }
 }
 
-impl<'a, 'mir, 'tcx, M> layout::HasTyCtxt<'tcx> for &'a EvalContext<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>
+impl<'b, 'a, 'mir, 'tcx, M> layout::HasTyCtxt<'tcx> for &'b EvalContext<'a, 'mir, 'tcx, M>
+    where M: Machine<'a, 'mir, 'tcx>
 {
     #[inline]
-    fn tcx<'b>(&'b self) -> TyCtxt<'b, 'tcx, 'tcx> {
+    fn tcx<'d>(&'d self) -> TyCtxt<'d, 'tcx, 'tcx> {
         *self.tcx
     }
 }
 
-impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> layout::HasTyCtxt<'tcx>
-    for &'c &'b mut EvalContext<'a, 'mir, 'tcx, M> {
+impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> layout::HasTyCtxt<'tcx>
+    for &'c &'b mut EvalContext<'a, 'mir, 'tcx, M>
+{
     #[inline]
     fn tcx<'d>(&'d self) -> TyCtxt<'d, 'tcx, 'tcx> {
         *self.tcx
     }
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> LayoutOf for &'a EvalContext<'a, 'mir, 'tcx, M> {
+impl<'b, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> LayoutOf
+    for &'b EvalContext<'a, 'mir, 'tcx, M>
+{
     type Ty = Ty<'tcx>;
     type TyLayout = EvalResult<'tcx, TyLayout<'tcx>>;
 
@@ -238,8 +183,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> LayoutOf for &'a EvalContext<'a, 'm
     }
 }
 
-impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> LayoutOf
-    for &'c &'b mut EvalContext<'a, 'mir, 'tcx, M> {
+impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> LayoutOf
+    for &'c &'b mut EvalContext<'a, 'mir, 'tcx, M>
+{
     type Ty = Ty<'tcx>;
     type TyLayout = EvalResult<'tcx, TyLayout<'tcx>>;
 
@@ -249,9 +195,7 @@ impl<'c, 'b, 'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> LayoutOf
     }
 }
 
-const STEPS_UNTIL_DETECTOR_ENABLED: isize = 1_000_000;
-
-impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     pub fn new(
         tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
         param_env: ty::ParamEnv<'tcx>,
@@ -264,22 +208,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
             param_env,
             memory: Memory::new(tcx, memory_data),
             stack: Vec::new(),
-            stack_limit: tcx.sess.const_eval_stack_frame_limit,
-            loop_detector: Default::default(),
-            steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED,
         }
     }
 
-    pub(crate) fn with_fresh_body<F: FnOnce(&mut Self) -> R, R>(&mut self, f: F) -> R {
-        let stack = mem::replace(&mut self.stack, Vec::new());
-        let steps = mem::replace(&mut self.steps_since_detector_enabled,
-                                 -STEPS_UNTIL_DETECTOR_ENABLED);
-        let r = f(self);
-        self.stack = stack;
-        self.steps_since_detector_enabled = steps;
-        r
-    }
-
     pub fn memory(&self) -> &Memory<'a, 'mir, 'tcx, M> {
         &self.memory
     }
@@ -553,7 +484,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M
             self.frame_mut().locals = locals;
         }
 
-        if self.stack.len() > self.stack_limit {
+        if self.stack.len() > self.tcx.sess.const_eval_stack_frame_limit {
             err!(StackFrameLimitReached)
         } else {
             Ok(())
diff --git a/src/librustc_mir/interpret/intrinsics.rs b/src/librustc_mir/interpret/intrinsics.rs
index 48085c2145418..d2f274231c170 100644
--- a/src/librustc_mir/interpret/intrinsics.rs
+++ b/src/librustc_mir/interpret/intrinsics.rs
@@ -46,7 +46,7 @@ fn numeric_intrinsic<'tcx>(
     Ok(Scalar::from_uint(bits_out, size))
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     /// Returns whether emulation happened.
     pub fn emulate_intrinsic(
         &mut self,
diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs
index f33cb4791029a..1eb0280409527 100644
--- a/src/librustc_mir/interpret/machine.rs
+++ b/src/librustc_mir/interpret/machine.rs
@@ -20,20 +20,22 @@ use rustc::ty::{self, layout::TyLayout, query::TyCtxtAt};
 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 {
+/// and some use case dependent behaviour can instead be applied.
+/// FIXME: We should be able to get rid of the 'a here if we can get rid of the 'a in
+/// `snapshot::EvalSnapshot`.
+pub trait Machine<'a, 'mir, 'tcx>: Sized {
     /// Additional data that can be accessed via the Memory
-    type MemoryData: Clone + Eq;
+    type MemoryData;
 
     /// Additional memory kinds a machine wishes to distinguish from the builtin ones
-    type MemoryKinds: ::std::fmt::Debug + Copy + Clone + Eq;
+    type MemoryKinds: ::std::fmt::Debug + Copy + Eq;
 
     /// The memory kind to use for mutated statics -- or None if those are not supported.
     const MUT_STATIC_KIND: Option<Self::MemoryKinds>;
 
-    /// Whether to attempt to detect infinite loops (any kind of infinite
-    /// execution, really).
-    const DETECT_LOOPS: bool;
+    /// Called before a basic block terminator is executed.
+    /// You can use this to detect endlessly running programs.
+    fn before_terminator(ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>) -> EvalResult<'tcx>;
 
     /// Entry point to all function calls.
     ///
@@ -45,7 +47,7 @@ pub trait Machine<'mir, 'tcx>: Clone + Eq {
     /// nor just jump to `ret`, but instead push their own stack frame.)
     /// Passing `dest`and `ret` in the same `Option` proved very annoying when only one of them
     /// was used.
-    fn find_fn<'a>(
+    fn find_fn(
         ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
         instance: ty::Instance<'tcx>,
         args: &[OpTy<'tcx>],
@@ -55,7 +57,7 @@ pub trait Machine<'mir, 'tcx>: Clone + Eq {
 
     /// Directly process an intrinsic without pushing a stack frame.
     /// If this returns successfully, the engine will take care of jumping to the next block.
-    fn call_intrinsic<'a>(
+    fn call_intrinsic(
         ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
         instance: ty::Instance<'tcx>,
         args: &[OpTy<'tcx>],
@@ -66,7 +68,7 @@ pub trait Machine<'mir, 'tcx>: Clone + Eq {
     /// This can be called multiple times for the same static item and should return consistent
     /// results.  Once the item is *written* the first time, as usual for statics a copy is
     /// made and this function is not called again.
-    fn find_foreign_static<'a>(
+    fn find_foreign_static(
         tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
         def_id: DefId,
     ) -> EvalResult<'tcx, &'tcx Allocation>;
@@ -75,7 +77,7 @@ pub trait Machine<'mir, 'tcx>: Clone + Eq {
     /// value, and for the `Offset` operation that is inherently about pointers.
     ///
     /// Returns a (value, overflowed) pair if the operation succeeded
-    fn ptr_op<'a>(
+    fn ptr_op(
         ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
         bin_op: mir::BinOp,
         left: Scalar,
@@ -87,13 +89,13 @@ pub trait Machine<'mir, 'tcx>: Clone + Eq {
     /// Heap allocations via the `box` keyword
     ///
     /// Returns a pointer to the allocated memory
-    fn box_alloc<'a>(
+    fn box_alloc(
         ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
         dest: PlaceTy<'tcx>,
     ) -> EvalResult<'tcx>;
 
     /// Execute a validation operation
-    fn validation_op<'a>(
+    fn validation_op(
         _ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
         _op: ::rustc::mir::ValidationOp,
         _operand: &::rustc::mir::ValidationOperand<'tcx, ::rustc::mir::Place<'tcx>>,
diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs
index fcb310f704567..b5eb06b82ff75 100644
--- a/src/librustc_mir/interpret/memory.rs
+++ b/src/librustc_mir/interpret/memory.rs
@@ -39,8 +39,9 @@ pub enum MemoryKind<T> {
     Machine(T),
 }
 
-#[derive(Clone)]
-pub struct Memory<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
+// `Memory` has to depend on the `Machine` because some of its operations
+// (e.g. `get`) call a `Machine` hook.
+pub struct Memory<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>> {
     /// Additional data required by the Machine
     pub data: M::MemoryData,
 
@@ -56,16 +57,19 @@ pub struct Memory<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
     /// that do not exist any more.
     dead_alloc_map: FxHashMap<AllocId, (Size, Align)>,
 
-    pub tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
+    /// Lets us implement `HasDataLayout`, which is awfully convenient.
+    pub(super) tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for &'a Memory<'a, 'mir, 'tcx, M> {
+impl<'b, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> HasDataLayout
+    for &'b Memory<'a, 'mir, 'tcx, M>
+{
     #[inline]
     fn data_layout(&self) -> &TargetDataLayout {
         &self.tcx.data_layout
     }
 }
-impl<'a, 'b, 'c, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout
+impl<'a, 'b, 'c, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> HasDataLayout
     for &'b &'c mut Memory<'a, 'mir, 'tcx, M>
 {
     #[inline]
@@ -74,7 +78,23 @@ impl<'a, 'b, 'c, 'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout
     }
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
+// FIXME: Really we shouldnt clone memory, ever. Snapshot machinery should instad
+// carefully copy only the reachable parts.
+impl<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>>
+    Clone for Memory<'a, 'mir, 'tcx, M>
+    where M::MemoryData: Clone
+{
+    fn clone(&self) -> Self {
+        Memory {
+            data: self.data.clone(),
+            alloc_map: self.alloc_map.clone(),
+            dead_alloc_map: self.dead_alloc_map.clone(),
+            tcx: self.tcx,
+        }
+    }
+}
+
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
     pub fn new(tcx: TyCtxtAt<'a, 'tcx, 'tcx>, data: M::MemoryData) -> Self {
         Memory {
             data,
@@ -279,7 +299,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
 }
 
 /// Allocation accessors
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
     /// Helper function to obtain the global (tcx) allocation for a static
     fn get_static_alloc(
         tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
@@ -491,7 +511,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
 }
 
 /// Byte accessors
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
     /// The last argument controls whether we error out when there are undefined
     /// or pointer bytes.  You should never call this, call `get_bytes` or
     /// `get_bytes_with_undef_and_ptr` instead,
@@ -564,7 +584,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
 }
 
 /// Reading and writing
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
     /// mark an allocation as static and initialized, either mutable or not
     pub fn intern_static(
         &mut self,
@@ -877,7 +897,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
 }
 
 /// Relocations
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
     /// Return all relocations overlapping with the given ptr-offset pair.
     fn relocations(
         &self,
@@ -950,7 +970,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
 }
 
 /// Undefined bytes
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
     // FIXME(solson): This is a very naive, slow version.
     fn copy_undef_mask(
         &mut self,
diff --git a/src/librustc_mir/interpret/mod.rs b/src/librustc_mir/interpret/mod.rs
index 1e8de02923240..6f07c2996b9cb 100644
--- a/src/librustc_mir/interpret/mod.rs
+++ b/src/librustc_mir/interpret/mod.rs
@@ -17,7 +17,7 @@ mod operand;
 mod machine;
 mod memory;
 mod operator;
-mod snapshot;
+pub(crate) mod snapshot; // for const_eval
 mod step;
 mod terminator;
 mod traits;
@@ -35,16 +35,3 @@ pub use self::memory::{Memory, MemoryKind};
 pub use self::machine::Machine;
 
 pub use self::operand::{Value, ValTy, Operand, OpTy};
-
-// reexports for compatibility
-pub use const_eval::{
-    eval_promoted,
-    mk_borrowck_eval_cx,
-    mk_eval_cx,
-    CompileTimeEvaluator,
-    const_to_allocation_provider,
-    const_eval_provider,
-    const_field,
-    const_variant_index,
-    op_to_const,
-};
diff --git a/src/librustc_mir/interpret/operand.rs b/src/librustc_mir/interpret/operand.rs
index c7f84f7683953..fef2f916b4156 100644
--- a/src/librustc_mir/interpret/operand.rs
+++ b/src/librustc_mir/interpret/operand.rs
@@ -82,11 +82,6 @@ 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)]
@@ -132,11 +127,6 @@ 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
@@ -206,7 +196,7 @@ fn from_known_layout<'tcx>(
     }
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     /// Try reading a value in memory; this is interesting particularily for ScalarPair.
     /// Return None if the layout does not permit loading this as a value.
     pub(super) fn try_read_value_from_mplace(
diff --git a/src/librustc_mir/interpret/operator.rs b/src/librustc_mir/interpret/operator.rs
index d07d37d43b13e..dd6ee374c0fac 100644
--- a/src/librustc_mir/interpret/operator.rs
+++ b/src/librustc_mir/interpret/operator.rs
@@ -18,7 +18,7 @@ use rustc::mir::interpret::{EvalResult, Scalar};
 use super::{EvalContext, PlaceTy, Value, Machine, ValTy};
 
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     /// Applies the binary operation `op` to the two operands and writes a tuple of the result
     /// and a boolean signifying the potential overflow to the destination.
     pub fn binop_with_overflow(
@@ -47,7 +47,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     }
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     fn binary_char_op(
         &self,
         bin_op: mir::BinOp,
diff --git a/src/librustc_mir/interpret/place.rs b/src/librustc_mir/interpret/place.rs
index e3f7f26f53efd..80229f66765f2 100644
--- a/src/librustc_mir/interpret/place.rs
+++ b/src/librustc_mir/interpret/place.rs
@@ -13,13 +13,10 @@
 //! All high-level functions to write to memory work on places as destinations.
 
 use std::convert::TryFrom;
-use std::mem;
 
-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::stable_hasher::{HashStable, StableHasher, StableHasherResult};
 
 use rustc::mir::interpret::{
     GlobalId, AllocId, Scalar, EvalResult, Pointer, ScalarMaybeUndef, PointerArithmetic
@@ -39,12 +36,6 @@ pub struct MemPlace<Id=AllocId> {
     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<Id=AllocId> {
     /// A place referring to a value allocated in the `Memory` system.
@@ -58,23 +49,6 @@ pub enum Place<Id=AllocId> {
     },
 }
 
-// Can't use the macro here because that does not support named enum fields.
-impl<'a> HashStable<StableHashingContext<'a>> for Place {
-    fn hash_stable<W: StableHasherResult>(
-        &self, hcx: &mut StableHashingContext<'a>,
-        hasher: &mut StableHasher<W>)
-    {
-        mem::discriminant(self).hash_stable(hcx, hasher);
-        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,
@@ -255,7 +229,7 @@ impl<'tcx> PlaceTy<'tcx> {
     }
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     /// Take a value, which represents a (thin or fat) reference, and make it a place.
     /// Alignment is just based on the type.  This is the inverse of `MemPlace::to_ref`.
     pub fn ref_to_mplace(
diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs
index 0e8466905eb76..18ca284811080 100644
--- a/src/librustc_mir/interpret/snapshot.rs
+++ b/src/librustc_mir/interpret/snapshot.rs
@@ -2,7 +2,11 @@
 //! 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 std::mem;
 
 use rustc::ich::{StableHashingContext, StableHashingContextProvider};
 use rustc::mir;
@@ -21,9 +25,11 @@ use syntax::ast::Mutability;
 use syntax::source_map::Span;
 
 use super::eval_context::{LocalValue, StackPopCleanup};
-use super::{Frame, Memory, Machine, Operand, MemPlace, Place, Value};
+use super::{Frame, Memory, Operand, MemPlace, Place, Value};
+use const_eval::CompileTimeInterpreter;
 
-pub(super) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
+#[derive(Default)]
+pub(crate) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir> {
     /// The set of all `EvalSnapshot` *hashes* observed by this detector.
     ///
     /// When a collision occurs in this table, we store the full snapshot in
@@ -35,47 +41,36 @@ pub(super) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mi
     /// 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(),
-        }
-    }
+    snapshots: FxHashSet<EvalSnapshot<'a, 'mir, 'tcx>>,
 }
 
-impl<'a, 'mir, 'tcx, M> InfiniteLoopDetector<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
-          'tcx: 'a + 'mir,
+impl<'a, 'mir, 'tcx> InfiniteLoopDetector<'a, 'mir, 'tcx>
 {
-    /// 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(
+    pub fn observe_and_analyze<'b>(
         &mut self,
         tcx: &TyCtxt<'b, 'tcx, 'tcx>,
-        memory: &Memory<'a, 'mir, 'tcx, M>,
+        span: Span,
+        memory: &Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>,
         stack: &[Frame<'mir, 'tcx>],
     ) -> EvalResult<'tcx, ()> {
-
+        // Compute stack's hash before copying anything
         let mut hcx = tcx.get_stable_hashing_context();
         let mut hasher = StableHasher::<u64>::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(EvalSnapshot::new(memory, stack)) {
@@ -179,7 +174,7 @@ impl<'a, Ctx> Snapshot<'a, Ctx> for AllocId
 
 impl_snapshot_for!(struct Pointer {
     alloc_id,
-    offset -> *offset,
+    offset -> *offset, // just copy offset verbatim
 });
 
 impl<'a, Ctx> Snapshot<'a, Ctx> for Scalar
@@ -203,12 +198,34 @@ impl_snapshot_for!(enum ScalarMaybeUndef {
     Undef,
 });
 
+impl_stable_hash_for!(struct ::interpret::MemPlace {
+    ptr,
+    align,
+    extra,
+});
 impl_snapshot_for!(struct MemPlace {
     ptr,
     extra,
-    align -> *align,
+    align -> *align, // just copy alignment verbatim
 });
 
+// Can't use the macro here because that does not support named enum fields.
+impl<'a> HashStable<StableHashingContext<'a>> for Place {
+    fn hash_stable<W: StableHasherResult>(
+        &self, hcx: &mut StableHashingContext<'a>,
+        hasher: &mut StableHasher<W>)
+    {
+        mem::discriminant(self).hash_stable(hcx, hasher);
+        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);
+            },
+        }
+    }
+}
 impl<'a, Ctx> Snapshot<'a, Ctx> for Place
     where Ctx: SnapshotContext<'a>,
 {
@@ -226,16 +243,28 @@ impl<'a, Ctx> Snapshot<'a, Ctx> for Place
     }
 }
 
+impl_stable_hash_for!(enum ::interpret::Value {
+    Scalar(x),
+    ScalarPair(x, y),
+});
 impl_snapshot_for!(enum Value {
     Scalar(s),
     ScalarPair(s, t),
 });
 
+impl_stable_hash_for!(enum ::interpret::Operand {
+    Immediate(x),
+    Indirect(x),
+});
 impl_snapshot_for!(enum Operand {
     Immediate(v),
     Indirect(m),
 });
 
+impl_stable_hash_for!(enum ::interpret::LocalValue {
+    Dead,
+    Live(x),
+});
 impl_snapshot_for!(enum LocalValue {
     Live(v),
     Dead,
@@ -280,6 +309,21 @@ impl<'a, Ctx> Snapshot<'a, Ctx> for &'a Allocation
     }
 }
 
+// Can't use the macro here because that does not support named enum fields.
+impl<'a> HashStable<StableHashingContext<'a>> for StackPopCleanup {
+    fn hash_stable<W: StableHasherResult>(
+        &self,
+        hcx: &mut StableHashingContext<'a>,
+        hasher: &mut StableHasher<W>)
+    {
+        mem::discriminant(self).hash_stable(hcx, hasher);
+        match self {
+            StackPopCleanup::Goto(ref block) => block.hash_stable(hcx, hasher),
+            StackPopCleanup::None { cleanup } => cleanup.hash_stable(hcx, hasher),
+        }
+    }
+}
+
 #[derive(Eq, PartialEq)]
 struct FrameSnapshot<'a, 'tcx: 'a> {
     instance: &'a ty::Instance<'tcx>,
@@ -291,6 +335,28 @@ struct FrameSnapshot<'a, 'tcx: 'a> {
     stmt: usize,
 }
 
+// Not using the macro because that does not support types depending on two lifetimes
+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>) {
+
+        let Frame {
+            mir,
+            instance,
+            span,
+            return_to_block,
+            return_place,
+            locals,
+            block,
+            stmt,
+        } = self;
+
+        (mir, instance, span, return_to_block).hash_stable(hcx, hasher);
+        (return_place, locals, block, stmt).hash_stable(hcx, hasher);
+    }
+}
 impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx>
     where Ctx: SnapshotContext<'a>,
 {
@@ -320,22 +386,8 @@ impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx>
     }
 }
 
-#[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>,
+impl<'a, 'b, 'mir, 'tcx: 'a+'mir> SnapshotContext<'b>
+    for Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>
 {
     fn resolve(&'b self, id: &AllocId) -> Option<&'b Allocation> {
         self.get(*id).ok()
@@ -343,16 +395,17 @@ impl<'a, 'b, 'mir, 'tcx, M> SnapshotContext<'b> for Memory<'a, 'mir, 'tcx, M>
 }
 
 /// The virtual machine state during const-evaluation at a given point in time.
-struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
-    memory: Memory<'a, 'mir, 'tcx, M>,
+/// We assume the `CompileTimeInterpreter` has no interesting extra state that
+/// is worth considering here.
+struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir> {
+    memory: Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>,
     stack: Vec<Frame<'mir, 'tcx>>,
 }
 
-impl<'a, 'mir, 'tcx, M> EvalSnapshot<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
+impl<'a, 'mir, 'tcx: 'a + 'mir> EvalSnapshot<'a, 'mir, 'tcx>
 {
     fn new(
-        memory: &Memory<'a, 'mir, 'tcx, M>,
+        memory: &Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>,
         stack: &[Frame<'mir, 'tcx>]
     ) -> Self {
         EvalSnapshot {
@@ -361,16 +414,17 @@ impl<'a, 'mir, 'tcx, M> EvalSnapshot<'a, 'mir, 'tcx, M>
         }
     }
 
-    fn snapshot<'b: 'a>(&'b self)
-        -> (MemorySnapshot<'b, 'mir, 'tcx, M>, Vec<FrameSnapshot<'a, 'tcx>>)
+    // Used to compare two snapshots
+    fn snapshot(&'b self)
+        -> Vec<FrameSnapshot<'b, 'tcx>>
     {
-        let EvalSnapshot{ memory, stack } = self;
-        (memory.snapshot(), stack.iter().map(|frame| frame.snapshot(memory)).collect())
+        // Start with the stack, iterate and recursively snapshot
+        self.stack.iter().map(|frame| frame.snapshot(&self.memory)).collect()
     }
+
 }
 
-impl<'a, 'mir, 'tcx, M> Hash for EvalSnapshot<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
+impl<'a, 'mir, 'tcx> Hash for EvalSnapshot<'a, 'mir, 'tcx>
 {
     fn hash<H: Hasher>(&self, state: &mut H) {
         // Implement in terms of hash stable, so that k1 == k2 -> hash(k1) == hash(k2)
@@ -383,28 +437,29 @@ impl<'a, 'mir, 'tcx, M> Hash for EvalSnapshot<'a, 'mir, 'tcx, M>
 
 // Not using the macro because we need special handling for `memory`, which the macro
 // does not support at the same time as the extra bounds on the type.
-impl<'a, 'b, 'mir, 'tcx, M> HashStable<StableHashingContext<'b>>
-    for EvalSnapshot<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
+impl<'a, 'b, 'mir, 'tcx> HashStable<StableHashingContext<'b>>
+    for EvalSnapshot<'a, 'mir, 'tcx>
 {
     fn hash_stable<W: StableHasherResult>(
         &self,
         hcx: &mut StableHashingContext<'b>,
         hasher: &mut StableHasher<W>)
     {
+        // Not hashing memory: Avoid hashing memory all the time during execution
         let EvalSnapshot{ memory: _, stack } = self;
         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> Eq for EvalSnapshot<'a, 'mir, 'tcx>
 {}
 
-impl<'a, 'mir, 'tcx, M> PartialEq for EvalSnapshot<'a, 'mir, 'tcx, M>
-    where M: Machine<'mir, 'tcx>,
+impl<'a, 'mir, 'tcx> PartialEq for EvalSnapshot<'a, 'mir, 'tcx>
 {
     fn eq(&self, other: &Self) -> bool {
+        // FIXME: This looks to be a *ridicolously expensive* comparison operation.
+        // Doesn't this make tons of copies?  Either `snapshot` is very badly named,
+        // or it does!
         self.snapshot() == other.snapshot()
     }
 }
diff --git a/src/librustc_mir/interpret/step.rs b/src/librustc_mir/interpret/step.rs
index 5bdaf6ba72963..180c8f211a6a0 100644
--- a/src/librustc_mir/interpret/step.rs
+++ b/src/librustc_mir/interpret/step.rs
@@ -45,45 +45,7 @@ fn binop_right_homogeneous(op: mir::BinOp) -> bool {
     }
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
-    pub fn inc_step_counter_and_detect_loops(&mut self) -> EvalResult<'tcx, ()> {
-        /// The number of steps between loop detector snapshots.
-        /// Should be a power of two for performance reasons.
-        const DETECTOR_SNAPSHOT_PERIOD: isize = 256;
-
-        {
-            let steps = &mut self.steps_since_detector_enabled;
-
-            *steps += 1;
-            if *steps < 0 {
-                return Ok(());
-            }
-
-            *steps %= DETECTOR_SNAPSHOT_PERIOD;
-            if *steps != 0 {
-                return Ok(());
-            }
-        }
-
-        if !M::DETECT_LOOPS {
-            return Ok(());
-        }
-
-        if self.loop_detector.is_empty() {
-            // First run of the loop detector
-
-            // FIXME(#49980): make this warning a lint
-            self.tcx.sess.span_warn(self.frame().span,
-                "Constant evaluating a complex constant, this might take some time");
-        }
-
-        self.loop_detector.observe_and_analyze(
-            &self.tcx,
-            &self.memory,
-            &self.stack[..],
-        )
-    }
-
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     pub fn run(&mut self) -> EvalResult<'tcx> {
         while self.step()? {}
         Ok(())
@@ -108,7 +70,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
             return Ok(true);
         }
 
-        self.inc_step_counter_and_detect_loops()?;
+        M::before_terminator(self)?;
 
         let terminator = basic_block.terminator();
         assert_eq!(old_frames, self.cur_frame());
diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs
index 7ce96b1f62626..c7ed69e0cb66d 100644
--- a/src/librustc_mir/interpret/terminator.rs
+++ b/src/librustc_mir/interpret/terminator.rs
@@ -20,7 +20,7 @@ use super::{
     EvalContext, Machine, Value, OpTy, Place, PlaceTy, Operand, StackPopCleanup
 };
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     #[inline]
     pub fn goto_block(&mut self, target: Option<mir::BasicBlock>) -> EvalResult<'tcx> {
         if let Some(target) = target {
diff --git a/src/librustc_mir/interpret/traits.rs b/src/librustc_mir/interpret/traits.rs
index 0e09f65f0a8ea..5ea588b957a4f 100644
--- a/src/librustc_mir/interpret/traits.rs
+++ b/src/librustc_mir/interpret/traits.rs
@@ -16,7 +16,7 @@ use syntax::ast::Mutability;
 
 use super::{EvalContext, Machine, MemoryKind};
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     /// Creates a dynamic vtable for the given type and vtable origin. This is used only for
     /// objects.
     ///
diff --git a/src/librustc_mir/interpret/validity.rs b/src/librustc_mir/interpret/validity.rs
index 8292869ca588e..f816983ab47a2 100644
--- a/src/librustc_mir/interpret/validity.rs
+++ b/src/librustc_mir/interpret/validity.rs
@@ -95,7 +95,7 @@ fn path_format(path: &Vec<PathElem>) -> String {
     out
 }
 
-impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
     fn validate_scalar(
         &self,
         value: ScalarMaybeUndef,
diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs
index 1594755b4ab87..4546e0bf253c3 100644
--- a/src/librustc_mir/lib.rs
+++ b/src/librustc_mir/lib.rs
@@ -95,7 +95,7 @@ pub fn provide(providers: &mut Providers) {
     borrow_check::provide(providers);
     shim::provide(providers);
     transform::provide(providers);
-    providers.const_eval = interpret::const_eval_provider;
+    providers.const_eval = const_eval::const_eval_provider;
     providers.check_match = hair::pattern::check_match;
 }
 
diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs
index aaf4cf69b3a4a..e2a6cee054970 100644
--- a/src/librustc_mir/transform/const_prop.rs
+++ b/src/librustc_mir/transform/const_prop.rs
@@ -21,8 +21,8 @@ use rustc::mir::interpret::{
     ConstEvalErr, EvalErrorKind, ScalarMaybeUndef, Scalar, GlobalId, EvalResult
 };
 use rustc::ty::{TyCtxt, self, Instance};
-use interpret::{EvalContext, CompileTimeEvaluator, eval_promoted, mk_borrowck_eval_cx};
-use interpret::{self, Value, OpTy, MemoryKind};
+use interpret::{self, EvalContext, Value, OpTy, MemoryKind};
+use const_eval::{CompileTimeInterpreter, eval_promoted, mk_borrowck_eval_cx};
 use transform::{MirPass, MirSource};
 use syntax::source_map::{Span, DUMMY_SP};
 use rustc::ty::subst::Substs;
@@ -68,9 +68,9 @@ impl MirPass for ConstProp {
 type Const<'tcx> = (OpTy<'tcx>, Span);
 
 /// Finds optimization opportunities on the MIR.
-struct ConstPropagator<'b, 'a, 'tcx:'a+'b> {
-    ecx: EvalContext<'a, 'b, 'tcx, CompileTimeEvaluator>,
-    mir: &'b Mir<'tcx>,
+struct ConstPropagator<'a, 'mir, 'tcx:'a+'mir> {
+    ecx: EvalContext<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>,
+    mir: &'mir Mir<'tcx>,
     tcx: TyCtxt<'a, 'tcx, 'tcx>,
     source: MirSource,
     places: IndexVec<Local, Option<Const<'tcx>>>,
@@ -101,12 +101,12 @@ impl<'a, 'b, 'tcx> HasTyCtxt<'tcx> for &'a ConstPropagator<'a, 'b, 'tcx> {
     }
 }
 
-impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
+impl<'a, 'mir, 'tcx> ConstPropagator<'a, 'mir, 'tcx> {
     fn new(
-        mir: &'b Mir<'tcx>,
+        mir: &'mir Mir<'tcx>,
         tcx: TyCtxt<'a, 'tcx, 'tcx>,
         source: MirSource,
-    ) -> ConstPropagator<'b, 'a, 'tcx> {
+    ) -> ConstPropagator<'a, 'mir, 'tcx> {
         let param_env = tcx.param_env(source.def_id);
         let substs = Substs::identity_for_item(tcx, source.def_id);
         let instance = Instance::new(source.def_id, substs);
@@ -310,7 +310,7 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
                 // cannot use `const_eval` here, because that would require having the MIR
                 // for the current function available, but we're producing said MIR right now
                 let res = self.use_ecx(source_info, |this| {
-                    eval_promoted(&mut this.ecx, cid, this.mir, this.param_env)
+                    eval_promoted(this.tcx, cid, this.mir, this.param_env)
                 })?;
                 trace!("evaluated promoted {:?} to {:?}", promoted, res);
                 Some((res, source_info.span))