Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

graphviz drawing of constraints for region inference #19892

Merged
merged 4 commits into from
Dec 17, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 43 additions & 7 deletions src/libgraphviz/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,14 @@ pub trait Labeller<'a,N,E> {
}

impl<'a> LabelText<'a> {
pub fn label<S:IntoCow<'a, String, str>>(s: S) -> LabelText<'a> {
LabelStr(s.into_cow())
}

pub fn escaped<S:IntoCow<'a, String, str>>(s: S) -> LabelText<'a> {
EscStr(s.into_cow())
}

fn escape_char<F>(c: char, mut f: F) where F: FnMut(char) {
match c {
// not escaping \\, since Graphviz escString needs to
Expand Down Expand Up @@ -505,11 +513,29 @@ pub trait GraphWalk<'a, N, E> {
fn target(&'a self, edge: &E) -> N;
}

#[deriving(Copy, PartialEq, Eq, Show)]
pub enum RenderOption {
NoEdgeLabels,
NoNodeLabels,
}

/// Returns vec holding all the default render options.
pub fn default_options() -> Vec<RenderOption> { vec![] }

/// Renders directed graph `g` into the writer `w` in DOT syntax.
/// (Main entry point for the library.)
/// (Simple wrapper around `render_opts` that passes a default set of options.)
pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>(
g: &'a G,
w: &mut W) -> io::IoResult<()>
w: &mut W) -> io::IoResult<()> {
render_opts(g, w, &[])
}

/// Renders directed graph `g` into the writer `w` in DOT syntax.
/// (Main entry point for the library.)
pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>(
g: &'a G,
w: &mut W,
options: &[RenderOption]) -> io::IoResult<()>
{
fn writeln<W:Writer>(w: &mut W, arg: &[&str]) -> io::IoResult<()> {
for &s in arg.iter() { try!(w.write_str(s)); }
Expand All @@ -524,9 +550,13 @@ pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>,
for n in g.nodes().iter() {
try!(indent(w));
let id = g.node_id(n);
let escaped = g.node_label(n).escape();
try!(writeln(w, &[id.as_slice(),
"[label=\"", escaped.as_slice(), "\"];"]));
if options.contains(&RenderOption::NoNodeLabels) {
try!(writeln(w, &[id.as_slice(), ";"]));
} else {
let escaped = g.node_label(n).escape();
try!(writeln(w, &[id.as_slice(),
"[label=\"", escaped.as_slice(), "\"];"]));
}
}

for e in g.edges().iter() {
Expand All @@ -536,8 +566,14 @@ pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>,
let target = g.target(e);
let source_id = g.node_id(&source);
let target_id = g.node_id(&target);
try!(writeln(w, &[source_id.as_slice(), " -> ", target_id.as_slice(),
"[label=\"", escaped_label.as_slice(), "\"];"]));
if options.contains(&RenderOption::NoEdgeLabels) {
try!(writeln(w, &[source_id.as_slice(),
" -> ", target_id.as_slice(), ";"]));
} else {
try!(writeln(w, &[source_id.as_slice(),
" -> ", target_id.as_slice(),
"[label=\"", escaped_label.as_slice(), "\"];"]));
}
}

writeln(w, &["}"])
Expand Down
4 changes: 2 additions & 2 deletions src/librustc/middle/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -814,8 +814,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
self.region_vars.new_bound(debruijn)
}

pub fn resolve_regions_and_report_errors(&self) {
let errors = self.region_vars.resolve_regions();
pub fn resolve_regions_and_report_errors(&self, subject_node_id: ast::NodeId) {
let errors = self.region_vars.resolve_regions(subject_node_id);
self.report_region_errors(&errors); // see error_reporting.rs
}

Expand Down
227 changes: 227 additions & 0 deletions src/librustc/middle/infer/region_inference/graphviz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! This module provides linkage between libgraphviz traits and
//! `rustc::middle::typeck::infer::region_inference`, generating a
//! rendering of the graph represented by the list of `Constraint`
//! instances (which make up the edges of the graph), as well as the
//! origin for each constraint (which are attached to the labels on
//! each edge).

/// For clarity, rename the graphviz crate locally to dot.
use graphviz as dot;

use middle::ty;
use super::Constraint;
use middle::infer::SubregionOrigin;
use middle::infer::region_inference::RegionVarBindings;
use session::config;
use util::nodemap::{FnvHashMap, FnvHashSet};
use util::ppaux::Repr;

use std::collections::hash_map::Vacant;
use std::io::{mod, File};
use std::os;
use std::sync::atomic;
use syntax::ast;

fn print_help_message() {
println!("\
-Z print-region-graph by default prints a region constraint graph for every \n\
function body, to the path `/tmp/constraints.nodeXXX.dot`, where the XXX is \n\
replaced with the node id of the function under analysis. \n\
\n\
To select one particular function body, set `RUST_REGION_GRAPH_NODE=XXX`, \n\
where XXX is the node id desired. \n\
\n\
To generate output to some path other than the default \n\
`/tmp/constraints.nodeXXX.dot`, set `RUST_REGION_GRAPH=/path/desired.dot`; \n\
occurrences of the character `%` in the requested path will be replaced with\n\
the node id of the function under analysis. \n\
\n\
(Since you requested help via RUST_REGION_GRAPH=help, no region constraint \n\
graphs will be printed. \n\
");
}

pub fn maybe_print_constraints_for<'a, 'tcx>(region_vars: &RegionVarBindings<'a, 'tcx>,
subject_node: ast::NodeId) {
let tcx = region_vars.tcx;

if !region_vars.tcx.sess.debugging_opt(config::PRINT_REGION_GRAPH) {
return;
}

let requested_node : Option<ast::NodeId> =
os::getenv("RUST_REGION_GRAPH_NODE").and_then(|s|from_str(s.as_slice()));

if requested_node.is_some() && requested_node != Some(subject_node) {
return;
}

let requested_output = os::getenv("RUST_REGION_GRAPH");
debug!("requested_output: {} requested_node: {}",
requested_output, requested_node);

let output_path = {
let output_template = match requested_output {
Some(ref s) if s.as_slice() == "help" => {
static PRINTED_YET : atomic::AtomicBool = atomic::INIT_ATOMIC_BOOL;
if !PRINTED_YET.load(atomic::SeqCst) {
print_help_message();
PRINTED_YET.store(true, atomic::SeqCst);
}
return;
}

Some(other_path) => other_path,
None => "/tmp/constraints.node%.dot".to_string(),
};

if output_template.len() == 0 {
tcx.sess.bug("empty string provided as RUST_REGION_GRAPH");
}

if output_template.contains_char('%') {
let mut new_str = String::new();
for c in output_template.chars() {
if c == '%' {
new_str.push_str(subject_node.to_string().as_slice());
} else {
new_str.push(c);
}
}
new_str
} else {
output_template
}
};

let constraints = &*region_vars.constraints.borrow();
match dump_region_constraints_to(tcx, constraints, output_path.as_slice()) {
Ok(()) => {}
Err(e) => {
let msg = format!("io error dumping region constraints: {}", e);
region_vars.tcx.sess.err(msg.as_slice())
}
}
}

struct ConstraintGraph<'a, 'tcx: 'a> {
tcx: &'a ty::ctxt<'tcx>,
graph_name: String,
map: &'a FnvHashMap<Constraint, SubregionOrigin<'tcx>>,
node_ids: FnvHashMap<Node, uint>,
}

#[deriving(Clone, Hash, PartialEq, Eq, Show)]
enum Node {
RegionVid(ty::RegionVid),
Region(ty::Region),
}

type Edge = Constraint;

impl<'a, 'tcx> ConstraintGraph<'a, 'tcx> {
fn new(tcx: &'a ty::ctxt<'tcx>,
name: String,
map: &'a ConstraintMap<'tcx>) -> ConstraintGraph<'a, 'tcx> {
let mut i = 0;
let mut node_ids = FnvHashMap::new();
{
let add_node = |node| {
if let Vacant(e) = node_ids.entry(node) {
e.set(i);
i += 1;
}
};

for (n1, n2) in map.keys().map(|c|constraint_to_nodes(c)) {
add_node(n1);
add_node(n2);
}
}

ConstraintGraph { tcx: tcx,
graph_name: name,
map: map,
node_ids: node_ids }
}
}

impl<'a, 'tcx> dot::Labeller<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> {
fn graph_id(&self) -> dot::Id {
dot::Id::new(self.graph_name.as_slice()).unwrap()
}
fn node_id(&self, n: &Node) -> dot::Id {
dot::Id::new(format!("node_{}", self.node_ids.get(n).unwrap())).unwrap()
}
fn node_label(&self, n: &Node) -> dot::LabelText {
match *n {
Node::RegionVid(n_vid) =>
dot::LabelText::label(format!("{}", n_vid)),
Node::Region(n_rgn) =>
dot::LabelText::label(format!("{}", n_rgn.repr(self.tcx))),
}
}
fn edge_label(&self, e: &Edge) -> dot::LabelText {
dot::LabelText::label(format!("{}", self.map.get(e).unwrap().repr(self.tcx)))
}
}

fn constraint_to_nodes(c: &Constraint) -> (Node, Node) {
match *c {
Constraint::ConstrainVarSubVar(rv_1, rv_2) => (Node::RegionVid(rv_1),
Node::RegionVid(rv_2)),
Constraint::ConstrainRegSubVar(r_1, rv_2) => (Node::Region(r_1),
Node::RegionVid(rv_2)),
Constraint::ConstrainVarSubReg(rv_1, r_2) => (Node::RegionVid(rv_1),
Node::Region(r_2)),
}
}

impl<'a, 'tcx> dot::GraphWalk<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> {
fn nodes(&self) -> dot::Nodes<Node> {
let mut set = FnvHashSet::new();
for constraint in self.map.keys() {
let (n1, n2) = constraint_to_nodes(constraint);
set.insert(n1);
set.insert(n2);
}
debug!("constraint graph has {} nodes", set.len());
set.into_iter().collect()
}
fn edges(&self) -> dot::Edges<Edge> {
debug!("constraint graph has {} edges", self.map.len());
self.map.keys().map(|e|*e).collect()
}
fn source(&self, edge: &Edge) -> Node {
let (n1, _) = constraint_to_nodes(edge);
debug!("edge {} has source {}", edge, n1);
n1
}
fn target(&self, edge: &Edge) -> Node {
let (_, n2) = constraint_to_nodes(edge);
debug!("edge {} has target {}", edge, n2);
n2
}
}

pub type ConstraintMap<'tcx> = FnvHashMap<Constraint, SubregionOrigin<'tcx>>;

fn dump_region_constraints_to<'a, 'tcx:'a >(tcx: &'a ty::ctxt<'tcx>,
map: &ConstraintMap<'tcx>,
path: &str) -> io::IoResult<()> {
debug!("dump_region_constraints map (len: {}) path: {}", map.len(), path);
let g = ConstraintGraph::new(tcx, format!("region_constraints"), map);
let mut f = File::create(&Path::new(path));
debug!("dump_region_constraints calling render");
dot::render(&g, &mut f)
}
12 changes: 7 additions & 5 deletions src/librustc/middle/infer/region_inference/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ use std::uint;
use syntax::ast;

mod doc;
mod graphviz;

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

impl<'a, 'tcx> RegionVarBindings<'a, 'tcx> {
fn infer_variable_values(&self,
errors: &mut Vec<RegionResolutionError<'tcx>>)
-> Vec<VarValue>
errors: &mut Vec<RegionResolutionError<'tcx>>,
subject: ast::NodeId) -> Vec<VarValue>
{
let mut var_data = self.construct_var_data();

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

self.expansion(var_data.as_mut_slice());
self.contraction(var_data.as_mut_slice());
Expand Down
8 changes: 6 additions & 2 deletions src/librustc/session/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ debugging_opts!(
FLOWGRAPH_PRINT_MOVES,
FLOWGRAPH_PRINT_ASSIGNS,
FLOWGRAPH_PRINT_ALL,
PRINT_SYSROOT
PRINT_SYSROOT,
PRINT_REGION_GRAPH
]
0
)
Expand Down Expand Up @@ -322,7 +323,10 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
("flowgraph-print-all", "Include all dataflow analysis data in \
--pretty flowgraph output", FLOWGRAPH_PRINT_ALL),
("print-sysroot", "Print the sysroot as used by this rustc invocation",
PRINT_SYSROOT)]
PRINT_SYSROOT),
("print-region-graph", "Prints region inference graph. \
Use with RUST_REGION_GRAPH=help for more info",
PRINT_REGION_GRAPH)]
}

#[deriving(Clone)]
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,7 @@ fn compare_impl_method<'tcx>(tcx: &ty::ctxt<'tcx>,

// Finally, resolve all regions. This catches wily misuses of lifetime
// parameters.
infcx.resolve_regions_and_report_errors();
infcx.resolve_regions_and_report_errors(impl_m_body_id);

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