Put Analysis back into Results.

`Results` used to contain an `Analysis`, but it was removed in #140234.
That change made sense because the analysis was mutable but the entry
states were immutable and it was good to separate them so the mutability
of the different pieces was clear.

Now that analyses are immutable there is no need for the separation,
lots of analysis+results pairs can be combined, and the names are going
back to what they were before:
- `Results` -> `EntryStates`
- `AnalysisAndResults` -> `Results`
This commit is contained in:
Nicholas Nethercote 2025-10-28 09:41:58 +11:00
parent a97cd3ba57
commit 8793239702
11 changed files with 77 additions and 107 deletions

View file

@ -49,7 +49,7 @@ use rustc_mir_dataflow::move_paths::{
InitIndex, InitLocation, LookupResult, MoveData, MovePathIndex,
};
use rustc_mir_dataflow::points::DenseLocationMap;
use rustc_mir_dataflow::{Analysis, Results, ResultsVisitor, visit_results};
use rustc_mir_dataflow::{Analysis, EntryStates, Results, ResultsVisitor, visit_results};
use rustc_session::lint::builtin::{TAIL_EXPR_DROP_ORDER, UNUSED_MUT};
use rustc_span::{ErrorGuaranteed, Span, Symbol};
use smallvec::SmallVec;
@ -537,12 +537,10 @@ fn borrowck_check_region_constraints<'tcx>(
mbcx.report_region_errors(nll_errors);
}
let (flow_analysis, flow_results) =
get_flow_results(tcx, body, &move_data, &borrow_set, &regioncx);
let flow_results = get_flow_results(tcx, body, &move_data, &borrow_set, &regioncx);
visit_results(
body,
traversal::reverse_postorder(body).map(|(bb, _)| bb),
&flow_analysis,
&flow_results,
&mut mbcx,
);
@ -604,7 +602,7 @@ fn get_flow_results<'a, 'tcx>(
move_data: &'a MoveData<'tcx>,
borrow_set: &'a BorrowSet<'tcx>,
regioncx: &RegionInferenceContext<'tcx>,
) -> (Borrowck<'a, 'tcx>, Results<BorrowckDomain>) {
) -> Results<'tcx, Borrowck<'a, 'tcx>> {
// We compute these three analyses individually, but them combine them into
// a single results so that `mbcx` can visit them all together.
let borrows = Borrows::new(tcx, body, regioncx, borrow_set).iterate_to_fixpoint(
@ -629,14 +627,14 @@ fn get_flow_results<'a, 'tcx>(
ever_inits: ever_inits.analysis,
};
assert_eq!(borrows.results.len(), uninits.results.len());
assert_eq!(borrows.results.len(), ever_inits.results.len());
let results: Results<_> =
itertools::izip!(borrows.results, uninits.results, ever_inits.results)
assert_eq!(borrows.entry_states.len(), uninits.entry_states.len());
assert_eq!(borrows.entry_states.len(), ever_inits.entry_states.len());
let entry_states: EntryStates<_> =
itertools::izip!(borrows.entry_states, uninits.entry_states, ever_inits.entry_states)
.map(|(borrows, uninits, ever_inits)| BorrowckDomain { borrows, uninits, ever_inits })
.collect();
(analysis, results)
Results { analysis, entry_states }
}
pub(crate) struct BorrowckInferCtxt<'tcx> {

View file

@ -42,8 +42,7 @@ where
A: Analysis<'tcx>,
{
body: &'mir mir::Body<'tcx>,
analysis: SimpleCow<'mir, A>,
results: SimpleCow<'mir, Results<A::Domain>>,
results: SimpleCow<'mir, Results<'tcx, A>>,
state: A::Domain,
pos: CursorPosition,
@ -71,15 +70,10 @@ where
self.body
}
fn new(
body: &'mir mir::Body<'tcx>,
analysis: SimpleCow<'mir, A>,
results: SimpleCow<'mir, Results<A::Domain>>,
) -> Self {
let bottom_value = analysis.bottom_value(body);
fn new(body: &'mir mir::Body<'tcx>, results: SimpleCow<'mir, Results<'tcx, A>>) -> Self {
let bottom_value = results.analysis.bottom_value(body);
ResultsCursor {
body,
analysis,
results,
// Initialize to the `bottom_value` and set `state_needs_reset` to tell the cursor that
@ -95,21 +89,13 @@ 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, SimpleCow::Owned(analysis), SimpleCow::Owned(results))
pub fn new_owning(body: &'mir mir::Body<'tcx>, results: Results<'tcx, A>) -> Self {
Self::new(body, SimpleCow::Owned(results))
}
/// Returns a new cursor that borrows and inspects analysis results.
pub fn new_borrowing(
body: &'mir mir::Body<'tcx>,
analysis: &'mir A,
results: &'mir Results<A::Domain>,
) -> Self {
Self::new(body, SimpleCow::Borrowed(analysis), SimpleCow::Borrowed(results))
pub fn new_borrowing(body: &'mir mir::Body<'tcx>, results: &'mir Results<'tcx, A>) -> Self {
Self::new(body, SimpleCow::Borrowed(results))
}
/// Allows inspection of unreachable basic blocks even with `debug_assertions` enabled.
@ -121,7 +107,7 @@ where
/// Returns the `Analysis` used to generate the underlying `Results`.
pub fn analysis(&self) -> &A {
&self.analysis
&self.results.analysis
}
/// Resets the cursor to hold the entry set for the given basic block.
@ -133,7 +119,7 @@ where
#[cfg(debug_assertions)]
assert!(self.reachable_blocks.contains(block));
self.state.clone_from(&self.results[block]);
self.state.clone_from(&self.results.entry_states[block]);
self.pos = CursorPosition::block_entry(block);
self.state_needs_reset = false;
}
@ -225,7 +211,7 @@ where
let target_effect_index = effect.at_index(target.statement_index);
A::Direction::apply_effects_in_range(
&*self.analysis,
&self.results.analysis,
&mut self.state,
target.block,
block_data,
@ -241,7 +227,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(&A, &mut A::Domain)) {
f(&self.analysis, &mut self.state);
f(&self.results.analysis, &mut self.state);
self.state_needs_reset = true;
}
}

View file

@ -32,8 +32,7 @@ use crate::errors::{
pub(super) fn write_graphviz_results<'tcx, A>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
analysis: &A,
results: &Results<A::Domain>,
results: &Results<'tcx, A>,
pass_name: Option<&'static str>,
) -> std::io::Result<()>
where
@ -80,7 +79,7 @@ where
let mut buf = Vec::new();
let graphviz = Formatter::new(body, analysis, results, style);
let graphviz = Formatter::new(body, 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 {
@ -205,8 +204,7 @@ where
A: Analysis<'tcx>,
{
body: &'mir Body<'tcx>,
analysis: &'mir A,
results: &'mir Results<A::Domain>,
results: &'mir Results<'tcx, A>,
style: OutputStyle,
reachable: DenseBitSet<BasicBlock>,
}
@ -215,14 +213,9 @@ impl<'mir, 'tcx, A> Formatter<'mir, 'tcx, A>
where
A: Analysis<'tcx>,
{
fn new(
body: &'mir Body<'tcx>,
analysis: &'mir A,
results: &'mir Results<A::Domain>,
style: OutputStyle,
) -> Self {
fn new(body: &'mir Body<'tcx>, results: &'mir Results<'tcx, A>, style: OutputStyle) -> Self {
let reachable = traversal::reachable_as_bitset(body);
Formatter { body, analysis, results, style, reachable }
Formatter { body, results, style, reachable }
}
}
@ -260,11 +253,10 @@ where
}
fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
let diffs =
StateDiffCollector::run(self.body, *block, self.analysis, self.results, self.style);
let diffs = StateDiffCollector::run(self.body, *block, self.results, self.style);
let mut fmt = BlockFormatter {
cursor: ResultsCursor::new_borrowing(self.body, self.analysis, self.results),
cursor: ResultsCursor::new_borrowing(self.body, self.results),
style: self.style,
bg: Background::Light,
};
@ -692,8 +684,7 @@ impl<D> StateDiffCollector<D> {
fn run<'tcx, A>(
body: &Body<'tcx>,
block: BasicBlock,
analysis: &A,
results: &Results<A::Domain>,
results: &Results<'tcx, A>,
style: OutputStyle,
) -> Self
where
@ -701,12 +692,12 @@ impl<D> StateDiffCollector<D> {
D: DebugWithContext<A>,
{
let mut collector = StateDiffCollector {
prev_state: analysis.bottom_value(body),
prev_state: results.analysis.bottom_value(body),
after: vec![],
before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]),
};
visit_results(body, std::iter::once(block), analysis, results, &mut collector);
visit_results(body, std::iter::once(block), results, &mut collector);
collector
}
}

View file

@ -58,8 +58,7 @@ mod visitor;
pub use self::cursor::ResultsCursor;
pub use self::direction::{Backward, Direction, Forward};
pub use self::lattice::{JoinSemiLattice, MaybeReachable};
pub(crate) use self::results::AnalysisAndResults;
pub use self::results::Results;
pub use self::results::{EntryStates, Results};
pub use self::visitor::{ResultsVisitor, visit_reachable_results, visit_results};
/// Analysis domains are all bitsets of various kinds. This trait holds
@ -249,15 +248,17 @@ pub trait Analysis<'tcx> {
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
pass_name: Option<&'static str>,
) -> AnalysisAndResults<'tcx, Self>
) -> Results<'tcx, Self>
where
Self: Sized,
Self::Domain: DebugWithContext<Self>,
{
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]);
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]);
if Self::Direction::IS_BACKWARD && results[mir::START_BLOCK] != self.bottom_value(body) {
if Self::Direction::IS_BACKWARD && entry_states[mir::START_BLOCK] != self.bottom_value(body)
{
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
}
@ -281,8 +282,8 @@ pub trait Analysis<'tcx> {
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 =
// results[bb].clone()`, but it saves an allocation, thus improving compile times.
state.clone_from(&results[bb]);
// entry_states[bb].clone()`, but it saves an allocation, thus improving compile times.
state.clone_from(&entry_states[bb]);
Self::Direction::apply_effects_in_block(
&self,
@ -291,7 +292,7 @@ pub trait Analysis<'tcx> {
bb,
&body[bb],
|target: BasicBlock, state: &Self::Domain| {
let set_changed = results[target].join(state);
let set_changed = entry_states[target].join(state);
if set_changed {
dirty_queue.insert(target);
}
@ -299,14 +300,16 @@ pub trait Analysis<'tcx> {
);
}
let results = Results { analysis: self, entry_states };
if tcx.sess.opts.unstable_opts.dump_mir_dataflow {
let res = write_graphviz_results(tcx, body, &self, &results, pass_name);
let res = write_graphviz_results(tcx, body, &results, pass_name);
if let Err(e) = res {
error!("Failed to write graphviz dataflow results: {}", e);
}
}
AnalysisAndResults { analysis: self, results }
results
}
}

View file

@ -5,26 +5,26 @@ use rustc_middle::mir::{BasicBlock, Body};
use super::{Analysis, ResultsCursor};
/// 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>;
pub type EntryStates<D> = IndexVec<BasicBlock, D>;
/// Utility type used in a few places where it's convenient to bundle an analysis with its results.
pub struct AnalysisAndResults<'tcx, A>
/// The results of a dataflow analysis that has converged to fixpoint. It holds the domain values
/// (states) 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). The
/// analysis is also present because it's often needed alongside the entry states.
pub struct Results<'tcx, A>
where
A: Analysis<'tcx>,
{
pub analysis: A,
pub results: Results<A::Domain>,
pub entry_states: EntryStates<A::Domain>,
}
impl<'tcx, A> AnalysisAndResults<'tcx, A>
impl<'tcx, A> Results<'tcx, A>
where
A: Analysis<'tcx>,
{
/// 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_owning(body, self.analysis, self.results)
ResultsCursor::new_owning(body, self)
}
}

View file

@ -71,15 +71,15 @@ fn mock_body<'tcx>() -> mir::Body<'tcx> {
///
/// | Location | Before | After |
/// |------------------------|-------------------|--------|
/// | (on_entry) | {102} ||
/// | (on_entry) | {102} | |
/// | statement 0 | +0 | +1 |
/// | statement 1 | +2 | +3 |
/// | `Call` terminator | +4 | +5 |
/// | (on unwind) | {102,0,1,2,3,4,5} ||
/// | (on unwind) | {102,0,1,2,3,4,5} | |
///
/// 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_results`, not from actually running `MockAnalysis` to fixpoint.
/// `mock_entry_states`, 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_results(&self) -> IndexVec<BasicBlock, DenseBitSet<usize>> {
fn mock_entry_states(&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 =
AnalysisAndResults { results: analysis.mock_results(), analysis }.into_results_cursor(body);
Results { entry_states: analysis.mock_entry_states(), analysis }.into_results_cursor(body);
cursor.allow_unreachable();

View file

@ -7,13 +7,12 @@ use super::{Analysis, Direction, Results};
pub fn visit_results<'mir, 'tcx, A>(
body: &'mir mir::Body<'tcx>,
blocks: impl IntoIterator<Item = BasicBlock>,
analysis: &A,
results: &Results<A::Domain>,
results: &Results<'tcx, A>,
vis: &mut impl ResultsVisitor<'tcx, A>,
) where
A: Analysis<'tcx>,
{
let mut state = analysis.bottom_value(body);
let mut state = results.analysis.bottom_value(body);
#[cfg(debug_assertions)]
let reachable_blocks = mir::traversal::reachable_as_bitset(body);
@ -23,22 +22,21 @@ pub fn visit_results<'mir, 'tcx, A>(
assert!(reachable_blocks.contains(block));
let block_data = &body[block];
state.clone_from(&results[block]);
A::Direction::visit_results_in_block(analysis, &mut state, block, block_data, vis);
state.clone_from(&results.entry_states[block]);
A::Direction::visit_results_in_block(&results.analysis, &mut state, block, block_data, vis);
}
}
/// Like `visit_results`, but only for reachable blocks.
pub fn visit_reachable_results<'mir, 'tcx, A>(
body: &'mir mir::Body<'tcx>,
analysis: &A,
results: &Results<A::Domain>,
results: &Results<'tcx, A>,
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)
visit_results(body, blocks, results, vis)
}
/// A visitor over the results of an `Analysis`. Use this when you want to inspect domain values in

View file

@ -17,8 +17,9 @@ pub use self::drop_flag_effects::{
move_path_children_matching, on_all_children_bits, on_lookup_result_bits,
};
pub use self::framework::{
Analysis, Backward, Direction, Forward, GenKill, JoinSemiLattice, MaybeReachable, Results,
ResultsCursor, ResultsVisitor, fmt, graphviz, lattice, visit_reachable_results, visit_results,
Analysis, Backward, Direction, EntryStates, Forward, GenKill, JoinSemiLattice, MaybeReachable,
Results, ResultsCursor, ResultsVisitor, fmt, graphviz, lattice, visit_reachable_results,
visit_results,
};
use self::move_paths::MoveData;

View file

@ -720,16 +720,13 @@ fn locals_live_across_suspend_points<'tcx>(
// Calculate the MIR locals that have been previously borrowed (even if they are still active).
let borrowed_locals = MaybeBorrowedLocals.iterate_to_fixpoint(tcx, body, Some("coroutine"));
let borrowed_locals_cursor1 =
ResultsCursor::new_borrowing(body, &MaybeBorrowedLocals, &borrowed_locals.results);
let mut borrowed_locals_cursor2 =
ResultsCursor::new_borrowing(body, &MaybeBorrowedLocals, &borrowed_locals.results);
let borrowed_locals_cursor1 = ResultsCursor::new_borrowing(body, &borrowed_locals);
let mut borrowed_locals_cursor2 = ResultsCursor::new_borrowing(body, &borrowed_locals);
// Calculate the MIR locals that we need to keep storage around for.
let requires_storage =
MaybeRequiresStorage::new(borrowed_locals_cursor1).iterate_to_fixpoint(tcx, body, None);
let mut requires_storage_cursor =
ResultsCursor::new_borrowing(body, &requires_storage.analysis, &requires_storage.results);
let mut requires_storage_cursor = ResultsCursor::new_borrowing(body, &requires_storage);
// Calculate the liveness of MIR locals ignoring borrows.
let mut liveness =
@ -801,8 +798,7 @@ fn locals_live_across_suspend_points<'tcx>(
body,
&saved_locals,
always_live_locals.clone(),
&requires_storage.analysis,
&requires_storage.results,
&requires_storage,
);
LivenessInfo {
@ -867,8 +863,7 @@ fn compute_storage_conflicts<'mir, 'tcx>(
body: &'mir Body<'tcx>,
saved_locals: &'mir CoroutineSavedLocals,
always_live_locals: DenseBitSet<Local>,
analysis: &MaybeRequiresStorage<'mir, 'tcx>,
results: &Results<DenseBitSet<Local>>,
results: &Results<'tcx, MaybeRequiresStorage<'mir, 'tcx>>,
) -> BitMatrix<CoroutineSavedLocal, CoroutineSavedLocal> {
assert_eq!(body.local_decls.len(), saved_locals.domain_size());
@ -888,7 +883,7 @@ fn compute_storage_conflicts<'mir, 'tcx>(
eligible_storage_live: DenseBitSet::new_empty(body.local_decls.len()),
};
visit_reachable_results(body, analysis, results, &mut visitor);
visit_reachable_results(body, results, &mut visitor);
let local_conflicts = visitor.local_conflicts;

View file

@ -66,9 +66,7 @@ impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp {
// Collect results and patch the body afterwards.
let mut visitor = Collector::new(tcx, &body.local_decls);
debug_span!("collect").in_scope(|| {
visit_reachable_results(body, &const_.analysis, &const_.results, &mut visitor)
});
debug_span!("collect").in_scope(|| visit_reachable_results(body, &const_, &mut visitor));
let mut patch = visitor.patch;
debug_span!("patch").in_scope(|| patch.visit_body_preserves_cfg(body));
}

View file

@ -146,7 +146,7 @@ use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_mir_dataflow::impls::{DefUse, MaybeLiveLocals};
use rustc_mir_dataflow::points::DenseLocationMap;
use rustc_mir_dataflow::{Analysis, Results};
use rustc_mir_dataflow::{Analysis, EntryStates};
use tracing::{debug, trace};
pub(super) struct DestinationPropagation;
@ -173,7 +173,7 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
let points = DenseLocationMap::new(body);
let mut relevant = RelevantLocals::compute(&candidates, body.local_decls.len());
let mut live = save_as_intervals(&points, body, &relevant, live.results);
let mut live = save_as_intervals(&points, body, &relevant, live.entry_states);
dest_prop_mir_dump(tcx, body, &points, &live, &relevant);
@ -506,7 +506,7 @@ fn save_as_intervals<'tcx>(
elements: &DenseLocationMap,
body: &Body<'tcx>,
relevant: &RelevantLocals,
results: Results<DenseBitSet<Local>>,
entry_states: EntryStates<DenseBitSet<Local>>,
) -> SparseIntervalMatrix<RelevantLocal, TwoStepIndex> {
let mut values = SparseIntervalMatrix::new(2 * elements.num_points());
let mut state = MaybeLiveLocals.bottom_value(body);
@ -529,7 +529,7 @@ fn save_as_intervals<'tcx>(
continue;
}
state.clone_from(&results[block]);
state.clone_from(&entry_states[block]);
let block_data = &body.basic_blocks[block];
let loc = Location { block, statement_index: block_data.statements.len() };