Graphviz based flow graph pretty-printing.
Passing `--pretty flowgraph=<NODEID>` makes rustc print a control flow graph. In pratice, you will also need to pass the additional option: `-o <FILE>` to emit output to a `.dot` file for graphviz. (You can only print the flow-graph for a particular block in the AST.) ---- An interesting implementation detail is the way the code puts both the node index (`cfg::CFGIndex`) and a reference to the payload (`cfg::CFGNode`) into the single `Node` type that is used for labelling and walking the graph. I had once mistakenly thought that I only wanted the `cfg::CFGNode`, but for labelling, you really want the cfg index too, rather than e.g. trying to use the `ast::NodeId` as the label (which breaks down e.g. due to `ast::DUMMY_NODE_ID`). ---- As a drive-by fix, I had to fix `rustc::middle::cfg::construct` interface to reflect changes that have happened on the master branch while I was getting this integrated into the compiler. (The next commit actually adds tests of the `--pretty flowgraph` functionality, so that should ensure that the `rustc::middle::cfg` code does not go stale again.)
This commit is contained in:
parent
65b65fe448
commit
aaf398f26a
|
@ -59,12 +59,13 @@ TOOLS := compiletest rustdoc rustc
|
|||
DEPS_core :=
|
||||
DEPS_rlibc :=
|
||||
DEPS_std := core libc native:rustrt native:compiler-rt native:backtrace native:jemalloc
|
||||
DEPS_graphviz := std
|
||||
DEPS_green := std rand native:context_switch
|
||||
DEPS_rustuv := std native:uv native:uv_support
|
||||
DEPS_native := std
|
||||
DEPS_syntax := std term serialize collections log fmt_macros
|
||||
DEPS_rustc := syntax native:rustllvm flate arena serialize sync getopts \
|
||||
collections time log
|
||||
collections time log graphviz
|
||||
DEPS_rustdoc := rustc native:hoedown serialize sync getopts collections \
|
||||
test time
|
||||
DEPS_flate := std native:miniz
|
||||
|
|
|
@ -516,12 +516,13 @@ pub fn optgroups() -> Vec<getopts::OptGroup> {
|
|||
optopt( "", "out-dir", "Write output to compiler-chosen filename in <dir>", "DIR"),
|
||||
optflag("", "parse-only", "Parse only; do not compile, assemble, or link"),
|
||||
optflagopt("", "pretty",
|
||||
"Pretty-print the input instead of compiling;
|
||||
valid types are: normal (un-annotated source),
|
||||
expanded (crates expanded),
|
||||
typed (crates expanded, with type annotations),
|
||||
or identified (fully parenthesized,
|
||||
AST nodes and blocks with IDs)", "TYPE"),
|
||||
"Pretty-print the input instead of compiling;
|
||||
valid types are: `normal` (un-annotated source),
|
||||
`expanded` (crates expanded),
|
||||
`typed` (crates expanded, with type annotations),
|
||||
`expanded,identified` (fully parenthesized, AST nodes with IDs), or
|
||||
`flowgraph=<nodeid>` (graphviz formatted flowgraph for node)",
|
||||
"TYPE"),
|
||||
optflagopt("", "dep-info",
|
||||
"Output dependency info to <filename> after compiling, \
|
||||
in a format suitable for use by Makefiles", "FILENAME"),
|
||||
|
|
|
@ -11,12 +11,15 @@
|
|||
|
||||
use back::link;
|
||||
use driver::session::Session;
|
||||
use driver::config;
|
||||
use driver::{config, PpMode};
|
||||
use driver::PpmFlowGraph; // FIXME (#14221).
|
||||
use front;
|
||||
use lib::llvm::{ContextRef, ModuleRef};
|
||||
use metadata::common::LinkMeta;
|
||||
use metadata::creader;
|
||||
use metadata::creader::Loader;
|
||||
use middle::cfg;
|
||||
use middle::cfg::graphviz::LabelledCFG;
|
||||
use middle::{trans, freevars, kind, ty, typeck, lint, reachable};
|
||||
use middle::dependency_format;
|
||||
use middle;
|
||||
|
@ -24,6 +27,8 @@ use util::common::time;
|
|||
use util::ppaux;
|
||||
use util::nodemap::{NodeSet};
|
||||
|
||||
use dot = graphviz;
|
||||
|
||||
use serialize::{json, Encodable};
|
||||
|
||||
use std::io;
|
||||
|
@ -582,14 +587,14 @@ impl pprust::PpAnn for TypedAnnotation {
|
|||
pub fn pretty_print_input(sess: Session,
|
||||
cfg: ast::CrateConfig,
|
||||
input: &Input,
|
||||
ppm: ::driver::PpMode,
|
||||
ppm: PpMode,
|
||||
ofile: Option<Path>) {
|
||||
let krate = phase_1_parse_input(&sess, cfg, input);
|
||||
let id = link::find_crate_id(krate.attrs.as_slice(),
|
||||
input.filestem().as_slice());
|
||||
|
||||
let (krate, ast_map, is_expanded) = match ppm {
|
||||
PpmExpanded | PpmExpandedIdentified | PpmTyped => {
|
||||
PpmExpanded | PpmExpandedIdentified | PpmTyped | PpmFlowGraph(_) => {
|
||||
let loader = &mut Loader::new(&sess);
|
||||
let (krate, ast_map) = phase_2_configure_and_expand(&sess,
|
||||
loader,
|
||||
|
@ -644,6 +649,18 @@ pub fn pretty_print_input(sess: Session,
|
|||
&annotation,
|
||||
is_expanded)
|
||||
}
|
||||
PpmFlowGraph(nodeid) => {
|
||||
let ast_map = ast_map.expect("--pretty flowgraph missing ast_map");
|
||||
let node = ast_map.find(nodeid).unwrap_or_else(|| {
|
||||
fail!("--pretty flowgraph=id couldn't find id: {}", id)
|
||||
});
|
||||
let block = match node {
|
||||
syntax::ast_map::NodeBlock(block) => block,
|
||||
_ => fail!("--pretty=flowgraph needs block, got {:?}", node)
|
||||
};
|
||||
let analysis = phase_3_run_analysis_passes(sess, &krate, ast_map);
|
||||
print_flowgraph(analysis, block, out)
|
||||
}
|
||||
_ => {
|
||||
pprust::print_crate(sess.codemap(),
|
||||
sess.diagnostic(),
|
||||
|
@ -658,6 +675,32 @@ pub fn pretty_print_input(sess: Session,
|
|||
|
||||
}
|
||||
|
||||
fn print_flowgraph<W:io::Writer>(analysis: CrateAnalysis,
|
||||
block: ast::P<ast::Block>,
|
||||
mut out: W) -> io::IoResult<()> {
|
||||
let ty_cx = &analysis.ty_cx;
|
||||
let cfg = cfg::CFG::new(ty_cx, block);
|
||||
let lcfg = LabelledCFG { ast_map: &ty_cx.map,
|
||||
cfg: &cfg,
|
||||
name: format!("block{}", block.id).to_strbuf(), };
|
||||
debug!("cfg: {:?}", cfg);
|
||||
let r = dot::render(&lcfg, &mut out);
|
||||
return expand_err_details(r);
|
||||
|
||||
fn expand_err_details(r: io::IoResult<()>) -> io::IoResult<()> {
|
||||
r.map_err(|ioerr| {
|
||||
let orig_detail = ioerr.detail.clone();
|
||||
let m = "graphviz::render failed";
|
||||
io::IoError {
|
||||
detail: Some(match orig_detail {
|
||||
None => m.into_owned(), Some(d) => format!("{}: {}", m, d)
|
||||
}),
|
||||
..ioerr
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_crate_types(session: &Session,
|
||||
attrs: &[ast::Attribute]) -> Vec<config::CrateType> {
|
||||
// If we're generating a test executable, then ignore all other output
|
||||
|
|
|
@ -285,20 +285,32 @@ pub enum PpMode {
|
|||
PpmExpanded,
|
||||
PpmTyped,
|
||||
PpmIdentified,
|
||||
PpmExpandedIdentified
|
||||
PpmExpandedIdentified,
|
||||
PpmFlowGraph(ast::NodeId),
|
||||
}
|
||||
|
||||
pub fn parse_pretty(sess: &Session, name: &str) -> PpMode {
|
||||
match name {
|
||||
"normal" => PpmNormal,
|
||||
"expanded" => PpmExpanded,
|
||||
"typed" => PpmTyped,
|
||||
"expanded,identified" => PpmExpandedIdentified,
|
||||
"identified" => PpmIdentified,
|
||||
let mut split = name.splitn('=', 1);
|
||||
let first = split.next().unwrap();
|
||||
let opt_second = split.next();
|
||||
match (opt_second, first) {
|
||||
(None, "normal") => PpmNormal,
|
||||
(None, "expanded") => PpmExpanded,
|
||||
(None, "typed") => PpmTyped,
|
||||
(None, "expanded,identified") => PpmExpandedIdentified,
|
||||
(None, "identified") => PpmIdentified,
|
||||
(Some(s), "flowgraph") => {
|
||||
match from_str(s) {
|
||||
Some(id) => PpmFlowGraph(id),
|
||||
None => sess.fatal(format!("`pretty flowgraph=<nodeid>` needs \
|
||||
an integer <nodeid>; got {}", s))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
sess.fatal("argument to `pretty` must be one of `normal`, \
|
||||
`expanded`, `typed`, `identified`, \
|
||||
or `expanded,identified`");
|
||||
sess.fatal(format!(
|
||||
"argument to `pretty` must be one of `normal`, \
|
||||
`expanded`, `flowgraph=<nodeid>`, `typed`, `identified`, \
|
||||
or `expanded,identified`; got {}", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ This API is completely unstable and subject to change.
|
|||
|
||||
extern crate flate;
|
||||
extern crate arena;
|
||||
extern crate graphviz;
|
||||
extern crate syntax;
|
||||
extern crate serialize;
|
||||
extern crate sync;
|
||||
|
@ -122,4 +123,3 @@ pub mod lib {
|
|||
pub fn main() {
|
||||
std::os::set_exit_status(driver::main_args(std::os::args().as_slice()));
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ use util::nodemap::NodeMap;
|
|||
|
||||
struct CFGBuilder<'a> {
|
||||
tcx: &'a ty::ctxt,
|
||||
method_map: typeck::MethodMap,
|
||||
exit_map: NodeMap<CFGIndex>,
|
||||
graph: CFGGraph,
|
||||
fn_exit: CFGIndex,
|
||||
|
@ -32,7 +31,6 @@ struct LoopScope {
|
|||
}
|
||||
|
||||
pub fn construct(tcx: &ty::ctxt,
|
||||
method_map: typeck::MethodMap,
|
||||
blk: &ast::Block) -> CFG {
|
||||
let mut graph = graph::Graph::new();
|
||||
let entry = add_initial_dummy_node(&mut graph);
|
||||
|
@ -49,7 +47,6 @@ pub fn construct(tcx: &ty::ctxt,
|
|||
graph: graph,
|
||||
fn_exit: fn_exit,
|
||||
tcx: tcx,
|
||||
method_map: method_map,
|
||||
loop_scopes: Vec::new()
|
||||
};
|
||||
block_exit = cfg_builder.block(blk, entry);
|
||||
|
@ -551,6 +548,6 @@ impl<'a> CFGBuilder<'a> {
|
|||
|
||||
fn is_method_call(&self, expr: &ast::Expr) -> bool {
|
||||
let method_call = typeck::MethodCall::expr(expr.id);
|
||||
self.method_map.borrow().contains_key(&method_call)
|
||||
self.tcx.method_map.borrow().contains_key(&method_call)
|
||||
}
|
||||
}
|
||||
|
|
116
src/librustc/middle/cfg/graphviz.rs
Normal file
116
src/librustc/middle/cfg/graphviz.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
// 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 rustc::middle::graph and
|
||||
/// libgraphviz traits.
|
||||
|
||||
/// For clarity, rename the graphviz crate locally to dot.
|
||||
use dot = graphviz;
|
||||
|
||||
use syntax::ast;
|
||||
use syntax::ast_map;
|
||||
|
||||
use middle::cfg;
|
||||
|
||||
pub type Node<'a> = (cfg::CFGIndex, &'a cfg::CFGNode);
|
||||
pub type Edge<'a> = &'a cfg::CFGEdge;
|
||||
|
||||
pub struct LabelledCFG<'a>{
|
||||
pub ast_map: &'a ast_map::Map,
|
||||
pub cfg: &'a cfg::CFG,
|
||||
pub name: StrBuf,
|
||||
}
|
||||
|
||||
fn replace_newline_with_backslash_l(s: StrBuf) -> StrBuf {
|
||||
// Replacing newlines with \\l causes each line to be left-aligned,
|
||||
// improving presentation of (long) pretty-printed expressions.
|
||||
if s.as_slice().contains("\n") {
|
||||
let mut s = s.replace("\n", "\\l");
|
||||
// Apparently left-alignment applies to the line that precedes
|
||||
// \l, not the line that follows; so, add \l at end of string
|
||||
// if not already present, ensuring last line gets left-aligned
|
||||
// as well.
|
||||
let mut last_two : Vec<_> = s.chars().rev().take(2).collect();
|
||||
last_two.reverse();
|
||||
if last_two.as_slice() != ['\\', 'l'] {
|
||||
s = s.append("\\l");
|
||||
}
|
||||
s.to_strbuf()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> dot::Labeller<'a, Node<'a>, Edge<'a>> for LabelledCFG<'a> {
|
||||
fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.name.as_slice()) }
|
||||
|
||||
fn node_id(&'a self, &(i,_): &Node<'a>) -> dot::Id<'a> {
|
||||
dot::Id::new(format!("N{:u}", i.node_id()))
|
||||
}
|
||||
|
||||
fn node_label(&'a self, &(i, n): &Node<'a>) -> dot::LabelText<'a> {
|
||||
if i == self.cfg.entry {
|
||||
dot::LabelStr("entry".into_maybe_owned())
|
||||
} else if i == self.cfg.exit {
|
||||
dot::LabelStr("exit".into_maybe_owned())
|
||||
} else if n.data.id == ast::DUMMY_NODE_ID {
|
||||
dot::LabelStr("(dummy_node)".into_maybe_owned())
|
||||
} else {
|
||||
let s = self.ast_map.node_to_str(n.data.id);
|
||||
// left-aligns the lines
|
||||
let s = replace_newline_with_backslash_l(s);
|
||||
dot::EscStr(s.into_maybe_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn edge_label(&self, e: &Edge<'a>) -> dot::LabelText<'a> {
|
||||
let mut label = StrBuf::new();
|
||||
let mut put_one = false;
|
||||
for (i, &node_id) in e.data.exiting_scopes.iter().enumerate() {
|
||||
if put_one {
|
||||
label = label.append(",\\l");
|
||||
} else {
|
||||
put_one = true;
|
||||
}
|
||||
let s = self.ast_map.node_to_str(node_id);
|
||||
// left-aligns the lines
|
||||
let s = replace_newline_with_backslash_l(s);
|
||||
label = label.append(format!("exiting scope_{} {}", i, s.as_slice()));
|
||||
}
|
||||
dot::EscStr(label.into_maybe_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for &'a cfg::CFG {
|
||||
fn nodes(&self) -> dot::Nodes<'a, Node<'a>> {
|
||||
let mut v = Vec::new();
|
||||
self.graph.each_node(|i, nd| { v.push((i, nd)); true });
|
||||
dot::maybe_owned_vec::Growable(v)
|
||||
}
|
||||
fn edges(&self) -> dot::Edges<'a, Edge<'a>> {
|
||||
self.graph.all_edges().iter().collect()
|
||||
}
|
||||
fn source(&self, edge: &Edge<'a>) -> Node<'a> {
|
||||
let i = edge.source();
|
||||
(i, self.graph.node(i))
|
||||
}
|
||||
fn target(&self, edge: &Edge<'a>) -> Node<'a> {
|
||||
let i = edge.target();
|
||||
(i, self.graph.node(i))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for LabelledCFG<'a>
|
||||
{
|
||||
fn nodes(&self) -> dot::Nodes<'a, Node<'a>> { self.cfg.nodes() }
|
||||
fn edges(&self) -> dot::Edges<'a, Edge<'a>> { self.cfg.edges() }
|
||||
fn source(&self, edge: &Edge<'a>) -> Node<'a> { self.cfg.source(edge) }
|
||||
fn target(&self, edge: &Edge<'a>) -> Node<'a> { self.cfg.target(edge) }
|
||||
}
|
|
@ -19,25 +19,25 @@ Uses `Graph` as the underlying representation.
|
|||
|
||||
use middle::graph;
|
||||
use middle::ty;
|
||||
use middle::typeck;
|
||||
use syntax::ast;
|
||||
use util::nodemap::NodeMap;
|
||||
|
||||
mod construct;
|
||||
pub mod graphviz;
|
||||
|
||||
pub struct CFG {
|
||||
exit_map: NodeMap<CFGIndex>,
|
||||
graph: CFGGraph,
|
||||
entry: CFGIndex,
|
||||
exit: CFGIndex,
|
||||
pub exit_map: NodeMap<CFGIndex>,
|
||||
pub graph: CFGGraph,
|
||||
pub entry: CFGIndex,
|
||||
pub exit: CFGIndex,
|
||||
}
|
||||
|
||||
pub struct CFGNodeData {
|
||||
id: ast::NodeId
|
||||
pub id: ast::NodeId
|
||||
}
|
||||
|
||||
pub struct CFGEdgeData {
|
||||
exiting_scopes: Vec<ast::NodeId>
|
||||
pub exiting_scopes: Vec<ast::NodeId>
|
||||
}
|
||||
|
||||
pub type CFGIndex = graph::NodeIndex;
|
||||
|
@ -55,8 +55,7 @@ pub struct CFGIndices {
|
|||
|
||||
impl CFG {
|
||||
pub fn new(tcx: &ty::ctxt,
|
||||
method_map: typeck::MethodMap,
|
||||
blk: &ast::Block) -> CFG {
|
||||
construct::construct(tcx, method_map, blk)
|
||||
construct::construct(tcx, blk)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue