1
+ use std:: collections:: { BTreeMap , BTreeSet } ;
2
+
1
3
use either:: Either ;
2
4
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
+ } ;
4
10
use rustc_middle:: ty:: { RegionVid , TyCtxt } ;
5
11
use rustc_mir_dataflow:: points:: PointIndex ;
6
12
7
- use super :: { LiveLoans , LocalizedOutlivesConstraintSet } ;
8
- use crate :: BorrowSet ;
13
+ use super :: LocalizedOutlivesConstraintSet ;
14
+ use crate :: dataflow :: BorrowIndex ;
9
15
use crate :: region_infer:: values:: LivenessValues ;
16
+ use crate :: { BorrowSet , PlaceConflictBias , places_conflict} ;
10
17
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.
13
20
pub ( super ) fn compute_loan_liveness < ' tcx > (
14
- _tcx : TyCtxt < ' tcx > ,
15
- _body : & Body < ' tcx > ,
21
+ tcx : TyCtxt < ' tcx > ,
22
+ body : & Body < ' tcx > ,
16
23
liveness : & LivenessValues ,
17
24
borrow_set : & BorrowSet < ' tcx > ,
18
25
localized_outlives_constraints : & LocalizedOutlivesConstraintSet ,
19
- live_loans : & mut LiveLoans ,
26
+ live_loans : & mut SparseBitMatrix < PointIndex , BorrowIndex > ,
20
27
) {
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
+
21
33
let graph = index_constraints ( & localized_outlives_constraints) ;
22
34
let mut visited = FxHashSet :: default ( ) ;
23
35
let mut stack = Vec :: new ( ) ;
@@ -42,7 +54,16 @@ pub(super) fn compute_loan_liveness<'tcx>(
42
54
// Record the loan as being live on entry to this point.
43
55
live_loans. insert ( node. point , loan_idx) ;
44
56
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
+
45
62
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
+ }
46
67
stack. push ( succ) ;
47
68
}
48
69
}
@@ -60,7 +81,7 @@ struct LocalizedNode {
60
81
point : PointIndex ,
61
82
}
62
83
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.
64
85
fn index_constraints ( constraints : & LocalizedOutlivesConstraintSet ) -> LocalizedConstraintGraph {
65
86
let mut edges = LocalizedConstraintGraph :: default ( ) ;
66
87
for constraint in & constraints. outlives {
@@ -83,3 +104,101 @@ fn outgoing_edges(
83
104
Either :: Right ( std:: iter:: empty ( ) )
84
105
}
85
106
}
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