rollup merge of #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.
This commit is contained in:
Alex Crichton 2014-12-17 08:35:07 -08:00
commit 4a7757038a
8 changed files with 320 additions and 26 deletions

View File

@ -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
@ -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)); }
@ -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() {
@ -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, &["}"])

View File

@ -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
}

View File

@ -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)
}

View File

@ -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),
@ -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
}
@ -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());

View File

@ -276,7 +276,8 @@ debugging_opts!(
FLOWGRAPH_PRINT_MOVES,
FLOWGRAPH_PRINT_ASSIGNS,
FLOWGRAPH_PRINT_ALL,
PRINT_SYSROOT
PRINT_SYSROOT,
PRINT_REGION_GRAPH
]
0
)
@ -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)]

View File

@ -1188,7 +1188,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

View File

@ -139,27 +139,31 @@ use syntax::visit::Visitor;
use std::cell::{RefCell};
use std::collections::hash_map::{Vacant, Occupied};
use self::RepeatingScope::Repeating;
use self::SubjectNode::Subject;
///////////////////////////////////////////////////////////////////////////
// PUBLIC ENTRY POINTS
pub fn regionck_expr(fcx: &FnCtxt, e: &ast::Expr) {
let mut rcx = Rcx::new(fcx, e.id);
let mut rcx = Rcx::new(fcx, Repeating(e.id), Subject(e.id));
if fcx.err_count_since_creation() == 0 {
// regionck assumes typeck succeeded
rcx.visit_expr(e);
rcx.visit_region_obligations(e.id);
}
fcx.infcx().resolve_regions_and_report_errors();
rcx.resolve_regions_and_report_errors();
}
pub fn regionck_item(fcx: &FnCtxt, item: &ast::Item) {
let mut rcx = Rcx::new(fcx, item.id);
let mut rcx = Rcx::new(fcx, Repeating(item.id), Subject(item.id));
rcx.visit_region_obligations(item.id);
fcx.infcx().resolve_regions_and_report_errors();
rcx.resolve_regions_and_report_errors();
}
pub fn regionck_fn(fcx: &FnCtxt, id: ast::NodeId, decl: &ast::FnDecl, blk: &ast::Block) {
let mut rcx = Rcx::new(fcx, blk.id);
let mut rcx = Rcx::new(fcx, Repeating(blk.id), Subject(id));
if fcx.err_count_since_creation() == 0 {
// regionck assumes typeck succeeded
rcx.visit_fn_body(id, decl, blk);
@ -169,7 +173,7 @@ pub fn regionck_fn(fcx: &FnCtxt, id: ast::NodeId, decl: &ast::FnDecl, blk: &ast:
// particularly around closure bounds.
vtable::select_all_fcx_obligations_or_error(fcx);
fcx.infcx().resolve_regions_and_report_errors();
rcx.resolve_regions_and_report_errors();
}
/// Checks that the types in `component_tys` are well-formed. This will add constraints into the
@ -177,7 +181,7 @@ pub fn regionck_fn(fcx: &FnCtxt, id: ast::NodeId, decl: &ast::FnDecl, blk: &ast:
pub fn regionck_ensure_component_tys_wf<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
span: Span,
component_tys: &[Ty<'tcx>]) {
let mut rcx = Rcx::new(fcx, 0);
let mut rcx = Rcx::new(fcx, Repeating(0), SubjectNode::None);
for &component_ty in component_tys.iter() {
// Check that each type outlives the empty region. Since the
// empty region is a subregion of all others, this can't fail
@ -225,6 +229,9 @@ pub struct Rcx<'a, 'tcx: 'a> {
// id of innermost fn or loop
repeating_scope: ast::NodeId,
// id of AST node being analyzed (the subject of the analysis).
subject: SubjectNode,
// Possible region links we will establish if an upvar
// turns out to be unique/mutable
maybe_links: MaybeLinkMap<'tcx>
@ -251,11 +258,17 @@ fn region_of_def(fcx: &FnCtxt, def: def::Def) -> ty::Region {
}
}
pub enum RepeatingScope { Repeating(ast::NodeId) }
pub enum SubjectNode { Subject(ast::NodeId), None }
impl<'a, 'tcx> Rcx<'a, 'tcx> {
pub fn new(fcx: &'a FnCtxt<'a, 'tcx>,
initial_repeating_scope: ast::NodeId) -> Rcx<'a, 'tcx> {
initial_repeating_scope: RepeatingScope,
subject: SubjectNode) -> Rcx<'a, 'tcx> {
let Repeating(initial_repeating_scope) = initial_repeating_scope;
Rcx { fcx: fcx,
repeating_scope: initial_repeating_scope,
subject: subject,
region_param_pairs: Vec::new(),
maybe_links: RefCell::new(FnvHashMap::new()) }
}
@ -425,6 +438,18 @@ impl<'a, 'tcx> Rcx<'a, 'tcx> {
debug!("<< relate_free_regions");
}
fn resolve_regions_and_report_errors(&self) {
let subject_node_id = match self.subject {
Subject(s) => s,
SubjectNode::None => {
self.tcx().sess.bug("cannot resolve_regions_and_report_errors \
without subject node");
}
};
self.fcx.infcx().resolve_regions_and_report_errors(subject_node_id);
}
}
impl<'fcx, 'tcx> mc::Typer<'tcx> for Rcx<'fcx, 'tcx> {

View File

@ -2237,6 +2237,6 @@ fn check_method_self_type<'a, 'tcx, RS:RegionScope>(
format!("mismatched self type: expected `{}`",
ppaux::ty_to_string(crate_context.tcx, required_type))
}));
infcx.resolve_regions_and_report_errors();
infcx.resolve_regions_and_report_errors(body_id);
}
}