about summary refs log tree commit diff
path: root/compiler/rustc_mir_dataflow/src/framework
diff options
context:
space:
mode:
authorNicholas Nethercote <n.nethercote@gmail.com>2025-04-18 10:46:20 +1000
committerNicholas Nethercote <n.nethercote@gmail.com>2025-04-24 11:36:07 +1000
commit92799b6f89d2d9bbfcb9b11d1bac57899d0bc435 (patch)
treeab76629093e4f9aa2668aafe41cd4155ab1c5e4d /compiler/rustc_mir_dataflow/src/framework
parent4ff55588d33366d0572c1865b43dec8ac00237b1 (diff)
Separate `Analysis` and `Results`.
`Results` contains and `Analysis` and an `EntryStates`. The unfortunate
thing about this is that the analysis needs to be mutable everywhere
(`&mut Analysis`) which forces the `Results` to be mutable everywhere,
even though `EntryStates` is immutable everywhere.

To fix this, this commit renames `Results` as `AnalysisAndResults`,
renames `EntryStates` as `Results`, and separates the analysis and
results as much as possible. (`AnalysisAndResults` doesn't get much use,
it's mostly there to facilitate method chaining of
`iterate_to_fixpoint`.)

`Results` is immutable everywhere, which:
- is a bit clearer on how the data is used,
- avoids an unnecessary clone of entry states in
  `locals_live_across_suspend_points`, and
- moves the results outside the `RefCell` in Formatter.

The commit also reformulates `ResultsHandle` as the generic `CowMut`,
which is simpler than `ResultsHandle` because it doesn't need the
`'tcx` lifetime and the trait bounds. It also which sits nicely
alongside the new use of `Cow` in `ResultsCursor`.
Diffstat (limited to 'compiler/rustc_mir_dataflow/src/framework')
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/cursor.rs78
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/direction.rs18
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/graphviz.rs32
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/mod.rs30
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/results.rs57
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/tests.rs6
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/visitor.rs23
7 files changed, 119 insertions, 125 deletions
diff --git a/compiler/rustc_mir_dataflow/src/framework/cursor.rs b/compiler/rustc_mir_dataflow/src/framework/cursor.rs
index d5005768b80..3f6e7a06619 100644
--- a/compiler/rustc_mir_dataflow/src/framework/cursor.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/cursor.rs
@@ -1,5 +1,6 @@
 //! Random access inspection of the results of a dataflow analysis.
 
+use std::borrow::Cow;
 use std::cmp::Ordering;
 use std::ops::{Deref, DerefMut};
 
@@ -9,38 +10,30 @@ use rustc_middle::mir::{self, BasicBlock, Location};
 
 use super::{Analysis, Direction, Effect, EffectIndex, Results};
 
-/// Some `ResultsCursor`s want to own a `Results`, and some want to borrow a `Results`, either
-/// mutable or immutably. This type allows all of the above. It's similar to `Cow`.
-pub enum ResultsHandle<'a, 'tcx, A>
-where
-    A: Analysis<'tcx>,
-{
-    BorrowedMut(&'a mut Results<'tcx, A>),
-    Owned(Results<'tcx, A>),
+/// Some `ResultsCursor`s want to own an `Analysis`, and some want to borrow an `Analysis`, either
+/// mutable or immutably. This type allows all of the above. It's similar to `Cow`, but `Cow`
+/// doesn't allow mutable borrowing.
+enum CowMut<'a, T> {
+    BorrowedMut(&'a mut T),
+    Owned(T),
 }
 
-impl<'tcx, A> Deref for ResultsHandle<'_, 'tcx, A>
-where
-    A: Analysis<'tcx>,
-{
-    type Target = Results<'tcx, A>;
+impl<T> Deref for CowMut<'_, T> {
+    type Target = T;
 
-    fn deref(&self) -> &Results<'tcx, A> {
+    fn deref(&self) -> &T {
         match self {
-            ResultsHandle::BorrowedMut(borrowed) => borrowed,
-            ResultsHandle::Owned(owned) => owned,
+            CowMut::BorrowedMut(borrowed) => borrowed,
+            CowMut::Owned(owned) => owned,
         }
     }
 }
 
-impl<'tcx, A> DerefMut for ResultsHandle<'_, 'tcx, A>
-where
-    A: Analysis<'tcx>,
-{
-    fn deref_mut(&mut self) -> &mut Results<'tcx, A> {
+impl<T> DerefMut for CowMut<'_, T> {
+    fn deref_mut(&mut self) -> &mut T {
         match self {
-            ResultsHandle::BorrowedMut(borrowed) => borrowed,
-            ResultsHandle::Owned(owned) => owned,
+            CowMut::BorrowedMut(borrowed) => borrowed,
+            CowMut::Owned(owned) => owned,
         }
     }
 }
@@ -60,7 +53,8 @@ where
     A: Analysis<'tcx>,
 {
     body: &'mir mir::Body<'tcx>,
-    results: ResultsHandle<'mir, 'tcx, A>,
+    analysis: CowMut<'mir, A>,
+    results: Cow<'mir, Results<A::Domain>>,
     state: A::Domain,
 
     pos: CursorPosition,
@@ -88,11 +82,15 @@ where
         self.body
     }
 
-    /// Returns a new cursor that can inspect `results`.
-    pub fn new(body: &'mir mir::Body<'tcx>, results: ResultsHandle<'mir, 'tcx, A>) -> Self {
-        let bottom_value = results.analysis.bottom_value(body);
+    fn new(
+        body: &'mir mir::Body<'tcx>,
+        analysis: CowMut<'mir, A>,
+        results: Cow<'mir, Results<A::Domain>>,
+    ) -> Self {
+        let bottom_value = analysis.bottom_value(body);
         ResultsCursor {
             body,
+            analysis,
             results,
 
             // Initialize to the `bottom_value` and set `state_needs_reset` to tell the cursor that
@@ -107,6 +105,24 @@ where
         }
     }
 
+    /// Returns a new cursor that takes ownership of and inspects analysis results.
+    pub fn new_owning(
+        body: &'mir mir::Body<'tcx>,
+        analysis: A,
+        results: Results<A::Domain>,
+    ) -> Self {
+        Self::new(body, CowMut::Owned(analysis), Cow::Owned(results))
+    }
+
+    /// Returns a new cursor that borrows and inspects analysis results.
+    pub fn new_borrowing(
+        body: &'mir mir::Body<'tcx>,
+        analysis: &'mir mut A,
+        results: &'mir Results<A::Domain>,
+    ) -> Self {
+        Self::new(body, CowMut::BorrowedMut(analysis), Cow::Borrowed(results))
+    }
+
     /// Allows inspection of unreachable basic blocks even with `debug_assertions` enabled.
     #[cfg(test)]
     pub(crate) fn allow_unreachable(&mut self) {
@@ -116,7 +132,7 @@ where
 
     /// Returns the `Analysis` used to generate the underlying `Results`.
     pub fn analysis(&self) -> &A {
-        &self.results.analysis
+        &self.analysis
     }
 
     /// Resets the cursor to hold the entry set for the given basic block.
@@ -128,7 +144,7 @@ where
         #[cfg(debug_assertions)]
         assert!(self.reachable_blocks.contains(block));
 
-        self.state.clone_from(self.results.entry_set_for_block(block));
+        self.state.clone_from(&self.results[block]);
         self.pos = CursorPosition::block_entry(block);
         self.state_needs_reset = false;
     }
@@ -220,7 +236,7 @@ where
         let target_effect_index = effect.at_index(target.statement_index);
 
         A::Direction::apply_effects_in_range(
-            &mut self.results.analysis,
+            &mut *self.analysis,
             &mut self.state,
             target.block,
             block_data,
@@ -236,7 +252,7 @@ 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(&mut A, &mut A::Domain)) {
-        f(&mut self.results.analysis, &mut self.state);
+        f(&mut self.analysis, &mut self.state);
         self.state_needs_reset = true;
     }
 }
diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs
index 5f34650d460..e955e38ad10 100644
--- a/compiler/rustc_mir_dataflow/src/framework/direction.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs
@@ -5,7 +5,7 @@ use rustc_middle::mir::{
 };
 
 use super::visitor::ResultsVisitor;
-use super::{Analysis, Effect, EffectIndex, Results};
+use super::{Analysis, Effect, EffectIndex};
 
 pub trait Direction {
     const IS_FORWARD: bool;
@@ -36,13 +36,13 @@ pub trait Direction {
         A: Analysis<'tcx>;
 
     /// Called by `ResultsVisitor` to recompute the analysis domain values for
-    /// all locations in a basic block (starting from the entry value stored
-    /// in `Results`) and to visit them with `vis`.
+    /// all locations in a basic block (starting from `entry_state` and to
+    /// visit them with `vis`.
     fn visit_results_in_block<'mir, 'tcx, A>(
         state: &mut A::Domain,
         block: BasicBlock,
         block_data: &'mir mir::BasicBlockData<'tcx>,
-        results: &mut Results<'tcx, A>,
+        analysis: &mut A,
         vis: &mut impl ResultsVisitor<'tcx, A>,
     ) where
         A: Analysis<'tcx>;
@@ -211,18 +211,15 @@ impl Direction for Backward {
         state: &mut A::Domain,
         block: BasicBlock,
         block_data: &'mir mir::BasicBlockData<'tcx>,
-        results: &mut Results<'tcx, A>,
+        analysis: &mut A,
         vis: &mut impl ResultsVisitor<'tcx, A>,
     ) where
         A: Analysis<'tcx>,
     {
-        state.clone_from(results.entry_set_for_block(block));
-
         vis.visit_block_end(state);
 
         let loc = Location { block, statement_index: block_data.statements.len() };
         let term = block_data.terminator();
-        let analysis = &mut results.analysis;
         analysis.apply_early_terminator_effect(state, term, loc);
         vis.visit_after_early_terminator_effect(analysis, state, term, loc);
         analysis.apply_primary_terminator_effect(state, term, loc);
@@ -394,16 +391,13 @@ impl Direction for Forward {
         state: &mut A::Domain,
         block: BasicBlock,
         block_data: &'mir mir::BasicBlockData<'tcx>,
-        results: &mut Results<'tcx, A>,
+        analysis: &mut A,
         vis: &mut impl ResultsVisitor<'tcx, A>,
     ) where
         A: Analysis<'tcx>,
     {
-        state.clone_from(results.entry_set_for_block(block));
-
         vis.visit_block_start(state);
 
-        let analysis = &mut results.analysis;
         for (statement_index, stmt) in block_data.statements.iter().enumerate() {
             let loc = Location { block, statement_index };
             analysis.apply_early_statement_effect(state, stmt, loc);
diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
index 14389afff6e..a7d5422a3d7 100644
--- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
@@ -21,7 +21,9 @@ use tracing::debug;
 use {rustc_ast as ast, rustc_graphviz as dot};
 
 use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
-use super::{Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor};
+use super::{
+    Analysis, CallReturnPlaces, Direction, Results, ResultsCursor, ResultsVisitor, visit_results,
+};
 use crate::errors::{
     DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter,
 };
@@ -32,7 +34,8 @@ use crate::errors::{
 pub(super) fn write_graphviz_results<'tcx, A>(
     tcx: TyCtxt<'tcx>,
     body: &Body<'tcx>,
-    results: &mut Results<'tcx, A>,
+    analysis: &mut A,
+    results: &Results<A::Domain>,
     pass_name: Option<&'static str>,
 ) -> std::io::Result<()>
 where
@@ -77,7 +80,7 @@ where
 
     let mut buf = Vec::new();
 
-    let graphviz = Formatter::new(body, results, style);
+    let graphviz = Formatter::new(body, analysis, results, style);
     let mut render_opts =
         vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())];
     if tcx.sess.opts.unstable_opts.graphviz_dark_mode {
@@ -203,10 +206,11 @@ where
 {
     body: &'mir Body<'tcx>,
     // The `RefCell` is used because `<Formatter as Labeller>::node_label`
-    // takes `&self`, but it needs to modify the results. This is also the
+    // takes `&self`, but it needs to modify the analysis. This is also the
     // reason for the `Formatter`/`BlockFormatter` split; `BlockFormatter` has
     // the operations that involve the mutation, i.e. within the `borrow_mut`.
-    results: RefCell<&'mir mut Results<'tcx, A>>,
+    analysis: RefCell<&'mir mut A>,
+    results: &'mir Results<A::Domain>,
     style: OutputStyle,
     reachable: DenseBitSet<BasicBlock>,
 }
@@ -217,11 +221,12 @@ where
 {
     fn new(
         body: &'mir Body<'tcx>,
-        results: &'mir mut Results<'tcx, A>,
+        analysis: &'mir mut A,
+        results: &'mir Results<A::Domain>,
         style: OutputStyle,
     ) -> Self {
         let reachable = traversal::reachable_as_bitset(body);
-        Formatter { body, results: results.into(), style, reachable }
+        Formatter { body, analysis: analysis.into(), results, style, reachable }
     }
 }
 
@@ -259,12 +264,12 @@ where
     }
 
     fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
-        let mut results = self.results.borrow_mut();
+        let analysis = &mut **self.analysis.borrow_mut();
 
-        let diffs = StateDiffCollector::run(self.body, *block, *results, self.style);
+        let diffs = StateDiffCollector::run(self.body, *block, analysis, self.results, self.style);
 
         let mut fmt = BlockFormatter {
-            cursor: results.as_results_cursor(self.body),
+            cursor: ResultsCursor::new_borrowing(self.body, analysis, self.results),
             style: self.style,
             bg: Background::Light,
         };
@@ -692,7 +697,8 @@ impl<D> StateDiffCollector<D> {
     fn run<'tcx, A>(
         body: &Body<'tcx>,
         block: BasicBlock,
-        results: &mut Results<'tcx, A>,
+        analysis: &mut A,
+        results: &Results<A::Domain>,
         style: OutputStyle,
     ) -> Self
     where
@@ -700,12 +706,12 @@ impl<D> StateDiffCollector<D> {
         D: DebugWithContext<A>,
     {
         let mut collector = StateDiffCollector {
-            prev_state: results.analysis.bottom_value(body),
+            prev_state: analysis.bottom_value(body),
             after: vec![],
             before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
         };
 
-        results.visit_with(body, std::iter::once(block), &mut collector);
+        visit_results(body, std::iter::once(block), analysis, results, &mut collector);
         collector
     }
 }
diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs
index 09f6cdb5c4a..9cadec100b5 100644
--- a/compiler/rustc_mir_dataflow/src/framework/mod.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs
@@ -58,8 +58,9 @@ mod visitor;
 pub use self::cursor::ResultsCursor;
 pub use self::direction::{Backward, Direction, Forward};
 pub use self::lattice::{JoinSemiLattice, MaybeReachable};
-pub use self::results::{EntryStates, Results};
-pub use self::visitor::{ResultsVisitor, visit_results};
+pub(crate) use self::results::AnalysisAndResults;
+pub use self::results::Results;
+pub use self::visitor::{ResultsVisitor, visit_reachable_results, visit_results};
 
 /// Analysis domains are all bitsets of various kinds. This trait holds
 /// operations needed by all of them.
@@ -247,17 +248,15 @@ pub trait Analysis<'tcx> {
         tcx: TyCtxt<'tcx>,
         body: &'mir mir::Body<'tcx>,
         pass_name: Option<&'static str>,
-    ) -> Results<'tcx, Self>
+    ) -> AnalysisAndResults<'tcx, Self>
     where
         Self: Sized,
         Self::Domain: DebugWithContext<Self>,
     {
-        let mut entry_states =
-            IndexVec::from_fn_n(|_| self.bottom_value(body), body.basic_blocks.len());
-        self.initialize_start_block(body, &mut entry_states[mir::START_BLOCK]);
+        let mut results = IndexVec::from_fn_n(|_| self.bottom_value(body), body.basic_blocks.len());
+        self.initialize_start_block(body, &mut results[mir::START_BLOCK]);
 
-        if Self::Direction::IS_BACKWARD && entry_states[mir::START_BLOCK] != self.bottom_value(body)
-        {
+        if Self::Direction::IS_BACKWARD && results[mir::START_BLOCK] != self.bottom_value(body) {
             bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
         }
 
@@ -280,10 +279,9 @@ pub trait Analysis<'tcx> {
         // every iteration.
         let mut state = self.bottom_value(body);
         while let Some(bb) = dirty_queue.pop() {
-            // Set the state to the entry state of the block.
-            // This is equivalent to `state = entry_states[bb].clone()`,
-            // but it saves an allocation, thus improving compile times.
-            state.clone_from(&entry_states[bb]);
+            // Set the state to the entry state of the block. This is equivalent to `state =
+            // results[bb].clone()`, but it saves an allocation, thus improving compile times.
+            state.clone_from(&results[bb]);
 
             Self::Direction::apply_effects_in_block(
                 &mut self,
@@ -292,7 +290,7 @@ pub trait Analysis<'tcx> {
                 bb,
                 &body[bb],
                 |target: BasicBlock, state: &Self::Domain| {
-                    let set_changed = entry_states[target].join(state);
+                    let set_changed = results[target].join(state);
                     if set_changed {
                         dirty_queue.insert(target);
                     }
@@ -300,16 +298,14 @@ pub trait Analysis<'tcx> {
             );
         }
 
-        let mut results = Results { analysis: self, entry_states };
-
         if tcx.sess.opts.unstable_opts.dump_mir_dataflow {
-            let res = write_graphviz_results(tcx, body, &mut results, pass_name);
+            let res = write_graphviz_results(tcx, body, &mut self, &results, pass_name);
             if let Err(e) = res {
                 error!("Failed to write graphviz dataflow results: {}", e);
             }
         }
 
-        results
+        AnalysisAndResults { analysis: self, results }
     }
 }
 
diff --git a/compiler/rustc_mir_dataflow/src/framework/results.rs b/compiler/rustc_mir_dataflow/src/framework/results.rs
index 93dfc06a878..7b7e981d3a5 100644
--- a/compiler/rustc_mir_dataflow/src/framework/results.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/results.rs
@@ -1,63 +1,30 @@
 //! Dataflow analysis results.
 
 use rustc_index::IndexVec;
-use rustc_middle::mir::{BasicBlock, Body, traversal};
+use rustc_middle::mir::{BasicBlock, Body};
 
-use super::{Analysis, ResultsCursor, ResultsVisitor, visit_results};
-use crate::framework::cursor::ResultsHandle;
+use super::{Analysis, ResultsCursor};
 
-pub type EntryStates<'tcx, A> = IndexVec<BasicBlock, <A as Analysis<'tcx>>::Domain>;
+/// The results of a dataflow analysis that has converged to fixpoint. It only holds the domain
+/// values at the entry of each basic block. Domain values in other parts of the block are
+/// recomputed on the fly by visitors (i.e. `ResultsCursor`, or `ResultsVisitor` impls).
+pub type Results<D> = IndexVec<BasicBlock, D>;
 
-/// A dataflow analysis that has converged to fixpoint. It only holds the domain values at the
-/// entry of each basic block. Domain values in other parts of the block are recomputed on the fly
-/// by visitors (i.e. `ResultsCursor`, or `ResultsVisitor` impls).
-#[derive(Clone)]
-pub struct Results<'tcx, A>
+/// Utility type used in a few places where it's convenient to bundle an analysis with its results.
+pub struct AnalysisAndResults<'tcx, A>
 where
     A: Analysis<'tcx>,
 {
     pub analysis: A,
-    pub entry_states: EntryStates<'tcx, A>,
+    pub results: Results<A::Domain>,
 }
 
-impl<'tcx, A> Results<'tcx, A>
+impl<'tcx, A> AnalysisAndResults<'tcx, A>
 where
     A: Analysis<'tcx>,
 {
-    /// Creates a `ResultsCursor` that mutably borrows the `Results`, which is appropriate when the
-    /// `Results` is also used outside the cursor.
-    pub fn as_results_cursor<'mir>(
-        &'mir mut self,
-        body: &'mir Body<'tcx>,
-    ) -> ResultsCursor<'mir, 'tcx, A> {
-        ResultsCursor::new(body, ResultsHandle::BorrowedMut(self))
-    }
-
-    /// Creates a `ResultsCursor` that takes ownership of the `Results`.
+    /// Creates a `ResultsCursor` that takes ownership of `self`.
     pub fn into_results_cursor<'mir>(self, body: &'mir Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
-        ResultsCursor::new(body, ResultsHandle::Owned(self))
-    }
-
-    /// Gets the dataflow state for the given block.
-    pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain {
-        &self.entry_states[block]
-    }
-
-    pub fn visit_with<'mir>(
-        &mut self,
-        body: &'mir Body<'tcx>,
-        blocks: impl IntoIterator<Item = BasicBlock>,
-        vis: &mut impl ResultsVisitor<'tcx, A>,
-    ) {
-        visit_results(body, blocks, self, vis)
-    }
-
-    pub fn visit_reachable_with<'mir>(
-        &mut self,
-        body: &'mir Body<'tcx>,
-        vis: &mut impl ResultsVisitor<'tcx, A>,
-    ) {
-        let blocks = traversal::reachable(body);
-        visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
+        ResultsCursor::new_owning(body, self.analysis, self.results)
     }
 }
diff --git a/compiler/rustc_mir_dataflow/src/framework/tests.rs b/compiler/rustc_mir_dataflow/src/framework/tests.rs
index ae0f1179e6f..8602bb55765 100644
--- a/compiler/rustc_mir_dataflow/src/framework/tests.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/tests.rs
@@ -79,7 +79,7 @@ fn mock_body<'tcx>() -> mir::Body<'tcx> {
 ///
 /// The `102` in the block's entry set is derived from the basic block index and ensures that the
 /// expected state is unique across all basic blocks. Remember, it is generated by
-/// `mock_entry_states`, not from actually running `MockAnalysis` to fixpoint.
+/// `mock_results`, not from actually running `MockAnalysis` to fixpoint.
 struct MockAnalysis<'tcx, D> {
     body: &'tcx mir::Body<'tcx>,
     dir: PhantomData<D>,
@@ -96,7 +96,7 @@ impl<D: Direction> MockAnalysis<'_, D> {
         ret
     }
 
-    fn mock_entry_states(&self) -> IndexVec<BasicBlock, DenseBitSet<usize>> {
+    fn mock_results(&self) -> IndexVec<BasicBlock, DenseBitSet<usize>> {
         let empty = self.bottom_value(self.body);
         let mut ret = IndexVec::from_elem(empty, &self.body.basic_blocks);
 
@@ -255,7 +255,7 @@ fn test_cursor<D: Direction>(analysis: MockAnalysis<'_, D>) {
     let body = analysis.body;
 
     let mut cursor =
-        Results { entry_states: analysis.mock_entry_states(), analysis }.into_results_cursor(body);
+        AnalysisAndResults { results: analysis.mock_results(), analysis }.into_results_cursor(body);
 
     cursor.allow_unreachable();
 
diff --git a/compiler/rustc_mir_dataflow/src/framework/visitor.rs b/compiler/rustc_mir_dataflow/src/framework/visitor.rs
index 07360eb68d1..fbb9e410872 100644
--- a/compiler/rustc_mir_dataflow/src/framework/visitor.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/visitor.rs
@@ -1,4 +1,4 @@
-use rustc_middle::mir::{self, BasicBlock, Location};
+use rustc_middle::mir::{self, BasicBlock, Location, traversal};
 
 use super::{Analysis, Direction, Results};
 
@@ -7,12 +7,13 @@ use super::{Analysis, Direction, Results};
 pub fn visit_results<'mir, 'tcx, A>(
     body: &'mir mir::Body<'tcx>,
     blocks: impl IntoIterator<Item = BasicBlock>,
-    results: &mut Results<'tcx, A>,
+    analysis: &mut A,
+    results: &Results<A::Domain>,
     vis: &mut impl ResultsVisitor<'tcx, A>,
 ) where
     A: Analysis<'tcx>,
 {
-    let mut state = results.analysis.bottom_value(body);
+    let mut state = analysis.bottom_value(body);
 
     #[cfg(debug_assertions)]
     let reachable_blocks = mir::traversal::reachable_as_bitset(body);
@@ -22,10 +23,24 @@ pub fn visit_results<'mir, 'tcx, A>(
         assert!(reachable_blocks.contains(block));
 
         let block_data = &body[block];
-        A::Direction::visit_results_in_block(&mut state, block, block_data, results, vis);
+        state.clone_from(&results[block]);
+        A::Direction::visit_results_in_block(&mut state, block, block_data, analysis, vis);
     }
 }
 
+/// Like `visit_results`, but only for reachable blocks.
+pub fn visit_reachable_results<'mir, 'tcx, A>(
+    body: &'mir mir::Body<'tcx>,
+    analysis: &mut A,
+    results: &Results<A::Domain>,
+    vis: &mut impl ResultsVisitor<'tcx, A>,
+) where
+    A: Analysis<'tcx>,
+{
+    let blocks = traversal::reachable(body).map(|(bb, _)| bb);
+    visit_results(body, blocks, analysis, results, vis)
+}
+
 /// A visitor over the results of an `Analysis`. Use this when you want to inspect domain values in
 /// many or all locations; use `ResultsCursor` if you want to inspect domain values only in certain
 /// locations.