Skip to content

Extend --pretty flowgraph=ID to include dataflow results in output. #15371

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

Merged
merged 1 commit into from
Jul 15, 2014
Merged
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
36 changes: 30 additions & 6 deletions src/libgraphviz/lib.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 {:?}",
Copy link
Member

Choose a reason for hiding this comment

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

Oh, cool. I like this generalisation. (Would be neat to allow flowgraph=some::module::function rather than having to manually extract the node, but... extracting it is not so hard so long-term goal.)

Copy link
Member Author

Choose a reason for hiding this comment

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

(yeah I don't feel like adding this right now.)

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
Original file line number Diff line number Diff line change
@@ -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) }
}
Loading