Skip to content

Commit aaf398f

Browse files
pnkfelixalexcrichton
authored andcommitted
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.)
1 parent 65b65fe commit aaf398f

File tree

8 files changed

+203
-34
lines changed

8 files changed

+203
-34
lines changed

mk/crates.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ TOOLS := compiletest rustdoc rustc
5959
DEPS_core :=
6060
DEPS_rlibc :=
6161
DEPS_std := core libc native:rustrt native:compiler-rt native:backtrace native:jemalloc
62+
DEPS_graphviz := std
6263
DEPS_green := std rand native:context_switch
6364
DEPS_rustuv := std native:uv native:uv_support
6465
DEPS_native := std
6566
DEPS_syntax := std term serialize collections log fmt_macros
6667
DEPS_rustc := syntax native:rustllvm flate arena serialize sync getopts \
67-
collections time log
68+
collections time log graphviz
6869
DEPS_rustdoc := rustc native:hoedown serialize sync getopts collections \
6970
test time
7071
DEPS_flate := std native:miniz

src/librustc/driver/config.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -516,12 +516,13 @@ pub fn optgroups() -> Vec<getopts::OptGroup> {
516516
optopt( "", "out-dir", "Write output to compiler-chosen filename in <dir>", "DIR"),
517517
optflag("", "parse-only", "Parse only; do not compile, assemble, or link"),
518518
optflagopt("", "pretty",
519-
"Pretty-print the input instead of compiling;
520-
valid types are: normal (un-annotated source),
521-
expanded (crates expanded),
522-
typed (crates expanded, with type annotations),
523-
or identified (fully parenthesized,
524-
AST nodes and blocks with IDs)", "TYPE"),
519+
"Pretty-print the input instead of compiling;
520+
valid types are: `normal` (un-annotated source),
521+
`expanded` (crates expanded),
522+
`typed` (crates expanded, with type annotations),
523+
`expanded,identified` (fully parenthesized, AST nodes with IDs), or
524+
`flowgraph=<nodeid>` (graphviz formatted flowgraph for node)",
525+
"TYPE"),
525526
optflagopt("", "dep-info",
526527
"Output dependency info to <filename> after compiling, \
527528
in a format suitable for use by Makefiles", "FILENAME"),

src/librustc/driver/driver.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,24 @@
1111

1212
use back::link;
1313
use driver::session::Session;
14-
use driver::config;
14+
use driver::{config, PpMode};
15+
use driver::PpmFlowGraph; // FIXME (#14221).
1516
use front;
1617
use lib::llvm::{ContextRef, ModuleRef};
1718
use metadata::common::LinkMeta;
1819
use metadata::creader;
1920
use metadata::creader::Loader;
21+
use middle::cfg;
22+
use middle::cfg::graphviz::LabelledCFG;
2023
use middle::{trans, freevars, kind, ty, typeck, lint, reachable};
2124
use middle::dependency_format;
2225
use middle;
2326
use util::common::time;
2427
use util::ppaux;
2528
use util::nodemap::{NodeSet};
2629

30+
use dot = graphviz;
31+
2732
use serialize::{json, Encodable};
2833

2934
use std::io;
@@ -582,14 +587,14 @@ impl pprust::PpAnn for TypedAnnotation {
582587
pub fn pretty_print_input(sess: Session,
583588
cfg: ast::CrateConfig,
584589
input: &Input,
585-
ppm: ::driver::PpMode,
590+
ppm: PpMode,
586591
ofile: Option<Path>) {
587592
let krate = phase_1_parse_input(&sess, cfg, input);
588593
let id = link::find_crate_id(krate.attrs.as_slice(),
589594
input.filestem().as_slice());
590595

591596
let (krate, ast_map, is_expanded) = match ppm {
592-
PpmExpanded | PpmExpandedIdentified | PpmTyped => {
597+
PpmExpanded | PpmExpandedIdentified | PpmTyped | PpmFlowGraph(_) => {
593598
let loader = &mut Loader::new(&sess);
594599
let (krate, ast_map) = phase_2_configure_and_expand(&sess,
595600
loader,
@@ -644,6 +649,18 @@ pub fn pretty_print_input(sess: Session,
644649
&annotation,
645650
is_expanded)
646651
}
652+
PpmFlowGraph(nodeid) => {
653+
let ast_map = ast_map.expect("--pretty flowgraph missing ast_map");
654+
let node = ast_map.find(nodeid).unwrap_or_else(|| {
655+
fail!("--pretty flowgraph=id couldn't find id: {}", id)
656+
});
657+
let block = match node {
658+
syntax::ast_map::NodeBlock(block) => block,
659+
_ => fail!("--pretty=flowgraph needs block, got {:?}", node)
660+
};
661+
let analysis = phase_3_run_analysis_passes(sess, &krate, ast_map);
662+
print_flowgraph(analysis, block, out)
663+
}
647664
_ => {
648665
pprust::print_crate(sess.codemap(),
649666
sess.diagnostic(),
@@ -658,6 +675,32 @@ pub fn pretty_print_input(sess: Session,
658675

659676
}
660677

678+
fn print_flowgraph<W:io::Writer>(analysis: CrateAnalysis,
679+
block: ast::P<ast::Block>,
680+
mut out: W) -> io::IoResult<()> {
681+
let ty_cx = &analysis.ty_cx;
682+
let cfg = cfg::CFG::new(ty_cx, block);
683+
let lcfg = LabelledCFG { ast_map: &ty_cx.map,
684+
cfg: &cfg,
685+
name: format!("block{}", block.id).to_strbuf(), };
686+
debug!("cfg: {:?}", cfg);
687+
let r = dot::render(&lcfg, &mut out);
688+
return expand_err_details(r);
689+
690+
fn expand_err_details(r: io::IoResult<()>) -> io::IoResult<()> {
691+
r.map_err(|ioerr| {
692+
let orig_detail = ioerr.detail.clone();
693+
let m = "graphviz::render failed";
694+
io::IoError {
695+
detail: Some(match orig_detail {
696+
None => m.into_owned(), Some(d) => format!("{}: {}", m, d)
697+
}),
698+
..ioerr
699+
}
700+
})
701+
}
702+
}
703+
661704
pub fn collect_crate_types(session: &Session,
662705
attrs: &[ast::Attribute]) -> Vec<config::CrateType> {
663706
// If we're generating a test executable, then ignore all other output

src/librustc/driver/mod.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -285,20 +285,32 @@ pub enum PpMode {
285285
PpmExpanded,
286286
PpmTyped,
287287
PpmIdentified,
288-
PpmExpandedIdentified
288+
PpmExpandedIdentified,
289+
PpmFlowGraph(ast::NodeId),
289290
}
290291

291292
pub fn parse_pretty(sess: &Session, name: &str) -> PpMode {
292-
match name {
293-
"normal" => PpmNormal,
294-
"expanded" => PpmExpanded,
295-
"typed" => PpmTyped,
296-
"expanded,identified" => PpmExpandedIdentified,
297-
"identified" => PpmIdentified,
293+
let mut split = name.splitn('=', 1);
294+
let first = split.next().unwrap();
295+
let opt_second = split.next();
296+
match (opt_second, first) {
297+
(None, "normal") => PpmNormal,
298+
(None, "expanded") => PpmExpanded,
299+
(None, "typed") => PpmTyped,
300+
(None, "expanded,identified") => PpmExpandedIdentified,
301+
(None, "identified") => PpmIdentified,
302+
(Some(s), "flowgraph") => {
303+
match from_str(s) {
304+
Some(id) => PpmFlowGraph(id),
305+
None => sess.fatal(format!("`pretty flowgraph=<nodeid>` needs \
306+
an integer <nodeid>; got {}", s))
307+
}
308+
}
298309
_ => {
299-
sess.fatal("argument to `pretty` must be one of `normal`, \
300-
`expanded`, `typed`, `identified`, \
301-
or `expanded,identified`");
310+
sess.fatal(format!(
311+
"argument to `pretty` must be one of `normal`, \
312+
`expanded`, `flowgraph=<nodeid>`, `typed`, `identified`, \
313+
or `expanded,identified`; got {}", name));
302314
}
303315
}
304316
}

src/librustc/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ This API is completely unstable and subject to change.
3333

3434
extern crate flate;
3535
extern crate arena;
36+
extern crate graphviz;
3637
extern crate syntax;
3738
extern crate serialize;
3839
extern crate sync;
@@ -122,4 +123,3 @@ pub mod lib {
122123
pub fn main() {
123124
std::os::set_exit_status(driver::main_args(std::os::args().as_slice()));
124125
}
125-

src/librustc/middle/cfg/construct.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use util::nodemap::NodeMap;
1818

1919
struct CFGBuilder<'a> {
2020
tcx: &'a ty::ctxt,
21-
method_map: typeck::MethodMap,
2221
exit_map: NodeMap<CFGIndex>,
2322
graph: CFGGraph,
2423
fn_exit: CFGIndex,
@@ -32,7 +31,6 @@ struct LoopScope {
3231
}
3332

3433
pub fn construct(tcx: &ty::ctxt,
35-
method_map: typeck::MethodMap,
3634
blk: &ast::Block) -> CFG {
3735
let mut graph = graph::Graph::new();
3836
let entry = add_initial_dummy_node(&mut graph);
@@ -49,7 +47,6 @@ pub fn construct(tcx: &ty::ctxt,
4947
graph: graph,
5048
fn_exit: fn_exit,
5149
tcx: tcx,
52-
method_map: method_map,
5350
loop_scopes: Vec::new()
5451
};
5552
block_exit = cfg_builder.block(blk, entry);
@@ -551,6 +548,6 @@ impl<'a> CFGBuilder<'a> {
551548

552549
fn is_method_call(&self, expr: &ast::Expr) -> bool {
553550
let method_call = typeck::MethodCall::expr(expr.id);
554-
self.method_map.borrow().contains_key(&method_call)
551+
self.tcx.method_map.borrow().contains_key(&method_call)
555552
}
556553
}

src/librustc/middle/cfg/graphviz.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
/// This module provides linkage between rustc::middle::graph and
12+
/// libgraphviz traits.
13+
14+
/// For clarity, rename the graphviz crate locally to dot.
15+
use dot = graphviz;
16+
17+
use syntax::ast;
18+
use syntax::ast_map;
19+
20+
use middle::cfg;
21+
22+
pub type Node<'a> = (cfg::CFGIndex, &'a cfg::CFGNode);
23+
pub type Edge<'a> = &'a cfg::CFGEdge;
24+
25+
pub struct LabelledCFG<'a>{
26+
pub ast_map: &'a ast_map::Map,
27+
pub cfg: &'a cfg::CFG,
28+
pub name: StrBuf,
29+
}
30+
31+
fn replace_newline_with_backslash_l(s: StrBuf) -> StrBuf {
32+
// Replacing newlines with \\l causes each line to be left-aligned,
33+
// improving presentation of (long) pretty-printed expressions.
34+
if s.as_slice().contains("\n") {
35+
let mut s = s.replace("\n", "\\l");
36+
// Apparently left-alignment applies to the line that precedes
37+
// \l, not the line that follows; so, add \l at end of string
38+
// if not already present, ensuring last line gets left-aligned
39+
// as well.
40+
let mut last_two : Vec<_> = s.chars().rev().take(2).collect();
41+
last_two.reverse();
42+
if last_two.as_slice() != ['\\', 'l'] {
43+
s = s.append("\\l");
44+
}
45+
s.to_strbuf()
46+
} else {
47+
s
48+
}
49+
}
50+
51+
impl<'a> dot::Labeller<'a, Node<'a>, Edge<'a>> for LabelledCFG<'a> {
52+
fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.name.as_slice()) }
53+
54+
fn node_id(&'a self, &(i,_): &Node<'a>) -> dot::Id<'a> {
55+
dot::Id::new(format!("N{:u}", i.node_id()))
56+
}
57+
58+
fn node_label(&'a self, &(i, n): &Node<'a>) -> dot::LabelText<'a> {
59+
if i == self.cfg.entry {
60+
dot::LabelStr("entry".into_maybe_owned())
61+
} else if i == self.cfg.exit {
62+
dot::LabelStr("exit".into_maybe_owned())
63+
} else if n.data.id == ast::DUMMY_NODE_ID {
64+
dot::LabelStr("(dummy_node)".into_maybe_owned())
65+
} else {
66+
let s = self.ast_map.node_to_str(n.data.id);
67+
// left-aligns the lines
68+
let s = replace_newline_with_backslash_l(s);
69+
dot::EscStr(s.into_maybe_owned())
70+
}
71+
}
72+
73+
fn edge_label(&self, e: &Edge<'a>) -> dot::LabelText<'a> {
74+
let mut label = StrBuf::new();
75+
let mut put_one = false;
76+
for (i, &node_id) in e.data.exiting_scopes.iter().enumerate() {
77+
if put_one {
78+
label = label.append(",\\l");
79+
} else {
80+
put_one = true;
81+
}
82+
let s = self.ast_map.node_to_str(node_id);
83+
// left-aligns the lines
84+
let s = replace_newline_with_backslash_l(s);
85+
label = label.append(format!("exiting scope_{} {}", i, s.as_slice()));
86+
}
87+
dot::EscStr(label.into_maybe_owned())
88+
}
89+
}
90+
91+
impl<'a> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for &'a cfg::CFG {
92+
fn nodes(&self) -> dot::Nodes<'a, Node<'a>> {
93+
let mut v = Vec::new();
94+
self.graph.each_node(|i, nd| { v.push((i, nd)); true });
95+
dot::maybe_owned_vec::Growable(v)
96+
}
97+
fn edges(&self) -> dot::Edges<'a, Edge<'a>> {
98+
self.graph.all_edges().iter().collect()
99+
}
100+
fn source(&self, edge: &Edge<'a>) -> Node<'a> {
101+
let i = edge.source();
102+
(i, self.graph.node(i))
103+
}
104+
fn target(&self, edge: &Edge<'a>) -> Node<'a> {
105+
let i = edge.target();
106+
(i, self.graph.node(i))
107+
}
108+
}
109+
110+
impl<'a> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for LabelledCFG<'a>
111+
{
112+
fn nodes(&self) -> dot::Nodes<'a, Node<'a>> { self.cfg.nodes() }
113+
fn edges(&self) -> dot::Edges<'a, Edge<'a>> { self.cfg.edges() }
114+
fn source(&self, edge: &Edge<'a>) -> Node<'a> { self.cfg.source(edge) }
115+
fn target(&self, edge: &Edge<'a>) -> Node<'a> { self.cfg.target(edge) }
116+
}

src/librustc/middle/cfg/mod.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,25 @@ Uses `Graph` as the underlying representation.
1919

2020
use middle::graph;
2121
use middle::ty;
22-
use middle::typeck;
2322
use syntax::ast;
2423
use util::nodemap::NodeMap;
2524

2625
mod construct;
26+
pub mod graphviz;
2727

2828
pub struct CFG {
29-
exit_map: NodeMap<CFGIndex>,
30-
graph: CFGGraph,
31-
entry: CFGIndex,
32-
exit: CFGIndex,
29+
pub exit_map: NodeMap<CFGIndex>,
30+
pub graph: CFGGraph,
31+
pub entry: CFGIndex,
32+
pub exit: CFGIndex,
3333
}
3434

3535
pub struct CFGNodeData {
36-
id: ast::NodeId
36+
pub id: ast::NodeId
3737
}
3838

3939
pub struct CFGEdgeData {
40-
exiting_scopes: Vec<ast::NodeId>
40+
pub exiting_scopes: Vec<ast::NodeId>
4141
}
4242

4343
pub type CFGIndex = graph::NodeIndex;
@@ -55,8 +55,7 @@ pub struct CFGIndices {
5555

5656
impl CFG {
5757
pub fn new(tcx: &ty::ctxt,
58-
method_map: typeck::MethodMap,
5958
blk: &ast::Block) -> CFG {
60-
construct::construct(tcx, method_map, blk)
59+
construct::construct(tcx, blk)
6160
}
6261
}

0 commit comments

Comments
 (0)