use crate::term::BvTerm;
pub fn to_le_bytes(x: &BvTerm, width: u32) -> Vec<BvTerm> {
assert!(
width > 0 && width.is_multiple_of(8),
"to_le_bytes: width {width} is not a positive multiple of 8"
);
(0..width / 8)
.map(|i| BvTerm::Extract {
hi: 8 * i + 7,
lo: 8 * i,
arg: Box::new(x.clone()),
})
.collect()
}
pub fn from_le_bytes(bytes: &[BvTerm]) -> BvTerm {
let mut it = bytes.iter();
let mut acc = it
.next()
.expect("from_le_bytes: needs at least one byte")
.clone();
for higher in it {
acc = BvTerm::Concat(Box::new(higher.clone()), Box::new(acc));
}
acc
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eval::{Env, bv_sort, eval_bv};
use crate::solver::{CheckResult, Solver};
use crate::term::Sort;
fn v(name: &str, w: u32) -> BvTerm {
BvTerm::Var {
name: name.into(),
sort: Sort::new(w),
}
}
#[test]
fn to_le_bytes_extracts_each_byte_least_significant_first() {
let w = 32;
let x = v("x", w);
let bytes = to_le_bytes(&x, w);
assert_eq!(bytes.len(), 4);
for (i, byte) in bytes.iter().enumerate() {
assert_eq!(bv_sort(byte).unwrap().width, 8, "each byte is 8 bits");
let mut env = Env::new();
env.insert("x".into(), 0xDEAD_BEEF);
let expected = (0xDEAD_BEEFu128 >> (8 * i)) & 0xFF;
assert_eq!(eval_bv(byte, &env).unwrap(), expected, "byte {i}");
}
}
#[test]
fn round_trip_evaluates_identically_at_every_width() {
for w in [8u32, 16, 32, 64] {
let x = v("x", w);
let rebuilt = from_le_bytes(&to_le_bytes(&x, w));
assert_eq!(bv_sort(&rebuilt).unwrap().width, w, "width preserved");
for val in [0u128, 1, 0xFF, 0x1234, 0xDEAD_BEEF, u64::MAX as u128] {
let masked = if w >= 128 {
val
} else {
val & ((1u128 << w) - 1)
};
let mut env = Env::new();
env.insert("x".into(), masked);
assert_eq!(
eval_bv(&rebuilt, &env).unwrap(),
masked,
"round-trip w{w} value {masked:#x}"
);
}
}
}
#[test]
fn round_trip_is_certificate_checked_equivalent() {
for w in [16u32, 32] {
let x = v("x", w);
let rebuilt = from_le_bytes(&to_le_bytes(&x, w));
match Solver::prove_equiv(x, rebuilt) {
CheckResult::Unsat(cert) => {
cert.recheck()
.expect("round-trip certificate must re-check");
}
other => panic!("le-bytes round-trip must be proven at w{w}, got {other:?}"),
}
}
}
#[test]
fn a_wrong_byte_order_is_caught_with_a_counterexample() {
let w = 32;
let x = v("x", w);
let mut bytes = to_le_bytes(&x, w);
bytes.reverse(); match Solver::prove_equiv(x, from_le_bytes(&bytes)) {
CheckResult::Sat(_) => {}
other => panic!("byte-swapped reassembly must be Sat, got {other:?}"),
}
}
}