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:
parent
138365368a
commit
047bec69b9
2 changed files with 155 additions and 148 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue