Skip to content

Commit

Permalink
Merge branch 'main' into prestwich/shift
Browse files Browse the repository at this point in the history
  • Loading branch information
prestwich authored Oct 13, 2023
2 parents 1f8c921 + 0fdcd4d commit 17f7300
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 62 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `U768` alias ([#310])
- `bytemuck` feature ([#292])
- `Uint::is_zero() -> bool` ([#296])
- `num-traits` features ([#298])
- Improve `add` and `sub` performance ([#316])
- Make `add` and `sub` functions `const` ([#324])
- `U768` alias ([#310])
- Improved `add` and `sub` performance ([#316])
- Made `add` and `sub` functions `const` ([#324])
- Made `{from,to}_{b,l}e_bytes` `const` ([#329])

[#292]: https://github.com/recmo/uint/pulls/292
[#296]: https://github.com/recmo/uint/pulls/296
[#298]: https://github.com/recmo/uint/pulls/298
[#310]: https://github.com/recmo/uint/pulls/310
[#316]: https://github.com/recmo/uint/pulls/316
[#324]: https://github.com/recmo/uint/pulls/324
[#329]: https://github.com/recmo/uint/pulls/329

### Fixed

Expand Down
241 changes: 182 additions & 59 deletions src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,27 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// [#60551]: https://github.com/rust-lang/rust/issues/60551
#[inline]
#[must_use]
pub fn to_le_bytes<const BYTES: usize>(&self) -> [u8; BYTES] {
pub const fn to_le_bytes<const BYTES: usize>(&self) -> [u8; BYTES] {
// TODO: Use a `const {}` block for this assertion
assert_eq!(BYTES, Self::BYTES, "BYTES must be equal to Self::BYTES");

let mut bytes = [0; BYTES];
assert!(BYTES == Self::BYTES, "BYTES must be equal to Self::BYTES");

// Specialized impl
#[cfg(target_endian = "little")]
bytes.copy_from_slice(self.as_le_slice());
// SAFETY: BYTES == Self::BYTES == self.as_le_slice().len()
return unsafe { *self.as_le_slice().as_ptr().cast() };

// Generic impl
#[cfg(target_endian = "big")]
for (chunk, limb) in bytes.chunks_mut(8).zip(self.as_limbs()) {
chunk.copy_from_slice(&limb.to_le_bytes());
{
let mut limbs = self.limbs;
let mut i = 0;
while i < LIMBS {
limbs[i] = limbs[i].to_le();
i += 1;
}
// SAFETY: BYTES <= LIMBS * 8
unsafe { *limbs.as_ptr().cast() }
}

bytes
}

/// Converts the [`Uint`] to a little-endian byte vector of size exactly
Expand Down Expand Up @@ -144,9 +150,20 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
/// [#60551]: https://github.com/rust-lang/rust/issues/60551
#[must_use]
#[inline]
pub fn to_be_bytes<const BYTES: usize>(&self) -> [u8; BYTES] {
let mut bytes = self.to_le_bytes();
bytes.reverse();
pub const fn to_be_bytes<const BYTES: usize>(&self) -> [u8; BYTES] {
let mut bytes = self.to_le_bytes::<BYTES>();

// bytes.reverse()
let len = bytes.len();
let half_len = len / 2;
let mut i = 0;
while i < half_len {
let tmp = bytes[i];
bytes[i] = bytes[len - 1 - i];
bytes[len - 1 - i] = tmp;
i += 1;
}

bytes
}

Expand Down Expand Up @@ -175,57 +192,85 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
bytes
}

/// Creates a new integer from a little endian stream of bytes.
/// Converts a big-endian byte array of size exactly
/// [`Self::BYTES`] to [`Uint`].
///
/// # Panics
///
/// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`].
/// Ideally this would be a compile time error, but this is blocked by
/// Rust issue [#60551].
///
/// [#60551]: https://github.com/rust-lang/rust/issues/60551
///
/// Panics if the value is too large for the bit-size of the Uint.
#[must_use]
#[allow(clippy::cast_lossless)]
#[track_caller]
#[inline]
fn try_from_le_byte_iter<I>(iter: I) -> Option<Self>
where
I: Iterator<Item = u8>,
{
let mut limbs = [0; LIMBS];
for (i, byte) in iter.enumerate() {
if byte == 0 {
continue;
}
let limb_index = i / 8;
if limb_index >= Self::LIMBS {
return None;
pub const fn from_be_bytes<const BYTES: usize>(bytes: [u8; BYTES]) -> Self {
// TODO: Use a `const {}` block for this assertion
assert!(BYTES == Self::BYTES, "BYTES must be equal to Self::BYTES");
if BYTES % 8 == 0 {
// Optimized implementation for full-limb types.
let mut limbs = [0; LIMBS];
let end = bytes.as_ptr_range().end;
let mut i = 0;
while i < LIMBS {
limbs[i] = u64::from_be_bytes(unsafe { *end.sub((i + 1) * 8).cast() });
i += 1;
}
let byte_index = i % 8;
limbs[limb_index] += (byte as u64) << (byte_index * 8);
}
if Self::LIMBS > 0 && limbs[Self::LIMBS - 1] > Self::MASK {
return None;
Self::from_limbs(limbs)
} else {
Self::from_be_slice(&bytes)
}
Some(Self::from_limbs(limbs))
}

/// Creates a new integer from a big endian slice of bytes.
///
/// The slice is interpreted as a big endian number. Leading zeros
/// are ignored. The slice can be any length.
///
/// Returns [`None`] if the value is larger than fits the [`Uint`].
/// # Panics
///
/// Panics if the value is larger than fits the [`Uint`].
#[must_use]
#[track_caller]
#[inline]
pub fn try_from_be_slice(bytes: &[u8]) -> Option<Self> {
Self::try_from_le_byte_iter(bytes.iter().copied().rev())
pub const fn from_be_slice(bytes: &[u8]) -> Self {
match Self::try_from_be_slice(bytes) {
Some(value) => value,
None => panic!("Value too large for Uint"),
}
}

/// Creates a new integer from a little endian slice of bytes.
/// Creates a new integer from a big endian slice of bytes.
///
/// The slice is interpreted as a little endian number. Leading zeros
/// The slice is interpreted as a big endian number. Leading zeros
/// are ignored. The slice can be any length.
///
/// Returns [`None`] if the value is larger than fits the [`Uint`].
#[must_use]
#[inline]
pub fn try_from_le_slice(bytes: &[u8]) -> Option<Self> {
Self::try_from_le_byte_iter(bytes.iter().copied())
pub const fn try_from_be_slice(bytes: &[u8]) -> Option<Self> {
if bytes.len() > Self::BYTES {
return None;
}

let mut limbs = [0; LIMBS];
let mut i = 0;
let mut c = bytes.len();
while i < bytes.len() {
c -= 1;
limbs[i / 8] += (bytes[c] as u64) << ((i % 8) * 8);
i += 1;
}
if Self::LIMBS > 0 && limbs[Self::LIMBS - 1] > Self::MASK {
return None;
}
Some(Self::from_limbs(limbs))
}

/// Converts a big-endian byte array of size exactly
/// Converts a little-endian byte array of size exactly
/// [`Self::BYTES`] to [`Uint`].
///
/// # Panics
Expand All @@ -240,42 +285,64 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[must_use]
#[track_caller]
#[inline]
pub fn from_be_bytes<const BYTES: usize>(bytes: [u8; BYTES]) -> Self {
pub const fn from_le_bytes<const BYTES: usize>(bytes: [u8; BYTES]) -> Self {
// TODO: Use a `const {}` block for this assertion
assert_eq!(BYTES, Self::BYTES, "BYTES must be equal to Self::BYTES");

assert!(BYTES == Self::BYTES, "BYTES must be equal to Self::BYTES");
if BYTES % 8 == 0 {
// Optimized implementation for full-limb types.
let mut limbs = [0_u64; LIMBS];
for (limb, bytes) in limbs.iter_mut().zip(bytes.rchunks_exact(8)) {
*limb = u64::from_be_bytes(bytes.try_into().unwrap());
let mut limbs = [0; LIMBS];
let mut i = 0;
while i < LIMBS {
limbs[i] = u64::from_le_bytes(unsafe { *bytes.as_ptr().add(i * 8).cast() });
i += 1;
}
Self::from_limbs(limbs)
} else {
Self::try_from_be_slice(&bytes).unwrap()
Self::from_le_slice(&bytes)
}
}

/// Converts a little-endian byte array of size exactly
/// [`Self::BYTES`] to [`Uint`].
/// Creates a new integer from a little endian slice of bytes.
///
/// The slice is interpreted as a little endian number. Leading zeros
/// are ignored. The slice can be any length.
///
/// # Panics
///
/// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`].
/// Ideally this would be a compile time error, but this is blocked by
/// Rust issue [#60551].
/// Panics if the value is larger than fits the [`Uint`].
#[must_use]
#[track_caller]
#[inline]
pub const fn from_le_slice(bytes: &[u8]) -> Self {
match Self::try_from_le_slice(bytes) {
Some(value) => value,
None => panic!("Value too large for Uint"),
}
}

/// Creates a new integer from a little endian slice of bytes.
///
/// [#60551]: https://github.com/rust-lang/rust/issues/60551
/// The slice is interpreted as a little endian number. Leading zeros
/// are ignored. The slice can be any length.
///
/// Panics if the value is too large for the bit-size of the Uint.
/// Returns [`None`] if the value is larger than fits the [`Uint`].
#[must_use]
#[track_caller]
#[inline]
pub fn from_le_bytes<const BYTES: usize>(bytes: [u8; BYTES]) -> Self {
// TODO: Use a `const {}` block for this assertion
assert_eq!(BYTES, Self::BYTES, "BYTES must be equal to Self::BYTES");
pub const fn try_from_le_slice(bytes: &[u8]) -> Option<Self> {
if bytes.len() / 8 > Self::LIMBS {
return None;
}

Self::try_from_le_slice(&bytes).expect("Value too large for Uint")
let mut limbs = [0; LIMBS];
let mut i = 0;
while i < bytes.len() {
limbs[i / 8] += (bytes[i] as u64) << ((i % 8) * 8);
i += 1;
}
if Self::LIMBS > 0 && limbs[Self::LIMBS - 1] > Self::MASK {
return None;
}
Some(Self::from_limbs(limbs))
}
}

Expand Down Expand Up @@ -311,6 +378,62 @@ mod tests {
const KBE: [u8; 9] = [0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78];
const KLE: [u8; 9] = [0x78, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12];

#[test]
const fn const_from_to_bytes() {
const NL: [u64; 2] = N.limbs;
assert!(matches!(Uint::<128, 2>::from_be_bytes(BE).limbs, NL));
assert!(matches!(Uint::<128, 2>::from_le_bytes(LE).limbs, NL));
assert!(matches!(N.to_be_bytes::<{ BE.len() }>(), BE));
assert!(matches!(N.to_le_bytes::<{ LE.len() }>(), LE));

const KL: [u64; 2] = K.limbs;
assert!(matches!(Uint::<72, 2>::from_be_bytes(KBE).limbs, KL));
assert!(matches!(Uint::<72, 2>::from_le_bytes(KLE).limbs, KL));
assert!(matches!(K.to_be_bytes::<{ KBE.len() }>(), KBE));
assert!(matches!(K.to_le_bytes::<{ KLE.len() }>(), KLE));

assert!(matches!(Uint::<0, 0>::ZERO.to_be_bytes::<0>(), []));
assert!(matches!(Uint::<1, 1>::ZERO.to_be_bytes::<1>(), [0]));
assert!(matches!(
Uint::<1, 1>::from_limbs([1]).to_be_bytes::<1>(),
[1]
));
assert!(matches!(
Uint::<16, 1>::from_limbs([0x1234]).to_be_bytes::<2>(),
[0x12, 0x34]
));

assert!(matches!(Uint::<0, 0>::ZERO.to_be_bytes::<0>(), []));
assert!(matches!(Uint::<0, 0>::ZERO.to_le_bytes::<0>(), []));
assert!(matches!(Uint::<1, 1>::ZERO.to_be_bytes::<1>(), [0]));
assert!(matches!(Uint::<1, 1>::ZERO.to_le_bytes::<1>(), [0]));
assert!(matches!(
Uint::<1, 1>::from_limbs([1]).to_be_bytes::<1>(),
[1]
));
assert!(matches!(
Uint::<1, 1>::from_limbs([1]).to_le_bytes::<1>(),
[1]
));
assert!(matches!(
Uint::<16, 1>::from_limbs([0x1234]).to_be_bytes::<2>(),
[0x12, 0x34]
));
assert!(matches!(
Uint::<16, 1>::from_limbs([0x1234]).to_le_bytes::<2>(),
[0x34, 0x12]
));

assert!(matches!(
Uint::<63, 1>::from_limbs([0x010203]).to_be_bytes::<8>(),
[0, 0, 0, 0, 0, 1, 2, 3]
));
assert!(matches!(
Uint::<63, 1>::from_limbs([0x010203]).to_le_bytes::<8>(),
[3, 2, 1, 0, 0, 0, 0, 0]
));
}

#[test]
fn test_from_bytes() {
assert_eq!(Uint::<0, 0>::from_be_bytes([]), Uint::ZERO);
Expand Down

0 comments on commit 17f7300

Please sign in to comment.