diff --git a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs index 0d600f0f937de..471d03229c49d 100644 --- a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs +++ b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs @@ -1,6 +1,6 @@ use rustc_middle::mir::patch::MirPatch; use rustc_middle::mir::*; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{Ty, TyCtxt}; use std::fmt::Debug; use super::simplify::simplify_cfg; @@ -11,6 +11,7 @@ use super::simplify::simplify_cfg; /// let y: Option<()>; /// match (x,y) { /// (Some(_), Some(_)) => {0}, +/// (None, None) => {2}, /// _ => {1} /// } /// ``` @@ -23,10 +24,10 @@ use super::simplify::simplify_cfg; /// if discriminant_x == discriminant_y { /// match x { /// Some(_) => 0, -/// _ => 1, // <---- -/// } // | Actually the same bb -/// } else { // | -/// 1 // <-------------- +/// None => 2, +/// } +/// } else { +/// 1 /// } /// ``` /// @@ -47,18 +48,18 @@ use super::simplify::simplify_cfg; /// | | | /// ================= | | | /// | BBU | <-| | | ============================ -/// |---------------| | \-------> | BBD | -/// |---------------| | | |--------------------------| -/// | unreachable | | | | _dl = discriminant(P) | -/// ================= | | |--------------------------| -/// | | | switchInt(_dl) | -/// ================= | | | d | ---> BBD.2 +/// |---------------| \-------> | 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 +/// Where the `otherwise` branch on `BB1` is permitted to either go to `BBU`. In the /// code: /// - `BB1` is `parent` and `BBC, BBD` are children /// - `P` is `child_place` @@ -78,7 +79,7 @@ use super::simplify::simplify_cfg; /// |---------------------| | | switchInt(Q) | /// | switchInt(_t) | | | c | ---> BBC.2 /// | false | --------/ | d | ---> BBD.2 -/// | otherwise | ---------------- | otherwise | +/// | otherwise | /--------- | otherwise | /// ======================= | ============================ /// | /// ================= | @@ -87,22 +88,18 @@ use super::simplify::simplify_cfg; /// | ... | /// ================= /// ``` -/// -/// 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 { - // unsound: https://github.com/rust-lang/rust/issues/95162 - sess.mir_opt_level() >= 3 && sess.opts.unstable_opts.unsound_mir_opts + sess.mir_opt_level() >= 2 } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { trace!("running EarlyOtherwiseBranch on {:?}", body.source); - let mut should_cleanup = false; + let mut should_apply_patch = false; + let mut patch = MirPatch::new(body); // Also consider newly generated bbs in the same pass for i in 0..body.basic_blocks.len() { @@ -116,7 +113,7 @@ impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch { trace!("SUCCESS: found optimization possibility to apply: {:?}", &opt_data); - should_cleanup = true; + should_apply_patch = true; let TerminatorKind::SwitchInt { discr: parent_op, targets: parent_targets } = &bbs[parent].terminator().kind @@ -133,175 +130,136 @@ impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch { 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, `_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)); + let (second_discriminant_temp, second_operand) = if opt_data.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); - // create assignment of discriminant - patch.add_assign( - parent_end, - Place::from(second_discriminant_temp), - Rvalue::Discriminant(opt_data.child_place), - ); - - // 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)); + patch.add_statement( + parent_end, + StatementKind::StorageLive(second_discriminant_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)))), - ); - patch.add_statement( - parent_end, - StatementKind::Assign(Box::new((Place::from(comp_temp), comp_rvalue))), - ); + // 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)) + }; 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)) + (value, targets.all_targets()[0]) }); - let eq_targets = SwitchTargets::new(eq_new_targets, opt_data.destination); + + 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, + discr: parent_op.clone(), targets: eq_targets, }, })); let eq_bb = patch.new_block(eq_switch); - // Jump to it on the basis of the inequality comparison - let true_case = opt_data.destination; - let false_case = eq_bb; - patch.patch_terminator( - parent, - TerminatorKind::if_(Operand::Move(Place::from(comp_temp)), true_case, false_case), - ); + if let Some(same_target_value) = opt_data.same_target_value { + let t = TerminatorKind::SwitchInt { + discr: second_operand, + targets: SwitchTargets::static_if( + same_target_value, + eq_bb, + opt_data.destination, + ), + }; + patch.patch_terminator(parent, t); - // 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 a StorageDead for second_discriminant_temp in each of the targets, since we moved it into + // the switch + for bb in [eq_bb, opt_data.destination].iter() { + patch.add_statement( + Location { block: *bb, statement_index: 0 }, + StatementKind::StorageDead(second_discriminant_temp), + ); + } + } + } else { + // 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)); - // 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() { + // create inequality comparison between the two discriminants + let comp_rvalue = Rvalue::BinaryOp(nequal, Box::new((parent_op, second_operand))); patch.add_statement( - Location { block: *bb, statement_index: 0 }, - StatementKind::StorageDead(comp_temp), + parent_end, + StatementKind::Assign(Box::new((Place::from(comp_temp), comp_rvalue))), ); - } - patch.apply(body); + // Jump to it on the basis of the inequality comparison + let true_case = opt_data.destination; + let false_case = eq_bb; + patch.patch_terminator( + parent, + TerminatorKind::if_( + Operand::Move(Place::from(comp_temp)), + true_case, + false_case, + ), + ); + + // 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(comp_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), + ); + } + } } // Since this optimization adds new basic blocks and invalidates others, // clean up the cfg to make it nicer for other passes - if should_cleanup { + if should_apply_patch { + patch.apply(body); simplify_cfg(body); } } } -/// 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 { - // FIXME(JakobDegen): This is unsound. Someone could write code like this: - // ```rust - // let Q = val; - // if discriminant(P) == otherwise { - // let ptr = &mut Q as *mut _ as *mut u8; - // unsafe { *ptr = 10; } // Any invalid value for the type - // } - // - // 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, including the `otherwise` branch, or we would need - // another analysis pass to determine that the place is fully initialized. It might even be best - // to have the hoisting be performed in a different pass and just do the CFG changing in this - // pass. - for (place, proj) in place.iter_projections() { - match proj { - // Dereferencing in the computation of `place` might cause issues from one of two - // categories. First, the referent 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 -} - #[derive(Debug)] struct OptimizationData<'tcx> { destination: BasicBlock, child_place: Place<'tcx>, child_ty: Ty<'tcx>, child_source: SourceInfo, + hoist_discriminant: bool, + same_target_value: Option, } fn evaluate_candidate<'tcx>( @@ -315,54 +273,135 @@ fn evaluate_candidate<'tcx>( return None; }; let parent_ty = parent_discr.ty(body.local_decls(), tcx); - 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 (_, child) = targets.iter().next()?; - let child_terminator = &bbs[child].terminator(); - let TerminatorKind::SwitchInt { targets: child_targets, discr: child_discr } = - &child_terminator.kind + let mut targets_iter = targets.iter(); + let (_, first_child) = targets_iter.next()?; + let first_child_terminator = &bbs[first_child].terminator(); + let TerminatorKind::SwitchInt { targets: first_child_targets, discr: first_child_discr } = + &first_child_terminator.kind else { return None; }; - let child_ty = child_discr.ty(body.local_decls(), tcx); - if child_ty != parent_ty { - return None; - } - let Some(StatementKind::Assign(boxed)) = &bbs[child].statements.first().map(|x| &x.kind) else { + let hoist_discriminant = if bbs[first_child].statements.len() == 1 { + 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; + // // Any invalid value for the type. It is possible to be opaque, such as in other functions. + // 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. + // So we need the `otherwise` branch has no statements and an unreachable terminator. + return None; + } + true + } else if bbs[first_child].statements.is_empty() { + false + } else { return None; }; - let (_, Rvalue::Discriminant(child_place)) = &**boxed else { - return None; + let destination = if hoist_discriminant || bbs[targets.otherwise()].is_empty_unreachable() { + first_child_targets.otherwise() + } else { + if first_child_targets.otherwise() != targets.otherwise() { + return None; + } + targets.otherwise() }; - let destination = parent_dest.unwrap_or(child_targets.otherwise()); - - // 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; + while let Some((_, child)) = targets_iter.next() { + let child_branch = &bbs[child]; + // In order for the optimization to be correct, the branch must... + // ...have exactly one or empty statement + if (hoist_discriminant && child_branch.statements.len() != 1) + || (!hoist_discriminant && !child_branch.statements.is_empty()) + { + return None; + } + // ...terminate on a `SwitchInt` that invalidates that local + let TerminatorKind::SwitchInt { targets: child_targets, .. } = + &child_branch.terminator().kind + else { + return None; + }; + if child_targets.otherwise() != destination { + return None; + } + // Make sure there are only two branches. } + let child_ty = first_child_discr.ty(body.local_decls(), tcx); + let child_place = if hoist_discriminant { + let Some(StatementKind::Assign(boxed)) = + &bbs[first_child].statements.first().map(|x| &x.kind) + else { + return None; + }; + let (_, Rvalue::Discriminant(child_place)) = &**boxed else { + return None; + }; + *child_place + } else { + let TerminatorKind::SwitchInt { discr, .. } = &bbs[first_child].terminator().kind else { + return None; + }; + let Operand::Copy(child_place) = discr else { + return None; + }; + *child_place + }; // 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) { + let Some((may_same_target_value, _)) = first_child_targets.iter().next() else { + return None; + }; + let mut same_target_value = Some(may_same_target_value); + for (_, child) in targets.iter() { + if !verify_candidate_branch( + &bbs[child], + may_same_target_value, + child_place, + hoist_discriminant, + ) { + same_target_value = None; + break; + } + } + if same_target_value.is_none() { + if child_ty != parent_ty { return None; } + for (value, child) in targets.iter() { + if !verify_candidate_branch(&bbs[child], value, child_place, hoist_discriminant) { + return None; + } + } } Some(OptimizationData { destination, - child_place: *child_place, + child_place, child_ty, - child_source: child_terminator.source_info, + child_source: first_child_terminator.source_info, + hoist_discriminant, + same_target_value, }) } @@ -370,34 +409,30 @@ fn verify_candidate_branch<'tcx>( branch: &BasicBlockData<'tcx>, value: u128, place: Place<'tcx>, - destination: BasicBlock, + hoist_discriminant: bool, ) -> 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 discriminant 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; + unreachable!() }; - if *switch_op != Operand::Move(*discr_place) { - return false; - } - // ...fall through to `destination` if the switch misses - if destination != targets.otherwise() { - return false; + if hoist_discriminant { + // ...assign the discriminant 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; + } + if *switch_op != Operand::Move(*discr_place) { + return false; + } + } else { + if *switch_op != Operand::Copy(place) { + return false; + } } // ...have a branch for value `value` let mut iter = targets.iter(); @@ -408,8 +443,8 @@ fn verify_candidate_branch<'tcx>( return false; } // ...and have no more branches - if let Some(_) = iter.next() { + if iter.next().is_some() { return false; } - return true; + true } diff --git a/tests/codegen/enum/enum-early-otherwise-branch.rs b/tests/codegen/enum/enum-early-otherwise-branch.rs new file mode 100644 index 0000000000000..07c8aed2624c1 --- /dev/null +++ b/tests/codegen/enum/enum-early-otherwise-branch.rs @@ -0,0 +1,25 @@ +//@ compile-flags: -O + +#![crate_type = "lib"] + +pub enum Enum { + A(u32), + B(u32), + C(u32), +} + +#[no_mangle] +pub fn foo(lhs: &Enum, rhs: &Enum) -> bool { + // CHECK-LABEL: define{{.*}}i1 @foo( + // CHECK-NOT: switch + // CHECK-NOT: br + // CHECK: [[SELECT:%.*]] = select + // CHECK-NEXT: ret i1 [[SELECT]] + // CHECK-NEXT: } + match (lhs, rhs) { + (Enum::A(lhs), Enum::A(rhs)) => lhs == rhs, + (Enum::B(lhs), Enum::B(rhs)) => lhs == rhs, + (Enum::C(lhs), Enum::C(rhs)) => lhs == rhs, + _ => false, + } +} diff --git a/tests/mir-opt/early_otherwise_branch.opt1.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt1.EarlyOtherwiseBranch.diff index 7a374c5675ab9..7203d39543934 100644 --- a/tests/mir-opt/early_otherwise_branch.opt1.EarlyOtherwiseBranch.diff +++ b/tests/mir-opt/early_otherwise_branch.opt1.EarlyOtherwiseBranch.diff @@ -12,8 +12,6 @@ let mut _7: isize; let _8: u32; let _9: u32; -+ let mut _10: isize; -+ let mut _11: bool; scope 1 { debug a => _8; debug b => _9; @@ -29,28 +27,20 @@ StorageDead(_5); StorageDead(_4); _7 = discriminant((_3.0: std::option::Option)); -- switchInt(move _7) -> [1: bb2, otherwise: bb1]; -+ StorageLive(_10); -+ _10 = discriminant((_3.1: std::option::Option)); -+ StorageLive(_11); -+ _11 = Ne(_7, move _10); -+ StorageDead(_10); -+ switchInt(move _11) -> [0: bb4, otherwise: bb1]; + switchInt(move _7) -> [1: bb2, otherwise: bb1]; } bb1: { -+ StorageDead(_11); _0 = const 1_u32; -- goto -> bb4; -+ goto -> bb3; + goto -> bb4; } bb2: { -- _6 = discriminant((_3.1: std::option::Option)); -- switchInt(move _6) -> [1: bb3, otherwise: bb1]; -- } -- -- bb3: { + _6 = discriminant((_3.1: std::option::Option)); + switchInt(move _6) -> [1: bb3, otherwise: bb1]; + } + + bb3: { StorageLive(_8); _8 = (((_3.0: std::option::Option) as Some).0: u32); StorageLive(_9); @@ -58,19 +48,12 @@ _0 = const 0_u32; StorageDead(_9); StorageDead(_8); -- goto -> bb4; -+ goto -> bb3; + goto -> bb4; } -- bb4: { -+ bb3: { + bb4: { StorageDead(_3); return; -+ } -+ -+ bb4: { -+ StorageDead(_11); -+ switchInt(_7) -> [1: bb2, otherwise: bb1]; } } diff --git a/tests/mir-opt/early_otherwise_branch.opt10.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt10.EarlyOtherwiseBranch.diff new file mode 100644 index 0000000000000..7e813a476563e --- /dev/null +++ b/tests/mir-opt/early_otherwise_branch.opt10.EarlyOtherwiseBranch.diff @@ -0,0 +1,90 @@ +- // MIR for `opt10` before EarlyOtherwiseBranch ++ // MIR for `opt10` after EarlyOtherwiseBranch + + fn opt10(_1: E8, _2: E16) -> u32 { + debug x => _1; + debug y => _2; + let mut _0: u32; + let mut _3: (E8, E16); + let mut _4: E8; + let mut _5: E16; + let mut _6: u16; + let mut _7: u16; + let mut _8: u16; + let mut _9: u8; ++ let mut _10: u16; + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = move _1; + StorageLive(_5); + _5 = move _2; + _3 = (move _4, move _5); + StorageDead(_5); + StorageDead(_4); + _9 = discriminant((_3.0: E8)); +- switchInt(move _9) -> [0: bb2, 1: bb3, 2: bb4, otherwise: bb9]; ++ StorageLive(_10); ++ _10 = discriminant((_3.1: E16)); ++ switchInt(move _10) -> [0: bb7, otherwise: bb1]; + } + + bb1: { ++ StorageDead(_10); + _0 = const 0_u32; +- goto -> bb8; ++ goto -> bb5; + } + + bb2: { +- _6 = discriminant((_3.1: E16)); +- switchInt(move _6) -> [0: bb5, otherwise: bb1]; ++ _0 = const 1_u32; ++ goto -> bb5; + } + + bb3: { +- _7 = discriminant((_3.1: E16)); +- switchInt(move _7) -> [0: bb6, otherwise: bb1]; ++ _0 = const 2_u32; ++ goto -> bb5; + } + + bb4: { +- _8 = discriminant((_3.1: E16)); +- switchInt(move _8) -> [0: bb7, otherwise: bb1]; ++ _0 = const 3_u32; ++ goto -> bb5; + } + + bb5: { +- _0 = const 1_u32; +- goto -> bb8; ++ StorageDead(_3); ++ return; + } + + bb6: { +- _0 = const 2_u32; +- goto -> bb8; ++ unreachable; + } + + bb7: { +- _0 = const 3_u32; +- goto -> bb8; +- } +- +- bb8: { +- StorageDead(_3); +- return; +- } +- +- bb9: { +- unreachable; ++ StorageDead(_10); ++ switchInt(_9) -> [0: bb2, 1: bb3, 2: bb4, otherwise: bb6]; + } + } + diff --git a/tests/mir-opt/early_otherwise_branch.opt2.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt2.EarlyOtherwiseBranch.diff index 95bcfe71792b1..3a9b8997b78d9 100644 --- a/tests/mir-opt/early_otherwise_branch.opt2.EarlyOtherwiseBranch.diff +++ b/tests/mir-opt/early_otherwise_branch.opt2.EarlyOtherwiseBranch.diff @@ -30,13 +30,13 @@ StorageDead(_5); StorageDead(_4); _8 = discriminant((_3.0: std::option::Option)); -- switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb1]; +- switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb7]; + StorageLive(_11); + _11 = discriminant((_3.1: std::option::Option)); + StorageLive(_12); + _12 = Ne(_8, move _11); + StorageDead(_11); -+ switchInt(move _12) -> [0: bb5, otherwise: bb1]; ++ switchInt(move _12) -> [0: bb6, otherwise: bb1]; } bb1: { @@ -70,7 +70,7 @@ - bb5: { + bb3: { - _0 = const 0_u32; + _0 = const 2_u32; - goto -> bb6; + goto -> bb4; } @@ -79,11 +79,16 @@ + bb4: { StorageDead(_3); return; + } + +- bb7: { ++ bb5: { + unreachable; + } + -+ bb5: { ++ bb6: { + StorageDead(_12); -+ switchInt(_8) -> [0: bb3, 1: bb2, otherwise: bb1]; ++ switchInt(_8) -> [0: bb3, 1: bb2, otherwise: bb5]; } } diff --git a/tests/mir-opt/early_otherwise_branch.opt3.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt3.EarlyOtherwiseBranch.diff index e058c409cb596..bffca79f248fc 100644 --- a/tests/mir-opt/early_otherwise_branch.opt3.EarlyOtherwiseBranch.diff +++ b/tests/mir-opt/early_otherwise_branch.opt3.EarlyOtherwiseBranch.diff @@ -10,13 +10,14 @@ let mut _5: std::option::Option; let mut _6: isize; let mut _7: isize; - let _8: u32; - let _9: bool; -+ let mut _10: isize; -+ let mut _11: bool; + let mut _8: isize; + let _9: u32; + let _10: bool; ++ let mut _11: isize; ++ let mut _12: bool; scope 1 { - debug a => _8; - debug b => _9; + debug a => _9; + debug b => _10; } bb0: { @@ -28,49 +29,66 @@ _3 = (move _4, move _5); StorageDead(_5); StorageDead(_4); - _7 = discriminant((_3.0: std::option::Option)); -- switchInt(move _7) -> [1: bb2, otherwise: bb1]; -+ StorageLive(_10); -+ _10 = discriminant((_3.1: std::option::Option)); + _8 = discriminant((_3.0: std::option::Option)); +- switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb7]; + StorageLive(_11); -+ _11 = Ne(_7, move _10); -+ StorageDead(_10); -+ switchInt(move _11) -> [0: bb4, otherwise: bb1]; ++ _11 = discriminant((_3.1: std::option::Option)); ++ StorageLive(_12); ++ _12 = Ne(_8, move _11); ++ StorageDead(_11); ++ switchInt(move _12) -> [0: bb6, otherwise: bb1]; } bb1: { -+ StorageDead(_11); ++ StorageDead(_12); _0 = const 1_u32; -- goto -> bb4; -+ goto -> bb3; +- goto -> bb6; ++ goto -> bb4; } bb2: { - _6 = discriminant((_3.1: std::option::Option)); -- switchInt(move _6) -> [1: bb3, otherwise: bb1]; +- switchInt(move _6) -> [0: bb5, otherwise: bb1]; - } - - bb3: { - StorageLive(_8); - _8 = (((_3.0: std::option::Option) as Some).0: u32); +- _7 = discriminant((_3.1: std::option::Option)); +- switchInt(move _7) -> [1: bb4, otherwise: bb1]; +- } +- +- bb4: { StorageLive(_9); - _9 = (((_3.1: std::option::Option) as Some).0: bool); + _9 = (((_3.0: std::option::Option) as Some).0: u32); + StorageLive(_10); + _10 = (((_3.1: std::option::Option) as Some).0: bool); _0 = const 0_u32; + StorageDead(_10); StorageDead(_9); - StorageDead(_8); -- goto -> bb4; -+ goto -> bb3; +- goto -> bb6; ++ goto -> bb4; } -- bb4: { +- bb5: { + bb3: { + _0 = const 2_u32; +- goto -> bb6; ++ goto -> bb4; + } + +- bb6: { ++ bb4: { StorageDead(_3); return; + } + +- bb7: { ++ bb5: { + unreachable; + } + -+ bb4: { -+ StorageDead(_11); -+ switchInt(_7) -> [1: bb2, otherwise: bb1]; ++ bb6: { ++ StorageDead(_12); ++ switchInt(_8) -> [0: bb3, 1: bb2, otherwise: bb5]; } } diff --git a/tests/mir-opt/early_otherwise_branch.opt4.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt4.EarlyOtherwiseBranch.diff new file mode 100644 index 0000000000000..3311151da2b62 --- /dev/null +++ b/tests/mir-opt/early_otherwise_branch.opt4.EarlyOtherwiseBranch.diff @@ -0,0 +1,77 @@ +- // MIR for `opt4` before EarlyOtherwiseBranch ++ // MIR for `opt4` after EarlyOtherwiseBranch + + fn opt4(_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 = _1; + StorageLive(_5); + _5 = _2; + _3 = (move _4, move _5); + StorageDead(_5); + StorageDead(_4); +- switchInt((_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1]; ++ StorageLive(_6); ++ _6 = Ne((_3.0: u32), (_3.1: u32)); ++ switchInt(move _6) -> [0: bb6, otherwise: bb1]; + } + + bb1: { ++ StorageDead(_6); + _0 = const 0_u32; +- goto -> bb8; ++ goto -> bb5; + } + + bb2: { +- switchInt((_3.1: u32)) -> [1: bb5, otherwise: bb1]; ++ _0 = const 4_u32; ++ goto -> bb5; + } + + bb3: { +- switchInt((_3.1: u32)) -> [2: bb6, otherwise: bb1]; ++ _0 = const 5_u32; ++ goto -> bb5; + } + + bb4: { +- switchInt((_3.1: u32)) -> [3: bb7, otherwise: bb1]; ++ _0 = const 6_u32; ++ goto -> bb5; + } + + bb5: { +- _0 = const 4_u32; +- goto -> bb8; ++ StorageDead(_3); ++ return; + } + + bb6: { +- _0 = const 5_u32; +- goto -> bb8; +- } +- +- bb7: { +- _0 = const 6_u32; +- goto -> bb8; +- } +- +- bb8: { +- StorageDead(_3); +- return; ++ StorageDead(_6); ++ switchInt((_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1]; + } + } + diff --git a/tests/mir-opt/early_otherwise_branch.opt5.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt5.EarlyOtherwiseBranch.diff new file mode 100644 index 0000000000000..0f93d284a37bd --- /dev/null +++ b/tests/mir-opt/early_otherwise_branch.opt5.EarlyOtherwiseBranch.diff @@ -0,0 +1,61 @@ +- // 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; + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; + _3 = (move _4, move _5); + StorageDead(_5); + StorageDead(_4); + switchInt((_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1]; + } + + bb1: { + _0 = const 0_u32; + goto -> bb8; + } + + bb2: { + switchInt((_3.1: u32)) -> [1: bb5, otherwise: bb1]; + } + + bb3: { + switchInt((_3.1: u32)) -> [2: bb6, otherwise: bb1]; + } + + bb4: { + switchInt((_3.1: u32)) -> [2: bb7, otherwise: bb1]; + } + + bb5: { + _0 = const 4_u32; + goto -> bb8; + } + + bb6: { + _0 = const 5_u32; + goto -> bb8; + } + + bb7: { + _0 = const 6_u32; + goto -> bb8; + } + + bb8: { + StorageDead(_3); + return; + } + } + diff --git a/tests/mir-opt/early_otherwise_branch.opt6.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt6.EarlyOtherwiseBranch.diff new file mode 100644 index 0000000000000..1e22cfb12bbe6 --- /dev/null +++ b/tests/mir-opt/early_otherwise_branch.opt6.EarlyOtherwiseBranch.diff @@ -0,0 +1,72 @@ +- // MIR for `opt6` before EarlyOtherwiseBranch ++ // MIR for `opt6` after EarlyOtherwiseBranch + + fn opt6(_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 = _1; + StorageLive(_5); + _5 = _2; + _3 = (move _4, move _5); + StorageDead(_5); + StorageDead(_4); +- switchInt((_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1]; ++ switchInt((_3.1: u32)) -> [10: bb6, otherwise: bb1]; + } + + bb1: { + _0 = const 0_u32; +- goto -> bb8; ++ goto -> bb5; + } + + bb2: { +- switchInt((_3.1: u32)) -> [10: bb5, otherwise: bb1]; ++ _0 = const 4_u32; ++ goto -> bb5; + } + + bb3: { +- switchInt((_3.1: u32)) -> [10: bb6, otherwise: bb1]; ++ _0 = const 5_u32; ++ goto -> bb5; + } + + bb4: { +- switchInt((_3.1: u32)) -> [10: bb7, otherwise: bb1]; ++ _0 = const 6_u32; ++ goto -> bb5; + } + + bb5: { +- _0 = const 4_u32; +- goto -> bb8; ++ StorageDead(_3); ++ return; + } + + bb6: { +- _0 = const 5_u32; +- goto -> bb8; +- } +- +- bb7: { +- _0 = const 6_u32; +- goto -> bb8; +- } +- +- bb8: { +- StorageDead(_3); +- return; ++ switchInt((_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1]; + } + } + diff --git a/tests/mir-opt/early_otherwise_branch.opt7.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt7.EarlyOtherwiseBranch.diff new file mode 100644 index 0000000000000..37b8a8cc173ba --- /dev/null +++ b/tests/mir-opt/early_otherwise_branch.opt7.EarlyOtherwiseBranch.diff @@ -0,0 +1,97 @@ +- // MIR for `opt7` before EarlyOtherwiseBranch ++ // MIR for `opt7` after EarlyOtherwiseBranch + + fn opt7(_1: Option, _2: Option) -> u32 { + debug x => _1; + debug y => _2; + let mut _0: u32; + let mut _3: (std::option::Option, std::option::Option); + let mut _4: std::option::Option; + let mut _5: std::option::Option; + let mut _6: isize; + let mut _7: isize; + let mut _8: isize; + let _9: u32; + let _10: u32; + let _11: u32; ++ let mut _12: isize; + scope 1 { + debug a => _9; + debug b => _10; + } + scope 2 { + debug b => _11; + } + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; + _3 = (move _4, move _5); + StorageDead(_5); + StorageDead(_4); + _8 = discriminant((_3.0: std::option::Option)); +- switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb7]; ++ StorageLive(_12); ++ _12 = discriminant((_3.1: std::option::Option)); ++ switchInt(move _12) -> [1: bb6, otherwise: bb1]; + } + + bb1: { ++ StorageDead(_12); + _0 = const 1_u32; +- goto -> bb6; ++ goto -> bb4; + } + + bb2: { +- _6 = discriminant((_3.1: std::option::Option)); +- switchInt(move _6) -> [1: bb5, otherwise: bb1]; +- } +- +- bb3: { +- _7 = discriminant((_3.1: std::option::Option)); +- switchInt(move _7) -> [1: bb4, otherwise: bb1]; +- } +- +- bb4: { + StorageLive(_9); + _9 = (((_3.0: std::option::Option) as Some).0: u32); + StorageLive(_10); + _10 = (((_3.1: std::option::Option) as Some).0: u32); + _0 = const 0_u32; + StorageDead(_10); + StorageDead(_9); +- goto -> bb6; ++ goto -> bb4; + } + +- bb5: { ++ bb3: { + StorageLive(_11); + _11 = (((_3.1: std::option::Option) as Some).0: u32); + _0 = const 2_u32; + StorageDead(_11); +- goto -> bb6; ++ goto -> bb4; + } + +- bb6: { ++ bb4: { + StorageDead(_3); + return; + } + +- bb7: { ++ bb5: { + unreachable; ++ } ++ ++ bb6: { ++ StorageDead(_12); ++ switchInt(_8) -> [0: bb3, 1: bb2, otherwise: bb5]; + } + } + diff --git a/tests/mir-opt/early_otherwise_branch.opt8.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt8.EarlyOtherwiseBranch.diff new file mode 100644 index 0000000000000..7c5482b7e10fe --- /dev/null +++ b/tests/mir-opt/early_otherwise_branch.opt8.EarlyOtherwiseBranch.diff @@ -0,0 +1,72 @@ +- // MIR for `opt8` before EarlyOtherwiseBranch ++ // MIR for `opt8` after EarlyOtherwiseBranch + + fn opt8(_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 = _1; + StorageLive(_5); + _5 = _2; + _3 = (move _4, move _5); + StorageDead(_5); + StorageDead(_4); +- switchInt((_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1]; ++ switchInt((_3.1: u64)) -> [10: bb6, otherwise: bb1]; + } + + bb1: { + _0 = const 0_u32; +- goto -> bb8; ++ goto -> bb5; + } + + bb2: { +- switchInt((_3.1: u64)) -> [10: bb5, otherwise: bb1]; ++ _0 = const 4_u32; ++ goto -> bb5; + } + + bb3: { +- switchInt((_3.1: u64)) -> [10: bb6, otherwise: bb1]; ++ _0 = const 5_u32; ++ goto -> bb5; + } + + bb4: { +- switchInt((_3.1: u64)) -> [10: bb7, otherwise: bb1]; ++ _0 = const 6_u32; ++ goto -> bb5; + } + + bb5: { +- _0 = const 4_u32; +- goto -> bb8; ++ StorageDead(_3); ++ return; + } + + bb6: { +- _0 = const 5_u32; +- goto -> bb8; +- } +- +- bb7: { +- _0 = const 6_u32; +- goto -> bb8; +- } +- +- bb8: { +- StorageDead(_3); +- return; ++ switchInt((_3.0: u32)) -> [1: bb2, 2: bb3, 3: bb4, otherwise: bb1]; + } + } + diff --git a/tests/mir-opt/early_otherwise_branch.opt9.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch.opt9.EarlyOtherwiseBranch.diff new file mode 100644 index 0000000000000..0a5a6f6897ba8 --- /dev/null +++ b/tests/mir-opt/early_otherwise_branch.opt9.EarlyOtherwiseBranch.diff @@ -0,0 +1,73 @@ +- // MIR for `opt9` before EarlyOtherwiseBranch ++ // MIR for `opt9` after EarlyOtherwiseBranch + + fn opt9(_1: E8, _2: E16) -> u32 { + debug x => _1; + debug y => _2; + let mut _0: u32; + let mut _3: (E8, E16); + let mut _4: E8; + let mut _5: E16; + let mut _6: u16; + let mut _7: u16; + let mut _8: u16; + let mut _9: u8; + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = move _1; + StorageLive(_5); + _5 = move _2; + _3 = (move _4, move _5); + StorageDead(_5); + StorageDead(_4); + _9 = discriminant((_3.0: E8)); + switchInt(move _9) -> [0: bb2, 1: bb3, 2: bb4, otherwise: bb9]; + } + + bb1: { + _0 = const 0_u32; + goto -> bb8; + } + + bb2: { + _6 = discriminant((_3.1: E16)); + switchInt(move _6) -> [0: bb5, otherwise: bb1]; + } + + bb3: { + _7 = discriminant((_3.1: E16)); + switchInt(move _7) -> [1: bb6, otherwise: bb1]; + } + + bb4: { + _8 = discriminant((_3.1: E16)); + switchInt(move _8) -> [2: bb7, otherwise: bb1]; + } + + bb5: { + _0 = const 1_u32; + goto -> bb8; + } + + bb6: { + _0 = const 2_u32; + goto -> bb8; + } + + bb7: { + _0 = const 3_u32; + goto -> bb8; + } + + bb8: { + StorageDead(_3); + return; + } + + bb9: { + unreachable; + } + } + diff --git a/tests/mir-opt/early_otherwise_branch.rs b/tests/mir-opt/early_otherwise_branch.rs index c984c271ccd53..db93c93f2aead 100644 --- a/tests/mir-opt/early_otherwise_branch.rs +++ b/tests/mir-opt/early_otherwise_branch.rs @@ -1,7 +1,16 @@ -// skip-filecheck //@ unit-test: EarlyOtherwiseBranch +//@ compile-flags: -Zmir-enable-passes=+UninhabitedEnumBranching + +// We can't optimize it because y may be an invalid value. // EMIT_MIR early_otherwise_branch.opt1.EarlyOtherwiseBranch.diff fn opt1(x: Option, y: Option) -> u32 { + // CHECK-LABEL: fn opt1( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK-NOT: Ne + // CHECK-NOT: discriminant + // CHECK: switchInt(move [[LOCAL1]]) -> [ + // CHECK-NEXT: } match (x, y) { (Some(a), Some(b)) => 0, _ => 1, @@ -10,9 +19,17 @@ fn opt1(x: Option, y: Option) -> u32 { // EMIT_MIR early_otherwise_branch.opt2.EarlyOtherwiseBranch.diff fn opt2(x: Option, y: Option) -> u32 { + // CHECK-LABEL: fn opt2( + // CHECK: let mut [[CMP_LOCAL:_.*]]: bool; + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK: [[LOCAL2:_.*]] = discriminant({{.*}}); + // CHECK: [[CMP_LOCAL]] = Ne([[LOCAL1]], move [[LOCAL2]]); + // CHECK: switchInt(move [[CMP_LOCAL]]) -> [ + // CHECK-NEXT: } match (x, y) { (Some(a), Some(b)) => 0, - (None, None) => 0, + (None, None) => 2, _ => 1, } } @@ -20,14 +37,164 @@ fn opt2(x: Option, y: Option) -> u32 { // optimize despite different types // EMIT_MIR early_otherwise_branch.opt3.EarlyOtherwiseBranch.diff fn opt3(x: Option, y: Option) -> u32 { + // CHECK-LABEL: fn opt3( + // CHECK: let mut [[CMP_LOCAL:_.*]]: bool; + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK: [[LOCAL2:_.*]] = discriminant({{.*}}); + // CHECK: [[CMP_LOCAL]] = Ne([[LOCAL1]], move [[LOCAL2]]); + // CHECK: switchInt(move [[CMP_LOCAL]]) -> [ + // CHECK-NEXT: } match (x, y) { (Some(a), Some(b)) => 0, + (None, None) => 2, _ => 1, } } +// EMIT_MIR early_otherwise_branch.opt4.EarlyOtherwiseBranch.diff +fn opt4(x: u32, y: u32) -> u32 { + // CHECK-LABEL: fn opt4( + // 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.EarlyOtherwiseBranch.diff +fn opt5(x: u32, y: u32) -> u32 { + // CHECK-LABEL: fn opt5( + // 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.opt6.EarlyOtherwiseBranch.diff +fn opt6(x: u32, y: u32) -> u32 { + // CHECK-LABEL: fn opt6( + // CHECK: bb0: { + // CHECK: switchInt((_{{.*}}: u32)) -> [10: [[SWITCH_BB:bb.*]], otherwise: [[OTHERWISE:bb.*]]]; + // CHECK-NEXT: } + // CHECK: [[SWITCH_BB]]: + // CHECK: switchInt((_{{.*}}: u32)) -> [1: bb{{.*}}, 2: bb{{.*}}, 3: bb{{.*}}, otherwise: [[OTHERWISE]]]; + // CHECK-NEXT: } + match (x, y) { + (1, 10) => 4, + (2, 10) => 5, + (3, 10) => 6, + _ => 0, + } +} + +// EMIT_MIR early_otherwise_branch.opt7.EarlyOtherwiseBranch.diff +fn opt7(x: Option, y: Option) -> u32 { + // CHECK-LABEL: fn opt7( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK: [[LOCAL2:_.*]] = discriminant({{.*}}); + // CHECK: switchInt(move [[LOCAL2]]) -> [ + // CHECK-NEXT: } + match (x, y) { + (Some(a), Some(b)) => 0, + (None, Some(b)) => 2, + _ => 1, + } +} + +// EMIT_MIR early_otherwise_branch.opt8.EarlyOtherwiseBranch.diff +fn opt8(x: u32, y: u64) -> u32 { + // CHECK-LABEL: fn opt8( + // CHECK: bb0: { + // CHECK: switchInt((_{{.*}}: u64)) -> [10: [[SWITCH_BB:bb.*]], otherwise: [[OTHERWISE:bb.*]]]; + // CHECK-NEXT: } + // CHECK: [[SWITCH_BB]]: + // CHECK: switchInt((_{{.*}}: u32)) -> [1: bb{{.*}}, 2: bb{{.*}}, 3: bb{{.*}}, otherwise: [[OTHERWISE]]]; + // CHECK-NEXT: } + match (x, y) { + (1, 10) => 4, + (2, 10) => 5, + (3, 10) => 6, + _ => 0, + } +} + +#[repr(u8)] +enum E8 { + A, + B, + C, +} + +#[repr(u16)] +enum E16 { + A, + B, + C, +} + +// Can we add a cast instruction for transformation? +// EMIT_MIR early_otherwise_branch.opt9.EarlyOtherwiseBranch.diff +fn opt9(x: E8, y: E16) -> u32 { + // CHECK-LABEL: fn opt9( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK-NOT: discriminant + // CHECK: switchInt(move [[LOCAL1]]) -> [ + // CHECK-NEXT: } + match (x, y) { + (E8::A, E16::A) => 1, + (E8::B, E16::B) => 2, + (E8::C, E16::C) => 3, + _ => 0, + } +} + +// Since the target values are the same, we can optimize. +// EMIT_MIR early_otherwise_branch.opt10.EarlyOtherwiseBranch.diff +fn opt10(x: E8, y: E16) -> u32 { + // CHECK-LABEL: fn opt10( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK: [[LOCAL2:_.*]] = discriminant({{.*}}); + // CHECK: switchInt(move [[LOCAL2]]) -> [0: [[SWITCH_BB:bb.*]], otherwise: {{bb.*}}]; + // CHECK-NEXT: } + // CHECK: [[UNREACHABLE:bb6]]: { + // CHECK-NEXT: unreachable; + // CHECK-NEXT: } + // CHECK: [[SWITCH_BB]]: + // CHECK: switchInt([[LOCAL1]]) -> [0: bb{{.*}}, 1: bb{{.*}}, 2: bb{{.*}}, otherwise: [[UNREACHABLE]]]; + // CHECK-NEXT: } + match (x, y) { + (E8::A, E16::A) => 1, + (E8::B, E16::A) => 2, + (E8::C, E16::A) => 3, + _ => 0, + } +} + fn main() { opt1(None, Some(0)); opt2(None, Some(0)); opt3(None, Some(false)); + opt4(0, 0); + opt5(0, 0); + opt6(0, 0); + opt7(None, Some(0)); + opt8(0, 0); + opt9(E8::A, E16::A); + opt10(E8::A, E16::A); } diff --git a/tests/mir-opt/early_otherwise_branch_3_element_tuple.opt1.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch_3_element_tuple.opt1.EarlyOtherwiseBranch.diff index f98d68e6ffce3..dd430a0887d7d 100644 --- a/tests/mir-opt/early_otherwise_branch_3_element_tuple.opt1.EarlyOtherwiseBranch.diff +++ b/tests/mir-opt/early_otherwise_branch_3_element_tuple.opt1.EarlyOtherwiseBranch.diff @@ -13,17 +13,17 @@ let mut _8: isize; let mut _9: isize; let mut _10: isize; - let _11: u32; - let _12: u32; + let mut _11: isize; + let mut _12: isize; let _13: u32; -+ let mut _14: isize; -+ let mut _15: bool; + let _14: u32; + let _15: u32; + let mut _16: isize; + let mut _17: bool; scope 1 { - debug a => _11; - debug b => _12; - debug c => _13; + debug a => _13; + debug b => _14; + debug c => _15; } bb0: { @@ -38,60 +38,83 @@ StorageDead(_7); StorageDead(_6); StorageDead(_5); - _10 = discriminant((_4.0: std::option::Option)); -- switchInt(move _10) -> [1: bb2, otherwise: bb1]; -+ StorageLive(_14); -+ _14 = discriminant((_4.1: std::option::Option)); -+ StorageLive(_15); -+ _15 = Ne(_10, move _14); -+ StorageDead(_14); -+ switchInt(move _15) -> [0: bb5, otherwise: bb1]; + _12 = discriminant((_4.0: std::option::Option)); +- switchInt(move _12) -> [0: bb2, 1: bb4, otherwise: bb9]; ++ StorageLive(_16); ++ _16 = discriminant((_4.1: std::option::Option)); ++ StorageLive(_17); ++ _17 = Ne(_12, move _16); ++ StorageDead(_16); ++ switchInt(move _17) -> [0: bb8, otherwise: bb1]; } bb1: { + StorageDead(_17); -+ StorageDead(_15); _0 = const 1_u32; -- goto -> bb5; -+ goto -> bb4; +- goto -> bb8; ++ goto -> bb6; } bb2: { - _9 = discriminant((_4.1: std::option::Option)); -- switchInt(move _9) -> [1: bb3, otherwise: bb1]; +- switchInt(move _9) -> [0: bb3, otherwise: bb1]; - } - - bb3: { _8 = discriminant((_4.2: std::option::Option)); -- switchInt(move _8) -> [1: bb4, otherwise: bb1]; -+ switchInt(move _8) -> [1: bb3, otherwise: bb1]; +- switchInt(move _8) -> [0: bb7, otherwise: bb1]; ++ switchInt(move _8) -> [0: bb5, otherwise: bb1]; } - bb4: { +- _11 = discriminant((_4.1: std::option::Option)); +- switchInt(move _11) -> [1: bb5, otherwise: bb1]; +- } +- +- bb5: { + bb3: { - StorageLive(_11); - _11 = (((_4.0: std::option::Option) as Some).0: u32); - StorageLive(_12); - _12 = (((_4.1: std::option::Option) as Some).0: u32); + _10 = discriminant((_4.2: std::option::Option)); +- switchInt(move _10) -> [1: bb6, otherwise: bb1]; ++ switchInt(move _10) -> [1: bb4, otherwise: bb1]; + } + +- bb6: { ++ bb4: { StorageLive(_13); - _13 = (((_4.2: std::option::Option) as Some).0: u32); + _13 = (((_4.0: std::option::Option) as Some).0: u32); + StorageLive(_14); + _14 = (((_4.1: std::option::Option) as Some).0: u32); + StorageLive(_15); + _15 = (((_4.2: std::option::Option) as Some).0: u32); _0 = const 0_u32; + StorageDead(_15); + StorageDead(_14); StorageDead(_13); - StorageDead(_12); - StorageDead(_11); -- goto -> bb5; -+ goto -> bb4; +- goto -> bb8; ++ goto -> bb6; } -- bb5: { -+ bb4: { +- bb7: { ++ bb5: { + _0 = const 0_u32; +- goto -> bb8; ++ goto -> bb6; + } + +- bb8: { ++ bb6: { StorageDead(_4); return; + } + +- bb9: { ++ bb7: { + unreachable; + } + -+ bb5: { -+ StorageDead(_15); -+ switchInt(_10) -> [1: bb2, otherwise: bb1]; ++ bb8: { ++ StorageDead(_17); ++ switchInt(_12) -> [0: bb2, 1: bb3, otherwise: bb7]; } } diff --git a/tests/mir-opt/early_otherwise_branch_3_element_tuple.rs b/tests/mir-opt/early_otherwise_branch_3_element_tuple.rs index 3208134755877..522247121bbb7 100644 --- a/tests/mir-opt/early_otherwise_branch_3_element_tuple.rs +++ b/tests/mir-opt/early_otherwise_branch_3_element_tuple.rs @@ -1,10 +1,19 @@ -// skip-filecheck //@ unit-test: EarlyOtherwiseBranch +//@ compile-flags: -Zmir-enable-passes=+UninhabitedEnumBranching // EMIT_MIR early_otherwise_branch_3_element_tuple.opt1.EarlyOtherwiseBranch.diff fn opt1(x: Option, y: Option, z: Option) -> u32 { + // CHECK-LABEL: fn opt1( + // CHECK: let mut [[CMP_LOCAL:_.*]]: bool; + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK: [[LOCAL2:_.*]] = discriminant({{.*}}); + // CHECK: [[CMP_LOCAL]] = Ne([[LOCAL1]], move [[LOCAL2]]); + // CHECK: switchInt(move [[CMP_LOCAL]]) -> [ + // CHECK-NEXT: } match (x, y, z) { (Some(a), Some(b), Some(c)) => 0, + (None, None, None) => 0, _ => 1, } } diff --git a/tests/mir-opt/early_otherwise_branch_68867.rs b/tests/mir-opt/early_otherwise_branch_68867.rs index 805d21533c5de..f99db481a566f 100644 --- a/tests/mir-opt/early_otherwise_branch_68867.rs +++ b/tests/mir-opt/early_otherwise_branch_68867.rs @@ -1,5 +1,5 @@ -// skip-filecheck //@ unit-test: EarlyOtherwiseBranch +//@ compile-flags: -Zmir-enable-passes=+UninhabitedEnumBranching // FIXME: This test was broken by the derefer change. @@ -19,6 +19,13 @@ pub extern "C" fn try_sum( x: &ViewportPercentageLength, other: &ViewportPercentageLength, ) -> Result { + // CHECK-LABEL: fn try_sum( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK-NOT: Ne + // CHECK-NOT: discriminant + // CHECK: switchInt(move [[LOCAL1]]) -> [ + // CHECK-NEXT: } use self::ViewportPercentageLength::*; Ok(match (x, other) { (&Vw(one), &Vw(other)) => Vw(one + other), diff --git a/tests/mir-opt/early_otherwise_branch_68867.try_sum.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch_68867.try_sum.EarlyOtherwiseBranch.diff index a5b5659a31a2d..de12fe8f120a7 100644 --- a/tests/mir-opt/early_otherwise_branch_68867.try_sum.EarlyOtherwiseBranch.diff +++ b/tests/mir-opt/early_otherwise_branch_68867.try_sum.EarlyOtherwiseBranch.diff @@ -78,7 +78,7 @@ StorageDead(_5); _34 = deref_copy (_4.0: &ViewportPercentageLength); _11 = discriminant((*_34)); - switchInt(move _11) -> [0: bb2, 1: bb3, 2: bb4, 3: bb5, otherwise: bb1]; + switchInt(move _11) -> [0: bb2, 1: bb3, 2: bb4, 3: bb5, otherwise: bb12]; } bb1: { @@ -213,5 +213,9 @@ bb11: { return; } + + bb12: { + unreachable; + } } diff --git a/tests/mir-opt/early_otherwise_branch_noopt.noopt1.EarlyOtherwiseBranch.diff b/tests/mir-opt/early_otherwise_branch_noopt.noopt1.EarlyOtherwiseBranch.diff index 7fdd8554e3885..63c4286b8c39f 100644 --- a/tests/mir-opt/early_otherwise_branch_noopt.noopt1.EarlyOtherwiseBranch.diff +++ b/tests/mir-opt/early_otherwise_branch_noopt.noopt1.EarlyOtherwiseBranch.diff @@ -36,7 +36,7 @@ StorageDead(_5); StorageDead(_4); _8 = discriminant((_3.0: std::option::Option)); - switchInt(move _8) -> [0: bb2, 1: bb4, otherwise: bb1]; + switchInt(move _8) -> [0: bb2, 1: bb4, otherwise: bb9]; } bb1: { @@ -45,7 +45,7 @@ bb2: { _6 = discriminant((_3.1: std::option::Option)); - switchInt(move _6) -> [0: bb3, 1: bb7, otherwise: bb1]; + switchInt(move _6) -> [0: bb3, 1: bb7, otherwise: bb9]; } bb3: { @@ -55,7 +55,7 @@ bb4: { _7 = discriminant((_3.1: std::option::Option)); - switchInt(move _7) -> [0: bb6, 1: bb5, otherwise: bb1]; + switchInt(move _7) -> [0: bb6, 1: bb5, otherwise: bb9]; } bb5: { @@ -89,5 +89,9 @@ StorageDead(_3); return; } + + bb9: { + unreachable; + } } diff --git a/tests/mir-opt/early_otherwise_branch_noopt.rs b/tests/mir-opt/early_otherwise_branch_noopt.rs index 648089e2df1d5..c421643f257bc 100644 --- a/tests/mir-opt/early_otherwise_branch_noopt.rs +++ b/tests/mir-opt/early_otherwise_branch_noopt.rs @@ -1,11 +1,18 @@ -// skip-filecheck //@ unit-test: EarlyOtherwiseBranch +//@ compile-flags: -Zmir-enable-passes=+UninhabitedEnumBranching // must not optimize as it does not follow the pattern of // left and right hand side being the same variant // EMIT_MIR early_otherwise_branch_noopt.noopt1.EarlyOtherwiseBranch.diff fn noopt1(x: Option, y: Option) -> u32 { + // CHECK-LABEL: fn noopt1( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK-NOT: Ne + // CHECK-NOT: discriminant + // CHECK: switchInt(move [[LOCAL1]]) -> [ + // CHECK-NEXT: } match (x, y) { (Some(a), Some(b)) => 0, (Some(a), None) => 1, diff --git a/tests/mir-opt/early_otherwise_branch_soundness.rs b/tests/mir-opt/early_otherwise_branch_soundness.rs index b4f5821c420cd..c5c2a8e2d601c 100644 --- a/tests/mir-opt/early_otherwise_branch_soundness.rs +++ b/tests/mir-opt/early_otherwise_branch_soundness.rs @@ -1,5 +1,5 @@ -// skip-filecheck //@ unit-test: EarlyOtherwiseBranch +//@ compile-flags: -Zmir-enable-passes=+UninhabitedEnumBranching // Tests various cases that the `early_otherwise_branch` opt should *not* optimize @@ -11,12 +11,26 @@ enum E<'a> { // EMIT_MIR early_otherwise_branch_soundness.no_downcast.EarlyOtherwiseBranch.diff fn no_downcast(e: &E) -> u32 { + // CHECK-LABEL: fn no_downcast( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK-NOT: Ne + // CHECK-NOT: discriminant + // CHECK: switchInt(move [[LOCAL1]]) -> [ + // CHECK-NEXT: } 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, b: *const Option) -> i32 { + // CHECK-LABEL: fn no_deref_ptr( + // CHECK: bb0: { + // CHECK: [[LOCAL1:_.*]] = discriminant({{.*}}); + // CHECK-NOT: Ne + // CHECK-NOT: discriminant + // CHECK: switchInt(move [[LOCAL1]]) -> [ + // CHECK-NEXT: } match a { // `*b` being correct depends on `a == Some(_)` Some(_) => match *b {