fayalite/crates/fayalite/src/int.rs

1493 lines
46 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{Expr, ToExpr},
intern::{Intern, Interned, Memoize},
source_location::SourceLocation,
ty::{
impl_match_values_as_self, CanonicalType, CanonicalTypeKind, CanonicalValue, Connect,
DynCanonicalType, FixedType, Type, TypeEnum, Value, ValueEnum,
},
util::{ConstBool, GenericConstBool},
valueless::Valueless,
};
use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView};
use num_bigint::{BigInt, BigUint, Sign};
use num_traits::{ToPrimitive, Zero};
use std::{
fmt,
hash::Hash,
marker::PhantomData,
ops::{
Add, BitAnd, BitOr, BitXor, Bound, Mul, Neg, Not, Range, RangeBounds, RangeInclusive, Shl,
Shr, Sub,
},
};
#[derive(Clone, Eq, PartialEq, Hash, Default)]
pub struct IntValue<T> {
ty: T,
uint_value: BigUint,
}
pub type DynInt<Signed> = IntValue<DynIntType<Signed>>;
pub type DynUInt = DynInt<ConstBool<false>>;
pub type DynSInt = DynInt<ConstBool<true>>;
pub type Int<Signed, const WIDTH: usize> = IntValue<IntType<Signed, WIDTH>>;
pub type UInt<const WIDTH: usize> = Int<ConstBool<false>, WIDTH>;
pub type SInt<const WIDTH: usize> = Int<ConstBool<true>, WIDTH>;
impl<
T: IntTypeTrait<
CanonicalType = DynIntType<<T as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<T as IntTypeTrait>::Signed>,
>,
> ToExpr for IntValue<T>
{
type Type = T;
fn ty(&self) -> Self::Type {
self.ty
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
Expr::from_value(self)
}
}
impl<
T: IntTypeTrait<
CanonicalType = DynIntType<<T as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<T as IntTypeTrait>::Signed>,
>,
> fmt::Display for IntValue<T>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.to_canonical(), f)
}
}
impl<
T: IntTypeTrait<
CanonicalType = DynIntType<<T as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<T as IntTypeTrait>::Signed>,
>,
> fmt::Debug for IntValue<T>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let neg;
let (sign, magnitude) = if self.is_negative() {
neg = self.ty.modulo() - &self.uint_value;
("-", &neg)
} else if T::Signed::VALUE {
("+", &self.uint_value)
} else {
("", &self.uint_value)
};
write!(f, "{sign}0x{magnitude:X}_{:?}", self.ty)
}
}
impl<Signed: GenericConstBool, const WIDTH: usize> Int<Signed, WIDTH> {
pub fn new<I: Into<BigInt>>(value: I) -> Self {
Self::with_type::<BigInt>(IntType::new(), value.into())
}
}
impl<Signed: GenericConstBool> DynInt<Signed> {
pub fn from_bit_slice(v: &BitSlice) -> Self {
let mut small_buf = [0u32; 8];
let small_buf_view = small_buf.view_bits_mut::<Lsb0>();
let uint_value = if v.len() <= small_buf_view.len() {
small_buf_view[..v.len()].clone_from_bitslice(v);
BigUint::from_slice(&small_buf)
} else {
let mut buf = BitVec::<u32, Lsb0>::with_capacity(v.len());
buf.extend_from_bitslice(v);
BigUint::from_slice(buf.as_raw_slice())
};
Self {
ty: DynIntType::new(v.len()),
uint_value,
}
}
}
impl<
T: IntTypeTrait<
CanonicalType = DynIntType<<T as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<T as IntTypeTrait>::Signed>,
>,
> IntValue<T>
{
pub fn with_type<I: Into<BigInt>>(ty: T, value: I) -> Self {
let int_type = ty.canonical();
let mut value: BigInt = value.into();
if value.sign() == Sign::Minus
|| usize::try_from(value.bits())
.map(|v| v > int_type.width)
.unwrap_or(true)
{
value &= BigInt::from(int_type.as_same_width_uint().mask());
}
Self {
ty,
uint_value: value.try_into().unwrap(),
}
}
pub fn into_canonical(self) -> IntValue<DynIntType<T::Signed>> {
IntValue {
ty: self.ty.canonical(),
uint_value: self.uint_value,
}
}
pub fn to_canonical(&self) -> IntValue<DynIntType<T::Signed>> {
IntValue {
ty: self.ty.canonical(),
uint_value: self.uint_value.clone(),
}
}
pub fn ty(&self) -> T {
self.ty
}
pub fn uint_value(&self) -> BigUint {
self.uint_value.clone()
}
pub fn into_uint_value(self) -> BigUint {
self.uint_value
}
pub fn value(&self) -> BigInt {
if self.is_negative() {
BigInt::from(self.uint_value.clone()) - BigInt::from(self.ty.modulo())
} else {
self.uint_value.clone().into()
}
}
pub fn into_value(self) -> BigInt {
if self.is_negative() {
BigInt::from(self.uint_value) - BigInt::from(self.ty.modulo())
} else {
self.uint_value.into()
}
}
pub fn zero() -> Self
where
T: Default,
{
Self::default()
}
pub fn is_zero(&self) -> bool {
self.uint_value.is_zero()
}
pub fn set_zero(&mut self) {
self.uint_value.set_zero();
}
pub fn signum(&self) -> SInt<2> {
if self.is_negative() {
IntValue::new(-1)
} else if self.is_zero() {
IntValue::new(0)
} else {
IntValue::new(1)
}
}
pub fn is_positive(&self) -> bool {
!self.is_negative() && !self.is_zero()
}
pub fn is_negative(&self) -> bool {
let width = self.ty.width();
T::Signed::VALUE && width > 0 && self.uint_value.bit((width - 1).try_into().unwrap())
}
pub fn into_cast<
NewType: IntTypeTrait<
CanonicalType = DynIntType<<NewType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<NewType as IntTypeTrait>::Signed>,
>,
>(
self,
new_type: NewType,
) -> IntValue<NewType> {
if new_type.width() > self.ty.width() && self.is_negative() {
IntValue::with_type(new_type, self.into_value())
} else {
IntValue::with_type(new_type, self.into_uint_value())
}
}
pub fn cast_as_type<
NewType: IntTypeTrait<
CanonicalType = DynIntType<<NewType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<NewType as IntTypeTrait>::Signed>,
>,
>(
&self,
new_type: NewType,
) -> IntValue<NewType> {
if new_type.width() > self.ty.width() && self.is_negative() {
IntValue::with_type(new_type, self.value())
} else {
IntValue::with_type(new_type, self.uint_value())
}
}
pub fn cast<Signed: GenericConstBool, const WIDTH: usize>(self) -> Int<Signed, WIDTH> {
self.cast_as_type(IntType::new())
}
pub fn as_same_width_uint(self) -> IntValue<T::SameWidthUInt> {
IntValue {
ty: self.ty.as_same_width_uint(),
uint_value: self.uint_value,
}
}
pub fn as_same_width_sint(self) -> IntValue<T::SameWidthSInt> {
IntValue {
ty: self.ty.as_same_width_sint(),
uint_value: self.uint_value,
}
}
pub fn as_same_value_uint(self) -> IntValue<DynUIntType> {
IntValue {
ty: self.ty.as_same_value_uint(),
uint_value: self.uint_value,
}
}
pub fn as_same_value_sint(self) -> IntValue<DynSIntType> {
IntValue {
ty: self.ty.as_same_value_sint(),
uint_value: self.uint_value,
}
}
pub fn bit(&self, index: usize) -> bool {
if index >= self.ty.width() {
self.is_negative()
} else {
self.uint_value.bit(index.try_into().unwrap())
}
}
pub fn set_bit(&mut self, index: usize, value: bool) {
assert!(index < self.ty.width(), "bit index out of range");
self.uint_value.set_bit(index.try_into().unwrap(), value)
}
pub fn set_slice<
I: RangeBounds<usize>,
RhsType: IntTypeTrait<
Signed = ConstBool<false>,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>,
>(
&mut self,
index: I,
value: impl Into<IntValue<RhsType>>,
) {
let (ty, shift) = self.ty.slice_and_shift(index);
let value: IntValue<RhsType> = value.into();
let value = value.into_cast(ty);
let mut mask = ty.mask();
let mut uint_value = value.uint_value;
mask <<= shift;
uint_value <<= shift;
// can't just use self.uint_value &= !mask since Not isn't implemented...work around with & and subtraction
mask &= &self.uint_value;
self.uint_value -= mask;
self.uint_value |= uint_value;
}
pub fn with_replaced_slice<
I: RangeBounds<usize>,
RhsType: IntTypeTrait<
Signed = ConstBool<false>,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>,
>(
mut self,
index: I,
value: impl Into<IntValue<RhsType>>,
) -> Self {
self.set_slice(index, value);
self
}
pub fn concat<
HighType: IntTypeTrait<
CanonicalType = DynIntType<<HighType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<HighType as IntTypeTrait>::Signed>,
>,
>(
&self,
high_part: IntValue<HighType>,
) -> IntValue<DynIntType<HighType::Signed>> {
let self_type = self.ty.canonical();
let ty = self.valueless().concat(high_part.valueless()).ty;
let mut uint_value = high_part.uint_value << self_type.width;
uint_value |= &self.uint_value;
IntValue { ty, uint_value }
}
pub fn repeat(&self, count: usize) -> IntValue<DynIntType<T::Signed>> {
let width = self.ty.width();
let ty = self.valueless().repeat(count).ty;
let mut factor = BigUint::from(0u8);
// reversed so BigUint only reallocates once
for i in (0..count).rev() {
factor.set_bit((i * width).try_into().unwrap(), true);
}
IntValue {
ty,
uint_value: &self.uint_value * factor,
}
}
pub fn slice<I: RangeBounds<usize>>(&self, index: I) -> DynUInt {
let (ty, shift) = self.ty.slice_and_shift(index);
// correct since slice_and_shift ensures we're not trying to slice out of range
IntValue::with_type(ty, &self.uint_value >> shift)
}
}
impl<
Ty: IntTypeTrait<
CanonicalType = DynIntType<<Ty as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<Ty as IntTypeTrait>::Signed>,
>,
> Value for IntValue<Ty>
{
fn to_canonical(&self) -> <Self::Type as Type>::CanonicalValue {
IntValue {
ty: self.ty.canonical(),
uint_value: self.uint_value.clone(),
}
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
#[derive(Hash, Eq, PartialEq)]
struct ToBitsMemoize<T>(PhantomData<T>);
impl<T> Clone for ToBitsMemoize<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for ToBitsMemoize<T> {}
impl<
Ty: IntTypeTrait<
CanonicalType = DynIntType<<Ty as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<Ty as IntTypeTrait>::Signed>,
>,
> Memoize for ToBitsMemoize<IntValue<Ty>>
{
type Input = IntValue<Ty>;
type InputOwned = IntValue<Ty>;
type Output = Interned<BitSlice>;
fn inner(self, input: &Self::Input) -> Self::Output {
let u64_digits = input.uint_value.to_u64_digits();
let width = input.ty.width();
let mut bits = BitVec::with_capacity(width);
let mut slice = u64_digits.view_bits::<Lsb0>();
if slice.len() > width {
slice = &slice[..width];
}
bits.extend_from_bitslice(slice);
bits.resize(width, false);
Intern::intern_owned(bits)
}
}
ToBitsMemoize::<Self>(PhantomData).get(this)
}
}
macro_rules! impl_add_sub {
($Op:ident::$op:ident) => {
impl<LhsType, RhsType> $Op<IntValue<RhsType>> for IntValue<LhsType>
where
LhsType: IntTypeTrait<
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = <LhsType as IntTypeTrait>::Signed,
CanonicalType = DynIntType<<RhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<RhsType as IntTypeTrait>::Signed>,
>,
{
type Output = IntValue<DynIntType<LhsType::Signed>>;
fn $op(self, rhs: IntValue<RhsType>) -> Self::Output {
let ty = $Op::$op(self.valueless(), rhs.valueless()).ty;
IntValue::with_type(ty, $Op::$op(self.into_value(), rhs.into_value()))
}
}
impl<LhsType, RhsType> $Op<Valueless<RhsType>> for Valueless<LhsType>
where
LhsType: IntTypeTrait<
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = <LhsType as IntTypeTrait>::Signed,
CanonicalType = DynIntType<<RhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<RhsType as IntTypeTrait>::Signed>,
>,
{
type Output = Valueless<DynIntType<LhsType::Signed>>;
fn $op(self, rhs: Valueless<RhsType>) -> Self::Output {
let ty = DynIntType::new(
self.ty
.width()
.max(rhs.ty.width())
.checked_add(1)
.expect("result has too many bits"),
);
Valueless { ty }
}
}
};
}
macro_rules! impl_bitwise {
($Op:ident::$op:ident) => {
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<RhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<RhsType as IntTypeTrait>::Signed>,
>,
> $Op<IntValue<RhsType>> for IntValue<LhsType>
{
type Output = IntValue<DynIntType<Signed>>;
fn $op(self, rhs: IntValue<RhsType>) -> Self::Output {
let ty = $Op::$op(self.valueless(), rhs.valueless()).ty;
IntValue::with_type(ty, $Op::$op(self.into_value(), rhs.into_value()))
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<RhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<RhsType as IntTypeTrait>::Signed>,
>,
> $Op<Valueless<RhsType>> for Valueless<LhsType>
{
type Output = Valueless<DynIntType<Signed>>;
fn $op(self, rhs: Valueless<RhsType>) -> Self::Output {
let ty = DynIntType::new(self.ty.width().max(rhs.ty.width()));
Valueless { ty }
}
}
};
}
impl_add_sub!(Add::add);
impl_add_sub!(Sub::sub);
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<RhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<RhsType as IntTypeTrait>::Signed>,
>,
> Mul<IntValue<RhsType>> for IntValue<LhsType>
{
type Output = IntValue<DynIntType<Signed>>;
fn mul(self, rhs: IntValue<RhsType>) -> Self::Output {
let ty = self.valueless().mul(rhs.valueless()).ty;
IntValue::with_type(ty, Mul::mul(self.into_value(), rhs.into_value()))
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<RhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<RhsType as IntTypeTrait>::Signed>,
>,
> Mul<Valueless<RhsType>> for Valueless<LhsType>
{
type Output = Valueless<DynIntType<Signed>>;
fn mul(self, rhs: Valueless<RhsType>) -> Self::Output {
let ty = DynIntType::new(
self.ty
.width()
.checked_add(rhs.ty.width())
.expect("product has too many bits"),
);
Valueless { ty }
}
}
impl_bitwise!(BitAnd::bitand);
impl_bitwise!(BitOr::bitor);
impl_bitwise!(BitXor::bitxor);
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = ConstBool<false>,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>,
> Shl<IntValue<RhsType>> for IntValue<LhsType>
{
type Output = IntValue<DynIntType<Signed>>;
fn shl(self, rhs: IntValue<RhsType>) -> Self::Output {
let ty = self.valueless().shl(rhs.valueless()).ty;
IntValue::with_type(
ty,
Shl::shl(
self.into_value(),
rhs.into_value()
.to_usize()
.expect("ty was checked, so the shift must be in-range"),
),
)
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = ConstBool<false>,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>,
> Shl<Valueless<RhsType>> for Valueless<LhsType>
{
type Output = Valueless<DynIntType<Signed>>;
fn shl(self, rhs: Valueless<RhsType>) -> Self::Output {
let ty = DynIntType::new(
rhs.ty
.width()
.try_into()
.ok()
.and_then(|v| 2usize.checked_pow(v))
.and_then(|v| self.ty.width().checked_add(v - 1))
.expect("shift amount can be too big"),
);
Valueless { ty }
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
RhsType: IntTypeTrait<
Signed = ConstBool<false>,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>,
> Shr<IntValue<RhsType>> for IntValue<LhsType>
{
type Output = IntValue<DynIntType<Signed>>;
fn shr(self, rhs: IntValue<RhsType>) -> Self::Output {
IntValue::with_type(
self.valueless().shr(rhs.valueless()).ty,
Shr::shr(
self.into_value(),
rhs.into_value().to_usize().unwrap_or(usize::MAX),
),
)
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<Signed = Signed, CanonicalType = DynIntType<Signed>>,
RhsType: IntTypeTrait<
Signed = ConstBool<false>,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>,
> Shr<Valueless<RhsType>> for Valueless<LhsType>
{
type Output = Valueless<DynIntType<Signed>>;
fn shr(self, _rhs: Valueless<RhsType>) -> Self::Output {
Valueless {
ty: self.ty.canonical(),
}
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
> Shl<usize> for IntValue<LhsType>
{
type Output = IntValue<DynIntType<Signed>>;
fn shl(self, rhs: usize) -> Self::Output {
let ty = self.valueless().shl(rhs).ty;
IntValue::with_type(ty, Shl::shl(self.into_value(), rhs))
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
> Shl<usize> for Valueless<LhsType>
{
type Output = Valueless<DynIntType<Signed>>;
fn shl(self, rhs: usize) -> Self::Output {
let ty = DynIntType::new(
self.ty
.width()
.checked_add(rhs)
.expect("shift amount is too big"),
);
Valueless { ty }
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
> Shr<usize> for IntValue<LhsType>
{
type Output = IntValue<DynIntType<Signed>>;
fn shr(self, rhs: usize) -> Self::Output {
IntValue::with_type(
self.valueless().shr(rhs).ty,
Shr::shr(self.into_value(), rhs),
)
}
}
impl<
Signed: GenericConstBool,
LhsType: IntTypeTrait<
Signed = Signed,
CanonicalType = DynIntType<<LhsType as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<LhsType as IntTypeTrait>::Signed>,
>,
> Shr<usize> for Valueless<LhsType>
{
type Output = Valueless<DynIntType<Signed>>;
fn shr(self, rhs: usize) -> Self::Output {
let ty = DynIntType::new(self.ty.width().saturating_sub(rhs).max(1));
Valueless { ty }
}
}
impl<
T: IntTypeTrait<
Signed = ConstBool<true>,
CanonicalType = DynSIntType,
CanonicalValue = DynSInt,
>,
> Neg for IntValue<T>
{
type Output = IntValue<DynSIntType>;
fn neg(self) -> Self::Output {
let ty = self.valueless().neg().ty;
IntValue::with_type(ty, Neg::neg(self.into_value()))
}
}
impl<
T: IntTypeTrait<
Signed = ConstBool<true>,
CanonicalType = DynSIntType,
CanonicalValue = DynSInt,
>,
> Neg for Valueless<T>
{
type Output = Valueless<DynSIntType>;
fn neg(self) -> Self::Output {
let ty = DynIntType::new(
self.ty
.width()
.checked_add(1)
.expect("result has too many bits"),
);
Valueless { ty }
}
}
impl<
T: IntTypeTrait<
CanonicalType = DynIntType<<T as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<T as IntTypeTrait>::Signed>,
>,
> Not for IntValue<T>
{
type Output = IntValue<T>;
fn not(self) -> Self::Output {
IntValue::with_type(self.valueless().not().ty, Not::not(self.into_value()))
}
}
impl<
T: IntTypeTrait<
CanonicalType = DynIntType<<T as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<T as IntTypeTrait>::Signed>,
>,
> Not for Valueless<T>
{
type Output = Valueless<T>;
fn not(self) -> Self::Output {
self
}
}
macro_rules! impl_int {
($ty:ident, $SIGNED:literal) => {
impl From<$ty> for Int<ConstBool<$SIGNED>, { $ty::BITS as usize }> {
fn from(v: $ty) -> Self {
Self::new(v)
}
}
impl From<Int<ConstBool<$SIGNED>, { $ty::BITS as usize }>> for $ty {
fn from(v: Int<ConstBool<$SIGNED>, { $ty::BITS as usize }>) -> Self {
v.value().try_into().unwrap()
}
}
impl ToExpr for $ty {
type Type = IntType<ConstBool<$SIGNED>, { $ty::BITS as usize }>;
fn ty(&self) -> Self::Type {
IntType::new()
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
IntValue::from(*self).to_expr()
}
}
};
}
impl_int!(u8, false);
impl_int!(u16, false);
impl_int!(u32, false);
impl_int!(u64, false);
impl_int!(u128, false);
impl_int!(i8, true);
impl_int!(i16, true);
impl_int!(i32, true);
impl_int!(i64, true);
impl_int!(i128, true);
impl<
T: FixedOrDynIntType<
1,
Signed = ConstBool<false>,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>,
> From<bool> for IntValue<T>
{
fn from(v: bool) -> Self {
IntValue::with_type(T::new(), v)
}
}
impl ToExpr for bool {
type Type = UIntType<1>;
fn ty(&self) -> Self::Type {
IntType::new()
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
UInt::from(*self).to_expr()
}
}
pub trait IntCmp<Rhs> {
type Output;
fn cmp_eq(self, rhs: Rhs) -> Self::Output;
fn cmp_ne(self, rhs: Rhs) -> Self::Output;
fn cmp_lt(self, rhs: Rhs) -> Self::Output;
fn cmp_le(self, rhs: Rhs) -> Self::Output;
fn cmp_gt(self, rhs: Rhs) -> Self::Output;
fn cmp_ge(self, rhs: Rhs) -> Self::Output;
}
macro_rules! forward_prim_int_cmp {
($prim_ty:ident) => {
impl<Rhs> IntCmp<Rhs> for $prim_ty
where
IntValue<<$prim_ty as ToExpr>::Type>: IntCmp<Rhs>,
{
type Output = <IntValue<<$prim_ty as ToExpr>::Type> as IntCmp<Rhs>>::Output;
fn cmp_eq(self, rhs: Rhs) -> Self::Output {
IntValue::<<$prim_ty as ToExpr>::Type>::from(self).cmp_eq(rhs)
}
fn cmp_ne(self, rhs: Rhs) -> Self::Output {
IntValue::<<$prim_ty as ToExpr>::Type>::from(self).cmp_ne(rhs)
}
fn cmp_lt(self, rhs: Rhs) -> Self::Output {
IntValue::<<$prim_ty as ToExpr>::Type>::from(self).cmp_lt(rhs)
}
fn cmp_le(self, rhs: Rhs) -> Self::Output {
IntValue::<<$prim_ty as ToExpr>::Type>::from(self).cmp_le(rhs)
}
fn cmp_gt(self, rhs: Rhs) -> Self::Output {
IntValue::<<$prim_ty as ToExpr>::Type>::from(self).cmp_gt(rhs)
}
fn cmp_ge(self, rhs: Rhs) -> Self::Output {
IntValue::<<$prim_ty as ToExpr>::Type>::from(self).cmp_ge(rhs)
}
}
};
}
forward_prim_int_cmp!(bool);
forward_prim_int_cmp!(u8);
forward_prim_int_cmp!(u16);
forward_prim_int_cmp!(u32);
forward_prim_int_cmp!(u64);
forward_prim_int_cmp!(u128);
forward_prim_int_cmp!(i8);
forward_prim_int_cmp!(i16);
forward_prim_int_cmp!(i32);
forward_prim_int_cmp!(i64);
forward_prim_int_cmp!(i128);
mod sealed {
pub trait Sealed {}
}
pub trait IntTypeTrait:
sealed::Sealed
+ Copy
+ 'static
+ Eq
+ Hash
+ fmt::Debug
+ Type<Value = IntValue<Self>>
+ Connect<Self>
+ Connect<DynIntType<<Self as IntTypeTrait>::Signed>>
{
type Signed: GenericConstBool;
type SameWidthUInt: IntTypeTrait<
Signed = ConstBool<false>,
SameWidthUInt = Self::SameWidthUInt,
SameWidthSInt = Self::SameWidthSInt,
CanonicalType = DynUIntType,
CanonicalValue = DynUInt,
>;
type SameWidthSInt: IntTypeTrait<
Signed = ConstBool<true>,
SameWidthUInt = Self::SameWidthUInt,
SameWidthSInt = Self::SameWidthSInt,
CanonicalType = DynSIntType,
CanonicalValue = DynSInt,
>;
fn literal(self, value: impl Into<BigInt>) -> Expr<IntValue<Self>>
where
Self: IntTypeTrait<
CanonicalType = DynIntType<<Self as IntTypeTrait>::Signed>,
CanonicalValue = DynInt<<Self as IntTypeTrait>::Signed>,
>,
{
IntValue::with_type(self, value).to_expr()
}
fn from_width_unchecked(width: usize) -> Self;
fn width(self) -> usize;
fn as_same_width_uint(self) -> Self::SameWidthUInt;
fn as_same_width_sint(self) -> Self::SameWidthSInt;
fn as_same_value_uint(self) -> DynUIntType {
let mut width = self.width();
if Self::Signed::VALUE {
width = width.saturating_sub(1);
}
DynIntType {
width,
_phantom: PhantomData,
}
}
fn as_same_value_sint(self) -> DynSIntType {
let mut width = self.width();
if !Self::Signed::VALUE {
width = width.checked_add(1).expect("result too big");
}
DynIntType::new(width)
}
fn min_value(self) -> BigInt {
let width = self.width();
if Self::Signed::VALUE && width > 0 {
BigInt::from(-1i8) << (width - 1)
} else {
BigInt::from(0u8)
}
}
fn max_value(self) -> BigUint {
(BigUint::from(1u8) << self.width().saturating_sub(Self::Signed::VALUE.into())) - 1u8
}
fn modulo(self) -> BigUint {
BigUint::from(1u8) << self.width()
}
fn slice_index_to_range<I: RangeBounds<usize>>(self, index: I) -> Range<usize> {
let width = self.width();
let start = match index.start_bound() {
Bound::Included(start) => *start,
Bound::Excluded(start) => *start + 1,
Bound::Unbounded => 0,
};
let end = match index.end_bound() {
Bound::Included(end) => *end + 1,
Bound::Excluded(end) => *end,
Bound::Unbounded => width,
};
assert!(start <= end && end <= width, "slice range out-of-range");
start..end
}
fn slice_and_shift<I: RangeBounds<usize>>(self, index: I) -> (DynUIntType, usize) {
let range = self.slice_index_to_range(index);
let width = range.end - range.start;
(DynUIntType::new(width), range.start)
}
fn slice<I: RangeBounds<usize>>(self, index: I) -> DynUIntType {
self.slice_and_shift(index).0
}
}
pub trait FixedOrDynIntType<const WIDTH: usize>: IntTypeTrait {
fn new() -> Self;
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct DynIntType<Signed: GenericConstBool> {
pub width: usize,
_phantom: PhantomData<Signed>,
}
impl<Signed: GenericConstBool> fmt::Debug for DynIntType<Signed> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if Signed::VALUE {
write!(f, "dyn_i{}", self.width)
} else {
write!(f, "dyn_u{}", self.width)
}
}
}
impl<Signed: GenericConstBool> DynIntType<Signed> {
pub const fn new(width: usize) -> Self {
Self {
width,
_phantom: PhantomData,
}
}
}
pub type DynUIntType = DynIntType<ConstBool<false>>;
pub type DynSIntType = DynIntType<ConstBool<true>>;
impl<Signed: GenericConstBool> fmt::Display for DynIntType<Signed> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}({})",
if Signed::VALUE { "SInt" } else { "UInt" },
self.width
)
}
}
impl DynUIntType {
pub fn mask(self) -> BigUint {
self.modulo() - 1u8
}
/// gets the smallest `UInt` that fits `v` losslessly
pub fn for_value(v: impl Into<BigUint>) -> Self {
let v: BigUint = v.into();
Self::new(v.bits().try_into().expect("too big"))
}
/// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty
#[track_caller]
pub fn range(r: Range<impl Into<BigUint>>) -> Self {
let start: BigUint = r.start.into();
let end: BigUint = r.end.into();
assert!(!end.is_zero(), "empty range");
Self::range_inclusive(start..=(end - 1u8))
}
/// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty
#[track_caller]
pub fn range_inclusive(r: RangeInclusive<impl Into<BigUint>>) -> Self {
let (start, end) = r.into_inner();
let start: BigUint = start.into();
let end: BigUint = end.into();
assert!(start <= end, "empty range");
// no need to check `start`` since it's no larger than `end`
// so must not take more bits than `end`
Self::for_value(end)
}
}
impl DynSIntType {
/// gets the smallest `SInt` that fits `v` losslessly
pub fn for_value(v: impl Into<BigInt>) -> Self {
let v: BigInt = v.into();
Self::new(
match v.sign() {
Sign::Minus => {
// account for sign bit and for the minimum value of an `SInt`
// being the negative of the maximum value minus one.
v.not().bits().checked_add(1).expect("too big")
}
Sign::NoSign => 0,
Sign::Plus => v.bits(),
}
.try_into()
.expect("too big"),
)
}
/// gets the smallest `SInt` that fits `r` losslessly, panics if `r` is empty
#[track_caller]
pub fn range(r: Range<impl Into<BigInt>>) -> Self {
let start: BigInt = r.start.into();
let end: BigInt = r.end.into();
Self::range_inclusive(start..=(end - 1))
}
/// gets the smallest `SInt` that fits `r` losslessly, panics if `r` is empty
#[track_caller]
pub fn range_inclusive(r: RangeInclusive<impl Into<BigInt>>) -> Self {
let (start, end) = r.into_inner();
let start: BigInt = start.into();
let end: BigInt = end.into();
assert!(start <= end, "empty range");
Self::new(Self::for_value(start).width.max(Self::for_value(end).width))
}
}
impl<Signed: GenericConstBool> sealed::Sealed for DynIntType<Signed> {}
impl<Signed: GenericConstBool> Type for DynIntType<Signed> {
type CanonicalType = DynIntType<Signed>;
type Value = IntValue<DynIntType<Signed>>;
type CanonicalValue = IntValue<DynIntType<Signed>>;
type MaskType = UIntType<1>;
type MaskValue = UInt<1>;
impl_match_values_as_self!();
fn mask_type(&self) -> Self::MaskType {
UIntType::new()
}
fn canonical(&self) -> Self::CanonicalType {
*self
}
fn source_location(&self) -> SourceLocation {
SourceLocation::builtin()
}
fn type_enum(&self) -> TypeEnum {
if Signed::VALUE {
TypeEnum::SInt(self.as_same_width_sint())
} else {
TypeEnum::UInt(self.as_same_width_uint())
}
}
fn from_canonical_type(t: Self::CanonicalType) -> Self {
t
}
fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> {
Some(this)
}
}
impl<Signed: GenericConstBool> Connect<Self> for DynIntType<Signed> {}
impl<Signed: GenericConstBool, const A: usize> Connect<IntType<Signed, A>> for DynIntType<Signed> {}
impl<Signed: GenericConstBool, const B: usize> Connect<DynIntType<Signed>> for IntType<Signed, B> {}
impl<Signed: GenericConstBool, const A: usize, const B: usize> Connect<IntType<Signed, A>>
for IntType<Signed, B>
{
}
impl<Signed: GenericConstBool> CanonicalType for DynIntType<Signed> {
const CANONICAL_TYPE_KIND: CanonicalTypeKind = if Signed::VALUE {
CanonicalTypeKind::SInt
} else {
CanonicalTypeKind::UInt
};
}
impl<Signed: GenericConstBool> CanonicalValue for IntValue<DynIntType<Signed>> {
fn value_enum_impl(this: &Self) -> ValueEnum {
if Signed::VALUE {
ValueEnum::SInt(this.clone().as_same_width_sint())
} else {
ValueEnum::UInt(this.clone().as_same_width_uint())
}
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
<Self as Value>::to_bits_impl(this)
}
}
impl<Signed: GenericConstBool> IntTypeTrait for DynIntType<Signed> {
type Signed = Signed;
type SameWidthUInt = DynUIntType;
type SameWidthSInt = DynSIntType;
fn width(self) -> usize {
self.width
}
fn as_same_width_uint(self) -> Self::SameWidthUInt {
DynIntType::new(self.width)
}
fn as_same_width_sint(self) -> Self::SameWidthSInt {
DynIntType::new(self.width)
}
fn from_width_unchecked(width: usize) -> Self {
DynIntType::new(width)
}
}
impl<Signed: GenericConstBool, const WIDTH: usize> FixedOrDynIntType<WIDTH> for DynIntType<Signed> {
fn new() -> Self {
DynIntType::new(WIDTH)
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct IntType<Signed: GenericConstBool, const WIDTH: usize>(PhantomData<Signed>);
impl<Signed: GenericConstBool, const WIDTH: usize> fmt::Debug for IntType<Signed, WIDTH> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if Signed::VALUE {
write!(f, "i{WIDTH}")
} else {
write!(f, "u{WIDTH}")
}
}
}
impl<Signed: GenericConstBool, const WIDTH: usize> IntType<Signed, WIDTH> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
pub type UIntType<const WIDTH: usize> = IntType<ConstBool<false>, WIDTH>;
pub type SIntType<const WIDTH: usize> = IntType<ConstBool<true>, WIDTH>;
impl<Signed: GenericConstBool, const WIDTH: usize> sealed::Sealed for IntType<Signed, WIDTH> {}
impl<Signed: GenericConstBool, const WIDTH: usize> Type for IntType<Signed, WIDTH> {
type CanonicalType = DynIntType<Signed>;
type Value = IntValue<IntType<Signed, WIDTH>>;
type CanonicalValue = IntValue<DynIntType<Signed>>;
type MaskType = UIntType<1>;
type MaskValue = UInt<1>;
impl_match_values_as_self!();
fn mask_type(&self) -> Self::MaskType {
UIntType::new()
}
fn canonical(&self) -> Self::CanonicalType {
DynIntType::new(WIDTH)
}
fn source_location(&self) -> SourceLocation {
SourceLocation::builtin()
}
fn type_enum(&self) -> TypeEnum {
self.canonical().type_enum()
}
fn from_canonical_type(t: Self::CanonicalType) -> Self {
assert_eq!(t.width, WIDTH);
IntType::new()
}
}
impl<Signed: GenericConstBool, const WIDTH: usize> FixedType for IntType<Signed, WIDTH> {
fn fixed_type() -> Self {
Self::new()
}
}
impl<Signed: GenericConstBool, const WIDTH: usize> IntTypeTrait for IntType<Signed, WIDTH> {
type Signed = Signed;
type SameWidthUInt = UIntType<WIDTH>;
type SameWidthSInt = SIntType<WIDTH>;
fn width(self) -> usize {
WIDTH
}
fn as_same_width_uint(self) -> Self::SameWidthUInt {
IntType::new()
}
fn as_same_width_sint(self) -> Self::SameWidthSInt {
IntType::new()
}
fn from_width_unchecked(_width: usize) -> Self {
IntType::new()
}
}
impl<Signed: GenericConstBool, const WIDTH: usize> FixedOrDynIntType<WIDTH>
for IntType<Signed, WIDTH>
{
fn new() -> Self {
IntType::new()
}
}
impl<Signed: GenericConstBool, const WIDTH: usize> fmt::Display for IntType<Signed, WIDTH> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.canonical().fmt(f)
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct IntLiteral<'a, Signed: GenericConstBool, const WIDTH: usize> {
magnitude_le_bytes: &'a [u8],
negative: bool,
_phantom: PhantomData<Signed>,
}
#[track_caller]
pub const fn make_int_literal<const SIGNED: bool, const WIDTH: usize>(
negative: bool,
magnitude_le_bytes: &[u8],
) -> IntLiteral<'_, ConstBool<SIGNED>, WIDTH> {
IntLiteral::new(negative, magnitude_le_bytes)
}
impl<'a, Signed: GenericConstBool, const WIDTH: usize> IntLiteral<'a, Signed, WIDTH> {
pub const ZERO: IntLiteral<'static, Signed, WIDTH> = IntLiteral {
magnitude_le_bytes: &[],
negative: false,
_phantom: PhantomData,
};
#[track_caller]
pub const fn new(negative: bool, mut magnitude_le_bytes: &'a [u8]) -> Self {
while let [prefix @ .., 0] = magnitude_le_bytes {
magnitude_le_bytes = prefix;
}
assert!(
magnitude_le_bytes.len() <= Self::WIDTH_IN_BYTES,
"literal magnitude is too large"
);
if magnitude_le_bytes.is_empty() {
return Self::ZERO;
}
assert!(WIDTH != 0, "zero-bit integer literal must be zero");
let retval = Self {
magnitude_le_bytes,
negative,
_phantom: PhantomData,
};
let Some(magnitude_ilog2) = retval.magnitude_ilog2() else {
panic!("literal magnitude is too large");
};
if Self::SIGNED {
if negative {
assert!(
magnitude_ilog2 < Self::WIDTH,
"literal magnitude is too large"
);
if magnitude_ilog2 == Self::WIDTH - 1 {
// must be `1 << (WIDTH - 1)`, so the msb byte is a
// power of two, and all other bytes are zero
let [prefix @ .., last] = magnitude_le_bytes else {
panic!("checked that magnitude_le_bytes is not empty");
};
assert!(last.is_power_of_two(), "literal magnitude is too large");
let mut i = 0;
while i < prefix.len() {
assert!(prefix[i] == 0, "literal magnitude is too large");
i += 1;
}
}
} else {
assert!(
magnitude_ilog2 < Self::WIDTH - 1,
"literal magnitude is too large"
);
}
} else {
assert!(!negative, "unsigned literal can't be negative");
assert!(
magnitude_ilog2 < Self::WIDTH,
"literal magnitude is too large"
);
}
retval
}
pub const fn magnitude_ilog2(self) -> Option<usize> {
let Some(&last) = self.magnitude_le_bytes.last() else {
return None; // trying to calculate ilog2 of 0
};
// last is known to be non-zero, so ilog2 won't panic
let last_ilog2 = last.ilog2() as usize;
let Some(prod) = (u8::BITS as usize).checked_mul(self.magnitude_le_bytes.len() - 1) else {
return None; // product overflows
};
last_ilog2.checked_add(prod)
}
pub const fn magnitude_le_bytes(self) -> &'a [u8] {
self.magnitude_le_bytes
}
#[track_caller]
pub const fn magnitude_le_bytes_array<const N: usize>(self) -> [u8; N] {
assert!(self.magnitude_le_bytes.len() <= N, "literal too big");
let mut retval = [0u8; N];
let mut i = 0;
while i < self.magnitude_le_bytes.len() {
retval[i] = self.magnitude_le_bytes[i];
i += 1;
}
retval
}
pub const fn wrapping_le_bytes_array<const N: usize>(self) -> [u8; N] {
let mut retval = [0u8; N];
let mut i = 0;
let mut carry = self.negative;
while i < N {
let mut byte = if i < self.magnitude_le_bytes.len() {
self.magnitude_le_bytes[i]
} else {
0
};
if self.negative {
(byte, carry) = (carry as u8).overflowing_add(!byte);
}
retval[i] = byte;
i += 1;
}
retval
}
pub const fn negative(self) -> bool {
self.negative
}
pub const SIGNED: bool = Signed::VALUE;
pub const WIDTH: usize = {
// check isize::MAX instead of usize::MAX because we need to be able to have arrays that big
assert!(
WIDTH.div_ceil(u8::BITS as usize) < isize::MAX as usize,
"WIDTH too big"
);
WIDTH
};
pub const WIDTH_IN_BYTES: usize = {
// use Self::WIDTH instead of WIDTH to assert WIDTH isn't too big
Self::WIDTH.div_ceil(u8::BITS as usize)
};
pub fn to_int_value(self) -> Int<Signed, WIDTH> {
Int::new(BigInt::from_bytes_le(
if self.negative() {
Sign::Minus
} else {
Sign::Plus
},
self.magnitude_le_bytes(),
))
}
pub fn to_int_expr(self) -> Expr<Int<Signed, WIDTH>> {
self.to_int_value().to_expr()
}
}
const _: () = {
// test make_int_literal
#[track_caller]
const fn check<const SIGNED: bool, const WIDTH: usize>(
negative: bool,
magnitude_le_bytes: &[u8],
expected: i64,
) {
let value = i64::from_le_bytes(
make_int_literal::<SIGNED, WIDTH>(negative, magnitude_le_bytes)
.wrapping_le_bytes_array(),
);
assert!(value == expected);
}
check::<false, 0>(false, &[0], 0);
check::<true, 0>(true, &[0], 0);
check::<false, 1>(false, &[1], 1);
check::<true, 1>(true, &[1], -1);
check::<false, 7>(false, &[0x7F], 0x7F);
check::<true, 7>(false, &[0x3F], 0x3F);
check::<true, 7>(true, &[0x40], -0x40);
check::<false, 8>(false, &[0xFF], 0xFF);
check::<true, 8>(false, &[0x7F], 0x7F);
check::<true, 8>(true, &[0x80], -0x80);
check::<false, 15>(false, &[0xFF, 0x7F], 0x7FFF);
check::<true, 15>(false, &[0xFF, 0x3F], 0x3FFF);
check::<true, 15>(true, &[0, 0x40], -0x4000);
check::<false, 16>(false, &[0xFF, 0xFF], 0xFFFF);
check::<true, 16>(false, &[0xFF, 0x7F], 0x7FFF);
check::<true, 16>(true, &[0, 0x80], -0x8000);
check::<false, 72>(false, &[0xFF; 9], !0);
check::<true, 73>(false, &[0xFF; 9], !0);
check::<true, 73>(true, &[0xFF; 9], 1);
};