mir dataflow: change graphviz output

The new output format is perhaps a little more readable. As a bonus, we get
labels on the outgoing edges to more easily corroborate the dataflow with the
plain MIR graphviz output.
This commit is contained in:
bobtwinkles 2018-03-03 17:44:06 -05:00
parent 138365368a
commit 047bec69b9
2 changed files with 155 additions and 148 deletions

View file

@ -711,6 +711,12 @@ impl<'a> IntoCow<'a, str> for &'a str {
}
}
impl<'a> IntoCow<'a, str> for Cow<'a, str> {
fn into_cow(self) -> Cow<'a, str> {
self
}
}
impl<'a, T: Clone> IntoCow<'a, [T]> for Vec<T> {
fn into_cow(self) -> Cow<'a, [T]> {
Cow::Owned(self)

View file

@ -20,12 +20,9 @@ use dot::IntoCow;
use std::fs;
use std::io;
use std::io::prelude::*;
use std::marker::PhantomData;
use std::path::Path;
use util;
use super::{BitDenotation, DataflowState};
use super::DataflowBuilder;
use super::DebugFormatted;
@ -98,158 +95,162 @@ impl<'a, 'tcx, MWF, P> dot::Labeller<'a> for Graph<'a, 'tcx, MWF, P>
}
fn node_label(&self, n: &Node) -> dot::LabelText {
// A standard MIR label, as generated by write_node_label, is
// presented in a single column in a table.
//
// The code below does a bunch of formatting work to format a
// node (i.e. MIR basic-block) label with extra
// dataflow-enriched information. In particular, the goal is
// to add extra columns that present the three dataflow
// bitvectors, and the data those bitvectors represent.
//
// It presents it in the following format (where I am
// presenting the table rendering via ASCII art, one line per
// row of the table, and a chunk size of 3 rather than 5):
//
// ------ ----------------------- ------------ --------------------
// [e1, e3, e4]
// [e8, e9] "= ENTRY:" <ENTRY-BITS>
// ------ ----------------------- ------------ --------------------
// Left
// Most
// Column
// Is
// Just
// Normal
// Series
// Of
// MIR
// Stmts
// ------ ----------------------- ------------ --------------------
// [g1, g4, g5] "= GEN:" <GEN-BITS>
// ------ ----------------------- ------------ --------------------
// "KILL:" <KILL-BITS> "=" [k1, k3, k8]
// [k9]
// ------ ----------------------- ------------ --------------------
//
// (In addition, the added dataflow is rendered with a colored
// background just so it will stand out compared to the
// statements.)
// Node label is something like this:
// +---------+----------------------------------+------------------+------------------+
// | ENTRY | MIR | GEN | KILL |
// +---------+----------------------------------+------------------+------------------+
// | | 0: StorageLive(_7) | bb3[2]: reserved | bb2[0]: reserved |
// | | 1: StorageLive(_8) | bb3[2]: active | bb2[0]: active |
// | | 2: _8 = &mut _1 | | bb4[2]: reserved |
// | | | | bb4[2]: active |
// | | | | bb9[0]: reserved |
// | | | | bb9[0]: active |
// | | | | bb10[0]: reserved|
// | | | | bb10[0]: active |
// | | | | bb11[0]: reserved|
// | | | | bb11[0]: active |
// +---------+----------------------------------+------------------+------------------+
// | [00-00] | _7 = const Foo::twiddle(move _8) | [0c-00] | [f3-0f] |
// +---------+----------------------------------+------------------+------------------+
let mut v = Vec::new();
let i = n.index();
let chunk_size = 5;
const BG_FLOWCONTENT: &'static str = r#"bgcolor="pink""#;
const ALIGN_RIGHT: &'static str = r#"align="right""#;
const FACE_MONOSPACE: &'static str = r#"FACE="Courier""#;
fn chunked_present_left<W:io::Write>(w: &mut W,
interpreted: &[DebugFormatted],
chunk_size: usize)
-> io::Result<()>
{
// This function may emit a sequence of <tr>'s, but it
// always finishes with an (unfinished)
// <tr><td></td><td>
//
// Thus, after being called, one should finish both the
// pending <td> as well as the <tr> itself.
let mut seen_one = false;
for c in interpreted.chunks(chunk_size) {
if seen_one {
// if not the first row, finish off the previous row
write!(w, "</td><td></td><td></td></tr>")?;
}
write!(w, "<tr><td></td><td {bg} {align}>{objs:?}",
bg = BG_FLOWCONTENT,
align = ALIGN_RIGHT,
objs = c)?;
seen_one = true;
}
if !seen_one {
write!(w, "<tr><td></td><td {bg} {align}>[]",
bg = BG_FLOWCONTENT,
align = ALIGN_RIGHT)?;
}
Ok(())
}
util::write_graphviz_node_label(
*n, self.mbcx.mir(), &mut v, 4,
|w| {
let flow = self.mbcx.flow_state();
let entry_interp = flow.interpret_set(&flow.operator,
flow.sets.on_entry_set_for(i),
&self.render_idx);
chunked_present_left(w, &entry_interp[..], chunk_size)?;
let bits_per_block = flow.sets.bits_per_block();
let entry = flow.sets.on_entry_set_for(i);
debug!("entry set for i={i} bits_per_block: {bpb} entry: {e:?} interp: {ei:?}",
i=i, e=entry, bpb=bits_per_block, ei=entry_interp);
write!(w, "= ENTRY:</td><td {bg}><FONT {face}>{entrybits:?}</FONT></td>\
<td></td></tr>",
bg = BG_FLOWCONTENT,
face = FACE_MONOSPACE,
entrybits=bits_to_string(entry.words(), bits_per_block))
},
|w| {
let flow = self.mbcx.flow_state();
let gen_interp =
flow.interpret_set(&flow.operator, flow.sets.gen_set_for(i), &self.render_idx);
let kill_interp =
flow.interpret_set(&flow.operator, flow.sets.kill_set_for(i), &self.render_idx);
chunked_present_left(w, &gen_interp[..], chunk_size)?;
let bits_per_block = flow.sets.bits_per_block();
{
let gen = flow.sets.gen_set_for(i);
debug!("gen set for i={i} bits_per_block: {bpb} gen: {g:?} interp: {gi:?}",
i=i, g=gen, bpb=bits_per_block, gi=gen_interp);
write!(w, " = GEN:</td><td {bg}><FONT {face}>{genbits:?}</FONT></td>\
<td></td></tr>",
bg = BG_FLOWCONTENT,
face = FACE_MONOSPACE,
genbits=bits_to_string(gen.words(), bits_per_block))?;
}
{
let kill = flow.sets.kill_set_for(i);
debug!("kill set for i={i} bits_per_block: {bpb} kill: {k:?} interp: {ki:?}",
i=i, k=kill, bpb=bits_per_block, ki=kill_interp);
write!(w, "<tr><td></td><td {bg} {align}>KILL:</td>\
<td {bg}><FONT {face}>{killbits:?}</FONT></td>",
bg = BG_FLOWCONTENT,
align = ALIGN_RIGHT,
face = FACE_MONOSPACE,
killbits=bits_to_string(kill.words(), bits_per_block))?;
}
// (chunked_present_right)
let mut seen_one = false;
for k in kill_interp.chunks(chunk_size) {
if !seen_one {
// continuation of row; this is fourth <td>
write!(w, "<td {bg}>= {kill:?}</td></tr>",
bg = BG_FLOWCONTENT,
kill=k)?;
} else {
// new row, with indent of three <td>'s
write!(w, "<tr><td></td><td></td><td></td><td {bg}>{kill:?}</td></tr>",
bg = BG_FLOWCONTENT,
kill=k)?;
}
seen_one = true;
}
if !seen_one {
write!(w, "<td {bg}>= []</td></tr>",
bg = BG_FLOWCONTENT)?;
}
Ok(())
})
.unwrap();
self.node_label_internal(n, &mut v, *n, self.mbcx.mir()).unwrap();
dot::LabelText::html(String::from_utf8(v).unwrap())
}
fn node_shape(&self, _n: &Node) -> Option<dot::LabelText> {
Some(dot::LabelText::label("none"))
}
fn edge_label(&'a self, e: &Edge) -> dot::LabelText<'a> {
let term = self.mbcx.mir()[e.source].terminator();
let label = &term.kind.fmt_successor_labels()[e.index];
dot::LabelText::label(label.clone())
}
}
impl<'a, 'tcx, MWF, P> Graph<'a, 'tcx, MWF, P>
where MWF: MirWithFlowState<'tcx>,
P: Fn(&MWF::BD, <MWF::BD as BitDenotation>::Idx) -> DebugFormatted,
{
/// Generate the node label
fn node_label_internal<W: io::Write>(&self,
n: &Node,
w: &mut W,
block: BasicBlock,
mir: &Mir) -> io::Result<()> {
// Header rows
const HDRS: [&'static str; 4] = ["ENTRY", "MIR", "GEN", "KILL"];
const HDR_FMT: &'static str = "bgcolor=\"grey\"";
write!(w, "<table><tr><td rowspan=\"{}\">", HDRS.len())?;
write!(w, "{:?}", block.index())?;
write!(w, "</td></tr><tr>")?;
for hdr in &HDRS {
write!(w, "<td {}>{}</td>", HDR_FMT, hdr)?;
}
write!(w, "</tr>")?;
// Data row
self.node_label_verbose_row(n, w, block, mir)?;
self.node_label_final_row(n, w, block, mir)?;
write!(w, "</table>")?;
Ok(())
}
/// Build the verbose row: full MIR data, and detailed gen/kill/entry sets
fn node_label_verbose_row<W: io::Write>(&self,
n: &Node,
w: &mut W,
block: BasicBlock,
mir: &Mir)
-> io::Result<()> {
let i = n.index();
macro_rules! dump_set_for {
($set:ident) => {
write!(w, "<td>")?;
let flow = self.mbcx.flow_state();
let entry_interp = flow.interpret_set(&flow.operator,
flow.sets.$set(i),
&self.render_idx);
for e in &entry_interp {
write!(w, "{:?}<br/>", e)?;
}
write!(w, "</td>")?;
}
}
write!(w, "<tr>")?;
// Entry
dump_set_for!(on_entry_set_for);
// MIR statements
write!(w, "<td>")?;
{
let data = &mir[block];
for (i, statement) in data.statements.iter().enumerate() {
write!(w, "{}<br align=\"left\"/>",
dot::escape_html(&format!("{:3}: {:?}", i, statement)))?;
}
}
write!(w, "</td>")?;
// Gen
dump_set_for!(gen_set_for);
// Kill
dump_set_for!(kill_set_for);
write!(w, "</tr>")?;
Ok(())
}
/// Build the summary row: terminator, gen/kill/entry bit sets
fn node_label_final_row<W: io::Write>(&self,
n: &Node,
w: &mut W,
block: BasicBlock,
mir: &Mir)
-> io::Result<()> {
let i = n.index();
macro_rules! dump_set_for {
($set:ident) => {
let flow = self.mbcx.flow_state();
let bits_per_block = flow.sets.bits_per_block();
let set = flow.sets.$set(i);
write!(w, "<td>{:?}</td>",
dot::escape_html(&bits_to_string(set.words(), bits_per_block)))?;
}
}
write!(w, "<tr>")?;
// Entry
dump_set_for!(on_entry_set_for);
// Terminator
write!(w, "<td>")?;
{
let data = &mir[block];
let mut terminator_head = String::new();
data.terminator().kind.fmt_head(&mut terminator_head).unwrap();
write!(w, "{}", dot::escape_html(&terminator_head))?;
}
write!(w, "</td>")?;
// Gen
dump_set_for!(gen_set_for);
// Kill
dump_set_for!(kill_set_for);
write!(w, "</tr>")?;
Ok(())
}
}
impl<'a, 'tcx, MWF, P> dot::GraphWalk<'a> for Graph<'a, 'tcx, MWF, P>