use crate::term::{BvTerm, Sort};
pub fn mask(width: u32) -> u128 {
if width >= 128 {
u128::MAX
} else {
(1u128 << width) - 1
}
}
fn const_bv(value: u128, width: u32) -> BvTerm {
BvTerm::Const {
value,
sort: Sort::new(width),
}
}
fn sign_mask(x: BvTerm, width: u32) -> BvTerm {
BvTerm::Ashr(Box::new(x), Box::new(const_bv((width - 1) as u128, width)))
}
fn abs_bv(x: BvTerm, width: u32) -> BvTerm {
let s = sign_mask(x.clone(), width);
BvTerm::Sub(
Box::new(BvTerm::Xor(Box::new(x), Box::new(s.clone()))),
Box::new(s),
)
}
pub fn bvnot(x: BvTerm, width: u32) -> BvTerm {
BvTerm::Xor(Box::new(x), Box::new(const_bv(mask(width), width)))
}
pub fn bvneg(x: BvTerm, width: u32) -> BvTerm {
BvTerm::Sub(Box::new(const_bv(0, width)), Box::new(x))
}
pub fn bvrotl(a: BvTerm, b: BvTerm, width: u32) -> BvTerm {
let neg_b = BvTerm::Sub(Box::new(const_bv(0, width)), Box::new(b));
BvTerm::Rotr(Box::new(a), Box::new(neg_b))
}
pub fn bvurem(a: BvTerm, b: BvTerm, width: u32) -> BvTerm {
let _ = width;
let q = BvTerm::Udiv(Box::new(a.clone()), Box::new(b.clone()));
let prod = BvTerm::Mul(Box::new(q), Box::new(b));
BvTerm::Sub(Box::new(a), Box::new(prod))
}
pub fn bvsdiv(a: BvTerm, b: BvTerm, width: u32) -> BvTerm {
let sa = sign_mask(a.clone(), width);
let sb = sign_mask(b.clone(), width);
let result_sign = BvTerm::Xor(Box::new(sa), Box::new(sb));
let q = BvTerm::Udiv(Box::new(abs_bv(a, width)), Box::new(abs_bv(b, width)));
BvTerm::Sub(
Box::new(BvTerm::Xor(Box::new(q), Box::new(result_sign.clone()))),
Box::new(result_sign),
)
}
pub fn bvsrem(a: BvTerm, b: BvTerm, width: u32) -> BvTerm {
let q = bvsdiv(a.clone(), b.clone(), width);
let prod = BvTerm::Mul(Box::new(q), Box::new(b));
BvTerm::Sub(Box::new(a), Box::new(prod))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eval::bv_sort;
fn var(name: &str, width: u32) -> BvTerm {
BvTerm::Var {
name: name.into(),
sort: Sort::new(width),
}
}
#[test]
fn mask_is_all_ones() {
assert_eq!(mask(1), 0x1);
assert_eq!(mask(8), 0xFF);
assert_eq!(mask(32), 0xFFFF_FFFF);
assert_eq!(mask(64), 0xFFFF_FFFF_FFFF_FFFF);
assert_eq!(mask(128), u128::MAX);
}
#[test]
fn constructors_are_well_sorted() {
for &w in &[8u32, 32, 64] {
let a = var("a", w);
let b = var("b", w);
let cases = [
bvnot(a.clone(), w),
bvneg(a.clone(), w),
bvrotl(a.clone(), b.clone(), w),
bvurem(a.clone(), b.clone(), w),
bvsdiv(a.clone(), b.clone(), w),
bvsrem(a.clone(), b.clone(), w),
];
for (i, t) in cases.iter().enumerate() {
assert_eq!(
bv_sort(t),
Ok(Sort::new(w)),
"case {i} at width {w} is not well-sorted at width {w}"
);
}
}
}
}
#[cfg(all(test, feature = "oracle"))]
mod lowering_diff {
use super::*;
use crate::oracle::bv_to_z3;
use z3::ast::BV;
use z3::{Params, SatResult, Solver};
fn equiv_verdict(derived: &BvTerm, native: BV) -> SatResult {
let solver = Solver::new();
let mut params = Params::new();
params.set_u32("timeout", 20_000);
solver.set_params(¶ms);
solver.assert(bv_to_z3(derived).eq(native).not());
solver.check()
}
fn assert_equiv(derived: &BvTerm, native: BV, label: &str) {
assert_eq!(
equiv_verdict(derived, native),
SatResult::Unsat,
"{label}: derived form disagrees with Z3's native operator"
);
}
fn assert_no_counterexample(derived: &BvTerm, native: BV, label: &str) {
assert_ne!(
equiv_verdict(derived, native),
SatResult::Sat,
"{label}: Z3 found a counterexample — derived form is WRONG"
);
}
fn edges(width: u32) -> Vec<u128> {
let m = mask(width);
vec![
0,
1,
m, 1u128 << (width - 1), (1u128 << (width - 1)) - 1, 2,
]
}
fn c(value: u128, width: u32) -> BvTerm {
BvTerm::Const {
value,
sort: Sort::new(width),
}
}
#[test]
fn bvnot_symbolic() {
for &w in &[8u32, 32, 64] {
let a = BvTerm::Var {
name: "a".into(),
sort: Sort::new(w),
};
assert_equiv(
&bvnot(a.clone(), w),
bv_to_z3(&a).bvnot(),
&format!("bvnot w{w}"),
);
}
}
#[test]
fn bvneg_symbolic() {
for &w in &[8u32, 32, 64] {
let a = BvTerm::Var {
name: "a".into(),
sort: Sort::new(w),
};
assert_equiv(
&bvneg(a.clone(), w),
bv_to_z3(&a).bvneg(),
&format!("bvneg w{w}"),
);
}
}
#[test]
fn bvrotl_symbolic() {
for &w in &[8u32, 32, 64] {
let a = BvTerm::Var {
name: "a".into(),
sort: Sort::new(w),
};
let b = BvTerm::Var {
name: "b".into(),
sort: Sort::new(w),
};
assert_equiv(
&bvrotl(a.clone(), b.clone(), w),
bv_to_z3(&a).bvrotl(bv_to_z3(&b)),
&format!("bvrotl w{w}"),
);
}
}
fn ab(w: u32) -> (BvTerm, BvTerm) {
(
BvTerm::Var {
name: "a".into(),
sort: Sort::new(w),
},
BvTerm::Var {
name: "b".into(),
sort: Sort::new(w),
},
)
}
#[test]
fn bvurem_symbolic() {
for &w in &[8u32, 32, 64] {
let (a, b) = ab(w);
let derived = bvurem(a.clone(), b.clone(), w);
let native = bv_to_z3(&a).bvurem(bv_to_z3(&b));
let label = format!("bvurem w{w}");
if w == 8 {
assert_equiv(&derived, native, &label);
} else {
assert_no_counterexample(&derived, native, &label);
}
}
}
#[test]
fn bvsdiv_symbolic() {
for &w in &[8u32, 32, 64] {
let (a, b) = ab(w);
let derived = bvsdiv(a.clone(), b.clone(), w);
let native = bv_to_z3(&a).bvsdiv(bv_to_z3(&b));
let label = format!("bvsdiv w{w}");
if w == 8 {
assert_equiv(&derived, native, &label);
} else {
assert_no_counterexample(&derived, native, &label);
}
}
}
#[test]
fn bvsrem_symbolic() {
for &w in &[8u32, 32, 64] {
let (a, b) = ab(w);
let derived = bvsrem(a.clone(), b.clone(), w);
let native = bv_to_z3(&a).bvsrem(bv_to_z3(&b));
let label = format!("bvsrem w{w}");
if w == 8 {
assert_equiv(&derived, native, &label);
} else {
assert_no_counterexample(&derived, native, &label);
}
}
}
#[test]
fn boundary_pairs_match_z3() {
for &w in &[8u32, 32, 64] {
let vals = edges(w);
for &x in &vals {
assert_equiv(
&bvnot(c(x, w), w),
bv_to_z3(&c(x, w)).bvnot(),
&format!("bvnot w{w} x={x:#x}"),
);
assert_equiv(
&bvneg(c(x, w), w),
bv_to_z3(&c(x, w)).bvneg(),
&format!("bvneg w{w} x={x:#x}"),
);
for &y in &vals {
let (ca, cb) = (c(x, w), c(y, w));
assert_equiv(
&bvrotl(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvrotl(bv_to_z3(&cb)),
&format!("bvrotl w{w} a={x:#x} b={y:#x}"),
);
assert_equiv(
&bvurem(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvurem(bv_to_z3(&cb)),
&format!("bvurem w{w} a={x:#x} b={y:#x}"),
);
assert_equiv(
&bvsdiv(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvsdiv(bv_to_z3(&cb)),
&format!("bvsdiv w{w} a={x:#x} b={y:#x}"),
);
assert_equiv(
&bvsrem(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvsrem(bv_to_z3(&cb)),
&format!("bvsrem w{w} a={x:#x} b={y:#x}"),
);
}
}
}
}
fn xorshift(state: &mut u64) -> u64 {
let mut x = *state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*state = x;
x
}
#[test]
fn randomized_pairs_match_z3() {
let mut state: u64 = 0x0D2E_A101_5EED_0029;
for &w in &[8u32, 32, 64] {
let m = mask(w);
for _ in 0..128 {
let x = xorshift(&mut state) as u128 & m;
let y = xorshift(&mut state) as u128 & m;
let (ca, cb) = (c(x, w), c(y, w));
assert_equiv(
&bvnot(ca.clone(), w),
bv_to_z3(&ca).bvnot(),
&format!("bvnot w{w} x={x:#x}"),
);
assert_equiv(
&bvneg(ca.clone(), w),
bv_to_z3(&ca).bvneg(),
&format!("bvneg w{w} x={x:#x}"),
);
assert_equiv(
&bvrotl(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvrotl(bv_to_z3(&cb)),
&format!("bvrotl w{w} a={x:#x} b={y:#x}"),
);
assert_equiv(
&bvurem(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvurem(bv_to_z3(&cb)),
&format!("bvurem w{w} a={x:#x} b={y:#x}"),
);
assert_equiv(
&bvsdiv(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvsdiv(bv_to_z3(&cb)),
&format!("bvsdiv w{w} a={x:#x} b={y:#x}"),
);
assert_equiv(
&bvsrem(ca.clone(), cb.clone(), w),
bv_to_z3(&ca).bvsrem(bv_to_z3(&cb)),
&format!("bvsrem w{w} a={x:#x} b={y:#x}"),
);
}
}
}
}