Skip to main content

rustc_mir_dataflow/framework/
graphviz.rs

1//! A helpful diagram for debugging dataflow problems.
2
3use std::borrow::Cow;
4use std::ffi::OsString;
5use std::path::PathBuf;
6use std::sync::OnceLock;
7use std::{io, ops, str};
8
9use regex::Regex;
10use rustc_graphviz as dot;
11use rustc_hir::attrs::{AttributeKind, BorrowckGraphvizFormatKind, RustcMirKind};
12use rustc_hir::find_attr;
13use rustc_index::bit_set::DenseBitSet;
14use rustc_middle::mir::{
15    self, BasicBlock, Body, Location, MirDumper, graphviz_safe_def_name, traversal,
16};
17use rustc_middle::ty::TyCtxt;
18use rustc_middle::ty::print::with_no_trimmed_paths;
19use rustc_span::def_id::DefId;
20use tracing::debug;
21
22use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
23use super::{
24    Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor, visit_results,
25};
26
27/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
28/// `rustc_mir` attributes and `-Z dump-mir-dataflow`. The `Result` in and the `Results` out are
29/// the same.
30pub(super) fn write_graphviz_results<'tcx, A>(
31    tcx: TyCtxt<'tcx>,
32    body: &Body<'tcx>,
33    results: &Results<'tcx, A>,
34    pass_name: Option<&'static str>,
35) -> std::io::Result<()>
36where
37    A: Analysis<'tcx>,
38    A::Domain: DebugWithContext<A>,
39{
40    use std::fs;
41    use std::io::Write;
42
43    let def_id = body.source.def_id();
44    let attrs = RustcMirAttrs::parse(tcx, def_id);
45
46    let file = try {
47        match attrs.output_path(A::NAME) {
48            Some(path) => {
49                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/framework/graphviz.rs:49",
                        "rustc_mir_dataflow::framework::graphviz",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/framework/graphviz.rs"),
                        ::tracing_core::__macro_support::Option::Some(49u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::framework::graphviz"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("printing dataflow results for {0:?} to {1}",
                                                    def_id, path.display()) as &dyn Value))])
            });
    } else { ; }
};debug!("printing dataflow results for {:?} to {}", def_id, path.display());
50                if let Some(parent) = path.parent() {
51                    fs::create_dir_all(parent)?;
52                }
53                fs::File::create_buffered(&path)?
54            }
55
56            None => {
57                let Some(dumper) = MirDumper::new(tcx, A::NAME, body) else {
58                    return Ok(());
59                };
60                let disambiguator = &pass_name.unwrap_or("-----");
61                dumper.set_disambiguator(disambiguator).create_dump_file("dot", body)?
62            }
63        }
64    };
65    let mut file = match file {
66        Ok(f) => f,
67        Err(e) => return Err(e),
68    };
69
70    let style = attrs.formatter.unwrap_or(OutputStyle::AfterOnly);
71
72    let mut buf = Vec::new();
73
74    let graphviz = Formatter::new(body, results, style);
75    let mut render_opts =
76        <[_]>::into_vec(::alloc::boxed::box_new([dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())]))vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())];
77    if tcx.sess.opts.unstable_opts.graphviz_dark_mode {
78        render_opts.push(dot::RenderOption::DarkTheme);
79    }
80    let r = {
    let _guard = NoTrimmedGuard::new();
    dot::render_opts(&graphviz, &mut buf, &render_opts)
}with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts));
81
82    let lhs = try {
83        r?;
84        file.write_all(&buf)?;
85    };
86
87    lhs
88}
89
90#[derive(#[automatically_derived]
impl ::core::default::Default for RustcMirAttrs {
    #[inline]
    fn default() -> RustcMirAttrs {
        RustcMirAttrs {
            basename_and_suffix: ::core::default::Default::default(),
            formatter: ::core::default::Default::default(),
        }
    }
}Default)]
91struct RustcMirAttrs {
92    basename_and_suffix: Option<PathBuf>,
93    formatter: Option<OutputStyle>,
94}
95
96impl RustcMirAttrs {
97    fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Self {
98        let mut ret = RustcMirAttrs::default();
99
100        let attrs = tcx.get_all_attrs(def_id);
101        if let Some(rustc_mir_attrs) = {
    'done:
        {
        for i in attrs {
            let i: &rustc_hir::Attribute = i;
            match i {
                rustc_hir::Attribute::Parsed(AttributeKind::RustcMir(kind)) =>
                    {
                    break 'done Some(kind);
                }
                _ => {}
            }
        }
        None
    }
}find_attr!(attrs, AttributeKind::RustcMir(kind) => kind) {
102            for attr in rustc_mir_attrs {
103                match attr {
104                    RustcMirKind::BorrowckGraphvizPostflow { path } => {
105                        ret.basename_and_suffix = Some(path.clone());
106                    }
107                    RustcMirKind::BorrowckGraphvizFormat { format } => {
108                        ret.formatter = match format {
109                            BorrowckGraphvizFormatKind::TwoPhase => {
110                                Some(OutputStyle::BeforeAndAfter)
111                            }
112                        };
113                    }
114                    _ => (),
115                };
116            }
117        }
118
119        ret
120    }
121
122    /// Returns the path where dataflow results should be written, or `None`
123    /// `borrowck_graphviz_postflow` was not specified.
124    ///
125    /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
126    ///
127    /// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
128    fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
129        let mut ret = self.basename_and_suffix.as_ref().cloned()?;
130        let suffix = ret.file_name().unwrap(); // Checked when parsing attrs
131
132        let mut file_name: OsString = analysis_name.into();
133        file_name.push("_");
134        file_name.push(suffix);
135        ret.set_file_name(file_name);
136
137        Some(ret)
138    }
139}
140
141#[derive(#[automatically_derived]
impl ::core::clone::Clone for OutputStyle {
    #[inline]
    fn clone(&self) -> OutputStyle { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for OutputStyle { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for OutputStyle {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                OutputStyle::AfterOnly => "AfterOnly",
                OutputStyle::BeforeAndAfter => "BeforeAndAfter",
            })
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for OutputStyle {
    #[inline]
    fn eq(&self, other: &OutputStyle) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for OutputStyle {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) {}
}Eq)]
142enum OutputStyle {
143    AfterOnly,
144    BeforeAndAfter,
145}
146
147impl OutputStyle {
148    fn num_state_columns(&self) -> usize {
149        match self {
150            Self::AfterOnly => 1,
151            Self::BeforeAndAfter => 2,
152        }
153    }
154}
155
156struct Formatter<'mir, 'tcx, A>
157where
158    A: Analysis<'tcx>,
159{
160    body: &'mir Body<'tcx>,
161    results: &'mir Results<'tcx, A>,
162    style: OutputStyle,
163    reachable: DenseBitSet<BasicBlock>,
164}
165
166impl<'mir, 'tcx, A> Formatter<'mir, 'tcx, A>
167where
168    A: Analysis<'tcx>,
169{
170    fn new(body: &'mir Body<'tcx>, results: &'mir Results<'tcx, A>, style: OutputStyle) -> Self {
171        let reachable = traversal::reachable_as_bitset(body);
172        Formatter { body, results, style, reachable }
173    }
174}
175
176/// A pair of a basic block and an index into that basic blocks `successors`.
177#[derive(#[automatically_derived]
impl ::core::marker::Copy for CfgEdge { }Copy, #[automatically_derived]
impl ::core::clone::Clone for CfgEdge {
    #[inline]
    fn clone(&self) -> CfgEdge {
        let _: ::core::clone::AssertParamIsClone<BasicBlock>;
        let _: ::core::clone::AssertParamIsClone<usize>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for CfgEdge {
    #[inline]
    fn eq(&self, other: &CfgEdge) -> bool {
        self.source == other.source && self.index == other.index
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CfgEdge {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<BasicBlock>;
        let _: ::core::cmp::AssertParamIsEq<usize>;
    }
}Eq, #[automatically_derived]
impl ::core::fmt::Debug for CfgEdge {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "CfgEdge",
            "source", &self.source, "index", &&self.index)
    }
}Debug)]
178struct CfgEdge {
179    source: BasicBlock,
180    index: usize,
181}
182
183fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
184    body[bb]
185        .terminator()
186        .successors()
187        .enumerate()
188        .map(|(index, _)| CfgEdge { source: bb, index })
189        .collect()
190}
191
192impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A>
193where
194    A: Analysis<'tcx>,
195    A::Domain: DebugWithContext<A>,
196{
197    type Node = BasicBlock;
198    type Edge = CfgEdge;
199
200    fn graph_id(&self) -> dot::Id<'_> {
201        let name = graphviz_safe_def_name(self.body.source.def_id());
202        dot::Id::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("graph_for_def_id_{0}", name))
    })format!("graph_for_def_id_{name}")).unwrap()
203    }
204
205    fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
206        dot::Id::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("bb_{0}", n.index()))
    })format!("bb_{}", n.index())).unwrap()
207    }
208
209    fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
210        let diffs = StateDiffCollector::run(self.body, *block, self.results, self.style);
211
212        let mut fmt = BlockFormatter {
213            cursor: ResultsCursor::new_borrowing(self.body, self.results),
214            style: self.style,
215            bg: Background::Light,
216        };
217        let label = fmt.write_node_label(*block, diffs).unwrap();
218
219        dot::LabelText::html(String::from_utf8(label).unwrap())
220    }
221
222    fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
223        Some(dot::LabelText::label("none"))
224    }
225
226    fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
227        let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
228        dot::LabelText::label(label.clone())
229    }
230}
231
232impl<'tcx, A> dot::GraphWalk<'_> for Formatter<'_, 'tcx, A>
233where
234    A: Analysis<'tcx>,
235{
236    type Node = BasicBlock;
237    type Edge = CfgEdge;
238
239    fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
240        self.body
241            .basic_blocks
242            .indices()
243            .filter(|&idx| self.reachable.contains(idx))
244            .collect::<Vec<_>>()
245            .into()
246    }
247
248    fn edges(&self) -> dot::Edges<'_, Self::Edge> {
249        self.body
250            .basic_blocks
251            .indices()
252            .flat_map(|bb| dataflow_successors(self.body, bb))
253            .collect::<Vec<_>>()
254            .into()
255    }
256
257    fn source(&self, edge: &Self::Edge) -> Self::Node {
258        edge.source
259    }
260
261    fn target(&self, edge: &Self::Edge) -> Self::Node {
262        self.body[edge.source].terminator().successors().nth(edge.index).unwrap()
263    }
264}
265
266struct BlockFormatter<'mir, 'tcx, A>
267where
268    A: Analysis<'tcx>,
269{
270    cursor: ResultsCursor<'mir, 'tcx, A>,
271    bg: Background,
272    style: OutputStyle,
273}
274
275impl<'tcx, A> BlockFormatter<'_, 'tcx, A>
276where
277    A: Analysis<'tcx>,
278    A::Domain: DebugWithContext<A>,
279{
280    const HEADER_COLOR: &'static str = "#a0a0a0";
281
282    fn toggle_background(&mut self) -> Background {
283        let bg = self.bg;
284        self.bg = !bg;
285        bg
286    }
287
288    fn write_node_label(
289        &mut self,
290        block: BasicBlock,
291        diffs: StateDiffCollector<A::Domain>,
292    ) -> io::Result<Vec<u8>> {
293        use std::io::Write;
294
295        //   Sample output:
296        //   +-+-----------------------------------------------+
297        // A |                      bb4                        |
298        //   +-+----------------------------------+------------+
299        // B |                MIR                 |   STATE    |
300        //   +-+----------------------------------+------------+
301        // C | | (on entry)                       | {_0,_2,_3} |
302        //   +-+----------------------------------+------------+
303        // D |0| StorageLive(_7)                  |            |
304        //   +-+----------------------------------+------------+
305        //   |1| StorageLive(_8)                  |            |
306        //   +-+----------------------------------+------------+
307        //   |2| _8 = &mut _1                     | +_8        |
308        //   +-+----------------------------------+------------+
309        // E |T| _4 = const Foo::twiddle(move _2) | -_2        |
310        //   +-+----------------------------------+------------+
311        // F | | (on unwind)                      | {_0,_3,_8} |
312        //   +-+----------------------------------+------------+
313        //   | | (on successful return)           | +_4        |
314        //   +-+----------------------------------+------------+
315
316        // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
317        // children. This is because `xdot` seemed to have a hard time correctly propagating
318        // attributes. Make sure to test the output before trying to remove the redundancy.
319        // Notably, `align` was found to have no effect when applied only to <table>.
320
321        let mut v = ::alloc::vec::Vec::new()vec![];
322        let w = &mut v;
323
324        let table_fmt = " border=\"1\" cellborder=\"1\" cellspacing=\"0\" cellpadding=\"3\" sides=\"rb\""concat!(
325            " border=\"1\"",
326            " cellborder=\"1\"",
327            " cellspacing=\"0\"",
328            " cellpadding=\"3\"",
329            " sides=\"rb\"",
330        );
331        w.write_fmt(format_args!("<table{0}>", table_fmt))write!(w, r#"<table{table_fmt}>"#)?;
332
333        // A + B: Block header
334        match self.style {
335            OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
336            OutputStyle::BeforeAndAfter => {
337                self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
338            }
339        }
340
341        // C: State at start of block
342        self.bg = Background::Light;
343        self.cursor.seek_to_block_start(block);
344        let block_start_state = self.cursor.get().clone();
345        self.write_row_with_full_state(w, "", "(on start)")?;
346
347        // D + E: Statement and terminator transfer functions
348        self.write_statements_and_terminator(w, block, diffs)?;
349
350        // F: State at end of block
351
352        let terminator = self.cursor.body()[block].terminator();
353
354        // Write the full dataflow state immediately after the terminator if it differs from the
355        // state at block entry.
356        self.cursor.seek_to_block_end(block);
357        if self.cursor.get() != &block_start_state || A::Direction::IS_BACKWARD {
358            let after_terminator_name = match terminator.kind {
359                mir::TerminatorKind::Call { target: Some(_), .. } => "(on unwind)",
360                _ => "(on end)",
361            };
362
363            self.write_row_with_full_state(w, "", after_terminator_name)?;
364        }
365
366        // Write any changes caused by terminator-specific effects.
367        //
368        // FIXME: These should really be printed as part of each outgoing edge rather than the node
369        // for the basic block itself. That way, we could display terminator-specific effects for
370        // backward dataflow analyses as well as effects for `SwitchInt` terminators.
371        match terminator.kind {
372            mir::TerminatorKind::Call { destination, .. } => {
373                self.write_row(w, "", "(on successful return)", |this, w, fmt| {
374                    let state_on_unwind = this.cursor.get().clone();
375                    this.cursor.apply_custom_effect(|analysis, state| {
376                        analysis.apply_call_return_effect(
377                            state,
378                            block,
379                            CallReturnPlaces::Call(destination),
380                        );
381                    });
382
383                    w.write_fmt(format_args!("<td balign=\"left\" colspan=\"{0}\" {1} align=\"left\">{2}</td>",
        this.style.num_state_columns(), fmt,
        diff_pretty(this.cursor.get(), &state_on_unwind,
            this.cursor.analysis())))write!(
384                        w,
385                        r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
386                        colspan = this.style.num_state_columns(),
387                        fmt = fmt,
388                        diff = diff_pretty(
389                            this.cursor.get(),
390                            &state_on_unwind,
391                            this.cursor.analysis()
392                        ),
393                    )
394                })?;
395            }
396
397            mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
398                self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
399                    let state_on_coroutine_drop = this.cursor.get().clone();
400                    this.cursor.apply_custom_effect(|analysis, state| {
401                        analysis.apply_call_return_effect(
402                            state,
403                            resume,
404                            CallReturnPlaces::Yield(resume_arg),
405                        );
406                    });
407
408                    w.write_fmt(format_args!("<td balign=\"left\" colspan=\"{0}\" {1} align=\"left\">{2}</td>",
        this.style.num_state_columns(), fmt,
        diff_pretty(this.cursor.get(), &state_on_coroutine_drop,
            this.cursor.analysis())))write!(
409                        w,
410                        r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
411                        colspan = this.style.num_state_columns(),
412                        fmt = fmt,
413                        diff = diff_pretty(
414                            this.cursor.get(),
415                            &state_on_coroutine_drop,
416                            this.cursor.analysis()
417                        ),
418                    )
419                })?;
420            }
421
422            mir::TerminatorKind::InlineAsm { ref targets, ref operands, .. }
423                if !targets.is_empty() =>
424            {
425                self.write_row(w, "", "(on successful return)", |this, w, fmt| {
426                    let state_on_unwind = this.cursor.get().clone();
427                    this.cursor.apply_custom_effect(|analysis, state| {
428                        analysis.apply_call_return_effect(
429                            state,
430                            block,
431                            CallReturnPlaces::InlineAsm(operands),
432                        );
433                    });
434
435                    w.write_fmt(format_args!("<td balign=\"left\" colspan=\"{0}\" {1} align=\"left\">{2}</td>",
        this.style.num_state_columns(), fmt,
        diff_pretty(this.cursor.get(), &state_on_unwind,
            this.cursor.analysis())))write!(
436                        w,
437                        r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
438                        colspan = this.style.num_state_columns(),
439                        fmt = fmt,
440                        diff = diff_pretty(
441                            this.cursor.get(),
442                            &state_on_unwind,
443                            this.cursor.analysis()
444                        ),
445                    )
446                })?;
447            }
448
449            _ => {}
450        };
451
452        w.write_fmt(format_args!("</table>"))write!(w, "</table>")?;
453
454        Ok(v)
455    }
456
457    fn write_block_header_simple(
458        &mut self,
459        w: &mut impl io::Write,
460        block: BasicBlock,
461    ) -> io::Result<()> {
462        //   +-------------------------------------------------+
463        // A |                      bb4                        |
464        //   +-----------------------------------+-------------+
465        // B |                MIR                |    STATE    |
466        //   +-+---------------------------------+-------------+
467        //   | |              ...                |             |
468
469        // A
470        w.write_fmt(format_args!("<tr><td colspan=\"3\" sides=\"tl\">bb{0}</td></tr>",
        block.index()))write!(
471            w,
472            concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
473            block_id = block.index(),
474        )?;
475
476        // B
477        w.write_fmt(format_args!("<tr><td colspan=\"2\" {0}>MIR</td><td {0}>STATE</td></tr>",
        ::alloc::__export::must_use({
                ::alloc::fmt::format(format_args!("bgcolor=\"{0}\" sides=\"tl\"",
                        Self::HEADER_COLOR))
            })))write!(
478            w,
479            concat!(
480                "<tr>",
481                r#"<td colspan="2" {fmt}>MIR</td>"#,
482                r#"<td {fmt}>STATE</td>"#,
483                "</tr>",
484            ),
485            fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
486        )
487    }
488
489    fn write_block_header_with_state_columns(
490        &mut self,
491        w: &mut impl io::Write,
492        block: BasicBlock,
493        state_column_names: &[&str],
494    ) -> io::Result<()> {
495        //   +------------------------------------+-------------+
496        // A |                bb4                 |    STATE    |
497        //   +------------------------------------+------+------+
498        // B |                MIR                 |  GEN | KILL |
499        //   +-+----------------------------------+------+------+
500        //   | |              ...                 |      |      |
501
502        // A
503        w.write_fmt(format_args!("<tr><td {0} colspan=\"2\">bb{2}</td><td {0} colspan=\"{1}\">STATE</td></tr>",
        "sides=\"tl\"", state_column_names.len(), block.index()))write!(
504            w,
505            concat!(
506                "<tr>",
507                r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
508                r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
509                "</tr>",
510            ),
511            fmt = "sides=\"tl\"",
512            num_state_cols = state_column_names.len(),
513            block_id = block.index(),
514        )?;
515
516        // B
517        let fmt = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("bgcolor=\"{0}\" sides=\"tl\"",
                Self::HEADER_COLOR))
    })format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
518        w.write_fmt(format_args!("<tr><td colspan=\"2\" {0}>MIR</td>", fmt))write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
519
520        for name in state_column_names {
521            w.write_fmt(format_args!("<td {0}>{1}</td>", fmt, name))write!(w, "<td {fmt}>{name}</td>")?;
522        }
523
524        w.write_fmt(format_args!("</tr>"))write!(w, "</tr>")
525    }
526
527    fn write_statements_and_terminator(
528        &mut self,
529        w: &mut impl io::Write,
530        block: BasicBlock,
531        diffs: StateDiffCollector<A::Domain>,
532    ) -> io::Result<()> {
533        let mut diffs_before = diffs.before.map(|v| v.into_iter());
534        let mut diffs_after = diffs.after.into_iter();
535
536        let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
537            if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() }
538        };
539
540        for (i, statement) in self.cursor.body()[block].statements.iter().enumerate() {
541            let statement_str = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", statement))
    })format!("{statement:?}");
542            let index_str = ::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}");
543
544            let after = next_in_dataflow_order(&mut diffs_after);
545            let before = diffs_before.as_mut().map(next_in_dataflow_order);
546
547            self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
548                if let Some(before) = before {
549                    w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, before))write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
550                }
551
552                w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, after))write!(w, r#"<td {fmt} align="left">{after}</td>"#)
553            })?;
554        }
555
556        let after = next_in_dataflow_order(&mut diffs_after);
557        let before = diffs_before.as_mut().map(next_in_dataflow_order);
558
559        if !diffs_after.is_empty() {
    ::core::panicking::panic("assertion failed: diffs_after.is_empty()")
};assert!(diffs_after.is_empty());
560        if !diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty) {
    ::core::panicking::panic("assertion failed: diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty)")
};assert!(diffs_before.as_ref().is_none_or(ExactSizeIterator::is_empty));
561
562        let terminator = self.cursor.body()[block].terminator();
563        let mut terminator_str = String::new();
564        terminator.kind.fmt_head(&mut terminator_str).unwrap();
565
566        self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
567            if let Some(before) = before {
568                w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, before))write!(w, r#"<td {fmt} align="left">{before}</td>"#)?;
569            }
570
571            w.write_fmt(format_args!("<td {0} align=\"left\">{1}</td>", fmt, after))write!(w, r#"<td {fmt} align="left">{after}</td>"#)
572        })
573    }
574
575    /// Write a row with the given index and MIR, using the function argument to fill in the
576    /// "STATE" column(s).
577    fn write_row<W: io::Write>(
578        &mut self,
579        w: &mut W,
580        i: &str,
581        mir: &str,
582        f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
583    ) -> io::Result<()> {
584        let bg = self.toggle_background();
585        let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
586
587        let fmt = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("valign=\"{0}\" sides=\"tl\" {1}",
                valign, bg.attr()))
    })format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
588
589        w.write_fmt(format_args!("<tr><td {1} align=\"right\">{0}</td><td {1} align=\"left\">{2}</td>",
        i, fmt, dot::escape_html(mir)))write!(
590            w,
591            concat!(
592                "<tr>",
593                r#"<td {fmt} align="right">{i}</td>"#,
594                r#"<td {fmt} align="left">{mir}</td>"#,
595            ),
596            i = i,
597            fmt = fmt,
598            mir = dot::escape_html(mir),
599        )?;
600
601        f(self, w, &fmt)?;
602        w.write_fmt(format_args!("</tr>"))write!(w, "</tr>")
603    }
604
605    fn write_row_with_full_state(
606        &mut self,
607        w: &mut impl io::Write,
608        i: &str,
609        mir: &str,
610    ) -> io::Result<()> {
611        self.write_row(w, i, mir, |this, w, fmt| {
612            let state = this.cursor.get();
613            let analysis = this.cursor.analysis();
614
615            // FIXME: The full state vector can be quite long. It would be nice to split on commas
616            // and use some text wrapping algorithm.
617            w.write_fmt(format_args!("<td colspan=\"{0}\" {1} align=\"left\">{2}</td>",
        this.style.num_state_columns(), fmt,
        dot::escape_html(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("{0:?}",
                                DebugWithAdapter { this: state, ctxt: analysis }))
                    }))))write!(
618                w,
619                r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
620                colspan = this.style.num_state_columns(),
621                fmt = fmt,
622                state = dot::escape_html(&format!(
623                    "{:?}",
624                    DebugWithAdapter { this: state, ctxt: analysis }
625                )),
626            )
627        })
628    }
629}
630
631struct StateDiffCollector<D> {
632    prev_state: D,
633    before: Option<Vec<String>>,
634    after: Vec<String>,
635}
636
637impl<D> StateDiffCollector<D> {
638    fn run<'tcx, A>(
639        body: &Body<'tcx>,
640        block: BasicBlock,
641        results: &Results<'tcx, A>,
642        style: OutputStyle,
643    ) -> Self
644    where
645        A: Analysis<'tcx, Domain = D>,
646        D: DebugWithContext<A>,
647    {
648        let mut collector = StateDiffCollector {
649            prev_state: results.analysis.bottom_value(body),
650            after: ::alloc::vec::Vec::new()vec![],
651            before: (style == OutputStyle::BeforeAndAfter).then_some(::alloc::vec::Vec::new()vec![]),
652        };
653
654        visit_results(body, std::iter::once(block), results, &mut collector);
655        collector
656    }
657}
658
659impl<'tcx, A> ResultsVisitor<'tcx, A> for StateDiffCollector<A::Domain>
660where
661    A: Analysis<'tcx>,
662    A::Domain: DebugWithContext<A>,
663{
664    fn visit_block_start(&mut self, state: &A::Domain) {
665        if A::Direction::IS_FORWARD {
666            self.prev_state.clone_from(state);
667        }
668    }
669
670    fn visit_block_end(&mut self, state: &A::Domain) {
671        if A::Direction::IS_BACKWARD {
672            self.prev_state.clone_from(state);
673        }
674    }
675
676    fn visit_after_early_statement_effect(
677        &mut self,
678        analysis: &A,
679        state: &A::Domain,
680        _statement: &mir::Statement<'tcx>,
681        _location: Location,
682    ) {
683        if let Some(before) = self.before.as_mut() {
684            before.push(diff_pretty(state, &self.prev_state, analysis));
685            self.prev_state.clone_from(state)
686        }
687    }
688
689    fn visit_after_primary_statement_effect(
690        &mut self,
691        analysis: &A,
692        state: &A::Domain,
693        _statement: &mir::Statement<'tcx>,
694        _location: Location,
695    ) {
696        self.after.push(diff_pretty(state, &self.prev_state, analysis));
697        self.prev_state.clone_from(state)
698    }
699
700    fn visit_after_early_terminator_effect(
701        &mut self,
702        analysis: &A,
703        state: &A::Domain,
704        _terminator: &mir::Terminator<'tcx>,
705        _location: Location,
706    ) {
707        if let Some(before) = self.before.as_mut() {
708            before.push(diff_pretty(state, &self.prev_state, analysis));
709            self.prev_state.clone_from(state)
710        }
711    }
712
713    fn visit_after_primary_terminator_effect(
714        &mut self,
715        analysis: &A,
716        state: &A::Domain,
717        _terminator: &mir::Terminator<'tcx>,
718        _location: Location,
719    ) {
720        self.after.push(diff_pretty(state, &self.prev_state, analysis));
721        self.prev_state.clone_from(state)
722    }
723}
724
725macro_rules! regex {
726    ($re:literal $(,)?) => {{
727        static RE: OnceLock<regex::Regex> = OnceLock::new();
728        RE.get_or_init(|| Regex::new($re).unwrap())
729    }};
730}
731
732fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
733where
734    T: DebugWithContext<C>,
735{
736    if new == old {
737        return String::new();
738    }
739
740    let re = {
    static RE: OnceLock<regex::Regex> = OnceLock::new();
    RE.get_or_init(|| Regex::new("\t?\u{001f}([+-])").unwrap())
}regex!("\t?\u{001f}([+-])");
741
742    let raw_diff = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:#?}",
                DebugDiffWithAdapter { new, old, ctxt }))
    })format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
743    let raw_diff = dot::escape_html(&raw_diff);
744
745    // Replace newlines in the `Debug` output with `<br/>`
746    let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
747
748    let mut inside_font_tag = false;
749    let html_diff = re.replace_all(&raw_diff, |captures: &regex::Captures<'_>| {
750        let mut ret = String::new();
751        if inside_font_tag {
752            ret.push_str(r#"</font>"#);
753        }
754
755        let tag = match &captures[1] {
756            "+" => r#"<font color="darkgreen">+"#,
757            "-" => r#"<font color="red">-"#,
758            _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
759        };
760
761        inside_font_tag = true;
762        ret.push_str(tag);
763        ret
764    });
765
766    let Cow::Owned(mut html_diff) = html_diff else {
767        return raw_diff;
768    };
769
770    if inside_font_tag {
771        html_diff.push_str("</font>");
772    }
773
774    html_diff
775}
776
777/// The background color used for zebra-striping the table.
778#[derive(#[automatically_derived]
impl ::core::clone::Clone for Background {
    #[inline]
    fn clone(&self) -> Background { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Background { }Copy)]
779enum Background {
780    Light,
781    Dark,
782}
783
784impl Background {
785    fn attr(self) -> &'static str {
786        match self {
787            Self::Dark => "bgcolor=\"#f0f0f0\"",
788            Self::Light => "",
789        }
790    }
791}
792
793impl ops::Not for Background {
794    type Output = Self;
795
796    fn not(self) -> Self {
797        match self {
798            Self::Light => Self::Dark,
799            Self::Dark => Self::Light,
800        }
801    }
802}