use std::sync::LazyLock;
use crate::symcc::type_abbrevs::{Int, Nat, Width};
use miette::Diagnostic;
use num_bigint::{BigInt, BigUint, ToBigInt};
use num_traits::cast::ToPrimitive;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BitVec {
width: Width,
v: BigUint,
}
static TWO: LazyLock<BigUint> = LazyLock::new(|| BigUint::from(2u128));
#[derive(Debug, Diagnostic, Error)]
pub enum BitVecError {
#[error("extract out of bounds")]
ExtractOutOfBounds,
#[error("cannot create a bit-vector with zero width")]
ZeroWidthBitVec,
#[error("mismatched bit-vector widths in {0}")]
MismatchedWidths(String),
#[error("shift amount too large to fit in u32")]
ShiftAmountTooLarge,
}
type Result<T> = std::result::Result<T, BitVecError>;
impl BitVec {
pub fn of_nat(width: Width, v: Nat) -> Result<Self> {
BitVec::new(width, v)
}
pub fn of_int(width: Width, v: Int) -> Result<Self> {
if v >= BigInt::ZERO {
#[allow(
clippy::unwrap_used,
reason = "to_biguint only panicks if the value is negative so unwrap is safe here."
)]
BitVec::new(width, v.to_biguint().unwrap())
} else {
#[allow(
clippy::unwrap_used,
reason = "Ssafe because -v is guaranteed to be positive now."
)]
let pos = BitVec::new(width, (-v).to_biguint().unwrap())?;
Ok(BitVec::add(
&pos.not(),
&BitVec::of_nat(width, BigUint::from(1u128))?,
)?)
}
}
pub fn of_u128(width: Width, val: u128) -> Result<Self> {
BitVec::of_nat(width, BigUint::from(val))
}
pub fn of_i128(width: Width, val: i128) -> Result<Self> {
BitVec::of_int(width, BigInt::from(val))
}
pub fn to_nat(&self) -> Nat {
self.v.clone()
}
pub fn as_nat(&self) -> &Nat {
&self.v
}
pub fn to_int(&self) -> Int {
let sign_bit = self.msb();
if self.width < 2 {
if sign_bit {
BigInt::from(-1)
} else {
BigInt::ZERO
}
} else {
#[allow(
clippy::unwrap_used,
reason = "If condition ensures extract is within bounds."
)]
let val = self.extract_bits(0, self.width - 2).unwrap();
#[allow(
clippy::unwrap_used,
reason = "The implementation of BigUint::to_bigint always returns Some"
)]
let val_bigint = BigUint::to_bigint(&val.v).unwrap();
if !sign_bit {
val_bigint
} else {
#[allow(
clippy::unwrap_used,
reason = "The implementation of BigUint::to_bigint always returns Some"
)]
let res = -1 * BigUint::to_bigint(&TWO.pow(self.width - 1)).unwrap() + val_bigint;
res
}
}
}
pub fn extract_bits(&self, low: Width, high: Width) -> Result<Self> {
if high >= self.width || low > high {
Err(BitVecError::ExtractOutOfBounds)
} else {
let rem = &self.v % TWO.pow(high + 1);
let quotient = rem / TWO.pow(low);
BitVec::of_nat(high - low + 1, quotient)
}
}
fn new(width: Width, val: Nat) -> Result<Self> {
if width == 0 {
Err(BitVecError::ZeroWidthBitVec)
} else {
let v = val % TWO.pow(width);
Ok(BitVec { width, v })
}
}
fn all_ones(width: Width) -> Result<Self> {
let all_ones = TWO.pow(width + 1) - 1u32;
BitVec::of_nat(width, all_ones)
}
#[allow(
clippy::unwrap_used,
reason = "BitVec constructors enforce the invariant that width is always > 0."
)]
fn msb(&self) -> bool {
self.extract_bits(self.width - 1, self.width - 1).unwrap().v != BigUint::ZERO
}
fn is_zero(&self) -> bool {
self.v == BigUint::ZERO
}
pub const fn width(&self) -> Width {
self.width
}
pub fn signed_min(n: Width) -> Result<Int> {
if n == 0 {
Err(BitVecError::ZeroWidthBitVec)
} else {
#[allow(
clippy::unwrap_used,
reason = "The implementation of BigUint::to_bigint always returns Some"
)]
Ok(-(BigUint::to_bigint(&TWO.pow(n - 1)).unwrap()))
}
}
pub fn signed_max(n: Width) -> Result<Int> {
if n == 0 {
Err(BitVecError::ZeroWidthBitVec)
} else {
#[allow(
clippy::unwrap_used,
reason = "The implementation of BigUint::to_bigint always returns Some"
)]
Ok(BigUint::to_bigint(&TWO.pow(n - 1)).unwrap() - 1)
}
}
pub fn overflows(n: Width, i: &Int) -> Result<bool> {
Ok(i < &BitVec::signed_min(n)? || i > &BitVec::signed_max(n)?)
}
pub fn not(&self) -> Self {
#[allow(clippy::unwrap_used)]
BitVec::of_nat(
self.width,
&self.v ^ BitVec::all_ones(self.width).unwrap().v,
)
.unwrap()
}
pub fn neg(&self) -> Self {
#[allow(
clippy::unwrap_used,
reason = "BitVec construction cannot fail: bitwidth is non-zero by invariant."
)]
let one = BitVec::of_u128(self.width, 1).unwrap();
#[allow(clippy::unwrap_used)]
BitVec::add(&self.not(), &one).unwrap()
}
pub fn int_min(width: Width) -> Result<Self> {
BitVec::of_nat(width, TWO.pow(width - 1))
}
pub fn slt(lhs: &Self, rhs: &Self) -> Result<bool> {
if lhs.width != rhs.width {
Err(BitVecError::MismatchedWidths("slt".into()))
} else {
Ok(lhs.to_int() < rhs.to_int())
}
}
pub fn sle(lhs: &Self, rhs: &Self) -> Result<bool> {
if lhs.width != rhs.width {
Err(BitVecError::MismatchedWidths("sle".into()))
} else {
Ok(lhs.to_int() <= rhs.to_int())
}
}
pub fn ule(lhs: &Self, rhs: &Self) -> Result<bool> {
if lhs.width != rhs.width {
Err(BitVecError::MismatchedWidths("ule".into()))
} else {
Ok(lhs.v <= rhs.v)
}
}
pub fn ult(lhs: &Self, rhs: &Self) -> Result<bool> {
if lhs.width != rhs.width {
Err(BitVecError::MismatchedWidths("ult".into()))
} else {
Ok(lhs.v < rhs.v)
}
}
pub fn add(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
Err(BitVecError::MismatchedWidths("add".into()))
} else {
BitVec::of_nat(lhs.width, &lhs.v + &rhs.v)
}
}
pub fn sub(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
Err(BitVecError::MismatchedWidths("sub".into()))
} else {
BitVec::add(lhs, &rhs.neg())
}
}
pub fn mul(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
Err(BitVecError::MismatchedWidths("mul".into()))
} else {
BitVec::of_nat(lhs.width, &lhs.v * &rhs.v)
}
}
pub fn udiv(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
return Err(BitVecError::MismatchedWidths("udiv".into()));
};
if rhs.v == BigUint::ZERO {
BitVec::all_ones(lhs.width)
} else {
BitVec::of_nat(lhs.width, &lhs.v / &rhs.v)
}
}
pub fn urem(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
return Err(BitVecError::MismatchedWidths("urem".into()));
};
if rhs.v == BigUint::ZERO {
Ok(lhs.clone())
} else {
BitVec::of_nat(lhs.width, &lhs.v % &rhs.v)
}
}
pub fn sdiv(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
return Err(BitVecError::MismatchedWidths("sdiv".into()));
};
let lhs_msb = lhs.msb();
let rhs_msb = rhs.msb();
if !lhs_msb && !rhs_msb {
BitVec::udiv(lhs, rhs)
} else if lhs_msb && !rhs_msb {
Ok(BitVec::neg(&BitVec::udiv(&BitVec::neg(lhs), rhs)?))
} else if !lhs_msb && rhs_msb {
Ok(BitVec::neg(&BitVec::udiv(lhs, &BitVec::neg(rhs))?))
} else {
BitVec::udiv(&BitVec::neg(lhs), &BitVec::neg(rhs))
}
}
pub fn srem(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
return Err(BitVecError::MismatchedWidths("srem".into()));
};
let lhs_msb = lhs.msb();
let rhs_msb = rhs.msb();
if !lhs_msb && !rhs_msb {
BitVec::urem(lhs, rhs)
} else if lhs_msb && !rhs_msb {
Ok(BitVec::neg(&BitVec::urem(&BitVec::neg(lhs), rhs)?))
} else if !lhs_msb && rhs_msb {
BitVec::urem(lhs, &BitVec::neg(rhs))
} else {
Ok(BitVec::neg(&BitVec::urem(
&BitVec::neg(lhs),
&BitVec::neg(rhs),
)?))
}
}
pub fn smod(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
return Err(BitVecError::MismatchedWidths("smod".into()));
};
let lhs_msb = lhs.msb();
let rhs_msb = rhs.msb();
let abs_lhs = if !lhs_msb { lhs } else { &BitVec::neg(lhs) };
let abs_rhs = if !rhs_msb { rhs } else { &BitVec::neg(rhs) };
let u = BitVec::urem(abs_lhs, abs_rhs)?;
if u.is_zero() || (!lhs_msb && !rhs_msb) {
Ok(u)
} else if lhs_msb && !rhs_msb {
BitVec::add(&BitVec::neg(&u), rhs)
} else if !lhs_msb && rhs_msb {
BitVec::add(&u, rhs)
} else {
Ok(BitVec::neg(&u))
}
}
pub fn shl(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
return Err(BitVecError::MismatchedWidths("shl".into()));
};
let shift_amount = rhs.v.to_u32().ok_or(BitVecError::ShiftAmountTooLarge)?;
let val = &lhs.v * TWO.pow(shift_amount);
BitVec::of_nat(lhs.width, val)
}
pub fn lshr(lhs: &Self, rhs: &Self) -> Result<Self> {
if lhs.width != rhs.width {
return Err(BitVecError::MismatchedWidths("lshr".into()));
};
let shift_amount = rhs.v.to_u32().ok_or(BitVecError::ShiftAmountTooLarge)?;
let val = &lhs.v / TWO.pow(shift_amount);
BitVec::of_nat(lhs.width, val)
}
pub fn concat(lhs: &Self, rhs: &Self) -> Result<Self> {
let width = lhs.width + rhs.width;
let new_val = (&lhs.v << rhs.width()) + &rhs.v;
BitVec::of_nat(width, new_val)
}
pub fn zero_extend(bv: &Self, n: Width) -> Result<Self> {
BitVec::of_nat(n, bv.to_nat())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn from_bin_str(s: &str) -> BitVec {
assert!(!s.is_empty(), "Cannot create bitvector from empty string.");
for c in s.chars() {
assert!(
c == '0' || c == '1',
"Binary string must only contain '0' or '1'"
);
}
let val = BigUint::parse_bytes(s.as_bytes(), 2).unwrap();
BitVec::of_nat(s.len() as Width, val).unwrap()
}
fn bitvec(width: u32, val: u128) -> BitVec {
BitVec::of_u128(width, val).unwrap()
}
fn bitvec_i(width: u32, val: i128) -> BitVec {
BitVec::of_i128(width, val).unwrap()
}
fn assert_eq_int(rhs: BigInt, lhs: i32) {
assert_eq!(rhs, BigInt::from(lhs));
}
fn assert_eq_nat(rhs: BigUint, lhs: u32) {
assert_eq!(rhs, BigUint::from(lhs));
}
#[test]
fn test_from_bin_str() {
let bv = from_bin_str("0010");
assert_eq!(bv.width(), 4);
assert_eq_nat(bv.to_nat(), 2);
assert_eq_nat(from_bin_str("101").to_nat(), 5);
assert_eq_nat(from_bin_str("1111").to_nat(), 15);
assert_eq_nat(from_bin_str("10000").to_nat(), 16);
assert_eq_nat(from_bin_str("00000").to_nat(), 0);
}
#[test]
fn test_constructors() {
let bv1 = bitvec(4, 2);
assert_eq!(bv1.width(), 4);
assert_eq_nat(bv1.to_nat(), 2);
let bv2 = bitvec(3, 10); assert_eq!(bv2.width(), 3);
assert_eq_nat(bv2.to_nat(), 2);
let bv3 = bitvec(5, 10);
assert_eq!(bv3.width(), 5);
assert_eq_nat(bv3.to_nat(), 10);
let bv4 = bitvec_i(6, -1); assert_eq!(bv4.width(), 6);
assert_eq_nat(bv4.to_nat(), 63); assert_eq_int(bv4.to_int(), -1);
assert_eq!(bv4, BitVec::all_ones(bv4.width()).unwrap());
}
#[test]
fn test_extract_bits() {
let bv = from_bin_str("110101");
let extracted = bv.extract_bits(1, 3).unwrap(); assert_eq!(extracted.width(), 3);
assert_eq_nat(extracted.to_nat(), 2);
let bit = bv.extract_bits(5, 5).unwrap(); assert_eq!(bit.width(), 1);
assert_eq_nat(bit.to_nat(), 1);
let all = bv.extract_bits(0, 5).unwrap(); assert_eq!(all.width(), 6);
assert_eq_nat(all.to_nat(), 53); }
#[test]
fn test_bitwise_ops() {
let bv = from_bin_str("1010");
let not_bv = bv.not();
assert_eq_nat(not_bv.to_nat(), 5);
assert_eq!(not_bv.not(), bv);
}
#[test]
fn test_arithmetic_ops() {
let bv = from_bin_str("1010");
assert_eq_int(bv.to_int(), -6);
let neg_bv = bv.neg();
assert_eq_nat(neg_bv.to_nat(), 6);
let bv_min = from_bin_str("1000");
assert_eq!(bv_min, bv_min.neg());
let bv1 = from_bin_str("0101"); let bv2: BitVec = from_bin_str("0011"); let bv3: BitVec = from_bin_str("1011");
let sum = BitVec::add(&bv1, &bv2).unwrap();
assert_eq_nat(sum.to_nat(), 8);
let sum = BitVec::add(&bv1, &bv3).unwrap();
assert_eq_nat(sum.to_nat(), 0);
let diff = BitVec::sub(&bv1, &bv2).unwrap();
assert_eq_nat(diff.to_nat(), 2);
let diff = BitVec::sub(&bv2, &bv1).unwrap();
assert_eq_int(diff.to_int(), -2);
let prod = BitVec::mul(&bv1, &bv2).unwrap();
assert_eq_nat(prod.to_nat(), 15);
let prod = BitVec::mul(&bv2, &bv3).unwrap();
assert_eq_nat(prod.to_nat(), 1); }
#[test]
fn test_division() {
let bv1 = from_bin_str("0101"); let bv2: BitVec = from_bin_str("0011");
let quot = BitVec::udiv(&bv1, &bv2).unwrap();
assert_eq_nat(quot.to_nat(), 1);
let rem = BitVec::urem(&bv1, &bv2).unwrap();
assert_eq_nat(rem.to_nat(), 2);
let zero = bitvec(4, 0);
let div_by_zero = BitVec::udiv(&bv1, &zero).unwrap();
assert_eq_nat(div_by_zero.to_nat(), 15);
let rem_by_zero = BitVec::urem(&bv1, &zero).unwrap();
assert_eq!(rem_by_zero.to_nat(), bv1.to_nat());
let quot = BitVec::sdiv(&bv1, &bv2).unwrap();
assert_eq_nat(quot.to_nat(), 1); let srem = BitVec::srem(&bv1, &bv2).unwrap();
assert_eq_int(srem.to_int(), 2);
let smod = BitVec::smod(&bv1, &bv2).unwrap();
assert_eq_int(smod.to_int(), 2);
let neg_bv1 = from_bin_str("1011"); let pos_bv2 = from_bin_str("0011");
let quot_neg_pos = BitVec::sdiv(&neg_bv1, &pos_bv2).unwrap();
assert_eq_int(quot_neg_pos.to_int(), -1);
let srem_neg_pos = BitVec::srem(&neg_bv1, &pos_bv2).unwrap();
assert_eq_int(srem_neg_pos.to_int(), -2);
let smod_neg_pos = BitVec::smod(&neg_bv1, &pos_bv2).unwrap();
assert_eq_int(smod_neg_pos.to_int(), 1);
let int_min = from_bin_str("1000"); let quot_min_pos = BitVec::sdiv(&int_min, &pos_bv2).unwrap();
assert_eq_int(quot_min_pos.to_int(), -2);
let srem_min_pos = BitVec::srem(&int_min, &pos_bv2).unwrap();
assert_eq_int(srem_min_pos.to_int(), -2);
let smod_min_pos = BitVec::smod(&int_min, &pos_bv2).unwrap();
assert_eq_int(smod_min_pos.to_int(), 1);
let pos_bv1 = from_bin_str("0101"); let neg_bv2 = from_bin_str("1101");
let quot_pos_neg = BitVec::sdiv(&pos_bv1, &neg_bv2).unwrap();
assert_eq_int(quot_pos_neg.to_int(), -1);
let srem_pos_neg = BitVec::srem(&pos_bv1, &neg_bv2).unwrap();
assert_eq_int(srem_pos_neg.to_int(), 2);
let smod_pos_neg = BitVec::smod(&pos_bv1, &neg_bv2).unwrap();
assert_eq_int(smod_pos_neg.to_int(), -1);
let pos_bv7 = from_bin_str("0111"); let quot_7_neg3 = BitVec::sdiv(&pos_bv7, &neg_bv2).unwrap();
assert_eq_int(quot_7_neg3.to_int(), -2);
let srem_7_neg3 = BitVec::srem(&pos_bv7, &neg_bv2).unwrap();
assert_eq_int(srem_7_neg3.to_int(), 1);
let smod_7_neg3 = BitVec::smod(&pos_bv7, &neg_bv2).unwrap();
assert_eq_int(smod_7_neg3.to_int(), -2);
let neg_bv5 = from_bin_str("1011"); let neg_bv3 = from_bin_str("1101");
let quot_neg_neg = BitVec::sdiv(&neg_bv5, &neg_bv3).unwrap();
assert_eq_int(quot_neg_neg.to_int(), 1);
let srem_neg_neg = BitVec::srem(&neg_bv5, &neg_bv3).unwrap();
assert_eq_int(srem_neg_neg.to_int(), -2);
let smod_neg_neg = BitVec::smod(&neg_bv5, &neg_bv3).unwrap();
assert_eq_int(smod_neg_neg.to_int(), -2);
let quot_min_neg = BitVec::sdiv(&int_min, &neg_bv3).unwrap();
assert_eq_int(quot_min_neg.to_int(), 2);
let srem_min_neg = BitVec::srem(&int_min, &neg_bv3).unwrap();
assert_eq_int(srem_min_neg.to_int(), -2);
let smod_min_neg = BitVec::smod(&int_min, &neg_bv3).unwrap();
assert_eq_int(smod_min_neg.to_int(), -2);
let neg_one = from_bin_str("1111"); let quot_min_neg1 = BitVec::sdiv(&int_min, &neg_one).unwrap();
assert_eq_int(quot_min_neg1.to_int(), -8);
let srem_min_neg1 = BitVec::srem(&int_min, &neg_one).unwrap();
assert_eq_int(srem_min_neg1.to_int(), 0);
let smod_min_neg1 = BitVec::smod(&int_min, &neg_one).unwrap();
assert_eq_int(smod_min_neg1.to_int(), 0);
let div_by_zero = BitVec::sdiv(&bv1, &zero).unwrap();
assert_eq_nat(div_by_zero.to_nat(), 15);
let div_by_zero_neg = BitVec::sdiv(&neg_bv1, &zero).unwrap();
assert_eq_nat(div_by_zero_neg.to_nat(), 1);
let srem_by_zero = BitVec::srem(&bv1, &zero).unwrap();
assert_eq!(srem_by_zero.to_nat(), bv1.to_nat());
let smod_by_zero = BitVec::smod(&bv1, &zero).unwrap();
assert_eq!(smod_by_zero.to_nat(), bv1.to_nat()); }
#[test]
fn test_comparison_ops() {
let bv1 = from_bin_str("0101"); let bv2 = from_bin_str("0011");
assert!(!BitVec::slt(&bv1, &bv2).unwrap()); assert!(BitVec::slt(&bv2, &bv1).unwrap());
assert!(!BitVec::sle(&bv1, &bv2).unwrap()); assert!(BitVec::sle(&bv2, &bv1).unwrap()); assert!(BitVec::sle(&bv1, &bv1).unwrap());
let zero = from_bin_str("0000"); let neg_5 = from_bin_str("1011"); let neg_3: BitVec = from_bin_str("1101"); let neg_8: BitVec = from_bin_str("1000"); let one: BitVec = from_bin_str("0001");
assert!(BitVec::ult(&neg_5, &neg_3).unwrap());
assert!(BitVec::ult(&one, &neg_8).unwrap());
assert!(BitVec::slt(&neg_5, &neg_3).unwrap());
assert!(BitVec::slt(&neg_5, &zero).unwrap());
}
#[test]
fn test_shift_ops() {
let bv1 = from_bin_str("0101"); let shift1 = from_bin_str("0001"); let shift2 = from_bin_str("0010");
let left_shift1 = BitVec::shl(&bv1, &shift1).unwrap(); assert_eq_nat(left_shift1.to_nat(), 10);
let left_shift2 = BitVec::shl(&bv1, &shift2).unwrap(); assert_eq_nat(left_shift2.to_nat(), 4);
let bv2 = from_bin_str("1100"); let right_shift1 = BitVec::lshr(&bv2, &shift1).unwrap(); assert_eq_nat(right_shift1.to_nat(), 6);
let right_shift2 = BitVec::lshr(&bv2, &shift2).unwrap(); assert_eq_nat(right_shift2.to_nat(), 3);
}
#[test]
fn test_concat() {
let bv1 = from_bin_str("101"); let bv2 = from_bin_str("11");
let concat1 = BitVec::concat(&bv1, &bv2).unwrap(); assert_eq!(concat1.width(), 5); assert_eq_nat(concat1.to_nat(), 23);
let concat2 = BitVec::concat(&bv2, &bv1).unwrap(); assert_eq!(concat2.width(), 5); assert_eq_nat(concat2.to_nat(), 29);
}
#[test]
fn test_zero_extend() {
let bv = from_bin_str("101");
let extended1 = BitVec::zero_extend(&bv, 3).unwrap();
assert_eq!(extended1.width(), 3);
assert_eq_nat(extended1.to_nat(), 5);
let extended2 = BitVec::zero_extend(&bv, 2).unwrap();
assert_eq!(extended2.width(), 2);
assert_eq_nat(extended2.to_nat(), 1); }
#[test]
fn test_overflow() {
assert_eq!(BitVec::signed_min(4).unwrap(), BigInt::from(-8)); assert_eq!(BitVec::signed_max(4).unwrap(), BigInt::from(7));
assert!(BitVec::overflows(4, &BigInt::from(8)).unwrap()); assert!(BitVec::overflows(4, &BigInt::from(-9)).unwrap()); assert!(!BitVec::overflows(4, &BigInt::from(7)).unwrap()); assert!(!BitVec::overflows(4, &BigInt::from(-8)).unwrap());
let min = BitVec::int_min(4).unwrap();
assert_eq!(min.width(), 4);
assert_eq_nat(min.to_nat(), 8); }
}