blob: 47ace8f33ecacecea50a6b8035c5fc39a03e55bd [file] [log] [blame]
use std::cell::RefCell;
use std::io::{self, Write};
use std::{ops, str};
use rustc::hir::def_id::DefId;
use rustc::mir::{self, BasicBlock, Body, Location};
use rustc_index::bit_set::{BitSet, HybridBitSet};
use rustc_index::vec::Idx;
use crate::util::graphviz_safe_def_name;
use super::{Analysis, Results, ResultsRefCursor};
pub struct Formatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
body: &'a Body<'tcx>,
def_id: DefId,
// This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
}
impl<A> Formatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
pub fn new(
body: &'a Body<'tcx>,
def_id: DefId,
results: &'a Results<'tcx, A>,
) -> Self {
let block_formatter = BlockFormatter {
bg: Background::Light,
prev_state: BitSet::new_empty(results.analysis.bits_per_block(body)),
results: ResultsRefCursor::new(body, results),
};
Formatter {
body,
def_id,
block_formatter: RefCell::new(block_formatter),
}
}
}
/// A pair of a basic block and an index into that basic blocks `successors`.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct CfgEdge {
source: BasicBlock,
index: usize,
}
fn outgoing_edges(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
body[bb]
.terminator()
.successors()
.enumerate()
.map(|(index, _)| CfgEdge { source: bb, index })
.collect()
}
impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
type Node = BasicBlock;
type Edge = CfgEdge;
fn graph_id(&self) -> dot::Id<'_> {
let name = graphviz_safe_def_name(self.def_id);
dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
}
fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
dot::Id::new(format!("bb_{}", n.index())).unwrap()
}
fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
let mut label = Vec::new();
self.block_formatter
.borrow_mut()
.write_node_label(&mut label, self.body, *block)
.unwrap();
dot::LabelText::html(String::from_utf8(label).unwrap())
}
fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
Some(dot::LabelText::label("none"))
}
fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
let label = &self.body
[e.source]
.terminator()
.kind
.fmt_successor_labels()
[e.index];
dot::LabelText::label(label.clone())
}
}
impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
type Node = BasicBlock;
type Edge = CfgEdge;
fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
self.body
.basic_blocks()
.indices()
.collect::<Vec<_>>()
.into()
}
fn edges(&self) -> dot::Edges<'_, Self::Edge> {
self.body
.basic_blocks()
.indices()
.flat_map(|bb| outgoing_edges(self.body, bb))
.collect::<Vec<_>>()
.into()
}
fn source(&self, edge: &Self::Edge) -> Self::Node {
edge.source
}
fn target(&self, edge: &Self::Edge) -> Self::Node {
self.body
[edge.source]
.terminator()
.successors()
.nth(edge.index)
.copied()
.unwrap()
}
}
struct BlockFormatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
prev_state: BitSet<A::Idx>,
results: ResultsRefCursor<'a, 'a, 'tcx, A>,
bg: Background,
}
impl<A> BlockFormatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
fn toggle_background(&mut self) -> Background {
let bg = self.bg;
self.bg = !bg;
bg
}
fn write_node_label(
&mut self,
w: &mut impl io::Write,
body: &'a Body<'tcx>,
block: BasicBlock,
) -> io::Result<()> {
// Sample output:
// +-+-----------------------------------------------+
// A | bb4 |
// +-+----------------------------------+------------+
// B | MIR | STATE |
// +-+----------------------------------+------------+
// C | | (on entry) | {_0,_2,_3} |
// +-+----------------------------------+------------+
// D |0| StorageLive(_7) | |
// +-+----------------------------------+------------+
// |1| StorageLive(_8) | |
// +-+----------------------------------+------------+
// |2| _8 = &mut _1 | +_8 |
// +-+----------------------------------+------------+
// E |T| _4 = const Foo::twiddle(move _2) | -_2 |
// +-+----------------------------------+------------+
// F | | (on unwind) | {_0,_3,_8} |
// +-+----------------------------------+------------+
// | | (on successful return) | +_4 |
// +-+----------------------------------+------------+
write!(
w,
r#"<table border="1" cellborder="1" cellspacing="0" cellpadding="3" sides="rb">"#,
)?;
// A: Block info
write!(
w,
r#"<tr>
<td colspan="{num_headers}" sides="tl">bb{block_id}</td>
</tr>"#,
num_headers = 3,
block_id = block.index(),
)?;
// B: Column headings
write!(
w,
r#"<tr>
<td colspan="2" {fmt}>MIR</td>
<td {fmt}>STATE</td>
</tr>"#,
fmt = r##"bgcolor="#a0a0a0" sides="tl""##,
)?;
// C: Entry state
self.bg = Background::Light;
self.results.seek_to_block_start(block);
self.write_row_with_curr_state(w, "", "(on entry)")?;
self.prev_state.overwrite(self.results.get());
// D: Statement transfer functions
for (i, statement) in body[block].statements.iter().enumerate() {
let location = Location { block, statement_index: i };
let mir_col = format!("{:?}", statement);
let i_col = i.to_string();
self.results.seek_after(location);
self.write_row_with_curr_diff(w, &i_col, &mir_col)?;
self.prev_state.overwrite(self.results.get());
}
// E: Terminator transfer function
let terminator = body[block].terminator();
let location = body.terminator_loc(block);
let mut mir_col = String::new();
terminator.kind.fmt_head(&mut mir_col).unwrap();
self.results.seek_after(location);
self.write_row_with_curr_diff(w, "T", &mir_col)?;
self.prev_state.overwrite(self.results.get());
// F: Exit state
if let mir::TerminatorKind::Call { destination: Some(_), .. } = &terminator.kind {
self.write_row_with_curr_state(w, "", "(on unwind)")?;
self.results.seek_after_assume_call_returns(location);
self.write_row_with_curr_diff(w, "", "(on successful return)")?;
} else {
self.write_row_with_curr_state(w, "", "(on exit)")?;
}
write!(w, "</table>")
}
fn write_row_with_curr_state(
&mut self,
w: &mut impl io::Write,
i: &str,
mir: &str,
) -> io::Result<()> {
let bg = self.toggle_background();
let mut out = Vec::new();
write!(&mut out, "{{")?;
pretty_print_state_elems(&mut out, self.results.analysis(), self.results.get().iter())?;
write!(&mut out, "}}")?;
write!(
w,
r#"<tr>
<td {fmt} align="right">{i}</td>
<td {fmt} align="left">{mir}</td>
<td {fmt} align="left">{state}</td>
</tr>"#,
fmt = &["sides=\"tl\"", bg.attr()].join(" "),
i = i,
mir = dot::escape_html(mir),
state = dot::escape_html(str::from_utf8(&out).unwrap()),
)
}
fn write_row_with_curr_diff(
&mut self,
w: &mut impl io::Write,
i: &str,
mir: &str,
) -> io::Result<()> {
let bg = self.toggle_background();
let analysis = self.results.analysis();
let diff = BitSetDiff::compute(&self.prev_state, self.results.get());
let mut set = Vec::new();
pretty_print_state_elems(&mut set, analysis, diff.set.iter())?;
let mut clear = Vec::new();
pretty_print_state_elems(&mut clear, analysis, diff.clear.iter())?;
write!(
w,
r#"<tr>
<td {fmt} align="right">{i}</td>
<td {fmt} align="left">{mir}</td>
<td {fmt} align="left">"#,
i = i,
fmt = &["sides=\"tl\"", bg.attr()].join(" "),
mir = dot::escape_html(mir),
)?;
if !set.is_empty() {
write!(
w,
r#"<font color="darkgreen">+{}</font>"#,
dot::escape_html(str::from_utf8(&set).unwrap()),
)?;
}
if !set.is_empty() && !clear.is_empty() {
write!(w, " ")?;
}
if !clear.is_empty() {
write!(
w,
r#"<font color="red">-{}</font>"#,
dot::escape_html(str::from_utf8(&clear).unwrap()),
)?;
}
write!(w, "</td></tr>")
}
}
/// The operations required to transform one `BitSet` into another.
struct BitSetDiff<T: Idx> {
set: HybridBitSet<T>,
clear: HybridBitSet<T>,
}
impl<T: Idx> BitSetDiff<T> {
fn compute(from: &BitSet<T>, to: &BitSet<T>) -> Self {
assert_eq!(from.domain_size(), to.domain_size());
let len = from.domain_size();
let mut set = HybridBitSet::new_empty(len);
let mut clear = HybridBitSet::new_empty(len);
// FIXME: This could be made faster if `BitSet::xor` were implemented.
for i in (0..len).map(|i| T::new(i)) {
match (from.contains(i), to.contains(i)) {
(false, true) => set.insert(i),
(true, false) => clear.insert(i),
_ => continue,
};
}
BitSetDiff {
set,
clear,
}
}
}
/// Formats each `elem` using the pretty printer provided by `analysis` into a comma-separated
/// list.
fn pretty_print_state_elems<A>(
w: &mut impl io::Write,
analysis: &A,
elems: impl Iterator<Item = A::Idx>,
) -> io::Result<()>
where
A: Analysis<'tcx>,
{
let mut first = true;
for idx in elems {
if first {
first = false;
} else {
write!(w, ",")?;
}
analysis.pretty_print_idx(w, idx)?;
}
Ok(())
}
/// The background color used for zebra-striping the table.
#[derive(Clone, Copy)]
enum Background {
Light,
Dark,
}
impl Background {
fn attr(self) -> &'static str {
match self {
Self::Dark => "bgcolor=\"#f0f0f0\"",
Self::Light => "",
}
}
}
impl ops::Not for Background {
type Output = Self;
fn not(self) -> Self {
match self {
Self::Light => Self::Dark,
Self::Dark => Self::Light,
}
}
}