use crate::aig::{Aig, Lit, Word};
fn stage_count(a: &Word, b: &Word) -> usize {
let w = a.len();
debug_assert_eq!(w, b.len(), "shift operands must share a width");
debug_assert!(w.is_power_of_two(), "shift width must be a power of two");
w.trailing_zeros() as usize
}
fn out_of_range(aig: &mut Aig, b: &Word, stages: usize) -> Lit {
b[stages..]
.iter()
.fold(Lit::FALSE, |acc, &bit| aig.or(acc, bit))
}
fn barrel_right(aig: &mut Aig, a: &Word, b: &Word, fill: Lit) -> Word {
let w = a.len();
let stages = stage_count(a, b);
let mut cur = a.clone();
for (k, &sel) in b.iter().enumerate().take(stages) {
let s = 1usize << k;
cur = (0..w)
.map(|i| {
let shifted = if i + s < w { cur[i + s] } else { fill };
aig.mux(sel, shifted, cur[i])
})
.collect();
}
let oor = out_of_range(aig, b, stages);
cur.iter().map(|&bit| aig.mux(oor, fill, bit)).collect()
}
pub fn blast_shl(aig: &mut Aig, a: &Word, b: &Word) -> Word {
let w = a.len();
let stages = stage_count(a, b);
let mut cur = a.clone();
for (k, &sel) in b.iter().enumerate().take(stages) {
let s = 1usize << k;
cur = (0..w)
.map(|i| {
let shifted = if i >= s { cur[i - s] } else { Lit::FALSE };
aig.mux(sel, shifted, cur[i])
})
.collect();
}
let oor = out_of_range(aig, b, stages);
cur.iter()
.map(|&bit| aig.mux(oor, Lit::FALSE, bit))
.collect()
}
pub fn blast_lshr(aig: &mut Aig, a: &Word, b: &Word) -> Word {
barrel_right(aig, a, b, Lit::FALSE)
}
pub fn blast_ashr(aig: &mut Aig, a: &Word, b: &Word) -> Word {
let sign = *a.last().expect("ashr operand must be non-empty");
barrel_right(aig, a, b, sign)
}
pub fn blast_rotr(aig: &mut Aig, a: &Word, b: &Word) -> Word {
let w = a.len();
let stages = stage_count(a, b);
let mut cur = a.clone();
for (k, &sel) in b.iter().enumerate().take(stages) {
let s = 1usize << k;
cur = (0..w)
.map(|i| aig.mux(sel, cur[(i + s) % w], cur[i]))
.collect();
}
cur
}
#[cfg(test)]
mod tests {
use super::*;
use crate::aig::{word_input, word_value};
use crate::eval::{Env, eval_bv};
use crate::term::{BvTerm, Sort};
type Ctor = fn(Box<BvTerm>, Box<BvTerm>) -> BvTerm;
fn oracle(op: Ctor, a: u128, b: u128, w: u32) -> u128 {
let c = |value: u128| {
Box::new(BvTerm::Const {
value,
sort: Sort::new(w),
})
};
eval_bv(&op(c(a), c(b)), &Env::new()).expect("oracle eval")
}
fn blast_all(w: u32) -> (Aig, [(&'static str, Ctor, Word); 4]) {
let mut aig = Aig::new();
let a = word_input(&mut aig, w);
let b = word_input(&mut aig, w);
let ops: [(&'static str, Ctor, Word); 4] = [
("shl", BvTerm::Shl, blast_shl(&mut aig, &a, &b)),
("lshr", BvTerm::Lshr, blast_lshr(&mut aig, &a, &b)),
("ashr", BvTerm::Ashr, blast_ashr(&mut aig, &a, &b)),
("rotr", BvTerm::Rotr, blast_rotr(&mut aig, &a, &b)),
];
(aig, ops)
}
fn check(aig: &Aig, ops: &[(&'static str, Ctor, Word); 4], a: u128, b: u128, w: u32) {
let inputs: Vec<bool> = (0..w)
.map(|i| (a >> i) & 1 == 1)
.chain((0..w).map(|i| (b >> i) & 1 == 1))
.collect();
let values = aig.simulate(&inputs);
for (name, ctor, word) in ops {
let got = word_value(aig, &values, word);
let want = oracle(*ctor, a, b, w);
assert_eq!(got, want, "{name} w={w} a={a:#x} b={b:#x}");
}
}
#[test]
fn exhaustive_width_8() {
let (aig, ops) = blast_all(8);
for a in 0u128..256 {
for b in 0u128..256 {
check(&aig, &ops, a, b, 8);
}
}
}
struct XorShift64(u64);
impl XorShift64 {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn next_u128(&mut self) -> u128 {
((self.next() as u128) << 64) | self.next() as u128
}
}
fn randomized(w: u32, seed: u64) {
let (aig, ops) = blast_all(w);
let mask = (1u128 << w) - 1;
let mut rng = XorShift64(seed);
for case in 0..200u32 {
let a = rng.next_u128() & mask;
let raw = rng.next_u128() & mask;
let b = match case % 4 {
0 | 1 => raw % w as u128,
2 => w as u128,
_ => (raw | (w as u128 + 1)) & mask,
};
check(&aig, &ops, a, b, w);
}
}
#[test]
fn randomized_width_32() {
randomized(32, 0xDE50_0701);
}
#[test]
fn randomized_width_64() {
randomized(64, 0xDE50_0702);
}
}