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 ,
@@ -122,7 +123,6 @@ rustc_index::newtype_index! {
122123pub struct Borrows < ' a , ' tcx > {
123124 tcx : TyCtxt < ' tcx > ,
124125 body : & ' a Body < ' tcx > ,
125-
126126 borrow_set : & ' a BorrowSet < ' tcx > ,
127127 borrows_out_of_scope_at_location : FxIndexMap < Location , Vec < BorrowIndex > > ,
128128}
@@ -222,6 +222,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {
222222 }
223223}
224224
225+ // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`.
225226pub fn calculate_borrows_out_of_scope_at_location < ' tcx > (
226227 body : & Body < ' tcx > ,
227228 regioncx : & RegionInferenceContext < ' tcx > ,
@@ -238,15 +239,196 @@ pub fn calculate_borrows_out_of_scope_at_location<'tcx>(
238239 prec. borrows_out_of_scope_at_location
239240}
240241
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+
241398impl < ' a , ' tcx > Borrows < ' a , ' tcx > {
242399 pub fn new (
243400 tcx : TyCtxt < ' tcx > ,
244401 body : & ' a Body < ' tcx > ,
245- nonlexical_regioncx : & ' a RegionInferenceContext < ' tcx > ,
402+ regioncx : & ' a RegionInferenceContext < ' tcx > ,
246403 borrow_set : & ' a BorrowSet < ' tcx > ,
247404 ) -> 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+
250432 Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }
251433 }
252434
@@ -333,6 +515,13 @@ impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
333515 }
334516}
335517
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.
336525impl < ' tcx > rustc_mir_dataflow:: GenKillAnalysis < ' tcx > for Borrows < ' _ , ' tcx > {
337526 type Idx = BorrowIndex ;
338527
0 commit comments