Rollup merge of #136031 - lqd:polonius-debugger-episode-1, r=compiler-errors
Expand polonius MIR dump This PR starts expanding the polonius MIR: - switches to an HTML file, to show graphs in the same document as the MIR dump, share them more easily since it's a single file that can be hosted as a gist, and also to allow for interactivity in the near future. - adds the regular NLL MIR + polonius constraints - embeds a mermaid version of the CFG, similar to the graphviz one, but that needs a smaller js than `dot`'s emscripten js from graphvizonline [Here's an example](https://gistpreview.github.io/?0c18f2a59b5e24ac0f96447aa34ffe00) of how it looks. --- In future PRs: mermaid graphs of the NLL region graph, of the NLL SCCs, of the polonius localized outlives constraints, and the interactive polonius MIR dump. r? ```@matthewjasper```
This commit is contained in:
commit
182ccfa11f
3 changed files with 216 additions and 34 deletions
|
|
@ -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 {
|
||||
'>' => ">",
|
||||
'<' => "<",
|
||||
'&' => "&",
|
||||
'\'' => "'",
|
||||
'"' => """,
|
||||
_ => {
|
||||
// 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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue