Remove Engine::new_gen_kill.

This is an alternative to `Engine::new_generic` for gen/kill analyses.
It's supposed to be an optimization, but it has negligible effect.
The commit merges `Engine::new_generic` into `Engine::new`.

This allows the removal of various other things: `GenKillSet`,
`gen_kill_statement_effects_in_block`, `is_cfg_cyclic`.
This commit is contained in:
Nicholas Nethercote 2024-10-10 06:41:03 +11:00
parent 874b03ec28
commit e0b83c34c3
4 changed files with 17 additions and 197 deletions

View File

@ -26,7 +26,6 @@ type SwitchSources = FxHashMap<(BasicBlock, BasicBlock), SmallVec<[Option<u128>;
struct Cache { struct Cache {
predecessors: OnceLock<Predecessors>, predecessors: OnceLock<Predecessors>,
switch_sources: OnceLock<SwitchSources>, switch_sources: OnceLock<SwitchSources>,
is_cyclic: OnceLock<bool>,
reverse_postorder: OnceLock<Vec<BasicBlock>>, reverse_postorder: OnceLock<Vec<BasicBlock>>,
dominators: OnceLock<Dominators<BasicBlock>>, dominators: OnceLock<Dominators<BasicBlock>>,
} }
@ -37,12 +36,6 @@ impl<'tcx> BasicBlocks<'tcx> {
BasicBlocks { basic_blocks, cache: Cache::default() } BasicBlocks { basic_blocks, cache: Cache::default() }
} }
/// Returns true if control-flow graph contains a cycle reachable from the `START_BLOCK`.
#[inline]
pub fn is_cfg_cyclic(&self) -> bool {
*self.cache.is_cyclic.get_or_init(|| graph::is_cyclic(self))
}
pub fn dominators(&self) -> &Dominators<BasicBlock> { pub fn dominators(&self) -> &Dominators<BasicBlock> {
self.cache.dominators.get_or_init(|| dominators(self)) self.cache.dominators.get_or_init(|| dominators(self))
} }

View File

@ -5,7 +5,7 @@ use rustc_middle::mir::{
}; };
use super::visitor::{ResultsVisitable, ResultsVisitor}; use super::visitor::{ResultsVisitable, ResultsVisitor};
use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget}; use super::{Analysis, Effect, EffectIndex, SwitchIntTarget};
pub trait Direction { pub trait Direction {
const IS_FORWARD: bool; const IS_FORWARD: bool;
@ -29,19 +29,10 @@ pub trait Direction {
state: &mut A::Domain, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>, block_data: &'mir mir::BasicBlockData<'tcx>,
statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
) -> TerminatorEdges<'mir, 'tcx> ) -> TerminatorEdges<'mir, 'tcx>
where where
A: Analysis<'tcx>; A: Analysis<'tcx>;
fn gen_kill_statement_effects_in_block<'tcx, A>(
analysis: &mut A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: GenKillAnalysis<'tcx>;
fn visit_results_in_block<'mir, 'tcx, D, R>( fn visit_results_in_block<'mir, 'tcx, D, R>(
state: &mut D, state: &mut D,
block: BasicBlock, block: BasicBlock,
@ -73,7 +64,6 @@ impl Direction for Backward {
state: &mut A::Domain, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>, block_data: &'mir mir::BasicBlockData<'tcx>,
statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
) -> TerminatorEdges<'mir, 'tcx> ) -> TerminatorEdges<'mir, 'tcx>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
@ -82,31 +72,12 @@ impl Direction for Backward {
let location = Location { block, statement_index: block_data.statements.len() }; let location = Location { block, statement_index: block_data.statements.len() };
analysis.apply_before_terminator_effect(state, terminator, location); analysis.apply_before_terminator_effect(state, terminator, location);
let edges = analysis.apply_terminator_effect(state, terminator, location); let edges = analysis.apply_terminator_effect(state, terminator, location);
if let Some(statement_effect) = statement_effect {
statement_effect(block, state)
} else {
for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
let location = Location { block, statement_index };
analysis.apply_before_statement_effect(state, statement, location);
analysis.apply_statement_effect(state, statement, location);
}
}
edges
}
fn gen_kill_statement_effects_in_block<'tcx, A>(
analysis: &mut A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: GenKillAnalysis<'tcx>,
{
for (statement_index, statement) in block_data.statements.iter().enumerate().rev() { for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
let location = Location { block, statement_index }; let location = Location { block, statement_index };
analysis.before_statement_effect(trans, statement, location); analysis.apply_before_statement_effect(state, statement, location);
analysis.statement_effect(trans, statement, location); analysis.apply_statement_effect(state, statement, location);
} }
edges
} }
fn apply_effects_in_range<'tcx, A>( fn apply_effects_in_range<'tcx, A>(
@ -330,42 +301,21 @@ impl Direction for Forward {
state: &mut A::Domain, state: &mut A::Domain,
block: BasicBlock, block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>, block_data: &'mir mir::BasicBlockData<'tcx>,
statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
) -> TerminatorEdges<'mir, 'tcx> ) -> TerminatorEdges<'mir, 'tcx>
where where
A: Analysis<'tcx>, A: Analysis<'tcx>,
{ {
if let Some(statement_effect) = statement_effect { for (statement_index, statement) in block_data.statements.iter().enumerate() {
statement_effect(block, state) let location = Location { block, statement_index };
} else { analysis.apply_before_statement_effect(state, statement, location);
for (statement_index, statement) in block_data.statements.iter().enumerate() { analysis.apply_statement_effect(state, statement, location);
let location = Location { block, statement_index };
analysis.apply_before_statement_effect(state, statement, location);
analysis.apply_statement_effect(state, statement, location);
}
} }
let terminator = block_data.terminator(); let terminator = block_data.terminator();
let location = Location { block, statement_index: block_data.statements.len() }; let location = Location { block, statement_index: block_data.statements.len() };
analysis.apply_before_terminator_effect(state, terminator, location); analysis.apply_before_terminator_effect(state, terminator, location);
analysis.apply_terminator_effect(state, terminator, location) analysis.apply_terminator_effect(state, terminator, location)
} }
fn gen_kill_statement_effects_in_block<'tcx, A>(
analysis: &mut A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: GenKillAnalysis<'tcx>,
{
for (statement_index, statement) in block_data.statements.iter().enumerate() {
let location = Location { block, statement_index };
analysis.before_statement_effect(trans, statement, location);
analysis.statement_effect(trans, statement, location);
}
}
fn apply_effects_in_range<'tcx, A>( fn apply_effects_in_range<'tcx, A>(
analysis: &mut A, analysis: &mut A,
state: &mut A::Domain, state: &mut A::Domain,

View File

@ -5,7 +5,7 @@ use std::path::PathBuf;
use rustc_data_structures::work_queue::WorkQueue; use rustc_data_structures::work_queue::WorkQueue;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_index::{Idx, IndexVec}; use rustc_index::IndexVec;
use rustc_middle::bug; use rustc_middle::bug;
use rustc_middle::mir::{self, BasicBlock, create_dump_file, dump_enabled, traversal}; use rustc_middle::mir::{self, BasicBlock, create_dump_file, dump_enabled, traversal};
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
@ -16,13 +16,12 @@ use {rustc_ast as ast, rustc_graphviz as dot};
use super::fmt::DebugWithContext; use super::fmt::DebugWithContext;
use super::{ use super::{
Analysis, AnalysisDomain, Direction, GenKill, GenKillAnalysis, GenKillSet, JoinSemiLattice, Analysis, AnalysisDomain, Direction, JoinSemiLattice, ResultsCursor, ResultsVisitor, graphviz,
ResultsCursor, ResultsVisitor, graphviz, visit_results, visit_results,
}; };
use crate::errors::{ use crate::errors::{
DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter, DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter,
}; };
use crate::framework::BitSetExt;
type EntrySets<'tcx, A> = IndexVec<BasicBlock, <A as AnalysisDomain<'tcx>>::Domain>; type EntrySets<'tcx, A> = IndexVec<BasicBlock, <A as AnalysisDomain<'tcx>>::Domain>;
@ -82,53 +81,6 @@ where
entry_sets: IndexVec<BasicBlock, A::Domain>, entry_sets: IndexVec<BasicBlock, A::Domain>,
pass_name: Option<&'static str>, pass_name: Option<&'static str>,
analysis: A, analysis: A,
/// Cached, cumulative transfer functions for each block.
//
// FIXME(ecstaticmorse): This boxed `Fn` trait object is invoked inside a tight loop for
// gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
// performance in practice. I've tried a few ways to avoid this, but they have downsides. See
// the message for the commit that added this FIXME for more information.
apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
}
impl<'mir, 'tcx, A, D, T> Engine<'mir, 'tcx, A>
where
A: GenKillAnalysis<'tcx, Idx = T, Domain = D>,
D: Clone + JoinSemiLattice + GenKill<T> + BitSetExt<T>,
T: Idx,
{
/// Creates a new `Engine` to solve a gen-kill dataflow problem.
pub fn new_gen_kill(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>, mut analysis: A) -> Self {
// If there are no back-edges in the control-flow graph, we only ever need to apply the
// transfer function for each block exactly once (assuming that we process blocks in RPO).
//
// In this case, there's no need to compute the block transfer functions ahead of time.
if !body.basic_blocks.is_cfg_cyclic() {
return Self::new(tcx, body, analysis, None);
}
// Otherwise, compute and store the cumulative transfer function for each block.
let identity = GenKillSet::identity(analysis.domain_size(body));
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_statement_effects_in_block(
&mut analysis,
trans,
block,
block_data,
);
}
let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
trans_for_block[bb].apply(state);
});
Self::new(tcx, body, analysis, Some(apply_trans as Box<_>))
}
} }
impl<'mir, 'tcx, A, D> Engine<'mir, 'tcx, A> impl<'mir, 'tcx, A, D> Engine<'mir, 'tcx, A>
@ -138,19 +90,7 @@ where
{ {
/// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
/// function. /// function.
/// pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>, analysis: A) -> Self {
/// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
/// better performance.
pub fn new_generic(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>, analysis: A) -> Self {
Self::new(tcx, body, analysis, None)
}
fn new(
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
analysis: A,
apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
) -> Self {
let mut entry_sets = let mut entry_sets =
IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len()); IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len());
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]); analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
@ -160,7 +100,7 @@ where
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses"); bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
} }
Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_statement_trans_for_block } Engine { analysis, tcx, body, pass_name: None, entry_sets }
} }
/// Adds an identifier to the graphviz output for this particular run of a dataflow analysis. /// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
@ -177,14 +117,7 @@ where
where where
A::Domain: DebugWithContext<A>, A::Domain: DebugWithContext<A>,
{ {
let Engine { let Engine { mut analysis, body, mut entry_sets, tcx, pass_name } = self;
mut analysis,
body,
mut entry_sets,
tcx,
apply_statement_trans_for_block,
pass_name,
} = self;
let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len()); let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());
@ -213,13 +146,8 @@ where
state.clone_from(&entry_sets[bb]); state.clone_from(&entry_sets[bb]);
// Apply the block transfer function, using the cached one if it exists. // Apply the block transfer function, using the cached one if it exists.
let edges = A::Direction::apply_effects_in_block( let edges =
&mut analysis, A::Direction::apply_effects_in_block(&mut analysis, &mut state, bb, bb_data);
&mut state,
bb,
bb_data,
apply_statement_trans_for_block.as_deref(),
);
A::Direction::join_state_into_successors_of( A::Direction::join_state_into_successors_of(
&mut analysis, &mut analysis,

View File

@ -242,7 +242,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
where where
Self: Sized, Self: Sized,
{ {
Engine::new_generic(tcx, body, self) Engine::new(tcx, body, self)
} }
} }
@ -376,19 +376,6 @@ where
) { ) {
self.switch_int_edge_effects(block, discr, edge_effects); self.switch_int_edge_effects(block, discr, edge_effects);
} }
/* Extension methods */
#[inline]
fn into_engine<'mir>(
self,
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
) -> Engine<'mir, 'tcx, Self>
where
Self: Sized,
{
Engine::new_gen_kill(tcx, body, self)
}
} }
/// The legal operations for a transfer function in a gen/kill problem. /// The legal operations for a transfer function in a gen/kill problem.
@ -422,44 +409,6 @@ pub trait GenKill<T> {
} }
} }
/// Stores a transfer function for a gen/kill problem.
///
/// Calling `gen_`/`kill` on a `GenKillSet` will "build up" a transfer function so that it can be
/// 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> {
gen_: HybridBitSet<T>,
kill: HybridBitSet<T>,
}
impl<T: Idx> GenKillSet<T> {
/// Creates a new transfer function that will leave the dataflow state unchanged.
pub fn identity(universe: usize) -> Self {
GenKillSet {
gen_: HybridBitSet::new_empty(universe),
kill: HybridBitSet::new_empty(universe),
}
}
pub fn apply(&self, state: &mut impl BitSetExt<T>) {
state.union(&self.gen_);
state.subtract(&self.kill);
}
}
impl<T: Idx> GenKill<T> for GenKillSet<T> {
fn gen_(&mut self, elem: T) {
self.gen_.insert(elem);
self.kill.remove(elem);
}
fn kill(&mut self, elem: T) {
self.kill.insert(elem);
self.gen_.remove(elem);
}
}
impl<T: Idx> GenKill<T> for BitSet<T> { impl<T: Idx> GenKill<T> for BitSet<T> {
fn gen_(&mut self, elem: T) { fn gen_(&mut self, elem: T) {
self.insert(elem); self.insert(elem);