use std::{cmp::Ordering, fmt, num::NonZeroU32};
use crate::target::Target;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SsaVarId(NonZeroU32);
impl SsaVarId {
pub const PLACEHOLDER: Self = Self(NonZeroU32::MIN);
#[must_use]
pub const fn is_placeholder(self) -> bool {
self.0.get() == NonZeroU32::MIN.get()
}
#[must_use]
pub const fn from_index(index: usize) -> Self {
let idx = index as u32;
match NonZeroU32::new(!idx) {
Some(stored) => Self(stored),
None => Self::PLACEHOLDER,
}
}
#[must_use]
pub const fn index(self) -> usize {
(!self.0.get()) as usize
}
#[must_use]
pub const fn as_u32(self) -> u32 {
!self.0.get()
}
}
impl Default for SsaVarId {
fn default() -> Self {
Self::from_index(0)
}
}
impl PartialOrd for SsaVarId {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SsaVarId {
fn cmp(&self, other: &Self) -> Ordering {
self.index().cmp(&other.index())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SsaVarId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u32(self.as_u32())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SsaVarId {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let index = <u32 as serde::Deserialize>::deserialize(deserializer)?;
Ok(Self::from_index(index as usize))
}
}
impl fmt::Debug for SsaVarId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.index())
}
}
impl fmt::Display for SsaVarId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.index())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum VariableOrigin {
Argument(u16),
Local(u16),
Phi,
EntryLiveIn,
}
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 is_entry_live_in(&self) -> bool {
matches!(self, Self::EntryLiveIn)
}
#[must_use]
pub const fn is_caller_supplied(&self) -> bool {
matches!(self, Self::Argument(_) | Self::EntryLiveIn)
}
#[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"),
Self::EntryLiveIn => write!(f, "livein"),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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 = self.next_id.saturating_add(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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(bound(
serialize = "T::Type: serde::Serialize",
deserialize = "T::Type: serde::Deserialize<'de>"
))
)]
pub struct SsaVariable<T: Target> {
id: SsaVarId,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
var_type: T::Type,
uses: Vec<UseSite>,
address_taken: bool,
}
impl<T: Target> Clone for SsaVariable<T> {
fn clone(&self) -> Self {
let Self {
id,
origin,
version,
def_site,
var_type,
uses,
address_taken,
} = self;
Self {
id: *id,
origin: *origin,
version: *version,
def_site: *def_site,
var_type: var_type.clone(),
uses: uses.clone(),
address_taken: *address_taken,
}
}
fn clone_from(&mut self, source: &Self) {
let Self {
id,
origin,
version,
def_site,
var_type,
uses,
address_taken,
} = source;
self.id = *id;
self.origin = *origin;
self.version = *version;
self.def_site = *def_site;
self.var_type.clone_from(var_type);
self.uses.clone_from(uses);
self.address_taken = *address_taken;
}
}
impl<T: Target> SsaVariable<T> {
#[must_use]
pub fn new(
id: SsaVarId,
origin: VariableOrigin,
version: u32,
def_site: DefSite,
var_type: T::Type,
) -> 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) -> &T::Type {
&self.var_type
}
pub fn set_def_site(&mut self, site: DefSite) {
self.def_site = site;
}
pub fn set_type(&mut self, var_type: T::Type) {
self.var_type = var_type;
}
#[must_use]
pub fn has_known_type(&self) -> bool {
!T::is_unknown(&self.var_type)
}
#[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 retain_uses<F>(&mut self, keep: F)
where
F: FnMut(&UseSite) -> bool,
{
self.uses.retain(keep);
}
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<T: Target> fmt::Display for SsaVariable<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}_{}", self.origin, self.version)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{MockTarget, MockType};
type SsaVariable = super::SsaVariable<MockTarget>;
#[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),
MockType::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),
MockType::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),
MockType::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),
MockType::Unknown,
);
assert_eq!(format!("{var}"), "arg2_3");
let var2 = SsaVariable::new(
SsaVarId::from_index(1),
VariableOrigin::Local(0),
1,
DefSite::instruction(1, 2),
MockType::Unknown,
);
assert_eq!(format!("{var2}"), "loc0_1");
}
}