1- use rustc_index:: bit_set:: BitSet ;
1+ use rustc_index:: bit_set:: { BitSet , ChunkedBitSet } ;
22use rustc_middle:: mir:: visit:: { MutatingUseContext , NonMutatingUseContext , PlaceContext , Visitor } ;
3- use rustc_middle:: mir:: { self , Local , Location } ;
3+ use rustc_middle:: mir:: { self , Local , LocalDecls , Location , Place , StatementKind } ;
4+ use rustc_middle:: ty:: TyCtxt ;
45
5- use crate :: { AnalysisDomain , Backward , CallReturnPlaces , GenKill , GenKillAnalysis } ;
6+ use crate :: { Analysis , AnalysisDomain , Backward , CallReturnPlaces , GenKill , GenKillAnalysis } ;
67
78/// A [live-variable dataflow analysis][liveness].
89///
@@ -98,30 +99,27 @@ where
9899 T : GenKill < Local > ,
99100{
100101 fn visit_place ( & mut self , place : & mir:: Place < ' tcx > , context : PlaceContext , location : Location ) {
101- let mir :: Place { projection , local } = * place;
102+ let local = place. local ;
102103
103104 // We purposefully do not call `super_place` here to avoid calling `visit_local` for this
104105 // place with one of the `Projection` variants of `PlaceContext`.
105106 self . visit_projection ( place. as_ref ( ) , context, location) ;
106107
107- match DefUse :: for_place ( context) {
108- // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a use.
109- Some ( _) if place. is_indirect ( ) => self . 0 . gen ( local) ,
110-
111- Some ( DefUse :: Def ) if projection. is_empty ( ) => self . 0 . kill ( local) ,
108+ match DefUse :: for_place ( * place, context) {
109+ Some ( DefUse :: Def ) => self . 0 . kill ( local) ,
112110 Some ( DefUse :: Use ) => self . 0 . gen ( local) ,
113- _ => { }
111+ None => { }
114112 }
115113 }
116114
117115 fn visit_local ( & mut self , & local: & Local , context : PlaceContext , _: Location ) {
118116 // Because we do not call `super_place` above, `visit_local` is only called for locals that
119117 // do not appear as part of a `Place` in the MIR. This handles cases like the implicit use
120118 // of the return place in a `Return` terminator or the index in an `Index` projection.
121- match DefUse :: for_place ( context) {
119+ match DefUse :: for_place ( local . into ( ) , context) {
122120 Some ( DefUse :: Def ) => self . 0 . kill ( local) ,
123121 Some ( DefUse :: Use ) => self . 0 . gen ( local) ,
124- _ => { }
122+ None => { }
125123 }
126124 }
127125}
@@ -133,27 +131,37 @@ enum DefUse {
133131}
134132
135133impl DefUse {
136- fn for_place ( context : PlaceContext ) -> Option < DefUse > {
134+ fn for_place < ' tcx > ( place : Place < ' tcx > , context : PlaceContext ) -> Option < DefUse > {
137135 match context {
138136 PlaceContext :: NonUse ( _) => None ,
139137
140138 PlaceContext :: MutatingUse ( MutatingUseContext :: Store | MutatingUseContext :: Deinit ) => {
141- Some ( DefUse :: Def )
139+ if place. is_indirect ( ) {
140+ // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
141+ // use.
142+ Some ( DefUse :: Use )
143+ } else if place. projection . is_empty ( ) {
144+ Some ( DefUse :: Def )
145+ } else {
146+ None
147+ }
142148 }
143149
144150 // Setting the discriminant is not a use because it does no reading, but it is also not
145151 // a def because it does not overwrite the whole place
146- PlaceContext :: MutatingUse ( MutatingUseContext :: SetDiscriminant ) => None ,
152+ PlaceContext :: MutatingUse ( MutatingUseContext :: SetDiscriminant ) => {
153+ place. is_indirect ( ) . then_some ( DefUse :: Use )
154+ }
147155
148- // `MutatingUseContext::Call` and `MutatingUseContext::Yield` indicate that this is the
149- // destination place for a `Call` return or `Yield` resume respectively. Since this is
150- // only a `Def` when the function returns successfully, we handle this case separately
151- // in `call_return_effect` above .
156+ // For the associated terminators, this is only a `Def` when the terminator returns
157+ // "successfully." As such, we handle this case separately in `call_return_effect`
158+ // above. However, if the place looks like `*_5`, this is still unconditionally a use of
159+ // `_5` .
152160 PlaceContext :: MutatingUse (
153161 MutatingUseContext :: Call
154- | MutatingUseContext :: AsmOutput
155- | MutatingUseContext :: Yield ,
156- ) => None ,
162+ | MutatingUseContext :: Yield
163+ | MutatingUseContext :: AsmOutput ,
164+ ) => place . is_indirect ( ) . then_some ( DefUse :: Use ) ,
157165
158166 // All other contexts are uses...
159167 PlaceContext :: MutatingUse (
@@ -179,3 +187,133 @@ impl DefUse {
179187 }
180188 }
181189}
190+
191+ /// Like `MaybeLiveLocals`, but does not mark locals as live if they are used in a dead assignment.
192+ ///
193+ /// This is basically written for dead store elimination and nothing else.
194+ ///
195+ /// All of the caveats of `MaybeLiveLocals` apply.
196+ pub struct MaybeTransitiveLiveLocals < ' a , ' tcx > {
197+ always_live : & ' a BitSet < Local > ,
198+ local_decls : & ' a LocalDecls < ' tcx > ,
199+ tcx : TyCtxt < ' tcx > ,
200+ }
201+
202+ impl < ' a , ' tcx > MaybeTransitiveLiveLocals < ' a , ' tcx > {
203+ /// The `always_alive` set is the set of locals to which all stores should unconditionally be
204+ /// considered live.
205+ ///
206+ /// This should include at least all locals that are ever borrowed.
207+ pub fn new (
208+ always_live : & ' a BitSet < Local > ,
209+ local_decls : & ' a LocalDecls < ' tcx > ,
210+ tcx : TyCtxt < ' tcx > ,
211+ ) -> Self {
212+ MaybeTransitiveLiveLocals { always_live, local_decls, tcx }
213+ }
214+ }
215+
216+ impl < ' a , ' tcx > AnalysisDomain < ' tcx > for MaybeTransitiveLiveLocals < ' a , ' tcx > {
217+ type Domain = ChunkedBitSet < Local > ;
218+ type Direction = Backward ;
219+
220+ const NAME : & ' static str = "transitive liveness" ;
221+
222+ fn bottom_value ( & self , body : & mir:: Body < ' tcx > ) -> Self :: Domain {
223+ // bottom = not live
224+ ChunkedBitSet :: new_empty ( body. local_decls . len ( ) )
225+ }
226+
227+ fn initialize_start_block ( & self , _: & mir:: Body < ' tcx > , _: & mut Self :: Domain ) {
228+ // No variables are live until we observe a use
229+ }
230+ }
231+
232+ struct TransferWrapper < ' a > ( & ' a mut ChunkedBitSet < Local > ) ;
233+
234+ impl < ' a > GenKill < Local > for TransferWrapper < ' a > {
235+ fn gen ( & mut self , l : Local ) {
236+ self . 0 . insert ( l) ;
237+ }
238+
239+ fn kill ( & mut self , l : Local ) {
240+ self . 0 . remove ( l) ;
241+ }
242+ }
243+
244+ impl < ' a , ' tcx > Analysis < ' tcx > for MaybeTransitiveLiveLocals < ' a , ' tcx > {
245+ fn apply_statement_effect (
246+ & self ,
247+ trans : & mut Self :: Domain ,
248+ statement : & mir:: Statement < ' tcx > ,
249+ location : Location ,
250+ ) {
251+ // Compute the place that we are storing to, if any
252+ let destination = match & statement. kind {
253+ StatementKind :: Assign ( assign) => {
254+ if assign. 1 . is_pointer_int_cast ( self . local_decls , self . tcx ) {
255+ // Pointer to int casts may be side-effects due to exposing the provenance.
256+ // While the model is undecided, we should be conservative. See
257+ // <https://www.ralfj.de/blog/2022/04/11/provenance-exposed.html>
258+ None
259+ } else {
260+ Some ( assign. 0 )
261+ }
262+ }
263+ StatementKind :: SetDiscriminant { place, .. } | StatementKind :: Deinit ( place) => {
264+ Some ( * * place)
265+ }
266+ StatementKind :: FakeRead ( _)
267+ | StatementKind :: StorageLive ( _)
268+ | StatementKind :: StorageDead ( _)
269+ | StatementKind :: Retag ( ..)
270+ | StatementKind :: AscribeUserType ( ..)
271+ | StatementKind :: Coverage ( ..)
272+ | StatementKind :: CopyNonOverlapping ( ..)
273+ | StatementKind :: Nop => None ,
274+ } ;
275+ if let Some ( destination) = destination {
276+ if !destination. is_indirect ( )
277+ && !trans. contains ( destination. local )
278+ && !self . always_live . contains ( destination. local )
279+ {
280+ // This store is dead
281+ return ;
282+ }
283+ }
284+ TransferFunction ( & mut TransferWrapper ( trans) ) . visit_statement ( statement, location) ;
285+ }
286+
287+ fn apply_terminator_effect (
288+ & self ,
289+ trans : & mut Self :: Domain ,
290+ terminator : & mir:: Terminator < ' tcx > ,
291+ location : Location ,
292+ ) {
293+ TransferFunction ( & mut TransferWrapper ( trans) ) . visit_terminator ( terminator, location) ;
294+ }
295+
296+ fn apply_call_return_effect (
297+ & self ,
298+ trans : & mut Self :: Domain ,
299+ _block : mir:: BasicBlock ,
300+ return_places : CallReturnPlaces < ' _ , ' tcx > ,
301+ ) {
302+ return_places. for_each ( |place| {
303+ if let Some ( local) = place. as_local ( ) {
304+ trans. remove ( local) ;
305+ }
306+ } ) ;
307+ }
308+
309+ fn apply_yield_resume_effect (
310+ & self ,
311+ trans : & mut Self :: Domain ,
312+ _resume_block : mir:: BasicBlock ,
313+ resume_place : mir:: Place < ' tcx > ,
314+ ) {
315+ if let Some ( local) = resume_place. as_local ( ) {
316+ trans. remove ( local) ;
317+ }
318+ }
319+ }
0 commit comments