@@ -238,15 +238,231 @@ pub fn calculate_borrows_out_of_scope_at_location<'tcx>(
238238 prec. borrows_out_of_scope_at_location
239239}
240240
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+
241432impl < ' a , ' tcx > Borrows < ' a , ' tcx > {
242433 pub fn new (
243434 tcx : TyCtxt < ' tcx > ,
244435 body : & ' a Body < ' tcx > ,
245436 regioncx : & ' a RegionInferenceContext < ' tcx > ,
246437 borrow_set : & ' a BorrowSet < ' tcx > ,
247438 ) -> Self {
248- let borrows_out_of_scope_at_location =
439+ let mut borrows_out_of_scope_at_location =
249440 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+
250466 Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }
251467 }
252468
0 commit comments