use std::{collections::HashMap, hash::BuildHasher};
use crate::analysis::{ConstValue, SsaOp, SsaVarId};
#[derive(Debug, Clone, PartialEq)]
pub enum SimplifyResult {
Constant(ConstValue),
Copy(SsaVarId),
None,
}
impl SimplifyResult {
#[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<S: BuildHasher>(
op: &SsaOp,
constants: &HashMap<SsaVarId, ConstValue, S>,
) -> SimplifyResult {
match op {
SsaOp::Xor { left, right, .. } => {
if left == right {
return SimplifyResult::Constant(ConstValue::I32(0));
}
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 {
return SimplifyResult::Constant(ConstValue::I32(0));
}
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) {
if c.is_zero() {
return SimplifyResult::Constant(c.clone());
}
}
SimplifyResult::None
}
SsaOp::Rem { left, right, .. } => {
if let Some(c) = constants.get(left) {
if c.is_zero() {
return SimplifyResult::Constant(c.clone());
}
}
if let Some(c) = constants.get(right) {
if c.is_one() {
return SimplifyResult::Constant(c.zero_of_same_type());
}
}
SimplifyResult::None
}
SsaOp::Shl { value, amount, .. } | SsaOp::Shr { 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 {
return SimplifyResult::Constant(ConstValue::I32(1));
}
SimplifyResult::None
}
SsaOp::Clt { left, right, .. } | SsaOp::Cgt { left, right, .. } => {
if left == right {
return SimplifyResult::Constant(ConstValue::I32(0));
}
SimplifyResult::None
}
_ => SimplifyResult::None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_constants(pairs: &[(SsaVarId, ConstValue)]) -> HashMap<SsaVarId, ConstValue> {
pairs.iter().cloned().collect()
}
#[test]
fn test_xor_self_cancels() {
let v1 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Xor {
dest,
left: v1,
right: v1,
};
assert_eq!(
simplify_op(&op, &HashMap::new()),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn test_xor_zero_identity() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Xor {
dest,
left: v1,
right: v2,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(simplify_op(&op, &constants), SimplifyResult::Copy(v1));
}
#[test]
fn test_mul_zero_absorbs() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Mul {
dest,
left: v1,
right: v2,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn test_mul_one_identity() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Mul {
dest,
left: v1,
right: v2,
};
let constants = make_constants(&[(v2, ConstValue::I32(1))]);
assert_eq!(simplify_op(&op, &constants), SimplifyResult::Copy(v1));
}
#[test]
fn test_add_zero_identity() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Add {
dest,
left: v1,
right: v2,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(simplify_op(&op, &constants), SimplifyResult::Copy(v1));
}
#[test]
fn test_sub_self_cancels() {
let v1 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Sub {
dest,
left: v1,
right: v1,
};
assert_eq!(
simplify_op(&op, &HashMap::new()),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn test_and_zero_absorbs() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::And {
dest,
left: v1,
right: v2,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(
simplify_op(&op, &constants),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn test_or_zero_identity() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Or {
dest,
left: v1,
right: v2,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(simplify_op(&op, &constants), SimplifyResult::Copy(v1));
}
#[test]
fn test_div_one_identity() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Div {
dest,
left: v1,
right: v2,
unsigned: false,
};
let constants = make_constants(&[(v2, ConstValue::I32(1))]);
assert_eq!(simplify_op(&op, &constants), SimplifyResult::Copy(v1));
}
#[test]
fn test_shl_zero_identity() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Shl {
dest,
value: v1,
amount: v2,
};
let constants = make_constants(&[(v2, ConstValue::I32(0))]);
assert_eq!(simplify_op(&op, &constants), SimplifyResult::Copy(v1));
}
#[test]
fn test_no_simplification() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Add {
dest,
left: v1,
right: v2,
};
assert_eq!(simplify_op(&op, &HashMap::new()), SimplifyResult::None);
}
#[test]
fn test_rem_one_zero() {
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Rem {
dest,
left: v1,
right: v2,
unsigned: false,
};
let constants = make_constants(&[(v2, ConstValue::I32(1))]);
assert_eq!(
simplify_op(&op, &constants),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn test_ceq_self_true() {
let v1 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Ceq {
dest,
left: v1,
right: v1,
};
assert_eq!(
simplify_op(&op, &HashMap::new()),
SimplifyResult::Constant(ConstValue::I32(1))
);
}
#[test]
fn test_clt_self_false() {
let v1 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Clt {
dest,
left: v1,
right: v1,
unsigned: false,
};
assert_eq!(
simplify_op(&op, &HashMap::new()),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
#[test]
fn test_cgt_self_false() {
let v1 = SsaVarId::new();
let dest = SsaVarId::new();
let op = SsaOp::Cgt {
dest,
left: v1,
right: v1,
unsigned: false,
};
assert_eq!(
simplify_op(&op, &HashMap::new()),
SimplifyResult::Constant(ConstValue::I32(0))
);
}
}