Rollup merge of #130786 - DianQK:early_otherwise_branch_cleanup, r=oli-obk
mir-opt: a sub-BB of a cleanup BB must also be a cleanup BB in `EarlyOtherwiseBranch` Fixes #130769. r? `@cjgillot` or mir-opt
This commit is contained in:
commit
2d08e94d12
14 changed files with 739 additions and 109 deletions
|
@ -1348,8 +1348,8 @@ pub struct BasicBlockData<'tcx> {
|
|||
}
|
||||
|
||||
impl<'tcx> BasicBlockData<'tcx> {
|
||||
pub fn new(terminator: Option<Terminator<'tcx>>) -> BasicBlockData<'tcx> {
|
||||
BasicBlockData { statements: vec![], terminator, is_cleanup: false }
|
||||
pub fn new(terminator: Option<Terminator<'tcx>>, is_cleanup: bool) -> BasicBlockData<'tcx> {
|
||||
BasicBlockData { statements: vec![], terminator, is_cleanup }
|
||||
}
|
||||
|
||||
/// Accessor for terminator.
|
||||
|
|
|
@ -19,7 +19,7 @@ impl<'tcx> CFG<'tcx> {
|
|||
// it as #[inline(never)] to keep rustc's stack use in check.
|
||||
#[inline(never)]
|
||||
pub(crate) fn start_new_block(&mut self) -> BasicBlock {
|
||||
self.basic_blocks.push(BasicBlockData::new(None))
|
||||
self.basic_blocks.push(BasicBlockData::new(None, false))
|
||||
}
|
||||
|
||||
pub(crate) fn start_new_cleanup_block(&mut self) -> BasicBlock {
|
||||
|
|
|
@ -64,7 +64,7 @@ pub(super) fn build_custom_mir<'tcx>(
|
|||
};
|
||||
|
||||
body.local_decls.push(LocalDecl::new(return_ty, return_ty_span));
|
||||
body.basic_blocks_mut().push(BasicBlockData::new(None));
|
||||
body.basic_blocks_mut().push(BasicBlockData::new(None, false));
|
||||
body.source_scopes.push(SourceScopeData {
|
||||
span,
|
||||
parent_scope: None,
|
||||
|
|
|
@ -199,10 +199,12 @@ impl<'a, 'tcx> ParseCtxt<'a, 'tcx> {
|
|||
match &self.thir[stmt].kind {
|
||||
StmtKind::Let { pattern, initializer: Some(initializer), .. } => {
|
||||
let (var, ..) = self.parse_var(pattern)?;
|
||||
let mut data = BasicBlockData::new(None);
|
||||
data.is_cleanup = parse_by_kind!(self, *initializer, _, "basic block declaration",
|
||||
@variant(mir_basic_block, Normal) => false,
|
||||
@variant(mir_basic_block, Cleanup) => true,
|
||||
let data = BasicBlockData::new(
|
||||
None,
|
||||
parse_by_kind!(self, *initializer, _, "basic block declaration",
|
||||
@variant(mir_basic_block, Normal) => false,
|
||||
@variant(mir_basic_block, Cleanup) => true,
|
||||
),
|
||||
);
|
||||
let block = self.body.basic_blocks_mut().push(data);
|
||||
self.block_map.insert(var, block);
|
||||
|
@ -308,8 +310,7 @@ impl<'a, 'tcx> ParseCtxt<'a, 'tcx> {
|
|||
ExprKind::Block { block } => &self.thir[*block],
|
||||
);
|
||||
|
||||
let mut data = BasicBlockData::new(None);
|
||||
data.is_cleanup = is_cleanup;
|
||||
let mut data = BasicBlockData::new(None, is_cleanup);
|
||||
for stmt_id in &*block.stmts {
|
||||
let stmt = self.statement_as_expr(*stmt_id)?;
|
||||
let span = self.thir[stmt].span;
|
||||
|
|
|
@ -129,18 +129,29 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch {
|
|||
|
||||
let mut patch = MirPatch::new(body);
|
||||
|
||||
// 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);
|
||||
let (second_discriminant_temp, second_operand) = if opt_data.need_hoist_discriminant {
|
||||
// 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(parent_end, StatementKind::StorageLive(second_discriminant_temp));
|
||||
patch.add_statement(
|
||||
parent_end,
|
||||
StatementKind::StorageLive(second_discriminant_temp),
|
||||
);
|
||||
|
||||
// create assignment of discriminant
|
||||
patch.add_assign(
|
||||
parent_end,
|
||||
Place::from(second_discriminant_temp),
|
||||
Rvalue::Discriminant(opt_data.child_place),
|
||||
);
|
||||
// create assignment of discriminant
|
||||
patch.add_assign(
|
||||
parent_end,
|
||||
Place::from(second_discriminant_temp),
|
||||
Rvalue::Discriminant(opt_data.child_place),
|
||||
);
|
||||
(
|
||||
Some(second_discriminant_temp),
|
||||
Operand::Move(Place::from(second_discriminant_temp)),
|
||||
)
|
||||
} else {
|
||||
(None, Operand::Copy(opt_data.child_place))
|
||||
};
|
||||
|
||||
// create temp to store inequality comparison between the two discriminants, `_t` in
|
||||
// example above
|
||||
|
@ -149,11 +160,9 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch {
|
|||
let comp_temp = patch.new_temp(comp_res_type, opt_data.child_source.span);
|
||||
patch.add_statement(parent_end, StatementKind::StorageLive(comp_temp));
|
||||
|
||||
// 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)))),
|
||||
);
|
||||
// create inequality comparison
|
||||
let comp_rvalue =
|
||||
Rvalue::BinaryOp(nequal, Box::new((parent_op.clone(), second_operand)));
|
||||
patch.add_statement(
|
||||
parent_end,
|
||||
StatementKind::Assign(Box::new((Place::from(comp_temp), comp_rvalue))),
|
||||
|
@ -170,14 +179,17 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch {
|
|||
let eq_targets = SwitchTargets::new(eq_new_targets, parent_targets.otherwise());
|
||||
|
||||
// Create `bbEq` in example above
|
||||
let eq_switch = BasicBlockData::new(Some(Terminator {
|
||||
source_info: bbs[parent].terminator().source_info,
|
||||
kind: TerminatorKind::SwitchInt {
|
||||
// switch on the first discriminant, so we can mark the second one as dead
|
||||
discr: parent_op,
|
||||
targets: eq_targets,
|
||||
},
|
||||
}));
|
||||
let eq_switch = BasicBlockData::new(
|
||||
Some(Terminator {
|
||||
source_info: bbs[parent].terminator().source_info,
|
||||
kind: TerminatorKind::SwitchInt {
|
||||
// switch on the first discriminant, so we can mark the second one as dead
|
||||
discr: parent_op,
|
||||
targets: eq_targets,
|
||||
},
|
||||
}),
|
||||
bbs[parent].is_cleanup,
|
||||
);
|
||||
|
||||
let eq_bb = patch.new_block(eq_switch);
|
||||
|
||||
|
@ -189,8 +201,13 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch {
|
|||
TerminatorKind::if_(Operand::Move(Place::from(comp_temp)), true_case, false_case),
|
||||
);
|
||||
|
||||
// generate StorageDead for the second_discriminant_temp not in use anymore
|
||||
patch.add_statement(parent_end, StatementKind::StorageDead(second_discriminant_temp));
|
||||
if let Some(second_discriminant_temp) = second_discriminant_temp {
|
||||
// generate StorageDead for the second_discriminant_temp not in use anymore
|
||||
patch.add_statement(
|
||||
parent_end,
|
||||
StatementKind::StorageDead(second_discriminant_temp),
|
||||
);
|
||||
}
|
||||
|
||||
// Generate a StorageDead for comp_temp in each of the targets, since we moved it into
|
||||
// the switch
|
||||
|
@ -218,6 +235,7 @@ struct OptimizationData<'tcx> {
|
|||
child_place: Place<'tcx>,
|
||||
child_ty: Ty<'tcx>,
|
||||
child_source: SourceInfo,
|
||||
need_hoist_discriminant: bool,
|
||||
}
|
||||
|
||||
fn evaluate_candidate<'tcx>(
|
||||
|
@ -226,49 +244,21 @@ fn evaluate_candidate<'tcx>(
|
|||
parent: BasicBlock,
|
||||
) -> Option<OptimizationData<'tcx>> {
|
||||
let bbs = &body.basic_blocks;
|
||||
// NB: If this BB is a cleanup, we may need to figure out what else needs to be handled.
|
||||
if bbs[parent].is_cleanup {
|
||||
return None;
|
||||
}
|
||||
let TerminatorKind::SwitchInt { targets, discr: parent_discr } = &bbs[parent].terminator().kind
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let parent_ty = parent_discr.ty(body.local_decls(), tcx);
|
||||
if !bbs[targets.otherwise()].is_empty_unreachable() {
|
||||
// Someone could write code like this:
|
||||
// ```rust
|
||||
// let Q = val;
|
||||
// if discriminant(P) == otherwise {
|
||||
// let ptr = &mut Q as *mut _ as *mut u8;
|
||||
// // It may be difficult for us to effectively determine whether values are valid.
|
||||
// // Invalid values can come from all sorts of corners.
|
||||
// unsafe { *ptr = 10; }
|
||||
// }
|
||||
//
|
||||
// match P {
|
||||
// A => match Q {
|
||||
// A => {
|
||||
// // code
|
||||
// }
|
||||
// _ => {
|
||||
// // don't use Q
|
||||
// }
|
||||
// }
|
||||
// _ => {
|
||||
// // don't use Q
|
||||
// }
|
||||
// };
|
||||
// ```
|
||||
//
|
||||
// Hoisting the `discriminant(Q)` out of the `A` arm causes us to compute the discriminant
|
||||
// of an invalid value, which is UB.
|
||||
// In order to fix this, **we would either need to show that the discriminant computation of
|
||||
// `place` is computed in all branches**.
|
||||
// FIXME(#95162) For the moment, we adopt a conservative approach and
|
||||
// consider only the `otherwise` branch has no statements and an unreachable terminator.
|
||||
return None;
|
||||
}
|
||||
let (_, child) = targets.iter().next()?;
|
||||
let child_terminator = &bbs[child].terminator();
|
||||
let TerminatorKind::SwitchInt { targets: child_targets, discr: child_discr } =
|
||||
&child_terminator.kind
|
||||
|
||||
let Terminator {
|
||||
kind: TerminatorKind::SwitchInt { targets: child_targets, discr: child_discr },
|
||||
source_info,
|
||||
} = bbs[child].terminator()
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
@ -276,25 +266,115 @@ fn evaluate_candidate<'tcx>(
|
|||
if child_ty != parent_ty {
|
||||
return None;
|
||||
}
|
||||
let Some(StatementKind::Assign(boxed)) = &bbs[child].statements.first().map(|x| &x.kind) else {
|
||||
|
||||
// We only handle:
|
||||
// ```
|
||||
// bb4: {
|
||||
// _8 = discriminant((_3.1: Enum1));
|
||||
// switchInt(move _8) -> [2: bb7, otherwise: bb1];
|
||||
// }
|
||||
// ```
|
||||
// and
|
||||
// ```
|
||||
// bb2: {
|
||||
// switchInt((_3.1: u64)) -> [1: bb5, otherwise: bb1];
|
||||
// }
|
||||
// ```
|
||||
if bbs[child].statements.len() > 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// When thie BB has exactly one statement, this statement should be discriminant.
|
||||
let need_hoist_discriminant = bbs[child].statements.len() == 1;
|
||||
let child_place = if need_hoist_discriminant {
|
||||
if !bbs[targets.otherwise()].is_empty_unreachable() {
|
||||
// Someone could write code like this:
|
||||
// ```rust
|
||||
// let Q = val;
|
||||
// if discriminant(P) == otherwise {
|
||||
// let ptr = &mut Q as *mut _ as *mut u8;
|
||||
// // It may be difficult for us to effectively determine whether values are valid.
|
||||
// // Invalid values can come from all sorts of corners.
|
||||
// unsafe { *ptr = 10; }
|
||||
// }
|
||||
//
|
||||
// match P {
|
||||
// A => match Q {
|
||||
// A => {
|
||||
// // code
|
||||
// }
|
||||
// _ => {
|
||||
// // don't use Q
|
||||
// }
|
||||
// }
|
||||
// _ => {
|
||||
// // don't use Q
|
||||
// }
|
||||
// };
|
||||
// ```
|
||||
//
|
||||
// Hoisting the `discriminant(Q)` out of the `A` arm causes us to compute the discriminant of an
|
||||
// invalid value, which is UB.
|
||||
// In order to fix this, **we would either need to show that the discriminant computation of
|
||||
// `place` is computed in all branches**.
|
||||
// FIXME(#95162) For the moment, we adopt a conservative approach and
|
||||
// consider only the `otherwise` branch has no statements and an unreachable terminator.
|
||||
return None;
|
||||
}
|
||||
// Handle:
|
||||
// ```
|
||||
// bb4: {
|
||||
// _8 = discriminant((_3.1: Enum1));
|
||||
// switchInt(move _8) -> [2: bb7, otherwise: bb1];
|
||||
// }
|
||||
// ```
|
||||
let [
|
||||
Statement {
|
||||
kind: StatementKind::Assign(box (_, Rvalue::Discriminant(child_place))),
|
||||
..
|
||||
},
|
||||
] = bbs[child].statements.as_slice()
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
*child_place
|
||||
} else {
|
||||
// Handle:
|
||||
// ```
|
||||
// bb2: {
|
||||
// switchInt((_3.1: u64)) -> [1: bb5, otherwise: bb1];
|
||||
// }
|
||||
// ```
|
||||
let Operand::Copy(child_place) = child_discr else {
|
||||
return None;
|
||||
};
|
||||
*child_place
|
||||
};
|
||||
let (_, Rvalue::Discriminant(child_place)) = &**boxed else {
|
||||
return None;
|
||||
let destination = if need_hoist_discriminant || bbs[targets.otherwise()].is_empty_unreachable()
|
||||
{
|
||||
child_targets.otherwise()
|
||||
} else {
|
||||
targets.otherwise()
|
||||
};
|
||||
let destination = child_targets.otherwise();
|
||||
|
||||
// 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) {
|
||||
if !verify_candidate_branch(
|
||||
&bbs[child],
|
||||
value,
|
||||
child_place,
|
||||
destination,
|
||||
need_hoist_discriminant,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(OptimizationData {
|
||||
destination,
|
||||
child_place: *child_place,
|
||||
child_place,
|
||||
child_ty,
|
||||
child_source: child_terminator.source_info,
|
||||
child_source: *source_info,
|
||||
need_hoist_discriminant,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -303,31 +383,48 @@ fn verify_candidate_branch<'tcx>(
|
|||
value: u128,
|
||||
place: Place<'tcx>,
|
||||
destination: BasicBlock,
|
||||
need_hoist_discriminant: bool,
|
||||
) -> bool {
|
||||
// In order for the optimization to be correct, the branch must...
|
||||
// ...have exactly one statement
|
||||
if let [statement] = branch.statements.as_slice()
|
||||
// ...assign the discriminant of `place` in that statement
|
||||
&& let StatementKind::Assign(boxed) = &statement.kind
|
||||
&& let (discr_place, Rvalue::Discriminant(from_place)) = &**boxed
|
||||
&& *from_place == place
|
||||
// ...make that assignment to a local
|
||||
&& discr_place.projection.is_empty()
|
||||
// ...terminate on a `SwitchInt` that invalidates that local
|
||||
&& let TerminatorKind::SwitchInt { discr: switch_op, targets, .. } =
|
||||
&branch.terminator().kind
|
||||
&& *switch_op == Operand::Move(*discr_place)
|
||||
// ...fall through to `destination` if the switch misses
|
||||
&& destination == targets.otherwise()
|
||||
// ...have a branch for value `value`
|
||||
&& let mut iter = targets.iter()
|
||||
&& let Some((target_value, _)) = iter.next()
|
||||
&& target_value == value
|
||||
// ...and have no more branches
|
||||
&& iter.next().is_none()
|
||||
{
|
||||
true
|
||||
// In order for the optimization to be correct, the terminator must be a `SwitchInt`.
|
||||
let TerminatorKind::SwitchInt { discr: switch_op, targets } = &branch.terminator().kind else {
|
||||
return false;
|
||||
};
|
||||
if need_hoist_discriminant {
|
||||
// If we need hoist discriminant, the branch must have exactly one statement.
|
||||
let [statement] = branch.statements.as_slice() else {
|
||||
return false;
|
||||
};
|
||||
// The statement must assign the discriminant of `place`.
|
||||
let StatementKind::Assign(box (discr_place, Rvalue::Discriminant(from_place))) =
|
||||
statement.kind
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if from_place != place {
|
||||
return false;
|
||||
}
|
||||
// The assignment must invalidate a local that terminate on a `SwitchInt`.
|
||||
if !discr_place.projection.is_empty() || *switch_op != Operand::Move(discr_place) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
false
|
||||
// If we don't need hoist discriminant, the branch must not have any statements.
|
||||
if !branch.statements.is_empty() {
|
||||
return false;
|
||||
}
|
||||
// The place on `SwitchInt` must be the same.
|
||||
if *switch_op != Operand::Copy(place) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// It must fall through to `destination` if the switch misses.
|
||||
if destination != targets.otherwise() {
|
||||
return false;
|
||||
}
|
||||
// It must have exactly one branch for value `value` and have no more branches.
|
||||
let mut iter = targets.iter();
|
||||
let (Some((target_value, _)), None) = (iter.next(), iter.next()) else {
|
||||
return false;
|
||||
};
|
||||
target_value == value
|
||||
}
|
||||
|
|
|
@ -572,11 +572,13 @@ impl<'tcx> Inliner<'tcx> {
|
|||
let return_block = if let Some(block) = target {
|
||||
// Prepare a new block for code that should execute when call returns. We don't use
|
||||
// target block directly since it might have other predecessors.
|
||||
let mut data = BasicBlockData::new(Some(Terminator {
|
||||
source_info: terminator.source_info,
|
||||
kind: TerminatorKind::Goto { target: block },
|
||||
}));
|
||||
data.is_cleanup = caller_body[block].is_cleanup;
|
||||
let data = BasicBlockData::new(
|
||||
Some(Terminator {
|
||||
source_info: terminator.source_info,
|
||||
kind: TerminatorKind::Goto { target: block },
|
||||
}),
|
||||
caller_body[block].is_cleanup,
|
||||
);
|
||||
Some(caller_body.basic_blocks_mut().push(data))
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -96,7 +96,7 @@ impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
|
|||
typing_env,
|
||||
|
||||
stack: Vec::with_capacity(Self::MAX_STACK_LEN),
|
||||
last_bb: bbs.push(BasicBlockData::new(None)),
|
||||
last_bb: bbs.push(BasicBlockData::new(None, false)),
|
||||
top_cleanup_bb: match tcx.sess.panic_strategy() {
|
||||
PanicStrategy::Unwind => {
|
||||
// Don't drop input arg because it's just a pointer
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
- // MIR for `opt5` before EarlyOtherwiseBranch
|
||||
+ // MIR for `opt5` after EarlyOtherwiseBranch
|
||||
|
||||
fn opt5(_1: u32, _2: u32) -> u32 {
|
||||
debug x => _1;
|
||||
debug y => _2;
|
||||
let mut _0: u32;
|
||||
let mut _3: (u32, u32);
|
||||
let mut _4: u32;
|
||||
let mut _5: u32;
|
||||
+ let mut _6: bool;
|
||||
|
||||
bb0: {
|
||||
StorageLive(_3);
|
||||
StorageLive(_4);
|
||||
_4 = copy _1;
|
||||
StorageLive(_5);
|
||||
_5 = copy _2;
|
||||
_3 = (move _4, move _5);
|
||||
StorageDead(_5);
|
||||
StorageDead(_4);
|
||||
- switchInt(copy (_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1];
|
||||
+ StorageLive(_6);
|
||||
+ _6 = Ne(copy (_3.0: u32), copy (_3.1: u32));
|
||||
+ switchInt(move _6) -> [0: bb6, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
+ StorageDead(_6);
|
||||
_0 = const 0_u32;
|
||||
- goto -> bb8;
|
||||
+ goto -> bb5;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
- switchInt(copy (_3.1: u32)) -> [1: bb7, otherwise: bb1];
|
||||
+ _0 = const 6_u32;
|
||||
+ goto -> bb5;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
- switchInt(copy (_3.1: u32)) -> [2: bb6, otherwise: bb1];
|
||||
+ _0 = const 5_u32;
|
||||
+ goto -> bb5;
|
||||
}
|
||||
|
||||
bb4: {
|
||||
- switchInt(copy (_3.1: u32)) -> [3: bb5, otherwise: bb1];
|
||||
+ _0 = const 4_u32;
|
||||
+ goto -> bb5;
|
||||
}
|
||||
|
||||
bb5: {
|
||||
- _0 = const 6_u32;
|
||||
- goto -> bb8;
|
||||
+ StorageDead(_3);
|
||||
+ return;
|
||||
}
|
||||
|
||||
bb6: {
|
||||
- _0 = const 5_u32;
|
||||
- goto -> bb8;
|
||||
- }
|
||||
-
|
||||
- bb7: {
|
||||
- _0 = const 4_u32;
|
||||
- goto -> bb8;
|
||||
- }
|
||||
-
|
||||
- bb8: {
|
||||
- StorageDead(_3);
|
||||
- return;
|
||||
+ StorageDead(_6);
|
||||
+ switchInt(copy (_3.0: u32)) -> [1: bb4, 2: bb3, 3: bb2, otherwise: bb1];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
- // MIR for `opt5_failed` before EarlyOtherwiseBranch
|
||||
+ // MIR for `opt5_failed` after EarlyOtherwiseBranch
|
||||
|
||||
fn opt5_failed(_1: u32, _2: u32) -> u32 {
|
||||
debug x => _1;
|
||||
debug y => _2;
|
||||
let mut _0: u32;
|
||||
let mut _3: (u32, u32);
|
||||
let mut _4: u32;
|
||||
let mut _5: u32;
|
||||
|
||||
bb0: {
|
||||
StorageLive(_3);
|
||||
StorageLive(_4);
|
||||
_4 = copy _1;
|
||||
StorageLive(_5);
|
||||
_5 = copy _2;
|
||||
_3 = (move _4, move _5);
|
||||
StorageDead(_5);
|
||||
StorageDead(_4);
|
||||
switchInt(copy (_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
_0 = const 0_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
switchInt(copy (_3.1: u32)) -> [1: bb7, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb3: {
|
||||
switchInt(copy (_3.1: u32)) -> [2: bb6, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb4: {
|
||||
switchInt(copy (_3.1: u32)) -> [2: bb5, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb5: {
|
||||
_0 = const 6_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb6: {
|
||||
_0 = const 5_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb7: {
|
||||
_0 = const 4_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb8: {
|
||||
StorageDead(_3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
- // MIR for `opt5_failed_type` before EarlyOtherwiseBranch
|
||||
+ // MIR for `opt5_failed_type` after EarlyOtherwiseBranch
|
||||
|
||||
fn opt5_failed_type(_1: u32, _2: u64) -> u32 {
|
||||
debug x => _1;
|
||||
debug y => _2;
|
||||
let mut _0: u32;
|
||||
let mut _3: (u32, u64);
|
||||
let mut _4: u32;
|
||||
let mut _5: u64;
|
||||
|
||||
bb0: {
|
||||
StorageLive(_3);
|
||||
StorageLive(_4);
|
||||
_4 = copy _1;
|
||||
StorageLive(_5);
|
||||
_5 = copy _2;
|
||||
_3 = (move _4, move _5);
|
||||
StorageDead(_5);
|
||||
StorageDead(_4);
|
||||
switchInt(copy (_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
_0 = const 0_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
switchInt(copy (_3.1: u64)) -> [1: bb7, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb3: {
|
||||
switchInt(copy (_3.1: u64)) -> [2: bb6, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb4: {
|
||||
switchInt(copy (_3.1: u64)) -> [3: bb5, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb5: {
|
||||
_0 = const 6_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb6: {
|
||||
_0 = const 5_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb7: {
|
||||
_0 = const 4_u32;
|
||||
goto -> bb8;
|
||||
}
|
||||
|
||||
bb8: {
|
||||
StorageDead(_3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -78,9 +78,57 @@ fn opt4(x: Option2<u32>, y: Option2<u32>) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
// EMIT_MIR early_otherwise_branch.opt5.EarlyOtherwiseBranch.diff
|
||||
fn opt5(x: u32, y: u32) -> u32 {
|
||||
// CHECK-LABEL: fn opt5(
|
||||
// CHECK: let mut [[CMP_LOCAL:_.*]]: bool;
|
||||
// CHECK: bb0: {
|
||||
// CHECK: [[CMP_LOCAL]] = Ne(
|
||||
// CHECK: switchInt(move [[CMP_LOCAL]]) -> [
|
||||
// CHECK-NEXT: }
|
||||
match (x, y) {
|
||||
(1, 1) => 4,
|
||||
(2, 2) => 5,
|
||||
(3, 3) => 6,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// EMIT_MIR early_otherwise_branch.opt5_failed.EarlyOtherwiseBranch.diff
|
||||
fn opt5_failed(x: u32, y: u32) -> u32 {
|
||||
// CHECK-LABEL: fn opt5_failed(
|
||||
// CHECK: bb0: {
|
||||
// CHECK-NOT: Ne(
|
||||
// CHECK: switchInt(
|
||||
// CHECK-NEXT: }
|
||||
match (x, y) {
|
||||
(1, 1) => 4,
|
||||
(2, 2) => 5,
|
||||
(3, 2) => 6,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// EMIT_MIR early_otherwise_branch.opt5_failed_type.EarlyOtherwiseBranch.diff
|
||||
fn opt5_failed_type(x: u32, y: u64) -> u32 {
|
||||
// CHECK-LABEL: fn opt5_failed_type(
|
||||
// CHECK: bb0: {
|
||||
// CHECK-NOT: Ne(
|
||||
// CHECK: switchInt(
|
||||
// CHECK-NEXT: }
|
||||
match (x, y) {
|
||||
(1, 1) => 4,
|
||||
(2, 2) => 5,
|
||||
(3, 3) => 6,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
opt1(None, Some(0));
|
||||
opt2(None, Some(0));
|
||||
opt3(Option2::None, Option2::Some(false));
|
||||
opt4(Option2::None, Option2::Some(0));
|
||||
opt5(0, 0);
|
||||
opt5_failed(0, 0);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
- // MIR for `poll` before EarlyOtherwiseBranch
|
||||
+ // MIR for `poll` after EarlyOtherwiseBranch
|
||||
|
||||
fn poll(_1: Poll<Result<Option<Vec<u8>>, u8>>) -> () {
|
||||
debug val => _1;
|
||||
let mut _0: ();
|
||||
let mut _2: isize;
|
||||
let mut _3: isize;
|
||||
let mut _4: isize;
|
||||
let _5: std::vec::Vec<u8>;
|
||||
let _6: u8;
|
||||
let mut _7: bool;
|
||||
let mut _8: bool;
|
||||
let mut _9: isize;
|
||||
scope 1 {
|
||||
debug _trailers => _5;
|
||||
}
|
||||
scope 2 {
|
||||
debug _err => _6;
|
||||
}
|
||||
|
||||
bb0: {
|
||||
_7 = const false;
|
||||
_8 = const false;
|
||||
_7 = const true;
|
||||
_8 = const true;
|
||||
_4 = discriminant(_1);
|
||||
switchInt(copy _4) -> [0: bb2, 1: bb4, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
_3 = discriminant(((_1 as Ready).0: std::result::Result<std::option::Option<std::vec::Vec<u8>>, u8>));
|
||||
switchInt(copy _3) -> [0: bb3, 1: bb6, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_2 = discriminant(((((_1 as Ready).0: std::result::Result<std::option::Option<std::vec::Vec<u8>>, u8>) as Ok).0: std::option::Option<std::vec::Vec<u8>>));
|
||||
switchInt(copy _2) -> [0: bb5, 1: bb7, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb4: {
|
||||
_0 = const ();
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb5: {
|
||||
_0 = const ();
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb6: {
|
||||
StorageLive(_6);
|
||||
_6 = copy ((((_1 as Ready).0: std::result::Result<std::option::Option<std::vec::Vec<u8>>, u8>) as Err).0: u8);
|
||||
_0 = const ();
|
||||
StorageDead(_6);
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb7: {
|
||||
StorageLive(_5);
|
||||
_5 = move ((((((_1 as Ready).0: std::result::Result<std::option::Option<std::vec::Vec<u8>>, u8>) as Ok).0: std::option::Option<std::vec::Vec<u8>>) as Some).0: std::vec::Vec<u8>);
|
||||
_0 = const ();
|
||||
drop(_5) -> [return: bb8, unwind: bb20];
|
||||
}
|
||||
|
||||
bb8: {
|
||||
StorageDead(_5);
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb9 (cleanup): {
|
||||
resume;
|
||||
}
|
||||
|
||||
bb10: {
|
||||
return;
|
||||
}
|
||||
|
||||
bb11: {
|
||||
switchInt(copy _7) -> [0: bb12, otherwise: bb16];
|
||||
}
|
||||
|
||||
bb12: {
|
||||
_7 = const false;
|
||||
goto -> bb10;
|
||||
}
|
||||
|
||||
bb13: {
|
||||
switchInt(copy _8) -> [0: bb14, otherwise: bb15];
|
||||
}
|
||||
|
||||
bb14: {
|
||||
_8 = const false;
|
||||
goto -> bb12;
|
||||
}
|
||||
|
||||
bb15: {
|
||||
goto -> bb14;
|
||||
}
|
||||
|
||||
bb16: {
|
||||
_9 = discriminant(((_1 as Ready).0: std::result::Result<std::option::Option<std::vec::Vec<u8>>, u8>));
|
||||
switchInt(move _9) -> [0: bb13, otherwise: bb12];
|
||||
}
|
||||
|
||||
bb17: {
|
||||
switchInt(copy _4) -> [0: bb11, otherwise: bb10];
|
||||
}
|
||||
|
||||
bb18 (cleanup): {
|
||||
switchInt(copy _3) -> [0: bb19, otherwise: bb9];
|
||||
}
|
||||
|
||||
bb19 (cleanup): {
|
||||
goto -> bb9;
|
||||
}
|
||||
|
||||
bb20 (cleanup): {
|
||||
switchInt(copy _4) -> [0: bb18, otherwise: bb9];
|
||||
}
|
||||
}
|
||||
|
38
tests/mir-opt/early_otherwise_branch_unwind.rs
Normal file
38
tests/mir-opt/early_otherwise_branch_unwind.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
//@ test-mir-pass: EarlyOtherwiseBranch
|
||||
//@ compile-flags: -Zmir-enable-passes=+GVN,+SimplifyLocals-after-value-numbering
|
||||
//@ needs-unwind
|
||||
|
||||
use std::task::Poll;
|
||||
|
||||
// We find a matching pattern in the unwind path,
|
||||
// and we need to create a cleanup BB for this case to meet the unwind invariants rule.
|
||||
// NB: This transform is not happening currently.
|
||||
|
||||
// EMIT_MIR early_otherwise_branch_unwind.unwind.EarlyOtherwiseBranch.diff
|
||||
fn unwind<T>(val: Option<Option<Option<T>>>) {
|
||||
// CHECK-LABEL: fn unwind(
|
||||
// CHECK: drop({{.*}}) -> [return: bb{{.*}}, unwind: [[PARENT_UNWIND_BB:bb.*]]];
|
||||
// CHECK: [[PARENT_UNWIND_BB]] (cleanup): {
|
||||
// CHECK-NEXT: switchInt
|
||||
match val {
|
||||
Some(Some(Some(_v))) => {}
|
||||
Some(Some(None)) => {}
|
||||
Some(None) => {}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
// From https://github.com/rust-lang/rust/issues/130769#issuecomment-2370443086.
|
||||
// EMIT_MIR early_otherwise_branch_unwind.poll.EarlyOtherwiseBranch.diff
|
||||
pub fn poll(val: Poll<Result<Option<Vec<u8>>, u8>>) {
|
||||
// CHECK-LABEL: fn poll(
|
||||
// CHECK: drop({{.*}}) -> [return: bb{{.*}}, unwind: [[PARENT_UNWIND_BB:bb.*]]];
|
||||
// CHECK: [[PARENT_UNWIND_BB]] (cleanup): {
|
||||
// CHECK-NEXT: switchInt
|
||||
match val {
|
||||
Poll::Ready(Ok(Some(_trailers))) => {}
|
||||
Poll::Ready(Err(_err)) => {}
|
||||
Poll::Ready(Ok(None)) => {}
|
||||
Poll::Pending => {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
- // MIR for `unwind` before EarlyOtherwiseBranch
|
||||
+ // MIR for `unwind` after EarlyOtherwiseBranch
|
||||
|
||||
fn unwind(_1: Option<Option<Option<T>>>) -> () {
|
||||
debug val => _1;
|
||||
let mut _0: ();
|
||||
let mut _2: isize;
|
||||
let mut _3: isize;
|
||||
let mut _4: isize;
|
||||
let _5: T;
|
||||
let mut _6: bool;
|
||||
let mut _7: bool;
|
||||
let mut _8: isize;
|
||||
scope 1 {
|
||||
debug _v => _5;
|
||||
}
|
||||
|
||||
bb0: {
|
||||
_6 = const false;
|
||||
_7 = const false;
|
||||
_6 = const true;
|
||||
_7 = const true;
|
||||
_4 = discriminant(_1);
|
||||
switchInt(copy _4) -> [0: bb4, 1: bb2, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
_3 = discriminant(((_1 as Some).0: std::option::Option<std::option::Option<T>>));
|
||||
switchInt(copy _3) -> [0: bb5, 1: bb3, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_2 = discriminant(((((_1 as Some).0: std::option::Option<std::option::Option<T>>) as Some).0: std::option::Option<T>));
|
||||
switchInt(copy _2) -> [0: bb6, 1: bb7, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb4: {
|
||||
_0 = const ();
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb5: {
|
||||
_0 = const ();
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb6: {
|
||||
_0 = const ();
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb7: {
|
||||
StorageLive(_5);
|
||||
_5 = move ((((((_1 as Some).0: std::option::Option<std::option::Option<T>>) as Some).0: std::option::Option<T>) as Some).0: T);
|
||||
_0 = const ();
|
||||
drop(_5) -> [return: bb8, unwind: bb20];
|
||||
}
|
||||
|
||||
bb8: {
|
||||
StorageDead(_5);
|
||||
goto -> bb17;
|
||||
}
|
||||
|
||||
bb9 (cleanup): {
|
||||
resume;
|
||||
}
|
||||
|
||||
bb10: {
|
||||
return;
|
||||
}
|
||||
|
||||
bb11: {
|
||||
switchInt(copy _6) -> [0: bb12, otherwise: bb16];
|
||||
}
|
||||
|
||||
bb12: {
|
||||
_6 = const false;
|
||||
goto -> bb10;
|
||||
}
|
||||
|
||||
bb13: {
|
||||
switchInt(copy _7) -> [0: bb14, otherwise: bb15];
|
||||
}
|
||||
|
||||
bb14: {
|
||||
_7 = const false;
|
||||
goto -> bb12;
|
||||
}
|
||||
|
||||
bb15: {
|
||||
goto -> bb14;
|
||||
}
|
||||
|
||||
bb16: {
|
||||
_8 = discriminant(((_1 as Some).0: std::option::Option<std::option::Option<T>>));
|
||||
switchInt(move _8) -> [1: bb13, otherwise: bb12];
|
||||
}
|
||||
|
||||
bb17: {
|
||||
switchInt(copy _4) -> [1: bb11, otherwise: bb10];
|
||||
}
|
||||
|
||||
bb18 (cleanup): {
|
||||
switchInt(copy _3) -> [1: bb19, otherwise: bb9];
|
||||
}
|
||||
|
||||
bb19 (cleanup): {
|
||||
goto -> bb9;
|
||||
}
|
||||
|
||||
bb20 (cleanup): {
|
||||
switchInt(copy _4) -> [1: bb18, otherwise: bb9];
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue