Skip to content

Commit 8cdc67e

Browse files
committed
Auto merge of #134670 - lqd:polonius-next-episode-4, r=jackh726
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 6cd33d8 + 089c525 commit 8cdc67e

File tree

9 files changed

+498
-128
lines changed

9 files changed

+498
-128
lines changed

compiler/rustc_borrowck/src/nll.rs

+20-19
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| {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
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+
//
235+
// Note: even if we currently bail for two cases of unexpected region kinds here, missing
236+
// variance data is not a soundness problem: the regions with missing variance will still be
237+
// present in the constraint graph as they are live, and liveness edges construction has a
238+
// fallback for this case.
239+
//
240+
// FIXME: that being said, we need to investigate these cases better to not ignore regions
241+
// in general.
242+
if region.is_bound() {
243+
// We ignore these because they cannot be turned into the vids we need.
244+
return;
245+
}
246+
247+
if region.is_erased() {
248+
// These cannot be turned into a vid either, and we also ignore them: the fact that they
249+
// show up here looks like either an issue upstream or a combination with unexpectedly
250+
// continuing compilation too far when we're in a tainted by errors situation.
251+
//
252+
// FIXME: investigate the `generic_const_exprs` test that triggers this issue,
253+
// `ui/const-generics/generic_const_exprs/issue-97047-ice-2.rs`
254+
return;
255+
}
256+
257+
let direction = match variance {
258+
ty::Covariant => ConstraintDirection::Forward,
259+
ty::Contravariant => ConstraintDirection::Backward,
260+
ty::Invariant => ConstraintDirection::Bidirectional,
261+
ty::Bivariant => {
262+
// We don't add edges for bivariant cases.
263+
return;
264+
}
265+
};
266+
267+
let region = self.universal_regions.to_region_vid(region);
268+
self.directions
269+
.entry(region)
270+
.and_modify(|entry| {
271+
// If there's already a recorded direction for this region, we combine the two:
272+
// - combining the same direction is idempotent
273+
// - combining different directions is trivially bidirectional
274+
if entry != &direction {
275+
*entry = ConstraintDirection::Bidirectional;
276+
}
277+
})
278+
.or_insert(direction);
279+
}
280+
}
281+
282+
impl<'tcx> TypeRelation<TyCtxt<'tcx>> for VarianceExtractor<'_, 'tcx> {
283+
fn cx(&self) -> TyCtxt<'tcx> {
284+
self.tcx
285+
}
286+
287+
fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>(
288+
&mut self,
289+
variance: ty::Variance,
290+
_info: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
291+
a: T,
292+
b: T,
293+
) -> RelateResult<'tcx, T> {
294+
let old_ambient_variance = self.ambient_variance;
295+
self.ambient_variance = self.ambient_variance.xform(variance);
296+
let r = self.relate(a, b)?;
297+
self.ambient_variance = old_ambient_variance;
298+
Ok(r)
299+
}
300+
301+
fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
302+
assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
303+
relate::structurally_relate_tys(self, a, b)
304+
}
305+
306+
fn regions(
307+
&mut self,
308+
a: ty::Region<'tcx>,
309+
b: ty::Region<'tcx>,
310+
) -> RelateResult<'tcx, ty::Region<'tcx>> {
311+
assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
312+
self.record_variance(a, self.ambient_variance);
313+
Ok(a)
314+
}
315+
316+
fn consts(
317+
&mut self,
318+
a: ty::Const<'tcx>,
319+
b: ty::Const<'tcx>,
320+
) -> RelateResult<'tcx, ty::Const<'tcx>> {
321+
assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
322+
relate::structurally_relate_consts(self, a, b)
323+
}
324+
325+
fn binders<T>(
326+
&mut self,
327+
a: ty::Binder<'tcx, T>,
328+
_: ty::Binder<'tcx, T>,
329+
) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
330+
where
331+
T: Relate<TyCtxt<'tcx>>,
332+
{
333+
self.relate(a.skip_binder(), a.skip_binder())?;
334+
Ok(a)
335+
}
336+
}

0 commit comments

Comments
 (0)