2019-11-11 11:48:47 -08:00
|
|
|
//! A helpful diagram for debugging dataflow problems.
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
use std::borrow::Cow;
|
2022-06-16 19:39:39 +04:00
|
|
|
use std::sync::OnceLock;
|
2019-11-11 11:48:47 -08:00
|
|
|
use std::{io, ops, str};
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
use regex::Regex;
|
2020-06-02 20:19:49 +03:00
|
|
|
use rustc_graphviz as dot;
|
2021-01-05 19:53:07 +01:00
|
|
|
use rustc_middle::mir::graphviz_safe_def_name;
|
2020-03-29 17:19:48 +02:00
|
|
|
use rustc_middle::mir::{self, BasicBlock, Body, Location};
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
|
2021-08-30 01:23:33 +01:00
|
|
|
use super::{Analysis, CallReturnPlaces, Direction, Results, ResultsRefCursor, ResultsVisitor};
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
|
|
pub enum OutputStyle {
|
|
|
|
AfterOnly,
|
|
|
|
BeforeAndAfter,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OutputStyle {
|
|
|
|
fn num_state_columns(&self) -> usize {
|
|
|
|
match self {
|
|
|
|
Self::AfterOnly => 1,
|
|
|
|
Self::BeforeAndAfter => 2,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 16:26:11 -07:00
|
|
|
pub struct Formatter<'a, 'tcx, A>
|
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
|
|
|
{
|
|
|
|
body: &'a Body<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
results: &'a Results<'tcx, A>,
|
|
|
|
style: OutputStyle,
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
|
2021-12-14 12:02:45 -05:00
|
|
|
impl<'a, 'tcx, A> Formatter<'a, 'tcx, A>
|
2019-09-26 16:26:11 -07:00
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
|
|
|
{
|
2020-10-04 15:22:23 -07:00
|
|
|
pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>, style: OutputStyle) -> Self {
|
|
|
|
Formatter { body, results, style }
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
}
|
|
|
|
|
2021-12-14 12:02:45 -05:00
|
|
|
fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
|
2019-09-26 16:26:11 -07:00
|
|
|
body[bb]
|
|
|
|
.terminator()
|
|
|
|
.successors()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(index, _)| CfgEdge { source: bb, index })
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2021-12-14 12:02:45 -05:00
|
|
|
impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A>
|
2019-09-26 16:26:11 -07:00
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
A::Domain: DebugWithContext<A>,
|
2019-09-26 16:26:11 -07:00
|
|
|
{
|
|
|
|
type Node = BasicBlock;
|
|
|
|
type Edge = CfgEdge;
|
|
|
|
|
|
|
|
fn graph_id(&self) -> dot::Id<'_> {
|
2020-10-04 15:22:23 -07:00
|
|
|
let name = graphviz_safe_def_name(self.body.source.def_id());
|
2019-09-26 16:26:11 -07:00
|
|
|
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();
|
2020-08-27 23:02:46 -07:00
|
|
|
let mut fmt = BlockFormatter {
|
|
|
|
results: ResultsRefCursor::new(self.body, self.results),
|
|
|
|
style: self.style,
|
|
|
|
bg: Background::Light,
|
|
|
|
};
|
|
|
|
|
|
|
|
fmt.write_node_label(&mut label, self.body, *block).unwrap();
|
2019-09-26 16:26:11 -07:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-14 12:02:45 -05:00
|
|
|
impl<'a, 'tcx, A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
|
2019-09-26 16:26:11 -07:00
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
|
|
|
{
|
|
|
|
type Node = BasicBlock;
|
|
|
|
type Edge = CfgEdge;
|
|
|
|
|
|
|
|
fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
|
2022-07-05 00:00:00 +00:00
|
|
|
self.body.basic_blocks.indices().collect::<Vec<_>>().into()
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn edges(&self) -> dot::Edges<'_, Self::Edge> {
|
|
|
|
self.body
|
2022-07-05 00:00:00 +00:00
|
|
|
.basic_blocks
|
2019-09-26 16:26:11 -07:00
|
|
|
.indices()
|
2020-03-22 12:09:40 -07:00
|
|
|
.flat_map(|bb| dataflow_successors(self.body, bb))
|
2019-09-26 16:26:11 -07:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn source(&self, edge: &Self::Edge) -> Self::Node {
|
|
|
|
edge.source
|
|
|
|
}
|
|
|
|
|
|
|
|
fn target(&self, edge: &Self::Edge) -> Self::Node {
|
2022-05-17 08:41:01 +08:00
|
|
|
self.body[edge.source].terminator().successors().nth(edge.index).unwrap()
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct BlockFormatter<'a, 'tcx, A>
|
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
|
|
|
{
|
|
|
|
results: ResultsRefCursor<'a, 'a, 'tcx, A>,
|
|
|
|
bg: Background,
|
2020-08-27 23:02:46 -07:00
|
|
|
style: OutputStyle,
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
|
2021-12-14 12:02:45 -05:00
|
|
|
impl<'a, 'tcx, A> BlockFormatter<'a, 'tcx, A>
|
2019-09-26 16:26:11 -07:00
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
A::Domain: DebugWithContext<A>,
|
2019-09-26 16:26:11 -07:00
|
|
|
{
|
2019-11-11 11:48:47 -08:00
|
|
|
const HEADER_COLOR: &'static str = "#a0a0a0";
|
|
|
|
|
2019-09-26 16:26:11 -07:00
|
|
|
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:
|
2019-10-01 15:23:04 -07:00
|
|
|
// +-+-----------------------------------------------+
|
|
|
|
// 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 |
|
|
|
|
// +-+----------------------------------+------------+
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-01-21 13:37:59 -08:00
|
|
|
// N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
|
|
|
|
// children. This is because `xdot` seemed to have a hard time correctly propagating
|
|
|
|
// attributes. Make sure to test the output before trying to remove the redundancy.
|
|
|
|
// Notably, `align` was found to have no effect when applied only to <table>.
|
|
|
|
|
|
|
|
let table_fmt = concat!(
|
|
|
|
" border=\"1\"",
|
|
|
|
" cellborder=\"1\"",
|
|
|
|
" cellspacing=\"0\"",
|
|
|
|
" cellpadding=\"3\"",
|
|
|
|
" sides=\"rb\"",
|
|
|
|
);
|
|
|
|
write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
// A + B: Block header
|
2020-08-27 23:02:46 -07:00
|
|
|
match self.style {
|
|
|
|
OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
|
|
|
|
OutputStyle::BeforeAndAfter => {
|
|
|
|
self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
|
|
|
|
}
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-03-22 12:09:40 -07:00
|
|
|
// C: State at start of block
|
2019-10-01 15:21:58 -07:00
|
|
|
self.bg = Background::Light;
|
2019-09-26 16:26:11 -07:00
|
|
|
self.results.seek_to_block_start(block);
|
2020-08-27 23:02:46 -07:00
|
|
|
let block_start_state = self.results.get().clone();
|
2020-03-22 12:09:40 -07:00
|
|
|
self.write_row_with_full_state(w, "", "(on start)")?;
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
// D + E: Statement and terminator transfer functions
|
|
|
|
self.write_statements_and_terminator(w, body, block)?;
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-03-22 12:09:40 -07:00
|
|
|
// F: State at end of block
|
2020-02-13 15:27:54 -08:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
let terminator = body[block].terminator();
|
|
|
|
|
2020-02-13 15:27:54 -08:00
|
|
|
// Write the full dataflow state immediately after the terminator if it differs from the
|
|
|
|
// state at block entry.
|
2020-03-22 12:09:40 -07:00
|
|
|
self.results.seek_to_block_end(block);
|
2022-06-05 00:00:00 +00:00
|
|
|
if self.results.get() != &block_start_state || A::Direction::IS_BACKWARD {
|
2020-02-13 15:27:54 -08:00
|
|
|
let after_terminator_name = match terminator.kind {
|
2022-04-16 09:27:54 -04:00
|
|
|
mir::TerminatorKind::Call { target: Some(_), .. } => "(on unwind)",
|
2020-03-22 12:09:40 -07:00
|
|
|
_ => "(on end)",
|
2020-02-13 15:27:54 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
self.write_row_with_full_state(w, "", after_terminator_name)?;
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
// Write any changes caused by terminator-specific effects.
|
|
|
|
//
|
|
|
|
// FIXME: These should really be printed as part of each outgoing edge rather than the node
|
|
|
|
// for the basic block itself. That way, we could display terminator-specific effects for
|
|
|
|
// backward dataflow analyses as well as effects for `SwitchInt` terminators.
|
2020-03-22 12:09:40 -07:00
|
|
|
match terminator.kind {
|
2022-04-16 09:27:54 -04:00
|
|
|
mir::TerminatorKind::Call { destination, .. } => {
|
2020-03-22 12:09:40 -07:00
|
|
|
self.write_row(w, "", "(on successful return)", |this, w, fmt| {
|
|
|
|
let state_on_unwind = this.results.get().clone();
|
|
|
|
this.results.apply_custom_effect(|analysis, state| {
|
2021-08-30 01:23:33 +01:00
|
|
|
analysis.apply_call_return_effect(
|
|
|
|
state,
|
|
|
|
block,
|
2022-04-16 09:27:54 -04:00
|
|
|
CallReturnPlaces::Call(destination),
|
2021-08-30 01:23:33 +01:00
|
|
|
);
|
2020-03-22 12:09:40 -07:00
|
|
|
});
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
write!(
|
|
|
|
w,
|
|
|
|
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
|
|
|
|
colspan = this.style.num_state_columns(),
|
|
|
|
fmt = fmt,
|
|
|
|
diff = diff_pretty(
|
|
|
|
this.results.get(),
|
|
|
|
&state_on_unwind,
|
|
|
|
this.results.analysis()
|
|
|
|
),
|
|
|
|
)
|
2020-03-22 12:09:40 -07:00
|
|
|
})?;
|
|
|
|
}
|
|
|
|
|
|
|
|
mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
|
|
|
|
self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
|
|
|
|
let state_on_generator_drop = this.results.get().clone();
|
|
|
|
this.results.apply_custom_effect(|analysis, state| {
|
|
|
|
analysis.apply_yield_resume_effect(state, resume, resume_arg);
|
|
|
|
});
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
write!(
|
2020-03-22 12:09:40 -07:00
|
|
|
w,
|
2020-08-27 23:02:46 -07:00
|
|
|
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
|
|
|
|
colspan = this.style.num_state_columns(),
|
|
|
|
fmt = fmt,
|
|
|
|
diff = diff_pretty(
|
|
|
|
this.results.get(),
|
|
|
|
&state_on_generator_drop,
|
|
|
|
this.results.analysis()
|
|
|
|
),
|
|
|
|
)
|
2020-03-22 12:09:40 -07:00
|
|
|
})?;
|
|
|
|
}
|
|
|
|
|
2021-08-30 01:23:33 +01:00
|
|
|
mir::TerminatorKind::InlineAsm { destination: Some(_), ref operands, .. } => {
|
|
|
|
self.write_row(w, "", "(on successful return)", |this, w, fmt| {
|
|
|
|
let state_on_unwind = this.results.get().clone();
|
|
|
|
this.results.apply_custom_effect(|analysis, state| {
|
|
|
|
analysis.apply_call_return_effect(
|
|
|
|
state,
|
|
|
|
block,
|
|
|
|
CallReturnPlaces::InlineAsm(operands),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
write!(
|
|
|
|
w,
|
|
|
|
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
|
|
|
|
colspan = this.style.num_state_columns(),
|
|
|
|
fmt = fmt,
|
|
|
|
diff = diff_pretty(
|
|
|
|
this.results.get(),
|
|
|
|
&state_on_unwind,
|
|
|
|
this.results.analysis()
|
|
|
|
),
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
|
2020-03-22 12:09:40 -07:00
|
|
|
_ => {}
|
2020-02-13 15:27:54 -08:00
|
|
|
};
|
|
|
|
|
2019-09-26 16:26:11 -07:00
|
|
|
write!(w, "</table>")
|
|
|
|
}
|
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
fn write_block_header_simple(
|
2019-09-26 16:26:11 -07:00
|
|
|
&mut self,
|
|
|
|
w: &mut impl io::Write,
|
2019-11-11 11:48:47 -08:00
|
|
|
block: BasicBlock,
|
2019-09-26 16:26:11 -07:00
|
|
|
) -> io::Result<()> {
|
2019-11-11 11:48:47 -08:00
|
|
|
// +-------------------------------------------------+
|
|
|
|
// A | bb4 |
|
|
|
|
// +-----------------------------------+-------------+
|
|
|
|
// B | MIR | STATE |
|
|
|
|
// +-+---------------------------------+-------------+
|
|
|
|
// | | ... | |
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
// A
|
|
|
|
write!(
|
|
|
|
w,
|
|
|
|
concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
|
|
|
|
block_id = block.index(),
|
|
|
|
)?;
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
// B
|
2019-09-26 16:26:11 -07:00
|
|
|
write!(
|
|
|
|
w,
|
2019-11-11 11:48:47 -08:00
|
|
|
concat!(
|
|
|
|
"<tr>",
|
|
|
|
r#"<td colspan="2" {fmt}>MIR</td>"#,
|
|
|
|
r#"<td {fmt}>STATE</td>"#,
|
|
|
|
"</tr>",
|
|
|
|
),
|
|
|
|
fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
|
2019-09-26 16:26:11 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
fn write_block_header_with_state_columns(
|
2019-09-26 16:26:11 -07:00
|
|
|
&mut self,
|
|
|
|
w: &mut impl io::Write,
|
2019-11-11 11:48:47 -08:00
|
|
|
block: BasicBlock,
|
2020-08-27 23:02:46 -07:00
|
|
|
state_column_names: &[&str],
|
2019-09-26 16:26:11 -07:00
|
|
|
) -> io::Result<()> {
|
2019-11-11 11:48:47 -08:00
|
|
|
// +------------------------------------+-------------+
|
|
|
|
// A | bb4 | STATE |
|
|
|
|
// +------------------------------------+------+------+
|
|
|
|
// B | MIR | GEN | KILL |
|
|
|
|
// +-+----------------------------------+------+------+
|
|
|
|
// | | ... | | |
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
// A
|
|
|
|
write!(
|
|
|
|
w,
|
|
|
|
concat!(
|
|
|
|
"<tr>",
|
|
|
|
r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
|
|
|
|
r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
|
|
|
|
"</tr>",
|
|
|
|
),
|
|
|
|
fmt = "sides=\"tl\"",
|
|
|
|
num_state_cols = state_column_names.len(),
|
|
|
|
block_id = block.index(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// B
|
|
|
|
let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
|
|
|
|
write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
for name in state_column_names {
|
|
|
|
write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(w, "</tr>")
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn write_statements_and_terminator(
|
|
|
|
&mut self,
|
|
|
|
w: &mut impl io::Write,
|
|
|
|
body: &'a Body<'tcx>,
|
|
|
|
block: BasicBlock,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
let diffs = StateDiffCollector::run(body, block, self.results.results(), self.style);
|
|
|
|
|
|
|
|
let mut befores = diffs.before.map(|v| v.into_iter());
|
|
|
|
let mut afters = diffs.after.into_iter();
|
|
|
|
|
|
|
|
let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
|
2022-06-05 00:00:00 +00:00
|
|
|
if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() }
|
2020-08-27 23:02:46 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
for (i, statement) in body[block].statements.iter().enumerate() {
|
|
|
|
let statement_str = format!("{:?}", statement);
|
|
|
|
let index_str = format!("{}", i);
|
|
|
|
|
|
|
|
let after = next_in_dataflow_order(&mut afters);
|
|
|
|
let before = befores.as_mut().map(next_in_dataflow_order);
|
|
|
|
|
|
|
|
self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
|
|
|
|
if let Some(before) = before {
|
|
|
|
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let after = next_in_dataflow_order(&mut afters);
|
|
|
|
let before = befores.as_mut().map(next_in_dataflow_order);
|
|
|
|
|
|
|
|
assert!(afters.is_empty());
|
|
|
|
assert!(befores.as_ref().map_or(true, ExactSizeIterator::is_empty));
|
|
|
|
|
|
|
|
let terminator = body[block].terminator();
|
|
|
|
let mut terminator_str = String::new();
|
|
|
|
terminator.kind.fmt_head(&mut terminator_str).unwrap();
|
|
|
|
|
|
|
|
self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
|
|
|
|
if let Some(before) = before {
|
|
|
|
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
/// Write a row with the given index and MIR, using the function argument to fill in the
|
|
|
|
/// "STATE" column(s).
|
|
|
|
fn write_row<W: io::Write>(
|
|
|
|
&mut self,
|
|
|
|
w: &mut W,
|
|
|
|
i: &str,
|
|
|
|
mir: &str,
|
|
|
|
f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
let bg = self.toggle_background();
|
2020-01-21 13:37:59 -08:00
|
|
|
let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
|
|
|
|
|
|
|
|
let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
|
2019-09-26 16:26:11 -07:00
|
|
|
|
|
|
|
write!(
|
|
|
|
w,
|
2019-11-11 11:48:47 -08:00
|
|
|
concat!(
|
|
|
|
"<tr>",
|
|
|
|
r#"<td {fmt} align="right">{i}</td>"#,
|
|
|
|
r#"<td {fmt} align="left">{mir}</td>"#,
|
|
|
|
),
|
2019-09-26 16:26:11 -07:00
|
|
|
i = i,
|
2019-11-11 11:48:47 -08:00
|
|
|
fmt = fmt,
|
2019-09-26 16:26:11 -07:00
|
|
|
mir = dot::escape_html(mir),
|
|
|
|
)?;
|
|
|
|
|
2019-11-11 11:48:47 -08:00
|
|
|
f(self, w, &fmt)?;
|
|
|
|
write!(w, "</tr>")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write_row_with_full_state(
|
|
|
|
&mut self,
|
|
|
|
w: &mut impl io::Write,
|
|
|
|
i: &str,
|
|
|
|
mir: &str,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
self.write_row(w, i, mir, |this, w, fmt| {
|
|
|
|
let state = this.results.get();
|
|
|
|
let analysis = this.results.analysis();
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
// FIXME: The full state vector can be quite long. It would be nice to split on commas
|
|
|
|
// and use some text wrapping algorithm.
|
2019-09-26 16:26:11 -07:00
|
|
|
write!(
|
|
|
|
w,
|
2020-08-27 23:02:46 -07:00
|
|
|
r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
|
|
|
|
colspan = this.style.num_state_columns(),
|
2019-11-11 11:48:47 -08:00
|
|
|
fmt = fmt,
|
2020-08-27 23:02:46 -07:00
|
|
|
state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
|
|
|
|
)
|
2019-11-11 11:48:47 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
struct StateDiffCollector<'a, 'tcx, A>
|
2019-11-11 11:48:47 -08:00
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
|
|
|
{
|
2020-08-27 23:02:46 -07:00
|
|
|
analysis: &'a A,
|
|
|
|
prev_state: A::Domain,
|
|
|
|
before: Option<Vec<String>>,
|
|
|
|
after: Vec<String>,
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
|
2021-12-14 12:02:45 -05:00
|
|
|
impl<'a, 'tcx, A> StateDiffCollector<'a, 'tcx, A>
|
2020-03-22 12:09:40 -07:00
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
A::Domain: DebugWithContext<A>,
|
2020-03-22 12:09:40 -07:00
|
|
|
{
|
2020-08-27 23:02:46 -07:00
|
|
|
fn run(
|
|
|
|
body: &'a mir::Body<'tcx>,
|
|
|
|
block: BasicBlock,
|
|
|
|
results: &'a Results<'tcx, A>,
|
|
|
|
style: OutputStyle,
|
|
|
|
) -> Self {
|
|
|
|
let mut collector = StateDiffCollector {
|
|
|
|
analysis: &results.analysis,
|
|
|
|
prev_state: results.analysis.bottom_value(body),
|
|
|
|
after: vec![],
|
|
|
|
before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
|
|
|
|
};
|
2019-11-11 11:48:47 -08:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
results.visit_with(body, std::iter::once(block), &mut collector);
|
|
|
|
collector
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-14 12:02:45 -05:00
|
|
|
impl<'a, 'tcx, A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
|
2019-11-11 11:48:47 -08:00
|
|
|
where
|
|
|
|
A: Analysis<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
A::Domain: DebugWithContext<A>,
|
2019-11-11 11:48:47 -08:00
|
|
|
{
|
2020-08-27 23:02:46 -07:00
|
|
|
type FlowState = A::Domain;
|
2019-11-11 11:48:47 -08:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn visit_block_start(
|
2019-11-11 11:48:47 -08:00
|
|
|
&mut self,
|
2020-08-27 23:02:46 -07:00
|
|
|
state: &Self::FlowState,
|
2021-12-14 12:02:45 -05:00
|
|
|
_block_data: &mir::BasicBlockData<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
_block: BasicBlock,
|
|
|
|
) {
|
2022-06-05 00:00:00 +00:00
|
|
|
if A::Direction::IS_FORWARD {
|
2020-08-27 23:02:46 -07:00
|
|
|
self.prev_state.clone_from(state);
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn visit_block_end(
|
|
|
|
&mut self,
|
|
|
|
state: &Self::FlowState,
|
2021-12-14 12:02:45 -05:00
|
|
|
_block_data: &mir::BasicBlockData<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
_block: BasicBlock,
|
|
|
|
) {
|
2022-06-05 00:00:00 +00:00
|
|
|
if A::Direction::IS_BACKWARD {
|
2020-08-27 23:02:46 -07:00
|
|
|
self.prev_state.clone_from(state);
|
|
|
|
}
|
|
|
|
}
|
2019-11-11 11:48:47 -08:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn visit_statement_before_primary_effect(
|
|
|
|
&mut self,
|
|
|
|
state: &Self::FlowState,
|
2021-12-14 12:02:45 -05:00
|
|
|
_statement: &mir::Statement<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
_location: Location,
|
|
|
|
) {
|
|
|
|
if let Some(before) = self.before.as_mut() {
|
|
|
|
before.push(diff_pretty(state, &self.prev_state, self.analysis));
|
|
|
|
self.prev_state.clone_from(state)
|
|
|
|
}
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn visit_statement_after_primary_effect(
|
|
|
|
&mut self,
|
|
|
|
state: &Self::FlowState,
|
2021-12-14 12:02:45 -05:00
|
|
|
_statement: &mir::Statement<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
_location: Location,
|
|
|
|
) {
|
|
|
|
self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
|
|
|
|
self.prev_state.clone_from(state)
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn visit_terminator_before_primary_effect(
|
2019-11-11 11:48:47 -08:00
|
|
|
&mut self,
|
2020-08-27 23:02:46 -07:00
|
|
|
state: &Self::FlowState,
|
2021-12-14 12:02:45 -05:00
|
|
|
_terminator: &mir::Terminator<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
_location: Location,
|
|
|
|
) {
|
|
|
|
if let Some(before) = self.before.as_mut() {
|
|
|
|
before.push(diff_pretty(state, &self.prev_state, self.analysis));
|
|
|
|
self.prev_state.clone_from(state)
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn visit_terminator_after_primary_effect(
|
|
|
|
&mut self,
|
|
|
|
state: &Self::FlowState,
|
2021-12-14 12:02:45 -05:00
|
|
|
_terminator: &mir::Terminator<'tcx>,
|
2020-08-27 23:02:46 -07:00
|
|
|
_location: Location,
|
|
|
|
) {
|
|
|
|
self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
|
|
|
|
self.prev_state.clone_from(state)
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 10:56:08 +08:00
|
|
|
macro_rules! regex {
|
|
|
|
($re:literal $(,)?) => {{
|
2022-06-16 19:39:39 +04:00
|
|
|
static RE: OnceLock<regex::Regex> = OnceLock::new();
|
2020-09-17 10:56:08 +08:00
|
|
|
RE.get_or_init(|| Regex::new($re).unwrap())
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
|
2019-11-11 11:48:47 -08:00
|
|
|
where
|
2020-08-27 23:02:46 -07:00
|
|
|
T: DebugWithContext<C>,
|
2019-11-11 11:48:47 -08:00
|
|
|
{
|
2020-08-27 23:02:46 -07:00
|
|
|
if new == old {
|
|
|
|
return String::new();
|
2019-11-11 11:48:47 -08:00
|
|
|
}
|
|
|
|
|
2020-09-17 10:56:08 +08:00
|
|
|
let re = regex!("\t?\u{001f}([+-])");
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
|
2019-11-11 11:48:47 -08:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
// Replace newlines in the `Debug` output with `<br/>`
|
|
|
|
let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
|
2019-11-11 11:48:47 -08:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
let mut inside_font_tag = false;
|
|
|
|
let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| {
|
|
|
|
let mut ret = String::new();
|
|
|
|
if inside_font_tag {
|
|
|
|
ret.push_str(r#"</font>"#);
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
let tag = match &captures[1] {
|
|
|
|
"+" => r#"<font color="darkgreen">+"#,
|
|
|
|
"-" => r#"<font color="red">-"#,
|
|
|
|
_ => unreachable!(),
|
2019-11-11 11:48:47 -08:00
|
|
|
};
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
inside_font_tag = true;
|
|
|
|
ret.push_str(tag);
|
|
|
|
ret
|
|
|
|
});
|
2020-01-21 13:37:59 -08:00
|
|
|
|
2022-02-19 00:48:49 +01:00
|
|
|
let Cow::Owned(mut html_diff) = html_diff else {
|
|
|
|
return raw_diff;
|
2020-08-27 23:02:46 -07:00
|
|
|
};
|
2019-09-26 16:26:11 -07:00
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
if inside_font_tag {
|
|
|
|
html_diff.push_str("</font>");
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
|
2020-08-27 23:02:46 -07:00
|
|
|
html_diff
|
2019-09-26 16:26:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|