diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs index 561229cf7255b..f844e8fbe0304 100644 --- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs +++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs @@ -2,19 +2,186 @@ use std::borrow::Cow; use std::cell::RefCell; +use std::ffi::OsString; +use std::path::PathBuf; use std::sync::OnceLock; use std::{io, ops, str}; use regex::Regex; -use rustc_graphviz as dot; +use rustc_hir::def_id::DefId; use rustc_index::bit_set::BitSet; -use rustc_middle::mir::{self, BasicBlock, Body, Location, graphviz_safe_def_name}; +use rustc_middle::mir::{ + self, BasicBlock, Body, Location, create_dump_file, dump_enabled, graphviz_safe_def_name, + traversal, +}; +use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_span::symbol::{Symbol, sym}; +use tracing::debug; +use {rustc_ast as ast, rustc_graphviz as dot}; use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext}; use super::{Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor}; +use crate::errors::{ + DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter, +}; + +/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via +/// `rustc_mir` attributes and `-Z dump-mir-dataflow`. The `Result` in and the `Results` out are +/// the same. +pub(super) fn write_graphviz_results<'tcx, A>( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + results: &mut Results<'tcx, A>, + pass_name: Option<&'static str>, +) -> std::io::Result<()> +where + A: Analysis<'tcx>, + A::Domain: DebugWithContext, +{ + use std::fs; + use std::io::Write; + + let def_id = body.source.def_id(); + let Ok(attrs) = RustcMirAttrs::parse(tcx, def_id) else { + // Invalid `rustc_mir` attrs are reported in `RustcMirAttrs::parse` + return Ok(()); + }; + + let file = try { + match attrs.output_path(A::NAME) { + Some(path) => { + debug!("printing dataflow results for {:?} to {}", def_id, path.display()); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::File::create_buffered(&path)? + } + + None if dump_enabled(tcx, A::NAME, def_id) => { + create_dump_file(tcx, "dot", false, A::NAME, &pass_name.unwrap_or("-----"), body)? + } + + _ => return Ok(()), + } + }; + let mut file = match file { + Ok(f) => f, + Err(e) => return Err(e), + }; + + let style = match attrs.formatter { + Some(sym::two_phase) => OutputStyle::BeforeAndAfter, + _ => OutputStyle::AfterOnly, + }; + + let mut buf = Vec::new(); + + let graphviz = Formatter::new(body, results, style); + let mut render_opts = + vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())]; + if tcx.sess.opts.unstable_opts.graphviz_dark_mode { + render_opts.push(dot::RenderOption::DarkTheme); + } + let r = with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts)); + + let lhs = try { + r?; + file.write_all(&buf)?; + }; + + lhs +} + +#[derive(Default)] +struct RustcMirAttrs { + basename_and_suffix: Option, + formatter: Option, +} + +impl RustcMirAttrs { + fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result { + let mut result = Ok(()); + let mut ret = RustcMirAttrs::default(); + + let rustc_mir_attrs = tcx + .get_attrs(def_id, sym::rustc_mir) + .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter())); + + for attr in rustc_mir_attrs { + let attr_result = if attr.has_name(sym::borrowck_graphviz_postflow) { + Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| { + let path = PathBuf::from(s.to_string()); + match path.file_name() { + Some(_) => Ok(path), + None => { + tcx.dcx().emit_err(PathMustEndInFilename { span: attr.span() }); + Err(()) + } + } + }) + } else if attr.has_name(sym::borrowck_graphviz_format) { + Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s { + sym::gen_kill | sym::two_phase => Ok(s), + _ => { + tcx.dcx().emit_err(UnknownFormatter { span: attr.span() }); + Err(()) + } + }) + } else { + Ok(()) + }; + + result = result.and(attr_result); + } + + result.map(|()| ret) + } + + fn set_field( + field: &mut Option, + tcx: TyCtxt<'_>, + attr: &ast::MetaItemInner, + mapper: impl FnOnce(Symbol) -> Result, + ) -> Result<(), ()> { + if field.is_some() { + tcx.dcx() + .emit_err(DuplicateValuesFor { span: attr.span(), name: attr.name_or_empty() }); + + return Err(()); + } + + if let Some(s) = attr.value_str() { + *field = Some(mapper(s)?); + Ok(()) + } else { + tcx.dcx() + .emit_err(RequiresAnArgument { span: attr.span(), name: attr.name_or_empty() }); + Err(()) + } + } + + /// Returns the path where dataflow results should be written, or `None` + /// `borrowck_graphviz_postflow` was not specified. + /// + /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`: + /// + /// "path/suffix.dot" -> "path/analysis_name_suffix.dot" + fn output_path(&self, analysis_name: &str) -> Option { + let mut ret = self.basename_and_suffix.as_ref().cloned()?; + let suffix = ret.file_name().unwrap(); // Checked when parsing attrs + + let mut file_name: OsString = analysis_name.into(); + file_name.push("_"); + file_name.push(suffix); + ret.set_file_name(file_name); + + Some(ret) + } +} #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum OutputStyle { +enum OutputStyle { AfterOnly, BeforeAndAfter, } @@ -28,7 +195,7 @@ impl OutputStyle { } } -pub(crate) struct Formatter<'mir, 'tcx, A> +struct Formatter<'mir, 'tcx, A> where A: Analysis<'tcx>, { @@ -45,12 +212,12 @@ impl<'mir, 'tcx, A> Formatter<'mir, 'tcx, A> where A: Analysis<'tcx>, { - pub(crate) fn new( + fn new( body: &'mir Body<'tcx>, results: &'mir mut Results<'tcx, A>, style: OutputStyle, ) -> Self { - let reachable = mir::traversal::reachable_as_bitset(body); + let reachable = traversal::reachable_as_bitset(body); Formatter { cursor: results.as_results_cursor(body).into(), style, reachable } } @@ -61,7 +228,7 @@ where /// A pair of a basic block and an index into that basic blocks `successors`. #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub(crate) struct CfgEdge { +struct CfgEdge { source: BasicBlock, index: usize, } @@ -520,7 +687,7 @@ struct StateDiffCollector { impl StateDiffCollector { fn run<'tcx, A>( - body: &mir::Body<'tcx>, + body: &Body<'tcx>, block: BasicBlock, results: &mut Results<'tcx, A>, style: OutputStyle, diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs index caff2a81ff306..7f5a68e884eaf 100644 --- a/compiler/rustc_mir_dataflow/src/framework/mod.rs +++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs @@ -42,7 +42,7 @@ use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, Terminator use rustc_middle::ty::TyCtxt; use tracing::error; -use self::results::write_graphviz_results; +use self::graphviz::write_graphviz_results; use super::fmt::DebugWithContext; mod cursor; diff --git a/compiler/rustc_mir_dataflow/src/framework/results.rs b/compiler/rustc_mir_dataflow/src/framework/results.rs index c4321c454e6ef..a7dbd99b8abde 100644 --- a/compiler/rustc_mir_dataflow/src/framework/results.rs +++ b/compiler/rustc_mir_dataflow/src/framework/results.rs @@ -1,22 +1,9 @@ //! Dataflow analysis results. -use std::ffi::OsString; -use std::path::PathBuf; - -use rustc_hir::def_id::DefId; use rustc_index::IndexVec; -use rustc_middle::mir::{self, BasicBlock, create_dump_file, dump_enabled, traversal}; -use rustc_middle::ty::TyCtxt; -use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_span::symbol::{Symbol, sym}; -use tracing::debug; -use {rustc_ast as ast, rustc_graphviz as dot}; +use rustc_middle::mir::{BasicBlock, Body, traversal}; -use super::fmt::DebugWithContext; -use super::{Analysis, ResultsCursor, ResultsVisitor, graphviz, visit_results}; -use crate::errors::{ - DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter, -}; +use super::{Analysis, ResultsCursor, ResultsVisitor, visit_results}; use crate::framework::cursor::ResultsHandle; pub type EntrySets<'tcx, A> = IndexVec>::Domain>; @@ -41,16 +28,13 @@ where /// `Results` is also used outside the cursor. pub fn as_results_cursor<'mir>( &'mir mut self, - body: &'mir mir::Body<'tcx>, + body: &'mir Body<'tcx>, ) -> ResultsCursor<'mir, 'tcx, A> { ResultsCursor::new(body, ResultsHandle::BorrowedMut(self)) } /// Creates a `ResultsCursor` that takes ownership of the `Results`. - pub fn into_results_cursor<'mir>( - self, - body: &'mir mir::Body<'tcx>, - ) -> ResultsCursor<'mir, 'tcx, A> { + pub fn into_results_cursor<'mir>(self, body: &'mir Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> { ResultsCursor::new(body, ResultsHandle::Owned(self)) } @@ -61,7 +45,7 @@ where pub fn visit_with<'mir>( &mut self, - body: &'mir mir::Body<'tcx>, + body: &'mir Body<'tcx>, blocks: impl IntoIterator, vis: &mut impl ResultsVisitor<'mir, 'tcx, A>, ) { @@ -70,166 +54,10 @@ where pub fn visit_reachable_with<'mir>( &mut self, - body: &'mir mir::Body<'tcx>, + body: &'mir Body<'tcx>, vis: &mut impl ResultsVisitor<'mir, 'tcx, A>, ) { let blocks = traversal::reachable(body); visit_results(body, blocks.map(|(bb, _)| bb), self, vis) } } - -// Graphviz - -/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via -/// `rustc_mir` attributes and `-Z dump-mir-dataflow`. The `Result` in and the `Results` out are -/// the same. -pub(super) fn write_graphviz_results<'tcx, A>( - tcx: TyCtxt<'tcx>, - body: &mir::Body<'tcx>, - results: &mut Results<'tcx, A>, - pass_name: Option<&'static str>, -) -> std::io::Result<()> -where - A: Analysis<'tcx>, - A::Domain: DebugWithContext, -{ - use std::fs; - use std::io::Write; - - let def_id = body.source.def_id(); - let Ok(attrs) = RustcMirAttrs::parse(tcx, def_id) else { - // Invalid `rustc_mir` attrs are reported in `RustcMirAttrs::parse` - return Ok(()); - }; - - let file = try { - match attrs.output_path(A::NAME) { - Some(path) => { - debug!("printing dataflow results for {:?} to {}", def_id, path.display()); - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::File::create_buffered(&path)? - } - - None if dump_enabled(tcx, A::NAME, def_id) => { - create_dump_file(tcx, "dot", false, A::NAME, &pass_name.unwrap_or("-----"), body)? - } - - _ => return Ok(()), - } - }; - let mut file = match file { - Ok(f) => f, - Err(e) => return Err(e), - }; - - let style = match attrs.formatter { - Some(sym::two_phase) => graphviz::OutputStyle::BeforeAndAfter, - _ => graphviz::OutputStyle::AfterOnly, - }; - - let mut buf = Vec::new(); - - let graphviz = graphviz::Formatter::new(body, results, style); - let mut render_opts = - vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())]; - if tcx.sess.opts.unstable_opts.graphviz_dark_mode { - render_opts.push(dot::RenderOption::DarkTheme); - } - let r = with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts)); - - let lhs = try { - r?; - file.write_all(&buf)?; - }; - - lhs -} - -#[derive(Default)] -struct RustcMirAttrs { - basename_and_suffix: Option, - formatter: Option, -} - -impl RustcMirAttrs { - fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result { - let mut result = Ok(()); - let mut ret = RustcMirAttrs::default(); - - let rustc_mir_attrs = tcx - .get_attrs(def_id, sym::rustc_mir) - .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter())); - - for attr in rustc_mir_attrs { - let attr_result = if attr.has_name(sym::borrowck_graphviz_postflow) { - Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| { - let path = PathBuf::from(s.to_string()); - match path.file_name() { - Some(_) => Ok(path), - None => { - tcx.dcx().emit_err(PathMustEndInFilename { span: attr.span() }); - Err(()) - } - } - }) - } else if attr.has_name(sym::borrowck_graphviz_format) { - Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s { - sym::gen_kill | sym::two_phase => Ok(s), - _ => { - tcx.dcx().emit_err(UnknownFormatter { span: attr.span() }); - Err(()) - } - }) - } else { - Ok(()) - }; - - result = result.and(attr_result); - } - - result.map(|()| ret) - } - - fn set_field( - field: &mut Option, - tcx: TyCtxt<'_>, - attr: &ast::MetaItemInner, - mapper: impl FnOnce(Symbol) -> Result, - ) -> Result<(), ()> { - if field.is_some() { - tcx.dcx() - .emit_err(DuplicateValuesFor { span: attr.span(), name: attr.name_or_empty() }); - - return Err(()); - } - - if let Some(s) = attr.value_str() { - *field = Some(mapper(s)?); - Ok(()) - } else { - tcx.dcx() - .emit_err(RequiresAnArgument { span: attr.span(), name: attr.name_or_empty() }); - Err(()) - } - } - - /// Returns the path where dataflow results should be written, or `None` - /// `borrowck_graphviz_postflow` was not specified. - /// - /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`: - /// - /// "path/suffix.dot" -> "path/analysis_name_suffix.dot" - fn output_path(&self, analysis_name: &str) -> Option { - let mut ret = self.basename_and_suffix.as_ref().cloned()?; - let suffix = ret.file_name().unwrap(); // Checked when parsing attrs - - let mut file_name: OsString = analysis_name.into(); - file_name.push("_"); - file_name.push(suffix); - ret.set_file_name(file_name); - - Some(ret) - } -} diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs index b9e194b00c54e..d69a8019c8d0e 100644 --- a/compiler/rustc_mir_dataflow/src/impls/mod.rs +++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs @@ -1,7 +1,3 @@ -//! Dataflow analyses are built upon some interpretation of the -//! bitvectors attached to each basic block, represented via a -//! zero-sized structure. - mod borrowed_locals; mod initialized; mod liveness;