use std::fmt;
use crate::{
analysis::ssa::{
types::{FieldRef, MethodRef, TypeRef},
SsaType, SsaVarId,
},
assembly::Immediate,
metadata::typesystem::PointerSize,
Error,
};
#[derive(Debug, Clone, PartialEq)]
pub enum ConstValue {
I8(i8),
I16(i16),
I32(i32),
I64(i64),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
NativeInt(i64),
NativeUInt(u64),
F32(f32),
F64(f64),
String(u32),
DecryptedString(String),
Null,
True,
False,
Type(TypeRef),
MethodHandle(MethodRef),
FieldHandle(FieldRef),
}
impl ConstValue {
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub const fn is_bool(&self) -> bool {
matches!(self, Self::True | Self::False)
}
#[must_use]
pub const fn is_integer(&self) -> bool {
matches!(
self,
Self::I8(_)
| Self::I16(_)
| Self::I32(_)
| Self::I64(_)
| Self::U8(_)
| Self::U16(_)
| Self::U32(_)
| Self::U64(_)
| Self::NativeInt(_)
| Self::NativeUInt(_)
)
}
#[must_use]
pub const fn is_signed(&self) -> bool {
matches!(
self,
Self::I8(_) | Self::I16(_) | Self::I32(_) | Self::I64(_) | Self::NativeInt(_)
)
}
#[must_use]
pub const fn is_unsigned(&self) -> bool {
matches!(
self,
Self::U8(_) | Self::U16(_) | Self::U32(_) | Self::U64(_) | Self::NativeUInt(_)
)
}
#[must_use]
pub const fn is_float(&self) -> bool {
matches!(self, Self::F32(_) | Self::F64(_))
}
#[must_use]
pub const fn ssa_type(&self) -> SsaType {
match self {
Self::I8(_) => SsaType::I8,
Self::I16(_) => SsaType::I16,
Self::I32(_) => SsaType::I32,
Self::I64(_) => SsaType::I64,
Self::U8(_) => SsaType::U8,
Self::U16(_) => SsaType::U16,
Self::U32(_) => SsaType::U32,
Self::U64(_) => SsaType::U64,
Self::F32(_) => SsaType::F32,
Self::F64(_) => SsaType::F64,
Self::NativeInt(_) | Self::Type(_) | Self::MethodHandle(_) | Self::FieldHandle(_) => {
SsaType::NativeInt
}
Self::NativeUInt(_) => SsaType::NativeUInt,
Self::True | Self::False => SsaType::Bool,
Self::Null | Self::String(_) | Self::DecryptedString(_) => SsaType::Object,
}
}
#[must_use]
pub const fn as_i32(&self) -> Option<i32> {
match self {
Self::I8(v) => Some(*v as i32),
Self::I16(v) => Some(*v as i32),
Self::I32(v) => Some(*v),
Self::U8(v) => Some(*v as i32),
Self::U16(v) => Some(*v as i32),
Self::True => Some(1),
Self::False => Some(0),
_ => None,
}
}
#[must_use]
#[allow(clippy::match_same_arms)] pub const fn as_i64(&self) -> Option<i64> {
match self {
Self::I8(v) => Some(*v as i64),
Self::I16(v) => Some(*v as i64),
Self::I32(v) => Some(*v as i64),
Self::I64(v) => Some(*v),
Self::U8(v) => Some(*v as i64),
Self::U16(v) => Some(*v as i64),
Self::U32(v) => Some(*v as i64),
Self::NativeInt(v) => Some(*v),
Self::True => Some(1),
Self::False => Some(0),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_sign_loss)] #[allow(clippy::match_same_arms)] pub const fn as_u64(&self) -> Option<u64> {
match self {
Self::U8(v) => Some(*v as u64),
Self::U16(v) => Some(*v as u64),
Self::U32(v) => Some(*v as u64),
Self::U64(v) => Some(*v),
Self::NativeUInt(v) => Some(*v),
Self::I8(v) if *v >= 0 => Some(*v as u64),
Self::I16(v) if *v >= 0 => Some(*v as u64),
Self::I32(v) if *v >= 0 => Some(*v as u64),
Self::I64(v) if *v >= 0 => Some(*v as u64),
Self::True => Some(1),
Self::False => Some(0),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_sign_loss)] pub const fn as_u32(&self) -> Option<u32> {
match self {
Self::U8(v) => Some(*v as u32),
Self::U16(v) => Some(*v as u32),
Self::U32(v) => Some(*v),
Self::I8(v) if *v >= 0 => Some(*v as u32),
Self::I16(v) if *v >= 0 => Some(*v as u32),
Self::I32(v) if *v >= 0 => Some(*v as u32),
Self::True => Some(1),
Self::False => Some(0),
_ => None,
}
}
#[must_use]
pub const fn as_f32(&self) -> Option<f32> {
match self {
Self::F32(v) => Some(*v),
_ => None,
}
}
#[must_use]
pub const fn as_f64(&self) -> Option<f64> {
match self {
Self::F64(v) => Some(*v),
_ => None,
}
}
#[must_use]
pub const fn as_bool(&self) -> Option<bool> {
match self {
Self::False
| Self::Null
| Self::I8(0)
| Self::I16(0)
| Self::I32(0)
| Self::I64(0)
| Self::U8(0)
| Self::U16(0)
| Self::U32(0)
| Self::U64(0) => Some(false),
Self::True
| Self::I8(_)
| Self::I16(_)
| Self::I32(_)
| Self::I64(_)
| Self::U8(_)
| Self::U16(_)
| Self::U32(_)
| Self::U64(_) => Some(true),
_ => None,
}
}
#[must_use]
pub const fn from_bool(value: bool) -> Self {
if value {
Self::True
} else {
Self::False
}
}
#[must_use]
pub const fn is_zero(&self) -> bool {
matches!(
self,
Self::I8(0)
| Self::I16(0)
| Self::I32(0)
| Self::I64(0)
| Self::U8(0)
| Self::U16(0)
| Self::U32(0)
| Self::U64(0)
| Self::NativeInt(0)
| Self::NativeUInt(0)
| Self::False
)
}
#[must_use]
pub const fn is_one(&self) -> bool {
matches!(
self,
Self::I8(1)
| Self::I16(1)
| Self::I32(1)
| Self::I64(1)
| Self::U8(1)
| Self::U16(1)
| Self::U32(1)
| Self::U64(1)
| Self::NativeInt(1)
| Self::NativeUInt(1)
| Self::True
)
}
#[must_use]
pub const fn is_minus_one(&self) -> bool {
matches!(
self,
Self::I8(-1) | Self::I16(-1) | Self::I32(-1) | Self::I64(-1) | Self::NativeInt(-1)
)
}
#[must_use]
pub const fn is_all_ones(&self) -> bool {
matches!(
self,
Self::I8(-1)
| Self::I16(-1)
| Self::I32(-1)
| Self::I64(-1)
| Self::NativeInt(-1)
| Self::U8(u8::MAX)
| Self::U16(u16::MAX)
| Self::U32(u32::MAX)
| Self::U64(u64::MAX)
| Self::NativeUInt(u64::MAX)
)
}
#[must_use]
pub const fn zero_of_same_type(&self) -> Self {
match self {
Self::I8(_) => Self::I8(0),
Self::I16(_) => Self::I16(0),
Self::I64(_) => Self::I64(0),
Self::U8(_) => Self::U8(0),
Self::U16(_) => Self::U16(0),
Self::U32(_) => Self::U32(0),
Self::U64(_) => Self::U64(0),
Self::NativeInt(_) => Self::NativeInt(0),
Self::NativeUInt(_) => Self::NativeUInt(0),
Self::F32(_) => Self::F32(0.0),
Self::F64(_) => Self::F64(0.0),
_ => Self::I32(0),
}
}
#[must_use]
pub fn negate(&self, ptr_size: PointerSize) -> Option<Self> {
match self {
Self::I8(v) => Some(Self::I8(v.wrapping_neg())),
Self::I16(v) => Some(Self::I16(v.wrapping_neg())),
Self::I32(v) => Some(Self::I32(v.wrapping_neg())),
Self::I64(v) => Some(Self::I64(v.wrapping_neg())),
Self::NativeInt(v) => Some(Self::NativeInt(v.wrapping_neg())),
Self::F32(v) => Some(Self::F32(-v)),
Self::F64(v) => Some(Self::F64(-v)),
Self::U8(v) => Some(Self::U8(v.wrapping_neg())),
Self::U16(v) => Some(Self::U16(v.wrapping_neg())),
Self::U32(v) => Some(Self::U32(v.wrapping_neg())),
Self::U64(v) => Some(Self::U64(v.wrapping_neg())),
Self::NativeUInt(v) => Some(Self::NativeUInt(v.wrapping_neg())),
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn bitwise_not(&self, ptr_size: PointerSize) -> Option<Self> {
match self {
Self::I8(v) => Some(Self::I8(!v)),
Self::I16(v) => Some(Self::I16(!v)),
Self::I32(v) => Some(Self::I32(!v)),
Self::I64(v) => Some(Self::I64(!v)),
Self::U8(v) => Some(Self::U8(!v)),
Self::U16(v) => Some(Self::U16(!v)),
Self::U32(v) => Some(Self::U32(!v)),
Self::U64(v) => Some(Self::U64(!v)),
Self::NativeInt(v) => Some(Self::NativeInt(!v)),
Self::NativeUInt(v) => Some(Self::NativeUInt(!v)),
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn bitwise_and(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::I8(a & b)),
(Self::I16(a), Self::I16(b)) => Some(Self::I16(a & b)),
(Self::I32(a), Self::I32(b)) => Some(Self::I32(a & b)),
(Self::I64(a), Self::I64(b)) => Some(Self::I64(a & b)),
(Self::U8(a), Self::U8(b)) => Some(Self::U8(a & b)),
(Self::U16(a), Self::U16(b)) => Some(Self::U16(a & b)),
(Self::U32(a), Self::U32(b)) => Some(Self::U32(a & b)),
(Self::U64(a), Self::U64(b)) => Some(Self::U64(a & b)),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::NativeInt(a & b)),
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::NativeUInt(a & b)),
(Self::I32(a), Self::I64(b)) | (Self::I64(b), Self::I32(a)) => {
Some(Self::I64(i64::from(*a) & b))
}
(Self::U32(a), Self::U64(b)) | (Self::U64(b), Self::U32(a)) => {
Some(Self::U64(u64::from(*a) & b))
}
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn bitwise_or(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::I8(a | b)),
(Self::I16(a), Self::I16(b)) => Some(Self::I16(a | b)),
(Self::I32(a), Self::I32(b)) => Some(Self::I32(a | b)),
(Self::I64(a), Self::I64(b)) => Some(Self::I64(a | b)),
(Self::U8(a), Self::U8(b)) => Some(Self::U8(a | b)),
(Self::U16(a), Self::U16(b)) => Some(Self::U16(a | b)),
(Self::U32(a), Self::U32(b)) => Some(Self::U32(a | b)),
(Self::U64(a), Self::U64(b)) => Some(Self::U64(a | b)),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::NativeInt(a | b)),
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::NativeUInt(a | b)),
(Self::I32(a), Self::I64(b)) | (Self::I64(b), Self::I32(a)) => {
Some(Self::I64(i64::from(*a) | b))
}
(Self::U32(a), Self::U64(b)) | (Self::U64(b), Self::U32(a)) => {
Some(Self::U64(u64::from(*a) | b))
}
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn bitwise_xor(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::I8(a ^ b)),
(Self::I16(a), Self::I16(b)) => Some(Self::I16(a ^ b)),
(Self::I32(a), Self::I32(b)) => Some(Self::I32(a ^ b)),
(Self::I64(a), Self::I64(b)) => Some(Self::I64(a ^ b)),
(Self::U8(a), Self::U8(b)) => Some(Self::U8(a ^ b)),
(Self::U16(a), Self::U16(b)) => Some(Self::U16(a ^ b)),
(Self::U32(a), Self::U32(b)) => Some(Self::U32(a ^ b)),
(Self::U64(a), Self::U64(b)) => Some(Self::U64(a ^ b)),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::NativeInt(a ^ b)),
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::NativeUInt(a ^ b)),
(Self::I32(a), Self::I64(b)) | (Self::I64(b), Self::I32(a)) => {
Some(Self::I64(i64::from(*a) ^ b))
}
(Self::U32(a), Self::U64(b)) | (Self::U64(b), Self::U32(a)) => {
Some(Self::U64(u64::from(*a) ^ b))
}
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
#[allow(clippy::cast_sign_loss)] pub fn shl(&self, amount: &Self, ptr_size: PointerSize) -> Option<Self> {
let shift = amount.as_i32()? as u32;
match self {
Self::I8(v) => Some(Self::I8(v.wrapping_shl(shift))),
Self::I16(v) => Some(Self::I16(v.wrapping_shl(shift))),
Self::I32(v) => Some(Self::I32(v.wrapping_shl(shift))),
Self::I64(v) => Some(Self::I64(v.wrapping_shl(shift))),
Self::U8(v) => Some(Self::U8(v.wrapping_shl(shift))),
Self::U16(v) => Some(Self::U16(v.wrapping_shl(shift))),
Self::U32(v) => Some(Self::U32(v.wrapping_shl(shift))),
Self::U64(v) => Some(Self::U64(v.wrapping_shl(shift))),
Self::NativeInt(v) => Some(Self::NativeInt(v.wrapping_shl(shift))),
Self::NativeUInt(v) => Some(Self::NativeUInt(v.wrapping_shl(shift))),
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_possible_wrap)] pub fn shr(&self, amount: &Self, unsigned: bool, ptr_size: PointerSize) -> Option<Self> {
let shift = amount.as_i32()? as u32;
match self {
Self::I8(v) => {
if unsigned {
Some(Self::I8((*v as u8).wrapping_shr(shift) as i8))
} else {
Some(Self::I8(v.wrapping_shr(shift)))
}
}
Self::I16(v) => {
if unsigned {
Some(Self::I16((*v as u16).wrapping_shr(shift) as i16))
} else {
Some(Self::I16(v.wrapping_shr(shift)))
}
}
Self::I32(v) => {
if unsigned {
Some(Self::I32((*v as u32).wrapping_shr(shift) as i32))
} else {
Some(Self::I32(v.wrapping_shr(shift)))
}
}
Self::I64(v) => {
if unsigned {
Some(Self::I64((*v as u64).wrapping_shr(shift) as i64))
} else {
Some(Self::I64(v.wrapping_shr(shift)))
}
}
Self::U8(v) => Some(Self::U8(v.wrapping_shr(shift))),
Self::U16(v) => Some(Self::U16(v.wrapping_shr(shift))),
Self::U32(v) => Some(Self::U32(v.wrapping_shr(shift))),
Self::U64(v) => Some(Self::U64(v.wrapping_shr(shift))),
Self::NativeInt(v) => {
if unsigned {
Some(Self::NativeInt((*v as u64).wrapping_shr(shift) as i64))
} else {
Some(Self::NativeInt(v.wrapping_shr(shift)))
}
}
Self::NativeUInt(v) => Some(Self::NativeUInt(v.wrapping_shr(shift))),
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn add(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::I8(a.wrapping_add(*b))),
(Self::I16(a), Self::I16(b)) => Some(Self::I16(a.wrapping_add(*b))),
(Self::I32(a), Self::I32(b)) => Some(Self::I32(a.wrapping_add(*b))),
(Self::I64(a), Self::I64(b)) => Some(Self::I64(a.wrapping_add(*b))),
(Self::U8(a), Self::U8(b)) => Some(Self::U8(a.wrapping_add(*b))),
(Self::U16(a), Self::U16(b)) => Some(Self::U16(a.wrapping_add(*b))),
(Self::U32(a), Self::U32(b)) => Some(Self::U32(a.wrapping_add(*b))),
(Self::U64(a), Self::U64(b)) => Some(Self::U64(a.wrapping_add(*b))),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::NativeInt(a.wrapping_add(*b))),
(Self::NativeUInt(a), Self::NativeUInt(b)) => {
Some(Self::NativeUInt(a.wrapping_add(*b)))
}
(Self::F32(a), Self::F32(b)) => Some(Self::F32(a + b)),
(Self::F64(a), Self::F64(b)) => Some(Self::F64(a + b)),
(Self::I32(a), Self::I64(b)) | (Self::I64(b), Self::I32(a)) => {
Some(Self::I64(i64::from(*a).wrapping_add(*b)))
}
(Self::U32(a), Self::U64(b)) | (Self::U64(b), Self::U32(a)) => {
Some(Self::U64(u64::from(*a).wrapping_add(*b)))
}
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn sub(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::I8(a.wrapping_sub(*b))),
(Self::I16(a), Self::I16(b)) => Some(Self::I16(a.wrapping_sub(*b))),
(Self::I32(a), Self::I32(b)) => Some(Self::I32(a.wrapping_sub(*b))),
(Self::I64(a), Self::I64(b)) => Some(Self::I64(a.wrapping_sub(*b))),
(Self::U8(a), Self::U8(b)) => Some(Self::U8(a.wrapping_sub(*b))),
(Self::U16(a), Self::U16(b)) => Some(Self::U16(a.wrapping_sub(*b))),
(Self::U32(a), Self::U32(b)) => Some(Self::U32(a.wrapping_sub(*b))),
(Self::U64(a), Self::U64(b)) => Some(Self::U64(a.wrapping_sub(*b))),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::NativeInt(a.wrapping_sub(*b))),
(Self::NativeUInt(a), Self::NativeUInt(b)) => {
Some(Self::NativeUInt(a.wrapping_sub(*b)))
}
(Self::F32(a), Self::F32(b)) => Some(Self::F32(a - b)),
(Self::F64(a), Self::F64(b)) => Some(Self::F64(a - b)),
(Self::I32(a), Self::I64(b)) => Some(Self::I64(i64::from(*a).wrapping_sub(*b))),
(Self::I64(a), Self::I32(b)) => Some(Self::I64(a.wrapping_sub(i64::from(*b)))),
(Self::U32(a), Self::U64(b)) => Some(Self::U64(u64::from(*a).wrapping_sub(*b))),
(Self::U64(a), Self::U32(b)) => Some(Self::U64(a.wrapping_sub(u64::from(*b)))),
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn mul(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::I8(a.wrapping_mul(*b))),
(Self::I16(a), Self::I16(b)) => Some(Self::I16(a.wrapping_mul(*b))),
(Self::I32(a), Self::I32(b)) => Some(Self::I32(a.wrapping_mul(*b))),
(Self::I64(a), Self::I64(b)) => Some(Self::I64(a.wrapping_mul(*b))),
(Self::U8(a), Self::U8(b)) => Some(Self::U8(a.wrapping_mul(*b))),
(Self::U16(a), Self::U16(b)) => Some(Self::U16(a.wrapping_mul(*b))),
(Self::U32(a), Self::U32(b)) => Some(Self::U32(a.wrapping_mul(*b))),
(Self::U64(a), Self::U64(b)) => Some(Self::U64(a.wrapping_mul(*b))),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::NativeInt(a.wrapping_mul(*b))),
(Self::NativeUInt(a), Self::NativeUInt(b)) => {
Some(Self::NativeUInt(a.wrapping_mul(*b)))
}
(Self::F32(a), Self::F32(b)) => Some(Self::F32(a * b)),
(Self::F64(a), Self::F64(b)) => Some(Self::F64(a * b)),
(Self::I32(a), Self::I64(b)) | (Self::I64(b), Self::I32(a)) => {
Some(Self::I64(i64::from(*a).wrapping_mul(*b)))
}
(Self::U32(a), Self::U64(b)) | (Self::U64(b), Self::U32(a)) => {
Some(Self::U64(u64::from(*a).wrapping_mul(*b)))
}
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn add_checked(&self, other: &Self, unsigned: bool, ptr_size: PointerSize) -> Option<Self> {
if unsigned {
match (self, other) {
(Self::I32(a), Self::I32(b)) => (*a)
.cast_unsigned()
.checked_add((*b).cast_unsigned())
.map(|r| Self::I32(r.cast_signed())),
(Self::I64(a), Self::I64(b)) => (*a)
.cast_unsigned()
.checked_add((*b).cast_unsigned())
.map(|r| Self::I64(r.cast_signed())),
(Self::U8(a), Self::U8(b)) => a.checked_add(*b).map(Self::U8),
(Self::U16(a), Self::U16(b)) => a.checked_add(*b).map(Self::U16),
(Self::U32(a), Self::U32(b)) => a.checked_add(*b).map(Self::U32),
(Self::U64(a), Self::U64(b)) => a.checked_add(*b).map(Self::U64),
(Self::NativeUInt(a), Self::NativeUInt(b)) => {
a.checked_add(*b).map(Self::NativeUInt)
}
_ => None,
}
} else {
match (self, other) {
(Self::I8(a), Self::I8(b)) => a.checked_add(*b).map(Self::I8),
(Self::I16(a), Self::I16(b)) => a.checked_add(*b).map(Self::I16),
(Self::I32(a), Self::I32(b)) => a.checked_add(*b).map(Self::I32),
(Self::I64(a), Self::I64(b)) => a.checked_add(*b).map(Self::I64),
(Self::NativeInt(a), Self::NativeInt(b)) => a.checked_add(*b).map(Self::NativeInt),
_ => None,
}
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn sub_checked(&self, other: &Self, unsigned: bool, ptr_size: PointerSize) -> Option<Self> {
if unsigned {
match (self, other) {
(Self::I32(a), Self::I32(b)) => (*a)
.cast_unsigned()
.checked_sub((*b).cast_unsigned())
.map(|r| Self::I32(r.cast_signed())),
(Self::I64(a), Self::I64(b)) => (*a)
.cast_unsigned()
.checked_sub((*b).cast_unsigned())
.map(|r| Self::I64(r.cast_signed())),
(Self::U8(a), Self::U8(b)) => a.checked_sub(*b).map(Self::U8),
(Self::U16(a), Self::U16(b)) => a.checked_sub(*b).map(Self::U16),
(Self::U32(a), Self::U32(b)) => a.checked_sub(*b).map(Self::U32),
(Self::U64(a), Self::U64(b)) => a.checked_sub(*b).map(Self::U64),
(Self::NativeUInt(a), Self::NativeUInt(b)) => {
a.checked_sub(*b).map(Self::NativeUInt)
}
_ => None,
}
} else {
match (self, other) {
(Self::I8(a), Self::I8(b)) => a.checked_sub(*b).map(Self::I8),
(Self::I16(a), Self::I16(b)) => a.checked_sub(*b).map(Self::I16),
(Self::I32(a), Self::I32(b)) => a.checked_sub(*b).map(Self::I32),
(Self::I64(a), Self::I64(b)) => a.checked_sub(*b).map(Self::I64),
(Self::NativeInt(a), Self::NativeInt(b)) => a.checked_sub(*b).map(Self::NativeInt),
_ => None,
}
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn mul_checked(&self, other: &Self, unsigned: bool, ptr_size: PointerSize) -> Option<Self> {
if unsigned {
match (self, other) {
(Self::I32(a), Self::I32(b)) => (*a)
.cast_unsigned()
.checked_mul((*b).cast_unsigned())
.map(|r| Self::I32(r.cast_signed())),
(Self::I64(a), Self::I64(b)) => (*a)
.cast_unsigned()
.checked_mul((*b).cast_unsigned())
.map(|r| Self::I64(r.cast_signed())),
(Self::U8(a), Self::U8(b)) => a.checked_mul(*b).map(Self::U8),
(Self::U16(a), Self::U16(b)) => a.checked_mul(*b).map(Self::U16),
(Self::U32(a), Self::U32(b)) => a.checked_mul(*b).map(Self::U32),
(Self::U64(a), Self::U64(b)) => a.checked_mul(*b).map(Self::U64),
(Self::NativeUInt(a), Self::NativeUInt(b)) => {
a.checked_mul(*b).map(Self::NativeUInt)
}
_ => None,
}
} else {
match (self, other) {
(Self::I8(a), Self::I8(b)) => a.checked_mul(*b).map(Self::I8),
(Self::I16(a), Self::I16(b)) => a.checked_mul(*b).map(Self::I16),
(Self::I32(a), Self::I32(b)) => a.checked_mul(*b).map(Self::I32),
(Self::I64(a), Self::I64(b)) => a.checked_mul(*b).map(Self::I64),
(Self::NativeInt(a), Self::NativeInt(b)) => a.checked_mul(*b).map(Self::NativeInt),
_ => None,
}
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn div(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) if *b != 0 => Some(Self::I8(a.wrapping_div(*b))),
(Self::I16(a), Self::I16(b)) if *b != 0 => Some(Self::I16(a.wrapping_div(*b))),
(Self::I32(a), Self::I32(b)) if *b != 0 => Some(Self::I32(a.wrapping_div(*b))),
(Self::I64(a), Self::I64(b)) if *b != 0 => Some(Self::I64(a.wrapping_div(*b))),
(Self::U8(a), Self::U8(b)) if *b != 0 => Some(Self::U8(a / b)),
(Self::U16(a), Self::U16(b)) if *b != 0 => Some(Self::U16(a / b)),
(Self::U32(a), Self::U32(b)) if *b != 0 => Some(Self::U32(a / b)),
(Self::U64(a), Self::U64(b)) if *b != 0 => Some(Self::U64(a / b)),
(Self::NativeInt(a), Self::NativeInt(b)) if *b != 0 => {
Some(Self::NativeInt(a.wrapping_div(*b)))
}
(Self::NativeUInt(a), Self::NativeUInt(b)) if *b != 0 => Some(Self::NativeUInt(a / b)),
(Self::F32(a), Self::F32(b)) => Some(Self::F32(a / b)), (Self::F64(a), Self::F64(b)) => Some(Self::F64(a / b)),
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn rem(&self, other: &Self, ptr_size: PointerSize) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) if *b != 0 => Some(Self::I8(a.wrapping_rem(*b))),
(Self::I16(a), Self::I16(b)) if *b != 0 => Some(Self::I16(a.wrapping_rem(*b))),
(Self::I32(a), Self::I32(b)) if *b != 0 => Some(Self::I32(a.wrapping_rem(*b))),
(Self::I64(a), Self::I64(b)) if *b != 0 => Some(Self::I64(a.wrapping_rem(*b))),
(Self::U8(a), Self::U8(b)) if *b != 0 => Some(Self::U8(a % b)),
(Self::U16(a), Self::U16(b)) if *b != 0 => Some(Self::U16(a % b)),
(Self::U32(a), Self::U32(b)) if *b != 0 => Some(Self::U32(a % b)),
(Self::U64(a), Self::U64(b)) if *b != 0 => Some(Self::U64(a % b)),
(Self::NativeInt(a), Self::NativeInt(b)) if *b != 0 => {
Some(Self::NativeInt(a.wrapping_rem(*b)))
}
(Self::NativeUInt(a), Self::NativeUInt(b)) if *b != 0 => Some(Self::NativeUInt(a % b)),
(Self::F32(a), Self::F32(b)) => Some(Self::F32(a % b)),
(Self::F64(a), Self::F64(b)) => Some(Self::F64(a % b)),
_ => None,
}
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
#[allow(clippy::float_cmp)] #[allow(clippy::match_same_arms)] pub fn ceq(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::from_bool(a == b)),
(Self::I16(a), Self::I16(b)) => Some(Self::from_bool(a == b)),
(Self::I32(a), Self::I32(b)) => Some(Self::from_bool(a == b)),
(Self::I64(a), Self::I64(b)) => Some(Self::from_bool(a == b)),
(Self::U8(a), Self::U8(b)) => Some(Self::from_bool(a == b)),
(Self::U16(a), Self::U16(b)) => Some(Self::from_bool(a == b)),
(Self::U32(a), Self::U32(b)) => Some(Self::from_bool(a == b)),
(Self::U64(a), Self::U64(b)) => Some(Self::from_bool(a == b)),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::from_bool(a == b)),
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::from_bool(a == b)),
(Self::F32(a), Self::F32(b)) => Some(Self::from_bool(a == b)),
(Self::F64(a), Self::F64(b)) => Some(Self::from_bool(a == b)),
(Self::Null, Self::Null) | (Self::True, Self::True) | (Self::False, Self::False) => {
Some(Self::True)
}
(Self::True, Self::False) | (Self::False, Self::True) => Some(Self::False),
(Self::I32(a), Self::I64(b)) | (Self::I64(b), Self::I32(a)) => {
Some(Self::from_bool(i64::from(*a) == *b))
}
(Self::U32(a), Self::U64(b)) | (Self::U64(b), Self::U32(a)) => {
Some(Self::from_bool(u64::from(*a) == *b))
}
_ => None,
}
}
#[must_use]
#[allow(clippy::match_same_arms)] pub fn clt(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::from_bool(a < b)),
(Self::I16(a), Self::I16(b)) => Some(Self::from_bool(a < b)),
(Self::I32(a), Self::I32(b)) => Some(Self::from_bool(a < b)),
(Self::I64(a), Self::I64(b)) => Some(Self::from_bool(a < b)),
(Self::U8(a), Self::U8(b)) => Some(Self::from_bool(a < b)),
(Self::U16(a), Self::U16(b)) => Some(Self::from_bool(a < b)),
(Self::U32(a), Self::U32(b)) => Some(Self::from_bool(a < b)),
(Self::U64(a), Self::U64(b)) => Some(Self::from_bool(a < b)),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::from_bool(a < b)),
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::from_bool(a < b)),
(Self::F32(a), Self::F32(b)) => Some(Self::from_bool(a < b)),
(Self::F64(a), Self::F64(b)) => Some(Self::from_bool(a < b)),
(Self::I32(a), Self::I64(b)) => Some(Self::from_bool(i64::from(*a) < *b)),
(Self::I64(a), Self::I32(b)) => Some(Self::from_bool(*a < i64::from(*b))),
(Self::U32(a), Self::U64(b)) => Some(Self::from_bool(u64::from(*a) < *b)),
(Self::U64(a), Self::U32(b)) => Some(Self::from_bool(*a < u64::from(*b))),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_sign_loss)] #[allow(clippy::match_same_arms)] pub fn clt_un(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::from_bool((*a as u8) < (*b as u8))),
(Self::I16(a), Self::I16(b)) => Some(Self::from_bool((*a as u16) < (*b as u16))),
(Self::I32(a), Self::I32(b)) => Some(Self::from_bool((*a as u32) < (*b as u32))),
(Self::I64(a), Self::I64(b)) => Some(Self::from_bool((*a as u64) < (*b as u64))),
(Self::U8(a), Self::U8(b)) => Some(Self::from_bool(a < b)),
(Self::U16(a), Self::U16(b)) => Some(Self::from_bool(a < b)),
(Self::U32(a), Self::U32(b)) => Some(Self::from_bool(a < b)),
(Self::U64(a), Self::U64(b)) => Some(Self::from_bool(a < b)),
(Self::NativeInt(a), Self::NativeInt(b)) => {
Some(Self::from_bool((*a as u64) < (*b as u64)))
}
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::from_bool(a < b)),
(Self::F32(a), Self::F32(b)) => {
Some(Self::from_bool(a.is_nan() || b.is_nan() || a < b))
}
(Self::F64(a), Self::F64(b)) => {
Some(Self::from_bool(a.is_nan() || b.is_nan() || a < b))
}
_ => None,
}
}
#[must_use]
#[allow(clippy::match_same_arms)] pub fn cgt(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::from_bool(a > b)),
(Self::I16(a), Self::I16(b)) => Some(Self::from_bool(a > b)),
(Self::I32(a), Self::I32(b)) => Some(Self::from_bool(a > b)),
(Self::I64(a), Self::I64(b)) => Some(Self::from_bool(a > b)),
(Self::U8(a), Self::U8(b)) => Some(Self::from_bool(a > b)),
(Self::U16(a), Self::U16(b)) => Some(Self::from_bool(a > b)),
(Self::U32(a), Self::U32(b)) => Some(Self::from_bool(a > b)),
(Self::U64(a), Self::U64(b)) => Some(Self::from_bool(a > b)),
(Self::NativeInt(a), Self::NativeInt(b)) => Some(Self::from_bool(a > b)),
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::from_bool(a > b)),
(Self::F32(a), Self::F32(b)) => Some(Self::from_bool(a > b)),
(Self::F64(a), Self::F64(b)) => Some(Self::from_bool(a > b)),
(Self::I32(a), Self::I64(b)) => Some(Self::from_bool(i64::from(*a) > *b)),
(Self::I64(a), Self::I32(b)) => Some(Self::from_bool(*a > i64::from(*b))),
(Self::U32(a), Self::U64(b)) => Some(Self::from_bool(u64::from(*a) > *b)),
(Self::U64(a), Self::U32(b)) => Some(Self::from_bool(*a > u64::from(*b))),
_ => None,
}
}
#[must_use]
#[allow(clippy::cast_sign_loss)] #[allow(clippy::match_same_arms)] pub fn cgt_un(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::I8(a), Self::I8(b)) => Some(Self::from_bool((*a as u8) > (*b as u8))),
(Self::I16(a), Self::I16(b)) => Some(Self::from_bool((*a as u16) > (*b as u16))),
(Self::I32(a), Self::I32(b)) => Some(Self::from_bool((*a as u32) > (*b as u32))),
(Self::I64(a), Self::I64(b)) => Some(Self::from_bool((*a as u64) > (*b as u64))),
(Self::U8(a), Self::U8(b)) => Some(Self::from_bool(a > b)),
(Self::U16(a), Self::U16(b)) => Some(Self::from_bool(a > b)),
(Self::U32(a), Self::U32(b)) => Some(Self::from_bool(a > b)),
(Self::U64(a), Self::U64(b)) => Some(Self::from_bool(a > b)),
(Self::NativeInt(a), Self::NativeInt(b)) => {
Some(Self::from_bool((*a as u64) > (*b as u64)))
}
(Self::NativeUInt(a), Self::NativeUInt(b)) => Some(Self::from_bool(a > b)),
(Self::F32(a), Self::F32(b)) => {
Some(Self::from_bool(a.is_nan() || b.is_nan() || a > b))
}
(Self::F64(a), Self::F64(b)) => {
Some(Self::from_bool(a.is_nan() || b.is_nan() || a > b))
}
_ => None,
}
}
#[must_use]
pub fn convert_to(
&self,
target: &SsaType,
unsigned_source: bool,
ptr_size: PointerSize,
) -> Option<Self> {
let (signed_val, unsigned_val) = if unsigned_source {
let u = self.as_u64()?;
(i64::from_ne_bytes(u.to_ne_bytes()), u)
} else {
let s = self.as_i64()?;
(s, u64::from_ne_bytes(s.to_ne_bytes()))
};
#[allow(clippy::cast_possible_truncation)]
Some(match target {
SsaType::I8 => Self::I8(signed_val as i8),
SsaType::U8 => Self::U8(unsigned_val as u8),
SsaType::I16 => Self::I16(signed_val as i16),
SsaType::U16 | SsaType::Char => Self::U16(unsigned_val as u16),
SsaType::I32 => Self::I32(signed_val as i32),
SsaType::U32 => Self::U32(unsigned_val as u32),
SsaType::I64 => Self::I64(signed_val),
SsaType::U64 => Self::U64(unsigned_val),
SsaType::NativeInt => Self::NativeInt(signed_val),
SsaType::NativeUInt => Self::NativeUInt(unsigned_val),
SsaType::F32 =>
{
#[allow(clippy::cast_precision_loss)]
if unsigned_source {
Self::F32(unsigned_val as f32)
} else {
Self::F32(signed_val as f32)
}
}
SsaType::F64 =>
{
#[allow(clippy::cast_precision_loss)]
if unsigned_source {
Self::F64(unsigned_val as f64)
} else {
Self::F64(signed_val as f64)
}
}
SsaType::Bool => Self::from_bool(signed_val != 0),
_ => return None,
})
.map(|v| v.mask_native(ptr_size))
}
#[must_use]
pub fn convert_to_checked(
&self,
target: &SsaType,
unsigned_source: bool,
ptr_size: PointerSize,
) -> Option<Self> {
let (signed_val, unsigned_val) = if unsigned_source {
let u = self.as_u64()?;
(i64::from_ne_bytes(u.to_ne_bytes()), u)
} else {
let s = self.as_i64()?;
(s, u64::from_ne_bytes(s.to_ne_bytes()))
};
let fits = match target {
SsaType::I8 => i8::try_from(signed_val).is_ok(),
SsaType::U8 => u8::try_from(unsigned_val).is_ok() && signed_val >= 0,
SsaType::I16 => i16::try_from(signed_val).is_ok(),
SsaType::U16 => u16::try_from(unsigned_val).is_ok() && signed_val >= 0,
SsaType::I32 => i32::try_from(signed_val).is_ok(),
SsaType::U32 => u32::try_from(unsigned_val).is_ok() && signed_val >= 0,
SsaType::I64
| SsaType::NativeInt
| SsaType::Bool
| SsaType::Char
| SsaType::F32
| SsaType::F64 => true,
SsaType::U64 | SsaType::NativeUInt => signed_val >= 0,
_ => return None,
};
if !fits {
return None; }
self.convert_to(target, unsigned_source, ptr_size)
}
#[must_use]
pub fn mask_native(self, ptr_size: PointerSize) -> Self {
match self {
Self::NativeInt(v) => Self::NativeInt(ptr_size.mask_signed(v)),
Self::NativeUInt(v) => Self::NativeUInt(ptr_size.mask_unsigned(v)),
other => other,
}
}
}
impl fmt::Display for ConstValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::I8(v) => write!(f, "{v}i8"),
Self::I16(v) => write!(f, "{v}i16"),
Self::I32(v) => write!(f, "{v}"),
Self::I64(v) => write!(f, "{v}L"),
Self::U8(v) => write!(f, "{v}u8"),
Self::U16(v) => write!(f, "{v}u16"),
Self::U32(v) => write!(f, "{v}u"),
Self::U64(v) => write!(f, "{v}UL"),
Self::NativeInt(v) => write!(f, "{v}n"),
Self::NativeUInt(v) => write!(f, "{v}un"),
Self::F32(v) => write!(f, "{v}f"),
Self::F64(v) => write!(f, "{v}"),
Self::String(idx) => write!(f, "str@{idx}"),
Self::DecryptedString(s) => write!(f, "\"{}\"", s.escape_default()),
Self::Null => write!(f, "null"),
Self::True => write!(f, "true"),
Self::False => write!(f, "false"),
Self::Type(t) => write!(f, "typeof({t})"),
Self::MethodHandle(m) => write!(f, "methodof({m})"),
Self::FieldHandle(fl) => write!(f, "fieldof({fl})"),
}
}
}
impl TryFrom<&ConstValue> for Immediate {
type Error = Error;
#[allow(clippy::cast_possible_wrap)] fn try_from(value: &ConstValue) -> Result<Self, Self::Error> {
match value {
ConstValue::I8(v) => Ok(Immediate::Int8(*v)),
ConstValue::I16(v) => Ok(Immediate::Int16(*v)),
ConstValue::I32(v) => Ok(Immediate::Int32(*v)),
ConstValue::U8(v) => Ok(Immediate::Int8(*v as i8)),
ConstValue::U16(v) => Ok(Immediate::Int16(*v as i16)),
ConstValue::U32(v) => Ok(Immediate::Int32(*v as i32)),
ConstValue::I64(v) | ConstValue::NativeInt(v) => Ok(Immediate::Int64(*v)),
ConstValue::U64(v) | ConstValue::NativeUInt(v) => Ok(Immediate::Int64(*v as i64)),
ConstValue::F32(v) => Ok(Immediate::Float32(*v)),
ConstValue::F64(v) => Ok(Immediate::Float64(*v)),
ConstValue::True => Ok(Immediate::Int32(1)),
ConstValue::False => Ok(Immediate::Int32(0)),
ConstValue::String(_)
| ConstValue::DecryptedString(_)
| ConstValue::Null
| ConstValue::Type(_)
| ConstValue::MethodHandle(_)
| ConstValue::FieldHandle(_) => Err(Error::SsaError(format!(
"Cannot convert {value:?} to Immediate - use pattern matching to handle this case"
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum AbstractValue {
#[default]
Top,
Constant(ConstValue),
NonNull,
Range {
min: i64,
max: i64,
},
SameAs(SsaVarId),
Computed(ComputedValue),
Bottom,
}
impl AbstractValue {
#[must_use]
pub const fn is_top(&self) -> bool {
matches!(self, Self::Top)
}
#[must_use]
pub const fn is_bottom(&self) -> bool {
matches!(self, Self::Bottom)
}
#[must_use]
pub const fn is_constant(&self) -> bool {
matches!(self, Self::Constant(_))
}
#[must_use]
pub const fn as_constant(&self) -> Option<&ConstValue> {
match self {
Self::Constant(c) => Some(c),
_ => None,
}
}
#[must_use]
pub const fn is_non_null(&self) -> bool {
matches!(self, Self::NonNull | Self::Constant(_))
}
#[must_use]
#[allow(clippy::match_same_arms)] pub fn meet(&self, other: &Self) -> Self {
match (self, other) {
(Self::Top, x) | (x, Self::Top) => x.clone(),
(Self::Bottom, _) | (_, Self::Bottom) => Self::Bottom,
(Self::Constant(a), Self::Constant(b)) if a == b => Self::Constant(a.clone()),
(Self::Constant(_), Self::Constant(_)) => Self::Bottom,
(Self::NonNull, Self::NonNull) => Self::NonNull,
(Self::NonNull, Self::Constant(c)) | (Self::Constant(c), Self::NonNull) => {
if c.is_null() {
Self::Bottom } else {
Self::Constant(c.clone())
}
}
(
Self::Range {
min: a_min,
max: a_max,
},
Self::Range {
min: b_min,
max: b_max,
},
) => {
let new_min = (*a_min).min(*b_min);
let new_max = (*a_max).max(*b_max);
Self::Range {
min: new_min,
max: new_max,
}
}
(Self::SameAs(a), Self::SameAs(b)) if a == b => Self::SameAs(*a),
(Self::Computed(a), Self::Computed(b)) if a == b => Self::Computed(a.clone()),
_ => Self::Bottom,
}
}
#[must_use]
pub fn join(&self, other: &Self) -> Self {
match (self, other) {
(Self::Bottom, x) | (x, Self::Bottom) => x.clone(),
(Self::Top, _) | (_, Self::Top) => Self::Top,
(a, b) if a == b => a.clone(),
_ => Self::Top,
}
}
}
impl fmt::Display for AbstractValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Top => write!(f, "⊤"),
Self::Constant(c) => write!(f, "{c}"),
Self::NonNull => write!(f, "!null"),
Self::Range { min, max } => write!(f, "[{min}..{max}]"),
Self::SameAs(v) => write!(f, "={v}"),
Self::Computed(c) => write!(f, "{c}"),
Self::Bottom => write!(f, "⊥"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ComputedValue {
pub op: ComputedOp,
pub operands: Vec<SsaVarId>,
}
impl ComputedValue {
#[must_use]
pub fn new(op: ComputedOp, operands: Vec<SsaVarId>) -> Self {
Self { op, operands }
}
#[must_use]
pub fn unary(op: ComputedOp, operand: SsaVarId) -> Self {
Self {
op,
operands: vec![operand],
}
}
#[must_use]
pub fn binary(op: ComputedOp, left: SsaVarId, right: SsaVarId) -> Self {
Self {
op,
operands: vec![left, right],
}
}
#[must_use]
pub fn normalized(self) -> Self {
if self.op.is_commutative() && self.operands.len() == 2 {
let mut ops = self.operands;
if ops[0].index() > ops[1].index() {
ops.swap(0, 1);
}
Self {
op: self.op,
operands: ops,
}
} else {
self
}
}
}
impl fmt::Display for ComputedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.op)?;
for (i, op) in self.operands.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{op}")?;
}
write!(f, ")")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ComputedOp {
Add,
Sub,
Mul,
Div,
Rem,
Neg,
And,
Or,
Xor,
Not,
Shl,
Shr,
Ceq,
Cne,
Clt,
Cgt,
Cle,
Cge,
ConvI1,
ConvI2,
ConvI4,
ConvI8,
ConvU1,
ConvU2,
ConvU4,
ConvU8,
ConvR4,
ConvR8,
}
impl ComputedOp {
#[must_use]
pub const fn is_commutative(&self) -> bool {
matches!(
self,
Self::Add | Self::Mul | Self::And | Self::Or | Self::Xor | Self::Ceq | Self::Cne
)
}
#[must_use]
pub const fn is_comparison(&self) -> bool {
matches!(
self,
Self::Ceq | Self::Cne | Self::Clt | Self::Cgt | Self::Cle | Self::Cge
)
}
}
impl fmt::Display for ComputedOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Add => "add",
Self::Sub => "sub",
Self::Mul => "mul",
Self::Div => "div",
Self::Rem => "rem",
Self::Neg => "neg",
Self::And => "and",
Self::Or => "or",
Self::Xor => "xor",
Self::Not => "not",
Self::Shl => "shl",
Self::Shr => "shr",
Self::Ceq => "ceq",
Self::Cne => "cne",
Self::Clt => "clt",
Self::Cgt => "cgt",
Self::Cle => "cle",
Self::Cge => "cge",
Self::ConvI1 => "conv.i1",
Self::ConvI2 => "conv.i2",
Self::ConvI4 => "conv.i4",
Self::ConvI8 => "conv.i8",
Self::ConvU1 => "conv.u1",
Self::ConvU2 => "conv.u2",
Self::ConvU4 => "conv.u4",
Self::ConvU8 => "conv.u8",
Self::ConvR4 => "conv.r4",
Self::ConvR8 => "conv.r8",
};
write!(f, "{s}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::typesystem::PointerSize;
#[test]
fn test_const_arithmetic() {
let a = ConstValue::I32(10);
let b = ConstValue::I32(3);
assert_eq!(a.add(&b, PointerSize::Bit64), Some(ConstValue::I32(13)));
assert_eq!(a.sub(&b, PointerSize::Bit64), Some(ConstValue::I32(7)));
assert_eq!(a.mul(&b, PointerSize::Bit64), Some(ConstValue::I32(30)));
}
#[test]
fn test_const_comparison() {
let a = ConstValue::I32(10);
let b = ConstValue::I32(3);
let c = ConstValue::I32(10);
assert_eq!(a.ceq(&b), Some(ConstValue::False));
assert_eq!(a.ceq(&c), Some(ConstValue::True));
assert_eq!(a.clt(&b), Some(ConstValue::False));
assert_eq!(b.clt(&a), Some(ConstValue::True));
}
#[test]
fn test_const_bool_conversion() {
assert_eq!(ConstValue::True.as_bool(), Some(true));
assert_eq!(ConstValue::False.as_bool(), Some(false));
assert_eq!(ConstValue::I32(0).as_bool(), Some(false));
assert_eq!(ConstValue::I32(42).as_bool(), Some(true));
assert_eq!(ConstValue::Null.as_bool(), Some(false));
}
#[test]
fn test_abstract_value_meet() {
assert_eq!(
AbstractValue::Top.meet(&AbstractValue::Constant(ConstValue::I32(5))),
AbstractValue::Constant(ConstValue::I32(5))
);
assert_eq!(
AbstractValue::Constant(ConstValue::I32(5))
.meet(&AbstractValue::Constant(ConstValue::I32(5))),
AbstractValue::Constant(ConstValue::I32(5))
);
assert_eq!(
AbstractValue::Constant(ConstValue::I32(5))
.meet(&AbstractValue::Constant(ConstValue::I32(10))),
AbstractValue::Bottom
);
assert_eq!(
AbstractValue::Bottom.meet(&AbstractValue::Constant(ConstValue::I32(5))),
AbstractValue::Bottom
);
}
#[test]
fn test_range_merge() {
let r1 = AbstractValue::Range { min: 0, max: 10 };
let r2 = AbstractValue::Range { min: 5, max: 15 };
assert_eq!(r1.meet(&r2), AbstractValue::Range { min: 0, max: 15 });
}
#[test]
fn test_computed_value_normalization() {
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let cv1 = ComputedValue::binary(ComputedOp::Add, v1, v0).normalized();
let cv2 = ComputedValue::binary(ComputedOp::Add, v0, v1).normalized();
assert_eq!(cv1, cv2);
let cv3 = ComputedValue::binary(ComputedOp::Sub, v1, v0).normalized();
let cv4 = ComputedValue::binary(ComputedOp::Sub, v0, v1).normalized();
assert_ne!(cv3, cv4);
}
#[test]
fn test_is_zero() {
assert!(ConstValue::I8(0).is_zero());
assert!(ConstValue::I16(0).is_zero());
assert!(ConstValue::I32(0).is_zero());
assert!(ConstValue::I64(0).is_zero());
assert!(ConstValue::U8(0).is_zero());
assert!(ConstValue::U16(0).is_zero());
assert!(ConstValue::U32(0).is_zero());
assert!(ConstValue::U64(0).is_zero());
assert!(ConstValue::NativeInt(0).is_zero());
assert!(ConstValue::NativeUInt(0).is_zero());
assert!(ConstValue::False.is_zero());
assert!(!ConstValue::I32(1).is_zero());
assert!(!ConstValue::I32(-1).is_zero());
assert!(!ConstValue::True.is_zero());
assert!(!ConstValue::Null.is_zero()); }
#[test]
fn test_is_one() {
assert!(ConstValue::I8(1).is_one());
assert!(ConstValue::I16(1).is_one());
assert!(ConstValue::I32(1).is_one());
assert!(ConstValue::I64(1).is_one());
assert!(ConstValue::U8(1).is_one());
assert!(ConstValue::U16(1).is_one());
assert!(ConstValue::U32(1).is_one());
assert!(ConstValue::U64(1).is_one());
assert!(ConstValue::NativeInt(1).is_one());
assert!(ConstValue::NativeUInt(1).is_one());
assert!(ConstValue::True.is_one());
assert!(!ConstValue::I32(0).is_one());
assert!(!ConstValue::I32(2).is_one());
assert!(!ConstValue::False.is_one());
}
#[test]
fn test_is_minus_one() {
assert!(ConstValue::I8(-1).is_minus_one());
assert!(ConstValue::I16(-1).is_minus_one());
assert!(ConstValue::I32(-1).is_minus_one());
assert!(ConstValue::I64(-1).is_minus_one());
assert!(ConstValue::NativeInt(-1).is_minus_one());
assert!(!ConstValue::I32(0).is_minus_one());
assert!(!ConstValue::I32(1).is_minus_one());
assert!(!ConstValue::U32(u32::MAX).is_minus_one());
}
#[test]
fn test_convert_to_widening() {
let v = ConstValue::I32(42);
assert_eq!(
v.convert_to(&SsaType::I64, false, PointerSize::Bit64),
Some(ConstValue::I64(42))
);
let v = ConstValue::I32(-42);
assert_eq!(
v.convert_to(&SsaType::I64, false, PointerSize::Bit64),
Some(ConstValue::I64(-42))
);
let v = ConstValue::U32(42);
assert_eq!(
v.convert_to(&SsaType::U64, false, PointerSize::Bit64),
Some(ConstValue::U64(42))
);
}
#[test]
fn test_convert_to_narrowing() {
let v = ConstValue::I32(42);
assert_eq!(
v.convert_to(&SsaType::I8, false, PointerSize::Bit64),
Some(ConstValue::I8(42))
);
let v = ConstValue::I32(1000);
assert_eq!(
v.convert_to(&SsaType::I8, false, PointerSize::Bit64),
Some(ConstValue::I8(-24))
);
let v = ConstValue::I64(0x1_0000_0042);
assert_eq!(
v.convert_to(&SsaType::I32, false, PointerSize::Bit64),
Some(ConstValue::I32(0x42))
);
}
#[test]
fn test_convert_to_float() {
let v = ConstValue::I32(42);
assert_eq!(
v.convert_to(&SsaType::F32, false, PointerSize::Bit64),
Some(ConstValue::F32(42.0))
);
let v = ConstValue::I32(42);
assert_eq!(
v.convert_to(&SsaType::F64, false, PointerSize::Bit64),
Some(ConstValue::F64(42.0))
);
let v = ConstValue::U32(42);
assert_eq!(
v.convert_to(&SsaType::F32, true, PointerSize::Bit64),
Some(ConstValue::F32(42.0))
);
}
#[test]
fn test_convert_to_bool() {
let v = ConstValue::I32(42);
assert_eq!(
v.convert_to(&SsaType::Bool, false, PointerSize::Bit64),
Some(ConstValue::True)
);
let v = ConstValue::I32(0);
assert_eq!(
v.convert_to(&SsaType::Bool, false, PointerSize::Bit64),
Some(ConstValue::False)
);
}
#[test]
fn test_convert_to_checked_in_range() {
let v = ConstValue::I32(100);
assert_eq!(
v.convert_to_checked(&SsaType::I8, false, PointerSize::Bit64),
Some(ConstValue::I8(100))
);
let v = ConstValue::I32(127);
assert_eq!(
v.convert_to_checked(&SsaType::I8, false, PointerSize::Bit64),
Some(ConstValue::I8(127))
);
let v = ConstValue::I32(-128);
assert_eq!(
v.convert_to_checked(&SsaType::I8, false, PointerSize::Bit64),
Some(ConstValue::I8(-128))
);
}
#[test]
fn test_convert_to_checked_overflow() {
let v = ConstValue::I32(1000);
assert_eq!(
v.convert_to_checked(&SsaType::I8, false, PointerSize::Bit64),
None
);
let v = ConstValue::I32(-1);
assert_eq!(
v.convert_to_checked(&SsaType::U8, false, PointerSize::Bit64),
None
);
assert_eq!(
v.convert_to_checked(&SsaType::U32, false, PointerSize::Bit64),
None
);
assert_eq!(
v.convert_to_checked(&SsaType::U64, false, PointerSize::Bit64),
None
);
}
#[test]
fn test_convert_to_char() {
let v = ConstValue::I32(65); assert_eq!(
v.convert_to(&SsaType::Char, false, PointerSize::Bit64),
Some(ConstValue::U16(65))
);
}
#[test]
fn test_convert_to_native() {
let v = ConstValue::I32(42);
assert_eq!(
v.convert_to(&SsaType::NativeInt, false, PointerSize::Bit64),
Some(ConstValue::NativeInt(42))
);
let v = ConstValue::I32(42);
assert_eq!(
v.convert_to(&SsaType::NativeUInt, false, PointerSize::Bit64),
Some(ConstValue::NativeUInt(42))
);
}
#[test]
fn test_const_div() {
let a = ConstValue::I32(100);
let b = ConstValue::I32(7);
assert_eq!(a.div(&b, PointerSize::Bit64), Some(ConstValue::I32(14)));
let a = ConstValue::U32(100);
let b = ConstValue::U32(7);
assert_eq!(a.div(&b, PointerSize::Bit64), Some(ConstValue::U32(14)));
let a = ConstValue::I64(1000);
let b = ConstValue::I64(33);
assert_eq!(a.div(&b, PointerSize::Bit64), Some(ConstValue::I64(30)));
}
#[test]
fn test_const_div_by_zero() {
let a = ConstValue::I32(100);
let zero = ConstValue::I32(0);
assert_eq!(a.div(&zero, PointerSize::Bit64), None);
let a = ConstValue::U64(100);
let zero = ConstValue::U64(0);
assert_eq!(a.div(&zero, PointerSize::Bit64), None);
}
#[test]
fn test_const_div_negative() {
let a = ConstValue::I32(-100);
let b = ConstValue::I32(7);
assert_eq!(a.div(&b, PointerSize::Bit64), Some(ConstValue::I32(-14))); }
#[test]
fn test_const_rem() {
let a = ConstValue::I32(100);
let b = ConstValue::I32(7);
assert_eq!(a.rem(&b, PointerSize::Bit64), Some(ConstValue::I32(2)));
let a = ConstValue::U32(100);
let b = ConstValue::U32(7);
assert_eq!(a.rem(&b, PointerSize::Bit64), Some(ConstValue::U32(2)));
}
#[test]
fn test_const_rem_by_zero() {
let a = ConstValue::I32(100);
let zero = ConstValue::I32(0);
assert_eq!(a.rem(&zero, PointerSize::Bit64), None);
}
#[test]
fn test_const_rem_negative() {
let a = ConstValue::I32(-100);
let b = ConstValue::I32(7);
assert_eq!(a.rem(&b, PointerSize::Bit64), Some(ConstValue::I32(-2)));
}
#[test]
fn test_const_shl() {
let a = ConstValue::I32(1);
let shift = ConstValue::I32(4);
assert_eq!(a.shl(&shift, PointerSize::Bit64), Some(ConstValue::I32(16)));
let a = ConstValue::I32(1);
let shift = ConstValue::I32(31);
assert_eq!(
a.shl(&shift, PointerSize::Bit64),
Some(ConstValue::I32(i32::MIN))
); }
#[test]
fn test_const_shr_signed() {
let a = ConstValue::I32(-16);
let shift = ConstValue::I32(2);
assert_eq!(
a.shr(&shift, false, PointerSize::Bit64),
Some(ConstValue::I32(-4))
);
let b = ConstValue::I32(16);
let shift = ConstValue::I32(2);
assert_eq!(
b.shr(&shift, false, PointerSize::Bit64),
Some(ConstValue::I32(4))
);
}
#[test]
fn test_const_shr_unsigned() {
let a = ConstValue::I32(-1); let shift = ConstValue::I32(1);
let result = a.shr(&shift, true, PointerSize::Bit64);
assert!(result.is_some());
if let Some(ConstValue::I32(v)) = result {
assert_eq!(v, 0x7FFFFFFF);
}
}
#[test]
fn test_const_shr_i64() {
let a = ConstValue::I64(256);
let shift = ConstValue::I32(4);
assert_eq!(
a.shr(&shift, false, PointerSize::Bit64),
Some(ConstValue::I64(16))
);
}
#[test]
fn test_const_cgt() {
let a = ConstValue::I32(10);
let b = ConstValue::I32(5);
assert_eq!(a.cgt(&b), Some(ConstValue::True));
assert_eq!(b.cgt(&a), Some(ConstValue::False));
assert_eq!(a.cgt(&a), Some(ConstValue::False)); }
#[test]
fn test_const_cgt_i64() {
let a = ConstValue::I64(1000);
let b = ConstValue::I64(500);
assert_eq!(a.cgt(&b), Some(ConstValue::True));
}
#[test]
fn test_const_clt_negative() {
let a = ConstValue::I32(-10);
let b = ConstValue::I32(5);
assert_eq!(a.clt(&b), Some(ConstValue::True)); }
#[test]
fn test_const_float_arithmetic() {
let a = ConstValue::F32(10.5);
let b = ConstValue::F32(2.5);
assert_eq!(a.add(&b, PointerSize::Bit64), Some(ConstValue::F32(13.0)));
assert_eq!(a.sub(&b, PointerSize::Bit64), Some(ConstValue::F32(8.0)));
assert_eq!(a.mul(&b, PointerSize::Bit64), Some(ConstValue::F32(26.25)));
let a = ConstValue::F64(100.0);
let b = ConstValue::F64(4.0);
assert_eq!(a.div(&b, PointerSize::Bit64), Some(ConstValue::F64(25.0)));
}
#[test]
fn test_const_float_comparison() {
let a = ConstValue::F64(std::f64::consts::PI);
let b = ConstValue::F64(std::f64::consts::E);
assert_eq!(a.cgt(&b), Some(ConstValue::True));
assert_eq!(a.clt(&b), Some(ConstValue::False));
assert_eq!(a.ceq(&a), Some(ConstValue::True));
}
#[test]
fn test_const_float_div_by_zero() {
let a = ConstValue::F64(1.0);
let b = ConstValue::F64(0.0);
let result = a.div(&b, PointerSize::Bit64);
assert!(result.is_some());
if let Some(ConstValue::F64(v)) = result {
assert!(v.is_infinite());
}
}
#[test]
fn test_is_zero_integer_types() {
assert!(ConstValue::I8(0).is_zero());
assert!(ConstValue::I16(0).is_zero());
assert!(ConstValue::I32(0).is_zero());
assert!(ConstValue::I64(0).is_zero());
assert!(ConstValue::U8(0).is_zero());
assert!(ConstValue::U16(0).is_zero());
assert!(ConstValue::U32(0).is_zero());
assert!(ConstValue::U64(0).is_zero());
assert!(ConstValue::NativeInt(0).is_zero());
assert!(ConstValue::NativeUInt(0).is_zero());
assert!(ConstValue::False.is_zero());
assert!(!ConstValue::I32(1).is_zero());
assert!(!ConstValue::True.is_zero());
}
#[test]
fn test_is_one_integer_types() {
assert!(ConstValue::I8(1).is_one());
assert!(ConstValue::I16(1).is_one());
assert!(ConstValue::I32(1).is_one());
assert!(ConstValue::I64(1).is_one());
assert!(ConstValue::U8(1).is_one());
assert!(ConstValue::U16(1).is_one());
assert!(ConstValue::U32(1).is_one());
assert!(ConstValue::U64(1).is_one());
assert!(ConstValue::NativeInt(1).is_one());
assert!(ConstValue::NativeUInt(1).is_one());
assert!(ConstValue::True.is_one());
assert!(!ConstValue::I32(0).is_one());
assert!(!ConstValue::False.is_one());
}
#[test]
fn test_is_minus_one_signed_types() {
assert!(ConstValue::I8(-1).is_minus_one());
assert!(ConstValue::I16(-1).is_minus_one());
assert!(ConstValue::I32(-1).is_minus_one());
assert!(ConstValue::I64(-1).is_minus_one());
assert!(ConstValue::NativeInt(-1).is_minus_one());
assert!(!ConstValue::U8(255).is_minus_one());
assert!(!ConstValue::U32(0xFFFFFFFF).is_minus_one());
}
#[test]
fn test_zero_of_same_type_preserves_type() {
assert_eq!(ConstValue::I8(42).zero_of_same_type(), ConstValue::I8(0));
assert_eq!(ConstValue::I16(42).zero_of_same_type(), ConstValue::I16(0));
assert_eq!(ConstValue::I32(42).zero_of_same_type(), ConstValue::I32(0));
assert_eq!(ConstValue::I64(42).zero_of_same_type(), ConstValue::I64(0));
assert_eq!(ConstValue::U8(42).zero_of_same_type(), ConstValue::U8(0));
assert_eq!(ConstValue::U16(42).zero_of_same_type(), ConstValue::U16(0));
assert_eq!(ConstValue::U32(42).zero_of_same_type(), ConstValue::U32(0));
assert_eq!(ConstValue::U64(42).zero_of_same_type(), ConstValue::U64(0));
assert_eq!(
ConstValue::NativeInt(42).zero_of_same_type(),
ConstValue::NativeInt(0)
);
assert_eq!(
ConstValue::NativeUInt(42).zero_of_same_type(),
ConstValue::NativeUInt(0)
);
assert_eq!(
ConstValue::F32(std::f32::consts::PI).zero_of_same_type(),
ConstValue::F32(0.0)
);
assert_eq!(
ConstValue::F64(std::f64::consts::PI).zero_of_same_type(),
ConstValue::F64(0.0)
);
assert_eq!(ConstValue::Null.zero_of_same_type(), ConstValue::I32(0));
assert_eq!(ConstValue::True.zero_of_same_type(), ConstValue::I32(0));
}
#[test]
fn test_abstract_value_join() {
let top = AbstractValue::Top;
let bottom = AbstractValue::Bottom;
let const_a = AbstractValue::Constant(ConstValue::I32(42));
let const_b = AbstractValue::Constant(ConstValue::I32(42));
let const_c = AbstractValue::Constant(ConstValue::I32(99));
assert_eq!(top.join(&const_a), AbstractValue::Top);
assert_eq!(const_a.join(&top), AbstractValue::Top);
assert_eq!(bottom.join(&const_a), const_a);
assert_eq!(const_a.join(&bottom), const_a);
assert_eq!(const_a.join(&const_b), const_a);
assert_eq!(const_a.join(&const_c), AbstractValue::Top);
}
#[test]
fn test_abstract_value_is_constant() {
let top = AbstractValue::Top;
let bottom = AbstractValue::Bottom;
let const_val = AbstractValue::Constant(ConstValue::I32(42));
assert!(!top.is_constant());
assert!(!bottom.is_constant());
assert!(const_val.is_constant());
}
#[test]
fn test_abstract_value_as_constant() {
let const_val = AbstractValue::Constant(ConstValue::I32(42));
assert_eq!(const_val.as_constant(), Some(&ConstValue::I32(42)));
let top = AbstractValue::Top;
assert_eq!(top.as_constant(), None);
}
#[test]
fn test_as_i64() {
assert_eq!(ConstValue::I8(-100).as_i64(), Some(-100));
assert_eq!(ConstValue::I16(-30000).as_i64(), Some(-30000));
assert_eq!(
ConstValue::I32(-2_000_000_000).as_i64(),
Some(-2_000_000_000)
);
assert_eq!(
ConstValue::I64(-9_000_000_000_000).as_i64(),
Some(-9_000_000_000_000)
);
assert_eq!(ConstValue::NativeInt(42).as_i64(), Some(42));
assert_eq!(ConstValue::U8(200).as_i64(), Some(200));
assert_eq!(ConstValue::U16(60000).as_i64(), Some(60000));
assert_eq!(ConstValue::U32(4_000_000_000).as_i64(), Some(4_000_000_000));
assert_eq!(ConstValue::U64(1_000_000).as_i64(), None);
assert_eq!(ConstValue::True.as_i64(), Some(1));
assert_eq!(ConstValue::False.as_i64(), Some(0));
}
#[test]
fn test_as_u64() {
assert_eq!(ConstValue::U8(200).as_u64(), Some(200));
assert_eq!(ConstValue::U16(60000).as_u64(), Some(60000));
assert_eq!(ConstValue::U32(4_000_000_000).as_u64(), Some(4_000_000_000));
assert_eq!(ConstValue::U64(u64::MAX).as_u64(), Some(u64::MAX));
assert_eq!(ConstValue::I32(100).as_u64(), Some(100));
assert_eq!(ConstValue::I32(-1).as_u64(), None);
}
#[test]
fn test_cross_type_arithmetic() {
let i32_val = ConstValue::I32(10);
let i64_val = ConstValue::I64(20);
assert_eq!(
i32_val.add(&i64_val, PointerSize::Bit64),
Some(ConstValue::I64(30))
);
let u32_val = ConstValue::U32(100);
let u64_val = ConstValue::U64(200);
assert_eq!(
u32_val.add(&u64_val, PointerSize::Bit64),
Some(ConstValue::U64(300))
);
}
#[test]
fn test_incompatible_type_arithmetic() {
let i32_val = ConstValue::I32(10);
let f32_val = ConstValue::F32(std::f32::consts::PI);
assert!(i32_val.add(&f32_val, PointerSize::Bit64).is_none());
let u64_val = ConstValue::U64(100);
let i64_val = ConstValue::I64(200);
assert!(u64_val.add(&i64_val, PointerSize::Bit64).is_none());
}
#[test]
fn test_null_const_value() {
let null = ConstValue::Null;
assert!(null.is_null());
assert!(!null.is_one());
assert!(!null.is_bool());
}
#[test]
fn test_string_const_value() {
let s = ConstValue::String(123);
assert!(!s.is_zero());
assert!(!s.is_one());
}
#[test]
fn test_bool_const_values() {
let t = ConstValue::True;
let f = ConstValue::False;
assert!(t.is_bool());
assert!(f.is_bool());
assert!(t.is_one());
assert!(f.is_zero());
}
}