Skip to content

Commit df62f54

Browse files
committed
Auto merge of #134670 - lqd:polonius-next-episode-4, r=<try>
Compute liveness constraints in location-sensitive polonius This continues the location-sensitive prototype. In this episode, we build the liveness constraints. Reminder of the approach we're taking: we need variance data to create liveness edges in the forward/backward/both directions (respectively in the cases of covariance, contravariance, invariance) in the localized constraint graph. This PR: - introduces the holder for that, and for the liveness data in the correct shape: the transpose of what we're using today, "live regions per points". - records use/drop live region variance during tracing - records regular live region variance at the end of liveness - records the correctly shaped live region per point matrix - uses all of the above to compute the liveness constraints (There's still technically one tiny part of the liveness owl left to do, but I'll leave it for a future PR: we also need to disable the NLL optimization that avoids computing liveness for locals whose types contain a region outliving a free region -- the existing constraints make it effectively live at all points; this doesn't work under polonius) r? `@jackh726` cc `@matthewjasper`
2 parents e108481 + 47d46ae commit df62f54

File tree

9 files changed

+495
-134
lines changed

9 files changed

+495
-134
lines changed

compiler/rustc_borrowck/src/nll.rs

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,23 @@ pub(crate) fn compute_regions<'a, 'tcx>(
100100
let elements = Rc::new(DenseLocationMap::new(body));
101101

102102
// Run the MIR type-checker.
103-
let MirTypeckResults { constraints, universal_region_relations, opaque_type_values } =
104-
type_check::type_check(
105-
infcx,
106-
body,
107-
promoted,
108-
universal_regions,
109-
location_table,
110-
borrow_set,
111-
&mut all_facts,
112-
flow_inits,
113-
move_data,
114-
Rc::clone(&elements),
115-
);
103+
let MirTypeckResults {
104+
constraints,
105+
universal_region_relations,
106+
opaque_type_values,
107+
mut polonius_context,
108+
} = type_check::type_check(
109+
infcx,
110+
body,
111+
promoted,
112+
universal_regions,
113+
location_table,
114+
borrow_set,
115+
&mut all_facts,
116+
flow_inits,
117+
move_data,
118+
Rc::clone(&elements),
119+
);
116120

117121
// Create the region inference context, taking ownership of the
118122
// region inference data that was contained in `infcx`, and the
@@ -141,12 +145,9 @@ pub(crate) fn compute_regions<'a, 'tcx>(
141145

142146
// If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives
143147
// constraints.
144-
let localized_outlives_constraints =
145-
if infcx.tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {
146-
Some(polonius::create_localized_constraints(&mut regioncx, body))
147-
} else {
148-
None
149-
};
148+
let localized_outlives_constraints = polonius_context
149+
.as_mut()
150+
.map(|polonius_context| polonius_context.create_localized_constraints(&mut regioncx, body));
150151

151152
// If requested: dump NLL facts, and run legacy polonius analysis.
152153
let polonius_output = all_facts.as_ref().and_then(|all_facts| {
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
use std::collections::BTreeMap;
2+
3+
use rustc_index::bit_set::SparseBitMatrix;
4+
use rustc_index::interval::SparseIntervalMatrix;
5+
use rustc_middle::mir::{Body, Location};
6+
use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation};
7+
use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeVisitable};
8+
use rustc_mir_dataflow::points::PointIndex;
9+
10+
use super::{
11+
ConstraintDirection, LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet,
12+
PoloniusContext,
13+
};
14+
use crate::region_infer::values::LivenessValues;
15+
use crate::universal_regions::UniversalRegions;
16+
17+
impl PoloniusContext {
18+
/// Record the variance of each region contained within the given value.
19+
pub(crate) fn record_live_region_variance<'tcx>(
20+
&mut self,
21+
tcx: TyCtxt<'tcx>,
22+
universal_regions: &UniversalRegions<'tcx>,
23+
value: impl TypeVisitable<TyCtxt<'tcx>> + Relate<TyCtxt<'tcx>>,
24+
) {
25+
let mut extractor = VarianceExtractor {
26+
tcx,
27+
ambient_variance: ty::Variance::Covariant,
28+
directions: &mut self.live_region_variances,
29+
universal_regions,
30+
};
31+
extractor.relate(value, value).expect("Can't have a type error relating to itself");
32+
}
33+
34+
/// Unlike NLLs, in polonius we traverse the cfg to look for regions live across an edge, so we
35+
/// need to transpose the "points where each region is live" matrix to a "live regions per point"
36+
/// matrix.
37+
// FIXME: avoid this conversion by always storing liveness data in this shape in the rest of
38+
// borrowck.
39+
pub(crate) fn record_live_regions_per_point(
40+
&mut self,
41+
num_regions: usize,
42+
points_per_live_region: &SparseIntervalMatrix<RegionVid, PointIndex>,
43+
) {
44+
let mut live_regions_per_point = SparseBitMatrix::new(num_regions);
45+
for region in points_per_live_region.rows() {
46+
for point in points_per_live_region.row(region).unwrap().iter() {
47+
live_regions_per_point.insert(point, region);
48+
}
49+
}
50+
self.live_regions = Some(live_regions_per_point);
51+
}
52+
}
53+
54+
/// Propagate loans throughout the CFG: for each statement in the MIR, create localized outlives
55+
/// constraints for loans that are propagated to the next statements.
56+
pub(super) fn create_liveness_constraints<'tcx>(
57+
body: &Body<'tcx>,
58+
liveness: &LivenessValues,
59+
live_regions: &SparseBitMatrix<PointIndex, RegionVid>,
60+
live_region_variances: &BTreeMap<RegionVid, ConstraintDirection>,
61+
universal_regions: &UniversalRegions<'tcx>,
62+
localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
63+
) {
64+
for (block, bb) in body.basic_blocks.iter_enumerated() {
65+
let statement_count = bb.statements.len();
66+
for statement_index in 0..=statement_count {
67+
let current_location = Location { block, statement_index };
68+
let current_point = liveness.point_from_location(current_location);
69+
70+
if statement_index < statement_count {
71+
// Intra-block edges, straight line constraints from each point to its successor
72+
// within the same block.
73+
let next_location = Location { block, statement_index: statement_index + 1 };
74+
let next_point = liveness.point_from_location(next_location);
75+
propagate_loans_between_points(
76+
current_point,
77+
next_point,
78+
live_regions,
79+
live_region_variances,
80+
universal_regions,
81+
localized_outlives_constraints,
82+
);
83+
} else {
84+
// Inter-block edges, from the block's terminator to each successor block's entry
85+
// point.
86+
for successor_block in bb.terminator().successors() {
87+
let next_location = Location { block: successor_block, statement_index: 0 };
88+
let next_point = liveness.point_from_location(next_location);
89+
propagate_loans_between_points(
90+
current_point,
91+
next_point,
92+
live_regions,
93+
live_region_variances,
94+
universal_regions,
95+
localized_outlives_constraints,
96+
);
97+
}
98+
}
99+
}
100+
}
101+
}
102+
103+
/// Propagate loans within a region between two points in the CFG, if that region is live at both
104+
/// the source and target points.
105+
fn propagate_loans_between_points(
106+
current_point: PointIndex,
107+
next_point: PointIndex,
108+
live_regions: &SparseBitMatrix<PointIndex, RegionVid>,
109+
live_region_variances: &BTreeMap<RegionVid, ConstraintDirection>,
110+
universal_regions: &UniversalRegions<'_>,
111+
localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
112+
) {
113+
// Universal regions are semantically live at all points.
114+
// Note: we always have universal regions but they're not always (or often) involved in the
115+
// subset graph. For now, we emit all their edges unconditionally, but some of these subgraphs
116+
// will be disconnected from the rest of the graph and thus, unnecessary.
117+
//
118+
// FIXME: only emit the edges of universal regions that existential regions can reach.
119+
for region in universal_regions.universal_regions_iter() {
120+
localized_outlives_constraints.push(LocalizedOutlivesConstraint {
121+
source: region,
122+
from: current_point,
123+
target: region,
124+
to: next_point,
125+
});
126+
}
127+
128+
let Some(current_live_regions) = live_regions.row(current_point) else {
129+
// There are no constraints to add: there are no live regions at the current point.
130+
return;
131+
};
132+
let Some(next_live_regions) = live_regions.row(next_point) else {
133+
// There are no constraints to add: there are no live regions at the next point.
134+
return;
135+
};
136+
137+
for region in next_live_regions.iter() {
138+
if !current_live_regions.contains(region) {
139+
continue;
140+
}
141+
142+
// `region` is indeed live at both points, add a constraint between them, according to
143+
// variance.
144+
if let Some(&direction) = live_region_variances.get(&region) {
145+
add_liveness_constraint(
146+
region,
147+
current_point,
148+
next_point,
149+
direction,
150+
localized_outlives_constraints,
151+
);
152+
} else {
153+
// Note: there currently are cases related to promoted and const generics, where we
154+
// don't yet have variance information (possibly about temporary regions created when
155+
// typeck sanitizes the promoteds). Until that is done, we conservatively fallback to
156+
// maximizing reachability by adding a bidirectional edge here. This will not limit
157+
// traversal whatsoever, and thus propagate liveness when needed.
158+
//
159+
// FIXME: add the missing variance information and remove this fallback bidirectional
160+
// edge.
161+
let fallback = ConstraintDirection::Bidirectional;
162+
add_liveness_constraint(
163+
region,
164+
current_point,
165+
next_point,
166+
fallback,
167+
localized_outlives_constraints,
168+
);
169+
}
170+
}
171+
}
172+
173+
/// Adds `LocalizedOutlivesConstraint`s between two connected points, according to the given edge
174+
/// direction.
175+
fn add_liveness_constraint(
176+
region: RegionVid,
177+
current_point: PointIndex,
178+
next_point: PointIndex,
179+
direction: ConstraintDirection,
180+
localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
181+
) {
182+
match direction {
183+
ConstraintDirection::Forward => {
184+
// Covariant cases: loans flow in the regular direction, from the current point to the
185+
// next point.
186+
localized_outlives_constraints.push(LocalizedOutlivesConstraint {
187+
source: region,
188+
from: current_point,
189+
target: region,
190+
to: next_point,
191+
});
192+
}
193+
ConstraintDirection::Backward => {
194+
// Contravariant cases: loans flow in the inverse direction, from the next point to the
195+
// current point.
196+
localized_outlives_constraints.push(LocalizedOutlivesConstraint {
197+
source: region,
198+
from: next_point,
199+
target: region,
200+
to: current_point,
201+
});
202+
}
203+
ConstraintDirection::Bidirectional => {
204+
// For invariant cases, loans can flow in both directions: we add both edges.
205+
localized_outlives_constraints.push(LocalizedOutlivesConstraint {
206+
source: region,
207+
from: current_point,
208+
target: region,
209+
to: next_point,
210+
});
211+
localized_outlives_constraints.push(LocalizedOutlivesConstraint {
212+
source: region,
213+
from: next_point,
214+
target: region,
215+
to: current_point,
216+
});
217+
}
218+
}
219+
}
220+
221+
/// Extracts variances for regions contained within types. Follows the same structure as
222+
/// `rustc_infer`'s `Generalizer`: we try to relate a type with itself to track and extract the
223+
/// variances of regions.
224+
struct VarianceExtractor<'a, 'tcx> {
225+
tcx: TyCtxt<'tcx>,
226+
ambient_variance: ty::Variance,
227+
directions: &'a mut BTreeMap<RegionVid, ConstraintDirection>,
228+
universal_regions: &'a UniversalRegions<'tcx>,
229+
}
230+
231+
impl<'tcx> VarianceExtractor<'_, 'tcx> {
232+
fn record_variance(&mut self, region: ty::Region<'tcx>, variance: ty::Variance) {
233+
// We're only interested in the variance of vars and free regions.
234+
let Some(region) = self.universal_regions.try_to_region_vid(region) else {
235+
return;
236+
};
237+
238+
let direction = match variance {
239+
ty::Variance::Covariant => ConstraintDirection::Forward,
240+
ty::Variance::Contravariant => ConstraintDirection::Backward,
241+
ty::Variance::Invariant => ConstraintDirection::Bidirectional,
242+
ty::Variance::Bivariant => {
243+
// We don't add edges for bivariant cases.
244+
return;
245+
}
246+
};
247+
248+
self.directions
249+
.entry(region)
250+
.and_modify(|entry| {
251+
// If there's already a recorded direction for this region, we combine the two:
252+
// - combining the same direction is idempotent
253+
// - combining different directions is trivially bidirectional
254+
if entry != &direction {
255+
*entry = ConstraintDirection::Bidirectional;
256+
}
257+
})
258+
.or_insert(direction);
259+
}
260+
}
261+
262+
impl<'tcx> TypeRelation<TyCtxt<'tcx>> for VarianceExtractor<'_, 'tcx> {
263+
fn cx(&self) -> TyCtxt<'tcx> {
264+
self.tcx
265+
}
266+
267+
fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>(
268+
&mut self,
269+
variance: ty::Variance,
270+
_info: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
271+
a: T,
272+
b: T,
273+
) -> RelateResult<'tcx, T> {
274+
let old_ambient_variance = self.ambient_variance;
275+
self.ambient_variance = self.ambient_variance.xform(variance);
276+
let r = self.relate(a, b)?;
277+
self.ambient_variance = old_ambient_variance;
278+
Ok(r)
279+
}
280+
281+
fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
282+
assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
283+
relate::structurally_relate_tys(self, a, b)
284+
}
285+
286+
fn regions(
287+
&mut self,
288+
a: ty::Region<'tcx>,
289+
b: ty::Region<'tcx>,
290+
) -> RelateResult<'tcx, ty::Region<'tcx>> {
291+
assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
292+
self.record_variance(a, self.ambient_variance);
293+
Ok(a)
294+
}
295+
296+
fn consts(
297+
&mut self,
298+
a: ty::Const<'tcx>,
299+
b: ty::Const<'tcx>,
300+
) -> RelateResult<'tcx, ty::Const<'tcx>> {
301+
assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
302+
relate::structurally_relate_consts(self, a, b)
303+
}
304+
305+
fn binders<T>(
306+
&mut self,
307+
a: ty::Binder<'tcx, T>,
308+
_: ty::Binder<'tcx, T>,
309+
) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
310+
where
311+
T: Relate<TyCtxt<'tcx>>,
312+
{
313+
let result = self.relate(a.skip_binder(), a.skip_binder())?;
314+
Ok(a.rebind(result))
315+
}
316+
}

0 commit comments

Comments
 (0)