use std::fmt;
use crate::analysis::ssa::{SsaVarId, VariableOrigin};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PhiOperand {
value: SsaVarId,
predecessor: usize,
}
impl PhiOperand {
#[must_use]
pub const fn new(value: SsaVarId, predecessor: usize) -> Self {
Self { value, predecessor }
}
#[must_use]
pub const fn value(&self) -> SsaVarId {
self.value
}
#[must_use]
pub const fn predecessor(&self) -> usize {
self.predecessor
}
}
impl fmt::Display for PhiOperand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} from B{}", self.value, self.predecessor)
}
}
#[derive(Debug, Clone)]
pub struct PhiNode {
result: SsaVarId,
origin: VariableOrigin,
operands: Vec<PhiOperand>,
}
impl PhiNode {
#[must_use]
pub fn new(result: SsaVarId, origin: VariableOrigin) -> Self {
Self {
result,
origin,
operands: Vec::new(),
}
}
#[must_use]
pub fn with_capacity(
result: SsaVarId,
origin: VariableOrigin,
predecessor_count: usize,
) -> Self {
Self {
result,
origin,
operands: Vec::with_capacity(predecessor_count),
}
}
#[must_use]
pub const fn result(&self) -> SsaVarId {
self.result
}
#[must_use]
pub const fn origin(&self) -> VariableOrigin {
self.origin
}
pub fn set_result(&mut self, var: SsaVarId) {
self.result = var;
}
#[must_use]
pub fn operands(&self) -> &[PhiOperand] {
&self.operands
}
pub fn operands_mut(&mut self) -> &mut Vec<PhiOperand> {
&mut self.operands
}
pub fn add_operand(&mut self, operand: PhiOperand) {
self.operands.push(operand);
}
#[must_use]
pub fn operand_count(&self) -> usize {
self.operands.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.operands.is_empty()
}
#[must_use]
pub fn operand_from(&self, predecessor: usize) -> Option<&PhiOperand> {
self.operands
.iter()
.find(|op| op.predecessor == predecessor)
}
pub fn used_variables(&self) -> impl Iterator<Item = SsaVarId> + '_ {
self.operands.iter().map(|op| op.value)
}
pub fn set_origin(&mut self, origin: VariableOrigin) {
self.origin = origin;
}
pub fn set_operand(&mut self, predecessor: usize, value: SsaVarId) {
if let Some(existing) = self
.operands
.iter_mut()
.find(|op| op.predecessor == predecessor)
{
existing.value = value;
} else {
self.operands.push(PhiOperand::new(value, predecessor));
}
}
}
impl fmt::Display for PhiNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = phi(", self.result)?;
for (i, operand) in self.operands.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{operand}")?;
}
write!(f, ")")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phi_operand_creation() {
let v = SsaVarId::new();
let operand = PhiOperand::new(v, 2);
assert_eq!(operand.value(), v);
assert_eq!(operand.predecessor(), 2);
}
#[test]
fn test_phi_operand_display() {
let operand = PhiOperand::new(SsaVarId::from_index(3), 1);
assert_eq!(format!("{operand}"), "v3 from B1");
}
#[test]
fn test_phi_node_creation() {
let result = SsaVarId::new();
let phi = PhiNode::new(result, VariableOrigin::Local(0));
assert_eq!(phi.result(), result);
assert_eq!(phi.origin(), VariableOrigin::Local(0));
assert!(phi.is_empty());
assert_eq!(phi.operand_count(), 0);
}
#[test]
fn test_phi_node_with_capacity() {
let result = SsaVarId::new();
let phi = PhiNode::with_capacity(result, VariableOrigin::Argument(1), 3);
assert_eq!(phi.result(), result);
assert_eq!(phi.origin(), VariableOrigin::Argument(1));
assert!(phi.is_empty());
}
#[test]
fn test_phi_node_add_operands() {
let result = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v1, 0));
phi.add_operand(PhiOperand::new(v2, 1));
assert!(!phi.is_empty());
assert_eq!(phi.operand_count(), 2);
let ops = phi.operands();
assert_eq!(ops[0].value(), v1);
assert_eq!(ops[0].predecessor(), 0);
assert_eq!(ops[1].value(), v2);
assert_eq!(ops[1].predecessor(), 1);
}
#[test]
fn test_phi_node_operand_from() {
let result = SsaVarId::new();
let v1 = SsaVarId::new();
let v3 = SsaVarId::new();
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v1, 2));
phi.add_operand(PhiOperand::new(v3, 4));
assert!(phi.operand_from(2).is_some());
assert_eq!(phi.operand_from(2).unwrap().value(), v1);
assert!(phi.operand_from(4).is_some());
assert_eq!(phi.operand_from(4).unwrap().value(), v3);
assert!(phi.operand_from(0).is_none());
assert!(phi.operand_from(99).is_none());
}
#[test]
fn test_phi_node_set_operand_new() {
let result = SsaVarId::new();
let v10 = SsaVarId::new();
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.set_operand(1, v10);
assert_eq!(phi.operand_count(), 1);
assert_eq!(phi.operand_from(1).unwrap().value(), v10);
}
#[test]
fn test_phi_node_set_operand_update() {
let result = SsaVarId::new();
let v10 = SsaVarId::new();
let v20 = SsaVarId::new();
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.set_operand(1, v10);
phi.set_operand(1, v20);
assert_eq!(phi.operand_count(), 1);
assert_eq!(phi.operand_from(1).unwrap().value(), v20);
}
#[test]
fn test_phi_node_used_variables() {
let result = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let v3 = SsaVarId::new();
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v1, 0));
phi.add_operand(PhiOperand::new(v2, 1));
phi.add_operand(PhiOperand::new(v3, 2));
let used: Vec<_> = phi.used_variables().collect();
assert_eq!(used.len(), 3);
assert!(used.contains(&v1));
assert!(used.contains(&v2));
assert!(used.contains(&v3));
}
#[test]
fn test_phi_node_display() {
let mut phi = PhiNode::new(SsaVarId::from_index(5), VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(SsaVarId::from_index(1), 0));
phi.add_operand(PhiOperand::new(SsaVarId::from_index(2), 1));
let display = format!("{phi}");
assert_eq!(display, "v5 = phi(v1 from B0, v2 from B1)");
}
#[test]
fn test_phi_node_display_empty() {
let phi = PhiNode::new(SsaVarId::from_index(3), VariableOrigin::Local(0));
assert_eq!(format!("{phi}"), "v3 = phi()");
}
#[test]
fn test_phi_node_display_single_operand() {
let mut phi = PhiNode::new(SsaVarId::from_index(7), VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(SsaVarId::from_index(4), 2));
assert_eq!(format!("{phi}"), "v7 = phi(v4 from B2)");
}
}