use super::*;
#[cfg(test)]
mod tests;
struct Case {
comparison: MInst,
predicate: VReg,
instructions: Vec<usize>,
}
struct Plan {
guard: Option<VReg>,
cases: Vec<Case>,
removed: HashSet<usize>,
}
fn and_operands(inst: &MInst) -> Option<(VReg, VReg)> {
match *inst {
MInst::And { lhs, rhs, .. } | MInst::And32 { lhs, rhs, .. } => Some((lhs, rhs)),
_ => None,
}
}
fn or_operands(inst: &MInst) -> Option<(VReg, VReg)> {
match *inst {
MInst::Or { lhs, rhs, .. } | MInst::Or32 { lhs, rhs, .. } => Some((lhs, rhs)),
_ => None,
}
}
fn plan(block: &MBlock, cond: VReg, uses: &[usize]) -> Option<Plan> {
if block.insts[..block.insts.len() - 1]
.iter()
.any(|inst| inst.uses().contains(&cond))
{
return None;
}
let definitions = block
.insts
.iter()
.enumerate()
.filter_map(|(position, inst)| inst.def().map(|dst| (dst, position)))
.collect::<HashMap<_, _>>();
let definition = |value: VReg| {
definitions
.get(&value)
.map(|&position| &block.insts[position])
};
let root = definition(cond)?;
let (guard, disjunction) = if or_operands(root).is_some() {
(None, cond)
} else {
let (lhs, rhs) = and_operands(root)?;
[(lhs, rhs), (rhs, lhs)]
.into_iter()
.find_map(|(guard, disjunction)| {
or_operands(definition(disjunction)?)
.is_some()
.then_some((Some(guard), disjunction))
})?
};
let mut removed = HashSet::default();
removed.insert(definitions[&cond]);
let mut pending = vec![disjunction];
let mut terms = Vec::new();
while let Some(value) = pending.pop() {
if value != cond && uses[value.0 as usize] != 1 {
return None;
}
let position = *definitions.get(&value)?;
if let Some((lhs, rhs)) = or_operands(&block.insts[position]) {
removed.insert(position);
pending.extend([rhs, lhs]);
} else {
let (lhs, rhs) = and_operands(&block.insts[position])?;
removed.insert(position);
terms.push((lhs, rhs));
}
if pending.len() + terms.len() > 8 {
return None;
}
}
if terms.len() < 2 {
return None;
}
let equality = |value: VReg| {
if uses[value.0 as usize] != 1 {
return None;
}
match *definition(value)? {
MInst::CmpImm {
lhs,
imm,
kind: CmpKind::Eq,
..
} => Some((lhs, imm)),
_ => None,
}
};
for first_guard in [terms[0].0, terms[0].1] {
let Some((selector, _)) = equality(first_guard) else {
continue;
};
let mut values = HashSet::default();
let mut cases = Vec::new();
let mut selected = removed.clone();
let mut cost = 0usize;
for &(lhs, rhs) in &terms {
let pair = [(lhs, rhs), (rhs, lhs)]
.into_iter()
.find_map(|(comparison, predicate)| {
let (key, value) = equality(comparison)?;
(key == selector).then_some((comparison, predicate, value))
});
let Some((comparison, predicate, value)) = pair else {
break;
};
if !values.insert(value) {
break;
}
let instructions = loop_guard::private_operand(block, predicate, uses);
let Some(&predicate_position) = definitions.get(&predicate) else {
break;
};
if !instructions.contains(&predicate_position) {
break;
}
cost += instructions
.iter()
.filter(|&&position| {
!matches!(
block.insts[position],
MInst::LoadImm { .. } | MInst::Mov { .. } | MInst::Mov32 { .. }
)
})
.count();
if instructions
.iter()
.any(|position| selected.contains(position))
{
break;
}
selected.extend(instructions.iter().copied());
selected.insert(definitions[&comparison]);
cases.push(Case {
comparison: definition(comparison)?.clone(),
predicate,
instructions,
});
}
if cases.len() != terms.len() || cost < 10 {
continue;
}
let first = cases
.iter()
.flat_map(|case| case.instructions.iter().copied())
.min()?;
if block.insts[first..block.insts.len() - 1]
.iter()
.any(|inst| !loop_guard::movable(inst))
{
continue;
}
return Some(Plan {
guard,
cases,
removed: selected,
});
}
None
}
pub(super) fn run(func: &mut MFunction) {
let positions = func
.blocks
.iter()
.enumerate()
.map(|(index, block)| (block.id, index))
.collect::<HashMap<_, _>>();
let successors = func
.blocks
.iter()
.map(|block| {
block
.successors()
.into_iter()
.map(|target| positions[&target])
.collect::<Vec<_>>()
})
.collect();
let Ok(cfg) = celox_analysis::cfg::ForwardControlFlowGraph::analyze_structure(successors, 0)
else {
return;
};
let headers = cfg
.loops
.iter()
.map(|region| func.blocks[region.header].id)
.collect::<HashSet<_>>();
let zeros = known_bits::known_zeros(func);
let mut uses = vec![0usize; func.vregs.count() as usize];
for block in &func.blocks {
for phi in &block.phis {
for &(_, source) in &phi.sources {
uses[source.0 as usize] += 1;
}
}
for inst in &block.insts {
for source in inst.uses() {
uses[source.0 as usize] += 1;
}
}
}
let Some(mut next_id) = func
.blocks
.iter()
.map(|block| block.id.0)
.max()
.and_then(|id| id.checked_add(1))
else {
return;
};
let original_count = func.blocks.len();
for position in 0..original_count {
let block = &func.blocks[position];
if !headers.contains(&block.id) {
continue;
}
let Some(&MInst::Branch {
cond,
true_bb,
false_bb,
}) = block.terminator()
else {
continue;
};
if true_bb == false_bb {
continue;
}
let Some(plan) = plan(block, cond, &uses) else {
continue;
};
let count = plan.cases.len() as u32;
let Some(end_id) = next_id.checked_add(count * 2 + 1) else {
return;
};
let dispatch = (0..count)
.map(|offset| BlockId(next_id + offset))
.collect::<Vec<_>>();
let bodies = (0..count)
.map(|offset| BlockId(next_id + count + offset))
.collect::<Vec<_>>();
let join_id = BlockId(next_id + count * 2);
next_id = end_id;
let original_id = block.id;
let original = std::mem::take(&mut func.blocks[position].insts);
let zero = func.vregs.alloc();
func.spill_descs.push(SpillDesc::remat(0));
let header = &mut func.blocks[position];
for (index, inst) in original.iter().enumerate().take(original.len() - 1) {
if !plan.removed.contains(&index) {
header.push(inst.clone());
}
}
header.push(MInst::LoadImm {
dst: zero,
value: 0,
});
if let Some(mut guard) = plan.guard {
if zeros[guard.0 as usize] & !1 != !1 {
let normalized = func.vregs.alloc();
func.spill_descs.push(SpillDesc::transient());
header.push(MInst::AndImm32 {
dst: normalized,
src: guard,
imm: 1,
});
guard = normalized;
}
header.push(MInst::Branch {
cond: guard,
true_bb: dispatch[0],
false_bb: join_id,
});
} else {
header.push(MInst::Jump {
target: dispatch[0],
});
}
let mut sources = vec![(*dispatch.last().unwrap(), zero)];
if plan.guard.is_some() {
sources.push((original_id, zero));
}
for (index, case) in plan.cases.into_iter().enumerate() {
let mut decision = MBlock::new(dispatch[index]);
let condition = case.comparison.def().unwrap();
decision.push(case.comparison);
decision.push(MInst::Branch {
cond: condition,
true_bb: bodies[index],
false_bb: dispatch.get(index + 1).copied().unwrap_or(join_id),
});
let mut body = MBlock::new(bodies[index]);
for instruction in case.instructions {
body.push(original[instruction].clone());
}
let predicate = if zeros[case.predicate.0 as usize] & !1 == !1 {
case.predicate
} else {
let normalized = func.vregs.alloc();
func.spill_descs.push(SpillDesc::transient());
body.push(MInst::AndImm32 {
dst: normalized,
src: case.predicate,
imm: 1,
});
normalized
};
body.push(MInst::Jump { target: join_id });
sources.push((body.id, predicate));
func.push_block(decision);
func.push_block(body);
}
let mut join = MBlock::new(join_id);
join.phis.push(PhiNode { dst: cond, sources });
join.push(MInst::Branch {
cond,
true_bb,
false_bb,
});
for successor in &mut func.blocks {
if successor.id == true_bb || successor.id == false_bb {
for phi in &mut successor.phis {
for (predecessor, _) in &mut phi.sources {
if *predecessor == original_id {
*predecessor = join_id;
}
}
}
}
}
func.push_block(join);
}
}