From 3e7fa3c6f22798d4228a0dc3ec50bdf185084d40 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 25 Dec 2019 01:04:32 +0100 Subject: [PATCH 01/14] Move const eval machine into its own module --- src/librustc_mir/const_eval.rs | 355 +------------------------ src/librustc_mir/const_eval/error.rs | 30 +++ src/librustc_mir/const_eval/machine.rs | 328 +++++++++++++++++++++++ 3 files changed, 369 insertions(+), 344 deletions(-) create mode 100644 src/librustc_mir/const_eval/error.rs create mode 100644 src/librustc_mir/const_eval/machine.rs diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index 8a3bab2a28208..833547d4d059b 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -1,21 +1,18 @@ // Not in interpret to make sure we do not use private implementation details -use std::borrow::{Borrow, Cow}; -use std::collections::hash_map::Entry; use std::convert::TryInto; use std::error::Error; use std::fmt; use std::hash::Hash; +use crate::interpret::eval_nullary_intrinsic; use crate::interpret::eval_nullary_intrinsic; use rustc::hir::def::DefKind; -use rustc::hir::def_id::DefId; use rustc::mir; use rustc::mir::interpret::{ConstEvalErr, ErrorHandled, ScalarMaybeUndef}; use rustc::traits::Reveal; -use rustc::ty::layout::{self, HasTyCtxt, LayoutOf, VariantIdx}; -use rustc::ty::{self, subst::Subst, Ty, TyCtxt}; -use rustc_data_structures::fx::FxHashMap; +use rustc::ty::layout::{self, LayoutOf, VariantIdx}; +use rustc::ty::{self, subst::Subst, TyCtxt}; use syntax::{ source_map::{Span, DUMMY_SP}, @@ -23,17 +20,16 @@ use syntax::{ }; use crate::interpret::{ - self, intern_const_alloc_recursive, snapshot, AllocId, Allocation, AssertMessage, ConstValue, - GlobalId, ImmTy, Immediate, InterpCx, InterpErrorInfo, InterpResult, MPlaceTy, Machine, Memory, - MemoryKind, OpTy, PlaceTy, Pointer, RawConst, RefTracking, Scalar, StackPopCleanup, + intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, ImmTy, Immediate, InterpCx, + InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, RawConst, RefTracking, + Scalar, StackPopCleanup, }; -/// 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; +mod error; +mod machine; + +pub use error::*; +pub use machine::*; /// The `InterpCx` is only meant to be used to do field and index projections into constants for /// `simd_shuffle` and const patterns in match arms. @@ -173,335 +169,6 @@ fn eval_body_using_ecx<'mir, 'tcx>( Ok(ret) } -#[derive(Clone, Debug)] -pub enum ConstEvalError { - NeedsRfc(String), - ConstAccessesStatic, -} - -impl<'tcx> Into> for ConstEvalError { - fn into(self) -> InterpErrorInfo<'tcx> { - err_unsup!(Unsupported(self.to_string())).into() - } -} - -impl fmt::Display for ConstEvalError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::ConstEvalError::*; - match *self { - NeedsRfc(ref msg) => { - write!(f, "\"{}\" needs an rfc before being allowed inside constants", msg) - } - ConstAccessesStatic => write!(f, "constant accesses static"), - } - } -} - -impl Error for ConstEvalError {} - -// Extra machine state for CTFE, and the Machine instance -pub struct CompileTimeInterpreter<'mir, 'tcx> { - /// When this value is negative, it indicates the number of interpreter - /// steps *until* the loop detector is enabled. When it is positive, it is - /// the number of steps after the detector has been enabled modulo the loop - /// detector period. - pub(super) steps_since_detector_enabled: isize, - - /// Extra state to detect loops. - pub(super) loop_detector: snapshot::InfiniteLoopDetector<'mir, 'tcx>, -} - -#[derive(Copy, Clone, Debug)] -pub struct MemoryExtra { - /// Whether this machine may read from statics - can_access_statics: bool, -} - -impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { - fn new() -> Self { - CompileTimeInterpreter { - loop_detector: Default::default(), - steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED, - } - } -} - -impl interpret::AllocMap for FxHashMap { - #[inline(always)] - fn contains_key(&mut self, k: &Q) -> bool - where - K: Borrow, - { - FxHashMap::contains_key(self, k) - } - - #[inline(always)] - fn insert(&mut self, k: K, v: V) -> Option { - FxHashMap::insert(self, k, v) - } - - #[inline(always)] - fn remove(&mut self, k: &Q) -> Option - where - K: Borrow, - { - FxHashMap::remove(self, k) - } - - #[inline(always)] - fn filter_map_collect(&self, mut f: impl FnMut(&K, &V) -> Option) -> Vec { - self.iter().filter_map(move |(k, v)| f(k, &*v)).collect() - } - - #[inline(always)] - fn get_or(&self, k: K, vacant: impl FnOnce() -> Result) -> Result<&V, E> { - match self.get(&k) { - Some(v) => Ok(v), - None => { - vacant()?; - bug!("The CTFE machine shouldn't ever need to extend the alloc_map when reading") - } - } - } - - #[inline(always)] - fn get_mut_or(&mut self, k: K, vacant: impl FnOnce() -> Result) -> Result<&mut V, E> { - match self.entry(k) { - Entry::Occupied(e) => Ok(e.into_mut()), - Entry::Vacant(e) => { - let v = vacant()?; - Ok(e.insert(v)) - } - } - } -} - -crate type CompileTimeEvalContext<'mir, 'tcx> = - InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>; - -impl interpret::MayLeak for ! { - #[inline(always)] - fn may_leak(self) -> bool { - // `self` is uninhabited - self - } -} - -impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, 'tcx> { - type MemoryKinds = !; - type PointerTag = (); - type ExtraFnVal = !; - - type FrameExtra = (); - type MemoryExtra = MemoryExtra; - type AllocExtra = (); - - type MemoryMap = FxHashMap, Allocation)>; - - const STATIC_KIND: Option = None; // no copying of statics allowed - - // We do not check for alignment to avoid having to carry an `Align` - // in `ConstValue::ByRef`. - const CHECK_ALIGN: bool = false; - - #[inline(always)] - fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool { - false // for now, we don't enforce validity - } - - fn find_mir_or_eval_fn( - ecx: &mut InterpCx<'mir, 'tcx, Self>, - instance: ty::Instance<'tcx>, - args: &[OpTy<'tcx>], - ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, - _unwind: Option, // unwinding is not supported in consts - ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> { - debug!("find_mir_or_eval_fn: {:?}", instance); - - // Only check non-glue functions - if let ty::InstanceDef::Item(def_id) = instance.def { - // Execution might have wandered off into other crates, so we cannot do a stability- - // sensitive check here. But we can at least rule out functions that are not const - // at all. - if ecx.tcx.is_const_fn_raw(def_id) { - // If this function is a `const fn` then as an optimization we can query this - // evaluation immediately. - // - // For the moment we only do this for functions which take no arguments - // (or all arguments are ZSTs) so that we don't memoize too much. - // - // Because `#[track_caller]` adds an implicit non-ZST argument, we also cannot - // perform this optimization on items tagged with it. - let no_implicit_args = !instance.def.requires_caller_location(ecx.tcx()); - if args.iter().all(|a| a.layout.is_zst()) && no_implicit_args { - let gid = GlobalId { instance, promoted: None }; - ecx.eval_const_fn_call(gid, ret)?; - return Ok(None); - } - } else { - // Some functions we support even if they are non-const -- but avoid testing - // that for const fn! We certainly do *not* want to actually call the fn - // though, so be sure we return here. - return if ecx.hook_panic_fn(instance, args, ret)? { - Ok(None) - } else { - throw_unsup_format!("calling non-const function `{}`", instance) - }; - } - } - // This is a const fn. Call it. - Ok(Some(match ecx.load_mir(instance.def, None) { - Ok(body) => *body, - Err(err) => { - if let err_unsup!(NoMirFor(ref path)) = err.kind { - return Err(ConstEvalError::NeedsRfc(format!( - "calling extern function `{}`", - path - )) - .into()); - } - return Err(err); - } - })) - } - - fn call_extra_fn( - _ecx: &mut InterpCx<'mir, 'tcx, Self>, - fn_val: !, - _args: &[OpTy<'tcx>], - _ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, - _unwind: Option, - ) -> InterpResult<'tcx> { - match fn_val {} - } - - fn call_intrinsic( - ecx: &mut InterpCx<'mir, 'tcx, Self>, - span: Span, - instance: ty::Instance<'tcx>, - args: &[OpTy<'tcx>], - ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, - _unwind: Option, - ) -> InterpResult<'tcx> { - if ecx.emulate_intrinsic(span, instance, args, ret)? { - return Ok(()); - } - // An intrinsic that we do not support - let intrinsic_name = ecx.tcx.item_name(instance.def_id()); - Err(ConstEvalError::NeedsRfc(format!("calling intrinsic `{}`", intrinsic_name)).into()) - } - - fn assert_panic( - ecx: &mut InterpCx<'mir, 'tcx, Self>, - _span: Span, - msg: &AssertMessage<'tcx>, - _unwind: Option, - ) -> InterpResult<'tcx> { - use rustc::mir::interpret::PanicInfo::*; - Err(match msg { - BoundsCheck { ref len, ref index } => { - let len = ecx - .read_immediate(ecx.eval_operand(len, None)?) - .expect("can't eval len") - .to_scalar()? - .to_machine_usize(&*ecx)?; - let index = ecx - .read_immediate(ecx.eval_operand(index, None)?) - .expect("can't eval index") - .to_scalar()? - .to_machine_usize(&*ecx)?; - err_panic!(BoundsCheck { len, index }) - } - Overflow(op) => err_panic!(Overflow(*op)), - OverflowNeg => err_panic!(OverflowNeg), - DivisionByZero => err_panic!(DivisionByZero), - RemainderByZero => err_panic!(RemainderByZero), - ResumedAfterReturn(generator_kind) => err_panic!(ResumedAfterReturn(*generator_kind)), - ResumedAfterPanic(generator_kind) => err_panic!(ResumedAfterPanic(*generator_kind)), - Panic { .. } => bug!("`Panic` variant cannot occur in MIR"), - } - .into()) - } - - fn ptr_to_int(_mem: &Memory<'mir, 'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx, u64> { - Err(ConstEvalError::NeedsRfc("pointer-to-integer cast".to_string()).into()) - } - - fn binary_ptr_op( - _ecx: &InterpCx<'mir, 'tcx, Self>, - _bin_op: mir::BinOp, - _left: ImmTy<'tcx>, - _right: ImmTy<'tcx>, - ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> { - Err(ConstEvalError::NeedsRfc("pointer arithmetic or comparison".to_string()).into()) - } - - fn find_foreign_static( - _tcx: TyCtxt<'tcx>, - _def_id: DefId, - ) -> InterpResult<'tcx, Cow<'tcx, Allocation>> { - throw_unsup!(ReadForeignStatic) - } - - #[inline(always)] - fn init_allocation_extra<'b>( - _memory_extra: &MemoryExtra, - _id: AllocId, - alloc: Cow<'b, Allocation>, - _kind: Option>, - ) -> (Cow<'b, Allocation>, Self::PointerTag) { - // We do not use a tag so we can just cheaply forward the allocation - (alloc, ()) - } - - #[inline(always)] - fn tag_static_base_pointer(_memory_extra: &MemoryExtra, _id: AllocId) -> Self::PointerTag { - () - } - - fn box_alloc( - _ecx: &mut InterpCx<'mir, 'tcx, Self>, - _dest: PlaceTy<'tcx>, - ) -> InterpResult<'tcx> { - Err(ConstEvalError::NeedsRfc("heap allocations via `box` keyword".to_string()).into()) - } - - fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'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[..]) - } - - #[inline(always)] - fn stack_push(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> { - Ok(()) - } - - fn before_access_static( - memory_extra: &MemoryExtra, - _allocation: &Allocation, - ) -> InterpResult<'tcx> { - if memory_extra.can_access_statics { - Ok(()) - } else { - Err(ConstEvalError::ConstAccessesStatic.into()) - } - } -} - /// Extracts a field of a (variant of a) const. // this function uses `unwrap` copiously, because an already validated constant must have valid // fields and can thus never fail outside of compiler bugs diff --git a/src/librustc_mir/const_eval/error.rs b/src/librustc_mir/const_eval/error.rs new file mode 100644 index 0000000000000..8948cc0fc3ea5 --- /dev/null +++ b/src/librustc_mir/const_eval/error.rs @@ -0,0 +1,30 @@ +use std::error::Error; +use std::fmt; + +use crate::interpret::InterpErrorInfo; + +#[derive(Clone, Debug)] +pub enum ConstEvalError { + NeedsRfc(String), + ConstAccessesStatic, +} + +impl<'tcx> Into> for ConstEvalError { + fn into(self) -> InterpErrorInfo<'tcx> { + err_unsup!(Unsupported(self.to_string())).into() + } +} + +impl fmt::Display for ConstEvalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use self::ConstEvalError::*; + match *self { + NeedsRfc(ref msg) => { + write!(f, "\"{}\" needs an rfc before being allowed inside constants", msg) + } + ConstAccessesStatic => write!(f, "constant accesses static"), + } + } +} + +impl Error for ConstEvalError {} diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs new file mode 100644 index 0000000000000..0e3794ee3ab88 --- /dev/null +++ b/src/librustc_mir/const_eval/machine.rs @@ -0,0 +1,328 @@ +use rustc::hir::def_id::DefId; +use rustc::mir; +use rustc::ty::layout::HasTyCtxt; +use rustc::ty::{self, Ty, TyCtxt}; +use std::borrow::{Borrow, Cow}; +use std::collections::hash_map::Entry; +use std::hash::Hash; + +use rustc_data_structures::fx::FxHashMap; + +use syntax::source_map::Span; + +use crate::interpret::{ + self, snapshot, AllocId, Allocation, AssertMessage, GlobalId, ImmTy, InterpCx, InterpResult, + Memory, MemoryKind, OpTy, PlaceTy, Pointer, Scalar, +}; + +use super::error::*; + +/// 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; + +// Extra machine state for CTFE, and the Machine instance +pub struct CompileTimeInterpreter<'mir, 'tcx> { + /// When this value is negative, it indicates the number of interpreter + /// steps *until* the loop detector is enabled. When it is positive, it is + /// the number of steps after the detector has been enabled modulo the loop + /// detector period. + pub(super) steps_since_detector_enabled: isize, + + /// Extra state to detect loops. + pub(super) loop_detector: snapshot::InfiniteLoopDetector<'mir, 'tcx>, +} + +#[derive(Copy, Clone, Debug)] +pub struct MemoryExtra { + /// Whether this machine may read from statics + can_access_statics: bool, +} + +impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { + fn new() -> Self { + CompileTimeInterpreter { + loop_detector: Default::default(), + steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED, + } + } +} + +impl interpret::AllocMap for FxHashMap { + #[inline(always)] + fn contains_key(&mut self, k: &Q) -> bool + where + K: Borrow, + { + FxHashMap::contains_key(self, k) + } + + #[inline(always)] + fn insert(&mut self, k: K, v: V) -> Option { + FxHashMap::insert(self, k, v) + } + + #[inline(always)] + fn remove(&mut self, k: &Q) -> Option + where + K: Borrow, + { + FxHashMap::remove(self, k) + } + + #[inline(always)] + fn filter_map_collect(&self, mut f: impl FnMut(&K, &V) -> Option) -> Vec { + self.iter().filter_map(move |(k, v)| f(k, &*v)).collect() + } + + #[inline(always)] + fn get_or(&self, k: K, vacant: impl FnOnce() -> Result) -> Result<&V, E> { + match self.get(&k) { + Some(v) => Ok(v), + None => { + vacant()?; + bug!("The CTFE machine shouldn't ever need to extend the alloc_map when reading") + } + } + } + + #[inline(always)] + fn get_mut_or(&mut self, k: K, vacant: impl FnOnce() -> Result) -> Result<&mut V, E> { + match self.entry(k) { + Entry::Occupied(e) => Ok(e.into_mut()), + Entry::Vacant(e) => { + let v = vacant()?; + Ok(e.insert(v)) + } + } + } +} + +crate type CompileTimeEvalContext<'mir, 'tcx> = + InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>; + +impl interpret::MayLeak for ! { + #[inline(always)] + fn may_leak(self) -> bool { + // `self` is uninhabited + self + } +} + +impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, 'tcx> { + type MemoryKinds = !; + type PointerTag = (); + type ExtraFnVal = !; + + type FrameExtra = (); + type MemoryExtra = MemoryExtra; + type AllocExtra = (); + + type MemoryMap = FxHashMap, Allocation)>; + + const STATIC_KIND: Option = None; // no copying of statics allowed + + // We do not check for alignment to avoid having to carry an `Align` + // in `ConstValue::ByRef`. + const CHECK_ALIGN: bool = false; + + #[inline(always)] + fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool { + false // for now, we don't enforce validity + } + + fn find_mir_or_eval_fn( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + instance: ty::Instance<'tcx>, + args: &[OpTy<'tcx>], + ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, + _unwind: Option, // unwinding is not supported in consts + ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> { + debug!("find_mir_or_eval_fn: {:?}", instance); + + // Only check non-glue functions + if let ty::InstanceDef::Item(def_id) = instance.def { + // Execution might have wandered off into other crates, so we cannot do a stability- + // sensitive check here. But we can at least rule out functions that are not const + // at all. + if ecx.tcx.is_const_fn_raw(def_id) { + // If this function is a `const fn` then as an optimization we can query this + // evaluation immediately. + // + // For the moment we only do this for functions which take no arguments + // (or all arguments are ZSTs) so that we don't memoize too much. + // + // Because `#[track_caller]` adds an implicit non-ZST argument, we also cannot + // perform this optimization on items tagged with it. + let no_implicit_args = !instance.def.requires_caller_location(ecx.tcx()); + if args.iter().all(|a| a.layout.is_zst()) && no_implicit_args { + let gid = GlobalId { instance, promoted: None }; + ecx.eval_const_fn_call(gid, ret)?; + return Ok(None); + } + } else { + // Some functions we support even if they are non-const -- but avoid testing + // that for const fn! We certainly do *not* want to actually call the fn + // though, so be sure we return here. + return if ecx.hook_panic_fn(instance, args, ret)? { + Ok(None) + } else { + throw_unsup_format!("calling non-const function `{}`", instance) + }; + } + } + // This is a const fn. Call it. + Ok(Some(match ecx.load_mir(instance.def, None) { + Ok(body) => *body, + Err(err) => { + if let err_unsup!(NoMirFor(ref path)) = err.kind { + return Err(ConstEvalError::NeedsRfc(format!( + "calling extern function `{}`", + path + )) + .into()); + } + return Err(err); + } + })) + } + + fn call_extra_fn( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + fn_val: !, + _args: &[OpTy<'tcx>], + _ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, + _unwind: Option, + ) -> InterpResult<'tcx> { + match fn_val {} + } + + fn call_intrinsic( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + span: Span, + instance: ty::Instance<'tcx>, + args: &[OpTy<'tcx>], + ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, + _unwind: Option, + ) -> InterpResult<'tcx> { + if ecx.emulate_intrinsic(span, instance, args, ret)? { + return Ok(()); + } + // An intrinsic that we do not support + let intrinsic_name = ecx.tcx.item_name(instance.def_id()); + Err(ConstEvalError::NeedsRfc(format!("calling intrinsic `{}`", intrinsic_name)).into()) + } + + fn assert_panic( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + _span: Span, + msg: &AssertMessage<'tcx>, + _unwind: Option, + ) -> InterpResult<'tcx> { + use rustc::mir::interpret::PanicInfo::*; + Err(match msg { + BoundsCheck { ref len, ref index } => { + let len = ecx + .read_immediate(ecx.eval_operand(len, None)?) + .expect("can't eval len") + .to_scalar()? + .to_machine_usize(&*ecx)?; + let index = ecx + .read_immediate(ecx.eval_operand(index, None)?) + .expect("can't eval index") + .to_scalar()? + .to_machine_usize(&*ecx)?; + err_panic!(BoundsCheck { len, index }) + } + Overflow(op) => err_panic!(Overflow(*op)), + OverflowNeg => err_panic!(OverflowNeg), + DivisionByZero => err_panic!(DivisionByZero), + RemainderByZero => err_panic!(RemainderByZero), + ResumedAfterReturn(generator_kind) => err_panic!(ResumedAfterReturn(*generator_kind)), + ResumedAfterPanic(generator_kind) => err_panic!(ResumedAfterPanic(*generator_kind)), + Panic { .. } => bug!("`Panic` variant cannot occur in MIR"), + } + .into()) + } + + fn ptr_to_int(_mem: &Memory<'mir, 'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx, u64> { + Err(ConstEvalError::NeedsRfc("pointer-to-integer cast".to_string()).into()) + } + + fn binary_ptr_op( + _ecx: &InterpCx<'mir, 'tcx, Self>, + _bin_op: mir::BinOp, + _left: ImmTy<'tcx>, + _right: ImmTy<'tcx>, + ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> { + Err(ConstEvalError::NeedsRfc("pointer arithmetic or comparison".to_string()).into()) + } + + fn find_foreign_static( + _tcx: TyCtxt<'tcx>, + _def_id: DefId, + ) -> InterpResult<'tcx, Cow<'tcx, Allocation>> { + throw_unsup!(ReadForeignStatic) + } + + #[inline(always)] + fn init_allocation_extra<'b>( + _memory_extra: &MemoryExtra, + _id: AllocId, + alloc: Cow<'b, Allocation>, + _kind: Option>, + ) -> (Cow<'b, Allocation>, Self::PointerTag) { + // We do not use a tag so we can just cheaply forward the allocation + (alloc, ()) + } + + #[inline(always)] + fn tag_static_base_pointer(_memory_extra: &MemoryExtra, _id: AllocId) -> Self::PointerTag { + () + } + + fn box_alloc( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + _dest: PlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + Err(ConstEvalError::NeedsRfc("heap allocations via `box` keyword".to_string()).into()) + } + + fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'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[..]) + } + + #[inline(always)] + fn stack_push(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> { + Ok(()) + } + + fn before_access_static( + memory_extra: &MemoryExtra, + _allocation: &Allocation, + ) -> InterpResult<'tcx> { + if memory_extra.can_access_statics { + Ok(()) + } else { + Err(ConstEvalError::ConstAccessesStatic.into()) + } + } +} From 2d67edd32c71ac32c8dafb7106e7187993750629 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 25 Dec 2019 01:06:51 +0100 Subject: [PATCH 02/14] Move const eval query components into their own module --- src/librustc_mir/const_eval.rs | 230 +-------------------------- src/librustc_mir/const_eval/error.rs | 16 +- src/librustc_mir/const_eval/query.rs | 216 +++++++++++++++++++++++++ 3 files changed, 234 insertions(+), 228 deletions(-) create mode 100644 src/librustc_mir/const_eval/query.rs diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index 833547d4d059b..c308bf8a46c2f 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -5,12 +5,8 @@ use std::error::Error; use std::fmt; use std::hash::Hash; -use crate::interpret::eval_nullary_intrinsic; -use crate::interpret::eval_nullary_intrinsic; -use rustc::hir::def::DefKind; use rustc::mir; -use rustc::mir::interpret::{ConstEvalErr, ErrorHandled, ScalarMaybeUndef}; -use rustc::traits::Reveal; +use rustc::mir::interpret::ScalarMaybeUndef; use rustc::ty::layout::{self, LayoutOf, VariantIdx}; use rustc::ty::{self, subst::Subst, TyCtxt}; @@ -21,15 +17,14 @@ use syntax::{ use crate::interpret::{ intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, ImmTy, Immediate, InterpCx, - InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, RawConst, RefTracking, - Scalar, StackPopCleanup, + InterpResult, MPlaceTy, MemoryKind, OpTy, Scalar, StackPopCleanup, }; mod error; -mod machine; +mod query; pub use error::*; -pub use machine::*; +pub use query::*; /// The `InterpCx` is only meant to be used to do field and index projections into constants for /// `simd_shuffle` and const patterns in match arms. @@ -225,220 +220,3 @@ pub fn const_variant_index<'tcx>( let op = ecx.eval_const_to_op(val, None).unwrap(); ecx.read_discriminant(op).unwrap().1 } - -/// Turn an interpreter error into something to report to the user. -/// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace. -/// Should be called only if the error is actually going to to be reported! -pub fn error_to_const_error<'mir, 'tcx, M: Machine<'mir, 'tcx>>( - ecx: &InterpCx<'mir, 'tcx, M>, - mut error: InterpErrorInfo<'tcx>, -) -> ConstEvalErr<'tcx> { - error.print_backtrace(); - let stacktrace = ecx.generate_stacktrace(None); - ConstEvalErr { error: error.kind, stacktrace, span: ecx.tcx.span } -} - -pub fn note_on_undefined_behavior_error() -> &'static str { - "The rules on what exactly is undefined behavior aren't clear, \ - so this check might be overzealous. Please open an issue on the rustc \ - repository if you believe it should not be considered undefined behavior." -} - -fn validate_and_turn_into_const<'tcx>( - tcx: TyCtxt<'tcx>, - constant: RawConst<'tcx>, - key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, -) -> ::rustc::mir::interpret::ConstEvalResult<'tcx> { - let cid = key.value; - let def_id = cid.instance.def.def_id(); - let is_static = tcx.is_static(def_id); - let ecx = mk_eval_cx(tcx, tcx.def_span(key.value.instance.def_id()), key.param_env, is_static); - let val = (|| { - let mplace = ecx.raw_const_to_mplace(constant)?; - let mut ref_tracking = RefTracking::new(mplace); - while let Some((mplace, path)) = ref_tracking.todo.pop() { - ecx.validate_operand(mplace.into(), path, Some(&mut ref_tracking))?; - } - // Now that we validated, turn this into a proper constant. - // Statics/promoteds are always `ByRef`, for the rest `op_to_const` decides - // whether they become immediates. - if is_static || cid.promoted.is_some() { - let ptr = mplace.ptr.to_ptr()?; - Ok(tcx.mk_const(ty::Const { - val: ty::ConstKind::Value(ConstValue::ByRef { - alloc: ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), - offset: ptr.offset, - }), - ty: mplace.layout.ty, - })) - } else { - Ok(op_to_const(&ecx, mplace.into())) - } - })(); - - val.map_err(|error| { - let err = error_to_const_error(&ecx, error); - match err.struct_error(ecx.tcx, "it is undefined behavior to use this value") { - Ok(mut diag) => { - diag.note(note_on_undefined_behavior_error()); - diag.emit(); - ErrorHandled::Reported - } - Err(err) => err, - } - }) -} - -pub fn const_eval_validated_provider<'tcx>( - tcx: TyCtxt<'tcx>, - key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, -) -> ::rustc::mir::interpret::ConstEvalResult<'tcx> { - // see comment in const_eval_raw_provider for what we're doing here - if key.param_env.reveal == Reveal::All { - let mut key = key.clone(); - key.param_env.reveal = Reveal::UserFacing; - match tcx.const_eval_validated(key) { - // try again with reveal all as requested - Err(ErrorHandled::TooGeneric) => { - // Promoteds should never be "too generic" when getting evaluated. - // They either don't get evaluated, or we are in a monomorphic context - assert!(key.value.promoted.is_none()); - } - // dedupliate calls - other => return other, - } - } - - // We call `const_eval` for zero arg intrinsics, too, in order to cache their value. - // Catch such calls and evaluate them instead of trying to load a constant's MIR. - if let ty::InstanceDef::Intrinsic(def_id) = key.value.instance.def { - let ty = key.value.instance.ty(tcx); - let substs = match ty.kind { - ty::FnDef(_, substs) => substs, - _ => bug!("intrinsic with type {:?}", ty), - }; - return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| { - let span = tcx.def_span(def_id); - let error = ConstEvalErr { error: error.kind, stacktrace: vec![], span }; - error.report_as_error(tcx.at(span), "could not evaluate nullary intrinsic") - }); - } - - tcx.const_eval_raw(key).and_then(|val| validate_and_turn_into_const(tcx, val, key)) -} - -pub fn const_eval_raw_provider<'tcx>( - tcx: TyCtxt<'tcx>, - key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, -) -> ::rustc::mir::interpret::ConstEvalRawResult<'tcx> { - // Because the constant is computed twice (once per value of `Reveal`), we are at risk of - // reporting the same error twice here. To resolve this, we check whether we can evaluate the - // constant in the more restrictive `Reveal::UserFacing`, which most likely already was - // computed. For a large percentage of constants that will already have succeeded. Only - // associated constants of generic functions will fail due to not enough monomorphization - // information being available. - - // In case we fail in the `UserFacing` variant, we just do the real computation. - if key.param_env.reveal == Reveal::All { - let mut key = key.clone(); - key.param_env.reveal = Reveal::UserFacing; - match tcx.const_eval_raw(key) { - // try again with reveal all as requested - Err(ErrorHandled::TooGeneric) => {} - // dedupliate calls - other => return other, - } - } - if cfg!(debug_assertions) { - // Make sure we format the instance even if we do not print it. - // This serves as a regression test against an ICE on printing. - // The next two lines concatenated contain some discussion: - // https://rust-lang.zulipchat.com/#narrow/stream/146212-t-compiler.2Fconst-eval/ - // subject/anon_const_instance_printing/near/135980032 - let instance = key.value.instance.to_string(); - trace!("const eval: {:?} ({})", key, instance); - } - - let cid = key.value; - let def_id = cid.instance.def.def_id(); - - if def_id.is_local() && tcx.typeck_tables_of(def_id).tainted_by_errors { - return Err(ErrorHandled::Reported); - } - - let is_static = tcx.is_static(def_id); - - let span = tcx.def_span(cid.instance.def_id()); - let mut ecx = InterpCx::new( - tcx.at(span), - key.param_env, - CompileTimeInterpreter::new(), - MemoryExtra { can_access_statics: is_static }, - ); - - let res = ecx.load_mir(cid.instance.def, cid.promoted); - res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, *body)) - .and_then(|place| { - Ok(RawConst { alloc_id: place.ptr.assert_ptr().alloc_id, ty: place.layout.ty }) - }) - .map_err(|error| { - let err = error_to_const_error(&ecx, error); - // errors in statics are always emitted as fatal errors - if is_static { - // Ensure that if the above error was either `TooGeneric` or `Reported` - // an error must be reported. - let v = err.report_as_error(ecx.tcx, "could not evaluate static initializer"); - tcx.sess.delay_span_bug( - err.span, - &format!("static eval failure did not emit an error: {:#?}", v), - ); - v - } else if def_id.is_local() { - // constant defined in this crate, we can figure out a lint level! - match tcx.def_kind(def_id) { - // constants never produce a hard error at the definition site. Anything else is - // a backwards compatibility hazard (and will break old versions of winapi for sure) - // - // note that validation may still cause a hard error on this very same constant, - // because any code that existed before validation could not have failed validation - // thus preventing such a hard error from being a backwards compatibility hazard - Some(DefKind::Const) | Some(DefKind::AssocConst) => { - let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap(); - err.report_as_lint( - tcx.at(tcx.def_span(def_id)), - "any use of this value will cause an error", - hir_id, - Some(err.span), - ) - } - // promoting runtime code is only allowed to error if it references broken constants - // any other kind of error will be reported to the user as a deny-by-default lint - _ => { - if let Some(p) = cid.promoted { - let span = tcx.promoted_mir(def_id)[p].span; - if let err_inval!(ReferencedConstant) = err.error { - err.report_as_error( - tcx.at(span), - "evaluation of constant expression failed", - ) - } else { - err.report_as_lint( - tcx.at(span), - "reaching this expression at runtime will panic or abort", - tcx.hir().as_local_hir_id(def_id).unwrap(), - Some(err.span), - ) - } - // anything else (array lengths, enum initializers, constant patterns) are reported - // as hard errors - } else { - err.report_as_error(ecx.tcx, "evaluation of constant value failed") - } - } - } - } else { - // use of broken constant from other crate - err.report_as_error(ecx.tcx, "could not evaluate constant") - } - }) -} diff --git a/src/librustc_mir/const_eval/error.rs b/src/librustc_mir/const_eval/error.rs index 8948cc0fc3ea5..c2db3c31f85be 100644 --- a/src/librustc_mir/const_eval/error.rs +++ b/src/librustc_mir/const_eval/error.rs @@ -1,8 +1,8 @@ use std::error::Error; use std::fmt; -use crate::interpret::InterpErrorInfo; - +use super::InterpCx; +use crate::interpret::{ConstEvalErr, InterpErrorInfo, Machine}; #[derive(Clone, Debug)] pub enum ConstEvalError { NeedsRfc(String), @@ -28,3 +28,15 @@ impl fmt::Display for ConstEvalError { } impl Error for ConstEvalError {} + +/// Turn an interpreter error into something to report to the user. +/// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace. +/// Should be called only if the error is actually going to to be reported! +pub fn error_to_const_error<'mir, 'tcx, M: Machine<'mir, 'tcx>>( + ecx: &InterpCx<'mir, 'tcx, M>, + mut error: InterpErrorInfo<'tcx>, +) -> ConstEvalErr<'tcx> { + error.print_backtrace(); + let stacktrace = ecx.generate_stacktrace(None); + ConstEvalErr { error: error.kind, stacktrace, span: ecx.tcx.span } +} diff --git a/src/librustc_mir/const_eval/query.rs b/src/librustc_mir/const_eval/query.rs new file mode 100644 index 0000000000000..0a33143e8c682 --- /dev/null +++ b/src/librustc_mir/const_eval/query.rs @@ -0,0 +1,216 @@ +use crate::interpret::eval_nullary_intrinsic; +use rustc::hir::def::DefKind; +use rustc::mir::interpret::{ConstEvalErr, ErrorHandled}; +use rustc::traits::Reveal; +use rustc::ty::{self, TyCtxt}; + +use crate::interpret::{ConstValue, GlobalId, InterpCx, RawConst, RefTracking}; + +use super::{ + error_to_const_error, eval_body_using_ecx, mk_eval_cx, op_to_const, CompileTimeInterpreter, +}; + +pub fn note_on_undefined_behavior_error() -> &'static str { + "The rules on what exactly is undefined behavior aren't clear, \ + so this check might be overzealous. Please open an issue on the rustc \ + repository if you believe it should not be considered undefined behavior." +} + +fn validate_and_turn_into_const<'tcx>( + tcx: TyCtxt<'tcx>, + constant: RawConst<'tcx>, + key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, +) -> ::rustc::mir::interpret::ConstEvalResult<'tcx> { + let cid = key.value; + let def_id = cid.instance.def.def_id(); + let is_static = tcx.is_static(def_id); + let ecx = mk_eval_cx(tcx, tcx.def_span(key.value.instance.def_id()), key.param_env, is_static); + let val = (|| { + let mplace = ecx.raw_const_to_mplace(constant)?; + let mut ref_tracking = RefTracking::new(mplace); + while let Some((mplace, path)) = ref_tracking.todo.pop() { + ecx.validate_operand(mplace.into(), path, Some(&mut ref_tracking))?; + } + // Now that we validated, turn this into a proper constant. + // Statics/promoteds are always `ByRef`, for the rest `op_to_const` decides + // whether they become immediates. + if is_static || cid.promoted.is_some() { + let ptr = mplace.ptr.to_ptr()?; + Ok(tcx.mk_const(ty::Const { + val: ty::ConstKind::Value(ConstValue::ByRef { + alloc: ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), + offset: ptr.offset, + }), + ty: mplace.layout.ty, + })) + } else { + Ok(op_to_const(&ecx, mplace.into())) + } + })(); + + val.map_err(|error| { + let err = error_to_const_error(&ecx, error); + match err.struct_error(ecx.tcx, "it is undefined behavior to use this value") { + Ok(mut diag) => { + diag.note(note_on_undefined_behavior_error()); + diag.emit(); + ErrorHandled::Reported + } + Err(err) => err, + } + }) +} + +pub fn const_eval_validated_provider<'tcx>( + tcx: TyCtxt<'tcx>, + key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, +) -> ::rustc::mir::interpret::ConstEvalResult<'tcx> { + // see comment in const_eval_raw_provider for what we're doing here + if key.param_env.reveal == Reveal::All { + let mut key = key.clone(); + key.param_env.reveal = Reveal::UserFacing; + match tcx.const_eval_validated(key) { + // try again with reveal all as requested + Err(ErrorHandled::TooGeneric) => { + // Promoteds should never be "too generic" when getting evaluated. + // They either don't get evaluated, or we are in a monomorphic context + assert!(key.value.promoted.is_none()); + } + // dedupliate calls + other => return other, + } + } + + // We call `const_eval` for zero arg intrinsics, too, in order to cache their value. + // Catch such calls and evaluate them instead of trying to load a constant's MIR. + if let ty::InstanceDef::Intrinsic(def_id) = key.value.instance.def { + let ty = key.value.instance.ty(tcx); + let substs = match ty.kind { + ty::FnDef(_, substs) => substs, + _ => bug!("intrinsic with type {:?}", ty), + }; + return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| { + let span = tcx.def_span(def_id); + let error = ConstEvalErr { error: error.kind, stacktrace: vec![], span }; + error.report_as_error(tcx.at(span), "could not evaluate nullary intrinsic") + }); + } + + tcx.const_eval_raw(key).and_then(|val| validate_and_turn_into_const(tcx, val, key)) +} + +pub fn const_eval_raw_provider<'tcx>( + tcx: TyCtxt<'tcx>, + key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, +) -> ::rustc::mir::interpret::ConstEvalRawResult<'tcx> { + // Because the constant is computed twice (once per value of `Reveal`), we are at risk of + // reporting the same error twice here. To resolve this, we check whether we can evaluate the + // constant in the more restrictive `Reveal::UserFacing`, which most likely already was + // computed. For a large percentage of constants that will already have succeeded. Only + // associated constants of generic functions will fail due to not enough monomorphization + // information being available. + + // In case we fail in the `UserFacing` variant, we just do the real computation. + if key.param_env.reveal == Reveal::All { + let mut key = key.clone(); + key.param_env.reveal = Reveal::UserFacing; + match tcx.const_eval_raw(key) { + // try again with reveal all as requested + Err(ErrorHandled::TooGeneric) => {} + // dedupliate calls + other => return other, + } + } + if cfg!(debug_assertions) { + // Make sure we format the instance even if we do not print it. + // This serves as a regression test against an ICE on printing. + // The next two lines concatenated contain some discussion: + // https://rust-lang.zulipchat.com/#narrow/stream/146212-t-compiler.2Fconst-eval/ + // subject/anon_const_instance_printing/near/135980032 + let instance = key.value.instance.to_string(); + trace!("const eval: {:?} ({})", key, instance); + } + + let cid = key.value; + let def_id = cid.instance.def.def_id(); + + if def_id.is_local() && tcx.typeck_tables_of(def_id).tainted_by_errors { + return Err(ErrorHandled::Reported); + } + + let is_static = tcx.is_static(def_id); + + let span = tcx.def_span(cid.instance.def_id()); + let mut ecx = InterpCx::new( + tcx.at(span), + key.param_env, + CompileTimeInterpreter::new(), + MemoryExtra { can_access_statics: is_static }, + ); + + let res = ecx.load_mir(cid.instance.def, cid.promoted); + res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, *body)) + .and_then(|place| { + Ok(RawConst { alloc_id: place.ptr.assert_ptr().alloc_id, ty: place.layout.ty }) + }) + .map_err(|error| { + let err = error_to_const_error(&ecx, error); + // errors in statics are always emitted as fatal errors + if is_static { + // Ensure that if the above error was either `TooGeneric` or `Reported` + // an error must be reported. + let v = err.report_as_error(ecx.tcx, "could not evaluate static initializer"); + tcx.sess.delay_span_bug( + err.span, + &format!("static eval failure did not emit an error: {:#?}", v), + ); + v + } else if def_id.is_local() { + // constant defined in this crate, we can figure out a lint level! + match tcx.def_kind(def_id) { + // constants never produce a hard error at the definition site. Anything else is + // a backwards compatibility hazard (and will break old versions of winapi for sure) + // + // note that validation may still cause a hard error on this very same constant, + // because any code that existed before validation could not have failed validation + // thus preventing such a hard error from being a backwards compatibility hazard + Some(DefKind::Const) | Some(DefKind::AssocConst) => { + let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap(); + err.report_as_lint( + tcx.at(tcx.def_span(def_id)), + "any use of this value will cause an error", + hir_id, + Some(err.span), + ) + } + // promoting runtime code is only allowed to error if it references broken constants + // any other kind of error will be reported to the user as a deny-by-default lint + _ => { + if let Some(p) = cid.promoted { + let span = tcx.promoted_mir(def_id)[p].span; + if let err_inval!(ReferencedConstant) = err.error { + err.report_as_error( + tcx.at(span), + "evaluation of constant expression failed", + ) + } else { + err.report_as_lint( + tcx.at(span), + "reaching this expression at runtime will panic or abort", + tcx.hir().as_local_hir_id(def_id).unwrap(), + Some(err.span), + ) + } + // anything else (array lengths, enum initializers, constant patterns) are reported + // as hard errors + } else { + err.report_as_error(ecx.tcx, "evaluation of constant value failed") + } + } + } + } else { + // use of broken constant from other crate + err.report_as_error(ecx.tcx, "could not evaluate constant") + } + }) +} From 9a82458e70296eb55f9c7af7a9c892f8b5477684 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 25 Dec 2019 01:08:39 +0100 Subject: [PATCH 03/14] Move `eval_const_fn_call` to the `const_eval` module --- src/librustc_mir/const_eval/machine.rs | 55 ++++++++++++++++++------ src/librustc_mir/interpret/terminator.rs | 23 +--------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs index 0e3794ee3ab88..875e912b4ce3d 100644 --- a/src/librustc_mir/const_eval/machine.rs +++ b/src/librustc_mir/const_eval/machine.rs @@ -149,18 +149,9 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, // sensitive check here. But we can at least rule out functions that are not const // at all. if ecx.tcx.is_const_fn_raw(def_id) { - // If this function is a `const fn` then as an optimization we can query this - // evaluation immediately. - // - // For the moment we only do this for functions which take no arguments - // (or all arguments are ZSTs) so that we don't memoize too much. - // - // Because `#[track_caller]` adds an implicit non-ZST argument, we also cannot - // perform this optimization on items tagged with it. - let no_implicit_args = !instance.def.requires_caller_location(ecx.tcx()); - if args.iter().all(|a| a.layout.is_zst()) && no_implicit_args { - let gid = GlobalId { instance, promoted: None }; - ecx.eval_const_fn_call(gid, ret)?; + // If this function is a `const fn` then under certain circumstances we + // can evaluate call via the query system, thus memoizing all future calls. + if ecx.try_eval_const_fn_call(instance, ret, args)? { return Ok(None); } } else { @@ -326,3 +317,43 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, } } } + +impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { + /// Evaluate a const function where all arguments (if any) are zero-sized types. + /// The evaluation is memoized thanks to the query system. + /// + /// Returns `true` if the call has been evaluated. + fn try_eval_const_fn_call( + &mut self, + instance: ty::Instance<'tcx>, + ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, + args: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, bool> { + trace!("try_eval_const_fn_call: {:?}", instance); + // Because `#[track_caller]` adds an implicit non-ZST argument, we also cannot + // perform this optimization on items tagged with it. + if instance.def.requires_caller_location(self.tcx()) { + return Ok(false); + } + // For the moment we only do this for functions which take no arguments + // (or all arguments are ZSTs) so that we don't memoize too much. + if args.iter().any(|a| !a.layout.is_zst()) { + return Ok(false); + } + + let gid = GlobalId { instance, promoted: None }; + + let place = self.const_eval_raw(gid)?; + let dest = match ret { + Some((dest, _)) => dest, + // Don't memoize diverging function calls. + None => return Ok(false), + }; + + self.copy_op(place.into(), dest)?; + + self.return_to_block(ret.map(|r| r.1))?; + self.dump_place(*dest); + return Ok(true); + } +} diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs index 9bd288171b851..1cc22c03a05f9 100644 --- a/src/librustc_mir/interpret/terminator.rs +++ b/src/librustc_mir/interpret/terminator.rs @@ -7,8 +7,7 @@ use rustc_target::spec::abi::Abi; use syntax::source_map::Span; use super::{ - FnVal, GlobalId, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, - StackPopCleanup, + FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, StackPopCleanup, }; impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { @@ -402,26 +401,6 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - /// Evaluate a const function where all arguments (if any) are zero-sized types. - /// The evaluation is memoized thanks to the query system. - // FIXME: Consider moving this to `const_eval.rs`. - pub(crate) fn eval_const_fn_call( - &mut self, - gid: GlobalId<'tcx>, - ret: Option<(PlaceTy<'tcx, M::PointerTag>, mir::BasicBlock)>, - ) -> InterpResult<'tcx> { - trace!("eval_const_fn_call: {:?}", gid); - - let place = self.const_eval_raw(gid)?; - let dest = ret.ok_or_else(|| err_ub!(Unreachable))?.0; - - self.copy_op(place.into(), dest)?; - - self.return_to_block(ret.map(|r| r.1))?; - self.dump_place(*dest); - return Ok(()); - } - fn drop_in_place( &mut self, place: PlaceTy<'tcx, M::PointerTag>, From eda3fa9eb5e0b2eb3cee55a6cc0a23a207b56059 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 25 Dec 2019 01:09:47 +0100 Subject: [PATCH 04/14] Move `eval_body_using_ecx` to the only module it is used in --- src/librustc_mir/const_eval.rs | 51 ++------------------------- src/librustc_mir/const_eval/query.rs | 52 ++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index c308bf8a46c2f..aefc8a3ff0ffc 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -6,9 +6,8 @@ use std::fmt; use std::hash::Hash; use rustc::mir; -use rustc::mir::interpret::ScalarMaybeUndef; -use rustc::ty::layout::{self, LayoutOf, VariantIdx}; -use rustc::ty::{self, subst::Subst, TyCtxt}; +use rustc::ty::layout::{self, VariantIdx}; +use rustc::ty::{self, TyCtxt}; use syntax::{ source_map::{Span, DUMMY_SP}, @@ -16,8 +15,7 @@ use syntax::{ }; use crate::interpret::{ - intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, ImmTy, Immediate, InterpCx, - InterpResult, MPlaceTy, MemoryKind, OpTy, Scalar, StackPopCleanup, + intern_const_alloc_recursive, Allocation, ConstValue, ImmTy, Immediate, InterpCx, OpTy, Scalar, }; mod error; @@ -121,49 +119,6 @@ fn op_to_const<'tcx>( ecx.tcx.mk_const(ty::Const { val: ty::ConstKind::Value(val), ty: op.layout.ty }) } -// Returns a pointer to where the result lives -fn eval_body_using_ecx<'mir, 'tcx>( - ecx: &mut CompileTimeEvalContext<'mir, 'tcx>, - cid: GlobalId<'tcx>, - body: &'mir mir::Body<'tcx>, -) -> InterpResult<'tcx, MPlaceTy<'tcx>> { - debug!("eval_body_using_ecx: {:?}, {:?}", cid, ecx.param_env); - let tcx = ecx.tcx.tcx; - let layout = ecx.layout_of(body.return_ty().subst(tcx, cid.instance.substs))?; - assert!(!layout.is_unsized()); - let ret = ecx.allocate(layout, MemoryKind::Stack); - - let name = ty::tls::with(|tcx| tcx.def_path_str(cid.instance.def_id())); - let prom = cid.promoted.map_or(String::new(), |p| format!("::promoted[{:?}]", p)); - trace!("eval_body_using_ecx: pushing stack frame for global: {}{}", name, prom); - - // Assert all args (if any) are zero-sized types; `eval_body_using_ecx` doesn't - // make sense if the body is expecting nontrivial arguments. - // (The alternative would be to use `eval_fn_call` with an args slice.) - for arg in body.args_iter() { - let decl = body.local_decls.get(arg).expect("arg missing from local_decls"); - let layout = ecx.layout_of(decl.ty.subst(tcx, cid.instance.substs))?; - assert!(layout.is_zst()) - } - - ecx.push_stack_frame( - cid.instance, - body.span, - body, - Some(ret.into()), - StackPopCleanup::None { cleanup: false }, - )?; - - // The main interpreter loop. - ecx.run()?; - - // Intern the result - intern_const_alloc_recursive(ecx, tcx.static_mutability(cid.instance.def_id()), ret)?; - - debug!("eval_body_using_ecx done: {:?}", *ret); - Ok(ret) -} - /// Extracts a field of a (variant of a) const. // this function uses `unwrap` copiously, because an already validated constant must have valid // fields and can thus never fail outside of compiler bugs diff --git a/src/librustc_mir/const_eval/query.rs b/src/librustc_mir/const_eval/query.rs index 0a33143e8c682..1ab8377c4b938 100644 --- a/src/librustc_mir/const_eval/query.rs +++ b/src/librustc_mir/const_eval/query.rs @@ -1,13 +1,18 @@ use crate::interpret::eval_nullary_intrinsic; use rustc::hir::def::DefKind; +use rustc::mir; use rustc::mir::interpret::{ConstEvalErr, ErrorHandled}; use rustc::traits::Reveal; +use rustc::ty::{self, layout::LayoutOf, subst::Subst, TyCtxt}; use rustc::ty::{self, TyCtxt}; -use crate::interpret::{ConstValue, GlobalId, InterpCx, RawConst, RefTracking}; +use crate::interpret::{ + intern_const_alloc_recursive, ConstValue, GlobalId, InterpCx, InterpResult, MPlaceTy, + MemoryKind, RawConst, RefTracking, StackPopCleanup, +}; use super::{ - error_to_const_error, eval_body_using_ecx, mk_eval_cx, op_to_const, CompileTimeInterpreter, + error_to_const_error, mk_eval_cx, op_to_const, CompileTimeEvalContext, CompileTimeInterpreter, }; pub fn note_on_undefined_behavior_error() -> &'static str { @@ -214,3 +219,46 @@ pub fn const_eval_raw_provider<'tcx>( } }) } + +// Returns a pointer to where the result lives +fn eval_body_using_ecx<'mir, 'tcx>( + ecx: &mut CompileTimeEvalContext<'mir, 'tcx>, + cid: GlobalId<'tcx>, + body: &'mir mir::Body<'tcx>, +) -> InterpResult<'tcx, MPlaceTy<'tcx>> { + debug!("eval_body_using_ecx: {:?}, {:?}", cid, ecx.param_env); + let tcx = ecx.tcx.tcx; + let layout = ecx.layout_of(body.return_ty().subst(tcx, cid.instance.substs))?; + assert!(!layout.is_unsized()); + let ret = ecx.allocate(layout, MemoryKind::Stack); + + let name = ty::tls::with(|tcx| tcx.def_path_str(cid.instance.def_id())); + let prom = cid.promoted.map_or(String::new(), |p| format!("::promoted[{:?}]", p)); + trace!("eval_body_using_ecx: pushing stack frame for global: {}{}", name, prom); + + // Assert all args (if any) are zero-sized types; `eval_body_using_ecx` doesn't + // make sense if the body is expecting nontrivial arguments. + // (The alternative would be to use `eval_fn_call` with an args slice.) + for arg in body.args_iter() { + let decl = body.local_decls.get(arg).expect("arg missing from local_decls"); + let layout = ecx.layout_of(decl.ty.subst(tcx, cid.instance.substs))?; + assert!(layout.is_zst()) + } + + ecx.push_stack_frame( + cid.instance, + body.span, + body, + Some(ret.into()), + StackPopCleanup::None { cleanup: false }, + )?; + + // The main interpreter loop. + ecx.run()?; + + // Intern the result + intern_const_alloc_recursive(ecx, tcx.static_mutability(cid.instance.def_id()), ret)?; + + debug!("eval_body_using_ecx done: {:?}", *ret); + Ok(ret) +} From eea51e34275086158af22de1b55a4fd0bd39a4a3 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sun, 15 Dec 2019 17:50:37 +0100 Subject: [PATCH 05/14] Make some functions crate local --- src/librustc_mir/const_eval.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index aefc8a3ff0ffc..e614ad8539e7b 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -122,7 +122,7 @@ fn op_to_const<'tcx>( /// Extracts a field of a (variant of a) const. // this function uses `unwrap` copiously, because an already validated constant must have valid // fields and can thus never fail outside of compiler bugs -pub fn const_field<'tcx>( +pub(crate) fn const_field<'tcx>( tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, variant: Option, @@ -145,7 +145,7 @@ pub fn const_field<'tcx>( op_to_const(&ecx, field) } -pub fn const_caller_location<'tcx>( +pub(crate) fn const_caller_location<'tcx>( tcx: TyCtxt<'tcx>, (file, line, col): (Symbol, u32, u32), ) -> &'tcx ty::Const<'tcx> { @@ -165,7 +165,7 @@ pub fn const_caller_location<'tcx>( // this function uses `unwrap` copiously, because an already validated constant must have valid // fields and can thus never fail outside of compiler bugs -pub fn const_variant_index<'tcx>( +pub(crate) fn const_variant_index<'tcx>( tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, val: &'tcx ty::Const<'tcx>, From 43221a684db39d6f9b72db9f099c24e8e51f1dc4 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sun, 22 Dec 2019 21:10:43 +0100 Subject: [PATCH 06/14] Move a function to make its adjacent impl block easier to discover --- src/librustc_mir/const_eval/machine.rs | 82 ++++++++++++++------------ 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs index 875e912b4ce3d..115511fa70a00 100644 --- a/src/librustc_mir/const_eval/machine.rs +++ b/src/librustc_mir/const_eval/machine.rs @@ -17,6 +17,46 @@ use crate::interpret::{ use super::error::*; +impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { + /// Evaluate a const function where all arguments (if any) are zero-sized types. + /// The evaluation is memoized thanks to the query system. + /// + /// Returns `true` if the call has been evaluated. + fn try_eval_const_fn_call( + &mut self, + instance: ty::Instance<'tcx>, + ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, + args: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, bool> { + trace!("try_eval_const_fn_call: {:?}", instance); + // Because `#[track_caller]` adds an implicit non-ZST argument, we also cannot + // perform this optimization on items tagged with it. + if instance.def.requires_caller_location(self.tcx()) { + return Ok(false); + } + // For the moment we only do this for functions which take no arguments + // (or all arguments are ZSTs) so that we don't memoize too much. + if args.iter().any(|a| !a.layout.is_zst()) { + return Ok(false); + } + + let gid = GlobalId { instance, promoted: None }; + + let place = self.const_eval_raw(gid)?; + let dest = match ret { + Some((dest, _)) => dest, + // Don't memoize diverging function calls. + None => return Ok(false), + }; + + self.copy_op(place.into(), dest)?; + + self.return_to_block(ret.map(|r| r.1))?; + self.dump_place(*dest); + return Ok(true); + } +} + /// 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; @@ -318,42 +358,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, } } -impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { - /// Evaluate a const function where all arguments (if any) are zero-sized types. - /// The evaluation is memoized thanks to the query system. - /// - /// Returns `true` if the call has been evaluated. - fn try_eval_const_fn_call( - &mut self, - instance: ty::Instance<'tcx>, - ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, - args: &[OpTy<'tcx>], - ) -> InterpResult<'tcx, bool> { - trace!("try_eval_const_fn_call: {:?}", instance); - // Because `#[track_caller]` adds an implicit non-ZST argument, we also cannot - // perform this optimization on items tagged with it. - if instance.def.requires_caller_location(self.tcx()) { - return Ok(false); - } - // For the moment we only do this for functions which take no arguments - // (or all arguments are ZSTs) so that we don't memoize too much. - if args.iter().any(|a| !a.layout.is_zst()) { - return Ok(false); - } - - let gid = GlobalId { instance, promoted: None }; - - let place = self.const_eval_raw(gid)?; - let dest = match ret { - Some((dest, _)) => dest, - // Don't memoize diverging function calls. - None => return Ok(false), - }; - - self.copy_op(place.into(), dest)?; - - self.return_to_block(ret.map(|r| r.1))?; - self.dump_place(*dest); - return Ok(true); - } -} +// Please do not add any code below the above `Machine` trait impl. I (oli-obk) plan more cleanups +// so we can end up having a file with just that impl, but for now, let's keep the impl discoverable +// at the bottom of this file. From e21d2c8f9b797a05a1eba0610e5dae0ba0ced4b9 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 25 Dec 2019 01:15:26 +0100 Subject: [PATCH 07/14] Move all functions used by the queries to query.rs --- src/librustc_mir/const_eval.rs | 111 +-------------------------- src/librustc_mir/const_eval/query.rs | 95 +++++++++++++++++++++++ 2 files changed, 98 insertions(+), 108 deletions(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index e614ad8539e7b..579b79d92f8dc 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -1,22 +1,12 @@ // Not in interpret to make sure we do not use private implementation details -use std::convert::TryInto; -use std::error::Error; -use std::fmt; -use std::hash::Hash; - use rustc::mir; -use rustc::ty::layout::{self, VariantIdx}; +use rustc::ty::layout::VariantIdx; use rustc::ty::{self, TyCtxt}; -use syntax::{ - source_map::{Span, DUMMY_SP}, - symbol::Symbol, -}; +use syntax::{source_map::DUMMY_SP, symbol::Symbol}; -use crate::interpret::{ - intern_const_alloc_recursive, Allocation, ConstValue, ImmTy, Immediate, InterpCx, OpTy, Scalar, -}; +use crate::interpret::{intern_const_alloc_recursive, ConstValue, InterpCx}; mod error; mod query; @@ -24,101 +14,6 @@ mod query; pub use error::*; pub use query::*; -/// The `InterpCx` is only meant to be used to do field and index projections into constants for -/// `simd_shuffle` and const patterns in match arms. -/// -/// The function containing the `match` that is currently being analyzed may have generic bounds -/// that inform us about the generic bounds of the constant. E.g., using an associated constant -/// of a function's generic parameter will require knowledge about the bounds on the generic -/// parameter. These bounds are passed to `mk_eval_cx` via the `ParamEnv` argument. -fn mk_eval_cx<'mir, 'tcx>( - tcx: TyCtxt<'tcx>, - span: Span, - param_env: ty::ParamEnv<'tcx>, - can_access_statics: bool, -) -> CompileTimeEvalContext<'mir, 'tcx> { - debug!("mk_eval_cx: {:?}", param_env); - InterpCx::new( - tcx.at(span), - param_env, - CompileTimeInterpreter::new(), - MemoryExtra { can_access_statics }, - ) -} - -fn op_to_const<'tcx>( - ecx: &CompileTimeEvalContext<'_, 'tcx>, - op: OpTy<'tcx>, -) -> &'tcx ty::Const<'tcx> { - // We do not have value optimizations for everything. - // Only scalars and slices, since they are very common. - // Note that further down we turn scalars of undefined bits back to `ByRef`. These can result - // from scalar unions that are initialized with one of their zero sized variants. We could - // instead allow `ConstValue::Scalar` to store `ScalarMaybeUndef`, but that would affect all - // the usual cases of extracting e.g. a `usize`, without there being a real use case for the - // `Undef` situation. - let try_as_immediate = match op.layout.abi { - layout::Abi::Scalar(..) => true, - layout::Abi::ScalarPair(..) => match op.layout.ty.kind { - ty::Ref(_, inner, _) => match inner.kind { - ty::Slice(elem) => elem == ecx.tcx.types.u8, - ty::Str => true, - _ => false, - }, - _ => false, - }, - _ => false, - }; - let immediate = if try_as_immediate { - Err(ecx.read_immediate(op).expect("normalization works on validated constants")) - } else { - // It is guaranteed that any non-slice scalar pair is actually ByRef here. - // When we come back from raw const eval, we are always by-ref. The only way our op here is - // by-val is if we are in const_field, i.e., if this is (a field of) something that we - // "tried to make immediate" before. We wouldn't do that for non-slice scalar pairs or - // structs containing such. - op.try_as_mplace() - }; - let val = match immediate { - Ok(mplace) => { - let ptr = mplace.ptr.to_ptr().unwrap(); - let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); - ConstValue::ByRef { alloc, offset: ptr.offset } - } - // see comment on `let try_as_immediate` above - Err(ImmTy { imm: Immediate::Scalar(x), .. }) => match x { - ScalarMaybeUndef::Scalar(s) => ConstValue::Scalar(s), - ScalarMaybeUndef::Undef => { - // When coming out of "normal CTFE", we'll always have an `Indirect` operand as - // argument and we will not need this. The only way we can already have an - // `Immediate` is when we are called from `const_field`, and that `Immediate` - // comes from a constant so it can happen have `Undef`, because the indirect - // memory that was read had undefined bytes. - let mplace = op.assert_mem_place(); - let ptr = mplace.ptr.to_ptr().unwrap(); - let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); - ConstValue::ByRef { alloc, offset: ptr.offset } - } - }, - Err(ImmTy { imm: Immediate::ScalarPair(a, b), .. }) => { - let (data, start) = match a.not_undef().unwrap() { - Scalar::Ptr(ptr) => { - (ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), ptr.offset.bytes()) - } - Scalar::Raw { .. } => ( - ecx.tcx.intern_const_alloc(Allocation::from_byte_aligned_bytes(b"" as &[u8])), - 0, - ), - }; - let len = b.to_machine_usize(&ecx.tcx.tcx).unwrap(); - let start = start.try_into().unwrap(); - let len: usize = len.try_into().unwrap(); - ConstValue::Slice { data, start, end: start + len } - } - }; - ecx.tcx.mk_const(ty::Const { val: ty::ConstKind::Value(val), ty: op.layout.ty }) -} - /// Extracts a field of a (variant of a) const. // this function uses `unwrap` copiously, because an already validated constant must have valid // fields and can thus never fail outside of compiler bugs diff --git a/src/librustc_mir/const_eval/query.rs b/src/librustc_mir/const_eval/query.rs index 1ab8377c4b938..f92475e0375b0 100644 --- a/src/librustc_mir/const_eval/query.rs +++ b/src/librustc_mir/const_eval/query.rs @@ -262,3 +262,98 @@ fn eval_body_using_ecx<'mir, 'tcx>( debug!("eval_body_using_ecx done: {:?}", *ret); Ok(ret) } + +/// The `InterpCx` is only meant to be used to do field and index projections into constants for +/// `simd_shuffle` and const patterns in match arms. +/// +/// The function containing the `match` that is currently being analyzed may have generic bounds +/// that inform us about the generic bounds of the constant. E.g., using an associated constant +/// of a function's generic parameter will require knowledge about the bounds on the generic +/// parameter. These bounds are passed to `mk_eval_cx` via the `ParamEnv` argument. +pub(super) fn mk_eval_cx<'mir, 'tcx>( + tcx: TyCtxt<'tcx>, + span: Span, + param_env: ty::ParamEnv<'tcx>, + can_access_statics: bool, +) -> CompileTimeEvalContext<'mir, 'tcx> { + debug!("mk_eval_cx: {:?}", param_env); + InterpCx::new( + tcx.at(span), + param_env, + CompileTimeInterpreter::new(), + MemoryExtra { can_access_statics }, + ) +} + +pub(super) fn op_to_const<'tcx>( + ecx: &CompileTimeEvalContext<'_, 'tcx>, + op: OpTy<'tcx>, +) -> &'tcx ty::Const<'tcx> { + // We do not have value optimizations for everything. + // Only scalars and slices, since they are very common. + // Note that further down we turn scalars of undefined bits back to `ByRef`. These can result + // from scalar unions that are initialized with one of their zero sized variants. We could + // instead allow `ConstValue::Scalar` to store `ScalarMaybeUndef`, but that would affect all + // the usual cases of extracting e.g. a `usize`, without there being a real use case for the + // `Undef` situation. + let try_as_immediate = match op.layout.abi { + layout::Abi::Scalar(..) => true, + layout::Abi::ScalarPair(..) => match op.layout.ty.kind { + ty::Ref(_, inner, _) => match inner.kind { + ty::Slice(elem) => elem == ecx.tcx.types.u8, + ty::Str => true, + _ => false, + }, + _ => false, + }, + _ => false, + }; + let immediate = if try_as_immediate { + Err(ecx.read_immediate(op).expect("normalization works on validated constants")) + } else { + // It is guaranteed that any non-slice scalar pair is actually ByRef here. + // When we come back from raw const eval, we are always by-ref. The only way our op here is + // by-val is if we are in const_field, i.e., if this is (a field of) something that we + // "tried to make immediate" before. We wouldn't do that for non-slice scalar pairs or + // structs containing such. + op.try_as_mplace() + }; + let val = match immediate { + Ok(mplace) => { + let ptr = mplace.ptr.to_ptr().unwrap(); + let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); + ConstValue::ByRef { alloc, offset: ptr.offset } + } + // see comment on `let try_as_immediate` above + Err(ImmTy { imm: Immediate::Scalar(x), .. }) => match x { + ScalarMaybeUndef::Scalar(s) => ConstValue::Scalar(s), + ScalarMaybeUndef::Undef => { + // When coming out of "normal CTFE", we'll always have an `Indirect` operand as + // argument and we will not need this. The only way we can already have an + // `Immediate` is when we are called from `const_field`, and that `Immediate` + // comes from a constant so it can happen have `Undef`, because the indirect + // memory that was read had undefined bytes. + let mplace = op.assert_mem_place(); + let ptr = mplace.ptr.to_ptr().unwrap(); + let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); + ConstValue::ByRef { alloc, offset: ptr.offset } + } + }, + Err(ImmTy { imm: Immediate::ScalarPair(a, b), .. }) => { + let (data, start) = match a.not_undef().unwrap() { + Scalar::Ptr(ptr) => { + (ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), ptr.offset.bytes()) + } + Scalar::Raw { .. } => ( + ecx.tcx.intern_const_alloc(Allocation::from_byte_aligned_bytes(b"" as &[u8])), + 0, + ), + }; + let len = b.to_machine_usize(&ecx.tcx.tcx).unwrap(); + let start = start.try_into().unwrap(); + let len: usize = len.try_into().unwrap(); + ConstValue::Slice { data, start, end: start + len } + } + }; + ecx.tcx.mk_const(ty::Const { val: ty::ConstKind::Value(val), ty: op.layout.ty }) +} From 4ffdd9a16a38dfe795ba31e87441785606190dd5 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sun, 22 Dec 2019 22:20:46 +0100 Subject: [PATCH 08/14] Move function definitions before their first use --- src/librustc_mir/const_eval/query.rs | 276 +++++++++++++-------------- 1 file changed, 138 insertions(+), 138 deletions(-) diff --git a/src/librustc_mir/const_eval/query.rs b/src/librustc_mir/const_eval/query.rs index f92475e0375b0..2aeb6df6dd6b9 100644 --- a/src/librustc_mir/const_eval/query.rs +++ b/src/librustc_mir/const_eval/query.rs @@ -21,6 +21,144 @@ pub fn note_on_undefined_behavior_error() -> &'static str { repository if you believe it should not be considered undefined behavior." } +// Returns a pointer to where the result lives +fn eval_body_using_ecx<'mir, 'tcx>( + ecx: &mut CompileTimeEvalContext<'mir, 'tcx>, + cid: GlobalId<'tcx>, + body: &'mir mir::Body<'tcx>, +) -> InterpResult<'tcx, MPlaceTy<'tcx>> { + debug!("eval_body_using_ecx: {:?}, {:?}", cid, ecx.param_env); + let tcx = ecx.tcx.tcx; + let layout = ecx.layout_of(body.return_ty().subst(tcx, cid.instance.substs))?; + assert!(!layout.is_unsized()); + let ret = ecx.allocate(layout, MemoryKind::Stack); + + let name = ty::tls::with(|tcx| tcx.def_path_str(cid.instance.def_id())); + let prom = cid.promoted.map_or(String::new(), |p| format!("::promoted[{:?}]", p)); + trace!("eval_body_using_ecx: pushing stack frame for global: {}{}", name, prom); + + // Assert all args (if any) are zero-sized types; `eval_body_using_ecx` doesn't + // make sense if the body is expecting nontrivial arguments. + // (The alternative would be to use `eval_fn_call` with an args slice.) + for arg in body.args_iter() { + let decl = body.local_decls.get(arg).expect("arg missing from local_decls"); + let layout = ecx.layout_of(decl.ty.subst(tcx, cid.instance.substs))?; + assert!(layout.is_zst()) + } + + ecx.push_stack_frame( + cid.instance, + body.span, + body, + Some(ret.into()), + StackPopCleanup::None { cleanup: false }, + )?; + + // The main interpreter loop. + ecx.run()?; + + // Intern the result + intern_const_alloc_recursive(ecx, tcx.static_mutability(cid.instance.def_id()), ret)?; + + debug!("eval_body_using_ecx done: {:?}", *ret); + Ok(ret) +} + +/// The `InterpCx` is only meant to be used to do field and index projections into constants for +/// `simd_shuffle` and const patterns in match arms. +/// +/// The function containing the `match` that is currently being analyzed may have generic bounds +/// that inform us about the generic bounds of the constant. E.g., using an associated constant +/// of a function's generic parameter will require knowledge about the bounds on the generic +/// parameter. These bounds are passed to `mk_eval_cx` via the `ParamEnv` argument. +pub(super) fn mk_eval_cx<'mir, 'tcx>( + tcx: TyCtxt<'tcx>, + span: Span, + param_env: ty::ParamEnv<'tcx>, + can_access_statics: bool, +) -> CompileTimeEvalContext<'mir, 'tcx> { + debug!("mk_eval_cx: {:?}", param_env); + InterpCx::new( + tcx.at(span), + param_env, + CompileTimeInterpreter::new(), + MemoryExtra { can_access_statics }, + ) +} + +pub(super) fn op_to_const<'tcx>( + ecx: &CompileTimeEvalContext<'_, 'tcx>, + op: OpTy<'tcx>, +) -> &'tcx ty::Const<'tcx> { + // We do not have value optimizations for everything. + // Only scalars and slices, since they are very common. + // Note that further down we turn scalars of undefined bits back to `ByRef`. These can result + // from scalar unions that are initialized with one of their zero sized variants. We could + // instead allow `ConstValue::Scalar` to store `ScalarMaybeUndef`, but that would affect all + // the usual cases of extracting e.g. a `usize`, without there being a real use case for the + // `Undef` situation. + let try_as_immediate = match op.layout.abi { + layout::Abi::Scalar(..) => true, + layout::Abi::ScalarPair(..) => match op.layout.ty.kind { + ty::Ref(_, inner, _) => match inner.kind { + ty::Slice(elem) => elem == ecx.tcx.types.u8, + ty::Str => true, + _ => false, + }, + _ => false, + }, + _ => false, + }; + let immediate = if try_as_immediate { + Err(ecx.read_immediate(op).expect("normalization works on validated constants")) + } else { + // It is guaranteed that any non-slice scalar pair is actually ByRef here. + // When we come back from raw const eval, we are always by-ref. The only way our op here is + // by-val is if we are in const_field, i.e., if this is (a field of) something that we + // "tried to make immediate" before. We wouldn't do that for non-slice scalar pairs or + // structs containing such. + op.try_as_mplace() + }; + let val = match immediate { + Ok(mplace) => { + let ptr = mplace.ptr.to_ptr().unwrap(); + let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); + ConstValue::ByRef { alloc, offset: ptr.offset } + } + // see comment on `let try_as_immediate` above + Err(ImmTy { imm: Immediate::Scalar(x), .. }) => match x { + ScalarMaybeUndef::Scalar(s) => ConstValue::Scalar(s), + ScalarMaybeUndef::Undef => { + // When coming out of "normal CTFE", we'll always have an `Indirect` operand as + // argument and we will not need this. The only way we can already have an + // `Immediate` is when we are called from `const_field`, and that `Immediate` + // comes from a constant so it can happen have `Undef`, because the indirect + // memory that was read had undefined bytes. + let mplace = op.assert_mem_place(); + let ptr = mplace.ptr.to_ptr().unwrap(); + let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); + ConstValue::ByRef { alloc, offset: ptr.offset } + } + }, + Err(ImmTy { imm: Immediate::ScalarPair(a, b), .. }) => { + let (data, start) = match a.not_undef().unwrap() { + Scalar::Ptr(ptr) => { + (ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), ptr.offset.bytes()) + } + Scalar::Raw { .. } => ( + ecx.tcx.intern_const_alloc(Allocation::from_byte_aligned_bytes(b"" as &[u8])), + 0, + ), + }; + let len = b.to_machine_usize(&ecx.tcx.tcx).unwrap(); + let start = start.try_into().unwrap(); + let len: usize = len.try_into().unwrap(); + ConstValue::Slice { data, start, end: start + len } + } + }; + ecx.tcx.mk_const(ty::Const { val: ty::ConstKind::Value(val), ty: op.layout.ty }) +} + fn validate_and_turn_into_const<'tcx>( tcx: TyCtxt<'tcx>, constant: RawConst<'tcx>, @@ -219,141 +357,3 @@ pub fn const_eval_raw_provider<'tcx>( } }) } - -// Returns a pointer to where the result lives -fn eval_body_using_ecx<'mir, 'tcx>( - ecx: &mut CompileTimeEvalContext<'mir, 'tcx>, - cid: GlobalId<'tcx>, - body: &'mir mir::Body<'tcx>, -) -> InterpResult<'tcx, MPlaceTy<'tcx>> { - debug!("eval_body_using_ecx: {:?}, {:?}", cid, ecx.param_env); - let tcx = ecx.tcx.tcx; - let layout = ecx.layout_of(body.return_ty().subst(tcx, cid.instance.substs))?; - assert!(!layout.is_unsized()); - let ret = ecx.allocate(layout, MemoryKind::Stack); - - let name = ty::tls::with(|tcx| tcx.def_path_str(cid.instance.def_id())); - let prom = cid.promoted.map_or(String::new(), |p| format!("::promoted[{:?}]", p)); - trace!("eval_body_using_ecx: pushing stack frame for global: {}{}", name, prom); - - // Assert all args (if any) are zero-sized types; `eval_body_using_ecx` doesn't - // make sense if the body is expecting nontrivial arguments. - // (The alternative would be to use `eval_fn_call` with an args slice.) - for arg in body.args_iter() { - let decl = body.local_decls.get(arg).expect("arg missing from local_decls"); - let layout = ecx.layout_of(decl.ty.subst(tcx, cid.instance.substs))?; - assert!(layout.is_zst()) - } - - ecx.push_stack_frame( - cid.instance, - body.span, - body, - Some(ret.into()), - StackPopCleanup::None { cleanup: false }, - )?; - - // The main interpreter loop. - ecx.run()?; - - // Intern the result - intern_const_alloc_recursive(ecx, tcx.static_mutability(cid.instance.def_id()), ret)?; - - debug!("eval_body_using_ecx done: {:?}", *ret); - Ok(ret) -} - -/// The `InterpCx` is only meant to be used to do field and index projections into constants for -/// `simd_shuffle` and const patterns in match arms. -/// -/// The function containing the `match` that is currently being analyzed may have generic bounds -/// that inform us about the generic bounds of the constant. E.g., using an associated constant -/// of a function's generic parameter will require knowledge about the bounds on the generic -/// parameter. These bounds are passed to `mk_eval_cx` via the `ParamEnv` argument. -pub(super) fn mk_eval_cx<'mir, 'tcx>( - tcx: TyCtxt<'tcx>, - span: Span, - param_env: ty::ParamEnv<'tcx>, - can_access_statics: bool, -) -> CompileTimeEvalContext<'mir, 'tcx> { - debug!("mk_eval_cx: {:?}", param_env); - InterpCx::new( - tcx.at(span), - param_env, - CompileTimeInterpreter::new(), - MemoryExtra { can_access_statics }, - ) -} - -pub(super) fn op_to_const<'tcx>( - ecx: &CompileTimeEvalContext<'_, 'tcx>, - op: OpTy<'tcx>, -) -> &'tcx ty::Const<'tcx> { - // We do not have value optimizations for everything. - // Only scalars and slices, since they are very common. - // Note that further down we turn scalars of undefined bits back to `ByRef`. These can result - // from scalar unions that are initialized with one of their zero sized variants. We could - // instead allow `ConstValue::Scalar` to store `ScalarMaybeUndef`, but that would affect all - // the usual cases of extracting e.g. a `usize`, without there being a real use case for the - // `Undef` situation. - let try_as_immediate = match op.layout.abi { - layout::Abi::Scalar(..) => true, - layout::Abi::ScalarPair(..) => match op.layout.ty.kind { - ty::Ref(_, inner, _) => match inner.kind { - ty::Slice(elem) => elem == ecx.tcx.types.u8, - ty::Str => true, - _ => false, - }, - _ => false, - }, - _ => false, - }; - let immediate = if try_as_immediate { - Err(ecx.read_immediate(op).expect("normalization works on validated constants")) - } else { - // It is guaranteed that any non-slice scalar pair is actually ByRef here. - // When we come back from raw const eval, we are always by-ref. The only way our op here is - // by-val is if we are in const_field, i.e., if this is (a field of) something that we - // "tried to make immediate" before. We wouldn't do that for non-slice scalar pairs or - // structs containing such. - op.try_as_mplace() - }; - let val = match immediate { - Ok(mplace) => { - let ptr = mplace.ptr.to_ptr().unwrap(); - let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); - ConstValue::ByRef { alloc, offset: ptr.offset } - } - // see comment on `let try_as_immediate` above - Err(ImmTy { imm: Immediate::Scalar(x), .. }) => match x { - ScalarMaybeUndef::Scalar(s) => ConstValue::Scalar(s), - ScalarMaybeUndef::Undef => { - // When coming out of "normal CTFE", we'll always have an `Indirect` operand as - // argument and we will not need this. The only way we can already have an - // `Immediate` is when we are called from `const_field`, and that `Immediate` - // comes from a constant so it can happen have `Undef`, because the indirect - // memory that was read had undefined bytes. - let mplace = op.assert_mem_place(); - let ptr = mplace.ptr.to_ptr().unwrap(); - let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); - ConstValue::ByRef { alloc, offset: ptr.offset } - } - }, - Err(ImmTy { imm: Immediate::ScalarPair(a, b), .. }) => { - let (data, start) = match a.not_undef().unwrap() { - Scalar::Ptr(ptr) => { - (ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), ptr.offset.bytes()) - } - Scalar::Raw { .. } => ( - ecx.tcx.intern_const_alloc(Allocation::from_byte_aligned_bytes(b"" as &[u8])), - 0, - ), - }; - let len = b.to_machine_usize(&ecx.tcx.tcx).unwrap(); - let start = start.try_into().unwrap(); - let len: usize = len.try_into().unwrap(); - ConstValue::Slice { data, start, end: start + len } - } - }; - ecx.tcx.mk_const(ty::Const { val: ty::ConstKind::Value(val), ty: op.layout.ty }) -} From d5f1d75dd243125a3ecdbc0783b877144fd58c8d Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 23 Dec 2019 12:55:16 +0100 Subject: [PATCH 09/14] Rename `query` module --- src/librustc_mir/const_eval.rs | 4 ++-- src/librustc_mir/const_eval/{query.rs => eval_queries.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/librustc_mir/const_eval/{query.rs => eval_queries.rs} (100%) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index 579b79d92f8dc..f80185ba99d82 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -9,10 +9,10 @@ use syntax::{source_map::DUMMY_SP, symbol::Symbol}; use crate::interpret::{intern_const_alloc_recursive, ConstValue, InterpCx}; mod error; -mod query; +mod eval_queries; pub use error::*; -pub use query::*; +pub use eval_queries::*; /// Extracts a field of a (variant of a) const. // this function uses `unwrap` copiously, because an already validated constant must have valid diff --git a/src/librustc_mir/const_eval/query.rs b/src/librustc_mir/const_eval/eval_queries.rs similarity index 100% rename from src/librustc_mir/const_eval/query.rs rename to src/librustc_mir/const_eval/eval_queries.rs From 49f5b0834b21025946839328aa38ab123de54913 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 23 Dec 2019 15:02:55 +0100 Subject: [PATCH 10/14] Fix imports after rebase --- src/librustc_mir/const_eval/eval_queries.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/librustc_mir/const_eval/eval_queries.rs b/src/librustc_mir/const_eval/eval_queries.rs index 2aeb6df6dd6b9..37cda87c3f61b 100644 --- a/src/librustc_mir/const_eval/eval_queries.rs +++ b/src/librustc_mir/const_eval/eval_queries.rs @@ -1,19 +1,17 @@ +use super::{error_to_const_error, CompileTimeEvalContext, CompileTimeInterpreter}; use crate::interpret::eval_nullary_intrinsic; +use crate::interpret::{ + intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, ImmTy, Immediate, InterpCx, + InterpResult, MPlaceTy, MemoryKind, OpTy, RawConst, RefTracking, Scalar, ScalarMaybeUndef, + StackPopCleanup, +}; use rustc::hir::def::DefKind; use rustc::mir; use rustc::mir::interpret::{ConstEvalErr, ErrorHandled}; use rustc::traits::Reveal; -use rustc::ty::{self, layout::LayoutOf, subst::Subst, TyCtxt}; -use rustc::ty::{self, TyCtxt}; - -use crate::interpret::{ - intern_const_alloc_recursive, ConstValue, GlobalId, InterpCx, InterpResult, MPlaceTy, - MemoryKind, RawConst, RefTracking, StackPopCleanup, -}; - -use super::{ - error_to_const_error, mk_eval_cx, op_to_const, CompileTimeEvalContext, CompileTimeInterpreter, -}; +use rustc::ty::{self, layout, layout::LayoutOf, subst::Subst, TyCtxt}; +use std::convert::TryInto; +use syntax::source_map::Span; pub fn note_on_undefined_behavior_error() -> &'static str { "The rules on what exactly is undefined behavior aren't clear, \ From b97abd0761d3949cd6e17ba0d31c3d8ea1c8f0fc Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 23 Dec 2019 17:31:55 +0100 Subject: [PATCH 11/14] Tidy --- src/librustc_mir/const_eval/eval_queries.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/librustc_mir/const_eval/eval_queries.rs b/src/librustc_mir/const_eval/eval_queries.rs index 37cda87c3f61b..6b0635f1c76c5 100644 --- a/src/librustc_mir/const_eval/eval_queries.rs +++ b/src/librustc_mir/const_eval/eval_queries.rs @@ -310,11 +310,13 @@ pub fn const_eval_raw_provider<'tcx>( // constant defined in this crate, we can figure out a lint level! match tcx.def_kind(def_id) { // constants never produce a hard error at the definition site. Anything else is - // a backwards compatibility hazard (and will break old versions of winapi for sure) + // a backwards compatibility hazard (and will break old versions of winapi for + // sure) // // note that validation may still cause a hard error on this very same constant, - // because any code that existed before validation could not have failed validation - // thus preventing such a hard error from being a backwards compatibility hazard + // because any code that existed before validation could not have failed + // validation thus preventing such a hard error from being a backwards + // compatibility hazard Some(DefKind::Const) | Some(DefKind::AssocConst) => { let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap(); err.report_as_lint( @@ -324,8 +326,9 @@ pub fn const_eval_raw_provider<'tcx>( Some(err.span), ) } - // promoting runtime code is only allowed to error if it references broken constants - // any other kind of error will be reported to the user as a deny-by-default lint + // promoting runtime code is only allowed to error if it references broken + // constants any other kind of error will be reported to the user as a + // deny-by-default lint _ => { if let Some(p) = cid.promoted { let span = tcx.promoted_mir(def_id)[p].span; @@ -342,8 +345,8 @@ pub fn const_eval_raw_provider<'tcx>( Some(err.span), ) } - // anything else (array lengths, enum initializers, constant patterns) are reported - // as hard errors + // anything else (array lengths, enum initializers, constant patterns) are + // reported as hard errors } else { err.report_as_error(ecx.tcx, "evaluation of constant value failed") } From 2e66f85111da32cc7789957097b7d10c3e314515 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 23 Dec 2019 17:33:09 +0100 Subject: [PATCH 12/14] Bail out before running the query --- src/librustc_mir/const_eval/machine.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs index 115511fa70a00..0cb654b30ccb6 100644 --- a/src/librustc_mir/const_eval/machine.rs +++ b/src/librustc_mir/const_eval/machine.rs @@ -40,15 +40,16 @@ impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { return Ok(false); } - let gid = GlobalId { instance, promoted: None }; - - let place = self.const_eval_raw(gid)?; let dest = match ret { Some((dest, _)) => dest, // Don't memoize diverging function calls. None => return Ok(false), }; + let gid = GlobalId { instance, promoted: None }; + + let place = self.const_eval_raw(gid)?; + self.copy_op(place.into(), dest)?; self.return_to_block(ret.map(|r| r.1))?; From 7d5f36a1e5a277da8e1419557ba1edcfe94c6f07 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 23 Dec 2019 23:11:03 +0100 Subject: [PATCH 13/14] A cycle error on a diverging function is now a const stack overflow again --- .../consts/uninhabited-const-issue-61744.rs | 8 +- .../uninhabited-const-issue-61744.stderr | 99 ++++++++++++++++--- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/src/test/ui/consts/uninhabited-const-issue-61744.rs b/src/test/ui/consts/uninhabited-const-issue-61744.rs index 945017b49373f..15436f9c1b2cf 100644 --- a/src/test/ui/consts/uninhabited-const-issue-61744.rs +++ b/src/test/ui/consts/uninhabited-const-issue-61744.rs @@ -5,15 +5,15 @@ pub const unsafe fn fake_type() -> T { } pub const unsafe fn hint_unreachable() -> ! { - fake_type() //~ ERROR cycle detected when const-evaluating `hint_unreachable` [E0391] + fake_type() //~ ERROR evaluation of constant value failed } trait Const { - const CONSTANT: i32 = unsafe { fake_type() }; + const CONSTANT: i32 = unsafe { fake_type() }; //~ ERROR any use of this value will cause an err } -impl Const for T {} +impl Const for T {} pub fn main() -> () { - dbg!(i32::CONSTANT); + dbg!(i32::CONSTANT); //~ ERROR erroneous constant used } diff --git a/src/test/ui/consts/uninhabited-const-issue-61744.stderr b/src/test/ui/consts/uninhabited-const-issue-61744.stderr index 2a25ecc55e874..1f3e2cf5b2f58 100644 --- a/src/test/ui/consts/uninhabited-const-issue-61744.stderr +++ b/src/test/ui/consts/uninhabited-const-issue-61744.stderr @@ -1,21 +1,96 @@ -error[E0391]: cycle detected when const-evaluating `hint_unreachable` +error[E0080]: evaluation of constant value failed --> $DIR/uninhabited-const-issue-61744.rs:8:5 | +LL | hint_unreachable() + | ------------------ + | | + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 + | inside call to `hint_unreachable` at $DIR/uninhabited-const-issue-61744.rs:4:5 +... LL | fake_type() | ^^^^^^^^^^^ + | | + | reached the configured maximum number of stack frames + | inside call to `fake_type::` at $DIR/uninhabited-const-issue-61744.rs:8:5 + +error: any use of this value will cause an error + --> $DIR/uninhabited-const-issue-61744.rs:12:36 | -note: ...which requires const-evaluating `fake_type`... - --> $DIR/uninhabited-const-issue-61744.rs:4:5 +LL | const CONSTANT: i32 = unsafe { fake_type() }; + | -------------------------------^^^^^^^^^^^--- + | | + | referenced constant has errors | -LL | hint_unreachable() - | ^^^^^^^^^^^^^^^^^^ - = note: ...which again requires const-evaluating `hint_unreachable`, completing the cycle -note: cycle used when const-evaluating `fake_type` - --> $DIR/uninhabited-const-issue-61744.rs:4:5 + = note: `#[deny(const_err)]` on by default + +error[E0080]: erroneous constant used + --> $DIR/uninhabited-const-issue-61744.rs:18:10 | -LL | hint_unreachable() - | ^^^^^^^^^^^^^^^^^^ +LL | dbg!(i32::CONSTANT); + | ^^^^^^^^^^^^^ referenced constant has errors -error: aborting due to previous error +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0391`. +For more information about this error, try `rustc --explain E0080`. From 07df1479e5c79e5f62520d5e13796a6696a16421 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 25 Dec 2019 01:28:30 +0100 Subject: [PATCH 14/14] Rebase fallout --- src/librustc_mir/const_eval.rs | 2 ++ src/librustc_mir/const_eval/eval_queries.rs | 2 +- src/librustc_mir/const_eval/machine.rs | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/librustc_mir/const_eval.rs b/src/librustc_mir/const_eval.rs index f80185ba99d82..63e63f304d213 100644 --- a/src/librustc_mir/const_eval.rs +++ b/src/librustc_mir/const_eval.rs @@ -10,9 +10,11 @@ use crate::interpret::{intern_const_alloc_recursive, ConstValue, InterpCx}; mod error; mod eval_queries; +mod machine; pub use error::*; pub use eval_queries::*; +pub use machine::*; /// Extracts a field of a (variant of a) const. // this function uses `unwrap` copiously, because an already validated constant must have valid diff --git a/src/librustc_mir/const_eval/eval_queries.rs b/src/librustc_mir/const_eval/eval_queries.rs index 6b0635f1c76c5..62ec4bbaec769 100644 --- a/src/librustc_mir/const_eval/eval_queries.rs +++ b/src/librustc_mir/const_eval/eval_queries.rs @@ -1,4 +1,4 @@ -use super::{error_to_const_error, CompileTimeEvalContext, CompileTimeInterpreter}; +use super::{error_to_const_error, CompileTimeEvalContext, CompileTimeInterpreter, MemoryExtra}; use crate::interpret::eval_nullary_intrinsic; use crate::interpret::{ intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, ImmTy, Immediate, InterpCx, diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs index 0cb654b30ccb6..a76153c19ec67 100644 --- a/src/librustc_mir/const_eval/machine.rs +++ b/src/librustc_mir/const_eval/machine.rs @@ -80,11 +80,11 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> { #[derive(Copy, Clone, Debug)] pub struct MemoryExtra { /// Whether this machine may read from statics - can_access_statics: bool, + pub(super) can_access_statics: bool, } impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { - fn new() -> Self { + pub(super) fn new() -> Self { CompileTimeInterpreter { loop_detector: Default::default(), steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED,