1 //! A helpful diagram for debugging dataflow problems.
4 use std::{io, ops, str};
7 use rustc_graphviz as dot;
8 use rustc_hir::def_id::DefId;
9 use rustc_middle::mir::{self, BasicBlock, Body, Location};
11 use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
12 use super::{Analysis, Direction, Results, ResultsRefCursor, ResultsVisitor};
13 use crate::util::graphviz_safe_def_name;
15 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
16 pub enum OutputStyle {
22 fn num_state_columns(&self) -> usize {
25 Self::BeforeAndAfter => 2,
30 pub struct Formatter<'a, 'tcx, A>
36 results: &'a Results<'tcx, A>,
40 impl<A> Formatter<'a, 'tcx, A>
47 results: &'a Results<'tcx, A>,
50 Formatter { body, def_id, results, style }
54 /// A pair of a basic block and an index into that basic blocks `successors`.
55 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
61 fn dataflow_successors(body: &Body<'tcx>, bb: BasicBlock) -> Vec<CfgEdge> {
66 .map(|(index, _)| CfgEdge { source: bb, index })
70 impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
73 A::Domain: DebugWithContext<A>,
75 type Node = BasicBlock;
78 fn graph_id(&self) -> dot::Id<'_> {
79 let name = graphviz_safe_def_name(self.def_id);
80 dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
83 fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
84 dot::Id::new(format!("bb_{}", n.index())).unwrap()
87 fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
88 let mut label = Vec::new();
89 let mut fmt = BlockFormatter {
90 results: ResultsRefCursor::new(self.body, self.results),
92 bg: Background::Light,
95 fmt.write_node_label(&mut label, self.body, *block).unwrap();
96 dot::LabelText::html(String::from_utf8(label).unwrap())
99 fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
100 Some(dot::LabelText::label("none"))
103 fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
104 let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
105 dot::LabelText::label(label.clone())
109 impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
113 type Node = BasicBlock;
116 fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
117 self.body.basic_blocks().indices().collect::<Vec<_>>().into()
120 fn edges(&self) -> dot::Edges<'_, Self::Edge> {
124 .flat_map(|bb| dataflow_successors(self.body, bb))
129 fn source(&self, edge: &Self::Edge) -> Self::Node {
133 fn target(&self, edge: &Self::Edge) -> Self::Node {
134 self.body[edge.source].terminator().successors().nth(edge.index).copied().unwrap()
138 struct BlockFormatter<'a, 'tcx, A>
142 results: ResultsRefCursor<'a, 'a, 'tcx, A>,
147 impl<A> BlockFormatter<'a, 'tcx, A>
150 A::Domain: DebugWithContext<A>,
152 const HEADER_COLOR: &'static str = "#a0a0a0";
154 fn toggle_background(&mut self) -> Background {
162 w: &mut impl io::Write,
163 body: &'a Body<'tcx>,
165 ) -> io::Result<()> {
167 // +-+-----------------------------------------------+
169 // +-+----------------------------------+------------+
171 // +-+----------------------------------+------------+
172 // C | | (on entry) | {_0,_2,_3} |
173 // +-+----------------------------------+------------+
174 // D |0| StorageLive(_7) | |
175 // +-+----------------------------------+------------+
176 // |1| StorageLive(_8) | |
177 // +-+----------------------------------+------------+
178 // |2| _8 = &mut _1 | +_8 |
179 // +-+----------------------------------+------------+
180 // E |T| _4 = const Foo::twiddle(move _2) | -_2 |
181 // +-+----------------------------------+------------+
182 // F | | (on unwind) | {_0,_3,_8} |
183 // +-+----------------------------------+------------+
184 // | | (on successful return) | +_4 |
185 // +-+----------------------------------+------------+
187 // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
188 // children. This is because `xdot` seemed to have a hard time correctly propagating
189 // attributes. Make sure to test the output before trying to remove the redundancy.
190 // Notably, `align` was found to have no effect when applied only to <table>.
192 let table_fmt = concat!(
195 " cellspacing=\"0\"",
196 " cellpadding=\"3\"",
199 write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
201 // A + B: Block header
203 OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
204 OutputStyle::BeforeAndAfter => {
205 self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
209 // C: State at start of block
210 self.bg = Background::Light;
211 self.results.seek_to_block_start(block);
212 let block_start_state = self.results.get().clone();
213 self.write_row_with_full_state(w, "", "(on start)")?;
215 // D + E: Statement and terminator transfer functions
216 self.write_statements_and_terminator(w, body, block)?;
218 // F: State at end of block
220 let terminator = body[block].terminator();
222 // Write the full dataflow state immediately after the terminator if it differs from the
223 // state at block entry.
224 self.results.seek_to_block_end(block);
225 if self.results.get() != &block_start_state || A::Direction::is_backward() {
226 let after_terminator_name = match terminator.kind {
227 mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
231 self.write_row_with_full_state(w, "", after_terminator_name)?;
234 // Write any changes caused by terminator-specific effects.
236 // FIXME: These should really be printed as part of each outgoing edge rather than the node
237 // for the basic block itself. That way, we could display terminator-specific effects for
238 // backward dataflow analyses as well as effects for `SwitchInt` terminators.
239 match terminator.kind {
240 mir::TerminatorKind::Call {
241 destination: Some((return_place, _)),
246 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
247 let state_on_unwind = this.results.get().clone();
248 this.results.apply_custom_effect(|analysis, state| {
249 analysis.apply_call_return_effect(state, block, func, args, return_place);
254 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
255 colspan = this.style.num_state_columns(),
260 this.results.analysis()
266 mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
267 self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
268 let state_on_generator_drop = this.results.get().clone();
269 this.results.apply_custom_effect(|analysis, state| {
270 analysis.apply_yield_resume_effect(state, resume, resume_arg);
275 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
276 colspan = this.style.num_state_columns(),
280 &state_on_generator_drop,
281 this.results.analysis()
290 write!(w, "</table>")
293 fn write_block_header_simple(
295 w: &mut impl io::Write,
297 ) -> io::Result<()> {
298 // +-------------------------------------------------+
300 // +-----------------------------------+-------------+
302 // +-+---------------------------------+-------------+
308 concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
309 block_id = block.index(),
317 r#"<td colspan="2" {fmt}>MIR</td>"#,
318 r#"<td {fmt}>STATE</td>"#,
321 fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
325 fn write_block_header_with_state_columns(
327 w: &mut impl io::Write,
329 state_column_names: &[&str],
330 ) -> io::Result<()> {
331 // +------------------------------------+-------------+
333 // +------------------------------------+------+------+
334 // B | MIR | GEN | KILL |
335 // +-+----------------------------------+------+------+
343 r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
344 r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
347 fmt = "sides=\"tl\"",
348 num_state_cols = state_column_names.len(),
349 block_id = block.index(),
353 let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
354 write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
356 for name in state_column_names {
357 write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?;
363 fn write_statements_and_terminator(
365 w: &mut impl io::Write,
366 body: &'a Body<'tcx>,
368 ) -> io::Result<()> {
369 let diffs = StateDiffCollector::run(body, block, self.results.results(), self.style);
371 let mut befores = diffs.before.map(|v| v.into_iter());
372 let mut afters = diffs.after.into_iter();
374 let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
375 if A::Direction::is_forward() { it.next().unwrap() } else { it.next_back().unwrap() }
378 for (i, statement) in body[block].statements.iter().enumerate() {
379 let statement_str = format!("{:?}", statement);
380 let index_str = format!("{}", i);
382 let after = next_in_dataflow_order(&mut afters);
383 let before = befores.as_mut().map(next_in_dataflow_order);
385 self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
386 if let Some(before) = before {
387 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
390 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
394 let after = next_in_dataflow_order(&mut afters);
395 let before = befores.as_mut().map(next_in_dataflow_order);
397 assert!(afters.is_empty());
398 assert!(befores.as_ref().map_or(true, ExactSizeIterator::is_empty));
400 let terminator = body[block].terminator();
401 let mut terminator_str = String::new();
402 terminator.kind.fmt_head(&mut terminator_str).unwrap();
404 self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
405 if let Some(before) = before {
406 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
409 write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
413 /// Write a row with the given index and MIR, using the function argument to fill in the
414 /// "STATE" column(s).
415 fn write_row<W: io::Write>(
420 f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
421 ) -> io::Result<()> {
422 let bg = self.toggle_background();
423 let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
425 let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
431 r#"<td {fmt} align="right">{i}</td>"#,
432 r#"<td {fmt} align="left">{mir}</td>"#,
436 mir = dot::escape_html(mir),
443 fn write_row_with_full_state(
445 w: &mut impl io::Write,
448 ) -> io::Result<()> {
449 self.write_row(w, i, mir, |this, w, fmt| {
450 let state = this.results.get();
451 let analysis = this.results.analysis();
453 // FIXME: The full state vector can be quite long. It would be nice to split on commas
454 // and use some text wrapping algorithm.
457 r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
458 colspan = this.style.num_state_columns(),
460 state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
466 struct StateDiffCollector<'a, 'tcx, A>
471 prev_state: A::Domain,
472 before: Option<Vec<String>>,
476 impl<A> StateDiffCollector<'a, 'tcx, A>
479 A::Domain: DebugWithContext<A>,
482 body: &'a mir::Body<'tcx>,
484 results: &'a Results<'tcx, A>,
487 let mut collector = StateDiffCollector {
488 analysis: &results.analysis,
489 prev_state: results.analysis.bottom_value(body),
491 before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
494 results.visit_with(body, std::iter::once(block), &mut collector);
499 impl<A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
502 A::Domain: DebugWithContext<A>,
504 type FlowState = A::Domain;
506 fn visit_block_start(
508 state: &Self::FlowState,
509 _block_data: &'mir mir::BasicBlockData<'tcx>,
512 if A::Direction::is_forward() {
513 self.prev_state.clone_from(state);
519 state: &Self::FlowState,
520 _block_data: &'mir mir::BasicBlockData<'tcx>,
523 if A::Direction::is_backward() {
524 self.prev_state.clone_from(state);
528 fn visit_statement_before_primary_effect(
530 state: &Self::FlowState,
531 _statement: &'mir mir::Statement<'tcx>,
534 if let Some(before) = self.before.as_mut() {
535 before.push(diff_pretty(state, &self.prev_state, self.analysis));
536 self.prev_state.clone_from(state)
540 fn visit_statement_after_primary_effect(
542 state: &Self::FlowState,
543 _statement: &'mir mir::Statement<'tcx>,
546 self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
547 self.prev_state.clone_from(state)
550 fn visit_terminator_before_primary_effect(
552 state: &Self::FlowState,
553 _terminator: &'mir mir::Terminator<'tcx>,
556 if let Some(before) = self.before.as_mut() {
557 before.push(diff_pretty(state, &self.prev_state, self.analysis));
558 self.prev_state.clone_from(state)
562 fn visit_terminator_after_primary_effect(
564 state: &Self::FlowState,
565 _terminator: &'mir mir::Terminator<'tcx>,
568 self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
569 self.prev_state.clone_from(state)
573 fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
575 T: DebugWithContext<C>,
578 return String::new();
581 let re = Regex::new("\t?\u{001f}([+-])").unwrap();
583 let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
585 // Replace newlines in the `Debug` output with `<br/>`
586 let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
588 let mut inside_font_tag = false;
589 let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| {
590 let mut ret = String::new();
592 ret.push_str(r#"</font>"#);
595 let tag = match &captures[1] {
596 "+" => r#"<font color="darkgreen">+"#,
597 "-" => r#"<font color="red">-"#,
601 inside_font_tag = true;
606 let mut html_diff = match html_diff {
607 Cow::Borrowed(_) => return raw_diff,
612 html_diff.push_str("</font>");
618 /// The background color used for zebra-striping the table.
619 #[derive(Clone, Copy)]
626 fn attr(self) -> &'static str {
628 Self::Dark => "bgcolor=\"#f0f0f0\"",
634 impl ops::Not for Background {
637 fn not(self) -> Self {
639 Self::Light => Self::Dark,
640 Self::Dark => Self::Light,