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::{LocalizedConstraintGraphVisitor, LocalizedNode, PoloniusContext};
14use crate::region_infer::values::LivenessValues;
15use crate::type_check::Locations;
16use crate::{BorrowckInferCtxt, ClosureRegionRequirements, RegionInferenceContext};
17
18pub(crate) fn dump_polonius_mir<'tcx>(
20 infcx: &BorrowckInferCtxt<'tcx>,
21 body: &Body<'tcx>,
22 regioncx: &RegionInferenceContext<'tcx>,
23 closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
24 borrow_set: &BorrowSet<'tcx>,
25 polonius_context: Option<&PoloniusContext>,
26) {
27 let tcx = infcx.tcx;
28 if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {
29 return;
30 }
31
32 let Some(dumper) = MirDumper::new(tcx, "polonius", body) else { return };
33
34 let polonius_context =
35 polonius_context.expect("missing polonius context with `-Zpolonius=next`");
36
37 let mut collector = LocalizedOutlivesConstraintCollector { constraints: Vec::new() };
40 if let Some(graph) = &polonius_context.graph {
41 graph.traverse(
42 body,
43 regioncx.liveness_constraints(),
44 &polonius_context.live_region_variances,
45 regioncx.universal_regions(),
46 borrow_set,
47 &mut collector,
48 );
49 }
50
51 let extra_data = &|pass_where, out: &mut dyn io::Write| {
52 emit_polonius_mir(
53 tcx,
54 regioncx,
55 closure_region_requirements,
56 borrow_set,
57 &collector.constraints,
58 pass_where,
59 out,
60 )
61 };
62 let options = PrettyPrintMirOptions {
65 include_extra_comments: #[allow(non_exhaustive_omitted_patterns)] match tcx.sess.opts.unstable_opts.mir_include_spans
{
MirIncludeSpans::On | MirIncludeSpans::Nll => true,
_ => false,
}matches!(
66 tcx.sess.opts.unstable_opts.mir_include_spans,
67 MirIncludeSpans::On | MirIncludeSpans::Nll
68 ),
69 };
70
71 let dumper = dumper.set_extra_data(extra_data).set_options(options);
72
73 let _ = try {
74 let mut file = dumper.create_dump_file("html", body)?;
75 emit_polonius_dump(&dumper, body, regioncx, borrow_set, &collector.constraints, &mut file)?;
76 };
77}
78
79struct LocalizedOutlivesConstraint {
81 source: RegionVid,
82 from: PointIndex,
83 target: RegionVid,
84 to: PointIndex,
85}
86
87struct LocalizedOutlivesConstraintCollector {
89 constraints: Vec<LocalizedOutlivesConstraint>,
90}
91
92impl LocalizedConstraintGraphVisitor for LocalizedOutlivesConstraintCollector {
93 fn on_successor_discovered(&mut self, current_node: LocalizedNode, successor: LocalizedNode) {
94 self.constraints.push(LocalizedOutlivesConstraint {
95 source: current_node.region,
96 from: current_node.point,
97 target: successor.region,
98 to: successor.point,
99 });
100 }
101}
102
103fn emit_polonius_dump<'tcx>(
110 dumper: &MirDumper<'_, '_, 'tcx>,
111 body: &Body<'tcx>,
112 regioncx: &RegionInferenceContext<'tcx>,
113 borrow_set: &BorrowSet<'tcx>,
114 localized_outlives_constraints: &[LocalizedOutlivesConstraint],
115 out: &mut dyn io::Write,
116) -> io::Result<()> {
117 out.write_fmt(format_args!("<!DOCTYPE html>\n"))writeln!(out, "<!DOCTYPE html>")?;
119 out.write_fmt(format_args!("<html>\n"))writeln!(out, "<html>")?;
120 out.write_fmt(format_args!("<head><title>Polonius MIR dump</title></head>\n"))writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
121 out.write_fmt(format_args!("<body>\n"))writeln!(out, "<body>")?;
122
123 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
125 out.write_fmt(format_args!("Raw MIR dump\n"))writeln!(out, "Raw MIR dump")?;
126 out.write_fmt(format_args!("<pre><code>\n"))writeln!(out, "<pre><code>")?;
127 emit_html_mir(dumper, body, out)?;
128 out.write_fmt(format_args!("</code></pre>\n"))writeln!(out, "</code></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!("Polonius constraint graph\n"))writeln!(out, "Polonius constraint graph")?;
134 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))writeln!(out, "<pre class='mermaid'>")?;
135 let edge_count = emit_mermaid_constraint_graph(
136 borrow_set,
137 regioncx.liveness_constraints(),
138 &localized_outlives_constraints,
139 out,
140 )?;
141 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
142 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
143
144 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
146 out.write_fmt(format_args!("Control-flow graph\n"))writeln!(out, "Control-flow graph")?;
147 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))writeln!(out, "<pre class='mermaid'>")?;
148 emit_mermaid_cfg(body, out)?;
149 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
150 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
151
152 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
154 out.write_fmt(format_args!("NLL regions\n"))writeln!(out, "NLL regions")?;
155 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))writeln!(out, "<pre class='mermaid'>")?;
156 emit_mermaid_nll_regions(dumper.tcx(), regioncx, out)?;
157 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
158 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
159
160 out.write_fmt(format_args!("<div>\n"))writeln!(out, "<div>")?;
162 out.write_fmt(format_args!("NLL SCCs\n"))writeln!(out, "NLL SCCs")?;
163 out.write_fmt(format_args!("<pre class=\'mermaid\'>\n"))writeln!(out, "<pre class='mermaid'>")?;
164 emit_mermaid_nll_sccs(dumper.tcx(), regioncx, out)?;
165 out.write_fmt(format_args!("</pre>\n"))writeln!(out, "</pre>")?;
166 out.write_fmt(format_args!("</div>\n"))writeln!(out, "</div>")?;
167
168 out.write_fmt(format_args!("<script src=\'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\'></script>\n"))writeln!(
170 out,
171 "<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
172 )?;
173 out.write_fmt(format_args!("<script>\n"))writeln!(out, "<script>")?;
174 out.write_fmt(format_args!("mermaid.initialize({{ startOnLoad: false, maxEdges: {0} }});\n",
edge_count.max(100)))writeln!(
175 out,
176 "mermaid.initialize({{ startOnLoad: false, maxEdges: {} }});",
177 edge_count.max(100),
178 )?;
179 out.write_fmt(format_args!("mermaid.run({{ querySelector: \'.mermaid\' }})\n"))writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
180 out.write_fmt(format_args!("</script>\n"))writeln!(out, "</script>")?;
181 out.write_fmt(format_args!("</body>\n"))writeln!(out, "</body>")?;
182 out.write_fmt(format_args!("</html>\n"))writeln!(out, "</html>")?;
183
184 Ok(())
185}
186
187fn emit_html_mir<'tcx>(
189 dumper: &MirDumper<'_, '_, 'tcx>,
190 body: &Body<'tcx>,
191 out: &mut dyn io::Write,
192) -> io::Result<()> {
193 let mut buffer = Vec::new();
195
196 dumper.dump_mir_to_writer(body, &mut buffer)?;
197
198 let buffer = String::from_utf8_lossy(&buffer);
201 for ch in buffer.chars() {
202 let escaped = match ch {
203 '>' => ">",
204 '<' => "<",
205 '&' => "&",
206 '\'' => "'",
207 '"' => """,
208 _ => {
209 out.write_fmt(format_args!("{0}", ch))write!(out, "{}", ch)?;
211 continue;
212 }
213 };
214 out.write_fmt(format_args!("{0}", escaped))write!(out, "{}", escaped)?;
215 }
216 Ok(())
217}
218
219fn emit_polonius_mir<'tcx>(
221 tcx: TyCtxt<'tcx>,
222 regioncx: &RegionInferenceContext<'tcx>,
223 closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
224 borrow_set: &BorrowSet<'tcx>,
225 localized_outlives_constraints: &[LocalizedOutlivesConstraint],
226 pass_where: PassWhere,
227 out: &mut dyn io::Write,
228) -> io::Result<()> {
229 crate::nll::emit_nll_mir(
231 tcx,
232 regioncx,
233 closure_region_requirements,
234 borrow_set,
235 pass_where,
236 out,
237 )?;
238
239 let liveness = regioncx.liveness_constraints();
240
241 match pass_where {
243 PassWhere::BeforeCFG => {
244 if localized_outlives_constraints.len() > 0 {
245 out.write_fmt(format_args!("| Localized constraints\n"))writeln!(out, "| Localized constraints")?;
246
247 for constraint in localized_outlives_constraints {
248 let LocalizedOutlivesConstraint { source, from, target, to } = constraint;
249 let from = liveness.location_from_point(*from);
250 let to = liveness.location_from_point(*to);
251 out.write_fmt(format_args!("| {0:?} at {1:?} -> {2:?} at {3:?}\n", source,
from, target, to))writeln!(out, "| {source:?} at {from:?} -> {target:?} at {to:?}")?;
252 }
253 out.write_fmt(format_args!("|\n"))writeln!(out, "|")?;
254 }
255 }
256 _ => {}
257 }
258
259 Ok(())
260}
261
262fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
264 use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
265
266 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
268
269 for (block_idx, block) in body.basic_blocks.iter_enumerated() {
271 let block_idx = block_idx.as_usize();
272 let cleanup = if block.is_cleanup { " (cleanup)" } else { "" };
273 out.write_fmt(format_args!("{0}[\"bb{0}{1}\"]\n", block_idx, cleanup))writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
274 }
275
276 for (block_idx, block) in body.basic_blocks.iter_enumerated() {
278 let block_idx = block_idx.as_usize();
279 let terminator = block.terminator();
280 match terminator.edges() {
281 TerminatorEdges::None => {}
282 TerminatorEdges::Single(bb) => {
283 out.write_fmt(format_args!("{1} --> {0}\n", bb.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
284 }
285 TerminatorEdges::Double(bb1, bb2) => {
286 if #[allow(non_exhaustive_omitted_patterns)] match terminator.kind {
TerminatorKind::FalseEdge { .. } => true,
_ => false,
}matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
287 out.write_fmt(format_args!("{1} --> {0}\n", bb1.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
288 out.write_fmt(format_args!("{1} -- imaginary --> {0}\n", bb2.as_usize(),
block_idx))writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
289 } else {
290 out.write_fmt(format_args!("{1} --> {0}\n", bb1.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
291 out.write_fmt(format_args!("{1} -- unwind --> {0}\n", bb2.as_usize(),
block_idx))writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
292 }
293 }
294 TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
295 for to_idx in return_ {
296 out.write_fmt(format_args!("{1} --> {0}\n", to_idx.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
297 }
298
299 if let Some(to_idx) = cleanup {
300 out.write_fmt(format_args!("{1} -- unwind --> {0}\n", to_idx.as_usize(),
block_idx))writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
301 }
302 }
303 TerminatorEdges::SwitchInt { targets, .. } => {
304 for to_idx in targets.all_targets() {
305 out.write_fmt(format_args!("{1} --> {0}\n", to_idx.as_usize(), block_idx))writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
306 }
307 }
308 }
309 }
310
311 Ok(())
312}
313
314fn render_region<'tcx>(
316 tcx: TyCtxt<'tcx>,
317 region: RegionVid,
318 regioncx: &RegionInferenceContext<'tcx>,
319 out: &mut dyn io::Write,
320) -> io::Result<()> {
321 let def = regioncx.region_definition(region);
322 let universe = def.universe;
323
324 out.write_fmt(format_args!("\'{0}", region.as_usize()))write!(out, "'{}", region.as_usize())?;
325 if !universe.is_root() {
326 out.write_fmt(format_args!("/{0:?}", universe))write!(out, "/{universe:?}")?;
327 }
328 if let Some(name) = def.external_name.and_then(|e| e.get_name(tcx)) {
329 out.write_fmt(format_args!(" ({0})", name))write!(out, " ({name})")?;
330 }
331 Ok(())
332}
333
334fn emit_mermaid_nll_regions<'tcx>(
337 tcx: TyCtxt<'tcx>,
338 regioncx: &RegionInferenceContext<'tcx>,
339 out: &mut dyn io::Write,
340) -> io::Result<()> {
341 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
343
344 for region in regioncx.definitions.indices() {
346 out.write_fmt(format_args!("{0}[\"", region.as_usize()))write!(out, "{}[\"", region.as_usize())?;
347 render_region(tcx, region, regioncx, out)?;
348 out.write_fmt(format_args!("\"]\n"))writeln!(out, "\"]")?;
349 }
350
351 let edges: FxHashSet<_> = regioncx.outlives_constraints().map(|c| (c.sup, c.sub)).collect();
353
354 let constraint_key = |c: &OutlivesConstraint<'_>| {
356 let min = c.sup.min(c.sub);
357 let max = c.sup.max(c.sub);
358 (min, max)
359 };
360 let mut ordered_edges: Vec<_> = regioncx.outlives_constraints().collect();
361 ordered_edges.sort_by_key(|c| constraint_key(c));
362 ordered_edges.dedup_by_key(|c| constraint_key(c));
363
364 for outlives in ordered_edges {
365 out.write_fmt(format_args!("{0} ", outlives.sup.as_usize()))write!(out, "{} ", outlives.sup.as_usize())?;
367
368 if edges.contains(&(outlives.sub, outlives.sup)) {
370 out.write_fmt(format_args!("<"))write!(out, "<")?;
371 }
372 out.write_fmt(format_args!("-- "))write!(out, "-- ")?;
373
374 match outlives.locations {
376 Locations::All(_) => out.write_fmt(format_args!("All"))write!(out, "All")?,
377 Locations::Single(location) => out.write_fmt(format_args!("{0:?}", location))write!(out, "{:?}", location)?,
378 }
379
380 out.write_fmt(format_args!(" --> {0}\n", outlives.sub.as_usize()))writeln!(out, " --> {}", outlives.sub.as_usize())?;
382 }
383 Ok(())
384}
385
386fn emit_mermaid_nll_sccs<'tcx>(
389 tcx: TyCtxt<'tcx>,
390 regioncx: &RegionInferenceContext<'tcx>,
391 out: &mut dyn io::Write,
392) -> io::Result<()> {
393 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
395
396 let mut nodes_per_scc: IndexVec<_, _> =
398 regioncx.constraint_sccs().all_sccs().map(|_| Vec::new()).collect();
399 for region in regioncx.definitions.indices() {
400 let scc = regioncx.constraint_sccs().scc(region);
401 nodes_per_scc[scc].push(region);
402 }
403 for (scc, regions) in nodes_per_scc.iter_enumerated() {
404 out.write_fmt(format_args!("{0}[\"SCC({0}) = {{", scc.as_usize()))write!(out, "{scc}[\"SCC({scc}) = {{", scc = scc.as_usize())?;
406 for (idx, ®ion) in regions.iter().enumerate() {
407 render_region(tcx, region, regioncx, out)?;
408 if idx < regions.len() - 1 {
409 out.write_fmt(format_args!(","))write!(out, ",")?;
410 }
411 }
412 out.write_fmt(format_args!("}}\"]\n"))writeln!(out, "}}\"]")?;
413 }
414
415 let edges = regioncx.constraint_sccs().all_sccs().flat_map(|source| {
417 regioncx.constraint_sccs().successors(source).iter().map(move |&target| (source, target))
418 });
419 for (source, target) in edges {
420 out.write_fmt(format_args!("{0} --> {1}\n", source.as_usize(),
target.as_usize()))writeln!(out, "{} --> {}", source.as_usize(), target.as_usize())?;
421 }
422
423 Ok(())
424}
425
426fn emit_mermaid_constraint_graph<'tcx>(
429 borrow_set: &BorrowSet<'tcx>,
430 liveness: &LivenessValues,
431 localized_outlives_constraints: &[LocalizedOutlivesConstraint],
432 out: &mut dyn io::Write,
433) -> io::Result<usize> {
434 let location_name = |location: Location| {
435 ::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)
438 };
439 let region_name = |region: RegionVid| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\'{0}", region.index()))
})format!("'{}", region.index());
440 let node_name = |region: RegionVid, point: PointIndex| {
441 let location = liveness.location_from_point(point);
442 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}_{1}", region_name(region),
location_name(location)))
})format!("{}_{}", region_name(region), location_name(location))
443 };
444
445 out.write_fmt(format_args!("flowchart TD\n"))writeln!(out, "flowchart TD")?;
447
448 out.write_fmt(format_args!(" subgraph \"Loans\"\n"))writeln!(out, " subgraph \"Loans\"")?;
450 for loan_idx in 0..borrow_set.len() {
451 out.write_fmt(format_args!(" L{0}\n", loan_idx))writeln!(out, " L{loan_idx}")?;
452 }
453 out.write_fmt(format_args!(" end\n\n"))writeln!(out, " end\n")?;
454
455 for (loan_idx, loan) in borrow_set.iter_enumerated() {
457 out.write_fmt(format_args!(" L{0} --> {1}_{2}\n", loan_idx.index(),
region_name(loan.region), location_name(loan.reserve_location)))writeln!(
458 out,
459 " L{} --> {}_{}",
460 loan_idx.index(),
461 region_name(loan.region),
462 location_name(loan.reserve_location),
463 )?;
464 }
465 out.write_fmt(format_args!("\n"))writeln!(out, "")?;
466
467 let mut points_per_region: FxIndexMap<RegionVid, FxIndexSet<PointIndex>> =
469 FxIndexMap::default();
470 for constraint in localized_outlives_constraints {
471 points_per_region.entry(constraint.source).or_default().insert(constraint.from);
472 points_per_region.entry(constraint.target).or_default().insert(constraint.to);
473 }
474 for (region, points) in points_per_region {
475 out.write_fmt(format_args!(" subgraph \"{0}\"\n", region_name(region)))writeln!(out, " subgraph \"{}\"", region_name(region))?;
476 for point in points {
477 out.write_fmt(format_args!(" {0}\n", node_name(region, point)))writeln!(out, " {}", node_name(region, point))?;
478 }
479 out.write_fmt(format_args!(" end\n\n"))writeln!(out, " end\n")?;
480 }
481
482 for constraint in localized_outlives_constraints {
484 out.write_fmt(format_args!(" {0} --> {1}\n",
node_name(constraint.source, constraint.from),
node_name(constraint.target, constraint.to)))writeln!(
486 out,
487 " {} --> {}",
488 node_name(constraint.source, constraint.from),
489 node_name(constraint.target, constraint.to),
490 )?;
491 }
492
493 let edge_count = borrow_set.len() + localized_outlives_constraints.len();
496 Ok(edge_count)
497}