@@ -9,31 +9,33 @@ use rustc_middle::mir::*;
99
1010use crate :: { Analysis , AnalysisDomain } ;
1111
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.
1319pub struct SingleEnumVariant < ' a , ' tcx > {
1420 tcx : TyCtxt < ' tcx > ,
1521 body : & ' a mir:: Body < ' tcx > ,
1622}
1723
1824impl < ' tcx > AnalysisDomain < ' tcx > for SingleEnumVariant < ' _ , ' tcx > {
1925 /// For each local, keep track of which enum index it is, if its uninhabited, or unknown.
20- //type Domain = FactArray<Fact, 128>;
2126 type Domain = FactCache < Local , Location , VariantIdx , 16 > ;
2227
2328 const NAME : & ' static str = "single_enum_variant" ;
2429
2530 fn bottom_value ( & self , _: & mir:: Body < ' tcx > ) -> Self :: Domain {
26- //FactArray { arr: [Fact::TOP; 128] }
2731 FactCache :: new ( Local :: from_u32 ( 0 ) , Location :: START , VariantIdx :: MAX )
2832 }
2933
3034 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) .
3236 let local_decls = body. local_decls ( ) ;
3337 for ( l, _) in local_decls. iter_enumerated ( ) {
3438 state. remove ( l) ;
35- //state.insert(l, Fact::TOP);
36- //state[l] = Fact::TOP;
3739 }
3840 }
3941}
@@ -57,18 +59,26 @@ impl<'tcx> SingleEnumVariant<'_, 'tcx> {
5759 if !self . is_tracked ( lhs) {
5860 return ;
5961 }
60- let lhs_local = lhs. local_or_deref_local ( ) ?;
62+ let lhs_local = lhs. as_local ( ) ?;
6163
6264 let new_fact = match rhs {
6365 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 ( ) {
6567 state. get ( rhs_local) . map ( |f| f. 1 ) . copied ( )
6668 } 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
6876 }
6977 }
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 ,
7282 } ;
7383 if let Some ( new_fact) = new_fact {
7484 state. insert ( lhs_local, location, new_fact) ;
@@ -86,6 +96,9 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
8696 statement : & Statement < ' tcx > ,
8797 loc : Location ,
8898 ) {
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).
89102 let ( place, fact) = match & statement. kind {
90103 StatementKind :: Deinit ( box place) => ( place, None ) ,
91104 StatementKind :: SetDiscriminant { box place, variant_index } => {
@@ -112,7 +125,7 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
112125 if !self . is_tracked ( place) {
113126 return ;
114127 }
115- let Some ( local) = place. local_or_deref_local ( ) else { return } ;
128+ let Some ( local) = place. as_local ( ) else { return } ;
116129 if let Some ( fact) = fact {
117130 state. insert ( local, loc, fact) ;
118131 } else {
@@ -130,7 +143,7 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
130143 self . assign ( state, place, value, loc)
131144 }
132145 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 } ;
134147 state. remove ( local) ;
135148 }
136149 _ => { }
@@ -147,18 +160,50 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
147160
148161 fn apply_switch_int_edge_effects (
149162 & self ,
150- _block : BasicBlock ,
163+ from_block : BasicBlock ,
151164 discr : & Operand < ' tcx > ,
152165 apply_edge_effects : & mut impl SwitchIntEdgeEffects < Self :: Domain > ,
153166 ) {
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) {
156199 return ;
157200 }
158- let Some ( local) = place. local_or_deref_local ( ) else { return } ;
201+ let Some ( local) = src_place. as_local ( ) else { return } ;
202+
159203 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+ } ) ;
162207
163208 if let Some ( new_fact) = new_fact {
164209 let loc = Location { block : target. target , statement_index : 0 } ;
0 commit comments