use crate::{
PointerSize,
ir::{value::ConstValue, variable::SsaVarId},
target::Target,
};
#[derive(Debug, Clone, PartialEq)]
pub enum Constraint<T: Target> {
Equal(ConstValue<T>),
NotEqual(ConstValue<T>),
GreaterThan(ConstValue<T>),
LessThan(ConstValue<T>),
GreaterOrEqual(ConstValue<T>),
LessOrEqual(ConstValue<T>),
GreaterThanUnsigned(ConstValue<T>),
LessThanUnsigned(ConstValue<T>),
GreaterOrEqualUnsigned(ConstValue<T>),
LessOrEqualUnsigned(ConstValue<T>),
}
impl<T: Target> Constraint<T> {
#[must_use]
pub fn is_satisfied_by(&self, value: &ConstValue<T>) -> bool {
match self {
Self::Equal(v) => value.ceq(v).is_some_and(|r| !r.is_zero()),
Self::NotEqual(v) => value.ceq(v).is_some_and(|r| r.is_zero()),
Self::GreaterThan(v) => value.cgt(v).is_some_and(|r| !r.is_zero()),
Self::LessThan(v) => value.clt(v).is_some_and(|r| !r.is_zero()),
Self::GreaterOrEqual(v) => value.clt(v).is_some_and(|r| r.is_zero()),
Self::LessOrEqual(v) => value.cgt(v).is_some_and(|r| r.is_zero()),
Self::GreaterThanUnsigned(v) => value.cgt_un(v).is_some_and(|r| !r.is_zero()),
Self::LessThanUnsigned(v) => value.clt_un(v).is_some_and(|r| !r.is_zero()),
Self::GreaterOrEqualUnsigned(v) => value.clt_un(v).is_some_and(|r| r.is_zero()),
Self::LessOrEqualUnsigned(v) => value.cgt_un(v).is_some_and(|r| r.is_zero()),
}
}
#[must_use]
pub fn as_equal(&self) -> Option<&ConstValue<T>> {
match self {
Self::Equal(v) => Some(v),
_ => None,
}
}
#[must_use]
pub fn conflicts_with(&self, other: &Constraint<T>, ptr_size: PointerSize) -> bool {
match (self, other) {
(Self::Equal(a), Self::Equal(b)) => a.ceq(b).is_some_and(|r| r.is_zero()),
(Self::Equal(a), Self::NotEqual(b)) | (Self::NotEqual(b), Self::Equal(a)) => a == b,
(Self::Equal(a), Self::GreaterThan(b)) | (Self::GreaterThan(b), Self::Equal(a)) => {
a.cgt(b).is_some_and(|r| r.is_zero())
}
(Self::Equal(a), Self::LessThan(b)) | (Self::LessThan(b), Self::Equal(a)) => {
a.clt(b).is_some_and(|r| r.is_zero())
}
(Self::GreaterThan(a), Self::LessThan(b))
| (Self::LessThan(b), Self::GreaterThan(a)) => {
let one: ConstValue<T> = ConstValue::I32(1);
a.add(&one, ptr_size)
.and_then(|a_plus_1| b.cgt(&a_plus_1))
.is_some_and(|r| r.is_zero())
}
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathConstraint<T: Target> {
pub variable: SsaVarId,
pub constraint: Constraint<T>,
}
impl<T: Target> PathConstraint<T> {
#[must_use]
pub fn equal(variable: SsaVarId, value: ConstValue<T>) -> Self {
Self {
variable,
constraint: Constraint::Equal(value),
}
}
#[must_use]
pub fn not_equal(variable: SsaVarId, value: ConstValue<T>) -> Self {
Self {
variable,
constraint: Constraint::NotEqual(value),
}
}
#[must_use]
pub fn less_than(variable: SsaVarId, value: ConstValue<T>) -> Self {
Self {
variable,
constraint: Constraint::LessThan(value),
}
}
#[must_use]
pub fn greater_than(variable: SsaVarId, value: ConstValue<T>) -> Self {
Self {
variable,
constraint: Constraint::GreaterThan(value),
}
}
#[must_use]
pub fn is_satisfied_by(&self, value: &ConstValue<T>) -> bool {
self.constraint.is_satisfied_by(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::MockTarget;
const PTR: PointerSize = PointerSize::Bit64;
fn eq(v: ConstValue<MockTarget>) -> Constraint<MockTarget> {
Constraint::Equal(v)
}
#[test]
fn uncomparable_constants_are_not_a_conflict() {
assert!(
!eq(ConstValue::I32(5))
.conflicts_with(&Constraint::GreaterThan(ConstValue::U32(3)), PTR),
"mixed signedness cannot be compared, so no conflict is proved"
);
assert!(
!eq(ConstValue::I32(5)).conflicts_with(&Constraint::LessThan(ConstValue::U32(9)), PTR),
);
assert!(
!eq(ConstValue::I8(5))
.conflicts_with(&Constraint::GreaterThan(ConstValue::I32(3)), PTR),
);
assert!(
!eq(ConstValue::True).conflicts_with(&Constraint::LessThan(ConstValue::I32(1)), PTR)
);
}
#[test]
fn equal_constraints_compare_numerically_not_structurally() {
assert!(
!eq(ConstValue::I32(5)).conflicts_with(&eq(ConstValue::I64(5)), PTR),
"I32(5) and I64(5) are the same value and must not conflict"
);
assert!(
eq(ConstValue::I32(5)).conflicts_with(&eq(ConstValue::I32(6)), PTR),
"two different values of the same type do conflict"
);
assert!(
!eq(ConstValue::I32(5)).conflicts_with(&eq(ConstValue::U32(5)), PTR),
"uncomparable pair proves nothing either way"
);
}
#[test]
fn unsigned_negations_use_unsigned_ordering() {
let big = ConstValue::<MockTarget>::U32(0xFFFF_FFFF);
let ten = ConstValue::<MockTarget>::U32(10);
assert!(Constraint::GreaterOrEqualUnsigned(ten.clone()).is_satisfied_by(&big));
assert!(!Constraint::<MockTarget>::LessThanUnsigned(ten.clone()).is_satisfied_by(&big));
assert!(Constraint::LessOrEqualUnsigned(ten.clone()).is_satisfied_by(&ConstValue::U32(10)));
assert!(!Constraint::LessOrEqualUnsigned(ten).is_satisfied_by(&big));
}
#[test]
fn provable_conflicts_are_still_reported() {
assert!(
eq(ConstValue::I32(5)).conflicts_with(&Constraint::NotEqual(ConstValue::I32(5)), PTR)
);
assert!(
eq(ConstValue::I32(5))
.conflicts_with(&Constraint::GreaterThan(ConstValue::I32(10)), PTR)
);
assert!(
eq(ConstValue::I32(20)).conflicts_with(&Constraint::LessThan(ConstValue::I32(10)), PTR)
);
assert!(
Constraint::<MockTarget>::GreaterThan(ConstValue::I32(5))
.conflicts_with(&Constraint::LessThan(ConstValue::I32(6)), PTR)
);
assert!(
!Constraint::<MockTarget>::GreaterThan(ConstValue::I32(5))
.conflicts_with(&Constraint::LessThan(ConstValue::I32(100)), PTR)
);
}
}