1
- //! A pass that eliminates branches on uninhabited enum variants.
1
+ //! A pass that eliminates branches on uninhabited or unreachable enum variants.
2
2
3
3
use crate :: MirPass ;
4
4
use rustc_data_structures:: fx:: FxHashSet ;
@@ -11,7 +11,7 @@ use rustc_middle::ty::layout::TyAndLayout;
11
11
use rustc_middle:: ty:: { Ty , TyCtxt } ;
12
12
use rustc_target:: abi:: { Abi , Variants } ;
13
13
14
- pub struct UninhabitedEnumBranching ;
14
+ pub struct UnreachableEnumBranching ;
15
15
16
16
fn get_discriminant_local ( terminator : & TerminatorKind < ' _ > ) -> Option < Local > {
17
17
if let TerminatorKind :: SwitchInt { discr : Operand :: Move ( p) , .. } = terminator {
@@ -71,13 +71,13 @@ fn variant_discriminants<'tcx>(
71
71
}
72
72
}
73
73
74
- impl < ' tcx > MirPass < ' tcx > for UninhabitedEnumBranching {
74
+ impl < ' tcx > MirPass < ' tcx > for UnreachableEnumBranching {
75
75
fn is_enabled ( & self , sess : & rustc_session:: Session ) -> bool {
76
76
sess. mir_opt_level ( ) > 0
77
77
}
78
78
79
79
fn run_pass ( & self , tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
80
- trace ! ( "UninhabitedEnumBranching starting for {:?}" , body. source) ;
80
+ trace ! ( "UnreachableEnumBranching starting for {:?}" , body. source) ;
81
81
82
82
let mut unreachable_targets = Vec :: new ( ) ;
83
83
let mut patch = MirPatch :: new ( body) ;
@@ -96,8 +96,10 @@ impl<'tcx> MirPass<'tcx> for UninhabitedEnumBranching {
96
96
) ;
97
97
98
98
let mut allowed_variants = if let Ok ( layout) = layout {
99
+ // Find allowed variants based on uninhabited.
99
100
variant_discriminants ( & layout, discriminant_ty, tcx)
100
101
} else if let Some ( variant_range) = discriminant_ty. variant_range ( tcx) {
102
+ // If there are some generics, we can still get the allowed variants.
101
103
variant_range
102
104
. map ( |variant| {
103
105
discriminant_ty. discriminant_for_variant ( tcx, variant) . unwrap ( ) . val
@@ -121,9 +123,26 @@ impl<'tcx> MirPass<'tcx> for UninhabitedEnumBranching {
121
123
}
122
124
let otherwise_is_empty_unreachable =
123
125
body. basic_blocks [ targets. otherwise ( ) ] . is_empty_unreachable ( ) ;
124
- // After resolving https://github.com/llvm/llvm-project/issues/78578,
125
- // we can remove the limit on the number of successors.
126
126
fn check_successors ( basic_blocks : & BasicBlocks < ' _ > , bb : BasicBlock ) -> bool {
127
+ // After resolving https://github.com/llvm/llvm-project/issues/78578,
128
+ // We can remove this check.
129
+ // The main issue here is that `early-tailduplication` causes compile time overhead
130
+ // and potential performance problems.
131
+ // Simply put, when encounter a switch (indirect branch) statement,
132
+ // `early-tailduplication` tries to duplicate the switch branch statement with BB
133
+ // into (each) predecessors. This makes CFG very complex.
134
+ // We can understand it as it transforms the following code
135
+ // ```rust
136
+ // match a { ... many cases };
137
+ // match b { ... many cases };
138
+ // ```
139
+ // into
140
+ // ```rust
141
+ // match a { ... many match b { goto BB cases } }
142
+ // ... BB cases
143
+ // ```
144
+ // Abandon this transformation when it is possible (the best effort)
145
+ // to encounter the problem.
127
146
let mut successors = basic_blocks[ bb] . terminator ( ) . successors ( ) ;
128
147
let Some ( first_successor) = successors. next ( ) else { return true } ;
129
148
if successors. next ( ) . is_some ( ) {
@@ -136,11 +155,32 @@ impl<'tcx> MirPass<'tcx> for UninhabitedEnumBranching {
136
155
} ;
137
156
true
138
157
}
158
+ // If and only if there is a variant that does not have a branch set,
159
+ // change the current of otherwise as the variant branch and set otherwise to unreachable.
160
+ // It transforms following code
161
+ // ```rust
162
+ // match c {
163
+ // Ordering::Less => 1,
164
+ // Ordering::Equal => 2,
165
+ // _ => 3,
166
+ // }
167
+ // ```
168
+ // to
169
+ // ```rust
170
+ // match c {
171
+ // Ordering::Less => 1,
172
+ // Ordering::Equal => 2,
173
+ // Ordering::Greater => 3,
174
+ // }
175
+ // ```
139
176
let otherwise_is_last_variant = !otherwise_is_empty_unreachable
140
177
&& allowed_variants. len ( ) == 1
141
- && check_successors ( & body. basic_blocks , targets. otherwise ( ) ) ;
178
+ // Despite the LLVM issue, we hope that small enum can still be transformed.
179
+ // This is valuable for both `a <= b` and `if let Some/Ok(v)`.
180
+ && ( targets. all_targets ( ) . len ( ) <= 3
181
+ || check_successors ( & body. basic_blocks , targets. otherwise ( ) ) ) ;
142
182
let replace_otherwise_to_unreachable = otherwise_is_last_variant
143
- || !otherwise_is_empty_unreachable && allowed_variants. is_empty ( ) ;
183
+ || ( !otherwise_is_empty_unreachable && allowed_variants. is_empty ( ) ) ;
144
184
145
185
if unreachable_targets. is_empty ( ) && !replace_otherwise_to_unreachable {
146
186
continue ;
@@ -150,6 +190,7 @@ impl<'tcx> MirPass<'tcx> for UninhabitedEnumBranching {
150
190
let mut targets = targets. clone ( ) ;
151
191
if replace_otherwise_to_unreachable {
152
192
if otherwise_is_last_variant {
193
+ // We have checked that `allowed_variants` has only one element.
153
194
#[ allow( rustc:: potential_query_instability) ]
154
195
let last_variant = * allowed_variants. iter ( ) . next ( ) . unwrap ( ) ;
155
196
targets. add_target ( last_variant, targets. otherwise ( ) ) ;
0 commit comments