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

perf: use borrowing/carrying ops in add/sub, remove bound checks in shifts #366

Merged
merged 1 commit into from
Apr 20, 2024
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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Use borrowing/carrying ops in add/sub, remove bound checks in shifts ([#366])

### Fixed

- add `alloc` requirement to `num-traits` feature [#363]
- Add `alloc` requirement to `num-traits` feature [#363]

[#363]: https://github.com/recmo/uint/pull/363
[#366]: https://github.com/recmo/uint/pull/366

## [1.12.1] - 2024-03-12

Expand Down
35 changes: 22 additions & 13 deletions src/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,24 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[inline]
#[must_use]
pub const fn overflowing_add(mut self, rhs: Self) -> (Self, bool) {
// TODO: Replace with `u64::carrying_add` once stable.
#[inline]
const fn u64_carrying_add(lhs: u64, rhs: u64, carry: bool) -> (u64, bool) {
let (a, b) = lhs.overflowing_add(rhs);
let (c, d) = a.overflowing_add(carry as u64);
(c, b || d)
}

if BITS == 0 {
return (Self::ZERO, false);
}
let mut carry = 0_u128;
let mut carry = false;
let mut i = 0;
#[allow(clippy::cast_possible_truncation)] // Intentional
while i < LIMBS {
carry += self.limbs[i] as u128 + rhs.limbs[i] as u128;
self.limbs[i] = carry as u64;
carry >>= 64;
(self.limbs[i], carry) = u64_carrying_add(self.limbs[i], rhs.limbs[i], carry);
i += 1;
}
let overflow = carry != 0 || self.limbs[LIMBS - 1] > Self::MASK;
let overflow = carry || self.limbs[LIMBS - 1] > Self::MASK;
self.limbs[LIMBS - 1] &= Self::MASK;
(self, overflow)
}
Expand All @@ -93,20 +98,24 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[inline]
#[must_use]
pub const fn overflowing_sub(mut self, rhs: Self) -> (Self, bool) {
// TODO: Replace with `u64::borrowing_sub` once stable.
#[inline]
const fn u64_borrowing_sub(lhs: u64, rhs: u64, borrow: bool) -> (u64, bool) {
let (a, b) = lhs.overflowing_sub(rhs);
let (c, d) = a.overflowing_sub(borrow as u64);
(c, b || d)
}

if BITS == 0 {
return (Self::ZERO, false);
}
let mut carry = 0_i128;
let mut borrow = false;
let mut i = 0;
#[allow(clippy::cast_possible_truncation)] // Intentional
#[allow(clippy::cast_sign_loss)] // Intentional
while i < LIMBS {
carry += self.limbs[i] as i128 - rhs.limbs[i] as i128;
self.limbs[i] = carry as u64;
carry >>= 64;
(self.limbs[i], borrow) = u64_borrowing_sub(self.limbs[i], rhs.limbs[i], borrow);
i += 1;
}
let overflow = carry != 0 || self.limbs[LIMBS - 1] > Self::MASK;
let overflow = borrow || self.limbs[LIMBS - 1] > Self::MASK;
self.limbs[LIMBS - 1] &= Self::MASK;
(self, overflow)
}
Expand Down
1 change: 0 additions & 1 deletion src/algorithms/div/knuth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{
algorithms::{add::adc_n, mul::submul_nx1},
utils::{likely, unlikely},
};
use core::u64;

/// ⚠️ In-place Knuth normalized long division with reciprocals.
///
Expand Down
23 changes: 10 additions & 13 deletions src/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,10 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {

// Shift
for i in (limbs..LIMBS).rev() {
assume!(i >= limbs && i - limbs < LIMBS);
self.limbs[i] = self.limbs[i - limbs];
}
for i in 0..limbs {
self.limbs[i] = 0;
}
self.limbs[..limbs].fill(0);
self.limbs[LIMBS - 1] &= Self::MASK;
return (self, overflow);
}
Expand All @@ -294,13 +293,12 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {

// Shift
for i in (limbs + 1..LIMBS).rev() {
assume!(i - limbs < LIMBS && i - limbs - 1 < LIMBS);
self.limbs[i] = self.limbs[i - limbs] << bits;
self.limbs[i] |= self.limbs[i - limbs - 1] >> (64 - bits);
}
self.limbs[limbs] = self.limbs[0] << bits;
for i in 0..limbs {
self.limbs[i] = 0;
}
self.limbs[..limbs].fill(0);
self.limbs[LIMBS - 1] &= Self::MASK;
(self, overflow)
}
Expand Down Expand Up @@ -367,9 +365,7 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
for i in 0..(LIMBS - limbs) {
self.limbs[i] = self.limbs[i + limbs];
}
for i in (LIMBS - limbs)..LIMBS {
self.limbs[i] = 0;
}
self.limbs[LIMBS - limbs..].fill(0);
return (self, overflow);
}

Expand All @@ -378,13 +374,12 @@ impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {

// Shift
for i in 0..(LIMBS - limbs - 1) {
assume!(i + limbs < LIMBS && i + limbs + 1 < LIMBS);
self.limbs[i] = self.limbs[i + limbs] >> bits;
self.limbs[i] |= self.limbs[i + limbs + 1] << (64 - bits);
}
self.limbs[LIMBS - limbs - 1] = self.limbs[LIMBS - 1] >> bits;
for i in (LIMBS - limbs)..LIMBS {
self.limbs[i] = 0;
}
self.limbs[LIMBS - limbs..].fill(0);
(self, overflow)
}

Expand Down Expand Up @@ -630,6 +625,7 @@ macro_rules! impl_shift {
type Output = Self;

#[inline(always)]
#[allow(clippy::cast_possible_truncation)]
fn shl(self, rhs: $u) -> Self::Output {
self.wrapping_shl(rhs as usize)
}
Expand All @@ -639,8 +635,9 @@ macro_rules! impl_shift {
type Output = Self;

#[inline(always)]
#[allow(clippy::cast_possible_truncation)]
fn shr(self, rhs: $u) -> Self::Output {
self.wrapping_shr(rhs as usize)
self.wrapping_shr(rhs as usize)
}
}
};
Expand Down
1 change: 1 addition & 0 deletions src/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ macro_rules! to_int {
type Error = FromUintError<Self>;

#[inline]
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
fn try_from(value: &Uint<BITS, LIMBS>) -> Result<Self, Self::Error> {
const SIGNED: bool = <$int>::MIN != 0;
const CAPACITY: usize = if SIGNED { <$int>::BITS - 1 } else { <$int>::BITS } as usize;
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
clippy::unreadable_literal,
clippy::let_unit_value,
clippy::option_if_let_else,
clippy::cast_sign_loss,
clippy::cast_lossless,
)]
#![cfg_attr(
any(test, feature = "bench"),
Expand Down
24 changes: 24 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,30 @@ macro_rules! impl_bin_op {
};
}

macro_rules! assume {
($e:expr $(,)?) => {
if !$e {
debug_unreachable!(stringify!($e));
}
};

($e:expr, $($t:tt)+) => {
if !$e {
debug_unreachable!($($t)+);
}
};
}

macro_rules! debug_unreachable {
($($t:tt)*) => {
if cfg!(debug_assertions) {
unreachable!($($t)*);
} else {
unsafe { core::hint::unreachable_unchecked() };
}
};
}

#[cfg(test)]
mod tests {
// https://github.com/recmo/uint/issues/359
Expand Down
Loading