diff --git a/src/librustc_mir/dataflow/generic.rs b/src/librustc_mir/dataflow/generic.rs index 400f612a0fc9..dd6238b80d17 100644 --- a/src/librustc_mir/dataflow/generic.rs +++ b/src/librustc_mir/dataflow/generic.rs @@ -16,16 +16,24 @@ //! [gk]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems //! [#64566]: https://github.com/rust-lang/rust/pull/64566 +use std::borrow::Borrow; use std::cmp::Ordering; -use std::ops; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; +use std::{fs, io, ops}; +use rustc::hir::def_id::DefId; use rustc::mir::{self, traversal, BasicBlock, Location}; +use rustc::ty::{self, TyCtxt}; +use rustc_data_structures::work_queue::WorkQueue; use rustc_index::bit_set::BitSet; use rustc_index::vec::{Idx, IndexVec}; -use rustc_data_structures::work_queue::WorkQueue; +use syntax::symbol::sym; use crate::dataflow::BottomValue; +mod graphviz; + /// A specific kind of dataflow analysis. /// /// To run a dataflow analysis, one must set the initial state of the `START_BLOCK` via @@ -62,6 +70,13 @@ pub trait Analysis<'tcx>: BottomValue { /// and try to keep it short. const NAME: &'static str; + /// How each element of your dataflow state will be displayed during debugging. + /// + /// By default, this is the `fmt::Debug` representation of `Self::Idx`. + fn pretty_print_idx(&self, w: &mut impl io::Write, idx: Self::Idx) -> io::Result<()> { + write!(w, "{:?}", idx) + } + /// The size of each bitvector allocated for each block. fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize; @@ -357,7 +372,9 @@ where { analysis: A, bits_per_block: usize, + tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, + def_id: DefId, dead_unwinds: &'a BitSet, entry_sets: IndexVec>, } @@ -367,7 +384,9 @@ where A: Analysis<'tcx>, { pub fn new( + tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, + def_id: DefId, dead_unwinds: &'a BitSet, analysis: A, ) -> Self { @@ -385,7 +404,9 @@ where Engine { analysis, bits_per_block, + tcx, body, + def_id, dead_unwinds, entry_sets, } @@ -421,10 +442,26 @@ where ); } - Results { - analysis: self.analysis, - entry_sets: self.entry_sets, + let Engine { + tcx, + body, + def_id, + analysis, + entry_sets, + .. + } = self; + + let results = Results { analysis, entry_sets }; + + let attrs = tcx.get_attrs(def_id); + if let Some(path) = get_dataflow_graphviz_output_path(tcx, attrs, A::NAME) { + let result = write_dataflow_graphviz_results(body, def_id, &path, &results); + if let Err(e) = result { + warn!("Failed to write dataflow results to {}: {}", path.display(), e); + } } + + results } fn propagate_bits_into_graph_successors_of( @@ -518,3 +555,59 @@ where } } } + +/// Looks for attributes like `#[rustc_mir(borrowck_graphviz_postflow="./path/to/suffix.dot")]` and +/// extracts the path with the given analysis name prepended to the suffix. +/// +/// Returns `None` if no such attribute exists. +fn get_dataflow_graphviz_output_path( + tcx: TyCtxt<'tcx>, + attrs: ty::Attributes<'tcx>, + analysis: &str, +) -> Option { + let mut rustc_mir_attrs = attrs + .into_iter() + .filter(|attr| attr.check_name(sym::rustc_mir)) + .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter())); + + let borrowck_graphviz_postflow = rustc_mir_attrs + .find(|attr| attr.check_name(sym::borrowck_graphviz_postflow))?; + + let path_and_suffix = match borrowck_graphviz_postflow.value_str() { + Some(p) => p, + None => { + tcx.sess.span_err( + borrowck_graphviz_postflow.span(), + "borrowck_graphviz_postflow requires a path", + ); + + return None; + } + }; + + // Change "path/suffix.dot" to "path/analysis_name_suffix.dot" + let mut ret = PathBuf::from(path_and_suffix.to_string()); + let suffix = ret.file_name().unwrap(); + + let mut file_name: OsString = analysis.into(); + file_name.push("_"); + file_name.push(suffix); + ret.set_file_name(file_name); + + Some(ret) +} + +fn write_dataflow_graphviz_results>( + body: &mir::Body<'tcx>, + def_id: DefId, + path: &Path, + results: &Results<'tcx, A> +) -> io::Result<()> { + debug!("printing dataflow results for {:?} to {}", def_id, path.display()); + + let mut buf = Vec::new(); + let graphviz = graphviz::Formatter::new(body, def_id, results); + + dot::render(&graphviz, &mut buf)?; + fs::write(path, buf) +} diff --git a/src/librustc_mir/dataflow/generic/graphviz.rs b/src/librustc_mir/dataflow/generic/graphviz.rs new file mode 100644 index 000000000000..2a08feff9e77 --- /dev/null +++ b/src/librustc_mir/dataflow/generic/graphviz.rs @@ -0,0 +1,412 @@ +use std::cell::RefCell; +use std::io::{self, Write}; +use std::{ops, str}; + +use rustc::hir::def_id::DefId; +use rustc::mir::{self, BasicBlock, Body, Location}; +use rustc_index::bit_set::{BitSet, HybridBitSet}; +use rustc_index::vec::Idx; + +use crate::util::graphviz_safe_def_name; +use super::{Analysis, Results, ResultsRefCursor}; + +pub struct Formatter<'a, 'tcx, A> +where + A: Analysis<'tcx>, +{ + body: &'a Body<'tcx>, + def_id: DefId, + + // This must be behind a `RefCell` because `dot::Labeller` takes `&self`. + block_formatter: RefCell>, +} + +impl Formatter<'a, 'tcx, A> +where + A: Analysis<'tcx>, +{ + pub fn new( + body: &'a Body<'tcx>, + def_id: DefId, + results: &'a Results<'tcx, A>, + ) -> Self { + let block_formatter = BlockFormatter { + bg: Background::Light, + prev_state: BitSet::new_empty(results.analysis.bits_per_block(body)), + results: ResultsRefCursor::new(body, results), + }; + + Formatter { + body, + def_id, + block_formatter: RefCell::new(block_formatter), + } + } +} + +/// A pair of a basic block and an index into that basic blocks `successors`. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct CfgEdge { + source: BasicBlock, + index: usize, +} + +fn outgoing_edges(body: &Body<'_>, bb: BasicBlock) -> Vec { + body[bb] + .terminator() + .successors() + .enumerate() + .map(|(index, _)| CfgEdge { source: bb, index }) + .collect() +} + +impl dot::Labeller<'_> for Formatter<'a, 'tcx, A> +where + A: Analysis<'tcx>, +{ + type Node = BasicBlock; + type Edge = CfgEdge; + + fn graph_id(&self) -> dot::Id<'_> { + let name = graphviz_safe_def_name(self.def_id); + dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap() + } + + fn node_id(&self, n: &Self::Node) -> dot::Id<'_> { + dot::Id::new(format!("bb_{}", n.index())).unwrap() + } + + fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> { + let mut label = Vec::new(); + self.block_formatter + .borrow_mut() + .write_node_label(&mut label, self.body, *block) + .unwrap(); + dot::LabelText::html(String::from_utf8(label).unwrap()) + } + + fn node_shape(&self, _n: &Self::Node) -> Option> { + Some(dot::LabelText::label("none")) + } + + fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> { + let label = &self.body + [e.source] + .terminator() + .kind + .fmt_successor_labels() + [e.index]; + dot::LabelText::label(label.clone()) + } +} + +impl dot::GraphWalk<'a> for Formatter<'a, 'tcx, A> +where + A: Analysis<'tcx>, +{ + type Node = BasicBlock; + type Edge = CfgEdge; + + fn nodes(&self) -> dot::Nodes<'_, Self::Node> { + self.body + .basic_blocks() + .indices() + .collect::>() + .into() + } + + fn edges(&self) -> dot::Edges<'_, Self::Edge> { + self.body + .basic_blocks() + .indices() + .flat_map(|bb| outgoing_edges(self.body, bb)) + .collect::>() + .into() + } + + fn source(&self, edge: &Self::Edge) -> Self::Node { + edge.source + } + + fn target(&self, edge: &Self::Edge) -> Self::Node { + self.body + [edge.source] + .terminator() + .successors() + .nth(edge.index) + .copied() + .unwrap() + } +} + +struct BlockFormatter<'a, 'tcx, A> +where + A: Analysis<'tcx>, +{ + prev_state: BitSet, + results: ResultsRefCursor<'a, 'a, 'tcx, A>, + bg: Background, +} + +impl BlockFormatter<'a, 'tcx, A> +where + A: Analysis<'tcx>, +{ + fn toggle_background(&mut self) -> Background { + let bg = self.bg; + self.bg = !bg; + bg + } + + fn write_node_label( + &mut self, + w: &mut impl io::Write, + body: &'a Body<'tcx>, + block: BasicBlock, + ) -> io::Result<()> { + // Sample output: + // +-+--------------------------------------------------+ + // A | bb4 | + // +-+----------------------------------+---------------+ + // B | MIR | STATE | + // +-+----------------------------------+---------------+ + // C | | (on entry) | {_0,_2,_3} | + // +-+----------------------------------+---------------+ + // D |0| 0: StorageLive(_7) | | + // +-+----------------------------------+---------------+ + // |1| 1: StorageLive(_8) | | + // +-+----------------------------------+---------------+ + // |2| 2: _8 = &mut _1 | +_8 | + // +-+----------------------------------+---------------+ + // E |T| _7 = const Foo::twiddle(move _8) | -_8 | + // +-+----------------------------------+---------------+ + // F | | (on unwind) | {_0,_2,_3,_7} | + // +-+----------------------------------+---------------+ + // | | (on successful return) | +_7 | + // +-+----------------------------------+---------------+ + + write!( + w, + r#""#, + )?; + + // A: Block info + write!( + w, + r#" + + "#, + num_headers = 3, + block_id = block.index(), + )?; + + // B: Column headings + write!( + w, + r#" + + + "#, + fmt = r##"bgcolor="#a0a0a0" sides="tl""##, + )?; + + // C: Entry state + self.results.seek_to_block_start(block); + self.write_row_with_curr_state(w, "", "(on entry)")?; + self.prev_state.overwrite(self.results.get()); + + // D: Statement transfer functions + for (i, statement) in body[block].statements.iter().enumerate() { + let location = Location { block, statement_index: i }; + + let mir_col = format!("{:?}", statement); + let i_col = i.to_string(); + + self.results.seek_after(location); + self.write_row_with_curr_diff(w, &i_col, &mir_col)?; + self.prev_state.overwrite(self.results.get()); + } + + // E: Terminator transfer function + let terminator = body[block].terminator(); + let location = body.terminator_loc(block); + + let mut mir_col = String::new(); + terminator.kind.fmt_head(&mut mir_col).unwrap(); + + self.results.seek_after(location); + self.write_row_with_curr_diff(w, "T", &mir_col)?; + self.prev_state.overwrite(self.results.get()); + + // F: Exit state + if let mir::TerminatorKind::Call { destination: Some(_), .. } = &terminator.kind { + self.write_row_with_curr_state(w, "", "(on unwind)")?; + + self.results.seek_after_assume_call_returns(location); + self.write_row_with_curr_diff(w, "", "(on successful return)")?; + } else { + self.write_row_with_curr_state(w, "", "(on exit)")?; + } + + write!(w, "
bb{block_id}
MIRSTATE
") + } + + fn write_row_with_curr_state( + &mut self, + w: &mut impl io::Write, + i: &str, + mir: &str, + ) -> io::Result<()> { + let bg = self.toggle_background(); + + let mut out = Vec::new(); + write!(&mut out, "{{")?; + pretty_print_state_elems(&mut out, self.results.analysis(), self.results.get().iter())?; + write!(&mut out, "}}")?; + + write!( + w, + r#" + {i} + {mir} + {state} + "#, + fmt = &["sides=\"tl\"", bg.attr()].join(" "), + i = i, + mir = dot::escape_html(mir), + state = dot::escape_html(str::from_utf8(&out).unwrap()), + ) + } + + fn write_row_with_curr_diff( + &mut self, + w: &mut impl io::Write, + i: &str, + mir: &str, + ) -> io::Result<()> { + let bg = self.toggle_background(); + let analysis = self.results.analysis(); + + let diff = BitSetDiff::compute(&self.prev_state, self.results.get()); + + let mut set = Vec::new(); + pretty_print_state_elems(&mut set, analysis, diff.set.iter())?; + + let mut clear = Vec::new(); + pretty_print_state_elems(&mut clear, analysis, diff.clear.iter())?; + + write!( + w, + r#" + {i} + {mir} + "#, + i = i, + fmt = &["sides=\"tl\"", bg.attr()].join(" "), + mir = dot::escape_html(mir), + )?; + + if !set.is_empty() { + write!( + w, + r#"+{}"#, + dot::escape_html(str::from_utf8(&set).unwrap()), + )?; + } + + if !set.is_empty() && !clear.is_empty() { + write!(w, " ")?; + } + + if !clear.is_empty() { + write!( + w, + r#"-{}"#, + dot::escape_html(str::from_utf8(&clear).unwrap()), + )?; + } + + write!(w, "") + } +} + +/// The operations required to transform one `BitSet` into another. +struct BitSetDiff { + set: HybridBitSet, + clear: HybridBitSet, +} + +impl BitSetDiff { + fn compute(from: &BitSet, to: &BitSet) -> Self { + assert_eq!(from.domain_size(), to.domain_size()); + let len = from.domain_size(); + + let mut set = HybridBitSet::new_empty(len); + let mut clear = HybridBitSet::new_empty(len); + + // FIXME: This could be made faster if `BitSet::xor` were implemented. + for i in (0..len).map(|i| T::new(i)) { + match (from.contains(i), to.contains(i)) { + (false, true) => set.insert(i), + (true, false) => clear.insert(i), + _ => continue, + }; + } + + BitSetDiff { + set, + clear, + } + } +} + +/// Formats each `elem` using the pretty printer provided by `analysis` into a comma-separated +/// list. +fn pretty_print_state_elems
( + w: &mut impl io::Write, + analysis: &A, + elems: impl Iterator, +) -> io::Result<()> +where + A: Analysis<'tcx>, +{ + let mut first = true; + for idx in elems { + if first { + first = false; + } else { + write!(w, ",")?; + } + + analysis.pretty_print_idx(w, idx)?; + } + + Ok(()) +} + +/// The background color used for zebra-striping the table. +#[derive(Clone, Copy)] +enum Background { + Light, + Dark, +} + +impl Background { + fn attr(self) -> &'static str { + match self { + Self::Dark => "bgcolor=\"#f0f0f0\"", + Self::Light => "", + } + } +} + +impl ops::Not for Background { + type Output = Self; + + fn not(self) -> Self { + match self { + Self::Light => Self::Dark, + Self::Dark => Self::Light, + } + } +}