From 169f7911e9852dcd047d0f0c144ce3b18d77f946 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 20 Sep 2018 10:12:21 +0200 Subject: [PATCH 1/7] move CTFE engine snapshot state out of miri engine into CTFE machine instance --- src/librustc_mir/const_eval.rs | 102 +++++++++---- src/librustc_mir/interpret/cast.rs | 2 +- src/librustc_mir/interpret/eval_context.rs | 107 +++---------- src/librustc_mir/interpret/intrinsics.rs | 2 +- src/librustc_mir/interpret/machine.rs | 28 ++-- src/librustc_mir/interpret/memory.rs | 39 +++-- src/librustc_mir/interpret/mod.rs | 2 +- src/librustc_mir/interpret/operand.rs | 12 +- src/librustc_mir/interpret/operator.rs | 4 +- src/librustc_mir/interpret/place.rs | 28 +--- src/librustc_mir/interpret/snapshot.rs | 170 ++++++++++++++------- src/librustc_mir/interpret/step.rs | 42 +---- src/librustc_mir/interpret/terminator.rs | 2 +- src/librustc_mir/interpret/traits.rs | 2 +- src/librustc_mir/interpret/validity.rs | 2 +- src/librustc_mir/transform/const_prop.rs | 4 +- 16 files changed, 266 insertions(+), 282 deletions(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index 82cc1b7f66166..34237a557305c 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,6 +31,7 @@ use rustc::mir::interpret::{ use interpret::{self, Place, PlaceTy, MemPlace, OpTy, Operand, Value, EvalContext, StackPopCleanup, MemoryKind, + snapshot, }; pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>( @@ -38,10 +39,10 @@ pub fn mk_borrowck_eval_cx<'a, 'mir, '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, CompileTimeEvaluator::new(), ()); // insert a stack frame so any queries have the correct substs ecx.stack.push(interpret::Frame { block: mir::START_BLOCK, @@ -60,10 +61,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, CompileTimeEvaluator::new(), ()); let mir = ecx.load_mir(instance.def)?; // insert a stack frame so any queries have the correct substs ecx.push_stack_frame( @@ -77,18 +78,17 @@ pub fn mk_eval_cx<'a, 'tcx>( } pub fn eval_promoted<'a, 'mir, 'tcx>( - ecx: &mut EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>, + 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 +128,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, CompileTimeEvaluator::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 +187,12 @@ fn eval_body_using_ecx<'a, 'mir, 'tcx>( Ok(ret.into()) } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct CompileTimeEvaluator; - impl<'tcx> Into> 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 +229,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 CompileTimeEvaluator<'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> CompileTimeEvaluator<'a, 'mir, 'tcx> { + fn new() -> Self { + CompileTimeEvaluator { + loop_detector: Default::default(), + steps_since_detector_enabled: -snapshot::STEPS_UNTIL_DETECTOR_ENABLED, + } + } +} + +type CompileTimeEvalContext<'a, 'mir, 'tcx> = + EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator<'a, 'mir, 'tcx>>; + +impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx> + for CompileTimeEvaluator<'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 +295,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 +311,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 +324,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 +339,36 @@ 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 %= snapshot::DETECTOR_SNAPSHOT_PERIOD; + if *steps != 0 { + return Ok(()); + } + } + + if ecx.machine.loop_detector.is_empty() { + // First run of the loop detector + + // FIXME(#49980): make this warning a lint + ecx.tcx.sess.span_warn(ecx.frame().span, + "Constant evaluating a complex constant, this might take some time"); + } + + ecx.machine.loop_detector.observe_and_analyze( + &ecx.tcx, + &ecx.memory, + &ecx.stack[..], + ) + } } /// Project to a field of a (variant of a) const 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..91990551b5c9b 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, @@ -55,19 +51,6 @@ pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> { /// The virtual call stack. pub(crate) stack: Vec>, - - /// 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> for Frame<'mir, 'tcx> { - fn hash_stable( - &self, - hcx: &mut StableHashingContext<'a>, - hasher: &mut StableHasher) { - - 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> for StackPopCleanup { - fn hash_stable( - &self, - hcx: &mut StableHashingContext<'a>, - hasher: &mut StableHasher) - { - 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 { @@ -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 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; - /// 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..7e843fd1ddea9 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -39,8 +39,9 @@ pub enum MemoryKind { 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, @@ -59,13 +60,15 @@ pub struct Memory<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> { pub 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 +77,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 +298,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 +510,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 +583,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 +896,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 +969,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..870ce4574e297 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; 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 { pub extra: Option>, } -impl_stable_hash_for!(struct ::interpret::MemPlace { - ptr, - align, - extra, -}); - #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum Place { /// A place referring to a value allocated in the `Memory` system. @@ -58,23 +49,6 @@ pub enum Place { }, } -// Can't use the macro here because that does not support named enum fields. -impl<'a> HashStable> for Place { - fn hash_stable( - &self, hcx: &mut StableHashingContext<'a>, - hasher: &mut StableHasher) - { - 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..89287d5871f24 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,18 @@ 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>> { +use super::{Frame, Memory, Operand, MemPlace, Place, Value}; +use const_eval::CompileTimeEvaluator; + +/// Number of steps until the detector even starts doing anything. +/// Also, a warning is shown to the user when this number is reached. +pub(crate) 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. +pub(crate) const DETECTOR_SNAPSHOT_PERIOD: isize = 256; + +#[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,34 +48,20 @@ 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>, -} - -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>, } -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>, + memory: &Memory<'a, 'mir, 'tcx, CompileTimeEvaluator<'a, 'mir, 'tcx>>, stack: &[Frame<'mir, 'tcx>], ) -> EvalResult<'tcx, ()> { @@ -179,7 +178,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 +202,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> for Place { + fn hash_stable( + &self, hcx: &mut StableHashingContext<'a>, + hasher: &mut StableHasher) + { + 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 +247,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 +313,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> for StackPopCleanup { + fn hash_stable( + &self, + hcx: &mut StableHashingContext<'a>, + hasher: &mut StableHasher) + { + 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 +339,28 @@ struct FrameSnapshot<'a, 'tcx: 'a> { stmt: usize, } +// Not using the macro because that does not support types depending on 'tcx +impl<'a, 'mir, 'tcx: 'mir> HashStable> for Frame<'mir, 'tcx> { + fn hash_stable( + &self, + hcx: &mut StableHashingContext<'a>, + hasher: &mut StableHasher) { + + 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 +390,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, CompileTimeEvaluator<'a, 'mir, 'tcx>> { fn resolve(&'b self, id: &AllocId) -> Option<&'b Allocation> { self.get(*id).ok() @@ -343,16 +399,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 `CompileTimeEvaluator` has no interesting extra state that +/// is worth considering here. +struct EvalSnapshot<'a, 'mir, 'tcx: 'a + 'mir> { + memory: Memory<'a, 'mir, 'tcx, CompileTimeEvaluator<'a, 'mir, 'tcx>>, stack: Vec>, } -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, CompileTimeEvaluator<'a, 'mir, 'tcx>>, stack: &[Frame<'mir, 'tcx>] ) -> Self { EvalSnapshot { @@ -361,16 +418,17 @@ impl<'a, 'mir, 'tcx, M> EvalSnapshot<'a, 'mir, 'tcx, M> } } - fn snapshot<'b: 'a>(&'b self) - -> (MemorySnapshot<'b, 'mir, 'tcx, M>, Vec>) + // Used to compare two snapshots + fn snapshot(&'b self) + -> Vec> { - 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(&self, state: &mut H) { // Implement in terms of hash stable, so that k1 == k2 -> hash(k1) == hash(k2) @@ -383,26 +441,24 @@ 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> - for EvalSnapshot<'a, 'mir, 'tcx, M> - where M: Machine<'mir, 'tcx>, +impl<'a, 'b, 'mir, 'tcx> HashStable> + for EvalSnapshot<'a, 'mir, 'tcx> { fn hash_stable( &self, hcx: &mut StableHashingContext<'b>, hasher: &mut StableHasher) { + // 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 { 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) -> 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) -> 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/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs index aaf4cf69b3a4a..e48af71b646b7 100644 --- a/src/librustc_mir/transform/const_prop.rs +++ b/src/librustc_mir/transform/const_prop.rs @@ -69,7 +69,7 @@ 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>, + ecx: EvalContext<'a, 'b, 'tcx, CompileTimeEvaluator<'a, 'b, 'tcx>>, mir: &'b Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>, source: MirSource, @@ -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)) From 018d128325da01a236f94afac0e1b40ce1865e84 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 20 Sep 2018 10:17:14 +0200 Subject: [PATCH 2/7] rename evaluator -> interpreter to make eddyb happy --- src/librustc_codegen_llvm/mir/constant.rs | 2 +- src/librustc_lint/builtin.rs | 2 +- src/librustc_mir/const_eval.rs | 16 ++++++++-------- src/librustc_mir/hair/pattern/mod.rs | 2 +- src/librustc_mir/interpret/mod.rs | 13 ------------- src/librustc_mir/interpret/snapshot.rs | 12 ++++++------ src/librustc_mir/lib.rs | 2 +- src/librustc_mir/transform/const_prop.rs | 6 +++--- 8 files changed, 21 insertions(+), 34 deletions(-) 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 34237a557305c..0015f62a8f02a 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -42,7 +42,7 @@ pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>( ) -> 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::new(), ()); + 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, @@ -64,7 +64,7 @@ pub fn mk_eval_cx<'a, 'tcx>( ) -> 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::new(), ()); + 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( @@ -133,7 +133,7 @@ fn eval_body_and_ecx<'a, 'mir, 'tcx>( // 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::new(), ()); + 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) } @@ -230,7 +230,7 @@ impl Error for ConstEvalError { } // Extra machine state for CTFE, and the Machine instance -pub struct CompileTimeEvaluator<'a, 'mir, 'tcx: 'a+'mir> { +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 @@ -241,9 +241,9 @@ pub struct CompileTimeEvaluator<'a, 'mir, 'tcx: 'a+'mir> { pub(super) loop_detector: snapshot::InfiniteLoopDetector<'a, 'mir, 'tcx>, } -impl<'a, 'mir, 'tcx> CompileTimeEvaluator<'a, 'mir, 'tcx> { +impl<'a, 'mir, 'tcx> CompileTimeInterpreter<'a, 'mir, 'tcx> { fn new() -> Self { - CompileTimeEvaluator { + CompileTimeInterpreter { loop_detector: Default::default(), steps_since_detector_enabled: -snapshot::STEPS_UNTIL_DETECTOR_ENABLED, } @@ -251,10 +251,10 @@ impl<'a, 'mir, 'tcx> CompileTimeEvaluator<'a, 'mir, 'tcx> { } type CompileTimeEvalContext<'a, 'mir, 'tcx> = - EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator<'a, 'mir, 'tcx>>; + EvalContext<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>; impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx> - for CompileTimeEvaluator<'a, 'mir, 'tcx> + for CompileTimeInterpreter<'a, 'mir, 'tcx> { type MemoryData = (); type MemoryKinds = !; 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/mod.rs b/src/librustc_mir/interpret/mod.rs index 870ce4574e297..6f07c2996b9cb 100644 --- a/src/librustc_mir/interpret/mod.rs +++ b/src/librustc_mir/interpret/mod.rs @@ -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/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs index 89287d5871f24..388e0fd859d65 100644 --- a/src/librustc_mir/interpret/snapshot.rs +++ b/src/librustc_mir/interpret/snapshot.rs @@ -26,7 +26,7 @@ use syntax::source_map::Span; use super::eval_context::{LocalValue, StackPopCleanup}; use super::{Frame, Memory, Operand, MemPlace, Place, Value}; -use const_eval::CompileTimeEvaluator; +use const_eval::CompileTimeInterpreter; /// Number of steps until the detector even starts doing anything. /// Also, a warning is shown to the user when this number is reached. @@ -61,7 +61,7 @@ impl<'a, 'mir, 'tcx> InfiniteLoopDetector<'a, 'mir, 'tcx> pub fn observe_and_analyze<'b>( &mut self, tcx: &TyCtxt<'b, 'tcx, 'tcx>, - memory: &Memory<'a, 'mir, 'tcx, CompileTimeEvaluator<'a, 'mir, 'tcx>>, + memory: &Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>, stack: &[Frame<'mir, 'tcx>], ) -> EvalResult<'tcx, ()> { @@ -391,7 +391,7 @@ impl<'a, 'mir, 'tcx, Ctx> Snapshot<'a, Ctx> for &'a Frame<'mir, 'tcx> } impl<'a, 'b, 'mir, 'tcx: 'a+'mir> SnapshotContext<'b> - for Memory<'a, 'mir, 'tcx, CompileTimeEvaluator<'a, 'mir, 'tcx>> + for Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>> { fn resolve(&'b self, id: &AllocId) -> Option<&'b Allocation> { self.get(*id).ok() @@ -399,17 +399,17 @@ impl<'a, 'b, 'mir, 'tcx: 'a+'mir> SnapshotContext<'b> } /// The virtual machine state during const-evaluation at a given point in time. -/// We assume the `CompileTimeEvaluator` has no interesting extra state that +/// 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, CompileTimeEvaluator<'a, 'mir, 'tcx>>, + memory: Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>, stack: Vec>, } impl<'a, 'mir, 'tcx: 'a + 'mir> EvalSnapshot<'a, 'mir, 'tcx> { fn new( - memory: &Memory<'a, 'mir, 'tcx, CompileTimeEvaluator<'a, 'mir, 'tcx>>, + memory: &Memory<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>, stack: &[Frame<'mir, 'tcx>] ) -> Self { EvalSnapshot { 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 e48af71b646b7..7cfa7de8138a9 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; @@ -69,7 +69,7 @@ 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<'a, 'b, 'tcx>>, + ecx: EvalContext<'a, 'b, 'tcx, CompileTimeInterpreter<'a, 'b, 'tcx>>, mir: &'b Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>, source: MirSource, From b1453dda0f69148ba1ec627b3f9770603b7817d4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 20 Sep 2018 10:22:11 +0200 Subject: [PATCH 3/7] make some things a bit more private --- src/librustc_mir/const_eval.rs | 2 +- src/librustc_mir/interpret/eval_context.rs | 4 ++-- src/librustc_mir/interpret/memory.rs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index 0015f62a8f02a..e2a5d40a1236e 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -77,7 +77,7 @@ pub fn mk_eval_cx<'a, 'tcx>( Ok(ecx) } -pub fn eval_promoted<'a, 'mir, 'tcx>( +pub(crate) fn eval_promoted<'a, 'mir, 'tcx>( tcx: TyCtxt<'a, 'tcx, 'tcx>, cid: GlobalId<'tcx>, mir: &'mir mir::Mir<'tcx>, diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs index 91990551b5c9b..a17c469a0a62f 100644 --- a/src/librustc_mir/interpret/eval_context.rs +++ b/src/librustc_mir/interpret/eval_context.rs @@ -41,10 +41,10 @@ pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>> { pub machine: M, /// The results of the type checker, from rustc. - pub tcx: TyCtxtAt<'a, 'tcx, 'tcx>, + pub(crate) 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>, diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs index 7e843fd1ddea9..b5eb06b82ff75 100644 --- a/src/librustc_mir/interpret/memory.rs +++ b/src/librustc_mir/interpret/memory.rs @@ -57,7 +57,8 @@ pub struct Memory<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>> { /// that do not exist any more. dead_alloc_map: FxHashMap, - pub tcx: TyCtxtAt<'a, 'tcx, 'tcx>, + /// Lets us implement `HasDataLayout`, which is awfully convenient. + pub(super) tcx: TyCtxtAt<'a, 'tcx, 'tcx>, } impl<'b, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> HasDataLayout From c16336a014176e82f1bcbba84f99a228b4a321f5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 20 Sep 2018 11:57:45 +0200 Subject: [PATCH 4/7] move loop detector constants to the module that uses them; make lifetime order in ConstPropagator consistent with Memory --- src/librustc_mir/const_eval.rs | 20 +++++++++---------- src/librustc_mir/interpret/snapshot.rs | 25 ++++++++++++------------ src/librustc_mir/transform/const_prop.rs | 12 ++++++------ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index e2a5d40a1236e..bde0e95d372f5 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -34,6 +34,13 @@ use interpret::{self, 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>, @@ -245,7 +252,7 @@ impl<'a, 'mir, 'tcx> CompileTimeInterpreter<'a, 'mir, 'tcx> { fn new() -> Self { CompileTimeInterpreter { loop_detector: Default::default(), - steps_since_detector_enabled: -snapshot::STEPS_UNTIL_DETECTOR_ENABLED, + steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED, } } } @@ -349,22 +356,15 @@ impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx> return Ok(()); } - *steps %= snapshot::DETECTOR_SNAPSHOT_PERIOD; + *steps %= DETECTOR_SNAPSHOT_PERIOD; if *steps != 0 { return Ok(()); } } - if ecx.machine.loop_detector.is_empty() { - // First run of the loop detector - - // FIXME(#49980): make this warning a lint - ecx.tcx.sess.span_warn(ecx.frame().span, - "Constant evaluating a complex constant, this might take some time"); - } - ecx.machine.loop_detector.observe_and_analyze( &ecx.tcx, + ecx.frame().span, &ecx.memory, &ecx.stack[..], ) diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs index 388e0fd859d65..938f2e6db1cd2 100644 --- a/src/librustc_mir/interpret/snapshot.rs +++ b/src/librustc_mir/interpret/snapshot.rs @@ -28,13 +28,6 @@ use super::eval_context::{LocalValue, StackPopCleanup}; use super::{Frame, Memory, Operand, MemPlace, Place, Value}; use const_eval::CompileTimeInterpreter; -/// Number of steps until the detector even starts doing anything. -/// Also, a warning is shown to the user when this number is reached. -pub(crate) 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. -pub(crate) const DETECTOR_SNAPSHOT_PERIOD: isize = 256; - #[derive(Default)] pub(crate) struct InfiniteLoopDetector<'a, 'mir, 'tcx: 'a + 'mir> { /// The set of all `EvalSnapshot` *hashes* observed by this detector. @@ -53,28 +46,31 @@ pub(crate) struct InfiniteLoopDetector<'a, 'mir, '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<'b>( &mut self, tcx: &TyCtxt<'b, 'tcx, 'tcx>, + 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::::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)) { @@ -461,6 +457,9 @@ impl<'a, 'mir, 'tcx> Eq for EvalSnapshot<'a, '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/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs index 7cfa7de8138a9..e2a6cee054970 100644 --- a/src/librustc_mir/transform/const_prop.rs +++ b/src/librustc_mir/transform/const_prop.rs @@ -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, CompileTimeInterpreter<'a, 'b, 'tcx>>, - 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>>, @@ -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); From c711e155215c01846b8fd0bf42d9f0495f4b52d6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 20 Sep 2018 12:06:39 +0200 Subject: [PATCH 5/7] unsurprisingly, miri needs tcx --- src/librustc_mir/interpret/eval_context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs index a17c469a0a62f..6cbe8065c7e24 100644 --- a/src/librustc_mir/interpret/eval_context.rs +++ b/src/librustc_mir/interpret/eval_context.rs @@ -41,7 +41,7 @@ pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'a, 'mir, 'tcx>> { pub machine: M, /// The results of the type checker, from rustc. - pub(crate) tcx: TyCtxtAt<'a, 'tcx, 'tcx>, + pub tcx: TyCtxtAt<'a, 'tcx, 'tcx>, /// Bounds in scope for polymorphic evaluations. pub(crate) param_env: ty::ParamEnv<'tcx>, From 0309664491ba2cebc3dc7f509cfbad864f35d8d1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 20 Sep 2018 12:27:50 +0200 Subject: [PATCH 6/7] fix stage 0 compilation --- src/librustc_mir/const_eval.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index bde0e95d372f5..99a49122ef7e6 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -362,9 +362,10 @@ impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx> } } + let span = ecx.frame().span; ecx.machine.loop_detector.observe_and_analyze( &ecx.tcx, - ecx.frame().span, + span, &ecx.memory, &ecx.stack[..], ) From 8e74ee0998a5b11f28d61600dbb881c7168a4a40 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 21 Sep 2018 16:25:33 +0200 Subject: [PATCH 7/7] fix comment --- src/librustc_mir/interpret/snapshot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc_mir/interpret/snapshot.rs b/src/librustc_mir/interpret/snapshot.rs index 938f2e6db1cd2..18ca284811080 100644 --- a/src/librustc_mir/interpret/snapshot.rs +++ b/src/librustc_mir/interpret/snapshot.rs @@ -335,7 +335,7 @@ struct FrameSnapshot<'a, 'tcx: 'a> { stmt: usize, } -// Not using the macro because that does not support types depending on 'tcx +// Not using the macro because that does not support types depending on two lifetimes impl<'a, 'mir, 'tcx: 'mir> HashStable> for Frame<'mir, 'tcx> { fn hash_stable( &self,