use std::collections::HashMap;
use crate::analysis::ssa::{
ConstValue, DefSite, MethodRef, PhiNode, PhiOperand, SsaBlock, SsaFunction, SsaInstruction,
SsaOp, SsaType, SsaVarId, SsaVariable, VariableOrigin,
};
#[derive(Debug)]
pub struct SsaFunctionBuilder {
num_args: usize,
num_locals: usize,
next_stack_slot: u32,
arg_vars: Vec<SsaVarId>,
local_vars: Vec<SsaVarId>,
variables: Vec<SsaVariable>,
blocks: HashMap<usize, SsaBlock>,
max_block_id: usize,
}
impl SsaFunctionBuilder {
#[must_use]
pub fn new(num_args: usize, num_locals: usize) -> Self {
let mut builder = Self {
num_args,
num_locals,
next_stack_slot: 0,
arg_vars: Vec::with_capacity(num_args),
local_vars: Vec::with_capacity(num_locals),
variables: Vec::new(),
blocks: HashMap::new(),
max_block_id: 0,
};
for i in 0..num_args {
#[allow(clippy::cast_possible_truncation)]
let idx = i as u16;
let id = builder.alloc_var_with_origin(VariableOrigin::Argument(idx));
builder.arg_vars.push(id);
}
for i in 0..num_locals {
#[allow(clippy::cast_possible_truncation)]
let idx = i as u16;
let id = builder.alloc_var_with_origin(VariableOrigin::Local(idx));
builder.local_vars.push(id);
}
builder
}
fn alloc_var_with_origin(&mut self, origin: VariableOrigin) -> SsaVarId {
let var = SsaVariable::new(origin, 0, DefSite::entry());
let id = var.id();
self.variables.push(var);
id
}
fn alloc_stack_var(&mut self) -> SsaVarId {
let slot = self.next_stack_slot;
self.next_stack_slot += 1;
self.alloc_var_with_origin(VariableOrigin::Stack(slot))
}
fn alloc_stack_var_typed(&mut self, var_type: SsaType) -> SsaVarId {
let id = self.alloc_stack_var();
if let Some(var) = self.variables.last_mut() {
var.set_type(var_type);
}
id
}
pub fn build_with<F>(mut self, f: F) -> SsaFunction
where
F: FnOnce(&mut SsaFunctionContext<'_>),
{
let mut ctx = SsaFunctionContext { builder: &mut self };
f(&mut ctx);
self.build()
}
fn build(self) -> SsaFunction {
let mut func = SsaFunction::new(self.num_args, self.num_locals);
for var in self.variables {
func.add_variable(var);
}
for id in 0..=self.max_block_id {
if let Some(block) = self.blocks.get(&id) {
func.add_block(block.clone());
} else {
func.add_block(SsaBlock::new(id));
}
}
func
}
}
pub struct SsaFunctionContext<'a> {
builder: &'a mut SsaFunctionBuilder,
}
impl SsaFunctionContext<'_> {
#[must_use]
pub fn arg(&self, index: usize) -> SsaVarId {
self.builder.arg_vars[index]
}
#[must_use]
pub fn local(&self, index: usize) -> SsaVarId {
self.builder.local_vars[index]
}
#[must_use]
pub fn var(&mut self) -> SsaVarId {
self.builder.alloc_stack_var()
}
pub fn block<F>(&mut self, id: usize, f: F)
where
F: FnOnce(&mut SsaBlockBuilder<'_>),
{
if id > self.builder.max_block_id {
self.builder.max_block_id = id;
}
let mut block = SsaBlock::new(id);
let mut block_builder = SsaBlockBuilder {
builder: self.builder,
block: &mut block,
block_id: id,
};
f(&mut block_builder);
self.builder.blocks.insert(id, block);
}
}
pub struct SsaBlockBuilder<'a> {
builder: &'a mut SsaFunctionBuilder,
block: &'a mut SsaBlock,
block_id: usize,
}
impl SsaBlockBuilder<'_> {
fn add_op(&mut self, op: SsaOp) -> SsaVarId {
let dest = op.dest().unwrap_or_else(|| self.builder.alloc_stack_var());
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
fn add_op_void(&mut self, op: SsaOp) {
self.block.add_instruction(SsaInstruction::synthetic(op));
}
#[must_use]
pub fn const_i32(&mut self, value: i32) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Const {
dest,
value: ConstValue::I32(value),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn const_i64(&mut self, value: i64) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Const {
dest,
value: ConstValue::I64(value),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn const_f32(&mut self, value: f32) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Const {
dest,
value: ConstValue::F32(value),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn const_f64(&mut self, value: f64) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Const {
dest,
value: ConstValue::F64(value),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn const_null(&mut self) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Const {
dest,
value: ConstValue::Null,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn const_true(&mut self) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Const {
dest,
value: ConstValue::True,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn const_false(&mut self) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Const {
dest,
value: ConstValue::False,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn const_val(&mut self, value: ConstValue) -> SsaVarId {
let var_type = value.ssa_type();
let dest = self.builder.alloc_stack_var_typed(var_type);
let op = SsaOp::Const { dest, value };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn add(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Add { dest, left, right };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn sub(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Sub { dest, left, right };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn mul(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Mul { dest, left, right };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn div(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Div {
dest,
left,
right,
unsigned: false,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn div_un(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Div {
dest,
left,
right,
unsigned: true,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn rem(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Rem {
dest,
left,
right,
unsigned: false,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn rem_un(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Rem {
dest,
left,
right,
unsigned: true,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn and(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::And { dest, left, right };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn or(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Or { dest, left, right };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn xor(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Xor { dest, left, right };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn shl(&mut self, value: SsaVarId, amount: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Shl {
dest,
value,
amount,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn shr(&mut self, value: SsaVarId, amount: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Shr {
dest,
value,
amount,
unsigned: false,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn shr_un(&mut self, value: SsaVarId, amount: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Shr {
dest,
value,
amount,
unsigned: true,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn neg(&mut self, operand: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Neg { dest, operand };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn not(&mut self, operand: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Not { dest, operand };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn copy(&mut self, src: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Copy { dest, src };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn ceq(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Ceq { dest, left, right };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn clt(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Clt {
dest,
left,
right,
unsigned: false,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn clt_un(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Clt {
dest,
left,
right,
unsigned: true,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn cgt(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Cgt {
dest,
left,
right,
unsigned: false,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn cgt_un(&mut self, left: SsaVarId, right: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Cgt {
dest,
left,
right,
unsigned: true,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn conv(&mut self, operand: SsaVarId, target: SsaType) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Conv {
dest,
operand,
target,
overflow_check: false,
unsigned: false,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn conv_un(&mut self, operand: SsaVarId, target: SsaType) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Conv {
dest,
operand,
target,
overflow_check: false,
unsigned: true,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn conv_ovf(&mut self, operand: SsaVarId, target: SsaType) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Conv {
dest,
operand,
target,
overflow_check: true,
unsigned: false,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn conv_ovf_un(&mut self, operand: SsaVarId, target: SsaType) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Conv {
dest,
operand,
target,
overflow_check: true,
unsigned: true,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
pub fn jump(&mut self, target: usize) {
let op = SsaOp::Jump { target };
self.block.add_instruction(SsaInstruction::synthetic(op));
}
pub fn branch(&mut self, condition: SsaVarId, true_target: usize, false_target: usize) {
let op = SsaOp::Branch {
condition,
true_target,
false_target,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
}
pub fn switch(&mut self, value: SsaVarId, targets: Vec<usize>, default: usize) {
let op = SsaOp::Switch {
value,
targets,
default,
};
self.block.add_instruction(SsaInstruction::synthetic(op));
}
pub fn ret(&mut self) {
let op = SsaOp::Return { value: None };
self.block.add_instruction(SsaInstruction::synthetic(op));
}
pub fn ret_val(&mut self, value: SsaVarId) {
let op = SsaOp::Return { value: Some(value) };
self.block.add_instruction(SsaInstruction::synthetic(op));
}
pub fn leave(&mut self, target: usize) {
let op = SsaOp::Leave { target };
self.block.add_instruction(SsaInstruction::synthetic(op));
}
pub fn throw(&mut self, exception: SsaVarId) {
let op = SsaOp::Throw { exception };
self.block.add_instruction(SsaInstruction::synthetic(op));
}
#[must_use]
pub fn call(&mut self, method: MethodRef, args: &[SsaVarId]) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::Call {
dest: Some(dest),
method,
args: args.to_vec(),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
pub fn call_void(&mut self, method: MethodRef, args: &[SsaVarId]) {
let op = SsaOp::Call {
dest: None,
method,
args: args.to_vec(),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
}
#[must_use]
pub fn callvirt(&mut self, method: MethodRef, args: &[SsaVarId]) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::CallVirt {
dest: Some(dest),
method,
args: args.to_vec(),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
pub fn callvirt_void(&mut self, method: MethodRef, args: &[SsaVarId]) {
let op = SsaOp::CallVirt {
dest: None,
method,
args: args.to_vec(),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
}
#[must_use]
pub fn newobj(&mut self, ctor: MethodRef, args: &[SsaVarId]) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::NewObj {
dest,
ctor,
args: args.to_vec(),
};
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn array_length(&mut self, array: SsaVarId) -> SsaVarId {
let dest = self.builder.alloc_stack_var();
let op = SsaOp::ArrayLength { dest, array };
self.block.add_instruction(SsaInstruction::synthetic(op));
dest
}
#[must_use]
pub fn phi(&mut self, operands: &[(usize, SsaVarId)]) -> SsaVarId {
let result = self.builder.alloc_stack_var();
#[allow(clippy::cast_possible_truncation)]
let slot = result.index() as u32;
let mut phi = PhiNode::new(result, VariableOrigin::Stack(slot));
for &(pred, val) in operands {
phi.add_operand(PhiOperand::new(val, pred));
}
self.block.add_phi(phi);
result
}
pub fn op(&mut self, op: SsaOp) {
self.block.add_instruction(SsaInstruction::synthetic(op));
}
pub fn nop(&mut self) {
self.block
.add_instruction(SsaInstruction::synthetic(SsaOp::Nop));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_function() {
let ssa = SsaFunctionBuilder::new(2, 0).build_with(|f| {
let (a, b) = (f.arg(0), f.arg(1));
f.block(0, |blk| {
let sum = blk.add(a, b);
blk.ret_val(sum);
});
});
assert_eq!(ssa.num_args(), 2);
assert_eq!(ssa.num_locals(), 0);
assert_eq!(ssa.block_count(), 1);
let block = ssa.block(0).unwrap();
assert_eq!(block.instruction_count(), 2); }
#[test]
fn test_diamond_control_flow() {
let ssa = SsaFunctionBuilder::new(1, 0).build_with(|f| {
let cond = f.arg(0);
let (mut v_then, mut v_else) = (SsaVarId::new(), SsaVarId::new());
f.block(0, |b| {
b.branch(cond, 1, 2);
});
f.block(1, |b| {
v_then = b.const_i32(1);
b.jump(3);
});
f.block(2, |b| {
v_else = b.const_i32(0);
b.jump(3);
});
f.block(3, |b| {
let result = b.phi(&[(1, v_then), (2, v_else)]);
b.ret_val(result);
});
});
assert_eq!(ssa.block_count(), 4);
let block3 = ssa.block(3).unwrap();
assert_eq!(block3.phi_count(), 1);
}
#[test]
fn test_jump_threading_pattern() {
let ssa = SsaFunctionBuilder::new(1, 0).build_with(|f| {
let cond = f.arg(0);
f.block(0, |b| b.branch(cond, 1, 2));
f.block(1, |b| b.jump(3));
f.block(2, |b| b.jump(4));
f.block(3, |b| b.ret());
f.block(4, |b| b.ret());
});
assert_eq!(ssa.block_count(), 5);
let block0 = ssa.block(0).unwrap();
assert!(matches!(block0.terminator_op(), Some(SsaOp::Branch { .. })));
}
#[test]
fn test_constant_propagation_pattern() {
let ssa = SsaFunctionBuilder::new(0, 0).build_with(|f| {
f.block(0, |b| {
let v0 = b.const_i32(10);
let v1 = b.const_i32(32);
let sum = b.add(v0, v1);
b.ret_val(sum);
});
});
assert_eq!(ssa.block_count(), 1);
let block = ssa.block(0).unwrap();
assert_eq!(block.instruction_count(), 4); }
#[test]
fn test_switch_pattern() {
let ssa = SsaFunctionBuilder::new(1, 0).build_with(|f| {
let val = f.arg(0);
f.block(0, |b| b.switch(val, vec![1, 2], 3));
f.block(1, |b| b.jump(4));
f.block(2, |b| b.jump(4));
f.block(3, |b| b.jump(4));
f.block(4, |b| b.ret());
});
assert_eq!(ssa.block_count(), 5);
let block0 = ssa.block(0).unwrap();
assert!(matches!(block0.terminator_op(), Some(SsaOp::Switch { .. })));
}
#[test]
fn test_arithmetic_operations() {
let ssa = SsaFunctionBuilder::new(2, 0).build_with(|f| {
let (a, b) = (f.arg(0), f.arg(1));
f.block(0, |blk| {
let sum = blk.add(a, b);
let diff = blk.sub(a, b);
let prod = blk.mul(sum, diff);
let quot = blk.div(prod, a);
let rem = blk.rem(quot, b);
blk.ret_val(rem);
});
});
let block = ssa.block(0).unwrap();
assert_eq!(block.instruction_count(), 6); }
#[test]
fn test_bitwise_operations() {
let ssa = SsaFunctionBuilder::new(2, 0).build_with(|f| {
let (a, b) = (f.arg(0), f.arg(1));
f.block(0, |blk| {
let v_and = blk.and(a, b);
let _v_or = blk.or(a, b);
let _v_xor = blk.xor(a, b);
let _v_shl = blk.shl(a, b);
let _v_shr = blk.shr(a, b);
let v_not = blk.not(v_and);
blk.ret_val(v_not);
});
});
let block = ssa.block(0).unwrap();
assert_eq!(block.instruction_count(), 7);
}
#[test]
fn test_comparisons() {
let ssa = SsaFunctionBuilder::new(2, 0).build_with(|f| {
let (a, b) = (f.arg(0), f.arg(1));
f.block(0, |blk| {
let eq = blk.ceq(a, b);
let _lt = blk.clt(a, b);
let _gt = blk.cgt(a, b);
blk.ret_val(eq);
});
});
let block = ssa.block(0).unwrap();
assert_eq!(block.instruction_count(), 4);
}
#[test]
fn test_locals() {
let ssa = SsaFunctionBuilder::new(1, 2).build_with(|f| {
let arg = f.arg(0);
let _local0 = f.local(0);
let _local1 = f.local(1);
f.block(0, |blk| {
blk.ret_val(arg);
});
});
assert_eq!(ssa.num_locals(), 2);
}
}