use crate::dominator_tree::DominatorTree;
use crate::ir;
use crate::ir::Function;
use crate::ir::{Block, BlockArg, BlockCall, Inst, Value};
use crate::timing;
use crate::{FxHashMap, FxHashSet};
use bumpalo::Bump;
use cranelift_entity::SecondaryMap;
use smallvec::SmallVec;
#[derive(Clone, Copy, Debug, PartialEq)]
enum AbstractValue {
Many,
One(Value ),
None,
}
impl AbstractValue {
fn join(self, other: AbstractValue) -> AbstractValue {
match (self, other) {
(AbstractValue::None, p2) => p2,
(p1, AbstractValue::None) => p1,
(AbstractValue::Many, _p2) => AbstractValue::Many,
(_p1, AbstractValue::Many) => AbstractValue::Many,
(AbstractValue::One(v1), AbstractValue::One(v2)) => {
if v1 == v2 {
AbstractValue::One(v1)
} else {
AbstractValue::Many
}
}
}
}
fn is_one(self) -> bool {
matches!(self, AbstractValue::One(_))
}
}
#[derive(Clone, Copy, Debug)]
struct OutEdge<'a> {
inst: Inst,
branch_index: u32,
block: Block,
args: &'a [BlockArg],
}
impl<'a> OutEdge<'a> {
#[inline]
fn new(
bump: &'a Bump,
dfg: &ir::DataFlowGraph,
inst: Inst,
branch_index: usize,
block: BlockCall,
) -> Option<Self> {
let inst_var_args = block.args(&dfg.value_lists);
if inst_var_args.len() == 0 {
return None;
}
Some(OutEdge {
inst,
branch_index: branch_index as u32,
block: block.block(&dfg.value_lists),
args: bump.alloc_slice_fill_iter(
inst_var_args.map(|arg| arg.map_value(|value| dfg.resolve_aliases(value))),
),
})
}
}
#[derive(Clone, Debug, Default)]
struct BlockSummary<'a> {
formals: &'a [Value],
dests: SmallVec<[OutEdge<'a>; 2]>,
}
impl<'a> BlockSummary<'a> {
#[inline]
fn new(bump: &'a Bump, formals: &[Value]) -> Self {
Self {
formals: bump.alloc_slice_copy(formals),
dests: Default::default(),
}
}
}
struct SolverState {
absvals: FxHashMap<Value , AbstractValue>,
}
impl SolverState {
fn new() -> Self {
Self {
absvals: FxHashMap::default(),
}
}
fn get(&self, actual: Value) -> AbstractValue {
*self
.absvals
.get(&actual)
.unwrap_or_else(|| panic!("SolverState::get: formal param {actual:?} is untracked?!"))
}
fn maybe_get(&self, actual: Value) -> Option<&AbstractValue> {
self.absvals.get(&actual)
}
fn set(&mut self, actual: Value, lp: AbstractValue) {
match self.absvals.insert(actual, lp) {
Some(_old_lp) => {}
None => panic!("SolverState::set: formal param {actual:?} is untracked?!"),
}
}
}
#[inline(never)]
pub fn do_remove_constant_phis(func: &mut Function, domtree: &mut DominatorTree) {
let _tt = timing::remove_constant_phis();
debug_assert!(domtree.is_valid());
let bump =
Bump::with_capacity(domtree.cfg_postorder().len() * 4 * core::mem::size_of::<Value>());
let mut summaries =
SecondaryMap::<Block, BlockSummary>::with_capacity(domtree.cfg_postorder().len());
for b in domtree.cfg_rpo().copied() {
let formals = func.dfg.block_params(b);
let mut summary = BlockSummary::new(&bump, formals);
for inst in func.layout.block_insts(b) {
for (ix, dest) in func.dfg.insts[inst]
.branch_destination(&func.dfg.jump_tables, &func.dfg.exception_tables)
.iter()
.enumerate()
{
if let Some(edge) = OutEdge::new(&bump, &func.dfg, inst, ix, *dest) {
summary.dests.push(edge);
}
}
}
if formals.len() > 0 || summary.dests.len() > 0 {
summaries[b] = summary;
}
}
let entry_block = func
.layout
.entry_block()
.expect("remove_constant_phis: entry block unknown");
let mut state = SolverState::new();
for b in domtree.cfg_rpo().copied() {
if b == entry_block {
continue;
}
let formals = func.dfg.block_params(b);
for formal in formals {
let mb_old_absval = state.absvals.insert(*formal, AbstractValue::None);
assert!(mb_old_absval.is_none());
}
}
let mut iter_no = 0;
loop {
iter_no += 1;
let mut changed = false;
for src in domtree.cfg_rpo().copied() {
let src_summary = &summaries[src];
for edge in &src_summary.dests {
assert!(edge.block != entry_block);
let dst_summary = &summaries[edge.block];
let dst_formals = &dst_summary.formals;
assert_eq!(edge.args.len(), dst_formals.len());
for (formal, actual) in dst_formals.iter().zip(edge.args) {
let actual_absval = match actual {
BlockArg::Value(actual) => match state.maybe_get(*actual) {
Some(pt) => *pt,
None => AbstractValue::One(*actual),
},
_ => AbstractValue::Many,
};
let formal_absval_old = state.get(*formal);
let formal_absval_new = formal_absval_old.join(actual_absval);
if formal_absval_new != formal_absval_old {
changed = true;
state.set(*formal, formal_absval_new);
}
}
}
}
if !changed {
break;
}
}
let mut n_consts = 0;
for absval in state.absvals.values() {
if absval.is_one() {
n_consts += 1;
}
}
let mut need_editing = FxHashSet::<Block>::default();
for (block, summary) in summaries.iter() {
if block == entry_block {
continue;
}
for formal in summary.formals {
let formal_absval = state.get(*formal);
if formal_absval.is_one() {
need_editing.insert(block);
break;
}
}
}
for b in &need_editing {
let mut del_these = SmallVec::<[(Value, Value); 32]>::new();
let formals: &[Value] = func.dfg.block_params(*b);
for formal in formals {
if let AbstractValue::One(replacement_val) = state.get(*formal) {
del_these.push((*formal, replacement_val));
}
}
for (redundant_formal, replacement_val) in del_these.into_iter().rev() {
func.dfg.remove_block_param(redundant_formal);
func.dfg.change_to_alias(redundant_formal, replacement_val);
}
}
let mut old_actuals = alloc::vec::Vec::new();
for summary in summaries.values() {
for edge in &summary.dests {
if !need_editing.contains(&edge.block) {
continue;
}
let dfg = &mut func.dfg;
let dests = dfg.insts[edge.inst]
.branch_destination_mut(&mut dfg.jump_tables, &mut dfg.exception_tables);
let block = &mut dests[edge.branch_index as usize];
old_actuals.extend(block.args(&dfg.value_lists));
let formals = &summaries[edge.block].formals;
assert_eq!(formals.len(), old_actuals.len());
let mut formals = formals.iter();
old_actuals.retain(|_| {
let formal_i = formals.next().unwrap();
!state.get(*formal_i).is_one()
});
let destination = block.block(&dfg.value_lists);
*block = BlockCall::new(destination, old_actuals.drain(..), &mut dfg.value_lists);
}
}
log::debug!(
"do_remove_constant_phis: done, {} iters. {} formals, of which {} const.",
iter_no,
state.absvals.len(),
n_consts
);
}