1
Fork 0

Auto merge of #80475 - simonvandel:fix-77355, r=oli-obk

New mir-opt pass to simplify gotos with const values (reopening #77486)

Reopening PR #77486

Fixes #77355

This pass optimizes the following sequence
```rust
bb2: {
    _2 = const true;
    goto -> bb3;
}

bb3: {
    switchInt(_2) -> [false: bb4, otherwise: bb5];
}
```
into
```rust
bb2: {
    _2 = const true;
    goto -> bb5;
}
```
This commit is contained in:
bors 2021-02-24 07:23:54 +00:00
commit 6b56603e35
22 changed files with 652 additions and 374 deletions

View file

@ -514,6 +514,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Evaluate the operand, returning a place where you can then find the data.
/// If you already know the layout, you can save two table lookups
/// by passing it in here.
#[inline]
pub fn eval_operand(
&self,
mir_op: &mir::Operand<'tcx>,

View file

@ -0,0 +1,122 @@
//! This pass optimizes the following sequence
//! ```rust,ignore (example)
//! bb2: {
//! _2 = const true;
//! goto -> bb3;
//! }
//!
//! bb3: {
//! switchInt(_2) -> [false: bb4, otherwise: bb5];
//! }
//! ```
//! into
//! ```rust,ignore (example)
//! bb2: {
//! _2 = const true;
//! goto -> bb5;
//! }
//! ```
use crate::transform::MirPass;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_middle::{mir::visit::Visitor, ty::ParamEnv};
use super::simplify::{simplify_cfg, simplify_locals};
pub struct ConstGoto;
impl<'tcx> MirPass<'tcx> for ConstGoto {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.opts.debugging_opts.mir_opt_level < 3 {
return;
}
trace!("Running ConstGoto on {:?}", body.source);
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let mut opt_finder =
ConstGotoOptimizationFinder { tcx, body, optimizations: vec![], param_env };
opt_finder.visit_body(body);
let should_simplify = !opt_finder.optimizations.is_empty();
for opt in opt_finder.optimizations {
let terminator = body.basic_blocks_mut()[opt.bb_with_goto].terminator_mut();
let new_goto = TerminatorKind::Goto { target: opt.target_to_use_in_goto };
debug!("SUCCESS: replacing `{:?}` with `{:?}`", terminator.kind, new_goto);
terminator.kind = new_goto;
}
// if we applied optimizations, we potentially have some cfg to cleanup to
// make it easier for further passes
if should_simplify {
simplify_cfg(body);
simplify_locals(body, tcx);
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for ConstGotoOptimizationFinder<'a, 'tcx> {
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
let _: Option<_> = try {
let target = terminator.kind.as_goto()?;
// We only apply this optimization if the last statement is a const assignment
let last_statement = self.body.basic_blocks()[location.block].statements.last()?;
if let (place, Rvalue::Use(Operand::Constant(_const))) =
last_statement.kind.as_assign()?
{
// We found a constant being assigned to `place`.
// Now check that the target of this Goto switches on this place.
let target_bb = &self.body.basic_blocks()[target];
// FIXME(simonvandel): We are conservative here when we don't allow
// any statements in the target basic block.
// This could probably be relaxed to allow `StorageDead`s which could be
// copied to the predecessor of this block.
if !target_bb.statements.is_empty() {
None?
}
let target_bb_terminator = target_bb.terminator();
let (discr, switch_ty, targets) = target_bb_terminator.kind.as_switch()?;
if discr.place() == Some(*place) {
// We now know that the Switch matches on the const place, and it is statementless
// Now find which value in the Switch matches the const value.
let const_value =
_const.literal.try_eval_bits(self.tcx, self.param_env, switch_ty)?;
let found_value_idx_option = targets
.iter()
.enumerate()
.find(|(_, (value, _))| const_value == *value)
.map(|(idx, _)| idx);
let target_to_use_in_goto =
if let Some(found_value_idx) = found_value_idx_option {
targets.iter().nth(found_value_idx).unwrap().1
} else {
// If we did not find the const value in values, it must be the otherwise case
targets.otherwise()
};
self.optimizations.push(OptimizationToApply {
bb_with_goto: location.block,
target_to_use_in_goto,
});
}
}
Some(())
};
self.super_terminator(terminator, location);
}
}
struct OptimizationToApply {
bb_with_goto: BasicBlock,
target_to_use_in_goto: BasicBlock,
}
pub struct ConstGotoOptimizationFinder<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
param_env: ParamEnv<'tcx>,
optimizations: Vec<OptimizationToApply>,
}

View file

@ -22,6 +22,7 @@ pub mod check_packed_ref;
pub mod check_unsafety;
pub mod cleanup_post_borrowck;
pub mod const_debuginfo;
pub mod const_goto;
pub mod const_prop;
pub mod coverage;
pub mod deaggregator;
@ -492,6 +493,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// The main optimizations that we do on MIR.
let optimizations: &[&dyn MirPass<'tcx>] = &[
&const_goto::ConstGoto,
&remove_unneeded_drops::RemoveUnneededDrops,
&match_branches::MatchBranchSimplification,
// inst combine is after MatchBranchSimplification to clean up Ne(_1, false)

View file

@ -320,28 +320,31 @@ pub struct SimplifyLocals;
impl<'tcx> MirPass<'tcx> for SimplifyLocals {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
trace!("running SimplifyLocals on {:?}", body.source);
simplify_locals(body, tcx);
}
}
// First, we're going to get a count of *actual* uses for every `Local`.
let mut used_locals = UsedLocals::new(body);
pub fn simplify_locals<'tcx>(body: &mut Body<'tcx>, tcx: TyCtxt<'tcx>) {
// First, we're going to get a count of *actual* uses for every `Local`.
let mut used_locals = UsedLocals::new(body);
// Next, we're going to remove any `Local` with zero actual uses. When we remove those
// `Locals`, we're also going to subtract any uses of other `Locals` from the `used_locals`
// count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from
// `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a
// fixedpoint where there are no more unused locals.
remove_unused_definitions(&mut used_locals, body);
// Next, we're going to remove any `Local` with zero actual uses. When we remove those
// `Locals`, we're also going to subtract any uses of other `Locals` from the `used_locals`
// count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from
// `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a
// fixedpoint where there are no more unused locals.
remove_unused_definitions(&mut used_locals, body);
// Finally, we'll actually do the work of shrinking `body.local_decls` and remapping the `Local`s.
let map = make_local_map(&mut body.local_decls, &used_locals);
// Finally, we'll actually do the work of shrinking `body.local_decls` and remapping the `Local`s.
let map = make_local_map(&mut body.local_decls, &used_locals);
// Only bother running the `LocalUpdater` if we actually found locals to remove.
if map.iter().any(Option::is_none) {
// Update references to all vars and tmps now
let mut updater = LocalUpdater { map, tcx };
updater.visit_body(body);
// Only bother running the `LocalUpdater` if we actually found locals to remove.
if map.iter().any(Option::is_none) {
// Update references to all vars and tmps now
let mut updater = LocalUpdater { map, tcx };
updater.visit_body(body);
body.local_decls.shrink_to_fit();
}
body.local_decls.shrink_to_fit();
}
}

View file

@ -80,7 +80,7 @@ impl<'tcx> MirPass<'tcx> for SimplifyComparisonIntegral {
// we convert the move in the comparison statement to a copy.
// unwrap is safe as we know this statement is an assign
let box (_, rhs) = bb.statements[opt.bin_op_stmt_idx].kind.as_assign_mut().unwrap();
let (_, rhs) = bb.statements[opt.bin_op_stmt_idx].kind.as_assign_mut().unwrap();
use Operand::*;
match rhs {