Skip to content

Commit

Permalink
compute live loans during liveness
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lqd committed Oct 5, 2023
1 parent 0035403 commit 51ed991
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 134 deletions.
150 changes: 48 additions & 102 deletions compiler/rustc_borrowck/src/dataflow.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![deny(rustc::untranslatable_diagnostic)]
#![deny(rustc::diagnostic_outside_of_impl)]
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_index::bit_set::{BitSet, SparseBitMatrix};
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::graph::WithSuccessors;
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::{
self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges,
};
Expand All @@ -13,8 +14,6 @@ use rustc_mir_dataflow::{self, fmt::DebugWithContext, GenKill};
use rustc_mir_dataflow::{Analysis, Direction, Results};
use std::fmt;

use crate::constraints::ConstraintSccIndex;
use crate::region_infer::values::PointIndex;
use crate::{places_conflict, BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext};

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

sccs_live_at_all_points: FxHashSet<ConstraintSccIndex>,
live_sccs_per_point: SparseBitMatrix<PointIndex, ConstraintSccIndex>,

reachability: BitSet<ConstraintSccIndex>,
reachability_stack: Vec<ConstraintSccIndex>,

loans_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,
}

impl<'a, 'tcx> PoloniusOutOfScopePrecomputer<'a, 'tcx> {
fn new(body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>) -> Self {
let sccs = regioncx.constraint_sccs();
let num_sccs = sccs.num_sccs();

// Compute the list of SCCs that are live at all points, as it will be used for all the
// loan scopes we'll compute.
// FIXME: they're surely already available somewhere.
let sccs_live_at_all_points: FxHashSet<_> = regioncx
.regions()
.filter(|&r| {
use rustc_infer::infer::{NllRegionVariableOrigin, RegionVariableOrigin};
let origin = regioncx.var_infos[r].origin;
let live_at_all_points = matches!(
origin,
RegionVariableOrigin::Nll(
NllRegionVariableOrigin::Placeholder(_)
| NllRegionVariableOrigin::FreeRegion
)
);
live_at_all_points
})
.map(|r| sccs.scc(r))
.collect();

// Pre-compute the set of live SCCs per point
let liveness = regioncx.liveness_values();
let mut live_sccs_per_point = SparseBitMatrix::new(num_sccs);

for region in liveness.rows() {
let scc = sccs.scc(region);
if sccs_live_at_all_points.contains(&scc) {
continue;
}
for location in liveness.get_elements(region) {
let point = liveness.point_from_location(location);
live_sccs_per_point.insert(point, scc);
}
}

Self {
visited: BitSet::new_empty(body.basic_blocks.len()),
visit_stack: vec![],
body,
regioncx,
loans_out_of_scope_at_location: FxIndexMap::default(),
sccs_live_at_all_points,
live_sccs_per_point,
reachability: BitSet::new_empty(num_sccs),
reachability_stack: vec![],
}
}
}
Expand All @@ -317,66 +268,50 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
&mut self,
loan_idx: BorrowIndex,
issuing_region: RegionVid,
first_location: Location,
loan_issued_at: Location,
) {
// Let's precompute the reachability set of the issuing region, via reachability on the
// condensation graph. We can also early return when reaching regions that outlive free
// regions via member constraints. (The `OutOfScopePrecomputer` wouldn't be called on a
// region that outlives free regions via outlives constraints.)

let sccs = self.regioncx.constraint_sccs();

let issuing_region_scc = sccs.scc(issuing_region);
self.reachability_stack.push(issuing_region_scc);
self.reachability.insert(issuing_region_scc);

while let Some(scc) = self.reachability_stack.pop() {
// Handle successors of this SCC:
//
// We first handle the cases where the loan doesn't go out of scope, depending on the issuing
// region's successors.
for scc in sccs.depth_first_search(issuing_region_scc) {
// 1. Via member constraints
//
// The issuing region can flow into the choice regions here, and they are either:
// The issuing region can flow into the choice regions, and they are either:
// - placeholders or free regions themselves,
// - or also transitively outlive a free region.
//
// That is to say, if there are member constraints here, the loan escapes the function
// and cannot go out of scope. We can early return.
//
if self.regioncx.scc_has_member_constraints(scc) {
return;
}

// 2. Via regions that are live at all points: placeholders and free regions.
//
// If the issuing region outlives such a region, its loan escapes the function and
// cannot go out of scope. We can early return.
if self.regioncx.scc_has_member_constraints(scc)
|| self.sccs_live_at_all_points.contains(&scc)
{
self.reachability_stack.clear();
self.reachability.clear();
if self.regioncx.scc_is_live_at_all_points(scc) {
return;
}

// 3. Via outlives successors, which we want to record and traverse: we add them to the
// worklist stack
for &succ_scc in sccs.successors(scc) {
if self.reachability.insert(succ_scc) {
self.reachability_stack.push(succ_scc);
}
}
}

let first_block = first_location.block;
let first_block = loan_issued_at.block;
let first_bb_data = &self.body.basic_blocks[first_block];

// The first block we visit is the one where the loan is issued, starting from the statement
// where the loan is issued: at `first_location`.
let first_lo = first_location.statement_index;
// where the loan is issued: at `loan_issued_at`.
let first_lo = loan_issued_at.statement_index;
let first_hi = first_bb_data.statements.len();

if let Some(kill_location) = self.loan_kill_location(first_block, first_lo, first_hi) {
if let Some(kill_location) =
self.loan_kill_location(loan_idx, loan_issued_at, first_block, first_lo, first_hi)
{
debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location);
self.loans_out_of_scope_at_location.entry(kill_location).or_default().push(loan_idx);

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

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

self.visited.clear();
self.reachability.clear();
assert!(self.visit_stack.is_empty(), "visit stack should be empty");
assert!(self.reachability_stack.is_empty(), "reachability stack should be empty");
}

/// Returns the lowest statement in `start..=end`, where the loan goes out of scope, if any.
/// This is the statement where the issuing region can't reach any of the regions that are live
/// at this point.
fn loan_kill_location(&self, block: BasicBlock, start: usize, end: usize) -> Option<Location> {
fn loan_kill_location(
&self,
loan_idx: BorrowIndex,
loan_issued_at: Location,
block: BasicBlock,
start: usize,
end: usize,
) -> Option<Location> {
for statement_index in start..=end {
let location = Location { block, statement_index };

// Check whether the issuing region can reach local regions that are live at this
// point.
// Check whether the issuing region can reach local regions that are live at this point:
// - a loan is always live at its issuing location because it can reach the issuing
// region, which is always live at this location.
if location == loan_issued_at {
continue;
}

// - the loan goes out of scope at `location` if it's not contained within any regions
// live at this point.
//
// FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and
// `r` is live at point `q`, then it's guaranteed that `i` would reach `r` at point
// `q`. Reachability is location-insensitive, and we could take advantage of
// that, by jumping to a further point than the next statement. We can jump to the
// furthest point within the block where `r` is live.
let point = self.regioncx.liveness_values().point_from_location(location);
if let Some(live_sccs) = self.live_sccs_per_point.row(point) {
if live_sccs.iter().any(|live_scc| self.reachability.contains(live_scc)) {
continue;
}
// FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and `r` is
// live at point `q`, then it's guaranteed that `i` would reach `r` at point `q`.
// Reachability is location-insensitive, and we could take advantage of that, by jumping
// to a further point than just the next statement: we can jump to the furthest point
// within the block where `r` is live.
if self.regioncx.is_loan_live_at(loan_idx, location) {
continue;
}

// No live region is reachable from the issuing region: the loan is killed at this point
// and goes out of scope.
// No live region is reachable from the issuing region: the loan is killed at this
// point.
return Some(location);
}

Expand Down
37 changes: 21 additions & 16 deletions compiler/rustc_borrowck/src/nll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,26 @@ pub(crate) fn compute_regions<'cx, 'tcx>(
let elements = &Rc::new(RegionValueElements::new(&body));

// Run the MIR type-checker.
let MirTypeckResults { constraints, universal_region_relations, opaque_type_values } =
type_check::type_check(
infcx,
param_env,
body,
promoted,
&universal_regions,
location_table,
borrow_set,
&mut all_facts,
flow_inits,
move_data,
elements,
upvars,
polonius_input,
);
let MirTypeckResults {
constraints,
universal_region_relations,
opaque_type_values,
live_loans,
} = type_check::type_check(
infcx,
param_env,
body,
promoted,
&universal_regions,
location_table,
borrow_set,
&mut all_facts,
flow_inits,
move_data,
elements,
upvars,
polonius_input,
);

if let Some(all_facts) = &mut all_facts {
let _prof_timer = infcx.tcx.prof.generic_activity("polonius_fact_generation");
Expand Down Expand Up @@ -275,6 +279,7 @@ pub(crate) fn compute_regions<'cx, 'tcx>(
type_tests,
liveness_constraints,
elements,
live_loans,
);

// Generate various additional constraints.
Expand Down
41 changes: 34 additions & 7 deletions compiler/rustc_borrowck/src/region_infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_data_structures::graph::scc::Sccs;
use rustc_errors::Diagnostic;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_index::bit_set::SparseBitMatrix;
use rustc_index::{IndexSlice, IndexVec};
use rustc_infer::infer::outlives::test_type_match;
use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound, VerifyIfEq};
Expand All @@ -21,6 +22,7 @@ use rustc_middle::traits::ObligationCauseCode;
use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitableExt};
use rustc_span::Span;

use crate::dataflow::BorrowIndex;
use crate::{
constraints::{
graph::NormalConstraintGraph, ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet,
Expand All @@ -30,8 +32,8 @@ use crate::{
nll::PoloniusOutput,
region_infer::reverse_sccs::ReverseSccGraph,
region_infer::values::{
LivenessValues, PlaceholderIndices, RegionElement, RegionValueElements, RegionValues,
ToElementIndex,
LivenessValues, PlaceholderIndices, PointIndex, RegionElement, RegionValueElements,
RegionValues, ToElementIndex,
},
type_check::{free_region_relations::UniversalRegionRelations, Locations},
universal_regions::UniversalRegions,
Expand Down Expand Up @@ -119,6 +121,9 @@ pub struct RegionInferenceContext<'tcx> {
/// Information about how the universally quantified regions in
/// scope on this function relate to one another.
universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,

/// The set of loans that are live at a given point in the CFG, when using `-Zpolonius=next`.
live_loans: SparseBitMatrix<PointIndex, BorrowIndex>,
}

/// Each time that `apply_member_constraint` is successful, it appends
Expand Down Expand Up @@ -330,6 +335,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
type_tests: Vec<TypeTest<'tcx>>,
liveness_constraints: LivenessValues<RegionVid>,
elements: &Rc<RegionValueElements>,
live_loans: SparseBitMatrix<PointIndex, BorrowIndex>,
) -> Self {
debug!("universal_regions: {:#?}", universal_regions);
debug!("outlives constraints: {:#?}", outlives_constraints);
Expand Down Expand Up @@ -383,6 +389,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
type_tests,
universal_regions,
universal_region_relations,
live_loans,
};

result.init_free_and_bound_regions();
Expand Down Expand Up @@ -2285,15 +2292,35 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.constraint_sccs.as_ref()
}

/// Access to the liveness values.
pub(crate) fn liveness_values(&self) -> &LivenessValues<RegionVid> {
&self.liveness_constraints
}

/// Returns whether the given SCC has any member constraints.
pub(crate) fn scc_has_member_constraints(&self, scc: ConstraintSccIndex) -> bool {
self.member_constraints.indices(scc).next().is_some()
}

/// Returns whether the given SCC is live at all points: whether the representative is a
/// placeholder or a free region.
pub(crate) fn scc_is_live_at_all_points(&self, scc: ConstraintSccIndex) -> bool {
// FIXME: there must be a cleaner way to find this information. At least, when
// higher-ranked subtyping is abstracted away from the borrowck main path, we'll only
// need to check whether this is a universal region.
let representative = self.scc_representatives[scc];
let origin = self.var_infos[representative].origin;
let live_at_all_points = matches!(
origin,
RegionVariableOrigin::Nll(
NllRegionVariableOrigin::Placeholder(_) | NllRegionVariableOrigin::FreeRegion
)
);
live_at_all_points
}

/// Returns whether the `loan_idx` is live at the given `location`: whether its issuing
/// region is contained within the type of a variable that is live at this point.
/// Note: for now, the sets of live loans is only available when using `-Zpolonius=next`.
pub(crate) fn is_loan_live_at(&self, loan_idx: BorrowIndex, location: Location) -> bool {
let point = self.liveness_constraints.point_from_location(location);
self.live_loans.contains(point, loan_idx)
}
}

impl<'tcx> RegionDefinition<'tcx> {
Expand Down
Loading

0 comments on commit 51ed991

Please sign in to comment.