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::{
6    PassWhere, PrettyPrintMirOptions, create_dump_file, dump_enabled, dump_mir_to_writer,
7};
8use rustc_middle::mir::{Body, ClosureRegionRequirements, Location};
9use rustc_middle::ty::{RegionVid, TyCtxt};
10use rustc_mir_dataflow::points::PointIndex;
11use rustc_session::config::MirIncludeSpans;
12
13use crate::borrow_set::BorrowSet;
14use crate::constraints::OutlivesConstraint;
15use crate::polonius::{
16    LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet, PoloniusDiagnosticsContext,
17};
18use crate::region_infer::values::LivenessValues;
19use crate::type_check::Locations;
20use crate::{BorrowckInferCtxt, RegionInferenceContext};
21
22/// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information.
23pub(crate) fn dump_polonius_mir<'tcx>(
24    infcx: &BorrowckInferCtxt<'tcx>,
25    body: &Body<'tcx>,
26    regioncx: &RegionInferenceContext<'tcx>,
27    borrow_set: &BorrowSet<'tcx>,
28    polonius_diagnostics: Option<&PoloniusDiagnosticsContext>,
29    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
30) {
31    let tcx = infcx.tcx;
32    if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {
33        return;
34    }
35
36    if !dump_enabled(tcx, "polonius", body.source.def_id()) {
37        return;
38    }
39
40    let polonius_diagnostics =
41        polonius_diagnostics.expect("missing diagnostics context with `-Zpolonius=next`");
42
43    let _: io::Result<()> = try {
44        let mut file = create_dump_file(tcx, "html", false, "polonius", &0, body)?;
45        emit_polonius_dump(
46            tcx,
47            body,
48            regioncx,
49            borrow_set,
50            &polonius_diagnostics.localized_outlives_constraints,
51            closure_region_requirements,
52            &mut file,
53        )?;
54    };
55}
56
57/// The polonius dump consists of:
58/// - the NLL MIR
59/// - the list of polonius localized constraints
60/// - a mermaid graph of the CFG
61/// - a mermaid graph of the NLL regions and the constraints between them
62/// - a mermaid graph of the NLL SCCs and the constraints between them
63fn emit_polonius_dump<'tcx>(
64    tcx: TyCtxt<'tcx>,
65    body: &Body<'tcx>,
66    regioncx: &RegionInferenceContext<'tcx>,
67    borrow_set: &BorrowSet<'tcx>,
68    localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
69    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
70    out: &mut dyn io::Write,
71) -> io::Result<()> {
72    // Prepare the HTML dump file prologue.
73    writeln!(out, "<!DOCTYPE html>")?;
74    writeln!(out, "<html>")?;
75    writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
76    writeln!(out, "<body>")?;
77
78    // Section 1: the NLL + Polonius MIR.
79    writeln!(out, "<div>")?;
80    writeln!(out, "Raw MIR dump")?;
81    writeln!(out, "<pre><code>")?;
82    emit_html_mir(
83        tcx,
84        body,
85        regioncx,
86        borrow_set,
87        &localized_outlives_constraints,
88        closure_region_requirements,
89        out,
90    )?;
91    writeln!(out, "</code></pre>")?;
92    writeln!(out, "</div>")?;
93
94    // Section 2: mermaid visualization of the polonius constraint graph.
95    writeln!(out, "<div>")?;
96    writeln!(out, "Polonius constraint graph")?;
97    writeln!(out, "<pre class='mermaid'>")?;
98    let edge_count = emit_mermaid_constraint_graph(
99        borrow_set,
100        regioncx.liveness_constraints(),
101        &localized_outlives_constraints,
102        out,
103    )?;
104    writeln!(out, "</pre>")?;
105    writeln!(out, "</div>")?;
106
107    // Section 3: mermaid visualization of the CFG.
108    writeln!(out, "<div>")?;
109    writeln!(out, "Control-flow graph")?;
110    writeln!(out, "<pre class='mermaid'>")?;
111    emit_mermaid_cfg(body, out)?;
112    writeln!(out, "</pre>")?;
113    writeln!(out, "</div>")?;
114
115    // Section 4: mermaid visualization of the NLL region graph.
116    writeln!(out, "<div>")?;
117    writeln!(out, "NLL regions")?;
118    writeln!(out, "<pre class='mermaid'>")?;
119    emit_mermaid_nll_regions(regioncx, out)?;
120    writeln!(out, "</pre>")?;
121    writeln!(out, "</div>")?;
122
123    // Section 5: mermaid visualization of the NLL SCC graph.
124    writeln!(out, "<div>")?;
125    writeln!(out, "NLL SCCs")?;
126    writeln!(out, "<pre class='mermaid'>")?;
127    emit_mermaid_nll_sccs(regioncx, out)?;
128    writeln!(out, "</pre>")?;
129    writeln!(out, "</div>")?;
130
131    // Finalize the dump with the HTML epilogue.
132    writeln!(
133        out,
134        "<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
135    )?;
136    writeln!(out, "<script>")?;
137    writeln!(
138        out,
139        "mermaid.initialize({{ startOnLoad: false, maxEdges: {} }});",
140        edge_count.max(100),
141    )?;
142    writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
143    writeln!(out, "</script>")?;
144    writeln!(out, "</body>")?;
145    writeln!(out, "</html>")?;
146
147    Ok(())
148}
149
150/// Emits the polonius MIR, as escaped HTML.
151fn emit_html_mir<'tcx>(
152    tcx: TyCtxt<'tcx>,
153    body: &Body<'tcx>,
154    regioncx: &RegionInferenceContext<'tcx>,
155    borrow_set: &BorrowSet<'tcx>,
156    localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
157    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
158    out: &mut dyn io::Write,
159) -> io::Result<()> {
160    // Buffer the regular MIR dump to be able to escape it.
161    let mut buffer = Vec::new();
162
163    // We want the NLL extra comments printed by default in NLL MIR dumps. Specifying `-Z
164    // mir-include-spans` on the CLI still has priority.
165    let options = PrettyPrintMirOptions {
166        include_extra_comments: matches!(
167            tcx.sess.opts.unstable_opts.mir_include_spans,
168            MirIncludeSpans::On | MirIncludeSpans::Nll
169        ),
170    };
171
172    dump_mir_to_writer(
173        tcx,
174        "polonius",
175        &0,
176        body,
177        &mut buffer,
178        |pass_where, out| {
179            emit_polonius_mir(
180                tcx,
181                regioncx,
182                closure_region_requirements,
183                borrow_set,
184                localized_outlives_constraints,
185                pass_where,
186                out,
187            )
188        },
189        options,
190    )?;
191
192    // Escape the handful of characters that need it. We don't need to be particularly efficient:
193    // we're actually writing into a buffered writer already. Note that MIR dumps are valid UTF-8.
194    let buffer = String::from_utf8_lossy(&buffer);
195    for ch in buffer.chars() {
196        let escaped = match ch {
197            '>' => "&gt;",
198            '<' => "&lt;",
199            '&' => "&amp;",
200            '\'' => "&#39;",
201            '"' => "&quot;",
202            _ => {
203                // The common case, no escaping needed.
204                write!(out, "{}", ch)?;
205                continue;
206            }
207        };
208        write!(out, "{}", escaped)?;
209    }
210    Ok(())
211}
212
213/// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
214fn emit_polonius_mir<'tcx>(
215    tcx: TyCtxt<'tcx>,
216    regioncx: &RegionInferenceContext<'tcx>,
217    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
218    borrow_set: &BorrowSet<'tcx>,
219    localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
220    pass_where: PassWhere,
221    out: &mut dyn io::Write,
222) -> io::Result<()> {
223    // Emit the regular NLL front-matter
224    crate::nll::emit_nll_mir(
225        tcx,
226        regioncx,
227        closure_region_requirements,
228        borrow_set,
229        pass_where.clone(),
230        out,
231    )?;
232
233    let liveness = regioncx.liveness_constraints();
234
235    // Add localized outlives constraints
236    match pass_where {
237        PassWhere::BeforeCFG => {
238            if localized_outlives_constraints.outlives.len() > 0 {
239                writeln!(out, "| Localized constraints")?;
240
241                for constraint in &localized_outlives_constraints.outlives {
242                    let LocalizedOutlivesConstraint { source, from, target, to } = constraint;
243                    let from = liveness.location_from_point(*from);
244                    let to = liveness.location_from_point(*to);
245                    writeln!(out, "| {source:?} at {from:?} -> {target:?} at {to:?}")?;
246                }
247                writeln!(out, "|")?;
248            }
249        }
250        _ => {}
251    }
252
253    Ok(())
254}
255
256/// Emits a mermaid flowchart of the CFG blocks and edges, similar to the graphviz version.
257fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
258    use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
259
260    // The mermaid chart type: a top-down flowchart.
261    writeln!(out, "flowchart TD")?;
262
263    // Emit the block nodes.
264    for (block_idx, block) in body.basic_blocks.iter_enumerated() {
265        let block_idx = block_idx.as_usize();
266        let cleanup = if block.is_cleanup { " (cleanup)" } else { "" };
267        writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
268    }
269
270    // Emit the edges between blocks, from the terminator edges.
271    for (block_idx, block) in body.basic_blocks.iter_enumerated() {
272        let block_idx = block_idx.as_usize();
273        let terminator = block.terminator();
274        match terminator.edges() {
275            TerminatorEdges::None => {}
276            TerminatorEdges::Single(bb) => {
277                writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
278            }
279            TerminatorEdges::Double(bb1, bb2) => {
280                if matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
281                    writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
282                    writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
283                } else {
284                    writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
285                    writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
286                }
287            }
288            TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
289                for to_idx in return_ {
290                    writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
291                }
292
293                if let Some(to_idx) = cleanup {
294                    writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
295                }
296            }
297            TerminatorEdges::SwitchInt { targets, .. } => {
298                for to_idx in targets.all_targets() {
299                    writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
300                }
301            }
302        }
303    }
304
305    Ok(())
306}
307
308/// Emits a region's label: index, universe, external name.
309fn render_region(
310    region: RegionVid,
311    regioncx: &RegionInferenceContext<'_>,
312    out: &mut dyn io::Write,
313) -> io::Result<()> {
314    let def = regioncx.region_definition(region);
315    let universe = def.universe;
316
317    write!(out, "'{}", region.as_usize())?;
318    if !universe.is_root() {
319        write!(out, "/{universe:?}")?;
320    }
321    if let Some(name) = def.external_name.and_then(|e| e.get_name()) {
322        write!(out, " ({name})")?;
323    }
324    Ok(())
325}
326
327/// Emits a mermaid flowchart of the NLL regions and the outlives constraints between them, similar
328/// to the graphviz version.
329fn emit_mermaid_nll_regions<'tcx>(
330    regioncx: &RegionInferenceContext<'tcx>,
331    out: &mut dyn io::Write,
332) -> io::Result<()> {
333    // The mermaid chart type: a top-down flowchart.
334    writeln!(out, "flowchart TD")?;
335
336    // Emit the region nodes.
337    for region in regioncx.var_infos.indices() {
338        write!(out, "{}[\"", region.as_usize())?;
339        render_region(region, regioncx, out)?;
340        writeln!(out, "\"]")?;
341    }
342
343    // Get a set of edges to check for the reverse edge being present.
344    let edges: FxHashSet<_> = regioncx.outlives_constraints().map(|c| (c.sup, c.sub)).collect();
345
346    // Order (and deduplicate) edges for traversal, to display them in a generally increasing order.
347    let constraint_key = |c: &OutlivesConstraint<'_>| {
348        let min = c.sup.min(c.sub);
349        let max = c.sup.max(c.sub);
350        (min, max)
351    };
352    let mut ordered_edges: Vec<_> = regioncx.outlives_constraints().collect();
353    ordered_edges.sort_by_key(|c| constraint_key(c));
354    ordered_edges.dedup_by_key(|c| constraint_key(c));
355
356    for outlives in ordered_edges {
357        // Source node.
358        write!(out, "{} ", outlives.sup.as_usize())?;
359
360        // The kind of arrow: bidirectional if the opposite edge exists in the set.
361        if edges.contains(&(outlives.sub, outlives.sup)) {
362            write!(out, "&lt;")?;
363        }
364        write!(out, "-- ")?;
365
366        // Edge label from its `Locations`.
367        match outlives.locations {
368            Locations::All(_) => write!(out, "All")?,
369            Locations::Single(location) => write!(out, "{:?}", location)?,
370        }
371
372        // Target node.
373        writeln!(out, " --> {}", outlives.sub.as_usize())?;
374    }
375    Ok(())
376}
377
378/// Emits a mermaid flowchart of the NLL SCCs and the outlives constraints between them, similar
379/// to the graphviz version.
380fn emit_mermaid_nll_sccs<'tcx>(
381    regioncx: &RegionInferenceContext<'tcx>,
382    out: &mut dyn io::Write,
383) -> io::Result<()> {
384    // The mermaid chart type: a top-down flowchart.
385    writeln!(out, "flowchart TD")?;
386
387    // Gather and emit the SCC nodes.
388    let mut nodes_per_scc: IndexVec<_, _> =
389        regioncx.constraint_sccs().all_sccs().map(|_| Vec::new()).collect();
390    for region in regioncx.var_infos.indices() {
391        let scc = regioncx.constraint_sccs().scc(region);
392        nodes_per_scc[scc].push(region);
393    }
394    for (scc, regions) in nodes_per_scc.iter_enumerated() {
395        // The node label: the regions contained in the SCC.
396        write!(out, "{scc}[\"SCC({scc}) = {{", scc = scc.as_usize())?;
397        for (idx, &region) in regions.iter().enumerate() {
398            render_region(region, regioncx, out)?;
399            if idx < regions.len() - 1 {
400                write!(out, ",")?;
401            }
402        }
403        writeln!(out, "}}\"]")?;
404    }
405
406    // Emit the edges between SCCs.
407    let edges = regioncx.constraint_sccs().all_sccs().flat_map(|source| {
408        regioncx.constraint_sccs().successors(source).iter().map(move |&target| (source, target))
409    });
410    for (source, target) in edges {
411        writeln!(out, "{} --> {}", source.as_usize(), target.as_usize())?;
412    }
413
414    Ok(())
415}
416
417/// Emits a mermaid flowchart of the polonius localized outlives constraints, with subgraphs per
418/// region, and loan introductions.
419fn emit_mermaid_constraint_graph<'tcx>(
420    borrow_set: &BorrowSet<'tcx>,
421    liveness: &LivenessValues,
422    localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
423    out: &mut dyn io::Write,
424) -> io::Result<usize> {
425    let location_name = |location: Location| {
426        // A MIR location looks like `bb5[2]`. As that is not a syntactically valid mermaid node id,
427        // transform it into `BB5_2`.
428        format!("BB{}_{}", location.block.index(), location.statement_index)
429    };
430    let region_name = |region: RegionVid| format!("'{}", region.index());
431    let node_name = |region: RegionVid, point: PointIndex| {
432        let location = liveness.location_from_point(point);
433        format!("{}_{}", region_name(region), location_name(location))
434    };
435
436    // The mermaid chart type: a top-down flowchart, which supports subgraphs.
437    writeln!(out, "flowchart TD")?;
438
439    // The loans subgraph: a node per loan.
440    writeln!(out, "    subgraph \"Loans\"")?;
441    for loan_idx in 0..borrow_set.len() {
442        writeln!(out, "        L{loan_idx}")?;
443    }
444    writeln!(out, "    end\n")?;
445
446    // And an edge from that loan node to where it enters the constraint graph.
447    for (loan_idx, loan) in borrow_set.iter_enumerated() {
448        writeln!(
449            out,
450            "    L{} --> {}_{}",
451            loan_idx.index(),
452            region_name(loan.region),
453            location_name(loan.reserve_location),
454        )?;
455    }
456    writeln!(out, "")?;
457
458    // The regions subgraphs containing the region/point nodes.
459    let mut points_per_region: FxIndexMap<RegionVid, FxIndexSet<PointIndex>> =
460        FxIndexMap::default();
461    for constraint in &localized_outlives_constraints.outlives {
462        points_per_region.entry(constraint.source).or_default().insert(constraint.from);
463        points_per_region.entry(constraint.target).or_default().insert(constraint.to);
464    }
465    for (region, points) in points_per_region {
466        writeln!(out, "    subgraph \"{}\"", region_name(region))?;
467        for point in points {
468            writeln!(out, "        {}", node_name(region, point))?;
469        }
470        writeln!(out, "    end\n")?;
471    }
472
473    // The constraint graph edges.
474    for constraint in &localized_outlives_constraints.outlives {
475        // FIXME: add killed loans and constraint kind as edge labels.
476        writeln!(
477            out,
478            "    {} --> {}",
479            node_name(constraint.source, constraint.from),
480            node_name(constraint.target, constraint.to),
481        )?;
482    }
483
484    // Return the number of edges: this is the biggest graph in the dump and its edge count will be
485    // mermaid's max edge count to support.
486    let edge_count = borrow_set.len() + localized_outlives_constraints.outlives.len();
487    Ok(edge_count)
488}