use crate::circuit::classical::ClassicalType;
use crate::circuit::classical_expr::expr::{
ClassicalBinaryOp, ClassicalCast, ClassicalCompareOp, ClassicalExpr, ClassicalExprKind,
ClassicalUnaryOp,
};
pub fn simplify(expr: &ClassicalExpr) -> ClassicalExpr {
simplify_node(expr)
}
fn simplify_node(expr: &ClassicalExpr) -> ClassicalExpr {
match expr.kind() {
ClassicalExprKind::Var(_)
| ClassicalExprKind::Value(_)
| ClassicalExprKind::BoolLiteral(_)
| ClassicalExprKind::BitLiteral(_)
| ClassicalExprKind::UIntLiteral { .. }
| ClassicalExprKind::BitVecLiteral { .. } => expr.clone(),
ClassicalExprKind::Unary { op, expr: inner } => {
let inner = simplify_node(inner);
simplify_unary(*op, &inner).unwrap_or_else(|| {
ClassicalExpr::from_kind(
inner.ty(),
ClassicalExprKind::Unary {
op: *op,
expr: inner,
},
)
})
}
ClassicalExprKind::Binary { op, lhs, rhs } => {
let lhs = simplify_node(lhs);
let rhs = simplify_node(rhs);
simplify_binary(*op, &lhs, &rhs).unwrap_or_else(|| {
let ty = lhs.ty();
ClassicalExpr::from_kind(ty, ClassicalExprKind::Binary { op: *op, lhs, rhs })
})
}
ClassicalExprKind::Compare { op, lhs, rhs } => {
let lhs = simplify_node(lhs);
let rhs = simplify_node(rhs);
simplify_compare(*op, &lhs, &rhs).unwrap_or_else(|| {
ClassicalExpr::from_kind(
ClassicalType::Bool,
ClassicalExprKind::Compare { op: *op, lhs, rhs },
)
})
}
ClassicalExprKind::Cast { cast, expr: inner } => {
let inner = simplify_node(inner);
simplify_cast(*cast, &inner).unwrap_or_else(|| {
let ty = match cast {
ClassicalCast::BitToBool => ClassicalType::Bool,
ClassicalCast::BitVecToUInt => {
if let ClassicalType::BitVec(width) = inner.ty() {
ClassicalType::UInt(width)
} else {
ClassicalType::Bool
}
}
};
ClassicalExpr::from_kind(
ty,
ClassicalExprKind::Cast {
cast: *cast,
expr: inner,
},
)
})
}
ClassicalExprKind::Select {
condition,
then_expr,
else_expr,
} => {
let condition = simplify_node(condition);
if let Some(result) = simplify_select(&condition, then_expr, else_expr) {
return result;
}
let then_expr = simplify_node(then_expr);
let else_expr = simplify_node(else_expr);
ClassicalExpr::from_kind(
then_expr.ty(),
ClassicalExprKind::Select {
condition,
then_expr,
else_expr,
},
)
}
ClassicalExprKind::ExtractBit { value, index } => {
let value = simplify_node(value);
ClassicalExpr::from_kind(
ClassicalType::Bit,
ClassicalExprKind::ExtractBit {
value,
index: *index,
},
)
}
ClassicalExprKind::ExtractBits {
value,
offset,
width,
} => {
let value = simplify_node(value);
ClassicalExpr::from_kind(
ClassicalType::BitVec(*width),
ClassicalExprKind::ExtractBits {
value,
offset: *offset,
width: *width,
},
)
}
ClassicalExprKind::Concat { parts } => {
let mut changed = false;
let simplified: Vec<ClassicalExpr> = parts
.iter()
.map(|part| {
let s = simplify_node(part);
if !changed && &s != part {
changed = true;
}
s
})
.collect();
if !changed {
return expr.clone();
}
let total_width = simplified
.iter()
.fold(0u32, |acc, part| acc.saturating_add(part.ty().width()));
let Some(width) = std::num::NonZeroU32::new(total_width) else {
return expr.clone();
};
ClassicalExpr::from_kind(
ClassicalType::BitVec(width),
ClassicalExprKind::Concat {
parts: simplified.into_boxed_slice(),
},
)
}
ClassicalExprKind::PackBits { bits } => {
let mut changed = false;
let simplified: Vec<ClassicalExpr> = bits
.iter()
.map(|bit| {
let s = simplify_node(bit);
if !changed && &s != bit {
changed = true;
}
s
})
.collect();
if !changed {
return expr.clone();
}
let Some(width) = std::num::NonZeroU32::new(simplified.len() as u32) else {
return expr.clone();
};
ClassicalExpr::from_kind(
ClassicalType::BitVec(width),
ClassicalExprKind::PackBits {
bits: simplified.into_boxed_slice(),
},
)
}
}
}
fn simplify_unary(op: ClassicalUnaryOp, child: &ClassicalExpr) -> Option<ClassicalExpr> {
if op != ClassicalUnaryOp::Not {
return None;
}
match child.kind() {
ClassicalExprKind::Unary {
op: ClassicalUnaryOp::Not,
expr: grandchild,
} => Some(grandchild.clone()),
_ => None,
}
}
fn simplify_binary(
op: ClassicalBinaryOp,
lhs: &ClassicalExpr,
rhs: &ClassicalExpr,
) -> Option<ClassicalExpr> {
let ty = lhs.ty();
match op {
ClassicalBinaryOp::And => {
if rhs.is_bool_true() || rhs.is_bit_true() {
return Some(lhs.clone());
}
if lhs.is_bool_true() || lhs.is_bit_true() {
return Some(rhs.clone());
}
}
ClassicalBinaryOp::Or => {
if rhs.is_bool_false() || rhs.is_bit_false() {
return Some(lhs.clone());
}
if lhs.is_bool_false() || lhs.is_bit_false() {
return Some(rhs.clone());
}
}
ClassicalBinaryOp::Xor => {
if rhs.is_bool_false() || rhs.is_bit_false() {
return Some(lhs.clone());
}
if lhs.is_bool_false() || lhs.is_bit_false() {
return Some(rhs.clone());
}
}
}
if lhs == rhs {
match op {
ClassicalBinaryOp::And | ClassicalBinaryOp::Or => {
return Some(lhs.clone());
}
ClassicalBinaryOp::Xor => {
return ty.zero_literal().ok();
}
}
}
for (a, b) in [(lhs, rhs), (rhs, lhs)] {
if let ClassicalExprKind::Unary {
op: ClassicalUnaryOp::Not,
expr: x,
} = b.kind()
&& a == x
{
return match op {
ClassicalBinaryOp::And => ty.zero_literal(),
ClassicalBinaryOp::Or | ClassicalBinaryOp::Xor => ty.one_literal(),
}
.ok();
}
}
None
}
fn simplify_compare(
op: ClassicalCompareOp,
lhs: &ClassicalExpr,
rhs: &ClassicalExpr,
) -> Option<ClassicalExpr> {
if lhs != rhs {
return None;
}
match op {
ClassicalCompareOp::Eq | ClassicalCompareOp::Le | ClassicalCompareOp::Ge => {
Some(ClassicalExpr::bool_literal(true))
}
ClassicalCompareOp::Ne | ClassicalCompareOp::Lt | ClassicalCompareOp::Gt => {
Some(ClassicalExpr::bool_literal(false))
}
}
}
fn simplify_select(
condition: &ClassicalExpr,
then_expr: &ClassicalExpr,
else_expr: &ClassicalExpr,
) -> Option<ClassicalExpr> {
match condition.kind() {
ClassicalExprKind::BoolLiteral(true) => Some(simplify_node(then_expr)),
ClassicalExprKind::BoolLiteral(false) => Some(simplify_node(else_expr)),
_ => None,
}
}
fn simplify_cast(cast: ClassicalCast, child: &ClassicalExpr) -> Option<ClassicalExpr> {
match (cast, child.kind()) {
(ClassicalCast::BitToBool, ClassicalExprKind::BitLiteral(v)) => {
Some(ClassicalExpr::bool_literal(*v))
}
(ClassicalCast::BitVecToUInt, ClassicalExprKind::BitVecLiteral { width, value }) => Some(
ClassicalExpr::uint_literal(width.get(), *value)
.expect("bit-vec literal value must be valid for uint of same width"),
),
_ => None,
}
}
#[cfg(test)]
#[path = "simplify_test.rs"]
mod simplify_test;