diff --git a/mk/crates.mk b/mk/crates.mk index b43accf610489eef5cd7f74974a8d4413a883e68..943cd528fcdc95b5527a2d039ff24aad40d65050 100644 --- a/mk/crates.mk +++ b/mk/crates.mk @@ -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 diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs index b60468e85bbf16fc5d569f496638c82a992a45d4..cd9e50157f2e2d3c4f46643c4426d95807e3e886 100644 --- a/src/librustc/driver/config.rs +++ b/src/librustc/driver/config.rs @@ -516,12 +516,13 @@ pub fn optgroups() -> Vec { optopt( "", "out-dir", "Write output to compiler-chosen filename in ", "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=` (graphviz formatted flowgraph for node)", + "TYPE"), optflagopt("", "dep-info", "Output dependency info to after compiling, \ in a format suitable for use by Makefiles", "FILENAME"), diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 0b731e18f557480264b0630181a937c57b093a2d..5f9fd7124a9e92289804ed8e3da117b5c3fed88d 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -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) { 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(analysis: CrateAnalysis, + block: ast::P, + 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 { // If we're generating a test executable, then ignore all other output diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index b92075444234bd17c4b926841e691288fa115b97..5a1e27a52560d701369dbd56da04070d957e82bf 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -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=` needs \ + an integer ; 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=`, `typed`, `identified`, \ + or `expanded,identified`; got {}", name)); } } } diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index d3b5fb1ca470c4f62e97f9a962c5c7e43e2346d2..18c59a955b527a413d9e1ed4c54c0f436d106a20 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -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())); } - diff --git a/src/librustc/middle/cfg/construct.rs b/src/librustc/middle/cfg/construct.rs index 40c43c1f42cd5a237ae72ca85664372789af6133..c9c397d3d6121141a0cc6a7dc901dfe884ff3653 100644 --- a/src/librustc/middle/cfg/construct.rs +++ b/src/librustc/middle/cfg/construct.rs @@ -18,7 +18,6 @@ struct CFGBuilder<'a> { tcx: &'a ty::ctxt, - method_map: typeck::MethodMap, exit_map: NodeMap, 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) } } diff --git a/src/librustc/middle/cfg/graphviz.rs b/src/librustc/middle/cfg/graphviz.rs new file mode 100644 index 0000000000000000000000000000000000000000..b8baeefd3d02efdfa8b8e0b75883b6bd5d969866 --- /dev/null +++ b/src/librustc/middle/cfg/graphviz.rs @@ -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 or the MIT license +// , 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) } +} diff --git a/src/librustc/middle/cfg/mod.rs b/src/librustc/middle/cfg/mod.rs index 97ea996bb7e458e3dd53aecd37b2c375e4b38118..f0b912fb87bbff17b7f4aac7ebf31b8c90fd2efb 100644 --- a/src/librustc/middle/cfg/mod.rs +++ b/src/librustc/middle/cfg/mod.rs @@ -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, - graph: CFGGraph, - entry: CFGIndex, - exit: CFGIndex, + pub exit_map: NodeMap, + 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 + pub exiting_scopes: Vec } 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) } }