From 71b35e8f034126d416d435fd718a2a6b95e94c4a Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 11 Mar 2023 04:12:06 -0600 Subject: [PATCH] Stage10 (#16) * remove most `const_as_ref` and `const_as_mut` * Improve InlAwi construction performance * Introduce `Digit` * fixes * add digit configuration flags and more * fix bug * fix bug with `subdigits` * introduce `bits` macro * enable more `bits` cases * test the new macro * test fix * add missing `_` to `digit_cin_mul_` * Version 0.10.0 --- .github/workflows/ci.yml | 31 +- CHANGELOG.md | 25 ++ README.md | 2 +- awint/Cargo.toml | 18 +- awint_core/Cargo.toml | 10 +- awint_core/src/data/bits.rs | 159 ++++----- awint_core/src/data/const_traits.rs | 26 +- awint_core/src/data/inlawi.rs | 158 +++++++-- awint_core/src/data/serde.rs | 8 +- awint_core/src/data/traits.rs | 26 +- awint_core/src/logic/bitwise.rs | 6 +- awint_core/src/logic/cc.rs | 1 + awint_core/src/logic/cmp.rs | 14 +- awint_core/src/logic/const_str.rs | 18 +- awint_core/src/logic/div.rs | 34 +- awint_core/src/logic/misc.rs | 2 +- awint_core/src/logic/mul.rs | 4 +- awint_core/src/logic/permute.rs | 22 +- awint_core/src/logic/primitives.rs | 45 +-- awint_core/src/logic/rand.rs | 2 +- awint_core/src/logic/sum.rs | 8 +- awint_dag/Cargo.toml | 8 +- awint_dag/src/common/eval.rs | 4 +- awint_dag/src/common/noop.rs | 12 +- awint_dag/src/common/state.rs | 4 +- awint_dag/src/lowering/meta.rs | 26 +- awint_dag/src/lowering/op_dag.rs | 4 +- awint_dag/src/mimick/awi_types.rs | 33 +- awint_dag/src/mimick/bits.rs | 8 +- awint_dag/src/mimick/ops.rs | 8 +- awint_dag/src/mimick/primitive.rs | 4 +- awint_ext/Cargo.toml | 10 +- awint_ext/src/extawi.rs | 93 +++--- awint_ext/src/fp_core.rs | 38 +-- awint_ext/src/fp_ieee.rs | 4 +- awint_ext/src/fp_logic.rs | 26 +- awint_ext/src/serde.rs | 5 +- awint_ext/src/strings.rs | 6 +- awint_internals/Cargo.toml | 10 +- awint_internals/src/lib.rs | 247 ++++++-------- awint_internals/src/macros.rs | 65 +++- awint_internals/src/serde_common.rs | 18 +- awint_internals/src/widening.rs | 358 +++++++++++++++++++++ awint_macro_internals/Cargo.toml | 4 +- awint_macro_internals/src/cc_macro.rs | 6 +- awint_macro_internals/src/component.rs | 2 +- awint_macro_internals/src/lib.rs | 24 +- awint_macro_internals/src/lower_structs.rs | 4 +- awint_macro_internals/src/lowering.rs | 9 +- awint_macro_internals/src/misc.rs | 95 +++++- awint_macro_internals/src/names.rs | 16 +- awint_macros/Cargo.toml | 6 +- awint_macros/src/lib.rs | 60 +++- no_alloc_test/Cargo.toml | 2 +- no_alloc_test/src/main.rs | 8 +- testcrate/assets/macro_outputs.vt100 | 98 +++++- testcrate/src/bin/macro_outputs.rs | 39 +++ testcrate/src/bin/stable.rs | 140 ++++++++ testcrate/tests/consts.rs | 28 +- testcrate/tests/dag_fuzzing.rs | 23 +- testcrate/tests/dag_macros.rs | 4 +- testcrate/tests/dag_misc.rs | 14 +- testcrate/tests/fuzz/identities.rs | 56 ++-- testcrate/tests/fuzz/mod.rs | 5 +- testcrate/tests/fuzz_all.rs | 60 ++-- testcrate/tests/macros.rs | 39 ++- testcrate/tests/misc.rs | 66 +++- testcrate/tests/serial.rs | 21 +- 68 files changed, 1713 insertions(+), 726 deletions(-) create mode 100644 awint_internals/src/widening.rs create mode 100644 testcrate/src/bin/stable.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d598db0..2903c10e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ env: # NOTE: do not set RUSTFLAGS here, because it overwrites # what `.cargo/config.toml` sets for `no_alloc_build` RUST_BACKTRACE: 1 + # we can't use `--all-features` because of the `_digits` flags, use + ALL_FEATURES: "--features=const_support,std,zeroize_support,rand_support,serde_support,dag,try_support,debug" + NON_STD_FEATURES: "--features=const_support,zeroize_support,rand_support,serde_support" jobs: test_suite: @@ -21,9 +24,14 @@ jobs: rustup default nightly - name: Run test suite run: | - cargo bench - cargo test --all-features - cargo test --release --all-features + cargo bench $ALL_FEATURES + cargo test $ALL_FEATURES + cargo test --release $ALL_FEATURES + cargo test $ALL_FEATURES,u8_digits + cargo test $ALL_FEATURES,u16_digits + cargo test $ALL_FEATURES,u32_digits + cargo test $ALL_FEATURES,u64_digits + cargo test $ALL_FEATURES,u128_digits miri: name: Miri @@ -41,7 +49,7 @@ jobs: rustup component add miri - name: Run test suite with Miri run: | - cargo miri test + cargo miri test $ALL_FEATURES # we run `cargo doc` here, because running `cargo doc` anywhere somehow requires `core` from the # target in no_alloc_test @@ -55,10 +63,10 @@ jobs: rustup set profile minimal rustup default nightly rustup target add riscv32i-unknown-none-elf - # no_alloc_test has `rand_support` and `serde_support` enabled in its `Cargo.toml` + # no_alloc_test has `NON_STD_FEATURES` enabled in its `Cargo.toml` - name: Run `cargo doc` and `cargo build` run: | - cargo doc --all-features + cargo doc $ALL_FEATURES cd no_alloc_test && cargo build --target riscv32i-unknown-none-elf stable_build: @@ -74,12 +82,13 @@ jobs: # mistakes, so we test several combinations here - name: Run `cargo build` run: | + cargo run --bin stable --no-default-features --features=std,rand_support,serde_support,zeroize_support,dag,debug cargo build --no-default-features --features=std,rand_support,serde_support,zeroize_support,dag,debug - cargo build --no-default-features --features=std cargo build --no-default-features --features=alloc + cargo build --no-default-features --features=std + cargo build --no-default-features --features=zeroize_support cargo build --no-default-features --features=rand_support cargo build --no-default-features --features=serde_support - cargo build --no-default-features --features=zeroize_support cargo build --no-default-features --features=dag cargo build --no-default-features --features=debug cargo build --no-default-features @@ -114,7 +123,7 @@ jobs: - name: Run `cargo clippy` # note: run clippy on no_alloc_test manually run: | - cargo clippy --all --all-targets --all-features -- -D clippy::all + cargo clippy --all --all-targets $ALL_FEATURES -- -D clippy::all # makes sure the tests work on 32 bit and 64 bit big endian targets cross-compile-big-endian: @@ -126,5 +135,5 @@ jobs: rustup set profile minimal rustup default nightly cargo install cross - cross test --target mips64-unknown-linux-gnuabi64 --all-features - cross test --target mips-unknown-linux-gnu --all-features + cross test --target mips64-unknown-linux-gnuabi64 $ALL_FEATURES + cross test --target mips-unknown-linux-gnu $ALL_FEATURES diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a03e90e..541c066a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [0.10.0] - 2023-03-11 +### Fixes +- Fixed that the overflow check in `chars_upper_bound` and `bits_upper_bound` was completely broken +- Greatly improved the efficiency of `awint` on restricted architectures such as AVR +- Fixed that `inlawi!` could cause double the necessary stack usage on all platforms +- Macro constants are now compiled down to `&'static [u8]` or `&'static Bits` +- `awint` should now theoretically work with 128 bit architectures like riscv128 + +### Additions +- Added `Digit`, a type alias for the underlying storage element for `Bits`. Also added various + primitive related functions for it. +- Added feature flags to control `Digit` +- Added the `bits` macro for creating `&'static Bits` constants easily +- Enabled `const` PartialEq + +### Changes +- Replaced `usize` with `Digit` where applicable. This does not immediately change things for common + architectures, but `Digit` can be different from `usize` now. +- Renamed `short_` functions to `digit_` functions +- Added missing `_` suffix to `digit_cin_mul_` +- `Digit` has a minimum guaranteed maximum value of `u8::MAX` rather than `u16::MAX` +- `const_as_ref` and `const_as_mut` removed from `InlAwi` and `ExtAwi` (although it still exists as + a hidden function on `Bits` for macro purposes) +- Many changes to hidden and unstable items + ## [0.9.0] - 2023-02-28 ### Fixes - Added a limiter to `FP::to_vec_general` and downstream string formatting to prevent easy resource diff --git a/README.md b/README.md index 494d28ea..448cbf27 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ system depending on these feature flags: Note: By default, "const_support" and "std" are turned on, use `default-features = false` and select specific features to avoid requiring nightly. -NOTE: As of Rust 1.64, if you try to use "const_support" with the macros you may get strange +NOTE: As of Rust 1.66, if you try to use "const_support" with the macros you may get strange "erroneous constant used" and "deref_mut" errors unless you add all of ``` #![feature(const_trait_impl)] diff --git a/awint/Cargo.toml b/awint/Cargo.toml index e6a8d7aa..ba084ae3 100644 --- a/awint/Cargo.toml +++ b/awint/Cargo.toml @@ -2,7 +2,7 @@ name = "awint" # note: when bumping versions, update `rust-version` in `awint_internals/Cargo.toml` and in # `ci.yml`, and add date to changelog -version = "0.9.0" # no 1.0 at least until `const_support` is all stabilized +version = "0.10.0" # no 1.0 at least until `const_support` is all stabilized edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" @@ -14,11 +14,11 @@ keywords = ["int", "integer", "big", "math", "no_std"] categories = ["data-structures", "mathematics", "algorithms", "no-std"] [dependencies] -awint_core = { version = "0.9.0", path = "../awint_core", default-features = false } -awint_dag = { version = "0.9.0", path = "../awint_dag", default-features = false, optional = true } -awint_ext = { version = "0.9.0", path = "../awint_ext", default-features = false, optional = true } -awint_macro_internals = { version = "0.9.0", path = "../awint_macro_internals", default-features = false, optional = true } -awint_macros = { version = "0.9.0", path = "../awint_macros" } +awint_core = { version = "0.10.0", path = "../awint_core", default-features = false } +awint_dag = { version = "0.10.0", path = "../awint_dag", default-features = false, optional = true } +awint_ext = { version = "0.10.0", path = "../awint_ext", default-features = false, optional = true } +awint_macro_internals = { version = "0.10.0", path = "../awint_macro_internals", default-features = false, optional = true } +awint_macros = { version = "0.10.0", path = "../awint_macros" } # note: running the tests requires `--all-features` [features] @@ -40,3 +40,9 @@ zeroize_support = ["awint_ext/zeroize_support"] # Turns on `awint_dag` support. Note: this activates `std`. dag = ["awint_dag", "std"] debug = ["awint_dag/debug", "awint_macro_internals/debug"] +# Only zero or one of these should be active +u8_digits = ["awint_core/u8_digits"] # Note: this is automatically active for AVR +u16_digits = ["awint_core/u16_digits"] +u32_digits = ["awint_core/u32_digits"] +u64_digits = ["awint_core/u64_digits"] +u128_digits = ["awint_core/u128_digits"] diff --git a/awint_core/Cargo.toml b/awint_core/Cargo.toml index 58673890..6cc34b7d 100644 --- a/awint_core/Cargo.toml +++ b/awint_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awint_core" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" @@ -12,7 +12,7 @@ keywords = ["int", "integer", "big", "math", "no_std"] categories = ["data-structures", "mathematics", "algorithms", "no-std"] [dependencies] -awint_internals = { version = "0.9.0", path = "../awint_internals", default-features = false } +awint_internals = { version = "0.10.0", path = "../awint_internals", default-features = false } const_fn = "0.4" rand_core = { version = "0.6", default-features = false, optional = true } serde = { version = "1.0", default-features = false, optional = true } @@ -33,3 +33,9 @@ rand_support = ["rand_core"] # Turns on `serde` support serde_support = ["serde"] zeroize_support = ["zeroize"] +# Only zero or one of these should be active +u8_digits = ["awint_internals/u8_digits"] +u16_digits = ["awint_internals/u16_digits"] +u32_digits = ["awint_internals/u32_digits"] +u64_digits = ["awint_internals/u64_digits"] +u128_digits = ["awint_internals/u128_digits"] diff --git a/awint_core/src/data/bits.rs b/awint_core/src/data/bits.rs index 56357cba..ca9d923f 100644 --- a/awint_core/src/data/bits.rs +++ b/awint_core/src/data/bits.rs @@ -4,12 +4,12 @@ //! nonzero, it eliminates several edge cases and ambiguities this crate would //! have to handle. //! - `Bits` are stored in little endian order with the same requirements as -//! `[usize]`. The number of `usize` digits is the minimum needed to store all -//! bits. If bitwidth is not a multiple of `usize::BITS`, then there will be -//! some unused bits in the last `usize` digit. For example, a bitwidth of of -//! 100 bits takes up 2 digits (if `usize::BITS == 64`): 64 bits in the first -//! digit, 36 bits in the least significant bits of the second, and 28 unused -//! bits in the remaining bits of the second. +//! `[Digit]`. The number of `Digit`s is the minimum needed to store all bits. +//! If bitwidth is not a multiple of `Digit::BITS`, then there will be some +//! unused bits in the last `Digit`. For example, a bitwidth of of 100 bits +//! takes up 2 digits (if `Digit::BITS == 64`): 64 bits in the first digit, 36 +//! bits in the least significant bits of the second, and 28 unused bits in +//! the remaining bits of the second. //! - Unused bits are zeroed. Note that this is not a safety critical invariant. //! Setting unused bits via `Bits::as_mut_slice` or `Bits::last_mut` will not //! cause Rust U.B., but it may result in arithmetically incorrect results or @@ -29,8 +29,6 @@ use core::{ use awint_internals::*; use const_fn::const_fn; -const BYTE_RATIO: usize = (usize::BITS / u8::BITS) as usize; - /// A reference to the bits in an `InlAwi`, `ExtAwi`, or other backing /// construct. If a function is written just in terms of `Bits`, it can work on /// mixed references to `InlAwi`s, `ExtAwi`s, and `FP`s. @@ -70,7 +68,7 @@ const BYTE_RATIO: usize = (usize::BITS / u8::BITS) as usize; /// # Portability /// /// This crate strives to maintain deterministic outputs across architectures -/// with different `usize::BITS` and different endiannesses. The +/// with different `usize::BITS`, `Digit::BITS`, and different endiannesses. The /// [Bits::u8_slice_] function, the [Bits::to_u8_slice] functions, the /// serialization impls enabled by `serde_support`, the strings produced by the /// `const` serialization functions, and functions like `bits_to_string_radix` @@ -86,31 +84,31 @@ const BYTE_RATIO: usize = (usize::BITS / u8::BITS) as usize; /// is because of technical problems, and the standard library docs say it is /// not intended to be portable anyway. /// -/// There are many functions that depend on `usize` and `NonZeroUsize`. In cases -/// where the `usize` describes the bitwidth, a bit shift, or a bit position, -/// the user should not need to worry about portability, since if the values are -/// close to `usize::MAX`, the user is already close to running out of possible -/// memory any way. +/// There are many functions that depend on `Digit`, `usize`, and +/// `NonZeroUsize`. In cases where the `usize` describes the bitwidth, a bit +/// shift, or a bit position, the user should not need to worry about +/// portability, since if the values are close to `usize::MAX`, the user is +/// already close to running out of possible memory any way. /// -/// There are a few usages of `usize` that are not just indexes but are actual +/// There are a few usages of `Digit` that are actual /// views into a contiguous range of bits inside `Bits`, such as /// `Bits::as_slice`, `Bits::first`, and `Bits::get_digit` (which are all hidden /// from the documentation, please refer to the source code of `bits.rs` if /// needed). Most end users should not use these, since they have a strong -/// dependence on the size of `usize`. These functions are actual views into the +/// dependence on the size of `Digit`. These functions are actual views into the /// inner building blocks of this crate that other functions are built around in /// such a way that they are portable (e.g. the addition functions may -/// internally operate on differing numbers of `usize` digits depending on the -/// size of `usize`, but the end result looks the same to users on different +/// internally operate on differing numbers of `Digit`s depending on the +/// size of `Digit`, but the end result looks the same to users on different /// architectures). The only reason these functions are exposed, is that someone /// may want to write their own custom performant algorithms, and they want as /// few abstractions as possible in the way. /// /// Visible functions that are not portable in general, but always start from -/// the zeroeth bit or a given bit position like [Bits::short_cin_mul], -/// [Bits::short_udivide_], or [Bits::usize_or_], are always +/// the zeroeth bit or a given bit position like [Bits::digit_cin_mul_], +/// [Bits::digit_udivide_], or [Bits::digit_or_], are always /// portable as long as the digit inputs and/or outputs are restricted to -/// `0..=u16::MAX`, or special care is taken. +/// `0..=u8::MAX`, or special care is taken. #[repr(transparent)] pub struct Bits { /// # Raw Invariants @@ -121,16 +119,17 @@ pub struct Bits { /// the power of Rust's desugering and inference surrounding other DSTS. /// /// In addition to the minimum number of digits required to store all the - /// bits, there is one more metadata digit on the end of the slice + /// bits, there is one or more metadata digits on the end of the slice /// responsible for storing the actual bitwidth. The length field on the - /// `[usize]` DST is the total number of digits in the slice, including - /// regular digits and the metadata digit. This design decision was made to + /// `[Digit]` DST is the total number of digits in the slice, including + /// regular digits and the metadata digits. This design decision was made to /// prevent invoking UB by having a fake slice with the bitwidth instead of /// the true slice width. Even if we completely avoid all Rust core methods /// on slices (and thus avoid practical UB due to avoiding standard slice /// functions expecting a standard length field), Miri can still detect a /// fake slice being made (even if we completely avoid - /// `core::ptr::slice_from_raw_parts`). + /// `core::ptr::slice_from_raw_parts` with the jankest of transmutation + /// shenanigans). /// /// The metadata bitwidth is on the end of the slice, because accesses of /// the bitwidth also commonly access the last digit right next to it @@ -144,11 +143,11 @@ pub struct Bits { /// The unfortunate consequence is that taking `Bits` digitwise subslices of /// `Bits` in the same general no-copy way that you can take subslices of /// regular Rust slices is not possible. `subdigits_mut!` almost achieves it - /// by temporarily replacing a digit with the needed metadata where the end + /// by temporarily replacing digits with the needed metadata where the end /// of the subslice is, running a closure on the subslice, and - /// then replacing the digit at the end. A different crate without fine - /// bitwidth control would have to be spun off of this one. - raw: [usize], + /// then replacing the digit at the end. However, we have the concatenation + /// macros which cover almost every bitfield transformation one could want. + raw: [Digit], } /// `Bits` is safe to send between threads since it does not own @@ -176,11 +175,11 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const unsafe fn from_raw_parts(raw_ptr: *const usize, raw_len: usize) -> &'a Self { + pub const unsafe fn from_raw_parts(raw_ptr: *const Digit, raw_len: usize) -> &'a Self { // Safety: `Bits` follows standard slice initialization invariants and is marked // `#[repr(transparent)]`. The explicit lifetimes make sure they do not become // unbounded. - unsafe { mem::transmute::<&[usize], &Bits>(&*ptr::slice_from_raw_parts(raw_ptr, raw_len)) } + unsafe { mem::transmute::<&[Digit], &Bits>(&*ptr::slice_from_raw_parts(raw_ptr, raw_len)) } } /// # Safety @@ -190,12 +189,12 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const unsafe fn from_raw_parts_mut(raw_ptr: *mut usize, raw_len: usize) -> &'a mut Self { + pub const unsafe fn from_raw_parts_mut(raw_ptr: *mut Digit, raw_len: usize) -> &'a mut Self { // Safety: `Bits` follows standard slice initialization invariants and is marked // `#[repr(transparent)]`. The explicit lifetimes make sure they do not become // unbounded. unsafe { - mem::transmute::<&mut [usize], &mut Bits>(&mut *ptr::slice_from_raw_parts_mut( + mem::transmute::<&mut [Digit], &mut Bits>(&mut *ptr::slice_from_raw_parts_mut( raw_ptr, raw_len, )) } @@ -228,7 +227,7 @@ impl<'a> Bits { #[doc(hidden)] #[inline] #[must_use] - pub const fn as_ptr(&self) -> *const usize { + pub const fn as_ptr(&self) -> *const Digit { self.raw.as_ptr() } @@ -238,11 +237,11 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn as_mut_ptr(&mut self) -> *mut usize { + pub const fn as_mut_ptr(&mut self) -> *mut Digit { self.raw.as_mut_ptr() } - /// Returns the raw total length of `self`, including the bitwidth digit. + /// Returns the raw total length of `self`, including the bitwidth metadata. #[doc(hidden)] #[inline] #[must_use] @@ -250,7 +249,7 @@ impl<'a> Bits { self.raw.len() } - /// This allows access of all digits including the bitwidth digit. + /// This allows access of all digits including the bitwidth digits. /// /// # Safety /// @@ -259,7 +258,7 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub(crate) const unsafe fn raw_get_unchecked(&self, i: usize) -> usize { + pub(crate) const unsafe fn raw_get_unchecked(&self, i: usize) -> Digit { debug_assert!(i < self.raw_len()); // Safety: `i` is bounded by `raw_len` unsafe { *self.as_ptr().add(i) } @@ -274,7 +273,7 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub(crate) const unsafe fn raw_get_unchecked_mut(&'a mut self, i: usize) -> &'a mut usize { + pub(crate) const unsafe fn raw_get_unchecked_mut(&'a mut self, i: usize) -> &'a mut Digit { debug_assert!(i < self.raw_len()); // Safety: `i` is bounded by `raw_len`. The lifetimes are bounded. unsafe { &mut *self.as_mut_ptr().add(i) } @@ -284,11 +283,16 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] + #[allow(clippy::unnecessary_cast)] // if `Digit == usize` clippy fires pub const fn nzbw(&self) -> NonZeroUsize { unsafe { - // Safety: The bitwidth is stored in the last raw slice element. The bitwidth is + let mut w = 0usize; + // Safety: The bitwidth is stored in the metadata digits. The bitwidth is // nonzero if invariants were maintained. - let w = self.raw_get_unchecked(self.raw_len() - 1); + let raw_len = self.raw_len(); + const_for!(i in {0..METADATA_DIGITS} { + w |= (self.raw_get_unchecked(i + raw_len - METADATA_DIGITS) as usize) << (i * BITS); + }); // If something with zeroing allocation or mutations accidentally breaks during // development, it will probably manifest itself here debug_assert!(w != 0); @@ -311,7 +315,7 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const unsafe fn get_unchecked(&self, i: usize) -> usize { + pub const unsafe fn get_unchecked(&self, i: usize) -> Digit { debug_assert!(i < self.len()); // Safety: `i < self.len()` means the access is within the slice unsafe { self.raw_get_unchecked(i) } @@ -324,7 +328,7 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const unsafe fn get_unchecked_mut(&'a mut self, i: usize) -> &'a mut usize { + pub const unsafe fn get_unchecked_mut(&'a mut self, i: usize) -> &'a mut Digit { debug_assert!(i < self.len()); // Safety: The bounds of this are a subset of `raw_get_unchecked_mut` unsafe { self.raw_get_unchecked_mut(i) } @@ -337,8 +341,8 @@ impl<'a> Bits { pub const fn len(&self) -> usize { // Safety: The length on the raw slice is the number of `usize` digits plus the // metadata bitwidth digit. To get the number of regular digits, we just - // subtract the metadata digit. - self.raw_len() - 1 + // subtract the metadata digits. + self.raw_len() - METADATA_DIGITS } /// Returns the number of unused bits. @@ -364,42 +368,42 @@ impl<'a> Bits { extra(self.nzbw()) } - /// Returns the first `usize` digit + /// Returns the first `Digit` #[doc(hidden)] #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn first(&self) -> usize { + pub const fn first(&self) -> Digit { // Safety: There is at least one digit since bitwidth has a nonzero invariant unsafe { self.get_unchecked(0) } } - /// Returns a mutable reference to the first `usize` digit + /// Returns a mutable reference to the first `Digit` #[doc(hidden)] #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn first_mut(&'a mut self) -> &'a mut usize { + pub const fn first_mut(&'a mut self) -> &'a mut Digit { // Safety: There is at least one digit since bitwidth has a nonzero invariant unsafe { self.get_unchecked_mut(0) } } - /// Returns the last `usize` digit + /// Returns the last `Digit` #[doc(hidden)] #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn last(&self) -> usize { + pub const fn last(&self) -> Digit { // Safety: There is at least one digit since bitwidth has a nonzero invariant unsafe { self.get_unchecked(self.len() - 1) } } - /// Returns a mutable reference to the last `usize` digit + /// Returns a mutable reference to the last `Digit` #[doc(hidden)] #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn last_mut(&'a mut self) -> &'a mut usize { + pub const fn last_mut(&'a mut self) -> &'a mut Digit { // Safety: There is at least one digit since bitwidth has a nonzero invariant unsafe { self.get_unchecked_mut(self.len() - 1) } } @@ -426,8 +430,12 @@ impl<'a> Bits { #[track_caller] #[const_fn(cfg(feature = "const_support"))] pub const fn assert_cleared_unused_bits(&self) { - if (self.extra() != 0) && (self.last() >= 1usize.wrapping_shl(self.extra() as u32)) { - panic!("unused bits are set"); + let one: Digit = 1; + if (self.extra() != 0) && (self.last() >= one.wrapping_shl(self.extra() as u32)) { + panic!( + "unused bits are set in a `Bits` struct, they have been set with one of the \ + hidden functions and not properly unset with `Bits::clear_unused_bits`" + ); } } @@ -442,7 +450,7 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn as_slice(&'a self) -> &'a [usize] { + pub const fn as_slice(&'a self) -> &'a [Digit] { // Safety: `Bits` already follows standard slice initialization invariants. This // acquires a subslice that includes everything except for the metadata digit. unsafe { &*ptr::slice_from_raw_parts(self.as_ptr(), self.len()) } @@ -453,7 +461,7 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn as_raw_slice(&'a self) -> &'a [usize] { + pub const fn as_raw_slice(&'a self) -> &'a [Digit] { // Safety: `Bits` already follows standard slice initialization invariants. This // acquires a subslice that includes everything except for the metadata digit. unsafe { &*ptr::slice_from_raw_parts(self.as_ptr(), self.raw_len()) } @@ -472,7 +480,7 @@ impl<'a> Bits { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn as_mut_slice(&'a mut self) -> &'a mut [usize] { + pub const fn as_mut_slice(&'a mut self) -> &'a mut [Digit] { // Safety: `Bits` already follows standard slice initialization invariants. This // acquires a subslice that includes everything except for the metadata digit, // so that it cannot be mutated. @@ -480,7 +488,7 @@ impl<'a> Bits { } /// Returns a reference to the underlying bits of `self`, including unused - /// bits (which occur if `self.bw()` is not a multiple of `usize::BITS`). + /// bits (which occur if `self.bw()` is not a multiple of `Digit::BITS`). /// /// # Note /// @@ -496,13 +504,13 @@ impl<'a> Bits { #[must_use] pub const fn as_bytes_full_width_nonportable(&'a self) -> &'a [u8] { // Previously, this function used to be called "portable" because it was - // intended as a way to get a slice view of `Bits` independent of `usize` width. + // intended as a way to get a slice view of `Bits` independent of `Digit` width. // It worked with unused bits by making the slice length such that the unused // bits were only in the last byte, which at first glance is portable. However, // I completely forgot about big-endian systems. Not taking a full width of the // byte slice can result in significant bytes being completely disregarded. - let size_in_u8 = self.len() * BYTE_RATIO; - // Safety: Adding on to what is satisfied in `as_slice`, [usize] can always be + let size_in_u8 = self.len() * DIGIT_BYTES; + // Safety: Adding on to what is satisfied in `as_slice`, [Digit] can always be // divided into [u8] and the correct length is calculated above. If the bitwidth // is not a multiple of eight, there must be at least enough unused bits to form // one more byte. This is returned as a reference with a constrained lifetime, @@ -512,7 +520,7 @@ impl<'a> Bits { /// Returns a mutable reference to the underlying bits of `self`, including /// unused bits (which occur if `self.bw()` is not a multiple of - /// `usize::BITS`). + /// `Digit::BITS`). /// /// # Note /// @@ -528,8 +536,9 @@ impl<'a> Bits { #[doc(hidden)] #[const_fn(cfg(feature = "const_support"))] #[must_use] + #[inline(always)] // this is needed for `unstable_from_u8_slice` pub const fn as_mut_bytes_full_width_nonportable(&'a mut self) -> &'a mut [u8] { - let size_in_u8 = self.len() * BYTE_RATIO; + let size_in_u8 = self.len() * DIGIT_BYTES; // Safety: Same reasoning as `as_bytes_full_width_nonportable` unsafe { &mut *ptr::slice_from_raw_parts_mut(self.as_mut_ptr() as *mut u8, size_in_u8) } } @@ -541,14 +550,14 @@ impl<'a> Bits { /// sizes and endianness. #[const_fn(cfg(feature = "const_support"))] pub const fn u8_slice_(&'a mut self, buf: &[u8]) { - let self_byte_width = self.len() * BYTE_RATIO; + let self_byte_width = self.len() * DIGIT_BYTES; let min_width = if self_byte_width < buf.len() { self_byte_width } else { buf.len() }; // start of digits that will not be completely overwritten - let start = min_width / BYTE_RATIO; + let start = min_width / DIGIT_BYTES; unsafe { // zero out first. self.digit_set(false, start..self.len(), false); @@ -568,7 +577,7 @@ impl<'a> Bits { }; const_for!(i in {0..cap} { // correct for big endian, otherwise no-op - *self.get_unchecked_mut(i) = usize::from_le(self.get_unchecked(i)); + *self.get_unchecked_mut(i) = Digit::from_le(self.get_unchecked(i)); }); } self.clear_unused_bits(); @@ -581,7 +590,7 @@ impl<'a> Bits { /// pointer sizes and endianness. #[const_fn(cfg(feature = "const_support"))] pub const fn to_u8_slice(&'a self, buf: &mut [u8]) { - let self_byte_width = self.len() * BYTE_RATIO; + let self_byte_width = self.len() * DIGIT_BYTES; let min_width = if self_byte_width < buf.len() { self_byte_width } else { @@ -604,8 +613,12 @@ impl<'a> Bits { { const_for!(i in {0..self.len()} { let x = self.as_slice()[i]; - let start = i * BYTE_RATIO; - let end = if (start + BYTE_RATIO) > buf.len() {buf.len()} else {start + BYTE_RATIO}; + let start = i * DIGIT_BYTES; + let end = if (start + DIGIT_BYTES) > buf.len() { + buf.len() + } else { + start + DIGIT_BYTES + }; let mut s = 0; const_for!(j in {start..end} { buf[j] = (x >> s) as u8; @@ -651,12 +664,12 @@ impl<'a> Bits { ); } - /// Gets one `usize` digit from `self` starting at the bit index `start`. + /// Gets one `Digit` from `self` starting at the bit index `start`. /// Bits that extend beyond `self.bw()` are zeroed. #[doc(hidden)] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn get_digit(&self, start: usize) -> usize { + pub const fn get_digit(&self, start: usize) -> Digit { let digits = digits_u(start); let bits = extra_u(start); let mut tmp = 0; @@ -678,7 +691,7 @@ impl<'a> Bits { #[doc(hidden)] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn get_double_digit(&self, start: usize) -> (usize, usize) { + pub const fn get_double_digit(&self, start: usize) -> (Digit, Digit) { let digits = digits_u(start); let bits = extra_u(start); let mut first = 0; diff --git a/awint_core/src/data/const_traits.rs b/awint_core/src/data/const_traits.rs index e08e1574..c6d13a57 100644 --- a/awint_core/src/data/const_traits.rs +++ b/awint_core/src/data/const_traits.rs @@ -10,14 +10,14 @@ impl const Deref for InlAwi { #[inline] fn deref(&self) -> &Self::Target { - self.const_as_ref() + self.internal_as_ref() } } impl const DerefMut for InlAwi { #[inline] fn deref_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self.internal_as_mut() } } @@ -26,42 +26,42 @@ impl const Index for InlAwi &Bits { - self.const_as_ref() + self } } impl const Borrow for InlAwi { #[inline] fn borrow(&self) -> &Bits { - self.const_as_ref() + self } } impl const AsRef for InlAwi { #[inline] fn as_ref(&self) -> &Bits { - self.const_as_ref() + self } } impl const IndexMut for InlAwi { #[inline] fn index_mut(&mut self, _i: RangeFull) -> &mut Bits { - self.const_as_mut() + self } } impl const BorrowMut for InlAwi { #[inline] fn borrow_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } impl const AsMut for InlAwi { #[inline] fn as_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } @@ -113,3 +113,13 @@ impl const From for UsizeInlAwi { Self::from_isize(x) } } + +/// If `self` and `other` have unmatching bit widths, `false` will be returned. +impl const PartialEq for Bits { + fn eq(&self, rhs: &Self) -> bool { + self.bw() == rhs.bw() && self.const_eq(rhs).unwrap() + } +} + +/// If `self` and `other` have unmatching bit widths, `false` will be returned. +impl Eq for Bits {} diff --git a/awint_core/src/data/inlawi.rs b/awint_core/src/data/inlawi.rs index 06698512..fcc7f14b 100644 --- a/awint_core/src/data/inlawi.rs +++ b/awint_core/src/data/inlawi.rs @@ -2,6 +2,7 @@ use core::{ fmt, hash::{Hash, Hasher}, num::NonZeroUsize, + ptr, }; use awint_internals::*; @@ -61,17 +62,18 @@ use crate::Bits; /// }; /// const X: &'static Bits = &AWI; /// -/// assert_eq!(X, inlawi!(-246i100).const_as_ref()); +/// assert_eq!(X, inlawi!(-246i100).as_ref()); /// ``` #[derive(Clone, Copy)] // following what arrays do pub struct InlAwi { /// # Raw Invariants /// /// The digits contained here have the raw invariants of `Bits`. This - /// implies that `BW >= 2`, or else there is not enough storage for the - /// first digit and metadata. The bitwidth must be set to value in the - /// range `(((BW - 2)*BITS) + 1)..=((BW - 1)*BITS)`. - raw: [usize; LEN], + /// implies that `LEN >= METADATA_DIGITS + 1`, or else there is not enough + /// storage for the first digit and metadata. The bitwidth must be set + /// to value in the range `(((LEN - METADATA_DIGITS - 1)*BITS) + + /// 1)..=((LEN - METADATA_DIGITS)*BITS)`. + raw: [Digit; LEN], } /// `InlAwi` is safe to send between threads since it does not own @@ -87,7 +89,7 @@ impl<'a, const BW: usize, const LEN: usize> InlAwi { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn const_as_ref(&'a self) -> &'a Bits { + pub(in crate::data) const fn internal_as_ref(&'a self) -> &'a Bits { // Safety: Only functions like `unstable_from_u8_slice` can construct the `raw` // field on `InlAwi`s. These always have the `assert_inlawi_invariants_` checks // to insure the raw invariants. The explicit lifetimes make sure they do not @@ -99,7 +101,7 @@ impl<'a, const BW: usize, const LEN: usize> InlAwi { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn const_as_mut(&'a mut self) -> &'a mut Bits { + pub(in crate::data) const fn internal_as_mut(&'a mut self) -> &'a mut Bits { // Safety: Only functions like `unstable_from_u8_slice` can construct the `raw` // field on `InlAwi`s. These always have the `assert_inlawi_invariants_` checks // to insure the raw invariants. The explicit lifetimes make sure they do not @@ -152,35 +154,95 @@ impl<'a, const BW: usize, const LEN: usize> InlAwi { #[const_fn(cfg(feature = "const_support"))] #[must_use] pub const fn len(&self) -> usize { - Self::const_raw_len() - 1 + Self::const_raw_len() - METADATA_DIGITS } /// This is not intended for direct use, use `awint_macros::inlawi` /// or some other constructor instead. The purpose of this function is to - /// allow for a `usize::BITS` difference between a target architecture and + /// allow for a `Digit::BITS` difference between a target architecture and /// the build architecture. Uses `u8_slice_`. #[doc(hidden)] #[const_fn(cfg(feature = "const_support"))] pub const fn unstable_from_u8_slice(buf: &[u8]) -> Self { - if LEN < 2 { - panic!("Tried to create an `InlAwi` with `LEN < 2`") + if LEN < METADATA_DIGITS + 1 { + // the invariants asserter checks for this, but we put a value + // in `raw` first + panic!("Tried to create an `InlAwi` with `LEN < METADATA_DIGITS + 1`") } - let mut raw = [0; LEN]; - raw[raw.len() - 1] = BW; + // note: this needs to be set as all zeros for the inlining below + let mut raw: [Digit; LEN] = [0; LEN]; + const_for!(i in {0..METADATA_DIGITS} { + raw[LEN - METADATA_DIGITS + i] = (BW >> (i * BITS)) as Digit; + }); assert_inlawi_invariants_slice::(&raw); let mut awi = InlAwi { raw }; - awi.const_as_mut().u8_slice_(buf); + + // At this point, we would call `awi.u8_slice_(buf)` and could return that. + // However, we run into a problem where the compiler has difficulty. For + // example, + // + // ``` + // pub fn internal(x: &mut Bits, neg: bool) { + // let y = inlawi!(-3858683298722959599393.23239873423987_i1024f512); + // x.neg_add_(neg, &y).unwrap(); + // } + // ``` + // + // should compile into a function that reserves 128 bytes (plus 32 or so more + // depending on architecture) on the stack pointer, a single + // memcpy, and then a call to `neg_add_`. However, if we don't inline + // most functions it will get confused, reserve a further 128 bytes, add + // a memcpy, and a memset. This is bad in general but very bad for architectures + // like AVR, where we don't want to double stack reservations. + + // inline `u8_slice_`, but we can also eliminate the `digit_set` + let self_byte_width = awi.len() * DIGIT_BYTES; + let min_width = if self_byte_width < buf.len() { + self_byte_width + } else { + buf.len() + }; + // start of digits that will not be completely overwritten + let start = min_width / DIGIT_BYTES; + unsafe { + // zero out first. + // (skip because we initialized `raw` to all zeros) + //awi.digit_set(false, start..self.len(), false); + // Safety: `src` is valid for reads at least up to `min_width`, `dst` is valid + // for writes at least up to `min_width`, they are aligned, and are + // nonoverlapping because `self` is a mutable reference. + ptr::copy_nonoverlapping( + buf.as_ptr(), + // note that we marked this as `#[inline]` + awi.as_mut_bytes_full_width_nonportable().as_mut_ptr(), + min_width, + ); + // `start` can be `self.len()`, so cap it + let cap = if start >= awi.len() { + awi.len() + } else { + start + 1 + }; + const_for!(i in {0..cap} { + // correct for big endian, otherwise no-op + *awi.get_unchecked_mut(i) = Digit::from_le(awi.get_unchecked(i)); + }); + } + awi.clear_unused_bits(); + awi } /// Zero-value construction with bitwidth `BW` #[const_fn(cfg(feature = "const_support"))] pub const fn zero() -> Self { - if LEN < 2 { - panic!("Tried to create an `InlAwi` with `LEN < 2`") + if LEN < METADATA_DIGITS + 1 { + panic!("Tried to create an `InlAwi` with `LEN < METADATA_DIGITS + 1`") } - let mut raw = [0; LEN]; - raw[raw.len() - 1] = BW; + let mut raw: [Digit; LEN] = [0; LEN]; + const_for!(i in {0..METADATA_DIGITS} { + raw[LEN - METADATA_DIGITS + i] = (BW >> (i * BITS)) as Digit; + }); assert_inlawi_invariants_slice::(&raw); InlAwi { raw } } @@ -188,11 +250,13 @@ impl<'a, const BW: usize, const LEN: usize> InlAwi { /// Unsigned-maximum-value construction with bitwidth `BW` #[const_fn(cfg(feature = "const_support"))] pub const fn umax() -> Self { - if LEN < 2 { - panic!("Tried to create an `InlAwi` with `LEN < 2`") + if LEN < METADATA_DIGITS + 1 { + panic!("Tried to create an `InlAwi` with `LEN < METADATA_DIGITS + 1`") } - let mut raw = [MAX; LEN]; - raw[raw.len() - 1] = BW; + let mut raw: [Digit; LEN] = [MAX; LEN]; + const_for!(i in {0..METADATA_DIGITS} { + raw[LEN - METADATA_DIGITS + i] = (BW >> (i * BITS)) as Digit; + }); assert_inlawi_invariants_slice::(&raw); let mut awi = InlAwi { raw }; awi.const_as_mut().clear_unused_bits(); @@ -203,7 +267,7 @@ impl<'a, const BW: usize, const LEN: usize> InlAwi { #[const_fn(cfg(feature = "const_support"))] pub const fn imax() -> Self { let mut awi = Self::umax(); - *awi.const_as_mut().last_mut() = (isize::MAX as usize) >> awi.const_as_ref().unused(); + *awi.const_as_mut().last_mut() = (MAX >> 1) >> awi.unused(); awi } @@ -211,7 +275,7 @@ impl<'a, const BW: usize, const LEN: usize> InlAwi { #[const_fn(cfg(feature = "const_support"))] pub const fn imin() -> Self { let mut awi = Self::zero(); - *awi.const_as_mut().last_mut() = (isize::MIN as usize) >> awi.const_as_ref().unused(); + *awi.const_as_mut().last_mut() = (IDigit::MIN as Digit) >> awi.unused(); awi } @@ -222,12 +286,27 @@ impl<'a, const BW: usize, const LEN: usize> InlAwi { *awi.const_as_mut().first_mut() = 1; awi } + + /// Used for tests, Can't put in `awint_internals` + #[doc(hidden)] + #[const_fn(cfg(feature = "const_support"))] + pub const fn assert_invariants(this: &Self) { + assert_inlawi_invariants::(); + if this.bw() != BW { + panic!("bitwidth digit does not equal BW") + } + if this.raw_len() != LEN { + panic!("`length of raw slice does not equal LEN") + } + // not a strict invariant but we want to test it + this.assert_cleared_unused_bits(); + } } /// If `self` and `other` have unmatching bit widths, `false` will be returned. impl PartialEq for InlAwi { fn eq(&self, rhs: &Self) -> bool { - self.const_as_ref() == rhs.const_as_ref() + self.as_ref() == rhs.as_ref() } } @@ -247,7 +326,7 @@ macro_rules! impl_fmt { impl fmt::$ty for InlAwi { /// Forwards to the corresponding impl for `Bits` fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::$ty::fmt(self.const_as_ref(), f) + fmt::$ty::fmt(self.as_ref(), f) } } )* @@ -258,7 +337,7 @@ impl_fmt!(Debug Display LowerHex UpperHex Octal Binary); impl Hash for InlAwi { fn hash(&self, state: &mut H) { - self.const_as_ref().hash(state); + self.as_ref().hash(state); } } @@ -267,7 +346,7 @@ impl InlAwi<1, { Bits::unstable_raw_digits(1) }> { #[const_fn(cfg(feature = "const_support"))] pub const fn from_bool(x: bool) -> Self { let mut awi = Self::zero(); - awi.const_as_mut().bool_(x); + awi.bool_(x); awi } } @@ -281,7 +360,7 @@ macro_rules! inlawi_from { #[const_fn(cfg(feature = "const_support"))] pub const fn $from_u(x: $u) -> Self { let mut awi = Self::zero(); - awi.const_as_mut().$u_(x); + awi.$u_(x); awi } @@ -289,7 +368,7 @@ macro_rules! inlawi_from { #[const_fn(cfg(feature = "const_support"))] pub const fn $from_i(x: $i) -> Self { let mut awi = Self::zero(); - awi.const_as_mut().$i_(x); + awi.$i_(x); awi } } @@ -305,15 +384,14 @@ inlawi_from!( 128, u128 from_u128 u128_ i128 from_i128 i128_; ); -pub(crate) type UsizeInlAwi = - InlAwi<{ usize::BITS as usize }, { Bits::unstable_raw_digits(usize::BITS as usize) }>; +pub(crate) type UsizeInlAwi = InlAwi<{ USIZE_BITS }, { Bits::unstable_raw_digits(USIZE_BITS) }>; impl UsizeInlAwi { /// Creates an `InlAwi` with the same bitwidth and bits as the integer #[const_fn(cfg(feature = "const_support"))] pub const fn from_usize(x: usize) -> Self { let mut awi = Self::zero(); - awi.const_as_mut().usize_(x); + awi.usize_(x); awi } @@ -321,7 +399,19 @@ impl UsizeInlAwi { #[const_fn(cfg(feature = "const_support"))] pub const fn from_isize(x: isize) -> Self { let mut awi = Self::zero(); - awi.const_as_mut().isize_(x); + awi.isize_(x); + awi + } +} + +pub(crate) type DigitInlAwi = InlAwi<{ BITS }, { Bits::unstable_raw_digits(BITS) }>; + +impl DigitInlAwi { + /// Creates an `InlAwi` with the same bitwidth and bits as `Digit` + #[const_fn(cfg(feature = "const_support"))] + pub const fn from_digit(x: Digit) -> Self { + let mut awi = Self::zero(); + awi.digit_(x); awi } } diff --git a/awint_core/src/data/serde.rs b/awint_core/src/data/serde.rs index 15780713..dd5e60b2 100644 --- a/awint_core/src/data/serde.rs +++ b/awint_core/src/data/serde.rs @@ -16,7 +16,7 @@ impl Serialize for InlAwi { /// it serializes into a struct named "InlAwi" with two fields "bw" and /// "bits". "bw" is the bitwidth in decimal, and "bits" are an unsigned /// hexadecimal string equivalent to what would be generated from - /// `ExtAwi::bits_to_string_radix(self.const_as_ref(), false, 16, false, 0)` + /// `ExtAwi::bits_to_string_radix(&self, false, 16, false, 0)` /// from the `awint_ext` crate. /// /// Note that there is clever use of buffers to avoid allocation on @@ -38,8 +38,6 @@ impl Serialize for InlAwi { where S: Serializer, { - // this is all done without allocation on our side - let bits = self.const_as_ref(); // TODO this buffer is ~5 times larger than needed. We have a // `panicking_chars_upper_bound` that can be used in array lengths if the input // is a constant, but annoyingly we currently can't use generic parameters for @@ -47,8 +45,8 @@ impl Serialize for InlAwi { let mut buf = [0u8; BW]; let mut pad = Self::zero(); // do the minimum amount of work necessary - let upper = chars_upper_bound(bits.sig(), 16).unwrap(); - bits.to_bytes_radix(false, &mut buf[..upper], 16, false, pad.const_as_mut()) + let upper = chars_upper_bound(self.sig(), 16).unwrap(); + self.to_bytes_radix(false, &mut buf[..upper], 16, false, pad.const_as_mut()) .unwrap(); // find the lower bound of signficant digits let mut lower = 0; diff --git a/awint_core/src/data/traits.rs b/awint_core/src/data/traits.rs index 3b21b9bb..cf20b503 100644 --- a/awint_core/src/data/traits.rs +++ b/awint_core/src/data/traits.rs @@ -10,14 +10,14 @@ impl Deref for InlAwi { #[inline] fn deref(&self) -> &Self::Target { - self.const_as_ref() + self.internal_as_ref() } } impl DerefMut for InlAwi { #[inline] fn deref_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self.internal_as_mut() } } @@ -26,42 +26,42 @@ impl Index for InlAwi { #[inline] fn index(&self, _i: RangeFull) -> &Bits { - self.const_as_ref() + self } } impl Borrow for InlAwi { #[inline] fn borrow(&self) -> &Bits { - self.const_as_ref() + self } } impl AsRef for InlAwi { #[inline] fn as_ref(&self) -> &Bits { - self.const_as_ref() + self } } impl IndexMut for InlAwi { #[inline] fn index_mut(&mut self, _i: RangeFull) -> &mut Bits { - self.const_as_mut() + self } } impl BorrowMut for InlAwi { #[inline] fn borrow_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } impl AsMut for InlAwi { #[inline] fn as_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } @@ -113,3 +113,13 @@ impl From for UsizeInlAwi { Self::from_isize(x) } } + +/// If `self` and `other` have unmatching bit widths, `false` will be returned. +impl PartialEq for Bits { + fn eq(&self, rhs: &Self) -> bool { + self.bw() == rhs.bw() && self.const_eq(rhs).unwrap() + } +} + +/// If `self` and `other` have unmatching bit widths, `false` will be returned. +impl Eq for Bits {} diff --git a/awint_core/src/logic/bitwise.rs b/awint_core/src/logic/bitwise.rs index d66b9adc..c9bb26da 100644 --- a/awint_core/src/logic/bitwise.rs +++ b/awint_core/src/logic/bitwise.rs @@ -24,14 +24,14 @@ impl Bits { #[const_fn(cfg(feature = "const_support"))] pub const fn imax_(&mut self) { unsafe { self.digit_set(true, 0..self.len(), false) } - *self.last_mut() = (isize::MAX as usize) >> self.unused(); + *self.last_mut() = (MAX >> 1) >> self.unused(); } /// Signed-minimum-value-assigns. Only the most significant bit is set. #[const_fn(cfg(feature = "const_support"))] pub const fn imin_(&mut self) { unsafe { self.digit_set(false, 0..self.len(), false) } - *self.last_mut() = (isize::MIN as usize) >> self.unused(); + *self.last_mut() = (IDigit::MIN as Digit) >> self.unused(); } /// Unsigned-one-assigns. Only the least significant bit is set. The @@ -140,7 +140,7 @@ impl Bits { /// Or-assigns `rhs` to `self` at a position `shl`. Set bits of `rhs` that /// are shifted beyond the bitwidth of `self` are truncated. #[const_fn(cfg(feature = "const_support"))] - pub const fn usize_or_(&mut self, rhs: usize, shl: usize) { + pub const fn digit_or_(&mut self, rhs: Digit, shl: usize) { if shl >= self.bw() { return } diff --git a/awint_core/src/logic/cc.rs b/awint_core/src/logic/cc.rs index 9a58da8c..46365b1d 100644 --- a/awint_core/src/logic/cc.rs +++ b/awint_core/src/logic/cc.rs @@ -4,6 +4,7 @@ use awint_internals::*; use crate::Bits; +/// This exists because we need the macros to work branchlessly with `awint_dag` #[doc(hidden)] pub struct CCResult { run_fielding: bool, diff --git a/awint_core/src/logic/cmp.rs b/awint_core/src/logic/cmp.rs index 74af20ce..b56d85db 100644 --- a/awint_core/src/logic/cmp.rs +++ b/awint_core/src/logic/cmp.rs @@ -1,20 +1,8 @@ -use core::cmp::*; - use awint_internals::*; use const_fn::const_fn; use crate::Bits; -/// If `self` and `other` have unmatching bit widths, `false` will be returned. -impl PartialEq for Bits { - fn eq(&self, rhs: &Self) -> bool { - self.bw() == rhs.bw() && self.const_eq(rhs).unwrap() - } -} - -/// If `self` and `other` have unmatching bit widths, `false` will be returned. -impl Eq for Bits {} - /// # Comparison impl Bits { /// If `self` is zero @@ -71,7 +59,7 @@ impl Bits { } }); if self.extra() == 0 { - self.last() == (isize::MIN as usize) + self.last() == IDigit::MIN as Digit } else { self.last() == (1 << (self.extra() - 1)) } diff --git a/awint_core/src/logic/const_str.rs b/awint_core/src/logic/const_str.rs index 8c2b00d7..f6037a24 100644 --- a/awint_core/src/logic/const_str.rs +++ b/awint_core/src/logic/const_str.rs @@ -76,8 +76,8 @@ impl Bits { b.wrapping_sub(b'A').wrapping_add(10) } else { b.wrapping_sub(b'a').wrapping_add(10) - } as usize; - pad.usize_or_(char_digit, shl); + } as Digit; + pad.digit_or_(char_digit, shl); shl += log2; if shl >= self.bw() { let tmp = if let Some(tmp) = BITS @@ -169,12 +169,12 @@ impl Bits { b.wrapping_sub(b'A').wrapping_add(10) } else { b.wrapping_sub(b'a').wrapping_add(10) - } as usize; - let o0 = pad0.short_mul_add_(pad1, char_digit).unwrap(); + } as Digit; + let o0 = pad0.digit_mul_add_(pad1, char_digit).unwrap(); if o0 { return Err(Overflow) } - let o1 = pad1.short_cin_mul(0, radix as usize); + let o1 = pad1.digit_cin_mul_(0, radix as Digit); if o1 != 0 { // there may be a bunch of leading zeros, so do not return an error yet const_for!(j in {0..i} { @@ -228,6 +228,9 @@ impl Bits { upper: bool, pad: &mut Self, ) -> Result<(), SerdeError> { + // there's going to be a lot of potential for downstream confusion if we do not + // check + self.assert_cleared_unused_bits(); if self.bw() != pad.bw() { return Err(NonEqualWidths) } @@ -238,7 +241,7 @@ impl Bits { // happens to do the right thing to `imin` pad.neg_(signed && pad.msb()); const_for!(i in {0..dst.len()}.rev() { - let rem = pad.short_udivide_inplace_(radix as usize).unwrap() as u8; + let rem = pad.digit_udivide_inplace_(radix as Digit).unwrap() as u8; if rem < 10 { dst[i] = b'0'.wrapping_add(rem); } else if upper { @@ -263,6 +266,7 @@ impl Bits { f: &mut fmt::Formatter, upper: bool, ) -> fmt::Result { + self.assert_cleared_unused_bits(); f.write_fmt(format_args!("0x"))?; const_for!(j0 in {0..(self.bw() >> 2).wrapping_add(1)}.rev() { if (self.get_digit(j0 << 2) & 0b1111) != 0 { @@ -295,6 +299,7 @@ impl Bits { #[inline] pub(crate) fn debug_format_octal(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.assert_cleared_unused_bits(); f.write_fmt(format_args!("0o"))?; const_for!(j0 in {0..(self.bw() / 3).wrapping_add(1)}.rev() { if (self.get_digit(j0.wrapping_mul(3)) & 0b111) != 0 { @@ -324,6 +329,7 @@ impl Bits { // TODO this could be optimized #[inline] pub(crate) fn debug_format_binary(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.assert_cleared_unused_bits(); f.write_fmt(format_args!("0b"))?; const_for!(j0 in {0..self.bw()}.rev() { if (self.get_digit(j0) & 0b1) != 0 { diff --git a/awint_core/src/logic/div.rs b/awint_core/src/logic/div.rs index 7e57adcf..43be4175 100644 --- a/awint_core/src/logic/div.rs +++ b/awint_core/src/logic/div.rs @@ -32,7 +32,7 @@ impl Bits { /// returns the remainder. Returns `None` if `div == 0`. #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn short_udivide_inplace_(&mut self, div: usize) -> Option { + pub const fn digit_udivide_inplace_(&mut self, div: Digit) -> Option { if div == 0 { return None } @@ -55,7 +55,7 @@ impl Bits { // `div == 0`. #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn short_udivide_(&mut self, duo: &Self, div: usize) -> Option { + pub const fn digit_udivide_(&mut self, duo: &Self, div: Digit) -> Option { if div == 0 || self.bw() != duo.bw() { return None } @@ -92,16 +92,16 @@ impl Bits { let duo_sig_dd = duo.get_double_digit(i); let div_sig_dd = div.get_double_digit(i); // Because `lz_diff < BITS`, the quotient will fit in one `usize` - let mut small_quo: usize = dd_division(duo_sig_dd, div_sig_dd).0 .0; + let mut small_quo = dd_division(duo_sig_dd, div_sig_dd).0 .0; // using `rem` as a temporary rem.copy_(div).unwrap(); - let uof = rem.short_cin_mul(0, small_quo); + let uof = rem.digit_cin_mul_(0, small_quo); rem.rsb_(duo).unwrap(); if (uof != 0) || rem.msb() { rem.add_(div).unwrap(); small_quo -= 1; } - quo.usize_(small_quo); + quo.digit_(small_quo); } /// Unsigned-divides `duo` by `div` and assigns the quotient to `quo` and @@ -142,10 +142,10 @@ impl Bits { // small division branch if (w - duo_lz) <= BITS { - let tmp_duo = duo.to_usize(); - let tmp_div = div.to_usize(); - quo.usize_(tmp_duo.wrapping_div(tmp_div)); - rem.usize_(tmp_duo.wrapping_rem(tmp_div)); + let tmp_duo = duo.to_digit(); + let tmp_div = div.to_digit(); + quo.digit_(tmp_duo.wrapping_div(tmp_div)); + rem.digit_(tmp_duo.wrapping_rem(tmp_div)); return Some(()) } @@ -157,10 +157,10 @@ impl Bits { (duo.first(), duo.get_unchecked(1)), (div.first(), div.get_unchecked(1)), ); - // using `usize_` to make sure other digits are zeroed - quo.usize_(tmp.0 .0); + // using `digit_` to make sure other digits are zeroed + quo.digit_(tmp.0 .0); *quo.get_unchecked_mut(1) = tmp.0 .1; - rem.usize_(tmp.1 .0); + rem.digit_(tmp.1 .0); *rem.get_unchecked_mut(1) = tmp.1 .1; } return Some(()) @@ -172,8 +172,8 @@ impl Bits { // short division branch if w - div_lz <= BITS { - let tmp = quo.short_udivide_(duo, div.to_usize()).unwrap(); - rem.usize_(tmp); + let tmp = quo.digit_udivide_(duo, div.to_digit()).unwrap(); + rem.digit_(tmp); return Some(()) } @@ -303,7 +303,7 @@ impl Bits { let duo_sig_dd = rem.get_double_digit(i); let div_sig_dd = div.get_double_digit(i); // Because `lz_diff < BITS`, the quotient will fit in one `usize` - let mut small_quo: usize = dd_division(duo_sig_dd, div_sig_dd).0 .0; + let mut small_quo = dd_division(duo_sig_dd, div_sig_dd).0 .0; // subtract `div*small_quo` from `rem` inplace let mut mul_carry = 0; let mut add_carry = 1; @@ -350,7 +350,6 @@ impl Bits { (rem.first(), rem.get_unchecked(1)), (div.first(), div.get_unchecked(1)), ); - // using `usize_` to make sure other digits are zeroed let tmp0 = widen_add(quo.first(), tmp.0 .0, 0); *quo.first_mut() = tmp0.0; @@ -358,7 +357,8 @@ impl Bits { subdigits_mut!(quo, { 1..len }, subquo, { subquo.inc_(tmp0.1 != 0); }); - rem.usize_(tmp.1 .0); + // using `digit_` to make sure other digits are zeroed + rem.digit_(tmp.1 .0); *rem.get_unchecked_mut(1) = tmp.1 .1; } return Some(()) diff --git a/awint_core/src/logic/misc.rs b/awint_core/src/logic/misc.rs index 1eb6000f..76126775 100644 --- a/awint_core/src/logic/misc.rs +++ b/awint_core/src/logic/misc.rs @@ -21,7 +21,7 @@ impl Bits { #[must_use] pub const fn msb(&self) -> bool { if self.extra() == 0 { - (self.last() as isize) < 0 + (self.last() as IDigit) < 0 } else { (self.last() & (1 << (self.extra() - 1))) != 0 } diff --git a/awint_core/src/logic/mul.rs b/awint_core/src/logic/mul.rs index 23372ad1..588db96f 100644 --- a/awint_core/src/logic/mul.rs +++ b/awint_core/src/logic/mul.rs @@ -9,7 +9,7 @@ use crate::Bits; impl Bits { /// Assigns `cin + (self * rhs)` to `self` and returns the overflow #[const_fn(cfg(feature = "const_support"))] - pub const fn short_cin_mul(&mut self, cin: usize, rhs: usize) -> usize { + pub const fn digit_cin_mul_(&mut self, cin: Digit, rhs: Digit) -> Digit { let mut carry = cin; unsafe_for_each_mut!( self, @@ -33,7 +33,7 @@ impl Bits { /// Add-assigns `lhs * rhs` to `self` and returns if overflow happened #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn short_mul_add_(&mut self, lhs: &Self, rhs: usize) -> Option { + pub const fn digit_mul_add_(&mut self, lhs: &Self, rhs: Digit) -> Option { let mut mul_carry = 0; let mut add_carry = 0; unsafe_binop_for_each_mut!( diff --git a/awint_core/src/logic/permute.rs b/awint_core/src/logic/permute.rs index 3918c4c3..168014b0 100644 --- a/awint_core/src/logic/permute.rs +++ b/awint_core/src/logic/permute.rs @@ -15,9 +15,9 @@ use crate::Bits; /// /// The range `[mid-left, mid+right)` must be valid for reading and writing #[const_fn(cfg(feature = "const_support"))] -const unsafe fn usize_rotate(mut left: usize, mut mid: *mut usize, mut right: usize) { +const unsafe fn digit_rotate(mut left: usize, mut mid: *mut Digit, mut right: usize) { unsafe { - type BufType = [usize; 32]; + type BufType = [Digit; 32]; loop { if (right == 0) || (left == 0) { return @@ -25,7 +25,7 @@ const unsafe fn usize_rotate(mut left: usize, mut mid: *mut usize, mut right: us if left + right < 24 { // Algorithm 1 let x = mid.sub(left); - let mut tmp: usize = x.read(); + let mut tmp: Digit = x.read(); let mut i = right; let mut gcd = right; loop { @@ -67,8 +67,8 @@ const unsafe fn usize_rotate(mut left: usize, mut mid: *mut usize, mut right: us // I have tested this with Miri to make sure it doesn't complain } else if left <= 32 || right <= 32 { // Algorithm 2 - let mut rawarray = MaybeUninit::<(BufType, [usize; 0])>::uninit(); - let buf = rawarray.as_mut_ptr() as *mut usize; + let mut rawarray = MaybeUninit::<(BufType, [Digit; 0])>::uninit(); + let buf = rawarray.as_mut_ptr() as *mut Digit; let dim = mid.sub(left).add(right); if left <= right { ptr::copy_nonoverlapping(mid.sub(left), buf, left); @@ -105,13 +105,13 @@ const unsafe fn usize_rotate(mut left: usize, mut mid: *mut usize, mut right: us } } -// `usize_rotate` has some unusually large thresholds for some branches that +// `digit_rotate` has some unusually large thresholds for some branches that // don't get tested well by Miri in fuzz.rs, so test them here #[test] -fn usize_rotate_test() { - let mut buf = [123usize; 123]; +fn digit_rotate_test() { + let mut buf: [Digit; 123] = [123; 123]; for k in 0..123 { - unsafe { usize_rotate(k, buf.as_mut_ptr().add(k), 123 - k) } + unsafe { digit_rotate(k, buf.as_mut_ptr().add(k), 123 - k) } } } @@ -381,7 +381,7 @@ impl Bits { let p = x.as_mut_ptr(); // Safety: this satisfies the requirements of `usize_rotate` unsafe { - usize_rotate(mid_digit, p.add(mid_digit), digits); + digit_rotate(mid_digit, p.add(mid_digit), digits); } if extra != 0 && digits != 0 { @@ -546,7 +546,7 @@ impl Bits { s.assert_cleared_unused_bits(); // We avoid overflow by checking in this order and with `BITS - 1` instead of // `BITS` - if (s.bw() >= (BITS - 1)) + if (s.bw() >= (USIZE_BITS - 1)) || ((1usize << s.bw()) != self.bw()) || ((self.bw() << 1) != rhs.bw()) { diff --git a/awint_core/src/logic/primitives.rs b/awint_core/src/logic/primitives.rs index 294a56a3..7b2ebba1 100644 --- a/awint_core/src/logic/primitives.rs +++ b/awint_core/src/logic/primitives.rs @@ -12,27 +12,23 @@ macro_rules! bits_ { pub const fn $unsigned_name(&mut self, x: $uX) { const BW: usize = $uX::BITS as usize; const LEN: usize = BW / BITS; - let mut x = x; - if LEN < 2 { - *self.first_mut() = x as usize; + if LEN <= 1 { + *self.first_mut() = x as Digit; if self.len() > 1 { - unsafe { - self.digit_set(false, 1..self.len(), false);} + unsafe {self.digit_set(false, 1..self.len(), false);} } } else if self.bw() > BW { // Safety: there are at least `LEN` digits in `self` unsafe { const_for!(i in {0..LEN} { - *self.get_unchecked_mut(i) = x as usize; - x = x.wrapping_shr(usize::BITS); + *self.get_unchecked_mut(i) = (x >> (i * BITS)) as Digit; }); self.digit_set(false, LEN..self.len(), false); } } else { unsafe { const_for!(i in {0..self.len()} { - *self.get_unchecked_mut(i) = x as usize; - x = x.wrapping_shr(usize::BITS); + *self.get_unchecked_mut(i) = (x >> (i * BITS)) as Digit; }); } } @@ -43,9 +39,8 @@ macro_rules! bits_ { pub const fn $signed_name(&mut self, x: $iX) { const BW: usize = $iX::BITS as usize; const LEN: usize = BW / BITS; - let mut x = x; - if LEN < 2 { - *self.first_mut() = x as isize as usize; + if LEN <= 1 { + *self.first_mut() = x as IDigit as Digit; if self.len() >= 1 { // Safety: there is at least 1 digit in `self` unsafe { @@ -57,16 +52,14 @@ macro_rules! bits_ { unsafe { let sign = x < 0; const_for!(i in {0..LEN} { - *self.get_unchecked_mut(i) = x as isize as usize; - x = x.wrapping_shr(usize::BITS); + *self.get_unchecked_mut(i) = (x >> (i * BITS)) as IDigit as Digit; }); self.digit_set(sign, LEN..self.len(), true); } } else { unsafe { const_for!(i in {0..self.len()} { - *self.get_unchecked_mut(i) = x as isize as usize; - x = x.wrapping_shr(usize::BITS); + *self.get_unchecked_mut(i) = (x >> (i * BITS)) as IDigit as Digit; }); } self.clear_unused_bits(); @@ -96,7 +89,13 @@ impl Bits { #[const_fn(cfg(feature = "const_support"))] pub const fn bool_(&mut self, x: bool) { self.zero_(); - *self.first_mut() = x as usize; + *self.first_mut() = x as Digit; + } + + #[const_fn(cfg(feature = "const_support"))] + pub const fn digit_(&mut self, x: Digit) { + self.zero_(); + *self.first_mut() = x; } } @@ -108,7 +107,7 @@ macro_rules! bits_convert { pub const fn $unsigned_name(&self) -> $uX { const BW: usize = $uX::BITS as usize; const LEN: usize = BW / BITS; - if LEN < 2 { + if LEN <= 1 { self.first() as $uX } else if self.bw() >= BW { // Safety: there are at least `LEN` digits in `self` @@ -135,8 +134,8 @@ macro_rules! bits_convert { pub const fn $signed_name(&self) -> $iX { const BW: usize = $uX::BITS as usize; const LEN: usize = BW / BITS; - if LEN < 2 && self.len() == 1 { - let sign_bit = 1usize << (self.bw() - 1); + if LEN <= 1 && self.len() == 1 { + let sign_bit: Digit = 1 << (self.bw() - 1); let extension = $uX::MIN.wrapping_sub((self.first() & sign_bit) as $uX); ((self.first() as $uX) | extension) as $iX } else { @@ -176,6 +175,12 @@ impl Bits { pub const fn to_bool(&self) -> bool { (self.first() & 1) != 0 } + + #[const_fn(cfg(feature = "const_support"))] + #[must_use] + pub const fn to_digit(&self) -> Digit { + self.first() + } } impl From<&Bits> for bool { diff --git a/awint_core/src/logic/rand.rs b/awint_core/src/logic/rand.rs index 005ec6af..c77e0c4d 100644 --- a/awint_core/src/logic/rand.rs +++ b/awint_core/src/logic/rand.rs @@ -44,7 +44,7 @@ impl Bits { // this is a no-op on little endian, but on big endian this fixes byte order in // regular digits and rotates out unused bytes const_for!(i in {0..self.len()} { - self.as_mut_slice()[i] = usize::from_le(self.as_mut_slice()[i]); + self.as_mut_slice()[i] = Digit::from_le(self.as_mut_slice()[i]); }); // clean up unused bits in last byte self.clear_unused_bits(); diff --git a/awint_core/src/logic/sum.rs b/awint_core/src/logic/sum.rs index fe6e539c..bf0c75cf 100644 --- a/awint_core/src/logic/sum.rs +++ b/awint_core/src/logic/sum.rs @@ -180,7 +180,7 @@ impl Bits { if self.bw() != lhs.bw() || self.bw() != rhs.bw() { return None } - let mut carry = cin as usize; + let mut carry = cin as Digit; unsafe { const_for!(i in {0..(self.len() - 1)} { let tmp = widen_add(lhs.get_unchecked(i), rhs.get_unchecked(i), carry); @@ -191,9 +191,9 @@ impl Bits { let tmp = widen_add(lhs.last(), rhs.last(), carry); let extra = self.extra(); Some(if extra == 0 { - let lhs_sign = (lhs.last() as isize) < 0; - let rhs_sign = (rhs.last() as isize) < 0; - let output_sign = (tmp.0 as isize) < 0; + let lhs_sign = (lhs.last() as IDigit) < 0; + let rhs_sign = (rhs.last() as IDigit) < 0; + let output_sign = (tmp.0 as IDigit) < 0; *self.last_mut() = tmp.0; ( tmp.1 != 0, diff --git a/awint_dag/Cargo.toml b/awint_dag/Cargo.toml index 147f1a21..9c722b05 100644 --- a/awint_dag/Cargo.toml +++ b/awint_dag/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awint_dag" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" @@ -12,9 +12,9 @@ keywords = ["int", "integer", "big", "math", "no_std"] categories = ["data-structures", "mathematics", "algorithms", "no-std"] [dependencies] -awint_ext = { version = "0.9.0", path = "../awint_ext", default-features = false } -awint_macro_internals = { version = "0.9.0", path = "../awint_macro_internals", default-features = false } -awint_macros = { version = "0.9.0", path = "../awint_macros", default-features = false } +awint_ext = { version = "0.10.0", path = "../awint_ext", default-features = false } +awint_macro_internals = { version = "0.10.0", path = "../awint_macro_internals", default-features = false } +awint_macros = { version = "0.10.0", path = "../awint_macros", default-features = false } # our minimum version is above the 1.51 required for some of these features smallvec = { version = "1.10.0", default-features = false, features = ["const_generics", "const_new", "union"] } diff --git a/awint_dag/src/common/eval.rs b/awint_dag/src/common/eval.rs index f898c9e9..c2614a2a 100644 --- a/awint_dag/src/common/eval.rs +++ b/awint_dag/src/common/eval.rs @@ -4,7 +4,7 @@ use std::num::NonZeroUsize; -use awint_ext::{awint_internals::BITS, Bits, ExtAwi}; +use awint_ext::{awint_internals::USIZE_BITS, Bits, ExtAwi}; use Op::*; use crate::{EvalError, Op}; @@ -53,7 +53,7 @@ fn cbool(x: &Bits) -> Result { } fn cusize(x: &Bits) -> Result { - if x.bw() == BITS { + if x.bw() == USIZE_BITS { Ok(x.to_usize()) } else { Err(EvalError::OtherStr( diff --git a/awint_dag/src/common/noop.rs b/awint_dag/src/common/noop.rs index 470f643a..021e6b09 100644 --- a/awint_dag/src/common/noop.rs +++ b/awint_dag/src/common/noop.rs @@ -3,7 +3,7 @@ use std::num::NonZeroUsize; -use awint_ext::{awi, awint_internals::BITS}; +use awint_ext::{awi, awint_internals::USIZE_BITS}; use Op::*; use crate::{EvalError, Op}; @@ -43,7 +43,7 @@ fn cbool(x: NonZeroUsize) -> Result<(), EvalError> { } fn cusize(x: NonZeroUsize) -> Result<(), EvalError> { - if x.get() == BITS { + if x.get() == USIZE_BITS { Ok(()) } else { Err(EvalError::OtherStr( @@ -117,7 +117,7 @@ impl Op { true } StaticLut([a], lit) => { - if a.get() < BITS { + if a.get() < USIZE_BITS { if let awi::Some(lut_len) = (1usize << a.get()).checked_mul(w.get()) { if lut_len == lit.bw() { return NoopResult::Operational @@ -151,7 +151,7 @@ impl Op { Copy([a]) => w == a, Lut([a, b]) => { let mut res = false; - if b.get() < BITS { + if b.get() < USIZE_BITS { if let awi::Some(lut_len) = (1usize << b.get()).checked_mul(w.get()) { if lut_len == a.get() { res = true; @@ -161,7 +161,7 @@ impl Op { res } Funnel([a, b]) => { - (b.get() < (BITS - 1)) + (b.get() < (USIZE_BITS - 1)) && ((1usize << b.get()) == w.get()) && ((w.get() << 1) == a.get()) } @@ -307,7 +307,7 @@ impl Op { } LutSet([a, b, c]) => { let mut res = false; - if c.get() < BITS { + if c.get() < USIZE_BITS { if let Some(lut_len) = (1usize << c.get()).checked_mul(b.get()) { if lut_len == a.get() { res = w == a; diff --git a/awint_dag/src/common/state.rs b/awint_dag/src/common/state.rs index eda73de1..cc08fa9b 100644 --- a/awint_dag/src/common/state.rs +++ b/awint_dag/src/common/state.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, num::NonZeroUsize}; use awint_ext::{ - awint_internals::{Location, BITS}, + awint_internals::{Location, USIZE_BITS}, bw, }; @@ -324,7 +324,7 @@ impl PState { pub fn try_get_as_usize(&self) -> Option { self.get_state(|state| { if let Op::Literal(ref lit) = state?.op { - if lit.bw() == BITS { + if lit.bw() == USIZE_BITS { return Some(lit.to_usize()) } } diff --git a/awint_dag/src/lowering/meta.rs b/awint_dag/src/lowering/meta.rs index 6e472a17..a738d92d 100644 --- a/awint_dag/src/lowering/meta.rs +++ b/awint_dag/src/lowering/meta.rs @@ -2,7 +2,7 @@ use std::{cmp::min, mem, num::NonZeroUsize}; -use awint_ext::{awi, awint_internals::BITS}; +use awint_ext::{awi, awint_internals::USIZE_BITS}; use awint_macros::*; use crate::mimick::{Bits, ExtAwi, InlAwi}; @@ -399,8 +399,8 @@ pub fn funnel_(x: &Bits, s: &Bits) -> ExtAwi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> ExtAwi { - assert_eq!(from.bw(), BITS); - assert_eq!(width.bw(), BITS); + assert_eq!(from.bw(), USIZE_BITS); + assert_eq!(width.bw(), USIZE_BITS); let mut out = ExtAwi::from_bits(lhs); // the `width == 0` case will result in a no-op from the later `field_width` // part, so we need to be able to handle just `rhs.bw()` possible shifts for @@ -416,7 +416,7 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> ExtAwi { } pub fn shl(x: &Bits, s: &Bits) -> ExtAwi { - assert_eq!(s.bw(), BITS); + assert_eq!(s.bw(), USIZE_BITS); let mut signals = selector(s, Some(x.bw())); signals.reverse(); let mut out = ExtAwi::zero(x.nzbw()); @@ -425,7 +425,7 @@ pub fn shl(x: &Bits, s: &Bits) -> ExtAwi { } pub fn lshr(x: &Bits, s: &Bits) -> ExtAwi { - assert_eq!(s.bw(), BITS); + assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); let mut out = ExtAwi::zero(x.nzbw()); crossbar(&mut out, x, &signals, (x.bw() - 1, 2 * x.bw() - 1)); @@ -433,7 +433,7 @@ pub fn lshr(x: &Bits, s: &Bits) -> ExtAwi { } pub fn ashr(x: &Bits, s: &Bits) -> ExtAwi { - assert_eq!(s.bw(), BITS); + assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); let mut out = ExtAwi::zero(x.nzbw()); crossbar(&mut out, x, &signals, (x.bw() - 1, 2 * x.bw() - 1)); @@ -480,7 +480,7 @@ pub fn ashr(x: &Bits, s: &Bits) -> ExtAwi { } pub fn rotl(x: &Bits, s: &Bits) -> ExtAwi { - assert_eq!(s.bw(), BITS); + assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); // we will use the whole cross bar, with every signal controlling two diagonals // for the wraparound except for the `x.bw() - 1` one @@ -497,7 +497,7 @@ pub fn rotl(x: &Bits, s: &Bits) -> ExtAwi { } pub fn rotr(x: &Bits, s: &Bits) -> ExtAwi { - assert_eq!(s.bw(), BITS); + assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); // we will use the whole cross bar, with every signal controlling two diagonals // for the wraparound except for the `x.bw() - 1` one @@ -626,8 +626,8 @@ pub fn negator(x: &Bits, neg: &Bits) -> ExtAwi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> ExtAwi { - assert_eq!(to.bw(), BITS); - assert_eq!(width.bw(), BITS); + assert_eq!(to.bw(), USIZE_BITS); + assert_eq!(width.bw(), USIZE_BITS); // simplified version of `field` below @@ -681,9 +681,9 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> ExtAwi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field(lhs: &Bits, to: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> ExtAwi { - assert_eq!(to.bw(), BITS); - assert_eq!(from.bw(), BITS); - assert_eq!(width.bw(), BITS); + assert_eq!(to.bw(), USIZE_BITS); + assert_eq!(from.bw(), USIZE_BITS); + assert_eq!(width.bw(), USIZE_BITS); // we use some summation to get the fielding done with a single crossbar diff --git a/awint_dag/src/lowering/op_dag.rs b/awint_dag/src/lowering/op_dag.rs index 84e8605e..f5ecbddf 100644 --- a/awint_dag/src/lowering/op_dag.rs +++ b/awint_dag/src/lowering/op_dag.rs @@ -8,7 +8,7 @@ use std::{ vec, }; -use awint_ext::{awint_internals::BITS, Bits}; +use awint_ext::{awint_internals::USIZE_BITS, Bits}; use Op::*; use crate::{ @@ -409,7 +409,7 @@ impl OpDag { #[track_caller] pub fn usize(&self, ptr: PNode) -> Result { if let Literal(ref lit) = self[ptr].op { - if lit.bw() == BITS { + if lit.bw() == USIZE_BITS { Ok(lit.to_usize()) } else { Err(EvalError::WrongBitwidth) diff --git a/awint_dag/src/mimick/awi_types.rs b/awint_dag/src/mimick/awi_types.rs index da7c7437..58e5cf60 100644 --- a/awint_dag/src/mimick/awi_types.rs +++ b/awint_dag/src/mimick/awi_types.rs @@ -9,7 +9,7 @@ use std::{ use awint_ext::{ awi, - awint_internals::{assert_inlawi_invariants, bw, forward_debug_fmt}, + awint_internals::{assert_inlawi_invariants, bw, forward_debug_fmt, USIZE_BITS}, }; use smallvec::smallvec; @@ -72,7 +72,7 @@ impl InlAwi { pub fn unstable_from_u8_slice(buf: &[u8]) -> Self { assert_inlawi_invariants::(); Self::new(Op::Literal(awi::ExtAwi::from_bits( - awi::InlAwi::::unstable_from_u8_slice(buf).const_as_ref(), + &awi::InlAwi::::unstable_from_u8_slice(buf), ))) } @@ -111,13 +111,13 @@ impl Deref for InlAwi { type Target = Bits; fn deref(&self) -> &Self::Target { - self.const_as_ref() + self.internal_as_ref() } } impl DerefMut for InlAwi { fn deref_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self.internal_as_mut() } } @@ -125,37 +125,37 @@ impl Index for InlAwi { type Output = Bits; fn index(&self, _i: RangeFull) -> &Bits { - self.const_as_ref() + self } } impl Borrow for InlAwi { fn borrow(&self) -> &Bits { - self.const_as_ref() + self } } impl AsRef for InlAwi { fn as_ref(&self) -> &Bits { - self.const_as_ref() + self } } impl IndexMut for InlAwi { fn index_mut(&mut self, _i: RangeFull) -> &mut Bits { - self.const_as_mut() + self } } impl BorrowMut for InlAwi { fn borrow_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } impl AsMut for InlAwi { fn as_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } @@ -252,8 +252,7 @@ inlawi_from!( 128, u128 from_u128 u128_ i128 from_i128 i128_; ); -type UsizeInlAwi = - InlAwi<{ awi::usize::BITS as usize }, { awi::Bits::unstable_raw_digits(usize::BITS as usize) }>; +type UsizeInlAwi = InlAwi<{ USIZE_BITS }, { awi::Bits::unstable_raw_digits(USIZE_BITS) }>; impl UsizeInlAwi { pub fn from_usize(x: impl Into) -> Self { @@ -427,13 +426,13 @@ impl Deref for ExtAwi { type Target = Bits; fn deref(&self) -> &Self::Target { - self.const_as_ref() + self.internal_as_ref() } } impl DerefMut for ExtAwi { fn deref_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self.internal_as_mut() } } @@ -441,19 +440,19 @@ impl Index for ExtAwi { type Output = Bits; fn index(&self, _i: RangeFull) -> &Bits { - self.const_as_ref() + self } } impl Borrow for ExtAwi { fn borrow(&self) -> &Bits { - self.const_as_ref() + self } } impl AsRef for ExtAwi { fn as_ref(&self) -> &Bits { - self.const_as_ref() + self } } diff --git a/awint_dag/src/mimick/bits.rs b/awint_dag/src/mimick/bits.rs index 29a6e738..373a50cf 100644 --- a/awint_dag/src/mimick/bits.rs +++ b/awint_dag/src/mimick/bits.rs @@ -41,21 +41,21 @@ impl<'a> Bits { } impl<'a> ExtAwi { - pub fn const_as_ref(&'a self) -> &'a Bits { + pub(in crate::mimick) fn internal_as_ref(&'a self) -> &'a Bits { unsafe { Bits::from_raw_parts(self._extawi_raw.as_ptr()) } } - pub fn const_as_mut(&'a mut self) -> &'a mut Bits { + pub(in crate::mimick) fn internal_as_mut(&'a mut self) -> &'a mut Bits { unsafe { Bits::from_raw_parts_mut(self._extawi_raw.as_mut_ptr()) } } } impl<'a, const BW: usize, const LEN: usize> InlAwi { - pub fn const_as_ref(&'a self) -> &'a Bits { + pub(in crate::mimick) fn internal_as_ref(&'a self) -> &'a Bits { unsafe { Bits::from_raw_parts(self._inlawi_raw.as_ptr()) } } - pub fn const_as_mut(&'a mut self) -> &'a mut Bits { + pub(in crate::mimick) fn internal_as_mut(&'a mut self) -> &'a mut Bits { unsafe { Bits::from_raw_parts_mut(self._inlawi_raw.as_mut_ptr()) } } } diff --git a/awint_dag/src/mimick/ops.rs b/awint_dag/src/mimick/ops.rs index fed1034a..350a8e09 100644 --- a/awint_dag/src/mimick/ops.rs +++ b/awint_dag/src/mimick/ops.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; -use awint_ext::{awi, awint_internals::BITS}; +use awint_ext::{awi, awint_internals::USIZE_BITS}; use smallvec::smallvec; use Op::*; @@ -693,7 +693,7 @@ impl Bits { let mut x: Vec<_> = Vec::from(x); let last = x.pop().unwrap().into(); let mut max = if let Op::Literal(ref lit) = last.state().cloned_state().unwrap().op { - assert_eq!(lit.bw(), BITS); + assert_eq!(lit.bw(), USIZE_BITS); lit.to_usize() } else { panic!(); @@ -701,7 +701,7 @@ impl Bits { for _ in 1..N { let last = x.pop().unwrap().into(); if let Op::Literal(ref lit) = last.state().cloned_state().unwrap().op { - assert_eq!(lit.bw(), BITS); + assert_eq!(lit.bw(), USIZE_BITS); let val = lit.to_usize(); if val > max { max = val; @@ -750,7 +750,7 @@ impl Bits { } if check_nonzero_cw { if let Op::Literal(ref lit) = cw.state().cloned_state().unwrap().op { - assert_eq!(lit.bw(), BITS); + assert_eq!(lit.bw(), USIZE_BITS); if lit.to_usize() == 0 { return CCResult { run_fielding: false, diff --git a/awint_dag/src/mimick/primitive.rs b/awint_dag/src/mimick/primitive.rs index df04da57..3adec453 100644 --- a/awint_dag/src/mimick/primitive.rs +++ b/awint_dag/src/mimick/primitive.rs @@ -204,11 +204,11 @@ prim!( u32 U32Assign 32, u64 U64Assign 64, u128 U128Assign 128, - usize UsizeAssign BITS, + usize UsizeAssign USIZE_BITS, i8 I8Assign 8, i16 I16Assign 16, i32 I32Assign 32, i64 I64Assign 64, i128 I128Assign 128, - isize IsizeAssign BITS, + isize IsizeAssign USIZE_BITS, ); diff --git a/awint_ext/Cargo.toml b/awint_ext/Cargo.toml index f00194bf..260f48c7 100644 --- a/awint_ext/Cargo.toml +++ b/awint_ext/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awint_ext" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" @@ -12,7 +12,7 @@ keywords = ["int", "integer", "big", "math", "no_std"] categories = ["data-structures", "mathematics", "algorithms", "no-std"] [dependencies] -awint_core = { version = "0.9.0", path = "../awint_core", default-features = false } +awint_core = { version = "0.10.0", path = "../awint_core", default-features = false } const_fn = "0.4" serde = { version = "1.0", default-features = false, optional = true } zeroize = { version = "1.5", default-features = false, optional = true } @@ -29,3 +29,9 @@ const_support = ["awint_core/const_support"] # Turns on `serde` support serde_support = ["serde"] zeroize_support = ["zeroize", "awint_core/zeroize_support"] +# Only zero or one of these should be active +u8_digits = ["awint_core/u8_digits"] +u16_digits = ["awint_core/u16_digits"] +u32_digits = ["awint_core/u32_digits"] +u64_digits = ["awint_core/u64_digits"] +u128_digits = ["awint_core/u128_digits"] diff --git a/awint_ext/src/extawi.rs b/awint_ext/src/extawi.rs index 91989640..ba851804 100644 --- a/awint_ext/src/extawi.rs +++ b/awint_ext/src/extawi.rs @@ -21,8 +21,8 @@ pub(crate) const fn layout(w: NonZeroUsize) -> Layout { // Safety: this produces the exact number of bytes needed to satisfy the raw // invariants of `Bits`. Layout::from_size_align_unchecked( - (regular_digits(w) + 1) * mem::size_of::(), - mem::align_of::(), + raw_digits(w.get()) * mem::size_of::(), + mem::align_of::(), ) } } @@ -51,7 +51,7 @@ pub(crate) const fn layout(w: NonZeroUsize) -> Layout { /// // macros with unbounded fillers from `awint_macros` /// x0.sign_resize_(x1); /// // multiply in place by 2 for an example -/// x0.short_cin_mul(0, 2); +/// x0.digit_cin_mul_(0, 2); /// } /// /// // using `bw` function for quick `NonZeroUsize` construction from a literal @@ -82,13 +82,13 @@ impl<'a> ExtAwi { /// # Safety /// /// `ptr` should be allocated according to the `extawi::layout` function and - /// all digits initialized. The metadata digit should be set to the + /// all digits initialized. The metadata digits should be set to the /// bitwidth. `raw_len` should correspond to the raw length (including /// metadata) of the corresponding `Bits`. #[doc(hidden)] #[inline] #[const_fn(cfg(feature = "const_support"))] - pub const unsafe fn from_raw_parts(ptr: *mut usize, raw_len: usize) -> ExtAwi { + pub const unsafe fn from_raw_parts(ptr: *mut Digit, raw_len: usize) -> ExtAwi { // Safety: The requirements on this function satisfies // `Bits::from_raw_parts_mut`. unsafe { @@ -102,7 +102,7 @@ impl<'a> ExtAwi { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn const_as_ref(&'a self) -> &'a Bits { + const fn internal_as_ref(&'a self) -> &'a Bits { // `as_ref` on NonNull is not const yet, so we have to use transmute. // Safety: The explicit lifetimes make sure they do not become unbounded. unsafe { mem::transmute::, &Bits>(self.raw) } @@ -112,7 +112,7 @@ impl<'a> ExtAwi { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub const fn const_as_mut(&'a mut self) -> &'a mut Bits { + const fn internal_as_mut(&'a mut self) -> &'a mut Bits { // Safety: The explicit lifetimes make sure they do not become unbounded. unsafe { mem::transmute::, &mut Bits>(self.raw) } } @@ -122,7 +122,7 @@ impl<'a> ExtAwi { #[const_fn(cfg(feature = "const_support"))] #[must_use] pub const fn nzbw(&self) -> NonZeroUsize { - self.const_as_ref().nzbw() + self.internal_as_ref().nzbw() } /// Returns the bitwidth of this `ExtAwi` as a `usize` @@ -130,7 +130,7 @@ impl<'a> ExtAwi { #[const_fn(cfg(feature = "const_support"))] #[must_use] pub const fn bw(&self) -> usize { - self.const_as_ref().bw() + self.internal_as_ref().bw() } /// Returns the exact number of `usize` digits needed to store all bits. @@ -138,7 +138,7 @@ impl<'a> ExtAwi { #[const_fn(cfg(feature = "const_support"))] #[must_use] pub const fn len(&self) -> usize { - self.const_as_ref().len() + self.internal_as_ref().len() } /// Returns the total length of the underlying raw array in `usize`s @@ -147,7 +147,7 @@ impl<'a> ExtAwi { #[const_fn(cfg(feature = "const_support"))] #[must_use] pub const fn raw_len(&self) -> usize { - self.const_as_ref().raw_len() + self.internal_as_ref().raw_len() } /// Returns a raw pointer to the underlying bit storage. The caller must @@ -158,8 +158,8 @@ impl<'a> ExtAwi { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub fn as_ptr(&self) -> *const usize { - self.const_as_ref().as_ptr() + pub fn as_ptr(&self) -> *const Digit { + self.internal_as_ref().as_ptr() } /// Returns a raw pointer to the underlying bit storage. The caller must @@ -169,8 +169,8 @@ impl<'a> ExtAwi { #[inline] #[const_fn(cfg(feature = "const_support"))] #[must_use] - pub fn as_mut_ptr(&mut self) -> *mut usize { - self.const_as_mut().as_mut_ptr() + pub fn as_mut_ptr(&mut self) -> *mut Digit { + self.internal_as_mut().as_mut_ptr() } #[doc(hidden)] @@ -194,10 +194,13 @@ impl<'a> ExtAwi { pub fn zero(w: NonZeroUsize) -> Self { // Safety: This satisfies `ExtAwi::from_raw_parts` unsafe { - let ptr: *mut usize = alloc_zeroed(layout(w)).cast(); + let ptr: *mut Digit = alloc_zeroed(layout(w)).cast(); // set bitwidth - ptr.add(regular_digits(w)).write(w.get()); - ExtAwi::from_raw_parts(ptr, regular_digits(w) + 1) + let regular_digits = regular_digits(w); + const_for!(i in {0..METADATA_DIGITS} { + ptr.add(i + regular_digits).write((w.get() >> (i * BITS)) as Digit); + }); + ExtAwi::from_raw_parts(ptr, regular_digits + METADATA_DIGITS) } } @@ -205,12 +208,15 @@ impl<'a> ExtAwi { pub fn umax(w: NonZeroUsize) -> Self { // Safety: This satisfies `ExtAwi::from_raw_parts` let mut x = unsafe { - let ptr: *mut usize = alloc(layout(w)).cast(); + let ptr: *mut Digit = alloc(layout(w)).cast(); // initialize everything except for the bitwidth - ptr.write_bytes(u8::MAX, regular_digits(w)); + let regular_digits = regular_digits(w); + ptr.write_bytes(u8::MAX, regular_digits); // set bitwidth - ptr.add(regular_digits(w)).write(w.get()); - ExtAwi::from_raw_parts(ptr, regular_digits(w) + 1) + const_for!(i in {0..METADATA_DIGITS} { + ptr.add(i + regular_digits).write((w.get() >> (i * BITS)) as Digit); + }); + ExtAwi::from_raw_parts(ptr, regular_digits + METADATA_DIGITS) }; x.const_as_mut().clear_unused_bits(); x @@ -219,14 +225,14 @@ impl<'a> ExtAwi { /// Signed-maximum-value construction with bitwidth `bw` pub fn imax(w: NonZeroUsize) -> Self { let mut awi = Self::umax(w); - *awi.const_as_mut().last_mut() = (isize::MAX as usize) >> awi.const_as_ref().unused(); + *awi.const_as_mut().last_mut() = (MAX >> 1) >> awi.unused(); awi } /// Signed-minimum-value construction with bitwidth `bw` pub fn imin(w: NonZeroUsize) -> Self { let mut awi = Self::zero(w); - *awi.const_as_mut().last_mut() = (isize::MIN as usize) >> awi.const_as_ref().unused(); + *awi.const_as_mut().last_mut() = (IDigit::MIN as Digit) >> awi.unused(); awi } @@ -290,7 +296,7 @@ impl Clone for ExtAwi { /// If `self` and `other` have unmatching bit widths, `false` will be returned. impl PartialEq for ExtAwi { fn eq(&self, rhs: &Self) -> bool { - self.const_as_ref() == rhs.const_as_ref() + self.as_ref() == rhs.as_ref() } } @@ -310,7 +316,7 @@ macro_rules! impl_fmt { /// Forwards to the corresponding impl for `Bits` impl fmt::$ty for ExtAwi { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::$ty::fmt(self.const_as_ref(), f) + fmt::$ty::fmt(self.as_ref(), f) } } )* @@ -321,7 +327,7 @@ impl_fmt!(Debug Display LowerHex UpperHex Octal Binary); impl Hash for ExtAwi { fn hash(&self, state: &mut H) { - self.const_as_ref().hash(state); + self.as_ref().hash(state); } } @@ -330,14 +336,14 @@ impl Deref for ExtAwi { #[inline] fn deref(&self) -> &Self::Target { - self.const_as_ref() + self.internal_as_ref() } } impl DerefMut for ExtAwi { #[inline] fn deref_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self.internal_as_mut() } } @@ -346,42 +352,42 @@ impl Index for ExtAwi { #[inline] fn index(&self, _i: RangeFull) -> &Bits { - self.const_as_ref() + self } } impl Borrow for ExtAwi { #[inline] fn borrow(&self) -> &Bits { - self.const_as_ref() + self } } impl AsRef for ExtAwi { #[inline] fn as_ref(&self) -> &Bits { - self.const_as_ref() + self } } impl IndexMut for ExtAwi { #[inline] fn index_mut(&mut self, _i: RangeFull) -> &mut Bits { - self.const_as_mut() + self } } impl BorrowMut for ExtAwi { #[inline] fn borrow_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } impl AsMut for ExtAwi { #[inline] fn as_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self } } @@ -401,7 +407,7 @@ impl From<&Bits> for ExtAwi { impl From> for ExtAwi { fn from(awi: InlAwi) -> ExtAwi { let mut tmp = ExtAwi::zero(awi.nzbw()); - tmp.const_as_mut().copy_(awi.const_as_ref()).unwrap(); + tmp.const_as_mut().copy_(&awi).unwrap(); tmp } } @@ -412,7 +418,7 @@ macro_rules! extawi_from_ty { /// Creates an `ExtAwi` with the same bitwidth and bits as the integer pub fn $from(x: $ty) -> Self { let mut tmp = ExtAwi::zero(bw($ty::BITS as usize)); - tmp.const_as_mut().$assign(x); + tmp.$assign(x); tmp } )* @@ -438,7 +444,14 @@ impl ExtAwi { /// Creates an `ExtAwi` with one bit set to this `bool` pub fn from_bool(x: bool) -> Self { let mut tmp = ExtAwi::zero(bw(1)); - tmp.const_as_mut().bool_(x); + tmp.bool_(x); + tmp + } + + /// Creates an `ExtAwi` with the same bitwidth and bits as the integer + pub fn from_digit(x: Digit) -> Self { + let mut tmp = ExtAwi::zero(bw(BITS)); + tmp.digit_(x); tmp } } @@ -446,7 +459,7 @@ impl ExtAwi { impl From for ExtAwi { fn from(x: bool) -> ExtAwi { let mut tmp = ExtAwi::zero(bw(1)); - tmp.const_as_mut().bool_(x); + tmp.bool_(x); tmp } } @@ -457,7 +470,7 @@ macro_rules! extawi_from { impl From<$ty> for ExtAwi { fn from(x: $ty) -> Self { let mut tmp = ExtAwi::zero(bw($ty::BITS as usize)); - tmp.const_as_mut().$assign(x); + tmp.$assign(x); tmp } } diff --git a/awint_ext/src/fp_core.rs b/awint_ext/src/fp_core.rs index a8346aba..072c959b 100644 --- a/awint_ext/src/fp_core.rs +++ b/awint_ext/src/fp_core.rs @@ -7,7 +7,7 @@ use core::{ ops::{Deref, DerefMut}, }; -use awint_core::Bits; +use awint_core::{awint_internals::Digit, Bits}; use crate::ExtAwi; @@ -27,7 +27,7 @@ impl FPType { /// unique representation of the fraction part of this fixed point type. /// This function performs allocation. Returns `None` if `radix < 2`. #[must_use] - pub fn unique_min_fraction_digits(&self, radix: usize) -> Option { + pub fn unique_min_fraction_digits(&self, radix: u8) -> Option { if radix < 2 { return None } @@ -38,7 +38,7 @@ impl FPType { let mut digits = 0; loop { digits += 1; - if test.short_cin_mul(0, radix) != 0 { + if test.digit_cin_mul_(0, radix as Digit) != 0 { // as soon as overflow happens, that means // `(((radix^digits) * 1 ULP) >> this.fp()) > 0` break @@ -101,58 +101,46 @@ impl> FP { &mut self.bits } - /// Returns a reference to `self` in the form of `&Bits` - #[inline] - pub fn const_as_ref(&self) -> &Bits { - self.bits.borrow() - } - - /// Returns a reference to `self` in the form of `&mut Bits` - #[inline] - pub fn const_as_mut(&mut self) -> &mut Bits { - self.bits.borrow_mut() - } - /// Returns the signedness of `self` #[inline] pub fn signed(&self) -> bool { self.signed } - /// Returns the sign of `self`, returning `Some(self.const_as_ref().msb())` + /// Returns the sign of `self`, returning `Some(self.msb())` /// if `self.signed()`, and `None` otherwise. #[inline] pub fn sign(&self) -> Option { if self.signed() { - Some(self.const_as_ref().msb()) + Some(self.msb()) } else { None } } - /// Returns if `self.signed() && self.const_as_ref().msb()` + /// Returns if `self.signed() && self.msb()` #[inline] pub fn is_negative(&self) -> bool { - self.signed() && self.const_as_ref().msb() + self.signed() && self.msb() } /// Returns the bitwidth of `self` as a `NonZeroUsize` #[inline] pub fn nzbw(&self) -> NonZeroUsize { - self.const_as_ref().nzbw() + self.b().borrow().nzbw() } /// Returns the bitwidth of `self` as a `usize` #[inline] pub fn bw(&self) -> usize { - self.const_as_ref().bw() + self.b().borrow().bw() } /// Returns the bitwidth of `self` as an `isize` #[inline] pub fn ibw(&self) -> isize { // this is ok because of the guard in `FP::new` - self.const_as_ref().bw() as isize + self.bw() as isize } /// Returns the fixed point of `self` @@ -190,14 +178,14 @@ impl> Deref for FP { #[inline] fn deref(&self) -> &Self::Target { - self.const_as_ref() + self.b().borrow() } } impl> DerefMut for FP { #[inline] fn deref_mut(&mut self) -> &mut Bits { - self.const_as_mut() + self.b_mut().borrow_mut() } } @@ -276,7 +264,7 @@ impl_fmt!( impl> Hash for FP { /// Uses the hash of `self.signed()`, `self.fp()`, and the `Hash` - /// implementation on `FP::into_inner(self)` (not `self.const_as_ref()`) + /// implementation on `self.b()` (not `self.as_ref()`) fn hash(&self, state: &mut H) { self.signed.hash(state); self.fp.hash(state); diff --git a/awint_ext/src/fp_ieee.rs b/awint_ext/src/fp_ieee.rs index 6e73b9b1..b798dffd 100644 --- a/awint_ext/src/fp_ieee.rs +++ b/awint_ext/src/fp_ieee.rs @@ -4,7 +4,7 @@ use awint_core::{Bits, InlAwi}; use crate::FP; -/// A minimal `FP` real number representation of an IEEE-754 32 /// bit floating point number. This derives from 23 mantissa bits, the omitted /// leading 1 bit, and sign bit. This cannot represent the infinity and NaN /// cases, but subnormal values can be represented. Canonically, the fixed point @@ -12,7 +12,7 @@ use crate::FP; /// exponent. pub type F32 = FP>; -/// A minimal `FP` real number representation of an IEEE-754 64 /// bit floating point number. This derives from 52 mantissa bits, the omitted /// leading 1 bit, and sign bit. This cannot represent the infinity and NaN /// cases, but subnormal values can be represented. Canonically, the fixed point diff --git a/awint_ext/src/fp_logic.rs b/awint_ext/src/fp_logic.rs index 1ebd6c80..b7ddb72a 100644 --- a/awint_ext/src/fp_logic.rs +++ b/awint_ext/src/fp_logic.rs @@ -5,7 +5,7 @@ use core::{ num::NonZeroUsize, }; -use awint_core::Bits; +use awint_core::{awint_internals::Digit, Bits}; use crate::{ awint_internals::{bits_upper_bound, SerdeError, SerdeError::*}, @@ -36,7 +36,7 @@ impl> FP { None } else { this.const_as_mut().zero_(); - this.const_as_mut().usize_or_(1, fp); + this.const_as_mut().digit_or_(1, fp); Some(()) } } @@ -74,9 +74,7 @@ impl> FP { let diff = lbb.0.abs_diff(rbb.0); // the fielding will start from 0 in one argument and end at `diff` in the other let (to, from) = if lbb.0 < rbb.0 { (diff, 0) } else { (0, diff) }; - this.const_as_mut() - .field(to, rhs.const_as_ref(), from, width) - .unwrap(); + this.field(to, rhs, from, width).unwrap(); } /// Truncate-assigns `rhs` to `this`. For the unsigned case, logically what @@ -116,9 +114,7 @@ impl> FP { let width = hi.wrapping_sub(lo).wrapping_add(1) as usize; let diff = lbb.0.abs_diff(rbb.0); let (to, from) = if lbb.0 < rbb.0 { (diff, 0) } else { (0, diff) }; - this.const_as_mut() - .field(to, rhs.const_as_ref(), from, width) - .unwrap(); + this.field(to, rhs, from, width).unwrap(); // when testing if a less significant numerical bit is cut off, we need to be // aware that it can be cut off from above even if overlap happens, for // example: @@ -133,12 +129,9 @@ impl> FP { // note overflow cannot happen because of the `rhs.is_zero()` early return and // invariants - let mut lsnb = rhs.const_as_ref().tz() as isize; + let mut lsnb = rhs.tz() as isize; lsnb = lsnb.wrapping_add(rbb.0); - let mut msnb = rhs - .bw() - .wrapping_sub(rhs.const_as_ref().lz()) - .wrapping_sub(1) as isize; + let mut msnb = rhs.bw().wrapping_sub(rhs.lz()).wrapping_sub(1) as isize; msnb = msnb.wrapping_add(rbb.0); ( (lsnb < lbb.0) || (lsnb > lbb.1), @@ -326,10 +319,7 @@ impl> FP { let mut fraction_part = if fraction_part_zero { alloc::vec![b'0'; min_fraction_chars] } else { - let unique_digits = this - .fp_ty() - .unique_min_fraction_digits(usize::from(radix)) - .unwrap(); + let unique_digits = this.fp_ty().unique_min_fraction_digits(radix).unwrap(); let calc_digits = max(unique_digits, min_fraction_chars); let multiplier_bits = bits_upper_bound(calc_digits, radix)?; // avoid needing some calculation by dropping zero bits that have no impact @@ -341,7 +331,7 @@ impl> FP { tmp.field_from(&unsigned, tot_tz as usize, field_bits) .unwrap(); for _ in 0..calc_digits { - tmp.short_cin_mul(0, usize::from(radix)); + tmp.digit_cin_mul_(0, Digit::from(radix)); } let inc = if (tmp.get_digit(calc_fp.checked_sub(1).ok_or(Overflow)?) & 1) == 0 { // round down diff --git a/awint_ext/src/serde.rs b/awint_ext/src/serde.rs index 69159c8e..cc1ad735 100644 --- a/awint_ext/src/serde.rs +++ b/awint_ext/src/serde.rs @@ -16,7 +16,7 @@ impl Serialize for ExtAwi { /// it serializes into a struct named "ExtAwi" with two fields "bw" and /// "bits". "bw" is the bitwidth in decimal, and "bits" are an unsigned /// hexadecimal string equivalent to what would be generated from - /// `ExtAwi::bits_to_string_radix(self.const_as_ref(), false, 16, false, 0)` + /// `ExtAwi::bits_to_string_radix(&self, false, 16, false, 0)` /// /// ``` /// // Example using the `ron` crate. Note that it @@ -34,8 +34,7 @@ impl Serialize for ExtAwi { where S: Serializer, { - let str_buf: &str = - &ExtAwi::bits_to_string_radix(self.const_as_ref(), false, 16, false, 0).unwrap(); + let str_buf: &str = &ExtAwi::bits_to_string_radix(self, false, 16, false, 0).unwrap(); if serializer.is_human_readable() { let mut s = serializer.serialize_struct("ExtAwi", 2)?; s.serialize_field("bw", &self.bw())?; diff --git a/awint_ext/src/strings.rs b/awint_ext/src/strings.rs index ba5763c3..a2005c7a 100644 --- a/awint_ext/src/strings.rs +++ b/awint_ext/src/strings.rs @@ -238,7 +238,7 @@ impl ExtAwi { let mut i_part = ExtAwi::from_bytes_radix(None, integer, radix, tmp_bw)?; // multiply by `radix^f_len` here for _ in 0..f_len { - i_part.const_as_mut().short_cin_mul(0, usize::from(radix)); + i_part.const_as_mut().digit_cin_mul_(0, Digit::from(radix)); } i_part } else { @@ -257,11 +257,11 @@ impl ExtAwi { if exp_sub_f_len < 0 { for _ in 0..exp_sub_f_len.unsigned_abs() { - den.short_cin_mul(0, usize::from(radix)); + den.digit_cin_mul_(0, Digit::from(radix)); } } else { for _ in 0..exp_sub_f_len.unsigned_abs() { - num.short_cin_mul(0, usize::from(radix)); + num.digit_cin_mul_(0, Digit::from(radix)); } } if fp < 0 { diff --git a/awint_internals/Cargo.toml b/awint_internals/Cargo.toml index 2acfccc9..892775d4 100644 --- a/awint_internals/Cargo.toml +++ b/awint_internals/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awint_internals" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" @@ -8,3 +8,11 @@ readme = "README.md" repository = "https://github.com/AaronKutch/awint" description = "Internal utilities for the `awint` system of crates" rust-version = "1.66" + +[features] +# Only zero or one of these should be active +u8_digits = [] +u16_digits = [] +u32_digits = [] +u64_digits = [] +u128_digits = [] diff --git a/awint_internals/src/lib.rs b/awint_internals/src/lib.rs index 767b9888..58eaaa36 100644 --- a/awint_internals/src/lib.rs +++ b/awint_internals/src/lib.rs @@ -19,16 +19,87 @@ mod macros; mod serde_common; +mod widening; use core::num::NonZeroUsize; pub use serde_common::*; - -/// Maximum bitwidth of an inline `Awi` -pub const BITS: usize = usize::BITS as usize; - -/// Maximum value of an inline `Awi` -pub const MAX: usize = usize::MAX; +pub use widening::{dd_division, widen_add, widen_mul_add, widening_mul_add_u128}; + +// If more than one flag is active it will cause an error because two `Digits` +// are defined. However, we have this one duplication check in case of trying to +// use `--all-features`. +#[cfg(all(feature = "u8_digits", feature = "u64_digits"))] +compile_error!( + "Attempted to activate multiple `*_digits` features at the same time. This is likely because \ + `--all-features` was used, which does not work for `awint`." +); + +/// The basic element of the internal slice in `Bits`. This should be a type +/// alias of the unsigned integer of the architecture's registers. On most +/// architectures, this is simply `usize`, however there are cases such as AVR +/// where the pointer size is 16 bits but the register size is 8 bits. If this +/// were not register size, it can incur excessive unrolling or underutilization +/// for every loop in the internals. +#[cfg(not(any( + feature = "u8_digits", + feature = "u16_digits", + feature = "u32_digits", + feature = "u64_digits", + feature = "u128_digits", + target_arch = "avr", +)))] +pub type Digit = usize; +#[cfg(any(feature = "u8_digits", target_arch = "avr"))] +pub type Digit = u8; +#[cfg(feature = "u16_digits")] +pub type Digit = u16; +#[cfg(feature = "u32_digits")] +pub type Digit = u32; +#[cfg(feature = "u64_digits")] +pub type Digit = u64; +#[cfg(feature = "u128_digits")] +pub type Digit = u128; + +/// Signed version of `Digit` +#[cfg(not(any( + feature = "u8_digits", + feature = "u16_digits", + feature = "u32_digits", + feature = "u64_digits", + feature = "u128_digits", + target_arch = "avr", +)))] +pub type IDigit = isize; +#[cfg(any(feature = "u8_digits", target_arch = "avr"))] +pub type IDigit = i8; +#[cfg(feature = "u16_digits")] +pub type IDigit = i16; +#[cfg(feature = "u32_digits")] +pub type IDigit = i32; +#[cfg(feature = "u64_digits")] +pub type IDigit = i64; +#[cfg(feature = "u128_digits")] +pub type IDigit = i128; + +/// Bitwidth of a `Digit` +pub const BITS: usize = Digit::BITS as usize; + +/// Maximum value of a `Digit` +pub const MAX: Digit = Digit::MAX; + +/// Number of bytes in a `Digit` +pub const DIGIT_BYTES: usize = (Digit::BITS / u8::BITS) as usize; + +/// Number of metadata digits +pub const METADATA_DIGITS: usize = if USIZE_BITS >= BITS { + USIZE_BITS / BITS +} else { + 1 +}; + +/// Number of bits in a `usize` +pub const USIZE_BITS: usize = usize::BITS as usize; /// Subset of `awint::awi` pub mod awi { @@ -96,32 +167,38 @@ pub const fn regular_digits(w: NonZeroUsize) -> usize { digits(w).wrapping_add((extra(w) != 0) as usize) } -/// Returns `regular_digits + 1` to account for the bitwidth digit +/// Returns `regular_digits + 1` to account for the metadata digits #[inline] pub const fn raw_digits(w: usize) -> usize { digits_u(w) .wrapping_add((extra_u(w) != 0) as usize) - .wrapping_add(1) + .wrapping_add(METADATA_DIGITS) } /// Checks that the `BW` and `LEN` values are valid for an `InlAwi`. /// /// # Panics /// -/// If `BW == 0`, `LEN < 2`, or the bitwidth is outside the range -/// `(((LEN - 2)*BITS) + 1)..=((LEN - 1)*BITS)` +/// If `BW == 0`, `LEN < METADATA_DIGITS + 1`, or the bitwidth is outside the +/// range `(((LEN - METADATA_DIGITS - 1)*BITS) + 1)..=((LEN - +/// METADATA_DIGITS)*BITS)` pub const fn assert_inlawi_invariants() { if BW == 0 { panic!("Tried to create an `InlAwi` with `BW == 0`") } - if LEN < 2 { - panic!("Tried to create an `InlAwi` with `LEN < 2`") + if LEN < METADATA_DIGITS + 1 { + panic!("Tried to create an `InlAwi` with `LEN < METADATA_DIGITS + 1`") } - if BW <= ((LEN - 2) * BITS) { - panic!("Tried to create an `InlAwi` with `BW <= BITS*(LEN - 2)`") + if BW <= ((LEN - METADATA_DIGITS - 1) * BITS) { + panic!( + "Tried to create an `InlAwi` with `BW <= Digit::BITS*(LEN - METADATA_DIGITS \ + - 1)`" + ) } - if BW > ((LEN - 1) * BITS) { - panic!("Tried to create an `InlAwi` with `BW > BITS*(LEN - 1)`") + if BW > ((LEN - METADATA_DIGITS) * BITS) { + panic!( + "Tried to create an `InlAwi` with `BW > Digit::BITS*(LEN - METADATA_DIGITS)`" + ) } } @@ -132,145 +209,23 @@ pub const fn assert_inlawi_invariants() { /// # Panics /// /// If `raw.len() != LEN`, the bitwidth digit is not equal to `BW`, `BW == 0`, -/// `LEN < 2`, or the bitwidth is outside the range `(((LEN - 2)*BITS) + -/// 1)..=((LEN - 1)*BITS)` -pub const fn assert_inlawi_invariants_slice(raw: &[usize]) { +/// `LEN < METADATA_DIGITS + 1`, or the bitwidth is outside the range +/// `(((LEN - METADATA_DIGITS - 1)*BITS) + 1)..=((LEN - METADATA_DIGITS)*BITS)` +#[allow(clippy::unnecessary_cast)] // if `Digit == usize` clippy fires +pub const fn assert_inlawi_invariants_slice(raw: &[Digit]) { assert_inlawi_invariants::(); if raw.len() != LEN { panic!("`length of raw slice does not equal LEN") } - let w = raw[raw.len() - 1]; + let mut w = 0usize; + const_for!(i in {0..METADATA_DIGITS} { + w |= (raw[i + raw.len() - METADATA_DIGITS] as usize) << (i * BITS); + }); if w != BW { panic!("bitwidth digit does not equal BW") } } -/// Computes x + y + z and returns the widened result as a tuple. -#[inline] -pub const fn widen_add(x: usize, y: usize, z: usize) -> (usize, usize) { - // TODO make sure this is adc on appropriate platforms and also works well on - // RISCV - let (sum, carry0) = x.overflowing_add(y); - let (sum, carry1) = sum.overflowing_add(z); - (sum, (carry0 as usize) + (carry1 as usize)) -} - -macro_rules! widen_mul_add_internal { - ($x:ident, $y:ident, $z:ident; 128 => $other:block $($bits:expr, $uD:ident);*;) => { - match BITS { - $( - $bits => { - let tmp = ($x as $uD).wrapping_mul($y as $uD).wrapping_add($z as $uD); - (tmp as usize, tmp.wrapping_shr($bits) as usize) - } - )* - 128 => $other - _ => panic!("Unsupported pointer size"), - } - }; -} - -pub const fn widening_mul_add_u128(lhs: u128, rhs: u128, add: u128) -> (u128, u128) { - // [rhs_hi] [rhs_lo] - // [lhs_hi] [lhs_lo] - // X___________________ - // [------tmp0------] - // [------tmp1------] - // [------tmp2------] - // [------tmp3------] - // [-------add------] - // +_______________________________________ - // [------sum0------] - // [------sum1------] - - let lhs_lo = lhs as u64; - let rhs_lo = rhs as u64; - let lhs_hi = (lhs.wrapping_shr(64)) as u64; - let rhs_hi = (rhs.wrapping_shr(64)) as u64; - let tmp0 = (lhs_lo as u128).wrapping_mul(rhs_lo as u128); - let tmp1 = (lhs_lo as u128).wrapping_mul(rhs_hi as u128); - let tmp2 = (lhs_hi as u128).wrapping_mul(rhs_lo as u128); - let tmp3 = (lhs_hi as u128).wrapping_mul(rhs_hi as u128); - // tmp1 and tmp2 straddle the boundary. We have to handle three carries - let (sum0, carry0) = tmp0.overflowing_add(tmp1.wrapping_shl(64)); - let (sum0, carry1) = sum0.overflowing_add(tmp2.wrapping_shl(64)); - let (sum0, carry2) = sum0.overflowing_add(add); - let sum1 = tmp3 - .wrapping_add(tmp1.wrapping_shr(64)) - .wrapping_add(tmp2.wrapping_shr(64)) - .wrapping_add(carry0 as u128) - .wrapping_add(carry1 as u128) - .wrapping_add(carry2 as u128); - (sum0, sum1) -} - -/// Computes (x * y) + z. This cannot overflow, because it returns the value -/// widened into a tuple, where the first element is the least significant part -/// of the integer and the second is the most significant. -#[inline] -pub const fn widen_mul_add(x: usize, y: usize, z: usize) -> (usize, usize) { - widen_mul_add_internal!( - x, y, z; - 128 => { - // Hopefully Rust has a built in `widening_mul` or LLVM recognizes this is one - // big widening multiplication by the time things like 128 bit RISCV are a - // thing. - let tmp = widening_mul_add_u128(x as u128, y as u128, z as u128); - (tmp.0 as usize, tmp.1 as usize) - } - 8, u16; - 16, u32; - 32, u64; - 64, u128; - ) -} - -macro_rules! dd_division_internal { - ($duo:ident, $div:ident; $($bits:expr, $uD:ident);*;) => { - match BITS { - $( - $bits => { - let duo = $duo.0 as $uD | (($duo.1 as $uD) << $bits); - let div = $div.0 as $uD | (($div.1 as $uD) << $bits); - let tmp0 = duo.wrapping_div(div); - let tmp1 = duo.wrapping_rem(div); - ( - ( - tmp0 as usize, - (tmp0 >> $bits) as usize, - ), - ( - tmp1 as usize, - (tmp1 >> $bits) as usize, - ) - ) - } - )* - _ => panic!("Unsupported pointer size"), - } - }; -} - -/// Divides `duo` by `div` and returns the quotient and remainder. -/// -/// # Panics -/// -/// If `div == 0`, this function will panic. -#[inline] -pub const fn dd_division( - duo: (usize, usize), - div: (usize, usize), -) -> ((usize, usize), (usize, usize)) { - dd_division_internal!( - duo, div; - 8, u16; - 16, u32; - 32, u64; - 64, u128; - // TODO fix this for 128 bits - ) -} - /// Location for an item in the source code #[derive(Debug, Clone, Copy)] pub struct Location { diff --git a/awint_internals/src/macros.rs b/awint_internals/src/macros.rs index 9e3f9f63..f7e1df6e 100644 --- a/awint_internals/src/macros.rs +++ b/awint_internals/src/macros.rs @@ -177,23 +177,54 @@ macro_rules! subdigits_mut { // Safety: This maintains the metadata raw invariants of `Bits`. This works even // if the range is a full range. The range is nonempty. - // We temporarily replace the digit needed for the subslice bitwidth - // digit. - let tmp = $bits.as_ptr().add($range.end).read(); - *$bits.raw_get_unchecked_mut($range.end) = - ($range.end - $range.start) * (usize::BITS as usize); - let $subbits: &mut Bits = Bits::from_raw_parts_mut( - $bits.as_mut_ptr().add($range.start), - ($range.end - $range.start) + 1, - ); - // then run the "closure" on the fixed subslice - $f - // make sure that the reference is not used again - #[allow(unused_variables)] - let $subbits = (); - // restore the subslice's bitwidth digit to whatever kind of digit it was in the - // original slice - *$bits.raw_get_unchecked_mut($range.end) = tmp; + // when saving the metadata, we need to handle the edge case where `USIZE_BITS < BITS` + if $crate::USIZE_BITS >= $crate::BITS { + // We temporarily replace the digits needed for the subslice metadata + let mut original = 0usize; + const_for!(i in {0..$crate::METADATA_DIGITS} { + original |= + ($bits.as_ptr().add(i + $range.end).read() as usize) << (i * $crate::BITS); + }); + // new metadata for the number of bits in our subdigits + let mut metadata: usize = ($range.end - $range.start) * $crate::BITS; + const_for!(i in {0..$crate::METADATA_DIGITS} { + *$bits.raw_get_unchecked_mut(i + $range.end) = + (metadata >> (i * $crate::BITS)) as $crate::Digit; + }); + let $subbits: &mut Bits = Bits::from_raw_parts_mut( + $bits.as_mut_ptr().add($range.start), + ($range.end - $range.start) + $crate::METADATA_DIGITS, + ); + // then run the "closure" on the fixed subslice + $f + // make sure that the reference is not used again + #[allow(unused_variables)] + let $subbits = (); + // restore the subslice's bitwidth digit to whatever kind of digit it was in the + // original slice + const_for!(i in {0..$crate::METADATA_DIGITS} { + *$bits.raw_get_unchecked_mut(i + $range.end) = + (original >> (i * $crate::BITS)) as $crate::Digit; + }); + } else { + // We temporarily replace the digits needed for the subslice metadata + let mut original: $crate::Digit = $bits.as_ptr().add($range.end).read(); + // new metadata for the number of bits in our subdigits + let mut metadata = (($range.end - $range.start) * $crate::BITS) as $crate::Digit; + *$bits.raw_get_unchecked_mut($range.end) = metadata; + let $subbits: &mut Bits = Bits::from_raw_parts_mut( + $bits.as_mut_ptr().add($range.start), + ($range.end - $range.start) + $crate::METADATA_DIGITS, + ); + // then run the "closure" on the fixed subslice + $f + // make sure that the reference is not used again + #[allow(unused_variables)] + let $subbits = (); + // restore the subslice's bitwidth digit to whatever kind of digit it was in the + // original slice + *$bits.raw_get_unchecked_mut($range.end) = original; + } } } } diff --git a/awint_internals/src/serde_common.rs b/awint_internals/src/serde_common.rs index aa814ee9..c49564b8 100644 --- a/awint_internals/src/serde_common.rs +++ b/awint_internals/src/serde_common.rs @@ -2,6 +2,8 @@ use core::fmt; +use crate::USIZE_BITS; + // The reason this is here is because I need the free functions in `awint_core` // for speeding up certain serialization tasks, but the free functions also need // `SerdeError` to make them more ergonomic in `awint_ext`. @@ -51,8 +53,6 @@ impl fmt::Display for SerdeError { use SerdeError::*; -use crate::BITS; - /// Binary logarithms of the integers 2..=36 rounded up and in u16p13 fixed /// point format pub const LB_I3F13: [u16; 37] = [ @@ -114,9 +114,9 @@ pub const fn bits_upper_bound(len: usize, radix: u8) -> Result> 13).wrapping_add(1) as usize; - if estimate & (1 << (BITS - 1)) == 0 { - return Ok(estimate) + let estimate = (tmp >> 13).wrapping_add(1); + if (estimate & (!((1u128 << (USIZE_BITS - 1)) - 1))) == 0 { + return Ok(estimate as usize) } } Err(Overflow) @@ -132,10 +132,10 @@ pub const fn chars_upper_bound(significant_bits: usize, radix: u8) -> Result> 15).wrapping_add(1) as usize; - // check that it would fit in an `isize` - if estimate & (1 << (BITS - 1)) == 0 { - return Ok(estimate) + let estimate = (tmp >> 15).wrapping_add(1); + // check that it would fit within `isize::MAX` + if (estimate & (!((1u128 << (USIZE_BITS - 1)) - 1))) == 0 { + return Ok(estimate as usize) } } Err(Overflow) diff --git a/awint_internals/src/widening.rs b/awint_internals/src/widening.rs new file mode 100644 index 00000000..17ac643a --- /dev/null +++ b/awint_internals/src/widening.rs @@ -0,0 +1,358 @@ +use crate::{Digit, BITS}; + +/// Computes x + y + z and returns the widened result as a tuple. +#[inline] +pub const fn widen_add(x: Digit, y: Digit, z: Digit) -> (Digit, Digit) { + // TODO make sure this is adc on appropriate platforms and also works well on + // RISCV + let (sum, carry0) = x.overflowing_add(y); + let (sum, carry1) = sum.overflowing_add(z); + (sum, (carry0 as Digit) + (carry1 as Digit)) +} + +macro_rules! widen_mul_add_internal { + ($x:ident, $y:ident, $z:ident; 128 => $other:block $($bits:expr, $uD:ident);*;) => { + match BITS { + $( + $bits => { + let tmp = ($x as $uD).wrapping_mul($y as $uD).wrapping_add($z as $uD); + (tmp as Digit, tmp.wrapping_shr($bits) as Digit) + } + )* + 128 => $other + _ => panic!("Unsupported digit size"), + } + }; +} + +pub const fn widening_mul_add_u128(lhs: u128, rhs: u128, add: u128) -> (u128, u128) { + // [rhs_hi] [rhs_lo] + // [lhs_hi] [lhs_lo] + // X___________________ + // [------tmp0------] + // [------tmp1------] + // [------tmp2------] + // [------tmp3------] + // [-------add------] + // +_______________________________________ + // [------sum0------] + // [------sum1------] + + let lhs_lo = lhs as u64; + let rhs_lo = rhs as u64; + let lhs_hi = (lhs.wrapping_shr(64)) as u64; + let rhs_hi = (rhs.wrapping_shr(64)) as u64; + let tmp0 = (lhs_lo as u128).wrapping_mul(rhs_lo as u128); + let tmp1 = (lhs_lo as u128).wrapping_mul(rhs_hi as u128); + let tmp2 = (lhs_hi as u128).wrapping_mul(rhs_lo as u128); + let tmp3 = (lhs_hi as u128).wrapping_mul(rhs_hi as u128); + // tmp1 and tmp2 straddle the boundary. We have to handle three carries + let (sum0, carry0) = tmp0.overflowing_add(tmp1.wrapping_shl(64)); + let (sum0, carry1) = sum0.overflowing_add(tmp2.wrapping_shl(64)); + let (sum0, carry2) = sum0.overflowing_add(add); + let sum1 = tmp3 + .wrapping_add(tmp1.wrapping_shr(64)) + .wrapping_add(tmp2.wrapping_shr(64)) + .wrapping_add(carry0 as u128) + .wrapping_add(carry1 as u128) + .wrapping_add(carry2 as u128); + (sum0, sum1) +} + +/// Computes (x * y) + z. This cannot overflow, because it returns the value +/// widened into a tuple, where the first element is the least significant part +/// of the integer and the second is the most significant. +#[inline] +pub const fn widen_mul_add(x: Digit, y: Digit, z: Digit) -> (Digit, Digit) { + widen_mul_add_internal!( + x, y, z; + 128 => { + // Hopefully Rust has a built in `widening_mul` or LLVM recognizes this is one + // big widening multiplication by the time things like 128 bit RISCV are a + // thing. + let tmp = widening_mul_add_u128(x as u128, y as u128, z as u128); + (tmp.0 as Digit, tmp.1 as Digit) + } + 8, u16; + 16, u32; + 32, u64; + 64, u128; + ) +} + +type U256 = (u128, u128); + +/// Computes the quotient and remainder of `duo` divided by `div` and returns +/// them as a tuple. +pub const fn dd_division_u256(duo: U256, div: U256) -> (U256, U256) { + // uses the trifecta algorithm from https://crates.io/crates/triple_arena + + const fn lz_u256(x: U256) -> usize { + let res = x.1.leading_zeros() as usize; + if res == 128 { + 128 + (x.0.leading_zeros() as usize) + } else { + res + } + } + + const fn is_zero_u256(x: U256) -> bool { + (x.0 == 0) && (x.1 == 0) + } + + const fn add_u256(lhs: U256, rhs: U256) -> U256 { + let (res0, o) = lhs.0.overflowing_add(rhs.0); + let res1 = lhs.1.wrapping_add(rhs.1).wrapping_add(o as u128); + (res0, res1) + } + + const fn sub_u256(lhs: U256, rhs: U256) -> U256 { + let (res0, o) = lhs.0.overflowing_sub(rhs.0); + let res1 = lhs.1.wrapping_sub(rhs.1).wrapping_sub(o as u128); + (res0, res1) + } + + const fn ge_u256(lhs: U256, rhs: U256) -> bool { + if lhs.1 > rhs.1 { + true + } else if lhs.1 == rhs.1 { + lhs.0 >= rhs.0 + } else { + false + } + } + + const fn lt_u256(lhs: U256, rhs: U256) -> bool { + if lhs.1 < rhs.1 { + true + } else if lhs.1 == rhs.1 { + lhs.0 < rhs.0 + } else { + false + } + } + + const fn or_u256(lhs: U256, rhs: U256) -> U256 { + (lhs.0 | rhs.0, lhs.1 | rhs.1) + } + + const fn shl_u256(x: U256, s: usize) -> U256 { + if s == 0 { + x + } else if s < 128 { + (x.0 << s, (x.0 >> (128 - s)) | (x.1 << s)) + } else { + (0, x.1 << (s - 128)) + } + } + + const fn shr_u256(x: U256, s: usize) -> U256 { + if s == 0 { + x + } else if s < 128 { + ((x.0 >> s) | (x.1 << (128 - s)), x.1 >> s) + } else { + (x.1 >> (s - 128), 0) + } + } + + const fn half_division(lhs: u128, rhs: u128) -> (u128, u128) { + (lhs / rhs, lhs % rhs) + } + + const fn u256_mul_u128(lhs: U256, rhs: u128) -> U256 { + let (lo, carry) = widening_mul_add_u128(lhs.0, rhs, 0); + let (hi, _) = widening_mul_add_u128(lhs.1, rhs, carry); + (lo, hi) + } + + let one_u256: U256 = (1, 0); + let zero_u256: U256 = (0, 0); + + // the number of bits in a $uX + let n = 128; + + if is_zero_u256(div) { + unreachable!(); + } + + let div_lz = lz_u256(div); + let mut duo_lz = lz_u256(duo); + + // quotient is 0 or 1 branch + if div_lz <= duo_lz { + if ge_u256(duo, div) { + return (one_u256, sub_u256(duo, div)) + } else { + return (zero_u256, duo) + } + } + + // smaller division branch + if duo_lz >= n { + let (quo, rem) = half_division(duo.0, div.0); + return ((quo, 0), (rem, 0)) + } + + // short division branch + if div_lz >= (128 + 64) { + let duo_hi = duo.1; + let div_0 = div.0 as u64 as u128; + let (quo_hi, rem_3) = half_division(duo_hi, div_0); + + let duo_mid = ((duo.0 >> 64) as u64 as u128) | (rem_3 << 64); + let (quo_1, rem_2) = half_division(duo_mid, div_0); + + let duo_lo = (duo.0 as u64 as u128) | (rem_2 << 64); + let (quo_0, rem_1) = half_division(duo_lo, div_0); + + return ( + or_u256(or_u256((quo_0, 0), (quo_1 << 64, quo_1 >> 64)), (0, quo_hi)), + (rem_1, 0), + ) + } + + let lz_diff = div_lz - duo_lz; + + if lz_diff < 64 { + // Two possibility division algorithm + + let shift = n - duo_lz; + let duo_sig_n = shr_u256(duo, shift).0; + let div_sig_n = shr_u256(div, shift).0; + let quo = half_division(duo_sig_n, div_sig_n).0; + + let div_lo = div.0; + let div_hi = div.1; + let (tmp_lo, carry) = widening_mul_add_u128(quo, div_lo, 0); + let (tmp_hi, overflow) = widening_mul_add_u128(quo, div_hi, carry); + let tmp = (tmp_lo, tmp_hi); + if (overflow != 0) || lt_u256(duo, tmp) { + return ((quo - 1, 0), sub_u256(add_u256(duo, div), tmp)) + } else { + return ((quo, 0), sub_u256(duo, tmp)) + } + } + + // Undersubtracting long division algorithm. + + let mut duo = duo; + let mut quo = zero_u256; + + let div_extra = (128 + 64) - div_lz; + + let div_sig_n_h = shr_u256(div, div_extra).0 as u64; + + let div_sig_n_h_add1 = (div_sig_n_h as u128) + 1; + + loop { + let duo_extra = n - duo_lz; + + let duo_sig_n = shr_u256(duo, duo_extra).0; + + if div_extra <= duo_extra { + // Undersubtracting long division step + let quo_part_lo = half_division(duo_sig_n, div_sig_n_h_add1).0; + let extra_shl = duo_extra - div_extra; + + quo = add_u256(quo, shl_u256((quo_part_lo, 0), extra_shl)); + + duo = sub_u256(duo, shl_u256(u256_mul_u128(div, quo_part_lo), extra_shl)); + } else { + // Two possibility algorithm + let shift = n - duo_lz; + let duo_sig_n = shr_u256(duo, shift).0; + let div_sig_n = shr_u256(div, shift).0; + let quo_part = half_division(duo_sig_n, div_sig_n).0; + let div_lo = div.0; + let div_hi = div.1; + + let (tmp_lo, carry) = widening_mul_add_u128(quo_part, div_lo, 0); + // The undersubtracting long division algorithm has already run once, so + // overflow beyond `$uD` bits is not possible here + let (tmp_hi, _) = widening_mul_add_u128(quo_part, div_hi, carry); + let tmp = (tmp_lo, tmp_hi); + + if lt_u256(duo, tmp) { + return ( + add_u256(quo, (quo_part - 1, 0)), + sub_u256(add_u256(duo, div), tmp), + ) + } else { + return (add_u256(quo, (quo_part, 0)), sub_u256(duo, tmp)) + } + } + + duo_lz = lz_u256(duo); + + if div_lz <= duo_lz { + // quotient can have 0 or 1 added to it + if ge_u256(duo, div) { + return (add_u256(quo, one_u256), sub_u256(duo, div)) + } else { + return (quo, duo) + } + } + + // This can only happen if `div_sd < n` (because of previous "quo = 0 or 1" + // branches), but it is not worth it to unroll further. + if n <= duo_lz { + // simple division and addition + let tmp = half_division(duo.0, div.0); + return (add_u256(quo, (tmp.0, 0)), (tmp.1, 0)) + } + } +} + +macro_rules! dd_division_internal { + ($duo:ident, $div:ident; 128 => $other:block $($bits:expr, $uD:ident);*;) => { + match BITS { + $( + $bits => { + let duo = $duo.0 as $uD | (($duo.1 as $uD) << $bits); + let div = $div.0 as $uD | (($div.1 as $uD) << $bits); + let tmp0 = duo.wrapping_div(div); + let tmp1 = duo.wrapping_rem(div); + ( + ( + tmp0 as Digit, + (tmp0 >> $bits) as Digit, + ), + ( + tmp1 as Digit, + (tmp1 >> $bits) as Digit, + ) + ) + } + )* + 128 => $other + _ => panic!("Unsupported digit size"), + } + }; +} + +/// Divides `duo` by `div` and returns the quotient and remainder. +/// +/// # Panics +/// +/// If `div == 0`, this function will panic. +#[inline] +pub const fn dd_division( + duo: (Digit, Digit), + div: (Digit, Digit), +) -> ((Digit, Digit), (Digit, Digit)) { + dd_division_internal!( + duo, div; + 128 => { + let (quo, rem) = dd_division_u256( + (duo.0 as u128, duo.1 as u128), + (div.0 as u128, div.1 as u128) + ); + ((quo.0 as Digit, quo.1 as Digit), (rem.0 as Digit, rem.1 as Digit)) + } + 8, u16; + 16, u32; + 32, u64; + 64, u128; + ) +} diff --git a/awint_macro_internals/Cargo.toml b/awint_macro_internals/Cargo.toml index 034108db..0805f112 100644 --- a/awint_macro_internals/Cargo.toml +++ b/awint_macro_internals/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awint_macro_internals" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" @@ -9,7 +9,7 @@ repository = "https://github.com/AaronKutch/awint" description = "Internal macro utilities for the `awint` system of crates" [dependencies] -awint_ext = { version = "0.9.0", path = "../awint_ext", default-features = false } +awint_ext = { version = "0.10.0", path = "../awint_ext", default-features = false } proc-macro2 = "1.0" triple_arena = "0.7" triple_arena_render = { version = "0.7", optional = true } diff --git a/awint_macro_internals/src/cc_macro.rs b/awint_macro_internals/src/cc_macro.rs index c19d54b1..4aa6a0bc 100644 --- a/awint_macro_internals/src/cc_macro.rs +++ b/awint_macro_internals/src/cc_macro.rs @@ -14,10 +14,12 @@ pub fn cc_macro< F0: FnMut(&str) -> String, // we run into lifetime generalization issues when trying `&Bits` F1: FnMut(ExtAwi) -> String, - F2: FnMut(&str, Option, Option<&str>) -> String, + F2: FnMut(ExtAwi) -> String, + F3: FnMut(&str, Option, Option<&str>) -> String, + F4: FnMut(String, Option, bool) -> String, >( input: &str, - code_gen: CodeGen<'_, F0, F1, F2>, + code_gen: CodeGen<'_, F0, F1, F2, F3, F4>, names: Names, ) -> Result { // we process in stages to handle more fundamental errors first, reducing bugs diff --git a/awint_macro_internals/src/component.rs b/awint_macro_internals/src/component.rs index 9e31a8a1..4d15d2dc 100644 --- a/awint_macro_internals/src/component.rs +++ b/awint_macro_internals/src/component.rs @@ -41,7 +41,7 @@ impl Component { Literal(ref mut lit) => { self.range.simplify()?; // note: `lit` here is a reference to a clone - self.range.simplify_literal(lit.const_as_ref())?; + self.range.simplify_literal(lit)?; // static ranges on literals have been verified, attempt to truncate if let Some(ref end) = self.range.end { if let Some(x) = end.static_val() { diff --git a/awint_macro_internals/src/lib.rs b/awint_macro_internals/src/lib.rs index c7a43fca..9971b777 100644 --- a/awint_macro_internals/src/lib.rs +++ b/awint_macro_internals/src/lib.rs @@ -45,8 +45,10 @@ pub fn awint_macro_cc(input: &str) -> Result { static_width: false, return_type: None, must_use: awint_must_use, - lit_construction_fn: awint_lit_construction_fn, + static_construction_fn: awint_static_construction_fn, + lit_construction_fn: awint_unreachable_construction_fn, construction_fn: cc_construction_fn, + const_wrapper: identity_const_wrapper, fn_names: AWINT_FN_NAMES, }; cc_macro(input, code_gen, AWINT_NAMES) @@ -57,8 +59,10 @@ pub fn awint_macro_inlawi(input: &str) -> Result { static_width: true, return_type: Some("InlAwi"), must_use: awint_must_use, - lit_construction_fn: awint_lit_construction_fn, + static_construction_fn: awint_static_construction_fn, + lit_construction_fn: awint_inlawi_lit_construction_fn, construction_fn: inlawi_construction_fn, + const_wrapper: identity_const_wrapper, fn_names: AWINT_FN_NAMES, }; cc_macro(input, code_gen, AWINT_NAMES) @@ -69,8 +73,24 @@ pub fn awint_macro_extawi(input: &str) -> Result { static_width: false, return_type: Some("ExtAwi"), must_use: awint_must_use, + static_construction_fn: awint_static_construction_fn, lit_construction_fn: awint_extawi_lit_construction_fn, construction_fn: extawi_construction_fn, + const_wrapper: identity_const_wrapper, + fn_names: AWINT_FN_NAMES, + }; + cc_macro(input, code_gen, AWINT_NAMES) +} + +pub fn awint_macro_bits(input: &str) -> Result { + let code_gen = CodeGen { + static_width: true, + return_type: Some("&'static Bits"), + must_use: awint_must_use, + static_construction_fn: awint_static_construction_fn, + lit_construction_fn: awint_bits_lit_construction_fn, + construction_fn: inlawi_construction_fn, + const_wrapper: awint_bits_const_wrapper, fn_names: AWINT_FN_NAMES, }; cc_macro(input, code_gen, AWINT_NAMES) diff --git a/awint_macro_internals/src/lower_structs.rs b/awint_macro_internals/src/lower_structs.rs index defdac69..7c27bfb6 100644 --- a/awint_macro_internals/src/lower_structs.rs +++ b/awint_macro_internals/src/lower_structs.rs @@ -683,7 +683,7 @@ impl<'a> Lower<'a> { pub fn lower_bindings String>( &mut self, - mut lit_construction_fn: F, + mut static_construction_fn: F, ) -> String { let mut s = String::new(); for (p_b, (bind, (used, mutable))) in self.binds.arena() { @@ -696,7 +696,7 @@ impl<'a> Lower<'a> { self.names.bind, p_b.inx(), self.fn_names.bits_ref, - (lit_construction_fn)(ExtAwi::from_bits(awi)) + (static_construction_fn)(ExtAwi::from_bits(awi)) ) .unwrap(); } diff --git a/awint_macro_internals/src/lowering.rs b/awint_macro_internals/src/lowering.rs index aa0ab4ec..62cbdb41 100644 --- a/awint_macro_internals/src/lowering.rs +++ b/awint_macro_internals/src/lowering.rs @@ -66,10 +66,12 @@ use crate::{chars_to_string, Ast, Bind, CodeGen, ComponentType::*, EitherResult, pub fn cc_macro_code_gen< F0: FnMut(&str) -> String, F1: FnMut(ExtAwi) -> String, - F2: FnMut(&str, Option, Option<&str>) -> String, + F2: FnMut(ExtAwi) -> String, + F3: FnMut(&str, Option, Option<&str>) -> String, + F4: FnMut(String, Option, bool) -> String, >( mut ast: Ast, - mut code_gen: CodeGen<'_, F0, F1, F2>, + mut code_gen: CodeGen<'_, F0, F1, F2, F3, F4>, names: Names, ) -> String { let is_returning = code_gen.return_type.is_some(); @@ -318,9 +320,10 @@ pub fn cc_macro_code_gen< let cws = l.lower_cws(); let widths = l.lower_widths(); let values = l.lower_values(); - let bindings = l.lower_bindings(code_gen.lit_construction_fn); + let bindings = l.lower_bindings(code_gen.static_construction_fn); let inner = format!("{{\n{bindings}{values}{widths}{cws}{common_cw}{inner}}}"); + let inner = (code_gen.const_wrapper)(inner, ast.common_bw, infallible); if wrap_must_use { (code_gen.must_use)(&inner) } else { diff --git a/awint_macro_internals/src/misc.rs b/awint_macro_internals/src/misc.rs index 619cb52a..42058664 100644 --- a/awint_macro_internals/src/misc.rs +++ b/awint_macro_internals/src/misc.rs @@ -30,6 +30,16 @@ pub fn chars_to_string(chars: &[char]) -> String { s } +pub fn awint_must_use(s: &str) -> String { + format!("Bits::must_use({s})") +} + +/// Returns architecture-independent Rust code that returns an +/// `InlAwi` type with bitwidth `w`. +pub fn unstable_native_inlawi_ty(w: u128) -> String { + format!("InlAwi::<{w},{{Bits::unstable_raw_digits({w})}}>") +} + /// Returns architecture-independent Rust code that returns an /// `InlAwi` preset with the value of `bits`. pub fn unstable_native_inlawi(bits: &Bits) -> String { @@ -46,25 +56,54 @@ pub fn unstable_native_inlawi(bits: &Bits) -> String { // potentially be used on multiple architectures). `unstable_raw_digits` adjusts // `LEN` based on the native `usize` width, and `unstable_from_u8_slice` also // adjusts for big endian archiectures. + + // note: it might be optimal in certain cases to make the `InlAwi` itself const, + // this would lead to being able to directly memcpy it. However, if we make the + // &[u8] level const then it allows for certain unsigned constants to be shared + // in .text even if in different sized integers and makes them smaller for + // things with lots of leading zeros. Also, we can't make the `InlAwi` level + // const anyway because of `awint_dag`. format!( - "InlAwi::<{},{{Bits::unstable_raw_digits({})}}>::unstable_from_u8_slice(&{:?})", + "InlAwi::<{},{{Bits::unstable_raw_digits({})}}>::unstable_from_u8_slice({{const B: \ + &[core::primitive::u8] = &{:?}; B}})", bits.bw(), bits.bw(), buf, ) } -/// Returns architecture-independent Rust code that returns an -/// `InlAwi` type with bitwidth `w`. -pub fn unstable_native_inlawi_ty(w: u128) -> String { - format!("InlAwi::<{w},{{Bits::unstable_raw_digits({w})}}>") +// there is some strange issue with feature flags through proc-macro crates that +// prevents this from working +/*#[cfg(not(feature = "const_support"))] +pub fn unstable_native_bits(bits: &Bits) -> String { + format!("{{let b: &Bits = &{}; b}}", unstable_native_inlawi(bits)) +}*/ + +// Returns architecture-independent Rust code that returns a `&'static Bits` +// equal to `bits`. +//#[cfg(feature = "const_support")] +pub fn unstable_native_bits(bits: &Bits) -> String { + format!("{{const B: &Bits = &{}; B}}", unstable_native_inlawi(bits)) } -pub fn awint_must_use(s: &str) -> String { - format!("Bits::must_use({s})") +// Originally, this was going to use `unstable_native_bits`, however: +// +// - This will currently require virtually all crates to use +// `#![feature(const_trait_impl)]` and some others unconditionally +// - If the same constant is used several times but with different leading zeros +// or other surrounding data, it impacts .text reusability +// - More generated Rust code +// +// `unstable_native_bits` is now just used for `awint_bits_lit_construction_fn` +pub fn awint_static_construction_fn(awi: ExtAwi) -> String { + unstable_native_inlawi(&awi) +} + +pub fn awint_unreachable_construction_fn(_awi: ExtAwi) -> String { + unreachable!() } -pub fn awint_lit_construction_fn(awi: ExtAwi) -> String { +pub fn awint_inlawi_lit_construction_fn(awi: ExtAwi) -> String { unstable_native_inlawi(&awi) } @@ -72,6 +111,10 @@ pub fn awint_extawi_lit_construction_fn(awi: ExtAwi) -> String { format!("ExtAwi::from_bits(&{})", unstable_native_inlawi(&awi)) } +pub fn awint_bits_lit_construction_fn(awi: ExtAwi) -> String { + unstable_native_bits(&awi) +} + pub fn extawi_s(init: &str, s: &str) -> String { format!("ExtAwi::panicking_{init}({s})") } @@ -126,3 +169,39 @@ pub fn extawi_construction_fn( unreachable!() } } + +pub fn identity_const_wrapper( + inner: String, + _w: Option, + _infallible: bool, +) -> String { + inner +} + +pub fn awint_bits_const_wrapper( + inner: String, + w: Option, + infallible: bool, +) -> String { + let w = if let Some(w) = w { + w + } else { + // this should only be used in a static width context + unreachable!() + }; + if infallible { + format!( + "{{const __B:{}={};\nconst __C:&Bits=&__B;__C}}", + unstable_native_inlawi_ty(w.get() as u128), + inner + ) + } else { + // the match is to avoid bringing in `const_option_ext` + format!( + "{{const __B:Option<{}>={};\nconst __C:Option<&Bits>=match __B {{Some(ref \ + b)=>Some(b),None=>None}};__C}}", + unstable_native_inlawi_ty(w.get() as u128), + inner + ) + } +} diff --git a/awint_macro_internals/src/names.rs b/awint_macro_internals/src/names.rs index 70b2fabd..3670335d 100644 --- a/awint_macro_internals/src/names.rs +++ b/awint_macro_internals/src/names.rs @@ -85,21 +85,29 @@ pub const AWINT_FN_NAMES: FnNames = FnNames { /// - `static_width`: if the type needs a statically known width /// - `return_type`: if the bits need to be returned /// - `must_use`: wraps return values in a function for insuring `#[must_use]` -/// - `lit_construction_fn`: construction function for known literals +/// - `static_construction_fn`: construction for internal constants +/// - `lit_construction_fn`: construction function for known literals of the +/// return type /// - `construction_fn`: is input the specified initialization, width if it is /// statically known, and dynamic width if known. As a special case, the /// initialization is empty for when initialization doesn't matter +/// - `const_wrapper`: used for the `bits` macro, pass the `String` straight +/// through otherwise pub struct CodeGen< 'a, F0: FnMut(&str) -> String, // I run into weird lifetime issues trying to use &Bits F1: FnMut(ExtAwi) -> String, - F2: FnMut(&str, Option, Option<&str>) -> String, + F2: FnMut(ExtAwi) -> String, + F3: FnMut(&str, Option, Option<&str>) -> String, + F4: FnMut(String, Option, bool) -> String, > { pub static_width: bool, pub return_type: Option<&'a str>, pub must_use: F0, - pub lit_construction_fn: F1, - pub construction_fn: F2, + pub static_construction_fn: F1, + pub lit_construction_fn: F2, + pub construction_fn: F3, + pub const_wrapper: F4, pub fn_names: FnNames<'a>, } diff --git a/awint_macros/Cargo.toml b/awint_macros/Cargo.toml index 3b0a57d8..87fb3ec6 100644 --- a/awint_macros/Cargo.toml +++ b/awint_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awint_macros" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" @@ -15,8 +15,8 @@ categories = ["data-structures", "mathematics", "algorithms"] proc-macro = true [dependencies] -awint_internals = { version = "0.9.0", path = "../awint_internals", default-features = false } -awint_macro_internals = { version = "0.9.0", path = "../awint_macro_internals" } +awint_internals = { version = "0.10.0", path = "../awint_internals", default-features = false } +awint_macro_internals = { version = "0.10.0", path = "../awint_macro_internals" } [dev-dependencies] # for examples diff --git a/awint_macros/src/lib.rs b/awint_macros/src/lib.rs index 9a7e5ab3..5459cb35 100644 --- a/awint_macros/src/lib.rs +++ b/awint_macros/src/lib.rs @@ -247,12 +247,12 @@ //! // a single variable component. //! cc!(source; a; b; c; d).unwrap(); //! -//! assert_eq!(a, inlawi!(0xc4di64).const_as_ref()); +//! assert_eq!(a, inlawi!(0xc4di64).as_ref()); //! assert_eq!(b, extawi!(0xc4di64)); //! assert_eq!(c, inlawi!(0xc4di64)); //! //! let awi = inlawi!(0xau4); -//! let a = awi.const_as_ref(); +//! let a = awi.as_ref(); //! let b = extawi!(0xbu4); //! let c = inlawi!(0xcu4); //! @@ -347,7 +347,7 @@ //! ``` //! //! Also, if you are using the `const_support` feature and are trying to use -//! the macros in a `const` context, as of Rust 1.63 you need to add all of +//! the macros in a `const` context, as of Rust 1.66 you need to add all of //! ```text //! #![feature(const_mut_refs)] //! #![feature(const_option)] @@ -356,6 +356,26 @@ //! to your crate roots or else you will encounter many confusing or misleading //! compiler errors. //! +//! ``` +//! #![feature(const_mut_refs)] +//! #![feature(const_option)] +//! #![feature(const_trait_impl)] +//! #![feature(inline_const)] +//! use awint::awi::*; +//! // `bits!` is mainly used to create single large `&'static Bits` constants, +//! // but the source concatenation capability is there. +//! const A: &Bits = bits!(umax: ..32, 0xfedcba98_u32); +//! const B: &Bits = bits!(0x3210u16); +//! const C: &Bits = bits!(A, 0x7654u16, B; ..96).unwrap(); +//! assert_eq!(C, bits!(0xffffffff_fedcba98_76543210_u96)); +//! // there is _a lot_ of stuff going on behind the scenes to make this possible +//! const D: &Bits = const { +//! const R: usize = 48; +//! bits!(C[(R-42)..R], C[R..(R+42)]).unwrap() +//! }; +//! assert_eq!(D, bits!(0xba987_654323ff_fffffedc_u84)); +//! ``` +//! //! ### Fillers //! //! The third type of component is written as a range by itself. When used in @@ -644,10 +664,10 @@ //! bitfields independently to a buffer, then field from the buffer to the //! sink components. When concatenations take the form `variable or constant //! with full range; var_1[..]; var_2[..]; var_3[..], ...`, the macros use -//! `Bits::copy_` to directly copy without an intermediate buffer. This -//! copy assigning mode cannot copy between `Bits` references that point to -//! the same underlying storage, because it results in aliasing. Thus, trying -//! to do something like `cc!(x; x)` results in the borrow checker complaining +//! `Bits::copy_` to directly copy without an intermediate buffer. This copy +//! assigning mode cannot copy between `Bits` references that point to the +//! same underlying storage, because it results in aliasing. Thus, trying to +//! do something like `cc!(x; x)` results in the borrow checker complaining //! about macro generated variables within the macro being borrowed as both //! immutable and mutable. `cc!(x; x)` is semantically a no-op anyway, so it //! should not be used. @@ -662,11 +682,13 @@ //! functions like [awint_macro_internals::awint_macro_inlawi] and call it //! with the macro input as a string -// TODO when fully-qualified syntax is supported, make links for `FromStr` above and elsewhere +// TODO when fully-qualified syntax is supported, make links for `FromStr` +// above and elsewhere extern crate proc_macro; use awint_macro_internals::{ - awint_macro_cc, awint_macro_extawi, awint_macro_inlawi, unstable_native_inlawi_ty, + awint_macro_bits, awint_macro_cc, awint_macro_extawi, awint_macro_inlawi, + unstable_native_inlawi_ty, }; use proc_macro::TokenStream; @@ -724,6 +746,20 @@ pub fn extawi(input: TokenStream) -> TokenStream { } } -// I considered `bits!` and `bits_mut!` macros, but they tend to run into weird -// errors that would confuse people, better to have us decide and use the -// storage types directly +// We make the `bits` macro `&'static`, because making a relaxed `bits` or +// `bits_mut` macro typically leads to unoptimality and weird compiler errors. +// Users should use references from `extawi` or `inlawi` in any other case. + +// TODO The only thing we might change is making the configuration +// `static_width: false` if `const` allocation is ever supported. + +/// A concatenations of components macro, additionally using the source value to +/// construct a `&'static Bits`. Requires `const_support` and some feature flags +/// to work. See the [crate documentation](crate) for more. +#[proc_macro] +pub fn bits(input: TokenStream) -> TokenStream { + match awint_macro_bits(&input.to_string()) { + Ok(s) => s.parse().unwrap(), + Err(s) => panic!("{}", s), + } +} diff --git a/no_alloc_test/Cargo.toml b/no_alloc_test/Cargo.toml index a8ac5984..dcf7065c 100644 --- a/no_alloc_test/Cargo.toml +++ b/no_alloc_test/Cargo.toml @@ -12,7 +12,7 @@ publish = false # into a no-alloc binary. I have chosen `riscv32i-unknown-none-elf` for its simplicity. [dependencies] -awint = { path = "../awint", default-features = false, features = ["const_support", "rand_support", "serde_support"] } +awint = { path = "../awint", default-features = false, features = ["const_support", "zeroize_support", "rand_support", "serde_support"] } panic-halt = "0.2" rand_xoshiro = "0.6" riscv-minimal-rt = "0.5" diff --git a/no_alloc_test/src/main.rs b/no_alloc_test/src/main.rs index 98b49b13..a4448bf7 100644 --- a/no_alloc_test/src/main.rs +++ b/no_alloc_test/src/main.rs @@ -13,8 +13,8 @@ fn main() -> ! { // without the dependency leaking into runtime let mut awi0 = inlawi!(12345i20); let awi1 = inlawi!(54321i20); - let mut x0 = awi0.const_as_mut(); - let x1 = awi1.const_as_ref(); + let mut x0 = awi0.as_mut(); + let x1 = awi1.as_ref(); x0.add_(x1).unwrap(); assert!(x0.is_zero()); let mut rng = Xoshiro128StarStar::seed_from_u64(0); @@ -37,8 +37,8 @@ fn main() -> ! { let mut c = inlawi!(0u4); cc!(a;b;c).unwrap(); assert_eq!(a, inlawi!(0xau4)); - assert_eq!(a.const_as_ref(), b); - assert_eq!(a.const_as_ref(), c.const_as_ref()); + assert_eq!(a.as_ref(), b); + assert_eq!(a.as_ref(), c.as_ref()); // dynamic ranges let x: usize = 8; let awi = inlawi!(0u12); diff --git a/testcrate/assets/macro_outputs.vt100 b/testcrate/assets/macro_outputs.vt100 index cb2dcb60..24d08c07 100644 --- a/testcrate/assets/macro_outputs.vt100 +++ b/testcrate/assets/macro_outputs.vt100 @@ -358,7 +358,7 @@ mu(lit(0xa37_u12)) 0x123u12[i] Ok: mu({ -let __awint_bind_0:&B=&lit(0x123_u12); +let __awint_bind_0:&B=&static(0x123_u12); let __awint_val_0=cast(i); let __awint_val_1=cast(1); let __awint_val_2=cast(add(i,1)); @@ -415,7 +415,7 @@ __awint_awi 0x123u12[(7 + j)..(i + 5)] Ok: mu({ -let __awint_bind_0:&B=&lit(0x123_u12); +let __awint_bind_0:&B=&static(0x123_u12); let __awint_val_0=cast(add(i,5)); let __awint_val_1=cast(bw(__awint_bind_0)); let __awint_val_2=cast(add(j,7)); @@ -434,7 +434,7 @@ __awint_awi 0x123u12[..(i - j + 5)] Ok: mu({ -let __awint_bind_0:&B=&lit(0x123_u12); +let __awint_bind_0:&B=&static(0x123_u12); let __awint_val_0=cast(add(i-j,5)); let __awint_val_1=cast(bw(__awint_bind_0)); let __awint_width_1=__awint_val_0; @@ -452,7 +452,7 @@ __awint_awi 0x123u12[(i - 1)..(i + 5)] Ok: mu({ -let __awint_bind_0:&B=&lit(0x123_u12); +let __awint_bind_0:&B=&static(0x123_u12); let __awint_val_0=cast(sub(i,1)); let __awint_val_1=cast(6); let __awint_val_2=cast(add(i,5)); @@ -471,7 +471,7 @@ __awint_awi 0x123u12[(+ 5 + i - j)..(var - 7)]; ..64 Ok: mu({ -let __awint_bind_0:&B=&lit(0x123_u12); +let __awint_bind_0:&B=&static(0x123_u12); let __awint_val_0=cast(sub(var,7)); let __awint_val_1=cast(bw(__awint_bind_0)); let __awint_val_2=cast(add(i-j,5)); @@ -491,7 +491,7 @@ __awint_awi 0x123u12[(+ 5 + i - j)..(var - 7)]; ..8 Ok: mu({ -let __awint_bind_0:&B=&lit(0x123_u12); +let __awint_bind_0:&B=&static(0x123_u12); let __awint_val_0=cast(sub(var,7)); let __awint_val_1=cast(bw(__awint_bind_0)); let __awint_val_2=cast(add(i-j,5)); @@ -565,12 +565,12 @@ __awint_awi umax: 0xau4, b, 0xcu4, .., 0x98765_u20[x..(sink0.bw() - 9)], e, 0xfu4; sink0; sink1,; Ok: mu({ -let __awint_bind_0:&B=&lit(0xf_u4); +let __awint_bind_0:&B=&static(0xf_u4); let __awint_bind_1:&B=&e; -let __awint_bind_2:&B=&lit(0x98765_u20); -let __awint_bind_3:&B=&lit(0xc_u4); +let __awint_bind_2:&B=&static(0x98765_u20); +let __awint_bind_3:&B=&static(0xc_u4); let __awint_bind_4:&B=&b; -let __awint_bind_5:&B=&lit(0xa_u4); +let __awint_bind_5:&B=&static(0xa_u4); let __awint_bind_6:&mut B=&mut sink0; let __awint_bind_7:&mut B=&mut sink1; let __awint_val_0=cast(4); @@ -674,7 +674,7 @@ __awint_awi ..8, 0x1111u16, ..8; a, b; c, d; Ok: { -let __awint_bind_0:&B=&lit(0x1111_u16); +let __awint_bind_0:&B=&static(0x1111_u16); let __awint_bind_1:&mut B=&mut b; let __awint_bind_2:&mut B=&mut a; let __awint_bind_3:&mut B=&mut d; @@ -741,12 +741,12 @@ __awint_awi}) umax: 0xau4, b, 0xcu4, .., 0xdu4, e, 0xfu4; sink0; sink1 Ok: mu({ -let __awint_bind_0:&B=&lit(0xf_u4); +let __awint_bind_0:&B=&static(0xf_u4); let __awint_bind_1:&B=&e; -let __awint_bind_2:&B=&lit(0xd_u4); -let __awint_bind_3:&B=&lit(0xc_u4); +let __awint_bind_2:&B=&static(0xd_u4); +let __awint_bind_3:&B=&static(0xc_u4); let __awint_bind_4:&B=&b; -let __awint_bind_5:&B=&lit(0xa_u4); +let __awint_bind_5:&B=&static(0xa_u4); let __awint_bind_6:&mut B=&mut sink0; let __awint_bind_7:&mut B=&mut sink1; let __awint_val_0=cast(4); @@ -785,7 +785,7 @@ imax: 0..=1, 0x0_u1[0..1], x[..=], 0..=r, ..3; y Ok: { let __awint_bind_0:&B=&x; -let __awint_bind_1:&B=&lit(0x0_u1); +let __awint_bind_1:&B=&static(0x0_u1); let __awint_bind_2:&mut B=&mut y; let __awint_val_0=cast(3); let __awint_val_1=cast(add(r,1)); @@ -931,3 +931,69 @@ let __awint_cw=__awint_cw_0; } +umax: ..32, 0xfedcba98_u32 +Ok: +mu(const({ +let __awint_bind_0:&B=&static(0xfedcba98_u32); +let __awint_val_0=cast(32); +let __awint_width_0=__awint_val_0; +let __awint_cw=64usize; +let mut __awint_awi=awi(umax,Some(64),Some("__awint_cw"));let __awint_awi_ref=&mut __awint_awi; +let _ = field_width(__awint_awi_ref,__awint_bind_0,__awint_width_0);let mut __awint_shl=__awint_width_0; + + +__awint_awi}, 64, true)) + + +0x3210u16 +Ok: +mu(lit(0x3210_u16)) + + +A, 0x7654u16, B; ..96 +Ok: +mu(const({ +let __awint_bind_0:&B=&B; +let __awint_bind_1:&B=&static(0x7654_u16); +let __awint_bind_2:&B=&A; +let __awint_val_0=cast(bw(__awint_bind_0)); +let __awint_val_1=cast(16); +let __awint_val_2=cast(bw(__awint_bind_2)); +let __awint_width_0=__awint_val_0; +let __awint_width_1=__awint_val_1; +let __awint_width_2=__awint_val_2; +let __awint_cw_0=add(add(__awint_width_0,__awint_width_1),__awint_width_2); +let __awint_cw=96usize; +let __awint_res = check_fn([0;0],[0;0],[0;0],[__awint_cw_0],__awint_cw,false,false); +if __awint_res.run_fielding() {__awint_res.wrap({ +let mut __awint_awi=awi(zero,Some(96),Some("__awint_cw"));let __awint_awi_ref=&mut __awint_awi; +let _ = field_width(__awint_awi_ref,__awint_bind_0,__awint_width_0);let mut __awint_shl=__awint_width_0; +let _ = field_to(__awint_awi_ref,__awint_shl,__awint_bind_1,__awint_width_1);__awint_shl=add(__awint_shl,__awint_width_1); +let _ = field_to(__awint_awi_ref,__awint_shl,__awint_bind_2,__awint_width_2); + + +__awint_awi +})} else {__awint_res.wrap_none()}}, 96, false)) + + +C[(R-42)..R], C[R..(R+42)] +Ok: +mu(const({ +let __awint_bind_0:&B=&C; +let __awint_val_0=cast(R); +let __awint_val_1=cast(42); +let __awint_val_2=cast(add(R,42)); +let __awint_val_3=cast(bw(__awint_bind_0)); +let __awint_val_4=cast(sub(R,42)); +let __awint_width_1=__awint_val_1; +let __awint_cw=84usize; +let __awint_res = check_fn([__awint_val_2,__awint_val_0],[__awint_val_3,__awint_val_3],[0;0],[0;0],__awint_cw,false,false); +if __awint_res.run_fielding() {__awint_res.wrap({ +let mut __awint_awi=awi(zero,Some(84),Some("__awint_cw"));let __awint_awi_ref=&mut __awint_awi; +let _ = field_from(__awint_awi_ref,__awint_bind_0,__awint_val_0,__awint_width_1);let mut __awint_shl=__awint_width_1; +let _ = field(__awint_awi_ref,__awint_shl,__awint_bind_0,__awint_val_4,__awint_width_1); + +__awint_awi +})} else {__awint_res.wrap_none()}}, 84, false)) + + diff --git a/testcrate/src/bin/macro_outputs.rs b/testcrate/src/bin/macro_outputs.rs index f16f70a1..d380645b 100644 --- a/testcrate/src/bin/macro_outputs.rs +++ b/testcrate/src/bin/macro_outputs.rs @@ -28,6 +28,7 @@ fn cc(mut f: impl Write, input: &str) { static_width: false, return_type: None, must_use: |s| format!("mu({s})"), + static_construction_fn: |awi| format!("static({awi})"), lit_construction_fn: |awi| format!("lit({awi})"), construction_fn: |s, w, d| { format!( @@ -37,6 +38,7 @@ fn cc(mut f: impl Write, input: &str) { d ) }, + const_wrapper: |s, _, _| s, fn_names: TEST_FN_NAMES, }; match cc_macro(input, code_gen, AWINT_NAMES) { @@ -54,6 +56,7 @@ fn static_cc(mut f: impl Write, input: &str) { static_width: true, return_type: Some("StaticAwi"), must_use: |s| format!("mu({s})"), + static_construction_fn: |awi| format!("static({awi})"), lit_construction_fn: |awi| format!("lit({awi})"), construction_fn: |s, w, d| { format!( @@ -63,6 +66,7 @@ fn static_cc(mut f: impl Write, input: &str) { d ) }, + const_wrapper: |s, _, _| s, fn_names: TEST_FN_NAMES, }; match cc_macro(input, code_gen, AWINT_NAMES) { @@ -80,6 +84,7 @@ fn dynamic_cc(mut f: impl Write, input: &str) { static_width: false, return_type: Some("DynamicAwi"), must_use: |s| format!("mu({s})"), + static_construction_fn: |awi| format!("static({awi})"), lit_construction_fn: |awi| format!("lit({awi})"), construction_fn: |s, w, d| { format!( @@ -89,6 +94,35 @@ fn dynamic_cc(mut f: impl Write, input: &str) { d ) }, + const_wrapper: |s, _, _| s, + fn_names: TEST_FN_NAMES, + }; + match cc_macro(input, code_gen, AWINT_NAMES) { + Ok(s) => { + writeln!(f, "{input}\nOk:\n{s}\n\n").unwrap(); + } + Err(e) => { + writeln!(f, "{input}\nErr:\n{e}\n\n").unwrap(); + } + } +} + +fn const_cc(mut f: impl Write, input: &str) { + let code_gen = CodeGen { + static_width: true, + return_type: Some("ConstAwi"), + must_use: |s| format!("mu({s})"), + static_construction_fn: |awi| format!("static({awi})"), + lit_construction_fn: |awi| format!("lit({awi})"), + construction_fn: |s, w, d| { + format!( + "awi({},{:?},{:?})", + if s.is_empty() { "zero" } else { s }, + w, + d + ) + }, + const_wrapper: |s, w, i| format!("const({s}, {}, {i})", w.unwrap()), fn_names: TEST_FN_NAMES, }; match cc_macro(input, code_gen, AWINT_NAMES) { @@ -201,6 +235,11 @@ fn main() { static_cc(&mut s, "imin: y; ..8"); cc(&mut s, "imin: y"); + const_cc(&mut s, "umax: ..32, 0xfedcba98_u32"); + const_cc(&mut s, "0x3210u16"); + const_cc(&mut s, "A, 0x7654u16, B; ..96"); + const_cc(&mut s, "C[(R-42)..R], C[R..(R+42)]"); + let mut f = OpenOptions::new() .truncate(true) .create(true) diff --git a/testcrate/src/bin/stable.rs b/testcrate/src/bin/stable.rs new file mode 100644 index 00000000..59ed056c --- /dev/null +++ b/testcrate/src/bin/stable.rs @@ -0,0 +1,140 @@ +// This is here because the macros aren't tested on stable by just building the +// crates + +use awint::awi::*; + +// can't work on stable yet +//const B: &Bits = bits!(0x1234u32); + +#[allow(clippy::let_unit_value)] +fn main() { + // both trailing comma and semicolon + let _ = inlawi!(0u1,;); + // basic concatenation + assert_eq!(inlawi!(0xau4, 0x4321u16, 0x7u4), inlawi!(0xa43217u24)); + assert_eq!(inlawi!(0xau4, 0x4321u32[8..12], 0x7u4), inlawi!(0xa37u12)); + // copy assign + let a = inlawi!(0xau4); + let mut awi = ExtAwi::zero(bw(4)); + let mut b = awi.const_as_mut(); + let mut c = extawi!(0u4); + cc!(a;b;c).unwrap(); + assert_eq!(a, inlawi!(0xau4)); + assert_eq!(a.as_ref(), b); + assert_eq!(a.as_ref(), c.as_ref()); + // dynamic ranges + let x: usize = 8; + let awi = ExtAwi::zero(bw(12)); + assert_eq!( + extawi!(0x98765_u20[x..(x + awi.bw())]).unwrap(), + extawi!(0x987u12) + ); + // unbounded fillers + let mut sink0 = ExtAwi::zero(bw(44)); + let mut sink1 = ExtAwi::zero(bw(44)); + let b = inlawi!(0xbbu8); + let e = inlawi!(0xeeeu12); + let result = extawi!(0xabbcffdeeefu44); + assert_eq!( + extawi!(umax: 0xau4, b, 0xcu4, .., 0xdu4, e, 0xfu4; sink0; sink1).unwrap(), + result + ); + assert_eq!(sink0, result); + assert_eq!(sink1, result); + let mut sink0 = extawi!(0xf0f0f0f0f0fu44); + let mut sink1 = extawi!(0xf0f0f0f0f0fu44); + let b = inlawi!(0xbbu8); + let e = inlawi!(0xeeeu12); + let result = extawi!(0xabbcf0deeefu44); + cc!(0xau4, b, 0xcu4, .., 0xdu4, e, 0xfu4; sink0; sink1).unwrap(); + assert_eq!(sink0, result); + assert_eq!(sink1, result); + assert_eq!(extawi!(umax: ..;..9), extawi!(umax: ..9)); + let a = inlawi!(0x123u12); + let b = inlawi!(0x4u4); + assert_eq!(extawi!(a, b), extawi!(0x1234u16)); + let a = inlawi!(0xau4); + let mut b = inlawi!(0xbu4); + let r0 = 0; + let r1 = 4; + assert_eq!(extawi!(a[..r0], b[..r1]), Some(extawi!(0xbu4))); + assert_eq!(cc!(a[..r0]; b[..r0]), Some(())); + assert_eq!(extawi!(a[..r0]), None); + assert_eq!(cc!(r0..r1), Some(())); + assert_eq!(inlawi!(0100[2]), inlawi!(1)); + assert_eq!(inlawi!(0100[3]), inlawi!(0)); + let r = 2; + assert_eq!(extawi!(0100[r]).unwrap(), extawi!(1)); + let a = inlawi!(0xau4); + let mut y = inlawi!(0u16); + cc!(imax: .., a, ..4; y).unwrap(); + assert_eq!(y, inlawi!(0x7fafu16)); + // make sure sink -> buffer refreshes between sinks + let mut a = inlawi!(0xaaaau16); + let mut b = inlawi!(0xbbbbu16); + let mut c = inlawi!(0xccccu16); + let mut d = inlawi!(0xddddu16); + cc!( + ..8, 0x1111u16, ..8; + a, b; + c, d; + ) + .unwrap(); + assert_eq!(a, inlawi!(0xaa11u16)); + assert_eq!(b, inlawi!(0x11bbu16)); + assert_eq!(c, inlawi!(0xcc11u16)); + assert_eq!(d, inlawi!(0x11ddu16)); + // check for borrow collisions + let mut a = inlawi!(0x9876543210u40); + let b = extawi!( + a[..=7], a[(a.bw() - 16)..]; + a[(5 * 4)..(9 * 4)], a[..(2 * 4)]; + ) + .unwrap(); + assert_eq!(a, inlawi!(0x9109843276u40)); + assert_eq!(b, extawi!(0x109876_u24)); + let mut a = inlawi!(0x9876543210u40); + let b = extawi!( + a[..=0x7], a[(a.bw() - 0b10000)..]; + a[(5 * 4)..(9 * 4)], a[..0o10]; + ) + .unwrap(); + assert_eq!(a, inlawi!(0x9109843276u40)); + assert_eq!(b, extawi!(0x109876_u24)); + let r0 = 3; + let r1 = 7; + assert_eq!(cc!(0x123u12[r0..r1]), Some(())); + let e = 2; + assert_eq!(extawi!(uone: ..=, ; ..=18, ..e, ..=, ), extawi!(0x1_u21)); + let r = 3; + let x = inlawi!(0x8_u5); + let mut y = inlawi!(0u15); + cc!(imax: 0..=1, 0x0_u1[0..1], x[..=], 0..=r, ..3; y).unwrap(); + assert_eq!(y, inlawi!(0x247fu15)); + let mut x = inlawi!(0xffu8); + let mut y = inlawi!(0xfu4); + cc!(uone: ..; .., x; .., y); + assert_eq!(x, inlawi!(1u8)); + assert_eq!(y, inlawi!(1u4)); + let mut x = extawi!(0u64); + assert_eq!(extawi!(umax: ..; x), ExtAwi::umax(bw(64))); + assert_eq!(x, ExtAwi::umax(bw(64))); + let r0 = 0; + let r1 = 0; + let mut x = inlawi!(0u4); + let mut y = inlawi!(0u8); + assert!(cc!(zero: ..; .., x[..r0]; .., y[..r1]).is_some()); + assert_eq!(extawi!(imax: ..; .., x; .., y), extawi!(0x7fu8)); + assert_eq!(x, inlawi!(0xfu4)); + assert_eq!(y, inlawi!(0x7fu8)); + let r = 0; + assert!(extawi!(imin: ..r).is_none()); + let r = 2; + assert_eq!(extawi!(imin: ..r), Some(extawi!(10))); + assert_eq!(extawi!(imin: ..2), extawi!(10)); + assert_eq!(inlawi!(imin: ..2), inlawi!(10)); + let y = inlawi!(0u8); + let _: () = cc!(imin: y); + assert_eq!(y, inlawi!(0u8)); + let _: () = cc!(imin: ..r); +} diff --git a/testcrate/tests/consts.rs b/testcrate/tests/consts.rs index 2bff5e09..63ea5f94 100644 --- a/testcrate/tests/consts.rs +++ b/testcrate/tests/consts.rs @@ -3,10 +3,10 @@ #![feature(const_trait_impl)] #![allow(clippy::reversed_empty_ranges)] -use awint::{bw, cc, inlawi, inlawi_ty, Bits, InlAwi}; +use awint::{awint_internals::Digit, bw, cc, inlawi, inlawi_ty, Bits, InlAwi}; const fn check_invariants(x: &Bits) { - if x.extra() != 0 && (x.last() & (usize::MAX << x.extra())) != 0 { + if x.extra() != 0 && (x.last() & (Digit::MAX << x.extra())) != 0 { panic!("unused bits are set"); } } @@ -40,12 +40,12 @@ const fn consts() { let mut b1337: inlawi_ty!(12) = inlawi!(010100111001); let c_100: inlawi_ty!(12) = inlawi!(100i12); let d1437: inlawi_ty!(12) = inlawi!(1437u12); - eq(a1337.const_as_ref(), b1337.const_as_ref()); + eq(a1337.as_ref(), b1337.as_ref()); let sum = b1337.const_as_mut(); - sum.add_(c_100.const_as_ref()).unwrap(); - eq(sum, d1437.const_as_ref()); + sum.add_(c_100.as_ref()).unwrap(); + eq(sum, d1437.as_ref()); let e1337: inlawi_ty!(12) = inlawi!(0101, 0011, 1001); - eq(a1337.const_as_ref(), e1337.const_as_ref()); + eq(a1337.as_ref(), e1337.as_ref()); let y3 = inlawi!(0xba9u12); let y2 = inlawi!(0x876u12); @@ -65,9 +65,9 @@ const fn consts() { ) .unwrap(); - eq(z2.const_as_ref(), inlawi!(0xba98u16).const_as_ref()); - eq(z1.const_as_ref(), inlawi!(0x7654u16).const_as_ref()); - eq(z0.const_as_ref(), inlawi!(0x3210u16).const_as_ref()); + eq(z2.as_ref(), inlawi!(0xba98u16).as_ref()); + eq(z1.as_ref(), inlawi!(0x7654u16).as_ref()); + eq(z0.as_ref(), inlawi!(0x3210u16).as_ref()); } #[test] @@ -176,7 +176,7 @@ const fn bits_functions() { assert!(x0.lut_set(x1, x3).is_none()); assert!(x0.funnel_(x1, x3).is_none()); - x0.short_cin_mul(0, 0); + x0.digit_cin_mul_(0, 0); assert!(x0.mul_add_(x1, x2).is_none()); assert!(x1.mul_add_(x0, x2).is_none()); @@ -196,7 +196,7 @@ const fn bits_functions() { assert!(x0.neg_add_(false, x1).is_none()); assert!(x0.cin_sum_(false, x1, x2).is_none()); - x0.usize_or_(123, 60); + x0.digit_or_(123, 60); // division by zero and differing size x1.umax_(); @@ -220,10 +220,10 @@ const fn bits_functions() { x3.umax_(); assert!(Bits::idivide(x0, x1, x2, x3).is_none()); x1.umax_(); - assert!(x4.short_udivide_(x1, 0).is_none()); + assert!(x4.digit_udivide_(x1, 0).is_none()); x0.umax_(); - assert!(x4.short_udivide_(x0, 1).is_none()); - assert!(x4.short_udivide_inplace_(0).is_none()); + assert!(x4.digit_udivide_(x0, 1).is_none()); + assert!(x4.digit_udivide_inplace_(0).is_none()); assert!(x0.get(128).is_none()); assert!(x0.set(128, false).is_none()); diff --git a/testcrate/tests/dag_fuzzing.rs b/testcrate/tests/dag_fuzzing.rs index 8e31062a..9598b9ea 100644 --- a/testcrate/tests/dag_fuzzing.rs +++ b/testcrate/tests/dag_fuzzing.rs @@ -1,11 +1,14 @@ -use std::{cmp::min, num::NonZeroUsize}; +use std::{ + cmp::{max, min}, + num::NonZeroUsize, +}; use awint::{ awi, awint_dag::{ lowering::OpDag, smallvec::smallvec, state::STATE_ARENA, EvalError, Lineage, Op, StateEpoch, }, - awint_internals::BITS, + awint_internals::USIZE_BITS, awint_macro_internals::triple_arena::{ptr_struct, Arena}, dag, }; @@ -35,7 +38,7 @@ impl Pair { pub fn new(lit: awi::ExtAwi) -> Self { Self { awi: lit.clone(), - dag: lit.const_as_ref().into(), + dag: lit.as_ref().into(), } } } @@ -43,21 +46,25 @@ impl Pair { #[derive(Debug)] struct Mem { a: Arena, - // The outer Vec has 65 Vecs for all the supported bitwidths (there is a dummy 0 bitwidth Vec - // and one for each of 1..=64), the inner Vecs are unsorted and used for random querying + // The outer Vec has `v_len` Vecs for all the supported bitwidths (there is a dummy 0 + // bitwidth Vec and one for each of 1..=(v_len - 1)), the inner Vecs are unsorted and used for + // random querying v: Vec>, + v_len: usize, rng: Xoshiro128StarStar, } impl Mem { pub fn new() -> Self { let mut v = vec![]; - for _ in 0..65 { + let v_len = max(65, USIZE_BITS + 1); + for _ in 0..v_len { v.push(vec![]); } Self { a: Arena::::new(), v, + v_len, rng: Xoshiro128StarStar::seed_from_u64(0), } } @@ -65,7 +72,7 @@ impl Mem { pub fn clear(&mut self) { self.a.clear(); self.v.clear(); - for _ in 0..65 { + for _ in 0..self.v_len { self.v.push(vec![]); } } @@ -110,7 +117,7 @@ impl Mem { } pub fn next_usize(&mut self, cap: usize) -> P0 { - self.next_capped(BITS, cap) + self.next_capped(USIZE_BITS, cap) } // just use cloning for the immutable indexing, because dealing with the guards diff --git a/testcrate/tests/dag_macros.rs b/testcrate/tests/dag_macros.rs index 7647b360..b7e0e9a0 100644 --- a/testcrate/tests/dag_macros.rs +++ b/testcrate/tests/dag_macros.rs @@ -22,8 +22,8 @@ fn dag_macros() { let mut c = extawi!(0u4); cc!(a;b;c).unwrap(); assert_eq!(a, inlawi!(0xau4)); - assert_eq!(a.const_as_ref(), b); - assert_eq!(a.const_as_ref(), c.const_as_ref()); + assert_eq!(a.as_ref(), b); + assert_eq!(a.as_ref(), c.as_ref()); // dynamic ranges let x = 8; let awi = ExtAwi::zero(bw(12)); diff --git a/testcrate/tests/dag_misc.rs b/testcrate/tests/dag_misc.rs index b065da80..5c7d969c 100644 --- a/testcrate/tests/dag_misc.rs +++ b/testcrate/tests/dag_misc.rs @@ -104,8 +104,7 @@ fn dag_bits_functions_internal( let [x0, x1, x2, x3, x4] = x; - // TODO `mul_`, `neg_add_`, `usize_or_`, `short_udivide_`, - // `short_udivide_inplace`, `range_and`? + // TODO `mul_`, `neg_add_`, never add any `digit_` dependent functions // test the inlawi macros first assert!(x0.is_zero()); @@ -167,8 +166,6 @@ fn dag_bits_functions_internal( assert!(x0.lut_set(x1, x3).is_none()); assert!(x0.funnel_(x1, x3).is_none()); - // x0.short_cin_mul(0, 0); - assert!(x0.mul_add_(x1, x2).is_none()); assert!(x1.mul_add_(x0, x2).is_none()); assert!(x2.mul_add_(x1, x0).is_none()); @@ -256,8 +253,6 @@ fn dag_bits_functions_internal( x0.dec_(true); x0.neg_(false); - // x0.usize_or_(123, 60); - // division by zero and differing size x4.zero_(); assert!(Bits::udivide(x1, x2, x3, x4).is_none()); @@ -267,11 +262,6 @@ fn dag_bits_functions_internal( assert!(Bits::idivide(x1, x2, x3, x4).is_none()); x3.umax_(); assert!(Bits::idivide(x0, x1, x2, x3).is_none()); - // x1.umax_(); - // assert!(x4.short_udivide_(x1, 0).is_none()); - // x0.umax_(); - // assert!(x4.short_udivide_(x0, 1).is_none()); - // assert!(x4.short_udivide_inplace_(0).is_none()); x0.bool_(true); test_unary_literal!( @@ -331,7 +321,7 @@ fn dag_bits_functions() { let num_assertions = 170; let eq = epoch0.assertions().bits.len() == num_assertions; if !eq { - println!( + panic!( "number of assertions ({}) is not as expected", epoch0.assertions().bits.len() ); diff --git a/testcrate/tests/fuzz/identities.rs b/testcrate/tests/fuzz/identities.rs index 00d2651c..c466d0ef 100644 --- a/testcrate/tests/fuzz/identities.rs +++ b/testcrate/tests/fuzz/identities.rs @@ -1,12 +1,15 @@ use core::cmp; -use awint::{Bits, ExtAwi}; +use awint::{ + awint_internals::{Digit, BITS, USIZE_BITS}, + Bits, ExtAwi, +}; use rand_xoshiro::{ rand_core::{RngCore, SeedableRng}, Xoshiro128StarStar, }; -use crate::fuzz::{eq, fuzz_step, ne, BITS}; +use crate::fuzz::{eq, fuzz_step, ne}; macro_rules! primitive_conversion { ( @@ -50,6 +53,7 @@ fn identities_inner( x5: &mut Bits, s0: usize, s1: usize, + d0: Digit, ) -> Option<()> { // `.unwrap()` is used on operations that fail in ways other than nonequal // bitwidth, because it is otherwise a pain to figure out what line the error @@ -181,7 +185,7 @@ fn identities_inner( x4.range_and_(s0..s1).unwrap(); eq(x2, x4); - // usize or assign + // digit or assign x2.copy_(x0)?; x3.copy_(x0)?; x4.copy_(x1)?; @@ -189,8 +193,8 @@ fn identities_inner( .unwrap(); x2.or_(x4)?; x4.lshr_(s0).unwrap(); - let digit = x4.to_usize(); - x3.usize_or_(digit, s0); + let digit = x4.to_digit(); + x3.digit_or_(digit, s0); eq(x2, x3); // arithmetic shift @@ -338,13 +342,14 @@ fn identities_inner( u32_, to_u32, 32; u64_, to_u64, 64; u128_, to_u128, 128; - usize_, to_usize, BITS; + usize_, to_usize, USIZE_BITS; i8_, to_i8, 8; i16_, to_i16, 16; i32_, to_i32, 32; i64_, to_i64, 64; i128_, to_i128, 128; - isize_, to_isize, BITS; + isize_, to_isize, USIZE_BITS; + digit_, to_digit, BITS; ); // multiplication and left shift @@ -358,43 +363,43 @@ fn identities_inner( eq(x3, x4); // negation and multiplication - x2.usize_(s0); + x2.digit_(d0); x2.mul_add_(x0, x1)?; x3.copy_(x0)?; x4.copy_(x1)?; x3.neg_(true); x4.neg_(true); - x5.usize_(s0); + x5.digit_(d0); x5.mul_add_(x3, x4)?; eq(x2, x5); - // short multiplication and division + // digit multiplication and division // duo:x0 div:x1,x3 quo:x4 rem:x5 - let div = x1.to_usize(); + let div = x1.to_digit(); if div != 0 { - x3.usize_(div); - let rem = x4.short_udivide_(x0, div)?; - x5.usize_(rem); - let oflow = x4.short_cin_mul(0, div); + x3.digit_(div); + let rem = x4.digit_udivide_(x0, div)?; + x5.digit_(rem); + let oflow = x4.digit_cin_mul_(0, div); assert_eq!(oflow, 0); x4.add_(x5)?; // `rem < div` and `(quo * div) + rem == duo` assert!(x5.ult(x3)? && x4.const_eq(x0)?); - // compare the two short divisions + // compare the two digit divisions x2.copy_(x0)?; - x2.short_udivide_inplace_(div)?; - x3.short_udivide_(x0, div)?; + x2.digit_udivide_inplace_(div)?; + x3.digit_udivide_(x0, div)?; eq(x2, x3); } - // compare short multiplications + // compare digit multiplications x2.copy_(x0)?; - let rhs = x0.to_usize(); - x2.short_cin_mul(0, rhs); + let rhs = x0.to_digit(); + x2.digit_cin_mul_(0, rhs); x3.copy_(x0)?; x4.copy_(x0)?; - x3.short_mul_add_(x4, rhs)?; + x3.digit_mul_add_(x4, rhs)?; // alternate multiplication x2.copy_(x0)?; @@ -559,7 +564,8 @@ pub fn identities(iters: u32, seed: u64, tmp: [&mut Bits; 6]) -> Option<()> { edge_cases!(fl, x1, x3, { let s0 = (rng.next_u32() as usize) % w; let s1 = (rng.next_u32() as usize) % w; - identities_inner(&mut rng, x0, x1, x2, x3, x4, x5, s0, s1)?; + let d0 = (u128::from(rng.next_u64()) | (u128::from(rng.next_u64()) << 64)) as Digit; + identities_inner(&mut rng, x0, x1, x2, x3, x4, x5, s0, s1, d0)?; }) }) } @@ -570,8 +576,8 @@ pub fn identities(iters: u32, seed: u64, tmp: [&mut Bits; 6]) -> Option<()> { fuzz_step(&mut rng, x1, x2); let s0 = (rng.next_u32() as usize) % w; let s1 = (rng.next_u32() as usize) % w; - - identities_inner(&mut rng, x0, x1, x2, x3, x4, x5, s0, s1)?; + let d0 = (u128::from(rng.next_u64()) | (u128::from(rng.next_u64()) << 64)) as Digit; + identities_inner(&mut rng, x0, x1, x2, x3, x4, x5, s0, s1, d0)?; // these are handled here because of the requirement that x0 and x1 are mutable diff --git a/testcrate/tests/fuzz/mod.rs b/testcrate/tests/fuzz/mod.rs index 525351b0..7aeaebd0 100644 --- a/testcrate/tests/fuzz/mod.rs +++ b/testcrate/tests/fuzz/mod.rs @@ -1,6 +1,6 @@ #![allow(clippy::too_many_arguments)] -use awint::Bits; +use awint::{awint_internals::Digit, Bits}; use rand_xoshiro::{rand_core::RngCore, Xoshiro128StarStar}; #[cfg(not(miri))] mod fp; @@ -14,7 +14,7 @@ mod one_run; #[track_caller] const fn check_invariants(x: &Bits) { - if x.extra() != 0 && (x.last() & (usize::MAX << x.extra())) != 0 { + if x.extra() != 0 && (x.last() & (Digit::MAX << x.extra())) != 0 { panic!("unused bits are set"); } } @@ -65,7 +65,6 @@ pub fn fuzz_step(rng: &mut Xoshiro128StarStar, x: &mut Bits, tmp: &mut Bits) { .unwrap() } -pub const BITS: usize = usize::BITS as usize; #[cfg(not(miri))] pub use fp::fp_identities; #[cfg(not(miri))] diff --git a/testcrate/tests/fuzz_all.rs b/testcrate/tests/fuzz_all.rs index 1b889e84..e8551f5a 100644 --- a/testcrate/tests/fuzz_all.rs +++ b/testcrate/tests/fuzz_all.rs @@ -4,7 +4,7 @@ mod fuzz; use core::cmp; -use awint::{bw, inlawi, Bits, ExtAwi, InlAwi}; +use awint::{bw, extawi, inlawi, Bits, ExtAwi, InlAwi}; const N: u32 = if cfg!(miri) { 32 @@ -19,16 +19,22 @@ macro_rules! test_extawi { $( #[test] fn $name() { - let w = bw($w); - let array = [ - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], - ]; - fuzz::identities($n, $seed, array).unwrap(); + let mut x0 = extawi!(zero: ..$w); + let mut x1 = extawi!(zero: ..$w); + let mut x2 = extawi!(zero: ..$w); + let mut x3 = extawi!(zero: ..$w); + let mut x4 = extawi!(zero: ..$w); + let mut x5 = extawi!(zero: ..$w); + fuzz::identities($n, $seed, + [&mut x0, &mut x1, &mut x2, &mut x3, &mut x4, &mut x5] + ).unwrap(); + // prevent certain crazy false negatives from happening + assert_eq!(x0.bw(), $w); + assert_eq!(x1.bw(), $w); + assert_eq!(x2.bw(), $w); + assert_eq!(x3.bw(), $w); + assert_eq!(x4.bw(), $w); + assert_eq!(x5.bw(), $w); } )* }; @@ -66,16 +72,22 @@ macro_rules! test_inlawi { $( #[test] fn $name() { + let mut x0 = inlawi!(zero: ..$len); + let mut x1 = inlawi!(zero: ..$len); + let mut x2 = inlawi!(zero: ..$len); + let mut x3 = inlawi!(zero: ..$len); + let mut x4 = inlawi!(zero: ..$len); + let mut x5 = inlawi!(zero: ..$len); fuzz::identities($n, $seed, - [ - inlawi!(zero: ..$len).const_as_mut(), - inlawi!(zero: ..$len).const_as_mut(), - inlawi!(zero: ..$len).const_as_mut(), - inlawi!(zero: ..$len).const_as_mut(), - inlawi!(zero: ..$len).const_as_mut(), - inlawi!(zero: ..$len).const_as_mut(), - ] + [&mut x0, &mut x1, &mut x2, &mut x3, &mut x4, &mut x5] ); + // prevent certain crazy false negatives from happening + InlAwi::assert_invariants(&x0); + InlAwi::assert_invariants(&x1); + InlAwi::assert_invariants(&x2); + InlAwi::assert_invariants(&x3); + InlAwi::assert_invariants(&x4); + InlAwi::assert_invariants(&x5); } )* }; @@ -107,11 +119,11 @@ fn one_run() { let n = 9000; for bw_i in 1..=n { let w = bw(bw_i); - let array = [ - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], - &mut ExtAwi::zero(w)[..], + let array: [&mut Bits; 4] = [ + &mut ExtAwi::zero(w), + &mut ExtAwi::zero(w), + &mut ExtAwi::zero(w), + &mut ExtAwi::zero(w), ]; fuzz::one_run(array).unwrap(); } diff --git a/testcrate/tests/macros.rs b/testcrate/tests/macros.rs index 31980681..9afc2c61 100644 --- a/testcrate/tests/macros.rs +++ b/testcrate/tests/macros.rs @@ -1,3 +1,8 @@ +#![feature(const_trait_impl)] +#![feature(const_mut_refs)] +#![feature(const_option)] +#![feature(inline_const)] + use awint::awi::*; macro_rules! construction { @@ -5,24 +10,24 @@ macro_rules! construction { $( let inlawi = inlawi!(zero: ..$w); let extawi = ExtAwi::zero(bw($w)); - assert!(inlawi.const_as_ref().is_zero()); - assert_eq!(inlawi.const_as_ref(), extawi.const_as_ref()); + assert!(inlawi.as_ref().is_zero()); + assert_eq!(inlawi.as_ref(), extawi.as_ref()); let inlawi = inlawi!(umax: ..$w); let extawi = ExtAwi::umax(bw($w)); - assert!(inlawi.const_as_ref().is_umax()); - assert_eq!(inlawi.const_as_ref(), extawi.const_as_ref()); + assert!(inlawi.as_ref().is_umax()); + assert_eq!(inlawi.as_ref(), extawi.as_ref()); let inlawi = inlawi!(imax: ..$w); let extawi = ExtAwi::imax(bw($w)); - assert!(inlawi.const_as_ref().is_imax()); - assert_eq!(inlawi.const_as_ref(), extawi.const_as_ref()); + assert!(inlawi.as_ref().is_imax()); + assert_eq!(inlawi.as_ref(), extawi.as_ref()); let inlawi = inlawi!(imin: ..$w); let extawi = ExtAwi::imin(bw($w)); - assert!(inlawi.const_as_ref().is_imin()); - assert_eq!(inlawi.const_as_ref(), extawi.const_as_ref()); + assert!(inlawi.as_ref().is_imin()); + assert_eq!(inlawi.as_ref(), extawi.as_ref()); let inlawi = inlawi!(uone: ..$w); let extawi = ExtAwi::uone(bw($w)); - assert!(inlawi.const_as_ref().is_uone()); - assert_eq!(inlawi.const_as_ref(), extawi.const_as_ref()); + assert!(inlawi.as_ref().is_uone()); + assert_eq!(inlawi.as_ref(), extawi.as_ref()); )* }; } @@ -47,8 +52,8 @@ fn macro_successes() { let mut c = extawi!(0u4); cc!(a;b;c).unwrap(); assert_eq!(a, inlawi!(0xau4)); - assert_eq!(a.const_as_ref(), b); - assert_eq!(a.const_as_ref(), c.const_as_ref()); + assert_eq!(a.as_ref(), b); + assert_eq!(a.as_ref(), c.as_ref()); // dynamic ranges let x: usize = 8; let awi = ExtAwi::zero(bw(12)); @@ -164,4 +169,14 @@ fn macro_successes() { let _: () = cc!(imin: y); assert_eq!(y, inlawi!(0u8)); let _: () = cc!(imin: ..r); + + const A: &Bits = bits!(umax: ..32, 0xfedcba98_u32); + const B: &Bits = bits!(0x3210u16); + const C: &Bits = bits!(A, 0x7654u16, B; ..96).unwrap(); + assert_eq!(C, bits!(0xffffffff_fedcba98_76543210_u96)); + const D: &Bits = const { + const R: usize = 48; + bits!(C[(R - 42)..R], C[R..(R + 42)]).unwrap() + }; + assert_eq!(D, bits!(0xba987_654323ff_fffffedc_u84)); } diff --git a/testcrate/tests/misc.rs b/testcrate/tests/misc.rs index b89e69fe..531bd905 100644 --- a/testcrate/tests/misc.rs +++ b/testcrate/tests/misc.rs @@ -1,5 +1,11 @@ -use awint::{awi::*, awint_internals::BITS}; -use rand_xoshiro::{rand_core::SeedableRng, Xoshiro128StarStar}; +use awint::{ + awi::*, + awint_internals::{Digit, BITS, USIZE_BITS}, +}; +use rand_xoshiro::{ + rand_core::{RngCore, SeedableRng}, + Xoshiro128StarStar, +}; /// [Bits::lut_] needs its own test because of its special requirements #[test] @@ -21,8 +27,8 @@ fn lut_and_field() { awi_lut.rand_(&mut rng).unwrap(); let mut awi_inx = ExtAwi::zero(bw(pow)); let out = awi_out.const_as_mut(); - let lut = awi_lut.const_as_ref(); - let inx = awi_inx.const_as_mut(); + let lut = awi_lut.as_ref(); + let inx = awi_inx.as_mut(); for i in 0..mul { inx.usize_(i); out.lut_(lut, inx).unwrap(); @@ -84,7 +90,7 @@ fn funnel_() { #[cfg(miri)] let max_pow = 7; #[cfg(not(miri))] - let max_pow = 10; + let max_pow = 10; // note this also is over BITS when `u8_digits` is active for pow in 1..max_pow { let mut awi_shift = ExtAwi::zero(bw(pow)); let mut awi_lhs = ExtAwi::zero(bw(1 << pow)); @@ -108,6 +114,15 @@ fn funnel_() { } } +macro_rules! test_unstable_from_u8_slice { + ($buf:ident, $($ty:expr)*) => { + $( + let x: inlawi_ty!($ty) = InlAwi::unstable_from_u8_slice($buf); + InlAwi::assert_invariants(&x); + )* + }; +} + #[test] fn awint_internals_test() { let mut rng = &mut Xoshiro128StarStar::seed_from_u64(0); @@ -128,6 +143,17 @@ fn awint_internals_test() { let mut add = inlawi!(zero: ..,add;..256).unwrap(); add.mul_add_(&lhs, &rhs).unwrap(); assert_eq!(&extawi!(tmp1, tmp0)[..], &add[..]); + + let mut buf = [0u8; 68]; + for x in &mut buf { + *x = rng.next_u32() as u8; + } + for i in 0..buf.len() { + // test `unstable_from_u8_slice` directly because the macros won't test some + // cases + let buf = &buf[0..i]; + test_unstable_from_u8_slice!(buf, 1 7 8 9 15 16 17 31 32 33 63 64 65 127 128 129 258); + } } #[test] @@ -155,14 +181,18 @@ fn from_primitive() { assert_eq!(InlAwi::from(i64::MAX), inlawi!(imax: ..64)); assert_eq!(InlAwi::from(i128::MAX), inlawi!(imax: ..128)); - assert_eq!(InlAwi::from_usize(usize::MAX).bw(), BITS); - assert_eq!(InlAwi::from_isize(isize::MAX).bw(), BITS); - assert_eq!(InlAwi::from(usize::MAX).bw(), BITS); - assert_eq!(InlAwi::from(isize::MAX).bw(), BITS); + assert_eq!(InlAwi::from_usize(usize::MAX).bw(), USIZE_BITS); + assert_eq!(InlAwi::from_isize(isize::MAX).bw(), USIZE_BITS); + assert_eq!(InlAwi::from_digit(Digit::MAX).bw(), BITS); + assert_eq!(InlAwi::from(usize::MAX).bw(), USIZE_BITS); + assert_eq!(InlAwi::from(isize::MAX).bw(), USIZE_BITS); + assert_eq!(InlAwi::from(Digit::MAX).bw(), BITS); assert_eq!(InlAwi::from_usize(usize::MAX).to_usize(), usize::MAX); assert_eq!(InlAwi::from_isize(isize::MAX).to_isize(), isize::MAX); + assert_eq!(InlAwi::from_digit(Digit::MAX).to_digit(), Digit::MAX); assert_eq!(InlAwi::from(usize::MAX).to_usize(), usize::MAX); assert_eq!(InlAwi::from(isize::MAX).to_isize(), isize::MAX); + assert_eq!(InlAwi::from(Digit::MAX).to_digit(), Digit::MAX); assert_eq!(ExtAwi::from_bool(true), extawi!(umax: ..1)); assert_eq!(ExtAwi::from_u8(u8::MAX), extawi!(umax: ..8)); @@ -172,7 +202,7 @@ fn from_primitive() { assert_eq!(ExtAwi::from_u128(u128::MAX), extawi!(umax: ..128)); assert_eq!( ExtAwi::from_usize(usize::MAX), - extawi!(umax: ..BITS).unwrap() + extawi!(umax: ..USIZE_BITS).unwrap() ); assert_eq!(ExtAwi::from_i8(i8::MAX), extawi!(imax: ..8)); assert_eq!(ExtAwi::from_i16(i16::MAX), extawi!(imax: ..16)); @@ -181,7 +211,11 @@ fn from_primitive() { assert_eq!(ExtAwi::from_i128(i128::MAX), extawi!(imax: ..128)); assert_eq!( ExtAwi::from_isize(isize::MAX), - extawi!(imax: ..BITS).unwrap() + extawi!(imax: ..(isize::BITS as usize)).unwrap() + ); + assert_eq!( + ExtAwi::from_digit(Digit::MAX), + extawi!(umax: ..BITS).unwrap() ); assert_eq!(ExtAwi::from(true), extawi!(umax: ..1)); assert_eq!(ExtAwi::from(u8::MAX), extawi!(umax: ..8)); @@ -189,14 +223,18 @@ fn from_primitive() { assert_eq!(ExtAwi::from(u32::MAX), extawi!(umax: ..32)); assert_eq!(ExtAwi::from(u64::MAX), extawi!(umax: ..64)); assert_eq!(ExtAwi::from(u128::MAX), extawi!(umax: ..128)); - assert_eq!(ExtAwi::from(usize::MAX), extawi!(umax: ..BITS).unwrap()); + assert_eq!( + ExtAwi::from(usize::MAX), + extawi!(umax: ..USIZE_BITS).unwrap() + ); assert_eq!(ExtAwi::from(i8::MAX), extawi!(imax: ..8)); assert_eq!(ExtAwi::from(i16::MAX), extawi!(imax: ..16)); assert_eq!(ExtAwi::from(i32::MAX), extawi!(imax: ..32)); assert_eq!(ExtAwi::from(i64::MAX), extawi!(imax: ..64)); assert_eq!(ExtAwi::from(i128::MAX), extawi!(imax: ..128)); assert_eq!( - ExtAwi::from_isize(isize::MAX), - extawi!(imax: ..BITS).unwrap() + ExtAwi::from(isize::MAX), + extawi!(imax: ..(isize::BITS as usize)).unwrap() ); + assert_eq!(ExtAwi::from(Digit::MAX), extawi!(umax: ..BITS).unwrap()); } diff --git a/testcrate/tests/serial.rs b/testcrate/tests/serial.rs index 68f32229..0c7bcc29 100644 --- a/testcrate/tests/serial.rs +++ b/testcrate/tests/serial.rs @@ -1,8 +1,21 @@ -use awint::{extawi, inlawi, Bits, ExtAwi, InlAwi, SerdeError::*, FP}; +use awint::{ + awint_internals::{bits_upper_bound, chars_upper_bound, USIZE_BITS}, + extawi, inlawi, Bits, ExtAwi, InlAwi, + SerdeError::*, + FP, +}; #[test] -fn string_max_fp() { - // tests the 4096 cap +fn string_overflows() { + assert_eq!(chars_upper_bound(1 << (USIZE_BITS - 1), 2), Err(Overflow)); + assert!(chars_upper_bound(1 << (USIZE_BITS - 2), 2).is_ok()); + assert!(chars_upper_bound(usize::MAX, 8).is_ok()); + assert_eq!(bits_upper_bound(1 << (USIZE_BITS - 1), 2), Err(Overflow)); + assert!(bits_upper_bound(1 << (USIZE_BITS - 2), 2).is_ok()); + assert!(bits_upper_bound((usize::MAX >> 2) / 6, 36).is_ok()); + assert_eq!(bits_upper_bound(usize::MAX / 6, 36), Err(Overflow)); + + // tests the 4096 fp cap assert_eq!( &format!("{:?}", FP::new(false, inlawi!(0), 4097).unwrap()), "" @@ -193,7 +206,7 @@ fn fmt_strings() { let inl_awi = inlawi!(0xfedcba9876543210u100); let ext_awi = extawi!(0xfedcba9876543210u100); let bits_awi = inlawi!(0xfedcba9876543210u100); - let bits = bits_awi.const_as_ref(); + let bits = bits_awi.as_ref(); fmt_test!(inl_awi ext_awi bits); assert_eq!(format!("{}", inlawi!(0u100)), "0x0_u100"); assert_eq!(