use std::collections::BTreeMap;
use crate::{
ir::{ops::SsaOp, value::ConstValue, variable::SsaVarId},
target::Target,
};
#[derive(Debug, Clone, PartialEq)]
pub enum SimplifyResult<T: Target> {
Constant(ConstValue<T>),
Copy(SsaVarId),
None,
}
impl<T: Target> SimplifyResult<T> {
#[must_use]
pub fn is_some(&self) -> bool {
!matches!(self, Self::None)
}
#[must_use]
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
#[must_use]
pub fn simplify_op<T: Target>(
op: &SsaOp<T>,
constants: &BTreeMap<SsaVarId, ConstValue<T>>,
operand_type: Option<&T::Type>,
) -> SimplifyResult<T> {
let self_identity_is_sound =
operand_type.is_some_and(|ty| !T::is_floating(ty) && !T::is_unknown(ty));
let typed_zero = |value: i64| -> Option<ConstValue<T>> {
let ty = operand_type?;
let width = T::bit_width(ty)?;
match width {
8 => Some(ConstValue::I8(i8::try_from(value).ok()?)),
16 => Some(ConstValue::I16(i16::try_from(value).ok()?)),
64 => Some(ConstValue::I64(value)),
_ => Some(ConstValue::I32(i32::try_from(value).ok()?)),
}
};
match op {
SsaOp::Xor { left, right, .. } => {
if left == right
&& self_identity_is_sound
&& let Some(zero) = typed_zero(0)
{
return SimplifyResult::Constant(zero);
}
if constants.get(right).is_some_and(ConstValue::is_zero) {
return SimplifyResult::Copy(*left);
}
if constants.get(left).is_some_and(ConstValue::is_zero) {
return SimplifyResult::Copy(*right);
}
SimplifyResult::None
}
SsaOp::Or { left, right, .. } => {
if left == right {
return SimplifyResult::Copy(*left);
}
if let Some(c) = constants.get(right) {
if c.is_zero() {
return SimplifyResult::Copy(*left);
}
if c.is_all_ones() {
return SimplifyResult::Constant(c.clone());
}
}
if let Some(c) = constants.get(left) {
if c.is_zero() {
return SimplifyResult::Copy(*right);
}
if c.is_all_ones() {
return SimplifyResult::Constant(c.clone());
}
}
SimplifyResult::None
}
SsaOp::And { left, right, .. } => {
if left == right {
return SimplifyResult::Copy(*left);
}
if let Some(c) = constants.get(right) {
if c.is_zero() {
return SimplifyResult::Constant(c.zero_of_same_type());
}
if c.is_all_ones() {
return SimplifyResult::Copy(*left);
}
}
if let Some(c) = constants.get(left) {
if c.is_zero() {
return SimplifyResult::Constant(c.zero_of_same_type());
}
if c.is_all_ones() {
return SimplifyResult::Copy(*right);
}
}
SimplifyResult::None
}
SsaOp::Add { left, right, .. } => {
if constants.get(right).is_some_and(ConstValue::is_zero) {
return SimplifyResult::Copy(*left);
}
if constants.get(left).is_some_and(ConstValue::is_zero) {
return SimplifyResult::Copy(*right);
}
SimplifyResult::None
}
SsaOp::Sub { left, right, .. } => {
if left == right
&& self_identity_is_sound
&& let Some(zero) = typed_zero(0)
{
return SimplifyResult::Constant(zero);
}
if constants.get(right).is_some_and(ConstValue::is_zero) {
return SimplifyResult::Copy(*left);
}
SimplifyResult::None
}
SsaOp::Mul { left, right, .. } => {
if let Some(c) = constants.get(right) {
if c.is_zero() {
return SimplifyResult::Constant(c.clone());
}
if c.is_one() {
return SimplifyResult::Copy(*left);
}
}
if let Some(c) = constants.get(left) {
if c.is_zero() {
return SimplifyResult::Constant(c.clone());
}
if c.is_one() {
return SimplifyResult::Copy(*right);
}
}
SimplifyResult::None
}
SsaOp::Div { left, right, .. } => {
if constants.get(right).is_some_and(ConstValue::is_one) {
return SimplifyResult::Copy(*left);
}
if let Some(c) = constants.get(left)
&& c.is_zero()
{
return SimplifyResult::Constant(c.clone());
}
SimplifyResult::None
}
SsaOp::Rem { left, right, .. } => {
if let Some(c) = constants.get(left)
&& c.is_zero()
{
return SimplifyResult::Constant(c.clone());
}
if let Some(c) = constants.get(right)
&& c.is_one()
{
return SimplifyResult::Constant(c.zero_of_same_type());
}
SimplifyResult::None
}
SsaOp::Shl { value, amount, .. }
| SsaOp::Shr { value, amount, .. }
| SsaOp::Rol { value, amount, .. }
| SsaOp::Ror { value, amount, .. }
| SsaOp::Rcl { value, amount, .. }
| SsaOp::Rcr { value, amount, .. } => {
if constants.get(amount).is_some_and(ConstValue::is_zero) {
return SimplifyResult::Copy(*value);
}
SimplifyResult::None
}
SsaOp::Ceq { left, right, .. } => {
if left == right && self_identity_is_sound {
return SimplifyResult::Constant(ConstValue::I32(1));
}
SimplifyResult::None
}
SsaOp::Clt { left, right, .. } | SsaOp::Cgt { left, right, .. } => {
if left == right && self_identity_is_sound {
return SimplifyResult::Constant(ConstValue::I32(0));
}
SimplifyResult::None
}
_ => SimplifyResult::None,
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::testing::{MockTarget, MockType};
fn make_constants(
pairs: &[(SsaVarId, ConstValue<MockTarget>)],
) -> BTreeMap<SsaVarId, ConstValue<MockTarget>> {
pairs.iter().cloned().collect()
}
#[test]
fn xor_self_cancels() {
let v1 = SsaVarId::from_index(0);
let dest = SsaVarId::from_index(1);
let op: SsaOp<MockTarget> = SsaOp::Xor {
dest,
left: v1,
right: v1,
flags: None,
};
assert_eq!(
simplify_op(&op, &BTreeMap::new(), Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn xor_zero_identity() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Xor {
dest,
left: v1,
right: v2,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Copy(v1)
);
}
#[test]
fn mul_zero_absorbs() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Mul {
dest,
left: v1,
right: v2,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn mul_one_identity() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Mul {
dest,
left: v1,
right: v2,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(1))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Copy(v1)
);
}
#[test]
fn add_zero_identity() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Add {
dest,
left: v1,
right: v2,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Copy(v1)
);
}
#[test]
fn sub_self_cancels() {
let v1 = SsaVarId::from_index(0);
let dest = SsaVarId::from_index(1);
let op: SsaOp<MockTarget> = SsaOp::Sub {
dest,
left: v1,
right: v1,
flags: None,
};
assert_eq!(
simplify_op(&op, &BTreeMap::new(), Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn and_zero_absorbs() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::And {
dest,
left: v1,
right: v2,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn or_zero_identity() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Or {
dest,
left: v1,
right: v2,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Copy(v1)
);
}
#[test]
fn div_one_identity() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Div {
dest,
left: v1,
right: v2,
unsigned: false,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(1))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Copy(v1)
);
}
#[test]
fn shl_zero_identity() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Shl {
dest,
value: v1,
amount: v2,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Copy(v1)
);
}
#[test]
fn no_simplification() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Add {
dest,
left: v1,
right: v2,
flags: None,
};
assert_eq!(
simplify_op(&op, &BTreeMap::new(), Some(&MockType::I32)),
SimplifyResult::None
);
}
#[test]
fn rem_one_zero() {
let v1 = SsaVarId::from_index(0);
let v2 = SsaVarId::from_index(1);
let dest = SsaVarId::from_index(2);
let op: SsaOp<MockTarget> = SsaOp::Rem {
dest,
left: v1,
right: v2,
unsigned: false,
flags: None,
};
let constants = make_constants(&[(v2, ConstValue::I32(1))]);
assert_eq!(
simplify_op(&op, &constants, Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn ceq_self_true() {
let v1 = SsaVarId::from_index(0);
let dest = SsaVarId::from_index(1);
let op: SsaOp<MockTarget> = SsaOp::Ceq {
dest,
left: v1,
right: v1,
};
assert_eq!(
simplify_op(&op, &BTreeMap::new(), Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(1))
);
}
#[test]
fn clt_self_false() {
let v1 = SsaVarId::from_index(0);
let dest = SsaVarId::from_index(1);
let op: SsaOp<MockTarget> = SsaOp::Clt {
dest,
left: v1,
right: v1,
unsigned: false,
};
assert_eq!(
simplify_op(&op, &BTreeMap::new(), Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn cgt_self_false() {
let v1 = SsaVarId::from_index(0);
let dest = SsaVarId::from_index(1);
let op: SsaOp<MockTarget> = SsaOp::Cgt {
dest,
left: v1,
right: v1,
unsigned: false,
};
assert_eq!(
simplify_op(&op, &BTreeMap::new(), Some(&MockType::I32)),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
}