11use std:: io;
22
3- use rustc_middle:: mir:: pretty:: { PrettyPrintMirOptions , dump_mir_with_options} ;
4- use rustc_middle:: mir:: { Body , ClosureRegionRequirements , PassWhere } ;
3+ use rustc_middle:: mir:: pretty:: {
4+ PassWhere , PrettyPrintMirOptions , create_dump_file, dump_enabled, dump_mir_to_writer,
5+ } ;
6+ use rustc_middle:: mir:: { Body , ClosureRegionRequirements } ;
57use rustc_middle:: ty:: TyCtxt ;
68use rustc_session:: config:: MirIncludeSpans ;
79
@@ -10,9 +12,6 @@ use crate::polonius::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSe
1012use crate :: { BorrowckInferCtxt , RegionInferenceContext } ;
1113
1214/// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information.
13- // Note: this currently duplicates most of NLL MIR, with some additions for the localized outlives
14- // constraints. This is ok for now as this dump will change in the near future to an HTML file to
15- // become more useful.
1615pub ( crate ) fn dump_polonius_mir < ' tcx > (
1716 infcx : & BorrowckInferCtxt < ' tcx > ,
1817 body : & Body < ' tcx > ,
@@ -26,25 +25,113 @@ pub(crate) fn dump_polonius_mir<'tcx>(
2625 return ;
2726 }
2827
28+ if !dump_enabled ( tcx, "polonius" , body. source . def_id ( ) ) {
29+ return ;
30+ }
31+
2932 let localized_outlives_constraints = localized_outlives_constraints
3033 . expect ( "missing localized constraints with `-Zpolonius=next`" ) ;
3134
32- // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
33- // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
34- // they're always disabled in mir-opt tests to make working with blessed dumps easier.
35+ let _: io:: Result < ( ) > = try {
36+ let mut file = create_dump_file ( tcx, "html" , false , "polonius" , & 0 , body) ?;
37+ emit_polonius_dump (
38+ tcx,
39+ body,
40+ regioncx,
41+ borrow_set,
42+ localized_outlives_constraints,
43+ closure_region_requirements,
44+ & mut file,
45+ ) ?;
46+ } ;
47+ }
48+
49+ /// The polonius dump consists of:
50+ /// - the NLL MIR
51+ /// - the list of polonius localized constraints
52+ /// - a mermaid graph of the CFG
53+ fn emit_polonius_dump < ' tcx > (
54+ tcx : TyCtxt < ' tcx > ,
55+ body : & Body < ' tcx > ,
56+ regioncx : & RegionInferenceContext < ' tcx > ,
57+ borrow_set : & BorrowSet < ' tcx > ,
58+ localized_outlives_constraints : LocalizedOutlivesConstraintSet ,
59+ closure_region_requirements : & Option < ClosureRegionRequirements < ' tcx > > ,
60+ out : & mut dyn io:: Write ,
61+ ) -> io:: Result < ( ) > {
62+ // Prepare the HTML dump file prologue.
63+ writeln ! ( out, "<!DOCTYPE html>" ) ?;
64+ writeln ! ( out, "<html>" ) ?;
65+ writeln ! ( out, "<head><title>Polonius MIR dump</title></head>" ) ?;
66+ writeln ! ( out, "<body>" ) ?;
67+
68+ // Section 1: the NLL + Polonius MIR.
69+ writeln ! ( out, "<div>" ) ?;
70+ writeln ! ( out, "Raw MIR dump" ) ?;
71+ writeln ! ( out, "<code><pre>" ) ?;
72+ emit_html_mir (
73+ tcx,
74+ body,
75+ regioncx,
76+ borrow_set,
77+ localized_outlives_constraints,
78+ closure_region_requirements,
79+ out,
80+ ) ?;
81+ writeln ! ( out, "</pre></code>" ) ?;
82+ writeln ! ( out, "</div>" ) ?;
83+
84+ // Section 2: mermaid visualization of the CFG.
85+ writeln ! ( out, "<div>" ) ?;
86+ writeln ! ( out, "Control-flow graph" ) ?;
87+ writeln ! ( out, "<code><pre class='mermaid'>" ) ?;
88+ emit_mermaid_cfg ( body, out) ?;
89+ writeln ! ( out, "</pre></code>" ) ?;
90+ writeln ! ( out, "</div>" ) ?;
91+
92+ // Finalize the dump with the HTML epilogue.
93+ writeln ! (
94+ out,
95+ "<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
96+ ) ?;
97+ writeln ! ( out, "<script>" ) ?;
98+ writeln ! ( out, "mermaid.initialize({{ startOnLoad: false, maxEdges: 100 }});" ) ?;
99+ writeln ! ( out, "mermaid.run({{ querySelector: '.mermaid' }})" ) ?;
100+ writeln ! ( out, "</script>" ) ?;
101+ writeln ! ( out, "</body>" ) ?;
102+ writeln ! ( out, "</html>" ) ?;
103+
104+ Ok ( ( ) )
105+ }
106+
107+ /// Emits the polonius MIR, as escaped HTML.
108+ fn emit_html_mir < ' tcx > (
109+ tcx : TyCtxt < ' tcx > ,
110+ body : & Body < ' tcx > ,
111+ regioncx : & RegionInferenceContext < ' tcx > ,
112+ borrow_set : & BorrowSet < ' tcx > ,
113+ localized_outlives_constraints : LocalizedOutlivesConstraintSet ,
114+ closure_region_requirements : & Option < ClosureRegionRequirements < ' tcx > > ,
115+ out : & mut dyn io:: Write ,
116+ ) -> io:: Result < ( ) > {
117+ // Buffer the regular MIR dump to be able to escape it.
118+ let mut buffer = Vec :: new ( ) ;
119+
120+ // We want the NLL extra comments printed by default in NLL MIR dumps. Specifying `-Z
121+ // mir-include-spans` on the CLI still has priority.
35122 let options = PrettyPrintMirOptions {
36123 include_extra_comments : matches ! (
37124 tcx. sess. opts. unstable_opts. mir_include_spans,
38125 MirIncludeSpans :: On | MirIncludeSpans :: Nll
39126 ) ,
40127 } ;
41128
42- dump_mir_with_options (
129+ dump_mir_to_writer (
43130 tcx,
44- false ,
45131 "polonius" ,
46132 & 0 ,
47133 body,
134+ & mut buffer,
48135 |pass_where, out| {
49136 emit_polonius_mir (
50137 tcx,
@@ -57,7 +144,27 @@ pub(crate) fn dump_polonius_mir<'tcx>(
57144 )
58145 } ,
59146 options,
60- ) ;
147+ ) ?;
148+
149+ // Escape the handful of characters that need it. We don't need to be particularly efficient:
150+ // we're actually writing into a buffered writer already. Note that MIR dumps are valid UTF-8.
151+ let buffer = String :: from_utf8_lossy ( & buffer) ;
152+ for ch in buffer. chars ( ) {
153+ let escaped = match ch {
154+ '>' => ">" ,
155+ '<' => "<" ,
156+ '&' => "&" ,
157+ '\'' => "'" ,
158+ '"' => """ ,
159+ _ => {
160+ // The common case, no escaping needed.
161+ write ! ( out, "{}" , ch) ?;
162+ continue ;
163+ }
164+ } ;
165+ write ! ( out, "{}" , escaped) ?;
166+ }
167+ Ok ( ( ) )
61168}
62169
63170/// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
@@ -102,3 +209,55 @@ fn emit_polonius_mir<'tcx>(
102209
103210 Ok ( ( ) )
104211}
212+
213+ /// Emits a mermaid flowchart of the CFG blocks and edges, similar to the graphviz version.
214+ fn emit_mermaid_cfg ( body : & Body < ' _ > , out : & mut dyn io:: Write ) -> io:: Result < ( ) > {
215+ use rustc_middle:: mir:: { TerminatorEdges , TerminatorKind } ;
216+
217+ // The mermaid chart type: a top-down flowchart.
218+ writeln ! ( out, "flowchart TD" ) ?;
219+
220+ // Emit the block nodes.
221+ for ( block_idx, block) in body. basic_blocks . iter_enumerated ( ) {
222+ let block_idx = block_idx. as_usize ( ) ;
223+ let cleanup = if block. is_cleanup { " (cleanup)" } else { "" } ;
224+ writeln ! ( out, "{block_idx}[\" bb{block_idx}{cleanup}\" ]" ) ?;
225+ }
226+
227+ // Emit the edges between blocks, from the terminator edges.
228+ for ( block_idx, block) in body. basic_blocks . iter_enumerated ( ) {
229+ let block_idx = block_idx. as_usize ( ) ;
230+ let terminator = block. terminator ( ) ;
231+ match terminator. edges ( ) {
232+ TerminatorEdges :: None => { }
233+ TerminatorEdges :: Single ( bb) => {
234+ writeln ! ( out, "{block_idx} --> {}" , bb. as_usize( ) ) ?;
235+ }
236+ TerminatorEdges :: Double ( bb1, bb2) => {
237+ if matches ! ( terminator. kind, TerminatorKind :: FalseEdge { .. } ) {
238+ writeln ! ( out, "{block_idx} --> {}" , bb1. as_usize( ) ) ?;
239+ writeln ! ( out, "{block_idx} -- imaginary --> {}" , bb2. as_usize( ) ) ?;
240+ } else {
241+ writeln ! ( out, "{block_idx} --> {}" , bb1. as_usize( ) ) ?;
242+ writeln ! ( out, "{block_idx} -- unwind --> {}" , bb2. as_usize( ) ) ?;
243+ }
244+ }
245+ TerminatorEdges :: AssignOnReturn { return_, cleanup, .. } => {
246+ for to_idx in return_ {
247+ writeln ! ( out, "{block_idx} --> {}" , to_idx. as_usize( ) ) ?;
248+ }
249+
250+ if let Some ( to_idx) = cleanup {
251+ writeln ! ( out, "{block_idx} -- unwind --> {}" , to_idx. as_usize( ) ) ?;
252+ }
253+ }
254+ TerminatorEdges :: SwitchInt { targets, .. } => {
255+ for to_idx in targets. all_targets ( ) {
256+ writeln ! ( out, "{block_idx} --> {}" , to_idx. as_usize( ) ) ?;
257+ }
258+ }
259+ }
260+ }
261+
262+ Ok ( ( ) )
263+ }
0 commit comments