use std::cmp::Ordering;
use rustc_index::bit_set::BitSet;
+use rustc_index::vec::Idx;
use rustc_middle::mir::{self, BasicBlock, Location};
use super::{Analysis, Direction, Effect, EffectIndex, Results};
{
body: &'mir mir::Body<'tcx>,
results: R,
- state: BitSet<A::Idx>,
+ state: A::Domain,
pos: CursorPosition,
{
/// Returns a new cursor that can inspect `results`.
pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self {
- let bits_per_block = results.borrow().entry_set_for_block(mir::START_BLOCK).domain_size();
-
+ let bottom_value = results.borrow().analysis.bottom_value(body);
ResultsCursor {
body,
results,
- // Initialize to an empty `BitSet` and set `state_needs_reset` to tell the cursor that
+ // Initialize to the `bottom_value` and set `state_needs_reset` to tell the cursor that
// it needs to reset to block entry before the first seek. The cursor position is
// immaterial.
state_needs_reset: true,
- state: BitSet::new_empty(bits_per_block),
+ state: bottom_value,
pos: CursorPosition::block_entry(mir::START_BLOCK),
#[cfg(debug_assertions)]
}
/// Returns the dataflow state at the current location.
- pub fn get(&self) -> &BitSet<A::Idx> {
+ pub fn get(&self) -> &A::Domain {
&self.state
}
- /// Returns `true` if the dataflow state at the current location contains the given element.
- ///
- /// Shorthand for `self.get().contains(elem)`
- pub fn contains(&self, elem: A::Idx) -> bool {
- self.state.contains(elem)
- }
-
/// Resets the cursor to hold the entry set for the given basic block.
///
/// For forward dataflow analyses, this is the dataflow state prior to the first statement.
#[cfg(debug_assertions)]
assert!(self.reachable_blocks.contains(block));
- self.state.overwrite(&self.results.borrow().entry_set_for_block(block));
+ self.state.clone_from(&self.results.borrow().entry_set_for_block(block));
self.pos = CursorPosition::block_entry(block);
self.state_needs_reset = false;
}
///
/// This can be used, e.g., to apply the call return effect directly to the cursor without
/// creating an extra copy of the dataflow state.
- pub fn apply_custom_effect(&mut self, f: impl FnOnce(&A, &mut BitSet<A::Idx>)) {
+ pub fn apply_custom_effect(&mut self, f: impl FnOnce(&A, &mut A::Domain)) {
f(&self.results.borrow().analysis, &mut self.state);
self.state_needs_reset = true;
}
}
+impl<'mir, 'tcx, A, R, T> ResultsCursor<'mir, 'tcx, A, R>
+where
+ A: Analysis<'tcx, Domain = BitSet<T>>,
+ T: Idx,
+ R: Borrow<Results<'tcx, A>>,
+{
+ pub fn contains(&self, elem: T) -> bool {
+ self.get().contains(elem)
+ }
+}
+
#[derive(Clone, Copy, Debug)]
struct CursorPosition {
block: BasicBlock,
/// `effects.start()` must precede or equal `effects.end()` in this direction.
fn apply_effects_in_range<A>(
analysis: &A,
- state: &mut BitSet<A::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>,
fn apply_effects_in_block<A>(
analysis: &A,
- state: &mut BitSet<A::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>,
- exit_state: &mut BitSet<A::Idx>,
+ exit_state: &mut A::Domain,
block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
- propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
+ propagate: impl FnMut(BasicBlock, &A::Domain),
) where
A: Analysis<'tcx>;
}
fn apply_effects_in_block<A>(
analysis: &A,
- state: &mut BitSet<A::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
fn apply_effects_in_range<A>(
analysis: &A,
- state: &mut BitSet<A::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>,
_tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>,
- exit_state: &mut BitSet<A::Idx>,
+ exit_state: &mut A::Domain,
(bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
- mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
+ mut propagate: impl FnMut(BasicBlock, &A::Domain),
) where
A: Analysis<'tcx>,
{
fn apply_effects_in_block<A>(
analysis: &A,
- state: &mut BitSet<A::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
fn apply_effects_in_range<A>(
analysis: &A,
- state: &mut BitSet<A::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>,
tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>,
- exit_state: &mut BitSet<A::Idx>,
+ exit_state: &mut A::Domain,
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
- mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
+ mut propagate: impl FnMut(BasicBlock, &A::Domain),
) where
A: Analysis<'tcx>,
{
// MIR building adds discriminants to the `values` array in the same order as they
// are yielded by `AdtDef::discriminants`. We rely on this to match each
// discriminant in `values` to its corresponding variant in linear time.
- let mut tmp = BitSet::new_empty(exit_state.domain_size());
+ let mut tmp = analysis.bottom_value(body);
let mut discriminants = enum_def.discriminants(tcx);
for (value, target) in values.iter().zip(targets.iter().copied()) {
let (variant_idx, _) =
from that of `SwitchInt::values`",
);
- tmp.overwrite(exit_state);
+ tmp.clone_from(exit_state);
analysis.apply_discriminant_switch_effect(
&mut tmp,
bb,
//! A solver for dataflow problems.
+use std::borrow::BorrowMut;
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use rustc_graphviz as dot;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::BitSet;
-use rustc_index::vec::IndexVec;
+use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::{self, traversal, BasicBlock};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::symbol::{sym, Symbol};
+use super::fmt::DebugWithContext;
use super::graphviz;
use super::{
- visit_results, Analysis, Direction, GenKillAnalysis, GenKillSet, ResultsCursor, ResultsVisitor,
+ visit_results, Analysis, Direction, GenKill, GenKillAnalysis, GenKillSet, JoinSemiLattice,
+ ResultsCursor, ResultsVisitor,
};
use crate::util::pretty::dump_enabled;
A: Analysis<'tcx>,
{
pub analysis: A,
- pub(super) entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
+ pub(super) entry_sets: IndexVec<BasicBlock, A::Domain>,
}
impl<A> Results<'tcx, A>
}
/// Gets the dataflow state for the given block.
- pub fn entry_set_for_block(&self, block: BasicBlock) -> &BitSet<A::Idx> {
+ pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain {
&self.entry_sets[block]
}
&self,
body: &'mir mir::Body<'tcx>,
blocks: impl IntoIterator<Item = BasicBlock>,
- vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
+ vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
) {
visit_results(body, blocks, self, vis)
}
pub fn visit_reachable_with(
&self,
body: &'mir mir::Body<'tcx>,
- vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
+ vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
) {
let blocks = mir::traversal::reachable(body);
visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
pub fn visit_in_rpo_with(
&self,
body: &'mir mir::Body<'tcx>,
- vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
+ vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
) {
let blocks = mir::traversal::reverse_postorder(body);
visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
where
A: Analysis<'tcx>,
{
- bits_per_block: usize,
tcx: TyCtxt<'tcx>,
body: &'a mir::Body<'tcx>,
def_id: DefId,
dead_unwinds: Option<&'a BitSet<BasicBlock>>,
- entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
+ entry_sets: IndexVec<BasicBlock, A::Domain>,
analysis: A,
/// Cached, cumulative transfer functions for each block.
- trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
+ apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
}
-impl<A> Engine<'a, 'tcx, A>
+impl<A, D, T> Engine<'a, 'tcx, A>
where
- A: GenKillAnalysis<'tcx>,
+ A: GenKillAnalysis<'tcx, Idx = T, Domain = D>,
+ D: Clone + JoinSemiLattice + GenKill<T> + BorrowMut<BitSet<T>>,
+ T: Idx,
{
/// Creates a new `Engine` to solve a gen-kill dataflow problem.
pub fn new_gen_kill(
// Otherwise, compute and store the cumulative transfer function for each block.
- let bits_per_block = analysis.bits_per_block(body);
- let mut trans_for_block =
- IndexVec::from_elem(GenKillSet::identity(bits_per_block), body.basic_blocks());
+ let identity = GenKillSet::identity(analysis.bottom_value(body).borrow().domain_size());
+ let mut trans_for_block = IndexVec::from_elem(identity, body.basic_blocks());
for (block, block_data) in body.basic_blocks().iter_enumerated() {
let trans = &mut trans_for_block[block];
A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data);
}
- Self::new(tcx, body, def_id, analysis, Some(trans_for_block))
+ let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
+ trans_for_block[bb].apply(state.borrow_mut());
+ });
+
+ Self::new(tcx, body, def_id, analysis, Some(apply_trans as Box<_>))
}
}
-impl<A> Engine<'a, 'tcx, A>
+impl<A, D> Engine<'a, 'tcx, A>
where
- A: Analysis<'tcx>,
+ A: Analysis<'tcx, Domain = D>,
+ D: Clone + JoinSemiLattice,
{
/// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
/// function.
body: &'a mir::Body<'tcx>,
def_id: DefId,
analysis: A,
- trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
+ apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
) -> Self {
- let bits_per_block = analysis.bits_per_block(body);
-
- let bottom_value_set = if A::BOTTOM_VALUE {
- BitSet::new_filled(bits_per_block)
- } else {
- BitSet::new_empty(bits_per_block)
- };
-
- let mut entry_sets = IndexVec::from_elem(bottom_value_set.clone(), body.basic_blocks());
+ let bottom_value = analysis.bottom_value(body);
+ let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), body.basic_blocks());
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
- if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value_set {
+ if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value {
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
}
Engine {
analysis,
- bits_per_block,
tcx,
body,
def_id,
dead_unwinds: None,
entry_sets,
- trans_for_block,
+ apply_trans_for_block,
}
}
}
/// Computes the fixpoint for this dataflow problem and returns it.
- pub fn iterate_to_fixpoint(self) -> Results<'tcx, A> {
+ pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
+ where
+ A::Domain: DebugWithContext<A>,
+ {
let Engine {
analysis,
- bits_per_block,
body,
dead_unwinds,
def_id,
mut entry_sets,
tcx,
- trans_for_block,
+ apply_trans_for_block,
..
} = self;
}
}
- let mut state = BitSet::new_empty(bits_per_block);
+ let mut state = analysis.bottom_value(body);
while let Some(bb) = dirty_queue.pop() {
let bb_data = &body[bb];
// Apply the block transfer function, using the cached one if it exists.
- state.overwrite(&entry_sets[bb]);
- match &trans_for_block {
- Some(trans_for_block) => trans_for_block[bb].apply(&mut state),
+ state.clone_from(&entry_sets[bb]);
+ match &apply_trans_for_block {
+ Some(apply) => apply(bb, &mut state),
None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data),
}
dead_unwinds,
&mut state,
(bb, bb_data),
- |target: BasicBlock, state: &BitSet<A::Idx>| {
- let set_changed = analysis.join(&mut entry_sets[target], state);
+ |target: BasicBlock, state: &A::Domain| {
+ let set_changed = entry_sets[target].join(state);
if set_changed {
dirty_queue.insert(target);
}
let results = Results { analysis, entry_sets };
- let res = write_graphviz_results(tcx, def_id, &body, &results, trans_for_block);
+ let res = write_graphviz_results(tcx, def_id, &body, &results);
if let Err(e) = res {
warn!("Failed to write graphviz dataflow results: {}", e);
}
def_id: DefId,
body: &mir::Body<'tcx>,
results: &Results<'tcx, A>,
- block_transfer_functions: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
) -> std::io::Result<()>
where
A: Analysis<'tcx>,
+ A::Domain: DebugWithContext<A>,
{
let attrs = match RustcMirAttrs::parse(tcx, def_id) {
Ok(attrs) => attrs,
None => return Ok(()),
};
- let bits_per_block = results.analysis.bits_per_block(body);
-
- let mut formatter: Box<dyn graphviz::StateFormatter<'tcx, _>> = match attrs.formatter {
- Some(sym::two_phase) => Box::new(graphviz::TwoPhaseDiff::new(bits_per_block)),
- Some(sym::gen_kill) => {
- if let Some(trans_for_block) = block_transfer_functions {
- Box::new(graphviz::BlockTransferFunc::new(body, trans_for_block))
- } else {
- Box::new(graphviz::SimpleDiff::new(body, &results))
- }
- }
-
- // Default to the `SimpleDiff` output style.
- _ => Box::new(graphviz::SimpleDiff::new(body, &results)),
+ let style = match attrs.formatter {
+ Some(sym::two_phase) => graphviz::OutputStyle::BeforeAndAfter,
+ _ => graphviz::OutputStyle::AfterOnly,
};
debug!("printing dataflow results for {:?} to {}", def_id, path.display());
let mut buf = Vec::new();
- let graphviz = graphviz::Formatter::new(body, def_id, results, &mut *formatter);
+ let graphviz = graphviz::Formatter::new(body, def_id, results, style);
dot::render_opts(&graphviz, &mut buf, &[dot::RenderOption::Monospace])?;
if let Some(parent) = path.parent() {
--- /dev/null
+//! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow
+//! analysis.
+
+use rustc_index::bit_set::{BitSet, HybridBitSet};
+use rustc_index::vec::Idx;
+use std::fmt;
+
+/// An extension to `fmt::Debug` for data that can be better printed with some auxiliary data `C`.
+pub trait DebugWithContext<C>: Eq + fmt::Debug {
+ fn fmt_with(&self, _ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(self, f)
+ }
+
+ /// Print the difference between `self` and `old`.
+ ///
+ /// This should print nothing if `self == old`.
+ ///
+ /// `+` and `-` are typically used to indicate differences. However, these characters are
+ /// fairly common and may be needed to print a types representation. If using them to indicate
+ /// a diff, prefix them with the "Unit Separator" control character (␟ U+001F).
+ fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self == old {
+ return Ok(());
+ }
+
+ write!(f, "\u{001f}+")?;
+ self.fmt_with(ctxt, f)?;
+
+ if f.alternate() {
+ write!(f, "\n")?;
+ } else {
+ write!(f, "\t")?;
+ }
+
+ write!(f, "\u{001f}-")?;
+ self.fmt_with(ctxt, f)
+ }
+}
+
+/// Implements `fmt::Debug` by deferring to `<T as DebugWithContext<C>>::fmt_with`.
+pub struct DebugWithAdapter<'a, T, C> {
+ pub this: T,
+ pub ctxt: &'a C,
+}
+
+impl<T, C> fmt::Debug for DebugWithAdapter<'_, T, C>
+where
+ T: DebugWithContext<C>,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.this.fmt_with(self.ctxt, f)
+ }
+}
+
+/// Implements `fmt::Debug` by deferring to `<T as DebugWithContext<C>>::fmt_diff_with`.
+pub struct DebugDiffWithAdapter<'a, T, C> {
+ pub new: T,
+ pub old: T,
+ pub ctxt: &'a C,
+}
+
+impl<T, C> fmt::Debug for DebugDiffWithAdapter<'_, T, C>
+where
+ T: DebugWithContext<C>,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.new.fmt_diff_with(&self.old, self.ctxt, f)
+ }
+}
+
+// Impls
+
+impl<T, C> DebugWithContext<C> for BitSet<T>
+where
+ T: Idx + DebugWithContext<C>,
+{
+ fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_set().entries(self.iter().map(|i| DebugWithAdapter { this: i, ctxt })).finish()
+ }
+
+ fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let size = self.domain_size();
+ assert_eq!(size, old.domain_size());
+
+ let mut set_in_self = HybridBitSet::new_empty(size);
+ let mut cleared_in_self = HybridBitSet::new_empty(size);
+
+ for i in (0..size).map(T::new) {
+ match (self.contains(i), old.contains(i)) {
+ (true, false) => set_in_self.insert(i),
+ (false, true) => cleared_in_self.insert(i),
+ _ => continue,
+ };
+ }
+
+ let mut first = true;
+ for idx in set_in_self.iter() {
+ let delim = if first {
+ "\u{001f}+"
+ } else if f.alternate() {
+ "\n\u{001f}+"
+ } else {
+ ", "
+ };
+
+ write!(f, "{}", delim)?;
+ idx.fmt_with(ctxt, f)?;
+ first = false;
+ }
+
+ if !f.alternate() {
+ first = true;
+ if !set_in_self.is_empty() && !cleared_in_self.is_empty() {
+ write!(f, "\t")?;
+ }
+ }
+
+ for idx in cleared_in_self.iter() {
+ let delim = if first {
+ "\u{001f}-"
+ } else if f.alternate() {
+ "\n\u{001f}-"
+ } else {
+ ", "
+ };
+
+ write!(f, "{}", delim)?;
+ idx.fmt_with(ctxt, f)?;
+ first = false;
+ }
+
+ Ok(())
+ }
+}
+
+impl<T, C> DebugWithContext<C> for &'_ T
+where
+ T: DebugWithContext<C>,
+{
+ fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ (*self).fmt_with(ctxt, f)
+ }
+
+ fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ (*self).fmt_diff_with(*old, ctxt, f)
+ }
+}
+
+impl<C> DebugWithContext<C> for rustc_middle::mir::Local {}
+impl<C> DebugWithContext<C> for crate::dataflow::move_paths::InitIndex {}
+
+impl<'tcx, C> DebugWithContext<C> for crate::dataflow::move_paths::MovePathIndex
+where
+ C: crate::dataflow::move_paths::HasMoveData<'tcx>,
+{
+ fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", ctxt.move_data().move_paths[*self])
+ }
+}
+
+impl<T, C> DebugWithContext<C> for crate::dataflow::lattice::Dual<T>
+where
+ T: DebugWithContext<C>,
+{
+ fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ (self.0).fmt_with(ctxt, f)
+ }
+
+ fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ (self.0).fmt_diff_with(&old.0, ctxt, f)
+ }
+}
//! A helpful diagram for debugging dataflow problems.
-use std::cell::RefCell;
+use std::borrow::Cow;
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, HybridBitSet};
-use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::{self, BasicBlock, Body, Location};
-use super::{Analysis, Direction, GenKillSet, Results, ResultsRefCursor};
+use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
+use super::{Analysis, Direction, Results, ResultsRefCursor, ResultsVisitor};
use crate::util::graphviz_safe_def_name;
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum OutputStyle {
+ AfterOnly,
+ BeforeAndAfter,
+}
+
+impl OutputStyle {
+ fn num_state_columns(&self) -> usize {
+ match self {
+ Self::AfterOnly => 1,
+ Self::BeforeAndAfter => 2,
+ }
+ }
+}
+
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<BlockFormatter<'a, 'tcx, A>>,
+ results: &'a Results<'tcx, A>,
+ style: OutputStyle,
}
impl<A> Formatter<'a, 'tcx, A>
body: &'a Body<'tcx>,
def_id: DefId,
results: &'a Results<'tcx, A>,
- state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
+ style: OutputStyle,
) -> Self {
- let block_formatter = BlockFormatter {
- bg: Background::Light,
- results: ResultsRefCursor::new(body, results),
- state_formatter,
- };
-
- Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) }
+ Formatter { body, def_id, results, style }
}
}
impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
+ A::Domain: DebugWithContext<A>,
{
type Node = BasicBlock;
type Edge = CfgEdge;
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();
+ let mut fmt = BlockFormatter {
+ results: ResultsRefCursor::new(self.body, self.results),
+ style: self.style,
+ bg: Background::Light,
+ };
+
+ fmt.write_node_label(&mut label, self.body, *block).unwrap();
dot::LabelText::html(String::from_utf8(label).unwrap())
}
{
results: ResultsRefCursor<'a, 'a, 'tcx, A>,
bg: Background,
- state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
+ style: OutputStyle,
}
impl<A> BlockFormatter<'a, 'tcx, A>
where
A: Analysis<'tcx>,
+ A::Domain: DebugWithContext<A>,
{
const HEADER_COLOR: &'static str = "#a0a0a0";
- fn num_state_columns(&self) -> usize {
- std::cmp::max(1, self.state_formatter.column_names().len())
- }
-
fn toggle_background(&mut self) -> Background {
let bg = self.bg;
self.bg = !bg;
write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
// A + B: Block header
- if self.state_formatter.column_names().is_empty() {
- self.write_block_header_simple(w, block)?;
- } else {
- self.write_block_header_with_state_columns(w, block)?;
+ match self.style {
+ OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
+ OutputStyle::BeforeAndAfter => {
+ self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
+ }
}
// C: State at start of block
self.bg = Background::Light;
self.results.seek_to_block_start(block);
- let block_entry_state = self.results.get().clone();
-
+ let block_start_state = self.results.get().clone();
self.write_row_with_full_state(w, "", "(on start)")?;
- // D: Statement transfer functions
- for (i, statement) in body[block].statements.iter().enumerate() {
- let location = Location { block, statement_index: i };
- let statement_str = format!("{:?}", statement);
- self.write_row_for_location(w, &i.to_string(), &statement_str, location)?;
- }
-
- // E: Terminator transfer function
- let terminator = body[block].terminator();
- let terminator_loc = body.terminator_loc(block);
- let mut terminator_str = String::new();
- terminator.kind.fmt_head(&mut terminator_str).unwrap();
-
- self.write_row_for_location(w, "T", &terminator_str, terminator_loc)?;
+ // D + E: Statement and terminator transfer functions
+ self.write_statements_and_terminator(w, body, block)?;
// F: State at end of block
+ let terminator = body[block].terminator();
+
// Write the full dataflow state immediately after the terminator if it differs from the
// state at block entry.
self.results.seek_to_block_end(block);
- if self.results.get() != &block_entry_state || A::Direction::is_backward() {
+ if self.results.get() != &block_start_state || A::Direction::is_backward() {
let after_terminator_name = match terminator.kind {
mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
_ => "(on end)",
self.write_row_with_full_state(w, "", after_terminator_name)?;
}
- // Write any changes caused by terminator-specific effects
- let num_state_columns = self.num_state_columns();
+ // Write any changes caused by terminator-specific effects.
+ //
+ // FIXME: These should really be printed as part of each outgoing edge rather than the node
+ // for the basic block itself. That way, we could display terminator-specific effects for
+ // backward dataflow analyses as well as effects for `SwitchInt` terminators.
match terminator.kind {
mir::TerminatorKind::Call {
destination: Some((return_place, _)),
..
} => {
self.write_row(w, "", "(on successful return)", |this, w, fmt| {
- write!(
- w,
- r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
- colspan = num_state_columns,
- fmt = fmt,
- )?;
-
let state_on_unwind = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| {
analysis.apply_call_return_effect(state, block, func, args, return_place);
});
- write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
- write!(w, "</td>")
+ write!(
+ w,
+ r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
+ colspan = this.style.num_state_columns(),
+ fmt = fmt,
+ diff = diff_pretty(
+ this.results.get(),
+ &state_on_unwind,
+ this.results.analysis()
+ ),
+ )
})?;
}
mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
- write!(
- w,
- r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
- colspan = num_state_columns,
- fmt = fmt,
- )?;
-
let state_on_generator_drop = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| {
analysis.apply_yield_resume_effect(state, resume, resume_arg);
});
- write_diff(
+ write!(
w,
- this.results.analysis(),
- &state_on_generator_drop,
- this.results.get(),
- )?;
- write!(w, "</td>")
+ r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
+ colspan = this.style.num_state_columns(),
+ fmt = fmt,
+ diff = diff_pretty(
+ this.results.get(),
+ &state_on_generator_drop,
+ this.results.analysis()
+ ),
+ )
})?;
}
&mut self,
w: &mut impl io::Write,
block: BasicBlock,
+ state_column_names: &[&str],
) -> io::Result<()> {
// +------------------------------------+-------------+
// A | bb4 | STATE |
// +-+----------------------------------+------+------+
// | | ... | | |
- let state_column_names = self.state_formatter.column_names();
-
// A
write!(
w,
write!(w, "</tr>")
}
+ fn write_statements_and_terminator(
+ &mut self,
+ w: &mut impl io::Write,
+ body: &'a Body<'tcx>,
+ block: BasicBlock,
+ ) -> io::Result<()> {
+ let diffs = StateDiffCollector::run(body, block, self.results.results(), self.style);
+
+ let mut befores = diffs.before.map(|v| v.into_iter());
+ let mut afters = diffs.after.into_iter();
+
+ let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
+ if A::Direction::is_forward() { it.next().unwrap() } else { it.next_back().unwrap() }
+ };
+
+ for (i, statement) in body[block].statements.iter().enumerate() {
+ let statement_str = format!("{:?}", statement);
+ let index_str = format!("{}", i);
+
+ let after = next_in_dataflow_order(&mut afters);
+ let before = befores.as_mut().map(next_in_dataflow_order);
+
+ self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
+ if let Some(before) = before {
+ write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
+ }
+
+ write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
+ })?;
+ }
+
+ let after = next_in_dataflow_order(&mut afters);
+ let before = befores.as_mut().map(next_in_dataflow_order);
+
+ assert!(afters.is_empty());
+ assert!(befores.as_ref().map_or(true, ExactSizeIterator::is_empty));
+
+ let terminator = body[block].terminator();
+ let mut terminator_str = String::new();
+ terminator.kind.fmt_head(&mut terminator_str).unwrap();
+
+ self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
+ if let Some(before) = before {
+ write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
+ }
+
+ write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
+ })
+ }
+
/// Write a row with the given index and MIR, using the function argument to fill in the
/// "STATE" column(s).
fn write_row<W: io::Write>(
let state = this.results.get();
let analysis = this.results.analysis();
+ // FIXME: The full state vector can be quite long. It would be nice to split on commas
+ // and use some text wrapping algorithm.
write!(
w,
- r#"<td colspan="{colspan}" {fmt} align="left">{{"#,
- colspan = this.num_state_columns(),
+ r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
+ colspan = this.style.num_state_columns(),
fmt = fmt,
- )?;
- pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?;
- write!(w, "}}</td>")
- })
- }
-
- fn write_row_for_location(
- &mut self,
- w: &mut impl io::Write,
- i: &str,
- mir: &str,
- location: Location,
- ) -> io::Result<()> {
- self.write_row(w, i, mir, |this, w, fmt| {
- this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location)
+ state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
+ )
})
}
}
-/// Controls what gets printed under the `STATE` header.
-pub trait StateFormatter<'tcx, A>
+struct StateDiffCollector<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
- /// The columns that will get printed under `STATE`.
- fn column_names(&self) -> &[&str];
-
- fn write_state_for_location(
- &mut self,
- w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()>;
+ analysis: &'a A,
+ prev_state: A::Domain,
+ before: Option<Vec<String>>,
+ after: Vec<String>,
}
-/// Prints a single column containing the state vector immediately *after* each statement.
-pub struct SimpleDiff<'a, 'tcx, A>
+impl<A> StateDiffCollector<'a, 'tcx, A>
where
A: Analysis<'tcx>,
+ A::Domain: DebugWithContext<A>,
{
- prev_state: ResultsRefCursor<'a, 'a, 'tcx, A>,
-}
+ fn run(
+ body: &'a mir::Body<'tcx>,
+ block: BasicBlock,
+ results: &'a Results<'tcx, A>,
+ style: OutputStyle,
+ ) -> Self {
+ let mut collector = StateDiffCollector {
+ analysis: &results.analysis,
+ prev_state: results.analysis.bottom_value(body),
+ after: vec![],
+ before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
+ };
-impl<A> SimpleDiff<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>) -> Self {
- SimpleDiff { prev_state: ResultsRefCursor::new(body, results) }
+ results.visit_with(body, std::iter::once(block), &mut collector);
+ collector
}
}
-impl<A> StateFormatter<'tcx, A> for SimpleDiff<'_, 'tcx, A>
+impl<A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
where
A: Analysis<'tcx>,
+ A::Domain: DebugWithContext<A>,
{
- fn column_names(&self) -> &[&str] {
- &[]
- }
+ type FlowState = A::Domain;
- fn write_state_for_location(
+ fn visit_block_start(
&mut self,
- mut w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()> {
+ state: &Self::FlowState,
+ _block_data: &'mir mir::BasicBlockData<'tcx>,
+ _block: BasicBlock,
+ ) {
if A::Direction::is_forward() {
- if location.statement_index == 0 {
- self.prev_state.seek_to_block_start(location.block);
- } else {
- self.prev_state.seek_after_primary_effect(Location {
- statement_index: location.statement_index - 1,
- ..location
- });
- }
- } else {
- if location == results.body().terminator_loc(location.block) {
- self.prev_state.seek_to_block_end(location.block);
- } else {
- self.prev_state.seek_after_primary_effect(location.successor_within_block());
- }
+ self.prev_state.clone_from(state);
}
-
- write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
- results.seek_after_primary_effect(location);
- let curr_state = results.get();
- write_diff(&mut w, results.analysis(), self.prev_state.get(), curr_state)?;
- write!(w, "</td>")
}
-}
-/// Prints two state columns, one containing only the "before" effect of each statement and one
-/// containing the full effect.
-pub struct TwoPhaseDiff<T: Idx> {
- prev_state: BitSet<T>,
- prev_loc: Location,
-}
+ fn visit_block_end(
+ &mut self,
+ state: &Self::FlowState,
+ _block_data: &'mir mir::BasicBlockData<'tcx>,
+ _block: BasicBlock,
+ ) {
+ if A::Direction::is_backward() {
+ self.prev_state.clone_from(state);
+ }
+ }
-impl<T: Idx> TwoPhaseDiff<T> {
- pub fn new(bits_per_block: usize) -> Self {
- TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
+ fn visit_statement_before_primary_effect(
+ &mut self,
+ state: &Self::FlowState,
+ _statement: &'mir mir::Statement<'tcx>,
+ _location: Location,
+ ) {
+ if let Some(before) = self.before.as_mut() {
+ before.push(diff_pretty(state, &self.prev_state, self.analysis));
+ self.prev_state.clone_from(state)
+ }
}
-}
-impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
-where
- A: Analysis<'tcx>,
-{
- fn column_names(&self) -> &[&str] {
- &["BEFORE", " AFTER"]
+ fn visit_statement_after_primary_effect(
+ &mut self,
+ state: &Self::FlowState,
+ _statement: &'mir mir::Statement<'tcx>,
+ _location: Location,
+ ) {
+ self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
+ self.prev_state.clone_from(state)
}
- fn write_state_for_location(
+ fn visit_terminator_before_primary_effect(
&mut self,
- mut w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()> {
- if location.statement_index == 0 {
- results.seek_to_block_entry(location.block);
- self.prev_state.overwrite(results.get());
- } else {
- // Ensure that we are visiting statements in order, so `prev_state` is correct.
- assert_eq!(self.prev_loc.successor_within_block(), location);
+ state: &Self::FlowState,
+ _terminator: &'mir mir::Terminator<'tcx>,
+ _location: Location,
+ ) {
+ if let Some(before) = self.before.as_mut() {
+ before.push(diff_pretty(state, &self.prev_state, self.analysis));
+ self.prev_state.clone_from(state)
}
-
- self.prev_loc = location;
-
- // Before
-
- write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
- results.seek_before_primary_effect(location);
- let curr_state = results.get();
- write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
- self.prev_state.overwrite(curr_state);
- write!(w, "</td>")?;
-
- // After
-
- write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
- results.seek_after_primary_effect(location);
- let curr_state = results.get();
- write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
- self.prev_state.overwrite(curr_state);
- write!(w, "</td>")
}
-}
-
-/// Prints the gen/kill set for the entire block.
-pub struct BlockTransferFunc<'a, 'tcx, T: Idx> {
- body: &'a mir::Body<'tcx>,
- trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
-}
-impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
- pub fn new(
- body: &'mir mir::Body<'tcx>,
- trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
- ) -> Self {
- BlockTransferFunc { body, trans_for_block }
+ fn visit_terminator_after_primary_effect(
+ &mut self,
+ state: &Self::FlowState,
+ _terminator: &'mir mir::Terminator<'tcx>,
+ _location: Location,
+ ) {
+ self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
+ self.prev_state.clone_from(state)
}
}
-impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
+fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
where
- A: Analysis<'tcx>,
+ T: DebugWithContext<C>,
{
- fn column_names(&self) -> &[&str] {
- &["GEN", "KILL"]
+ if new == old {
+ return String::new();
}
- fn write_state_for_location(
- &mut self,
- mut w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()> {
- // Only print a single row.
- if location.statement_index != 0 {
- return Ok(());
- }
+ let re = Regex::new("\u{001f}([+-])").unwrap();
- let block_trans = &self.trans_for_block[location.block];
- let rowspan = self.body.basic_blocks()[location.block].statements.len();
+ let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
- for set in &[&block_trans.gen, &block_trans.kill] {
- write!(
- w,
- r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
- fmt = fmt,
- rowspan = rowspan
- )?;
+ // Replace newlines in the `Debug` output with `<br/>`
+ let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
- pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
- write!(w, "</td>")?;
+ let mut inside_font_tag = false;
+ let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| {
+ let mut ret = String::new();
+ if inside_font_tag {
+ ret.push_str(r#"</font>"#);
}
- Ok(())
- }
-}
-
-/// Writes two lines, one containing the added bits and one the removed bits.
-fn write_diff<A: Analysis<'tcx>>(
- w: &mut impl io::Write,
- analysis: &A,
- from: &BitSet<A::Idx>,
- to: &BitSet<A::Idx>,
-) -> io::Result<()> {
- 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: Implement a lazy iterator over the symmetric difference of two bitsets.
- for i in (0..len).map(A::Idx::new) {
- match (from.contains(i), to.contains(i)) {
- (false, true) => set.insert(i),
- (true, false) => clear.insert(i),
- _ => continue,
+ let tag = match &captures[1] {
+ "+" => r#"<font color="darkgreen">+"#,
+ "-" => r#"<font color="red">-"#,
+ _ => unreachable!(),
};
- }
-
- if !set.is_empty() {
- write!(w, r#"<font color="darkgreen">+"#)?;
- pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?;
- write!(w, r#"</font>"#)?;
- }
-
- if !set.is_empty() && !clear.is_empty() {
- write!(w, "{}", BR_LEFT)?;
- }
-
- if !clear.is_empty() {
- write!(w, r#"<font color="red">-"#)?;
- pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?;
- write!(w, r#"</font>"#)?;
- }
-
- Ok(())
-}
-const BR_LEFT: &str = r#"<br align="left"/>"#;
-const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#;
+ inside_font_tag = true;
+ ret.push_str(tag);
+ ret
+ });
-/// Line break policy that breaks at 40 characters and starts the next line with a single space.
-const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 });
-
-struct LineBreak {
- sequence: &'static str,
- limit: usize,
-}
-
-/// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given
-/// separator (`sep`).
-///
-/// Optionally, it will break lines using the given character sequence (usually `<br/>`) and
-/// character limit.
-fn pretty_print_state_elems<A>(
- w: &mut impl io::Write,
- analysis: &A,
- elems: impl Iterator<Item = A::Idx>,
- sep: &str,
- line_break: Option<LineBreak>,
-) -> io::Result<bool>
-where
- A: Analysis<'tcx>,
-{
- let sep_width = sep.chars().count();
-
- let mut buf = Vec::new();
-
- let mut first = true;
- let mut curr_line_width = 0;
- let mut line_break_inserted = false;
-
- for idx in elems {
- buf.clear();
- analysis.pretty_print_idx(&mut buf, idx)?;
- let idx_str =
- str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8");
- let escaped = dot::escape_html(idx_str);
- let escaped_width = escaped.chars().count();
-
- if first {
- first = false;
- } else {
- write!(w, "{}", sep)?;
- curr_line_width += sep_width;
-
- if let Some(line_break) = &line_break {
- if curr_line_width + sep_width + escaped_width > line_break.limit {
- write!(w, "{}", line_break.sequence)?;
- line_break_inserted = true;
- curr_line_width = 0;
- }
- }
- }
+ let mut html_diff = match html_diff {
+ Cow::Borrowed(_) => return raw_diff,
+ Cow::Owned(s) => s,
+ };
- write!(w, "{}", escaped)?;
- curr_line_width += escaped_width;
+ if inside_font_tag {
+ html_diff.push_str("</font>");
}
- Ok(line_break_inserted)
+ html_diff
}
/// The background color used for zebra-striping the table.
--- /dev/null
+//! Traits used to represent [lattices] for use as the domain of a dataflow analysis.
+//!
+//! ## Implementation Notes
+//!
+//! Given that they represent partially ordered sets, you may be surprised that [`MeetSemiLattice`]
+//! and [`JoinSemiLattice`] do not have [`PartialOrd`][std::cmp::PartialOrd] as a supertrait. This
+//! is because most standard library types use lexicographic ordering instead of [set inclusion]
+//! for their `PartialOrd` impl. Since we do not actually need to compare lattice elements to run a
+//! dataflow analysis, there's no need for a hypothetical `SetInclusion` newtype with a custom
+//! `PartialOrd` impl. The only benefit would be the ability to check (in debug mode) that the
+//! least upper (or greatest lower) bound returned by the lattice join (or meet) operator was in
+//! fact greater (or lower) than the inputs.
+//!
+//! [lattices]: https://en.wikipedia.org/wiki/Lattice_(order)
+//! [set inclusion]: https://en.wikipedia.org/wiki/Subset
+
+use rustc_index::bit_set::BitSet;
+use rustc_index::vec::{Idx, IndexVec};
+
+/// A [partially ordered set][poset] that has a [least upper bound][lub] for any pair of elements
+/// in the set.
+///
+/// [lub]: https://en.wikipedia.org/wiki/Infimum_and_supremum
+/// [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
+pub trait JoinSemiLattice: Eq {
+ /// Computes the least upper bound of two elements, storing the result in `self` and returning
+ /// `true` if `self` has changed.
+ ///
+ /// The lattice join operator is abbreviated as `∨`.
+ fn join(&mut self, other: &Self) -> bool;
+}
+
+/// A [partially ordered set][poset] that has a [greatest lower bound][glb] for any pair of
+/// elements in the set.
+///
+/// Dataflow analyses only require that their domains implement [`JoinSemiLattice`], not
+/// `MeetSemiLattice`. However, types that will be used as dataflow domains should implement both
+/// so that they can be used with [`Dual`].
+///
+/// [glb]: https://en.wikipedia.org/wiki/Infimum_and_supremum
+/// [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
+pub trait MeetSemiLattice: Eq {
+ /// Computes the greatest lower bound of two elements, storing the result in `self` and
+ /// returning `true` if `self` has changed.
+ ///
+ /// The lattice meet operator is abbreviated as `∧`.
+ fn meet(&mut self, other: &Self) -> bool;
+}
+
+/// A `bool` is a "two-point" lattice with `true` as the top element and `false` as the bottom.
+impl JoinSemiLattice for bool {
+ fn join(&mut self, other: &Self) -> bool {
+ if let (false, true) = (*self, *other) {
+ *self = true;
+ return true;
+ }
+
+ false
+ }
+}
+
+impl MeetSemiLattice for bool {
+ fn meet(&mut self, other: &Self) -> bool {
+ if let (true, false) = (*self, *other) {
+ *self = false;
+ return true;
+ }
+
+ false
+ }
+}
+
+/// A tuple or list of lattices is itself a lattice whose least upper bound is the concatenation of
+/// the least upper bounds of each element of the tuple or list.
+impl<I: Idx, T: JoinSemiLattice> JoinSemiLattice for IndexVec<I, T> {
+ fn join(&mut self, other: &Self) -> bool {
+ assert_eq!(self.len(), other.len());
+
+ let mut changed = false;
+ for (a, b) in self.iter_mut().zip(other.iter()) {
+ changed |= a.join(b);
+ }
+ changed
+ }
+}
+
+impl<I: Idx, T: MeetSemiLattice> MeetSemiLattice for IndexVec<I, T> {
+ fn meet(&mut self, other: &Self) -> bool {
+ assert_eq!(self.len(), other.len());
+
+ let mut changed = false;
+ for (a, b) in self.iter_mut().zip(other.iter()) {
+ changed |= a.meet(b);
+ }
+ changed
+ }
+}
+
+/// A `BitSet` is an efficent way to store a tuple of "two-point" lattices. Equivalently, it is the
+/// lattice corresponding to the powerset of the set of all possibe values of the index type `T`
+/// ordered by inclusion.
+impl<T: Idx> JoinSemiLattice for BitSet<T> {
+ fn join(&mut self, other: &Self) -> bool {
+ self.union(other)
+ }
+}
+
+impl<T: Idx> MeetSemiLattice for BitSet<T> {
+ fn meet(&mut self, other: &Self) -> bool {
+ self.intersect(other)
+ }
+}
+
+/// The counterpart of a given semilattice `T` using the [inverse order].
+///
+/// The dual of a join-semilattice is a meet-semilattice and vice versa. For example, the dual of a
+/// powerset has the empty set as its top element and the full set as its bottom element and uses
+/// set *intersection* as its join operator.
+///
+/// [inverse order]: https://en.wikipedia.org/wiki/Duality_(order_theory)
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Dual<T>(pub T);
+
+impl<T> std::borrow::Borrow<T> for Dual<T> {
+ fn borrow(&self) -> &T {
+ &self.0
+ }
+}
+
+impl<T> std::borrow::BorrowMut<T> for Dual<T> {
+ fn borrow_mut(&mut self) -> &mut T {
+ &mut self.0
+ }
+}
+
+impl<T: MeetSemiLattice> JoinSemiLattice for Dual<T> {
+ fn join(&mut self, other: &Self) -> bool {
+ self.0.meet(&other.0)
+ }
+}
+
+impl<T: JoinSemiLattice> MeetSemiLattice for Dual<T> {
+ fn meet(&mut self, other: &Self) -> bool {
+ self.0.join(&other.0)
+ }
+}
+
+/// Extends a type `T` with top and bottom elements to make it a partially ordered set in which no
+/// value of `T` is comparable with any other. A flat set has the following [Hasse
+/// diagram](https://en.wikipedia.org/wiki/Hasse_diagram):
+///
+/// ```text
+/// top
+/// / / \ \
+/// all possible values of `T`
+/// \ \ / /
+/// bottom
+/// ```
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum FlatSet<T> {
+ Bottom,
+ Elem(T),
+ Top,
+}
+
+impl<T: Clone + Eq> JoinSemiLattice for FlatSet<T> {
+ fn join(&mut self, other: &Self) -> bool {
+ let result = match (&*self, other) {
+ (Self::Top, _) | (_, Self::Bottom) => return false,
+ (Self::Elem(a), Self::Elem(b)) if a == b => return false,
+
+ (Self::Bottom, Self::Elem(x)) => Self::Elem(x.clone()),
+
+ _ => Self::Top,
+ };
+
+ *self = result;
+ true
+ }
+}
+
+impl<T: Clone + Eq> MeetSemiLattice for FlatSet<T> {
+ fn meet(&mut self, other: &Self) -> bool {
+ let result = match (&*self, other) {
+ (Self::Bottom, _) | (_, Self::Top) => return false,
+ (Self::Elem(ref a), Self::Elem(ref b)) if a == b => return false,
+
+ (Self::Top, Self::Elem(ref x)) => Self::Elem(x.clone()),
+
+ _ => Self::Bottom,
+ };
+
+ *self = result;
+ true
+ }
+}
//!
//! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems
+use std::borrow::BorrowMut;
use std::cmp::Ordering;
-use std::io;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::{BitSet, HybridBitSet};
mod cursor;
mod direction;
mod engine;
+pub mod fmt;
mod graphviz;
+pub mod lattice;
mod visitor;
pub use self::cursor::{ResultsCursor, ResultsRefCursor};
pub use self::direction::{Backward, Direction, Forward};
pub use self::engine::{Engine, Results};
+pub use self::lattice::{JoinSemiLattice, MeetSemiLattice};
pub use self::visitor::{visit_results, ResultsVisitor};
pub use self::visitor::{BorrowckFlowState, BorrowckResults};
-/// Parameterization for the precise form of data flow that is used.
-///
-/// `BottomValue` determines whether the initial entry set for each basic block is empty or full.
-/// This also determines the semantics of the lattice `join` operator used to merge dataflow
-/// results, since dataflow works by starting at the bottom and moving monotonically to a fixed
-/// point.
-///
-/// This means, for propagation across the graph, that you either want to start at all-zeroes and
-/// then use Union as your merge when propagating, or you start at all-ones and then use Intersect
-/// as your merge when propagating.
-pub trait BottomValue {
- /// Specifies the initial value for each bit in the entry set for each basic block.
- const BOTTOM_VALUE: bool;
-
- /// Merges `in_set` into `inout_set`, returning `true` if `inout_set` changed.
- ///
- /// It is almost certainly wrong to override this, since it automatically applies
- /// * `inout_set & in_set` if `BOTTOM_VALUE == true`
- /// * `inout_set | in_set` if `BOTTOM_VALUE == false`
- ///
- /// This means that if a bit is not `BOTTOM_VALUE`, it is propagated into all target blocks.
- /// For clarity, the above statement again from a different perspective:
- /// A bit in the block's entry set is `!BOTTOM_VALUE` if *any* predecessor block's bit value is
- /// `!BOTTOM_VALUE`.
- ///
- /// There are situations where you want the opposite behaviour: propagate only if *all*
- /// predecessor blocks's value is `!BOTTOM_VALUE`.
- /// E.g. if you want to know whether a bit is *definitely* set at a specific location. This
- /// means that all code paths leading to the location must have set the bit, instead of any
- /// code path leading there.
- ///
- /// If you want this kind of "definitely set" analysis, you need to
- /// 1. Invert `BOTTOM_VALUE`
- /// 2. Reset the `entry_set` in `start_block_effect` to `!BOTTOM_VALUE`
- /// 3. Override `join` to do the opposite from what it's doing now.
- #[inline]
- fn join<T: Idx>(&self, inout_set: &mut BitSet<T>, in_set: &BitSet<T>) -> bool {
- if !Self::BOTTOM_VALUE { inout_set.union(in_set) } else { inout_set.intersect(in_set) }
- }
-}
-
/// Define the domain of a dataflow problem.
///
-/// This trait specifies the lattice on which this analysis operates. For now, this must be a
-/// powerset of values of type `Idx`. The elements of this lattice are represented with a `BitSet`
-/// and referred to as the state vector.
-///
-/// This trait also defines the initial value for the dataflow state upon entry to the
-/// `START_BLOCK`, as well as some names used to refer to this analysis when debugging.
-pub trait AnalysisDomain<'tcx>: BottomValue {
- /// The type of the elements in the state vector.
- type Idx: Idx;
+/// This trait specifies the lattice on which this analysis operates (the domain) as well as its
+/// initial value at the entry point of each basic block.
+pub trait AnalysisDomain<'tcx> {
+ type Domain: Clone + JoinSemiLattice;
/// The direction of this analyis. Either `Forward` or `Backward`.
type Direction: Direction = Forward;
/// suitable as part of a filename.
const NAME: &'static str;
- /// The size of the state vector.
- fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize;
+ fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain;
/// Mutates the entry set of the `START_BLOCK` to contain the initial state for dataflow
/// analysis.
// FIXME: For backward dataflow analyses, the initial state should be applied to every basic
// block where control flow could exit the MIR body (e.g., those terminated with `return` or
// `resume`). It's not obvious how to handle `yield` points in generators, however.
- fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>);
-
- /// Prints an element in the state vector for debugging.
- fn pretty_print_idx(&self, w: &mut impl io::Write, idx: Self::Idx) -> io::Result<()> {
- write!(w, "{:?}", idx)
- }
+ fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain);
}
/// A dataflow problem with an arbitrarily complex transfer function.
/// Updates the current dataflow state with the effect of evaluating a statement.
fn apply_statement_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
);
/// analyses should not implement this without implementing `apply_statement_effect`.
fn apply_before_statement_effect(
&self,
- _state: &mut BitSet<Self::Idx>,
+ _state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>,
_location: Location,
) {
/// initialized here.
fn apply_terminator_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut Self::Domain,
terminator: &mir::Terminator<'tcx>,
location: Location,
);
/// analyses should not implement this without implementing `apply_terminator_effect`.
fn apply_before_terminator_effect(
&self,
- _state: &mut BitSet<Self::Idx>,
+ _state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>,
_location: Location,
) {
/// edges.
fn apply_call_return_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut Self::Domain,
block: BasicBlock,
func: &mir::Operand<'tcx>,
args: &[mir::Operand<'tcx>],
/// By default, no effects happen.
fn apply_yield_resume_effect(
&self,
- _state: &mut BitSet<Self::Idx>,
+ _state: &mut Self::Domain,
_resume_block: BasicBlock,
_resume_place: mir::Place<'tcx>,
) {
/// FIXME: This class of effects is not supported for backward dataflow analyses.
fn apply_discriminant_switch_effect(
&self,
- _state: &mut BitSet<Self::Idx>,
+ _state: &mut Self::Domain,
_block: BasicBlock,
_enum_place: mir::Place<'tcx>,
_adt: &ty::AdtDef,
///
/// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`.
pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
+ type Idx: Idx;
+
/// See `Analysis::apply_statement_effect`.
fn statement_effect(
&self,
impl<A> Analysis<'tcx> for A
where
A: GenKillAnalysis<'tcx>,
+ A::Domain: GenKill<A::Idx> + BorrowMut<BitSet<A::Idx>>,
{
fn apply_statement_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut A::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
) {
fn apply_before_statement_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut A::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
) {
fn apply_terminator_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut A::Domain,
terminator: &mir::Terminator<'tcx>,
location: Location,
) {
fn apply_before_terminator_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut A::Domain,
terminator: &mir::Terminator<'tcx>,
location: Location,
) {
fn apply_call_return_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
func: &mir::Operand<'tcx>,
args: &[mir::Operand<'tcx>],
fn apply_yield_resume_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut A::Domain,
resume_block: BasicBlock,
resume_place: mir::Place<'tcx>,
) {
fn apply_discriminant_switch_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut A::Domain,
block: BasicBlock,
enum_place: mir::Place<'tcx>,
adt: &ty::AdtDef,
/// applied multiple times efficiently. When there are multiple calls to `gen` and/or `kill` for
/// the same element, the most recent one takes precedence.
#[derive(Clone)]
-pub struct GenKillSet<T: Idx> {
+pub struct GenKillSet<T> {
gen: HybridBitSet<T>,
kill: HybridBitSet<T>,
}
}
}
- /// Applies this transfer function to the given state vector.
pub fn apply(&self, state: &mut BitSet<T>) {
state.union(&self.gen);
state.subtract(&self.kill);
}
}
+impl<T: Idx> GenKill<T> for lattice::Dual<BitSet<T>> {
+ fn gen(&mut self, elem: T) {
+ self.0.insert(elem);
+ }
+
+ fn kill(&mut self, elem: T) {
+ self.0.remove(elem);
+ }
+}
+
// NOTE: DO NOT CHANGE VARIANT ORDER. The derived `Ord` impls rely on the current order.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Effect {
use rustc_span::DUMMY_SP;
use super::*;
-use crate::dataflow::BottomValue;
/// Creates a `mir::Body` with a few disconnected basic blocks.
///
/// The entry set for each `BasicBlock` is the ID of that block offset by a fixed amount to
/// avoid colliding with the statement/terminator effects.
fn mock_entry_set(&self, bb: BasicBlock) -> BitSet<usize> {
- let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
+ let mut ret = self.bottom_value(self.body);
ret.insert(Self::BASIC_BLOCK_OFFSET + bb.index());
ret
}
fn mock_entry_sets(&self) -> IndexVec<BasicBlock, BitSet<usize>> {
- let empty = BitSet::new_empty(self.bits_per_block(self.body));
+ let empty = self.bottom_value(self.body);
let mut ret = IndexVec::from_elem(empty, &self.body.basic_blocks());
for (bb, _) in self.body.basic_blocks().iter_enumerated() {
/// would be `[102, 0, 1, 2, 3, 4]`.
fn expected_state_at_target(&self, target: SeekTarget) -> BitSet<usize> {
let block = target.block();
- let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
+ let mut ret = self.bottom_value(self.body);
ret.insert(Self::BASIC_BLOCK_OFFSET + block.index());
let target = match target {
}
}
-impl<D: Direction> BottomValue for MockAnalysis<'tcx, D> {
- const BOTTOM_VALUE: bool = false;
-}
-
impl<D: Direction> AnalysisDomain<'tcx> for MockAnalysis<'tcx, D> {
- type Idx = usize;
+ type Domain = BitSet<usize>;
type Direction = D;
const NAME: &'static str = "mock";
- fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
- Self::BASIC_BLOCK_OFFSET + body.basic_blocks().len()
+ fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ BitSet::new_empty(Self::BASIC_BLOCK_OFFSET + body.basic_blocks().len())
}
- fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
+ fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
unimplemented!("This is never called since `MockAnalysis` is never iterated to fixpoint");
}
}
impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
fn apply_statement_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>,
location: Location,
) {
fn apply_before_statement_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>,
location: Location,
) {
fn apply_terminator_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
fn apply_before_terminator_effect(
&self,
- state: &mut BitSet<Self::Idx>,
+ state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
fn apply_call_return_effect(
&self,
- _state: &mut BitSet<Self::Idx>,
+ _state: &mut Self::Domain,
_block: BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
-use rustc_index::bit_set::BitSet;
use rustc_middle::mir::{self, BasicBlock, Location};
use super::{Analysis, Direction, Results};
where
A: Analysis<'tcx>,
{
- type FlowState = BitSet<A::Idx>;
+ type FlowState = A::Domain;
type Direction = A::Direction;
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
- BitSet::new_empty(self.analysis.bits_per_block(body))
+ self.analysis.bottom_value(body)
}
fn reset_to_block_entry(&self, state: &mut Self::FlowState, block: BasicBlock) {
- state.overwrite(&self.entry_set_for_block(block));
+ state.clone_from(&self.entry_set_for_block(block));
}
fn reconstruct_before_statement_effect(
$( $A: Analysis<'tcx, Direction = D>, )*
{
type Direction = D;
- type FlowState = $T<$( BitSet<$A::Idx> ),*>;
+ type FlowState = $T<$( $A::Domain ),*>;
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
$T {
- $( $field: BitSet::new_empty(self.$field.analysis.bits_per_block(body)) ),*
+ $( $field: self.$field.analysis.bottom_value(body) ),*
}
}
state: &mut Self::FlowState,
block: BasicBlock,
) {
- $( state.$field.overwrite(&self.$field.entry_set_for_block(block)); )*
+ $( state.$field.clone_from(&self.$field.entry_set_for_block(block)); )*
}
fn reconstruct_before_statement_effect(
pub(crate) use self::drop_flag_effects::*;
pub use self::framework::{
- visit_results, Analysis, AnalysisDomain, Backward, BorrowckFlowState, BorrowckResults,
- BottomValue, Engine, Forward, GenKill, GenKillAnalysis, Results, ResultsCursor,
- ResultsRefCursor, ResultsVisitor,
+ fmt, lattice, visit_results, Analysis, AnalysisDomain, Backward, BorrowckFlowState,
+ BorrowckResults, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice, Results,
+ ResultsCursor, ResultsRefCursor, ResultsVisitor,
};
use self::move_paths::MoveData;
#![feature(crate_visibility_modifier)]
#![feature(decl_macro)]
#![feature(drain_filter)]
+#![feature(exact_size_is_empty)]
#![feature(exhaustive_patterns)]
#![feature(iter_order_by)]
#![feature(never_type)]