Auto merge of #76044 - ecstatic-morse:dataflow-lattice, r=oli-obk

Support dataflow problems on arbitrary lattices

This PR implements last of the proposed extensions I mentioned in the design meeting for the original dataflow refactor. It extends the current dataflow framework to work with arbitrary lattices, not just `BitSet`s. This is a prerequisite for dataflow-enabled MIR const-propagation. Personally, I am skeptical of the usefulness of doing const-propagation pre-monomorphization, since many useful constants only become known after monomorphization (e.g. `size_of::<T>()`) and users have a natural tendency to hand-optimize the rest. It's probably worth exprimenting with, however, and others have shown interest cc `@rust-lang/wg-mir-opt.`

The `Idx` associated type is moved from `AnalysisDomain` to `GenKillAnalysis` and replaced with an associated `Domain` type that must implement `JoinSemiLattice`. Like before, each `Analysis` defines the "bottom value" for its domain, but can no longer override the dataflow join operator. Analyses that want to use set intersection must now use the `lattice::Dual` newtype. `GenKillAnalysis` impls have an additional requirement that `Self::Domain: BorrowMut<BitSet<Self::Idx>>`, which effectively means that they must use `BitSet<Self::Idx>` or `lattice::Dual<BitSet<Self::Idx>>` as their domain.

Most of these changes were mechanical. However, because a `Domain` is no longer always a powerset of some index type, we can no longer use an `IndexVec<BasicBlock, GenKillSet<A::Idx>>>` to store cached block transfer functions. Instead, we use a boxed `dyn Fn` trait object. I discuss a few alternatives to the current approach in a commit message.

The majority of new lines of code are to preserve existing Graphviz diagrams for those unlucky enough to have to debug dataflow analyses. I find these diagrams incredibly useful when things are going wrong and considered regressing them unacceptable, especially the pretty-printing of `MovePathIndex`s, which are used in many dataflow analyses. This required a parallel `fmt` trait used only for printing dataflow domains, as well as a refactoring of the `graphviz` module now that we cannot expect the domain to be a `BitSet`. Some features did have to be removed, such as the gen/kill display mode (which I didn't use but existed to mirror the output of the old dataflow framework) and line wrapping. Since I had to rewrite much of it anyway, I took the opportunity to switch to a `Visitor` for printing dataflow state diffs instead of using cursors, which are error prone for code that must be generic over both forward and backward analyses. As a side-effect of this change, we no longer have quadratic behavior when writing graphviz diagrams for backward dataflow analyses.

r? `@pnkfelix`
This commit is contained in:
bors 2020-09-07 21:29:43 +00:00
commit 0e2c1281e9
23 changed files with 974 additions and 705 deletions

View File

@ -3756,6 +3756,7 @@ dependencies = [
"itertools 0.8.2",
"log_settings",
"polonius-engine",
"regex",
"rustc_apfloat",
"rustc_ast",
"rustc_attr",

View File

@ -28,13 +28,20 @@ pub const WORD_BITS: usize = WORD_BYTES * 8;
/// will panic if the bitsets have differing domain sizes.
///
/// [`GrowableBitSet`]: struct.GrowableBitSet.html
#[derive(Clone, Eq, PartialEq, Decodable, Encodable)]
pub struct BitSet<T: Idx> {
#[derive(Eq, PartialEq, Decodable, Encodable)]
pub struct BitSet<T> {
domain_size: usize,
words: Vec<Word>,
marker: PhantomData<T>,
}
impl<T> BitSet<T> {
/// Gets the domain size.
pub fn domain_size(&self) -> usize {
self.domain_size
}
}
impl<T: Idx> BitSet<T> {
/// Creates a new, empty bitset with a given `domain_size`.
#[inline]
@ -52,11 +59,6 @@ impl<T: Idx> BitSet<T> {
result
}
/// Gets the domain size.
pub fn domain_size(&self) -> usize {
self.domain_size
}
/// Clear all elements.
#[inline]
pub fn clear(&mut self) {
@ -75,12 +77,6 @@ impl<T: Idx> BitSet<T> {
}
}
/// Efficiently overwrite `self` with `other`.
pub fn overwrite(&mut self, other: &BitSet<T>) {
assert!(self.domain_size == other.domain_size);
self.words.clone_from_slice(&other.words);
}
/// Count the number of set bits in the set.
pub fn count(&self) -> usize {
self.words.iter().map(|e| e.count_ones() as usize).sum()
@ -243,6 +239,21 @@ impl<T: Idx> SubtractFromBitSet<T> for BitSet<T> {
}
}
impl<T> Clone for BitSet<T> {
fn clone(&self) -> Self {
BitSet { domain_size: self.domain_size, words: self.words.clone(), marker: PhantomData }
}
fn clone_from(&mut self, from: &Self) {
if self.domain_size != from.domain_size {
self.words.resize(from.domain_size, 0);
self.domain_size = from.domain_size;
}
self.words.copy_from_slice(&from.words);
}
}
impl<T: Idx> fmt::Debug for BitSet<T> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
w.debug_list().entries(self.iter()).finish()
@ -363,7 +374,7 @@ const SPARSE_MAX: usize = 8;
///
/// This type is used by `HybridBitSet`; do not use directly.
#[derive(Clone, Debug)]
pub struct SparseBitSet<T: Idx> {
pub struct SparseBitSet<T> {
domain_size: usize,
elems: ArrayVec<[T; SPARSE_MAX]>,
}
@ -464,18 +475,27 @@ impl<T: Idx> SubtractFromBitSet<T> for SparseBitSet<T> {
/// All operations that involve an element will panic if the element is equal
/// to or greater than the domain size. All operations that involve two bitsets
/// will panic if the bitsets have differing domain sizes.
#[derive(Clone, Debug)]
pub enum HybridBitSet<T: Idx> {
#[derive(Clone)]
pub enum HybridBitSet<T> {
Sparse(SparseBitSet<T>),
Dense(BitSet<T>),
}
impl<T: Idx> fmt::Debug for HybridBitSet<T> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Sparse(b) => b.fmt(w),
Self::Dense(b) => b.fmt(w),
}
}
}
impl<T: Idx> HybridBitSet<T> {
pub fn new_empty(domain_size: usize) -> Self {
HybridBitSet::Sparse(SparseBitSet::new_empty(domain_size))
}
fn domain_size(&self) -> usize {
pub fn domain_size(&self) -> usize {
match self {
HybridBitSet::Sparse(sparse) => sparse.domain_size,
HybridBitSet::Dense(dense) => dense.domain_size,

View File

@ -14,6 +14,7 @@ itertools = "0.8"
tracing = "0.1"
log_settings = "0.1.1"
polonius-engine = "0.12.0"
regex = "1"
rustc_middle = { path = "../rustc_middle" }
rustc_attr = { path = "../rustc_attr" }
rustc_data_structures = { path = "../rustc_data_structures" }

View File

@ -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)]
@ -68,23 +68,21 @@ where
self.body
}
/// Returns the `Analysis` used to generate the underlying results.
/// Returns the underlying `Results`.
pub fn results(&self) -> &Results<'tcx, A> {
&self.results.borrow()
}
/// Returns the `Analysis` used to generate the underlying `Results`.
pub fn analysis(&self) -> &A {
&self.results.borrow().analysis
}
/// 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.
@ -94,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;
}
@ -202,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,

View File

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

View File

@ -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,27 @@ 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>>>,
//
// FIXME(ecstaticmorse): This boxed `Fn` trait object is invoked inside a tight loop for
// gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
// performance in practice. I've tried a few ways to avoid this, but they have downsides. See
// the message for the commit that added this FIXME for more information.
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 +118,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 +158,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 +190,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 +220,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 +238,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 +249,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 +267,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 +297,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() {

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.
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: &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.

View File

@ -0,0 +1,230 @@
//! Traits used to represent [lattices] for use as the domain of a dataflow analysis.
//!
//! # Overview
//!
//! The most common lattice is a powerset of some set `S`, ordered by [set inclusion]. The [Hasse
//! diagram] for the powerset of a set with two elements (`X` and `Y`) is shown below. Note that
//! distinct elements at the same height in a Hasse diagram (e.g. `{X}` and `{Y}`) are
//! *incomparable*, not equal.
//!
//! ```text
//! {X, Y} <- top
//! / \
//! {X} {Y}
//! \ /
//! {} <- bottom
//!
//! ```
//!
//! The defining characteristic of a lattice—the one that differentiates it from a [partially
//! ordered set][poset]—is the existence of a *unique* least upper and greatest lower bound for
//! every pair of elements. The lattice join operator (``) returns the least upper bound, and the
//! lattice meet operator (`∧`) returns the greatest lower bound. Types that implement one operator
//! but not the other are known as semilattices. Dataflow analysis only uses the join operator and
//! will work with any join-semilattice, but both should be specified when possible.
//!
//! ## `PartialOrd`
//!
//! Given that they represent partially ordered sets, you may be surprised that [`JoinSemiLattice`]
//! and [`MeetSemiLattice`] 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 newtype wrapper with a custom `PartialOrd` impl. The
//! only benefit would be the ability to check 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
//! [Hasse diagram]: https://en.wikipedia.org/wiki/Hasse_diagram
//! [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
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:
///
/// ```text
/// true
/// |
/// false
/// ```
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).
///
/// In other words:
/// (A₀, A₁, ..., Aₙ) (B₀, B₁, ..., Bₙ) = (A₀B₀, A₁B₁, ..., AₙBₙ)
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` represents the lattice formed by the powerset of all possible values of
/// the index type `T` ordered by inclusion. Equivalently, it is a tuple of "two-point" lattices,
/// one for each possible value of `T`.
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]:
///
/// ```text
/// top
/// / / \ \
/// all possible values of `T`
/// \ \ / /
/// bottom
/// ```
///
/// [Hasse diagram]: https://en.wikipedia.org/wiki/Hasse_diagram
#[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
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,69 +43,27 @@ 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> {
/// The type that holds the dataflow state at any given point in the program.
type Domain: Clone + JoinSemiLattice;
/// The direction of this analyis. Either `Forward` or `Backward`.
/// The direction of this analysis. Either `Forward` or `Backward`.
type Direction: Direction = Forward;
/// A descriptive name for this analysis. Used only for debugging.
@ -114,11 +72,10 @@ 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;
/// The initial value of the dataflow state upon entry to each basic block.
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.
/// Mutates the initial value of the dataflow state upon entry to the `START_BLOCK`.
///
/// For backward analyses, initial state besides the bottom value is not yet supported. Trying
/// to mutate the initial state will result in a panic.
@ -126,20 +83,30 @@ 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.
///
/// # Convergence
///
/// When implementing this trait directly (not via [`GenKillAnalysis`]), it's possible to choose a
/// transfer function such that the analysis does not reach fixpoint. To guarantee convergence,
/// your transfer functions must maintain the following invariant:
///
/// > If the dataflow state **before** some point in the program changes to be greater
/// than the prior state **before** that point, the dataflow state **after** that point must
/// also change to be greater than the prior state **after** that point.
///
/// This invariant guarantees that the dataflow state at a given point in the program increases
/// monotonically until fixpoint is reached. Note that this monotonicity requirement only applies
/// to the same point in the program at different points in time. The dataflow state at a given
/// point in the program may or may not be greater than the state at any preceding point.
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 +119,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 +133,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 +146,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 +159,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 +174,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 +189,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 +231,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 +301,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 +314,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 +323,7 @@ where
fn apply_terminator_effect(
&self,
state: &mut BitSet<Self::Idx>,
state: &mut A::Domain,
terminator: &mir::Terminator<'tcx>,
location: Location,
) {
@ -362,7 +332,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 +341,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 +352,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 +361,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 +420,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 +434,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 +462,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 {

View File

@ -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>],

View File

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

View File

@ -82,15 +82,15 @@ impl<K> AnalysisDomain<'tcx> for MaybeBorrowedLocals<K>
where
K: BorrowAnalysisKind<'tcx>,
{
type Idx = Local;
type Domain = BitSet<Local>;
const NAME: &'static str = K::ANALYSIS_NAME;
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls().len()
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = unborrowed
BitSet::new_empty(body.local_decls().len())
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
// No locals are aliased on function entry
}
}
@ -99,6 +99,8 @@ impl<K> GenKillAnalysis<'tcx> for MaybeBorrowedLocals<K>
where
K: BorrowAnalysisKind<'tcx>,
{
type Idx = Local;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -128,11 +130,6 @@ where
}
}
impl<K> BottomValue for MaybeBorrowedLocals<K> {
// bottom = unborrowed
const BOTTOM_VALUE: bool = false;
}
/// A `Visitor` that defines the transfer function for `MaybeBorrowedLocals`.
struct TransferFunction<'a, T, K> {
trans: &'a mut T,

View File

@ -8,9 +8,9 @@ use rustc_index::bit_set::BitSet;
use crate::borrow_check::{
places_conflict, BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, ToRegionVid,
};
use crate::dataflow::BottomValue;
use crate::dataflow::{self, GenKill};
use crate::dataflow::{self, fmt::DebugWithContext, GenKill};
use std::fmt;
use std::rc::Rc;
rustc_index::newtype_index! {
@ -227,25 +227,24 @@ impl<'a, 'tcx> Borrows<'a, 'tcx> {
}
impl<'tcx> dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
type Idx = BorrowIndex;
type Domain = BitSet<BorrowIndex>;
const NAME: &'static str = "borrows";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.borrow_set.len() * 2
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
// bottom = nothing is reserved or activated yet;
BitSet::new_empty(self.borrow_set.len() * 2)
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
// no borrows of code region_scopes have been taken prior to
// function execution, so this method has no effect.
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, idx: Self::Idx) -> std::io::Result<()> {
write!(w, "{:?}", self.location(idx))
}
}
impl<'tcx> dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
type Idx = BorrowIndex;
fn before_statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -344,7 +343,8 @@ impl<'tcx> dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
}
}
impl<'a, 'tcx> BottomValue for Borrows<'a, 'tcx> {
/// bottom = nothing is reserved or activated yet;
const BOTTOM_VALUE: bool = false;
impl DebugWithContext<Borrows<'_, '_>> for BorrowIndex {
fn fmt_with(&self, ctxt: &Borrows<'_, '_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", ctxt.location(*self))
}
}

View File

@ -2,7 +2,7 @@
//!
//! A local will be maybe initialized if *any* projections of that local might be initialized.
use crate::dataflow::{self, BottomValue, GenKill};
use crate::dataflow::{self, GenKill};
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{PlaceContext, Visitor};
@ -10,21 +10,17 @@ use rustc_middle::mir::{self, BasicBlock, Local, Location};
pub struct MaybeInitializedLocals;
impl BottomValue for MaybeInitializedLocals {
/// bottom = uninit
const BOTTOM_VALUE: bool = false;
}
impl dataflow::AnalysisDomain<'tcx> for MaybeInitializedLocals {
type Idx = Local;
type Domain = BitSet<Local>;
const NAME: &'static str = "maybe_init_locals";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = uninit
BitSet::new_empty(body.local_decls.len())
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, entry_set: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, body: &mir::Body<'tcx>, entry_set: &mut Self::Domain) {
// Function arguments are initialized to begin with.
for arg in body.args_iter() {
entry_set.insert(arg);
@ -33,6 +29,8 @@ impl dataflow::AnalysisDomain<'tcx> for MaybeInitializedLocals {
}
impl dataflow::GenKillAnalysis<'tcx> for MaybeInitializedLocals {
type Idx = Local;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,

View File

@ -2,7 +2,7 @@ use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::{self, Local, Location};
use crate::dataflow::{AnalysisDomain, Backward, BottomValue, GenKill, GenKillAnalysis};
use crate::dataflow::{AnalysisDomain, Backward, GenKill, GenKillAnalysis};
/// A [live-variable dataflow analysis][liveness].
///
@ -22,27 +22,25 @@ impl MaybeLiveLocals {
}
}
impl BottomValue for MaybeLiveLocals {
// bottom = not live
const BOTTOM_VALUE: bool = false;
}
impl AnalysisDomain<'tcx> for MaybeLiveLocals {
type Idx = Local;
type Domain = BitSet<Local>;
type Direction = Backward;
const NAME: &'static str = "liveness";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = not live
BitSet::new_empty(body.local_decls.len())
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
// No variables are live until we observe a use
}
}
impl GenKillAnalysis<'tcx> for MaybeLiveLocals {
type Idx = Local;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,

View File

@ -13,7 +13,7 @@ use super::MoveDataParamEnv;
use crate::util::elaborate_drops::DropFlagState;
use super::move_paths::{HasMoveData, InitIndex, InitKind, MoveData, MovePathIndex};
use super::{AnalysisDomain, BottomValue, GenKill, GenKillAnalysis};
use super::{lattice, AnalysisDomain, GenKill, GenKillAnalysis};
use super::drop_flag_effects_for_function_entry;
use super::drop_flag_effects_for_location;
@ -290,27 +290,25 @@ impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
}
impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
type Idx = MovePathIndex;
type Domain = BitSet<MovePathIndex>;
const NAME: &'static str = "maybe_init";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().move_paths.len()
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
// bottom = uninitialized
BitSet::new_empty(self.move_data().move_paths.len())
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
assert!(s == DropFlagState::Present);
state.insert(path);
});
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, mpi: Self::Idx) -> std::io::Result<()> {
write!(w, "{}", self.move_data().move_paths[mpi])
}
}
impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
type Idx = MovePathIndex;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -376,18 +374,18 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
}
impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
type Idx = MovePathIndex;
type Domain = BitSet<MovePathIndex>;
const NAME: &'static str = "maybe_uninit";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().move_paths.len()
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
// bottom = initialized (start_block_effect counters this at outset)
BitSet::new_empty(self.move_data().move_paths.len())
}
// sets on_entry bits for Arg places
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
// set all bits to 1 (uninit) before gathering counterevidence
assert!(self.bits_per_block(body) == state.domain_size());
state.insert_all();
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
@ -395,13 +393,11 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
state.remove(path);
});
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, mpi: Self::Idx) -> std::io::Result<()> {
write!(w, "{}", self.move_data().move_paths[mpi])
}
}
impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
type Idx = MovePathIndex;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -471,30 +467,30 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
type Idx = MovePathIndex;
/// Use set intersection as the join operator.
type Domain = lattice::Dual<BitSet<MovePathIndex>>;
const NAME: &'static str = "definite_init";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().move_paths.len()
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
// bottom = initialized (start_block_effect counters this at outset)
lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len()))
}
// sets on_entry bits for Arg places
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
state.clear();
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
state.0.clear();
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
assert!(s == DropFlagState::Present);
state.insert(path);
state.0.insert(path);
});
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, mpi: Self::Idx) -> std::io::Result<()> {
write!(w, "{}", self.move_data().move_paths[mpi])
}
}
impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
type Idx = MovePathIndex;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -540,15 +536,16 @@ impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
}
impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
type Idx = InitIndex;
type Domain = BitSet<InitIndex>;
const NAME: &'static str = "ever_init";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().inits.len()
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
// bottom = no initialized variables by default
BitSet::new_empty(self.move_data().inits.len())
}
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) {
for arg_init in 0..body.arg_count {
state.insert(InitIndex::new(arg_init));
}
@ -556,6 +553,8 @@ impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
}
impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
type Idx = InitIndex;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -625,23 +624,3 @@ impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
}
}
}
impl<'a, 'tcx> BottomValue for MaybeInitializedPlaces<'a, 'tcx> {
/// bottom = uninitialized
const BOTTOM_VALUE: bool = false;
}
impl<'a, 'tcx> BottomValue for MaybeUninitializedPlaces<'a, 'tcx> {
/// bottom = initialized (start_block_effect counters this at outset)
const BOTTOM_VALUE: bool = false;
}
impl<'a, 'tcx> BottomValue for DefinitelyInitializedPlaces<'a, 'tcx> {
/// bottom = initialized (start_block_effect counters this at outset)
const BOTTOM_VALUE: bool = true;
}
impl<'a, 'tcx> BottomValue for EverInitializedPlaces<'a, 'tcx> {
/// bottom = no initialized variables by default
const BOTTOM_VALUE: bool = false;
}

View File

@ -1,6 +1,5 @@
pub use super::*;
use crate::dataflow::BottomValue;
use crate::dataflow::{self, GenKill, Results, ResultsRefCursor};
use crate::util::storage::AlwaysLiveLocals;
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
@ -19,15 +18,16 @@ impl MaybeStorageLive {
}
impl dataflow::AnalysisDomain<'tcx> for MaybeStorageLive {
type Idx = Local;
type Domain = BitSet<Local>;
const NAME: &'static str = "maybe_storage_live";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = dead
BitSet::new_empty(body.local_decls.len())
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
for local in self.always_live_locals.iter() {
on_entry.insert(local);
@ -40,6 +40,8 @@ impl dataflow::AnalysisDomain<'tcx> for MaybeStorageLive {
}
impl dataflow::GenKillAnalysis<'tcx> for MaybeStorageLive {
type Idx = Local;
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -74,11 +76,6 @@ impl dataflow::GenKillAnalysis<'tcx> for MaybeStorageLive {
}
}
impl BottomValue for MaybeStorageLive {
/// bottom = dead
const BOTTOM_VALUE: bool = false;
}
type BorrowedLocalsResults<'a, 'tcx> = ResultsRefCursor<'a, 'a, 'tcx, MaybeBorrowedLocals>;
/// Dataflow analysis that determines whether each local requires storage at a
@ -101,15 +98,16 @@ impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
}
impl<'mir, 'tcx> dataflow::AnalysisDomain<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
type Idx = Local;
type Domain = BitSet<Local>;
const NAME: &'static str = "requires_storage";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = dead
BitSet::new_empty(body.local_decls.len())
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
// The resume argument is live on function entry (we don't care about
// the `self` argument)
for arg in body.args_iter().skip(1) {
@ -119,6 +117,8 @@ impl<'mir, 'tcx> dataflow::AnalysisDomain<'tcx> for MaybeRequiresStorage<'mir, '
}
impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
type Idx = Local;
fn before_statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
@ -285,11 +285,6 @@ impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
}
}
impl<'mir, 'tcx> BottomValue for MaybeRequiresStorage<'mir, 'tcx> {
/// bottom = dead
const BOTTOM_VALUE: bool = false;
}
struct MoveVisitor<'a, 'mir, 'tcx, T> {
borrowed_locals: &'a RefCell<BorrowedLocalsResults<'mir, 'tcx>>,
trans: &'a mut T,

View File

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

View File

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

View File

@ -165,23 +165,19 @@ where
}
}
impl<Q> dataflow::BottomValue for FlowSensitiveAnalysis<'_, '_, '_, Q> {
const BOTTOM_VALUE: bool = false;
}
impl<Q> dataflow::AnalysisDomain<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
where
Q: Qualif,
{
type Idx = Local;
type Domain = BitSet<Local>;
const NAME: &'static str = Q::ANALYSIS_NAME;
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
BitSet::new_empty(body.local_decls.len())
}
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) {
self.transfer_function(state).initialize_state();
}
}
@ -192,7 +188,7 @@ where
{
fn apply_statement_effect(
&self,
state: &mut BitSet<Self::Idx>,
state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
) {
@ -201,7 +197,7 @@ where
fn apply_terminator_effect(
&self,
state: &mut BitSet<Self::Idx>,
state: &mut Self::Domain,
terminator: &mir::Terminator<'tcx>,
location: Location,
) {
@ -210,7 +206,7 @@ where
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>],

View File

@ -1,4 +1,6 @@
use rustc_ast as ast;
use std::borrow::Borrow;
use rustc_ast::ast;
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
@ -16,7 +18,7 @@ use crate::dataflow::impls::{
use crate::dataflow::move_paths::{HasMoveData, MoveData};
use crate::dataflow::move_paths::{LookupResult, MovePathIndex};
use crate::dataflow::MoveDataParamEnv;
use crate::dataflow::{Analysis, Results, ResultsCursor};
use crate::dataflow::{Analysis, JoinSemiLattice, Results, ResultsCursor};
pub struct SanityCheck;
@ -248,25 +250,26 @@ pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
&self,
tcx: TyCtxt<'tcx>,
place: mir::Place<'tcx>,
flow_state: &BitSet<Self::Idx>,
flow_state: &Self::Domain,
call: PeekCall,
);
}
impl<'tcx, A> RustcPeekAt<'tcx> for A
impl<'tcx, A, D> RustcPeekAt<'tcx> for A
where
A: Analysis<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
D: JoinSemiLattice + Clone + Borrow<BitSet<MovePathIndex>>,
{
fn peek_at(
&self,
tcx: TyCtxt<'tcx>,
place: mir::Place<'tcx>,
flow_state: &BitSet<Self::Idx>,
flow_state: &Self::Domain,
call: PeekCall,
) {
match self.move_data().rev_lookup.find(place.as_ref()) {
LookupResult::Exact(peek_mpi) => {
let bit_state = flow_state.contains(peek_mpi);
let bit_state = flow_state.borrow().contains(peek_mpi);
debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
if !bit_state {
tcx.sess.span_err(call.span, "rustc_peek: bit not set");

View File

@ -14,7 +14,6 @@ use rustc_middle::mir::{
visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _},
};
use rustc_middle::ty::{self, fold::TypeVisitor, Ty};
use rustc_mir::dataflow::BottomValue;
use rustc_mir::dataflow::{Analysis, AnalysisDomain, GenKill, GenKillAnalysis, ResultsCursor};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::{BytePos, Span};
@ -411,14 +410,15 @@ impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
struct MaybeStorageLive;
impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
type Idx = mir::Local;
type Domain = BitSet<mir::Local>;
const NAME: &'static str = "maybe_storage_live";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = dead
BitSet::new_empty(body.local_decls.len())
}
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) {
for arg in body.args_iter() {
state.insert(arg);
}
@ -426,6 +426,8 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
}
impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
type Idx = mir::Local;
fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
match stmt.kind {
mir::StatementKind::StorageLive(l) => trans.gen(l),
@ -454,11 +456,6 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
}
}
impl BottomValue for MaybeStorageLive {
/// bottom = dead
const BOTTOM_VALUE: bool = false;
}
/// Collects the possible borrowers of each local.
/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
/// possible borrowers of `a`.