Alias analysis: refactor for use by other driver loops. (#5380)

* Alias analysis: refactor for use by other driver loops.

This PR pulls the core of the alias analysis infrastructure into a
`process_inst()` method that operates on a single instruction, and
allows another compiler pass to apply store-to-load forwarding and
redundant-load elimination interleaved with other work. The existing
behavior remains unchanged; the pass's toplevel loop calls this
extracted method.

This refactor is a prerequisite for using the alias analysis as part of
a refactored egraph-based optimization framework.

* Review feedback: remove unneeded mut.
This commit is contained in:
Chris Fallin
2022-12-06 10:30:02 -08:00
committed by GitHub
parent 4a0cefb1aa
commit feaa7ca75f
2 changed files with 141 additions and 123 deletions

View File

@@ -76,7 +76,7 @@ use cranelift_entity::{packed_option::PackedOption, EntityRef};
/// For a given program point, the vector of last-store instruction /// For a given program point, the vector of last-store instruction
/// indices for each disjoint category of abstract state. /// indices for each disjoint category of abstract state.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct LastStores { pub struct LastStores {
heap: PackedOption<Inst>, heap: PackedOption<Inst>,
table: PackedOption<Inst>, table: PackedOption<Inst>,
vmctx: PackedOption<Inst>, vmctx: PackedOption<Inst>,
@@ -179,9 +179,6 @@ struct MemoryLoc {
/// An alias-analysis pass. /// An alias-analysis pass.
pub struct AliasAnalysis<'a> { pub struct AliasAnalysis<'a> {
/// The function we're analyzing.
func: &'a mut Function,
/// The domtree for the function. /// The domtree for the function.
domtree: &'a DominatorTree, domtree: &'a DominatorTree,
@@ -198,23 +195,22 @@ pub struct AliasAnalysis<'a> {
impl<'a> AliasAnalysis<'a> { impl<'a> AliasAnalysis<'a> {
/// Perform an alias analysis pass. /// Perform an alias analysis pass.
pub fn new(func: &'a mut Function, domtree: &'a DominatorTree) -> AliasAnalysis<'a> { pub fn new(func: &Function, domtree: &'a DominatorTree) -> AliasAnalysis<'a> {
trace!("alias analysis: input is:\n{:?}", func); trace!("alias analysis: input is:\n{:?}", func);
let mut analysis = AliasAnalysis { let mut analysis = AliasAnalysis {
func,
domtree, domtree,
block_input: FxHashMap::default(), block_input: FxHashMap::default(),
mem_values: FxHashMap::default(), mem_values: FxHashMap::default(),
}; };
analysis.compute_block_input_states(); analysis.compute_block_input_states(func);
analysis analysis
} }
fn compute_block_input_states(&mut self) { fn compute_block_input_states(&mut self, func: &Function) {
let mut queue = vec![]; let mut queue = vec![];
let mut queue_set = FxHashSet::default(); let mut queue_set = FxHashSet::default();
let entry = self.func.layout.entry_block().unwrap(); let entry = func.layout.entry_block().unwrap();
queue.push(entry); queue.push(entry);
queue_set.insert(entry); queue_set.insert(entry);
@@ -232,19 +228,13 @@ impl<'a> AliasAnalysis<'a> {
state state
); );
for inst in self.func.layout.block_insts(block) { for inst in func.layout.block_insts(block) {
state.update(self.func, inst); state.update(func, inst);
trace!("after inst{}: state is {:?}", inst.index(), state); trace!("after inst{}: state is {:?}", inst.index(), state);
} }
visit_block_succs(self.func, block, |_inst, succ, _from_table| { visit_block_succs(func, block, |_inst, succ, _from_table| {
let succ_first_inst = self let succ_first_inst = func.layout.block_insts(succ).into_iter().next().unwrap();
.func
.layout
.block_insts(succ)
.into_iter()
.next()
.unwrap();
let updated = match self.block_input.get_mut(&succ) { let updated = match self.block_input.get_mut(&succ) {
Some(succ_state) => { Some(succ_state) => {
let old = succ_state.clone(); let old = succ_state.clone();
@@ -264,36 +254,40 @@ impl<'a> AliasAnalysis<'a> {
} }
} }
/// Make a pass and update known-redundant loads to aliased /// Get the starting state for a block.
/// values. We interleave the updates with the memory-location pub fn block_starting_state(&self, block: Block) -> LastStores {
/// tracking because resolving some aliases may expose others self.block_input
/// (e.g. in cases of double-indirection with two separate chains
/// of loads).
pub fn compute_and_update_aliases(&mut self) {
let mut pos = FuncCursor::new(self.func);
while let Some(block) = pos.next_block() {
let mut state = self
.block_input
.get(&block) .get(&block)
.cloned() .cloned()
.unwrap_or_else(|| LastStores::default()); .unwrap_or_else(|| LastStores::default())
}
while let Some(inst) = pos.next_inst() { /// Process one instruction. Meant to be invoked in program order
/// within a block, and ideally in RPO or at least some domtree
/// preorder for maximal reuse.
///
/// Returns `true` if instruction was removed.
pub fn process_inst(
&mut self,
func: &mut Function,
state: &mut LastStores,
inst: Inst,
) -> Option<Value> {
trace!( trace!(
"alias analysis: scanning at inst{} with state {:?} ({:?})", "alias analysis: scanning at inst{} with state {:?} ({:?})",
inst.index(), inst.index(),
state, state,
pos.func.dfg[inst], func.dfg[inst],
); );
if let Some((address, offset, ty)) = inst_addr_offset_type(pos.func, inst) { let replacing_value = if let Some((address, offset, ty)) = inst_addr_offset_type(func, inst)
let address = pos.func.dfg.resolve_aliases(address); {
let opcode = pos.func.dfg[inst].opcode(); let address = func.dfg.resolve_aliases(address);
let opcode = func.dfg[inst].opcode();
if opcode.can_store() { if opcode.can_store() {
let store_data = inst_store_data(pos.func, inst).unwrap(); let store_data = inst_store_data(func, inst).unwrap();
let store_data = pos.func.dfg.resolve_aliases(store_data); let store_data = func.dfg.resolve_aliases(store_data);
let mem_loc = MemoryLoc { let mem_loc = MemoryLoc {
last_store: inst.into(), last_store: inst.into(),
address, address,
@@ -308,9 +302,11 @@ impl<'a> AliasAnalysis<'a> {
mem_loc mem_loc
); );
self.mem_values.insert(mem_loc, (inst, store_data)); self.mem_values.insert(mem_loc, (inst, store_data));
None
} else if opcode.can_load() { } else if opcode.can_load() {
let last_store = state.get_last_store(pos.func, inst); let last_store = state.get_last_store(func, inst);
let load_result = pos.func.dfg.inst_results(inst)[0]; let load_result = func.dfg.inst_results(inst)[0];
let mem_loc = MemoryLoc { let mem_loc = MemoryLoc {
last_store, last_store,
address, address,
@@ -335,35 +331,30 @@ impl<'a> AliasAnalysis<'a> {
// load (stores will always dominate though if // load (stores will always dominate though if
// their `last_store` survives through // their `last_store` survives through
// meet-points to this use-site). // meet-points to this use-site).
let aliased = if let Some((def_inst, value)) = let aliased =
self.mem_values.get(&mem_loc).cloned() if let Some((def_inst, value)) = self.mem_values.get(&mem_loc).cloned() {
{
trace!( trace!(
" -> sees known value v{} from inst{}", " -> sees known value v{} from inst{}",
value.index(), value.index(),
def_inst.index() def_inst.index()
); );
if self.domtree.dominates(def_inst, inst, &pos.func.layout) { if self.domtree.dominates(def_inst, inst, &func.layout) {
trace!( trace!(
" -> dominates; value equiv from v{} to v{} inserted", " -> dominates; value equiv from v{} to v{} inserted",
load_result.index(), load_result.index(),
value.index() value.index()
); );
Some(value)
pos.func.dfg.detach_results(inst);
pos.func.dfg.change_to_alias(load_result, value);
pos.remove_inst_and_step_back();
true
} else { } else {
false None
} }
} else { } else {
false None
}; };
// Otherwise, we can keep *this* load around // Otherwise, we can keep *this* load around
// as a new equivalent value. // as a new equivalent value.
if !aliased { if aliased.is_none() {
trace!( trace!(
" -> inserting load result v{} at loc {:?}", " -> inserting load result v{} at loc {:?}",
load_result.index(), load_result.index(),
@@ -371,10 +362,37 @@ impl<'a> AliasAnalysis<'a> {
); );
self.mem_values.insert(mem_loc, (inst, load_result)); self.mem_values.insert(mem_loc, (inst, load_result));
} }
aliased
} else {
None
} }
} else {
None
};
state.update(func, inst);
replacing_value
} }
state.update(pos.func, inst); /// Make a pass and update known-redundant loads to aliased
/// values. We interleave the updates with the memory-location
/// tracking because resolving some aliases may expose others
/// (e.g. in cases of double-indirection with two separate chains
/// of loads).
pub fn compute_and_update_aliases(&mut self, func: &mut Function) {
let mut pos = FuncCursor::new(func);
while let Some(block) = pos.next_block() {
let mut state = self.block_starting_state(block);
while let Some(inst) = pos.next_inst() {
if let Some(replaced_result) = self.process_inst(pos.func, &mut state, inst) {
let result = pos.func.dfg.inst_results(inst)[0];
pos.func.dfg.detach_results(inst);
pos.func.dfg.change_to_alias(result, replaced_result);
pos.remove_inst_and_step_back();
}
} }
} }
} }

View File

@@ -365,8 +365,8 @@ impl Context {
/// by a store instruction to the same instruction (so-called /// by a store instruction to the same instruction (so-called
/// "store-to-load forwarding"). /// "store-to-load forwarding").
pub fn replace_redundant_loads(&mut self) -> CodegenResult<()> { pub fn replace_redundant_loads(&mut self) -> CodegenResult<()> {
let mut analysis = AliasAnalysis::new(&mut self.func, &self.domtree); let mut analysis = AliasAnalysis::new(&self.func, &self.domtree);
analysis.compute_and_update_aliases(); analysis.compute_and_update_aliases(&mut self.func);
Ok(()) Ok(())
} }