Remove Analysis::into_engine.

This is a standard pattern:
```
MyAnalysis.into_engine(tcx, body).iterate_to_fixpoint()
```
`into_engine` and `iterate_to_fixpoint` are always called in pairs, but
sometimes with a builder-style `pass_name` call between them. But a
builder-style interface is overkill here. This has been bugging me a for
a while.

This commit:
- Merges `Engine::new` and `Engine::iterate_to_fixpoint`. This removes
  the need for `Engine` to have fields, leaving it as a trivial type
  that the next commit will remove.
- Renames `Analysis::into_engine` as `Analysis::iterate_to_fixpoint`,
  gives it an extra argument for the optional pass name, and makes it
  call `Engine::iterate_to_fixpoint` instead of `Engine::new`.

This turns the pattern from above into this:
```
MyAnalysis.iterate_to_fixpoint(tcx, body, None)
```
which is shorter at every call site, and there's less plumbing required
to support it.
This commit is contained in:
Nicholas Nethercote 2024-10-30 08:45:46 +11:00
parent 31e102c509
commit e54c177118
14 changed files with 72 additions and 116 deletions

View File

@ -193,9 +193,7 @@ fn do_mir_borrowck<'tcx>(
.map(|(idx, body)| (idx, MoveData::gather_moves(body, tcx, |_| true)));
let mut flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, Some("borrowck"))
.into_results_cursor(body);
let locals_are_invalidated_at_exit = tcx.hir().body_owner_kind(def).is_fn_or_closure();
@ -243,18 +241,21 @@ fn do_mir_borrowck<'tcx>(
// usage significantly on some benchmarks.
drop(flow_inits);
let flow_borrows = Borrows::new(tcx, body, &regioncx, &borrow_set)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint();
let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint();
let flow_ever_inits = EverInitializedPlaces::new(body, &move_data)
.into_engine(tcx, body)
.pass_name("borrowck")
.iterate_to_fixpoint();
let flow_borrows = Borrows::new(tcx, body, &regioncx, &borrow_set).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let flow_ever_inits = EverInitializedPlaces::new(body, &move_data).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let movable_coroutine =
// The first argument is the coroutine type passed by value

View File

@ -63,8 +63,7 @@ fn needs_drop(
let ConstCx { tcx, body, .. } = *ccx;
FlowSensitiveAnalysis::new(NeedsDrop, ccx)
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body)
});
@ -93,8 +92,7 @@ pub(crate) fn needs_non_const_drop(
let ConstCx { tcx, body, .. } = *ccx;
FlowSensitiveAnalysis::new(NeedsNonConstDrop, ccx)
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body)
});
@ -123,8 +121,7 @@ fn has_mut_interior(
let ConstCx { tcx, body, .. } = *ccx;
FlowSensitiveAnalysis::new(HasMutInterior, ccx)
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body)
});
@ -239,8 +236,7 @@ fn local_is_transient(&mut self, local: Local) -> bool {
let always_live_locals = &always_storage_live_locals(&ccx.body);
let mut maybe_storage_live =
MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
.into_engine(ccx.tcx, &ccx.body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(ccx.tcx, &ccx.body, None)
.into_results_cursor(&ccx.body);
// And then check all `Return` in the MIR, and if a local is "maybe live" at a

View File

@ -71,25 +71,21 @@ pub fn visit_reachable_with<'mir>(
}
/// A solver for dataflow problems.
pub struct Engine<'mir, 'tcx, A>
where
A: Analysis<'tcx>,
{
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
entry_sets: IndexVec<BasicBlock, A::Domain>,
pass_name: Option<&'static str>,
analysis: A,
}
pub struct Engine;
impl<'mir, 'tcx, A, D> Engine<'mir, 'tcx, A>
where
A: Analysis<'tcx, Domain = D>,
D: Clone + JoinSemiLattice,
{
impl Engine {
/// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
/// function.
pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>, analysis: A) -> Self {
pub(crate) fn iterate_to_fixpoint<'mir, 'tcx, A>(
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
mut analysis: A,
pass_name: Option<&'static str>,
) -> Results<'tcx, A>
where
A: Analysis<'tcx>,
A::Domain: DebugWithContext<A> + Clone + JoinSemiLattice,
{
let mut entry_sets =
IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len());
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
@ -99,25 +95,6 @@ pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>, analysis: A) -
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
}
Engine { analysis, tcx, body, pass_name: None, entry_sets }
}
/// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
///
/// Some analyses are run multiple times in the compilation pipeline. Give them a `pass_name`
/// to differentiate them. Otherwise, only the results for the latest run will be saved.
pub fn pass_name(mut self, name: &'static str) -> Self {
self.pass_name = Some(name);
self
}
/// Computes the fixpoint for this dataflow problem and returns it.
pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
where
A::Domain: DebugWithContext<A>,
{
let Engine { mut analysis, body, mut entry_sets, tcx, pass_name } = self;
let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());
if A::Direction::IS_FORWARD {

View File

@ -7,18 +7,17 @@
//!
//! The `impls` module contains several examples of dataflow analyses.
//!
//! Create an `Engine` for your analysis using the `into_engine` method on the `Analysis` trait,
//! then call `iterate_to_fixpoint`. From there, you can use a `ResultsCursor` to inspect the
//! fixpoint solution to your dataflow problem, or implement the `ResultsVisitor` interface and use
//! `visit_results`. The following example uses the `ResultsCursor` approach.
//! Then call `iterate_to_fixpoint` on your type that impls `Analysis` to get a `Results`. From
//! there, you can use a `ResultsCursor` to inspect the fixpoint solution to your dataflow problem,
//! or implement the `ResultsVisitor` interface and use `visit_results`. The following example uses
//! the `ResultsCursor` approach.
//!
//! ```ignore (cross-crate-imports)
//! use rustc_const_eval::dataflow::Analysis; // Makes `into_engine` available.
//! use rustc_const_eval::dataflow::Analysis; // Makes `iterate_to_fixpoint` available.
//!
//! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
//! let analysis = MyAnalysis::new()
//! .into_engine(tcx, body)
//! .iterate_to_fixpoint()
//! .iterate_to_fixpoint(tcx, body, None)
//! .into_results_cursor(body);
//!
//! // Print the dataflow state *after* each statement in the start block.
@ -39,6 +38,8 @@
use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges};
use rustc_middle::ty::TyCtxt;
use super::fmt::DebugWithContext;
mod cursor;
mod direction;
mod engine;
@ -223,26 +224,30 @@ fn apply_switch_int_edge_effects(
/* Extension methods */
/// Creates an `Engine` to find the fixpoint for this dataflow problem.
/// Finds the fixpoint for this dataflow problem.
///
/// You shouldn't need to override this. Its purpose is to enable method chaining like so:
///
/// ```ignore (cross-crate-imports)
/// let results = MyAnalysis::new(tcx, body)
/// .into_engine(tcx, body, def_id)
/// .iterate_to_fixpoint()
/// .iterate_to_fixpoint(tcx, body, None)
/// .into_results_cursor(body);
/// ```
#[inline]
fn into_engine<'mir>(
/// You can optionally add a `pass_name` to the graphviz output for this particular run of a
/// dataflow analysis. Some analyses are run multiple times in the compilation pipeline.
/// Without a `pass_name` to differentiates them, only the results for the latest run will be
/// saved.
fn iterate_to_fixpoint<'mir>(
self,
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
) -> Engine<'mir, 'tcx, Self>
pass_name: Option<&'static str>,
) -> Results<'tcx, Self>
where
Self: Sized,
Self::Domain: DebugWithContext<Self>,
{
Engine::new(tcx, body, self)
Engine::iterate_to_fixpoint(tcx, body, self, pass_name)
}
}

View File

@ -43,31 +43,28 @@ pub fn sanity_check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
let move_data = MoveData::gather_moves(body, tcx, |_| true);
if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {
let flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
.into_engine(tcx, body)
.iterate_to_fixpoint();
let flow_inits =
MaybeInitializedPlaces::new(tcx, body, &move_data).iterate_to_fixpoint(tcx, body, None);
sanity_check_via_rustc_peek(tcx, flow_inits.into_results_cursor(body));
}
if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() {
let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data)
.into_engine(tcx, body)
.iterate_to_fixpoint();
.iterate_to_fixpoint(tcx, body, None);
sanity_check_via_rustc_peek(tcx, flow_uninits.into_results_cursor(body));
}
if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_definite_init).is_some() {
let flow_def_inits = DefinitelyInitializedPlaces::new(body, &move_data)
.into_engine(tcx, body)
.iterate_to_fixpoint();
let flow_def_inits =
DefinitelyInitializedPlaces::new(body, &move_data).iterate_to_fixpoint(tcx, body, None);
sanity_check_via_rustc_peek(tcx, flow_def_inits.into_results_cursor(body));
}
if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() {
let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint();
let flow_liveness = MaybeLiveLocals.iterate_to_fixpoint(tcx, body, None);
sanity_check_via_rustc_peek(tcx, flow_liveness.into_results_cursor(body));
}

View File

@ -666,14 +666,13 @@ fn locals_live_across_suspend_points<'tcx>(
// Calculate when MIR locals have live storage. This gives us an upper bound of their
// lifetimes.
let mut storage_live = MaybeStorageLive::new(std::borrow::Cow::Borrowed(always_live_locals))
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body);
// Calculate the MIR locals which have been previously
// borrowed (even if they are still active).
let borrowed_locals_results =
MaybeBorrowedLocals.into_engine(tcx, body).pass_name("coroutine").iterate_to_fixpoint();
MaybeBorrowedLocals.iterate_to_fixpoint(tcx, body, Some("coroutine"));
let mut borrowed_locals_cursor = borrowed_locals_results.clone().into_results_cursor(body);
@ -681,16 +680,12 @@ fn locals_live_across_suspend_points<'tcx>(
// for.
let mut requires_storage_cursor =
MaybeRequiresStorage::new(borrowed_locals_results.into_results_cursor(body))
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body);
// Calculate the liveness of MIR locals ignoring borrows.
let mut liveness = MaybeLiveLocals
.into_engine(tcx, body)
.pass_name("coroutine")
.iterate_to_fixpoint()
.into_results_cursor(body);
let mut liveness =
MaybeLiveLocals.iterate_to_fixpoint(tcx, body, Some("coroutine")).into_results_cursor(body);
let mut storage_liveness_map = IndexVec::from_elem(None, &body.basic_blocks);
let mut live_locals_at_suspension_points = Vec::new();

View File

@ -59,7 +59,7 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// Perform the actual dataflow analysis.
let analysis = ConstAnalysis::new(tcx, body, map);
let mut results = debug_span!("analyze")
.in_scope(|| analysis.wrap().into_engine(tcx, body).iterate_to_fixpoint());
.in_scope(|| analysis.wrap().iterate_to_fixpoint(tcx, body, None));
// Collect results and patch the body afterwards.
let mut visitor = Collector::new(tcx, &body.local_decls);

View File

@ -37,8 +37,7 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
always_live.union(&borrowed_locals);
let mut live = MaybeTransitiveLiveLocals::new(&always_live)
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body);
// For blocks with a call terminator, if an argument copy can be turned into a move,

View File

@ -169,10 +169,7 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let borrowed = rustc_mir_dataflow::impls::borrowed_locals(body);
let live = MaybeLiveLocals
.into_engine(tcx, body)
.pass_name("MaybeLiveLocals-DestinationPropagation")
.iterate_to_fixpoint();
let live = MaybeLiveLocals.iterate_to_fixpoint(tcx, body, Some("MaybeLiveLocals-DestProp"));
let points = DenseLocationMap::new(body);
let mut live = save_as_intervals(&points, body, live);

View File

@ -64,18 +64,14 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let mut inits = MaybeInitializedPlaces::new(tcx, body, &env.move_data)
.skipping_unreachable_unwind()
.into_engine(tcx, body)
.pass_name("elaborate_drops")
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, Some("elaborate_drops"))
.into_results_cursor(body);
let dead_unwinds = compute_dead_unwinds(body, &mut inits);
let uninits = MaybeUninitializedPlaces::new(tcx, body, &env.move_data)
.mark_inactive_variants_as_uninit()
.skipping_unreachable_unwind(dead_unwinds)
.into_engine(tcx, body)
.pass_name("elaborate_drops")
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, Some("elaborate_drops"))
.into_results_cursor(body);
let drop_flags = IndexVec::from_elem(None, &env.move_data.move_paths);

View File

@ -17,13 +17,11 @@ pub(super) fn lint_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, when: String
let always_live_locals = &always_storage_live_locals(body);
let maybe_storage_live = MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body);
let maybe_storage_dead = MaybeStorageDead::new(Cow::Borrowed(always_live_locals))
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body);
let mut lint = Lint {

View File

@ -126,8 +126,7 @@ fn compute_replacement<'tcx>(
// Compute `MaybeStorageDead` dataflow to check that we only replace when the pointee is
// definitely live.
let mut maybe_dead = MaybeStorageDead::new(Cow::Owned(always_live_locals))
.into_engine(tcx, body)
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, None)
.into_results_cursor(body);
// Map for each local to the pointee.

View File

@ -22,9 +22,7 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let move_data = MoveData::gather_moves(body, tcx, |ty| ty.needs_drop(tcx, param_env));
let mut maybe_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
.into_engine(tcx, body)
.pass_name("remove_uninit_drops")
.iterate_to_fixpoint()
.iterate_to_fixpoint(tcx, body, Some("remove_uninit_drops"))
.into_results_cursor(body);
let mut to_remove = vec![];

View File

@ -185,9 +185,7 @@ pub fn new(cx: &LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self {
vis.into_map(cx)
};
let maybe_storage_live_result = MaybeStorageLive::new(Cow::Owned(BitSet::new_empty(mir.local_decls.len())))
.into_engine(cx.tcx, mir)
.pass_name("redundant_clone")
.iterate_to_fixpoint()
.iterate_to_fixpoint(cx.tcx, mir, Some("redundant_clone"))
.into_results_cursor(mir);
let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
vis.visit_body(mir);