mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-31 14:31:55 +00:00
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:
commit
0e2c1281e9
@ -3756,6 +3756,7 @@ dependencies = [
|
||||
"itertools 0.8.2",
|
||||
"log_settings",
|
||||
"polonius-engine",
|
||||
"regex",
|
||||
"rustc_apfloat",
|
||||
"rustc_ast",
|
||||
"rustc_attr",
|
||||
|
@ -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,
|
||||
|
@ -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" }
|
||||
|
@ -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,
|
||||
|
@ -18,7 +18,7 @@ pub trait Direction {
|
||||
/// `effects.start()` must precede or equal `effects.end()` in this direction.
|
||||
fn apply_effects_in_range<A>(
|
||||
analysis: &A,
|
||||
state: &mut BitSet<A::Idx>,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
effects: RangeInclusive<EffectIndex>,
|
||||
@ -27,7 +27,7 @@ pub trait Direction {
|
||||
|
||||
fn apply_effects_in_block<A>(
|
||||
analysis: &A,
|
||||
state: &mut BitSet<A::Idx>,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
) where
|
||||
@ -55,9 +55,9 @@ pub trait Direction {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &mir::Body<'tcx>,
|
||||
dead_unwinds: Option<&BitSet<BasicBlock>>,
|
||||
exit_state: &mut BitSet<A::Idx>,
|
||||
exit_state: &mut A::Domain,
|
||||
block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||
propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
|
||||
propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||
) where
|
||||
A: Analysis<'tcx>;
|
||||
}
|
||||
@ -72,7 +72,7 @@ impl Direction for Backward {
|
||||
|
||||
fn apply_effects_in_block<A>(
|
||||
analysis: &A,
|
||||
state: &mut BitSet<A::Idx>,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
) where
|
||||
@ -112,7 +112,7 @@ impl Direction for Backward {
|
||||
|
||||
fn apply_effects_in_range<A>(
|
||||
analysis: &A,
|
||||
state: &mut BitSet<A::Idx>,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
effects: RangeInclusive<EffectIndex>,
|
||||
@ -224,9 +224,9 @@ impl Direction for Backward {
|
||||
_tcx: TyCtxt<'tcx>,
|
||||
body: &mir::Body<'tcx>,
|
||||
dead_unwinds: Option<&BitSet<BasicBlock>>,
|
||||
exit_state: &mut BitSet<A::Idx>,
|
||||
exit_state: &mut A::Domain,
|
||||
(bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||
mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
|
||||
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||
) where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
@ -281,7 +281,7 @@ impl Direction for Forward {
|
||||
|
||||
fn apply_effects_in_block<A>(
|
||||
analysis: &A,
|
||||
state: &mut BitSet<A::Idx>,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
) where
|
||||
@ -321,7 +321,7 @@ impl Direction for Forward {
|
||||
|
||||
fn apply_effects_in_range<A>(
|
||||
analysis: &A,
|
||||
state: &mut BitSet<A::Idx>,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
effects: RangeInclusive<EffectIndex>,
|
||||
@ -428,9 +428,9 @@ impl Direction for Forward {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &mir::Body<'tcx>,
|
||||
dead_unwinds: Option<&BitSet<BasicBlock>>,
|
||||
exit_state: &mut BitSet<A::Idx>,
|
||||
exit_state: &mut A::Domain,
|
||||
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||
mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
|
||||
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||
) where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
@ -499,7 +499,7 @@ impl Direction for Forward {
|
||||
// MIR building adds discriminants to the `values` array in the same order as they
|
||||
// are yielded by `AdtDef::discriminants`. We rely on this to match each
|
||||
// discriminant in `values` to its corresponding variant in linear time.
|
||||
let mut tmp = BitSet::new_empty(exit_state.domain_size());
|
||||
let mut tmp = analysis.bottom_value(body);
|
||||
let mut discriminants = enum_def.discriminants(tcx);
|
||||
for (value, target) in values.iter().zip(targets.iter().copied()) {
|
||||
let (variant_idx, _) =
|
||||
@ -508,7 +508,7 @@ impl Direction for Forward {
|
||||
from that of `SwitchInt::values`",
|
||||
);
|
||||
|
||||
tmp.overwrite(exit_state);
|
||||
tmp.clone_from(exit_state);
|
||||
analysis.apply_discriminant_switch_effect(
|
||||
&mut tmp,
|
||||
bb,
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! A solver for dataflow problems.
|
||||
|
||||
use std::borrow::BorrowMut;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
@ -9,14 +10,16 @@ use rustc_data_structures::work_queue::WorkQueue;
|
||||
use rustc_graphviz as dot;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_index::vec::{Idx, IndexVec};
|
||||
use rustc_middle::mir::{self, traversal, BasicBlock};
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
|
||||
use super::fmt::DebugWithContext;
|
||||
use super::graphviz;
|
||||
use super::{
|
||||
visit_results, Analysis, Direction, GenKillAnalysis, GenKillSet, ResultsCursor, ResultsVisitor,
|
||||
visit_results, Analysis, Direction, GenKill, GenKillAnalysis, GenKillSet, JoinSemiLattice,
|
||||
ResultsCursor, ResultsVisitor,
|
||||
};
|
||||
use crate::util::pretty::dump_enabled;
|
||||
|
||||
@ -26,7 +29,7 @@ where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
pub analysis: A,
|
||||
pub(super) entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
|
||||
pub(super) entry_sets: IndexVec<BasicBlock, A::Domain>,
|
||||
}
|
||||
|
||||
impl<A> Results<'tcx, A>
|
||||
@ -39,7 +42,7 @@ where
|
||||
}
|
||||
|
||||
/// Gets the dataflow state for the given block.
|
||||
pub fn entry_set_for_block(&self, block: BasicBlock) -> &BitSet<A::Idx> {
|
||||
pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain {
|
||||
&self.entry_sets[block]
|
||||
}
|
||||
|
||||
@ -47,7 +50,7 @@ where
|
||||
&self,
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
blocks: impl IntoIterator<Item = BasicBlock>,
|
||||
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
|
||||
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
|
||||
) {
|
||||
visit_results(body, blocks, self, vis)
|
||||
}
|
||||
@ -55,7 +58,7 @@ where
|
||||
pub fn visit_reachable_with(
|
||||
&self,
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
|
||||
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
|
||||
) {
|
||||
let blocks = mir::traversal::reachable(body);
|
||||
visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
|
||||
@ -64,7 +67,7 @@ where
|
||||
pub fn visit_in_rpo_with(
|
||||
&self,
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
|
||||
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
|
||||
) {
|
||||
let blocks = mir::traversal::reverse_postorder(body);
|
||||
visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
|
||||
@ -76,21 +79,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() {
|
||||
|
172
compiler/rustc_mir/src/dataflow/framework/fmt.rs
Normal file
172
compiler/rustc_mir/src/dataflow/framework/fmt.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -1,26 +1,40 @@
|
||||
//! A helpful diagram for debugging dataflow problems.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::borrow::Cow;
|
||||
use std::{io, ops, str};
|
||||
|
||||
use regex::Regex;
|
||||
use rustc_graphviz as dot;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_index::bit_set::{BitSet, HybridBitSet};
|
||||
use rustc_index::vec::{Idx, IndexVec};
|
||||
use rustc_middle::mir::{self, BasicBlock, Body, Location};
|
||||
|
||||
use super::{Analysis, Direction, GenKillSet, Results, ResultsRefCursor};
|
||||
use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
|
||||
use super::{Analysis, Direction, Results, ResultsRefCursor, ResultsVisitor};
|
||||
use crate::util::graphviz_safe_def_name;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum OutputStyle {
|
||||
AfterOnly,
|
||||
BeforeAndAfter,
|
||||
}
|
||||
|
||||
impl OutputStyle {
|
||||
fn num_state_columns(&self) -> usize {
|
||||
match self {
|
||||
Self::AfterOnly => 1,
|
||||
Self::BeforeAndAfter => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Formatter<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
body: &'a Body<'tcx>,
|
||||
def_id: DefId,
|
||||
|
||||
// This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
|
||||
block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
|
||||
results: &'a Results<'tcx, A>,
|
||||
style: OutputStyle,
|
||||
}
|
||||
|
||||
impl<A> Formatter<'a, 'tcx, A>
|
||||
@ -31,15 +45,9 @@ where
|
||||
body: &'a Body<'tcx>,
|
||||
def_id: DefId,
|
||||
results: &'a Results<'tcx, A>,
|
||||
state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
|
||||
style: OutputStyle,
|
||||
) -> Self {
|
||||
let block_formatter = BlockFormatter {
|
||||
bg: Background::Light,
|
||||
results: ResultsRefCursor::new(body, results),
|
||||
state_formatter,
|
||||
};
|
||||
|
||||
Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) }
|
||||
Formatter { body, def_id, results, style }
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +70,7 @@ fn dataflow_successors(body: &Body<'tcx>, bb: BasicBlock) -> Vec<CfgEdge> {
|
||||
impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
A::Domain: DebugWithContext<A>,
|
||||
{
|
||||
type Node = BasicBlock;
|
||||
type Edge = CfgEdge;
|
||||
@ -77,7 +86,13 @@ where
|
||||
|
||||
fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
|
||||
let mut label = Vec::new();
|
||||
self.block_formatter.borrow_mut().write_node_label(&mut label, self.body, *block).unwrap();
|
||||
let mut fmt = BlockFormatter {
|
||||
results: ResultsRefCursor::new(self.body, self.results),
|
||||
style: self.style,
|
||||
bg: Background::Light,
|
||||
};
|
||||
|
||||
fmt.write_node_label(&mut label, self.body, *block).unwrap();
|
||||
dot::LabelText::html(String::from_utf8(label).unwrap())
|
||||
}
|
||||
|
||||
@ -126,19 +141,16 @@ where
|
||||
{
|
||||
results: ResultsRefCursor<'a, 'a, 'tcx, A>,
|
||||
bg: Background,
|
||||
state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
|
||||
style: OutputStyle,
|
||||
}
|
||||
|
||||
impl<A> BlockFormatter<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
A::Domain: DebugWithContext<A>,
|
||||
{
|
||||
const HEADER_COLOR: &'static str = "#a0a0a0";
|
||||
|
||||
fn num_state_columns(&self) -> usize {
|
||||
std::cmp::max(1, self.state_formatter.column_names().len())
|
||||
}
|
||||
|
||||
fn toggle_background(&mut self) -> Background {
|
||||
let bg = self.bg;
|
||||
self.bg = !bg;
|
||||
@ -187,40 +199,30 @@ where
|
||||
write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
|
||||
|
||||
// A + B: Block header
|
||||
if self.state_formatter.column_names().is_empty() {
|
||||
self.write_block_header_simple(w, block)?;
|
||||
} else {
|
||||
self.write_block_header_with_state_columns(w, block)?;
|
||||
match self.style {
|
||||
OutputStyle::AfterOnly => self.write_block_header_simple(w, block)?,
|
||||
OutputStyle::BeforeAndAfter => {
|
||||
self.write_block_header_with_state_columns(w, block, &["BEFORE", "AFTER"])?
|
||||
}
|
||||
}
|
||||
|
||||
// C: State at start of block
|
||||
self.bg = Background::Light;
|
||||
self.results.seek_to_block_start(block);
|
||||
let block_entry_state = self.results.get().clone();
|
||||
|
||||
let block_start_state = self.results.get().clone();
|
||||
self.write_row_with_full_state(w, "", "(on start)")?;
|
||||
|
||||
// D: Statement transfer functions
|
||||
for (i, statement) in body[block].statements.iter().enumerate() {
|
||||
let location = Location { block, statement_index: i };
|
||||
let statement_str = format!("{:?}", statement);
|
||||
self.write_row_for_location(w, &i.to_string(), &statement_str, location)?;
|
||||
}
|
||||
|
||||
// E: Terminator transfer function
|
||||
let terminator = body[block].terminator();
|
||||
let terminator_loc = body.terminator_loc(block);
|
||||
let mut terminator_str = String::new();
|
||||
terminator.kind.fmt_head(&mut terminator_str).unwrap();
|
||||
|
||||
self.write_row_for_location(w, "T", &terminator_str, terminator_loc)?;
|
||||
// D + E: Statement and terminator transfer functions
|
||||
self.write_statements_and_terminator(w, body, block)?;
|
||||
|
||||
// F: State at end of block
|
||||
|
||||
let terminator = body[block].terminator();
|
||||
|
||||
// Write the full dataflow state immediately after the terminator if it differs from the
|
||||
// state at block entry.
|
||||
self.results.seek_to_block_end(block);
|
||||
if self.results.get() != &block_entry_state || A::Direction::is_backward() {
|
||||
if self.results.get() != &block_start_state || A::Direction::is_backward() {
|
||||
let after_terminator_name = match terminator.kind {
|
||||
mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
|
||||
_ => "(on end)",
|
||||
@ -229,8 +231,11 @@ where
|
||||
self.write_row_with_full_state(w, "", after_terminator_name)?;
|
||||
}
|
||||
|
||||
// Write any changes caused by terminator-specific effects
|
||||
let num_state_columns = self.num_state_columns();
|
||||
// Write any changes caused by terminator-specific effects.
|
||||
//
|
||||
// FIXME: These should really be printed as part of each outgoing edge rather than the node
|
||||
// for the basic block itself. That way, we could display terminator-specific effects for
|
||||
// backward dataflow analyses as well as effects for `SwitchInt` terminators.
|
||||
match terminator.kind {
|
||||
mir::TerminatorKind::Call {
|
||||
destination: Some((return_place, _)),
|
||||
@ -239,44 +244,43 @@ where
|
||||
..
|
||||
} => {
|
||||
self.write_row(w, "", "(on successful return)", |this, w, fmt| {
|
||||
write!(
|
||||
w,
|
||||
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
|
||||
colspan = num_state_columns,
|
||||
fmt = fmt,
|
||||
)?;
|
||||
|
||||
let state_on_unwind = this.results.get().clone();
|
||||
this.results.apply_custom_effect(|analysis, state| {
|
||||
analysis.apply_call_return_effect(state, block, func, args, return_place);
|
||||
});
|
||||
|
||||
write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
|
||||
write!(w, "</td>")
|
||||
write!(
|
||||
w,
|
||||
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
|
||||
colspan = this.style.num_state_columns(),
|
||||
fmt = fmt,
|
||||
diff = diff_pretty(
|
||||
this.results.get(),
|
||||
&state_on_unwind,
|
||||
this.results.analysis()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
|
||||
self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
|
||||
write!(
|
||||
w,
|
||||
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
|
||||
colspan = num_state_columns,
|
||||
fmt = fmt,
|
||||
)?;
|
||||
|
||||
let state_on_generator_drop = this.results.get().clone();
|
||||
this.results.apply_custom_effect(|analysis, state| {
|
||||
analysis.apply_yield_resume_effect(state, resume, resume_arg);
|
||||
});
|
||||
|
||||
write_diff(
|
||||
write!(
|
||||
w,
|
||||
this.results.analysis(),
|
||||
&state_on_generator_drop,
|
||||
this.results.get(),
|
||||
)?;
|
||||
write!(w, "</td>")
|
||||
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
|
||||
colspan = this.style.num_state_columns(),
|
||||
fmt = fmt,
|
||||
diff = diff_pretty(
|
||||
this.results.get(),
|
||||
&state_on_generator_drop,
|
||||
this.results.analysis()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
@ -322,6 +326,7 @@ where
|
||||
&mut self,
|
||||
w: &mut impl io::Write,
|
||||
block: BasicBlock,
|
||||
state_column_names: &[&str],
|
||||
) -> io::Result<()> {
|
||||
// +------------------------------------+-------------+
|
||||
// A | bb4 | STATE |
|
||||
@ -330,8 +335,6 @@ where
|
||||
// +-+----------------------------------+------+------+
|
||||
// | | ... | | |
|
||||
|
||||
let state_column_names = self.state_formatter.column_names();
|
||||
|
||||
// A
|
||||
write!(
|
||||
w,
|
||||
@ -357,6 +360,56 @@ where
|
||||
write!(w, "</tr>")
|
||||
}
|
||||
|
||||
fn write_statements_and_terminator(
|
||||
&mut self,
|
||||
w: &mut impl io::Write,
|
||||
body: &'a Body<'tcx>,
|
||||
block: BasicBlock,
|
||||
) -> io::Result<()> {
|
||||
let diffs = StateDiffCollector::run(body, block, self.results.results(), self.style);
|
||||
|
||||
let mut befores = diffs.before.map(|v| v.into_iter());
|
||||
let mut afters = diffs.after.into_iter();
|
||||
|
||||
let next_in_dataflow_order = |it: &mut std::vec::IntoIter<_>| {
|
||||
if A::Direction::is_forward() { it.next().unwrap() } else { it.next_back().unwrap() }
|
||||
};
|
||||
|
||||
for (i, statement) in body[block].statements.iter().enumerate() {
|
||||
let statement_str = format!("{:?}", statement);
|
||||
let index_str = format!("{}", i);
|
||||
|
||||
let after = next_in_dataflow_order(&mut afters);
|
||||
let before = befores.as_mut().map(next_in_dataflow_order);
|
||||
|
||||
self.write_row(w, &index_str, &statement_str, |_this, w, fmt| {
|
||||
if let Some(before) = before {
|
||||
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
|
||||
}
|
||||
|
||||
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
|
||||
})?;
|
||||
}
|
||||
|
||||
let after = next_in_dataflow_order(&mut afters);
|
||||
let before = befores.as_mut().map(next_in_dataflow_order);
|
||||
|
||||
assert!(afters.is_empty());
|
||||
assert!(befores.as_ref().map_or(true, ExactSizeIterator::is_empty));
|
||||
|
||||
let terminator = body[block].terminator();
|
||||
let mut terminator_str = String::new();
|
||||
terminator.kind.fmt_head(&mut terminator_str).unwrap();
|
||||
|
||||
self.write_row(w, "T", &terminator_str, |_this, w, fmt| {
|
||||
if let Some(before) = before {
|
||||
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = before)?;
|
||||
}
|
||||
|
||||
write!(w, r#"<td {fmt} align="left">{diff}</td>"#, fmt = fmt, diff = after)
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a row with the given index and MIR, using the function argument to fill in the
|
||||
/// "STATE" column(s).
|
||||
fn write_row<W: io::Write>(
|
||||
@ -397,319 +450,169 @@ where
|
||||
let state = this.results.get();
|
||||
let analysis = this.results.analysis();
|
||||
|
||||
// FIXME: The full state vector can be quite long. It would be nice to split on commas
|
||||
// and use some text wrapping algorithm.
|
||||
write!(
|
||||
w,
|
||||
r#"<td colspan="{colspan}" {fmt} align="left">{{"#,
|
||||
colspan = this.num_state_columns(),
|
||||
r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
|
||||
colspan = this.style.num_state_columns(),
|
||||
fmt = fmt,
|
||||
)?;
|
||||
pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?;
|
||||
write!(w, "}}</td>")
|
||||
})
|
||||
}
|
||||
|
||||
fn write_row_for_location(
|
||||
&mut self,
|
||||
w: &mut impl io::Write,
|
||||
i: &str,
|
||||
mir: &str,
|
||||
location: Location,
|
||||
) -> io::Result<()> {
|
||||
self.write_row(w, i, mir, |this, w, fmt| {
|
||||
this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location)
|
||||
state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls what gets printed under the `STATE` header.
|
||||
pub trait StateFormatter<'tcx, A>
|
||||
struct StateDiffCollector<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
/// The columns that will get printed under `STATE`.
|
||||
fn column_names(&self) -> &[&str];
|
||||
|
||||
fn write_state_for_location(
|
||||
&mut self,
|
||||
w: &mut dyn io::Write,
|
||||
fmt: &str,
|
||||
results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
|
||||
location: Location,
|
||||
) -> io::Result<()>;
|
||||
analysis: &'a A,
|
||||
prev_state: A::Domain,
|
||||
before: Option<Vec<String>>,
|
||||
after: Vec<String>,
|
||||
}
|
||||
|
||||
/// Prints a single column containing the state vector immediately *after* each statement.
|
||||
pub struct SimpleDiff<'a, 'tcx, A>
|
||||
impl<A> StateDiffCollector<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
A::Domain: DebugWithContext<A>,
|
||||
{
|
||||
prev_state: ResultsRefCursor<'a, 'a, 'tcx, A>,
|
||||
}
|
||||
|
||||
impl<A> SimpleDiff<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>) -> Self {
|
||||
SimpleDiff { prev_state: ResultsRefCursor::new(body, results) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> StateFormatter<'tcx, A> for SimpleDiff<'_, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
fn column_names(&self) -> &[&str] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn write_state_for_location(
|
||||
&mut self,
|
||||
mut w: &mut dyn io::Write,
|
||||
fmt: &str,
|
||||
results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
|
||||
location: Location,
|
||||
) -> io::Result<()> {
|
||||
if A::Direction::is_forward() {
|
||||
if location.statement_index == 0 {
|
||||
self.prev_state.seek_to_block_start(location.block);
|
||||
} else {
|
||||
self.prev_state.seek_after_primary_effect(Location {
|
||||
statement_index: location.statement_index - 1,
|
||||
..location
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if location == results.body().terminator_loc(location.block) {
|
||||
self.prev_state.seek_to_block_end(location.block);
|
||||
} else {
|
||||
self.prev_state.seek_after_primary_effect(location.successor_within_block());
|
||||
}
|
||||
}
|
||||
|
||||
write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
|
||||
results.seek_after_primary_effect(location);
|
||||
let curr_state = results.get();
|
||||
write_diff(&mut w, results.analysis(), self.prev_state.get(), curr_state)?;
|
||||
write!(w, "</td>")
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints two state columns, one containing only the "before" effect of each statement and one
|
||||
/// containing the full effect.
|
||||
pub struct TwoPhaseDiff<T: Idx> {
|
||||
prev_state: BitSet<T>,
|
||||
prev_loc: Location,
|
||||
}
|
||||
|
||||
impl<T: Idx> TwoPhaseDiff<T> {
|
||||
pub fn new(bits_per_block: usize) -> Self {
|
||||
TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
fn column_names(&self) -> &[&str] {
|
||||
&["BEFORE", " AFTER"]
|
||||
}
|
||||
|
||||
fn write_state_for_location(
|
||||
&mut self,
|
||||
mut w: &mut dyn io::Write,
|
||||
fmt: &str,
|
||||
results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
|
||||
location: Location,
|
||||
) -> io::Result<()> {
|
||||
if location.statement_index == 0 {
|
||||
results.seek_to_block_entry(location.block);
|
||||
self.prev_state.overwrite(results.get());
|
||||
} else {
|
||||
// Ensure that we are visiting statements in order, so `prev_state` is correct.
|
||||
assert_eq!(self.prev_loc.successor_within_block(), location);
|
||||
}
|
||||
|
||||
self.prev_loc = location;
|
||||
|
||||
// Before
|
||||
|
||||
write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
|
||||
results.seek_before_primary_effect(location);
|
||||
let curr_state = results.get();
|
||||
write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
|
||||
self.prev_state.overwrite(curr_state);
|
||||
write!(w, "</td>")?;
|
||||
|
||||
// After
|
||||
|
||||
write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
|
||||
results.seek_after_primary_effect(location);
|
||||
let curr_state = results.get();
|
||||
write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
|
||||
self.prev_state.overwrite(curr_state);
|
||||
write!(w, "</td>")
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the gen/kill set for the entire block.
|
||||
pub struct BlockTransferFunc<'a, 'tcx, T: Idx> {
|
||||
body: &'a mir::Body<'tcx>,
|
||||
trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
|
||||
}
|
||||
|
||||
impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
|
||||
pub fn new(
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
|
||||
fn run(
|
||||
body: &'a mir::Body<'tcx>,
|
||||
block: BasicBlock,
|
||||
results: &'a Results<'tcx, A>,
|
||||
style: OutputStyle,
|
||||
) -> Self {
|
||||
BlockTransferFunc { body, trans_for_block }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
fn column_names(&self) -> &[&str] {
|
||||
&["GEN", "KILL"]
|
||||
}
|
||||
|
||||
fn write_state_for_location(
|
||||
&mut self,
|
||||
mut w: &mut dyn io::Write,
|
||||
fmt: &str,
|
||||
results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
|
||||
location: Location,
|
||||
) -> io::Result<()> {
|
||||
// Only print a single row.
|
||||
if location.statement_index != 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let block_trans = &self.trans_for_block[location.block];
|
||||
let rowspan = self.body.basic_blocks()[location.block].statements.len();
|
||||
|
||||
for set in &[&block_trans.gen, &block_trans.kill] {
|
||||
write!(
|
||||
w,
|
||||
r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
|
||||
fmt = fmt,
|
||||
rowspan = rowspan
|
||||
)?;
|
||||
|
||||
pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
|
||||
write!(w, "</td>")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes two lines, one containing the added bits and one the removed bits.
|
||||
fn write_diff<A: Analysis<'tcx>>(
|
||||
w: &mut impl io::Write,
|
||||
analysis: &A,
|
||||
from: &BitSet<A::Idx>,
|
||||
to: &BitSet<A::Idx>,
|
||||
) -> io::Result<()> {
|
||||
assert_eq!(from.domain_size(), to.domain_size());
|
||||
let len = from.domain_size();
|
||||
|
||||
let mut set = HybridBitSet::new_empty(len);
|
||||
let mut clear = HybridBitSet::new_empty(len);
|
||||
|
||||
// FIXME: Implement a lazy iterator over the symmetric difference of two bitsets.
|
||||
for i in (0..len).map(A::Idx::new) {
|
||||
match (from.contains(i), to.contains(i)) {
|
||||
(false, true) => set.insert(i),
|
||||
(true, false) => clear.insert(i),
|
||||
_ => continue,
|
||||
let mut collector = StateDiffCollector {
|
||||
analysis: &results.analysis,
|
||||
prev_state: results.analysis.bottom_value(body),
|
||||
after: vec![],
|
||||
before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
|
||||
};
|
||||
}
|
||||
|
||||
if !set.is_empty() {
|
||||
write!(w, r#"<font color="darkgreen">+"#)?;
|
||||
pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?;
|
||||
write!(w, r#"</font>"#)?;
|
||||
results.visit_with(body, std::iter::once(block), &mut collector);
|
||||
collector
|
||||
}
|
||||
|
||||
if !set.is_empty() && !clear.is_empty() {
|
||||
write!(w, "{}", BR_LEFT)?;
|
||||
}
|
||||
|
||||
if !clear.is_empty() {
|
||||
write!(w, r#"<font color="red">-"#)?;
|
||||
pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?;
|
||||
write!(w, r#"</font>"#)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const BR_LEFT: &str = r#"<br align="left"/>"#;
|
||||
const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#;
|
||||
|
||||
/// Line break policy that breaks at 40 characters and starts the next line with a single space.
|
||||
const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 });
|
||||
|
||||
struct LineBreak {
|
||||
sequence: &'static str,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
/// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given
|
||||
/// separator (`sep`).
|
||||
///
|
||||
/// Optionally, it will break lines using the given character sequence (usually `<br/>`) and
|
||||
/// character limit.
|
||||
fn pretty_print_state_elems<A>(
|
||||
w: &mut impl io::Write,
|
||||
analysis: &A,
|
||||
elems: impl Iterator<Item = A::Idx>,
|
||||
sep: &str,
|
||||
line_break: Option<LineBreak>,
|
||||
) -> io::Result<bool>
|
||||
impl<A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
A::Domain: DebugWithContext<A>,
|
||||
{
|
||||
let sep_width = sep.chars().count();
|
||||
type FlowState = A::Domain;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut first = true;
|
||||
let mut curr_line_width = 0;
|
||||
let mut line_break_inserted = false;
|
||||
|
||||
for idx in elems {
|
||||
buf.clear();
|
||||
analysis.pretty_print_idx(&mut buf, idx)?;
|
||||
let idx_str =
|
||||
str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8");
|
||||
let escaped = dot::escape_html(idx_str);
|
||||
let escaped_width = escaped.chars().count();
|
||||
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
write!(w, "{}", sep)?;
|
||||
curr_line_width += sep_width;
|
||||
|
||||
if let Some(line_break) = &line_break {
|
||||
if curr_line_width + sep_width + escaped_width > line_break.limit {
|
||||
write!(w, "{}", line_break.sequence)?;
|
||||
line_break_inserted = true;
|
||||
curr_line_width = 0;
|
||||
}
|
||||
}
|
||||
fn visit_block_start(
|
||||
&mut self,
|
||||
state: &Self::FlowState,
|
||||
_block_data: &'mir mir::BasicBlockData<'tcx>,
|
||||
_block: BasicBlock,
|
||||
) {
|
||||
if A::Direction::is_forward() {
|
||||
self.prev_state.clone_from(state);
|
||||
}
|
||||
|
||||
write!(w, "{}", escaped)?;
|
||||
curr_line_width += escaped_width;
|
||||
}
|
||||
|
||||
Ok(line_break_inserted)
|
||||
fn visit_block_end(
|
||||
&mut self,
|
||||
state: &Self::FlowState,
|
||||
_block_data: &'mir mir::BasicBlockData<'tcx>,
|
||||
_block: BasicBlock,
|
||||
) {
|
||||
if A::Direction::is_backward() {
|
||||
self.prev_state.clone_from(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_statement_before_primary_effect(
|
||||
&mut self,
|
||||
state: &Self::FlowState,
|
||||
_statement: &'mir mir::Statement<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
if let Some(before) = self.before.as_mut() {
|
||||
before.push(diff_pretty(state, &self.prev_state, self.analysis));
|
||||
self.prev_state.clone_from(state)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_statement_after_primary_effect(
|
||||
&mut self,
|
||||
state: &Self::FlowState,
|
||||
_statement: &'mir mir::Statement<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
|
||||
self.prev_state.clone_from(state)
|
||||
}
|
||||
|
||||
fn visit_terminator_before_primary_effect(
|
||||
&mut self,
|
||||
state: &Self::FlowState,
|
||||
_terminator: &'mir mir::Terminator<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
if let Some(before) = self.before.as_mut() {
|
||||
before.push(diff_pretty(state, &self.prev_state, self.analysis));
|
||||
self.prev_state.clone_from(state)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator_after_primary_effect(
|
||||
&mut self,
|
||||
state: &Self::FlowState,
|
||||
_terminator: &'mir mir::Terminator<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
|
||||
self.prev_state.clone_from(state)
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
|
||||
where
|
||||
T: DebugWithContext<C>,
|
||||
{
|
||||
if new == old {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let re = Regex::new("\u{001f}([+-])").unwrap();
|
||||
|
||||
let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
|
||||
|
||||
// Replace newlines in the `Debug` output with `<br/>`
|
||||
let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
|
||||
|
||||
let mut inside_font_tag = false;
|
||||
let html_diff = re.replace_all(&raw_diff, |captures: ®ex::Captures<'_>| {
|
||||
let mut ret = String::new();
|
||||
if inside_font_tag {
|
||||
ret.push_str(r#"</font>"#);
|
||||
}
|
||||
|
||||
let tag = match &captures[1] {
|
||||
"+" => r#"<font color="darkgreen">+"#,
|
||||
"-" => r#"<font color="red">-"#,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
inside_font_tag = true;
|
||||
ret.push_str(tag);
|
||||
ret
|
||||
});
|
||||
|
||||
let mut html_diff = match html_diff {
|
||||
Cow::Borrowed(_) => return raw_diff,
|
||||
Cow::Owned(s) => s,
|
||||
};
|
||||
|
||||
if inside_font_tag {
|
||||
html_diff.push_str("</font>");
|
||||
}
|
||||
|
||||
html_diff
|
||||
}
|
||||
|
||||
/// The background color used for zebra-striping the table.
|
||||
|
230
compiler/rustc_mir/src/dataflow/framework/lattice.rs
Normal file
230
compiler/rustc_mir/src/dataflow/framework/lattice.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -9,7 +9,6 @@ use rustc_middle::ty;
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
use super::*;
|
||||
use crate::dataflow::BottomValue;
|
||||
|
||||
/// Creates a `mir::Body` with a few disconnected basic blocks.
|
||||
///
|
||||
@ -92,13 +91,13 @@ impl<D: Direction> MockAnalysis<'tcx, D> {
|
||||
/// The entry set for each `BasicBlock` is the ID of that block offset by a fixed amount to
|
||||
/// avoid colliding with the statement/terminator effects.
|
||||
fn mock_entry_set(&self, bb: BasicBlock) -> BitSet<usize> {
|
||||
let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
|
||||
let mut ret = self.bottom_value(self.body);
|
||||
ret.insert(Self::BASIC_BLOCK_OFFSET + bb.index());
|
||||
ret
|
||||
}
|
||||
|
||||
fn mock_entry_sets(&self) -> IndexVec<BasicBlock, BitSet<usize>> {
|
||||
let empty = BitSet::new_empty(self.bits_per_block(self.body));
|
||||
let empty = self.bottom_value(self.body);
|
||||
let mut ret = IndexVec::from_elem(empty, &self.body.basic_blocks());
|
||||
|
||||
for (bb, _) in self.body.basic_blocks().iter_enumerated() {
|
||||
@ -130,7 +129,7 @@ impl<D: Direction> MockAnalysis<'tcx, D> {
|
||||
/// would be `[102, 0, 1, 2, 3, 4]`.
|
||||
fn expected_state_at_target(&self, target: SeekTarget) -> BitSet<usize> {
|
||||
let block = target.block();
|
||||
let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
|
||||
let mut ret = self.bottom_value(self.body);
|
||||
ret.insert(Self::BASIC_BLOCK_OFFSET + block.index());
|
||||
|
||||
let target = match target {
|
||||
@ -161,21 +160,17 @@ impl<D: Direction> MockAnalysis<'tcx, D> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Direction> BottomValue for MockAnalysis<'tcx, D> {
|
||||
const BOTTOM_VALUE: bool = false;
|
||||
}
|
||||
|
||||
impl<D: Direction> AnalysisDomain<'tcx> for MockAnalysis<'tcx, D> {
|
||||
type Idx = usize;
|
||||
type Domain = BitSet<usize>;
|
||||
type Direction = D;
|
||||
|
||||
const NAME: &'static str = "mock";
|
||||
|
||||
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
|
||||
Self::BASIC_BLOCK_OFFSET + body.basic_blocks().len()
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
BitSet::new_empty(Self::BASIC_BLOCK_OFFSET + body.basic_blocks().len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
|
||||
unimplemented!("This is never called since `MockAnalysis` is never iterated to fixpoint");
|
||||
}
|
||||
}
|
||||
@ -183,7 +178,7 @@ impl<D: Direction> AnalysisDomain<'tcx> for MockAnalysis<'tcx, D> {
|
||||
impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
||||
fn apply_statement_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
@ -193,7 +188,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
||||
|
||||
fn apply_before_statement_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
@ -203,7 +198,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
||||
|
||||
fn apply_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
@ -213,7 +208,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
||||
|
||||
fn apply_before_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
state: &mut Self::Domain,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
@ -223,7 +218,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
||||
|
||||
fn apply_call_return_effect(
|
||||
&self,
|
||||
_state: &mut BitSet<Self::Idx>,
|
||||
_state: &mut Self::Domain,
|
||||
_block: BasicBlock,
|
||||
_func: &mir::Operand<'tcx>,
|
||||
_args: &[mir::Operand<'tcx>],
|
||||
|
@ -1,4 +1,3 @@
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::{self, BasicBlock, Location};
|
||||
|
||||
use super::{Analysis, Direction, Results};
|
||||
@ -139,16 +138,16 @@ impl<'tcx, A> ResultsVisitable<'tcx> for Results<'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
type FlowState = BitSet<A::Idx>;
|
||||
type FlowState = A::Domain;
|
||||
|
||||
type Direction = A::Direction;
|
||||
|
||||
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
|
||||
BitSet::new_empty(self.analysis.bits_per_block(body))
|
||||
self.analysis.bottom_value(body)
|
||||
}
|
||||
|
||||
fn reset_to_block_entry(&self, state: &mut Self::FlowState, block: BasicBlock) {
|
||||
state.overwrite(&self.entry_set_for_block(block));
|
||||
state.clone_from(&self.entry_set_for_block(block));
|
||||
}
|
||||
|
||||
fn reconstruct_before_statement_effect(
|
||||
@ -217,11 +216,11 @@ macro_rules! impl_visitable {
|
||||
$( $A: Analysis<'tcx, Direction = D>, )*
|
||||
{
|
||||
type Direction = D;
|
||||
type FlowState = $T<$( BitSet<$A::Idx> ),*>;
|
||||
type FlowState = $T<$( $A::Domain ),*>;
|
||||
|
||||
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
|
||||
$T {
|
||||
$( $field: BitSet::new_empty(self.$field.analysis.bits_per_block(body)) ),*
|
||||
$( $field: self.$field.analysis.bottom_value(body) ),*
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,7 +229,7 @@ macro_rules! impl_visitable {
|
||||
state: &mut Self::FlowState,
|
||||
block: BasicBlock,
|
||||
) {
|
||||
$( state.$field.overwrite(&self.$field.entry_set_for_block(block)); )*
|
||||
$( state.$field.clone_from(&self.$field.entry_set_for_block(block)); )*
|
||||
}
|
||||
|
||||
fn reconstruct_before_statement_effect(
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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>,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -5,9 +5,9 @@ use rustc_span::symbol::{sym, Symbol};
|
||||
|
||||
pub(crate) use self::drop_flag_effects::*;
|
||||
pub use self::framework::{
|
||||
visit_results, Analysis, AnalysisDomain, Backward, BorrowckFlowState, BorrowckResults,
|
||||
BottomValue, Engine, Forward, GenKill, GenKillAnalysis, Results, ResultsCursor,
|
||||
ResultsRefCursor, ResultsVisitor,
|
||||
fmt, lattice, visit_results, Analysis, AnalysisDomain, Backward, BorrowckFlowState,
|
||||
BorrowckResults, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice, Results,
|
||||
ResultsCursor, ResultsRefCursor, ResultsVisitor,
|
||||
};
|
||||
|
||||
use self::move_paths::MoveData;
|
||||
|
@ -14,6 +14,7 @@ Rust MIR: a lowered representation of Rust.
|
||||
#![feature(crate_visibility_modifier)]
|
||||
#![feature(decl_macro)]
|
||||
#![feature(drain_filter)]
|
||||
#![feature(exact_size_is_empty)]
|
||||
#![feature(exhaustive_patterns)]
|
||||
#![feature(iter_order_by)]
|
||||
#![feature(never_type)]
|
||||
|
@ -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>],
|
||||
|
@ -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");
|
||||
|
@ -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`.
|
||||
|
Loading…
Reference in New Issue
Block a user