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

Add flow-graph visualization (via graphviz) to rustc #14202

Closed
wants to merge 6 commits into from
Closed
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
3 changes: 2 additions & 1 deletion mk/crates.mk
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ TOOLS := compiletest rustdoc rustc

DEPS_core :=
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
Expand Down
13 changes: 7 additions & 6 deletions src/librustc/driver/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
49 changes: 46 additions & 3 deletions src/librustc/driver/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@

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;
use util::common::time;
use util::ppaux;
use util::nodemap::{NodeSet};

use dot = graphviz;

use serialize::{json, Encodable};

use std::io;
Expand Down Expand Up @@ -581,14 +586,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,
Expand Down Expand Up @@ -643,6 +648,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(),
Expand All @@ -657,6 +674,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
Expand Down
32 changes: 22 additions & 10 deletions src/librustc/driver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,20 +284,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));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -122,4 +123,3 @@ pub mod lib {
pub fn main() {
std::os::set_exit_status(driver::main_args(std::os::args().as_slice()));
}

65 changes: 46 additions & 19 deletions src/librustc/middle/cfg/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use util::nodemap::NodeMap;

struct CFGBuilder<'a> {
tcx: &'a ty::ctxt,
method_map: typeck::MethodMap,
exit_map: NodeMap<CFGIndex>,
graph: CFGGraph,
loop_scopes: Vec<LoopScope> ,
fn_exit: CFGIndex,
loop_scopes: Vec<LoopScope>,
}

struct LoopScope {
Expand All @@ -31,22 +31,35 @@ 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);

// `fn_exit` is target of return exprs, which lies somewhere
// outside input `blk`. (Distinguishing `fn_exit` and `block_exit`
// also resolves chicken-and-egg problem that arises if you try to
// have return exprs jump to `block_exit` during construction.)
let fn_exit = add_initial_dummy_node(&mut graph);
let block_exit;

let mut cfg_builder = CFGBuilder {
exit_map: NodeMap::new(),
graph: graph::Graph::new(),
graph: graph,
fn_exit: fn_exit,
tcx: tcx,
method_map: method_map,
loop_scopes: Vec::new()
};
let entry = cfg_builder.add_node(0, []);
let exit = cfg_builder.block(blk, entry);
block_exit = cfg_builder.block(blk, entry);
cfg_builder.add_contained_edge(block_exit, fn_exit);
let CFGBuilder {exit_map, graph, ..} = cfg_builder;
CFG {exit_map: exit_map,
graph: graph,
entry: entry,
exit: exit}
exit: fn_exit}
}

fn add_initial_dummy_node(g: &mut CFGGraph) -> CFGIndex {
g.add_node(CFGNodeData { id: ast::DUMMY_NODE_ID })
}

impl<'a> CFGBuilder<'a> {
Expand Down Expand Up @@ -327,24 +340,25 @@ impl<'a> CFGBuilder<'a> {

ast::ExprRet(v) => {
let v_exit = self.opt_expr(v, pred);
let loop_scope = *self.loop_scopes.get(0);
self.add_exiting_edge(expr, v_exit,
loop_scope, loop_scope.break_index);
self.add_node(expr.id, [])
let b = self.add_node(expr.id, [v_exit]);
self.add_returning_edge(expr, b);
self.add_node(ast::DUMMY_NODE_ID, [])
}

ast::ExprBreak(label) => {
let loop_scope = self.find_scope(expr, label);
self.add_exiting_edge(expr, pred,
let b = self.add_node(expr.id, [pred]);
self.add_exiting_edge(expr, b,
loop_scope, loop_scope.break_index);
self.add_node(expr.id, [])
self.add_node(ast::DUMMY_NODE_ID, [])
}

ast::ExprAgain(label) => {
let loop_scope = self.find_scope(expr, label);
self.add_exiting_edge(expr, pred,
let a = self.add_node(expr.id, [pred]);
self.add_exiting_edge(expr, a,
loop_scope, loop_scope.continue_index);
self.add_node(expr.id, [])
self.add_node(ast::DUMMY_NODE_ID, [])
}

ast::ExprVec(ref elems) => {
Expand Down Expand Up @@ -453,13 +467,16 @@ impl<'a> CFGBuilder<'a> {
}

fn add_dummy_node(&mut self, preds: &[CFGIndex]) -> CFGIndex {
self.add_node(0, preds)
self.add_node(ast::DUMMY_NODE_ID, preds)
}

fn add_node(&mut self, id: ast::NodeId, preds: &[CFGIndex]) -> CFGIndex {
assert!(!self.exit_map.contains_key(&id));
let node = self.graph.add_node(CFGNodeData {id: id});
self.exit_map.insert(id, node);
if id != ast::DUMMY_NODE_ID {
assert!(!self.exit_map.contains_key(&id));
self.exit_map.insert(id, node);
}
for &pred in preds.iter() {
self.add_contained_edge(pred, node);
}
Expand Down Expand Up @@ -488,6 +505,16 @@ impl<'a> CFGBuilder<'a> {
self.graph.add_edge(from_index, to_index, data);
}

fn add_returning_edge(&mut self,
_from_expr: @ast::Expr,
from_index: CFGIndex) {
let mut data = CFGEdgeData {exiting_scopes: vec!() };
for &LoopScope { loop_id: id, .. } in self.loop_scopes.iter().rev() {
data.exiting_scopes.push(id);
}
self.graph.add_edge(from_index, self.fn_exit, data);
}

fn find_scope(&self,
expr: @ast::Expr,
label: Option<ast::Ident>) -> LoopScope {
Expand Down Expand Up @@ -521,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)
}
}
Loading