Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: alloc feature #277

Merged
merged 4 commits into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Support for `no_std` environments (#274)
- Support for `no_std` environments ([#274])
- `alloc` feature ([#277])

[#274]: https://github.com/recmo/uint/pulls/274
[#277]: https://github.com/recmo/uint/pulls/277

## [1.9.0] - 2023-07-25

Expand Down
14 changes: 8 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ arbitrary = { version = "1", optional = true, default-features = false }
ark-ff-03 = { version = "0.3.0", package = "ark-ff", optional = true, default-features = false }
ark-ff-04 = { version = "0.4.0", package = "ark-ff", optional = true, default-features = false }
bn-rs = { version = "0.2", optional = true, default-features = true }
fastrlp = { version = "0.3", optional = true, default-features = false }
fastrlp = { version = "0.3", optional = true, default-features = false, features = ["alloc"] }
num-bigint = { version = "0.4", optional = true, default-features = false }
parity-scale-codec = { version = "3", optional = true, features = [
"derive",
Expand Down Expand Up @@ -95,6 +95,7 @@ serde_json = "1.0"
[features]
default = ["std"]
std = [
"alloc",
"alloy-rlp?/std",
"ark-ff-03?/std",
"ark-ff-04?/std",
Expand All @@ -110,22 +111,23 @@ std = [
"valuable?/std",
"zeroize?/std",
]
alloc = ["proptest?/alloc", "rand?/alloc", "serde?/alloc", "valuable?/alloc", "zeroize?/alloc"]

# nightly-only features
nightly = []
generic_const_exprs = ["nightly"]

# support
alloy-rlp = ["dep:alloy-rlp"]
alloy-rlp = ["dep:alloy-rlp", "alloc"]
arbitrary = ["dep:arbitrary", "std"]
ark-ff = ["dep:ark-ff-03"]
ark-ff-04 = ["dep:ark-ff-04"]
bn-rs = ["dep:bn-rs", "std"]
fastrlp = ["dep:fastrlp"]
num-bigint = ["dep:num-bigint"]
parity-scale-codec = ["dep:parity-scale-codec"]
fastrlp = ["dep:fastrlp", "alloc"]
num-bigint = ["dep:num-bigint", "alloc"]
parity-scale-codec = ["dep:parity-scale-codec", "alloc"]
primitive-types = ["dep:primitive-types"]
proptest = ["dep:proptest"]
proptest = ["dep:proptest", "alloc"]
pyo3 = ["dep:pyo3", "std"]
quickcheck = ["dep:quickcheck", "std"]
rand = ["dep:rand"]
Expand Down
2 changes: 0 additions & 2 deletions src/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
}
}

#[allow(clippy::doc_markdown)]
/// Calculates $\mod{\mathtt{self} + \mathtt{rhs}}_{2^{BITS}}$.
///
/// Returns a tuple of the addition along with a boolean indicating whether
Expand Down Expand Up @@ -88,7 +87,6 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
Self::ZERO.overflowing_sub(self)
}

#[allow(clippy::doc_markdown)]
/// Calculates $\mod{\mathtt{self} - \mathtt{rhs}}_{2^{BITS}}$.
///
/// Returns a tuple of the subtraction along with a boolean indicating
Expand Down
4 changes: 3 additions & 1 deletion src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod add;
pub mod div;
mod gcd;
mod mul;
#[cfg(feature = "alloc")] // TODO: Make mul_redc alloc-free
mod mul_redc;
mod ops;
mod shift;
Expand All @@ -16,10 +17,11 @@ pub use self::{
div::div,
gcd::{gcd, gcd_extended, inv_mod, LehmerMatrix},
mul::{add_nx1, addmul, addmul_n, addmul_nx1, addmul_ref, submul_nx1},
mul_redc::mul_redc,
ops::{adc, sbb},
shift::{shift_left_small, shift_right_small},
};
#[cfg(feature = "alloc")]
pub use mul_redc::mul_redc;

trait DoubleWord<T>: Sized + Copy {
fn join(high: T, low: T) -> Self;
Expand Down
116 changes: 79 additions & 37 deletions src/base_convert.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::Uint;
use alloc::vec::Vec;
use core::fmt;

/// Error for [`from_base_le`][Uint::from_base_le] and
Expand Down Expand Up @@ -45,6 +44,7 @@
/// # Panics
///
/// Panics if the base is less than 2.
#[inline]
pub fn to_base_le(&self, base: u64) -> impl Iterator<Item = u64> {
assert!(base > 1);
SpigotLittle {
Expand All @@ -63,14 +63,51 @@
/// # Panics
///
/// Panics if the base is less than 2.
#[inline]
#[cfg(feature = "alloc")] // OPT: Find an allocation free method. Maybe extract from the top?
pub fn to_base_be(&self, base: u64) -> impl Iterator<Item = u64> {
struct OwnedVecIterator {
vec: alloc::vec::Vec<u64>,
}

impl Iterator for OwnedVecIterator {
type Item = u64;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.vec.pop()
}
}

assert!(base > 1);
// OPT: Find an allocation free method. Maybe extract from the top?
OwnedVecIterator {
vec: self.to_base_le(base).collect(),
}
}

/// Adds a digit in base `base` to the number. This is used internally by
/// [`Uint::from_base_le`] and [`Uint::from_base_be`].
#[inline]
fn add_digit(&mut self, digit: u64, base: u64) -> Result<(), BaseConvertError> {
if digit >= base {
return Err(BaseConvertError::InvalidDigit(digit, base));

Check warning on line 93 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L93

Added line #L93 was not covered by tests
}
// Multiply by base.
// OPT: keep track of non-zero limbs and mul the minimum.
let mut carry: u128 = u128::from(digit);
#[allow(clippy::cast_possible_truncation)]
for limb in &mut self.limbs {
carry += u128::from(*limb) * u128::from(base);
*limb = carry as u64;
carry >>= 64;
}
if carry > 0 || (LIMBS != 0 && self.limbs[LIMBS - 1] > Self::MASK) {
return Err(BaseConvertError::Overflow);
}

Ok(())
}

/// Constructs the [`Uint`] from digits in the base `base` in little-endian.
///
/// # Errors
Expand All @@ -79,14 +116,44 @@
/// * [`BaseConvertError::InvalidDigit`] if a digit is out of range.
/// * [`BaseConvertError::Overflow`] if the number is too large to
/// fit.
pub fn from_base_le<I: IntoIterator<Item = u64>>(
#[inline]
pub fn from_base_le<I>(base: u64, digits: I) -> Result<Self, BaseConvertError>
where
I: IntoIterator<Item = u64>,
{
if base < 2 {
return Err(BaseConvertError::InvalidBase(base));

Check warning on line 125 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L125

Added line #L125 was not covered by tests
}

let mut tail = digits.into_iter();
match tail.next() {
Some(digit) => Self::from_base_le_recurse(digit, base, &mut tail),
None => Ok(Self::ZERO),

Check warning on line 131 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L131

Added line #L131 was not covered by tests
}
}

/// This is the recursive part of [`Uint::from_base_le`].
///
/// We drain the iterator via the recursive calls, and then perform the
/// same construction loop as [`Uint::from_base_be`] while exiting the
/// recursive callstack.
#[inline]
fn from_base_le_recurse<I: Iterator<Item = u64>>(
digit: u64,
base: u64,
digits: I,
tail: &mut I,
) -> Result<Self, BaseConvertError> {
// TODO: Do not allocate.
let mut digits: Vec<_> = digits.into_iter().collect();
digits.reverse();
Self::from_base_be(base, digits)
if digit > base {
return Err(BaseConvertError::InvalidDigit(digit, base));

Check warning on line 147 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L147

Added line #L147 was not covered by tests
}

let mut acc = match tail.next() {
Some(digit) => Self::from_base_le_recurse::<I>(digit, base, tail)?,
None => Self::ZERO,
};

acc.add_digit(digit, base)?;
Ok(acc)
}

/// Constructs the [`Uint`] from digits in the base `base` in big-endian.
Expand All @@ -108,23 +175,10 @@
if base < 2 {
return Err(BaseConvertError::InvalidBase(base));
}

let mut result = Self::ZERO;
for digit in digits {
if digit >= base {
return Err(BaseConvertError::InvalidDigit(digit, base));
}
// Multiply by base.
// OPT: keep track of non-zero limbs and mul the minimum.
let mut carry: u128 = u128::from(digit);
#[allow(clippy::cast_possible_truncation)]
for limb in &mut result.limbs {
carry += u128::from(*limb) * u128::from(base);
*limb = carry as u64;
carry >>= 64;
}
if carry > 0 || (LIMBS != 0 && result.limbs[LIMBS - 1] > Self::MASK) {
return Err(BaseConvertError::Overflow);
}
result.add_digit(digit, base)?;
}

Ok(result)
Expand All @@ -139,6 +193,7 @@
impl<const LIMBS: usize> Iterator for SpigotLittle<LIMBS> {
type Item = u64;

#[inline]
#[allow(clippy::cast_possible_truncation)] // Doesn't truncate
fn next(&mut self) -> Option<Self::Item> {
// Knuth Algorithm S.
Expand All @@ -147,8 +202,7 @@
// OPT: If we keep track of leading zero limbs we can half iterations.
for limb in self.limbs.iter_mut().rev() {
zero |= *limb;
remainder <<= 64;
remainder |= u128::from(*limb);
remainder = (remainder << 64) | u128::from(*limb);
*limb = (remainder / u128::from(self.base)) as u64;
remainder %= u128::from(self.base);
}
Expand All @@ -160,18 +214,6 @@
}
}

struct OwnedVecIterator {
vec: Vec<u64>,
}

impl Iterator for OwnedVecIterator {
type Item = u64;

fn next(&mut self) -> Option<Self::Item> {
self.vec.pop()
}
}

#[cfg(test)]
#[allow(clippy::unreadable_literal)]
#[allow(clippy::zero_prefixed_literal)]
Expand Down
9 changes: 7 additions & 2 deletions src/bit_arr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::{ParseError, Uint};
use alloc::{borrow::Cow, vec::Vec};
use core::{
ops::{
BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Index, Not, Shl, ShlAssign,
Expand All @@ -8,6 +7,9 @@ use core::{
str::FromStr,
};

#[cfg(feature = "alloc")]
use alloc::{borrow::Cow, vec::Vec};

/// A newtype wrapper around [`Uint`] that restricts operations to those
/// relevant for bit arrays.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
Expand Down Expand Up @@ -206,11 +208,14 @@ impl<const BITS: usize, const LIMBS: usize> Bits<BITS, LIMBS> {
forward! {
fn reverse_bits(self) -> Self;
}
#[cfg(feature = "alloc")]
forward! {
fn as_le_bytes(&self) -> Cow<'_, [u8]>;
fn to_be_bytes_vec(&self) -> Vec<u8>;
}
forward! {
fn to_le_bytes<const BYTES: usize>(&self) -> [u8; BYTES];
fn to_be_bytes<const BYTES: usize>(&self) -> [u8; BYTES];
fn to_be_bytes_vec(&self) -> Vec<u8>;
fn leading_zeros(&self) -> usize;
fn leading_ones(&self) -> usize;
fn trailing_zeros(&self) -> usize;
Expand Down
2 changes: 0 additions & 2 deletions src/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
}
}

#[allow(clippy::doc_markdown)]
/// Left shift by `rhs` bits with overflow detection.
///
/// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$.
Expand Down Expand Up @@ -309,7 +308,6 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
(self, overflow)
}

#[allow(clippy::doc_markdown)]
/// Left shift by `rhs` bits.
///
/// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$.
Expand Down
Loading