@@ -238,15 +238,231 @@ pub fn calculate_borrows_out_of_scope_at_location<'tcx>(
238
238
prec. borrows_out_of_scope_at_location
239
239
}
240
240
241
+ struct StackEntry {
242
+ bb : mir:: BasicBlock ,
243
+ lo : usize ,
244
+ hi : usize ,
245
+ }
246
+
247
+ struct PoloniusOutOfScopePrecomputer < ' a , ' tcx > {
248
+ visited : BitSet < mir:: BasicBlock > ,
249
+ visit_stack : Vec < StackEntry > ,
250
+ body : & ' a Body < ' tcx > ,
251
+ regioncx : & ' a RegionInferenceContext < ' tcx > ,
252
+ loans_out_of_scope_at_location : FxIndexMap < Location , Vec < BorrowIndex > > ,
253
+ placeholders : Vec < RegionVid > ,
254
+ }
255
+
256
+ impl < ' a , ' tcx > PoloniusOutOfScopePrecomputer < ' a , ' tcx > {
257
+ fn new ( body : & ' a Body < ' tcx > , regioncx : & ' a RegionInferenceContext < ' tcx > ) -> Self {
258
+ // Compute the placeholder list once, as it will be used for all the loan scopes we'll
259
+ // compute.
260
+ // FIXME: they're surely already available somewhere.
261
+ let placeholders = regioncx
262
+ . regions ( )
263
+ . filter ( |& r| {
264
+ use rustc_infer:: infer:: * ;
265
+ let origin = regioncx. var_infos [ r] . origin ;
266
+ let is_placeholder = matches ! (
267
+ origin,
268
+ RegionVariableOrigin :: Nll ( NllRegionVariableOrigin :: Placeholder ( _) )
269
+ ) ;
270
+ is_placeholder
271
+ } )
272
+ . collect ( ) ;
273
+
274
+ Self {
275
+ visited : BitSet :: new_empty ( body. basic_blocks . len ( ) ) ,
276
+ visit_stack : vec ! [ ] ,
277
+ body,
278
+ regioncx,
279
+ loans_out_of_scope_at_location : FxIndexMap :: default ( ) ,
280
+ placeholders,
281
+ }
282
+ }
283
+ }
284
+
285
+ impl < ' tcx > PoloniusOutOfScopePrecomputer < ' _ , ' tcx > {
286
+ /// Loans are in scope while they are live: whether they are contained within any live region.
287
+ /// In the location-insensitive analysis, a loan will be contained in a region if the issuing
288
+ /// region can reach it in the subset graph. So this is a reachability problem.
289
+ fn precompute_loans_out_of_scope (
290
+ & mut self ,
291
+ loan_idx : BorrowIndex ,
292
+ issuing_region : RegionVid ,
293
+ issued_location : Location ,
294
+ ) {
295
+ // Let's precompute the reachability set of the issuing region, via reachability on the
296
+ // condensation graph. We can also early return when reaching regions that outlive free
297
+ // regions via member constraints. (The `OutOfScopePrecomputer` wouldn't be called on a
298
+ // region that outlives free regions via outlives constraints.)
299
+
300
+ let liveness = & self . regioncx . liveness_constraints ;
301
+ let sccs = & self . regioncx . constraint_sccs ;
302
+
303
+ let mut reachability = BitSet :: new_empty ( sccs. num_sccs ( ) ) ;
304
+
305
+ let issuing_region_scc = sccs. scc ( issuing_region) ;
306
+ let mut stack = vec ! [ issuing_region_scc] ;
307
+ reachability. insert ( issuing_region_scc) ;
308
+
309
+ let member_constraints = & self . regioncx . member_constraints ;
310
+
311
+ while let Some ( scc) = stack. pop ( ) {
312
+ // Handle successors of this SCC:
313
+ //
314
+ // 1. Push outlives successors to the worklist stack
315
+ for & succ_scc in sccs. successors ( scc) {
316
+ if reachability. insert ( succ_scc) {
317
+ stack. push ( succ_scc) ;
318
+ }
319
+ }
320
+
321
+ // 2. Deal with member constraints
322
+ //
323
+ // The issuing region can flow into the choice regions here, and they are either:
324
+ // - placeholders or free regions themselves,
325
+ // - or also transitively outlive a free region.
326
+ //
327
+ // That is to say, if there are member constraints here, the loan escapes the
328
+ // function and cannot go out of scope. We can early return.
329
+ if member_constraints. indices ( scc) . next ( ) . is_some ( ) {
330
+ return ;
331
+ }
332
+ }
333
+
334
+ // We visit one BB at a time. The complication is that we may start in the
335
+ // middle of the first BB visited (the one containing `location`), in which
336
+ // case we may have to later on process the first part of that BB if there
337
+ // is a path back to its start.
338
+
339
+ // For visited BBs, we record the index of the first statement processed.
340
+ // (In fully processed BBs this index is 0.) Note also that we add BBs to
341
+ // `visited` once they are added to `stack`, before they are actually
342
+ // processed, because this avoids the need to look them up again on
343
+ // completion.
344
+ self . visited . insert ( issued_location. block ) ;
345
+
346
+ let mut first_lo = issued_location. statement_index ;
347
+ let first_hi = self . body [ issued_location. block ] . statements . len ( ) ;
348
+
349
+ self . visit_stack . push ( StackEntry { bb : issued_location. block , lo : first_lo, hi : first_hi } ) ;
350
+
351
+ while let Some ( StackEntry { bb, lo, hi } ) = self . visit_stack . pop ( ) {
352
+ // If we process the first part of the first basic block (i.e. we encounter that block
353
+ // for the second time), we no longer have to visit its successors again.
354
+ let mut finished_early = bb == issued_location. block && hi != first_hi;
355
+ for i in lo..=hi {
356
+ let location = Location { block : bb, statement_index : i } ;
357
+
358
+ // The loan is out of scope at point `location` if it's not contained within any
359
+ // live regions.
360
+ let mut issuing_region_can_reach_live_regions = false ;
361
+
362
+ // Check reachability of all live regions:
363
+ // - the local regions that are live at this point,
364
+ // - the placeholders, which are live at all points and don't need liveness to be
365
+ // computed, and are thus absent from the liveness values.
366
+ //
367
+ // As mentioned above, we don't need to check for free regions, if the issuing
368
+ // region outlived a free region via outlives constraints, we wouldn't need to
369
+ // compute its loan's scope.
370
+ for live_region in
371
+ liveness. live_regions_at ( location) . chain ( self . placeholders . iter ( ) . copied ( ) )
372
+ {
373
+ let live_region_scc = sccs. scc ( live_region) ;
374
+
375
+ // If a single live region is reachable from the issuing region, then the loan
376
+ // is still live at this point. We can stop checking other live regions at this
377
+ // location, and go to the next location.
378
+ if reachability. contains ( live_region_scc) {
379
+ issuing_region_can_reach_live_regions = true ;
380
+ break ;
381
+ }
382
+ }
383
+
384
+ // If no live region is reachable from the issuing region, then the loan is
385
+ // killed at this point, and goes out of scope.
386
+ if !issuing_region_can_reach_live_regions {
387
+ debug ! ( "loan {:?} gets killed at {:?}" , loan_idx, location) ;
388
+ self . loans_out_of_scope_at_location . entry ( location) . or_default ( ) . push ( loan_idx) ;
389
+ finished_early = true ;
390
+ break ;
391
+ }
392
+ }
393
+
394
+ if !finished_early {
395
+ // Add successor BBs to the work list, if necessary.
396
+ let bb_data = & self . body [ bb] ;
397
+ debug_assert ! ( hi == bb_data. statements. len( ) ) ;
398
+ for succ_bb in bb_data. terminator ( ) . successors ( ) {
399
+ if !self . visited . insert ( succ_bb) {
400
+ if succ_bb == issued_location. block && first_lo > 0 {
401
+ // `succ_bb` has been seen before. If it wasn't fully processed, add its
402
+ // first part to `visit_stack` for processing.
403
+ self . visit_stack . push ( StackEntry {
404
+ bb : succ_bb,
405
+ lo : 0 ,
406
+ hi : first_lo - 1 ,
407
+ } ) ;
408
+
409
+ // And update this entry with 0, to represent the whole BB being
410
+ // processed.
411
+ first_lo = 0 ;
412
+ }
413
+ } else {
414
+ // `succ_bb` hasn't been seen before. Add it to `visit_stack` for
415
+ // processing.
416
+ self . visit_stack . push ( StackEntry {
417
+ bb : succ_bb,
418
+ lo : 0 ,
419
+ hi : self . body [ succ_bb] . statements . len ( ) ,
420
+ } ) ;
421
+ }
422
+ }
423
+ }
424
+ }
425
+
426
+ assert ! ( self . visit_stack. is_empty( ) , "stack should be empty" ) ;
427
+
428
+ self . visited . clear ( ) ;
429
+ }
430
+ }
431
+
241
432
impl < ' a , ' tcx > Borrows < ' a , ' tcx > {
242
433
pub fn new (
243
434
tcx : TyCtxt < ' tcx > ,
244
435
body : & ' a Body < ' tcx > ,
245
436
regioncx : & ' a RegionInferenceContext < ' tcx > ,
246
437
borrow_set : & ' a BorrowSet < ' tcx > ,
247
438
) -> Self {
248
- let borrows_out_of_scope_at_location =
439
+ let mut borrows_out_of_scope_at_location =
249
440
calculate_borrows_out_of_scope_at_location ( body, regioncx, borrow_set) ;
441
+
442
+ // The in-tree polonius analysis computes loans going out of scope using the set-of-loans
443
+ // model, and makes sure they're identical to the existing computation of the set-of-points
444
+ // model.
445
+ if tcx. sess . opts . unstable_opts . polonius . is_next_enabled ( ) {
446
+ let mut polonius_prec = PoloniusOutOfScopePrecomputer :: new ( body, regioncx) ;
447
+ for ( loan_idx, loan_data) in borrow_set. iter_enumerated ( ) {
448
+ let issuing_region = loan_data. region ;
449
+ let issued_location = loan_data. reserve_location ;
450
+
451
+ polonius_prec. precompute_loans_out_of_scope (
452
+ loan_idx,
453
+ issuing_region,
454
+ issued_location,
455
+ ) ;
456
+ }
457
+
458
+ assert_eq ! (
459
+ borrows_out_of_scope_at_location, polonius_prec. loans_out_of_scope_at_location,
460
+ "the loans out of scope must be the same as the borrows out of scope"
461
+ ) ;
462
+
463
+ borrows_out_of_scope_at_location = polonius_prec. loans_out_of_scope_at_location ;
464
+ }
465
+
250
466
Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }
251
467
}
252
468
0 commit comments