@@ -9,31 +9,33 @@ use rustc_middle::mir::*;
9
9
10
10
use crate :: { Analysis , AnalysisDomain } ;
11
11
12
- /// A dataflow analysis that tracks whether an enum can hold 0, 1, or more than one variants.
12
+ /// A dataflow analysis that tracks whether an enum can hold exactly 1, or more than 1 variants.
13
+ ///
14
+ /// Specifically, if a local is constructed with a value of `Some(1)`,
15
+ /// We should be able to optimize it under the assumption that it has the `Some` variant.
16
+ /// If a local can be multiple variants, then we assume nothing.
17
+ /// This analysis returns whether or not an enum will have a specific discriminant
18
+ /// at a given time, associating that local with exactly the 1 discriminant it is.
13
19
pub struct SingleEnumVariant < ' a , ' tcx > {
14
20
tcx : TyCtxt < ' tcx > ,
15
21
body : & ' a mir:: Body < ' tcx > ,
16
22
}
17
23
18
24
impl < ' tcx > AnalysisDomain < ' tcx > for SingleEnumVariant < ' _ , ' tcx > {
19
25
/// For each local, keep track of which enum index it is, if its uninhabited, or unknown.
20
- //type Domain = FactArray<Fact, 128>;
21
26
type Domain = FactCache < Local , Location , VariantIdx , 16 > ;
22
27
23
28
const NAME : & ' static str = "single_enum_variant" ;
24
29
25
30
fn bottom_value ( & self , _: & mir:: Body < ' tcx > ) -> Self :: Domain {
26
- //FactArray { arr: [Fact::TOP; 128] }
27
31
FactCache :: new ( Local :: from_u32 ( 0 ) , Location :: START , VariantIdx :: MAX )
28
32
}
29
33
30
34
fn initialize_start_block ( & self , body : & mir:: Body < ' tcx > , state : & mut Self :: Domain ) {
31
- // assume everything is top initially.
35
+ // assume everything is TOP initially (i.e. it can be any variant) .
32
36
let local_decls = body. local_decls ( ) ;
33
37
for ( l, _) in local_decls. iter_enumerated ( ) {
34
38
state. remove ( l) ;
35
- //state.insert(l, Fact::TOP);
36
- //state[l] = Fact::TOP;
37
39
}
38
40
}
39
41
}
@@ -57,18 +59,26 @@ impl<'tcx> SingleEnumVariant<'_, 'tcx> {
57
59
if !self . is_tracked ( lhs) {
58
60
return ;
59
61
}
60
- let lhs_local = lhs. local_or_deref_local ( ) ?;
62
+ let lhs_local = lhs. as_local ( ) ?;
61
63
62
64
let new_fact = match rhs {
63
65
Operand :: Copy ( rhs) | Operand :: Move ( rhs) => {
64
- if let Some ( rhs_local) = rhs. local_or_deref_local ( ) {
66
+ if let Some ( rhs_local) = rhs. as_local ( ) {
65
67
state. get ( rhs_local) . map ( |f| f. 1 ) . copied ( )
66
68
} else {
67
- rhs. ty ( self . body , self . tcx ) . variant_index . map ( |var_idx| var_idx)
69
+ debug_assert ! (
70
+ rhs. ty( self . body, self . tcx)
71
+ . variant_index
72
+ . map( |var_idx| var_idx)
73
+ . is_none( )
74
+ ) ;
75
+ None
68
76
}
69
77
}
70
- // Assigning a constant does not affect discriminant?
71
- Operand :: Constant ( _c) => return ,
78
+ // For now, assume that assigning a constant removes known facts.
79
+ // More conservative than necessary, but a temp placeholder,
80
+ // rather than extracting an enum variant from a constant.
81
+ Operand :: Constant ( _c) => None ,
72
82
} ;
73
83
if let Some ( new_fact) = new_fact {
74
84
state. insert ( lhs_local, location, new_fact) ;
@@ -86,6 +96,9 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
86
96
statement : & Statement < ' tcx > ,
87
97
loc : Location ,
88
98
) {
99
+ // (place = location which has new information,
100
+ // fact = None if we no longer know what variant a value has
101
+ // OR fact = Some(var_idx) if we know what variant a value has).
89
102
let ( place, fact) = match & statement. kind {
90
103
StatementKind :: Deinit ( box place) => ( place, None ) ,
91
104
StatementKind :: SetDiscriminant { box place, variant_index } => {
@@ -112,7 +125,7 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
112
125
if !self . is_tracked ( place) {
113
126
return ;
114
127
}
115
- let Some ( local) = place. local_or_deref_local ( ) else { return } ;
128
+ let Some ( local) = place. as_local ( ) else { return } ;
116
129
if let Some ( fact) = fact {
117
130
state. insert ( local, loc, fact) ;
118
131
} else {
@@ -130,7 +143,7 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
130
143
self . assign ( state, place, value, loc)
131
144
}
132
145
TerminatorKind :: Drop { place, .. } if self . is_tracked ( place) => {
133
- let Some ( local) = place. local_or_deref_local ( ) else { return } ;
146
+ let Some ( local) = place. as_local ( ) else { return } ;
134
147
state. remove ( local) ;
135
148
}
136
149
_ => { }
@@ -147,18 +160,50 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
147
160
148
161
fn apply_switch_int_edge_effects (
149
162
& self ,
150
- _block : BasicBlock ,
163
+ from_block : BasicBlock ,
151
164
discr : & Operand < ' tcx > ,
152
165
apply_edge_effects : & mut impl SwitchIntEdgeEffects < Self :: Domain > ,
153
166
) {
154
- let Some ( place) = discr. place ( ) else { return } ;
155
- if !self . is_tracked ( & place) {
167
+ let Some ( switch_on) = discr. place ( ) else { return } ;
168
+
169
+ let mut src_place = None ;
170
+ let mut adt_def = None ;
171
+ for stmt in self . body [ from_block] . statements . iter ( ) . rev ( ) {
172
+ match stmt. kind {
173
+ StatementKind :: Assign ( box ( lhs, Rvalue :: Discriminant ( disc) ) )
174
+ if lhs == switch_on =>
175
+ {
176
+ match disc. ty ( self . body , self . tcx ) . ty . kind ( ) {
177
+ ty:: Adt ( adt, _) => {
178
+ src_place = Some ( disc) ;
179
+ adt_def = Some ( adt) ;
180
+ break ;
181
+ }
182
+
183
+ // `Rvalue::Discriminant` is also used to get the active yield point for a
184
+ // generator, but we do not need edge-specific effects in that case. This may
185
+ // change in the future.
186
+ ty:: Generator ( ..) => return ,
187
+
188
+ t => bug ! ( "`discriminant` called on unexpected type {:?}" , t) ,
189
+ }
190
+ }
191
+ StatementKind :: Coverage ( _) => continue ,
192
+ _ => return ,
193
+ } ;
194
+ }
195
+
196
+ let Some ( src_place) = src_place else { return } ;
197
+ let Some ( adt_def) = adt_def else { return } ;
198
+ if !self . is_tracked ( & src_place) {
156
199
return ;
157
200
}
158
- let Some ( local) = place. local_or_deref_local ( ) else { return } ;
201
+ let Some ( local) = src_place. as_local ( ) else { return } ;
202
+
159
203
apply_edge_effects. apply ( |state, target| {
160
- // This probably isn't right, need to check that it fits.
161
- let new_fact = target. value . map ( |v| VariantIdx :: from_u32 ( v as u32 ) ) ;
204
+ let new_fact = target. value . and_then ( |discr| {
205
+ adt_def. discriminants ( self . tcx ) . find ( |( _, d) | d. val == discr) . map ( |( vi, _) | vi)
206
+ } ) ;
162
207
163
208
if let Some ( new_fact) = new_fact {
164
209
let loc = Location { block : target. target , statement_index : 0 } ;
0 commit comments