Skip to content

Commit 60c6857

Browse files
committed
compute loan scopes with liveness in -Zpolonius=next
1 parent b2559b6 commit 60c6857

File tree

1 file changed

+217
-1
lines changed

1 file changed

+217
-1
lines changed

compiler/rustc_borrowck/src/dataflow.rs

Lines changed: 217 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
241432
impl<'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

Comments
 (0)