mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 23:04:33 +00:00
Auto merge of #91840 - JakobDegen:fix_early_otherwise, r=oli-obk
Fix the unsoundness in the `early_otherwise_branch` mir opt pass Closes #78496 . This change is a significant rewrite of much of the pass. Exactly what it does is documented in the source file (with ascii art!), and all the changes that are made to the MIR that are not trivially sound are carefully documented. That being said, this is my first time touching MIR, so there are probably some invariants I did not know about that I broke. This version of the optimization is also somewhat more flexible than the original; for example, we do not care how or where the value on which the parent is switching is computed. There is no requirement that any types be the same. This could be made even more flexible in the future by allowing a wider range of statements in the bodies of `BBC, BBD` (as long as they are all the same of course). This should be a good first step though. Probably needs a perf run. r? `@oli-obk` who reviewed things the last time this was touched
This commit is contained in:
commit
a7f375789b
@ -1,12 +1,12 @@
|
||||
use rustc_middle::mir::patch::MirPatch;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::{Ty, TyCtxt};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::simplify::simplify_cfg;
|
||||
|
||||
/// This pass optimizes something like
|
||||
/// ```text
|
||||
/// ```ignore (syntax-highlighting-only)
|
||||
/// let x: Option<()>;
|
||||
/// let y: Option<()>;
|
||||
/// match (x,y) {
|
||||
@ -15,144 +15,201 @@ use super::simplify::simplify_cfg;
|
||||
/// }
|
||||
/// ```
|
||||
/// into something like
|
||||
/// ```text
|
||||
/// ```ignore (syntax-highlighting-only)
|
||||
/// let x: Option<()>;
|
||||
/// let y: Option<()>;
|
||||
/// let discriminant_x = // get discriminant of x
|
||||
/// let discriminant_y = // get discriminant of y
|
||||
/// if discriminant_x != discriminant_y || discriminant_x == None {1} else {0}
|
||||
/// let discriminant_x = std::mem::discriminant(x);
|
||||
/// let discriminant_y = std::mem::discriminant(y);
|
||||
/// if discriminant_x == discriminant_y {
|
||||
/// match x {
|
||||
/// Some(_) => 0,
|
||||
/// _ => 1, // <----
|
||||
/// } // | Actually the same bb
|
||||
/// } else { // |
|
||||
/// 1 // <--------------
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Specifically, it looks for instances of control flow like this:
|
||||
/// ```text
|
||||
///
|
||||
/// =================
|
||||
/// | BB1 |
|
||||
/// |---------------| ============================
|
||||
/// | ... | /------> | BBC |
|
||||
/// |---------------| | |--------------------------|
|
||||
/// | switchInt(Q) | | | _cl = discriminant(P) |
|
||||
/// | c | --------/ |--------------------------|
|
||||
/// | d | -------\ | switchInt(_cl) |
|
||||
/// | ... | | | c | ---> BBC.2
|
||||
/// | otherwise | --\ | /--- | otherwise |
|
||||
/// ================= | | | ============================
|
||||
/// | | |
|
||||
/// ================= | | |
|
||||
/// | BBU | <-| | | ============================
|
||||
/// |---------------| | \-------> | BBD |
|
||||
/// |---------------| | | |--------------------------|
|
||||
/// | unreachable | | | | _dl = discriminant(P) |
|
||||
/// ================= | | |--------------------------|
|
||||
/// | | | switchInt(_dl) |
|
||||
/// ================= | | | d | ---> BBD.2
|
||||
/// | BB9 | <--------------- | otherwise |
|
||||
/// |---------------| ============================
|
||||
/// | ... |
|
||||
/// =================
|
||||
/// ```
|
||||
/// Where the `otherwise` branch on `BB1` is permitted to either go to `BBU` or to `BB9`. In the
|
||||
/// code:
|
||||
/// - `BB1` is `parent` and `BBC, BBD` are children
|
||||
/// - `P` is `child_place`
|
||||
/// - `child_ty` is the type of `_cl`.
|
||||
/// - `Q` is `parent_op`.
|
||||
/// - `parent_ty` is the type of `Q`.
|
||||
/// - `BB9` is `destination`
|
||||
/// All this is then transformed into:
|
||||
/// ```text
|
||||
///
|
||||
/// =======================
|
||||
/// | BB1 |
|
||||
/// |---------------------| ============================
|
||||
/// | ... | /------> | BBEq |
|
||||
/// | _s = discriminant(P)| | |--------------------------|
|
||||
/// | _t = Ne(Q, _s) | | |--------------------------|
|
||||
/// |---------------------| | | switchInt(Q) |
|
||||
/// | switchInt(_t) | | | c | ---> BBC.2
|
||||
/// | false | --------/ | d | ---> BBD.2
|
||||
/// | otherwise | ---------------- | otherwise |
|
||||
/// ======================= | ============================
|
||||
/// |
|
||||
/// ================= |
|
||||
/// | BB9 | <-----------/
|
||||
/// |---------------|
|
||||
/// | ... |
|
||||
/// =================
|
||||
/// ```
|
||||
///
|
||||
/// This is only correct for some `P`, since `P` is now computed outside the original `switchInt`.
|
||||
/// The filter on which `P` are allowed (together with discussion of its correctness) is found in
|
||||
/// `may_hoist`.
|
||||
pub struct EarlyOtherwiseBranch;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch {
|
||||
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
|
||||
// FIXME(#78496)
|
||||
sess.opts.debugging_opts.unsound_mir_opts && sess.mir_opt_level() >= 3
|
||||
sess.mir_opt_level() >= 2
|
||||
}
|
||||
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
trace!("running EarlyOtherwiseBranch on {:?}", body.source);
|
||||
|
||||
// we are only interested in this bb if the terminator is a switchInt
|
||||
let bbs_with_switch =
|
||||
body.basic_blocks().iter_enumerated().filter(|(_, bb)| is_switch(bb.terminator()));
|
||||
let mut should_cleanup = false;
|
||||
|
||||
let opts_to_apply: Vec<OptimizationToApply<'tcx>> = bbs_with_switch
|
||||
.flat_map(|(bb_idx, bb)| {
|
||||
let switch = bb.terminator();
|
||||
let helper = Helper { body, tcx };
|
||||
let infos = helper.go(bb, switch)?;
|
||||
Some(OptimizationToApply { infos, basic_block_first_switch: bb_idx })
|
||||
})
|
||||
.collect();
|
||||
// Also consider newly generated bbs in the same pass
|
||||
for i in 0..body.basic_blocks().len() {
|
||||
let bbs = body.basic_blocks();
|
||||
let parent = BasicBlock::from_usize(i);
|
||||
let Some(opt_data) = evaluate_candidate(tcx, body, parent) else {
|
||||
continue
|
||||
};
|
||||
|
||||
let should_cleanup = !opts_to_apply.is_empty();
|
||||
|
||||
for opt_to_apply in opts_to_apply {
|
||||
if !tcx.consider_optimizing(|| format!("EarlyOtherwiseBranch {:?}", &opt_to_apply)) {
|
||||
if !tcx.consider_optimizing(|| format!("EarlyOtherwiseBranch {:?}", &opt_data)) {
|
||||
break;
|
||||
}
|
||||
|
||||
trace!("SUCCESS: found optimization possibility to apply: {:?}", &opt_to_apply);
|
||||
trace!("SUCCESS: found optimization possibility to apply: {:?}", &opt_data);
|
||||
|
||||
let statements_before =
|
||||
body.basic_blocks()[opt_to_apply.basic_block_first_switch].statements.len();
|
||||
let end_of_block_location = Location {
|
||||
block: opt_to_apply.basic_block_first_switch,
|
||||
statement_index: statements_before,
|
||||
should_cleanup = true;
|
||||
|
||||
let TerminatorKind::SwitchInt {
|
||||
discr: parent_op,
|
||||
switch_ty: parent_ty,
|
||||
targets: parent_targets
|
||||
} = &bbs[parent].terminator().kind else {
|
||||
unreachable!()
|
||||
};
|
||||
// Always correct since we can only switch on `Copy` types
|
||||
let parent_op = match parent_op {
|
||||
Operand::Move(x) => Operand::Copy(*x),
|
||||
Operand::Copy(x) => Operand::Copy(*x),
|
||||
Operand::Constant(x) => Operand::Constant(x.clone()),
|
||||
};
|
||||
let statements_before = bbs[parent].statements.len();
|
||||
let parent_end = Location { block: parent, statement_index: statements_before };
|
||||
|
||||
let mut patch = MirPatch::new(body);
|
||||
|
||||
// create temp to store second discriminant in
|
||||
let discr_type = opt_to_apply.infos[0].second_switch_info.discr_ty;
|
||||
let discr_span = opt_to_apply.infos[0].second_switch_info.discr_source_info.span;
|
||||
let second_discriminant_temp = patch.new_temp(discr_type, discr_span);
|
||||
// create temp to store second discriminant in, `_s` in example above
|
||||
let second_discriminant_temp =
|
||||
patch.new_temp(opt_data.child_ty, opt_data.child_source.span);
|
||||
|
||||
patch.add_statement(
|
||||
end_of_block_location,
|
||||
StatementKind::StorageLive(second_discriminant_temp),
|
||||
);
|
||||
patch.add_statement(parent_end, StatementKind::StorageLive(second_discriminant_temp));
|
||||
|
||||
// create assignment of discriminant
|
||||
let place_of_adt_to_get_discriminant_of =
|
||||
opt_to_apply.infos[0].second_switch_info.place_of_adt_discr_read;
|
||||
patch.add_assign(
|
||||
end_of_block_location,
|
||||
parent_end,
|
||||
Place::from(second_discriminant_temp),
|
||||
Rvalue::Discriminant(place_of_adt_to_get_discriminant_of),
|
||||
Rvalue::Discriminant(opt_data.child_place),
|
||||
);
|
||||
|
||||
// create temp to store NotEqual comparison between the two discriminants
|
||||
let not_equal = BinOp::Ne;
|
||||
let not_equal_res_type = not_equal.ty(tcx, discr_type, discr_type);
|
||||
let not_equal_temp = patch.new_temp(not_equal_res_type, discr_span);
|
||||
patch.add_statement(end_of_block_location, StatementKind::StorageLive(not_equal_temp));
|
||||
// create temp to store inequality comparison between the two discriminants, `_t` in
|
||||
// example above
|
||||
let nequal = BinOp::Ne;
|
||||
let comp_res_type = nequal.ty(tcx, parent_ty, opt_data.child_ty);
|
||||
let comp_temp = patch.new_temp(comp_res_type, opt_data.child_source.span);
|
||||
patch.add_statement(parent_end, StatementKind::StorageLive(comp_temp));
|
||||
|
||||
// create NotEqual comparison between the two discriminants
|
||||
let first_descriminant_place =
|
||||
opt_to_apply.infos[0].first_switch_info.discr_used_in_switch;
|
||||
let not_equal_rvalue = Rvalue::BinaryOp(
|
||||
not_equal,
|
||||
Box::new((
|
||||
Operand::Copy(Place::from(second_discriminant_temp)),
|
||||
Operand::Copy(first_descriminant_place),
|
||||
)),
|
||||
// create inequality comparison between the two discriminants
|
||||
let comp_rvalue = Rvalue::BinaryOp(
|
||||
nequal,
|
||||
Box::new((parent_op.clone(), Operand::Move(Place::from(second_discriminant_temp)))),
|
||||
);
|
||||
patch.add_statement(
|
||||
end_of_block_location,
|
||||
StatementKind::Assign(Box::new((Place::from(not_equal_temp), not_equal_rvalue))),
|
||||
parent_end,
|
||||
StatementKind::Assign(Box::new((Place::from(comp_temp), comp_rvalue))),
|
||||
);
|
||||
|
||||
let new_targets = opt_to_apply
|
||||
.infos
|
||||
.iter()
|
||||
.flat_map(|x| x.second_switch_info.targets_with_values.iter())
|
||||
.cloned();
|
||||
let eq_new_targets = parent_targets.iter().map(|(value, child)| {
|
||||
let TerminatorKind::SwitchInt{ targets, .. } = &bbs[child].terminator().kind else {
|
||||
unreachable!()
|
||||
};
|
||||
(value, targets.target_for_value(value))
|
||||
});
|
||||
let eq_targets = SwitchTargets::new(eq_new_targets, opt_data.destination);
|
||||
|
||||
let targets = SwitchTargets::new(
|
||||
new_targets,
|
||||
opt_to_apply.infos[0].first_switch_info.otherwise_bb,
|
||||
);
|
||||
|
||||
// new block that jumps to the correct discriminant case. This block is switched to if the discriminants are equal
|
||||
let new_switch_data = BasicBlockData::new(Some(Terminator {
|
||||
source_info: opt_to_apply.infos[0].second_switch_info.discr_source_info,
|
||||
// Create `bbEq` in example above
|
||||
let eq_switch = BasicBlockData::new(Some(Terminator {
|
||||
source_info: bbs[parent].terminator().source_info,
|
||||
kind: TerminatorKind::SwitchInt {
|
||||
// the first and second discriminants are equal, so just pick one
|
||||
discr: Operand::Copy(first_descriminant_place),
|
||||
switch_ty: discr_type,
|
||||
targets,
|
||||
// switch on the first discriminant, so we can mark the second one as dead
|
||||
discr: parent_op,
|
||||
switch_ty: opt_data.child_ty,
|
||||
targets: eq_targets,
|
||||
},
|
||||
}));
|
||||
|
||||
let new_switch_bb = patch.new_block(new_switch_data);
|
||||
let eq_bb = patch.new_block(eq_switch);
|
||||
|
||||
// switch on the NotEqual. If true, then jump to the `otherwise` case.
|
||||
// If false, then jump to a basic block that then jumps to the correct disciminant case
|
||||
let true_case = opt_to_apply.infos[0].first_switch_info.otherwise_bb;
|
||||
let false_case = new_switch_bb;
|
||||
// Jump to it on the basis of the inequality comparison
|
||||
let true_case = opt_data.destination;
|
||||
let false_case = eq_bb;
|
||||
patch.patch_terminator(
|
||||
opt_to_apply.basic_block_first_switch,
|
||||
parent,
|
||||
TerminatorKind::if_(
|
||||
tcx,
|
||||
Operand::Move(Place::from(not_equal_temp)),
|
||||
Operand::Move(Place::from(comp_temp)),
|
||||
true_case,
|
||||
false_case,
|
||||
),
|
||||
);
|
||||
|
||||
// generate StorageDead for the second_discriminant_temp not in use anymore
|
||||
patch.add_statement(
|
||||
end_of_block_location,
|
||||
StatementKind::StorageDead(second_discriminant_temp),
|
||||
);
|
||||
patch.add_statement(parent_end, StatementKind::StorageDead(second_discriminant_temp));
|
||||
|
||||
// Generate a StorageDead for not_equal_temp in each of the targets, since we moved it into the switch
|
||||
// Generate a StorageDead for comp_temp in each of the targets, since we moved it into
|
||||
// the switch
|
||||
for bb in [false_case, true_case].iter() {
|
||||
patch.add_statement(
|
||||
Location { block: *bb, statement_index: 0 },
|
||||
StatementKind::StorageDead(not_equal_temp),
|
||||
StatementKind::StorageDead(comp_temp),
|
||||
);
|
||||
}
|
||||
|
||||
@ -167,201 +224,177 @@ impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_switch(terminator: &Terminator<'_>) -> bool {
|
||||
matches!(terminator.kind, TerminatorKind::SwitchInt { .. })
|
||||
/// Returns true if computing the discriminant of `place` may be hoisted out of the branch
|
||||
fn may_hoist<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, place: Place<'tcx>) -> bool {
|
||||
for (place, proj) in place.iter_projections() {
|
||||
match proj {
|
||||
// Dereferencing in the computation of `place` might cause issues from one of two
|
||||
// cateogires. First, the referrent might be invalid. We protect against this by
|
||||
// dereferencing references only (not pointers). Second, the use of a reference may
|
||||
// invalidate other references that are used later (for aliasing reasons). Consider
|
||||
// where such an invalidated reference may appear:
|
||||
// - In `Q`: Not possible since `Q` is used as the operand of a `SwitchInt` and so
|
||||
// cannot contain referenced data.
|
||||
// - In `BBU`: Not possible since that block contains only the `unreachable` terminator
|
||||
// - In `BBC.2, BBD.2`: Not possible, since `discriminant(P)` was computed prior to
|
||||
// reaching that block in the input to our transformation, and so any data
|
||||
// invalidated by that computation could not have been used there.
|
||||
// - In `BB9`: Not possible since control flow might have reached `BB9` via the
|
||||
// `otherwise` branch in `BBC, BBD` in the input to our transformation, which would
|
||||
// have invalidated the data when computing `discriminant(P)`
|
||||
// So dereferencing here is correct.
|
||||
ProjectionElem::Deref => match place.ty(body.local_decls(), tcx).ty.kind() {
|
||||
ty::Ref(..) => {}
|
||||
_ => return false,
|
||||
},
|
||||
// Field projections are always valid
|
||||
ProjectionElem::Field(..) => {}
|
||||
// We cannot allow
|
||||
// downcasts either, since the correctness of the downcast may depend on the parent
|
||||
// branch being taken. An easy example of this is
|
||||
// ```
|
||||
// Q = discriminant(_3)
|
||||
// P = (_3 as Variant)
|
||||
// ```
|
||||
// However, checking if the child and parent place are the same and only erroring then
|
||||
// is not sufficient either, since the `discriminant(_3) == 1` (or whatever) check may
|
||||
// be replaced by another optimization pass with any other condition that can be proven
|
||||
// equivalent.
|
||||
ProjectionElem::Downcast(..) => {
|
||||
return false;
|
||||
}
|
||||
// We cannot allow indexing since the index may be out of bounds.
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
struct Helper<'a, 'tcx> {
|
||||
body: &'a Body<'tcx>,
|
||||
#[derive(Debug)]
|
||||
struct OptimizationData<'tcx> {
|
||||
destination: BasicBlock,
|
||||
child_place: Place<'tcx>,
|
||||
child_ty: Ty<'tcx>,
|
||||
child_source: SourceInfo,
|
||||
}
|
||||
|
||||
fn evaluate_candidate<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
body: &Body<'tcx>,
|
||||
parent: BasicBlock,
|
||||
) -> Option<OptimizationData<'tcx>> {
|
||||
let bbs = body.basic_blocks();
|
||||
let TerminatorKind::SwitchInt {
|
||||
targets,
|
||||
switch_ty: parent_ty,
|
||||
..
|
||||
} = &bbs[parent].terminator().kind else {
|
||||
return None
|
||||
};
|
||||
let parent_dest = {
|
||||
let poss = targets.otherwise();
|
||||
// If the fallthrough on the parent is trivially unreachable, we can let the
|
||||
// children choose the destination
|
||||
if bbs[poss].statements.len() == 0
|
||||
&& bbs[poss].terminator().kind == TerminatorKind::Unreachable
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(poss)
|
||||
}
|
||||
};
|
||||
let Some((_, child)) = targets.iter().next() else {
|
||||
return None
|
||||
};
|
||||
let child_terminator = &bbs[child].terminator();
|
||||
let TerminatorKind::SwitchInt {
|
||||
switch_ty: child_ty,
|
||||
targets: child_targets,
|
||||
..
|
||||
} = &child_terminator.kind else {
|
||||
return None
|
||||
};
|
||||
if child_ty != parent_ty {
|
||||
return None;
|
||||
}
|
||||
let Some(StatementKind::Assign(boxed))
|
||||
= &bbs[child].statements.first().map(|x| &x.kind) else {
|
||||
return None;
|
||||
};
|
||||
let (_, Rvalue::Discriminant(child_place)) = &**boxed else {
|
||||
return None;
|
||||
};
|
||||
let destination = parent_dest.unwrap_or(child_targets.otherwise());
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SwitchDiscriminantInfo<'tcx> {
|
||||
/// Type of the discriminant being switched on
|
||||
discr_ty: Ty<'tcx>,
|
||||
/// The basic block that the otherwise branch points to
|
||||
otherwise_bb: BasicBlock,
|
||||
/// Target along with the value being branched from. Otherwise is not included
|
||||
targets_with_values: Vec<(u128, BasicBlock)>,
|
||||
discr_source_info: SourceInfo,
|
||||
/// The place of the discriminant used in the switch
|
||||
discr_used_in_switch: Place<'tcx>,
|
||||
/// The place of the adt that has its discriminant read
|
||||
place_of_adt_discr_read: Place<'tcx>,
|
||||
/// The type of the adt that has its discriminant read
|
||||
type_adt_matched_on: Ty<'tcx>,
|
||||
}
|
||||
// Verify that the optimization is legal in general
|
||||
// We can hoist evaluating the child discriminant out of the branch
|
||||
if !may_hoist(tcx, body, *child_place) {
|
||||
return None;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OptimizationToApply<'tcx> {
|
||||
infos: Vec<OptimizationInfo<'tcx>>,
|
||||
/// Basic block of the original first switch
|
||||
basic_block_first_switch: BasicBlock,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OptimizationInfo<'tcx> {
|
||||
/// Info about the first switch and discriminant
|
||||
first_switch_info: SwitchDiscriminantInfo<'tcx>,
|
||||
/// Info about the second switch and discriminant
|
||||
second_switch_info: SwitchDiscriminantInfo<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> Helper<'_, 'tcx> {
|
||||
pub fn go(
|
||||
&self,
|
||||
bb: &BasicBlockData<'tcx>,
|
||||
switch: &Terminator<'tcx>,
|
||||
) -> Option<Vec<OptimizationInfo<'tcx>>> {
|
||||
// try to find the statement that defines the discriminant that is used for the switch
|
||||
let discr = self.find_switch_discriminant_info(bb, switch)?;
|
||||
|
||||
// go through each target, finding a discriminant read, and a switch
|
||||
let results = discr
|
||||
.targets_with_values
|
||||
.iter()
|
||||
.map(|(value, target)| self.find_discriminant_switch_pairing(&discr, *target, *value));
|
||||
|
||||
// if the optimization did not apply for one of the targets, then abort
|
||||
if results.clone().any(|x| x.is_none()) || results.len() == 0 {
|
||||
trace!("NO: not all of the targets matched the pattern for optimization");
|
||||
// Verify that the optimization is legal for each branch
|
||||
for (value, child) in targets.iter() {
|
||||
if !verify_candidate_branch(&bbs[child], value, *child_place, destination) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(results.flatten().collect())
|
||||
}
|
||||
|
||||
fn find_discriminant_switch_pairing(
|
||||
&self,
|
||||
discr_info: &SwitchDiscriminantInfo<'tcx>,
|
||||
target: BasicBlock,
|
||||
value: u128,
|
||||
) -> Option<OptimizationInfo<'tcx>> {
|
||||
let bb = &self.body.basic_blocks()[target];
|
||||
// find switch
|
||||
let terminator = bb.terminator();
|
||||
if is_switch(terminator) {
|
||||
let this_bb_discr_info = self.find_switch_discriminant_info(bb, terminator)?;
|
||||
|
||||
// the types of the two adts matched on have to be equalfor this optimization to apply
|
||||
if discr_info.type_adt_matched_on != this_bb_discr_info.type_adt_matched_on {
|
||||
trace!(
|
||||
"NO: types do not match. LHS: {:?}, RHS: {:?}",
|
||||
discr_info.type_adt_matched_on,
|
||||
this_bb_discr_info.type_adt_matched_on
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// the otherwise branch of the two switches have to point to the same bb
|
||||
if discr_info.otherwise_bb != this_bb_discr_info.otherwise_bb {
|
||||
trace!("NO: otherwise target is not the same");
|
||||
return None;
|
||||
}
|
||||
|
||||
// check that the value being matched on is the same. The
|
||||
if !this_bb_discr_info.targets_with_values.iter().any(|x| x.0 == value) {
|
||||
trace!("NO: values being matched on are not the same");
|
||||
return None;
|
||||
}
|
||||
|
||||
// only allow optimization if the left and right of the tuple being matched are the same variants.
|
||||
// so the following should not optimize
|
||||
// ```rust
|
||||
// let x: Option<()>;
|
||||
// let y: Option<()>;
|
||||
// match (x,y) {
|
||||
// (Some(_), None) => {},
|
||||
// _ => {}
|
||||
// }
|
||||
// ```
|
||||
// We check this by seeing that the value of the first discriminant is the only other discriminant value being used as a target in the second switch
|
||||
if !(this_bb_discr_info.targets_with_values.len() == 1
|
||||
&& this_bb_discr_info.targets_with_values[0].0 == value)
|
||||
{
|
||||
trace!(
|
||||
"NO: The second switch did not have only 1 target (besides otherwise) that had the same value as the value from the first switch that got us here"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// when the second place is a projection of the first one, it's not safe to calculate their discriminant values sequentially.
|
||||
// for example, this should not be optimized:
|
||||
//
|
||||
// ```rust
|
||||
// enum E<'a> { Empty, Some(&'a E<'a>), }
|
||||
// let Some(Some(_)) = e;
|
||||
// ```
|
||||
//
|
||||
// ```mir
|
||||
// bb0: {
|
||||
// _2 = discriminant(*_1)
|
||||
// switchInt(_2) -> [...]
|
||||
// }
|
||||
// bb1: {
|
||||
// _3 = discriminant(*(((*_1) as Some).0: &E))
|
||||
// switchInt(_3) -> [...]
|
||||
// }
|
||||
// ```
|
||||
let discr_place = discr_info.place_of_adt_discr_read;
|
||||
let this_discr_place = this_bb_discr_info.place_of_adt_discr_read;
|
||||
if discr_place.local == this_discr_place.local
|
||||
&& this_discr_place.projection.starts_with(discr_place.projection)
|
||||
{
|
||||
trace!("NO: one target is the projection of another");
|
||||
return None;
|
||||
}
|
||||
|
||||
// if we reach this point, the optimization applies, and we should be able to optimize this case
|
||||
// store the info that is needed to apply the optimization
|
||||
|
||||
Some(OptimizationInfo {
|
||||
first_switch_info: discr_info.clone(),
|
||||
second_switch_info: this_bb_discr_info,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn find_switch_discriminant_info(
|
||||
&self,
|
||||
bb: &BasicBlockData<'tcx>,
|
||||
switch: &Terminator<'tcx>,
|
||||
) -> Option<SwitchDiscriminantInfo<'tcx>> {
|
||||
match &switch.kind {
|
||||
TerminatorKind::SwitchInt { discr, targets, .. } => {
|
||||
let discr_local = discr.place()?.as_local()?;
|
||||
// the declaration of the discriminant read. Place of this read is being used in the switch
|
||||
let discr_decl = &self.body.local_decls()[discr_local];
|
||||
let discr_ty = discr_decl.ty;
|
||||
// the otherwise target lies as the last element
|
||||
let otherwise_bb = targets.otherwise();
|
||||
let targets_with_values = targets.iter().collect();
|
||||
|
||||
// find the place of the adt where the discriminant is being read from
|
||||
// assume this is the last statement of the block
|
||||
let place_of_adt_discr_read = match bb.statements.last()?.kind {
|
||||
StatementKind::Assign(box (_, Rvalue::Discriminant(adt_place))) => {
|
||||
Some(adt_place)
|
||||
}
|
||||
_ => None,
|
||||
}?;
|
||||
|
||||
let type_adt_matched_on = place_of_adt_discr_read.ty(self.body, self.tcx).ty;
|
||||
|
||||
Some(SwitchDiscriminantInfo {
|
||||
discr_used_in_switch: discr.place()?,
|
||||
discr_ty,
|
||||
otherwise_bb,
|
||||
targets_with_values,
|
||||
discr_source_info: discr_decl.source_info,
|
||||
place_of_adt_discr_read,
|
||||
type_adt_matched_on,
|
||||
})
|
||||
}
|
||||
_ => unreachable!("must only be passed terminator that is a switch"),
|
||||
}
|
||||
}
|
||||
Some(OptimizationData {
|
||||
destination,
|
||||
child_place: *child_place,
|
||||
child_ty,
|
||||
child_source: child_terminator.source_info,
|
||||
})
|
||||
}
|
||||
|
||||
fn verify_candidate_branch<'tcx>(
|
||||
branch: &BasicBlockData<'tcx>,
|
||||
value: u128,
|
||||
place: Place<'tcx>,
|
||||
destination: BasicBlock,
|
||||
) -> bool {
|
||||
// In order for the optimization to be correct, the branch must...
|
||||
// ...have exactly one statement
|
||||
if branch.statements.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
// ...assign the descriminant of `place` in that statement
|
||||
let StatementKind::Assign(boxed) = &branch.statements[0].kind else {
|
||||
return false
|
||||
};
|
||||
let (discr_place, Rvalue::Discriminant(from_place)) = &**boxed else {
|
||||
return false
|
||||
};
|
||||
if *from_place != place {
|
||||
return false;
|
||||
}
|
||||
// ...make that assignment to a local
|
||||
if discr_place.projection.len() != 0 {
|
||||
return false;
|
||||
}
|
||||
// ...terminate on a `SwitchInt` that invalidates that local
|
||||
let TerminatorKind::SwitchInt{ discr: switch_op, targets, .. } = &branch.terminator().kind else {
|
||||
return false
|
||||
};
|
||||
if *switch_op != Operand::Move(*discr_place) {
|
||||
return false;
|
||||
}
|
||||
// ...fall through to `destination` if the switch misses
|
||||
if destination != targets.otherwise() {
|
||||
return false;
|
||||
}
|
||||
// ...have a branch for value `value`
|
||||
let mut iter = targets.iter();
|
||||
let Some((target_value, _)) = iter.next() else {
|
||||
return false;
|
||||
};
|
||||
if target_value != value {
|
||||
return false;
|
||||
}
|
||||
// ...and have no more branches
|
||||
if let Some(_) = iter.next() {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -12,8 +12,8 @@
|
||||
let mut _7: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:5:10: 5:17
|
||||
let _8: u32; // in scope 0 at $DIR/early_otherwise_branch.rs:5:15: 5:16
|
||||
let _9: u32; // in scope 0 at $DIR/early_otherwise_branch.rs:5:24: 5:25
|
||||
+ let mut _10: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:5:19: 5:26
|
||||
+ let mut _11: bool; // in scope 0 at $DIR/early_otherwise_branch.rs:5:19: 5:26
|
||||
+ let mut _10: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ let mut _11: bool; // in scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
scope 1 {
|
||||
debug a => _8; // in scope 1 at $DIR/early_otherwise_branch.rs:5:15: 5:16
|
||||
debug b => _9; // in scope 1 at $DIR/early_otherwise_branch.rs:5:24: 5:25
|
||||
@ -34,7 +34,7 @@
|
||||
+ StorageLive(_10); // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ _10 = discriminant((_3.1: std::option::Option<u32>)); // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ StorageLive(_11); // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ _11 = Ne(_10, _7); // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ _11 = Ne(_7, move _10); // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ StorageDead(_10); // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ switchInt(move _11) -> [false: bb4, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
}
|
||||
@ -70,8 +70,8 @@
|
||||
+ }
|
||||
+
|
||||
+ bb4: {
|
||||
+ StorageDead(_11); // scope 0 at $DIR/early_otherwise_branch.rs:5:19: 5:26
|
||||
+ switchInt(_7) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:5:19: 5:26
|
||||
+ StorageDead(_11); // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
+ switchInt(_7) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:4:5: 4:17
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,8 @@
|
||||
let mut _8: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:13:10: 13:17
|
||||
let _9: u32; // in scope 0 at $DIR/early_otherwise_branch.rs:13:15: 13:16
|
||||
let _10: u32; // in scope 0 at $DIR/early_otherwise_branch.rs:13:24: 13:25
|
||||
+ let mut _11: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:14:16: 14:20
|
||||
+ let mut _12: bool; // in scope 0 at $DIR/early_otherwise_branch.rs:14:16: 14:20
|
||||
+ let mut _11: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ let mut _12: bool; // in scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
scope 1 {
|
||||
debug a => _9; // in scope 1 at $DIR/early_otherwise_branch.rs:13:15: 13:16
|
||||
debug b => _10; // in scope 1 at $DIR/early_otherwise_branch.rs:13:24: 13:25
|
||||
@ -35,7 +35,7 @@
|
||||
+ StorageLive(_11); // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ _11 = discriminant((_3.1: std::option::Option<u32>)); // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ StorageLive(_12); // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ _12 = Ne(_11, _8); // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ _12 = Ne(_8, move _11); // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ StorageDead(_11); // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ switchInt(move _12) -> [false: bb5, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
}
|
||||
@ -84,8 +84,8 @@
|
||||
+ }
|
||||
+
|
||||
+ bb5: {
|
||||
+ StorageDead(_12); // scope 0 at $DIR/early_otherwise_branch.rs:14:16: 14:20
|
||||
+ switchInt(_8) -> [0_isize: bb3, 1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:14:16: 14:20
|
||||
+ StorageDead(_12); // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
+ switchInt(_8) -> [0_isize: bb3, 1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:12:5: 12:17
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,77 @@
|
||||
- // MIR for `opt3` before EarlyOtherwiseBranch
|
||||
+ // MIR for `opt3` after EarlyOtherwiseBranch
|
||||
|
||||
fn opt3(_1: Option<u32>, _2: Option<bool>) -> u32 {
|
||||
debug x => _1; // in scope 0 at $DIR/early_otherwise_branch.rs:21:9: 21:10
|
||||
debug y => _2; // in scope 0 at $DIR/early_otherwise_branch.rs:21:25: 21:26
|
||||
let mut _0: u32; // return place in scope 0 at $DIR/early_otherwise_branch.rs:21:45: 21:48
|
||||
let mut _3: (std::option::Option<u32>, std::option::Option<bool>); // in scope 0 at $DIR/early_otherwise_branch.rs:22:11: 22:17
|
||||
let mut _4: std::option::Option<u32>; // in scope 0 at $DIR/early_otherwise_branch.rs:22:12: 22:13
|
||||
let mut _5: std::option::Option<bool>; // in scope 0 at $DIR/early_otherwise_branch.rs:22:15: 22:16
|
||||
let mut _6: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:23:19: 23:26
|
||||
let mut _7: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:23:10: 23:17
|
||||
let _8: u32; // in scope 0 at $DIR/early_otherwise_branch.rs:23:15: 23:16
|
||||
let _9: bool; // in scope 0 at $DIR/early_otherwise_branch.rs:23:24: 23:25
|
||||
+ let mut _10: isize; // in scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ let mut _11: bool; // in scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
scope 1 {
|
||||
debug a => _8; // in scope 1 at $DIR/early_otherwise_branch.rs:23:15: 23:16
|
||||
debug b => _9; // in scope 1 at $DIR/early_otherwise_branch.rs:23:24: 23:25
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_3); // scope 0 at $DIR/early_otherwise_branch.rs:22:11: 22:17
|
||||
StorageLive(_4); // scope 0 at $DIR/early_otherwise_branch.rs:22:12: 22:13
|
||||
_4 = _1; // scope 0 at $DIR/early_otherwise_branch.rs:22:12: 22:13
|
||||
StorageLive(_5); // scope 0 at $DIR/early_otherwise_branch.rs:22:15: 22:16
|
||||
_5 = _2; // scope 0 at $DIR/early_otherwise_branch.rs:22:15: 22:16
|
||||
(_3.0: std::option::Option<u32>) = move _4; // scope 0 at $DIR/early_otherwise_branch.rs:22:11: 22:17
|
||||
(_3.1: std::option::Option<bool>) = move _5; // scope 0 at $DIR/early_otherwise_branch.rs:22:11: 22:17
|
||||
StorageDead(_5); // scope 0 at $DIR/early_otherwise_branch.rs:22:16: 22:17
|
||||
StorageDead(_4); // scope 0 at $DIR/early_otherwise_branch.rs:22:16: 22:17
|
||||
_7 = discriminant((_3.0: std::option::Option<u32>)); // scope 0 at $DIR/early_otherwise_branch.rs:22:11: 22:17
|
||||
- switchInt(move _7) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ StorageLive(_10); // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ _10 = discriminant((_3.1: std::option::Option<bool>)); // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ StorageLive(_11); // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ _11 = Ne(_7, move _10); // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ StorageDead(_10); // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ switchInt(move _11) -> [false: bb4, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
}
|
||||
|
||||
bb1: {
|
||||
+ StorageDead(_11); // scope 0 at $DIR/early_otherwise_branch.rs:24:14: 24:15
|
||||
_0 = const 1_u32; // scope 0 at $DIR/early_otherwise_branch.rs:24:14: 24:15
|
||||
- goto -> bb4; // scope 0 at $DIR/early_otherwise_branch.rs:24:14: 24:15
|
||||
+ goto -> bb3; // scope 0 at $DIR/early_otherwise_branch.rs:24:14: 24:15
|
||||
}
|
||||
|
||||
bb2: {
|
||||
- _6 = discriminant((_3.1: std::option::Option<bool>)); // scope 0 at $DIR/early_otherwise_branch.rs:22:11: 22:17
|
||||
- switchInt(move _6) -> [1_isize: bb3, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
- }
|
||||
-
|
||||
- bb3: {
|
||||
StorageLive(_8); // scope 0 at $DIR/early_otherwise_branch.rs:23:15: 23:16
|
||||
_8 = (((_3.0: std::option::Option<u32>) as Some).0: u32); // scope 0 at $DIR/early_otherwise_branch.rs:23:15: 23:16
|
||||
StorageLive(_9); // scope 0 at $DIR/early_otherwise_branch.rs:23:24: 23:25
|
||||
_9 = (((_3.1: std::option::Option<bool>) as Some).0: bool); // scope 0 at $DIR/early_otherwise_branch.rs:23:24: 23:25
|
||||
_0 = const 0_u32; // scope 1 at $DIR/early_otherwise_branch.rs:23:31: 23:32
|
||||
StorageDead(_9); // scope 0 at $DIR/early_otherwise_branch.rs:23:31: 23:32
|
||||
StorageDead(_8); // scope 0 at $DIR/early_otherwise_branch.rs:23:31: 23:32
|
||||
- goto -> bb4; // scope 0 at $DIR/early_otherwise_branch.rs:23:31: 23:32
|
||||
+ goto -> bb3; // scope 0 at $DIR/early_otherwise_branch.rs:23:31: 23:32
|
||||
}
|
||||
|
||||
- bb4: {
|
||||
+ bb3: {
|
||||
StorageDead(_3); // scope 0 at $DIR/early_otherwise_branch.rs:26:1: 26:2
|
||||
return; // scope 0 at $DIR/early_otherwise_branch.rs:26:2: 26:2
|
||||
+ }
|
||||
+
|
||||
+ bb4: {
|
||||
+ StorageDead(_11); // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
+ switchInt(_7) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch.rs:22:5: 22:17
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,17 @@ fn opt2(x: Option<u32>, y: Option<u32>) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
// optimize despite different types
|
||||
// EMIT_MIR early_otherwise_branch.opt3.EarlyOtherwiseBranch.diff
|
||||
fn opt3(x: Option<u32>, y: Option<bool>) -> u32 {
|
||||
match (x, y) {
|
||||
(Some(a), Some(b)) => 0,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
opt1(None, Some(0));
|
||||
opt2(None, Some(0));
|
||||
opt3(None, Some(false));
|
||||
}
|
||||
|
@ -16,10 +16,10 @@
|
||||
let _11: u32; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:15: 6:16
|
||||
let _12: u32; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:24: 6:25
|
||||
let _13: u32; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:33: 6:34
|
||||
+ let mut _14: isize; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:19: 6:26
|
||||
+ let mut _15: bool; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:19: 6:26
|
||||
+ let mut _16: isize; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:28: 6:35
|
||||
+ let mut _17: bool; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:28: 6:35
|
||||
+ let mut _14: isize; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ let mut _15: bool; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ let mut _16: isize; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ let mut _17: bool; // in scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
scope 1 {
|
||||
debug a => _11; // in scope 1 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:15: 6:16
|
||||
debug b => _12; // in scope 1 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:24: 6:25
|
||||
@ -45,7 +45,7 @@
|
||||
+ StorageLive(_14); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ _14 = discriminant((_4.1: std::option::Option<u32>)); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ StorageLive(_15); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ _15 = Ne(_14, _10); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ _15 = Ne(_10, move _14); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ StorageDead(_14); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ switchInt(move _15) -> [false: bb5, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
}
|
||||
@ -92,8 +92,8 @@
|
||||
+ }
|
||||
+
|
||||
+ bb5: {
|
||||
+ StorageDead(_15); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:19: 6:26
|
||||
+ switchInt(_10) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:6:19: 6:26
|
||||
+ StorageDead(_15); // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
+ switchInt(_10) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_3_element_tuple.rs:5:5: 5:20
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,8 @@
|
||||
let mut _31: f32; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:25:50: 25:55
|
||||
let mut _32: !; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:26:14: 26:28
|
||||
let mut _33: (); // in scope 0 at $DIR/early_otherwise_branch_68867.rs:26:25: 26:27
|
||||
+ let mut _34: isize; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ let mut _35: bool; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ let mut _34: isize; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ let mut _35: bool; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
scope 1 {
|
||||
- debug one => _12; // in scope 1 at $DIR/early_otherwise_branch_68867.rs:22:14: 22:17
|
||||
- debug other => _13; // in scope 1 at $DIR/early_otherwise_branch_68867.rs:22:24: 22:29
|
||||
@ -85,7 +85,7 @@
|
||||
+ StorageLive(_34); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ _34 = discriminant((*(_4.1: &ViewportPercentageLength))); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ StorageLive(_35); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ _35 = Ne(_34, _11); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ _35 = Ne(_11, move _34); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ StorageDead(_34); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ switchInt(move _35) -> [false: bb7, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
}
|
||||
@ -293,8 +293,8 @@
|
||||
- StorageDead(_3); // scope 0 at $DIR/early_otherwise_branch_68867.rs:27:6: 27:7
|
||||
- StorageDead(_4); // scope 0 at $DIR/early_otherwise_branch_68867.rs:28:1: 28:2
|
||||
- return; // scope 0 at $DIR/early_otherwise_branch_68867.rs:28:2: 28:2
|
||||
+ StorageDead(_35); // scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ switchInt(_11) -> [0_isize: bb2, 1_isize: bb3, 2_isize: bb4, 3_isize: bb5, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ StorageDead(_35); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ switchInt(_11) -> [0_isize: bb2, 1_isize: bb3, 2_isize: bb4, 3_isize: bb5, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,8 @@
|
||||
let mut _31: f32; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:25:50: 25:55
|
||||
let mut _32: !; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:26:14: 26:28
|
||||
let mut _33: (); // in scope 0 at $DIR/early_otherwise_branch_68867.rs:26:25: 26:27
|
||||
+ let mut _34: isize; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ let mut _35: bool; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ let mut _34: isize; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ let mut _35: bool; // in scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
scope 1 {
|
||||
debug one => _12; // in scope 1 at $DIR/early_otherwise_branch_68867.rs:22:14: 22:17
|
||||
debug other => _13; // in scope 1 at $DIR/early_otherwise_branch_68867.rs:22:24: 22:29
|
||||
@ -71,7 +71,7 @@
|
||||
+ StorageLive(_34); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ _34 = discriminant((*(_4.1: &ViewportPercentageLength))); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ StorageLive(_35); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ _35 = Ne(_34, _11); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ _35 = Ne(_11, move _34); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ StorageDead(_34); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ switchInt(move _35) -> [false: bb7, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
}
|
||||
@ -209,8 +209,8 @@
|
||||
+ }
|
||||
+
|
||||
+ bb7: {
|
||||
+ StorageDead(_35); // scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ switchInt(_11) -> [0_isize: bb2, 1_isize: bb3, 2_isize: bb4, 3_isize: bb5, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_68867.rs:22:21: 22:30
|
||||
+ StorageDead(_35); // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
+ switchInt(_11) -> [0_isize: bb2, 1_isize: bb3, 2_isize: bb4, 3_isize: bb5, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_68867.rs:21:8: 21:24
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,60 +0,0 @@
|
||||
- // MIR for `noopt2` before EarlyOtherwiseBranch
|
||||
+ // MIR for `noopt2` after EarlyOtherwiseBranch
|
||||
|
||||
fn noopt2(_1: Option<u32>, _2: Option<bool>) -> u32 {
|
||||
debug x => _1; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:18:11: 18:12
|
||||
debug y => _2; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:18:27: 18:28
|
||||
let mut _0: u32; // return place in scope 0 at $DIR/early_otherwise_branch_noopt.rs:18:47: 18:50
|
||||
let mut _3: (std::option::Option<u32>, std::option::Option<bool>); // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:11: 19:17
|
||||
let mut _4: std::option::Option<u32>; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:12: 19:13
|
||||
let mut _5: std::option::Option<bool>; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:15: 19:16
|
||||
let mut _6: isize; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:19: 20:26
|
||||
let mut _7: isize; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:10: 20:17
|
||||
let _8: u32; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:15: 20:16
|
||||
let _9: bool; // in scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:24: 20:25
|
||||
scope 1 {
|
||||
debug a => _8; // in scope 1 at $DIR/early_otherwise_branch_noopt.rs:20:15: 20:16
|
||||
debug b => _9; // in scope 1 at $DIR/early_otherwise_branch_noopt.rs:20:24: 20:25
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_3); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:11: 19:17
|
||||
StorageLive(_4); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:12: 19:13
|
||||
_4 = _1; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:12: 19:13
|
||||
StorageLive(_5); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:15: 19:16
|
||||
_5 = _2; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:15: 19:16
|
||||
(_3.0: std::option::Option<u32>) = move _4; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:11: 19:17
|
||||
(_3.1: std::option::Option<bool>) = move _5; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:11: 19:17
|
||||
StorageDead(_5); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:16: 19:17
|
||||
StorageDead(_4); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:16: 19:17
|
||||
_7 = discriminant((_3.0: std::option::Option<u32>)); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:11: 19:17
|
||||
switchInt(move _7) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:5: 19:17
|
||||
}
|
||||
|
||||
bb1: {
|
||||
_0 = const 1_u32; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:21:14: 21:15
|
||||
goto -> bb4; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:21:14: 21:15
|
||||
}
|
||||
|
||||
bb2: {
|
||||
_6 = discriminant((_3.1: std::option::Option<bool>)); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:11: 19:17
|
||||
switchInt(move _6) -> [1_isize: bb3, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:19:5: 19:17
|
||||
}
|
||||
|
||||
bb3: {
|
||||
StorageLive(_8); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:15: 20:16
|
||||
_8 = (((_3.0: std::option::Option<u32>) as Some).0: u32); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:15: 20:16
|
||||
StorageLive(_9); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:24: 20:25
|
||||
_9 = (((_3.1: std::option::Option<bool>) as Some).0: bool); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:24: 20:25
|
||||
_0 = const 0_u32; // scope 1 at $DIR/early_otherwise_branch_noopt.rs:20:31: 20:32
|
||||
StorageDead(_9); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:31: 20:32
|
||||
StorageDead(_8); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:31: 20:32
|
||||
goto -> bb4; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:20:31: 20:32
|
||||
}
|
||||
|
||||
bb4: {
|
||||
StorageDead(_3); // scope 0 at $DIR/early_otherwise_branch_noopt.rs:23:1: 23:2
|
||||
return; // scope 0 at $DIR/early_otherwise_branch_noopt.rs:23:2: 23:2
|
||||
}
|
||||
}
|
||||
|
@ -13,16 +13,6 @@ fn noopt1(x: Option<u32>, y: Option<u32>) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
// must not optimize as the types being matched on are not identical
|
||||
// EMIT_MIR early_otherwise_branch_noopt.noopt2.EarlyOtherwiseBranch.diff
|
||||
fn noopt2(x: Option<u32>, y: Option<bool>) -> u32 {
|
||||
match (x, y) {
|
||||
(Some(a), Some(b)) => 0,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
noopt1(None, Some(0));
|
||||
noopt2(None, Some(true));
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
- // MIR for `no_deref_ptr` before EarlyOtherwiseBranch
|
||||
+ // MIR for `no_deref_ptr` after EarlyOtherwiseBranch
|
||||
|
||||
fn no_deref_ptr(_1: Option<i32>, _2: *const Option<i32>) -> i32 {
|
||||
debug a => _1; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:18:24: 18:25
|
||||
debug b => _2; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:18:40: 18:41
|
||||
let mut _0: i32; // return place in scope 0 at $DIR/early_otherwise_branch_soundness.rs:18:66: 18:69
|
||||
let mut _3: isize; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:21:9: 21:16
|
||||
let mut _4: isize; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:22:13: 22:20
|
||||
let _5: i32; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:22:18: 22:19
|
||||
scope 1 {
|
||||
debug v => _5; // in scope 1 at $DIR/early_otherwise_branch_soundness.rs:22:18: 22:19
|
||||
}
|
||||
|
||||
bb0: {
|
||||
_3 = discriminant(_1); // scope 0 at $DIR/early_otherwise_branch_soundness.rs:19:11: 19:12
|
||||
switchInt(move _3) -> [1_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:19:5: 19:12
|
||||
}
|
||||
|
||||
bb1: {
|
||||
_0 = const 0_i32; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:25:14: 25:15
|
||||
return; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:25:14: 25:15
|
||||
}
|
||||
|
||||
bb2: {
|
||||
_4 = discriminant((*_2)); // scope 0 at $DIR/early_otherwise_branch_soundness.rs:21:26: 21:28
|
||||
switchInt(move _4) -> [1_isize: bb4, otherwise: bb3]; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:21:20: 21:28
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_0 = const 0_i32; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:23:18: 23:19
|
||||
return; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:23:18: 23:19
|
||||
}
|
||||
|
||||
bb4: {
|
||||
StorageLive(_5); // scope 0 at $DIR/early_otherwise_branch_soundness.rs:22:18: 22:19
|
||||
_5 = (((*_2) as Some).0: i32); // scope 0 at $DIR/early_otherwise_branch_soundness.rs:22:18: 22:19
|
||||
_0 = _5; // scope 1 at $DIR/early_otherwise_branch_soundness.rs:22:24: 22:25
|
||||
StorageDead(_5); // scope 0 at $DIR/early_otherwise_branch_soundness.rs:22:24: 22:25
|
||||
return; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:22:24: 22:25
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
- // MIR for `no_downcast` before EarlyOtherwiseBranch
|
||||
+ // MIR for `no_downcast` after EarlyOtherwiseBranch
|
||||
|
||||
fn no_downcast(_1: &E) -> u32 {
|
||||
debug e => _1; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:12:16: 12:17
|
||||
let mut _0: u32; // return place in scope 0 at $DIR/early_otherwise_branch_soundness.rs:12:26: 12:29
|
||||
let mut _2: isize; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:20: 13:30
|
||||
let mut _3: isize; // in scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:12: 13:31
|
||||
|
||||
bb0: {
|
||||
_3 = discriminant((*_1)); // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:12: 13:31
|
||||
switchInt(move _3) -> [1_isize: bb1, otherwise: bb3]; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:12: 13:31
|
||||
}
|
||||
|
||||
bb1: {
|
||||
_2 = discriminant((*(((*_1) as Some).0: &E))); // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:12: 13:31
|
||||
switchInt(move _2) -> [1_isize: bb2, otherwise: bb3]; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:12: 13:31
|
||||
}
|
||||
|
||||
bb2: {
|
||||
_0 = const 1_u32; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:38: 13:39
|
||||
return; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:5: 13:52
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_0 = const 2_u32; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:49: 13:50
|
||||
return; // scope 0 at $DIR/early_otherwise_branch_soundness.rs:13:5: 13:52
|
||||
}
|
||||
}
|
||||
|
32
src/test/mir-opt/early_otherwise_branch_soundness.rs
Normal file
32
src/test/mir-opt/early_otherwise_branch_soundness.rs
Normal file
@ -0,0 +1,32 @@
|
||||
// compile-flags: -Z mir-opt-level=4 -Zunsound-mir-opts
|
||||
|
||||
// Tests various cases that the `early_otherwise_branch` opt should *not* optimize
|
||||
|
||||
// From #78496
|
||||
enum E<'a> {
|
||||
Empty,
|
||||
Some(&'a E<'a>),
|
||||
}
|
||||
|
||||
// EMIT_MIR early_otherwise_branch_soundness.no_downcast.EarlyOtherwiseBranch.diff
|
||||
fn no_downcast(e: &E) -> u32 {
|
||||
if let E::Some(E::Some(_)) = e { 1 } else { 2 }
|
||||
}
|
||||
|
||||
// SAFETY: if `a` is `Some`, `b` must point to a valid, initialized value
|
||||
// EMIT_MIR early_otherwise_branch_soundness.no_deref_ptr.EarlyOtherwiseBranch.diff
|
||||
unsafe fn no_deref_ptr(a: Option<i32>, b: *const Option<i32>) -> i32 {
|
||||
match a {
|
||||
// `*b` being correct depends on `a == Some(_)`
|
||||
Some(_) => match *b {
|
||||
Some(v) => v,
|
||||
_ => 0,
|
||||
},
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
no_downcast(&E::Empty);
|
||||
unsafe { no_deref_ptr(None, std::ptr::null()) };
|
||||
}
|
Loading…
Reference in New Issue
Block a user