Extend dataflow framework to support arbitrary lattices
This commit is contained in:
parent
9e45e90596
commit
3233fb18a8
|
@ -4,6 +4,7 @@ use std::borrow::Borrow;
|
|||
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};
|
||||
|
@ -26,7 +27,7 @@ where
|
|||
{
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
results: R,
|
||||
state: BitSet<A::Idx>,
|
||||
state: A::Domain,
|
||||
|
||||
pos: CursorPosition,
|
||||
|
||||
|
@ -46,17 +47,16 @@ where
|
|||
{
|
||||
/// 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)]
|
||||
|
@ -79,17 +79,10 @@ where
|
|||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -99,7 +92,7 @@ where
|
|||
#[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;
|
||||
}
|
||||
|
@ -207,12 +200,23 @@ where
|
|||
///
|
||||
/// 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,
|
||||
|
|
|
@ -18,7 +18,7 @@ pub trait Direction {
|
|||
/// `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>,
|
||||
|
@ -27,7 +27,7 @@ pub trait Direction {
|
|||
|
||||
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
|
||||
|
@ -55,9 +55,9 @@ pub trait Direction {
|
|||
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>;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ impl Direction for Backward {
|
|||
|
||||
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
|
||||
|
@ -112,7 +112,7 @@ impl Direction for Backward {
|
|||
|
||||
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>,
|
||||
|
@ -224,9 +224,9 @@ impl Direction for Backward {
|
|||
_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>,
|
||||
{
|
||||
|
@ -281,7 +281,7 @@ impl Direction for Forward {
|
|||
|
||||
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
|
||||
|
@ -321,7 +321,7 @@ impl Direction for Forward {
|
|||
|
||||
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>,
|
||||
|
@ -428,9 +428,9 @@ impl Direction for Forward {
|
|||
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>,
|
||||
{
|
||||
|
@ -499,7 +499,7 @@ impl Direction for Forward {
|
|||
// 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, _) =
|
||||
|
@ -508,7 +508,7 @@ impl Direction for Forward {
|
|||
from that of `SwitchInt::values`",
|
||||
);
|
||||
|
||||
tmp.overwrite(exit_state);
|
||||
tmp.clone_from(exit_state);
|
||||
analysis.apply_discriminant_switch_effect(
|
||||
&mut tmp,
|
||||
bb,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! A solver for dataflow problems.
|
||||
|
||||
use std::borrow::BorrowMut;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
@ -9,14 +10,16 @@ use rustc_data_structures::work_queue::WorkQueue;
|
|||
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;
|
||||
|
||||
|
@ -26,7 +29,7 @@ where
|
|||
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>
|
||||
|
@ -39,7 +42,7 @@ where
|
|||
}
|
||||
|
||||
/// 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]
|
||||
}
|
||||
|
||||
|
@ -47,7 +50,7 @@ where
|
|||
&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)
|
||||
}
|
||||
|
@ -55,7 +58,7 @@ where
|
|||
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)
|
||||
|
@ -64,7 +67,7 @@ where
|
|||
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)
|
||||
|
@ -76,21 +79,22 @@ pub struct Engine<'a, 'tcx, A>
|
|||
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(
|
||||
|
@ -109,22 +113,26 @@ where
|
|||
|
||||
// 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.
|
||||
|
@ -145,32 +153,24 @@ where
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,16 +185,18 @@ where
|
|||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
|
@ -213,14 +215,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
|
@ -231,8 +233,8 @@ where
|
|||
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);
|
||||
}
|
||||
|
@ -242,7 +244,7 @@ where
|
|||
|
||||
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);
|
||||
}
|
||||
|
@ -260,10 +262,10 @@ fn write_graphviz_results<A>(
|
|||
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,
|
||||
|
@ -290,26 +292,15 @@ where
|
|||
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() {
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
//! 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)
|
||||
}
|
||||
}
|
|
@ -1,26 +1,40 @@
|
|||
//! 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>
|
||||
|
@ -31,15 +45,9 @@ where
|
|||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +70,7 @@ fn dataflow_successors(body: &Body<'tcx>, bb: BasicBlock) -> Vec<CfgEdge> {
|
|||
impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
A::Domain: DebugWithContext<A>,
|
||||
{
|
||||
type Node = BasicBlock;
|
||||
type Edge = CfgEdge;
|
||||
|
@ -77,7 +86,13 @@ where
|
|||
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -126,19 +141,16 @@ where
|
|||
{
|
||||
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;
|
||||
|
@ -187,40 +199,30 @@ where
|
|||
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)",
|
||||
|
@ -229,8 +231,11 @@ where
|
|||
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, _)),
|
||||
|
@ -239,44 +244,43 @@ where
|
|||
..
|
||||
} => {
|
||||
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()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
|
@ -322,6 +326,7 @@ where
|
|||
&mut self,
|
||||
w: &mut impl io::Write,
|
||||
block: BasicBlock,
|
||||
state_column_names: &[&str],
|
||||
) -> io::Result<()> {
|
||||
// +------------------------------------+-------------+
|
||||
// A | bb4 | STATE |
|
||||
|
@ -330,8 +335,6 @@ where
|
|||
// +-+----------------------------------+------+------+
|
||||
// | | ... | | |
|
||||
|
||||
let state_column_names = self.state_formatter.column_names();
|
||||
|
||||
// A
|
||||
write!(
|
||||
w,
|
||||
|
@ -357,6 +360,56 @@ where
|
|||
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>(
|
||||
|
@ -397,319 +450,169 @@ where
|
|||
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>,
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> StateFormatter<'tcx, A> for SimpleDiff<'_, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
fn column_names(&self) -> &[&str] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn write_state_for_location(
|
||||
&mut self,
|
||||
mut w: &mut dyn io::Write,
|
||||
fmt: &str,
|
||||
results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
|
||||
location: Location,
|
||||
) -> io::Result<()> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
fn column_names(&self) -> &[&str] {
|
||||
&["BEFORE", " AFTER"]
|
||||
}
|
||||
|
||||
fn write_state_for_location(
|
||||
&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);
|
||||
}
|
||||
|
||||
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>>,
|
||||
fn run(
|
||||
body: &'a mir::Body<'tcx>,
|
||||
block: BasicBlock,
|
||||
results: &'a Results<'tcx, A>,
|
||||
style: OutputStyle,
|
||||
) -> Self {
|
||||
BlockTransferFunc { body, trans_for_block }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
fn column_names(&self) -> &[&str] {
|
||||
&["GEN", "KILL"]
|
||||
}
|
||||
|
||||
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 block_trans = &self.trans_for_block[location.block];
|
||||
let rowspan = self.body.basic_blocks()[location.block].statements.len();
|
||||
|
||||
for set in &[&block_trans.gen, &block_trans.kill] {
|
||||
write!(
|
||||
w,
|
||||
r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
|
||||
fmt = fmt,
|
||||
rowspan = rowspan
|
||||
)?;
|
||||
|
||||
pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
|
||||
write!(w, "</td>")?;
|
||||
}
|
||||
|
||||
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 mut collector = StateDiffCollector {
|
||||
analysis: &results.analysis,
|
||||
prev_state: results.analysis.bottom_value(body),
|
||||
after: vec![],
|
||||
before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
|
||||
};
|
||||
}
|
||||
|
||||
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>"#)?;
|
||||
results.visit_with(body, std::iter::once(block), &mut collector);
|
||||
collector
|
||||
}
|
||||
|
||||
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"/> "#;
|
||||
|
||||
/// 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>
|
||||
impl<A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
A::Domain: DebugWithContext<A>,
|
||||
{
|
||||
let sep_width = sep.chars().count();
|
||||
type FlowState = A::Domain;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
fn visit_block_start(
|
||||
&mut self,
|
||||
state: &Self::FlowState,
|
||||
_block_data: &'mir mir::BasicBlockData<'tcx>,
|
||||
_block: BasicBlock,
|
||||
) {
|
||||
if A::Direction::is_forward() {
|
||||
self.prev_state.clone_from(state);
|
||||
}
|
||||
|
||||
write!(w, "{}", escaped)?;
|
||||
curr_line_width += escaped_width;
|
||||
}
|
||||
|
||||
Ok(line_break_inserted)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 visit_terminator_before_primary_effect(
|
||||
&mut self,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
|
||||
where
|
||||
T: DebugWithContext<C>,
|
||||
{
|
||||
if new == old {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let re = Regex::new("\u{001f}([+-])").unwrap();
|
||||
|
||||
let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
|
||||
|
||||
// Replace newlines in the `Debug` output with `<br/>`
|
||||
let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
|
||||
|
||||
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>"#);
|
||||
}
|
||||
|
||||
let tag = match &captures[1] {
|
||||
"+" => r#"<font color="darkgreen">+"#,
|
||||
"-" => r#"<font color="red">-"#,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
inside_font_tag = true;
|
||||
ret.push_str(tag);
|
||||
ret
|
||||
});
|
||||
|
||||
let mut html_diff = match html_diff {
|
||||
Cow::Borrowed(_) => return raw_diff,
|
||||
Cow::Owned(s) => s,
|
||||
};
|
||||
|
||||
if inside_font_tag {
|
||||
html_diff.push_str("</font>");
|
||||
}
|
||||
|
||||
html_diff
|
||||
}
|
||||
|
||||
/// The background color used for zebra-striping the table.
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
//! 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
|
||||
}
|
||||
}
|
|
@ -30,8 +30,8 @@
|
|||
//!
|
||||
//! [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};
|
||||
|
@ -43,67 +43,24 @@ use rustc_target::abi::VariantIdx;
|
|||
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;
|
||||
|
@ -114,8 +71,7 @@ pub trait AnalysisDomain<'tcx>: BottomValue {
|
|||
/// 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.
|
||||
|
@ -126,12 +82,7 @@ pub trait AnalysisDomain<'tcx>: BottomValue {
|
|||
// 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.
|
||||
|
@ -139,7 +90,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
|||
/// 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,
|
||||
);
|
||||
|
@ -152,7 +103,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
|||
/// 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,
|
||||
) {
|
||||
|
@ -166,7 +117,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
|||
/// initialized here.
|
||||
fn apply_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
);
|
||||
|
@ -179,7 +130,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
|||
/// 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,
|
||||
) {
|
||||
|
@ -192,7 +143,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
|||
/// 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>],
|
||||
|
@ -207,7 +158,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'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>,
|
||||
) {
|
||||
|
@ -222,7 +173,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'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,
|
||||
|
@ -264,6 +215,8 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
|||
///
|
||||
/// `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,
|
||||
|
@ -332,10 +285,11 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
|
|||
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,
|
||||
) {
|
||||
|
@ -344,7 +298,7 @@ where
|
|||
|
||||
fn apply_before_statement_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut A::Domain,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
|
@ -353,7 +307,7 @@ where
|
|||
|
||||
fn apply_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut A::Domain,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
|
@ -362,7 +316,7 @@ where
|
|||
|
||||
fn apply_before_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut A::Domain,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
|
@ -371,7 +325,7 @@ where
|
|||
|
||||
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>],
|
||||
|
@ -382,7 +336,7 @@ where
|
|||
|
||||
fn apply_yield_resume_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut A::Domain,
|
||||
resume_block: BasicBlock,
|
||||
resume_place: mir::Place<'tcx>,
|
||||
) {
|
||||
|
@ -391,7 +345,7 @@ where
|
|||
|
||||
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,
|
||||
|
@ -450,7 +404,7 @@ pub trait GenKill<T> {
|
|||
/// 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>,
|
||||
}
|
||||
|
@ -464,7 +418,6 @@ impl<T: Idx> GenKillSet<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);
|
||||
|
@ -493,6 +446,16 @@ impl<T: Idx> GenKill<T> for BitSet<T> {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -9,7 +9,6 @@ use rustc_middle::ty;
|
|||
use rustc_span::DUMMY_SP;
|
||||
|
||||
use super::*;
|
||||
use crate::dataflow::BottomValue;
|
||||
|
||||
/// Creates a `mir::Body` with a few disconnected basic blocks.
|
||||
///
|
||||
|
@ -92,13 +91,13 @@ impl<D: Direction> MockAnalysis<'tcx, D> {
|
|||
/// 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() {
|
||||
|
@ -130,7 +129,7 @@ impl<D: Direction> MockAnalysis<'tcx, D> {
|
|||
/// 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 {
|
||||
|
@ -161,21 +160,17 @@ impl<D: Direction> MockAnalysis<'tcx, D> {
|
|||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +178,7 @@ impl<D: Direction> AnalysisDomain<'tcx> for MockAnalysis<'tcx, D> {
|
|||
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,
|
||||
) {
|
||||
|
@ -193,7 +188,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
|||
|
||||
fn apply_before_statement_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
|
@ -203,7 +198,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
|||
|
||||
fn apply_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
|
@ -213,7 +208,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
|||
|
||||
fn apply_before_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
|
@ -223,7 +218,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
|||
|
||||
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>],
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::{self, BasicBlock, Location};
|
||||
|
||||
use super::{Analysis, Direction, Results};
|
||||
|
@ -139,16 +138,16 @@ impl<'tcx, A> ResultsVisitable<'tcx> for Results<'tcx, A>
|
|||
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(
|
||||
|
@ -217,11 +216,11 @@ macro_rules! impl_visitable {
|
|||
$( $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) ),*
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +229,7 @@ macro_rules! impl_visitable {
|
|||
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(
|
||||
|
|
|
@ -5,9 +5,9 @@ use rustc_span::symbol::{sym, Symbol};
|
|||
|
||||
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;
|
||||
|
|
|
@ -14,6 +14,7 @@ Rust MIR: a lowered representation of Rust.
|
|||
#![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)]
|
||||
|
|
Loading…
Reference in New Issue