1
- use rustc_middle:: mir:: { Body , Location } ;
1
+ use rustc_data_structures:: fx:: FxHashSet ;
2
+ use rustc_middle:: mir:: { Body , Location , Statement , StatementKind , Terminator , TerminatorKind } ;
3
+ use rustc_middle:: ty:: { TyCtxt , TypeVisitable } ;
4
+ use rustc_mir_dataflow:: points:: PointIndex ;
2
5
3
6
use super :: { LocalizedOutlivesConstraint , LocalizedOutlivesConstraintSet } ;
4
7
use crate :: constraints:: OutlivesConstraint ;
5
8
use crate :: region_infer:: values:: LivenessValues ;
6
9
use crate :: type_check:: Locations ;
10
+ use crate :: universal_regions:: UniversalRegions ;
7
11
8
12
/// Propagate loans throughout the subset graph at a given point (with some subtleties around the
9
13
/// location where effects start to be visible).
10
14
pub ( super ) fn convert_typeck_constraints < ' tcx > (
15
+ tcx : TyCtxt < ' tcx > ,
11
16
body : & Body < ' tcx > ,
12
17
liveness : & LivenessValues ,
13
18
outlives_constraints : impl Iterator < Item = OutlivesConstraint < ' tcx > > ,
19
+ universal_regions : & UniversalRegions < ' tcx > ,
14
20
localized_outlives_constraints : & mut LocalizedOutlivesConstraintSet ,
15
21
) {
16
22
for outlives_constraint in outlives_constraints {
@@ -35,7 +41,191 @@ pub(super) fn convert_typeck_constraints<'tcx>(
35
41
}
36
42
}
37
43
38
- _ => { }
44
+ Locations :: Single ( location) => {
45
+ // This constraint is marked as holding at one location, we localize it to that
46
+ // location or its successor, depending on the corresponding MIR
47
+ // statement/terminator. Unfortunately, they all show up from typeck as coming "on
48
+ // entry", so for now we modify them to take effects that should apply "on exit"
49
+ // into account.
50
+ //
51
+ // FIXME: this approach is subtle, complicated, and hard to test, so we should track
52
+ // this information better in MIR typeck instead, for example with a new `Locations`
53
+ // variant that contains which node is crossing over between entry and exit.
54
+ let point = liveness. point_from_location ( location) ;
55
+ let ( from, to) = if let Some ( stmt) =
56
+ body[ location. block ] . statements . get ( location. statement_index )
57
+ {
58
+ localize_statement_constraint (
59
+ tcx,
60
+ body,
61
+ stmt,
62
+ liveness,
63
+ & outlives_constraint,
64
+ location,
65
+ point,
66
+ universal_regions,
67
+ )
68
+ } else {
69
+ assert_eq ! ( location. statement_index, body[ location. block] . statements. len( ) ) ;
70
+ let terminator = body[ location. block ] . terminator ( ) ;
71
+ localize_terminator_constraint (
72
+ tcx,
73
+ body,
74
+ terminator,
75
+ liveness,
76
+ & outlives_constraint,
77
+ point,
78
+ universal_regions,
79
+ )
80
+ } ;
81
+ localized_outlives_constraints. push ( LocalizedOutlivesConstraint {
82
+ source : outlives_constraint. sup ,
83
+ from,
84
+ target : outlives_constraint. sub ,
85
+ to,
86
+ } ) ;
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ /// For a given outlives constraint arising from a MIR statement, computes the CFG `from`-`to`
93
+ /// intra-block nodes to localize the constraint.
94
+ fn localize_statement_constraint < ' tcx > (
95
+ tcx : TyCtxt < ' tcx > ,
96
+ body : & Body < ' tcx > ,
97
+ stmt : & Statement < ' tcx > ,
98
+ liveness : & LivenessValues ,
99
+ outlives_constraint : & OutlivesConstraint < ' tcx > ,
100
+ current_location : Location ,
101
+ current_point : PointIndex ,
102
+ universal_regions : & UniversalRegions < ' tcx > ,
103
+ ) -> ( PointIndex , PointIndex ) {
104
+ match & stmt. kind {
105
+ StatementKind :: Assign ( box ( lhs, rhs) ) => {
106
+ // To create localized outlives constraints without midpoints, we rely on the property
107
+ // that no input regions from the RHS of the assignment will flow into themselves: they
108
+ // should not appear in the output regions in the LHS. We believe this to be true by
109
+ // construction of the MIR, via temporaries, and assert it here.
110
+ //
111
+ // We think we don't need midpoints because:
112
+ // - every LHS Place has a unique set of regions that don't appear elsewhere
113
+ // - this implies that for them to be part of the RHS, the same Place must be read and
114
+ // written
115
+ // - and that should be impossible in MIR
116
+ //
117
+ // When we have a more complete implementation in the future, tested with crater, etc,
118
+ // we can relax this to a debug assert instead, or remove it.
119
+ assert ! (
120
+ {
121
+ let mut lhs_regions = FxHashSet :: default ( ) ;
122
+ tcx. for_each_free_region( lhs, |region| {
123
+ let region = universal_regions. to_region_vid( region) ;
124
+ lhs_regions. insert( region) ;
125
+ } ) ;
126
+
127
+ let mut rhs_regions = FxHashSet :: default ( ) ;
128
+ tcx. for_each_free_region( rhs, |region| {
129
+ let region = universal_regions. to_region_vid( region) ;
130
+ rhs_regions. insert( region) ;
131
+ } ) ;
132
+
133
+ // The intersection between LHS and RHS regions should be empty.
134
+ lhs_regions. is_disjoint( & rhs_regions)
135
+ } ,
136
+ "there should be no common regions between the LHS and RHS of an assignment"
137
+ ) ;
138
+
139
+ // As mentioned earlier, we should be tracking these better upstream but: we want to
140
+ // relate the types on entry to the type of the place on exit. That is, outlives
141
+ // constraints on the RHS are on entry, and outlives constraints to/from the LHS are on
142
+ // exit (i.e. on entry to the successor location).
143
+ let lhs_ty = body. local_decls [ lhs. local ] . ty ;
144
+ let successor_location = Location {
145
+ block : current_location. block ,
146
+ statement_index : current_location. statement_index + 1 ,
147
+ } ;
148
+ let successor_point = liveness. point_from_location ( successor_location) ;
149
+ compute_constraint_direction (
150
+ tcx,
151
+ outlives_constraint,
152
+ & lhs_ty,
153
+ current_point,
154
+ successor_point,
155
+ universal_regions,
156
+ )
157
+ }
158
+ _ => {
159
+ // For the other cases, we localize an outlives constraint to where it arises.
160
+ ( current_point, current_point)
39
161
}
40
162
}
41
163
}
164
+
165
+ /// For a given outlives constraint arising from a MIR terminator, computes the CFG `from`-`to`
166
+ /// inter-block nodes to localize the constraint.
167
+ fn localize_terminator_constraint < ' tcx > (
168
+ tcx : TyCtxt < ' tcx > ,
169
+ body : & Body < ' tcx > ,
170
+ terminator : & Terminator < ' tcx > ,
171
+ liveness : & LivenessValues ,
172
+ outlives_constraint : & OutlivesConstraint < ' tcx > ,
173
+ current_point : PointIndex ,
174
+ universal_regions : & UniversalRegions < ' tcx > ,
175
+ ) -> ( PointIndex , PointIndex ) {
176
+ // FIXME: check if other terminators need the same handling as `Call`s, in particular
177
+ // Assert/Yield/Drop. A handful of tests are failing with Drop related issues, as well as some
178
+ // coroutine tests, and that may be why.
179
+ match & terminator. kind {
180
+ // FIXME: also handle diverging calls.
181
+ TerminatorKind :: Call { destination, target : Some ( target) , .. } => {
182
+ // Calls are similar to assignments, and thus follow the same pattern. If there is a
183
+ // target for the call we also relate what flows into the destination here to entry to
184
+ // that successor.
185
+ let destination_ty = destination. ty ( & body. local_decls , tcx) ;
186
+ let successor_location = Location { block : * target, statement_index : 0 } ;
187
+ let successor_point = liveness. point_from_location ( successor_location) ;
188
+ compute_constraint_direction (
189
+ tcx,
190
+ outlives_constraint,
191
+ & destination_ty,
192
+ current_point,
193
+ successor_point,
194
+ universal_regions,
195
+ )
196
+ }
197
+ _ => {
198
+ // Typeck constraints guide loans between regions at the current point, so we do that in
199
+ // the general case, and liveness will take care of making them flow to the terminator's
200
+ // successors.
201
+ ( current_point, current_point)
202
+ }
203
+ }
204
+ }
205
+
206
+ /// For a given constraint, returns the `from`-`to` edge according to whether the constraint flows
207
+ /// to or from a free region in the given `value`, some kind of result for an effectful operation,
208
+ /// like the LHS of an assignment.
209
+ fn compute_constraint_direction < ' tcx > (
210
+ tcx : TyCtxt < ' tcx > ,
211
+ outlives_constraint : & OutlivesConstraint < ' tcx > ,
212
+ value : & impl TypeVisitable < TyCtxt < ' tcx > > ,
213
+ current_point : PointIndex ,
214
+ successor_point : PointIndex ,
215
+ universal_regions : & UniversalRegions < ' tcx > ,
216
+ ) -> ( PointIndex , PointIndex ) {
217
+ let mut to = current_point;
218
+ let mut from = current_point;
219
+ tcx. for_each_free_region ( value, |region| {
220
+ let region = universal_regions. to_region_vid ( region) ;
221
+ if region == outlives_constraint. sub {
222
+ // This constraint flows into the result, its effects start becoming visible on exit.
223
+ to = successor_point;
224
+ } else if region == outlives_constraint. sup {
225
+ // This constraint flows from the result, its effects start becoming visible on exit.
226
+ from = successor_point;
227
+ }
228
+ } ) ;
229
+
230
+ ( from, to)
231
+ }
0 commit comments