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;
BvTerm::Urem(Box::new(a), Box::new(b))
}
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::{Env, bv_sort, eval_bv};
fn smtlib_ref(x: u128, y: u128, w: u32) -> (u128, u128, u128) {
let m = if w >= 128 {
u128::MAX
} else {
(1u128 << w) - 1
};
let neg = |v: u128| v.wrapping_neg() & m;
let msb = |v: u128| (v >> (w - 1)) & 1 == 1;
let udiv = |p: u128, q: u128| p.checked_div(q).unwrap_or(m);
let urem = |p: u128, q: u128| if q == 0 { p } else { p % q };
let sdiv = match (msb(x), msb(y)) {
(false, false) => udiv(x, y),
(true, false) => neg(udiv(neg(x), y)),
(false, true) => neg(udiv(x, neg(y))),
(true, true) => udiv(neg(x), neg(y)),
};
let srem = match (msb(x), msb(y)) {
(false, false) => urem(x, y),
(true, false) => neg(urem(neg(x), y)),
(false, true) => urem(x, neg(y)),
(true, true) => neg(urem(neg(x), neg(y))),
};
(urem(x, y), sdiv, srem)
}
#[test]
fn exhaustive_width8_div_rem_family_matches_smtlib_reference() {
let w = 8u32;
let env = Env::new();
for x in 0..=0xFFu128 {
for y in 0..=0xFFu128 {
let (a, b) = (const_bv(x, w), const_bv(y, w));
let (r_urem, r_sdiv, r_srem) = smtlib_ref(x, y, w);
let got = |t: BvTerm| eval_bv(&t, &env).unwrap();
assert_eq!(
got(bvurem(a.clone(), b.clone(), w)),
r_urem,
"bvurem {x} {y}"
);
assert_eq!(
got(bvsdiv(a.clone(), b.clone(), w)),
r_sdiv,
"bvsdiv {x} {y}"
);
assert_eq!(got(bvsrem(a, b, w)), r_srem, "bvsrem {x} {y}");
}
}
}
#[test]
fn div_rem_derivation_shapes_are_deliberate() {
let w = 32u32;
let (a, b) = (var("a", w), var("b", w));
let muls = |t: &BvTerm| format!("{t:?}").matches("Mul(").count();
assert_eq!(
muls(&bvurem(a.clone(), b.clone(), w)),
0,
"bvurem must stay multiplier-free (native Urem)"
);
assert_eq!(
muls(&bvsdiv(a.clone(), b.clone(), w)),
0,
"bvsdiv must stay multiplier-free (sign-corrected Udiv)"
);
assert_eq!(
muls(&bvsrem(a.clone(), b.clone(), w)),
1,
"bvsrem must stay MULTIPLICATIVE (one Mul) — see #97"
);
}
#[test]
fn srem_vc_against_multiplicative_model_decides_fast() {
use crate::{BoolTerm, CheckResult, Solver};
let (a, b) = (var("a", 32), var("b", 32));
let ours = bvsrem(a.clone(), b.clone(), 32);
let q = bvsdiv(a.clone(), b.clone(), 32);
let theirs = BvTerm::Sub(Box::new(a), Box::new(BvTerm::Mul(Box::new(q), Box::new(b))));
let mut s = Solver::new();
s.assert(BoolTerm::Ne(Box::new(ours), Box::new(theirs)));
match s.check_with_deadline(10_000) {
CheckResult::Unsat(cert) => cert.recheck().expect("cert must re-check"),
other => panic!(
"srem VC must decide (fast) against the multiplicative model; got {other:?} — \
the #97 cross-circuit regression is back"
),
}
}
#[test]
fn width16_div_rem_family_matches_smtlib_reference() {
let w = 16u32;
let env = Env::new();
let mut vals: Vec<u128> = vec![0, 1, 2, 0x7FFF, 0x8000, 0x8001, 0xFFFE, 0xFFFF];
let mut s: u128 = 0x1234;
for _ in 0..40 {
s = (s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407))
& 0xFFFF;
vals.push(s);
}
for &x in &vals {
for &y in &vals {
let (a, b) = (const_bv(x, w), const_bv(y, w));
let (r_urem, r_sdiv, r_srem) = smtlib_ref(x, y, w);
let got = |t: BvTerm| eval_bv(&t, &env).unwrap();
assert_eq!(
got(bvurem(a.clone(), b.clone(), w)),
r_urem,
"bvurem w16 {x} {y}"
);
assert_eq!(
got(bvsdiv(a.clone(), b.clone(), w)),
r_sdiv,
"bvsdiv w16 {x} {y}"
);
assert_eq!(got(bvsrem(a, b, w)), r_srem, "bvsrem w16 {x} {y}");
}
}
}
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}"),
);
}
}
}
}