]> git.lizzy.rs Git - rust.git/commitdiff
Extend dataflow framework to support arbitrary lattices
authorDylan MacKenzie <ecstaticmorse@gmail.com>
Fri, 28 Aug 2020 06:02:46 +0000 (23:02 -0700)
committerDylan MacKenzie <ecstaticmorse@gmail.com>
Sun, 30 Aug 2020 18:15:24 +0000 (11:15 -0700)
compiler/rustc_mir/src/dataflow/framework/cursor.rs
compiler/rustc_mir/src/dataflow/framework/direction.rs
compiler/rustc_mir/src/dataflow/framework/engine.rs
compiler/rustc_mir/src/dataflow/framework/fmt.rs [new file with mode: 0644]
compiler/rustc_mir/src/dataflow/framework/graphviz.rs
compiler/rustc_mir/src/dataflow/framework/lattice.rs [new file with mode: 0644]
compiler/rustc_mir/src/dataflow/framework/mod.rs
compiler/rustc_mir/src/dataflow/framework/tests.rs
compiler/rustc_mir/src/dataflow/framework/visitor.rs
compiler/rustc_mir/src/dataflow/mod.rs
compiler/rustc_mir/src/lib.rs

index 8ec0169d8c1b40b2e5b3f4b5937845a7b4a23a13..4942bed656cc6b251e756e71e1871cd1686db926 100644 (file)
@@ -4,6 +4,7 @@
 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 @@ pub struct ResultsCursor<'mir, 'tcx, A, R = Results<'tcx, A>>
 {
     body: &'mir mir::Body<'tcx>,
     results: R,
-    state: BitSet<A::Idx>,
+    state: A::Domain,
 
     pos: CursorPosition,
 
@@ -46,17 +47,16 @@ impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
 {
     /// Returns a new cursor that can inspect `results`.
     pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self {
-        let bits_per_block = results.borrow().entry_set_for_block(mir::START_BLOCK).domain_size();
-
+        let bottom_value = results.borrow().analysis.bottom_value(body);
         ResultsCursor {
             body,
             results,
 
-            // Initialize to an empty `BitSet` and set `state_needs_reset` to tell the cursor that
+            // Initialize to the `bottom_value` and set `state_needs_reset` to tell the cursor that
             // it needs to reset to block entry before the first seek. The cursor position is
             // immaterial.
             state_needs_reset: true,
-            state: BitSet::new_empty(bits_per_block),
+            state: bottom_value,
             pos: CursorPosition::block_entry(mir::START_BLOCK),
 
             #[cfg(debug_assertions)]
@@ -79,17 +79,10 @@ pub fn analysis(&self) -> &A {
     }
 
     /// Returns the dataflow state at the current location.
-    pub fn get(&self) -> &BitSet<A::Idx> {
+    pub fn get(&self) -> &A::Domain {
         &self.state
     }
 
-    /// Returns `true` if the dataflow state at the current location contains the given element.
-    ///
-    /// Shorthand for `self.get().contains(elem)`
-    pub fn contains(&self, elem: A::Idx) -> bool {
-        self.state.contains(elem)
-    }
-
     /// Resets the cursor to hold the entry set for the given basic block.
     ///
     /// For forward dataflow analyses, this is the dataflow state prior to the first statement.
@@ -99,7 +92,7 @@ pub(super) fn seek_to_block_entry(&mut self, block: BasicBlock) {
         #[cfg(debug_assertions)]
         assert!(self.reachable_blocks.contains(block));
 
-        self.state.overwrite(&self.results.borrow().entry_set_for_block(block));
+        self.state.clone_from(&self.results.borrow().entry_set_for_block(block));
         self.pos = CursorPosition::block_entry(block);
         self.state_needs_reset = false;
     }
@@ -207,12 +200,23 @@ fn seek_after(&mut self, target: Location, effect: Effect) {
     ///
     /// 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,
index 4512ae96c0833f24d7af72180987e40b10d58fe0..94d299bd088ece1b9be0e510d54dfa12fa1c7e8a 100644 (file)
@@ -18,7 +18,7 @@ fn is_backward() -> bool {
     /// `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 @@ fn apply_effects_in_range<A>(
 
     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 @@ fn join_state_into_successors_of<A>(
         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 @@ fn is_forward() -> bool {
 
     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 @@ fn gen_kill_effects_in_block<A>(
 
     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 @@ fn join_state_into_successors_of<A>(
         _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 @@ fn is_forward() -> bool {
 
     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 @@ fn gen_kill_effects_in_block<A>(
 
     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 @@ fn join_state_into_successors_of<A>(
         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 @@ fn join_state_into_successors_of<A>(
                         // 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 @@ fn join_state_into_successors_of<A>(
                                          from that of `SwitchInt::values`",
                                 );
 
-                            tmp.overwrite(exit_state);
+                            tmp.clone_from(exit_state);
                             analysis.apply_discriminant_switch_effect(
                                 &mut tmp,
                                 bb,
index b703852b1def7a8af14a1911dd2d67c57c4d63b6..07f5ab5e7bd34d4a6091b8bf78cb3f3a1fee3d83 100644 (file)
@@ -1,5 +1,6 @@
 //! A solver for dataflow problems.
 
+use std::borrow::BorrowMut;
 use std::ffi::OsString;
 use std::fs;
 use std::path::PathBuf;
 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 @@ pub struct Results<'tcx, A>
     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 @@ pub fn into_results_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'
     }
 
     /// 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 @@ pub fn visit_with(
         &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 @@ pub fn visit_with(
     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 @@ pub fn visit_reachable_with(
     pub fn visit_in_rpo_with(
         &self,
         body: &'mir mir::Body<'tcx>,
-        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
+        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
     ) {
         let blocks = mir::traversal::reverse_postorder(body);
         visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
@@ -76,21 +79,22 @@ pub struct Engine<'a, 'tcx, A>
 where
     A: Analysis<'tcx>,
 {
-    bits_per_block: usize,
     tcx: TyCtxt<'tcx>,
     body: &'a mir::Body<'tcx>,
     def_id: DefId,
     dead_unwinds: Option<&'a BitSet<BasicBlock>>,
-    entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
+    entry_sets: IndexVec<BasicBlock, A::Domain>,
     analysis: A,
 
     /// Cached, cumulative transfer functions for each block.
-    trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
+    apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
 }
 
-impl<A> Engine<'a, 'tcx, A>
+impl<A, D, T> Engine<'a, 'tcx, A>
 where
-    A: GenKillAnalysis<'tcx>,
+    A: GenKillAnalysis<'tcx, Idx = T, Domain = D>,
+    D: Clone + JoinSemiLattice + GenKill<T> + BorrowMut<BitSet<T>>,
+    T: Idx,
 {
     /// Creates a new `Engine` to solve a gen-kill dataflow problem.
     pub fn new_gen_kill(
@@ -109,22 +113,26 @@ pub fn new_gen_kill(
 
         // Otherwise, compute and store the cumulative transfer function for each block.
 
-        let bits_per_block = analysis.bits_per_block(body);
-        let mut trans_for_block =
-            IndexVec::from_elem(GenKillSet::identity(bits_per_block), body.basic_blocks());
+        let identity = GenKillSet::identity(analysis.bottom_value(body).borrow().domain_size());
+        let mut trans_for_block = IndexVec::from_elem(identity, body.basic_blocks());
 
         for (block, block_data) in body.basic_blocks().iter_enumerated() {
             let trans = &mut trans_for_block[block];
             A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data);
         }
 
-        Self::new(tcx, body, def_id, analysis, Some(trans_for_block))
+        let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
+            trans_for_block[bb].apply(state.borrow_mut());
+        });
+
+        Self::new(tcx, body, def_id, analysis, Some(apply_trans as Box<_>))
     }
 }
 
-impl<A> Engine<'a, 'tcx, A>
+impl<A, D> Engine<'a, 'tcx, A>
 where
-    A: Analysis<'tcx>,
+    A: Analysis<'tcx, Domain = D>,
+    D: Clone + JoinSemiLattice,
 {
     /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
     /// function.
@@ -145,32 +153,24 @@ fn new(
         body: &'a mir::Body<'tcx>,
         def_id: DefId,
         analysis: A,
-        trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
+        apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
     ) -> Self {
-        let bits_per_block = analysis.bits_per_block(body);
-
-        let bottom_value_set = if A::BOTTOM_VALUE {
-            BitSet::new_filled(bits_per_block)
-        } else {
-            BitSet::new_empty(bits_per_block)
-        };
-
-        let mut entry_sets = IndexVec::from_elem(bottom_value_set.clone(), body.basic_blocks());
+        let bottom_value = analysis.bottom_value(body);
+        let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), body.basic_blocks());
         analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
 
-        if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value_set {
+        if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value {
             bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
         }
 
         Engine {
             analysis,
-            bits_per_block,
             tcx,
             body,
             def_id,
             dead_unwinds: None,
             entry_sets,
-            trans_for_block,
+            apply_trans_for_block,
         }
     }
 
@@ -185,16 +185,18 @@ pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
     }
 
     /// Computes the fixpoint for this dataflow problem and returns it.
-    pub fn iterate_to_fixpoint(self) -> Results<'tcx, A> {
+    pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
+    where
+        A::Domain: DebugWithContext<A>,
+    {
         let Engine {
             analysis,
-            bits_per_block,
             body,
             dead_unwinds,
             def_id,
             mut entry_sets,
             tcx,
-            trans_for_block,
+            apply_trans_for_block,
             ..
         } = self;
 
@@ -213,14 +215,14 @@ pub fn iterate_to_fixpoint(self) -> Results<'tcx, A> {
             }
         }
 
-        let mut state = BitSet::new_empty(bits_per_block);
+        let mut state = analysis.bottom_value(body);
         while let Some(bb) = dirty_queue.pop() {
             let bb_data = &body[bb];
 
             // Apply the block transfer function, using the cached one if it exists.
-            state.overwrite(&entry_sets[bb]);
-            match &trans_for_block {
-                Some(trans_for_block) => trans_for_block[bb].apply(&mut state),
+            state.clone_from(&entry_sets[bb]);
+            match &apply_trans_for_block {
+                Some(apply) => apply(bb, &mut state),
                 None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data),
             }
 
@@ -231,8 +233,8 @@ pub fn iterate_to_fixpoint(self) -> Results<'tcx, A> {
                 dead_unwinds,
                 &mut state,
                 (bb, bb_data),
-                |target: BasicBlock, state: &BitSet<A::Idx>| {
-                    let set_changed = analysis.join(&mut entry_sets[target], state);
+                |target: BasicBlock, state: &A::Domain| {
+                    let set_changed = entry_sets[target].join(state);
                     if set_changed {
                         dirty_queue.insert(target);
                     }
@@ -242,7 +244,7 @@ pub fn iterate_to_fixpoint(self) -> Results<'tcx, A> {
 
         let results = Results { analysis, entry_sets };
 
-        let res = write_graphviz_results(tcx, def_id, &body, &results, trans_for_block);
+        let res = write_graphviz_results(tcx, def_id, &body, &results);
         if let Err(e) = res {
             warn!("Failed to write graphviz dataflow results: {}", e);
         }
@@ -260,10 +262,10 @@ fn write_graphviz_results<A>(
     def_id: DefId,
     body: &mir::Body<'tcx>,
     results: &Results<'tcx, A>,
-    block_transfer_functions: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
 ) -> std::io::Result<()>
 where
     A: Analysis<'tcx>,
+    A::Domain: DebugWithContext<A>,
 {
     let attrs = match RustcMirAttrs::parse(tcx, def_id) {
         Ok(attrs) => attrs,
@@ -290,26 +292,15 @@ fn write_graphviz_results<A>(
         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() {
diff --git a/compiler/rustc_mir/src/dataflow/framework/fmt.rs b/compiler/rustc_mir/src/dataflow/framework/fmt.rs
new file mode 100644 (file)
index 0000000..0140a75
--- /dev/null
@@ -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)
+    }
+}
index 896616a2175ac543bb9010a3ba9238ca4bf60a3a..179c471cf4829fadba91106390e3c9913100cdcf 100644 (file)
@@ -1,26 +1,40 @@
 //! A helpful diagram for debugging dataflow problems.
 
-use std::cell::RefCell;
+use std::borrow::Cow;
 use std::{io, ops, str};
 
+use regex::Regex;
 use rustc_graphviz as dot;
 use rustc_hir::def_id::DefId;
-use rustc_index::bit_set::{BitSet, HybridBitSet};
-use rustc_index::vec::{Idx, IndexVec};
 use rustc_middle::mir::{self, BasicBlock, Body, Location};
 
-use super::{Analysis, Direction, GenKillSet, Results, ResultsRefCursor};
+use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
+use super::{Analysis, Direction, Results, ResultsRefCursor, ResultsVisitor};
 use crate::util::graphviz_safe_def_name;
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum OutputStyle {
+    AfterOnly,
+    BeforeAndAfter,
+}
+
+impl OutputStyle {
+    fn num_state_columns(&self) -> usize {
+        match self {
+            Self::AfterOnly => 1,
+            Self::BeforeAndAfter => 2,
+        }
+    }
+}
+
 pub struct Formatter<'a, 'tcx, A>
 where
     A: Analysis<'tcx>,
 {
     body: &'a Body<'tcx>,
     def_id: DefId,
-
-    // This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
-    block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
+    results: &'a Results<'tcx, A>,
+    style: OutputStyle,
 }
 
 impl<A> Formatter<'a, 'tcx, A>
@@ -31,15 +45,9 @@ pub fn new(
         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 @@ fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
 
     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 @@ struct BlockFormatter<'a, 'tcx, A>
 {
     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 @@ fn write_node_label(
         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 @@ fn write_node_label(
             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 @@ fn write_node_label(
                 ..
             } => {
                 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 @@ fn write_block_header_with_state_columns(
         &mut self,
         w: &mut impl io::Write,
         block: BasicBlock,
+        state_column_names: &[&str],
     ) -> io::Result<()> {
         //   +------------------------------------+-------------+
         // A |                bb4                 |    STATE    |
@@ -330,8 +335,6 @@ fn write_block_header_with_state_columns(
         //   +-+----------------------------------+------+------+
         //   | |              ...                 |      |      |
 
-        let state_column_names = self.state_formatter.column_names();
-
         // A
         write!(
             w,
@@ -357,6 +360,56 @@ fn write_block_header_with_state_columns(
         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 @@ fn write_row_with_full_state(
             let state = this.results.get();
             let analysis = this.results.analysis();
 
+            // FIXME: The full state vector can be quite long. It would be nice to split on commas
+            // and use some text wrapping algorithm.
             write!(
                 w,
-                r#"<td colspan="{colspan}" {fmt} align="left">{{"#,
-                colspan = this.num_state_columns(),
+                r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
+                colspan = this.style.num_state_columns(),
                 fmt = fmt,
-            )?;
-            pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?;
-            write!(w, "}}</td>")
-        })
-    }
-
-    fn write_row_for_location(
-        &mut self,
-        w: &mut impl io::Write,
-        i: &str,
-        mir: &str,
-        location: Location,
-    ) -> io::Result<()> {
-        self.write_row(w, i, mir, |this, w, fmt| {
-            this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location)
+                state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
+            )
         })
     }
 }
 
-/// Controls what gets printed under the `STATE` header.
-pub trait StateFormatter<'tcx, A>
+struct StateDiffCollector<'a, 'tcx, A>
 where
     A: Analysis<'tcx>,
 {
-    /// The columns that will get printed under `STATE`.
-    fn column_names(&self) -> &[&str];
-
-    fn write_state_for_location(
-        &mut self,
-        w: &mut dyn io::Write,
-        fmt: &str,
-        results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
-        location: Location,
-    ) -> io::Result<()>;
+    analysis: &'a A,
+    prev_state: A::Domain,
+    before: Option<Vec<String>>,
+    after: Vec<String>,
 }
 
-/// Prints a single column containing the state vector immediately *after* each statement.
-pub struct SimpleDiff<'a, 'tcx, A>
+impl<A> StateDiffCollector<'a, 'tcx, A>
 where
     A: Analysis<'tcx>,
+    A::Domain: DebugWithContext<A>,
 {
-    prev_state: ResultsRefCursor<'a, 'a, 'tcx, A>,
-}
+    fn run(
+        body: &'a mir::Body<'tcx>,
+        block: BasicBlock,
+        results: &'a Results<'tcx, A>,
+        style: OutputStyle,
+    ) -> Self {
+        let mut collector = StateDiffCollector {
+            analysis: &results.analysis,
+            prev_state: results.analysis.bottom_value(body),
+            after: vec![],
+            before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
+        };
 
-impl<A> SimpleDiff<'a, 'tcx, A>
-where
-    A: Analysis<'tcx>,
-{
-    pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>) -> Self {
-        SimpleDiff { prev_state: ResultsRefCursor::new(body, results) }
+        results.visit_with(body, std::iter::once(block), &mut collector);
+        collector
     }
 }
 
-impl<A> StateFormatter<'tcx, A> for SimpleDiff<'_, 'tcx, A>
+impl<A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A>
 where
     A: Analysis<'tcx>,
+    A::Domain: DebugWithContext<A>,
 {
-    fn column_names(&self) -> &[&str] {
-        &[]
-    }
+    type FlowState = A::Domain;
 
-    fn write_state_for_location(
+    fn visit_block_start(
         &mut self,
-        mut w: &mut dyn io::Write,
-        fmt: &str,
-        results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
-        location: Location,
-    ) -> io::Result<()> {
+        state: &Self::FlowState,
+        _block_data: &'mir mir::BasicBlockData<'tcx>,
+        _block: BasicBlock,
+    ) {
         if A::Direction::is_forward() {
-            if location.statement_index == 0 {
-                self.prev_state.seek_to_block_start(location.block);
-            } else {
-                self.prev_state.seek_after_primary_effect(Location {
-                    statement_index: location.statement_index - 1,
-                    ..location
-                });
-            }
-        } else {
-            if location == results.body().terminator_loc(location.block) {
-                self.prev_state.seek_to_block_end(location.block);
-            } else {
-                self.prev_state.seek_after_primary_effect(location.successor_within_block());
-            }
+            self.prev_state.clone_from(state);
         }
-
-        write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
-        results.seek_after_primary_effect(location);
-        let curr_state = results.get();
-        write_diff(&mut w, results.analysis(), self.prev_state.get(), curr_state)?;
-        write!(w, "</td>")
     }
-}
 
-/// Prints two state columns, one containing only the "before" effect of each statement and one
-/// containing the full effect.
-pub struct TwoPhaseDiff<T: Idx> {
-    prev_state: BitSet<T>,
-    prev_loc: Location,
-}
+    fn visit_block_end(
+        &mut self,
+        state: &Self::FlowState,
+        _block_data: &'mir mir::BasicBlockData<'tcx>,
+        _block: BasicBlock,
+    ) {
+        if A::Direction::is_backward() {
+            self.prev_state.clone_from(state);
+        }
+    }
 
-impl<T: Idx> TwoPhaseDiff<T> {
-    pub fn new(bits_per_block: usize) -> Self {
-        TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
+    fn visit_statement_before_primary_effect(
+        &mut self,
+        state: &Self::FlowState,
+        _statement: &'mir mir::Statement<'tcx>,
+        _location: Location,
+    ) {
+        if let Some(before) = self.before.as_mut() {
+            before.push(diff_pretty(state, &self.prev_state, self.analysis));
+            self.prev_state.clone_from(state)
+        }
     }
-}
 
-impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
-where
-    A: Analysis<'tcx>,
-{
-    fn column_names(&self) -> &[&str] {
-        &["BEFORE", " AFTER"]
+    fn visit_statement_after_primary_effect(
+        &mut self,
+        state: &Self::FlowState,
+        _statement: &'mir mir::Statement<'tcx>,
+        _location: Location,
+    ) {
+        self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
+        self.prev_state.clone_from(state)
     }
 
-    fn write_state_for_location(
+    fn visit_terminator_before_primary_effect(
         &mut self,
-        mut w: &mut dyn io::Write,
-        fmt: &str,
-        results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
-        location: Location,
-    ) -> io::Result<()> {
-        if location.statement_index == 0 {
-            results.seek_to_block_entry(location.block);
-            self.prev_state.overwrite(results.get());
-        } else {
-            // Ensure that we are visiting statements in order, so `prev_state` is correct.
-            assert_eq!(self.prev_loc.successor_within_block(), location);
+        state: &Self::FlowState,
+        _terminator: &'mir mir::Terminator<'tcx>,
+        _location: Location,
+    ) {
+        if let Some(before) = self.before.as_mut() {
+            before.push(diff_pretty(state, &self.prev_state, self.analysis));
+            self.prev_state.clone_from(state)
         }
-
-        self.prev_loc = location;
-
-        // Before
-
-        write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
-        results.seek_before_primary_effect(location);
-        let curr_state = results.get();
-        write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
-        self.prev_state.overwrite(curr_state);
-        write!(w, "</td>")?;
-
-        // After
-
-        write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
-        results.seek_after_primary_effect(location);
-        let curr_state = results.get();
-        write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
-        self.prev_state.overwrite(curr_state);
-        write!(w, "</td>")
     }
-}
-
-/// Prints the gen/kill set for the entire block.
-pub struct BlockTransferFunc<'a, 'tcx, T: Idx> {
-    body: &'a mir::Body<'tcx>,
-    trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
-}
 
-impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
-    pub fn new(
-        body: &'mir mir::Body<'tcx>,
-        trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
-    ) -> Self {
-        BlockTransferFunc { body, trans_for_block }
+    fn visit_terminator_after_primary_effect(
+        &mut self,
+        state: &Self::FlowState,
+        _terminator: &'mir mir::Terminator<'tcx>,
+        _location: Location,
+    ) {
+        self.after.push(diff_pretty(state, &self.prev_state, self.analysis));
+        self.prev_state.clone_from(state)
     }
 }
 
-impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
+fn diff_pretty<T, C>(new: T, old: T, ctxt: &C) -> String
 where
-    A: Analysis<'tcx>,
+    T: DebugWithContext<C>,
 {
-    fn column_names(&self) -> &[&str] {
-        &["GEN", "KILL"]
+    if new == old {
+        return String::new();
     }
 
-    fn write_state_for_location(
-        &mut self,
-        mut w: &mut dyn io::Write,
-        fmt: &str,
-        results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
-        location: Location,
-    ) -> io::Result<()> {
-        // Only print a single row.
-        if location.statement_index != 0 {
-            return Ok(());
-        }
+    let re = Regex::new("\u{001f}([+-])").unwrap();
 
-        let block_trans = &self.trans_for_block[location.block];
-        let rowspan = self.body.basic_blocks()[location.block].statements.len();
+    let raw_diff = format!("{:#?}", DebugDiffWithAdapter { new, old, ctxt });
 
-        for set in &[&block_trans.gen, &block_trans.kill] {
-            write!(
-                w,
-                r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
-                fmt = fmt,
-                rowspan = rowspan
-            )?;
+    // Replace newlines in the `Debug` output with `<br/>`
+    let raw_diff = raw_diff.replace('\n', r#"<br align="left"/>"#);
 
-            pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
-            write!(w, "</td>")?;
+    let mut inside_font_tag = false;
+    let html_diff = re.replace_all(&raw_diff, |captures: &regex::Captures<'_>| {
+        let mut ret = String::new();
+        if inside_font_tag {
+            ret.push_str(r#"</font>"#);
         }
 
-        Ok(())
-    }
-}
-
-/// Writes two lines, one containing the added bits and one the removed bits.
-fn write_diff<A: Analysis<'tcx>>(
-    w: &mut impl io::Write,
-    analysis: &A,
-    from: &BitSet<A::Idx>,
-    to: &BitSet<A::Idx>,
-) -> io::Result<()> {
-    assert_eq!(from.domain_size(), to.domain_size());
-    let len = from.domain_size();
-
-    let mut set = HybridBitSet::new_empty(len);
-    let mut clear = HybridBitSet::new_empty(len);
-
-    // FIXME: Implement a lazy iterator over the symmetric difference of two bitsets.
-    for i in (0..len).map(A::Idx::new) {
-        match (from.contains(i), to.contains(i)) {
-            (false, true) => set.insert(i),
-            (true, false) => clear.insert(i),
-            _ => continue,
+        let tag = match &captures[1] {
+            "+" => r#"<font color="darkgreen">+"#,
+            "-" => r#"<font color="red">-"#,
+            _ => unreachable!(),
         };
-    }
-
-    if !set.is_empty() {
-        write!(w, r#"<font color="darkgreen">+"#)?;
-        pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?;
-        write!(w, r#"</font>"#)?;
-    }
-
-    if !set.is_empty() && !clear.is_empty() {
-        write!(w, "{}", BR_LEFT)?;
-    }
-
-    if !clear.is_empty() {
-        write!(w, r#"<font color="red">-"#)?;
-        pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?;
-        write!(w, r#"</font>"#)?;
-    }
-
-    Ok(())
-}
 
-const BR_LEFT: &str = r#"<br align="left"/>"#;
-const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#;
+        inside_font_tag = true;
+        ret.push_str(tag);
+        ret
+    });
 
-/// Line break policy that breaks at 40 characters and starts the next line with a single space.
-const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 });
-
-struct LineBreak {
-    sequence: &'static str,
-    limit: usize,
-}
-
-/// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given
-/// separator (`sep`).
-///
-/// Optionally, it will break lines using the given character sequence (usually `<br/>`) and
-/// character limit.
-fn pretty_print_state_elems<A>(
-    w: &mut impl io::Write,
-    analysis: &A,
-    elems: impl Iterator<Item = A::Idx>,
-    sep: &str,
-    line_break: Option<LineBreak>,
-) -> io::Result<bool>
-where
-    A: Analysis<'tcx>,
-{
-    let sep_width = sep.chars().count();
-
-    let mut buf = Vec::new();
-
-    let mut first = true;
-    let mut curr_line_width = 0;
-    let mut line_break_inserted = false;
-
-    for idx in elems {
-        buf.clear();
-        analysis.pretty_print_idx(&mut buf, idx)?;
-        let idx_str =
-            str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8");
-        let escaped = dot::escape_html(idx_str);
-        let escaped_width = escaped.chars().count();
-
-        if first {
-            first = false;
-        } else {
-            write!(w, "{}", sep)?;
-            curr_line_width += sep_width;
-
-            if let Some(line_break) = &line_break {
-                if curr_line_width + sep_width + escaped_width > line_break.limit {
-                    write!(w, "{}", line_break.sequence)?;
-                    line_break_inserted = true;
-                    curr_line_width = 0;
-                }
-            }
-        }
+    let mut html_diff = match html_diff {
+        Cow::Borrowed(_) => return raw_diff,
+        Cow::Owned(s) => s,
+    };
 
-        write!(w, "{}", escaped)?;
-        curr_line_width += escaped_width;
+    if inside_font_tag {
+        html_diff.push_str("</font>");
     }
 
-    Ok(line_break_inserted)
+    html_diff
 }
 
 /// The background color used for zebra-striping the table.
diff --git a/compiler/rustc_mir/src/dataflow/framework/lattice.rs b/compiler/rustc_mir/src/dataflow/framework/lattice.rs
new file mode 100644 (file)
index 0000000..9294e0c
--- /dev/null
@@ -0,0 +1,196 @@
+//! Traits used to represent [lattices] for use as the domain of a dataflow analysis.
+//!
+//! ## Implementation Notes
+//!
+//! Given that they represent partially ordered sets, you may be surprised that [`MeetSemiLattice`]
+//! and [`JoinSemiLattice`] do not have [`PartialOrd`][std::cmp::PartialOrd] as a supertrait. This
+//! is because most standard library types use lexicographic ordering instead of [set inclusion]
+//! for their `PartialOrd` impl. Since we do not actually need to compare lattice elements to run a
+//! dataflow analysis, there's no need for a hypothetical `SetInclusion` newtype with a custom
+//! `PartialOrd` impl.  The only benefit would be the ability to check (in debug mode) that the
+//! least upper (or greatest lower) bound returned by the lattice join (or meet) operator was in
+//! fact greater (or lower) than the inputs.
+//!
+//! [lattices]: https://en.wikipedia.org/wiki/Lattice_(order)
+//! [set inclusion]: https://en.wikipedia.org/wiki/Subset
+
+use rustc_index::bit_set::BitSet;
+use rustc_index::vec::{Idx, IndexVec};
+
+/// A [partially ordered set][poset] that has a [least upper bound][lub] for any pair of elements
+/// in the set.
+///
+/// [lub]: https://en.wikipedia.org/wiki/Infimum_and_supremum
+/// [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
+pub trait JoinSemiLattice: Eq {
+    /// Computes the least upper bound of two elements, storing the result in `self` and returning
+    /// `true` if `self` has changed.
+    ///
+    /// The lattice join operator is abbreviated as `∨`.
+    fn join(&mut self, other: &Self) -> bool;
+}
+
+/// A [partially ordered set][poset] that has a [greatest lower bound][glb] for any pair of
+/// elements in the set.
+///
+/// Dataflow analyses only require that their domains implement [`JoinSemiLattice`], not
+/// `MeetSemiLattice`. However, types that will be used as dataflow domains should implement both
+/// so that they can be used with [`Dual`].
+///
+/// [glb]: https://en.wikipedia.org/wiki/Infimum_and_supremum
+/// [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
+pub trait MeetSemiLattice: Eq {
+    /// Computes the greatest lower bound of two elements, storing the result in `self` and
+    /// returning `true` if `self` has changed.
+    ///
+    /// The lattice meet operator is abbreviated as `∧`.
+    fn meet(&mut self, other: &Self) -> bool;
+}
+
+/// A `bool` is a "two-point" lattice with `true` as the top element and `false` as the bottom.
+impl JoinSemiLattice for bool {
+    fn join(&mut self, other: &Self) -> bool {
+        if let (false, true) = (*self, *other) {
+            *self = true;
+            return true;
+        }
+
+        false
+    }
+}
+
+impl MeetSemiLattice for bool {
+    fn meet(&mut self, other: &Self) -> bool {
+        if let (true, false) = (*self, *other) {
+            *self = false;
+            return true;
+        }
+
+        false
+    }
+}
+
+/// A tuple or list of lattices is itself a lattice whose least upper bound is the concatenation of
+/// the least upper bounds of each element of the tuple or list.
+impl<I: Idx, T: JoinSemiLattice> JoinSemiLattice for IndexVec<I, T> {
+    fn join(&mut self, other: &Self) -> bool {
+        assert_eq!(self.len(), other.len());
+
+        let mut changed = false;
+        for (a, b) in self.iter_mut().zip(other.iter()) {
+            changed |= a.join(b);
+        }
+        changed
+    }
+}
+
+impl<I: Idx, T: MeetSemiLattice> MeetSemiLattice for IndexVec<I, T> {
+    fn meet(&mut self, other: &Self) -> bool {
+        assert_eq!(self.len(), other.len());
+
+        let mut changed = false;
+        for (a, b) in self.iter_mut().zip(other.iter()) {
+            changed |= a.meet(b);
+        }
+        changed
+    }
+}
+
+/// A `BitSet` is an efficent way to store a tuple of "two-point" lattices. Equivalently, it is the
+/// lattice corresponding to the powerset of the set of all possibe values of the index type `T`
+/// ordered by inclusion.
+impl<T: Idx> JoinSemiLattice for BitSet<T> {
+    fn join(&mut self, other: &Self) -> bool {
+        self.union(other)
+    }
+}
+
+impl<T: Idx> MeetSemiLattice for BitSet<T> {
+    fn meet(&mut self, other: &Self) -> bool {
+        self.intersect(other)
+    }
+}
+
+/// The counterpart of a given semilattice `T` using the [inverse order].
+///
+/// The dual of a join-semilattice is a meet-semilattice and vice versa. For example, the dual of a
+/// powerset has the empty set as its top element and the full set as its bottom element and uses
+/// set *intersection* as its join operator.
+///
+/// [inverse order]: https://en.wikipedia.org/wiki/Duality_(order_theory)
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Dual<T>(pub T);
+
+impl<T> std::borrow::Borrow<T> for Dual<T> {
+    fn borrow(&self) -> &T {
+        &self.0
+    }
+}
+
+impl<T> std::borrow::BorrowMut<T> for Dual<T> {
+    fn borrow_mut(&mut self) -> &mut T {
+        &mut self.0
+    }
+}
+
+impl<T: MeetSemiLattice> JoinSemiLattice for Dual<T> {
+    fn join(&mut self, other: &Self) -> bool {
+        self.0.meet(&other.0)
+    }
+}
+
+impl<T: JoinSemiLattice> MeetSemiLattice for Dual<T> {
+    fn meet(&mut self, other: &Self) -> bool {
+        self.0.join(&other.0)
+    }
+}
+
+/// Extends a type `T` with top and bottom elements to make it a partially ordered set in which no
+/// value of `T` is comparable with any other. A flat set has the following [Hasse
+/// diagram](https://en.wikipedia.org/wiki/Hasse_diagram):
+///
+/// ```text
+///         top
+///       / /  \ \
+/// all possible values of `T`
+///       \ \  / /
+///        bottom
+/// ```
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum FlatSet<T> {
+    Bottom,
+    Elem(T),
+    Top,
+}
+
+impl<T: Clone + Eq> JoinSemiLattice for FlatSet<T> {
+    fn join(&mut self, other: &Self) -> bool {
+        let result = match (&*self, other) {
+            (Self::Top, _) | (_, Self::Bottom) => return false,
+            (Self::Elem(a), Self::Elem(b)) if a == b => return false,
+
+            (Self::Bottom, Self::Elem(x)) => Self::Elem(x.clone()),
+
+            _ => Self::Top,
+        };
+
+        *self = result;
+        true
+    }
+}
+
+impl<T: Clone + Eq> MeetSemiLattice for FlatSet<T> {
+    fn meet(&mut self, other: &Self) -> bool {
+        let result = match (&*self, other) {
+            (Self::Bottom, _) | (_, Self::Top) => return false,
+            (Self::Elem(ref a), Self::Elem(ref b)) if a == b => return false,
+
+            (Self::Top, Self::Elem(ref x)) => Self::Elem(x.clone()),
+
+            _ => Self::Bottom,
+        };
+
+        *self = result;
+        true
+    }
+}
index a21bbacb4676646ae5aca028f2e608e95ea6bc71..eba3b88d47ef9258b67063dea6a93144b00605f2 100644 (file)
@@ -30,8 +30,8 @@
 //!
 //! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems
 
+use std::borrow::BorrowMut;
 use std::cmp::Ordering;
-use std::io;
 
 use rustc_hir::def_id::DefId;
 use rustc_index::bit_set::{BitSet, HybridBitSet};
 mod cursor;
 mod direction;
 mod engine;
+pub mod fmt;
 mod graphviz;
+pub mod lattice;
 mod visitor;
 
 pub use self::cursor::{ResultsCursor, ResultsRefCursor};
 pub use self::direction::{Backward, Direction, Forward};
 pub use self::engine::{Engine, Results};
+pub use self::lattice::{JoinSemiLattice, MeetSemiLattice};
 pub use self::visitor::{visit_results, ResultsVisitor};
 pub use self::visitor::{BorrowckFlowState, BorrowckResults};
 
-/// Parameterization for the precise form of data flow that is used.
-///
-/// `BottomValue` determines whether the initial entry set for each basic block is empty or full.
-/// This also determines the semantics of the lattice `join` operator used to merge dataflow
-/// results, since dataflow works by starting at the bottom and moving monotonically to a fixed
-/// point.
-///
-/// This means, for propagation across the graph, that you either want to start at all-zeroes and
-/// then use Union as your merge when propagating, or you start at all-ones and then use Intersect
-/// as your merge when propagating.
-pub trait BottomValue {
-    /// Specifies the initial value for each bit in the entry set for each basic block.
-    const BOTTOM_VALUE: bool;
-
-    /// Merges `in_set` into `inout_set`, returning `true` if `inout_set` changed.
-    ///
-    /// It is almost certainly wrong to override this, since it automatically applies
-    /// * `inout_set & in_set` if `BOTTOM_VALUE == true`
-    /// * `inout_set | in_set` if `BOTTOM_VALUE == false`
-    ///
-    /// This means that if a bit is not `BOTTOM_VALUE`, it is propagated into all target blocks.
-    /// For clarity, the above statement again from a different perspective:
-    /// A bit in the block's entry set is `!BOTTOM_VALUE` if *any* predecessor block's bit value is
-    /// `!BOTTOM_VALUE`.
-    ///
-    /// There are situations where you want the opposite behaviour: propagate only if *all*
-    /// predecessor blocks's value is `!BOTTOM_VALUE`.
-    /// E.g. if you want to know whether a bit is *definitely* set at a specific location. This
-    /// means that all code paths leading to the location must have set the bit, instead of any
-    /// code path leading there.
-    ///
-    /// If you want this kind of "definitely set" analysis, you need to
-    /// 1. Invert `BOTTOM_VALUE`
-    /// 2. Reset the `entry_set` in `start_block_effect` to `!BOTTOM_VALUE`
-    /// 3. Override `join` to do the opposite from what it's doing now.
-    #[inline]
-    fn join<T: Idx>(&self, inout_set: &mut BitSet<T>, in_set: &BitSet<T>) -> bool {
-        if !Self::BOTTOM_VALUE { inout_set.union(in_set) } else { inout_set.intersect(in_set) }
-    }
-}
-
 /// Define the domain of a dataflow problem.
 ///
-/// This trait specifies the lattice on which this analysis operates. For now, this must be a
-/// powerset of values of type `Idx`. The elements of this lattice are represented with a `BitSet`
-/// and referred to as the state vector.
-///
-/// This trait also defines the initial value for the dataflow state upon entry to the
-/// `START_BLOCK`, as well as some names used to refer to this analysis when debugging.
-pub trait AnalysisDomain<'tcx>: BottomValue {
-    /// The type of the elements in the state vector.
-    type Idx: Idx;
+/// This trait specifies the lattice on which this analysis operates (the domain) as well as its
+/// initial value at the entry point of each basic block.
+pub trait AnalysisDomain<'tcx> {
+    type Domain: Clone + JoinSemiLattice;
 
     /// The direction of this analyis. Either `Forward` or `Backward`.
     type Direction: Direction = Forward;
@@ -114,8 +71,7 @@ pub trait AnalysisDomain<'tcx>: BottomValue {
     /// suitable as part of a filename.
     const NAME: &'static str;
 
-    /// The size of the state vector.
-    fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize;
+    fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain;
 
     /// Mutates the entry set of the `START_BLOCK` to contain the initial state for dataflow
     /// analysis.
@@ -126,12 +82,7 @@ pub trait AnalysisDomain<'tcx>: BottomValue {
     // FIXME: For backward dataflow analyses, the initial state should be applied to every basic
     // block where control flow could exit the MIR body (e.g., those terminated with `return` or
     // `resume`). It's not obvious how to handle `yield` points in generators, however.
-    fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>);
-
-    /// Prints an element in the state vector for debugging.
-    fn pretty_print_idx(&self, w: &mut impl io::Write, idx: Self::Idx) -> io::Result<()> {
-        write!(w, "{:?}", idx)
-    }
+    fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain);
 }
 
 /// A dataflow problem with an arbitrarily complex transfer function.
@@ -139,7 +90,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
     /// Updates the current dataflow state with the effect of evaluating a statement.
     fn apply_statement_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut Self::Domain,
         statement: &mir::Statement<'tcx>,
         location: Location,
     );
@@ -152,7 +103,7 @@ fn apply_statement_effect(
     /// analyses should not implement this without implementing `apply_statement_effect`.
     fn apply_before_statement_effect(
         &self,
-        _state: &mut BitSet<Self::Idx>,
+        _state: &mut Self::Domain,
         _statement: &mir::Statement<'tcx>,
         _location: Location,
     ) {
@@ -166,7 +117,7 @@ fn apply_before_statement_effect(
     /// initialized here.
     fn apply_terminator_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut Self::Domain,
         terminator: &mir::Terminator<'tcx>,
         location: Location,
     );
@@ -179,7 +130,7 @@ fn apply_terminator_effect(
     /// analyses should not implement this without implementing `apply_terminator_effect`.
     fn apply_before_terminator_effect(
         &self,
-        _state: &mut BitSet<Self::Idx>,
+        _state: &mut Self::Domain,
         _terminator: &mir::Terminator<'tcx>,
         _location: Location,
     ) {
@@ -192,7 +143,7 @@ fn apply_before_terminator_effect(
     /// edges.
     fn apply_call_return_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut Self::Domain,
         block: BasicBlock,
         func: &mir::Operand<'tcx>,
         args: &[mir::Operand<'tcx>],
@@ -207,7 +158,7 @@ fn apply_call_return_effect(
     /// By default, no effects happen.
     fn apply_yield_resume_effect(
         &self,
-        _state: &mut BitSet<Self::Idx>,
+        _state: &mut Self::Domain,
         _resume_block: BasicBlock,
         _resume_place: mir::Place<'tcx>,
     ) {
@@ -222,7 +173,7 @@ fn apply_yield_resume_effect(
     /// FIXME: This class of effects is not supported for backward dataflow analyses.
     fn apply_discriminant_switch_effect(
         &self,
-        _state: &mut BitSet<Self::Idx>,
+        _state: &mut Self::Domain,
         _block: BasicBlock,
         _enum_place: mir::Place<'tcx>,
         _adt: &ty::AdtDef,
@@ -264,6 +215,8 @@ fn into_engine(
 ///
 /// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`.
 pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
+    type Idx: Idx;
+
     /// See `Analysis::apply_statement_effect`.
     fn statement_effect(
         &self,
@@ -332,10 +285,11 @@ fn discriminant_switch_effect(
 impl<A> Analysis<'tcx> for A
 where
     A: GenKillAnalysis<'tcx>,
+    A::Domain: GenKill<A::Idx> + BorrowMut<BitSet<A::Idx>>,
 {
     fn apply_statement_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut A::Domain,
         statement: &mir::Statement<'tcx>,
         location: Location,
     ) {
@@ -344,7 +298,7 @@ fn apply_statement_effect(
 
     fn apply_before_statement_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut A::Domain,
         statement: &mir::Statement<'tcx>,
         location: Location,
     ) {
@@ -353,7 +307,7 @@ fn apply_before_statement_effect(
 
     fn apply_terminator_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut A::Domain,
         terminator: &mir::Terminator<'tcx>,
         location: Location,
     ) {
@@ -362,7 +316,7 @@ fn apply_terminator_effect(
 
     fn apply_before_terminator_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut A::Domain,
         terminator: &mir::Terminator<'tcx>,
         location: Location,
     ) {
@@ -371,7 +325,7 @@ fn apply_before_terminator_effect(
 
     fn apply_call_return_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut A::Domain,
         block: BasicBlock,
         func: &mir::Operand<'tcx>,
         args: &[mir::Operand<'tcx>],
@@ -382,7 +336,7 @@ fn apply_call_return_effect(
 
     fn apply_yield_resume_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut A::Domain,
         resume_block: BasicBlock,
         resume_place: mir::Place<'tcx>,
     ) {
@@ -391,7 +345,7 @@ fn apply_yield_resume_effect(
 
     fn apply_discriminant_switch_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut A::Domain,
         block: BasicBlock,
         enum_place: mir::Place<'tcx>,
         adt: &ty::AdtDef,
@@ -450,7 +404,7 @@ fn kill_all(&mut self, elems: impl IntoIterator<Item = T>) {
 /// applied multiple times efficiently. When there are multiple calls to `gen` and/or `kill` for
 /// the same element, the most recent one takes precedence.
 #[derive(Clone)]
-pub struct GenKillSet<T: Idx> {
+pub struct GenKillSet<T> {
     gen: HybridBitSet<T>,
     kill: HybridBitSet<T>,
 }
@@ -464,7 +418,6 @@ pub fn identity(universe: usize) -> Self {
         }
     }
 
-    /// Applies this transfer function to the given state vector.
     pub fn apply(&self, state: &mut BitSet<T>) {
         state.union(&self.gen);
         state.subtract(&self.kill);
@@ -493,6 +446,16 @@ fn kill(&mut self, elem: 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 {
index 9349f5133a55db43b2e41e46e708e6d980645c2f..a5989121679c4fa8fb2014cd9910581e30520a48 100644 (file)
@@ -9,7 +9,6 @@
 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 @@ fn effect(&self, loc: EffectIndex) -> usize {
     /// 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 @@ fn expected_state_at_target(&self, target: SeekTarget) -> BitSet<usize> {
     }
 }
 
-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 @@ fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>)
 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 @@ fn apply_statement_effect(
 
     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 @@ fn apply_before_statement_effect(
 
     fn apply_terminator_effect(
         &self,
-        state: &mut BitSet<Self::Idx>,
+        state: &mut Self::Domain,
         _terminator: &mir::Terminator<'tcx>,
         location: Location,
     ) {
@@ -213,7 +208,7 @@ fn apply_terminator_effect(
 
     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 @@ fn apply_before_terminator_effect(
 
     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>],
index 257f3cb9a6dd0ddd1026b1041fbe6babc3e13238..82eb734ed0699a2ae00f18635b59c9414cf552a3 100644 (file)
@@ -1,4 +1,3 @@
-use rustc_index::bit_set::BitSet;
 use rustc_middle::mir::{self, BasicBlock, Location};
 
 use super::{Analysis, Direction, Results};
@@ -139,16 +138,16 @@ impl<'tcx, A> ResultsVisitable<'tcx> for Results<'tcx, A>
 where
     A: Analysis<'tcx>,
 {
-    type FlowState = BitSet<A::Idx>;
+    type FlowState = A::Domain;
 
     type Direction = A::Direction;
 
     fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
-        BitSet::new_empty(self.analysis.bits_per_block(body))
+        self.analysis.bottom_value(body)
     }
 
     fn reset_to_block_entry(&self, state: &mut Self::FlowState, block: BasicBlock) {
-        state.overwrite(&self.entry_set_for_block(block));
+        state.clone_from(&self.entry_set_for_block(block));
     }
 
     fn reconstruct_before_statement_effect(
@@ -217,11 +216,11 @@ impl<'tcx, $($A),*, D: Direction> ResultsVisitable<'tcx> for $T<$( Results<'tcx,
             $( $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 @@ fn reset_to_block_entry(
                 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(
index a0c246360599c509eee8af12223753de43248fb4..5575a97982fc4e34bebb5d9db48e1754b7f208bd 100644 (file)
@@ -5,9 +5,9 @@
 
 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;
index 2e3b508463540ca5472f407c14abe41394705572..42717f273843aa4196ff0aaacd2cd18e901ecd22 100644 (file)
@@ -14,6 +14,7 @@
 #![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)]