Skip to content

Commit c1691db

Browse files
committed
Auto merge of rust-lang#113218 - lqd:polonius-scopes, r=jackh726
Compute NLL loan scopes using the polonius model For a *location-insensitive* analysis (that is, without expressiveness improvements for users yet), this PR implements loans going out of scope using reachability and liveness, rather than checking if the issuing region's values contain a given CFG point. This is equivalent to NLL scopes and computes the same data. r? `@matthewjasper` A couple of notes: - there are some assumptions about SCC representatives, placeholders, free regions, and member constraints that I believe hold, and they're documented in the code - this passes all the UI tests with `-Zpolonius=next` -- the perf is [not terrible](rust-lang#112432 (comment)) and there are a bunch of ways to improve it in the future. - there's a fixme left, hopefully Matthew you know a clean way to get the information it mentions.
2 parents 3c23df4 + 3dcff00 commit c1691db

File tree

10 files changed

+413
-36
lines changed

10 files changed

+413
-36
lines changed

Diff for: compiler/rustc_borrowck/src/dataflow.rs

+193-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![deny(rustc::untranslatable_diagnostic)]
22
#![deny(rustc::diagnostic_outside_of_impl)]
33
use rustc_data_structures::fx::FxIndexMap;
4+
use rustc_data_structures::graph::WithSuccessors;
45
use rustc_index::bit_set::BitSet;
56
use 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`.
225227
pub 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+
241399
impl<'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.
336526
impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
337527
type Idx = BorrowIndex;
338528

Diff for: compiler/rustc_borrowck/src/facts.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ pub(crate) trait AllFactsExt {
4141
impl AllFactsExt for AllFacts {
4242
/// Return
4343
fn enabled(tcx: TyCtxt<'_>) -> bool {
44-
tcx.sess.opts.unstable_opts.nll_facts || tcx.sess.opts.unstable_opts.polonius
44+
tcx.sess.opts.unstable_opts.nll_facts
45+
|| tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled()
4546
}
4647

4748
fn write_to_dir(

Diff for: compiler/rustc_borrowck/src/nll.rs

+24-18
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,11 @@ pub(crate) fn compute_regions<'cx, 'tcx>(
169169
upvars: &[Upvar<'tcx>],
170170
consumer_options: Option<ConsumerOptions>,
171171
) -> NllOutput<'tcx> {
172+
let is_polonius_legacy_enabled = infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled();
172173
let polonius_input = consumer_options.map(|c| c.polonius_input()).unwrap_or_default()
173-
|| infcx.tcx.sess.opts.unstable_opts.polonius;
174+
|| is_polonius_legacy_enabled;
174175
let polonius_output = consumer_options.map(|c| c.polonius_output()).unwrap_or_default()
175-
|| infcx.tcx.sess.opts.unstable_opts.polonius;
176+
|| is_polonius_legacy_enabled;
176177
let mut all_facts =
177178
(polonius_input || AllFacts::enabled(infcx.tcx)).then_some(AllFacts::default());
178179

@@ -181,22 +182,26 @@ pub(crate) fn compute_regions<'cx, 'tcx>(
181182
let elements = &Rc::new(RegionValueElements::new(&body));
182183

183184
// Run the MIR type-checker.
184-
let MirTypeckResults { constraints, universal_region_relations, opaque_type_values } =
185-
type_check::type_check(
186-
infcx,
187-
param_env,
188-
body,
189-
promoted,
190-
&universal_regions,
191-
location_table,
192-
borrow_set,
193-
&mut all_facts,
194-
flow_inits,
195-
move_data,
196-
elements,
197-
upvars,
198-
polonius_input,
199-
);
185+
let MirTypeckResults {
186+
constraints,
187+
universal_region_relations,
188+
opaque_type_values,
189+
live_loans,
190+
} = type_check::type_check(
191+
infcx,
192+
param_env,
193+
body,
194+
promoted,
195+
&universal_regions,
196+
location_table,
197+
borrow_set,
198+
&mut all_facts,
199+
flow_inits,
200+
move_data,
201+
elements,
202+
upvars,
203+
polonius_input,
204+
);
200205

201206
if let Some(all_facts) = &mut all_facts {
202207
let _prof_timer = infcx.tcx.prof.generic_activity("polonius_fact_generation");
@@ -274,6 +279,7 @@ pub(crate) fn compute_regions<'cx, 'tcx>(
274279
type_tests,
275280
liveness_constraints,
276281
elements,
282+
live_loans,
277283
);
278284

279285
// Generate various additional constraints.

0 commit comments

Comments
 (0)