1
+ use std::collections::{BTreeMap, BTreeSet};
2
+
1
3
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2
- use rustc_middle::mir::Body;
4
+ use rustc_middle::mir::visit::Visitor;
5
+ use rustc_middle::mir::{
6
+ Body, Local, Location, Place, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
7
+ };
3
8
use rustc_middle::ty::{RegionVid, TyCtxt};
4
9
use rustc_mir_dataflow::points::PointIndex;
5
10
6
11
use super::{LiveLoans, LocalizedOutlivesConstraintSet};
7
- use crate::BorrowSet ;
12
+ use crate::dataflow::BorrowIndex ;
8
13
use crate::region_infer::values::LivenessValues;
14
+ use crate::{BorrowSet, PlaceConflictBias, places_conflict};
9
15
10
- /// With the full graph of constraints, we can compute loan reachability, and trace loan liveness
11
- /// throughout the CFG.
16
+ /// With the full graph of constraints, we can compute loan reachability, stop at kills, and trace
17
+ /// loan liveness throughout the CFG.
12
18
pub(super) fn compute_loan_liveness<'tcx>(
13
- _tcx : TyCtxt<'tcx>,
14
- _body : &Body<'tcx>,
19
+ tcx : TyCtxt<'tcx>,
20
+ body : &Body<'tcx>,
15
21
liveness: &LivenessValues,
16
22
borrow_set: &BorrowSet<'tcx>,
17
23
localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
18
24
) -> LiveLoans {
19
25
let mut live_loans = LiveLoans::new(borrow_set.len());
26
+
27
+ // FIXME: it may be preferable for kills to be encoded in the edges themselves, to simplify and
28
+ // likely make traversal (and constraint generation) more efficient. We also display kills on
29
+ // edges when visualizing the constraint graph anyways.
30
+ let kills = collect_kills(body, tcx, borrow_set);
31
+
20
32
let graph = index_constraints(&localized_outlives_constraints);
21
33
let mut visited = FxHashSet::default();
22
34
let mut stack = Vec::new();
@@ -41,7 +53,16 @@ pub(super) fn compute_loan_liveness<'tcx>(
41
53
// Record the loan as being live on entry to this point.
42
54
live_loans.insert(node.point, loan_idx);
43
55
56
+ // Continuing traversal will depend on whether the loan is killed at this point.
57
+ let current_location = liveness.location_from_point(node.point);
58
+ let is_loan_killed =
59
+ kills.get(¤t_location).is_some_and(|kills| kills.contains(&loan_idx));
60
+
44
61
for succ in outgoing_edges(&graph, node) {
62
+ // If the loan is killed at this point, it is killed _on exit_.
63
+ if is_loan_killed {
64
+ continue;
65
+ }
45
66
stack.push(succ);
46
67
}
47
68
}
@@ -61,7 +82,7 @@ struct LocalizedNode {
61
82
point: PointIndex,
62
83
}
63
84
64
- /// Index the outlives constraints into a graph of edges per node.
85
+ /// Traverses the constraints and returns the indexable graph of edges per node.
65
86
fn index_constraints(constraints: &LocalizedOutlivesConstraintSet) -> LocalizedConstraintGraph {
66
87
let mut edges = LocalizedConstraintGraph::default();
67
88
for constraint in &constraints.outlives {
@@ -80,3 +101,101 @@ fn outgoing_edges(
80
101
) -> impl Iterator<Item = LocalizedNode> + use<'_> {
81
102
graph.get(&node).into_iter().flat_map(|edges| edges.iter().copied())
82
103
}
104
+
105
+ /// Traverses the MIR and collects kills.
106
+ fn collect_kills<'tcx>(
107
+ body: &Body<'tcx>,
108
+ tcx: TyCtxt<'tcx>,
109
+ borrow_set: &BorrowSet<'tcx>,
110
+ ) -> BTreeMap<Location, BTreeSet<BorrowIndex>> {
111
+ let mut collector = KillsCollector { borrow_set, tcx, body, kills: BTreeMap::default() };
112
+ for (block, data) in body.basic_blocks.iter_enumerated() {
113
+ collector.visit_basic_block_data(block, data);
114
+ }
115
+ collector.kills
116
+ }
117
+
118
+ struct KillsCollector<'a, 'tcx> {
119
+ body: &'a Body<'tcx>,
120
+ tcx: TyCtxt<'tcx>,
121
+ borrow_set: &'a BorrowSet<'tcx>,
122
+
123
+ /// The set of loans killed at each location.
124
+ kills: BTreeMap<Location, BTreeSet<BorrowIndex>>,
125
+ }
126
+
127
+ // This visitor has a similar structure to the `Borrows` dataflow computation with respect to kills,
128
+ // and the datalog polonius fact generation for the `loan_killed_at` relation.
129
+ impl<'tcx> KillsCollector<'_, 'tcx> {
130
+ /// Records the borrows on the specified place as `killed`. For example, when assigning to a
131
+ /// local, or on a call's return destination.
132
+ fn record_killed_borrows_for_place(&mut self, place: Place<'tcx>, location: Location) {
133
+ let other_borrows_of_local = self
134
+ .borrow_set
135
+ .local_map
136
+ .get(&place.local)
137
+ .into_iter()
138
+ .flat_map(|bs| bs.iter())
139
+ .copied();
140
+
141
+ // If the borrowed place is a local with no projections, all other borrows of this
142
+ // local must conflict. This is purely an optimization so we don't have to call
143
+ // `places_conflict` for every borrow.
144
+ if place.projection.is_empty() {
145
+ if !self.body.local_decls[place.local].is_ref_to_static() {
146
+ self.kills.entry(location).or_default().extend(other_borrows_of_local);
147
+ }
148
+ return;
149
+ }
150
+
151
+ // By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given
152
+ // pair of array indices are not equal, so that when `places_conflict` returns true, we
153
+ // will be assured that two places being compared definitely denotes the same sets of
154
+ // locations.
155
+ let definitely_conflicting_borrows = other_borrows_of_local.filter(|&i| {
156
+ places_conflict(
157
+ self.tcx,
158
+ self.body,
159
+ self.borrow_set[i].borrowed_place,
160
+ place,
161
+ PlaceConflictBias::NoOverlap,
162
+ )
163
+ });
164
+
165
+ self.kills.entry(location).or_default().extend(definitely_conflicting_borrows);
166
+ }
167
+
168
+ /// Records the borrows on the specified local as `killed`.
169
+ fn record_killed_borrows_for_local(&mut self, local: Local, location: Location) {
170
+ if let Some(borrow_indices) = self.borrow_set.local_map.get(&local) {
171
+ self.kills.entry(location).or_default().extend(borrow_indices.iter());
172
+ }
173
+ }
174
+ }
175
+
176
+ impl<'tcx> Visitor<'tcx> for KillsCollector<'_, 'tcx> {
177
+ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
178
+ // Make sure there are no remaining borrows for locals that have gone out of scope.
179
+ if let StatementKind::StorageDead(local) = statement.kind {
180
+ self.record_killed_borrows_for_local(local, location);
181
+ }
182
+
183
+ self.super_statement(statement, location);
184
+ }
185
+
186
+ fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
187
+ // When we see `X = ...`, then kill borrows of `(*X).foo` and so forth.
188
+ self.record_killed_borrows_for_place(*place, location);
189
+ self.super_assign(place, rvalue, location);
190
+ }
191
+
192
+ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
193
+ // A `Call` terminator's return value can be a local which has borrows, so we need to record
194
+ // those as killed as well.
195
+ if let TerminatorKind::Call { destination, .. } = terminator.kind {
196
+ self.record_killed_borrows_for_place(destination, location);
197
+ }
198
+
199
+ self.super_terminator(terminator, location);
200
+ }
201
+ }
0 commit comments