1use 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
27pub(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 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(); 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#[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 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 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 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 self.write_statements_and_terminator(w, block, diffs)?;
348
349 let terminator = self.cursor.body()[block].terminator();
352
353 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 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 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 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 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 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 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 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 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: ®ex::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#[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}