Skip to content

Commit

Permalink
Extend --pretty flowgraph=ID to include dataflow results in output.
Browse files Browse the repository at this point in the history
Use one or more of the following `-Z` flag options to tell the
graphviz renderer to include the corresponding dataflow sets (after
the iterative constraint propagation reaches a fixed-point solution):

  * `-Z flowgraph-print-loans` : loans computed via middle::borrowck
  * `-Z flowgraph-print-moves` : moves computed via middle::borrowck::move_data
  * `-Z flowgraph-print-assigns` : assignments, via middle::borrowck::move_data
  * `-Z flowgraph-print-all` : all of the available sets are included.

Fix #15016.

----

This also adds a module, `syntax::ast_map::blocks`, that captures a
common abstraction shared amongst code blocks and procedure-like
things.  As part of this, moved `ast_map.rs` to subdir
`ast_map/mod.rs`, to follow our directory layout conventions.

(incorporated review feedback from huon, acrichto.)
  • Loading branch information
pnkfelix committed Jul 15, 2014
1 parent 996263a commit e64f594
Show file tree
Hide file tree
Showing 10 changed files with 627 additions and 47 deletions.
36 changes: 30 additions & 6 deletions src/libgraphviz/lib.rs
Expand Up @@ -434,10 +434,37 @@ impl<'a> LabelText<'a> {
/// Renders text as string suitable for a label in a .dot file.
pub fn escape(&self) -> String {
match self {
&LabelStr(ref s) => s.as_slice().escape_default().to_string(),
&EscStr(ref s) => LabelText::escape_str(s.as_slice()).to_string(),
&LabelStr(ref s) => s.as_slice().escape_default(),
&EscStr(ref s) => LabelText::escape_str(s.as_slice()),
}
}

/// Decomposes content into string suitable for making EscStr that
/// yields same content as self. The result obeys the law
/// render(`lt`) == render(`EscStr(lt.pre_escaped_content())`) for
/// all `lt: LabelText`.
fn pre_escaped_content(self) -> str::MaybeOwned<'a> {
match self {
EscStr(s) => s,
LabelStr(s) => if s.as_slice().contains_char('\\') {
str::Owned(s.as_slice().escape_default())
} else {
s
},
}
}

/// Puts `prefix` on a line above this label, with a blank line separator.
pub fn prefix_line(self, prefix: LabelText) -> LabelText {
prefix.suffix_line(self)
}

/// Puts `suffix` on a line below this label, with a blank line separator.
pub fn suffix_line(self, suffix: LabelText) -> LabelText {
let prefix = self.pre_escaped_content().into_string();
let suffix = suffix.pre_escaped_content();
EscStr(str::Owned(prefix.append(r"\n\n").append(suffix.as_slice())))
}
}

pub type Nodes<'a,N> = MaybeOwnedVector<'a,N>;
Expand Down Expand Up @@ -664,10 +691,7 @@ mod tests {
let mut writer = MemWriter::new();
render(&g, &mut writer).unwrap();
let mut r = BufReader::new(writer.get_ref());
match r.read_to_string() {
Ok(string) => Ok(string.to_string()),
Err(err) => Err(err),
}
r.read_to_string()
}

// All of the tests use raw-strings as the format for the expected outputs,
Expand Down
16 changes: 14 additions & 2 deletions src/librustc/driver/config.rs
Expand Up @@ -179,7 +179,11 @@ debugging_opts!(
AST_JSON,
AST_JSON_NOEXPAND,
LS,
SAVE_ANALYSIS
SAVE_ANALYSIS,
FLOWGRAPH_PRINT_LOANS,
FLOWGRAPH_PRINT_MOVES,
FLOWGRAPH_PRINT_ASSIGNS,
FLOWGRAPH_PRINT_ALL
]
0
)
Expand Down Expand Up @@ -215,7 +219,15 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
("ast-json-noexpand", "Print the pre-expansion AST as JSON and halt", AST_JSON_NOEXPAND),
("ls", "List the symbols defined by a library crate", LS),
("save-analysis", "Write syntax and type analysis information \
in addition to normal output", SAVE_ANALYSIS))
in addition to normal output", SAVE_ANALYSIS),
("flowgraph-print-loans", "Include loan analysis data in \
--pretty flowgraph output", FLOWGRAPH_PRINT_LOANS),
("flowgraph-print-moves", "Include move analysis data in \
--pretty flowgraph output", FLOWGRAPH_PRINT_MOVES),
("flowgraph-print-assigns", "Include assignment analysis data in \
--pretty flowgraph output", FLOWGRAPH_PRINT_ASSIGNS),
("flowgraph-print-all", "Include all dataflow analysis data in \
--pretty flowgraph output", FLOWGRAPH_PRINT_ALL))
}

/// Declare a macro that will define all CodegenOptions fields and parsers all
Expand Down
94 changes: 78 additions & 16 deletions src/librustc/driver/driver.rs
Expand Up @@ -19,6 +19,9 @@ use lib::llvm::{ContextRef, ModuleRef};
use lint;
use metadata::common::LinkMeta;
use metadata::creader;
use middle::borrowck::{FnPartsWithCFG};
use middle::borrowck;
use borrowck_dot = middle::borrowck::graphviz;
use middle::cfg;
use middle::cfg::graphviz::LabelledCFG;
use middle::{trans, freevars, stability, kind, ty, typeck, reachable};
Expand All @@ -40,6 +43,7 @@ use std::io;
use std::io::fs;
use std::io::MemReader;
use syntax::ast;
use syntax::ast_map::blocks;
use syntax::attr;
use syntax::attr::{AttrMetaMethods};
use syntax::diagnostics;
Expand Down Expand Up @@ -662,6 +666,25 @@ impl pprust::PpAnn for TypedAnnotation {
}
}

fn gather_flowgraph_variants(sess: &Session) -> Vec<borrowck_dot::Variant> {
let print_loans = config::FLOWGRAPH_PRINT_LOANS;
let print_moves = config::FLOWGRAPH_PRINT_MOVES;
let print_assigns = config::FLOWGRAPH_PRINT_ASSIGNS;
let print_all = config::FLOWGRAPH_PRINT_ALL;
let opt = |print_which| sess.debugging_opt(print_which);
let mut variants = Vec::new();
if opt(print_all) || opt(print_loans) {
variants.push(borrowck_dot::Loans);
}
if opt(print_all) || opt(print_moves) {
variants.push(borrowck_dot::Moves);
}
if opt(print_all) || opt(print_assigns) {
variants.push(borrowck_dot::Assigns);
}
variants
}

pub fn pretty_print_input(sess: Session,
cfg: ast::CrateConfig,
input: &Input,
Expand Down Expand Up @@ -733,10 +756,17 @@ pub fn pretty_print_input(sess: Session,
sess.fatal(format!("--pretty flowgraph couldn't find id: {}",
nodeid).as_slice())
});
let block = match node {
syntax::ast_map::NodeBlock(block) => block,
_ => {
let message = format!("--pretty=flowgraph needs block, got {:?}",
let code = blocks::Code::from_node(node);
match code {
Some(code) => {
let variants = gather_flowgraph_variants(&sess);
let analysis = phase_3_run_analysis_passes(sess, &krate,
ast_map, id);
print_flowgraph(variants, analysis, code, out)
}
None => {
let message = format!("--pretty=flowgraph needs \
block, fn, or method; got {:?}",
node);

// point to what was found, if there's an
Expand All @@ -746,10 +776,7 @@ pub fn pretty_print_input(sess: Session,
None => sess.fatal(message.as_slice())
}
}
};
let analysis = phase_3_run_analysis_passes(sess, &krate,
ast_map, id);
print_flowgraph(analysis, block, out)
}
}
_ => {
pprust::print_crate(sess.codemap(),
Expand All @@ -765,17 +792,52 @@ pub fn pretty_print_input(sess: Session,

}

fn print_flowgraph<W:io::Writer>(analysis: CrateAnalysis,
block: ast::P<ast::Block>,
fn print_flowgraph<W:io::Writer>(variants: Vec<borrowck_dot::Variant>,
analysis: CrateAnalysis,
code: blocks::Code,
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), };
let cfg = match code {
blocks::BlockCode(block) => cfg::CFG::new(ty_cx, &*block),
blocks::FnLikeCode(fn_like) => cfg::CFG::new(ty_cx, fn_like.body()),
};
debug!("cfg: {:?}", cfg);
let r = dot::render(&lcfg, &mut out);
return expand_err_details(r);

match code {
_ if variants.len() == 0 => {
let lcfg = LabelledCFG {
ast_map: &ty_cx.map,
cfg: &cfg,
name: format!("node_{}", code.id()),
};
let r = dot::render(&lcfg, &mut out);
return expand_err_details(r);
}
blocks::BlockCode(_) => {
ty_cx.sess.err("--pretty flowgraph with -Z flowgraph-print \
annotations requires fn-like node id.");
return Ok(())
}
blocks::FnLikeCode(fn_like) => {
let fn_parts = FnPartsWithCFG::from_fn_like(&fn_like, &cfg);
let (bccx, analysis_data) =
borrowck::build_borrowck_dataflow_data_for_fn(ty_cx, fn_parts);

let lcfg = LabelledCFG {
ast_map: &ty_cx.map,
cfg: &cfg,
name: format!("node_{}", code.id()),
};
let lcfg = borrowck_dot::DataflowLabeller {
inner: lcfg,
variants: variants,
borrowck_ctxt: &bccx,
analysis_data: &analysis_data,
};
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| {
Expand Down
148 changes: 148 additions & 0 deletions src/librustc/middle/borrowck/graphviz.rs
@@ -0,0 +1,148 @@
// 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, specialized to attaching borrowck analysis
//! data to rendered labels.

/// For clarity, rename the graphviz crate locally to dot.
use dot = graphviz;
pub use middle::cfg::graphviz::{Node, Edge};
use cfg_dot = middle::cfg::graphviz;

use middle::borrowck;
use middle::borrowck::{BorrowckCtxt, LoanPath};
use middle::cfg::{CFGIndex};
use middle::dataflow::{DataFlowOperator, DataFlowContext, EntryOrExit};
use middle::dataflow;

use std::rc::Rc;
use std::str;

#[deriving(Show)]
pub enum Variant {
Loans,
Moves,
Assigns,
}

impl Variant {
pub fn short_name(&self) -> &'static str {
match *self {
Loans => "loans",
Moves => "moves",
Assigns => "assigns",
}
}
}

pub struct DataflowLabeller<'a> {
pub inner: cfg_dot::LabelledCFG<'a>,
pub variants: Vec<Variant>,
pub borrowck_ctxt: &'a BorrowckCtxt<'a>,
pub analysis_data: &'a borrowck::AnalysisData<'a>,
}

impl<'a> DataflowLabeller<'a> {
fn dataflow_for(&self, e: EntryOrExit, n: &Node<'a>) -> String {
let id = n.val1().data.id;
debug!("dataflow_for({}, id={}) {}", e, id, self.variants);
let mut sets = "".to_string();
let mut seen_one = false;
for &variant in self.variants.iter() {
if seen_one { sets.push_str(" "); } else { seen_one = true; }
sets.push_str(variant.short_name());
sets.push_str(": ");
sets.push_str(self.dataflow_for_variant(e, n, variant).as_slice());
}
sets
}

fn dataflow_for_variant(&self, e: EntryOrExit, n: &Node, v: Variant) -> String {
let cfgidx = n.val0();
match v {
Loans => self.dataflow_loans_for(e, cfgidx),
Moves => self.dataflow_moves_for(e, cfgidx),
Assigns => self.dataflow_assigns_for(e, cfgidx),
}
}

fn build_set<O:DataFlowOperator>(&self,
e: EntryOrExit,
cfgidx: CFGIndex,
dfcx: &DataFlowContext<'a, O>,
to_lp: |uint| -> Rc<LoanPath>) -> String {
let mut saw_some = false;
let mut set = "{".to_string();
dfcx.each_bit_for_node(e, cfgidx, |index| {
let lp = to_lp(index);
if saw_some {
set.push_str(", ");
}
let loan_str = self.borrowck_ctxt.loan_path_to_string(&*lp);
set.push_str(loan_str.as_slice());
saw_some = true;
true
});
set.append("}")
}

fn dataflow_loans_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
let dfcx = &self.analysis_data.loans;
let loan_index_to_path = |loan_index| {
let all_loans = &self.analysis_data.all_loans;
all_loans.get(loan_index).loan_path()
};
self.build_set(e, cfgidx, dfcx, loan_index_to_path)
}

fn dataflow_moves_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
let dfcx = &self.analysis_data.move_data.dfcx_moves;
let move_index_to_path = |move_index| {
let move_data = &self.analysis_data.move_data.move_data;
let moves = move_data.moves.borrow();
let move = moves.get(move_index);
move_data.path_loan_path(move.path)
};
self.build_set(e, cfgidx, dfcx, move_index_to_path)
}

fn dataflow_assigns_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
let dfcx = &self.analysis_data.move_data.dfcx_assign;
let assign_index_to_path = |assign_index| {
let move_data = &self.analysis_data.move_data.move_data;
let assignments = move_data.var_assignments.borrow();
let assignment = assignments.get(assign_index);
move_data.path_loan_path(assignment.path)
};
self.build_set(e, cfgidx, dfcx, assign_index_to_path)
}
}

impl<'a> dot::Labeller<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a> {
fn graph_id(&'a self) -> dot::Id<'a> { self.inner.graph_id() }
fn node_id(&'a self, n: &Node<'a>) -> dot::Id<'a> { self.inner.node_id(n) }
fn node_label(&'a self, n: &Node<'a>) -> dot::LabelText<'a> {
let prefix = self.dataflow_for(dataflow::Entry, n);
let suffix = self.dataflow_for(dataflow::Exit, n);
let inner_label = self.inner.node_label(n);
inner_label
.prefix_line(dot::LabelStr(str::Owned(prefix)))
.suffix_line(dot::LabelStr(str::Owned(suffix)))
}
fn edge_label(&'a self, e: &Edge<'a>) -> dot::LabelText<'a> { self.inner.edge_label(e) }
}

impl<'a> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a> {
fn nodes(&self) -> dot::Nodes<'a, Node<'a>> { self.inner.nodes() }
fn edges(&self) -> dot::Edges<'a, Edge<'a>> { self.inner.edges() }
fn source(&self, edge: &Edge<'a>) -> Node<'a> { self.inner.source(edge) }
fn target(&self, edge: &Edge<'a>) -> Node<'a> { self.inner.target(edge) }
}

5 comments on commit e64f594

@bors
Copy link
Contributor

@bors bors commented on e64f594 Jul 15, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saw approval from alexcrichton
at pnkfelix@e64f594

@bors
Copy link
Contributor

@bors bors commented on e64f594 Jul 15, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merging pnkfelix/rust/fsk-render-dataflow-on-dot = e64f594 into auto

@bors
Copy link
Contributor

@bors bors commented on e64f594 Jul 15, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pnkfelix/rust/fsk-render-dataflow-on-dot = e64f594 merged ok, testing candidate = d336c1a

@bors
Copy link
Contributor

@bors bors commented on e64f594 Jul 15, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fast-forwarding master to auto = d336c1a

Please sign in to comment.