use core::hash::{Hash, Hasher};
use crate::ops::{BinOp, UnOp};
use crate::{BitVec, Width};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MOp {
Const(BitVec),
Var(u32),
Add,
Sub,
Mul,
Neg,
And,
Or,
Xor,
Not,
Shl(u16),
LShr(u16),
Zext,
Sext,
Trunc,
}
impl MOp {
pub fn arity(&self) -> usize {
match self {
MOp::Const(_) | MOp::Var(_) => 0,
MOp::Neg
| MOp::Not
| MOp::Shl(_)
| MOp::LShr(_)
| MOp::Zext
| MOp::Sext
| MOp::Trunc => 1,
_ => 2,
}
}
fn arith(&self) -> bool {
matches!(self, MOp::Add | MOp::Sub | MOp::Mul | MOp::Neg)
}
fn bitwise(&self) -> bool {
matches!(self, MOp::And | MOp::Or | MOp::Xor)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct MNode {
pub op: MOp,
pub width: Width,
pub args: [u32; 2],
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MbaError {
BadOperand,
Width,
UnknownVar,
}
impl core::fmt::Display for MbaError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
MbaError::BadOperand => "an operand is not an earlier node",
MbaError::Width => "operand widths do not fit the operator",
MbaError::UnknownVar => "unknown variable",
})
}
}
impl std::error::Error for MbaError {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MbaExpr {
vars: Vec<Width>,
nodes: Vec<MNode>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Shape {
pub vars: u32,
pub nodes: u32,
pub height: u32,
pub mixed: bool,
pub degree: u32,
}
impl MbaExpr {
pub fn new(vars: Vec<Width>) -> MbaExpr {
MbaExpr {
vars,
nodes: Vec::new(),
}
}
pub fn vars(&self) -> &[Width] {
&self.vars
}
pub fn nodes(&self) -> &[MNode] {
&self.nodes
}
pub fn root(&self) -> Option<u32> {
self.nodes.len().checked_sub(1).map(|r| r as u32)
}
pub fn width(&self) -> Option<Width> {
self.nodes.last().map(|n| n.width)
}
pub fn push(&mut self, op: MOp, args: &[u32]) -> Result<u32, MbaError> {
let w = match op {
MOp::Const(v) => v.width(),
MOp::Var(i) => *self.vars.get(i as usize).ok_or(MbaError::UnknownVar)?,
MOp::Zext | MOp::Sext | MOp::Trunc => return Err(MbaError::Width),
_ => {
let a = self.arg(args, 0)?;
if op.arity() == 2 && self.arg(args, 1)? != a {
return Err(MbaError::Width);
}
if let MOp::Shl(k) | MOp::LShr(k) = op
&& k >= a.bits()
{
return Err(MbaError::Width);
}
a
}
};
self.add(op, w, args)
}
pub fn push_cast(&mut self, op: MOp, a: u32, width: Width) -> Result<u32, MbaError> {
let wa = self.arg(&[a], 0)?;
let ok = match op {
MOp::Zext | MOp::Sext => width > wa,
MOp::Trunc => width < wa,
_ => false,
};
if !ok {
return Err(MbaError::Width);
}
self.add(op, width, &[a])
}
fn arg(&self, args: &[u32], k: usize) -> Result<Width, MbaError> {
let i = *args.get(k).ok_or(MbaError::BadOperand)?;
self.nodes
.get(i as usize)
.map(|n| n.width)
.ok_or(MbaError::BadOperand)
}
fn add(&mut self, op: MOp, width: Width, args: &[u32]) -> Result<u32, MbaError> {
let mut a = [0u32; 2];
for (k, &x) in args.iter().take(op.arity()).enumerate() {
if x as usize >= self.nodes.len() {
return Err(MbaError::BadOperand);
}
a[k] = x;
}
self.nodes.push(MNode { op, width, args: a });
Ok(self.nodes.len() as u32 - 1)
}
pub fn eval(&self, vals: &[BitVec]) -> Option<BitVec> {
let mut v: Vec<BitVec> = Vec::with_capacity(self.nodes.len());
for n in &self.nodes {
let a = |k: usize| v[n.args[k] as usize];
let r = match n.op {
MOp::Const(c) => c,
MOp::Var(i) => {
let x = *vals.get(i as usize)?;
if x.width() != n.width {
return None;
}
x
}
MOp::Add => BitVec::bin_unchecked(BinOp::Add, &a(0), &a(1)),
MOp::Sub => BitVec::bin_unchecked(BinOp::Sub, &a(0), &a(1)),
MOp::Mul => BitVec::bin_unchecked(BinOp::Mul, &a(0), &a(1)),
MOp::And => BitVec::bin_unchecked(BinOp::And, &a(0), &a(1)),
MOp::Or => BitVec::bin_unchecked(BinOp::Or, &a(0), &a(1)),
MOp::Xor => BitVec::bin_unchecked(BinOp::Xor, &a(0), &a(1)),
MOp::Neg => BitVec::un_unchecked(UnOp::Neg, &a(0)),
MOp::Not => BitVec::un_unchecked(UnOp::Not, &a(0)),
MOp::Shl(k) | MOp::LShr(k) => {
let kv = BitVec::wrapping_from_u64(n.width, u64::from(k));
let op = if matches!(n.op, MOp::Shl(_)) {
BinOp::Shl
} else {
BinOp::LShr
};
BitVec::bin_unchecked(op, &a(0), &kv)
}
MOp::Zext => a(0).zext(n.width).ok()?,
MOp::Sext => a(0).sext(n.width).ok()?,
MOp::Trunc => a(0).trunc(n.width).ok()?,
};
v.push(r);
}
v.last().copied()
}
pub fn key(&self) -> [u64; 2] {
struct H(u64, u64);
impl Hasher for H {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = crate::hash::combine(self.0, u64::from(b));
self.1 = crate::hash::combine(self.1 ^ 0x5555, u64::from(b).rotate_left(29));
}
}
}
let mut h = H(0x6d62_615f_6578_7072, 0x6269_7477_7269_6768);
self.hash(&mut h);
[h.0, h.1]
}
pub fn shape(&self) -> Shape {
let mut height = vec![0u32; self.nodes.len()];
let mut degree = vec![0u32; self.nodes.len()];
let mut vars_used = vec![false; self.vars.len()];
let (mut arith, mut bit) = (false, false);
for (i, n) in self.nodes.iter().enumerate() {
let kids = &n.args[..n.op.arity()];
height[i] = kids
.iter()
.map(|&k| height[k as usize] + 1)
.max()
.unwrap_or(0);
arith |= n.op.arith();
bit |= n.op.bitwise();
degree[i] = match n.op {
MOp::Const(_) => 0,
MOp::Var(v) => {
if let Some(u) = vars_used.get_mut(v as usize) {
*u = true;
}
1
}
MOp::Mul => degree[n.args[0] as usize] + degree[n.args[1] as usize],
_ => kids.iter().map(|&k| degree[k as usize]).max().unwrap_or(0),
};
}
Shape {
vars: vars_used.iter().filter(|&&u| u).count() as u32,
nodes: self.nodes.len() as u32,
height: height.last().copied().unwrap_or(0),
mixed: arith && bit,
degree: degree.last().copied().unwrap_or(0),
}
}
pub fn is_linear(&self) -> bool {
let mut class = vec![0u8; self.nodes.len()];
let mut uniform_const = vec![false; self.nodes.len()];
for (i, n) in self.nodes.iter().enumerate() {
let c = |k: usize| class[n.args[k] as usize];
let u = |k: usize| uniform_const[n.args[k] as usize];
class[i] = match n.op {
MOp::Const(v) => {
uniform_const[i] = v.is_zero() || v.is_ones();
0
}
MOp::Var(_) => 1,
MOp::And | MOp::Or | MOp::Xor => {
let ok = (0..2).all(|k| c(k) == 1 || (c(k) == 0 && u(k)));
if !ok {
return false;
}
1
}
MOp::Not => {
if c(0) == 0 {
uniform_const[i] = u(0);
}
c(0)
}
MOp::Add | MOp::Sub => {
if c(0) == 0 && c(1) == 0 {
0
} else {
2
}
}
MOp::Neg => c(0).max(if c(0) == 0 { 0 } else { 2 }),
MOp::Mul => {
if c(0) != 0 && c(1) != 0 {
return false;
}
if c(0) == 0 && c(1) == 0 { 0 } else { 2 }
}
_ => return false,
};
}
true
}
pub(crate) fn corners(&self) -> Option<Vec<BitVec>> {
let t = self.vars.len();
if t > 16 {
return None;
}
(0..1usize << t)
.map(|p| {
let vals: Vec<BitVec> = self
.vars
.iter()
.enumerate()
.map(|(j, &w)| {
if p >> j & 1 == 1 {
BitVec::ones(w)
} else {
BitVec::zero(w)
}
})
.collect();
self.eval(&vals)
})
.collect()
}
}