use std::borrow::Cow;
use std::cell::Cell;
-use rustc::lint;
-use rustc::mir::interpret::{InterpResult, Scalar};
-use rustc::mir::visit::{
+use rustc_ast::ast::Mutability;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::DefKind;
+use rustc_hir::HirId;
+use rustc_index::vec::IndexVec;
+use rustc_middle::mir::interpret::{InterpResult, Scalar};
+use rustc_middle::mir::visit::{
MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
};
-use rustc::mir::{
+use rustc_middle::mir::{
read_only, AggregateKind, AssertKind, BasicBlock, BinOp, Body, BodyAndCache, ClearCrossCrate,
Constant, Local, LocalDecl, LocalKind, Location, Operand, Place, ReadOnlyBodyAndCache, Rvalue,
SourceInfo, SourceScope, SourceScopeData, Statement, StatementKind, Terminator, TerminatorKind,
UnOp, RETURN_PLACE,
};
-use rustc::ty::layout::{
- HasDataLayout, HasTyCtxt, LayoutError, LayoutOf, Size, TargetDataLayout, TyLayout,
-};
-use rustc::ty::subst::{InternalSubsts, Subst};
-use rustc::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable};
-use rustc_ast::ast::Mutability;
-use rustc_data_structures::fx::FxHashMap;
-use rustc_hir::def::DefKind;
-use rustc_hir::HirId;
-use rustc_index::vec::IndexVec;
-use rustc_infer::traits;
-use rustc_span::Span;
+use rustc_middle::ty::layout::{HasTyCtxt, LayoutError, TyAndLayout};
+use rustc_middle::ty::subst::{InternalSubsts, Subst};
+use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable};
+use rustc_session::lint;
+use rustc_span::{def_id::DefId, Span};
+use rustc_target::abi::{HasDataLayout, LayoutOf, Size, TargetDataLayout};
+use rustc_trait_selection::traits;
use crate::const_eval::error_to_const_error;
use crate::interpret::{
/// The maximum number of bytes that we'll allocate space for a return value.
const MAX_ALLOC_LIMIT: u64 = 1024;
+/// Macro for machine-specific `InterpError` without allocation.
+/// (These will never be shown to the user, but they help diagnose ICEs.)
+macro_rules! throw_machine_stop_str {
+ ($($tt:tt)*) => {{
+ // We make a new local type for it. The type itself does not carry any information,
+ // but its vtable (for the `MachineStopType` trait) does.
+ struct Zst;
+ // Debug-printing this type shows the desired string.
+ impl std::fmt::Debug for Zst {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, $($tt)*)
+ }
+ }
+ impl rustc_middle::mir::interpret::MachineStopType for Zst {}
+ throw_machine_stop!(Zst)
+ }};
+}
+
pub struct ConstProp;
impl<'tcx> MirPass<'tcx> for ConstProp {
return;
}
- use rustc::hir::map::blocks::FnLikeNode;
+ use rustc_middle::hir::map::blocks::FnLikeNode;
let hir_id = tcx
.hir()
.as_local_hir_id(source.def_id())
.predicates_of(source.def_id())
.predicates
.iter()
- .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None })
- .collect();
+ .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
if !traits::normalize_and_test_predicates(
tcx,
- traits::elaborate_predicates(tcx, predicates).collect(),
+ traits::elaborate_predicates(tcx, predicates).map(|o| o.predicate).collect(),
) {
trace!("ConstProp skipped for {:?}: found unsatisfiable predicates", source.def_id());
return;
}
}
-struct ConstPropMachine;
+struct ConstPropMachine<'mir, 'tcx> {
+ /// The virtual call stack.
+ stack: Vec<Frame<'mir, 'tcx, (), ()>>,
+}
+
+impl<'mir, 'tcx> ConstPropMachine<'mir, 'tcx> {
+ fn new() -> Self {
+ Self { stack: Vec::new() }
+ }
+}
-impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine {
- type MemoryKinds = !;
+impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> {
+ type MemoryKind = !;
type PointerTag = ();
type ExtraFnVal = !;
type MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation)>;
- const STATIC_KIND: Option<!> = None;
+ const GLOBAL_KIND: Option<!> = None; // no copying of globals from `tcx` to machine memory
- const CHECK_ALIGN: bool = false;
+ #[inline(always)]
+ fn enforce_alignment(_memory_extra: &Self::MemoryExtra) -> bool {
+ false
+ }
#[inline(always)]
fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
fn find_mir_or_eval_fn(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
- _span: Span,
_instance: ty::Instance<'tcx>,
_args: &[OpTy<'tcx>],
_ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
fn call_intrinsic(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
- _span: Span,
_instance: ty::Instance<'tcx>,
_args: &[OpTy<'tcx>],
_ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
_unwind: Option<BasicBlock>,
) -> InterpResult<'tcx> {
- throw_unsup!(ConstPropUnsupported("calling intrinsics isn't supported in ConstProp"));
+ throw_machine_stop_str!("calling intrinsics isn't supported in ConstProp")
}
fn assert_panic(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
- _span: Span,
- _msg: &rustc::mir::AssertMessage<'tcx>,
- _unwind: Option<rustc::mir::BasicBlock>,
+ _msg: &rustc_middle::mir::AssertMessage<'tcx>,
+ _unwind: Option<rustc_middle::mir::BasicBlock>,
) -> InterpResult<'tcx> {
- bug!("panics terminators are not evaluated in ConstProp");
+ bug!("panics terminators are not evaluated in ConstProp")
}
fn ptr_to_int(_mem: &Memory<'mir, 'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx, u64> {
- throw_unsup!(ConstPropUnsupported("ptr-to-int casts aren't supported in ConstProp"));
+ throw_unsup!(ReadPointerAsBytes)
}
fn binary_ptr_op(
_right: ImmTy<'tcx>,
) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> {
// We can't do this because aliasing of memory can differ between const eval and llvm
- throw_unsup!(ConstPropUnsupported(
- "pointer arithmetic or comparisons aren't supported \
- in ConstProp"
- ));
+ throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
}
#[inline(always)]
}
#[inline(always)]
- fn tag_static_base_pointer(_memory_extra: &(), _id: AllocId) -> Self::PointerTag {
- ()
- }
+ fn tag_global_base_pointer(_memory_extra: &(), _id: AllocId) -> Self::PointerTag {}
fn box_alloc(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
_dest: PlaceTy<'tcx>,
) -> InterpResult<'tcx> {
- throw_unsup!(ConstPropUnsupported("can't const prop `box` keyword"));
+ throw_machine_stop_str!("can't const prop heap allocations")
}
fn access_local(
let l = &frame.locals[local];
if l.value == LocalValue::Uninitialized {
- throw_unsup!(ConstPropUnsupported("tried to access an uninitialized local"));
+ throw_machine_stop_str!("tried to access an uninitialized local")
}
l.access()
}
- fn before_access_static(
+ fn before_access_global(
_memory_extra: &(),
+ _alloc_id: AllocId,
allocation: &Allocation<Self::PointerTag, Self::AllocExtra>,
+ _static_def_id: Option<DefId>,
+ is_write: bool,
) -> InterpResult<'tcx> {
- // if the static allocation is mutable or if it has relocations (it may be legal to mutate
- // the memory behind that in the future), then we can't const prop it
- if allocation.mutability == Mutability::Mut || allocation.relocations().len() > 0 {
- throw_unsup!(ConstPropUnsupported("can't eval mutable statics in ConstProp"));
+ if is_write {
+ throw_machine_stop_str!("can't write to global");
+ }
+ // If the static allocation is mutable, then we can't const prop it as its content
+ // might be different at runtime.
+ if allocation.mutability == Mutability::Mut {
+ throw_machine_stop_str!("can't access mutable globals in ConstProp");
}
Ok(())
}
#[inline(always)]
- fn stack_push(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
- Ok(())
+ fn init_frame_extra(
+ _ecx: &mut InterpCx<'mir, 'tcx, Self>,
+ frame: Frame<'mir, 'tcx>,
+ ) -> InterpResult<'tcx, Frame<'mir, 'tcx>> {
+ Ok(frame)
+ }
+
+ #[inline(always)]
+ fn stack(
+ ecx: &'a InterpCx<'mir, 'tcx, Self>,
+ ) -> &'a [Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>] {
+ &ecx.machine.stack
+ }
+
+ #[inline(always)]
+ fn stack_mut(
+ ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
+ ) -> &'a mut Vec<Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>> {
+ &mut ecx.machine.stack
}
}
/// Finds optimization opportunities on the MIR.
struct ConstPropagator<'mir, 'tcx> {
- ecx: InterpCx<'mir, 'tcx, ConstPropMachine>,
+ ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
tcx: TyCtxt<'tcx>,
can_const_prop: IndexVec<Local, ConstPropMode>,
param_env: ParamEnv<'tcx>,
impl<'mir, 'tcx> LayoutOf for ConstPropagator<'mir, 'tcx> {
type Ty = Ty<'tcx>;
- type TyLayout = Result<TyLayout<'tcx>, LayoutError<'tcx>>;
+ type TyAndLayout = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
- fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyLayout {
+ fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyAndLayout {
self.tcx.layout_of(self.param_env.and(ty))
}
}
) -> ConstPropagator<'mir, 'tcx> {
let def_id = source.def_id();
let substs = &InternalSubsts::identity_for_item(tcx, def_id);
- let mut param_env = tcx.param_env(def_id);
-
- // If we're evaluating inside a monomorphic function, then use `Reveal::All` because
- // we want to see the same instances that codegen will see. This allows us to `resolve()`
- // specializations.
- if !substs.needs_subst() {
- param_env = param_env.with_reveal_all();
- }
+ let param_env = tcx.param_env(def_id).with_reveal_all();
let span = tcx.def_span(def_id);
- let mut ecx = InterpCx::new(tcx.at(span), param_env, ConstPropMachine, ());
+ let mut ecx = InterpCx::new(tcx.at(span), param_env, ConstPropMachine::new(), ());
let can_const_prop = CanConstProp::check(body);
let ret = ecx
ecx.push_stack_frame(
Instance::new(def_id, substs),
- span,
dummy_body,
ret.map(Into::into),
StackPopCleanup::None { cleanup: false },
where
F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
{
- let r = match f(self) {
+ match f(self) {
Ok(val) => Some(val),
Err(error) => {
// Some errors shouldn't come up because creating them causes
// an allocation, which we should avoid. When that happens,
// dedicated error variants should be introduced instead.
- // Only test this in debug builds though to avoid disruptions.
- debug_assert!(
+ assert!(
!error.kind.allocates(),
"const-prop encountered allocating error: {}",
error
);
None
}
- };
- r
+ }
}
fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
- self.ecx.tcx.span = c.span;
-
// FIXME we need to revisit this for #67176
if c.needs_subst() {
return None;
match self.ecx.eval_const_to_op(c.literal, None) {
Ok(op) => Some(op),
Err(error) => {
+ // Make sure errors point at the constant.
+ self.ecx.set_span(c.span);
let err = error_to_const_error(&self.ecx, error);
if let Some(lint_root) = self.lint_root(source_info) {
let lint_only = match c.literal.val {
}
}
- fn eval_place(&mut self, place: &Place<'tcx>) -> Option<OpTy<'tcx>> {
+ fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
trace!("eval_place(place={:?})", place);
self.use_ecx(|this| this.ecx.eval_place_to_op(place, None))
}
fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
match *op {
Operand::Constant(ref c) => self.eval_constant(c, source_info),
- Operand::Move(ref place) | Operand::Copy(ref place) => self.eval_place(place),
+ Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
}
}
err.span_label(source_info.span, format!("{:?}", panic));
err.emit()
});
- return None;
+ None
}
fn check_unary_op(
fn const_prop(
&mut self,
rvalue: &Rvalue<'tcx>,
- place_layout: TyLayout<'tcx>,
+ place_layout: TyAndLayout<'tcx>,
source_info: SourceInfo,
- place: &Place<'tcx>,
+ place: Place<'tcx>,
) -> Option<()> {
// #66397: Don't try to eval into large places as that can cause an OOM
if place_layout.size >= Size::from_bytes(MAX_ALLOC_LIMIT) {
let ty1 = substs[0].expect_ty();
let ty2 = substs[1].expect_ty();
let ty_is_scalar = |ty| {
- this.ecx.layout_of(ty).ok().map(|ty| ty.details.abi.is_scalar())
+ this.ecx.layout_of(ty).ok().map(|layout| layout.abi.is_scalar())
== Some(true)
};
if ty_is_scalar(ty1) && ty_is_scalar(ty2) {
trace!("local {:?} can't be const propagated because it's not a temporary", local);
}
}
- cpv.visit_body(body);
+ cpv.visit_body(&body);
cpv.can_const_prop
}
}
impl<'tcx> Visitor<'tcx> for CanConstProp {
fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
- use rustc::mir::visit::PlaceContext::*;
+ use rustc_middle::mir::visit::PlaceContext::*;
match context {
// Constants must have at most one write
// FIXME(oli-obk): we could be more powerful here, if the multiple writes
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
trace!("visit_statement: {:?}", statement);
let source_info = statement.source_info;
+ self.ecx.set_span(source_info.span);
self.source_info = Some(source_info);
- if let StatementKind::Assign(box (ref place, ref mut rval)) = statement.kind {
+ if let StatementKind::Assign(box (place, ref mut rval)) = statement.kind {
let place_ty: Ty<'tcx> = place.ty(&self.local_decls, self.tcx).ty;
if let Ok(place_layout) = self.tcx.layout_of(self.param_env.and(place_ty)) {
if let Some(local) = place.as_local() {
fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
let source_info = terminator.source_info;
+ self.ecx.set_span(source_info.span);
self.source_info = Some(source_info);
self.super_terminator(terminator, location);
match &mut terminator.kind {