Skip to content

Commit 05441ab

Browse files
committed
add closure requirement tests, improve debugging output
The overall format is now easier to read. Also, There is now graphviz output, as well as a `#[rustc_regions]` annotation that dumps internal state.
1 parent ab1c1bc commit 05441ab

File tree

51 files changed

+1702
-77
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1702
-77
lines changed

src/librustc_mir/borrow_check/nll/mod.rs

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc::infer::InferCtxt;
1414
use rustc::ty::{self, RegionKind, RegionVid};
1515
use rustc::util::nodemap::FxHashMap;
1616
use std::collections::BTreeSet;
17+
use std::io;
1718
use transform::MirSource;
1819
use transform::type_check;
1920
use util::liveness::{self, LivenessMode, LivenessResult, LocalSet};
@@ -22,6 +23,7 @@ use dataflow::MaybeInitializedLvals;
2223
use dataflow::move_paths::MoveData;
2324

2425
use util as mir_util;
26+
use util::pretty::{self, ALIGN};
2527
use self::mir_util::PassWhere;
2628

2729
mod constraint_generation;
@@ -117,8 +119,19 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
117119
let closure_region_requirements = regioncx.solve(infcx, &mir, def_id);
118120

119121
// Dump MIR results into a file, if that is enabled. This let us
120-
// write unit-tests.
121-
dump_mir_results(infcx, liveness, MirSource::item(def_id), &mir, &regioncx);
122+
// write unit-tests, as well as helping with debugging.
123+
dump_mir_results(
124+
infcx,
125+
liveness,
126+
MirSource::item(def_id),
127+
&mir,
128+
&regioncx,
129+
&closure_region_requirements,
130+
);
131+
132+
// We also have a `#[rustc_nll]` annotation that causes us to dump
133+
// information
134+
dump_annotation(infcx, &mir, def_id, &regioncx, &closure_region_requirements);
122135

123136
(regioncx, closure_region_requirements)
124137
}
@@ -134,6 +147,7 @@ fn dump_mir_results<'a, 'gcx, 'tcx>(
134147
source: MirSource,
135148
mir: &Mir<'tcx>,
136149
regioncx: &RegionInferenceContext,
150+
closure_region_requirements: &Option<ClosureRegionRequirements>,
137151
) {
138152
if !mir_util::dump_enabled(infcx.tcx, "nll", source) {
139153
return;
@@ -168,9 +182,17 @@ fn dump_mir_results<'a, 'gcx, 'tcx>(
168182
mir_util::dump_mir(infcx.tcx, None, "nll", &0, source, mir, |pass_where, out| {
169183
match pass_where {
170184
// Before the CFG, dump out the values for each region variable.
171-
PassWhere::BeforeCFG => for region in regioncx.regions() {
172-
writeln!(out, "| {:?}: {}", region, regioncx.region_value_str(region))?;
173-
},
185+
PassWhere::BeforeCFG => {
186+
regioncx.dump_mir(out)?;
187+
188+
if let Some(closure_region_requirements) = closure_region_requirements {
189+
writeln!(out, "|")?;
190+
writeln!(out, "| Free Region Constraints")?;
191+
for_each_region_constraint(closure_region_requirements, &mut |msg| {
192+
writeln!(out, "| {}", msg)
193+
})?;
194+
}
195+
}
174196

175197
// Before each basic block, dump out the values
176198
// that are live on entry to the basic block.
@@ -184,13 +206,90 @@ fn dump_mir_results<'a, 'gcx, 'tcx>(
184206
&regular_liveness_per_location[&location],
185207
&drop_liveness_per_location[&location],
186208
);
187-
writeln!(out, " | Live variables at {:?}: {}", location, s)?;
209+
writeln!(
210+
out,
211+
"{:ALIGN$} | Live variables on entry to {:?}: {}",
212+
"",
213+
location,
214+
s,
215+
ALIGN = ALIGN
216+
)?;
188217
}
189218

190219
PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {}
191220
}
192221
Ok(())
193222
});
223+
224+
// Also dump the inference graph constraints as a graphviz file.
225+
let _: io::Result<()> = do catch {
226+
let mut file =
227+
pretty::create_dump_file(infcx.tcx, "regioncx.dot", None, "nll", &0, source)?;
228+
regioncx.dump_graphviz(&mut file)
229+
};
230+
}
231+
232+
fn dump_annotation<'a, 'gcx, 'tcx>(
233+
infcx: &InferCtxt<'a, 'gcx, 'tcx>,
234+
mir: &Mir<'tcx>,
235+
mir_def_id: DefId,
236+
regioncx: &RegionInferenceContext,
237+
closure_region_requirements: &Option<ClosureRegionRequirements>,
238+
) {
239+
let tcx = infcx.tcx;
240+
let base_def_id = tcx.closure_base_def_id(mir_def_id);
241+
if !tcx.has_attr(base_def_id, "rustc_regions") {
242+
return;
243+
}
244+
245+
// When the enclosing function is tagged with `#[rustc_regions]`,
246+
// we dump out various bits of state as warnings. This is useful
247+
// for verifying that the compiler is behaving as expected. These
248+
// warnings focus on the closure region requirements -- for
249+
// viewing the intraprocedural state, the -Zdump-mir output is
250+
// better.
251+
252+
if let Some(closure_region_requirements) = closure_region_requirements {
253+
let mut err = tcx.sess
254+
.diagnostic()
255+
.span_note_diag(mir.span, "External requirements");
256+
257+
regioncx.annotate(&mut err);
258+
259+
err.note(&format!(
260+
"number of external vids: {}",
261+
closure_region_requirements.num_external_vids
262+
));
263+
264+
// Dump the region constraints we are imposing *between* those
265+
// newly created variables.
266+
for_each_region_constraint(closure_region_requirements, &mut |msg| {
267+
err.note(msg);
268+
Ok(())
269+
}).unwrap();
270+
271+
err.emit();
272+
} else {
273+
let mut err = tcx.sess
274+
.diagnostic()
275+
.span_note_diag(mir.span, "No external requirements");
276+
regioncx.annotate(&mut err);
277+
err.emit();
278+
}
279+
}
280+
281+
fn for_each_region_constraint(
282+
closure_region_requirements: &ClosureRegionRequirements,
283+
with_msg: &mut FnMut(&str) -> io::Result<()>,
284+
) -> io::Result<()> {
285+
for req in &closure_region_requirements.outlives_requirements {
286+
with_msg(&format!(
287+
"where {:?}: {:?}",
288+
req.free_region,
289+
req.outlived_free_region,
290+
))?;
291+
}
292+
Ok(())
194293
}
195294

196295
/// Right now, we piggy back on the `ReVar` to store our NLL inference
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! As part of the NLL unit tests, you can annotate a function with
12+
//! `#[rustc_regions]`, and we will emit information about the region
13+
//! inference context and -- in particular -- the external constraints
14+
//! that this region imposes on others. The methods in this file
15+
//! handle the part about dumping the inference context internal
16+
//! state.
17+
18+
use rustc::ty;
19+
use rustc_errors::DiagnosticBuilder;
20+
use super::RegionInferenceContext;
21+
22+
impl<'gcx, 'tcx> RegionInferenceContext<'tcx> {
23+
/// Write out our state into the `.mir` files.
24+
pub(crate) fn annotate(&self, err: &mut DiagnosticBuilder<'_>) {
25+
match self.universal_regions.defining_ty.sty {
26+
ty::TyClosure(def_id, substs) => {
27+
err.note(&format!(
28+
"defining type: {:?} with closure substs {:#?}",
29+
def_id,
30+
&substs.substs[..]
31+
));
32+
}
33+
ty::TyFnDef(def_id, substs) => {
34+
err.note(&format!(
35+
"defining type: {:?} with substs {:#?}",
36+
def_id,
37+
&substs[..]
38+
));
39+
}
40+
_ => {
41+
err.note(&format!(
42+
"defining type: {:?}",
43+
self.universal_regions.defining_ty
44+
));
45+
}
46+
}
47+
}
48+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! As part of generating the regions, if you enable `-Zdump-mir=nll`,
12+
//! we will generate an annotated copy of the MIR that includes the
13+
//! state of region inference. This code handles emitting the region
14+
//! context internal state.
15+
16+
use std::io::{self, Write};
17+
use super::{Constraint, RegionInferenceContext};
18+
19+
// Room for "'_#NNNNr" before things get misaligned.
20+
// Easy enough to fix if this ever doesn't seem like
21+
// enough.
22+
const REGION_WIDTH: usize = 8;
23+
24+
impl<'tcx> RegionInferenceContext<'tcx> {
25+
/// Write out our state into the `.mir` files.
26+
pub(crate) fn dump_mir(&self, out: &mut Write) -> io::Result<()> {
27+
writeln!(out, "| Free Region Mapping")?;
28+
29+
for region in self.regions() {
30+
if self.definitions[region].is_universal {
31+
let classification = self.universal_regions.region_classification(region).unwrap();
32+
let outlived_by = self.universal_regions.regions_outlived_by(region);
33+
writeln!(
34+
out,
35+
"| {r:rw$} | {c:cw$} | {ob}",
36+
r = format!("{:?}", region),
37+
rw = REGION_WIDTH,
38+
c = format!("{:?}", classification),
39+
cw = 8, // "External" at most
40+
ob = format!("{:?}", outlived_by)
41+
)?;
42+
}
43+
}
44+
45+
writeln!(out, "|")?;
46+
writeln!(out, "| Inferred Region Values")?;
47+
for region in self.regions() {
48+
writeln!(
49+
out,
50+
"| {r:rw$} | {v}",
51+
r = format!("{:?}", region),
52+
rw = REGION_WIDTH,
53+
v = self.region_value_str(region),
54+
)?;
55+
}
56+
57+
writeln!(out, "|")?;
58+
writeln!(out, "| Inference Constraints")?;
59+
self.for_each_constraint(&mut |msg| writeln!(out, "| {}", msg))?;
60+
61+
Ok(())
62+
}
63+
64+
/// Debugging aid: Invokes the `with_msg` callback repeatedly with
65+
/// our internal region constraints. These are dumped into the
66+
/// -Zdump-mir file so that we can figure out why the region
67+
/// inference resulted in the values that it did when debugging.
68+
fn for_each_constraint(
69+
&self,
70+
with_msg: &mut FnMut(&str) -> io::Result<()>,
71+
) -> io::Result<()> {
72+
for region in self.definitions.indices() {
73+
let value = self.region_value_str_from_matrix(&self.liveness_constraints, region);
74+
if value != "{}" {
75+
with_msg(&format!("{:?} live at {}", region, value))?;
76+
}
77+
}
78+
79+
let mut constraints: Vec<_> = self.constraints.iter().collect();
80+
constraints.sort();
81+
for constraint in &constraints {
82+
let Constraint {
83+
sup,
84+
sub,
85+
point,
86+
span,
87+
} = constraint;
88+
with_msg(&format!(
89+
"{:?}: {:?} @ {:?} due to {:?}",
90+
sup,
91+
sub,
92+
point,
93+
span
94+
))?;
95+
}
96+
97+
Ok(())
98+
}
99+
}
100+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! This module provides linkage between RegionInferenceContext and
12+
//! libgraphviz traits, specialized to attaching borrowck analysis
13+
//! data to rendered labels.
14+
15+
use dot::{self, IntoCow};
16+
use rustc_data_structures::indexed_vec::Idx;
17+
use std::borrow::Cow;
18+
use std::io::{self, Write};
19+
use super::*;
20+
21+
impl<'tcx> RegionInferenceContext<'tcx> {
22+
/// Write out the region constraint graph.
23+
pub(crate) fn dump_graphviz(&self, mut w: &mut Write) -> io::Result<()> {
24+
dot::render(self, &mut w)
25+
}
26+
}
27+
28+
impl<'this, 'tcx> dot::Labeller<'this> for RegionInferenceContext<'tcx> {
29+
type Node = RegionVid;
30+
type Edge = Constraint;
31+
32+
fn graph_id(&'this self) -> dot::Id<'this> {
33+
dot::Id::new(format!("RegionInferenceContext")).unwrap()
34+
}
35+
fn node_id(&'this self, n: &RegionVid) -> dot::Id<'this> {
36+
dot::Id::new(format!("r{}", n.index())).unwrap()
37+
}
38+
fn node_shape(&'this self, _node: &RegionVid) -> Option<dot::LabelText<'this>> {
39+
Some(dot::LabelText::LabelStr(Cow::Borrowed("box")))
40+
}
41+
fn node_label(&'this self, n: &RegionVid) -> dot::LabelText<'this> {
42+
dot::LabelText::LabelStr(format!("{:?}", n).into_cow())
43+
}
44+
fn edge_label(&'this self, e: &Constraint) -> dot::LabelText<'this> {
45+
dot::LabelText::LabelStr(format!("{:?}", e.point).into_cow())
46+
}
47+
}
48+
49+
impl<'this, 'tcx> dot::GraphWalk<'this> for RegionInferenceContext<'tcx> {
50+
type Node = RegionVid;
51+
type Edge = Constraint;
52+
53+
fn nodes(&'this self) -> dot::Nodes<'this, RegionVid> {
54+
let vids: Vec<RegionVid> = self.definitions.indices().collect();
55+
vids.into_cow()
56+
}
57+
fn edges(&'this self) -> dot::Edges<'this, Constraint> {
58+
(&self.constraints[..]).into_cow()
59+
}
60+
61+
// Render `a: b` as `a <- b`, indicating the flow
62+
// of data during inference.
63+
64+
fn source(&'this self, edge: &Constraint) -> RegionVid {
65+
edge.sub
66+
}
67+
68+
fn target(&'this self, edge: &Constraint) -> RegionVid {
69+
edge.sup
70+
}
71+
}

src/librustc_mir/borrow_check/nll/region_infer/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ use std::collections::BTreeMap;
2525
use std::fmt;
2626
use syntax_pos::Span;
2727

28+
mod annotation;
29+
mod dump_mir;
30+
mod graphviz;
31+
2832
pub struct RegionInferenceContext<'tcx> {
2933
/// Contains the definition for every region variable. Region
3034
/// variables are identified by their index (`RegionVid`). The

0 commit comments

Comments
 (0)