Skip to content

Commit 426d173

Browse files
committed
Auto merge of rust-lang#134268 - lqd:polonius-next, r=jackh726
Foundations of location-sensitive polonius I'd like to land the prototype I'm describing in the [polonius project goal](rust-lang/rust-project-goals#118). It still is incomplete and naive and terrible but it's working "well enough" to consider landing. I'd also like to make review easier by not opening a huge PR, but have a couple small-ish ones (the +/- line change summary of this PR looks big, but >80% is moving datalog to a single place). This PR starts laying the foundation for that work: - it refactors and collects 99% of the old datalog fact gen, which was spread around everywhere, into a single dedicated module. It's still present at 3 small places (one of which we should revert anyways) that are kinda deep within localized components and are not as easily extractable into the rest of fact gen, so it's fine for now. - starts introducing the localized constraints, the building blocks of the naive way of implementing the location-sensitive analysis in-tree, which is roughly sketched out in https://smallcultfollowing.com/babysteps/blog/2023/09/22/polonius-part-1/ and https://smallcultfollowing.com/babysteps/blog/2023/09/29/polonius-part-2/ but with a different vibe than per-point environments described in these posts, just `r1@p: r2@q` constraints. - sets up the skeleton of generating these localized constraints: converting NLL typeck constraints, and creating liveness constraints - introduces the polonius dual to NLL MIR to help development and debugging. It doesn't do much currently but is a way to see these localized constraints: it's an NLL MIR dump + a dumb listing of the constraints, that can be dumped with `-Zdump-mir=polonius -Zpolonius=next`. Its current state is not intended to be a long-term thing, just for testing purposes -- I will replace its contents in the future with a different approach (an HTML+js file where we can more easily explore/filter/trace these constraints and loan reachability, have mermaid graphs of the usual graphviz dumps, etc). I've started documenting the approach in this PR, I'll add more in the future. It's quite simple, and should be very clear when more constraints are introduced anyways. r? `@matthewjasper` Best reviewed per commit so that the datalog move is less bothersome to read, but if you'd prefer we separate that into a different PR, I can do that (and michael has offered to review these more mechanical changes if it'd help).
2 parents 4c40c89 + aeb3d10 commit 426d173

File tree

8 files changed

+409
-34
lines changed

8 files changed

+409
-34
lines changed

compiler/rustc_borrowck/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ fn do_mir_borrowck<'tcx>(
202202
polonius_output,
203203
opt_closure_req,
204204
nll_errors,
205+
localized_outlives_constraints,
205206
} = nll::compute_regions(
206207
&infcx,
207208
free_regions,
@@ -315,6 +316,16 @@ fn do_mir_borrowck<'tcx>(
315316

316317
mbcx.report_move_errors();
317318

319+
// If requested, dump polonius MIR.
320+
polonius::dump_polonius_mir(
321+
&infcx,
322+
body,
323+
&regioncx,
324+
&borrow_set,
325+
localized_outlives_constraints,
326+
&opt_closure_req,
327+
);
328+
318329
// For each non-user used mutable variable, check if it's been assigned from
319330
// a user-declared local. If so, then put that local into the used_mut set.
320331
// Note that this set is expected to be small - only upvars from closures

compiler/rustc_borrowck/src/nll.rs

+60-34
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::consumers::ConsumerOptions;
2929
use crate::diagnostics::RegionErrors;
3030
use crate::facts::{AllFacts, AllFactsExt, RustcFacts};
3131
use crate::location::LocationTable;
32+
use crate::polonius::LocalizedOutlivesConstraintSet;
3233
use crate::region_infer::RegionInferenceContext;
3334
use crate::type_check::{self, MirTypeckResults};
3435
use crate::universal_regions::UniversalRegions;
@@ -45,6 +46,9 @@ pub(crate) struct NllOutput<'tcx> {
4546
pub polonius_output: Option<Box<PoloniusOutput>>,
4647
pub opt_closure_req: Option<ClosureRegionRequirements<'tcx>>,
4748
pub nll_errors: RegionErrors<'tcx>,
49+
50+
/// When using `-Zpolonius=next`: the localized typeck and liveness constraints.
51+
pub localized_outlives_constraints: Option<LocalizedOutlivesConstraintSet>,
4852
}
4953

5054
/// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal
@@ -135,6 +139,15 @@ pub(crate) fn compute_regions<'a, 'tcx>(
135139
elements,
136140
);
137141

142+
// If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives
143+
// 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+
};
150+
138151
// If requested: dump NLL facts, and run legacy polonius analysis.
139152
let polonius_output = all_facts.as_ref().and_then(|all_facts| {
140153
if infcx.tcx.sess.opts.unstable_opts.nll_facts {
@@ -175,6 +188,7 @@ pub(crate) fn compute_regions<'a, 'tcx>(
175188
polonius_output,
176189
opt_closure_req: closure_region_requirements,
177190
nll_errors,
191+
localized_outlives_constraints,
178192
}
179193
}
180194

@@ -215,40 +229,7 @@ pub(super) fn dump_nll_mir<'tcx>(
215229
&0,
216230
body,
217231
|pass_where, out| {
218-
match pass_where {
219-
// Before the CFG, dump out the values for each region variable.
220-
PassWhere::BeforeCFG => {
221-
regioncx.dump_mir(tcx, out)?;
222-
writeln!(out, "|")?;
223-
224-
if let Some(closure_region_requirements) = closure_region_requirements {
225-
writeln!(out, "| Free Region Constraints")?;
226-
for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
227-
writeln!(out, "| {msg}")
228-
})?;
229-
writeln!(out, "|")?;
230-
}
231-
232-
if borrow_set.len() > 0 {
233-
writeln!(out, "| Borrows")?;
234-
for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() {
235-
writeln!(
236-
out,
237-
"| {:?}: issued at {:?} in {:?}",
238-
borrow_idx, borrow_data.reserve_location, borrow_data.region
239-
)?;
240-
}
241-
writeln!(out, "|")?;
242-
}
243-
}
244-
245-
PassWhere::BeforeLocation(_) => {}
246-
247-
PassWhere::AfterTerminator(_) => {}
248-
249-
PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {}
250-
}
251-
Ok(())
232+
emit_nll_mir(tcx, regioncx, closure_region_requirements, borrow_set, pass_where, out)
252233
},
253234
options,
254235
);
@@ -266,6 +247,51 @@ pub(super) fn dump_nll_mir<'tcx>(
266247
};
267248
}
268249

250+
/// Produces the actual NLL MIR sections to emit during the dumping process.
251+
pub(crate) fn emit_nll_mir<'tcx>(
252+
tcx: TyCtxt<'tcx>,
253+
regioncx: &RegionInferenceContext<'tcx>,
254+
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
255+
borrow_set: &BorrowSet<'tcx>,
256+
pass_where: PassWhere,
257+
out: &mut dyn io::Write,
258+
) -> io::Result<()> {
259+
match pass_where {
260+
// Before the CFG, dump out the values for each region variable.
261+
PassWhere::BeforeCFG => {
262+
regioncx.dump_mir(tcx, out)?;
263+
writeln!(out, "|")?;
264+
265+
if let Some(closure_region_requirements) = closure_region_requirements {
266+
writeln!(out, "| Free Region Constraints")?;
267+
for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
268+
writeln!(out, "| {msg}")
269+
})?;
270+
writeln!(out, "|")?;
271+
}
272+
273+
if borrow_set.len() > 0 {
274+
writeln!(out, "| Borrows")?;
275+
for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() {
276+
writeln!(
277+
out,
278+
"| {:?}: issued at {:?} in {:?}",
279+
borrow_idx, borrow_data.reserve_location, borrow_data.region
280+
)?;
281+
}
282+
writeln!(out, "|")?;
283+
}
284+
}
285+
286+
PassWhere::BeforeLocation(_) => {}
287+
288+
PassWhere::AfterTerminator(_) => {}
289+
290+
PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {}
291+
}
292+
Ok(())
293+
}
294+
269295
#[allow(rustc::diagnostic_outside_of_impl)]
270296
#[allow(rustc::untranslatable_diagnostic)]
271297
pub(super) fn dump_annotation<'tcx, 'infcx>(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use rustc_middle::ty::RegionVid;
2+
use rustc_mir_dataflow::points::PointIndex;
3+
4+
/// A localized outlives constraint reifies the CFG location where the outlives constraint holds,
5+
/// within the origins themselves as if they were different from point to point: from `a: b`
6+
/// outlives constraints to `a@p: b@p`, where `p` is the point in the CFG.
7+
///
8+
/// This models two sources of constraints:
9+
/// - constraints that traverse the subsets between regions at a given point, `a@p: b@p`. These
10+
/// depend on typeck constraints generated via assignments, calls, etc. (In practice there are
11+
/// subtleties where a statement's effect only starts being visible at the successor point, via
12+
/// the "result" of that statement).
13+
/// - constraints that traverse the CFG via the same region, `a@p: a@q`, where `p` is a predecessor
14+
/// of `q`. These depend on the liveness of the regions at these points, as well as their
15+
/// variance.
16+
///
17+
/// The `source` origin at `from` flows into the `target` origin at `to`.
18+
///
19+
/// This dual of NLL's [crate::constraints::OutlivesConstraint] therefore encodes the
20+
/// position-dependent outlives constraints used by Polonius, to model the flow-sensitive loan
21+
/// propagation via reachability within a graph of localized constraints.
22+
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
23+
pub(crate) struct LocalizedOutlivesConstraint {
24+
pub source: RegionVid,
25+
pub from: PointIndex,
26+
pub target: RegionVid,
27+
pub to: PointIndex,
28+
}
29+
30+
/// A container of [LocalizedOutlivesConstraint]s that can be turned into a traversable
31+
/// `rustc_data_structures` graph.
32+
#[derive(Clone, Default, Debug)]
33+
pub(crate) struct LocalizedOutlivesConstraintSet {
34+
pub outlives: Vec<LocalizedOutlivesConstraint>,
35+
}
36+
37+
impl LocalizedOutlivesConstraintSet {
38+
pub(crate) fn push(&mut self, constraint: LocalizedOutlivesConstraint) {
39+
if constraint.source == constraint.target && constraint.from == constraint.to {
40+
// 'a@p: 'a@p is pretty uninteresting
41+
return;
42+
}
43+
self.outlives.push(constraint);
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use std::io;
2+
3+
use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
4+
use rustc_middle::mir::{Body, ClosureRegionRequirements, PassWhere};
5+
use rustc_middle::ty::TyCtxt;
6+
use rustc_session::config::MirIncludeSpans;
7+
8+
use crate::borrow_set::BorrowSet;
9+
use crate::polonius::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet};
10+
use crate::{BorrowckInferCtxt, RegionInferenceContext};
11+
12+
/// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information.
13+
// Note: this currently duplicates most of NLL MIR, with some additions for the localized outlives
14+
// constraints. This is ok for now as this dump will change in the near future to an HTML file to
15+
// become more useful.
16+
pub(crate) fn dump_polonius_mir<'tcx>(
17+
infcx: &BorrowckInferCtxt<'tcx>,
18+
body: &Body<'tcx>,
19+
regioncx: &RegionInferenceContext<'tcx>,
20+
borrow_set: &BorrowSet<'tcx>,
21+
localized_outlives_constraints: Option<LocalizedOutlivesConstraintSet>,
22+
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
23+
) {
24+
let tcx = infcx.tcx;
25+
if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {
26+
return;
27+
}
28+
29+
let localized_outlives_constraints = localized_outlives_constraints
30+
.expect("missing localized constraints with `-Zpolonius=next`");
31+
32+
// We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
33+
// #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
34+
// they're always disabled in mir-opt tests to make working with blessed dumps easier.
35+
let options = PrettyPrintMirOptions {
36+
include_extra_comments: matches!(
37+
tcx.sess.opts.unstable_opts.mir_include_spans,
38+
MirIncludeSpans::On | MirIncludeSpans::Nll
39+
),
40+
};
41+
42+
dump_mir_with_options(
43+
tcx,
44+
false,
45+
"polonius",
46+
&0,
47+
body,
48+
|pass_where, out| {
49+
emit_polonius_mir(
50+
tcx,
51+
regioncx,
52+
closure_region_requirements,
53+
borrow_set,
54+
&localized_outlives_constraints,
55+
pass_where,
56+
out,
57+
)
58+
},
59+
options,
60+
);
61+
}
62+
63+
/// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
64+
fn emit_polonius_mir<'tcx>(
65+
tcx: TyCtxt<'tcx>,
66+
regioncx: &RegionInferenceContext<'tcx>,
67+
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
68+
borrow_set: &BorrowSet<'tcx>,
69+
localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
70+
pass_where: PassWhere,
71+
out: &mut dyn io::Write,
72+
) -> io::Result<()> {
73+
// Emit the regular NLL front-matter
74+
crate::nll::emit_nll_mir(
75+
tcx,
76+
regioncx,
77+
closure_region_requirements,
78+
borrow_set,
79+
pass_where.clone(),
80+
out,
81+
)?;
82+
83+
let liveness = regioncx.liveness_constraints();
84+
85+
// Add localized outlives constraints
86+
match pass_where {
87+
PassWhere::BeforeCFG => {
88+
if localized_outlives_constraints.outlives.len() > 0 {
89+
writeln!(out, "| Localized constraints")?;
90+
91+
for constraint in &localized_outlives_constraints.outlives {
92+
let LocalizedOutlivesConstraint { source, from, target, to } = constraint;
93+
let from = liveness.location_from_point(*from);
94+
let to = liveness.location_from_point(*to);
95+
writeln!(out, "| {source:?} at {from:?} -> {target:?} at {to:?}")?;
96+
}
97+
writeln!(out, "|")?;
98+
}
99+
}
100+
_ => {}
101+
}
102+
103+
Ok(())
104+
}

0 commit comments

Comments
 (0)