use std::{
fmt,
sync::atomic::{AtomicUsize, Ordering},
};
use crate::analysis::ssa::SsaType;
static NEXT_SSA_VAR_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SsaVarId(usize);
impl SsaVarId {
#[must_use]
pub fn new() -> Self {
Self(NEXT_SSA_VAR_ID.fetch_add(1, Ordering::Relaxed))
}
#[must_use]
pub const fn from_index(index: usize) -> Self {
Self(index)
}
#[must_use]
pub const fn index(self) -> usize {
self.0
}
}
impl fmt::Debug for SsaVarId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.0)
}
}
impl fmt::Display for SsaVarId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum VariableOrigin {
Argument(u16),
Local(u16),
Stack(u32),
Phi,
}
impl VariableOrigin {
#[must_use]
pub const fn is_argument(&self) -> bool {
matches!(self, Self::Argument(_))
}
#[must_use]
pub const fn is_local(&self) -> bool {
matches!(self, Self::Local(_))
}
#[must_use]
pub const fn is_stack(&self) -> bool {
matches!(self, Self::Stack(_))
}
#[must_use]
pub const fn is_phi(&self) -> bool {
matches!(self, Self::Phi)
}
#[must_use]
pub const fn argument_index(&self) -> Option<u16> {
match self {
Self::Argument(idx) => Some(*idx),
_ => None,
}
}
#[must_use]
pub const fn local_index(&self) -> Option<u16> {
match self {
Self::Local(idx) => Some(*idx),
_ => None,
}
}
}
impl fmt::Display for VariableOrigin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Argument(idx) => write!(f, "arg{idx}"),
Self::Local(idx) => write!(f, "loc{idx}"),
Self::Stack(slot) => write!(f, "stk{slot}"),
Self::Phi => write!(f, "phi"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DefSite {
pub block: usize,
pub instruction: Option<usize>,
}
impl DefSite {
#[must_use]
pub const fn instruction(block: usize, instr_idx: usize) -> Self {
Self {
block,
instruction: Some(instr_idx),
}
}
#[must_use]
pub const fn phi(block: usize) -> Self {
Self {
block,
instruction: None,
}
}
#[must_use]
pub const fn entry() -> Self {
Self {
block: 0,
instruction: None,
}
}
#[must_use]
pub const fn is_phi(&self) -> bool {
self.instruction.is_none()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UseSite {
pub block: usize,
pub instruction: usize,
pub is_phi_operand: bool,
}
impl UseSite {
#[must_use]
pub const fn instruction(block: usize, instr_idx: usize) -> Self {
Self {
block,
instruction: instr_idx,
is_phi_operand: false,
}
}
#[must_use]
pub const fn phi_operand(block: usize, phi_idx: usize) -> Self {
Self {
block,
instruction: phi_idx,
is_phi_operand: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SsaVariable {
id: SsaVarId,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
var_type: SsaType,
uses: Vec<UseSite>,
address_taken: bool,
}
impl SsaVariable {
#[must_use]
pub fn new(origin: VariableOrigin, version: u32, def_site: DefSite) -> Self {
Self {
id: SsaVarId::new(),
origin,
version,
def_site,
var_type: SsaType::Unknown,
uses: Vec::new(),
address_taken: false,
}
}
#[must_use]
pub fn new_with_id(
id: SsaVarId,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
) -> Self {
Self {
id,
origin,
version,
def_site,
var_type: SsaType::Unknown,
uses: Vec::new(),
address_taken: false,
}
}
#[must_use]
pub fn new_with_id_typed(
id: SsaVarId,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
var_type: SsaType,
) -> Self {
Self {
id,
origin,
version,
def_site,
var_type,
uses: Vec::new(),
address_taken: false,
}
}
#[must_use]
pub fn new_typed(
origin: VariableOrigin,
version: u32,
def_site: DefSite,
var_type: SsaType,
) -> Self {
Self {
id: SsaVarId::new(),
origin,
version,
def_site,
var_type,
uses: Vec::new(),
address_taken: false,
}
}
#[must_use]
pub const fn id(&self) -> SsaVarId {
self.id
}
#[must_use]
pub const fn origin(&self) -> VariableOrigin {
self.origin
}
#[must_use]
pub const fn version(&self) -> u32 {
self.version
}
#[must_use]
pub const fn def_site(&self) -> DefSite {
self.def_site
}
#[must_use]
pub fn var_type(&self) -> &SsaType {
&self.var_type
}
pub fn set_type(&mut self, var_type: SsaType) {
self.var_type = var_type;
}
#[must_use]
pub fn has_known_type(&self) -> bool {
!matches!(self.var_type, SsaType::Unknown)
}
#[must_use]
pub fn uses(&self) -> &[UseSite] {
&self.uses
}
#[must_use]
pub const fn is_address_taken(&self) -> bool {
self.address_taken
}
#[must_use]
pub fn is_dead(&self) -> bool {
self.uses.is_empty()
}
#[must_use]
pub fn use_count(&self) -> usize {
self.uses.len()
}
pub fn add_use(&mut self, use_site: UseSite) {
self.uses.push(use_site);
}
pub fn clear_uses(&mut self) {
self.uses.clear();
}
pub fn set_address_taken(&mut self) {
self.address_taken = true;
}
pub fn set_origin(&mut self, origin: VariableOrigin) {
self.origin = origin;
}
}
impl fmt::Display for SsaVariable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}_{}", self.origin, self.version)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ssa_var_id_creation() {
let id = SsaVarId::from_index(42);
assert_eq!(id.index(), 42);
}
#[test]
fn test_ssa_var_id_display() {
let id = SsaVarId::new();
let expected = format!("v{}", id.index());
assert_eq!(format!("{id}"), expected);
assert_eq!(format!("{id:?}"), expected);
}
#[test]
fn test_ssa_var_id_equality() {
let id1 = SsaVarId::from_index(10);
let id2 = SsaVarId::from_index(10);
let id3 = SsaVarId::from_index(20);
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_variable_origin_argument() {
let origin = VariableOrigin::Argument(0);
assert!(origin.is_argument());
assert!(!origin.is_local());
assert!(!origin.is_stack());
assert!(!origin.is_phi());
assert_eq!(origin.argument_index(), Some(0));
assert_eq!(origin.local_index(), None);
assert_eq!(format!("{origin}"), "arg0");
}
#[test]
fn test_variable_origin_local() {
let origin = VariableOrigin::Local(3);
assert!(!origin.is_argument());
assert!(origin.is_local());
assert!(!origin.is_stack());
assert!(!origin.is_phi());
assert_eq!(origin.argument_index(), None);
assert_eq!(origin.local_index(), Some(3));
assert_eq!(format!("{origin}"), "loc3");
}
#[test]
fn test_variable_origin_stack() {
let origin = VariableOrigin::Stack(7);
assert!(!origin.is_argument());
assert!(!origin.is_local());
assert!(origin.is_stack());
assert!(!origin.is_phi());
assert_eq!(format!("{origin}"), "stk7");
}
#[test]
fn test_variable_origin_phi() {
let origin = VariableOrigin::Phi;
assert!(!origin.is_argument());
assert!(!origin.is_local());
assert!(!origin.is_stack());
assert!(origin.is_phi());
assert_eq!(format!("{origin}"), "phi");
}
#[test]
fn test_def_site_instruction() {
let site = DefSite::instruction(2, 5);
assert_eq!(site.block, 2);
assert_eq!(site.instruction, Some(5));
assert!(!site.is_phi());
}
#[test]
fn test_def_site_phi() {
let site = DefSite::phi(3);
assert_eq!(site.block, 3);
assert_eq!(site.instruction, None);
assert!(site.is_phi());
}
#[test]
fn test_use_site_instruction() {
let site = UseSite::instruction(1, 4);
assert_eq!(site.block, 1);
assert_eq!(site.instruction, 4);
assert!(!site.is_phi_operand);
}
#[test]
fn test_use_site_phi_operand() {
let site = UseSite::phi_operand(2, 0);
assert_eq!(site.block, 2);
assert_eq!(site.instruction, 0);
assert!(site.is_phi_operand);
}
#[test]
fn test_ssa_variable_creation() {
let var = SsaVariable::new(VariableOrigin::Argument(0), 0, DefSite::phi(0));
assert_eq!(var.origin(), VariableOrigin::Argument(0));
assert_eq!(var.version(), 0);
assert!(var.def_site().is_phi());
assert!(var.uses().is_empty());
assert!(!var.is_address_taken());
assert!(var.is_dead());
}
#[test]
fn test_ssa_variable_add_use() {
let mut var = SsaVariable::new(VariableOrigin::Local(0), 1, DefSite::instruction(0, 0));
assert!(var.is_dead());
var.add_use(UseSite::instruction(0, 5));
var.add_use(UseSite::instruction(1, 2));
assert!(!var.is_dead());
assert_eq!(var.uses().len(), 2);
}
#[test]
fn test_ssa_variable_address_taken() {
let mut var = SsaVariable::new(VariableOrigin::Local(1), 0, DefSite::phi(0));
assert!(!var.is_address_taken());
var.set_address_taken();
assert!(var.is_address_taken());
}
#[test]
fn test_ssa_variable_display() {
let var = SsaVariable::new(VariableOrigin::Argument(2), 3, DefSite::phi(0));
assert_eq!(format!("{var}"), "arg2_3");
let var2 = SsaVariable::new(VariableOrigin::Local(0), 1, DefSite::instruction(1, 2));
assert_eq!(format!("{var2}"), "loc0_1");
}
}