Skip to content

UnreachableProp: Preserve unreachable branches for multiple targets #99762

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 50 additions & 26 deletions compiler/rustc_mir_transform/src/unreachable_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ pub struct UnreachablePropagation;

impl MirPass<'_> for UnreachablePropagation {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
// Enable only under -Zmir-opt-level=4 as in some cases (check the deeply-nested-opt
// perf benchmark) LLVM may spend quite a lot of time optimizing the generated code.
sess.mir_opt_level() >= 4
// Enable only under -Zmir-opt-level=2 as this can make programs less debuggable.
sess.mir_opt_level() >= 2
}

fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
Expand All @@ -38,7 +37,19 @@ impl MirPass<'_> for UnreachablePropagation {
}
}

// We do want do keep some unreachable blocks, but make them empty.
for bb in unreachable_blocks {
if !tcx.consider_optimizing(|| {
format!("UnreachablePropagation {:?} ", body.source.def_id())
}) {
break;
}

body.basic_blocks_mut()[bb].statements.clear();
}

let replaced = !replacements.is_empty();

for (bb, terminator_kind) in replacements {
if !tcx.consider_optimizing(|| {
format!("UnreachablePropagation {:?} ", body.source.def_id())
Expand All @@ -57,42 +68,55 @@ impl MirPass<'_> for UnreachablePropagation {

fn remove_successors<'tcx, F>(
terminator_kind: &TerminatorKind<'tcx>,
predicate: F,
is_unreachable: F,
) -> Option<TerminatorKind<'tcx>>
where
F: Fn(BasicBlock) -> bool,
{
let terminator = match *terminator_kind {
TerminatorKind::Goto { target } if predicate(target) => TerminatorKind::Unreachable,
TerminatorKind::SwitchInt { ref discr, switch_ty, ref targets } => {
let terminator = match terminator_kind {
// This will unconditionally run into an unreachable and is therefore unreachable as well.
TerminatorKind::Goto { target } if is_unreachable(*target) => TerminatorKind::Unreachable,
TerminatorKind::SwitchInt { targets, discr, switch_ty } => {
let otherwise = targets.otherwise();

let original_targets_len = targets.iter().len() + 1;
let (mut values, mut targets): (Vec<_>, Vec<_>) =
targets.iter().filter(|(_, bb)| !predicate(*bb)).unzip();
// If all targets are unreachable, we can be unreachable as well.
if targets.all_targets().iter().all(|bb| is_unreachable(*bb)) {
TerminatorKind::Unreachable
} else if is_unreachable(otherwise) {
// If there are multiple targets, don't delete unreachable branches (like an unreachable otherwise)
// unless otherwise is unrachable, in which case deleting a normal branch causes it to be merged with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*unreachable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// the otherwise, keeping its unreachable.
// This looses information about reachability causing worse codegen.
// For example (see src/test/codegen/match-optimizes-away.rs)
//
// pub enum Two { A, B }
// pub fn identity(x: Two) -> Two {
// match x {
// Two::A => Two::A,
// Two::B => Two::B,
// }
// }
//
// This generates a `switchInt() -> [0: 0, 1: 1, otherwise: unreachable]`, which allows us or LLVM to
// turn it into just `x` later. Without the unreachable, such a transformation would be illegal.
Copy link
Contributor

@oli-obk oli-obk Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is only true because we don't have enough range information available, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also: please add a codegen test showing this opt)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/test/codegen/match-optimizes-away.rs is the test for this, and found this issue in the opt originally. And this doesn't necessarily have to do with range information only, it could also be an unreachable_unchecked the user wrote themselves to enable another optimization. Deleting unreachable branches like this just isn't a good idea because it loses information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is only true because we don't have enough range information available, right?

Potentially, but IMO it's a bad idea to turn this:

match x {
    Enum::A => 1,
    Enum::B => 2,
    Enum::C => 3,
    
    // Not in the user's code, but added for switchInt's "otherwise" target:
    // _ => unreachable_unchecked(),
}

into:

match x {
    Enum::A => 1,
    Enum::B => 2,
    _ => 3,
}

It's erasing the refinement that the third case is only reachable when x is Enum::C.
You can recompute this refinement with an analysis, but why erase it in the first place?

See also #99762 (comment) where I suggest a solution (make otherwise optional).

// If the otherwise branch is unreachable, we can delete all other unreacahble targets, as they will
// still point to the unreachable and therefore not lose reachability information.
let reachable_iter = targets.iter().filter(|(_, bb)| !is_unreachable(*bb));

if !predicate(otherwise) {
targets.push(otherwise);
} else {
values.pop();
}
let new_targets = SwitchTargets::new(reachable_iter, otherwise);

let retained_targets_len = targets.len();
// No unreachable branches were removed.
if new_targets.all_targets().len() == targets.all_targets().len() {
return None;
}

if targets.is_empty() {
TerminatorKind::Unreachable
} else if targets.len() == 1 {
TerminatorKind::Goto { target: targets[0] }
} else if original_targets_len != retained_targets_len {
TerminatorKind::SwitchInt {
discr: discr.clone(),
switch_ty,
targets: SwitchTargets::new(
values.iter().copied().zip(targets.iter().copied()),
*targets.last().unwrap(),
),
switch_ty: *switch_ty,
targets: new_targets,
}
} else {
// If the otherwise branch is reachable, we don't want to delete any unreachable branches.
return None;
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/test/mir-opt/issue_73223.main.SimplifyArmIdentity.32bit.diff
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
((_2 as Some).0: i32) = const 1_i32; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
discriminant(_2) = 1; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
_3 = const 1_isize; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
goto -> bb2; // scope 0 at $DIR/issue-73223.rs:+1:17: +1:30
goto -> bb3; // scope 0 at $DIR/issue-73223.rs:+1:17: +1:30
}

bb1: {
Expand All @@ -66,6 +66,10 @@
}

bb2: {
unreachable; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
}

bb3: {
StorageLive(_4); // scope 0 at $DIR/issue-73223.rs:+2:14: +2:15
_4 = ((_2 as Some).0: i32); // scope 0 at $DIR/issue-73223.rs:+2:14: +2:15
_1 = _4; // scope 2 at $DIR/issue-73223.rs:+2:20: +2:21
Expand Down Expand Up @@ -108,10 +112,10 @@
StorageDead(_17); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
_15 = Not(move _16); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
StorageDead(_16); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
switchInt(move _15) -> [false: bb4, otherwise: bb3]; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
switchInt(move _15) -> [false: bb5, otherwise: bb4]; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
}

bb3: {
bb4: {
StorageLive(_20); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
Deinit(_20); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
discriminant(_20) = 0; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
Expand Down Expand Up @@ -141,7 +145,7 @@
// + literal: Const { ty: core::panicking::AssertKind, val: Value(Scalar(0x00)) }
}

bb4: {
bb5: {
nop; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
StorageDead(_15); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
StorageDead(_14); // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
Expand Down
12 changes: 8 additions & 4 deletions src/test/mir-opt/issue_73223.main.SimplifyArmIdentity.64bit.diff
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
((_2 as Some).0: i32) = const 1_i32; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
discriminant(_2) = 1; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
_3 = const 1_isize; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
goto -> bb2; // scope 0 at $DIR/issue-73223.rs:+1:17: +1:30
goto -> bb3; // scope 0 at $DIR/issue-73223.rs:+1:17: +1:30
}

bb1: {
Expand All @@ -66,6 +66,10 @@
}

bb2: {
unreachable; // scope 0 at $DIR/issue-73223.rs:+1:23: +1:30
}

bb3: {
StorageLive(_4); // scope 0 at $DIR/issue-73223.rs:+2:14: +2:15
_4 = ((_2 as Some).0: i32); // scope 0 at $DIR/issue-73223.rs:+2:14: +2:15
_1 = _4; // scope 2 at $DIR/issue-73223.rs:+2:20: +2:21
Expand Down Expand Up @@ -108,10 +112,10 @@
StorageDead(_17); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
_15 = Not(move _16); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
StorageDead(_16); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
switchInt(move _15) -> [false: bb4, otherwise: bb3]; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
switchInt(move _15) -> [false: bb5, otherwise: bb4]; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
}

bb3: {
bb4: {
StorageLive(_20); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
Deinit(_20); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
discriminant(_20) = 0; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
Expand Down Expand Up @@ -141,7 +145,7 @@
// + literal: Const { ty: core::panicking::AssertKind, val: Value(Scalar(0x00)) }
}

bb4: {
bb5: {
nop; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
StorageDead(_15); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
StorageDead(_14); // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@

bb0: {
_2 = discriminant(_1); // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
switchInt(move _2) -> [0_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
switchInt(move _2) -> [0_isize: bb3, 1_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
}

bb1: {
_0 = const 1_u8; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
}

bb2: {
_0 = const 0_u8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
unreachable; // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
}

bb3: {
_0 = const 0_u8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
}

bb4: {
return; // scope 0 at $DIR/matches_u8.rs:+5:2: +5:2
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@

bb0: {
_2 = discriminant(_1); // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
switchInt(move _2) -> [0_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
switchInt(move _2) -> [0_isize: bb3, 1_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
}

bb1: {
_0 = const 1_u8; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
}

bb2: {
_0 = const 0_u8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
unreachable; // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
}

bb3: {
_0 = const 0_u8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
}

bb4: {
return; // scope 0 at $DIR/matches_u8.rs:+5:2: +5:2
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@

bb0: {
_2 = discriminant(_1); // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
switchInt(move _2) -> [0_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
switchInt(move _2) -> [0_isize: bb3, 1_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
}

bb1: {
_0 = const 1_i8; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
}

bb2: {
_0 = const 0_i8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
unreachable; // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
}

bb3: {
_0 = const 0_i8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
}

bb4: {
return; // scope 0 at $DIR/matches_u8.rs:+5:2: +5:2
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@

bb0: {
_2 = discriminant(_1); // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
switchInt(move _2) -> [0_isize: bb2, otherwise: bb1]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
switchInt(move _2) -> [0_isize: bb3, 1_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/matches_u8.rs:+1:5: +1:12
}

bb1: {
_0 = const 1_i8; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+3:17: +3:18
}

bb2: {
_0 = const 0_i8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb3; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
unreachable; // scope 0 at $DIR/matches_u8.rs:+1:11: +1:12
}

bb3: {
_0 = const 0_i8; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
goto -> bb4; // scope 0 at $DIR/matches_u8.rs:+2:17: +2:18
}

bb4: {
return; // scope 0 at $DIR/matches_u8.rs:+5:2: +5:2
}
}
Expand Down
20 changes: 12 additions & 8 deletions src/test/mir-opt/separate_const_switch.identity.ConstProp.diff
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
_4 = _1; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:9
StorageLive(_10); // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
_10 = discriminant(_4); // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
switchInt(move _10) -> [0_isize: bb5, 1_isize: bb3, otherwise: bb4]; // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
switchInt(move _10) -> [0_isize: bb6, 1_isize: bb4, otherwise: bb5]; // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
}

bb1: {
Expand All @@ -74,6 +74,10 @@
}

bb2: {
unreachable; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
}

bb3: {
StorageLive(_6); // scope 0 at $DIR/separate_const_switch.rs:+1:9: +1:10
_6 = ((_3 as Break).0: std::result::Result<std::convert::Infallible, i32>); // scope 0 at $DIR/separate_const_switch.rs:+1:9: +1:10
StorageLive(_8); // scope 2 at $DIR/separate_const_switch.rs:+1:9: +1:10
Expand All @@ -97,7 +101,7 @@
return; // scope 0 at $DIR/separate_const_switch.rs:+2:2: +2:2
}

bb3: {
bb4: {
StorageLive(_13); // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
_13 = move ((_4 as Err).0: i32); // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
StorageLive(_14); // scope 7 at $SRC_DIR/core/src/result.rs:LL:COL
Expand All @@ -115,16 +119,16 @@
StorageDead(_10); // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
StorageDead(_4); // scope 0 at $DIR/separate_const_switch.rs:+1:9: +1:10
- _5 = discriminant(_3); // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
- switchInt(move _5) -> [0_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
- switchInt(move _5) -> [0_isize: bb1, 1_isize: bb3, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
+ _5 = const 1_isize; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
+ switchInt(const 1_isize) -> [0_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
+ switchInt(const 1_isize) -> [0_isize: bb1, 1_isize: bb3, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
}

bb4: {
bb5: {
unreachable; // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
}

bb5: {
bb6: {
StorageLive(_11); // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
_11 = move ((_4 as Ok).0: i32); // scope 5 at $SRC_DIR/core/src/result.rs:LL:COL
StorageLive(_12); // scope 6 at $SRC_DIR/core/src/result.rs:LL:COL
Expand All @@ -137,9 +141,9 @@
StorageDead(_10); // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
StorageDead(_4); // scope 0 at $DIR/separate_const_switch.rs:+1:9: +1:10
- _5 = discriminant(_3); // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
- switchInt(move _5) -> [0_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
- switchInt(move _5) -> [0_isize: bb1, 1_isize: bb3, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
+ _5 = const 0_isize; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
+ switchInt(const 0_isize) -> [0_isize: bb1, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
+ switchInt(const 0_isize) -> [0_isize: bb1, 1_isize: bb3, otherwise: bb2]; // scope 0 at $DIR/separate_const_switch.rs:+1:8: +1:10
}
}

Loading