Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Const-prop bugfix: only add propagation inside own block for user variables #71518

Merged
merged 7 commits into from
Apr 29, 2020
91 changes: 70 additions & 21 deletions src/librustc_mir/transform/const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ struct ConstPropagator<'mir, '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>,
// Locals we need to forget at the end of the current block
locals_of_current_block: BitSet<Local>,
}

impl<'mir, 'tcx> LayoutOf for ConstPropagator<'mir, 'tcx> {
Expand Down Expand Up @@ -395,6 +397,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
//FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
local_decls: body.local_decls.clone(),
source_info: None,
locals_of_current_block: BitSet::new_empty(body.local_decls.len()),
}
}

Expand All @@ -409,8 +412,10 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}

fn remove_const(&mut self, local: Local) {
self.ecx.frame_mut().locals[local] =
/// 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::Uninitialized, layout: Cell::new(None) };
}

Expand Down Expand Up @@ -441,6 +446,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}

/// Returns the value, if any, of evaluating `c`.
fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
// FIXME we need to revisit this for #67176
if c.needs_subst() {
Expand Down Expand Up @@ -481,11 +487,14 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}

/// Returns the value, if any, of evaluating `place`.
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))
}

/// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
/// or `eval_place`, depending on the variant of `Operand` used.
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),
Expand Down Expand Up @@ -644,6 +653,7 @@ 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> {
Operand::Constant(Box::new(Constant {
span,
Expand Down Expand Up @@ -689,6 +699,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
// Found a value represented as a pair. For now only do cont-prop if type of
// Rvalue is also a pair with two scalars. The more general case is more
// complicated to implement so we'll do it later.
// FIXME: implement the general case stated above ^.
let ty = &value.layout.ty.kind;
// Only do it for tuples
if let ty::Tuple(substs) = ty {
Expand Down Expand Up @@ -725,6 +736,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}

/// Returns `true` if and only if this `op` should be const-propagated into.
fn should_const_prop(&mut self, op: OpTy<'tcx>) -> bool {
let mir_opt_level = self.tcx.sess.opts.debugging_opts.mir_opt_level;

Expand Down Expand Up @@ -756,6 +768,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
enum ConstPropMode {
/// The `Local` can be propagated into and reads of this `Local` can also be propagated.
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,
/// No propagation is allowed at all.
Expand All @@ -764,28 +778,41 @@ enum ConstPropMode {

struct CanConstProp {
can_const_prop: IndexVec<Local, ConstPropMode>,
// false at the beginning, once set, there are not allowed to be any more assignments
// 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 {
/// returns true if `local` can be propagated
/// Returns true if `local` can be propagated
fn check(body: &Body<'_>) -> IndexVec<Local, ConstPropMode> {
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() {
// cannot use args at all
// cannot use locals because if x < y { y - x } else { x - y } would
// 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
let local_kind = body.local_kind(local);

if local_kind == LocalKind::Arg || local_kind == LocalKind::Var {
if cpv.local_kinds[local] == LocalKind::Arg {
*val = ConstPropMode::OnlyPropagateInto;
trace!("local {:?} can't be const propagated because it's not a temporary", local);
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
);
}
}
cpv.visit_body(&body);
Expand All @@ -811,8 +838,12 @@ impl<'tcx> Visitor<'tcx> for CanConstProp {
| NonMutatingUse(NonMutatingUseContext::Move)
| NonMutatingUse(NonMutatingUseContext::Inspect)
| NonMutatingUse(NonMutatingUseContext::Projection)
| MutatingUse(MutatingUseContext::Projection)
| NonUse(_) => {}
MutatingUse(MutatingUseContext::Projection) => {
if self.local_kinds[local] != LocalKind::Temp {
self.can_const_prop[local] = ConstPropMode::NoPropagation;
}
}
_ => {
trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
self.can_const_prop[local] = ConstPropMode::NoPropagation;
Expand Down Expand Up @@ -849,25 +880,35 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
if let Some(local) = place.as_local() {
let can_const_prop = self.can_const_prop[local];
if let Some(()) = self.const_prop(rval, place_layout, source_info, place) {
if can_const_prop == ConstPropMode::FullConstProp
|| can_const_prop == ConstPropMode::OnlyPropagateInto
{
if can_const_prop != ConstPropMode::NoPropagation {
// This will return None for Locals that are from other blocks,
// so it should be okay to propagate from here on down.
if let Some(value) = self.get_const(local) {
if self.should_const_prop(value) {
trace!("replacing {:?} with {:?}", rval, value);
self.replace_with_const(rval, value, statement.source_info);

if can_const_prop == ConstPropMode::FullConstProp {
if can_const_prop == ConstPropMode::FullConstProp
|| can_const_prop == ConstPropMode::OnlyInsideOwnBlock
{
trace!("propagated into {:?}", local);
}
}
if can_const_prop == ConstPropMode::OnlyInsideOwnBlock {
trace!(
"found local restricted to its block. Will remove it from const-prop after block is finished. Local: {:?}",
local
);
self.locals_of_current_block.insert(local);
}
}
}
}
if self.can_const_prop[local] != ConstPropMode::FullConstProp {
if self.can_const_prop[local] == ConstPropMode::OnlyPropagateInto
|| self.can_const_prop[local] == ConstPropMode::NoPropagation
{
trace!("can't propagate into {:?}", local);
if local != RETURN_PLACE {
self.remove_const(local);
Self::remove_const(&mut self.ecx, local);
}
}
}
Expand Down Expand Up @@ -902,11 +943,11 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
let expected = ScalarMaybeUndef::from(Scalar::from_bool(*expected));
let value_const = self.ecx.read_scalar(value).unwrap();
if expected != value_const {
// poison all places this operand references so that further code
// Poison all places this operand references so that further code
// doesn't use the invalid value
match cond {
Operand::Move(ref place) | Operand::Copy(ref place) => {
self.remove_const(place.local);
Self::remove_const(&mut self.ecx, place.local);
}
Operand::Constant(_) => {}
}
Expand Down Expand Up @@ -968,7 +1009,7 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
}
}
}
//none of these have Operands to const-propagate
// None of these have Operands to const-propagate
TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Abort
Expand All @@ -983,5 +1024,13 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
//FIXME(wesleywiser) Call does have Operands that could be const-propagated
TerminatorKind::Call { .. } => {}
}
// We remove all Locals which are restricted in propagation to their containing blocks.
// We wouldn't need to clone, but the borrow checker can't see that we're not aliasing
// the locals_of_current_block field, so we need to clone it first.
// let ecx = &mut self.ecx;
for local in self.locals_of_current_block.iter() {
Self::remove_const(&mut self.ecx, local);
}
self.locals_of_current_block.clear();
}
}
6 changes: 6 additions & 0 deletions src/test/mir-opt/const_prop/bad_op_div_by_zero.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// EMIT_MIR rustc.main.ConstProp.diff
#[allow(unconditional_panic)]
fn main() {
let y = 0;
let _z = 1 / y;
}
Loading