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
22pub(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
57fn 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 writeln!(out, "<!DOCTYPE html>")?;
74 writeln!(out, "<html>")?;
75 writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
76 writeln!(out, "<body>")?;
77
78 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 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 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 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 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 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
150fn 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 let mut buffer = Vec::new();
162
163 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 let buffer = String::from_utf8_lossy(&buffer);
195 for ch in buffer.chars() {
196 let escaped = match ch {
197 '>' => ">",
198 '<' => "<",
199 '&' => "&",
200 '\'' => "'",
201 '"' => """,
202 _ => {
203 write!(out, "{}", ch)?;
205 continue;
206 }
207 };
208 write!(out, "{}", escaped)?;
209 }
210 Ok(())
211}
212
213fn 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 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 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
256fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
258 use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
259
260 writeln!(out, "flowchart TD")?;
262
263 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 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
308fn 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
327fn emit_mermaid_nll_regions<'tcx>(
330 regioncx: &RegionInferenceContext<'tcx>,
331 out: &mut dyn io::Write,
332) -> io::Result<()> {
333 writeln!(out, "flowchart TD")?;
335
336 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 let edges: FxHashSet<_> = regioncx.outlives_constraints().map(|c| (c.sup, c.sub)).collect();
345
346 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 write!(out, "{} ", outlives.sup.as_usize())?;
359
360 if edges.contains(&(outlives.sub, outlives.sup)) {
362 write!(out, "<")?;
363 }
364 write!(out, "-- ")?;
365
366 match outlives.locations {
368 Locations::All(_) => write!(out, "All")?,
369 Locations::Single(location) => write!(out, "{:?}", location)?,
370 }
371
372 writeln!(out, " --> {}", outlives.sub.as_usize())?;
374 }
375 Ok(())
376}
377
378fn emit_mermaid_nll_sccs<'tcx>(
381 regioncx: &RegionInferenceContext<'tcx>,
382 out: &mut dyn io::Write,
383) -> io::Result<()> {
384 writeln!(out, "flowchart TD")?;
386
387 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 write!(out, "{scc}[\"SCC({scc}) = {{", scc = scc.as_usize())?;
397 for (idx, ®ion) 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 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
417fn 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 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 writeln!(out, "flowchart TD")?;
438
439 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 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 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 for constraint in &localized_outlives_constraints.outlives {
475 writeln!(
477 out,
478 " {} --> {}",
479 node_name(constraint.source, constraint.from),
480 node_name(constraint.target, constraint.to),
481 )?;
482 }
483
484 let edge_count = borrow_set.len() + localized_outlives_constraints.outlives.len();
487 Ok(edge_count)
488}