Auto merge of #108872 - cjgillot:simp-const-prop, r=oli-obk
Strengthen state tracking in const-prop Some/many of the changes are replicated between both the const-prop lint and the const-prop optimization. Behaviour changes: - const-prop opt does not give a span to propagated values. This was useless as that span's primary purpose is to diagnose evaluation failure in codegen. - we remove the `OnlyPropagateInto` mode. It was only used for function arguments, which are better modeled by a write before entry. - the tracking of assignments and discriminants make clearer that we do nothing in `NoPropagation` mode or on indirect places.
This commit is contained in:
commit
b05bb29008
14 changed files with 254 additions and 335 deletions
|
@ -1,12 +1,9 @@
|
|||
//! Propagates constants for early reporting of statically known
|
||||
//! assertion failures
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use either::Right;
|
||||
|
||||
use rustc_const_eval::const_eval::CheckAlignment;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::vec::IndexVec;
|
||||
|
@ -17,7 +14,7 @@ use rustc_middle::mir::*;
|
|||
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
|
||||
use rustc_middle::ty::InternalSubsts;
|
||||
use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt};
|
||||
use rustc_span::{def_id::DefId, Span};
|
||||
use rustc_span::{def_id::DefId, Span, DUMMY_SP};
|
||||
use rustc_target::abi::{self, Align, HasDataLayout, Size, TargetDataLayout};
|
||||
use rustc_target::spec::abi::Abi as CallAbi;
|
||||
use rustc_trait_selection::traits;
|
||||
|
@ -25,8 +22,8 @@ use rustc_trait_selection::traits;
|
|||
use crate::MirPass;
|
||||
use rustc_const_eval::interpret::{
|
||||
self, compile_time_machine, AllocId, ConstAllocation, ConstValue, CtfeValidationMode, Frame,
|
||||
ImmTy, Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, PlaceTy,
|
||||
Pointer, Scalar, StackPopCleanup, StackPopUnwind,
|
||||
ImmTy, Immediate, InterpCx, InterpResult, LocalValue, MemoryKind, OpTy, PlaceTy, Pointer,
|
||||
Scalar, StackPopCleanup, StackPopUnwind,
|
||||
};
|
||||
|
||||
/// The maximum number of bytes that we'll allocate space for a local or the return value.
|
||||
|
@ -154,24 +151,12 @@ impl<'tcx> MirPass<'tcx> for ConstProp {
|
|||
pub struct ConstPropMachine<'mir, 'tcx> {
|
||||
/// The virtual call stack.
|
||||
stack: Vec<Frame<'mir, 'tcx>>,
|
||||
/// `OnlyInsideOwnBlock` locals that were written in the current block get erased at the end.
|
||||
pub written_only_inside_own_block_locals: FxHashSet<Local>,
|
||||
/// Locals that need to be cleared after every block terminates.
|
||||
pub only_propagate_inside_block_locals: BitSet<Local>,
|
||||
pub can_const_prop: IndexVec<Local, ConstPropMode>,
|
||||
}
|
||||
|
||||
impl ConstPropMachine<'_, '_> {
|
||||
pub fn new(
|
||||
only_propagate_inside_block_locals: BitSet<Local>,
|
||||
can_const_prop: IndexVec<Local, ConstPropMode>,
|
||||
) -> Self {
|
||||
Self {
|
||||
stack: Vec::new(),
|
||||
written_only_inside_own_block_locals: Default::default(),
|
||||
only_propagate_inside_block_locals,
|
||||
can_const_prop,
|
||||
}
|
||||
pub fn new(can_const_prop: IndexVec<Local, ConstPropMode>) -> Self {
|
||||
Self { stack: Vec::new(), can_const_prop }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,16 +242,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
|
|||
frame: usize,
|
||||
local: Local,
|
||||
) -> InterpResult<'tcx, &'a mut interpret::Operand<Self::Provenance>> {
|
||||
if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation {
|
||||
throw_machine_stop_str!("tried to write to a local that is marked as not propagatable")
|
||||
}
|
||||
if frame == 0 && ecx.machine.only_propagate_inside_block_locals.contains(local) {
|
||||
trace!(
|
||||
"mutating local {:?} which is restricted to its block. \
|
||||
Will remove it from const-prop after block is finished.",
|
||||
local
|
||||
);
|
||||
ecx.machine.written_only_inside_own_block_locals.insert(local);
|
||||
assert_eq!(frame, 0);
|
||||
match ecx.machine.can_const_prop[local] {
|
||||
ConstPropMode::NoPropagation => {
|
||||
throw_machine_stop_str!(
|
||||
"tried to write to a local that is marked as not propagatable"
|
||||
)
|
||||
}
|
||||
ConstPropMode::OnlyInsideOwnBlock | ConstPropMode::FullConstProp => {}
|
||||
}
|
||||
ecx.machine.stack[frame].locals[local].access_mut()
|
||||
}
|
||||
|
@ -328,9 +311,6 @@ struct ConstPropagator<'mir, 'tcx> {
|
|||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
local_decls: &'mir IndexVec<Local, LocalDecl<'tcx>>,
|
||||
// Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
|
||||
// the last known `SourceInfo` here and just keep revisiting it.
|
||||
source_info: Option<SourceInfo>,
|
||||
}
|
||||
|
||||
impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> {
|
||||
|
@ -374,17 +354,11 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
let param_env = tcx.param_env_reveal_all_normalized(def_id);
|
||||
|
||||
let can_const_prop = CanConstProp::check(tcx, param_env, body);
|
||||
let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
|
||||
for (l, mode) in can_const_prop.iter_enumerated() {
|
||||
if *mode == ConstPropMode::OnlyInsideOwnBlock {
|
||||
only_propagate_inside_block_locals.insert(l);
|
||||
}
|
||||
}
|
||||
let mut ecx = InterpCx::new(
|
||||
tcx,
|
||||
tcx.def_span(def_id),
|
||||
param_env,
|
||||
ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop),
|
||||
ConstPropMachine::new(can_const_prop),
|
||||
);
|
||||
|
||||
let ret_layout = ecx
|
||||
|
@ -411,13 +385,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
)
|
||||
.expect("failed to push initial stack frame");
|
||||
|
||||
ConstPropagator {
|
||||
ecx,
|
||||
tcx,
|
||||
param_env,
|
||||
local_decls: &dummy_body.local_decls,
|
||||
source_info: None,
|
||||
}
|
||||
ConstPropagator { ecx, tcx, param_env, local_decls: &dummy_body.local_decls }
|
||||
}
|
||||
|
||||
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
|
||||
|
@ -446,10 +414,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
/// Remove `local` from the pool of `Locals`. Allows writing to them,
|
||||
/// but not reading from them anymore.
|
||||
fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
|
||||
ecx.frame_mut().locals[local] = LocalState {
|
||||
value: LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit)),
|
||||
layout: Cell::new(None),
|
||||
};
|
||||
ecx.frame_mut().locals[local].value =
|
||||
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit));
|
||||
}
|
||||
|
||||
/// Returns the value, if any, of evaluating `c`.
|
||||
|
@ -492,11 +458,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
scalar,
|
||||
)) = *value
|
||||
{
|
||||
*operand = self.operand_from_scalar(
|
||||
scalar,
|
||||
value.layout.ty,
|
||||
self.source_info.unwrap().span,
|
||||
);
|
||||
*operand = self.operand_from_scalar(scalar, value.layout.ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -504,7 +466,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn const_prop(&mut self, rvalue: &Rvalue<'tcx>, place: Place<'tcx>) -> Option<()> {
|
||||
fn check_rvalue(&mut self, rvalue: &Rvalue<'tcx>) -> Option<()> {
|
||||
// Perform any special handling for specific Rvalue types.
|
||||
// Generally, checks here fall into one of two categories:
|
||||
// 1. Additional checking to provide useful lints to the user
|
||||
|
@ -561,7 +523,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
return None;
|
||||
}
|
||||
|
||||
self.eval_rvalue_with_identities(rvalue, place)
|
||||
Some(())
|
||||
}
|
||||
|
||||
// Attempt to use algebraic identities to eliminate constant expressions
|
||||
|
@ -621,20 +583,24 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
}
|
||||
|
||||
/// Creates a new `Operand::Constant` from a `Scalar` value
|
||||
fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>, span: Span) -> Operand<'tcx> {
|
||||
fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>) -> Operand<'tcx> {
|
||||
Operand::Constant(Box::new(Constant {
|
||||
span,
|
||||
span: DUMMY_SP,
|
||||
user_ty: None,
|
||||
literal: ConstantKind::from_scalar(self.tcx, scalar, ty),
|
||||
}))
|
||||
}
|
||||
|
||||
fn replace_with_const(
|
||||
&mut self,
|
||||
rval: &mut Rvalue<'tcx>,
|
||||
value: &OpTy<'tcx>,
|
||||
source_info: SourceInfo,
|
||||
) {
|
||||
fn replace_with_const(&mut self, place: Place<'tcx>, rval: &mut Rvalue<'tcx>) {
|
||||
// This will return None if the above `const_prop` invocation only "wrote" a
|
||||
// type whose creation requires no write. E.g. a generator whose initial state
|
||||
// consists solely of uninitialized memory (so it doesn't capture any locals).
|
||||
let Some(ref value) = self.get_const(place) else { return };
|
||||
if !self.should_const_prop(value) {
|
||||
return;
|
||||
}
|
||||
trace!("replacing {:?}={:?} with {:?}", place, rval, value);
|
||||
|
||||
if let Rvalue::Use(Operand::Constant(c)) = rval {
|
||||
match c.literal {
|
||||
ConstantKind::Ty(c) if matches!(c.kind(), ConstKind::Unevaluated(..)) => {}
|
||||
|
@ -664,11 +630,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
if let Some(Right(imm)) = imm {
|
||||
match *imm {
|
||||
interpret::Immediate::Scalar(scalar) => {
|
||||
*rval = Rvalue::Use(self.operand_from_scalar(
|
||||
scalar,
|
||||
value.layout.ty,
|
||||
source_info.span,
|
||||
));
|
||||
*rval = Rvalue::Use(self.operand_from_scalar(scalar, value.layout.ty));
|
||||
}
|
||||
Immediate::ScalarPair(..) => {
|
||||
// Found a value represented as a pair. For now only do const-prop if the type
|
||||
|
@ -701,7 +663,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
let const_val = ConstValue::ByRef { alloc, offset: Size::ZERO };
|
||||
let literal = ConstantKind::Val(const_val, ty);
|
||||
*rval = Rvalue::Use(Operand::Constant(Box::new(Constant {
|
||||
span: source_info.span,
|
||||
span: DUMMY_SP,
|
||||
user_ty: None,
|
||||
literal,
|
||||
})));
|
||||
|
@ -730,6 +692,19 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_not_propagated(&mut self, local: Local) {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(
|
||||
self.get_const(local.into()).is_none()
|
||||
|| self
|
||||
.layout_of(self.local_decls[local].ty)
|
||||
.map_or(true, |layout| layout.is_zst()),
|
||||
"failed to remove values for `{local:?}`, value={:?}",
|
||||
self.get_const(local.into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The mode that `ConstProp` is allowed to run in for a given `Local`.
|
||||
|
@ -739,8 +714,6 @@ pub enum ConstPropMode {
|
|||
FullConstProp,
|
||||
/// The `Local` can only be propagated into and from its own block.
|
||||
OnlyInsideOwnBlock,
|
||||
/// The `Local` can be propagated into but reads cannot be propagated.
|
||||
OnlyPropagateInto,
|
||||
/// The `Local` cannot be part of propagation at all. Any statement
|
||||
/// referencing it either for reading or writing will not get propagated.
|
||||
NoPropagation,
|
||||
|
@ -750,8 +723,6 @@ pub struct CanConstProp {
|
|||
can_const_prop: IndexVec<Local, ConstPropMode>,
|
||||
// False at the beginning. Once set, no more assignments are allowed to that local.
|
||||
found_assignment: BitSet<Local>,
|
||||
// Cache of locals' information
|
||||
local_kinds: IndexVec<Local, LocalKind>,
|
||||
}
|
||||
|
||||
impl CanConstProp {
|
||||
|
@ -764,10 +735,6 @@ impl CanConstProp {
|
|||
let mut cpv = CanConstProp {
|
||||
can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
|
||||
found_assignment: BitSet::new_empty(body.local_decls.len()),
|
||||
local_kinds: IndexVec::from_fn_n(
|
||||
|local| body.local_kind(local),
|
||||
body.local_decls.len(),
|
||||
),
|
||||
};
|
||||
for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
|
||||
let ty = body.local_decls[local].ty;
|
||||
|
@ -780,24 +747,10 @@ impl CanConstProp {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
// Cannot use args at all
|
||||
// Cannot use locals because if x < y { y - x } else { x - y } would
|
||||
// lint for x != y
|
||||
// FIXME(oli-obk): lint variables until they are used in a condition
|
||||
// FIXME(oli-obk): lint if return value is constant
|
||||
if cpv.local_kinds[local] == LocalKind::Arg {
|
||||
*val = ConstPropMode::OnlyPropagateInto;
|
||||
trace!(
|
||||
"local {:?} can't be const propagated because it's a function argument",
|
||||
local
|
||||
);
|
||||
} else if cpv.local_kinds[local] == LocalKind::Var {
|
||||
*val = ConstPropMode::OnlyInsideOwnBlock;
|
||||
trace!(
|
||||
"local {:?} will only be propagated inside its block, because it's a user variable",
|
||||
local
|
||||
);
|
||||
}
|
||||
}
|
||||
// Consider that arguments are assigned on entry.
|
||||
for arg in body.args_iter() {
|
||||
cpv.found_assignment.insert(arg);
|
||||
}
|
||||
cpv.visit_body(&body);
|
||||
cpv.can_const_prop
|
||||
|
@ -827,7 +780,6 @@ impl Visitor<'_> for CanConstProp {
|
|||
// states as applicable.
|
||||
ConstPropMode::OnlyInsideOwnBlock => {}
|
||||
ConstPropMode::NoPropagation => {}
|
||||
ConstPropMode::OnlyPropagateInto => {}
|
||||
other @ ConstPropMode::FullConstProp => {
|
||||
trace!(
|
||||
"local {:?} can't be propagated because of multiple assignments. Previous state: {:?}",
|
||||
|
@ -892,42 +844,23 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
|||
self.eval_constant(constant);
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
|
||||
trace!("visit_statement: {:?}", statement);
|
||||
let source_info = statement.source_info;
|
||||
self.source_info = Some(source_info);
|
||||
match statement.kind {
|
||||
StatementKind::Assign(box (place, ref mut rval)) => {
|
||||
let can_const_prop = self.ecx.machine.can_const_prop[place.local];
|
||||
if let Some(()) = self.const_prop(rval, place) {
|
||||
// This will return None if the above `const_prop` invocation only "wrote" a
|
||||
// type whose creation requires no write. E.g. a generator whose initial state
|
||||
// consists solely of uninitialized memory (so it doesn't capture any locals).
|
||||
if let Some(ref value) = self.get_const(place) && self.should_const_prop(value) {
|
||||
trace!("replacing {:?} with {:?}", rval, value);
|
||||
self.replace_with_const(rval, value, source_info);
|
||||
if can_const_prop == ConstPropMode::FullConstProp
|
||||
|| can_const_prop == ConstPropMode::OnlyInsideOwnBlock
|
||||
{
|
||||
trace!("propagated into {:?}", place);
|
||||
}
|
||||
}
|
||||
match can_const_prop {
|
||||
ConstPropMode::OnlyInsideOwnBlock => {
|
||||
trace!(
|
||||
"found local restricted to its block. \
|
||||
Will remove it from const-prop after block is finished. Local: {:?}",
|
||||
place.local
|
||||
);
|
||||
}
|
||||
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
|
||||
trace!("can't propagate into {:?}", place);
|
||||
if place.local != RETURN_PLACE {
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
ConstPropMode::FullConstProp => {}
|
||||
}
|
||||
fn visit_assign(
|
||||
&mut self,
|
||||
place: &mut Place<'tcx>,
|
||||
rvalue: &mut Rvalue<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
self.super_assign(place, rvalue, location);
|
||||
|
||||
let Some(()) = self.check_rvalue(rvalue) else { return };
|
||||
|
||||
match self.ecx.machine.can_const_prop[place.local] {
|
||||
// Do nothing if the place is indirect.
|
||||
_ if place.is_indirect() => {}
|
||||
ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
|
||||
ConstPropMode::OnlyInsideOwnBlock | ConstPropMode::FullConstProp => {
|
||||
if let Some(()) = self.eval_rvalue_with_identities(rvalue, *place) {
|
||||
self.replace_with_const(*place, rvalue);
|
||||
} else {
|
||||
// Const prop failed, so erase the destination, ensuring that whatever happens
|
||||
// from here on, does not know about the previous value.
|
||||
|
@ -947,8 +880,22 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
|||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
|
||||
trace!("visit_statement: {:?}", statement);
|
||||
|
||||
// We want to evaluate operands before any change to the assigned-to value,
|
||||
// so we recurse first.
|
||||
self.super_statement(statement, location);
|
||||
|
||||
match statement.kind {
|
||||
StatementKind::SetDiscriminant { ref place, .. } => {
|
||||
match self.ecx.machine.can_const_prop[place.local] {
|
||||
// Do nothing if the place is indirect.
|
||||
_ if place.is_indirect() => {}
|
||||
ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
|
||||
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
|
||||
if self.ecx.statement(statement).is_ok() {
|
||||
trace!("propped discriminant into {:?}", place);
|
||||
|
@ -956,28 +903,22 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
|||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
|
||||
StatementKind::StorageLive(local) => {
|
||||
let frame = self.ecx.frame_mut();
|
||||
frame.locals[local].value = if let StatementKind::StorageLive(_) = statement.kind {
|
||||
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit))
|
||||
} else {
|
||||
LocalValue::Dead
|
||||
};
|
||||
frame.locals[local].value =
|
||||
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit));
|
||||
}
|
||||
StatementKind::StorageDead(local) => {
|
||||
let frame = self.ecx.frame_mut();
|
||||
frame.locals[local].value = LocalValue::Dead;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.super_statement(statement, location);
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
|
||||
let source_info = terminator.source_info;
|
||||
self.source_info = Some(source_info);
|
||||
self.super_terminator(terminator, location);
|
||||
|
||||
match &mut terminator.kind {
|
||||
|
@ -987,11 +928,7 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
|||
&& self.should_const_prop(value)
|
||||
{
|
||||
trace!("assertion on {:?} should be {:?}", value, expected);
|
||||
*cond = self.operand_from_scalar(
|
||||
value_const,
|
||||
self.tcx.types.bool,
|
||||
source_info.span,
|
||||
);
|
||||
*cond = self.operand_from_scalar(value_const, self.tcx.types.bool);
|
||||
}
|
||||
}
|
||||
TerminatorKind::SwitchInt { ref mut discr, .. } => {
|
||||
|
@ -1026,23 +963,17 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
|||
// We remove all Locals which are restricted in propagation to their containing blocks and
|
||||
// which were modified in the current block.
|
||||
// Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
|
||||
let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
|
||||
for &local in locals.iter() {
|
||||
Self::remove_const(&mut self.ecx, local);
|
||||
}
|
||||
locals.clear();
|
||||
// Put it back so we reuse the heap of the storage
|
||||
self.ecx.machine.written_only_inside_own_block_locals = locals;
|
||||
if cfg!(debug_assertions) {
|
||||
// Ensure we are correctly erasing locals with the non-debug-assert logic.
|
||||
for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
|
||||
assert!(
|
||||
self.get_const(local.into()).is_none()
|
||||
|| self
|
||||
.layout_of(self.local_decls[local].ty)
|
||||
.map_or(true, |layout| layout.is_zst())
|
||||
)
|
||||
let can_const_prop = std::mem::take(&mut self.ecx.machine.can_const_prop);
|
||||
for (local, &mode) in can_const_prop.iter_enumerated() {
|
||||
match mode {
|
||||
ConstPropMode::FullConstProp => {}
|
||||
ConstPropMode::NoPropagation => self.ensure_not_propagated(local),
|
||||
ConstPropMode::OnlyInsideOwnBlock => {
|
||||
Self::remove_const(&mut self.ecx, local);
|
||||
self.ensure_not_propagated(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ecx.machine.can_const_prop = can_const_prop;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
//! Propagates constants for early reporting of statically known
|
||||
//! assertion failures
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use either::{Left, Right};
|
||||
|
||||
use rustc_const_eval::interpret::Immediate;
|
||||
use rustc_const_eval::interpret::{
|
||||
self, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
|
||||
self, InterpCx, InterpResult, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
|
||||
};
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::HirId;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_middle::mir::visit::Visitor;
|
||||
use rustc_middle::mir::{
|
||||
AssertKind, BinOp, Body, Constant, Local, LocalDecl, Location, Operand, Place, Rvalue,
|
||||
SourceInfo, SourceScope, SourceScopeData, Statement, StatementKind, Terminator, TerminatorKind,
|
||||
UnOp, RETURN_PLACE,
|
||||
};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
|
||||
use rustc_middle::ty::InternalSubsts;
|
||||
use rustc_middle::ty::{
|
||||
|
@ -185,17 +178,11 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
let param_env = tcx.param_env_reveal_all_normalized(def_id);
|
||||
|
||||
let can_const_prop = CanConstProp::check(tcx, param_env, body);
|
||||
let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
|
||||
for (l, mode) in can_const_prop.iter_enumerated() {
|
||||
if *mode == ConstPropMode::OnlyInsideOwnBlock {
|
||||
only_propagate_inside_block_locals.insert(l);
|
||||
}
|
||||
}
|
||||
let mut ecx = InterpCx::new(
|
||||
tcx,
|
||||
tcx.def_span(def_id),
|
||||
param_env,
|
||||
ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop),
|
||||
ConstPropMachine::new(can_const_prop),
|
||||
);
|
||||
|
||||
let ret_layout = ecx
|
||||
|
@ -258,10 +245,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
/// Remove `local` from the pool of `Locals`. Allows writing to them,
|
||||
/// but not reading from them anymore.
|
||||
fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
|
||||
ecx.frame_mut().locals[local] = LocalState {
|
||||
value: LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit)),
|
||||
layout: Cell::new(None),
|
||||
};
|
||||
ecx.frame_mut().locals[local].value =
|
||||
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit));
|
||||
}
|
||||
|
||||
fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
|
||||
|
@ -420,12 +405,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
Some(())
|
||||
}
|
||||
|
||||
fn const_prop(
|
||||
&mut self,
|
||||
rvalue: &Rvalue<'tcx>,
|
||||
source_info: SourceInfo,
|
||||
place: Place<'tcx>,
|
||||
) -> Option<()> {
|
||||
fn check_rvalue(&mut self, rvalue: &Rvalue<'tcx>, source_info: SourceInfo) -> Option<()> {
|
||||
// Perform any special handling for specific Rvalue types.
|
||||
// Generally, checks here fall into one of two categories:
|
||||
// 1. Additional checking to provide useful lints to the user
|
||||
|
@ -501,7 +481,20 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
return None;
|
||||
}
|
||||
|
||||
self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn ensure_not_propagated(&mut self, local: Local) {
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(
|
||||
self.get_const(local.into()).is_none()
|
||||
|| self
|
||||
.layout_of(self.local_decls[local].ty)
|
||||
.map_or(true, |layout| layout.is_zst()),
|
||||
"failed to remove values for `{local:?}`, value={:?}",
|
||||
self.get_const(local.into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -522,82 +515,78 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
|||
self.eval_constant(constant, self.source_info.unwrap());
|
||||
}
|
||||
|
||||
fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
|
||||
self.super_assign(place, rvalue, location);
|
||||
|
||||
let source_info = self.source_info.unwrap();
|
||||
let Some(()) = self.check_rvalue(rvalue, source_info) else { return };
|
||||
|
||||
match self.ecx.machine.can_const_prop[place.local] {
|
||||
// Do nothing if the place is indirect.
|
||||
_ if place.is_indirect() => {}
|
||||
ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
|
||||
ConstPropMode::OnlyInsideOwnBlock | ConstPropMode::FullConstProp => {
|
||||
if self
|
||||
.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, *place))
|
||||
.is_none()
|
||||
{
|
||||
// Const prop failed, so erase the destination, ensuring that whatever happens
|
||||
// from here on, does not know about the previous value.
|
||||
// This is important in case we have
|
||||
// ```rust
|
||||
// let mut x = 42;
|
||||
// x = SOME_MUTABLE_STATIC;
|
||||
// // x must now be uninit
|
||||
// ```
|
||||
// FIXME: we overzealously erase the entire local, because that's easier to
|
||||
// implement.
|
||||
trace!(
|
||||
"propagation into {:?} failed.
|
||||
Nuking the entire site from orbit, it's the only way to be sure",
|
||||
place,
|
||||
);
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
|
||||
trace!("visit_statement: {:?}", statement);
|
||||
let source_info = statement.source_info;
|
||||
self.source_info = Some(source_info);
|
||||
if let StatementKind::Assign(box (place, ref rval)) = statement.kind {
|
||||
let can_const_prop = self.ecx.machine.can_const_prop[place.local];
|
||||
if let Some(()) = self.const_prop(rval, source_info, place) {
|
||||
match can_const_prop {
|
||||
ConstPropMode::OnlyInsideOwnBlock => {
|
||||
trace!(
|
||||
"found local restricted to its block. \
|
||||
Will remove it from const-prop after block is finished. Local: {:?}",
|
||||
place.local
|
||||
);
|
||||
}
|
||||
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
|
||||
trace!("can't propagate into {:?}", place);
|
||||
if place.local != RETURN_PLACE {
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
ConstPropMode::FullConstProp => {}
|
||||
}
|
||||
} else {
|
||||
// Const prop failed, so erase the destination, ensuring that whatever happens
|
||||
// from here on, does not know about the previous value.
|
||||
// This is important in case we have
|
||||
// ```rust
|
||||
// let mut x = 42;
|
||||
// x = SOME_MUTABLE_STATIC;
|
||||
// // x must now be uninit
|
||||
// ```
|
||||
// FIXME: we overzealously erase the entire local, because that's easier to
|
||||
// implement.
|
||||
trace!(
|
||||
"propagation into {:?} failed.
|
||||
Nuking the entire site from orbit, it's the only way to be sure",
|
||||
place,
|
||||
);
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
} else {
|
||||
match statement.kind {
|
||||
StatementKind::SetDiscriminant { ref place, .. } => {
|
||||
match self.ecx.machine.can_const_prop[place.local] {
|
||||
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
|
||||
if self
|
||||
.use_ecx(source_info, |this| this.ecx.statement(statement))
|
||||
.is_some()
|
||||
{
|
||||
trace!("propped discriminant into {:?}", place);
|
||||
} else {
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
|
||||
let frame = self.ecx.frame_mut();
|
||||
frame.locals[local].value =
|
||||
if let StatementKind::StorageLive(_) = statement.kind {
|
||||
LocalValue::Live(interpret::Operand::Immediate(
|
||||
interpret::Immediate::Uninit,
|
||||
))
|
||||
} else {
|
||||
LocalValue::Dead
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// We want to evaluate operands before any change to the assigned-to value,
|
||||
// so we recurse first.
|
||||
self.super_statement(statement, location);
|
||||
|
||||
match statement.kind {
|
||||
StatementKind::SetDiscriminant { ref place, .. } => {
|
||||
match self.ecx.machine.can_const_prop[place.local] {
|
||||
// Do nothing if the place is indirect.
|
||||
_ if place.is_indirect() => {}
|
||||
ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
|
||||
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
|
||||
if self.use_ecx(source_info, |this| this.ecx.statement(statement)).is_some()
|
||||
{
|
||||
trace!("propped discriminant into {:?}", place);
|
||||
} else {
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementKind::StorageLive(local) => {
|
||||
let frame = self.ecx.frame_mut();
|
||||
frame.locals[local].value =
|
||||
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit));
|
||||
}
|
||||
StatementKind::StorageDead(local) => {
|
||||
let frame = self.ecx.frame_mut();
|
||||
frame.locals[local].value = LocalValue::Dead;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
|
||||
|
@ -694,27 +683,25 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
|||
| TerminatorKind::Call { .. }
|
||||
| TerminatorKind::InlineAsm { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_basic_block_data(&mut self, block: BasicBlock, data: &BasicBlockData<'tcx>) {
|
||||
self.super_basic_block_data(block, data);
|
||||
|
||||
// We remove all Locals which are restricted in propagation to their containing blocks and
|
||||
// which were modified in the current block.
|
||||
// Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
|
||||
let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
|
||||
for &local in locals.iter() {
|
||||
Self::remove_const(&mut self.ecx, local);
|
||||
}
|
||||
locals.clear();
|
||||
// Put it back so we reuse the heap of the storage
|
||||
self.ecx.machine.written_only_inside_own_block_locals = locals;
|
||||
if cfg!(debug_assertions) {
|
||||
// Ensure we are correctly erasing locals with the non-debug-assert logic.
|
||||
for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
|
||||
assert!(
|
||||
self.get_const(local.into()).is_none()
|
||||
|| self
|
||||
.layout_of(self.local_decls[local].ty)
|
||||
.map_or(true, |layout| layout.is_zst())
|
||||
)
|
||||
let can_const_prop = std::mem::take(&mut self.ecx.machine.can_const_prop);
|
||||
for (local, &mode) in can_const_prop.iter_enumerated() {
|
||||
match mode {
|
||||
ConstPropMode::FullConstProp => {}
|
||||
ConstPropMode::NoPropagation => self.ensure_not_propagated(local),
|
||||
ConstPropMode::OnlyInsideOwnBlock => {
|
||||
Self::remove_const(&mut self.ecx, local);
|
||||
self.ensure_not_propagated(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ecx.machine.can_const_prop = can_const_prop;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue