|  | 
| 1 | 1 | use std::io; | 
| 2 | 2 | 
 | 
| 3 | 3 | use rustc_middle::mir::pretty::{ | 
| 4 |  | -    PrettyPrintMirOptions, create_dump_file, dump_enabled, dump_mir_to_writer, | 
|  | 4 | +    PassWhere, PrettyPrintMirOptions, create_dump_file, dump_enabled, dump_mir_to_writer, | 
| 5 | 5 | }; | 
| 6 |  | -use rustc_middle::mir::{Body, ClosureRegionRequirements, PassWhere}; | 
|  | 6 | +use rustc_middle::mir::{Body, ClosureRegionRequirements}; | 
| 7 | 7 | use rustc_middle::ty::TyCtxt; | 
| 8 | 8 | use rustc_session::config::MirIncludeSpans; | 
| 9 | 9 | 
 | 
| @@ -49,6 +49,7 @@ pub(crate) fn dump_polonius_mir<'tcx>( | 
| 49 | 49 | /// The polonius dump consists of: | 
| 50 | 50 | /// - the NLL MIR | 
| 51 | 51 | /// - the list of polonius localized constraints | 
|  | 52 | +/// - a mermaid graph of the CFG | 
| 52 | 53 | fn emit_polonius_dump<'tcx>( | 
| 53 | 54 |     tcx: TyCtxt<'tcx>, | 
| 54 | 55 |     body: &Body<'tcx>, | 
| @@ -80,7 +81,23 @@ fn emit_polonius_dump<'tcx>( | 
| 80 | 81 |     writeln!(out, "</pre></code>")?; | 
| 81 | 82 |     writeln!(out, "</div>")?; | 
| 82 | 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 | + | 
| 83 | 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>")?; | 
| 84 | 101 |     writeln!(out, "</body>")?; | 
| 85 | 102 |     writeln!(out, "</html>")?; | 
| 86 | 103 | 
 | 
| @@ -192,3 +209,55 @@ fn emit_polonius_mir<'tcx>( | 
| 192 | 209 | 
 | 
| 193 | 210 |     Ok(()) | 
| 194 | 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