Skip to content

Commit 51ed991

Browse files
committed
compute live loans during liveness
Record all the loans flowing into the regions in a value's type as live at the given locations where the value is live. This will then be used to compute when a given loan goes out of scope in the polonius out-of-scope precomputer.
1 parent 0035403 commit 51ed991

File tree

5 files changed

+194
-134
lines changed

5 files changed

+194
-134
lines changed

compiler/rustc_borrowck/src/dataflow.rs

+48-102
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#![deny(rustc::untranslatable_diagnostic)]
22
#![deny(rustc::diagnostic_outside_of_impl)]
3-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
4-
use rustc_index::bit_set::{BitSet, SparseBitMatrix};
3+
use rustc_data_structures::fx::FxIndexMap;
4+
use rustc_data_structures::graph::WithSuccessors;
5+
use rustc_index::bit_set::BitSet;
56
use rustc_middle::mir::{
67
self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges,
78
};
@@ -13,8 +14,6 @@ use rustc_mir_dataflow::{self, fmt::DebugWithContext, GenKill};
1314
use rustc_mir_dataflow::{Analysis, Direction, Results};
1415
use std::fmt;
1516

16-
use crate::constraints::ConstraintSccIndex;
17-
use crate::region_infer::values::PointIndex;
1817
use crate::{places_conflict, BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext};
1918

2019
/// A tuple with named fields that can hold either the results or the transient state of the
@@ -246,65 +245,17 @@ struct PoloniusOutOfScopePrecomputer<'a, 'tcx> {
246245
body: &'a Body<'tcx>,
247246
regioncx: &'a RegionInferenceContext<'tcx>,
248247

249-
sccs_live_at_all_points: FxHashSet<ConstraintSccIndex>,
250-
live_sccs_per_point: SparseBitMatrix<PointIndex, ConstraintSccIndex>,
251-
252-
reachability: BitSet<ConstraintSccIndex>,
253-
reachability_stack: Vec<ConstraintSccIndex>,
254-
255248
loans_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,
256249
}
257250

258251
impl<'a, 'tcx> PoloniusOutOfScopePrecomputer<'a, 'tcx> {
259252
fn new(body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>) -> Self {
260-
let sccs = regioncx.constraint_sccs();
261-
let num_sccs = sccs.num_sccs();
262-
263-
// Compute the list of SCCs that are live at all points, as it will be used for all the
264-
// loan scopes we'll compute.
265-
// FIXME: they're surely already available somewhere.
266-
let sccs_live_at_all_points: FxHashSet<_> = regioncx
267-
.regions()
268-
.filter(|&r| {
269-
use rustc_infer::infer::{NllRegionVariableOrigin, RegionVariableOrigin};
270-
let origin = regioncx.var_infos[r].origin;
271-
let live_at_all_points = matches!(
272-
origin,
273-
RegionVariableOrigin::Nll(
274-
NllRegionVariableOrigin::Placeholder(_)
275-
| NllRegionVariableOrigin::FreeRegion
276-
)
277-
);
278-
live_at_all_points
279-
})
280-
.map(|r| sccs.scc(r))
281-
.collect();
282-
283-
// Pre-compute the set of live SCCs per point
284-
let liveness = regioncx.liveness_values();
285-
let mut live_sccs_per_point = SparseBitMatrix::new(num_sccs);
286-
287-
for region in liveness.rows() {
288-
let scc = sccs.scc(region);
289-
if sccs_live_at_all_points.contains(&scc) {
290-
continue;
291-
}
292-
for location in liveness.get_elements(region) {
293-
let point = liveness.point_from_location(location);
294-
live_sccs_per_point.insert(point, scc);
295-
}
296-
}
297-
298253
Self {
299254
visited: BitSet::new_empty(body.basic_blocks.len()),
300255
visit_stack: vec![],
301256
body,
302257
regioncx,
303258
loans_out_of_scope_at_location: FxIndexMap::default(),
304-
sccs_live_at_all_points,
305-
live_sccs_per_point,
306-
reachability: BitSet::new_empty(num_sccs),
307-
reachability_stack: vec![],
308259
}
309260
}
310261
}
@@ -317,66 +268,50 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
317268
&mut self,
318269
loan_idx: BorrowIndex,
319270
issuing_region: RegionVid,
320-
first_location: Location,
271+
loan_issued_at: Location,
321272
) {
322-
// Let's precompute the reachability set of the issuing region, via reachability on the
323-
// condensation graph. We can also early return when reaching regions that outlive free
324-
// regions via member constraints. (The `OutOfScopePrecomputer` wouldn't be called on a
325-
// region that outlives free regions via outlives constraints.)
326-
327273
let sccs = self.regioncx.constraint_sccs();
328-
329274
let issuing_region_scc = sccs.scc(issuing_region);
330-
self.reachability_stack.push(issuing_region_scc);
331-
self.reachability.insert(issuing_region_scc);
332275

333-
while let Some(scc) = self.reachability_stack.pop() {
334-
// Handle successors of this SCC:
335-
//
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) {
336279
// 1. Via member constraints
337280
//
338-
// The issuing region can flow into the choice regions here, and they are either:
281+
// The issuing region can flow into the choice regions, and they are either:
339282
// - placeholders or free regions themselves,
340283
// - or also transitively outlive a free region.
341284
//
342285
// That is to say, if there are member constraints here, the loan escapes the function
343286
// and cannot go out of scope. We can early return.
344-
//
287+
if self.regioncx.scc_has_member_constraints(scc) {
288+
return;
289+
}
290+
345291
// 2. Via regions that are live at all points: placeholders and free regions.
346292
//
347293
// If the issuing region outlives such a region, its loan escapes the function and
348294
// cannot go out of scope. We can early return.
349-
if self.regioncx.scc_has_member_constraints(scc)
350-
|| self.sccs_live_at_all_points.contains(&scc)
351-
{
352-
self.reachability_stack.clear();
353-
self.reachability.clear();
295+
if self.regioncx.scc_is_live_at_all_points(scc) {
354296
return;
355297
}
356-
357-
// 3. Via outlives successors, which we want to record and traverse: we add them to the
358-
// worklist stack
359-
for &succ_scc in sccs.successors(scc) {
360-
if self.reachability.insert(succ_scc) {
361-
self.reachability_stack.push(succ_scc);
362-
}
363-
}
364298
}
365299

366-
let first_block = first_location.block;
300+
let first_block = loan_issued_at.block;
367301
let first_bb_data = &self.body.basic_blocks[first_block];
368302

369303
// The first block we visit is the one where the loan is issued, starting from the statement
370-
// where the loan is issued: at `first_location`.
371-
let first_lo = first_location.statement_index;
304+
// where the loan is issued: at `loan_issued_at`.
305+
let first_lo = loan_issued_at.statement_index;
372306
let first_hi = first_bb_data.statements.len();
373307

374-
if let Some(kill_location) = self.loan_kill_location(first_block, first_lo, first_hi) {
308+
if let Some(kill_location) =
309+
self.loan_kill_location(loan_idx, loan_issued_at, first_block, first_lo, first_hi)
310+
{
375311
debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location);
376312
self.loans_out_of_scope_at_location.entry(kill_location).or_default().push(loan_idx);
377313

378314
// The loan dies within the first block, we're done and can early return.
379-
self.reachability.clear();
380315
return;
381316
}
382317

@@ -393,7 +328,9 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
393328
while let Some(block) = self.visit_stack.pop() {
394329
let bb_data = &self.body[block];
395330
let num_stmts = bb_data.statements.len();
396-
if let Some(kill_location) = self.loan_kill_location(block, 0, num_stmts) {
331+
if let Some(kill_location) =
332+
self.loan_kill_location(loan_idx, loan_issued_at, block, 0, num_stmts)
333+
{
397334
debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location);
398335
self.loans_out_of_scope_at_location
399336
.entry(kill_location)
@@ -413,35 +350,44 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
413350
}
414351

415352
self.visited.clear();
416-
self.reachability.clear();
417353
assert!(self.visit_stack.is_empty(), "visit stack should be empty");
418-
assert!(self.reachability_stack.is_empty(), "reachability stack should be empty");
419354
}
420355

421356
/// Returns the lowest statement in `start..=end`, where the loan goes out of scope, if any.
422357
/// This is the statement where the issuing region can't reach any of the regions that are live
423358
/// at this point.
424-
fn loan_kill_location(&self, block: BasicBlock, start: usize, end: usize) -> Option<Location> {
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> {
425367
for statement_index in start..=end {
426368
let location = Location { block, statement_index };
427369

428-
// Check whether the issuing region can reach local regions that are live at this
429-
// point.
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.
430379
//
431-
// FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and
432-
// `r` is live at point `q`, then it's guaranteed that `i` would reach `r` at point
433-
// `q`. Reachability is location-insensitive, and we could take advantage of
434-
// that, by jumping to a further point than the next statement. We can jump to the
435-
// furthest point within the block where `r` is live.
436-
let point = self.regioncx.liveness_values().point_from_location(location);
437-
if let Some(live_sccs) = self.live_sccs_per_point.row(point) {
438-
if live_sccs.iter().any(|live_scc| self.reachability.contains(live_scc)) {
439-
continue;
440-
}
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;
441387
}
442388

443-
// No live region is reachable from the issuing region: the loan is killed at this point
444-
// and goes out of scope.
389+
// No live region is reachable from the issuing region: the loan is killed at this
390+
// point.
445391
return Some(location);
446392
}
447393

compiler/rustc_borrowck/src/nll.rs

+21-16
Original file line numberDiff line numberDiff line change
@@ -182,22 +182,26 @@ pub(crate) fn compute_regions<'cx, 'tcx>(
182182
let elements = &Rc::new(RegionValueElements::new(&body));
183183

184184
// Run the MIR type-checker.
185-
let MirTypeckResults { constraints, universal_region_relations, opaque_type_values } =
186-
type_check::type_check(
187-
infcx,
188-
param_env,
189-
body,
190-
promoted,
191-
&universal_regions,
192-
location_table,
193-
borrow_set,
194-
&mut all_facts,
195-
flow_inits,
196-
move_data,
197-
elements,
198-
upvars,
199-
polonius_input,
200-
);
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+
);
201205

202206
if let Some(all_facts) = &mut all_facts {
203207
let _prof_timer = infcx.tcx.prof.generic_activity("polonius_fact_generation");
@@ -275,6 +279,7 @@ pub(crate) fn compute_regions<'cx, 'tcx>(
275279
type_tests,
276280
liveness_constraints,
277281
elements,
282+
live_loans,
278283
);
279284

280285
// Generate various additional constraints.

compiler/rustc_borrowck/src/region_infer/mod.rs

+34-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
77
use rustc_data_structures::graph::scc::Sccs;
88
use rustc_errors::Diagnostic;
99
use rustc_hir::def_id::CRATE_DEF_ID;
10+
use rustc_index::bit_set::SparseBitMatrix;
1011
use rustc_index::{IndexSlice, IndexVec};
1112
use rustc_infer::infer::outlives::test_type_match;
1213
use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound, VerifyIfEq};
@@ -21,6 +22,7 @@ use rustc_middle::traits::ObligationCauseCode;
2122
use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitableExt};
2223
use rustc_span::Span;
2324

25+
use crate::dataflow::BorrowIndex;
2426
use crate::{
2527
constraints::{
2628
graph::NormalConstraintGraph, ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet,
@@ -30,8 +32,8 @@ use crate::{
3032
nll::PoloniusOutput,
3133
region_infer::reverse_sccs::ReverseSccGraph,
3234
region_infer::values::{
33-
LivenessValues, PlaceholderIndices, RegionElement, RegionValueElements, RegionValues,
34-
ToElementIndex,
35+
LivenessValues, PlaceholderIndices, PointIndex, RegionElement, RegionValueElements,
36+
RegionValues, ToElementIndex,
3537
},
3638
type_check::{free_region_relations::UniversalRegionRelations, Locations},
3739
universal_regions::UniversalRegions,
@@ -119,6 +121,9 @@ pub struct RegionInferenceContext<'tcx> {
119121
/// Information about how the universally quantified regions in
120122
/// scope on this function relate to one another.
121123
universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
124+
125+
/// The set of loans that are live at a given point in the CFG, when using `-Zpolonius=next`.
126+
live_loans: SparseBitMatrix<PointIndex, BorrowIndex>,
122127
}
123128

124129
/// Each time that `apply_member_constraint` is successful, it appends
@@ -330,6 +335,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
330335
type_tests: Vec<TypeTest<'tcx>>,
331336
liveness_constraints: LivenessValues<RegionVid>,
332337
elements: &Rc<RegionValueElements>,
338+
live_loans: SparseBitMatrix<PointIndex, BorrowIndex>,
333339
) -> Self {
334340
debug!("universal_regions: {:#?}", universal_regions);
335341
debug!("outlives constraints: {:#?}", outlives_constraints);
@@ -383,6 +389,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
383389
type_tests,
384390
universal_regions,
385391
universal_region_relations,
392+
live_loans,
386393
};
387394

388395
result.init_free_and_bound_regions();
@@ -2285,15 +2292,35 @@ impl<'tcx> RegionInferenceContext<'tcx> {
22852292
self.constraint_sccs.as_ref()
22862293
}
22872294

2288-
/// Access to the liveness values.
2289-
pub(crate) fn liveness_values(&self) -> &LivenessValues<RegionVid> {
2290-
&self.liveness_constraints
2291-
}
2292-
22932295
/// Returns whether the given SCC has any member constraints.
22942296
pub(crate) fn scc_has_member_constraints(&self, scc: ConstraintSccIndex) -> bool {
22952297
self.member_constraints.indices(scc).next().is_some()
22962298
}
2299+
2300+
/// Returns whether the given SCC is live at all points: whether the representative is a
2301+
/// placeholder or a free region.
2302+
pub(crate) fn scc_is_live_at_all_points(&self, scc: ConstraintSccIndex) -> bool {
2303+
// FIXME: there must be a cleaner way to find this information. At least, when
2304+
// higher-ranked subtyping is abstracted away from the borrowck main path, we'll only
2305+
// need to check whether this is a universal region.
2306+
let representative = self.scc_representatives[scc];
2307+
let origin = self.var_infos[representative].origin;
2308+
let live_at_all_points = matches!(
2309+
origin,
2310+
RegionVariableOrigin::Nll(
2311+
NllRegionVariableOrigin::Placeholder(_) | NllRegionVariableOrigin::FreeRegion
2312+
)
2313+
);
2314+
live_at_all_points
2315+
}
2316+
2317+
/// Returns whether the `loan_idx` is live at the given `location`: whether its issuing
2318+
/// region is contained within the type of a variable that is live at this point.
2319+
/// Note: for now, the sets of live loans is only available when using `-Zpolonius=next`.
2320+
pub(crate) fn is_loan_live_at(&self, loan_idx: BorrowIndex, location: Location) -> bool {
2321+
let point = self.liveness_constraints.point_from_location(location);
2322+
self.live_loans.contains(point, loan_idx)
2323+
}
22972324
}
22982325

22992326
impl<'tcx> RegionDefinition<'tcx> {

0 commit comments

Comments
 (0)