Skip to content

Commit 94a2e55

Browse files
committed
handle kills in reachability
1 parent a1ebcd2 commit 94a2e55

File tree

1 file changed

+128
-9
lines changed

1 file changed

+128
-9
lines changed

compiler/rustc_borrowck/src/polonius/loan_liveness.rs

+128-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
1+
use std::collections::{BTreeMap, BTreeSet};
2+
13
use either::Either;
24
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
3-
use rustc_middle::mir::Body;
5+
use rustc_index::bit_set::SparseBitMatrix;
6+
use rustc_middle::mir::visit::Visitor;
7+
use rustc_middle::mir::{
8+
Body, Local, Location, Place, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
9+
};
410
use rustc_middle::ty::{RegionVid, TyCtxt};
511
use rustc_mir_dataflow::points::PointIndex;
612

7-
use super::{LiveLoans, LocalizedOutlivesConstraintSet};
8-
use crate::BorrowSet;
13+
use super::LocalizedOutlivesConstraintSet;
14+
use crate::dataflow::BorrowIndex;
915
use crate::region_infer::values::LivenessValues;
16+
use crate::{BorrowSet, PlaceConflictBias, places_conflict};
1017

11-
/// With the full graph of constraints, we can compute loan reachability, and trace loan liveness
12-
/// throughout the CFG.
18+
/// With the full graph of constraints, we can compute loan reachability, stop at kills, and trace
19+
/// loan liveness throughout the CFG.
1320
pub(super) fn compute_loan_liveness<'tcx>(
14-
_tcx: TyCtxt<'tcx>,
15-
_body: &Body<'tcx>,
21+
tcx: TyCtxt<'tcx>,
22+
body: &Body<'tcx>,
1623
liveness: &LivenessValues,
1724
borrow_set: &BorrowSet<'tcx>,
1825
localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
19-
live_loans: &mut LiveLoans,
26+
live_loans: &mut SparseBitMatrix<PointIndex, BorrowIndex>,
2027
) {
28+
// FIXME: it may be preferable for kills to be encoded in the edges themselves, to simplify and
29+
// likely make traversal (and constraint generation) more efficient. We also display kills on
30+
// edges when visualizing the constraint graph anyways.
31+
let kills = collect_kills(body, tcx, borrow_set);
32+
2133
let graph = index_constraints(&localized_outlives_constraints);
2234
let mut visited = FxHashSet::default();
2335
let mut stack = Vec::new();
@@ -42,7 +54,16 @@ pub(super) fn compute_loan_liveness<'tcx>(
4254
// Record the loan as being live on entry to this point.
4355
live_loans.insert(node.point, loan_idx);
4456

57+
// Continuing traversal will depend on whether the loan is killed at this point.
58+
let current_location = liveness.location_from_point(node.point);
59+
let is_loan_killed =
60+
kills.get(&current_location).is_some_and(|kills| kills.contains(&loan_idx));
61+
4562
for succ in outgoing_edges(&graph, node) {
63+
// If the loan is killed at this point, it is killed _on exit_.
64+
if is_loan_killed {
65+
continue;
66+
}
4667
stack.push(succ);
4768
}
4869
}
@@ -60,7 +81,7 @@ struct LocalizedNode {
6081
point: PointIndex,
6182
}
6283

63-
/// Index the outlives constraints into a graph of edges per node.
84+
/// Traverses the constraints and returns the indexable graph of edges per node.
6485
fn index_constraints(constraints: &LocalizedOutlivesConstraintSet) -> LocalizedConstraintGraph {
6586
let mut edges = LocalizedConstraintGraph::default();
6687
for constraint in &constraints.outlives {
@@ -83,3 +104,101 @@ fn outgoing_edges(
83104
Either::Right(std::iter::empty())
84105
}
85106
}
107+
108+
/// Traverses the MIR and collects kills.
109+
fn collect_kills<'tcx>(
110+
body: &Body<'tcx>,
111+
tcx: TyCtxt<'tcx>,
112+
borrow_set: &BorrowSet<'tcx>,
113+
) -> BTreeMap<Location, BTreeSet<BorrowIndex>> {
114+
let mut collector = KillsCollector { borrow_set, tcx, body, kills: BTreeMap::default() };
115+
for (block, data) in body.basic_blocks.iter_enumerated() {
116+
collector.visit_basic_block_data(block, data);
117+
}
118+
collector.kills
119+
}
120+
121+
struct KillsCollector<'a, 'tcx> {
122+
body: &'a Body<'tcx>,
123+
tcx: TyCtxt<'tcx>,
124+
borrow_set: &'a BorrowSet<'tcx>,
125+
126+
/// The set of loans killed at each location.
127+
kills: BTreeMap<Location, BTreeSet<BorrowIndex>>,
128+
}
129+
130+
// This visitor has a similar structure to the `Borrows` dataflow computation with respect to kills,
131+
// and the datalog polonius fact generation for the `loan_killed_at` relation.
132+
impl<'tcx> KillsCollector<'_, 'tcx> {
133+
/// Records the borrows on the specified place as `killed`. For example, when assigning to a
134+
/// local, or on a call's return destination.
135+
fn record_killed_borrows_for_place(&mut self, place: Place<'tcx>, location: Location) {
136+
let other_borrows_of_local = self
137+
.borrow_set
138+
.local_map
139+
.get(&place.local)
140+
.into_iter()
141+
.flat_map(|bs| bs.iter())
142+
.copied();
143+
144+
// If the borrowed place is a local with no projections, all other borrows of this
145+
// local must conflict. This is purely an optimization so we don't have to call
146+
// `places_conflict` for every borrow.
147+
if place.projection.is_empty() {
148+
if !self.body.local_decls[place.local].is_ref_to_static() {
149+
self.kills.entry(location).or_default().extend(other_borrows_of_local);
150+
}
151+
return;
152+
}
153+
154+
// By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given
155+
// pair of array indices are not equal, so that when `places_conflict` returns true, we
156+
// will be assured that two places being compared definitely denotes the same sets of
157+
// locations.
158+
let definitely_conflicting_borrows = other_borrows_of_local.filter(|&i| {
159+
places_conflict(
160+
self.tcx,
161+
self.body,
162+
self.borrow_set[i].borrowed_place,
163+
place,
164+
PlaceConflictBias::NoOverlap,
165+
)
166+
});
167+
168+
self.kills.entry(location).or_default().extend(definitely_conflicting_borrows);
169+
}
170+
171+
/// Records the borrows on the specified local as `killed`.
172+
fn record_killed_borrows_for_local(&mut self, local: Local, location: Location) {
173+
if let Some(borrow_indices) = self.borrow_set.local_map.get(&local) {
174+
self.kills.entry(location).or_default().extend(borrow_indices.iter());
175+
}
176+
}
177+
}
178+
179+
impl<'tcx> Visitor<'tcx> for KillsCollector<'_, 'tcx> {
180+
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
181+
// Make sure there are no remaining borrows for locals that have gone out of scope.
182+
if let StatementKind::StorageDead(local) = statement.kind {
183+
self.record_killed_borrows_for_local(local, location);
184+
}
185+
186+
self.super_statement(statement, location);
187+
}
188+
189+
fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
190+
// When we see `X = ...`, then kill borrows of `(*X).foo` and so forth.
191+
self.record_killed_borrows_for_place(*place, location);
192+
self.super_assign(place, rvalue, location);
193+
}
194+
195+
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
196+
// A `Call` terminator's return value can be a local which has borrows, so we need to record
197+
// those as killed as well.
198+
if let TerminatorKind::Call { destination, .. } = terminator.kind {
199+
self.record_killed_borrows_for_place(destination, location);
200+
}
201+
202+
self.super_terminator(terminator, location);
203+
}
204+
}

0 commit comments

Comments
 (0)