11#![ deny( rustc:: untranslatable_diagnostic) ]
22#![ deny( rustc:: diagnostic_outside_of_impl) ]
33use rustc_data_structures:: fx:: FxIndexMap ;
4+ use rustc_data_structures:: graph:: WithSuccessors ;
45use rustc_index:: bit_set:: BitSet ;
56use rustc_middle:: mir:: {
67 self , BasicBlock , Body , CallReturnPlaces , Location , Place , TerminatorEdges ,
@@ -222,6 +223,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {
222223 }
223224}
224225
226+ // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`.
225227pub fn calculate_borrows_out_of_scope_at_location < ' tcx > (
226228 body : & Body < ' tcx > ,
227229 regioncx : & RegionInferenceContext < ' tcx > ,
@@ -238,15 +240,196 @@ pub fn calculate_borrows_out_of_scope_at_location<'tcx>(
238240 prec. borrows_out_of_scope_at_location
239241}
240242
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+
241399impl < ' a , ' tcx > Borrows < ' a , ' tcx > {
242400 pub fn new (
243401 tcx : TyCtxt < ' tcx > ,
244402 body : & ' a Body < ' tcx > ,
245- nonlexical_regioncx : & ' a RegionInferenceContext < ' tcx > ,
403+ regioncx : & ' a RegionInferenceContext < ' tcx > ,
246404 borrow_set : & ' a BorrowSet < ' tcx > ,
247405 ) -> 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+
250433 Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }
251434 }
252435
@@ -333,6 +516,13 @@ impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
333516 }
334517}
335518
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.
336526impl < ' tcx > rustc_mir_dataflow:: GenKillAnalysis < ' tcx > for Borrows < ' _ , ' tcx > {
337527 type Idx = BorrowIndex ;
338528
0 commit comments