Skip to content

Commit 4a77570

Browse files
committed
rollup merge of rust-lang#19892: pnkfelix/region-graphviz
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.
2 parents 56fb9bc + 375b79a commit 4a77570

File tree

8 files changed

+320
-26
lines changed

8 files changed

+320
-26
lines changed

src/libgraphviz/lib.rs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,14 @@ pub trait Labeller<'a,N,E> {
421421
}
422422

423423
impl<'a> LabelText<'a> {
424+
pub fn label<S:IntoCow<'a, String, str>>(s: S) -> LabelText<'a> {
425+
LabelStr(s.into_cow())
426+
}
427+
428+
pub fn escaped<S:IntoCow<'a, String, str>>(s: S) -> LabelText<'a> {
429+
EscStr(s.into_cow())
430+
}
431+
424432
fn escape_char<F>(c: char, mut f: F) where F: FnMut(char) {
425433
match c {
426434
// not escaping \\, since Graphviz escString needs to
@@ -505,11 +513,29 @@ pub trait GraphWalk<'a, N, E> {
505513
fn target(&'a self, edge: &E) -> N;
506514
}
507515

516+
#[deriving(Copy, PartialEq, Eq, Show)]
517+
pub enum RenderOption {
518+
NoEdgeLabels,
519+
NoNodeLabels,
520+
}
521+
522+
/// Returns vec holding all the default render options.
523+
pub fn default_options() -> Vec<RenderOption> { vec![] }
524+
508525
/// Renders directed graph `g` into the writer `w` in DOT syntax.
509-
/// (Main entry point for the library.)
526+
/// (Simple wrapper around `render_opts` that passes a default set of options.)
510527
pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>(
511528
g: &'a G,
512-
w: &mut W) -> io::IoResult<()>
529+
w: &mut W) -> io::IoResult<()> {
530+
render_opts(g, w, &[])
531+
}
532+
533+
/// Renders directed graph `g` into the writer `w` in DOT syntax.
534+
/// (Main entry point for the library.)
535+
pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>(
536+
g: &'a G,
537+
w: &mut W,
538+
options: &[RenderOption]) -> io::IoResult<()>
513539
{
514540
fn writeln<W:Writer>(w: &mut W, arg: &[&str]) -> io::IoResult<()> {
515541
for &s in arg.iter() { try!(w.write_str(s)); }
@@ -524,9 +550,13 @@ pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>,
524550
for n in g.nodes().iter() {
525551
try!(indent(w));
526552
let id = g.node_id(n);
527-
let escaped = g.node_label(n).escape();
528-
try!(writeln(w, &[id.as_slice(),
529-
"[label=\"", escaped.as_slice(), "\"];"]));
553+
if options.contains(&RenderOption::NoNodeLabels) {
554+
try!(writeln(w, &[id.as_slice(), ";"]));
555+
} else {
556+
let escaped = g.node_label(n).escape();
557+
try!(writeln(w, &[id.as_slice(),
558+
"[label=\"", escaped.as_slice(), "\"];"]));
559+
}
530560
}
531561

532562
for e in g.edges().iter() {
@@ -536,8 +566,14 @@ pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>,
536566
let target = g.target(e);
537567
let source_id = g.node_id(&source);
538568
let target_id = g.node_id(&target);
539-
try!(writeln(w, &[source_id.as_slice(), " -> ", target_id.as_slice(),
540-
"[label=\"", escaped_label.as_slice(), "\"];"]));
569+
if options.contains(&RenderOption::NoEdgeLabels) {
570+
try!(writeln(w, &[source_id.as_slice(),
571+
" -> ", target_id.as_slice(), ";"]));
572+
} else {
573+
try!(writeln(w, &[source_id.as_slice(),
574+
" -> ", target_id.as_slice(),
575+
"[label=\"", escaped_label.as_slice(), "\"];"]));
576+
}
541577
}
542578

543579
writeln(w, &["}"])

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::infer::SubregionOrigin;
24+
use middle::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(Clone, 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
@@ -1188,7 +1188,7 @@ fn compare_impl_method<'tcx>(tcx: &ty::ctxt<'tcx>,
11881188

11891189
// Finally, resolve all regions. This catches wily misuses of lifetime
11901190
// parameters.
1191-
infcx.resolve_regions_and_report_errors();
1191+
infcx.resolve_regions_and_report_errors(impl_m_body_id);
11921192

11931193
/// Check that region bounds on impl method are the same as those on the trait. In principle,
11941194
/// 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)