1
- use rustc_index:: bit_set:: BitSet ;
1
+ use rustc_index:: bit_set:: { BitSet , ChunkedBitSet } ;
2
2
use 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 ;
4
5
5
- use crate :: { AnalysisDomain , Backward , CallReturnPlaces , GenKill , GenKillAnalysis } ;
6
+ use crate :: { Analysis , AnalysisDomain , Backward , CallReturnPlaces , GenKill , GenKillAnalysis } ;
6
7
7
8
/// A [live-variable dataflow analysis][liveness].
8
9
///
@@ -98,30 +99,27 @@ where
98
99
T : GenKill < Local > ,
99
100
{
100
101
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 ;
102
103
103
104
// We purposefully do not call `super_place` here to avoid calling `visit_local` for this
104
105
// place with one of the `Projection` variants of `PlaceContext`.
105
106
self . visit_projection ( place. as_ref ( ) , context, location) ;
106
107
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) ,
112
110
Some ( DefUse :: Use ) => self . 0 . gen ( local) ,
113
- _ => { }
111
+ None => { }
114
112
}
115
113
}
116
114
117
115
fn visit_local ( & mut self , & local: & Local , context : PlaceContext , _: Location ) {
118
116
// Because we do not call `super_place` above, `visit_local` is only called for locals that
119
117
// do not appear as part of a `Place` in the MIR. This handles cases like the implicit use
120
118
// 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) {
122
120
Some ( DefUse :: Def ) => self . 0 . kill ( local) ,
123
121
Some ( DefUse :: Use ) => self . 0 . gen ( local) ,
124
- _ => { }
122
+ None => { }
125
123
}
126
124
}
127
125
}
@@ -133,27 +131,37 @@ enum DefUse {
133
131
}
134
132
135
133
impl DefUse {
136
- fn for_place ( context : PlaceContext ) -> Option < DefUse > {
134
+ fn for_place < ' tcx > ( place : Place < ' tcx > , context : PlaceContext ) -> Option < DefUse > {
137
135
match context {
138
136
PlaceContext :: NonUse ( _) => None ,
139
137
140
138
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
+ }
142
148
}
143
149
144
150
// Setting the discriminant is not a use because it does no reading, but it is also not
145
151
// 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
+ }
147
155
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` .
152
160
PlaceContext :: MutatingUse (
153
161
MutatingUseContext :: Call
154
- | MutatingUseContext :: AsmOutput
155
- | MutatingUseContext :: Yield ,
156
- ) => None ,
162
+ | MutatingUseContext :: Yield
163
+ | MutatingUseContext :: AsmOutput ,
164
+ ) => place . is_indirect ( ) . then_some ( DefUse :: Use ) ,
157
165
158
166
// All other contexts are uses...
159
167
PlaceContext :: MutatingUse (
@@ -179,3 +187,133 @@ impl DefUse {
179
187
}
180
188
}
181
189
}
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