use crate::{FxHashMap, FxHashSet};
use crate::{
cursor::{Cursor, FuncCursor},
dominator_tree::DominatorTree,
inst_predicates::{
has_memory_fence_semantics, inst_addr_offset_type, inst_store_data, visit_block_succs,
},
ir::{AliasRegion, Block, Function, Inst, Opcode, Type, Value, immediates::Offset32},
trace,
};
use cranelift_entity::{EntityRef, packed_option::PackedOption};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct LastStores {
heap: PackedOption<Inst>,
table: PackedOption<Inst>,
vmctx: PackedOption<Inst>,
other: PackedOption<Inst>,
}
impl LastStores {
fn update(&mut self, func: &Function, inst: Inst) {
let opcode = func.dfg.insts[inst].opcode();
if has_memory_fence_semantics(opcode) {
self.heap = inst.into();
self.table = inst.into();
self.vmctx = inst.into();
self.other = inst.into();
} else if opcode.can_store() {
if let Some(memflags) = func.dfg.insts[inst].memflags() {
match memflags.alias_region() {
None => self.other = inst.into(),
Some(AliasRegion::Heap) => self.heap = inst.into(),
Some(AliasRegion::Table) => self.table = inst.into(),
Some(AliasRegion::Vmctx) => self.vmctx = inst.into(),
}
} else {
self.heap = inst.into();
self.table = inst.into();
self.vmctx = inst.into();
self.other = inst.into();
}
}
}
fn get_last_store(&self, func: &Function, inst: Inst) -> PackedOption<Inst> {
if let Some(memflags) = func.dfg.insts[inst].memflags() {
match memflags.alias_region() {
None => self.other,
Some(AliasRegion::Heap) => self.heap,
Some(AliasRegion::Table) => self.table,
Some(AliasRegion::Vmctx) => self.vmctx,
}
} else if func.dfg.insts[inst].opcode().can_load()
|| func.dfg.insts[inst].opcode().can_store()
{
inst.into()
} else {
PackedOption::default()
}
}
fn meet_from(&mut self, other: &LastStores, loc: Inst) {
let meet = |a: PackedOption<Inst>, b: PackedOption<Inst>| -> PackedOption<Inst> {
match (a.into(), b.into()) {
(None, None) => None.into(),
(Some(a), None) => a,
(None, Some(b)) => b,
(Some(a), Some(b)) if a == b => a,
_ => loc.into(),
}
};
self.heap = meet(self.heap, other.heap);
self.table = meet(self.table, other.table);
self.vmctx = meet(self.vmctx, other.vmctx);
self.other = meet(self.other, other.other);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct MemoryLoc {
last_store: PackedOption<Inst>,
address: Value,
offset: Offset32,
ty: Type,
extending_opcode: Option<Opcode>,
}
pub struct AliasAnalysis<'a> {
domtree: &'a DominatorTree,
block_input: FxHashMap<Block, LastStores>,
mem_values: FxHashMap<MemoryLoc, (Inst, Value)>,
}
impl<'a> AliasAnalysis<'a> {
pub fn new(func: &Function, domtree: &'a DominatorTree) -> AliasAnalysis<'a> {
trace!("alias analysis: input is:\n{:?}", func);
let mut analysis = AliasAnalysis {
domtree,
block_input: FxHashMap::default(),
mem_values: FxHashMap::default(),
};
analysis.compute_block_input_states(func);
analysis
}
fn compute_block_input_states(&mut self, func: &Function) {
let mut queue = vec![];
let mut queue_set = FxHashSet::default();
let entry = func.layout.entry_block().unwrap();
queue.push(entry);
queue_set.insert(entry);
while let Some(block) = queue.pop() {
queue_set.remove(&block);
let mut state = *self
.block_input
.entry(block)
.or_insert_with(|| LastStores::default());
trace!(
"alias analysis: input to block{} is {:?}",
block.index(),
state
);
for inst in func.layout.block_insts(block) {
state.update(func, inst);
trace!("after inst{}: state is {:?}", inst.index(), state);
}
visit_block_succs(func, block, |_inst, succ, _from_table| {
let succ_first_inst = func.layout.block_insts(succ).next().unwrap();
let updated = match self.block_input.get_mut(&succ) {
Some(succ_state) => {
let old = *succ_state;
succ_state.meet_from(&state, succ_first_inst);
*succ_state != old
}
None => {
self.block_input.insert(succ, state);
true
}
};
if updated && queue_set.insert(succ) {
queue.push(succ);
}
});
}
}
pub fn block_starting_state(&self, block: Block) -> LastStores {
self.block_input
.get(&block)
.cloned()
.unwrap_or_else(|| LastStores::default())
}
pub fn process_inst(
&mut self,
func: &mut Function,
state: &mut LastStores,
inst: Inst,
) -> Option<Value> {
trace!(
"alias analysis: scanning at inst{} with state {:?} ({:?})",
inst.index(),
state,
func.dfg.insts[inst],
);
let replacing_value = if let Some((address, offset, ty)) = inst_addr_offset_type(func, inst)
{
let address = func.dfg.resolve_aliases(address);
let opcode = func.dfg.insts[inst].opcode();
if opcode.can_store() {
let store_data = inst_store_data(func, inst).unwrap();
let store_data = func.dfg.resolve_aliases(store_data);
let mem_loc = MemoryLoc {
last_store: inst.into(),
address,
offset,
ty,
extending_opcode: get_ext_opcode(opcode),
};
trace!(
"alias analysis: at inst{}: store with data v{} at loc {:?}",
inst.index(),
store_data.index(),
mem_loc
);
self.mem_values.insert(mem_loc, (inst, store_data));
None
} else if opcode.can_load() {
let last_store = state.get_last_store(func, inst);
let load_result = func.dfg.inst_results(inst)[0];
let mem_loc = MemoryLoc {
last_store,
address,
offset,
ty,
extending_opcode: get_ext_opcode(opcode),
};
trace!(
"alias analysis: at inst{}: load with last_store inst{} at loc {:?}",
inst.index(),
last_store.map(|inst| inst.index()).unwrap_or(usize::MAX),
mem_loc
);
let aliased =
if let Some((def_inst, value)) = self.mem_values.get(&mem_loc).cloned() {
trace!(
" -> sees known value v{} from inst{}",
value.index(),
def_inst.index()
);
if self.domtree.dominates(def_inst, inst, &func.layout) {
trace!(
" -> dominates; value equiv from v{} to v{} inserted",
load_result.index(),
value.index()
);
Some(value)
} else {
None
}
} else {
None
};
if aliased.is_none() {
trace!(
" -> inserting load result v{} at loc {:?}",
load_result.index(),
mem_loc
);
self.mem_values.insert(mem_loc, (inst, load_result));
}
aliased
} else {
None
}
} else {
None
};
state.update(func, inst);
replacing_value
}
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.clear_results(inst);
pos.func.dfg.change_to_alias(result, replaced_result);
pos.remove_inst_and_step_back();
}
}
}
}
}
fn get_ext_opcode(op: Opcode) -> Option<Opcode> {
debug_assert!(op.can_load() || op.can_store());
match op {
Opcode::Load | Opcode::Store => None,
_ => Some(op),
}
}