diff options
| author | Nicholas Nethercote <n.nethercote@gmail.com> | 2025-08-15 14:38:17 +1000 |
|---|---|---|
| committer | Nicholas Nethercote <n.nethercote@gmail.com> | 2025-09-01 09:19:03 +1000 |
| commit | 5ce3797073ee5e6cc487b80effbc682533d9425c (patch) | |
| tree | 06a143d06ef4deed8856880424c3ff824cdbf756 /compiler/rustc_middle/src/mir/pretty.rs | |
| parent | 2d21c134054b6d337d4a1b81550a1ad10509b913 (diff) | |
| download | rust-5ce3797073ee5e6cc487b80effbc682533d9425c.tar.gz rust-5ce3797073ee5e6cc487b80effbc682533d9425c.zip | |
Introduce `MirDumper` and `MirWriter`.
MIR dumping is a mess. There are lots of functions and entry points, e.g. `dump_mir`, `dump_mir_with_options`, `dump_polonius_mir`, `dump_mir_to_writer`. Also, it's crucial that `create_dump_file` is never called without `dump_enabled` first being checked, but there is no mechanism for ensuring this and it's hard to tell if it is satisfied on all paths. (`dump_enabled` is checked twice on some paths, however!) This commit introduces `MirWriter`, which controls the MIR writing, and encapsulates the `extra_data` closure and `options`. Two existing functions are now methods of this type. It sets reasonable defaults, allowing the removal of many `|_, _| Ok(())` closures. The commit also introduces `MirDumper`, which is layered on top of `MirWriter`, and which manages the creation of the dump files, encapsulating pass names, disambiguators, etc. Four existing functions are now methods of this type. - `MirDumper::new` will only succeed if dumps are enabled, and will return `None` otherwise, which makes it impossible to dump when you shouldn't. - It also sets reasonable defaults for various things like disambiguators, which means you no longer need to specify them in many cases. When they do need to be specified, it's now done via setter methods. - It avoids some repetition. E.g. `dump_nll_mir` previously specifed the pass name `"nll"` four times and the disambiguator `&0` three times; now it specifies them just once, to put them in the `MirDumper`. - For Polonius, the `extra_data` closure can now be specified earlier, which avoids having to pass some arguments through some functions.
Diffstat (limited to 'compiler/rustc_middle/src/mir/pretty.rs')
| -rw-r--r-- | compiler/rustc_middle/src/mir/pretty.rs | 282 |
1 files changed, 143 insertions, 139 deletions
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 14f504bb2a9..a7d99f513a1 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -44,7 +44,7 @@ pub enum PassWhere { } /// Cosmetic options for pretty-printing the MIR contents, gathered from the CLI. Each pass can -/// override these when dumping its own specific MIR information with [`dump_mir_with_options`]. +/// override these when dumping its own specific MIR information with `dump_mir`. #[derive(Copy, Clone)] pub struct PrettyPrintMirOptions { /// Whether to include extra comments, like span info. From `-Z mir-include-spans`. @@ -58,6 +58,79 @@ impl PrettyPrintMirOptions { } } +/// Manages MIR dumping, which is MIR writing done to a file with a specific name. In particular, +/// it makes it impossible to dump MIR to one of these files when it hasn't been requested from the +/// command line. Layered on top of `MirWriter`, which does the actual writing. +pub struct MirDumper<'dis, 'de, 'tcx> { + show_pass_num: bool, + pass_name: &'static str, + disambiguator: &'dis dyn Display, + writer: MirWriter<'de, 'tcx>, +} + +impl<'dis, 'de, 'tcx> MirDumper<'dis, 'de, 'tcx> { + // If dumping should be performed (e.g. because it was requested on the + // CLI), returns a `MirDumper` with default values for the following fields: + // - `show_pass_num`: `false` + // - `disambiguator`: `&0` + // - `writer.extra_data`: a no-op + // - `writer.options`: default options derived from CLI flags + pub fn new(tcx: TyCtxt<'tcx>, pass_name: &'static str, body: &Body<'tcx>) -> Option<Self> { + let dump_enabled = if let Some(ref filters) = tcx.sess.opts.unstable_opts.dump_mir { + // see notes on #41697 below + let node_path = + ty::print::with_forced_impl_filename_line!(tcx.def_path_str(body.source.def_id())); + filters.split('|').any(|or_filter| { + or_filter.split('&').all(|and_filter| { + let and_filter_trimmed = and_filter.trim(); + and_filter_trimmed == "all" + || pass_name.contains(and_filter_trimmed) + || node_path.contains(and_filter_trimmed) + }) + }) + } else { + false + }; + + dump_enabled.then_some(MirDumper { + show_pass_num: false, + pass_name, + disambiguator: &0, + writer: MirWriter::new(tcx), + }) + } + + pub fn tcx(&self) -> TyCtxt<'tcx> { + self.writer.tcx + } + + #[must_use] + pub fn set_show_pass_num(mut self) -> Self { + self.show_pass_num = true; + self + } + + #[must_use] + pub fn set_disambiguator(mut self, disambiguator: &'dis dyn Display) -> Self { + self.disambiguator = disambiguator; + self + } + + #[must_use] + pub fn set_extra_data( + mut self, + extra_data: &'de dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>, + ) -> Self { + self.writer.extra_data = extra_data; + self + } + + #[must_use] + pub fn set_options(mut self, options: PrettyPrintMirOptions) -> Self { + self.writer.options = options; + self + } + /// If the session is properly configured, dumps a human-readable representation of the MIR /// (with default pretty-printing options) into: /// @@ -82,125 +155,49 @@ impl PrettyPrintMirOptions { /// or `typeck` appears in the name. /// - `foo & nll | bar & typeck` == match if `foo` and `nll` both appear in the name /// or `typeck` and `bar` both appear in the name. - #[inline] - pub fn dump_mir<'tcx>( - tcx: TyCtxt<'tcx>, - pass_num: bool, - pass_name: &str, - disambiguator: &dyn Display, - body: &Body<'tcx>, - extra_data: &dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>, - ) { - dump_mir_with_options( - tcx, - pass_num, - pass_name, - disambiguator, - body, - extra_data, - PrettyPrintMirOptions::from_cli(tcx), - ); - } - - /// If the session is properly configured, dumps a human-readable representation of the MIR, - /// with the given [pretty-printing options][PrettyPrintMirOptions]. - /// - /// See [`dump_mir`] for more details. - /// - #[inline] - pub fn dump_mir_with_options<'tcx>( - tcx: TyCtxt<'tcx>, - pass_num: bool, - pass_name: &str, - disambiguator: &dyn Display, - body: &Body<'tcx>, - extra_data: &dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>, - options: PrettyPrintMirOptions, - ) { - if !dump_enabled(tcx, pass_name, body.source.def_id()) { - return; - } - + pub fn dump_mir(&self, body: &Body<'tcx>) { let _: io::Result<()> = try { - let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, body)?; - dump_mir_to_writer(tcx, pass_name, disambiguator, body, &mut file, extra_data, options)?; + let mut file = self.create_dump_file("mir", body)?; + self.dump_mir_to_writer(body, &mut file)?; }; - if tcx.sess.opts.unstable_opts.dump_mir_graphviz { + if self.tcx().sess.opts.unstable_opts.dump_mir_graphviz { let _: io::Result<()> = try { - let mut file = create_dump_file(tcx, "dot", pass_num, pass_name, disambiguator, body)?; - write_mir_fn_graphviz(tcx, body, false, &mut file)?; + let mut file = self.create_dump_file("dot", body)?; + write_mir_fn_graphviz(self.tcx(), body, false, &mut file)?; }; } } - pub fn dump_enabled(tcx: TyCtxt<'_>, pass_name: &str, def_id: DefId) -> bool { - let Some(ref filters) = tcx.sess.opts.unstable_opts.dump_mir else { - return false; - }; - // see notes on #41697 below - let node_path = ty::print::with_forced_impl_filename_line!(tcx.def_path_str(def_id)); - filters.split('|').any(|or_filter| { - or_filter.split('&').all(|and_filter| { - let and_filter_trimmed = and_filter.trim(); - and_filter_trimmed == "all" - || pass_name.contains(and_filter_trimmed) - || node_path.contains(and_filter_trimmed) - }) - }) - } - - // #41697 -- we use `with_forced_impl_filename_line()` because - // `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>( - tcx: TyCtxt<'tcx>, - pass_name: &str, - disambiguator: &dyn Display, - body: &Body<'tcx>, - w: &mut dyn io::Write, - extra_data: &dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>, - options: PrettyPrintMirOptions, - ) -> io::Result<()> { + // #41697 -- we use `with_forced_impl_filename_line()` because `def_path_str()` would otherwise + // trigger `type_of`, and this can run while we are already attempting to evaluate `type_of`. + pub fn dump_mir_to_writer(&self, body: &Body<'tcx>, w: &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())); + let def_path = ty::print::with_forced_impl_filename_line!( + self.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}")?; + writeln!(w, " {} {}", self.disambiguator, self.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, extra_data, w, options)?; - extra_data(PassWhere::AfterCFG, w) + (self.writer.extra_data)(PassWhere::BeforeCFG, w)?; + write_user_type_annotations(self.tcx(), body, w)?; + self.writer.write_mir_fn(body, w)?; + (self.writer.extra_data)(PassWhere::AfterCFG, w) } /// Returns the path to the filename where we should dump a given MIR. /// Also used by other bits of code (e.g., NLL inference) that dump /// graphviz data or other things. - fn dump_path<'tcx>( - tcx: TyCtxt<'tcx>, - extension: &str, - pass_num: bool, - pass_name: &str, - disambiguator: &dyn Display, - body: &Body<'tcx>, - ) -> PathBuf { + fn dump_path(&self, extension: &str, body: &Body<'tcx>) -> PathBuf { + let tcx = self.tcx(); let source = body.source; let promotion_id = match source.promoted { Some(id) => format!("-{id:?}"), @@ -209,7 +206,7 @@ impl PrettyPrintMirOptions { let pass_num = if tcx.sess.opts.unstable_opts.dump_mir_exclude_pass_number { String::new() - } else if pass_num { + } else if self.show_pass_num { let (dialect_index, phase_index) = body.phase.index(); format!(".{}-{}-{:03}", dialect_index, phase_index, body.pass_count) } else { @@ -222,8 +219,8 @@ impl PrettyPrintMirOptions { // to get unique file names. let shim_disambiguator = match source.instance { ty::InstanceKind::DropGlue(_, Some(ty)) => { - // Unfortunately, pretty-printed typed are not very filename-friendly. - // We dome some filtering. + // Unfortunately, pretty-printed types are not very filename-friendly. + // We do some filtering. let mut s = ".".to_owned(); s.extend(ty.to_string().chars().filter_map(|c| match c { ' ' => None, @@ -275,6 +272,8 @@ impl PrettyPrintMirOptions { let mut file_path = PathBuf::new(); file_path.push(Path::new(&tcx.sess.opts.unstable_opts.dump_mir_dir)); + let pass_name = self.pass_name; + let disambiguator = self.disambiguator; let file_name = format!( "{crate_name}.{item_name}{shim_disambiguator}{promotion_id}{pass_num}.{pass_name}.{disambiguator}.{extension}", ); @@ -288,15 +287,12 @@ impl PrettyPrintMirOptions { /// bit of MIR-related data. Used by `mir-dump`, but also by other /// bits of code (e.g., NLL inference) that dump graphviz data or /// other things, and hence takes the extension as an argument. - pub fn create_dump_file<'tcx>( - tcx: TyCtxt<'tcx>, + pub fn create_dump_file( + &self, extension: &str, - pass_num: bool, - pass_name: &str, - disambiguator: &dyn Display, body: &Body<'tcx>, ) -> io::Result<io::BufWriter<fs::File>> { - let file_path = dump_path(tcx, extension, pass_num, pass_name, disambiguator, body); + let file_path = self.dump_path(extension, body); if let Some(parent) = file_path.parent() { fs::create_dir_all(parent).map_err(|e| { io::Error::new( @@ -309,6 +305,7 @@ impl PrettyPrintMirOptions { io::Error::new(e.kind(), format!("IO error creating MIR dump file: {file_path:?}; {e}")) }) } +} /////////////////////////////////////////////////////////////////////////// // Whole MIR bodies @@ -320,7 +317,7 @@ pub fn write_mir_pretty<'tcx>( single: Option<DefId>, w: &mut dyn io::Write, ) -> io::Result<()> { - let options = PrettyPrintMirOptions::from_cli(tcx); + let writer = MirWriter::new(tcx); writeln!(w, "// WARNING: This output format is intended for human consumers only")?; writeln!(w, "// and is subject to change without notice. Knock yourself out.")?; @@ -336,11 +333,11 @@ pub fn write_mir_pretty<'tcx>( } let render_body = |w: &mut dyn io::Write, body| -> io::Result<()> { - write_mir_fn(tcx, body, &|_, _| Ok(()), w, options)?; + writer.write_mir_fn(body, w)?; for body in tcx.promoted_mir(def_id) { writeln!(w)?; - write_mir_fn(tcx, body, &|_, _| Ok(()), w, options)?; + writer.write_mir_fn(body, w)?; } Ok(()) }; @@ -352,7 +349,7 @@ pub fn write_mir_pretty<'tcx>( writeln!(w, "// MIR FOR CTFE")?; // Do not use `render_body`, as that would render the promoteds again, but these // are shared between mir_for_ctfe and optimized_mir - write_mir_fn(tcx, tcx.mir_for_ctfe(def_id), &|_, _| Ok(()), w, options)?; + writer.write_mir_fn(tcx.mir_for_ctfe(def_id), w)?; } else { let instance_mir = tcx.instance_mir(ty::InstanceKind::Item(def_id)); render_body(w, instance_mir)?; @@ -361,18 +358,24 @@ pub fn write_mir_pretty<'tcx>( Ok(()) } +/// Does the writing of MIR to output, e.g. a file. +pub struct MirWriter<'de, 'tcx> { + tcx: TyCtxt<'tcx>, + extra_data: &'de dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>, + options: PrettyPrintMirOptions, +} + +impl<'de, 'tcx> MirWriter<'de, 'tcx> { + pub fn new(tcx: TyCtxt<'tcx>) -> Self { + MirWriter { tcx, extra_data: &|_, _| Ok(()), options: PrettyPrintMirOptions::from_cli(tcx) } + } + /// Write out a human-readable textual representation for the given function. - pub fn write_mir_fn<'tcx>( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - extra_data: &dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>, - w: &mut dyn io::Write, - options: PrettyPrintMirOptions, - ) -> io::Result<()> { - write_mir_intro(tcx, body, w, options)?; + pub fn write_mir_fn(&self, body: &Body<'tcx>, w: &mut dyn io::Write) -> io::Result<()> { + write_mir_intro(self.tcx, body, w, self.options)?; for block in body.basic_blocks.indices() { - extra_data(PassWhere::BeforeBlock(block), w)?; - write_basic_block(tcx, block, body, extra_data, w, options)?; + (self.extra_data)(PassWhere::BeforeBlock(block), w)?; + self.write_basic_block(block, body, w)?; if block.index() + 1 != body.basic_blocks.len() { writeln!(w)?; } @@ -380,10 +383,11 @@ pub fn write_mir_pretty<'tcx>( writeln!(w, "}}")?; - write_allocations(tcx, body, w)?; + write_allocations(self.tcx, body, w)?; Ok(()) } +} /// Prints local variables in a scope tree. fn write_scope_tree( @@ -695,14 +699,13 @@ pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option<DefId>) -> Vec<DefId> { /////////////////////////////////////////////////////////////////////////// // Basic blocks and their parts (statements, terminators, ...) +impl<'de, 'tcx> MirWriter<'de, 'tcx> { /// Write out a human-readable textual representation for the given basic block. - fn write_basic_block<'tcx>( - tcx: TyCtxt<'tcx>, + fn write_basic_block( + &self, block: BasicBlock, body: &Body<'tcx>, - extra_data: &dyn Fn(PassWhere, &mut dyn io::Write) -> io::Result<()>, w: &mut dyn io::Write, - options: PrettyPrintMirOptions, ) -> io::Result<()> { let data = &body[block]; @@ -713,19 +716,19 @@ pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option<DefId>) -> Vec<DefId> { // List of statements in the middle. let mut current_location = Location { block, statement_index: 0 }; for statement in &data.statements { - extra_data(PassWhere::BeforeLocation(current_location), w)?; + (self.extra_data)(PassWhere::BeforeLocation(current_location), w)?; let indented_body = format!("{INDENT}{INDENT}{statement:?};"); - if options.include_extra_comments { + if self.options.include_extra_comments { writeln!( w, "{:A$} // {}{}", indented_body, - if tcx.sess.verbose_internals() { + if self.tcx.sess.verbose_internals() { format!("{current_location:?}: ") } else { String::new() }, - comment(tcx, statement.source_info), + comment(self.tcx, statement.source_info), A = ALIGN, )?; } else { @@ -733,32 +736,32 @@ pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option<DefId>) -> Vec<DefId> { } write_extra( - tcx, + self.tcx, w, &|visitor| visitor.visit_statement(statement, current_location), - options, + self.options, )?; - extra_data(PassWhere::AfterLocation(current_location), w)?; + (self.extra_data)(PassWhere::AfterLocation(current_location), w)?; current_location.statement_index += 1; } // Terminator at the bottom. - extra_data(PassWhere::BeforeLocation(current_location), w)?; + (self.extra_data)(PassWhere::BeforeLocation(current_location), w)?; if data.terminator.is_some() { let indented_terminator = format!("{0}{0}{1:?};", INDENT, data.terminator().kind); - if options.include_extra_comments { + if self.options.include_extra_comments { writeln!( w, "{:A$} // {}{}", indented_terminator, - if tcx.sess.verbose_internals() { + if self.tcx.sess.verbose_internals() { format!("{current_location:?}: ") } else { String::new() }, - comment(tcx, data.terminator().source_info), + comment(self.tcx, data.terminator().source_info), A = ALIGN, )?; } else { @@ -766,18 +769,19 @@ pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option<DefId>) -> Vec<DefId> { } write_extra( - tcx, + self.tcx, w, &|visitor| visitor.visit_terminator(data.terminator(), current_location), - options, + self.options, )?; } - extra_data(PassWhere::AfterLocation(current_location), w)?; - extra_data(PassWhere::AfterTerminator(block), w)?; + (self.extra_data)(PassWhere::AfterLocation(current_location), w)?; + (self.extra_data)(PassWhere::AfterTerminator(block), w)?; writeln!(w, "{INDENT}}}") } +} impl Debug for Statement<'_> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { |
