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