Extend dataflow framework to support arbitrary lattices

This commit is contained in:
Dylan MacKenzie 2020-08-27 23:02:46 -07:00
parent 9e45e90596
commit 3233fb18a8
11 changed files with 765 additions and 541 deletions

View File

@ -4,6 +4,7 @@ use std::borrow::Borrow;
use std::cmp::Ordering; use std::cmp::Ordering;
use rustc_index::bit_set::BitSet; use rustc_index::bit_set::BitSet;
use rustc_index::vec::Idx;
use rustc_middle::mir::{self, BasicBlock, Location}; use rustc_middle::mir::{self, BasicBlock, Location};
use super::{Analysis, Direction, Effect, EffectIndex, Results}; use super::{Analysis, Direction, Effect, EffectIndex, Results};
@ -26,7 +27,7 @@ where
{ {
body: &'mir mir::Body<'tcx>, body: &'mir mir::Body<'tcx>,
results: R, results: R,
state: BitSet<A::Idx>, state: A::Domain,
pos: CursorPosition, pos: CursorPosition,
@ -46,17 +47,16 @@ where
{ {
/// Returns a new cursor that can inspect `results`. /// Returns a new cursor that can inspect `results`.
pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self { 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 { ResultsCursor {
body, body,
results, 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 // it needs to reset to block entry before the first seek. The cursor position is
// immaterial. // immaterial.
state_needs_reset: true, state_needs_reset: true,
state: BitSet::new_empty(bits_per_block), state: bottom_value,
pos: CursorPosition::block_entry(mir::START_BLOCK), pos: CursorPosition::block_entry(mir::START_BLOCK),
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -79,17 +79,10 @@ where
} }
/// Returns the dataflow state at the current location. /// Returns the dataflow state at the current location.
pub fn get(&self) -> &BitSet<A::Idx> { pub fn get(&self) -> &A::Domain {
&self.state &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. /// 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. /// For forward dataflow analyses, this is the dataflow state prior to the first statement.
@ -99,7 +92,7 @@ where
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert!(self.reachable_blocks.contains(block)); 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.pos = CursorPosition::block_entry(block);
self.state_needs_reset = false; 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 /// 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. /// 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); f(&self.results.borrow().analysis, &mut self.state);
self.state_needs_reset = true; 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)] #[derive(Clone, Copy, Debug)]
struct CursorPosition { struct CursorPosition {
block: BasicBlock, block: BasicBlock,

View File

@ -18,7 +18,7 @@ pub trait Direction {
/// `effects.start()` must precede or equal `effects.end()` in this direction. /// `effects.start()` must precede or equal `effects.end()` in this direction.
fn apply_effects_in_range<A>( fn apply_effects_in_range<A>(
analysis: &A, analysis: &A,
state: &mut BitSet<A::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>, block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>, effects: RangeInclusive<EffectIndex>,
@ -27,7 +27,7 @@ pub trait Direction {
fn apply_effects_in_block<A>( fn apply_effects_in_block<A>(
analysis: &A, analysis: &A,
state: &mut BitSet<A::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>, block_data: &mir::BasicBlockData<'tcx>,
) where ) where
@ -55,9 +55,9 @@ pub trait Direction {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>, body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>, dead_unwinds: Option<&BitSet<BasicBlock>>,
exit_state: &mut BitSet<A::Idx>, exit_state: &mut A::Domain,
block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>), block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>), propagate: impl FnMut(BasicBlock, &A::Domain),
) where ) where
A: Analysis<'tcx>; A: Analysis<'tcx>;
} }
@ -72,7 +72,7 @@ impl Direction for Backward {
fn apply_effects_in_block<A>( fn apply_effects_in_block<A>(
analysis: &A, analysis: &A,
state: &mut BitSet<A::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>, block_data: &mir::BasicBlockData<'tcx>,
) where ) where
@ -112,7 +112,7 @@ impl Direction for Backward {
fn apply_effects_in_range<A>( fn apply_effects_in_range<A>(
analysis: &A, analysis: &A,
state: &mut BitSet<A::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>, block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>, effects: RangeInclusive<EffectIndex>,
@ -224,9 +224,9 @@ impl Direction for Backward {
_tcx: TyCtxt<'tcx>, _tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>, body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>, dead_unwinds: Option<&BitSet<BasicBlock>>,
exit_state: &mut BitSet<A::Idx>, exit_state: &mut A::Domain,
(bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>), (bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>), mut propagate: impl FnMut(BasicBlock, &A::Domain),
) where ) where
A: Analysis<'tcx>, A: Analysis<'tcx>,
{ {
@ -281,7 +281,7 @@ impl Direction for Forward {
fn apply_effects_in_block<A>( fn apply_effects_in_block<A>(
analysis: &A, analysis: &A,
state: &mut BitSet<A::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>, block_data: &mir::BasicBlockData<'tcx>,
) where ) where
@ -321,7 +321,7 @@ impl Direction for Forward {
fn apply_effects_in_range<A>( fn apply_effects_in_range<A>(
analysis: &A, analysis: &A,
state: &mut BitSet<A::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>, block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>, effects: RangeInclusive<EffectIndex>,
@ -428,9 +428,9 @@ impl Direction for Forward {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>, body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>, dead_unwinds: Option<&BitSet<BasicBlock>>,
exit_state: &mut BitSet<A::Idx>, exit_state: &mut A::Domain,
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>), (bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>), mut propagate: impl FnMut(BasicBlock, &A::Domain),
) where ) where
A: Analysis<'tcx>, 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 // 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 // are yielded by `AdtDef::discriminants`. We rely on this to match each
// discriminant in `values` to its corresponding variant in linear time. // 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); let mut discriminants = enum_def.discriminants(tcx);
for (value, target) in values.iter().zip(targets.iter().copied()) { for (value, target) in values.iter().zip(targets.iter().copied()) {
let (variant_idx, _) = let (variant_idx, _) =
@ -508,7 +508,7 @@ impl Direction for Forward {
from that of `SwitchInt::values`", from that of `SwitchInt::values`",
); );
tmp.overwrite(exit_state); tmp.clone_from(exit_state);
analysis.apply_discriminant_switch_effect( analysis.apply_discriminant_switch_effect(
&mut tmp, &mut tmp,
bb, bb,

View File

@ -1,5 +1,6 @@
//! A solver for dataflow problems. //! A solver for dataflow problems.
use std::borrow::BorrowMut;
use std::ffi::OsString; use std::ffi::OsString;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@ -9,14 +10,16 @@ use rustc_data_structures::work_queue::WorkQueue;
use rustc_graphviz as dot; use rustc_graphviz as dot;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_index::bit_set::BitSet; 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::mir::{self, traversal, BasicBlock};
use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::ty::{self, TyCtxt};
use rustc_span::symbol::{sym, Symbol}; use rustc_span::symbol::{sym, Symbol};
use super::fmt::DebugWithContext;
use super::graphviz; use super::graphviz;
use super::{ 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; use crate::util::pretty::dump_enabled;
@ -26,7 +29,7 @@ where
A: Analysis<'tcx>, A: Analysis<'tcx>,
{ {
pub analysis: A, 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> impl<A> Results<'tcx, A>
@ -39,7 +42,7 @@ where
} }
/// Gets the dataflow state for the given block. /// 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.entry_sets[block]
} }
@ -47,7 +50,7 @@ where
&self, &self,
body: &'mir mir::Body<'tcx>, body: &'mir mir::Body<'tcx>,
blocks: impl IntoIterator<Item = BasicBlock>, 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) visit_results(body, blocks, self, vis)
} }
@ -55,7 +58,7 @@ where
pub fn visit_reachable_with( pub fn visit_reachable_with(
&self, &self,
body: &'mir mir::Body<'tcx>, 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); let blocks = mir::traversal::reachable(body);
visit_results(body, blocks.map(|(bb, _)| bb), self, vis) visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
@ -64,7 +67,7 @@ where
pub fn visit_in_rpo_with( pub fn visit_in_rpo_with(
&self, &self,
body: &'mir mir::Body<'tcx>, 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); let blocks = mir::traversal::reverse_postorder(body);
visit_results(body, blocks.map(|(bb, _)| bb), self, vis) visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
@ -76,21 +79,22 @@ pub struct Engine<'a, 'tcx, A>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
{ {
bits_per_block: usize,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
body: &'a mir::Body<'tcx>, body: &'a mir::Body<'tcx>,
def_id: DefId, def_id: DefId,
dead_unwinds: Option<&'a BitSet<BasicBlock>>, dead_unwinds: Option<&'a BitSet<BasicBlock>>,
entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>, entry_sets: IndexVec<BasicBlock, A::Domain>,
analysis: A, analysis: A,
/// Cached, cumulative transfer functions for each block. /// 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 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. /// Creates a new `Engine` to solve a gen-kill dataflow problem.
pub fn new_gen_kill( pub fn new_gen_kill(
@ -109,22 +113,26 @@ where
// Otherwise, compute and store the cumulative transfer function for each block. // Otherwise, compute and store the cumulative transfer function for each block.
let bits_per_block = analysis.bits_per_block(body); let identity = GenKillSet::identity(analysis.bottom_value(body).borrow().domain_size());
let mut trans_for_block = let mut trans_for_block = IndexVec::from_elem(identity, body.basic_blocks());
IndexVec::from_elem(GenKillSet::identity(bits_per_block), body.basic_blocks());
for (block, block_data) in body.basic_blocks().iter_enumerated() { for (block, block_data) in body.basic_blocks().iter_enumerated() {
let trans = &mut trans_for_block[block]; let trans = &mut trans_for_block[block];
A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data); 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 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 /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
/// function. /// function.
@ -145,32 +153,24 @@ where
body: &'a mir::Body<'tcx>, body: &'a mir::Body<'tcx>,
def_id: DefId, def_id: DefId,
analysis: A, analysis: A,
trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>, apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
) -> Self { ) -> Self {
let bits_per_block = analysis.bits_per_block(body); let bottom_value = analysis.bottom_value(body);
let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), body.basic_blocks());
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());
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]); 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"); bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
} }
Engine { Engine {
analysis, analysis,
bits_per_block,
tcx, tcx,
body, body,
def_id, def_id,
dead_unwinds: None, dead_unwinds: None,
entry_sets, entry_sets,
trans_for_block, apply_trans_for_block,
} }
} }
@ -185,16 +185,18 @@ where
} }
/// Computes the fixpoint for this dataflow problem and returns it. /// 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 { let Engine {
analysis, analysis,
bits_per_block,
body, body,
dead_unwinds, dead_unwinds,
def_id, def_id,
mut entry_sets, mut entry_sets,
tcx, tcx,
trans_for_block, apply_trans_for_block,
.. ..
} = self; } = 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() { while let Some(bb) = dirty_queue.pop() {
let bb_data = &body[bb]; let bb_data = &body[bb];
// Apply the block transfer function, using the cached one if it exists. // Apply the block transfer function, using the cached one if it exists.
state.overwrite(&entry_sets[bb]); state.clone_from(&entry_sets[bb]);
match &trans_for_block { match &apply_trans_for_block {
Some(trans_for_block) => trans_for_block[bb].apply(&mut state), Some(apply) => apply(bb, &mut state),
None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data), None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data),
} }
@ -231,8 +233,8 @@ where
dead_unwinds, dead_unwinds,
&mut state, &mut state,
(bb, bb_data), (bb, bb_data),
|target: BasicBlock, state: &BitSet<A::Idx>| { |target: BasicBlock, state: &A::Domain| {
let set_changed = analysis.join(&mut entry_sets[target], state); let set_changed = entry_sets[target].join(state);
if set_changed { if set_changed {
dirty_queue.insert(target); dirty_queue.insert(target);
} }
@ -242,7 +244,7 @@ where
let results = Results { analysis, entry_sets }; 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 { if let Err(e) = res {
warn!("Failed to write graphviz dataflow results: {}", e); warn!("Failed to write graphviz dataflow results: {}", e);
} }
@ -260,10 +262,10 @@ fn write_graphviz_results<A>(
def_id: DefId, def_id: DefId,
body: &mir::Body<'tcx>, body: &mir::Body<'tcx>,
results: &Results<'tcx, A>, results: &Results<'tcx, A>,
block_transfer_functions: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
) -> std::io::Result<()> ) -> std::io::Result<()>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
A::Domain: DebugWithContext<A>,
{ {
let attrs = match RustcMirAttrs::parse(tcx, def_id) { let attrs = match RustcMirAttrs::parse(tcx, def_id) {
Ok(attrs) => attrs, Ok(attrs) => attrs,
@ -290,26 +292,15 @@ where
None => return Ok(()), None => return Ok(()),
}; };
let bits_per_block = results.analysis.bits_per_block(body); let style = match attrs.formatter {
Some(sym::two_phase) => graphviz::OutputStyle::BeforeAndAfter,
let mut formatter: Box<dyn graphviz::StateFormatter<'tcx, _>> = match attrs.formatter { _ => graphviz::OutputStyle::AfterOnly,
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)),
}; };
debug!("printing dataflow results for {:?} to {}", def_id, path.display()); debug!("printing dataflow results for {:?} to {}", def_id, path.display());
let mut buf = Vec::new(); 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])?; dot::render_opts(&graphviz, &mut buf, &[dot::RenderOption::Monospace])?;
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {

View File

@ -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)
}
}

View File

@ -1,26 +1,40 @@
//! A helpful diagram for debugging dataflow problems. //! A helpful diagram for debugging dataflow problems.
use std::cell::RefCell; use std::borrow::Cow;
use std::{io, ops, str}; use std::{io, ops, str};
use regex::Regex;
use rustc_graphviz as dot; use rustc_graphviz as dot;
use rustc_hir::def_id::DefId; 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 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; 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> pub struct Formatter<'a, 'tcx, A>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
{ {
body: &'a Body<'tcx>, body: &'a Body<'tcx>,
def_id: DefId, def_id: DefId,
results: &'a Results<'tcx, A>,
// This must be behind a `RefCell` because `dot::Labeller` takes `&self`. style: OutputStyle,
block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
} }
impl<A> Formatter<'a, 'tcx, A> impl<A> Formatter<'a, 'tcx, A>
@ -31,15 +45,9 @@ where
body: &'a Body<'tcx>, body: &'a Body<'tcx>,
def_id: DefId, def_id: DefId,
results: &'a Results<'tcx, A>, results: &'a Results<'tcx, A>,
state_formatter: &'a mut dyn StateFormatter<'tcx, A>, style: OutputStyle,
) -> Self { ) -> Self {
let block_formatter = BlockFormatter { Formatter { body, def_id, results, style }
bg: Background::Light,
results: ResultsRefCursor::new(body, results),
state_formatter,
};
Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) }
} }
} }
@ -62,6 +70,7 @@ fn dataflow_successors(body: &Body<'tcx>, bb: BasicBlock) -> Vec<CfgEdge> {
impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A> impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
A::Domain: DebugWithContext<A>,
{ {
type Node = BasicBlock; type Node = BasicBlock;
type Edge = CfgEdge; type Edge = CfgEdge;
@ -77,7 +86,13 @@ where
fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> { fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
let mut label = Vec::new(); 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()) dot::LabelText::html(String::from_utf8(label).unwrap())
} }
@ -126,19 +141,16 @@ where
{ {
results: ResultsRefCursor<'a, 'a, 'tcx, A>, results: ResultsRefCursor<'a, 'a, 'tcx, A>,
bg: Background, bg: Background,
state_formatter: &'a mut dyn StateFormatter<'tcx, A>, style: OutputStyle,
} }
impl<A> BlockFormatter<'a, 'tcx, A> impl<A> BlockFormatter<'a, 'tcx, A>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
A::Domain: DebugWithContext<A>,
{ {
const HEADER_COLOR: &'static str = "#a0a0a0"; 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 { fn toggle_background(&mut self) -> Background {
let bg = self.bg; let bg = self.bg;
self.bg = !bg; self.bg = !bg;
@ -187,40 +199,30 @@ where
write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?; write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
// A + B: Block header // A + B: Block header
if self.state_formatter.column_names().is_empty() { match self.style {
self.write_block_header_simple(w, block)?; OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
} else { OutputStyle::BeforeAndAfter => {
self.write_block_header_with_state_columns(w, block)?; self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
}
} }
// C: State at start of block // C: State at start of block
self.bg = Background::Light; self.bg = Background::Light;
self.results.seek_to_block_start(block); 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)")?; self.write_row_with_full_state(w, "", "(on start)")?;
// D: Statement transfer functions // D + E: Statement and terminator transfer functions
for (i, statement) in body[block].statements.iter().enumerate() { self.write_statements_and_terminator(w, body, block)?;
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)?;
// F: State at end of 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 // Write the full dataflow state immediately after the terminator if it differs from the
// state at block entry. // state at block entry.
self.results.seek_to_block_end(block); 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 { let after_terminator_name = match terminator.kind {
mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)", mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
_ => "(on end)", _ => "(on end)",
@ -229,8 +231,11 @@ where
self.write_row_with_full_state(w, "", after_terminator_name)?; self.write_row_with_full_state(w, "", after_terminator_name)?;
} }
// Write any changes caused by terminator-specific effects // Write any changes caused by terminator-specific effects.
let num_state_columns = self.num_state_columns(); //
// 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 { match terminator.kind {
mir::TerminatorKind::Call { mir::TerminatorKind::Call {
destination: Some((return_place, _)), destination: Some((return_place, _)),
@ -239,44 +244,43 @@ where
.. ..
} => { } => {
self.write_row(w, "", "(on successful return)", |this, w, fmt| { 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(); let state_on_unwind = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| { this.results.apply_custom_effect(|analysis, state| {
analysis.apply_call_return_effect(state, block, func, args, return_place); analysis.apply_call_return_effect(state, block, func, args, return_place);
}); });
write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?; write!(
write!(w, "</td>") 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, .. } => { mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
self.write_row(w, "", "(on yield resume)", |this, w, fmt| { 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(); let state_on_generator_drop = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| { this.results.apply_custom_effect(|analysis, state| {
analysis.apply_yield_resume_effect(state, resume, resume_arg); analysis.apply_yield_resume_effect(state, resume, resume_arg);
}); });
write_diff( write!(
w, w,
this.results.analysis(), r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
&state_on_generator_drop, colspan = this.style.num_state_columns(),
fmt = fmt,
diff = diff_pretty(
this.results.get(), this.results.get(),
)?; &state_on_generator_drop,
write!(w, "</td>") this.results.analysis()
),
)
})?; })?;
} }
@ -322,6 +326,7 @@ where
&mut self, &mut self,
w: &mut impl io::Write, w: &mut impl io::Write,
block: BasicBlock, block: BasicBlock,
state_column_names: &[&str],
) -> io::Result<()> { ) -> io::Result<()> {
// +------------------------------------+-------------+ // +------------------------------------+-------------+
// A | bb4 | STATE | // A | bb4 | STATE |
@ -330,8 +335,6 @@ where
// +-+----------------------------------+------+------+ // +-+----------------------------------+------+------+
// | | ... | | | // | | ... | | |
let state_column_names = self.state_formatter.column_names();
// A // A
write!( write!(
w, w,
@ -357,6 +360,56 @@ where
write!(w, "</tr>") 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 /// Write a row with the given index and MIR, using the function argument to fill in the
/// "STATE" column(s). /// "STATE" column(s).
fn write_row<W: io::Write>( fn write_row<W: io::Write>(
@ -397,319 +450,169 @@ where
let state = this.results.get(); let state = this.results.get();
let analysis = this.results.analysis(); 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!( write!(
w, w,
r#"<td colspan="{colspan}" {fmt} align="left">{{"#, r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
colspan = this.num_state_columns(), colspan = this.style.num_state_columns(),
fmt = fmt, fmt = fmt,
)?; state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
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)
}) })
} }
} }
/// Controls what gets printed under the `STATE` header. struct StateDiffCollector<'a, 'tcx, A>
pub trait StateFormatter<'tcx, A>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
{ {
/// The columns that will get printed under `STATE`. analysis: &'a A,
fn column_names(&self) -> &[&str]; prev_state: A::Domain,
before: Option<Vec<String>>,
fn write_state_for_location( after: Vec<String>,
&mut self,
w: &mut dyn io::Write,
fmt: &str,
results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
location: Location,
) -> io::Result<()>;
} }
/// Prints a single column containing the state vector immediately *after* each statement. impl<A> StateDiffCollector<'a, 'tcx, A>
pub struct SimpleDiff<'a, 'tcx, A>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
A::Domain: DebugWithContext<A>,
{ {
prev_state: ResultsRefCursor<'a, 'a, 'tcx, A>, fn run(
}
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>, body: &'a mir::Body<'tcx>,
trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>, block: BasicBlock,
} results: &'a Results<'tcx, A>,
style: OutputStyle,
impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
pub fn new(
body: &'mir mir::Body<'tcx>,
trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
) -> Self { ) -> Self {
BlockTransferFunc { body, trans_for_block } let mut collector = StateDiffCollector {
} analysis: &results.analysis,
} prev_state: results.analysis.bottom_value(body),
after: vec![],
impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx> before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
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,
}; };
}
if !set.is_empty() { results.visit_with(body, std::iter::once(block), &mut collector);
write!(w, r#"<font color="darkgreen">+"#)?; collector
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"/>"#; impl<A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
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>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
A::Domain: DebugWithContext<A>,
{ {
let sep_width = sep.chars().count(); type FlowState = A::Domain;
let mut buf = Vec::new(); fn visit_block_start(
&mut self,
let mut first = true; state: &Self::FlowState,
let mut curr_line_width = 0; _block_data: &'mir mir::BasicBlockData<'tcx>,
let mut line_break_inserted = false; _block: BasicBlock,
) {
for idx in elems { if A::Direction::is_forward() {
buf.clear(); self.prev_state.clone_from(state);
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_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);
}
} }
write!(w, "{}", escaped)?; fn visit_statement_before_primary_effect(
curr_line_width += escaped_width; &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)
}
} }
Ok(line_break_inserted) 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: &regex::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. /// The background color used for zebra-striping the table.

View File

@ -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
}
}

View File

@ -30,8 +30,8 @@
//! //!
//! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems //! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems
use std::borrow::BorrowMut;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::io;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_index::bit_set::{BitSet, HybridBitSet}; use rustc_index::bit_set::{BitSet, HybridBitSet};
@ -43,67 +43,24 @@ use rustc_target::abi::VariantIdx;
mod cursor; mod cursor;
mod direction; mod direction;
mod engine; mod engine;
pub mod fmt;
mod graphviz; mod graphviz;
pub mod lattice;
mod visitor; mod visitor;
pub use self::cursor::{ResultsCursor, ResultsRefCursor}; pub use self::cursor::{ResultsCursor, ResultsRefCursor};
pub use self::direction::{Backward, Direction, Forward}; pub use self::direction::{Backward, Direction, Forward};
pub use self::engine::{Engine, Results}; pub use self::engine::{Engine, Results};
pub use self::lattice::{JoinSemiLattice, MeetSemiLattice};
pub use self::visitor::{visit_results, ResultsVisitor}; pub use self::visitor::{visit_results, ResultsVisitor};
pub use self::visitor::{BorrowckFlowState, BorrowckResults}; 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. /// Define the domain of a dataflow problem.
/// ///
/// This trait specifies the lattice on which this analysis operates. For now, this must be a /// This trait specifies the lattice on which this analysis operates (the domain) as well as its
/// powerset of values of type `Idx`. The elements of this lattice are represented with a `BitSet` /// initial value at the entry point of each basic block.
/// and referred to as the state vector. pub trait AnalysisDomain<'tcx> {
/// type Domain: Clone + JoinSemiLattice;
/// 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;
/// The direction of this analyis. Either `Forward` or `Backward`. /// The direction of this analyis. Either `Forward` or `Backward`.
type Direction: Direction = Forward; type Direction: Direction = Forward;
@ -114,8 +71,7 @@ pub trait AnalysisDomain<'tcx>: BottomValue {
/// suitable as part of a filename. /// suitable as part of a filename.
const NAME: &'static str; const NAME: &'static str;
/// The size of the state vector. fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain;
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize;
/// Mutates the entry set of the `START_BLOCK` to contain the initial state for dataflow /// Mutates the entry set of the `START_BLOCK` to contain the initial state for dataflow
/// analysis. /// analysis.
@ -126,12 +82,7 @@ pub trait AnalysisDomain<'tcx>: BottomValue {
// FIXME: For backward dataflow analyses, the initial state should be applied to every basic // 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 // 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. // `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>); fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain);
/// 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)
}
} }
/// A dataflow problem with an arbitrarily complex transfer function. /// 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. /// Updates the current dataflow state with the effect of evaluating a statement.
fn apply_statement_effect( fn apply_statement_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut Self::Domain,
statement: &mir::Statement<'tcx>, statement: &mir::Statement<'tcx>,
location: Location, location: Location,
); );
@ -152,7 +103,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// analyses should not implement this without implementing `apply_statement_effect`. /// analyses should not implement this without implementing `apply_statement_effect`.
fn apply_before_statement_effect( fn apply_before_statement_effect(
&self, &self,
_state: &mut BitSet<Self::Idx>, _state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>, _statement: &mir::Statement<'tcx>,
_location: Location, _location: Location,
) { ) {
@ -166,7 +117,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// initialized here. /// initialized here.
fn apply_terminator_effect( fn apply_terminator_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut Self::Domain,
terminator: &mir::Terminator<'tcx>, terminator: &mir::Terminator<'tcx>,
location: Location, location: Location,
); );
@ -179,7 +130,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// analyses should not implement this without implementing `apply_terminator_effect`. /// analyses should not implement this without implementing `apply_terminator_effect`.
fn apply_before_terminator_effect( fn apply_before_terminator_effect(
&self, &self,
_state: &mut BitSet<Self::Idx>, _state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>, _terminator: &mir::Terminator<'tcx>,
_location: Location, _location: Location,
) { ) {
@ -192,7 +143,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// edges. /// edges.
fn apply_call_return_effect( fn apply_call_return_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut Self::Domain,
block: BasicBlock, block: BasicBlock,
func: &mir::Operand<'tcx>, func: &mir::Operand<'tcx>,
args: &[mir::Operand<'tcx>], args: &[mir::Operand<'tcx>],
@ -207,7 +158,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// By default, no effects happen. /// By default, no effects happen.
fn apply_yield_resume_effect( fn apply_yield_resume_effect(
&self, &self,
_state: &mut BitSet<Self::Idx>, _state: &mut Self::Domain,
_resume_block: BasicBlock, _resume_block: BasicBlock,
_resume_place: mir::Place<'tcx>, _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. /// FIXME: This class of effects is not supported for backward dataflow analyses.
fn apply_discriminant_switch_effect( fn apply_discriminant_switch_effect(
&self, &self,
_state: &mut BitSet<Self::Idx>, _state: &mut Self::Domain,
_block: BasicBlock, _block: BasicBlock,
_enum_place: mir::Place<'tcx>, _enum_place: mir::Place<'tcx>,
_adt: &ty::AdtDef, _adt: &ty::AdtDef,
@ -264,6 +215,8 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// ///
/// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`. /// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`.
pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> { pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
type Idx: Idx;
/// See `Analysis::apply_statement_effect`. /// See `Analysis::apply_statement_effect`.
fn statement_effect( fn statement_effect(
&self, &self,
@ -332,10 +285,11 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
impl<A> Analysis<'tcx> for A impl<A> Analysis<'tcx> for A
where where
A: GenKillAnalysis<'tcx>, A: GenKillAnalysis<'tcx>,
A::Domain: GenKill<A::Idx> + BorrowMut<BitSet<A::Idx>>,
{ {
fn apply_statement_effect( fn apply_statement_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut A::Domain,
statement: &mir::Statement<'tcx>, statement: &mir::Statement<'tcx>,
location: Location, location: Location,
) { ) {
@ -344,7 +298,7 @@ where
fn apply_before_statement_effect( fn apply_before_statement_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut A::Domain,
statement: &mir::Statement<'tcx>, statement: &mir::Statement<'tcx>,
location: Location, location: Location,
) { ) {
@ -353,7 +307,7 @@ where
fn apply_terminator_effect( fn apply_terminator_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut A::Domain,
terminator: &mir::Terminator<'tcx>, terminator: &mir::Terminator<'tcx>,
location: Location, location: Location,
) { ) {
@ -362,7 +316,7 @@ where
fn apply_before_terminator_effect( fn apply_before_terminator_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut A::Domain,
terminator: &mir::Terminator<'tcx>, terminator: &mir::Terminator<'tcx>,
location: Location, location: Location,
) { ) {
@ -371,7 +325,7 @@ where
fn apply_call_return_effect( fn apply_call_return_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
func: &mir::Operand<'tcx>, func: &mir::Operand<'tcx>,
args: &[mir::Operand<'tcx>], args: &[mir::Operand<'tcx>],
@ -382,7 +336,7 @@ where
fn apply_yield_resume_effect( fn apply_yield_resume_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut A::Domain,
resume_block: BasicBlock, resume_block: BasicBlock,
resume_place: mir::Place<'tcx>, resume_place: mir::Place<'tcx>,
) { ) {
@ -391,7 +345,7 @@ where
fn apply_discriminant_switch_effect( fn apply_discriminant_switch_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
enum_place: mir::Place<'tcx>, enum_place: mir::Place<'tcx>,
adt: &ty::AdtDef, 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 /// applied multiple times efficiently. When there are multiple calls to `gen` and/or `kill` for
/// the same element, the most recent one takes precedence. /// the same element, the most recent one takes precedence.
#[derive(Clone)] #[derive(Clone)]
pub struct GenKillSet<T: Idx> { pub struct GenKillSet<T> {
gen: HybridBitSet<T>, gen: HybridBitSet<T>,
kill: 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>) { pub fn apply(&self, state: &mut BitSet<T>) {
state.union(&self.gen); state.union(&self.gen);
state.subtract(&self.kill); 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. // NOTE: DO NOT CHANGE VARIANT ORDER. The derived `Ord` impls rely on the current order.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Effect { pub enum Effect {

View File

@ -9,7 +9,6 @@ use rustc_middle::ty;
use rustc_span::DUMMY_SP; use rustc_span::DUMMY_SP;
use super::*; use super::*;
use crate::dataflow::BottomValue;
/// Creates a `mir::Body` with a few disconnected basic blocks. /// 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 /// 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. /// avoid colliding with the statement/terminator effects.
fn mock_entry_set(&self, bb: BasicBlock) -> BitSet<usize> { 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.insert(Self::BASIC_BLOCK_OFFSET + bb.index());
ret ret
} }
fn mock_entry_sets(&self) -> IndexVec<BasicBlock, BitSet<usize>> { 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()); let mut ret = IndexVec::from_elem(empty, &self.body.basic_blocks());
for (bb, _) in self.body.basic_blocks().iter_enumerated() { 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]`. /// would be `[102, 0, 1, 2, 3, 4]`.
fn expected_state_at_target(&self, target: SeekTarget) -> BitSet<usize> { fn expected_state_at_target(&self, target: SeekTarget) -> BitSet<usize> {
let block = target.block(); 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()); ret.insert(Self::BASIC_BLOCK_OFFSET + block.index());
let target = match target { 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> { impl<D: Direction> AnalysisDomain<'tcx> for MockAnalysis<'tcx, D> {
type Idx = usize; type Domain = BitSet<usize>;
type Direction = D; type Direction = D;
const NAME: &'static str = "mock"; const NAME: &'static str = "mock";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize { fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
Self::BASIC_BLOCK_OFFSET + body.basic_blocks().len() 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"); 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> { impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
fn apply_statement_effect( fn apply_statement_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>, _statement: &mir::Statement<'tcx>,
location: Location, location: Location,
) { ) {
@ -193,7 +188,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
fn apply_before_statement_effect( fn apply_before_statement_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>, _statement: &mir::Statement<'tcx>,
location: Location, location: Location,
) { ) {
@ -203,7 +198,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
fn apply_terminator_effect( fn apply_terminator_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>, _terminator: &mir::Terminator<'tcx>,
location: Location, location: Location,
) { ) {
@ -213,7 +208,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
fn apply_before_terminator_effect( fn apply_before_terminator_effect(
&self, &self,
state: &mut BitSet<Self::Idx>, state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>, _terminator: &mir::Terminator<'tcx>,
location: Location, location: Location,
) { ) {
@ -223,7 +218,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
fn apply_call_return_effect( fn apply_call_return_effect(
&self, &self,
_state: &mut BitSet<Self::Idx>, _state: &mut Self::Domain,
_block: BasicBlock, _block: BasicBlock,
_func: &mir::Operand<'tcx>, _func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>], _args: &[mir::Operand<'tcx>],

View File

@ -1,4 +1,3 @@
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::{self, BasicBlock, Location}; use rustc_middle::mir::{self, BasicBlock, Location};
use super::{Analysis, Direction, Results}; use super::{Analysis, Direction, Results};
@ -139,16 +138,16 @@ impl<'tcx, A> ResultsVisitable<'tcx> for Results<'tcx, A>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
{ {
type FlowState = BitSet<A::Idx>; type FlowState = A::Domain;
type Direction = A::Direction; type Direction = A::Direction;
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState { 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) { 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( fn reconstruct_before_statement_effect(
@ -217,11 +216,11 @@ macro_rules! impl_visitable {
$( $A: Analysis<'tcx, Direction = D>, )* $( $A: Analysis<'tcx, Direction = D>, )*
{ {
type 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 { fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
$T { $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, state: &mut Self::FlowState,
block: BasicBlock, 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( fn reconstruct_before_statement_effect(

View File

@ -5,9 +5,9 @@ use rustc_span::symbol::{sym, Symbol};
pub(crate) use self::drop_flag_effects::*; pub(crate) use self::drop_flag_effects::*;
pub use self::framework::{ pub use self::framework::{
visit_results, Analysis, AnalysisDomain, Backward, BorrowckFlowState, BorrowckResults, fmt, lattice, visit_results, Analysis, AnalysisDomain, Backward, BorrowckFlowState,
BottomValue, Engine, Forward, GenKill, GenKillAnalysis, Results, ResultsCursor, BorrowckResults, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice, Results,
ResultsRefCursor, ResultsVisitor, ResultsCursor, ResultsRefCursor, ResultsVisitor,
}; };
use self::move_paths::MoveData; use self::move_paths::MoveData;

View File

@ -14,6 +14,7 @@ Rust MIR: a lowered representation of Rust.
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(decl_macro)] #![feature(decl_macro)]
#![feature(drain_filter)] #![feature(drain_filter)]
#![feature(exact_size_is_empty)]
#![feature(exhaustive_patterns)] #![feature(exhaustive_patterns)]
#![feature(iter_order_by)] #![feature(iter_order_by)]
#![feature(never_type)] #![feature(never_type)]