use super::{Runner, Stop};
use crate::engine::budget::Counter;
use crate::expr::{Context, OpCode};
const MAX_LEAVES: usize = 3;
const MAX_NODES: usize = 32;
const MAX_COMBOS: u64 = 4096;
#[derive(Clone, Copy)]
enum Op {
Leaf(usize),
Const(u64),
Add(usize, usize),
Sub(usize, usize),
Mul(usize, usize),
Neg(usize),
Not(usize),
And(usize, usize),
Or(usize, usize),
Xor(usize, usize),
Shl(usize, u32),
}
pub(crate) struct Residues {
pub(super) leaves: Vec<u32>,
pub(super) mask: u64,
pub(super) values: Vec<u64>,
k: u32,
}
impl Residues {
pub(super) fn leaf(&self, c: usize, i: usize) -> u64 {
(c as u64 >> (self.k * i as u32)) & self.mask
}
}
fn compile(cx: &Context, x: u32) -> Option<(Vec<Op>, Vec<u32>)> {
let mut ops: Vec<Op> = Vec::new();
let mut at: crate::hash::IdMap<u32, usize> = crate::hash::IdMap::default();
let mut leaves: Vec<u32> = Vec::new();
let mut product = false;
let w = cx.wid(x);
let mut stack: Vec<(u32, bool)> = vec![(x, false)];
while let Some((i, done)) = stack.pop() {
if at.contains_key(&i) {
continue;
}
let node = cx.node(i);
let arith = cx.wid(i) == w
&& matches!(
node.op,
OpCode::Add
| OpCode::Sub
| OpCode::Mul
| OpCode::Neg
| OpCode::Not
| OpCode::And
| OpCode::Or
| OpCode::Xor
);
let shift = node.op == OpCode::Shl && cx.const_val(node.b).is_some();
if !done && (arith || shift) {
stack.push((i, true));
for c in node.children() {
if !(shift && c == node.b) {
stack.push((c, false));
}
}
continue;
}
let op = if let Some(v) = cx.const_val(i) {
Op::Const(v.limbs()[0])
} else if shift {
let s = cx.const_val(node.b)?.to_u64().unwrap_or(u64::MAX);
Op::Shl(at[&node.a], s.min(64) as u32)
} else if arith {
let (a, b) = (at[&node.a], node.children().nth(1).map(|c| at[&c]));
match node.op {
OpCode::Add => Op::Add(a, b?),
OpCode::Sub => Op::Sub(a, b?),
OpCode::Mul => {
product = true;
Op::Mul(a, b?)
}
OpCode::Neg => Op::Neg(a),
OpCode::Not => Op::Not(a),
OpCode::And => Op::And(a, b?),
OpCode::Or => Op::Or(a, b?),
_ => Op::Xor(a, b?),
}
} else {
if leaves.len() >= MAX_LEAVES {
return None;
}
leaves.push(i);
Op::Leaf(leaves.len() - 1)
};
ops.push(op);
at.insert(i, ops.len() - 1);
if ops.len() > MAX_NODES {
return None;
}
}
product.then_some((ops, leaves))
}
fn eval(ops: &[Op], k: u32, mask: u64, c: u64, vals: &mut [u64]) -> u64 {
for (j, op) in ops.iter().enumerate() {
vals[j] = match *op {
Op::Leaf(i) => c >> (k * i as u32),
Op::Const(v) => v,
Op::Add(a, b) => vals[a].wrapping_add(vals[b]),
Op::Sub(a, b) => vals[a].wrapping_sub(vals[b]),
Op::Mul(a, b) => vals[a].wrapping_mul(vals[b]),
Op::Neg(a) => vals[a].wrapping_neg(),
Op::Not(a) => !vals[a],
Op::And(a, b) => vals[a] & vals[b],
Op::Or(a, b) => vals[a] | vals[b],
Op::Xor(a, b) => vals[a] ^ vals[b],
Op::Shl(a, s) => {
if s >= 64 {
0
} else {
vals[a] << s
}
}
} & mask;
}
vals[ops.len() - 1]
}
pub(super) fn may_fold(r: &Runner<'_, '_>, cx: &Context, x: u32, k: u32, m: u64) -> bool {
if let Some(t) = r.residues.get(&(x, k)) {
return t.is_some();
}
if k == 0 || k > u32::from(cx.wid(x)) {
return false;
}
let Some((ops, leaves)) = compile(cx, x) else {
return false;
};
let combos = 1u64 << (k as usize * leaves.len());
if leaves.len() * k as usize > 16 || combos > MAX_COMBOS {
return false;
}
let mask = (1u64 << k) - 1;
let mut vals = vec![0u64; ops.len()];
let picks: Vec<u64> = (0..8u64)
.map(|j| j.wrapping_mul(0x9e37_79b9_7f4a_7c15).rotate_left(17) % combos)
.chain([0, combos - 1])
.collect();
let values: Vec<(u64, u64)> = picks
.iter()
.map(|&c| (c, eval(&ops, k, mask, c, &mut vals)))
.collect();
let first = values[0].1 & m;
if values.iter().all(|&(_, v)| v & m == first) {
return true;
}
(0..leaves.len()).any(|i| {
values
.iter()
.all(|&(c, v)| v & m == (c >> (k * i as u32)) & mask & m)
})
}
pub(super) fn residues(
r: &mut Runner<'_, '_>,
cx: &Context,
x: u32,
k: u32,
) -> Result<Option<std::rc::Rc<Residues>>, Stop> {
if let Some(t) = r.residues.get(&(x, k)) {
return Ok(t.clone());
}
let t = table(r, cx, x, k)?.map(std::rc::Rc::new);
r.residues.insert((x, k), t.clone());
Ok(t)
}
fn table(r: &mut Runner<'_, '_>, cx: &Context, x: u32, k: u32) -> Result<Option<Residues>, Stop> {
if k == 0 || k > u32::from(cx.wid(x)) {
return Ok(None);
}
let Some((ops, leaves)) = compile(cx, x) else {
return Ok(None);
};
let combos = 1u64 << (k as usize * leaves.len());
if leaves.len() * k as usize > 16 || combos > MAX_COMBOS {
return Ok(None);
}
r.meter
.charge(Counter::PassWork, combos * ops.len() as u64 / 64 + 1)?;
let mask = (1u64 << k) - 1;
let mut vals = vec![0u64; ops.len()];
let values = (0..combos)
.map(|c| eval(&ops, k, mask, c, &mut vals))
.collect();
Ok(Some(Residues {
leaves,
mask,
values,
k,
}))
}