use std::fmt;
use crate::decompiler::cfg::BlockId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SsaVariable {
pub base: String,
pub version: usize,
}
impl SsaVariable {
#[must_use]
pub const fn new(base: String, version: usize) -> Self {
Self { base, version }
}
#[must_use]
pub fn initial(base: String) -> Self {
Self::new(base, 0)
}
#[must_use]
pub fn next(&self) -> Self {
Self::new(self.base.clone(), self.version + 1)
}
#[must_use]
pub const fn is_initial(&self) -> bool {
self.version == 0
}
}
impl fmt::Display for SsaVariable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.base)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PhiNode {
pub target: SsaVariable,
pub operands: std::collections::BTreeMap<BlockId, SsaVariable>,
}
impl PhiNode {
#[must_use]
pub const fn new(target: SsaVariable) -> Self {
Self {
target,
operands: std::collections::BTreeMap::new(),
}
}
pub fn add_operand(&mut self, predecessor: BlockId, var: SsaVariable) {
self.operands.insert(predecessor, var);
}
#[must_use]
pub fn operand_count(&self) -> usize {
self.operands.len()
}
}
impl fmt::Display for PhiNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = φ(", self.target)?;
let mut first = true;
for (block, var) in &self.operands {
if !first {
write!(f, ", ")?;
}
write!(f, "{:?}: {}", block, var)?;
first = false;
}
write!(f, ")")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ssa_variable_versioning() {
let v0 = SsaVariable::initial("x".to_string());
assert_eq!(v0.base, "x");
assert_eq!(v0.version, 0);
assert!(v0.is_initial());
let v1 = v0.next();
assert_eq!(v1.base, "x");
assert_eq!(v1.version, 1);
assert!(!v1.is_initial());
let v2 = v1.next();
assert_eq!(v2.version, 2);
}
#[test]
fn test_ssa_variable_display_hides_version() {
let v0 = SsaVariable::initial("local_0".to_string());
let v1 = v0.next();
let v2 = v1.next();
assert_eq!(v0.to_string(), "local_0");
assert_eq!(v1.to_string(), "local_0");
assert_eq!(v2.to_string(), "local_0");
}
#[test]
fn test_ssa_variable_ord() {
let mut set = std::collections::BTreeSet::new();
set.insert(SsaVariable::new("z".to_string(), 1));
set.insert(SsaVariable::new("a".to_string(), 2));
set.insert(SsaVariable::new("m".to_string(), 0));
let vars: Vec<_> = set.iter().collect();
assert_eq!(vars[0].base, "a");
assert_eq!(vars[1].base, "m");
assert_eq!(vars[2].base, "z");
}
#[test]
fn test_phi_node_creation() {
let target = SsaVariable::initial("result".to_string());
let phi = PhiNode::new(target);
assert_eq!(phi.target.base, "result");
assert!(phi.operands.is_empty());
assert_eq!(phi.operand_count(), 0);
}
#[test]
fn test_phi_node_add_operands() {
let target = SsaVariable::initial("x".to_string());
let mut phi = PhiNode::new(target);
let block1 = BlockId(1);
let block2 = BlockId(2);
phi.add_operand(block1, SsaVariable::new("y".to_string(), 0));
phi.add_operand(block2, SsaVariable::new("z".to_string(), 0));
assert_eq!(phi.operand_count(), 2);
assert!(phi.operands.contains_key(&block1));
assert!(phi.operands.contains_key(&block2));
}
#[test]
fn test_phi_node_display() {
let target = SsaVariable::initial("result".to_string());
let mut phi = PhiNode::new(target);
let block1 = BlockId(0);
let block2 = BlockId(1);
phi.add_operand(block1, SsaVariable::new("x".to_string(), 0));
phi.add_operand(block2, SsaVariable::new("x".to_string(), 1));
let display = phi.to_string();
assert!(display.contains("result = φ("));
}
}