提交 aaf398f2 编写于 作者: F Felix S. Klock II 提交者: Alex Crichton

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.)
上级 65b65fe4
......@@ -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::ppaux;
use util::nodemap::{NodeSet};
use dot = graphviz;
use serialize::{json, Encodable};
use std::io;
......@@ -582,14 +587,14 @@ fn post(&self,
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 @@
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 @@
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 @@ fn find_scope(&self,
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)
}
}
// 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 @@
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)
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册