Skip to main content

rustc_borrowck/polonius/
dump.rs

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
24/// The polonius MIR dump template: a regular HTML file for easy editing, with special dummy
25/// sections to be replaced by real contents.
26const 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
28/// A `LivenessSource` for already-existing liveness and variance data.
29struct 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
46/// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information.
47pub(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    // If we have a polonius graph to dump along the rest of the MIR and NLL info, we extract its
66    // constraints here.
67    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    // We want the NLL extra comments printed by default in NLL MIR dumps. Specifying `-Z
90    // mir-include-spans` on the CLI still has priority.
91    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
106/// The constraints we'll dump as text or a mermaid graph.
107struct LocalizedOutlivesConstraint {
108    source: RegionVid,
109    from: PointIndex,
110    target: RegionVid,
111    to: PointIndex,
112}
113
114/// Visitor to record constraints encountered when traversing the localized constraint graph, as
115/// well as the reachability of each loan.
116#[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
137/// The polonius dump consists of:
138/// - the NLL MIR
139/// - the list of polonius localized constraints
140/// - a mermaid graph of the CFG
141/// - a mermaid graph of the NLL regions and the constraints between them
142/// - a mermaid graph of the NLL SCCs and the constraints between them
143fn 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    // We replace the dummy $SECTION tokens from the HTML polonius dump template, and emit the
154    // result into the given writer.
155    for chunk in TEMPLATE.split("$SECTION") {
156        match chunk.strip_prefix("_") {
157            None => {
158                // We're at the beginning of the template: this is the prologue to emit as-is.
159                out.write_fmt(format_args!("{0}\n", chunk))writeln!(out, "{}", chunk)?;
160            }
161            Some(section) => {
162                // This is the start of a prefixed section, we look for its identifier.
163                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                // Emit the real section instead of the dummy token.
169                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                // And finally, emit the contents that followed the dummy token.
215                out.write_fmt(format_args!("{0}\n", &section[dummy_section_end..]))writeln!(out, "{}", &section[dummy_section_end..])?;
216            }
217        }
218    }
219
220    Ok(())
221}
222
223/// Emits the polonius MIR, as escaped HTML.
224fn emit_html_mir<'tcx>(
225    dumper: &MirDumper<'_, 'tcx>,
226    body: &Body<'tcx>,
227    out: &mut dyn io::Write,
228) -> io::Result<()> {
229    // Buffer the regular MIR dump to be able to escape it.
230    let mut buffer = Vec::new();
231
232    dumper.dump_mir_to_writer(body, &mut buffer)?;
233
234    // Escape the handful of characters that need it. We don't need to be particularly efficient:
235    // we're actually writing into a buffered writer already. Note that MIR dumps are valid UTF-8.
236    let buffer = String::from_utf8_lossy(&buffer);
237    for ch in buffer.chars() {
238        let escaped = match ch {
239            '>' => "&gt;",
240            '<' => "&lt;",
241            '&' => "&amp;",
242            '\'' => "&#39;",
243            '"' => "&quot;",
244            _ => {
245                // The common case, no escaping needed.
246                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
255/// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
256fn 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    // Emit the regular NLL front-matter
266    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    // Add localized outlives constraints
278    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
298/// Emits a mermaid flowchart of the CFG blocks and edges, similar to the graphviz version.
299fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
300    use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
301
302    // The mermaid chart type: a top-down flowchart.
303    out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
304
305    // Emit the block nodes.
306    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    // Emit the edges between blocks, from the terminator edges.
313    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
350/// Emits a region's label: index, universe, external name.
351fn 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
370/// Emits a mermaid flowchart of the NLL regions and the outlives constraints between them, similar
371/// to the graphviz version.
372fn emit_mermaid_nll_regions<'tcx>(
373    tcx: TyCtxt<'tcx>,
374    regioncx: &RegionInferenceContext<'tcx>,
375    out: &mut dyn io::Write,
376) -> io::Result<()> {
377    // The mermaid chart type: a top-down flowchart.
378    out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
379
380    // Emit the region nodes.
381    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    // Get a set of edges to check for the reverse edge being present.
388    let edges: FxHashSet<_> = regioncx.outlives_constraints().map(|c| (c.sup, c.sub)).collect();
389
390    // Order (and deduplicate) edges for traversal, to display them in a generally increasing order.
391    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        // Source node.
402        out.write_fmt(format_args!("{0} ", outlives.sup.as_usize()))write!(out, "{} ", outlives.sup.as_usize())?;
403
404        // The kind of arrow: bidirectional if the opposite edge exists in the set.
405        if edges.contains(&(outlives.sub, outlives.sup)) {
406            out.write_fmt(format_args!("&lt;"))write!(out, "&lt;")?;
407        }
408        out.write_fmt(format_args!("-- "))write!(out, "-- ")?;
409
410        // Edge label from its `Locations`.
411        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        // Target node.
417        out.write_fmt(format_args!(" --> {0}\n", outlives.sub.as_usize()))writeln!(out, " --> {}", outlives.sub.as_usize())?;
418    }
419    Ok(())
420}
421
422/// Emits a mermaid flowchart of the NLL SCCs and the outlives constraints between them, similar
423/// to the graphviz version.
424fn emit_mermaid_nll_sccs<'tcx>(
425    tcx: TyCtxt<'tcx>,
426    regioncx: &RegionInferenceContext<'tcx>,
427    out: &mut dyn io::Write,
428) -> io::Result<()> {
429    // The mermaid chart type: a top-down flowchart.
430    out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
431
432    // Gather and emit the SCC nodes.
433    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        // The node label: the regions contained in the SCC.
441        out.write_fmt(format_args!("{0}[\"SCC({0}) = {{", scc.as_usize()))write!(out, "{scc}[\"SCC({scc}) = {{", scc = scc.as_usize())?;
442        for (idx, &region) 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    // Emit the edges between SCCs.
452    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
462/// Emits a mermaid flowchart of the polonius localized outlives constraints, with subgraphs per
463/// region, and loan introductions.
464fn 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    // The mermaid chart type: a top-down flowchart, which supports subgraphs.
476    out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
477
478    // The loans subgraph: a node per loan.
479    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    // And an edge from that loan node to where it enters the constraint graph.
486    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    // The regions subgraphs containing the region/point nodes.
498    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    // The constraint graph edges.
513    for constraint in localized_outlives_constraints {
514        // FIXME: add killed loans and constraint kind as edge labels.
515        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    // Return the number of edges: this is the biggest graph in the dump and its edge count will be
524    // mermaid's max edge count to support.
525    let edge_count = borrow_set.len() + localized_outlives_constraints.len();
526    Ok(edge_count)
527}
528
529/// Emits the reachability of loans: a list of all nodes reached while traversing the polonius
530/// constraint graph.
531fn 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        // The button to display the loan trace. The javascript event listener is hooked up in the
545        // template itself.
546        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        // The actual trace contents, hidden by default.
552        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            // It's useful to know whether the region we're reaching is live at this point.
567            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}
590/// A MIR location looks like `bb5[2]`. As that is not a syntactically valid mermaid node id,
591/// transform it into `BB5_2`.
592fn 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}