diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs index a6d8014903440..40e801d03885c 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>, + out: &mut dyn io::Write, +) -> io::Result<()> { + // Prepare the HTML dump file prologue. + writeln!(out, "")?; + writeln!(out, "")?; + writeln!(out, "Polonius MIR dump")?; + writeln!(out, "")?; + + // Section 1: the NLL + Polonius MIR. + writeln!(out, "
")?; + writeln!(out, "Raw MIR dump")?; + writeln!(out, "
")?;
+    emit_html_mir(
+        tcx,
+        body,
+        regioncx,
+        borrow_set,
+        localized_outlives_constraints,
+        closure_region_requirements,
+        out,
+    )?;
+    writeln!(out, "
")?; + writeln!(out, "
")?; + + // Section 2: mermaid visualization of the CFG. + writeln!(out, "
")?; + writeln!(out, "Control-flow graph")?; + writeln!(out, "
")?;
+    emit_mermaid_cfg(body, out)?;
+    writeln!(out, "
")?; + writeln!(out, "
")?; + + // Finalize the dump with the HTML epilogue. + writeln!( + out, + "" + )?; + writeln!(out, "")?; + writeln!(out, "")?; + writeln!(out, "")?; + + 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>, + 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 { + '>' => ">", + '<' => "<", + '&' => "&", + '\'' => "'", + '"' => """, + _ => { + // 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 ea35323ccc7f2..3b4fba97e60b9 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 473b817aed076..c04a8251fbc5e 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 {