Skip to content

Commit aaa37d2

Browse files
authored
Merge pull request #277 from DaniPopes/no_alloc
feat: alloc feature
2 parents 9c44aa8 + 286cfae commit aaa37d2

12 files changed

+243
-133
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Added
1313

14-
- Support for `no_std` environments (#274)
14+
- Support for `no_std` environments ([#274])
15+
- `alloc` feature ([#277])
1516

1617
[#274]: https://github.com/recmo/uint/pulls/274
18+
[#277]: https://github.com/recmo/uint/pulls/277
1719

1820
## [1.9.0] - 2023-07-25
1921

Cargo.toml

+8-6
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ arbitrary = { version = "1", optional = true, default-features = false }
5151
ark-ff-03 = { version = "0.3.0", package = "ark-ff", optional = true, default-features = false }
5252
ark-ff-04 = { version = "0.4.0", package = "ark-ff", optional = true, default-features = false }
5353
bn-rs = { version = "0.2", optional = true, default-features = true }
54-
fastrlp = { version = "0.3", optional = true, default-features = false }
54+
fastrlp = { version = "0.3", optional = true, default-features = false, features = ["alloc"] }
5555
num-bigint = { version = "0.4", optional = true, default-features = false }
5656
parity-scale-codec = { version = "3", optional = true, features = [
5757
"derive",
@@ -95,6 +95,7 @@ serde_json = "1.0"
9595
[features]
9696
default = ["std"]
9797
std = [
98+
"alloc",
9899
"alloy-rlp?/std",
99100
"ark-ff-03?/std",
100101
"ark-ff-04?/std",
@@ -110,22 +111,23 @@ std = [
110111
"valuable?/std",
111112
"zeroize?/std",
112113
]
114+
alloc = ["proptest?/alloc", "rand?/alloc", "serde?/alloc", "valuable?/alloc", "zeroize?/alloc"]
113115

114116
# nightly-only features
115117
nightly = []
116118
generic_const_exprs = ["nightly"]
117119

118120
# support
119-
alloy-rlp = ["dep:alloy-rlp"]
121+
alloy-rlp = ["dep:alloy-rlp", "alloc"]
120122
arbitrary = ["dep:arbitrary", "std"]
121123
ark-ff = ["dep:ark-ff-03"]
122124
ark-ff-04 = ["dep:ark-ff-04"]
123125
bn-rs = ["dep:bn-rs", "std"]
124-
fastrlp = ["dep:fastrlp"]
125-
num-bigint = ["dep:num-bigint"]
126-
parity-scale-codec = ["dep:parity-scale-codec"]
126+
fastrlp = ["dep:fastrlp", "alloc"]
127+
num-bigint = ["dep:num-bigint", "alloc"]
128+
parity-scale-codec = ["dep:parity-scale-codec", "alloc"]
127129
primitive-types = ["dep:primitive-types"]
128-
proptest = ["dep:proptest"]
130+
proptest = ["dep:proptest", "alloc"]
129131
pyo3 = ["dep:pyo3", "std"]
130132
quickcheck = ["dep:quickcheck", "std"]
131133
rand = ["dep:rand"]

src/add.rs

-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
5252
}
5353
}
5454

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

91-
#[allow(clippy::doc_markdown)]
9290
/// Calculates $\mod{\mathtt{self} - \mathtt{rhs}}_{2^{BITS}}$.
9391
///
9492
/// Returns a tuple of the subtraction along with a boolean indicating

src/algorithms/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod add;
77
pub mod div;
88
mod gcd;
99
mod mul;
10+
#[cfg(feature = "alloc")] // TODO: Make mul_redc alloc-free
1011
mod mul_redc;
1112
mod ops;
1213
mod shift;
@@ -16,10 +17,11 @@ pub use self::{
1617
div::div,
1718
gcd::{gcd, gcd_extended, inv_mod, LehmerMatrix},
1819
mul::{add_nx1, addmul, addmul_n, addmul_nx1, addmul_ref, submul_nx1},
19-
mul_redc::mul_redc,
2020
ops::{adc, sbb},
2121
shift::{shift_left_small, shift_right_small},
2222
};
23+
#[cfg(feature = "alloc")]
24+
pub use mul_redc::mul_redc;
2325

2426
trait DoubleWord<T>: Sized + Copy {
2527
fn join(high: T, low: T) -> Self;

src/base_convert.rs

+79-37
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use crate::Uint;
2-
use alloc::vec::Vec;
32
use core::fmt;
43

54
/// Error for [`from_base_le`][Uint::from_base_le] and
@@ -45,6 +44,7 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
4544
/// # Panics
4645
///
4746
/// Panics if the base is less than 2.
47+
#[inline]
4848
pub fn to_base_le(&self, base: u64) -> impl Iterator<Item = u64> {
4949
assert!(base > 1);
5050
SpigotLittle {
@@ -63,14 +63,51 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
6363
/// # Panics
6464
///
6565
/// Panics if the base is less than 2.
66+
#[inline]
67+
#[cfg(feature = "alloc")] // OPT: Find an allocation free method. Maybe extract from the top?
6668
pub fn to_base_be(&self, base: u64) -> impl Iterator<Item = u64> {
69+
struct OwnedVecIterator {
70+
vec: alloc::vec::Vec<u64>,
71+
}
72+
73+
impl Iterator for OwnedVecIterator {
74+
type Item = u64;
75+
76+
#[inline]
77+
fn next(&mut self) -> Option<Self::Item> {
78+
self.vec.pop()
79+
}
80+
}
81+
6782
assert!(base > 1);
68-
// OPT: Find an allocation free method. Maybe extract from the top?
6983
OwnedVecIterator {
7084
vec: self.to_base_le(base).collect(),
7185
}
7286
}
7387

88+
/// Adds a digit in base `base` to the number. This is used internally by
89+
/// [`Uint::from_base_le`] and [`Uint::from_base_be`].
90+
#[inline]
91+
fn add_digit(&mut self, digit: u64, base: u64) -> Result<(), BaseConvertError> {
92+
if digit >= base {
93+
return Err(BaseConvertError::InvalidDigit(digit, base));
94+
}
95+
// Multiply by base.
96+
// OPT: keep track of non-zero limbs and mul the minimum.
97+
let mut carry: u128 = u128::from(digit);
98+
#[allow(clippy::cast_possible_truncation)]
99+
for limb in &mut self.limbs {
100+
carry += u128::from(*limb) * u128::from(base);
101+
*limb = carry as u64;
102+
carry >>= 64;
103+
}
104+
if carry > 0 || (LIMBS != 0 && self.limbs[LIMBS - 1] > Self::MASK) {
105+
return Err(BaseConvertError::Overflow);
106+
}
107+
108+
Ok(())
109+
}
110+
74111
/// Constructs the [`Uint`] from digits in the base `base` in little-endian.
75112
///
76113
/// # Errors
@@ -79,14 +116,44 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
79116
/// * [`BaseConvertError::InvalidDigit`] if a digit is out of range.
80117
/// * [`BaseConvertError::Overflow`] if the number is too large to
81118
/// fit.
82-
pub fn from_base_le<I: IntoIterator<Item = u64>>(
119+
#[inline]
120+
pub fn from_base_le<I>(base: u64, digits: I) -> Result<Self, BaseConvertError>
121+
where
122+
I: IntoIterator<Item = u64>,
123+
{
124+
if base < 2 {
125+
return Err(BaseConvertError::InvalidBase(base));
126+
}
127+
128+
let mut tail = digits.into_iter();
129+
match tail.next() {
130+
Some(digit) => Self::from_base_le_recurse(digit, base, &mut tail),
131+
None => Ok(Self::ZERO),
132+
}
133+
}
134+
135+
/// This is the recursive part of [`Uint::from_base_le`].
136+
///
137+
/// We drain the iterator via the recursive calls, and then perform the
138+
/// same construction loop as [`Uint::from_base_be`] while exiting the
139+
/// recursive callstack.
140+
#[inline]
141+
fn from_base_le_recurse<I: Iterator<Item = u64>>(
142+
digit: u64,
83143
base: u64,
84-
digits: I,
144+
tail: &mut I,
85145
) -> Result<Self, BaseConvertError> {
86-
// TODO: Do not allocate.
87-
let mut digits: Vec<_> = digits.into_iter().collect();
88-
digits.reverse();
89-
Self::from_base_be(base, digits)
146+
if digit > base {
147+
return Err(BaseConvertError::InvalidDigit(digit, base));
148+
}
149+
150+
let mut acc = match tail.next() {
151+
Some(digit) => Self::from_base_le_recurse::<I>(digit, base, tail)?,
152+
None => Self::ZERO,
153+
};
154+
155+
acc.add_digit(digit, base)?;
156+
Ok(acc)
90157
}
91158

92159
/// Constructs the [`Uint`] from digits in the base `base` in big-endian.
@@ -108,23 +175,10 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
108175
if base < 2 {
109176
return Err(BaseConvertError::InvalidBase(base));
110177
}
178+
111179
let mut result = Self::ZERO;
112180
for digit in digits {
113-
if digit >= base {
114-
return Err(BaseConvertError::InvalidDigit(digit, base));
115-
}
116-
// Multiply by base.
117-
// OPT: keep track of non-zero limbs and mul the minimum.
118-
let mut carry: u128 = u128::from(digit);
119-
#[allow(clippy::cast_possible_truncation)]
120-
for limb in &mut result.limbs {
121-
carry += u128::from(*limb) * u128::from(base);
122-
*limb = carry as u64;
123-
carry >>= 64;
124-
}
125-
if carry > 0 || (LIMBS != 0 && result.limbs[LIMBS - 1] > Self::MASK) {
126-
return Err(BaseConvertError::Overflow);
127-
}
181+
result.add_digit(digit, base)?;
128182
}
129183

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

196+
#[inline]
142197
#[allow(clippy::cast_possible_truncation)] // Doesn't truncate
143198
fn next(&mut self) -> Option<Self::Item> {
144199
// Knuth Algorithm S.
@@ -147,8 +202,7 @@ impl<const LIMBS: usize> Iterator for SpigotLittle<LIMBS> {
147202
// OPT: If we keep track of leading zero limbs we can half iterations.
148203
for limb in self.limbs.iter_mut().rev() {
149204
zero |= *limb;
150-
remainder <<= 64;
151-
remainder |= u128::from(*limb);
205+
remainder = (remainder << 64) | u128::from(*limb);
152206
*limb = (remainder / u128::from(self.base)) as u64;
153207
remainder %= u128::from(self.base);
154208
}
@@ -160,18 +214,6 @@ impl<const LIMBS: usize> Iterator for SpigotLittle<LIMBS> {
160214
}
161215
}
162216

163-
struct OwnedVecIterator {
164-
vec: Vec<u64>,
165-
}
166-
167-
impl Iterator for OwnedVecIterator {
168-
type Item = u64;
169-
170-
fn next(&mut self) -> Option<Self::Item> {
171-
self.vec.pop()
172-
}
173-
}
174-
175217
#[cfg(test)]
176218
#[allow(clippy::unreadable_literal)]
177219
#[allow(clippy::zero_prefixed_literal)]

src/bit_arr.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use crate::{ParseError, Uint};
2-
use alloc::{borrow::Cow, vec::Vec};
32
use core::{
43
ops::{
54
BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Index, Not, Shl, ShlAssign,
@@ -8,6 +7,9 @@ use core::{
87
str::FromStr,
98
};
109

10+
#[cfg(feature = "alloc")]
11+
use alloc::{borrow::Cow, vec::Vec};
12+
1113
/// A newtype wrapper around [`Uint`] that restricts operations to those
1214
/// relevant for bit arrays.
1315
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
@@ -206,11 +208,14 @@ impl<const BITS: usize, const LIMBS: usize> Bits<BITS, LIMBS> {
206208
forward! {
207209
fn reverse_bits(self) -> Self;
208210
}
211+
#[cfg(feature = "alloc")]
209212
forward! {
210213
fn as_le_bytes(&self) -> Cow<'_, [u8]>;
214+
fn to_be_bytes_vec(&self) -> Vec<u8>;
215+
}
216+
forward! {
211217
fn to_le_bytes<const BYTES: usize>(&self) -> [u8; BYTES];
212218
fn to_be_bytes<const BYTES: usize>(&self) -> [u8; BYTES];
213-
fn to_be_bytes_vec(&self) -> Vec<u8>;
214219
fn leading_zeros(&self) -> usize;
215220
fn leading_ones(&self) -> usize;
216221
fn trailing_zeros(&self) -> usize;

src/bits.rs

-2
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
248248
}
249249
}
250250

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

312-
#[allow(clippy::doc_markdown)]
313311
/// Left shift by `rhs` bits.
314312
///
315313
/// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$.

0 commit comments

Comments
 (0)