1+ use std:: collections:: { BTreeMap , BTreeSet } ;
2+
13use 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+ } ;
38use rustc_middle:: ty:: { RegionVid , TyCtxt } ;
49use rustc_mir_dataflow:: points:: PointIndex ;
510
611use super :: { LiveLoans , LocalizedOutlivesConstraintSet } ;
7- use crate :: BorrowSet ;
12+ use crate :: dataflow :: BorrowIndex ;
813use crate :: region_infer:: values:: LivenessValues ;
14+ use crate :: { BorrowSet , PlaceConflictBias , places_conflict} ;
915
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.
1218pub ( super ) fn compute_loan_liveness < ' tcx > (
13- _tcx : TyCtxt < ' tcx > ,
14- _body : & Body < ' tcx > ,
19+ tcx : TyCtxt < ' tcx > ,
20+ body : & Body < ' tcx > ,
1521 liveness : & LivenessValues ,
1622 borrow_set : & BorrowSet < ' tcx > ,
1723 localized_outlives_constraints : & LocalizedOutlivesConstraintSet ,
1824) -> LiveLoans {
1925 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+
2032 let graph = index_constraints ( & localized_outlives_constraints) ;
2133 let mut visited = FxHashSet :: default ( ) ;
2234 let mut stack = Vec :: new ( ) ;
@@ -41,7 +53,16 @@ pub(super) fn compute_loan_liveness<'tcx>(
4153 // Record the loan as being live on entry to this point.
4254 live_loans. insert ( node. point , loan_idx) ;
4355
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 ( & current_location) . is_some_and ( |kills| kills. contains ( & loan_idx) ) ;
60+
4461 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+ }
4566 stack. push ( succ) ;
4667 }
4768 }
@@ -61,7 +82,7 @@ struct LocalizedNode {
6182 point : PointIndex ,
6283}
6384
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.
6586fn index_constraints ( constraints : & LocalizedOutlivesConstraintSet ) -> LocalizedConstraintGraph {
6687 let mut edges = LocalizedConstraintGraph :: default ( ) ;
6788 for constraint in & constraints. outlives {
@@ -80,3 +101,101 @@ fn outgoing_edges(
80101) -> impl Iterator < Item = LocalizedNode > + use < ' _ > {
81102 graph. get ( & node) . into_iter ( ) . flat_map ( |edges| edges. iter ( ) . copied ( ) )
82103}
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