Replace legacy ConstProp by GVN.
This commit is contained in:
parent
a03c972816
commit
2837727471
190 changed files with 1361 additions and 1714 deletions
|
@ -1,29 +1,22 @@
|
|||
//! Propagates constants for early reporting of statically known
|
||||
//! assertion failures
|
||||
|
||||
use either::Right;
|
||||
use rustc_const_eval::ReportErrorExt;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::{IndexSlice, IndexVec};
|
||||
use rustc_middle::mir::visit::{
|
||||
MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
|
||||
use rustc_const_eval::interpret::{
|
||||
self, compile_time_machine, AllocId, ConstAllocation, FnArg, Frame, ImmTy, InterpCx,
|
||||
InterpResult, OpTy, PlaceTy, Pointer,
|
||||
};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::query::TyCtxtAt;
|
||||
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
|
||||
use rustc_middle::ty::{self, GenericArgs, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt};
|
||||
use rustc_span::{def_id::DefId, Span};
|
||||
use rustc_target::abi::{self, HasDataLayout, Size, TargetDataLayout};
|
||||
use rustc_middle::ty::layout::TyAndLayout;
|
||||
use rustc_middle::ty::{self, ParamEnv, TyCtxt};
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_target::abi::Size;
|
||||
use rustc_target::spec::abi::Abi as CallAbi;
|
||||
|
||||
use crate::dataflow_const_prop::Patch;
|
||||
use rustc_const_eval::interpret::{
|
||||
self, compile_time_machine, AllocId, ConstAllocation, FnArg, Frame, ImmTy, Immediate, InterpCx,
|
||||
InterpResult, MemoryKind, OpTy, PlaceTy, Pointer, Scalar, StackPopCleanup,
|
||||
};
|
||||
|
||||
/// The maximum number of bytes that we'll allocate space for a local or the return value.
|
||||
/// Needed for #66397, because otherwise we eval into large places and that can cause OOM or just
|
||||
/// Severely regress performance.
|
||||
|
@ -56,62 +49,7 @@ pub(crate) macro throw_machine_stop_str($($tt:tt)*) {{
|
|||
throw_machine_stop!(Zst)
|
||||
}}
|
||||
|
||||
pub struct ConstProp;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for ConstProp {
|
||||
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
|
||||
sess.mir_opt_level() >= 2
|
||||
}
|
||||
|
||||
#[instrument(skip(self, tcx), level = "debug")]
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
// will be evaluated by miri and produce its errors there
|
||||
if body.source.promoted.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let def_id = body.source.def_id().expect_local();
|
||||
let def_kind = tcx.def_kind(def_id);
|
||||
let is_fn_like = def_kind.is_fn_like();
|
||||
let is_assoc_const = def_kind == DefKind::AssocConst;
|
||||
|
||||
// Only run const prop on functions, methods, closures and associated constants
|
||||
if !is_fn_like && !is_assoc_const {
|
||||
// skip anon_const/statics/consts because they'll be evaluated by miri anyway
|
||||
trace!("ConstProp skipped for {:?}", def_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME(welseywiser) const prop doesn't work on coroutines because of query cycles
|
||||
// computing their layout.
|
||||
if tcx.is_coroutine(def_id.to_def_id()) {
|
||||
trace!("ConstProp skipped for coroutine {:?}", def_id);
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("ConstProp starting for {:?}", def_id);
|
||||
|
||||
// FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
|
||||
// constants, instead of just checking for const-folding succeeding.
|
||||
// That would require a uniform one-def no-mutation analysis
|
||||
// and RPO (or recursing when needing the value of a local).
|
||||
let mut optimization_finder = ConstPropagator::new(body, tcx);
|
||||
|
||||
// Traverse the body in reverse post-order, to ensure that `FullConstProp` locals are
|
||||
// assigned before being read.
|
||||
for &bb in body.basic_blocks.reverse_postorder() {
|
||||
let data = &body.basic_blocks[bb];
|
||||
optimization_finder.visit_basic_block_data(bb, data);
|
||||
}
|
||||
|
||||
let mut patch = optimization_finder.patch;
|
||||
patch.visit_body_preserves_cfg(body);
|
||||
|
||||
trace!("ConstProp done for {:?}", def_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstPropMachine<'mir, 'tcx> {
|
||||
pub(crate) struct ConstPropMachine<'mir, 'tcx> {
|
||||
/// The virtual call stack.
|
||||
stack: Vec<Frame<'mir, 'tcx>>,
|
||||
pub written_only_inside_own_block_locals: FxHashSet<Local>,
|
||||
|
@ -267,297 +205,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
|
|||
}
|
||||
}
|
||||
|
||||
/// Finds optimization opportunities on the MIR.
|
||||
struct ConstPropagator<'mir, 'tcx> {
|
||||
ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
local_decls: &'mir IndexSlice<Local, LocalDecl<'tcx>>,
|
||||
patch: Patch<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> {
|
||||
type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
|
||||
|
||||
#[inline]
|
||||
fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDataLayout for ConstPropagator<'_, '_> {
|
||||
#[inline]
|
||||
fn data_layout(&self) -> &TargetDataLayout {
|
||||
&self.tcx.data_layout
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ty::layout::HasTyCtxt<'tcx> for ConstPropagator<'_, 'tcx> {
|
||||
#[inline]
|
||||
fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
self.tcx
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ty::layout::HasParamEnv<'tcx> for ConstPropagator<'_, 'tcx> {
|
||||
#[inline]
|
||||
fn param_env(&self) -> ty::ParamEnv<'tcx> {
|
||||
self.param_env
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
||||
fn new(body: &'mir Body<'tcx>, tcx: TyCtxt<'tcx>) -> ConstPropagator<'mir, 'tcx> {
|
||||
let def_id = body.source.def_id();
|
||||
let args = &GenericArgs::identity_for_item(tcx, def_id);
|
||||
let param_env = tcx.param_env_reveal_all_normalized(def_id);
|
||||
|
||||
let can_const_prop = CanConstProp::check(tcx, param_env, body);
|
||||
let mut ecx = InterpCx::new(
|
||||
tcx,
|
||||
tcx.def_span(def_id),
|
||||
param_env,
|
||||
ConstPropMachine::new(can_const_prop),
|
||||
);
|
||||
|
||||
let ret_layout = ecx
|
||||
.layout_of(body.bound_return_ty().instantiate(tcx, args))
|
||||
.ok()
|
||||
// Don't bother allocating memory for large values.
|
||||
// I don't know how return types can seem to be unsized but this happens in the
|
||||
// `type/type-unsatisfiable.rs` test.
|
||||
.filter(|ret_layout| {
|
||||
ret_layout.is_sized() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
|
||||
})
|
||||
.unwrap_or_else(|| ecx.layout_of(tcx.types.unit).unwrap());
|
||||
|
||||
let ret = ecx
|
||||
.allocate(ret_layout, MemoryKind::Stack)
|
||||
.expect("couldn't perform small allocation")
|
||||
.into();
|
||||
|
||||
ecx.push_stack_frame(
|
||||
Instance::new(def_id, args),
|
||||
body,
|
||||
&ret,
|
||||
StackPopCleanup::Root { cleanup: false },
|
||||
)
|
||||
.expect("failed to push initial stack frame");
|
||||
|
||||
for local in body.local_decls.indices() {
|
||||
// Mark everything initially live.
|
||||
// This is somewhat dicey since some of them might be unsized and it is incoherent to
|
||||
// mark those as live... We rely on `local_to_place`/`local_to_op` in the interpreter
|
||||
// stopping us before those unsized immediates can cause issues deeper in the
|
||||
// interpreter.
|
||||
ecx.frame_mut().locals[local].make_live_uninit();
|
||||
}
|
||||
|
||||
let patch = Patch::new(tcx);
|
||||
ConstPropagator { ecx, tcx, param_env, local_decls: &body.local_decls, patch }
|
||||
}
|
||||
|
||||
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
|
||||
let op = match self.ecx.eval_place_to_op(place, None) {
|
||||
Ok(op) => {
|
||||
if op
|
||||
.as_mplace_or_imm()
|
||||
.right()
|
||||
.is_some_and(|imm| matches!(*imm, Immediate::Uninit))
|
||||
{
|
||||
// Make sure nobody accidentally uses this value.
|
||||
return None;
|
||||
}
|
||||
op
|
||||
}
|
||||
Err(e) => {
|
||||
trace!("get_const failed: {:?}", e.into_kind().debug());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Try to read the local as an immediate so that if it is representable as a scalar, we can
|
||||
// handle it as such, but otherwise, just return the value as is.
|
||||
Some(match self.ecx.read_immediate_raw(&op) {
|
||||
Ok(Right(imm)) => imm.into(),
|
||||
_ => op,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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].make_live_uninit();
|
||||
ecx.machine.written_only_inside_own_block_locals.remove(&local);
|
||||
}
|
||||
|
||||
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
|
||||
// - In this case, we will do some validation and then fall through to the
|
||||
// end of the function which evals the assignment.
|
||||
// 2. Working around bugs in other parts of the compiler
|
||||
// - In this case, we'll return `None` from this function to stop evaluation.
|
||||
match rvalue {
|
||||
// Do not try creating references (#67862)
|
||||
Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => {
|
||||
trace!("skipping AddressOf | Ref for {:?}", place);
|
||||
|
||||
// This may be creating mutable references or immutable references to cells.
|
||||
// If that happens, the pointed to value could be mutated via that reference.
|
||||
// Since we aren't tracking references, the const propagator loses track of what
|
||||
// value the local has right now.
|
||||
// Thus, all locals that have their reference taken
|
||||
// must not take part in propagation.
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
|
||||
return None;
|
||||
}
|
||||
Rvalue::ThreadLocalRef(def_id) => {
|
||||
trace!("skipping ThreadLocalRef({:?})", def_id);
|
||||
|
||||
return None;
|
||||
}
|
||||
// There's no other checking to do at this time.
|
||||
Rvalue::Aggregate(..)
|
||||
| Rvalue::Use(..)
|
||||
| Rvalue::CopyForDeref(..)
|
||||
| Rvalue::Repeat(..)
|
||||
| Rvalue::Len(..)
|
||||
| Rvalue::Cast(..)
|
||||
| Rvalue::ShallowInitBox(..)
|
||||
| Rvalue::Discriminant(..)
|
||||
| Rvalue::NullaryOp(..)
|
||||
| Rvalue::UnaryOp(..)
|
||||
| Rvalue::BinaryOp(..)
|
||||
| Rvalue::CheckedBinaryOp(..) => {}
|
||||
}
|
||||
|
||||
// FIXME we need to revisit this for #67176
|
||||
if rvalue.has_param() {
|
||||
trace!("skipping, has param");
|
||||
return None;
|
||||
}
|
||||
if !rvalue
|
||||
.ty(&self.ecx.frame().body.local_decls, *self.ecx.tcx)
|
||||
.is_sized(*self.ecx.tcx, self.param_env)
|
||||
{
|
||||
// the interpreter doesn't support unsized locals (only unsized arguments),
|
||||
// but rustc does (in a kinda broken way), so we have to skip them here
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
// Attempt to use algebraic identities to eliminate constant expressions
|
||||
fn eval_rvalue_with_identities(
|
||||
&mut self,
|
||||
rvalue: &Rvalue<'tcx>,
|
||||
place: Place<'tcx>,
|
||||
) -> Option<()> {
|
||||
match rvalue {
|
||||
Rvalue::BinaryOp(op, box (left, right))
|
||||
| Rvalue::CheckedBinaryOp(op, box (left, right)) => {
|
||||
let l = self.ecx.eval_operand(left, None).and_then(|x| self.ecx.read_immediate(&x));
|
||||
let r =
|
||||
self.ecx.eval_operand(right, None).and_then(|x| self.ecx.read_immediate(&x));
|
||||
|
||||
let const_arg = match (l, r) {
|
||||
(Ok(x), Err(_)) | (Err(_), Ok(x)) => x, // exactly one side is known
|
||||
(Err(_), Err(_)) => return None, // neither side is known
|
||||
(Ok(_), Ok(_)) => return self.ecx.eval_rvalue_into_place(rvalue, place).ok(), // both sides are known
|
||||
};
|
||||
|
||||
if !matches!(const_arg.layout.abi, abi::Abi::Scalar(..)) {
|
||||
// We cannot handle Scalar Pair stuff.
|
||||
// No point in calling `eval_rvalue_into_place`, since only one side is known
|
||||
return None;
|
||||
}
|
||||
|
||||
let arg_value = const_arg.to_scalar().to_bits(const_arg.layout.size).ok()?;
|
||||
let dest = self.ecx.eval_place(place).ok()?;
|
||||
|
||||
match op {
|
||||
BinOp::BitAnd if arg_value == 0 => {
|
||||
self.ecx.write_immediate(*const_arg, &dest).ok()
|
||||
}
|
||||
BinOp::BitOr
|
||||
if arg_value == const_arg.layout.size.truncate(u128::MAX)
|
||||
|| (const_arg.layout.ty.is_bool() && arg_value == 1) =>
|
||||
{
|
||||
self.ecx.write_immediate(*const_arg, &dest).ok()
|
||||
}
|
||||
BinOp::Mul if const_arg.layout.ty.is_integral() && arg_value == 0 => {
|
||||
if let Rvalue::CheckedBinaryOp(_, _) = rvalue {
|
||||
let val = Immediate::ScalarPair(
|
||||
const_arg.to_scalar(),
|
||||
Scalar::from_bool(false),
|
||||
);
|
||||
self.ecx.write_immediate(val, &dest).ok()
|
||||
} else {
|
||||
self.ecx.write_immediate(*const_arg, &dest).ok()
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => self.ecx.eval_rvalue_into_place(rvalue, place).ok(),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_with_const(&mut self, place: Place<'tcx>) -> Option<Const<'tcx>> {
|
||||
// This will return None if the above `const_prop` invocation only "wrote" a
|
||||
// type whose creation requires no write. E.g. a coroutine whose initial state
|
||||
// consists solely of uninitialized memory (so it doesn't capture any locals).
|
||||
let value = self.get_const(place)?;
|
||||
if !self.tcx.consider_optimizing(|| format!("ConstantPropagation - {value:?}")) {
|
||||
return None;
|
||||
}
|
||||
trace!("replacing {:?} with {:?}", place, value);
|
||||
|
||||
// FIXME: figure out what to do when read_immediate_raw fails
|
||||
let imm = self.ecx.read_immediate_raw(&value).ok()?;
|
||||
|
||||
let Right(imm) = imm else { return None };
|
||||
match *imm {
|
||||
Immediate::Scalar(scalar) if scalar.try_to_int().is_ok() => {
|
||||
Some(Const::from_scalar(self.tcx, scalar, value.layout.ty))
|
||||
}
|
||||
Immediate::ScalarPair(l, r) if l.try_to_int().is_ok() && r.try_to_int().is_ok() => {
|
||||
let alloc_id = self
|
||||
.ecx
|
||||
.intern_with_temp_alloc(value.layout, |ecx, dest| {
|
||||
ecx.write_immediate(*imm, dest)
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
Some(Const::Val(
|
||||
ConstValue::Indirect { alloc_id, offset: Size::ZERO },
|
||||
value.layout.ty,
|
||||
))
|
||||
}
|
||||
// Scalars or scalar pairs that contain undef values are assumed to not have
|
||||
// successfully evaluated and are thus not propagated.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_not_propagated(&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`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ConstPropMode {
|
||||
|
@ -677,154 +324,3 @@ impl<'tcx> Visitor<'tcx> for CanConstProp {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
||||
fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
|
||||
self.super_operand(operand, location);
|
||||
if let Some(place) = operand.place()
|
||||
&& let Some(value) = self.replace_with_const(place)
|
||||
{
|
||||
self.patch.before_effect.insert((location, place), value);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_projection_elem(
|
||||
&mut self,
|
||||
_: PlaceRef<'tcx>,
|
||||
elem: PlaceElem<'tcx>,
|
||||
_: PlaceContext,
|
||||
location: Location,
|
||||
) {
|
||||
if let PlaceElem::Index(local) = elem
|
||||
&& let Some(value) = self.replace_with_const(local.into())
|
||||
{
|
||||
self.patch.before_effect.insert((location, local.into()), value);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
|
||||
self.super_assign(place, rvalue, location);
|
||||
|
||||
let Some(()) = self.check_rvalue(rvalue) else {
|
||||
trace!("rvalue check failed, removing const");
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
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) {
|
||||
// If this was already an evaluated constant, keep it.
|
||||
if let Rvalue::Use(Operand::Constant(c)) = rvalue
|
||||
&& let Const::Val(..) = c.const_
|
||||
{
|
||||
trace!(
|
||||
"skipping replace of Rvalue::Use({:?} because it is already a const",
|
||||
c
|
||||
);
|
||||
} else if let Some(operand) = self.replace_with_const(*place) {
|
||||
self.patch.assignments.insert(location, operand);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self, statement: &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);
|
||||
} else {
|
||||
Self::remove_const(&mut self.ecx, place.local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementKind::StorageLive(local) => {
|
||||
Self::remove_const(&mut self.ecx, local);
|
||||
}
|
||||
// We do not need to mark dead locals as such. For `FullConstProp` locals,
|
||||
// this allows to propagate the single assigned value in this case:
|
||||
// ```
|
||||
// let x = SOME_CONST;
|
||||
// if a {
|
||||
// f(copy x);
|
||||
// StorageDead(x);
|
||||
// } else {
|
||||
// g(copy x);
|
||||
// StorageDead(x);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// This may propagate a constant where the local would be uninit or dead.
|
||||
// In both cases, this does not matter, as those reads would be UB anyway.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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 written_only_inside_own_block_locals =
|
||||
std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
|
||||
|
||||
// This loop can get very hot for some bodies: it check each local in each bb.
|
||||
// To avoid this quadratic behaviour, we only clear the locals that were modified inside
|
||||
// the current block.
|
||||
for local in written_only_inside_own_block_locals.drain() {
|
||||
debug_assert_eq!(
|
||||
self.ecx.machine.can_const_prop[local],
|
||||
ConstPropMode::OnlyInsideOwnBlock
|
||||
);
|
||||
Self::remove_const(&mut self.ecx, local);
|
||||
}
|
||||
self.ecx.machine.written_only_inside_own_block_locals =
|
||||
written_only_inside_own_block_locals;
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
for (local, &mode) in self.ecx.machine.can_const_prop.iter_enumerated() {
|
||||
match mode {
|
||||
ConstPropMode::FullConstProp => {}
|
||||
ConstPropMode::NoPropagation | ConstPropMode::OnlyInsideOwnBlock => {
|
||||
self.ensure_not_propagated(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -590,7 +590,6 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
|||
&separate_const_switch::SeparateConstSwitch,
|
||||
&gvn::GVN,
|
||||
&simplify::SimplifyLocals::AfterGVN,
|
||||
&const_prop::ConstProp,
|
||||
&dataflow_const_prop::DataflowConstProp,
|
||||
&const_debuginfo::ConstDebugInfo,
|
||||
&o1(simplify_branches::SimplifyConstCondition::AfterConstProp),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue