use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use crate::decompiler::cfg::{BlockId, Cfg};
use crate::decompiler::ir::{BinOp, Literal, Stmt, UnaryOp};
use super::dominance::DominanceInfo;
use super::variable::{PhiNode, SsaVariable};
#[derive(Debug, Clone)]
pub struct SsaForm {
pub cfg: Cfg,
pub dominance: DominanceInfo,
pub blocks: BTreeMap<BlockId, SsaBlock>,
pub definitions: BTreeMap<SsaVariable, BlockId>,
pub uses: BTreeMap<SsaVariable, BTreeSet<UseSite>>,
}
impl SsaForm {
#[must_use]
pub fn new(cfg: Cfg, dominance: DominanceInfo) -> Self {
Self {
cfg,
dominance,
blocks: BTreeMap::new(),
definitions: BTreeMap::new(),
uses: BTreeMap::new(),
}
}
pub fn add_block(&mut self, id: BlockId, block: SsaBlock) {
self.blocks.insert(id, block);
}
#[must_use]
pub fn block(&self, id: BlockId) -> Option<&SsaBlock> {
self.blocks.get(&id)
}
pub fn blocks_iter(&self) -> impl Iterator<Item = (&BlockId, &SsaBlock)> {
self.blocks.iter()
}
#[must_use]
pub fn block_count(&self) -> usize {
self.blocks.len()
}
pub fn add_definition(&mut self, var: SsaVariable, block: BlockId) {
self.definitions.insert(var, block);
}
pub fn add_use(&mut self, var: SsaVariable, site: UseSite) {
self.uses.entry(var).or_default().insert(site);
}
#[must_use]
pub fn uses_of(&self, var: &SsaVariable) -> Option<&BTreeSet<UseSite>> {
self.uses.get(var)
}
#[must_use]
pub fn render(&self) -> String {
let mut output = String::new();
use std::fmt::Write;
writeln!(output, "// SSA Form - {} blocks", self.block_count()).unwrap();
writeln!(output).unwrap();
for (block_id, block) in self.blocks_iter() {
writeln!(output, "block {:?}:", block_id).unwrap();
write!(output, "{}", block).unwrap();
}
output
}
#[must_use]
pub fn stats(&self) -> SsaStats {
let total_phi_nodes: usize = self.blocks.values().map(SsaBlock::phi_count).sum();
let total_statements: usize = self.blocks.values().map(SsaBlock::stmt_count).sum();
let total_variables = self.definitions.len();
SsaStats {
block_count: self.block_count(),
total_phi_nodes,
total_statements,
total_variables,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SsaStats {
pub block_count: usize,
pub total_phi_nodes: usize,
pub total_statements: usize,
pub total_variables: usize,
}
impl fmt::Display for SsaStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SSA Stats: {} blocks, {} φ nodes, {} statements, {} variables",
self.block_count, self.total_phi_nodes, self.total_statements, self.total_variables
)
}
}
#[derive(Debug, Clone, Default)]
pub struct SsaBlock {
pub phi_nodes: Vec<PhiNode>,
pub stmts: Vec<SsaStmt>,
}
impl SsaBlock {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_phi(&mut self, phi: PhiNode) {
self.phi_nodes.push(phi);
}
pub fn add_stmt(&mut self, stmt: SsaStmt) {
self.stmts.push(stmt);
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.phi_nodes.is_empty() && self.stmts.is_empty()
}
#[must_use]
pub fn phi_count(&self) -> usize {
self.phi_nodes.len()
}
#[must_use]
pub fn stmt_count(&self) -> usize {
self.stmts.len()
}
}
impl fmt::Display for SsaBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for phi in &self.phi_nodes {
writeln!(f, " {}", phi)?;
}
for stmt in &self.stmts {
writeln!(f, " {}", stmt)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SsaStmt {
Assign {
target: SsaVariable,
value: SsaExpr,
},
Phi(PhiNode),
Other(Stmt),
}
impl SsaStmt {
#[must_use]
pub fn assign(target: SsaVariable, value: SsaExpr) -> Self {
Self::Assign { target, value }
}
#[must_use]
pub const fn phi(phi: PhiNode) -> Self {
Self::Phi(phi)
}
#[must_use]
pub const fn other(stmt: Stmt) -> Self {
Self::Other(stmt)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SsaExpr {
Variable(SsaVariable),
Literal(Literal),
Binary {
op: BinOp,
left: Box<SsaExpr>,
right: Box<SsaExpr>,
},
Unary {
op: UnaryOp,
operand: Box<SsaExpr>,
},
Call {
name: String,
args: Vec<SsaExpr>,
},
Index {
base: Box<SsaExpr>,
index: Box<SsaExpr>,
},
Member {
base: Box<SsaExpr>,
name: String,
},
Cast {
expr: Box<SsaExpr>,
target_type: String,
},
Array(Vec<SsaExpr>),
Map(Vec<(SsaExpr, SsaExpr)>),
Ternary {
condition: Box<SsaExpr>,
then_expr: Box<SsaExpr>,
else_expr: Box<SsaExpr>,
},
}
impl SsaExpr {
#[must_use]
pub fn var(var: SsaVariable) -> Self {
Self::Variable(var)
}
#[must_use]
pub const fn lit(literal: Literal) -> Self {
Self::Literal(literal)
}
#[must_use]
pub fn binary(op: BinOp, left: SsaExpr, right: SsaExpr) -> Self {
Self::Binary {
op,
left: Box::new(left),
right: Box::new(right),
}
}
#[must_use]
pub fn unary(op: UnaryOp, operand: SsaExpr) -> Self {
Self::Unary {
op,
operand: Box::new(operand),
}
}
#[must_use]
pub fn call(name: String, args: Vec<SsaExpr>) -> Self {
Self::Call { name, args }
}
}
impl fmt::Display for SsaExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Variable(var) => write!(f, "{}", var),
Self::Literal(lit) => write!(f, "{}", lit),
Self::Binary { op, left, right } => write!(f, "({} {} {})", left, op, right),
Self::Unary { op, operand } => write!(f, "{}({})", op, operand),
Self::Call { name, args } => {
write!(f, "{}(", name)?;
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", arg)?;
}
write!(f, ")")
}
Self::Index { base, index } => write!(f, "{}[{}]", base, index),
Self::Member { base, name } => write!(f, "{}.{}", base, name),
Self::Cast { expr, target_type } => write!(f, "{} as {}", expr, target_type),
Self::Array(elements) => {
write!(f, "[")?;
for (i, elem) in elements.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", elem)?;
}
write!(f, "]")
}
Self::Map(pairs) => {
write!(f, "{{")?;
for (i, (key, value)) in pairs.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}: {}", key, value)?;
}
write!(f, "}}")
}
Self::Ternary {
condition,
then_expr,
else_expr,
} => write!(f, "{} ? {} : {}", condition, then_expr, else_expr),
}
}
}
impl fmt::Display for SsaStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Assign { target, value } => write!(f, "{} = {};", target, value),
Self::Phi(phi) => write!(f, "{}", phi), Self::Other(stmt) => write!(f, "{:?}", stmt), }
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UseSite {
pub block: BlockId,
pub stmt_index: usize,
}
impl UseSite {
#[must_use]
pub const fn new(block: BlockId, stmt_index: usize) -> Self {
Self { block, stmt_index }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ssa_form_creation() {
let cfg = Cfg::new();
let dominance = DominanceInfo::new();
let ssa = SsaForm::new(cfg, dominance);
assert_eq!(ssa.block_count(), 0);
assert!(ssa.definitions.is_empty());
assert!(ssa.uses.is_empty());
}
#[test]
fn test_ssa_block_additions() {
let mut block = SsaBlock::new();
let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
block.add_phi(phi);
let stmt = SsaStmt::assign(
SsaVariable::new("y".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
);
block.add_stmt(stmt);
assert_eq!(block.phi_count(), 1);
assert_eq!(block.stmt_count(), 1);
assert!(!block.is_empty());
}
#[test]
fn test_dominance_info_empty() {
let info = DominanceInfo::new();
assert!(info.idom(BlockId(0)).is_none());
assert!(info.children(BlockId(0)).is_empty());
assert!(info.dominance_frontier_vec(BlockId(0)).is_empty());
}
#[test]
fn test_ssa_expr_constructors() {
let var = SsaVariable::initial("x".to_string());
let expr = SsaExpr::var(var);
assert!(matches!(expr, SsaExpr::Variable(_)));
let lit = SsaExpr::lit(Literal::Int(42));
assert!(matches!(lit, SsaExpr::Literal(_)));
let binary = SsaExpr::binary(
BinOp::Add,
SsaExpr::lit(Literal::Int(1)),
SsaExpr::lit(Literal::Int(2)),
);
assert!(matches!(binary, SsaExpr::Binary { .. }));
let call = SsaExpr::call("foo".to_string(), vec![]);
assert!(matches!(call, SsaExpr::Call { .. }));
}
#[test]
fn test_use_site() {
let site = UseSite::new(BlockId(5), 10);
assert_eq!(site.block, BlockId(5));
assert_eq!(site.stmt_index, 10);
}
#[test]
fn test_ssa_expr_display() {
let var = SsaVariable::initial("x".to_string());
assert_eq!(format!("{}", SsaExpr::var(var.clone())), "x");
let lit = SsaExpr::lit(Literal::Int(42));
assert_eq!(format!("{}", lit), "42");
let binary = SsaExpr::binary(
BinOp::Add,
SsaExpr::var(var.clone()),
SsaExpr::lit(Literal::Int(10)),
);
assert_eq!(format!("{}", binary), "(x + 10)");
let unary = SsaExpr::unary(UnaryOp::Neg, SsaExpr::var(var));
assert_eq!(format!("{}", unary), "-(x)");
let var2 = SsaVariable::initial("x".to_string());
let call = SsaExpr::call("foo".to_string(), vec![SsaExpr::var(var2)]);
assert_eq!(format!("{}", call), "foo(x)");
}
#[test]
fn test_ssa_stmt_display() {
let var = SsaVariable::new("result".to_string(), 0);
let stmt = SsaStmt::assign(var, SsaExpr::lit(Literal::Int(42)));
assert_eq!(format!("{}", stmt), "result = 42;");
}
#[test]
fn test_ssa_block_display() {
let mut block = SsaBlock::new();
let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
block.add_phi(phi);
let stmt = SsaStmt::assign(
SsaVariable::new("y".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
);
block.add_stmt(stmt);
let display = format!("{}", block);
assert!(display.contains("φ")); assert!(display.contains("y = 42;")); }
#[test]
fn test_ssa_form_render() {
let cfg = Cfg::new();
let dominance = DominanceInfo::new();
let mut ssa = SsaForm::new(cfg, dominance);
let mut block = SsaBlock::new();
block.add_stmt(SsaStmt::assign(
SsaVariable::new("x".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
));
ssa.add_block(BlockId::ENTRY, block);
let rendered = ssa.render();
assert!(rendered.contains("SSA Form"));
assert!(rendered.contains("block"));
assert!(rendered.contains("x = 42;"));
}
#[test]
fn test_ssa_stats() {
let cfg = Cfg::new();
let dominance = DominanceInfo::new();
let mut ssa = SsaForm::new(cfg, dominance);
let mut block = SsaBlock::new();
let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
block.add_phi(phi);
let stmt = SsaStmt::assign(
SsaVariable::new("y".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
);
block.add_stmt(stmt);
ssa.add_block(BlockId::ENTRY, block);
ssa.add_definition(SsaVariable::initial("y".to_string()), BlockId::ENTRY);
let stats = ssa.stats();
assert_eq!(stats.block_count, 1);
assert_eq!(stats.total_phi_nodes, 1);
assert_eq!(stats.total_statements, 1);
assert_eq!(stats.total_variables, 1);
}
#[test]
fn test_ssa_expr_complex() {
let arr = SsaExpr::Array(vec![
SsaExpr::lit(Literal::Int(1)),
SsaExpr::lit(Literal::Int(2)),
]);
assert_eq!(format!("{}", arr), "[1, 2]");
let map = SsaExpr::Map(vec![(
SsaExpr::lit(Literal::String("key".to_string())),
SsaExpr::lit(Literal::Int(1)),
)]);
assert!(format!("{}", map).contains("{"));
let ternary = SsaExpr::Ternary {
condition: Box::new(SsaExpr::lit(Literal::Bool(true))),
then_expr: Box::new(SsaExpr::lit(Literal::Int(1))),
else_expr: Box::new(SsaExpr::lit(Literal::Int(2))),
};
assert_eq!(format!("{}", ternary), "true ? 1 : 2");
}
#[test]
fn phi_placement_diamond_cfg() {
use super::super::dominance;
use crate::decompiler::cfg::{BasicBlock, EdgeKind, Terminator};
let mut cfg = Cfg::new();
let entry = BasicBlock::new(
BlockId(0),
0,
1,
0..1,
Terminator::Branch {
then_target: BlockId(1),
else_target: BlockId(2),
},
);
cfg.add_block(entry);
let left = BasicBlock::new(
BlockId(1),
1,
2,
1..2,
Terminator::Jump { target: BlockId(3) },
);
cfg.add_block(left);
cfg.add_edge(BlockId(0), BlockId(1), EdgeKind::ConditionalTrue);
let right = BasicBlock::new(
BlockId(2),
2,
3,
2..3,
Terminator::Jump { target: BlockId(3) },
);
cfg.add_block(right);
cfg.add_edge(BlockId(0), BlockId(2), EdgeKind::ConditionalFalse);
let exit = BasicBlock::new(BlockId(3), 3, 4, 3..4, Terminator::Return);
cfg.add_block(exit);
cfg.add_edge(BlockId(1), BlockId(3), EdgeKind::Unconditional);
cfg.add_edge(BlockId(2), BlockId(3), EdgeKind::Unconditional);
let dom = dominance::compute(&cfg);
assert_eq!(dom.dominance_frontier_vec(BlockId(1)), vec![BlockId(3)],);
assert_eq!(dom.dominance_frontier_vec(BlockId(2)), vec![BlockId(3)],);
let mut ssa = SsaForm::new(cfg, dom);
ssa.add_block(BlockId(0), SsaBlock::new());
let mut bb1 = SsaBlock::new();
let x0 = SsaVariable::new("x".to_string(), 0);
bb1.add_stmt(SsaStmt::assign(x0.clone(), SsaExpr::lit(Literal::Int(1))));
ssa.add_block(BlockId(1), bb1);
ssa.add_definition(x0, BlockId(1));
let mut bb2 = SsaBlock::new();
let x1 = SsaVariable::new("x".to_string(), 1);
bb2.add_stmt(SsaStmt::assign(x1.clone(), SsaExpr::lit(Literal::Int(2))));
ssa.add_block(BlockId(2), bb2);
ssa.add_definition(x1, BlockId(2));
let mut bb3 = SsaBlock::new();
let mut phi = PhiNode::new(SsaVariable::new("x".to_string(), 2));
phi.operands
.insert(BlockId(1), SsaVariable::new("x".to_string(), 0));
phi.operands
.insert(BlockId(2), SsaVariable::new("x".to_string(), 1));
bb3.add_phi(phi);
ssa.add_block(BlockId(3), bb3);
let merge_block = ssa.block(BlockId(3)).expect("BB3 must exist");
assert_eq!(merge_block.phi_count(), 1, "BB3 should have 1 phi node");
assert_eq!(
merge_block.phi_nodes[0].target.base, "x",
"phi target should be variable x"
);
assert_eq!(
merge_block.phi_nodes[0].operands.len(),
2,
"phi should have 2 operands (one per predecessor)"
);
assert_eq!(
ssa.block(BlockId(0)).unwrap().phi_count(),
0,
"entry should have no phi nodes"
);
assert_eq!(
ssa.block(BlockId(1)).unwrap().phi_count(),
0,
"BB1 should have no phi nodes"
);
assert_eq!(
ssa.block(BlockId(2)).unwrap().phi_count(),
0,
"BB2 should have no phi nodes"
);
}
}