@@ -38,7 +38,19 @@ impl MirPass<'_> for UnreachablePropagation {
38
38
}
39
39
}
40
40
41
+ // We do want do keep some unreachable blocks, but make them empty.
42
+ for bb in unreachable_blocks {
43
+ if !tcx.consider_optimizing(|| {
44
+ format!("UnreachablePropagation {:?} ", body.source.def_id())
45
+ }) {
46
+ break;
47
+ }
48
+
49
+ body.basic_blocks_mut()[bb].statements.clear();
50
+ }
51
+
41
52
let replaced = !replacements.is_empty();
53
+
42
54
for (bb, terminator_kind) in replacements {
43
55
if !tcx.consider_optimizing(|| {
44
56
format!("UnreachablePropagation {:?} ", body.source.def_id())
@@ -57,42 +69,55 @@ impl MirPass<'_> for UnreachablePropagation {
57
69
58
70
fn remove_successors<'tcx, F>(
59
71
terminator_kind: &TerminatorKind<'tcx>,
60
- predicate : F,
72
+ is_unreachable : F,
61
73
) -> Option<TerminatorKind<'tcx>>
62
74
where
63
75
F: Fn(BasicBlock) -> bool,
64
76
{
65
- let terminator = match *terminator_kind {
66
- TerminatorKind::Goto { target } if predicate(target) => TerminatorKind::Unreachable,
67
- TerminatorKind::SwitchInt { ref discr, switch_ty, ref targets } => {
77
+ let terminator = match terminator_kind {
78
+ // This will unconditionally run into an unreachable and is therefore unreachable as well.
79
+ TerminatorKind::Goto { target } if is_unreachable(*target) => TerminatorKind::Unreachable,
80
+ TerminatorKind::SwitchInt { targets, discr, switch_ty } => {
68
81
let otherwise = targets.otherwise();
69
82
70
- let original_targets_len = targets.iter().len() + 1;
71
- let (mut values, mut targets): (Vec<_>, Vec<_>) =
72
- targets.iter().filter(|(_, bb)| !predicate(*bb)).unzip();
83
+ // If all targets are unreachable, we can be unreachable as well.
84
+ if targets.all_targets().iter().all(|bb| is_unreachable(*bb)) {
85
+ TerminatorKind::Unreachable
86
+ } else if is_unreachable(otherwise) {
87
+ // If there are multiple targets, don't delete unreachable branches (like an unreachable otherwise)
88
+ // unless otherwise is unrachable, in which case deleting a normal branch causes it to be merged with
89
+ // the otherwise, keeping its unreachable.
90
+ // This looses information about reachability causing worse codegen.
91
+ // For example (see src/test/codegen/match-optimizes-away.rs)
92
+ //
93
+ // pub enum Two { A, B }
94
+ // pub fn identity(x: Two) -> Two {
95
+ // match x {
96
+ // Two::A => Two::A,
97
+ // Two::B => Two::B,
98
+ // }
99
+ // }
100
+ //
101
+ // This generates a `switchInt() -> [0: 0, 1: 1, otherwise: unreachable]`, which allows us or LLVM to
102
+ // turn it into just `x` later. Without the unreachable, such a transformation would be illegal.
103
+ // If the otherwise branch is unreachable, we can delete all other unreacahble targets, as they will
104
+ // still point to the unreachable and therefore not lose reachability information.
105
+ let reachable_iter = targets.iter().filter(|(_, bb)| !is_unreachable(*bb));
73
106
74
- if !predicate(otherwise) {
75
- targets.push(otherwise);
76
- } else {
77
- values.pop();
78
- }
107
+ let new_targets = SwitchTargets::new(reachable_iter, otherwise);
79
108
80
- let retained_targets_len = targets.len();
109
+ // No unreachable branches were removed.
110
+ if new_targets.all_targets().len() == targets.all_targets().len() {
111
+ return None;
112
+ }
81
113
82
- if targets.is_empty() {
83
- TerminatorKind::Unreachable
84
- } else if targets.len() == 1 {
85
- TerminatorKind::Goto { target: targets[0] }
86
- } else if original_targets_len != retained_targets_len {
87
114
TerminatorKind::SwitchInt {
88
115
discr: discr.clone(),
89
- switch_ty,
90
- targets: SwitchTargets::new(
91
- values.iter().copied().zip(targets.iter().copied()),
92
- *targets.last().unwrap(),
93
- ),
116
+ switch_ty: *switch_ty,
117
+ targets: new_targets,
94
118
}
95
119
} else {
120
+ // If the otherwise branch is reachable, we don't want to delete any unreachable branches.
96
121
return None;
97
122
}
98
123
}
0 commit comments