use std::fmt;
use crate::analysis::ssa::SsaType;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SsaVarId(usize);
impl SsaVarId {
pub const PLACEHOLDER: Self = Self(usize::MAX);
#[must_use]
pub const fn is_placeholder(self) -> bool {
self.0 == usize::MAX
}
#[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),
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_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::Phi => write!(f, "phi"),
}
}
}
#[derive(Debug, Clone)]
pub struct FunctionVarAllocator {
next_id: usize,
}
impl FunctionVarAllocator {
#[must_use]
pub fn new() -> Self {
Self { next_id: 0 }
}
#[must_use]
pub fn starting_from(start_id: usize) -> Self {
Self { next_id: start_id }
}
pub fn alloc(&mut self) -> SsaVarId {
let id = SsaVarId::from_index(self.next_id);
self.next_id += 1;
id
}
#[must_use]
pub fn count(&self) -> usize {
self.next_id
}
}
impl Default for FunctionVarAllocator {
fn default() -> Self {
Self::new()
}
}
#[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(crate) fn new(
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 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_def_site(&mut self, site: DefSite) {
self.def_site = site;
}
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;
}
pub fn set_id(&mut self, id: SsaVarId) {
self.id = id;
}
}
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::*;
use crate::analysis::ssa::SsaType;
#[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::from_index(0);
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_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_phi());
assert_eq!(origin.argument_index(), None);
assert_eq!(origin.local_index(), Some(3));
assert_eq!(format!("{origin}"), "loc3");
}
#[test]
fn test_variable_origin_phi() {
let origin = VariableOrigin::Phi;
assert!(!origin.is_argument());
assert!(!origin.is_local());
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(
SsaVarId::from_index(0),
VariableOrigin::Argument(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
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(
SsaVarId::from_index(0),
VariableOrigin::Local(0),
1,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
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(
SsaVarId::from_index(0),
VariableOrigin::Local(1),
0,
DefSite::phi(0),
SsaType::Unknown,
);
assert!(!var.is_address_taken());
var.set_address_taken();
assert!(var.is_address_taken());
}
#[test]
fn test_ssa_variable_display() {
let var = SsaVariable::new(
SsaVarId::from_index(0),
VariableOrigin::Argument(2),
3,
DefSite::phi(0),
SsaType::Unknown,
);
assert_eq!(format!("{var}"), "arg2_3");
let var2 = SsaVariable::new(
SsaVarId::from_index(1),
VariableOrigin::Local(0),
1,
DefSite::instruction(1, 2),
SsaType::Unknown,
);
assert_eq!(format!("{var2}"), "loc0_1");
}
}