Make Analysis immutable in many more places.

The `state: A::Domain` value is the primary things that's modified when
performing an analysis. The `Analysis` impl is immutable in every case
but one (`MaybeRequiredStorage`) and it now uses interior mutability.

As well as changing many `&mut A` arguments to `&A`, this also:
- lets `CowMut` be replaced with the simpler `SimpleCow` in `cursor.rs`;
- removes the need for the `RefCell` in `Formatter`;
- removes the need for `MaybeBorrowedLocals` to impl `Clone`, because
  it's a unit type and it's now clear that its constructor can be used
  directly instead of being put into a local variable and cloned.
This commit is contained in:
Nicholas Nethercote 2025-10-27 16:06:39 +11:00
parent 8afbd9fe02
commit a97cd3ba57
16 changed files with 111 additions and 140 deletions

View file

@ -1,8 +1,7 @@
//! Random access inspection of the results of a dataflow analysis.
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
#[cfg(debug_assertions)]
use rustc_index::bit_set::DenseBitSet;
@ -10,30 +9,20 @@ use rustc_middle::mir::{self, BasicBlock, Location};
use super::{Analysis, Direction, Effect, EffectIndex, Results};
/// 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),
/// This is like `Cow`, but it lacks the `T: ToOwned` bound and doesn't support
/// `to_owned`/`into_owned`.
enum SimpleCow<'a, T> {
Borrowed(&'a T),
Owned(T),
}
impl<T> Deref for CowMut<'_, T> {
impl<T> Deref for SimpleCow<'_, T> {
type Target = T;
fn deref(&self) -> &T {
match self {
CowMut::BorrowedMut(borrowed) => borrowed,
CowMut::Owned(owned) => owned,
}
}
}
impl<T> DerefMut for CowMut<'_, T> {
fn deref_mut(&mut self) -> &mut T {
match self {
CowMut::BorrowedMut(borrowed) => borrowed,
CowMut::Owned(owned) => owned,
SimpleCow::Borrowed(borrowed) => borrowed,
SimpleCow::Owned(owned) => owned,
}
}
}
@ -53,8 +42,8 @@ where
A: Analysis<'tcx>,
{
body: &'mir mir::Body<'tcx>,
analysis: CowMut<'mir, A>,
results: Cow<'mir, Results<A::Domain>>,
analysis: SimpleCow<'mir, A>,
results: SimpleCow<'mir, Results<A::Domain>>,
state: A::Domain,
pos: CursorPosition,
@ -84,8 +73,8 @@ where
fn new(
body: &'mir mir::Body<'tcx>,
analysis: CowMut<'mir, A>,
results: Cow<'mir, Results<A::Domain>>,
analysis: SimpleCow<'mir, A>,
results: SimpleCow<'mir, Results<A::Domain>>,
) -> Self {
let bottom_value = analysis.bottom_value(body);
ResultsCursor {
@ -111,16 +100,16 @@ where
analysis: A,
results: Results<A::Domain>,
) -> Self {
Self::new(body, CowMut::Owned(analysis), Cow::Owned(results))
Self::new(body, SimpleCow::Owned(analysis), SimpleCow::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,
analysis: &'mir A,
results: &'mir Results<A::Domain>,
) -> Self {
Self::new(body, CowMut::BorrowedMut(analysis), Cow::Borrowed(results))
Self::new(body, SimpleCow::Borrowed(analysis), SimpleCow::Borrowed(results))
}
/// Allows inspection of unreachable basic blocks even with `debug_assertions` enabled.
@ -236,7 +225,7 @@ where
let target_effect_index = effect.at_index(target.statement_index);
A::Direction::apply_effects_in_range(
&mut *self.analysis,
&*self.analysis,
&mut self.state,
target.block,
block_data,
@ -251,8 +240,8 @@ 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.analysis, &mut self.state);
pub fn apply_custom_effect(&mut self, f: impl FnOnce(&A, &mut A::Domain)) {
f(&self.analysis, &mut self.state);
self.state_needs_reset = true;
}
}

View file

@ -14,7 +14,7 @@ pub trait Direction {
/// Called by `iterate_to_fixpoint` during initial analysis computation.
fn apply_effects_in_block<'mir, 'tcx, A>(
analysis: &mut A,
analysis: &A,
body: &mir::Body<'tcx>,
state: &mut A::Domain,
block: BasicBlock,
@ -28,7 +28,7 @@ pub trait Direction {
///
/// `effects.start()` must precede or equal `effects.end()` in this direction.
fn apply_effects_in_range<'tcx, A>(
analysis: &mut A,
analysis: &A,
state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
@ -40,7 +40,7 @@ pub trait Direction {
/// all locations in a basic block (starting from `entry_state` and to
/// visit them with `vis`.
fn visit_results_in_block<'mir, 'tcx, A>(
analysis: &mut A,
analysis: &A,
state: &mut A::Domain,
block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>,
@ -56,7 +56,7 @@ impl Direction for Backward {
const IS_FORWARD: bool = false;
fn apply_effects_in_block<'mir, 'tcx, A>(
analysis: &mut A,
analysis: &A,
body: &mir::Body<'tcx>,
state: &mut A::Domain,
block: BasicBlock,
@ -129,7 +129,7 @@ impl Direction for Backward {
}
fn apply_effects_in_range<'tcx, A>(
analysis: &mut A,
analysis: &A,
state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
@ -206,7 +206,7 @@ impl Direction for Backward {
}
fn visit_results_in_block<'mir, 'tcx, A>(
analysis: &mut A,
analysis: &A,
state: &mut A::Domain,
block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>,
@ -242,7 +242,7 @@ impl Direction for Forward {
const IS_FORWARD: bool = true;
fn apply_effects_in_block<'mir, 'tcx, A>(
analysis: &mut A,
analysis: &A,
body: &mir::Body<'tcx>,
state: &mut A::Domain,
block: BasicBlock,
@ -312,7 +312,7 @@ impl Direction for Forward {
}
fn apply_effects_in_range<'tcx, A>(
analysis: &mut A,
analysis: &A,
state: &mut A::Domain,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
@ -386,7 +386,7 @@ impl Direction for Forward {
}
fn visit_results_in_block<'mir, 'tcx, A>(
analysis: &mut A,
analysis: &A,
state: &mut A::Domain,
block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>,

View file

@ -1,7 +1,6 @@
//! A helpful diagram for debugging dataflow problems.
use std::borrow::Cow;
use std::cell::RefCell;
use std::ffi::OsString;
use std::path::PathBuf;
use std::sync::OnceLock;
@ -33,7 +32,7 @@ use crate::errors::{
pub(super) fn write_graphviz_results<'tcx, A>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
analysis: &mut A,
analysis: &A,
results: &Results<A::Domain>,
pass_name: Option<&'static str>,
) -> std::io::Result<()>
@ -206,11 +205,7 @@ where
A: Analysis<'tcx>,
{
body: &'mir Body<'tcx>,
// The `RefCell` is used because `<Formatter as Labeller>::node_label`
// 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`.
analysis: RefCell<&'mir mut A>,
analysis: &'mir A,
results: &'mir Results<A::Domain>,
style: OutputStyle,
reachable: DenseBitSet<BasicBlock>,
@ -222,12 +217,12 @@ where
{
fn new(
body: &'mir Body<'tcx>,
analysis: &'mir mut A,
analysis: &'mir A,
results: &'mir Results<A::Domain>,
style: OutputStyle,
) -> Self {
let reachable = traversal::reachable_as_bitset(body);
Formatter { body, analysis: analysis.into(), results, style, reachable }
Formatter { body, analysis, results, style, reachable }
}
}
@ -265,12 +260,11 @@ where
}
fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
let analysis = &mut **self.analysis.borrow_mut();
let diffs = StateDiffCollector::run(self.body, *block, analysis, self.results, self.style);
let diffs =
StateDiffCollector::run(self.body, *block, self.analysis, self.results, self.style);
let mut fmt = BlockFormatter {
cursor: ResultsCursor::new_borrowing(self.body, analysis, self.results),
cursor: ResultsCursor::new_borrowing(self.body, self.analysis, self.results),
style: self.style,
bg: Background::Light,
};
@ -698,7 +692,7 @@ impl<D> StateDiffCollector<D> {
fn run<'tcx, A>(
body: &Body<'tcx>,
block: BasicBlock,
analysis: &mut A,
analysis: &A,
results: &Results<A::Domain>,
style: OutputStyle,
) -> Self

View file

@ -136,7 +136,7 @@ pub trait Analysis<'tcx> {
/// analyses should not implement this without also implementing
/// `apply_primary_statement_effect`.
fn apply_early_statement_effect(
&mut self,
&self,
_state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>,
_location: Location,
@ -145,7 +145,7 @@ pub trait Analysis<'tcx> {
/// Updates the current dataflow state with the effect of evaluating a statement.
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
@ -159,7 +159,7 @@ pub trait Analysis<'tcx> {
/// analyses should not implement this without also implementing
/// `apply_primary_terminator_effect`.
fn apply_early_terminator_effect(
&mut self,
&self,
_state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>,
_location: Location,
@ -173,7 +173,7 @@ pub trait Analysis<'tcx> {
/// `InitializedPlaces` analyses, the return place for a function call is not marked as
/// initialized here.
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
_state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
_location: Location,
@ -189,7 +189,7 @@ pub trait Analysis<'tcx> {
/// This is separate from `apply_primary_terminator_effect` to properly track state across
/// unwind edges.
fn apply_call_return_effect(
&mut self,
&self,
_state: &mut Self::Domain,
_block: BasicBlock,
_return_places: CallReturnPlaces<'_, 'tcx>,
@ -211,7 +211,7 @@ pub trait Analysis<'tcx> {
/// engine doesn't need to clone the exit state for a block unless
/// `get_switch_int_data` is actually called.
fn get_switch_int_data(
&mut self,
&self,
_block: mir::BasicBlock,
_discr: &mir::Operand<'tcx>,
) -> Option<Self::SwitchIntData> {
@ -220,7 +220,7 @@ pub trait Analysis<'tcx> {
/// See comments on `get_switch_int_data`.
fn apply_switch_int_edge_effect(
&mut self,
&self,
_data: &mut Self::SwitchIntData,
_state: &mut Self::Domain,
_value: SwitchTargetValue,
@ -245,7 +245,7 @@ pub trait Analysis<'tcx> {
/// Without a `pass_name` to differentiates them, only the results for the latest run will be
/// saved.
fn iterate_to_fixpoint<'mir>(
mut self,
self,
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
pass_name: Option<&'static str>,
@ -285,7 +285,7 @@ pub trait Analysis<'tcx> {
state.clone_from(&results[bb]);
Self::Direction::apply_effects_in_block(
&mut self,
&self,
body,
&mut state,
bb,
@ -300,7 +300,7 @@ pub trait Analysis<'tcx> {
}
if tcx.sess.opts.unstable_opts.dump_mir_dataflow {
let res = write_graphviz_results(tcx, body, &mut self, &results, pass_name);
let res = write_graphviz_results(tcx, body, &self, &results, pass_name);
if let Err(e) = res {
error!("Failed to write graphviz dataflow results: {}", e);
}

View file

@ -175,7 +175,7 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
}
fn apply_early_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>,
location: Location,
@ -185,7 +185,7 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>,
location: Location,
@ -195,7 +195,7 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
}
fn apply_early_terminator_effect(
&mut self,
&self,
state: &mut Self::Domain,
_terminator: &mir::Terminator<'tcx>,
location: Location,
@ -205,7 +205,7 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
}
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
location: Location,

View file

@ -7,7 +7,7 @@ use super::{Analysis, Direction, Results};
pub fn visit_results<'mir, 'tcx, A>(
body: &'mir mir::Body<'tcx>,
blocks: impl IntoIterator<Item = BasicBlock>,
analysis: &mut A,
analysis: &A,
results: &Results<A::Domain>,
vis: &mut impl ResultsVisitor<'tcx, A>,
) where
@ -31,7 +31,7 @@ pub fn visit_results<'mir, 'tcx, A>(
/// Like `visit_results`, but only for reachable blocks.
pub fn visit_reachable_results<'mir, 'tcx, A>(
body: &'mir mir::Body<'tcx>,
analysis: &mut A,
analysis: &A,
results: &Results<A::Domain>,
vis: &mut impl ResultsVisitor<'tcx, A>,
) where

View file

@ -11,7 +11,6 @@ use crate::{Analysis, GenKill};
/// At present, this is used as a very limited form of alias analysis. For example,
/// `MaybeBorrowedLocals` is used to compute which locals are live during a yield expression for
/// immovable coroutines.
#[derive(Clone)]
pub struct MaybeBorrowedLocals;
impl MaybeBorrowedLocals {
@ -34,7 +33,7 @@ impl<'tcx> Analysis<'tcx> for MaybeBorrowedLocals {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
statement: &Statement<'tcx>,
location: Location,
@ -43,7 +42,7 @@ impl<'tcx> Analysis<'tcx> for MaybeBorrowedLocals {
}
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'mir Terminator<'tcx>,
location: Location,

View file

@ -376,7 +376,7 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
@ -400,7 +400,7 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
}
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
location: Location,
@ -429,7 +429,7 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
}
fn apply_call_return_effect(
&mut self,
&self,
state: &mut Self::Domain,
_block: mir::BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
@ -448,7 +448,7 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
}
fn get_switch_int_data(
&mut self,
&self,
block: mir::BasicBlock,
discr: &mir::Operand<'tcx>,
) -> Option<Self::SwitchIntData> {
@ -460,7 +460,7 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
}
fn apply_switch_int_edge_effect(
&mut self,
&self,
data: &mut Self::SwitchIntData,
state: &mut Self::Domain,
value: SwitchTargetValue,
@ -513,7 +513,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
_statement: &mir::Statement<'tcx>,
location: Location,
@ -527,7 +527,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
location: Location,
@ -545,7 +545,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
fn apply_call_return_effect(
&mut self,
&self,
state: &mut Self::Domain,
_block: mir::BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
@ -564,7 +564,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
fn get_switch_int_data(
&mut self,
&self,
block: mir::BasicBlock,
discr: &mir::Operand<'tcx>,
) -> Option<Self::SwitchIntData> {
@ -580,7 +580,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
fn apply_switch_int_edge_effect(
&mut self,
&self,
data: &mut Self::SwitchIntData,
state: &mut Self::Domain,
value: SwitchTargetValue,
@ -627,7 +627,7 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
#[instrument(skip(self, state), level = "debug")]
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
stmt: &mir::Statement<'tcx>,
location: Location,
@ -652,7 +652,7 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
#[instrument(skip(self, state, terminator), level = "debug")]
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
location: Location,
@ -674,7 +674,7 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
}
fn apply_call_return_effect(
&mut self,
&self,
state: &mut Self::Domain,
block: mir::BasicBlock,
_return_places: CallReturnPlaces<'_, 'tcx>,

View file

@ -41,7 +41,7 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
@ -50,7 +50,7 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals {
}
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
location: Location,
@ -60,7 +60,7 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals {
}
fn apply_call_return_effect(
&mut self,
&self,
state: &mut Self::Domain,
_block: mir::BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
@ -278,7 +278,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
@ -294,7 +294,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
}
fn apply_primary_terminator_effect<'mir>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
location: Location,
@ -304,7 +304,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
}
fn apply_call_return_effect(
&mut self,
&self,
state: &mut Self::Domain,
_block: mir::BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,

View file

@ -54,7 +54,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeStorageLive<'a> {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
stmt: &Statement<'tcx>,
_: Location,
@ -98,7 +98,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeStorageDead<'a> {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
stmt: &Statement<'tcx>,
_: Location,
@ -144,7 +144,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
}
fn apply_early_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
stmt: &Statement<'tcx>,
loc: Location,
@ -177,7 +177,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
}
fn apply_primary_statement_effect(
&mut self,
&self,
state: &mut Self::Domain,
_: &Statement<'tcx>,
loc: Location,
@ -188,7 +188,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
}
fn apply_early_terminator_effect(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &Terminator<'tcx>,
loc: Location,
@ -243,7 +243,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
}
fn apply_primary_terminator_effect<'t>(
&mut self,
&self,
state: &mut Self::Domain,
terminator: &'t Terminator<'tcx>,
loc: Location,
@ -284,7 +284,7 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> {
}
fn apply_call_return_effect(
&mut self,
&self,
state: &mut Self::Domain,
_block: BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,