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