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
20pub(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 let options = PrettyPrintMirOptions {
53 include_extra_comments: #[allow(non_exhaustive_omitted_patterns)] match tcx.sess.opts.unstable_opts.mir_include_spans
{
MirIncludeSpans::On | MirIncludeSpans::Nll => true,
_ => false,
}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
74fn 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 out.write_fmt(format_args!("<!DOCTYPE html>\n"))writeln!(out, "<!DOCTYPE html>")?;
90 out.write_fmt(format_args!("<html>\n"))writeln!(out, "<html>")?;
91 out.write_fmt(format_args!("<head><title>Polonius MIR dump</title></head>\n"))writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
92 out.write_fmt(format_args!("<body>\n"))writeln!(out, "<body>")?;
93
94 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
96 out.write_fmt(format_args!("Raw MIR dump\n"))writeln!(out, "Raw MIR dump")?;
97 out.write_fmt(format_args!("<pre><code>\n"))writeln!(out, "<pre><code>")?;
98 emit_html_mir(dumper, body, out)?;
99 out.write_fmt(format_args!("</code></pre>\n"))writeln!(out, "</code></pre>")?;
100 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
101
102 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
104 out.write_fmt(format_args!("Polonius constraint graph\n"))writeln!(out, "Polonius constraint graph")?;
105 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))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 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
113 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
114
115 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
117 out.write_fmt(format_args!("Control-flow graph\n"))writeln!(out, "Control-flow graph")?;
118 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))writeln!(out, "<pre class='mermaid'>")?;
119 emit_mermaid_cfg(body, out)?;
120 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
121 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
122
123 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
125 out.write_fmt(format_args!("NLL regions\n"))writeln!(out, "NLL regions")?;
126 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))writeln!(out, "<pre class='mermaid'>")?;
127 emit_mermaid_nll_regions(dumper.tcx(), regioncx, out)?;
128 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
129 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
130
131 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
133 out.write_fmt(format_args!("NLL SCCs\n"))writeln!(out, "NLL SCCs")?;
134 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))writeln!(out, "<pre class='mermaid'>")?;
135 emit_mermaid_nll_sccs(dumper.tcx(), regioncx, out)?;
136 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
137 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
138
139 out.write_fmt(format_args!("<script src=\'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\'></script>\n"))writeln!(
141 out,
142 "<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
143 )?;
144 out.write_fmt(format_args!("<script>\n"))writeln!(out, "<script>")?;
145 out.write_fmt(format_args!("mermaid.initialize({{ startOnLoad: false, maxEdges: {0} }});\n",
edge_count.max(100)))writeln!(
146 out,
147 "mermaid.initialize({{ startOnLoad: false, maxEdges: {} }});",
148 edge_count.max(100),
149 )?;
150 out.write_fmt(format_args!("mermaid.run({{ querySelector: \'.mermaid\' }})\n"))writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
151 out.write_fmt(format_args!("</script>\n"))writeln!(out, "</script>")?;
152 out.write_fmt(format_args!("</body>\n"))writeln!(out, "</body>")?;
153 out.write_fmt(format_args!("</html>\n"))writeln!(out, "</html>")?;
154
155 Ok(())
156}
157
158fn emit_html_mir<'tcx>(
160 dumper: &MirDumper<'_, '_, 'tcx>,
161 body: &Body<'tcx>,
162 out: &mut dyn io::Write,
163) -> io::Result<()> {
164 let mut buffer = Vec::new();
166
167 dumper.dump_mir_to_writer(body, &mut buffer)?;
168
169 let buffer = String::from_utf8_lossy(&buffer);
172 for ch in buffer.chars() {
173 let escaped = match ch {
174 '>' => ">",
175 '<' => "<",
176 '&' => "&",
177 '\'' => "'",
178 '"' => """,
179 _ => {
180 out.write_fmt(format_args!("{0}", ch))write!(out, "{}", ch)?;
182 continue;
183 }
184 };
185 out.write_fmt(format_args!("{0}", escaped))write!(out, "{}", escaped)?;
186 }
187 Ok(())
188}
189
190fn 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 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 match pass_where {
214 PassWhere::BeforeCFG => {
215 if localized_outlives_constraints.outlives.len() > 0 {
216 out.write_fmt(format_args!("| Localized constraints\n"))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 out.write_fmt(format_args!("| {0:?} at {1:?} -> {2:?} at {3:?}\n", source,
from, target, to))writeln!(out, "| {source:?} at {from:?} -> {target:?} at {to:?}")?;
223 }
224 out.write_fmt(format_args!("|\n"))writeln!(out, "|")?;
225 }
226 }
227 _ => {}
228 }
229
230 Ok(())
231}
232
233fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
235 use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
236
237 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
239
240 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 out.write_fmt(format_args!("{0}[\"bb{0}{1}\"]\n", block_idx, cleanup))writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
245 }
246
247 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 out.write_fmt(format_args!("{1} --> {0}\n", bb.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
255 }
256 TerminatorEdges::Double(bb1, bb2) => {
257 if #[allow(non_exhaustive_omitted_patterns)] match terminator.kind {
TerminatorKind::FalseEdge { .. } => true,
_ => false,
}matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
258 out.write_fmt(format_args!("{1} --> {0}\n", bb1.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
259 out.write_fmt(format_args!("{1} -- imaginary --> {0}\n", bb2.as_usize(),
block_idx))writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
260 } else {
261 out.write_fmt(format_args!("{1} --> {0}\n", bb1.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
262 out.write_fmt(format_args!("{1} -- unwind --> {0}\n", bb2.as_usize(),
block_idx))writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
263 }
264 }
265 TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
266 for to_idx in return_ {
267 out.write_fmt(format_args!("{1} --> {0}\n", to_idx.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
268 }
269
270 if let Some(to_idx) = cleanup {
271 out.write_fmt(format_args!("{1} -- unwind --> {0}\n", to_idx.as_usize(),
block_idx))writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
272 }
273 }
274 TerminatorEdges::SwitchInt { targets, .. } => {
275 for to_idx in targets.all_targets() {
276 out.write_fmt(format_args!("{1} --> {0}\n", to_idx.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
277 }
278 }
279 }
280 }
281
282 Ok(())
283}
284
285fn 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 out.write_fmt(format_args!("\'{0}", region.as_usize()))write!(out, "'{}", region.as_usize())?;
296 if !universe.is_root() {
297 out.write_fmt(format_args!("/{0:?}", universe))write!(out, "/{universe:?}")?;
298 }
299 if let Some(name) = def.external_name.and_then(|e| e.get_name(tcx)) {
300 out.write_fmt(format_args!(" ({0})", name))write!(out, " ({name})")?;
301 }
302 Ok(())
303}
304
305fn emit_mermaid_nll_regions<'tcx>(
308 tcx: TyCtxt<'tcx>,
309 regioncx: &RegionInferenceContext<'tcx>,
310 out: &mut dyn io::Write,
311) -> io::Result<()> {
312 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
314
315 for region in regioncx.definitions.indices() {
317 out.write_fmt(format_args!("{0}[\"", region.as_usize()))write!(out, "{}[\"", region.as_usize())?;
318 render_region(tcx, region, regioncx, out)?;
319 out.write_fmt(format_args!("\"]\n"))writeln!(out, "\"]")?;
320 }
321
322 let edges: FxHashSet<_> = regioncx.outlives_constraints().map(|c| (c.sup, c.sub)).collect();
324
325 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 out.write_fmt(format_args!("{0} ", outlives.sup.as_usize()))write!(out, "{} ", outlives.sup.as_usize())?;
338
339 if edges.contains(&(outlives.sub, outlives.sup)) {
341 out.write_fmt(format_args!("<"))write!(out, "<")?;
342 }
343 out.write_fmt(format_args!("-- "))write!(out, "-- ")?;
344
345 match outlives.locations {
347 Locations::All(_) => out.write_fmt(format_args!("All"))write!(out, "All")?,
348 Locations::Single(location) => out.write_fmt(format_args!("{0:?}", location))write!(out, "{:?}", location)?,
349 }
350
351 out.write_fmt(format_args!(" --> {0}\n", outlives.sub.as_usize()))writeln!(out, " --> {}", outlives.sub.as_usize())?;
353 }
354 Ok(())
355}
356
357fn emit_mermaid_nll_sccs<'tcx>(
360 tcx: TyCtxt<'tcx>,
361 regioncx: &RegionInferenceContext<'tcx>,
362 out: &mut dyn io::Write,
363) -> io::Result<()> {
364 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
366
367 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 out.write_fmt(format_args!("{0}[\"SCC({0}) = {{", scc.as_usize()))write!(out, "{scc}[\"SCC({scc}) = {{", scc = scc.as_usize())?;
377 for (idx, ®ion) in regions.iter().enumerate() {
378 render_region(tcx, region, regioncx, out)?;
379 if idx < regions.len() - 1 {
380 out.write_fmt(format_args!(","))write!(out, ",")?;
381 }
382 }
383 out.write_fmt(format_args!("}}\"]\n"))writeln!(out, "}}\"]")?;
384 }
385
386 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 out.write_fmt(format_args!("{0} --> {1}\n", source.as_usize(),
target.as_usize()))writeln!(out, "{} --> {}", source.as_usize(), target.as_usize())?;
392 }
393
394 Ok(())
395}
396
397fn 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 ::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)
409 };
410 let region_name = |region: RegionVid| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\'{0}", region.index()))
})format!("'{}", region.index());
411 let node_name = |region: RegionVid, point: PointIndex| {
412 let location = liveness.location_from_point(point);
413 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}_{1}", region_name(region),
location_name(location)))
})format!("{}_{}", region_name(region), location_name(location))
414 };
415
416 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
418
419 out.write_fmt(format_args!(" subgraph \"Loans\"\n"))writeln!(out, " subgraph \"Loans\"")?;
421 for loan_idx in 0..borrow_set.len() {
422 out.write_fmt(format_args!(" L{0}\n", loan_idx))writeln!(out, " L{loan_idx}")?;
423 }
424 out.write_fmt(format_args!(" end\n\n"))writeln!(out, " end\n")?;
425
426 for (loan_idx, loan) in borrow_set.iter_enumerated() {
428 out.write_fmt(format_args!(" L{0} --> {1}_{2}\n", loan_idx.index(),
region_name(loan.region), location_name(loan.reserve_location)))writeln!(
429 out,
430 " L{} --> {}_{}",
431 loan_idx.index(),
432 region_name(loan.region),
433 location_name(loan.reserve_location),
434 )?;
435 }
436 out.write_fmt(format_args!("\n"))writeln!(out, "")?;
437
438 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 out.write_fmt(format_args!(" subgraph \"{0}\"\n", region_name(region)))writeln!(out, " subgraph \"{}\"", region_name(region))?;
447 for point in points {
448 out.write_fmt(format_args!(" {0}\n", node_name(region, point)))writeln!(out, " {}", node_name(region, point))?;
449 }
450 out.write_fmt(format_args!(" end\n\n"))writeln!(out, " end\n")?;
451 }
452
453 for constraint in &localized_outlives_constraints.outlives {
455 out.write_fmt(format_args!(" {0} --> {1}\n",
node_name(constraint.source, constraint.from),
node_name(constraint.target, constraint.to)))writeln!(
457 out,
458 " {} --> {}",
459 node_name(constraint.source, constraint.from),
460 node_name(constraint.target, constraint.to),
461 )?;
462 }
463
464 let edge_count = borrow_set.len() + localized_outlives_constraints.outlives.len();
467 Ok(edge_count)
468}