fayalite/crates/fayalite/src/int.rs

653 lines
21 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{
target::{GetTarget, Target},
Expr, NotALiteralExpr, ToExpr, ToLiteralBits,
},
intern::{Intern, Interned, Memoize},
source_location::SourceLocation,
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
util::{interned_bit, ConstBool, ConstUsize, GenericConstBool, GenericConstUsize},
};
use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec};
use num_bigint::{BigInt, BigUint, Sign};
use num_traits::{Signed, Zero};
use std::{
borrow::{BorrowMut, Cow},
fmt,
marker::PhantomData,
ops::{Bound, Index, Not, Range, RangeBounds, RangeInclusive},
sync::Arc,
};
mod sealed {
pub trait BoolOrIntTypeSealed {}
pub trait SizeSealed {}
}
pub const DYN_SIZE: usize = !0;
pub type DynSize = ConstUsize<DYN_SIZE>;
pub trait KnownSize: GenericConstUsize + Size<SizeType = Self> {
const SIZE: Self;
}
macro_rules! known_widths {
([] $($bits:literal)+) => {
impl KnownSize for ConstUsize<{
let v = 0;
$(let v = v * 2 + $bits;)*
v
}> {
const SIZE: Self = Self;
}
};
([2 $($rest:tt)*] $($bits:literal)+) => {
known_widths!([$($rest)*] $($bits)* 0);
known_widths!([$($rest)*] $($bits)* 1);
};
([2 $($rest:tt)*]) => {
known_widths!([$($rest)*] 0);
known_widths!([$($rest)*] 1);
impl KnownSize for ConstUsize<{2 $(* $rest)*}> {
const SIZE: Self = Self;
}
};
}
known_widths!([2 2 2 2 2 2 2 2 2]);
pub trait Size:
sealed::SizeSealed + Copy + Ord + std::hash::Hash + std::fmt::Debug + Send + Sync + 'static
{
type ArrayMatch<Element: Type>: AsRef<[Expr<Element>]>
+ AsMut<[Expr<Element>]>
+ BorrowMut<[Expr<Element>]>
+ 'static
+ Send
+ Sync
+ Eq
+ Clone
+ std::hash::Hash
+ std::fmt::Debug
+ IntoIterator<Item = Expr<Element>>
+ TryFrom<Vec<Expr<Element>>>
+ Into<Vec<Expr<Element>>>;
const KNOWN_VALUE: Option<usize>;
type SizeType: Copy + Ord + std::hash::Hash + std::fmt::Debug + Send + Sync + 'static;
fn as_usize(size_type: Self::SizeType) -> usize;
fn try_from_usize(v: usize) -> Option<Self::SizeType>;
#[track_caller]
fn from_usize(v: usize) -> Self::SizeType {
Self::try_from_usize(v).expect("wrong size")
}
}
impl Size for DynSize {
type ArrayMatch<Element: Type> = Box<[Expr<Element>]>;
const KNOWN_VALUE: Option<usize> = None;
type SizeType = usize;
fn as_usize(size_type: Self::SizeType) -> usize {
size_type
}
fn try_from_usize(v: usize) -> Option<Self::SizeType> {
Some(v)
}
}
impl<const VALUE: usize> sealed::SizeSealed for ConstUsize<VALUE> {}
impl<const VALUE: usize> Size for ConstUsize<VALUE>
where
ConstUsize<VALUE>: KnownSize,
{
type ArrayMatch<Element: Type> = [Expr<Element>; VALUE];
const KNOWN_VALUE: Option<usize> = Some(VALUE);
type SizeType = ConstUsize<VALUE>;
fn as_usize(_size_type: Self::SizeType) -> usize {
VALUE
}
fn try_from_usize(v: usize) -> Option<Self::SizeType> {
if v == VALUE {
Some(Self::SizeType::default())
} else {
None
}
}
}
macro_rules! impl_int {
($pretty_name:ident, $name:ident, $generic_name:ident, $value:ident, $SIGNED:literal) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct $name<Width: Size = DynSize> {
pub width: Width::SizeType,
}
impl<Width: Size> fmt::Debug for $name<Width> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}<{}>", stringify!($pretty_name), self.width())
}
}
pub type $pretty_name<
const WIDTH: usize = { <DynSize as crate::util::GenericConstUsize>::VALUE },
> = $name<ConstUsize<WIDTH>>;
#[allow(non_upper_case_globals)]
pub const $pretty_name: $generic_name = $generic_name;
#[allow(non_upper_case_globals)]
pub const $name: $generic_name = $generic_name;
impl<Width: Size> $name<Width> {
pub fn new(width: Width::SizeType) -> Self {
Self { width }
}
pub fn width(self) -> usize {
Width::as_usize(self.width)
}
pub fn type_properties(self) -> TypeProperties {
TypeProperties {
is_passive: true,
is_storable: true,
is_castable_from_bits: true,
bit_width: self.width(),
}
}
pub fn bits_from_bigint_wrapping(self, v: BigInt) -> BitVec {
BoolOrIntType::bits_from_bigint_wrapping(self, v)
}
pub fn from_bigint_wrapping(self, v: BigInt) -> $value<Width> {
$value {
bits: Arc::new(self.bits_from_bigint_wrapping(v)),
_phantom: PhantomData,
}
}
pub fn from_int_wrapping(self, v: impl Into<BigInt>) -> $value<Width> {
self.from_bigint_wrapping(v.into())
}
pub fn zero(self) -> $value<Width> {
self.from_int_wrapping(0u8)
}
pub fn can_connect<RhsWidth: Size>(self, _rhs: $name<RhsWidth>) -> bool {
true
}
}
impl<Width: Size> sealed::BoolOrIntTypeSealed for $name<Width> {}
impl<Width: Size> BoolOrIntType for $name<Width> {
type Width = Width;
type Signed = ConstBool<$SIGNED>;
fn width(self) -> usize {
$name::width(self)
}
fn new(width: Width::SizeType) -> Self {
$name { width }
}
fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr<Self> {
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct MemoizeBitsToExpr;
impl Memoize for MemoizeBitsToExpr {
type Input = BitSlice;
type InputOwned = BitVec;
type Output = Expr<$name>;
fn inner(self, input: &Self::Input) -> Self::Output {
$value::new(Arc::new(input.to_bitvec())).to_expr()
}
}
Expr::from_dyn_int(MemoizeBitsToExpr.get_cow(bits))
}
}
impl<Width: Size> IntType for $name<Width> {
type Dyn = $name;
}
impl $name {
pub fn new_dyn(width: usize) -> Self {
Self { width }
}
pub fn bits_to_bigint(bits: &BitSlice) -> BigInt {
<Self as BoolOrIntType>::bits_to_bigint(bits)
}
}
impl<Width: KnownSize> $name<Width> {
pub fn new_static() -> Self {
Self {
width: Width::SizeType::default(),
}
}
}
impl<Width: Size> Type for $name<Width> {
type BaseType = $pretty_name;
type MaskType = Bool;
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
Bool
}
fn canonical(&self) -> CanonicalType {
CanonicalType::$pretty_name($name::new_dyn(self.width()))
}
#[track_caller]
fn from_canonical(canonical_type: CanonicalType) -> Self {
let CanonicalType::$pretty_name(retval) = canonical_type else {
panic!("expected {}", stringify!($name));
};
$name {
width: Width::from_usize(retval.width),
}
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
}
impl<Width: KnownSize> StaticType for $name<Width> {
const TYPE: Self = Self { width: Width::SIZE };
const MASK_TYPE: Self::MaskType = Bool;
const TYPE_PROPERTIES: TypeProperties = TypeProperties {
is_passive: true,
is_storable: true,
is_castable_from_bits: true,
bit_width: Width::VALUE,
};
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct $generic_name;
impl Index<usize> for $generic_name {
type Output = $name;
fn index(&self, width: usize) -> &Self::Output {
Interned::<_>::into_inner(Intern::intern_sized($name::new_dyn(width)))
}
}
impl<const WIDTH: usize> Index<ConstUsize<WIDTH>> for $generic_name
where
ConstUsize<WIDTH>: KnownSize,
{
type Output = $pretty_name<WIDTH>;
fn index(&self, width: ConstUsize<WIDTH>) -> &Self::Output {
Interned::<_>::into_inner(Intern::intern_sized($name::new(width)))
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct $value<Width: Size = DynSize> {
bits: Arc<BitVec>,
_phantom: PhantomData<Width>,
}
impl<Width: Size> fmt::Debug for $value<Width> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = self.to_bigint();
let (sign, magnitude) = value.into_parts();
let sign = match sign {
Sign::Minus => "-",
Sign::NoSign | Sign::Plus => "",
};
let signed = if $SIGNED { "i" } else { "u" };
let width = self.width();
write!(f, "{sign}0x{magnitude:X}_{signed}{width}")
}
}
impl<Width: Size> $value<Width> {
pub fn width(&self) -> usize {
if let Some(retval) = Width::KNOWN_VALUE {
debug_assert!(self.bits.len() == retval);
retval
} else {
self.bits.len()
}
}
pub fn from_bigint_wrapping(ty: $name<Width>, v: BigInt) -> $value<Width> {
ty.from_bigint_wrapping(v)
}
pub fn to_bigint(&self) -> BigInt {
$name::bits_to_bigint(&self.bits)
}
pub fn into_bits(self) -> Arc<BitVec> {
self.bits
}
pub fn ty(&self) -> $name<Width> {
$name {
width: Width::from_usize(self.width()),
}
}
pub fn as_dyn_int(self) -> $value {
$value {
bits: self.bits,
_phantom: PhantomData,
}
}
pub fn bits(&self) -> &Arc<BitVec> {
&self.bits
}
}
impl<Width: Size> ToLiteralBits for $value<Width> {
fn to_literal_bits(&self) -> Result<Interned<BitSlice>, NotALiteralExpr> {
Ok(BitSlice::intern(&self.bits))
}
}
impl<Width: Size> GetTarget for $value<Width> {
fn target(&self) -> Option<Interned<Target>> {
None
}
}
impl<Width: Size> $value<Width> {
pub fn new(bits: Arc<BitVec>) -> Self {
Width::from_usize(bits.len()); // check that len is correct
Self {
bits,
_phantom: PhantomData,
}
}
}
impl<Width: KnownSize> $value<Width> {
pub fn new_static(bits: Arc<BitVec>) -> Self {
assert!(bits.len() == Width::VALUE);
Self {
bits,
_phantom: PhantomData,
}
}
}
impl $value {
pub fn new_dyn(bits: Arc<BitVec>) -> Self {
Self {
bits,
_phantom: PhantomData,
}
}
}
};
}
impl_int!(UInt, UIntType, UIntWithoutGenerics, UIntValue, false);
impl_int!(SInt, SIntType, SIntWithoutGenerics, SIntValue, true);
impl UInt {
/// 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 SInt {
/// 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))
}
}
macro_rules! impl_prim_int {
($prim_int:ident, $ty:ty) => {
impl ToExpr for $prim_int {
type Type = $ty;
fn to_expr(&self) -> Expr<Self::Type> {
<$ty>::le_bytes_to_expr_wrapping(
&self.to_le_bytes(),
<$ty as BoolOrIntType>::Width::VALUE,
)
}
}
};
}
impl_prim_int!(u8, UInt<8>);
impl_prim_int!(u16, UInt<16>);
impl_prim_int!(u32, UInt<32>);
impl_prim_int!(u64, UInt<64>);
impl_prim_int!(u128, UInt<128>);
impl_prim_int!(i8, SInt<8>);
impl_prim_int!(i16, SInt<16>);
impl_prim_int!(i32, SInt<32>);
impl_prim_int!(i64, SInt<64>);
impl_prim_int!(i128, SInt<128>);
pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
type Width: Size;
type Signed: GenericConstBool;
fn width(self) -> usize;
fn new(width: <Self::Width as Size>::SizeType) -> Self;
fn new_static() -> Self
where
Self::Width: KnownSize,
{
Self::new(<Self::Width as Size>::SizeType::default())
}
fn as_same_width_sint(self) -> SIntType<Self::Width> {
SIntType::new(Self::Width::from_usize(self.width()))
}
fn as_same_width_uint(self) -> UIntType<Self::Width> {
UIntType::new(Self::Width::from_usize(self.width()))
}
fn bits_from_bigint_wrapping(self, v: BigInt) -> BitVec {
let width = self.width();
let mut bytes = v.to_signed_bytes_le();
bytes.resize(
width.div_ceil(u8::BITS as usize),
if v.is_negative() { 0xFF } else { 0 },
);
let bitslice = &BitSlice::<u8, Lsb0>::from_slice(&bytes)[..width];
let mut bits = BitVec::new();
bits.extend_from_bitslice(bitslice);
bits
}
fn bits_to_bigint(bits: &BitSlice) -> BigInt {
let sign_byte = if Self::Signed::VALUE && bits.last().as_deref().copied().unwrap_or(false) {
0xFF
} else {
0
};
let mut bytes = vec![sign_byte; bits.len() / u8::BITS as usize + 1];
BitSlice::<u8, Lsb0>::from_slice_mut(&mut bytes)[..bits.len()].clone_from_bitslice(bits);
BigInt::from_signed_bytes_le(&bytes)
}
fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr<Self>;
fn le_bytes_to_expr_wrapping(bytes: &[u8], bit_width: usize) -> Expr<Self> {
let bitslice = BitSlice::<u8, Lsb0>::from_slice(&bytes);
let bitslice = &bitslice[..bit_width.min(bitslice.len())];
let mut bits = BitVec::new();
bits.extend_from_bitslice(bitslice);
bits.resize(
bit_width,
Self::Signed::VALUE && bits.last().as_deref().copied().unwrap_or(false),
);
Self::bits_to_expr(Cow::Owned(bits))
}
}
pub trait IntType: BoolOrIntType<BaseType = <Self as IntType>::Dyn> {
type Dyn: IntType<Dyn = Self::Dyn, Signed = Self::Signed, Width = DynSize>;
fn as_dyn_int(self) -> Self::Dyn {
Self::new_dyn(self.width())
}
fn try_from_dyn_int(value: Self::Dyn) -> Option<Self> {
Some(Self::new(Self::Width::try_from_usize(value.width())?))
}
#[track_caller]
fn from_dyn_int(value: Self::Dyn) -> Self {
Self::try_from_dyn_int(value).expect("wrong size")
}
fn new_dyn(width: usize) -> Self::Dyn {
Self::Dyn::new(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) -> (UInt, usize) {
let range = self.slice_index_to_range(index);
let width = range.end - range.start;
(UInt::new_dyn(width), range.start)
}
fn slice<I: RangeBounds<usize>>(self, index: I) -> UInt {
self.slice_and_shift(index).0
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Bool;
impl sealed::BoolOrIntTypeSealed for Bool {}
impl BoolOrIntType for Bool {
type Width = ConstUsize<1>;
type Signed = ConstBool<false>;
fn width(self) -> usize {
1
}
fn new(width: <Self::Width as Size>::SizeType) -> Self {
let ConstUsize {} = width;
Bool
}
fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr<Self> {
assert_eq!(bits.len(), 1);
bits[0].to_expr()
}
}
impl Bool {
pub fn type_properties(self) -> TypeProperties {
Self::TYPE_PROPERTIES
}
pub fn can_connect(self, _rhs: Self) -> bool {
true
}
}
impl Type for Bool {
type BaseType = Bool;
type MaskType = Bool;
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
Bool
}
fn canonical(&self) -> CanonicalType {
CanonicalType::Bool(Bool)
}
#[track_caller]
fn from_canonical(canonical_type: CanonicalType) -> Self {
let CanonicalType::Bool(retval) = canonical_type else {
panic!("expected Bool");
};
retval
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
}
impl StaticType for Bool {
const TYPE: Self = Bool;
const MASK_TYPE: Self::MaskType = Bool;
const TYPE_PROPERTIES: TypeProperties = TypeProperties {
is_passive: true,
is_storable: true,
is_castable_from_bits: true,
bit_width: 1,
};
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
pub trait IntCmp<Rhs> {
fn cmp_eq(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_ne(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_lt(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_le(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_gt(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_ge(self, rhs: Rhs) -> Expr<Bool>;
}
impl ToLiteralBits for bool {
fn to_literal_bits(&self) -> Result<Interned<BitSlice>, NotALiteralExpr> {
Ok(interned_bit(*self))
}
}