use alloc::vec;
use alloc::vec::Vec;
use ir_lang::{
Block, BlockArg, BlockCall, BuildError, Builder, Function, InstData, Position, Terminator,
Value, ValueDef,
};
use crate::Budget;
use crate::analysis::{Cfg, NONE, edge_mut, is_unwind_edge};
use crate::edit::{replace_terminator, rewrite_terminator, use_counts};
pub(crate) const MAX_ROUNDS: usize = 8;
fn round_cost(func: &Function) -> u64 {
3 * (func.inst_count() + func.block_count() + func.value_count()) as u64 + 16
}
pub(crate) fn run(b: &mut Builder<'_>, budget: &mut Budget) -> Result<bool, BuildError> {
let mut changed = false;
for _ in 0..MAX_ROUNDS {
if !budget.charge(round_cost(b.func())) {
break;
}
let mut round = fold_terminators(b)?;
round |= remove_unreachable(b)?;
round |= thread_jumps(b)?;
round |= remove_unreachable(b)?;
round |= merge_blocks(b)?;
if !round {
break;
}
changed = true;
}
Ok(changed)
}
fn const_bits(func: &Function, v: Value) -> Option<u64> {
match func.inst(func.defining_inst(func.resolve(v))?)? {
InstData::Const { bits, .. } => Some(*bits),
_ => None,
}
}
fn same_edge(func: &Function, a: &BlockCall, b: &BlockCall) -> bool {
a.block == b.block
&& a.args.len() == b.args.len()
&& a.args.iter().zip(&b.args).all(|(x, y)| match (x, y) {
(BlockArg::Value(x), BlockArg::Value(y)) => func.resolve(*x) == func.resolve(*y),
_ => x == y,
})
}
fn fold_terminators(b: &mut Builder<'_>) -> Result<bool, BuildError> {
let func = b.func();
let mut plans: Vec<(Block, Terminator)> = Vec::new();
for block in func.blocks() {
let Some(term) = func.terminator(block) else {
continue;
};
match term {
Terminator::Branch {
cond,
then_dest,
else_dest,
} => {
if let Some(c) = const_bits(func, *cond) {
let dest = if c & 1 == 1 { then_dest } else { else_dest };
plans.push((block, Terminator::Jump(dest.clone())));
} else if same_edge(func, then_dest, else_dest) {
plans.push((block, Terminator::Jump(then_dest.clone())));
}
}
Terminator::Switch {
value,
cases,
default,
} => {
if let Some(c) = const_bits(func, *value) {
let dest = cases
.iter()
.find(|k| k.value == c)
.map_or(default, |k| &k.dest);
plans.push((block, Terminator::Jump(dest.clone())));
} else if cases.iter().any(|k| same_edge(func, &k.dest, default)) {
let kept: Vec<_> = cases
.iter()
.filter(|k| !same_edge(func, &k.dest, default))
.cloned()
.collect();
let t = if kept.is_empty() {
Terminator::Jump(default.clone())
} else {
Terminator::Switch {
value: *value,
cases: kept,
default: default.clone(),
}
};
plans.push((block, t));
} else if cases.is_empty() {
plans.push((block, Terminator::Jump(default.clone())));
}
}
_ => {}
}
}
let changed = !plans.is_empty();
for (block, term) in plans {
replace_terminator(b, block, term)?;
}
Ok(changed)
}
fn remove_unreachable(b: &mut Builder<'_>) -> Result<bool, BuildError> {
let func = b.func();
let cfg = Cfg::new(func);
let dead: Vec<Block> = func
.blocks()
.filter(|bl| !cfg.reachable(bl.index()))
.collect();
let changed = !dead.is_empty();
for block in dead {
b.remove_block(block)?;
}
Ok(changed)
}
#[derive(Clone)]
struct Forward {
target: Block,
args: Vec<BlockArg>,
}
fn substitute(
func: &Function,
block: Block,
args: &[BlockArg],
actual: &[BlockArg],
) -> Vec<BlockArg> {
args.iter()
.map(|a| match a {
BlockArg::Value(v) => match func.value_def(func.resolve(*v)) {
Some(ValueDef::Param { block: pb, index }) if pb == block => {
actual.get(index as usize).copied().unwrap_or(*a)
}
_ => BlockArg::Value(func.resolve(*v)),
},
other => *other,
})
.collect()
}
fn thread_jumps(b: &mut Builder<'_>) -> Result<bool, BuildError> {
let func = b.func();
let cfg = Cfg::new(func);
let n = func.block_count();
let entry = func.entry();
let uses = use_counts(func);
let mut forwarder = vec![false; n];
for &bl in &cfg.rpo {
let block = Block::from_u32(bl);
if block == entry || func.first_inst(block).is_some() {
continue;
}
let Some(Terminator::Jump(dest)) = func.terminator(block) else {
continue;
};
if dest.block == block {
continue;
}
let local_only = func.block_params(block).iter().all(|&p| {
let here = dest
.args
.iter()
.filter(|a| matches!(a, BlockArg::Value(v) if func.resolve(*v) == p))
.count();
uses.get(p.index()).copied().unwrap_or(0) as usize == here
});
if !local_only {
continue;
}
let landing = cfg.preds(bl as usize).iter().any(|e| {
func.terminator(Block::from_u32(e.from))
.is_some_and(|t| is_unwind_edge(t, e.idx as usize))
});
if !landing {
forwarder[bl as usize] = true;
}
}
let mut state = vec![0u8; n];
let mut fwd: Vec<Option<Forward>> = vec![None; n];
let mut chain: Vec<u32> = Vec::new();
for start in 0..n {
if !forwarder[start] || state[start] != 0 {
continue;
}
chain.clear();
let mut cur = start as u32;
loop {
state[cur as usize] = 1;
chain.push(cur);
let Some(Terminator::Jump(dest)) = func.terminator(Block::from_u32(cur)) else {
break;
};
let next = dest.block.as_u32();
if forwarder.get(next as usize).copied().unwrap_or(false) && state[next as usize] == 0 {
cur = next;
} else {
break;
}
}
while let Some(x) = chain.pop() {
let block = Block::from_u32(x);
let Some(Terminator::Jump(dest)) = func.terminator(block) else {
state[x as usize] = 2;
continue;
};
let next = dest.block.as_u32();
let own = Forward {
target: dest.block,
args: dest
.args
.iter()
.map(|a| match a {
BlockArg::Value(v) => BlockArg::Value(func.resolve(*v)),
other => *other,
})
.collect(),
};
let f = match (state.get(next as usize), fwd.get(next as usize)) {
(Some(2), Some(Some(nf))) if forwarder[next as usize] => Forward {
target: nf.target,
args: substitute(func, dest.block, &nf.args, &own.args),
},
_ => own,
};
fwd[x as usize] = Some(f);
state[x as usize] = 2;
}
}
let mut plans: Vec<(Block, Vec<(usize, BlockCall)>)> = Vec::new();
for &bl in &cfg.rpo {
let block = Block::from_u32(bl);
let Some(term) = func.terminator(block) else {
continue;
};
let mut edits = Vec::new();
for (k, dest) in term.successors().enumerate() {
if is_unwind_edge(term, k)
|| !forwarder.get(dest.block.index()).copied().unwrap_or(false)
{
continue;
}
let Some(Some(f)) = fwd.get(dest.block.index()) else {
continue;
};
if f.target == dest.block || f.target == entry {
continue;
}
let args = substitute(func, dest.block, &f.args, &dest.args);
edits.push((k, BlockCall::with_args(f.target, &args)));
}
if !edits.is_empty() {
plans.push((block, edits));
}
}
let changed = !plans.is_empty();
for (block, edits) in plans {
rewrite_terminator(b, block, |term| {
for (k, new) in edits {
if let Some(slot) = edge_mut(term, k) {
*slot = new;
}
}
})?;
}
Ok(changed)
}
fn merge_blocks(b: &mut Builder<'_>) -> Result<bool, BuildError> {
let (rpo, pred_count, entry) = {
let func = b.func();
let cfg = Cfg::new(func);
let counts: Vec<u32> = (0..cfg.len()).map(|i| cfg.preds(i).len() as u32).collect();
(cfg.rpo.clone(), counts, func.entry())
};
let mut changed = false;
for bl in rpo {
let block = Block::from_u32(bl);
loop {
let func = b.func();
if !func.is_live_block(block) {
break;
}
let Some(Terminator::Jump(dest)) = func.terminator(block) else {
break;
};
let next = dest.block;
if next == block
|| next == entry
|| !func.is_live_block(next)
|| pred_count.get(next.index()).copied().unwrap_or(NONE) != 1
{
break;
}
let args: Vec<BlockArg> = dest.args.clone();
let params: Vec<Value> = func.block_params(next).to_vec();
let insts: Vec<_> = func.insts(next).collect();
for (p, a) in params.iter().zip(&args) {
if let BlockArg::Value(v) = a {
b.replace_all_uses(*p, *v)?;
}
}
let _ = b.remove_terminator(block)?;
for inst in insts {
b.move_inst(inst, Position::End(block))?;
}
if let Some(term) = b.remove_terminator(next)? {
b.switch_to(block)?;
b.set_terminator(term)?;
}
b.remove_block(next)?;
changed = true;
}
}
Ok(changed)
}