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