1
1
use std:: io;
2
2
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 } ;
5
7
use rustc_middle:: ty:: TyCtxt ;
6
8
use rustc_session:: config:: MirIncludeSpans ;
7
9
@@ -10,9 +12,6 @@ use crate::polonius::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSe
10
12
use crate :: { BorrowckInferCtxt , RegionInferenceContext } ;
11
13
12
14
/// `-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.
16
15
pub ( crate ) fn dump_polonius_mir < ' tcx > (
17
16
infcx : & BorrowckInferCtxt < ' tcx > ,
18
17
body : & Body < ' tcx > ,
@@ -26,25 +25,113 @@ pub(crate) fn dump_polonius_mir<'tcx>(
26
25
return ;
27
26
}
28
27
28
+ if !dump_enabled ( tcx, "polonius" , body. source . def_id ( ) ) {
29
+ return ;
30
+ }
31
+
29
32
let localized_outlives_constraints = localized_outlives_constraints
30
33
. expect ( "missing localized constraints with `-Zpolonius=next`" ) ;
31
34
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.
35
122
let options = PrettyPrintMirOptions {
36
123
include_extra_comments : matches ! (
37
124
tcx. sess. opts. unstable_opts. mir_include_spans,
38
125
MirIncludeSpans :: On | MirIncludeSpans :: Nll
39
126
) ,
40
127
} ;
41
128
42
- dump_mir_with_options (
129
+ dump_mir_to_writer (
43
130
tcx,
44
- false ,
45
131
"polonius" ,
46
132
& 0 ,
47
133
body,
134
+ & mut buffer,
48
135
|pass_where, out| {
49
136
emit_polonius_mir (
50
137
tcx,
@@ -57,7 +144,27 @@ pub(crate) fn dump_polonius_mir<'tcx>(
57
144
)
58
145
} ,
59
146
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 ( ( ) )
61
168
}
62
169
63
170
/// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
@@ -102,3 +209,55 @@ fn emit_polonius_mir<'tcx>(
102
209
103
210
Ok ( ( ) )
104
211
}
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