use std::collections::{BTreeMap, BTreeSet};
use crate::{
ir::{
block::SsaBlock, function::SsaFunction, instruction::SsaInstruction, ops::SsaOp,
phi::PhiOperand, variable::SsaVarId,
},
target::Target,
BitSet,
};
pub(crate) fn find_kept_predecessors(
removed_block: usize,
predecessors: &BTreeMap<usize, Vec<usize>>,
block_remap: &[Option<usize>],
redirect_map: &BTreeMap<usize, usize>,
) -> Vec<usize> {
let mut result = Vec::new();
let block_count = block_remap.len();
let mut visited = BitSet::new(block_count);
let mut queue = vec![removed_block];
while let Some(current) = queue.pop() {
if current >= block_count || !visited.insert(current) {
continue;
}
if let Some(preds) = predecessors.get(¤t) {
for &pred in preds {
if let Some(Some(new_idx)) = block_remap.get(pred) {
result.push(*new_idx);
} else if redirect_map.contains_key(&pred) {
queue.push(pred);
}
}
}
}
result
}
impl<T: Target> SsaFunction<T> {
pub fn canonicalize(&mut self) {
for block in &mut self.blocks {
block
.instructions_mut()
.retain(|instr| !matches!(instr.op(), SsaOp::Nop));
}
let block_count = self.blocks.len();
let mut protected_blocks = BitSet::new(block_count);
for handler in &self.exception_handlers {
if let Some(try_block) = handler.try_start_block {
if try_block < block_count {
protected_blocks.insert(try_block);
}
}
if let Some(handler_block) = handler.handler_start_block {
if handler_block < block_count {
protected_blocks.insert(handler_block);
}
}
if let Some(filter_block) = handler.filter_start_block {
if filter_block < block_count {
protected_blocks.insert(filter_block);
}
}
}
for block in &self.blocks {
if let Some(SsaOp::Leave { target }) = block.terminator_op() {
if *target < block_count {
protected_blocks.insert(*target);
}
}
}
let mut block_remap: Vec<Option<usize>> = Vec::with_capacity(block_count);
let mut new_index = 0usize;
for (old_index, block) in self.blocks.iter().enumerate() {
let is_empty = block.instructions().is_empty() && block.phi_nodes().is_empty();
let is_entry = old_index == 0;
let is_protected = protected_blocks.contains(old_index);
if is_empty && !is_entry && !is_protected {
block_remap.push(None); } else {
block_remap.push(Some(new_index));
new_index = new_index.saturating_add(1);
}
}
let mut redirect_map: BTreeMap<usize, usize> = BTreeMap::new();
for old_index in 0..self.blocks.len() {
if matches!(block_remap.get(old_index), Some(None)) {
if let Some(target) = self.find_ultimate_target(old_index, &block_remap) {
redirect_map.insert(old_index, target);
} else {
if let Some(slot) = block_remap.get_mut(old_index) {
*slot = Some(new_index);
new_index = new_index.saturating_add(1);
}
}
}
}
let mut predecessors: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
for (block_idx, block) in self.blocks.iter().enumerate() {
for target in block.successors() {
predecessors.entry(target).or_default().push(block_idx);
}
}
for block in &mut self.blocks {
for instr in block.instructions_mut() {
Self::remap_branch_targets(instr.op_mut(), &block_remap, &redirect_map);
}
}
for block_idx in 0..self.blocks.len() {
let phi_block_preds: Vec<usize> =
predecessors.get(&block_idx).cloned().unwrap_or_default();
let kept_phi_block_preds: Vec<usize> = phi_block_preds
.iter()
.filter_map(|&old_pred| block_remap.get(old_pred).and_then(|opt| *opt))
.collect();
let Some(block) = self.blocks.get_mut(block_idx) else {
continue;
};
for phi in block.phi_nodes_mut() {
let mut changes: Vec<(usize, Option<PhiOperand>, Vec<PhiOperand>)> = Vec::new();
let mut orphaned_values: Vec<SsaVarId> = Vec::new();
for (op_idx, operand) in phi.operands().iter().enumerate() {
let old_pred = operand.predecessor();
let value = operand.value();
if redirect_map.contains_key(&old_pred) {
let kept_preds = find_kept_predecessors(
old_pred,
&predecessors,
&block_remap,
&redirect_map,
);
if kept_preds.is_empty() {
orphaned_values.push(value);
}
let replacements: Vec<PhiOperand> = kept_preds
.into_iter()
.map(|new_pred| PhiOperand::new(value, new_pred))
.collect();
changes.push((op_idx, None, replacements));
} else if let Some(Some(new_pred)) = block_remap.get(old_pred) {
changes.push((op_idx, Some(PhiOperand::new(value, *new_pred)), Vec::new()));
}
}
for (op_idx, replacement, additions) in changes.into_iter().rev() {
if let Some(new_op) = replacement {
if let Some(operand) = phi.operands_mut().get_mut(op_idx) {
*operand = new_op;
}
} else {
phi.operands_mut().remove(op_idx);
for op in additions {
phi.add_operand(op);
}
}
}
if !orphaned_values.is_empty() {
let accounted_preds: BTreeSet<usize> =
phi.operands().iter().map(PhiOperand::predecessor).collect();
let unaccounted_preds: Vec<usize> = kept_phi_block_preds
.iter()
.copied()
.filter(|pred| !accounted_preds.contains(pred))
.collect();
for (orphan_val, &unaccounted_pred) in
orphaned_values.iter().zip(unaccounted_preds.iter())
{
phi.add_operand(PhiOperand::new(*orphan_val, unaccounted_pred));
}
}
}
}
let mut kept_blocks: Vec<SsaBlock<T>> = Vec::with_capacity(new_index);
for (old_index, block) in self.blocks.drain(..).enumerate() {
if matches!(block_remap.get(old_index), Some(Some(_))) {
kept_blocks.push(block);
}
}
for (new_idx, block) in kept_blocks.iter_mut().enumerate() {
block.set_id(new_idx);
}
self.blocks = kept_blocks;
for handler in &mut self.exception_handlers {
handler.remap_block_indices(&block_remap);
}
self.ensure_valid_terminator();
}
fn ensure_valid_terminator(&mut self) {
let has_useful_code = self.blocks.iter().any(|block| {
block.instructions().iter().any(|instr| {
match instr.op() {
SsaOp::Jump { .. } | SsaOp::Nop => false,
_ => true,
}
})
});
if !has_useful_code {
if let Some(entry_block) = self.blocks.first_mut() {
entry_block.instructions_mut().clear();
entry_block.phi_nodes_mut().clear();
entry_block
.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
}
}
}
fn find_ultimate_target(
&self,
block_idx: usize,
block_remap: &[Option<usize>],
) -> Option<usize> {
let mut visited = BitSet::new(self.blocks.len());
let mut current = block_idx;
while visited.insert(current) {
let block = self.blocks.get(current)?;
let terminator = block.terminator_op();
let target = terminator.and_then(|op| match op {
SsaOp::Jump { target } => Some(*target),
_ => None,
});
match target {
Some(t) if block_remap.get(t).copied().flatten().is_some() => {
return block_remap.get(t).copied().flatten();
}
Some(t) => {
current = t;
}
None => {
if terminator.is_none() && block.instructions().is_empty() {
let next_block = current.saturating_add(1);
if next_block < self.blocks.len() {
if let Some(Some(new_idx)) = block_remap.get(next_block) {
return Some(*new_idx);
} else if block_remap.get(next_block).is_some() {
current = next_block;
continue;
}
}
}
return None;
}
}
}
None }
fn remap_branch_targets(
op: &mut SsaOp<T>,
block_remap: &[Option<usize>],
redirect_map: &BTreeMap<usize, usize>,
) {
let remap_target = |target: &mut usize| {
if let Some(&new_target) = redirect_map.get(target) {
*target = new_target;
return;
}
if let Some(Some(new_target)) = block_remap.get(*target) {
*target = *new_target;
}
};
match op {
SsaOp::Jump { target } | SsaOp::Leave { target } => {
remap_target(target);
}
SsaOp::Branch {
true_target,
false_target,
..
}
| SsaOp::BranchCmp {
true_target,
false_target,
..
} => {
remap_target(true_target);
remap_target(false_target);
}
SsaOp::Switch {
targets, default, ..
} => {
for target in targets.iter_mut() {
remap_target(target);
}
remap_target(default);
}
_ => {}
}
}
}