Skip to content

Commit d6d0bb2

Browse files
committed
Added -Z print-region-graph debugging option; produces graphviz
visualization of region inference constraint graph. Optionally uses environment variables `RUST_REGION_GRAPH=<path_template>` and `RUST_REGION_GRAPH_NODE=<node-id>` to select which file to output to and which AST node to print. Note that in some cases of method AST's, the identification of AST node is based on the id for the *body* of the method; this is largely due to having the body node-id already available at the relevant point in the control-flow of rustc in its current incarnation. Ideally we would handle identifying AST's by name in addition to node-id, e.g. the same way that the pretty-printer supports path suffixes as well as node-ids for identifying subtrees to print.
1 parent a5e0624 commit d6d0bb2

File tree

7 files changed

+277
-19
lines changed

7 files changed

+277
-19
lines changed

src/librustc/middle/infer/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,8 +814,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
814814
self.region_vars.new_bound(debruijn)
815815
}
816816

817-
pub fn resolve_regions_and_report_errors(&self) {
818-
let errors = self.region_vars.resolve_regions();
817+
pub fn resolve_regions_and_report_errors(&self, subject_node_id: ast::NodeId) {
818+
let errors = self.region_vars.resolve_regions(subject_node_id);
819819
self.report_region_errors(&errors); // see error_reporting.rs
820820
}
821821

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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 libgraphviz traits and
12+
//! `rustc::middle::typeck::infer::region_inference`, generating a
13+
//! rendering of the graph represented by the list of `Constraint`
14+
//! instances (which make up the edges of the graph), as well as the
15+
//! origin for each constraint (which are attached to the labels on
16+
//! each edge).
17+
18+
/// For clarity, rename the graphviz crate locally to dot.
19+
use graphviz as dot;
20+
21+
use middle::ty;
22+
use super::Constraint;
23+
use middle::typeck::infer::SubregionOrigin;
24+
use middle::typeck::infer::region_inference::RegionVarBindings;
25+
use session::config;
26+
use util::nodemap::{FnvHashMap, FnvHashSet};
27+
use util::ppaux::Repr;
28+
29+
use std::collections::hash_map::Vacant;
30+
use std::io::{mod, File};
31+
use std::os;
32+
use std::sync::atomic;
33+
use syntax::ast;
34+
35+
fn print_help_message() {
36+
println!("\
37+
-Z print-region-graph by default prints a region constraint graph for every \n\
38+
function body, to the path `/tmp/constraints.nodeXXX.dot`, where the XXX is \n\
39+
replaced with the node id of the function under analysis. \n\
40+
\n\
41+
To select one particular function body, set `RUST_REGION_GRAPH_NODE=XXX`, \n\
42+
where XXX is the node id desired. \n\
43+
\n\
44+
To generate output to some path other than the default \n\
45+
`/tmp/constraints.nodeXXX.dot`, set `RUST_REGION_GRAPH=/path/desired.dot`; \n\
46+
occurrences of the character `%` in the requested path will be replaced with\n\
47+
the node id of the function under analysis. \n\
48+
\n\
49+
(Since you requested help via RUST_REGION_GRAPH=help, no region constraint \n\
50+
graphs will be printed. \n\
51+
");
52+
}
53+
54+
pub fn maybe_print_constraints_for<'a, 'tcx>(region_vars: &RegionVarBindings<'a, 'tcx>,
55+
subject_node: ast::NodeId) {
56+
let tcx = region_vars.tcx;
57+
58+
if !region_vars.tcx.sess.debugging_opt(config::PRINT_REGION_GRAPH) {
59+
return;
60+
}
61+
62+
let requested_node : Option<ast::NodeId> =
63+
os::getenv("RUST_REGION_GRAPH_NODE").and_then(|s|from_str(s.as_slice()));
64+
65+
if requested_node.is_some() && requested_node != Some(subject_node) {
66+
return;
67+
}
68+
69+
let requested_output = os::getenv("RUST_REGION_GRAPH");
70+
debug!("requested_output: {} requested_node: {}",
71+
requested_output, requested_node);
72+
73+
let output_path = {
74+
let output_template = match requested_output {
75+
Some(ref s) if s.as_slice() == "help" => {
76+
static PRINTED_YET : atomic::AtomicBool = atomic::INIT_ATOMIC_BOOL;
77+
if !PRINTED_YET.load(atomic::SeqCst) {
78+
print_help_message();
79+
PRINTED_YET.store(true, atomic::SeqCst);
80+
}
81+
return;
82+
}
83+
84+
Some(other_path) => other_path,
85+
None => "/tmp/constraints.node%.dot".to_string(),
86+
};
87+
88+
if output_template.len() == 0 {
89+
tcx.sess.bug("empty string provided as RUST_REGION_GRAPH");
90+
}
91+
92+
if output_template.contains_char('%') {
93+
let mut new_str = String::new();
94+
for c in output_template.chars() {
95+
if c == '%' {
96+
new_str.push_str(subject_node.to_string().as_slice());
97+
} else {
98+
new_str.push(c);
99+
}
100+
}
101+
new_str
102+
} else {
103+
output_template
104+
}
105+
};
106+
107+
let constraints = &*region_vars.constraints.borrow();
108+
match dump_region_constraints_to(tcx, constraints, output_path.as_slice()) {
109+
Ok(()) => {}
110+
Err(e) => {
111+
let msg = format!("io error dumping region constraints: {}", e);
112+
region_vars.tcx.sess.err(msg.as_slice())
113+
}
114+
}
115+
}
116+
117+
struct ConstraintGraph<'a, 'tcx: 'a> {
118+
tcx: &'a ty::ctxt<'tcx>,
119+
graph_name: String,
120+
map: &'a FnvHashMap<Constraint, SubregionOrigin<'tcx>>,
121+
node_ids: FnvHashMap<Node, uint>,
122+
}
123+
124+
#[deriving(Clone, Hash, PartialEq, Eq, Show)]
125+
enum Node {
126+
RegionVid(ty::RegionVid),
127+
Region(ty::Region),
128+
}
129+
130+
type Edge = Constraint;
131+
132+
impl<'a, 'tcx> ConstraintGraph<'a, 'tcx> {
133+
fn new(tcx: &'a ty::ctxt<'tcx>,
134+
name: String,
135+
map: &'a ConstraintMap<'tcx>) -> ConstraintGraph<'a, 'tcx> {
136+
let mut i = 0;
137+
let mut node_ids = FnvHashMap::new();
138+
{
139+
let add_node = |node| {
140+
if let Vacant(e) = node_ids.entry(node) {
141+
e.set(i);
142+
i += 1;
143+
}
144+
};
145+
146+
for (n1, n2) in map.keys().map(|c|constraint_to_nodes(c)) {
147+
add_node(n1);
148+
add_node(n2);
149+
}
150+
}
151+
152+
ConstraintGraph { tcx: tcx,
153+
graph_name: name,
154+
map: map,
155+
node_ids: node_ids }
156+
}
157+
}
158+
159+
impl<'a, 'tcx> dot::Labeller<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> {
160+
fn graph_id(&self) -> dot::Id {
161+
dot::Id::new(self.graph_name.as_slice()).unwrap()
162+
}
163+
fn node_id(&self, n: &Node) -> dot::Id {
164+
dot::Id::new(format!("node_{}", self.node_ids.get(n).unwrap())).unwrap()
165+
}
166+
fn node_label(&self, n: &Node) -> dot::LabelText {
167+
match *n {
168+
Node::RegionVid(n_vid) =>
169+
dot::LabelText::label(format!("{}", n_vid)),
170+
Node::Region(n_rgn) =>
171+
dot::LabelText::label(format!("{}", n_rgn.repr(self.tcx))),
172+
}
173+
}
174+
fn edge_label(&self, e: &Edge) -> dot::LabelText {
175+
dot::LabelText::label(format!("{}", self.map.get(e).unwrap().repr(self.tcx)))
176+
}
177+
}
178+
179+
fn constraint_to_nodes(c: &Constraint) -> (Node, Node) {
180+
match *c {
181+
Constraint::ConstrainVarSubVar(rv_1, rv_2) => (Node::RegionVid(rv_1),
182+
Node::RegionVid(rv_2)),
183+
Constraint::ConstrainRegSubVar(r_1, rv_2) => (Node::Region(r_1),
184+
Node::RegionVid(rv_2)),
185+
Constraint::ConstrainVarSubReg(rv_1, r_2) => (Node::RegionVid(rv_1),
186+
Node::Region(r_2)),
187+
}
188+
}
189+
190+
impl<'a, 'tcx> dot::GraphWalk<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> {
191+
fn nodes(&self) -> dot::Nodes<Node> {
192+
let mut set = FnvHashSet::new();
193+
for constraint in self.map.keys() {
194+
let (n1, n2) = constraint_to_nodes(constraint);
195+
set.insert(n1);
196+
set.insert(n2);
197+
}
198+
debug!("constraint graph has {} nodes", set.len());
199+
set.into_iter().collect()
200+
}
201+
fn edges(&self) -> dot::Edges<Edge> {
202+
debug!("constraint graph has {} edges", self.map.len());
203+
self.map.keys().map(|e|*e).collect()
204+
}
205+
fn source(&self, edge: &Edge) -> Node {
206+
let (n1, _) = constraint_to_nodes(edge);
207+
debug!("edge {} has source {}", edge, n1);
208+
n1
209+
}
210+
fn target(&self, edge: &Edge) -> Node {
211+
let (_, n2) = constraint_to_nodes(edge);
212+
debug!("edge {} has target {}", edge, n2);
213+
n2
214+
}
215+
}
216+
217+
pub type ConstraintMap<'tcx> = FnvHashMap<Constraint, SubregionOrigin<'tcx>>;
218+
219+
fn dump_region_constraints_to<'a, 'tcx:'a >(tcx: &'a ty::ctxt<'tcx>,
220+
map: &ConstraintMap<'tcx>,
221+
path: &str) -> io::IoResult<()> {
222+
debug!("dump_region_constraints map (len: {}) path: {}", map.len(), path);
223+
let g = ConstraintGraph::new(tcx, format!("region_constraints"), map);
224+
let mut f = File::create(&Path::new(path));
225+
debug!("dump_region_constraints calling render");
226+
dot::render(&g, &mut f)
227+
}

src/librustc/middle/infer/region_inference/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ use std::uint;
3737
use syntax::ast;
3838

3939
mod doc;
40+
mod graphviz;
4041

4142
// A constraint that influences the inference process.
42-
#[deriving(PartialEq, Eq, Hash)]
43+
#[deriving(PartialEq, Eq, Hash, Show)]
4344
pub enum Constraint {
4445
// One region variable is subregion of another
4546
ConstrainVarSubVar(RegionVid, RegionVid),
@@ -706,10 +707,10 @@ impl<'a, 'tcx> RegionVarBindings<'a, 'tcx> {
706707
/// fixed-point iteration to find region values which satisfy all
707708
/// constraints, assuming such values can be found; if they cannot,
708709
/// errors are reported.
709-
pub fn resolve_regions(&self) -> Vec<RegionResolutionError<'tcx>> {
710+
pub fn resolve_regions(&self, subject_node: ast::NodeId) -> Vec<RegionResolutionError<'tcx>> {
710711
debug!("RegionVarBindings: resolve_regions()");
711712
let mut errors = vec!();
712-
let v = self.infer_variable_values(&mut errors);
713+
let v = self.infer_variable_values(&mut errors, subject_node);
713714
*self.values.borrow_mut() = Some(v);
714715
errors
715716
}
@@ -958,14 +959,15 @@ type RegionGraph = graph::Graph<(), Constraint>;
958959

959960
impl<'a, 'tcx> RegionVarBindings<'a, 'tcx> {
960961
fn infer_variable_values(&self,
961-
errors: &mut Vec<RegionResolutionError<'tcx>>)
962-
-> Vec<VarValue>
962+
errors: &mut Vec<RegionResolutionError<'tcx>>,
963+
subject: ast::NodeId) -> Vec<VarValue>
963964
{
964965
let mut var_data = self.construct_var_data();
965966

966967
// Dorky hack to cause `dump_constraints` to only get called
967968
// if debug mode is enabled:
968969
debug!("----() End constraint listing {}---", self.dump_constraints());
970+
graphviz::maybe_print_constraints_for(self, subject);
969971

970972
self.expansion(var_data.as_mut_slice());
971973
self.contraction(var_data.as_mut_slice());

src/librustc/session/config.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ debugging_opts!(
276276
FLOWGRAPH_PRINT_MOVES,
277277
FLOWGRAPH_PRINT_ASSIGNS,
278278
FLOWGRAPH_PRINT_ALL,
279-
PRINT_SYSROOT
279+
PRINT_SYSROOT,
280+
PRINT_REGION_GRAPH
280281
]
281282
0
282283
)
@@ -322,7 +323,10 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
322323
("flowgraph-print-all", "Include all dataflow analysis data in \
323324
--pretty flowgraph output", FLOWGRAPH_PRINT_ALL),
324325
("print-sysroot", "Print the sysroot as used by this rustc invocation",
325-
PRINT_SYSROOT)]
326+
PRINT_SYSROOT),
327+
("print-region-graph", "Prints region inference graph. \
328+
Use with RUST_REGION_GRAPH=help for more info",
329+
PRINT_REGION_GRAPH)]
326330
}
327331

328332
#[deriving(Clone)]

src/librustc_typeck/check/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1190,7 +1190,7 @@ fn compare_impl_method<'tcx>(tcx: &ty::ctxt<'tcx>,
11901190

11911191
// Finally, resolve all regions. This catches wily misuses of lifetime
11921192
// parameters.
1193-
infcx.resolve_regions_and_report_errors();
1193+
infcx.resolve_regions_and_report_errors(impl_m_body_id);
11941194

11951195
/// Check that region bounds on impl method are the same as those on the trait. In principle,
11961196
/// it could be ok for there to be fewer region bounds on the impl method, but this leads to an

0 commit comments

Comments
 (0)