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::{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        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [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        if let Some(rustc_mir_attrs) = {

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