Skip to content

Commit 46154b2

Browse files
committed
localize typeck constraints
it's still partially a skeleton, but works well enough for almost all tests to pass
1 parent eb7da16 commit 46154b2

File tree

3 files changed

+199
-6
lines changed

3 files changed

+199
-6
lines changed

compiler/rustc_borrowck/src/nll.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ pub(crate) fn compute_regions<'a, 'tcx>(
142142

143143
// If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives
144144
// constraints.
145-
let localized_outlives_constraints = polonius_context
146-
.as_mut()
147-
.map(|polonius_context| polonius_context.create_localized_constraints(&mut regioncx, body));
145+
let localized_outlives_constraints = polonius_context.as_mut().map(|polonius_context| {
146+
polonius_context.create_localized_constraints(infcx.tcx, &regioncx, body)
147+
});
148148

149149
// If requested: dump NLL facts, and run legacy polonius analysis.
150150
let polonius_output = all_facts.as_ref().and_then(|all_facts| {

compiler/rustc_borrowck/src/polonius/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use std::collections::BTreeMap;
4343

4444
use rustc_index::bit_set::SparseBitMatrix;
4545
use rustc_middle::mir::Body;
46-
use rustc_middle::ty::RegionVid;
46+
use rustc_middle::ty::{RegionVid, TyCtxt};
4747
use rustc_mir_dataflow::points::PointIndex;
4848

4949
pub(crate) use self::constraints::*;
@@ -87,14 +87,17 @@ impl PoloniusContext {
8787
/// - encoding liveness constraints
8888
pub(crate) fn create_localized_constraints<'tcx>(
8989
&self,
90+
tcx: TyCtxt<'tcx>,
9091
regioncx: &RegionInferenceContext<'tcx>,
9192
body: &Body<'tcx>,
9293
) -> LocalizedOutlivesConstraintSet {
9394
let mut localized_outlives_constraints = LocalizedOutlivesConstraintSet::default();
9495
convert_typeck_constraints(
96+
tcx,
9597
body,
9698
regioncx.liveness_constraints(),
9799
regioncx.outlives_constraints(),
100+
regioncx.universal_regions(),
98101
&mut localized_outlives_constraints,
99102
);
100103

compiler/rustc_borrowck/src/polonius/typeck_constraints.rs

+192-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
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;
25

36
use super::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet};
47
use crate::constraints::OutlivesConstraint;
58
use crate::region_infer::values::LivenessValues;
69
use crate::type_check::Locations;
10+
use crate::universal_regions::UniversalRegions;
711

812
/// Propagate loans throughout the subset graph at a given point (with some subtleties around the
913
/// location where effects start to be visible).
1014
pub(super) fn convert_typeck_constraints<'tcx>(
15+
tcx: TyCtxt<'tcx>,
1116
body: &Body<'tcx>,
1217
liveness: &LivenessValues,
1318
outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
19+
universal_regions: &UniversalRegions<'tcx>,
1420
localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
1521
) {
1622
for outlives_constraint in outlives_constraints {
@@ -35,7 +41,191 @@ pub(super) fn convert_typeck_constraints<'tcx>(
3541
}
3642
}
3743

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)
39161
}
40162
}
41163
}
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

Comments
 (0)