1
1
#![ deny( rustc:: untranslatable_diagnostic) ]
2
2
#![ deny( rustc:: diagnostic_outside_of_impl) ]
3
3
use rustc_data_structures:: fx:: FxIndexMap ;
4
+ use rustc_data_structures:: graph:: WithSuccessors ;
4
5
use rustc_index:: bit_set:: BitSet ;
5
6
use rustc_middle:: mir:: {
6
7
self , BasicBlock , Body , CallReturnPlaces , Location , Place , TerminatorEdges ,
@@ -122,7 +123,6 @@ rustc_index::newtype_index! {
122
123
pub struct Borrows < ' a , ' tcx > {
123
124
tcx : TyCtxt < ' tcx > ,
124
125
body : & ' a Body < ' tcx > ,
125
-
126
126
borrow_set : & ' a BorrowSet < ' tcx > ,
127
127
borrows_out_of_scope_at_location : FxIndexMap < Location , Vec < BorrowIndex > > ,
128
128
}
@@ -222,6 +222,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {
222
222
}
223
223
}
224
224
225
+ // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`.
225
226
pub fn calculate_borrows_out_of_scope_at_location < ' tcx > (
226
227
body : & Body < ' tcx > ,
227
228
regioncx : & RegionInferenceContext < ' tcx > ,
@@ -238,15 +239,196 @@ pub fn calculate_borrows_out_of_scope_at_location<'tcx>(
238
239
prec. borrows_out_of_scope_at_location
239
240
}
240
241
242
+ struct PoloniusOutOfScopePrecomputer < ' a , ' tcx > {
243
+ visited : BitSet < mir:: BasicBlock > ,
244
+ visit_stack : Vec < mir:: BasicBlock > ,
245
+ body : & ' a Body < ' tcx > ,
246
+ regioncx : & ' a RegionInferenceContext < ' tcx > ,
247
+
248
+ loans_out_of_scope_at_location : FxIndexMap < Location , Vec < BorrowIndex > > ,
249
+ }
250
+
251
+ impl < ' a , ' tcx > PoloniusOutOfScopePrecomputer < ' a , ' tcx > {
252
+ fn new ( body : & ' a Body < ' tcx > , regioncx : & ' a RegionInferenceContext < ' tcx > ) -> Self {
253
+ Self {
254
+ visited : BitSet :: new_empty ( body. basic_blocks . len ( ) ) ,
255
+ visit_stack : vec ! [ ] ,
256
+ body,
257
+ regioncx,
258
+ loans_out_of_scope_at_location : FxIndexMap :: default ( ) ,
259
+ }
260
+ }
261
+ }
262
+
263
+ impl < ' tcx > PoloniusOutOfScopePrecomputer < ' _ , ' tcx > {
264
+ /// Loans are in scope while they are live: whether they are contained within any live region.
265
+ /// In the location-insensitive analysis, a loan will be contained in a region if the issuing
266
+ /// region can reach it in the subset graph. So this is a reachability problem.
267
+ fn precompute_loans_out_of_scope (
268
+ & mut self ,
269
+ loan_idx : BorrowIndex ,
270
+ issuing_region : RegionVid ,
271
+ loan_issued_at : Location ,
272
+ ) {
273
+ let sccs = self . regioncx . constraint_sccs ( ) ;
274
+ let issuing_region_scc = sccs. scc ( issuing_region) ;
275
+
276
+ // We first handle the cases where the loan doesn't go out of scope, depending on the issuing
277
+ // region's successors.
278
+ for scc in sccs. depth_first_search ( issuing_region_scc) {
279
+ // 1. Via member constraints
280
+ //
281
+ // The issuing region can flow into the choice regions, and they are either:
282
+ // - placeholders or free regions themselves,
283
+ // - or also transitively outlive a free region.
284
+ //
285
+ // That is to say, if there are member constraints here, the loan escapes the function
286
+ // and cannot go out of scope. We can early return.
287
+ if self . regioncx . scc_has_member_constraints ( scc) {
288
+ return ;
289
+ }
290
+
291
+ // 2. Via regions that are live at all points: placeholders and free regions.
292
+ //
293
+ // If the issuing region outlives such a region, its loan escapes the function and
294
+ // cannot go out of scope. We can early return.
295
+ if self . regioncx . scc_is_live_at_all_points ( scc) {
296
+ return ;
297
+ }
298
+ }
299
+
300
+ let first_block = loan_issued_at. block ;
301
+ let first_bb_data = & self . body . basic_blocks [ first_block] ;
302
+
303
+ // The first block we visit is the one where the loan is issued, starting from the statement
304
+ // where the loan is issued: at `loan_issued_at`.
305
+ let first_lo = loan_issued_at. statement_index ;
306
+ let first_hi = first_bb_data. statements . len ( ) ;
307
+
308
+ if let Some ( kill_location) =
309
+ self . loan_kill_location ( loan_idx, loan_issued_at, first_block, first_lo, first_hi)
310
+ {
311
+ debug ! ( "loan {:?} gets killed at {:?}" , loan_idx, kill_location) ;
312
+ self . loans_out_of_scope_at_location . entry ( kill_location) . or_default ( ) . push ( loan_idx) ;
313
+
314
+ // The loan dies within the first block, we're done and can early return.
315
+ return ;
316
+ }
317
+
318
+ // The loan is not dead. Add successor BBs to the work list, if necessary.
319
+ for succ_bb in first_bb_data. terminator ( ) . successors ( ) {
320
+ if self . visited . insert ( succ_bb) {
321
+ self . visit_stack . push ( succ_bb) ;
322
+ }
323
+ }
324
+
325
+ // We may end up visiting `first_block` again. This is not an issue: we know at this point
326
+ // that the loan is not killed in the `first_lo..=first_hi` range, so checking the
327
+ // `0..first_lo` range and the `0..first_hi` range gives the same result.
328
+ while let Some ( block) = self . visit_stack . pop ( ) {
329
+ let bb_data = & self . body [ block] ;
330
+ let num_stmts = bb_data. statements . len ( ) ;
331
+ if let Some ( kill_location) =
332
+ self . loan_kill_location ( loan_idx, loan_issued_at, block, 0 , num_stmts)
333
+ {
334
+ debug ! ( "loan {:?} gets killed at {:?}" , loan_idx, kill_location) ;
335
+ self . loans_out_of_scope_at_location
336
+ . entry ( kill_location)
337
+ . or_default ( )
338
+ . push ( loan_idx) ;
339
+
340
+ // The loan dies within this block, so we don't need to visit its successors.
341
+ continue ;
342
+ }
343
+
344
+ // Add successor BBs to the work list, if necessary.
345
+ for succ_bb in bb_data. terminator ( ) . successors ( ) {
346
+ if self . visited . insert ( succ_bb) {
347
+ self . visit_stack . push ( succ_bb) ;
348
+ }
349
+ }
350
+ }
351
+
352
+ self . visited . clear ( ) ;
353
+ assert ! ( self . visit_stack. is_empty( ) , "visit stack should be empty" ) ;
354
+ }
355
+
356
+ /// Returns the lowest statement in `start..=end`, where the loan goes out of scope, if any.
357
+ /// This is the statement where the issuing region can't reach any of the regions that are live
358
+ /// at this point.
359
+ fn loan_kill_location (
360
+ & self ,
361
+ loan_idx : BorrowIndex ,
362
+ loan_issued_at : Location ,
363
+ block : BasicBlock ,
364
+ start : usize ,
365
+ end : usize ,
366
+ ) -> Option < Location > {
367
+ for statement_index in start..=end {
368
+ let location = Location { block, statement_index } ;
369
+
370
+ // Check whether the issuing region can reach local regions that are live at this point:
371
+ // - a loan is always live at its issuing location because it can reach the issuing
372
+ // region, which is always live at this location.
373
+ if location == loan_issued_at {
374
+ continue ;
375
+ }
376
+
377
+ // - the loan goes out of scope at `location` if it's not contained within any regions
378
+ // live at this point.
379
+ //
380
+ // FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and `r` is
381
+ // live at point `q`, then it's guaranteed that `i` would reach `r` at point `q`.
382
+ // Reachability is location-insensitive, and we could take advantage of that, by jumping
383
+ // to a further point than just the next statement: we can jump to the furthest point
384
+ // within the block where `r` is live.
385
+ if self . regioncx . is_loan_live_at ( loan_idx, location) {
386
+ continue ;
387
+ }
388
+
389
+ // No live region is reachable from the issuing region: the loan is killed at this
390
+ // point.
391
+ return Some ( location) ;
392
+ }
393
+
394
+ None
395
+ }
396
+ }
397
+
241
398
impl < ' a , ' tcx > Borrows < ' a , ' tcx > {
242
399
pub fn new (
243
400
tcx : TyCtxt < ' tcx > ,
244
401
body : & ' a Body < ' tcx > ,
245
- nonlexical_regioncx : & ' a RegionInferenceContext < ' tcx > ,
402
+ regioncx : & ' a RegionInferenceContext < ' tcx > ,
246
403
borrow_set : & ' a BorrowSet < ' tcx > ,
247
404
) -> Self {
248
- let borrows_out_of_scope_at_location =
249
- calculate_borrows_out_of_scope_at_location ( body, nonlexical_regioncx, borrow_set) ;
405
+ let mut borrows_out_of_scope_at_location =
406
+ calculate_borrows_out_of_scope_at_location ( body, regioncx, borrow_set) ;
407
+
408
+ // The in-tree polonius analysis computes loans going out of scope using the set-of-loans
409
+ // model, and makes sure they're identical to the existing computation of the set-of-points
410
+ // model.
411
+ if tcx. sess . opts . unstable_opts . polonius . is_next_enabled ( ) {
412
+ let mut polonius_prec = PoloniusOutOfScopePrecomputer :: new ( body, regioncx) ;
413
+ for ( loan_idx, loan_data) in borrow_set. iter_enumerated ( ) {
414
+ let issuing_region = loan_data. region ;
415
+ let issued_location = loan_data. reserve_location ;
416
+
417
+ polonius_prec. precompute_loans_out_of_scope (
418
+ loan_idx,
419
+ issuing_region,
420
+ issued_location,
421
+ ) ;
422
+ }
423
+
424
+ assert_eq ! (
425
+ borrows_out_of_scope_at_location, polonius_prec. loans_out_of_scope_at_location,
426
+ "the loans out of scope must be the same as the borrows out of scope"
427
+ ) ;
428
+
429
+ borrows_out_of_scope_at_location = polonius_prec. loans_out_of_scope_at_location ;
430
+ }
431
+
250
432
Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }
251
433
}
252
434
@@ -333,6 +515,13 @@ impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
333
515
}
334
516
}
335
517
518
+ /// Forward dataflow computation of the set of borrows that are in scope at a particular location.
519
+ /// - we gen the introduced loans
520
+ /// - we kill loans on locals going out of (regular) scope
521
+ /// - we kill the loans going out of their region's NLL scope: in NLL terms, the frontier where a
522
+ /// region stops containing the CFG points reachable from the issuing location.
523
+ /// - we also kill loans of conflicting places when overwriting a shared path: e.g. borrows of
524
+ /// `a.b.c` when `a` is overwritten.
336
525
impl < ' tcx > rustc_mir_dataflow:: GenKillAnalysis < ' tcx > for Borrows < ' _ , ' tcx > {
337
526
type Idx = BorrowIndex ;
338
527
0 commit comments