mod canonical;
mod duplication;
mod queries;
mod rebuild;
mod repair;
mod semantics;
mod transforms;
pub use queries::{MethodPurity, ReturnInfo};
pub use transforms::TrivialPhiOptions;
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
};
use crate::{
analysis::ssa::{
exception::SsaExceptionHandler,
verifier::{SsaVerifier, VerifyLevel},
DefSite, FunctionVarAllocator, PhiNode, PhiOperand, SsaBlock, SsaInstruction, SsaOp,
SsaType, SsaVarId, SsaVariable, VariableOrigin,
},
metadata::signatures::SignatureLocalVariable,
};
#[derive(Debug, Clone)]
pub struct SsaFunction {
blocks: Vec<SsaBlock>,
variables: Vec<SsaVariable>,
var_allocator: FunctionVarAllocator,
origin_versions: BTreeMap<VariableOrigin, Vec<SsaVarId>>,
origin_types: BTreeMap<VariableOrigin, SsaType>,
num_args: usize,
num_locals: usize,
original_num_locals: usize,
preserved_dispatch_vars: BTreeSet<SsaVarId>,
original_local_types: Option<Vec<SignatureLocalVariable>>,
exception_handlers: Vec<SsaExceptionHandler>,
rename_groups: Vec<u32>,
}
impl SsaFunction {
#[must_use]
pub fn new(num_args: usize, num_locals: usize) -> Self {
Self {
blocks: Vec::new(),
variables: Vec::new(),
var_allocator: FunctionVarAllocator::new(),
origin_versions: BTreeMap::new(),
origin_types: BTreeMap::new(),
num_args,
num_locals,
original_num_locals: num_locals,
preserved_dispatch_vars: BTreeSet::new(),
original_local_types: None,
exception_handlers: Vec::new(),
rename_groups: Vec::new(),
}
}
#[must_use]
pub fn with_capacity(
num_args: usize,
num_locals: usize,
block_capacity: usize,
var_capacity: usize,
) -> Self {
Self {
blocks: Vec::with_capacity(block_capacity),
variables: Vec::with_capacity(var_capacity),
var_allocator: FunctionVarAllocator::new(),
origin_versions: BTreeMap::new(),
origin_types: BTreeMap::new(),
num_args,
num_locals,
original_num_locals: num_locals,
preserved_dispatch_vars: BTreeSet::new(),
original_local_types: None,
exception_handlers: Vec::new(),
rename_groups: Vec::with_capacity(var_capacity),
}
}
#[must_use]
pub fn blocks(&self) -> &[SsaBlock] {
&self.blocks
}
pub fn iter_blocks(&self) -> impl Iterator<Item = (usize, &SsaBlock)> {
self.blocks.iter().enumerate()
}
pub fn iter_instructions(&self) -> impl Iterator<Item = (usize, usize, &SsaInstruction)> {
self.blocks
.iter()
.enumerate()
.flat_map(|(block_idx, block)| {
block
.instructions()
.iter()
.enumerate()
.map(move |(instr_idx, instr)| (block_idx, instr_idx, instr))
})
}
pub fn iter_instructions_mut(
&mut self,
) -> impl Iterator<Item = (usize, usize, &mut SsaInstruction)> {
self.blocks
.iter_mut()
.enumerate()
.flat_map(|(block_idx, block)| {
block
.instructions_mut()
.iter_mut()
.enumerate()
.map(move |(instr_idx, instr)| (block_idx, instr_idx, instr))
})
}
pub fn iter_phis(&self) -> impl Iterator<Item = (usize, usize, &PhiNode)> {
self.blocks
.iter()
.enumerate()
.flat_map(|(block_idx, block)| {
block
.phi_nodes()
.iter()
.enumerate()
.map(move |(phi_idx, phi)| (block_idx, phi_idx, phi))
})
}
pub fn blocks_mut(&mut self) -> &mut Vec<SsaBlock> {
&mut self.blocks
}
#[must_use]
pub fn variables(&self) -> &[SsaVariable] {
&self.variables
}
pub fn variables_mut(&mut self) -> &mut Vec<SsaVariable> {
&mut self.variables
}
#[must_use]
pub const fn num_args(&self) -> usize {
self.num_args
}
#[must_use]
pub const fn num_locals(&self) -> usize {
self.num_locals
}
#[must_use]
pub const fn original_num_locals(&self) -> usize {
self.original_num_locals
}
pub(crate) fn set_num_locals(&mut self, num_locals: usize, original_num_locals: usize) {
self.num_locals = num_locals;
self.original_num_locals = original_num_locals;
}
#[must_use]
pub fn block_count(&self) -> usize {
self.blocks.len()
}
#[must_use]
pub fn variable_count(&self) -> usize {
self.variables.len()
}
#[must_use]
pub fn var_id_capacity(&self) -> usize {
let from_vars = self
.variables
.iter()
.map(|v| v.id().index() + 1)
.max()
.unwrap_or(0);
let from_blocks = self
.blocks
.iter()
.flat_map(|b| {
let phi_ids = b.phi_nodes().iter().flat_map(|p| {
std::iter::once(p.result().index())
.chain(p.operands().iter().map(|op| op.value().index()))
});
let instr_ids = b.instructions().iter().flat_map(|i| {
i.op()
.dest()
.into_iter()
.chain(i.op().uses())
.map(|v| v.index())
});
phi_ids.chain(instr_ids)
})
.max()
.map_or(0, |m| m + 1);
from_vars.max(from_blocks).max(self.variables.len())
}
#[must_use]
pub fn versions_of(&self, origin: VariableOrigin) -> &[SsaVarId] {
self.origin_versions
.get(&origin)
.map_or(&[], |v| v.as_slice())
}
#[must_use]
pub fn latest_version(&self, origin: VariableOrigin) -> Option<SsaVarId> {
self.origin_versions
.get(&origin)
.and_then(|v| v.last().copied())
}
#[must_use]
pub fn var_index(&self, id: SsaVarId) -> Option<usize> {
let idx = id.index();
if idx < self.variables.len() {
Some(idx)
} else {
None
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
#[must_use]
pub fn block(&self, index: usize) -> Option<&SsaBlock> {
self.blocks.get(index)
}
pub fn block_mut(&mut self, index: usize) -> Option<&mut SsaBlock> {
self.blocks.get_mut(index)
}
#[must_use]
pub fn variable(&self, id: SsaVarId) -> Option<&SsaVariable> {
self.variables.get(id.index())
}
pub fn variable_mut(&mut self, id: SsaVarId) -> Option<&mut SsaVariable> {
self.variables.get_mut(id.index())
}
pub fn add_block(&mut self, block: SsaBlock) {
self.blocks.push(block);
}
pub fn create_variable(
&mut self,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
var_type: SsaType,
) -> SsaVarId {
let id = self.var_allocator.alloc();
let var = SsaVariable::new(id, origin, version, def_site, var_type.clone());
debug_assert_eq!(id.index(), self.variables.len());
self.origin_versions.entry(origin).or_default().push(id);
if !var_type.is_unknown() && !self.origin_types.contains_key(&origin) {
self.origin_types.insert(origin, var_type);
}
self.variables.push(var);
if self.rename_groups.len() <= id.index() {
self.rename_groups.resize(id.index() + 1, u32::MAX);
}
id
}
pub fn create_variable_for_origin(
&mut self,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
) -> SsaVarId {
let var_type = self.origin_type(origin);
self.create_variable(origin, version, def_site, var_type)
}
pub fn register_origin_type(&mut self, origin: VariableOrigin, var_type: SsaType) {
if !var_type.is_unknown() && !self.origin_types.contains_key(&origin) {
self.origin_types.insert(origin, var_type);
}
}
#[must_use]
pub fn origin_type(&self, origin: VariableOrigin) -> SsaType {
self.origin_types
.get(&origin)
.cloned()
.unwrap_or(SsaType::Unknown)
}
#[must_use]
pub fn origin_types(&self) -> &BTreeMap<VariableOrigin, SsaType> {
&self.origin_types
}
fn rebuild_origin_versions(&mut self) {
self.origin_versions.clear();
for var in &self.variables {
self.origin_versions
.entry(var.origin())
.or_default()
.push(var.id());
}
}
fn reassign_dense_ids(&mut self) -> BTreeMap<SsaVarId, SsaVarId> {
let mut remap = BTreeMap::new();
let old_groups = std::mem::take(&mut self.rename_groups);
self.var_allocator = FunctionVarAllocator::starting_from(self.variables.len());
let mut new_groups = vec![u32::MAX; self.variables.len()];
for (index, var) in self.variables.iter_mut().enumerate() {
let old_id = var.id();
let new_id = SsaVarId::from_index(index);
if old_id.index() < old_groups.len() {
new_groups[index] = old_groups[old_id.index()];
}
if old_id != new_id {
remap.insert(old_id, new_id);
var.set_id(new_id);
}
}
self.rename_groups = new_groups;
remap
}
fn remap_var_ids_in_blocks(&mut self, remap: &BTreeMap<SsaVarId, SsaVarId>) {
if remap.is_empty() {
return;
}
let lookup = |id: SsaVarId| -> Option<SsaVarId> { remap.get(&id).copied() };
let resolve = |id: SsaVarId| -> SsaVarId { remap.get(&id).copied().unwrap_or(id) };
for block in &mut self.blocks {
for phi in block.phi_nodes_mut() {
let old_result = phi.result();
phi.set_result(resolve(old_result));
for operand in phi.operands_mut() {
let old_value = operand.value();
*operand = PhiOperand::new(resolve(old_value), operand.predecessor());
}
}
for instr in block.instructions_mut() {
let new_op = instr.op().remap_variables(lookup);
instr.set_op(new_op);
}
}
let remapped_dispatch: BTreeSet<SsaVarId> = self
.preserved_dispatch_vars
.iter()
.map(|id| resolve(*id))
.collect();
self.preserved_dispatch_vars = remapped_dispatch;
}
pub fn mark_preserved_dispatch_var(&mut self, var: SsaVarId) {
self.preserved_dispatch_vars.insert(var);
}
#[must_use]
pub fn is_preserved_dispatch_var(&self, var: SsaVarId) -> bool {
self.preserved_dispatch_vars.contains(&var)
}
#[must_use]
pub fn has_preserved_dispatch_vars(&self) -> bool {
!self.preserved_dispatch_vars.is_empty()
}
pub fn set_original_local_types(&mut self, types: Vec<SignatureLocalVariable>) {
self.original_local_types = Some(types);
}
#[must_use]
pub fn original_local_types(&self) -> Option<&[SignatureLocalVariable]> {
self.original_local_types.as_deref()
}
pub fn set_exception_handlers(&mut self, handlers: Vec<SsaExceptionHandler>) {
self.exception_handlers = handlers;
}
#[must_use]
pub fn exception_handlers(&self) -> &[SsaExceptionHandler] {
&self.exception_handlers
}
#[must_use]
pub fn has_exception_handlers(&self) -> bool {
!self.exception_handlers.is_empty()
}
#[must_use]
pub(crate) fn rename_group(&self, var_id: SsaVarId) -> u32 {
self.rename_groups
.get(var_id.index())
.copied()
.unwrap_or(u32::MAX)
}
pub(crate) fn set_rename_group(&mut self, var_id: SsaVarId, group: u32) {
let idx = var_id.index();
if idx >= self.rename_groups.len() {
self.rename_groups.resize(idx + 1, u32::MAX);
}
self.rename_groups[idx] = group;
}
pub fn rebuild_ssa(&mut self) -> crate::Result<()> {
if self.blocks.is_empty() {
return Ok(());
}
rebuild::SsaRebuilder::new(self).rebuild()
}
pub fn sort_all_blocks_topologically(&mut self) -> bool {
let mut all_sorted = true;
for block in &mut self.blocks {
if !block.sort_instructions_topologically() {
all_sorted = false;
}
}
all_sorted
}
pub fn validate_types(&self) -> Result<(), String> {
for var in &self.variables {
if !var.var_type().is_unknown() || var.uses().is_empty() {
continue;
}
let has_meaningful_use = var.uses().iter().any(|use_site| {
if use_site.is_phi_operand {
if let Some(block) = self.block(use_site.block) {
if let Some(phi) = block.phi(use_site.instruction) {
if let Some(result_var) = self.variable(phi.result()) {
return !result_var.var_type().is_unknown();
}
}
}
return false;
}
if let Some(block) = self.block(use_site.block) {
if let Some(instr) = block.instruction(use_site.instruction) {
return !matches!(instr.op(), SsaOp::Pop { .. });
}
}
true });
if has_meaningful_use {
let use_details: Vec<String> = var
.uses()
.iter()
.map(|use_site| {
if use_site.is_phi_operand {
return format!("phi in block {}", use_site.block);
}
if let Some(block) = self.block(use_site.block) {
if let Some(instr) = block.instruction(use_site.instruction) {
return format!(
"block {} instr {}: {:?}",
use_site.block,
use_site.instruction,
instr.op()
);
}
}
format!(
"block {} instr {}: <unknown>",
use_site.block, use_site.instruction
)
})
.collect();
return Err(format!(
"Variable {} (origin={:?}) has Unknown type but is used ({} uses): [{}]",
var.id(),
var.origin(),
var.uses().len(),
use_details.join(", ")
));
}
}
Ok(())
}
pub fn validate(&self) -> Result<(), String> {
let errors = SsaVerifier::new(self).verify(VerifyLevel::Standard);
if errors.is_empty() {
Ok(())
} else {
Err(errors
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("; "))
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.validate().is_ok()
}
}
impl fmt::Display for SsaFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"SSA Function ({} args, {} locals):",
self.num_args, self.num_locals
)?;
writeln!(f, " Variables: {}", self.variables.len())?;
writeln!(f, " Blocks: {}", self.blocks.len())?;
writeln!(f)?;
for block in &self.blocks {
write!(f, "{block}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
analysis::{
ssa::{
ConstValue, DefSite, PhiNode, PhiOperand, SsaBlock, SsaInstruction, SsaOp, SsaType,
SsaVarId, UseSite, VariableOrigin,
},
SsaFunctionBuilder,
},
assembly::{FlowType, Instruction, InstructionCategory, Operand, StackBehavior},
};
fn make_test_cil_instruction(mnemonic: &'static str) -> Instruction {
Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x00,
prefix: 0,
mnemonic,
category: InstructionCategory::Misc,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![],
}
}
#[test]
fn test_ssa_function_creation() {
let func = SsaFunction::new(2, 3);
assert_eq!(func.num_args(), 2);
assert_eq!(func.num_locals(), 3);
assert!(func.is_empty());
assert_eq!(func.block_count(), 0);
assert_eq!(func.variable_count(), 0);
}
#[test]
fn test_ssa_function_with_capacity() {
let func = SsaFunction::with_capacity(2, 1, 10, 50);
assert_eq!(func.num_args(), 2);
assert_eq!(func.num_locals(), 1);
assert!(func.is_empty());
}
#[test]
fn test_ssa_function_add_block() {
let mut func = SsaFunction::new(0, 0);
func.add_block(SsaBlock::new(0));
func.add_block(SsaBlock::new(1));
assert!(!func.is_empty());
assert_eq!(func.block_count(), 2);
assert!(func.block(0).is_some());
assert!(func.block(1).is_some());
assert!(func.block(2).is_none());
}
#[test]
fn test_ssa_function_add_variable() {
let mut func = SsaFunction::new(1, 0);
let id1 = func.create_variable(
VariableOrigin::Argument(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
let id2 = func.create_variable(
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
assert_ne!(id1, id2);
assert_eq!(func.variable_count(), 2);
}
#[test]
fn test_ssa_function_variable_access() {
let mut func = SsaFunction::new(1, 0);
let id = func.create_variable(
VariableOrigin::Argument(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
assert!(func.variable(id).is_some());
assert_eq!(
func.variable(id).unwrap().origin(),
VariableOrigin::Argument(0)
);
}
#[test]
fn test_ssa_function_argument_variables() {
let mut func = SsaFunction::new(2, 1);
func.create_variable(
VariableOrigin::Argument(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
func.create_variable(
VariableOrigin::Argument(1),
0,
DefSite::phi(0),
SsaType::Unknown,
);
func.create_variable(
VariableOrigin::Argument(0),
1,
DefSite::instruction(1, 0),
SsaType::Unknown,
);
func.create_variable(
VariableOrigin::Local(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
let args: Vec<_> = func.argument_variables().collect();
assert_eq!(args.len(), 2); }
#[test]
fn test_ssa_function_local_variables() {
let mut func = SsaFunction::new(0, 2);
func.create_variable(
VariableOrigin::Local(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
func.create_variable(
VariableOrigin::Local(1),
0,
DefSite::phi(0),
SsaType::Unknown,
);
func.create_variable(VariableOrigin::Phi, 0, DefSite::phi(0), SsaType::Unknown);
let locals: Vec<_> = func.local_variables().collect();
assert_eq!(locals.len(), 2);
}
#[test]
fn test_ssa_function_variables_from_argument() {
let mut func = SsaFunction::new(2, 0);
func.create_variable(
VariableOrigin::Argument(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
func.create_variable(
VariableOrigin::Argument(0),
1,
DefSite::instruction(1, 0),
SsaType::Unknown,
);
func.create_variable(
VariableOrigin::Argument(1),
0,
DefSite::phi(0),
SsaType::Unknown,
);
let arg0_vars: Vec<_> = func.variables_from_argument(0).collect();
assert_eq!(arg0_vars.len(), 2);
let arg1_vars: Vec<_> = func.variables_from_argument(1).collect();
assert_eq!(arg1_vars.len(), 1);
}
#[test]
fn test_ssa_function_total_phi_count() {
let mut func = SsaFunction::new(0, 0);
let mut block0 = SsaBlock::new(0);
block0.add_phi(PhiNode::new(
SsaVarId::from_index(0),
VariableOrigin::Local(0),
));
block0.add_phi(PhiNode::new(
SsaVarId::from_index(1),
VariableOrigin::Local(1),
));
func.add_block(block0);
let mut block1 = SsaBlock::new(1);
block1.add_phi(PhiNode::new(
SsaVarId::from_index(2),
VariableOrigin::Local(0),
));
func.add_block(block1);
func.add_block(SsaBlock::new(2));
assert_eq!(func.phi_count(), 3);
}
#[test]
fn test_ssa_function_total_instruction_count() {
let mut func = SsaFunction::new(0, 0);
let mut block0 = SsaBlock::new(0);
block0.add_instruction(SsaInstruction::new(
make_test_cil_instruction("nop"),
SsaOp::Nop,
));
block0.add_instruction(SsaInstruction::new(
make_test_cil_instruction("nop"),
SsaOp::Nop,
));
func.add_block(block0);
let mut block1 = SsaBlock::new(1);
block1.add_instruction(SsaInstruction::new(
make_test_cil_instruction("ret"),
SsaOp::Return { value: None },
));
func.add_block(block1);
assert_eq!(func.instruction_count(), 3);
}
#[test]
fn test_ssa_function_all_phi_nodes() {
let mut func = SsaFunction::new(0, 0);
let phi_result = SsaVarId::from_index(0);
let phi_operand = SsaVarId::from_index(1);
let mut block0 = SsaBlock::new(0);
let mut phi = PhiNode::new(phi_result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(phi_operand, 1));
block0.add_phi(phi);
func.add_block(block0);
let phis: Vec<_> = func.all_phi_nodes().collect();
assert_eq!(phis.len(), 1);
assert_eq!(phis[0].result(), phi_result);
}
#[test]
fn test_ssa_function_dead_variables() {
let mut func = SsaFunction::new(0, 0);
func.create_variable(
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
let live_id = func.create_variable(
VariableOrigin::Local(1),
0,
DefSite::instruction(0, 1),
SsaType::Unknown,
);
func.variable_mut(live_id)
.unwrap()
.add_use(UseSite::instruction(0, 2));
let dead: Vec<_> = func.dead_variables().collect();
assert_eq!(dead.len(), 1);
assert_eq!(func.dead_variable_count(), 1);
}
#[test]
fn test_ssa_function_display() {
let mut func = SsaFunction::new(1, 1);
func.add_block(SsaBlock::new(0));
let display = format!("{func}");
assert!(display.contains("SSA Function"));
assert!(display.contains("1 args"));
assert!(display.contains("1 locals"));
assert!(display.contains("B0:"));
}
#[test]
fn test_compact_variables_removes_orphaned() {
let mut func = SsaFunction::new(0, 0);
let defined_id = func.create_variable(
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
let mut block = SsaBlock::new(0);
let instr = SsaInstruction::new(
make_test_cil_instruction("nop"),
SsaOp::Const {
dest: defined_id,
value: ConstValue::I32(42),
},
);
block.add_instruction(instr);
let ret = SsaInstruction::new(
make_test_cil_instruction("ret"),
SsaOp::Return { value: None },
);
block.add_instruction(ret);
func.add_block(block);
func.create_variable(
VariableOrigin::Local(1),
1,
DefSite::instruction(0, 99),
SsaType::Unknown,
);
assert_eq!(func.variable_count(), 2);
let removed = func.compact_variables();
assert_eq!(removed, 1);
assert_eq!(func.variable_count(), 1);
assert!(func.variable(SsaVarId::from_index(0)).is_some());
}
#[test]
fn test_compact_variables_preserves_entry_vars() {
let mut func = SsaFunction::new(1, 1);
let arg_id = func.create_variable(
VariableOrigin::Argument(0),
0,
DefSite::entry(),
SsaType::Unknown,
);
let local_id = func.create_variable(
VariableOrigin::Local(0),
0,
DefSite::entry(),
SsaType::Unknown,
);
func.create_variable(
VariableOrigin::Local(2),
1,
DefSite::instruction(0, 99),
SsaType::Unknown,
);
let mut block = SsaBlock::new(0);
let ret = SsaInstruction::new(
make_test_cil_instruction("ret"),
SsaOp::Return { value: None },
);
block.add_instruction(ret);
func.add_block(block);
assert_eq!(func.variable_count(), 3);
let removed = func.compact_variables();
assert_eq!(removed, 1);
assert_eq!(func.variable_count(), 2);
assert!(func.variable(arg_id).is_some());
assert!(func.variable(local_id).is_some());
}
#[test]
fn test_find_constants_collects_all_const_instructions() {
let ssa = SsaFunctionBuilder::new(1, 0)
.build_with(|f| {
f.block(0, |b| {
let c1 = b.const_i32(42);
let c2 = b.const_i32(100);
let _ = b.add(c1, c2);
b.ret();
});
})
.unwrap();
let constants = ssa.find_constants();
assert_eq!(constants.len(), 2);
let values: Vec<_> = constants.values().collect();
assert!(values.iter().any(|v| **v == ConstValue::I32(42)));
assert!(values.iter().any(|v| **v == ConstValue::I32(100)));
}
#[test]
fn test_find_constants_across_multiple_blocks() {
let ssa = SsaFunctionBuilder::new(2, 0)
.build_with(|f| {
f.block(0, |b| {
let _ = b.const_i32(1);
b.jump(1);
});
f.block(1, |b| {
let _ = b.const_i32(2);
let _ = b.const_i32(3);
b.ret();
});
})
.unwrap();
let constants = ssa.find_constants();
assert_eq!(constants.len(), 3);
}
#[test]
fn test_find_constants_empty_when_no_constants() {
let ssa = SsaFunctionBuilder::new(1, 0)
.build_with(|f| {
f.block(0, |b| {
b.ret();
});
})
.unwrap();
let constants = ssa.find_constants();
assert!(constants.is_empty());
}
#[test]
fn test_find_trampoline_blocks_in_chain() {
let ssa = SsaFunctionBuilder::new(4, 0)
.build_with(|f| {
f.block(0, |b| b.jump(1)); f.block(1, |b| b.jump(2)); f.block(2, |b| b.jump(3)); f.block(3, |b| b.ret()); })
.unwrap();
let trampolines = ssa.find_trampoline_blocks(true);
assert_eq!(trampolines.len(), 2);
assert_eq!(trampolines.get(&1), Some(&2));
assert_eq!(trampolines.get(&2), Some(&3));
assert!(!trampolines.contains_key(&0));
let trampolines = ssa.find_trampoline_blocks(false);
assert_eq!(trampolines.len(), 3);
assert_eq!(trampolines.get(&0), Some(&1));
assert_eq!(trampolines.get(&1), Some(&2));
assert_eq!(trampolines.get(&2), Some(&3));
}
#[test]
fn test_find_trampoline_blocks_mixed_control_flow() {
let ssa = SsaFunctionBuilder::new(4, 0)
.build_with(|f| {
f.block(0, |b| {
let cond = b.const_true();
b.branch(cond, 1, 2); });
f.block(1, |b| b.jump(3)); f.block(2, |b| {
let _ = b.const_i32(42);
b.jump(3); });
f.block(3, |b| b.ret());
})
.unwrap();
let trampolines = ssa.find_trampoline_blocks(false);
assert_eq!(trampolines.len(), 1);
assert_eq!(trampolines.get(&1), Some(&3));
}
#[test]
fn test_find_trampoline_blocks_empty_result() {
let ssa = SsaFunctionBuilder::new(2, 0)
.build_with(|f| {
f.block(0, |b| {
let _ = b.const_i32(1);
b.ret();
});
f.block(1, |b| b.ret());
})
.unwrap();
let trampolines = ssa.find_trampoline_blocks(false);
assert!(trampolines.is_empty());
}
#[test]
fn test_iter_instructions_mut() {
let mut ssa = SsaFunctionBuilder::new(1, 0)
.build_with(|f| {
f.block(0, |b| {
let c1 = b.const_i32(10);
let c2 = b.const_i32(20);
let _ = b.add(c1, c2);
b.ret();
});
})
.unwrap();
let count = ssa.iter_instructions().count();
assert_eq!(count, 4);
let mut positions: Vec<(usize, usize)> = Vec::new();
for (block_idx, instr_idx, _instr) in ssa.iter_instructions_mut() {
positions.push((block_idx, instr_idx));
}
assert_eq!(positions.len(), 4);
assert_eq!(positions[0], (0, 0));
assert_eq!(positions[1], (0, 1));
assert_eq!(positions[2], (0, 2));
assert_eq!(positions[3], (0, 3));
}
#[test]
fn test_iter_instructions_mut_across_blocks() {
let mut ssa = SsaFunctionBuilder::new(2, 0)
.build_with(|f| {
f.block(0, |b| {
let _ = b.const_i32(1);
b.jump(1);
});
f.block(1, |b| {
let _ = b.const_i32(2);
b.ret();
});
})
.unwrap();
let positions: Vec<(usize, usize)> = ssa
.iter_instructions_mut()
.map(|(b, i, _)| (b, i))
.collect();
assert_eq!(positions.len(), 4);
assert_eq!(positions[0], (0, 0));
assert_eq!(positions[1], (0, 1));
assert_eq!(positions[2], (1, 0));
assert_eq!(positions[3], (1, 1));
}
}