about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_borrowck/src/polonius/dump.rs181
-rw-r--r--compiler/rustc_middle/src/mir/pretty.rs63
-rw-r--r--compiler/rustc_middle/src/mir/terminator.rs6
3 files changed, 216 insertions, 34 deletions
diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs
index a6d80149034..40e801d0388 100644
--- a/compiler/rustc_borrowck/src/polonius/dump.rs
+++ b/compiler/rustc_borrowck/src/polonius/dump.rs
@@ -1,7 +1,9 @@
 use std::io;
 
-use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
-use rustc_middle::mir::{Body, ClosureRegionRequirements, PassWhere};
+use rustc_middle::mir::pretty::{
+    PassWhere, PrettyPrintMirOptions, create_dump_file, dump_enabled, dump_mir_to_writer,
+};
+use rustc_middle::mir::{Body, ClosureRegionRequirements};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::config::MirIncludeSpans;
 
@@ -10,9 +12,6 @@ use crate::polonius::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSe
 use crate::{BorrowckInferCtxt, RegionInferenceContext};
 
 /// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information.
-// Note: this currently duplicates most of NLL MIR, with some additions for the localized outlives
-// constraints. This is ok for now as this dump will change in the near future to an HTML file to
-// become more useful.
 pub(crate) fn dump_polonius_mir<'tcx>(
     infcx: &BorrowckInferCtxt<'tcx>,
     body: &Body<'tcx>,
@@ -26,12 +25,100 @@ pub(crate) fn dump_polonius_mir<'tcx>(
         return;
     }
 
+    if !dump_enabled(tcx, "polonius", body.source.def_id()) {
+        return;
+    }
+
     let localized_outlives_constraints = localized_outlives_constraints
         .expect("missing localized constraints with `-Zpolonius=next`");
 
-    // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
-    // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
-    // they're always disabled in mir-opt tests to make working with blessed dumps easier.
+    let _: io::Result<()> = try {
+        let mut file = create_dump_file(tcx, "html", false, "polonius", &0, body)?;
+        emit_polonius_dump(
+            tcx,
+            body,
+            regioncx,
+            borrow_set,
+            localized_outlives_constraints,
+            closure_region_requirements,
+            &mut file,
+        )?;
+    };
+}
+
+/// The polonius dump consists of:
+/// - the NLL MIR
+/// - the list of polonius localized constraints
+/// - a mermaid graph of the CFG
+fn emit_polonius_dump<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    body: &Body<'tcx>,
+    regioncx: &RegionInferenceContext<'tcx>,
+    borrow_set: &BorrowSet<'tcx>,
+    localized_outlives_constraints: LocalizedOutlivesConstraintSet,
+    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
+    out: &mut dyn io::Write,
+) -> io::Result<()> {
+    // Prepare the HTML dump file prologue.
+    writeln!(out, "<!DOCTYPE html>")?;
+    writeln!(out, "<html>")?;
+    writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
+    writeln!(out, "<body>")?;
+
+    // Section 1: the NLL + Polonius MIR.
+    writeln!(out, "<div>")?;
+    writeln!(out, "Raw MIR dump")?;
+    writeln!(out, "<code><pre>")?;
+    emit_html_mir(
+        tcx,
+        body,
+        regioncx,
+        borrow_set,
+        localized_outlives_constraints,
+        closure_region_requirements,
+        out,
+    )?;
+    writeln!(out, "</pre></code>")?;
+    writeln!(out, "</div>")?;
+
+    // Section 2: mermaid visualization of the CFG.
+    writeln!(out, "<div>")?;
+    writeln!(out, "Control-flow graph")?;
+    writeln!(out, "<code><pre class='mermaid'>")?;
+    emit_mermaid_cfg(body, out)?;
+    writeln!(out, "</pre></code>")?;
+    writeln!(out, "</div>")?;
+
+    // Finalize the dump with the HTML epilogue.
+    writeln!(
+        out,
+        "<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
+    )?;
+    writeln!(out, "<script>")?;
+    writeln!(out, "mermaid.initialize({{ startOnLoad: false, maxEdges: 100 }});")?;
+    writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
+    writeln!(out, "</script>")?;
+    writeln!(out, "</body>")?;
+    writeln!(out, "</html>")?;
+
+    Ok(())
+}
+
+/// Emits the polonius MIR, as escaped HTML.
+fn emit_html_mir<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    body: &Body<'tcx>,
+    regioncx: &RegionInferenceContext<'tcx>,
+    borrow_set: &BorrowSet<'tcx>,
+    localized_outlives_constraints: LocalizedOutlivesConstraintSet,
+    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
+    out: &mut dyn io::Write,
+) -> io::Result<()> {
+    // Buffer the regular MIR dump to be able to escape it.
+    let mut buffer = Vec::new();
+
+    // We want the NLL extra comments printed by default in NLL MIR dumps. Specifying `-Z
+    // mir-include-spans` on the CLI still has priority.
     let options = PrettyPrintMirOptions {
         include_extra_comments: matches!(
             tcx.sess.opts.unstable_opts.mir_include_spans,
@@ -39,12 +126,12 @@ pub(crate) fn dump_polonius_mir<'tcx>(
         ),
     };
 
-    dump_mir_with_options(
+    dump_mir_to_writer(
         tcx,
-        false,
         "polonius",
         &0,
         body,
+        &mut buffer,
         |pass_where, out| {
             emit_polonius_mir(
                 tcx,
@@ -57,7 +144,27 @@ pub(crate) fn dump_polonius_mir<'tcx>(
             )
         },
         options,
-    );
+    )?;
+
+    // Escape the handful of characters that need it. We don't need to be particularly efficient:
+    // we're actually writing into a buffered writer already. Note that MIR dumps are valid UTF-8.
+    let buffer = String::from_utf8_lossy(&buffer);
+    for ch in buffer.chars() {
+        let escaped = match ch {
+            '>' => "&gt;",
+            '<' => "&lt;",
+            '&' => "&amp;",
+            '\'' => "&#39;",
+            '"' => "&quot;",
+            _ => {
+                // The common case, no escaping needed.
+                write!(out, "{}", ch)?;
+                continue;
+            }
+        };
+        write!(out, "{}", escaped)?;
+    }
+    Ok(())
 }
 
 /// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
@@ -102,3 +209,55 @@ fn emit_polonius_mir<'tcx>(
 
     Ok(())
 }
+
+/// Emits a mermaid flowchart of the CFG blocks and edges, similar to the graphviz version.
+fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
+    use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
+
+    // The mermaid chart type: a top-down flowchart.
+    writeln!(out, "flowchart TD")?;
+
+    // Emit the block nodes.
+    for (block_idx, block) in body.basic_blocks.iter_enumerated() {
+        let block_idx = block_idx.as_usize();
+        let cleanup = if block.is_cleanup { " (cleanup)" } else { "" };
+        writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
+    }
+
+    // Emit the edges between blocks, from the terminator edges.
+    for (block_idx, block) in body.basic_blocks.iter_enumerated() {
+        let block_idx = block_idx.as_usize();
+        let terminator = block.terminator();
+        match terminator.edges() {
+            TerminatorEdges::None => {}
+            TerminatorEdges::Single(bb) => {
+                writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
+            }
+            TerminatorEdges::Double(bb1, bb2) => {
+                if matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
+                    writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
+                    writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
+                } else {
+                    writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
+                    writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
+                }
+            }
+            TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
+                for to_idx in return_ {
+                    writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
+                }
+
+                if let Some(to_idx) = cleanup {
+                    writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
+                }
+            }
+            TerminatorEdges::SwitchInt { targets, .. } => {
+                for to_idx in targets.all_targets() {
+                    writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs
index ea35323ccc7..3b4fba97e60 100644
--- a/compiler/rustc_middle/src/mir/pretty.rs
+++ b/compiler/rustc_middle/src/mir/pretty.rs
@@ -1,8 +1,7 @@
 use std::collections::BTreeSet;
 use std::fmt::{Display, Write as _};
-use std::fs;
-use std::io::{self, Write as _};
 use std::path::{Path, PathBuf};
+use std::{fs, io};
 
 use rustc_abi::Size;
 use rustc_ast::InlineAsmTemplatePiece;
@@ -149,37 +148,59 @@ pub fn dump_enabled(tcx: TyCtxt<'_>, pass_name: &str, def_id: DefId) -> bool {
 // `def_path_str()` would otherwise trigger `type_of`, and this can
 // run while we are already attempting to evaluate `type_of`.
 
+/// Most use-cases of dumping MIR should use the [dump_mir] entrypoint instead, which will also
+/// check if dumping MIR is enabled, and if this body matches the filters passed on the CLI.
+///
+/// That being said, if the above requirements have been validated already, this function is where
+/// most of the MIR dumping occurs, if one needs to export it to a file they have created with
+/// [create_dump_file], rather than to a new file created as part of [dump_mir], or to stdout/stderr
+/// for debugging purposes.
+pub fn dump_mir_to_writer<'tcx, F>(
+    tcx: TyCtxt<'tcx>,
+    pass_name: &str,
+    disambiguator: &dyn Display,
+    body: &Body<'tcx>,
+    w: &mut dyn io::Write,
+    mut extra_data: F,
+    options: PrettyPrintMirOptions,
+) -> io::Result<()>
+where
+    F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>,
+{
+    // see notes on #41697 above
+    let def_path =
+        ty::print::with_forced_impl_filename_line!(tcx.def_path_str(body.source.def_id()));
+    // ignore-tidy-odd-backticks the literal below is fine
+    write!(w, "// MIR for `{def_path}")?;
+    match body.source.promoted {
+        None => write!(w, "`")?,
+        Some(promoted) => write!(w, "::{promoted:?}`")?,
+    }
+    writeln!(w, " {disambiguator} {pass_name}")?;
+    if let Some(ref layout) = body.coroutine_layout_raw() {
+        writeln!(w, "/* coroutine_layout = {layout:#?} */")?;
+    }
+    writeln!(w)?;
+    extra_data(PassWhere::BeforeCFG, w)?;
+    write_user_type_annotations(tcx, body, w)?;
+    write_mir_fn(tcx, body, &mut extra_data, w, options)?;
+    extra_data(PassWhere::AfterCFG, w)
+}
+
 fn dump_matched_mir_node<'tcx, F>(
     tcx: TyCtxt<'tcx>,
     pass_num: bool,
     pass_name: &str,
     disambiguator: &dyn Display,
     body: &Body<'tcx>,
-    mut extra_data: F,
+    extra_data: F,
     options: PrettyPrintMirOptions,
 ) where
     F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>,
 {
     let _: io::Result<()> = try {
         let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, body)?;
-        // see notes on #41697 above
-        let def_path =
-            ty::print::with_forced_impl_filename_line!(tcx.def_path_str(body.source.def_id()));
-        // ignore-tidy-odd-backticks the literal below is fine
-        write!(file, "// MIR for `{def_path}")?;
-        match body.source.promoted {
-            None => write!(file, "`")?,
-            Some(promoted) => write!(file, "::{promoted:?}`")?,
-        }
-        writeln!(file, " {disambiguator} {pass_name}")?;
-        if let Some(ref layout) = body.coroutine_layout_raw() {
-            writeln!(file, "/* coroutine_layout = {layout:#?} */")?;
-        }
-        writeln!(file)?;
-        extra_data(PassWhere::BeforeCFG, &mut file)?;
-        write_user_type_annotations(tcx, body, &mut file)?;
-        write_mir_fn(tcx, body, &mut extra_data, &mut file, options)?;
-        extra_data(PassWhere::AfterCFG, &mut file)?;
+        dump_mir_to_writer(tcx, pass_name, disambiguator, body, &mut file, extra_data, options)?;
     };
 
     if tcx.sess.opts.unstable_opts.dump_mir_graphviz {
diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs
index 473b817aed0..c04a8251fbc 100644
--- a/compiler/rustc_middle/src/mir/terminator.rs
+++ b/compiler/rustc_middle/src/mir/terminator.rs
@@ -581,9 +581,11 @@ impl<'tcx> TerminatorKind<'tcx> {
 pub enum TerminatorEdges<'mir, 'tcx> {
     /// For terminators that have no successor, like `return`.
     None,
-    /// For terminators that a single successor, like `goto`, and `assert` without cleanup block.
+    /// For terminators that have a single successor, like `goto`, and `assert` without a cleanup
+    /// block.
     Single(BasicBlock),
-    /// For terminators that two successors, `assert` with cleanup block and `falseEdge`.
+    /// For terminators that have two successors, like `assert` with a cleanup block, and
+    /// `falseEdge`.
     Double(BasicBlock, BasicBlock),
     /// Special action for `Yield`, `Call` and `InlineAsm` terminators.
     AssignOnReturn {