use std::collections::HashMap;
use crate::{
ir::{
block::SsaBlock,
function::SsaFunction,
instruction::SsaInstruction,
ops::SsaOp,
phi::{PhiNode, PhiOperand},
variable::SsaVarId,
},
target::Target,
};
impl<T: Target> SsaFunction<T> {
#[must_use]
pub fn allocate_fresh_variables_for_block(
&mut self,
block_idx: usize,
) -> Option<HashMap<SsaVarId, SsaVarId>> {
let block = self.block(block_idx)?;
let mut mapping = HashMap::new();
let phi_ids: Vec<SsaVarId> = block.phi_nodes().iter().map(|phi| phi.result()).collect();
let instr_dests: Vec<SsaVarId> = block
.instructions()
.iter()
.filter_map(|instr| instr.op().dest())
.collect();
for old_id in phi_ids {
let new_id = self.var_allocator.alloc();
mapping.insert(old_id, new_id);
}
for dest in instr_dests {
let new_id = self.var_allocator.alloc();
mapping.insert(dest, new_id);
}
Some(mapping)
}
#[must_use]
pub fn clone_block_with_remap(
&self,
block_idx: usize,
new_block_id: usize,
var_remap: &HashMap<SsaVarId, SsaVarId>,
pred_remap: Option<&HashMap<usize, usize>>,
) -> Option<SsaBlock<T>> {
let block = self.block(block_idx)?;
let mut new_block =
SsaBlock::with_capacity(new_block_id, block.phi_count(), block.instruction_count());
for phi in block.phi_nodes() {
let new_result = var_remap
.get(&phi.result())
.copied()
.unwrap_or(phi.result());
let mut new_phi = PhiNode::with_capacity(new_result, phi.origin(), phi.operand_count());
for operand in phi.operands() {
let new_value = var_remap
.get(&operand.value())
.copied()
.unwrap_or(operand.value());
let new_pred = pred_remap
.and_then(|m| m.get(&operand.predecessor()).copied())
.unwrap_or(operand.predecessor());
new_phi.add_operand(PhiOperand::new(new_value, new_pred));
}
new_block.add_phi(new_phi);
}
for instr in block.instructions() {
let new_instr = Self::clone_instruction_with_remap(instr, var_remap);
new_block.add_instruction(new_instr);
}
Some(new_block)
}
fn clone_instruction_with_remap(
instr: &SsaInstruction<T>,
var_remap: &HashMap<SsaVarId, SsaVarId>,
) -> SsaInstruction<T> {
let original = instr.original().clone();
let new_op = instr
.op()
.remap_variables(|old_id| var_remap.get(&old_id).copied());
let mut new_instr = SsaInstruction::new(original, new_op);
new_instr.set_result_type(instr.result_type().cloned());
new_instr
}
pub fn duplicate_block(
&mut self,
block_idx: usize,
) -> Option<(usize, HashMap<SsaVarId, SsaVarId>)> {
let var_remap = self.allocate_fresh_variables_for_block(block_idx)?;
let var_info: Vec<_> = var_remap
.iter()
.filter_map(|(&old_id, _)| {
self.variable(old_id).map(|v| {
(
old_id,
v.origin(),
v.version(),
v.def_site(),
v.var_type().clone(),
)
})
})
.collect();
for (_old_id, origin, version, def_site, var_type) in var_info {
self.create_variable(origin, version, def_site, var_type);
}
let new_block_id = self.blocks.len();
let new_block = self.clone_block_with_remap(block_idx, new_block_id, &var_remap, None)?;
self.add_block(new_block);
Some((new_block_id, var_remap))
}
pub fn remap_block_targets(
&mut self,
block_idx: usize,
target_remap: &HashMap<usize, usize>,
) -> bool {
let Some(block) = self.block_mut(block_idx) else {
return false;
};
let Some(last) = block.instructions_mut().last_mut() else {
return false;
};
let new_op = match last.op() {
SsaOp::Jump { target } => {
if let Some(&new_target) = target_remap.get(target) {
SsaOp::Jump { target: new_target }
} else {
return false;
}
}
SsaOp::Branch {
condition,
true_target,
false_target,
} => {
let new_true = target_remap
.get(true_target)
.copied()
.unwrap_or(*true_target);
let new_false = target_remap
.get(false_target)
.copied()
.unwrap_or(*false_target);
if new_true == *true_target && new_false == *false_target {
return false;
}
SsaOp::Branch {
condition: *condition,
true_target: new_true,
false_target: new_false,
}
}
SsaOp::BranchCmp {
left,
right,
cmp,
unsigned,
true_target,
false_target,
} => {
let new_true = target_remap
.get(true_target)
.copied()
.unwrap_or(*true_target);
let new_false = target_remap
.get(false_target)
.copied()
.unwrap_or(*false_target);
if new_true == *true_target && new_false == *false_target {
return false;
}
SsaOp::BranchCmp {
left: *left,
right: *right,
cmp: *cmp,
unsigned: *unsigned,
true_target: new_true,
false_target: new_false,
}
}
SsaOp::Switch {
value,
targets,
default,
} => {
let new_targets: Vec<usize> = targets
.iter()
.map(|&t| target_remap.get(&t).copied().unwrap_or(t))
.collect();
let new_default = target_remap.get(default).copied().unwrap_or(*default);
if new_targets == *targets && new_default == *default {
return false;
}
SsaOp::Switch {
value: *value,
targets: new_targets,
default: new_default,
}
}
_ => return false,
};
last.set_op(new_op);
true
}
}