1use std::io;
2
3use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
4use rustc_index::IndexVec;
5use rustc_middle::mir::pretty::{MirDumper, PassWhere, PrettyPrintMirOptions};
6use rustc_middle::mir::{Body, Location};
7use rustc_middle::ty::{RegionVid, TyCtxt};
8use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex};
9use rustc_session::config::MirIncludeSpans;
10
11use crate::borrow_set::BorrowSet;
12use crate::constraints::OutlivesConstraint;
13use crate::dataflow::BorrowIndex;
14use crate::polonius::liveness::RegionLiveness;
15use crate::polonius::{
16 LiveRegionVariances, LivenessSource, LocalizedConstraintGraphVisitor, LocalizedNode,
17 PoloniusContext,
18};
19use crate::region_infer::values::LivenessValues;
20use crate::type_check::Locations;
21use crate::universal_regions::UniversalRegions;
22use crate::{BorrowckInferCtxt, ClosureRegionRequirements, RegionInferenceContext};
23
24const TEMPLATE: &str = "<!DOCTYPE html>\n<html>\n<head>\n<title>Polonius MIR dump</title>\n<style>\npre {\n margin-top: 0;\n white-space: pre-wrap;\n}\n\n.section + .section {\n margin-top: 20px;\n border-top: 1px solid #aaa;\n padding-top: 10px;\n}\n\n.section-header {\n margin-bottom: 6px;\n}\n\n.trace + .trace {\n margin-top: 15px;\n}\n\n.trace ul {\n margin: 5px 0px;\n padding-left: 15px;\n}\n\n.trace-suffix {\n opacity: 0.8;\n margin-left: 10px;\n}\n\n.hidden {\n display: none;\n}\n</style>\n</head>\n\n<body>\n\n<!-- Links to the other sections -->\n<div class=\"section\">\n <div class=\"section-header\">Quick links</div>\n <a href=\"#mir\">Polonius MIR</a>\n <a href=\"#polonius-region-graph\">Polonius constraint graph</a>\n <a href=\"#loan-traces\">Loan traces</a>\n <a href=\"#cfg-graph\">Control-flow graph</a>\n <a href=\"#nll-region-graph\">NLL region graph</a>\n <a href=\"#nll-scc-graph\">NLL SCC graph</a>\n</div>\n\n<!-- The NLL + Polonius MIR -->\n<div class=\"section\" id=\"mir\">\n <div class=\"section-header\">Raw MIR dump</div>\n <pre><code>$SECTION_MIR</code></pre>\n</div>\n\n<!-- Mermaid visualization of the polonius constraint graph -->\n<div class=\"section\" id=\"polonius-region-graph\">\n <div class=\"section-header\">Polonius constraint graph</div>\n <pre class=\'mermaid\'>$SECTION_POLONIUS_CONSTRAINTS</pre>\n</div>\n\n<!-- The reachability of loans while traversing the polonius constraint graph -->\n<div class=\"section traces\" id=\"loan-traces\">\n <div class=\"section-header\">Loan Traces</div>\n $SECTION_POLONIUS_REACHABILITY\n</div>\n\n<!-- Mermaid visualization of the CFG -->\n<div class=\"section\" id=\"cfg-graph\">\n <div class=\"section-header\">Control-flow graph</div>\n <pre class=\'mermaid\'>$SECTION_CFG</pre>\n</div>\n\n<!-- Mermaid visualization of the NLL region graph -->\n<div class=\"section\" id=\"nll-region-graph\">\n <div class=\"section-header\">NLL regions</div>\n <pre class=\'mermaid\'>$SECTION_NLL_CONSTRAINTS</pre>\n</div>\n\n<!-- Mermaid visualization of the NLL SCC graph -->\n<div class=\"section\" id=\"nll-scc-graph\">\n <div class=\"section-header\">NLL SCCs</div>\n <pre class=\'mermaid\'>$SECTION_NLL_SCCS</pre>\n</div>\n\n<script src=\'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\'></script>\n$SECTION_INITIALIZATION\n<script>\n// Set up the loan traces buttons.\ndocument.querySelectorAll(\".traces button\").forEach(button => {\n button.addEventListener(\"click\", e => {\n // We remove the class hiding the trace by default.\n let loan = button.getAttribute(\"data-loan\");\n let trace = document.getElementById(`trace-${loan}`);\n trace.classList.remove(\"hidden\");\n\n // And we also remove the button\'s container.\n button.parentElement.remove();\n });\n});\n</script>\n</body>\n</html>\n"include_str!("./dump/polonius-mir-dump.template.html");
27
28struct CachedLivenessSource<'a, 'tcx> {
30 live_region_variances: &'a LiveRegionVariances,
31 universal_regions: &'a UniversalRegions<'tcx>,
32 liveness: &'a LivenessValues,
33}
34
35impl<'a, 'tcx> LivenessSource for CachedLivenessSource<'a, 'tcx> {
36 fn liveness_for_region(&mut self, region: RegionVid) -> RegionLiveness<'_> {
37 RegionLiveness::new(
38 region,
39 self.live_region_variances,
40 self.universal_regions,
41 self.liveness.points(),
42 )
43 }
44}
45
46pub(crate) fn dump_polonius_mir<'tcx>(
48 infcx: &BorrowckInferCtxt<'tcx>,
49 body: &Body<'tcx>,
50 regioncx: &RegionInferenceContext<'tcx>,
51 closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
52 borrow_set: &BorrowSet<'tcx>,
53 polonius_context: Option<&PoloniusContext<'tcx>>,
54) {
55 let tcx = infcx.tcx;
56 if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {
57 return;
58 }
59
60 let Some(dumper) = MirDumper::new(tcx, "polonius", body) else { return };
61
62 let polonius_context =
63 polonius_context.expect("missing polonius context with `-Zpolonius=next`");
64
65 let mut liveness_source = CachedLivenessSource {
68 live_region_variances: &polonius_context.live_region_variances,
69 universal_regions: regioncx.universal_regions(),
70 liveness: regioncx.liveness_constraints(),
71 };
72 let mut collector = MirDumpCollector::default();
73 if let Some(graph) = &polonius_context.graph {
74 let location_map = regioncx.liveness_constraints().location_map();
75 graph.traverse(body, borrow_set, location_map, &mut liveness_source, &mut collector);
76 }
77
78 let extra_data = &|pass_where, out: &mut dyn io::Write| {
79 emit_polonius_mir(
80 tcx,
81 regioncx,
82 closure_region_requirements,
83 borrow_set,
84 &collector.constraints,
85 pass_where,
86 out,
87 )
88 };
89 let options = PrettyPrintMirOptions {
92 include_extra_comments: #[allow(non_exhaustive_omitted_patterns)] match tcx.sess.opts.unstable_opts.mir_include_spans
{
MirIncludeSpans::On | MirIncludeSpans::Nll => true,
_ => false,
}matches!(
93 tcx.sess.opts.unstable_opts.mir_include_spans,
94 MirIncludeSpans::On | MirIncludeSpans::Nll
95 ),
96 };
97
98 let dumper = dumper.set_extra_data(extra_data).set_options(options);
99
100 let _ = try {
101 let mut file = dumper.create_dump_file("html", body)?;
102 emit_polonius_dump(&dumper, body, regioncx, borrow_set, &collector, &mut file)?;
103 };
104}
105
106struct LocalizedOutlivesConstraint {
108 source: RegionVid,
109 from: PointIndex,
110 target: RegionVid,
111 to: PointIndex,
112}
113
114#[derive(#[automatically_derived]
impl ::core::default::Default for MirDumpCollector {
#[inline]
fn default() -> Self {
MirDumpCollector {
constraints: ::core::default::Default::default(),
reachability: ::core::default::Default::default(),
}
}
}Default)]
117struct MirDumpCollector {
118 constraints: Vec<LocalizedOutlivesConstraint>,
119 reachability: FxIndexMap<BorrowIndex, Vec<LocalizedNode>>,
120}
121
122impl LocalizedConstraintGraphVisitor for MirDumpCollector {
123 fn on_node_traversed(&mut self, loan: BorrowIndex, node: LocalizedNode, _is_live: bool) {
124 self.reachability.entry(loan).or_default().push(node);
125 }
126
127 fn on_successor_discovered(&mut self, current_node: LocalizedNode, successor: LocalizedNode) {
128 self.constraints.push(LocalizedOutlivesConstraint {
129 source: current_node.region,
130 from: current_node.point,
131 target: successor.region,
132 to: successor.point,
133 });
134 }
135}
136
137fn emit_polonius_dump<'tcx>(
144 dumper: &MirDumper<'_, 'tcx>,
145 body: &Body<'tcx>,
146 regioncx: &RegionInferenceContext<'tcx>,
147 borrow_set: &BorrowSet<'tcx>,
148 collector: &MirDumpCollector,
149 out: &mut dyn io::Write,
150) -> io::Result<()> {
151 let mut edge_count = 0;
152
153 for chunk in TEMPLATE.split("$SECTION") {
156 match chunk.strip_prefix("_") {
157 None => {
158 out.write_fmt(format_args!("{0}\n", chunk))writeln!(out, "{}", chunk)?;
160 }
161 Some(section) => {
162 let dummy_section_end = section
164 .find("<")
165 .expect("the template section end boundary needs to be present");
166 let section_identifier = section[..dummy_section_end].trim();
167
168 match section_identifier {
170 "MIR" => {
171 emit_html_mir(dumper, body, out)?;
172 }
173 "POLONIUS_CONSTRAINTS" => {
174 edge_count = emit_mermaid_constraint_graph(
175 borrow_set,
176 regioncx.liveness_constraints().location_map(),
177 &collector.constraints,
178 out,
179 )?;
180 }
181 "POLONIUS_REACHABILITY" => {
182 emit_loan_reachability(
183 borrow_set,
184 regioncx.liveness_constraints(),
185 &collector.reachability,
186 out,
187 )?;
188 }
189 "CFG" => {
190 emit_mermaid_cfg(body, out)?;
191 }
192 "NLL_CONSTRAINTS" => {
193 emit_mermaid_nll_regions(dumper.tcx(), regioncx, out)?;
194 }
195 "NLL_SCCS" => {
196 emit_mermaid_nll_sccs(dumper.tcx(), regioncx, out)?;
197 }
198 "INITIALIZATION" => {
199 out.write_fmt(format_args!("<script>\n"))writeln!(out, "<script>")?;
200 out.write_fmt(format_args!("mermaid.initialize({{ startOnLoad: false, maxEdges: {0} }});\n",
edge_count.max(100)))writeln!(
201 out,
202 "mermaid.initialize({{ startOnLoad: false, maxEdges: {} }});",
203 edge_count.max(100),
204 )?;
205 out.write_fmt(format_args!("mermaid.run({{ querySelector: \'.mermaid\' }})\n"))writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
206 out.write_fmt(format_args!("</script>\n"))writeln!(out, "</script>")?;
207 }
208
209 _ => {
210 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("unexpected dummy section identifier {0:?}",
section_identifier)));
}unreachable!("unexpected dummy section identifier {:?}", section_identifier)
211 }
212 }
213
214 out.write_fmt(format_args!("{0}\n", §ion[dummy_section_end..]))writeln!(out, "{}", §ion[dummy_section_end..])?;
216 }
217 }
218 }
219
220 Ok(())
221}
222
223fn emit_html_mir<'tcx>(
225 dumper: &MirDumper<'_, 'tcx>,
226 body: &Body<'tcx>,
227 out: &mut dyn io::Write,
228) -> io::Result<()> {
229 let mut buffer = Vec::new();
231
232 dumper.dump_mir_to_writer(body, &mut buffer)?;
233
234 let buffer = String::from_utf8_lossy(&buffer);
237 for ch in buffer.chars() {
238 let escaped = match ch {
239 '>' => ">",
240 '<' => "<",
241 '&' => "&",
242 '\'' => "'",
243 '"' => """,
244 _ => {
245 out.write_fmt(format_args!("{0}", ch))write!(out, "{}", ch)?;
247 continue;
248 }
249 };
250 out.write_fmt(format_args!("{0}", escaped))write!(out, "{}", escaped)?;
251 }
252 Ok(())
253}
254
255fn emit_polonius_mir<'tcx>(
257 tcx: TyCtxt<'tcx>,
258 regioncx: &RegionInferenceContext<'tcx>,
259 closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
260 borrow_set: &BorrowSet<'tcx>,
261 localized_outlives_constraints: &[LocalizedOutlivesConstraint],
262 pass_where: PassWhere,
263 out: &mut dyn io::Write,
264) -> io::Result<()> {
265 crate::nll::emit_nll_mir(
267 tcx,
268 regioncx,
269 closure_region_requirements,
270 borrow_set,
271 pass_where,
272 out,
273 )?;
274
275 let location_map = regioncx.liveness_constraints().location_map();
276
277 match pass_where {
279 PassWhere::BeforeCFG => {
280 if localized_outlives_constraints.len() > 0 {
281 out.write_fmt(format_args!("| Localized constraints\n"))writeln!(out, "| Localized constraints")?;
282
283 for constraint in localized_outlives_constraints {
284 let LocalizedOutlivesConstraint { source, from, target, to } = constraint;
285 let from = location_map.to_location(*from);
286 let to = location_map.to_location(*to);
287 out.write_fmt(format_args!("| {0:?} at {1:?} -> {2:?} at {3:?}\n", source,
from, target, to))writeln!(out, "| {source:?} at {from:?} -> {target:?} at {to:?}")?;
288 }
289 out.write_fmt(format_args!("|\n"))writeln!(out, "|")?;
290 }
291 }
292 _ => {}
293 }
294
295 Ok(())
296}
297
298fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
300 use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
301
302 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
304
305 for (block_idx, block) in body.basic_blocks.iter_enumerated() {
307 let block_idx = block_idx.as_usize();
308 let cleanup = if block.is_cleanup { " (cleanup)" } else { "" };
309 out.write_fmt(format_args!("{0}[\"bb{0}{1}\"]\n", block_idx, cleanup))writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
310 }
311
312 for (block_idx, block) in body.basic_blocks.iter_enumerated() {
314 let block_idx = block_idx.as_usize();
315 let terminator = block.terminator();
316 match terminator.edges() {
317 TerminatorEdges::None => {}
318 TerminatorEdges::Single(bb) => {
319 out.write_fmt(format_args!("{1} --> {0}\n", bb.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
320 }
321 TerminatorEdges::Double(bb1, bb2) => {
322 if #[allow(non_exhaustive_omitted_patterns)] match terminator.kind {
TerminatorKind::FalseEdge { .. } => true,
_ => false,
}matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
323 out.write_fmt(format_args!("{1} --> {0}\n", bb1.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
324 out.write_fmt(format_args!("{1} -- imaginary --> {0}\n", bb2.as_usize(),
block_idx))writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
325 } else {
326 out.write_fmt(format_args!("{1} --> {0}\n", bb1.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
327 out.write_fmt(format_args!("{1} -- unwind --> {0}\n", bb2.as_usize(),
block_idx))writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
328 }
329 }
330 TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
331 for to_idx in return_ {
332 out.write_fmt(format_args!("{1} --> {0}\n", to_idx.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
333 }
334
335 if let Some(to_idx) = cleanup {
336 out.write_fmt(format_args!("{1} -- unwind --> {0}\n", to_idx.as_usize(),
block_idx))writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
337 }
338 }
339 TerminatorEdges::SwitchInt { targets, .. } => {
340 for to_idx in targets.all_targets() {
341 out.write_fmt(format_args!("{1} --> {0}\n", to_idx.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
342 }
343 }
344 }
345 }
346
347 Ok(())
348}
349
350fn render_region<'tcx>(
352 tcx: TyCtxt<'tcx>,
353 region: RegionVid,
354 regioncx: &RegionInferenceContext<'tcx>,
355 out: &mut dyn io::Write,
356) -> io::Result<()> {
357 let def = regioncx.region_definition(region);
358 let universe = def.universe;
359
360 out.write_fmt(format_args!("\'{0}", region.as_usize()))write!(out, "'{}", region.as_usize())?;
361 if !universe.is_root() {
362 out.write_fmt(format_args!("/{0:?}", universe))write!(out, "/{universe:?}")?;
363 }
364 if let Some(name) = def.external_name.and_then(|e| e.get_name(tcx)) {
365 out.write_fmt(format_args!(" ({0})", name))write!(out, " ({name})")?;
366 }
367 Ok(())
368}
369
370fn emit_mermaid_nll_regions<'tcx>(
373 tcx: TyCtxt<'tcx>,
374 regioncx: &RegionInferenceContext<'tcx>,
375 out: &mut dyn io::Write,
376) -> io::Result<()> {
377 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
379
380 for region in regioncx.definitions.indices() {
382 out.write_fmt(format_args!("{0}[\"", region.as_usize()))write!(out, "{}[\"", region.as_usize())?;
383 render_region(tcx, region, regioncx, out)?;
384 out.write_fmt(format_args!("\"]\n"))writeln!(out, "\"]")?;
385 }
386
387 let edges: FxHashSet<_> = regioncx.outlives_constraints().map(|c| (c.sup, c.sub)).collect();
389
390 let constraint_key = |c: &OutlivesConstraint<'_>| {
392 let min = c.sup.min(c.sub);
393 let max = c.sup.max(c.sub);
394 (min, max)
395 };
396 let mut ordered_edges: Vec<_> = regioncx.outlives_constraints().collect();
397 ordered_edges.sort_by_key(|c| constraint_key(c));
398 ordered_edges.dedup_by_key(|c| constraint_key(c));
399
400 for outlives in ordered_edges {
401 out.write_fmt(format_args!("{0} ", outlives.sup.as_usize()))write!(out, "{} ", outlives.sup.as_usize())?;
403
404 if edges.contains(&(outlives.sub, outlives.sup)) {
406 out.write_fmt(format_args!("<"))write!(out, "<")?;
407 }
408 out.write_fmt(format_args!("-- "))write!(out, "-- ")?;
409
410 match outlives.locations {
412 Locations::All(_) => out.write_fmt(format_args!("All"))write!(out, "All")?,
413 Locations::Single(location) => out.write_fmt(format_args!("{0:?}", location))write!(out, "{:?}", location)?,
414 }
415
416 out.write_fmt(format_args!(" --> {0}\n", outlives.sub.as_usize()))writeln!(out, " --> {}", outlives.sub.as_usize())?;
418 }
419 Ok(())
420}
421
422fn emit_mermaid_nll_sccs<'tcx>(
425 tcx: TyCtxt<'tcx>,
426 regioncx: &RegionInferenceContext<'tcx>,
427 out: &mut dyn io::Write,
428) -> io::Result<()> {
429 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
431
432 let mut nodes_per_scc: IndexVec<_, _> =
434 regioncx.constraint_sccs().all_sccs().map(|_| Vec::new()).collect();
435 for region in regioncx.definitions.indices() {
436 let scc = regioncx.constraint_sccs().scc(region);
437 nodes_per_scc[scc].push(region);
438 }
439 for (scc, regions) in nodes_per_scc.iter_enumerated() {
440 out.write_fmt(format_args!("{0}[\"SCC({0}) = {{", scc.as_usize()))write!(out, "{scc}[\"SCC({scc}) = {{", scc = scc.as_usize())?;
442 for (idx, ®ion) in regions.iter().enumerate() {
443 render_region(tcx, region, regioncx, out)?;
444 if idx < regions.len() - 1 {
445 out.write_fmt(format_args!(","))write!(out, ",")?;
446 }
447 }
448 out.write_fmt(format_args!("}}\"]\n"))writeln!(out, "}}\"]")?;
449 }
450
451 let edges = regioncx.constraint_sccs().all_sccs().flat_map(|source| {
453 regioncx.constraint_sccs().successors(source).iter().map(move |&target| (source, target))
454 });
455 for (source, target) in edges {
456 out.write_fmt(format_args!("{0} --> {1}\n", source.as_usize(),
target.as_usize()))writeln!(out, "{} --> {}", source.as_usize(), target.as_usize())?;
457 }
458
459 Ok(())
460}
461
462fn emit_mermaid_constraint_graph<'tcx>(
465 borrow_set: &BorrowSet<'tcx>,
466 location_map: &DenseLocationMap,
467 localized_outlives_constraints: &[LocalizedOutlivesConstraint],
468 out: &mut dyn io::Write,
469) -> io::Result<usize> {
470 let node_label = |region: RegionVid, point: PointIndex| {
471 let location = location_map.to_location(point);
472 node_name(region, location)
473 };
474
475 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
477
478 out.write_fmt(format_args!(" subgraph \"Loans\"\n"))writeln!(out, " subgraph \"Loans\"")?;
480 for loan_idx in 0..borrow_set.len() {
481 out.write_fmt(format_args!(" L{0}\n", loan_idx))writeln!(out, " L{loan_idx}")?;
482 }
483 out.write_fmt(format_args!(" end\n\n"))writeln!(out, " end\n")?;
484
485 for (loan_idx, loan) in borrow_set.iter_enumerated() {
487 out.write_fmt(format_args!(" L{0} --> {1}_{2}\n", loan_idx.index(),
region_name(loan.region), location_name(loan.reserve_location)))writeln!(
488 out,
489 " L{} --> {}_{}",
490 loan_idx.index(),
491 region_name(loan.region),
492 location_name(loan.reserve_location),
493 )?;
494 }
495 out.write_fmt(format_args!("\n"))writeln!(out, "")?;
496
497 let mut points_per_region: FxIndexMap<RegionVid, FxIndexSet<PointIndex>> =
499 FxIndexMap::default();
500 for constraint in localized_outlives_constraints {
501 points_per_region.entry(constraint.source).or_default().insert(constraint.from);
502 points_per_region.entry(constraint.target).or_default().insert(constraint.to);
503 }
504 for (region, points) in points_per_region {
505 out.write_fmt(format_args!(" subgraph \"{0}\"\n", region_name(region)))writeln!(out, " subgraph \"{}\"", region_name(region))?;
506 for point in points {
507 out.write_fmt(format_args!(" {0}\n", node_label(region, point)))writeln!(out, " {}", node_label(region, point))?;
508 }
509 out.write_fmt(format_args!(" end\n\n"))writeln!(out, " end\n")?;
510 }
511
512 for constraint in localized_outlives_constraints {
514 out.write_fmt(format_args!(" {0} --> {1}\n",
node_label(constraint.source, constraint.from),
node_label(constraint.target, constraint.to)))writeln!(
516 out,
517 " {} --> {}",
518 node_label(constraint.source, constraint.from),
519 node_label(constraint.target, constraint.to),
520 )?;
521 }
522
523 let edge_count = borrow_set.len() + localized_outlives_constraints.len();
526 Ok(edge_count)
527}
528
529fn emit_loan_reachability(
532 borrow_set: &BorrowSet<'_>,
533 liveness: &LivenessValues,
534 reachability: &FxIndexMap<BorrowIndex, Vec<LocalizedNode>>,
535 out: &mut dyn io::Write,
536) -> io::Result<()> {
537 let location_map = liveness.location_map();
538 for (loan, _) in borrow_set.iter_enumerated() {
539 let Some(reachability) = reachability.get(&loan) else {
540 continue;
541 };
542 let loan = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("L{0}", loan.index()))
})format!("L{}", loan.index());
543
544 out.write_fmt(format_args!("<div class=\'trace\'><button data-loan=\'{0}\'>Trace for loan {0}</button></div>\n",
loan))writeln!(
547 out,
548 "<div class='trace'><button data-loan='{loan}'>Trace for loan {loan}</button></div>"
549 )?;
550
551 out.write_fmt(format_args!("<div id=\'trace-{0}\' class=\'trace hidden\'>\n",
loan))writeln!(out, "<div id='trace-{loan}' class='trace hidden'>")?;
553 out.write_fmt(format_args!("<div>Trace for loan {0}</div>\n", loan))writeln!(out, "<div>Trace for loan {loan}</div>")?;
554 out.write_fmt(format_args!("<ul>\n"))writeln!(out, "<ul>")?;
555 for (idx, node) in reachability.iter().enumerate() {
556 out.write_fmt(format_args!("<li>\n"))writeln!(out, "<li>")?;
557
558 let location = location_map.to_location(node.point);
559 let kind = if idx == 0 { "starts in" } else { "reaches" };
560 out.write_fmt(format_args!("<code>{1}</code> {2} <code>{0}</code>\n",
node_name(node.region, location), loan, kind))writeln!(
561 out,
562 "<code>{loan}</code> {kind} <code>{}</code>",
563 node_name(node.region, location),
564 )?;
565
566 let node_liveness =
568 if liveness.is_live_at(node.region, location) { "live" } else { "not live" };
569 out.write_fmt(format_args!("<span class=\'trace-suffix\'>\n"))writeln!(out, "<span class='trace-suffix'>")?;
570 out.write_fmt(format_args!("/ at <code>{0:?}</code>: <code>\'{1}</code> is {2}\n",
location, node.region.index(), node_liveness))writeln!(
571 out,
572 "/ at <code>{:?}</code>: <code>'{}</code> is {}",
573 location,
574 node.region.index(),
575 node_liveness,
576 )?;
577 out.write_fmt(format_args!("</span>\n"))writeln!(out, "</span>")?;
578 out.write_fmt(format_args!("</li>\n"))writeln!(out, "</li>")?;
579 }
580 out.write_fmt(format_args!("</ul>\n"))writeln!(out, "</ul>")?;
581 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
582 }
583
584 Ok(())
585}
586
587fn region_name(region: RegionVid) -> String {
588 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\'{0}", region.index()))
})format!("'{}", region.index())
589}
590fn location_name(location: Location) -> String {
593 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("BB{0}_{1}", location.block.index(),
location.statement_index))
})format!("BB{}_{}", location.block.index(), location.statement_index)
594}
595fn node_name(region: RegionVid, location: Location) -> String {
596 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}_{1}", region_name(region),
location_name(location)))
})format!("{}_{}", region_name(region), location_name(location))
597}