use crate::Choice;
use core::hint::black_box;
macro_rules! bitle {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) | $y) & (($x ^ $y) | !($y.wrapping_sub($x)))) >> ($bits - 1)
};
}
macro_rules! bitborrow {
($x:expr, $y:expr, $result:expr, $bits:expr) => {
(((!$x) & $y) | (((!$x) | $y) & $result)) >> ($bits - 1)
};
}
macro_rules! bitlt {
($x:expr, $y:expr, $bits:expr) => {
$crate::primitives::bitborrow!($x, $y, ($x).wrapping_sub($y), $bits)
};
}
macro_rules! bitnz {
($value:expr, $bits:expr) => {
($value | $value.wrapping_neg()) >> ($bits - 1)
};
}
pub(crate) use {bitborrow, bitle, bitlt, bitnz};
macro_rules! comparison_primitives {
($ty:ty, $le:ident, $lt:ident, $nz:ident, $from_lsb:ident) => {
#[inline(always)]
pub(crate) const fn $le(lhs: $ty, rhs: $ty) -> Choice {
let bit = bitle!(lhs, rhs, <$ty>::BITS);
#[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
let bit = black_box(bit);
Choice::$from_lsb(bit)
}
#[cfg_attr(
all(target_arch = "arm", not(target_has_atomic = "ptr")),
inline(never)
)]
#[cfg_attr(
not(all(target_arch = "arm", not(target_has_atomic = "ptr"))),
inline(always)
)]
pub(crate) const fn $lt(lhs: $ty, rhs: $ty) -> Choice {
let bit = $crate::primitives::bitlt!(lhs, rhs, <$ty>::BITS);
#[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
let bit = black_box(bit);
Choice::$from_lsb(bit)
}
#[inline(always)]
pub(crate) const fn $nz(value: $ty) -> Choice {
let bit = bitnz!(value, <$ty>::BITS);
#[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
let bit = black_box(bit);
Choice::$from_lsb(bit)
}
};
}
comparison_primitives!(u32, le_u32, lt_u32, nz_u32, from_u32_lsb);
comparison_primitives!(u64, le_u64, lt_u64, nz_u64, from_u64_lsb);
macro_rules! borrowing_sub_primitives {
($ty:ty, $sub:ident, $sub_mask:ident, $sub_inner:ident, $from_lsb:ident) => {
#[inline]
#[must_use]
pub const fn $sub(lhs: $ty, rhs: $ty, borrow: Choice) -> ($ty, Choice) {
let (difference, borrow) = $sub_inner(lhs, rhs, borrow.to_u8() as $ty);
(difference, Choice::$from_lsb(borrow))
}
#[inline]
#[must_use]
pub const fn $sub_mask(lhs: $ty, rhs: $ty, borrow: $ty) -> ($ty, $ty) {
let (difference, borrow) = $sub_inner(lhs, rhs, borrow >> (<$ty>::BITS - 1));
(difference, borrow.wrapping_neg())
}
#[inline(always)]
const fn $sub_inner(lhs: $ty, rhs: $ty, borrow: $ty) -> ($ty, $ty) {
let difference = lhs.wrapping_sub(rhs).wrapping_sub(borrow);
let borrow = black_box(bitborrow!(lhs, rhs, difference, <$ty>::BITS));
(difference, borrow)
}
};
}
borrowing_sub_primitives!(
u32,
borrowing_sub_u32,
borrowing_sub_mask_u32,
borrowing_sub_inner_u32,
from_u32_lsb
);
borrowing_sub_primitives!(
u64,
borrowing_sub_u64,
borrowing_sub_mask_u64,
borrowing_sub_inner_u64,
from_u64_lsb
);
#[inline]
#[must_use]
pub const fn leading_zeros_u32(value: u32) -> u32 {
#[cfg(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_feature = "thumb2",
target_feature = "zbb"
))]
{
value.leading_zeros()
}
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_feature = "thumb2",
target_feature = "zbb"
)))]
{
leading_zeros_u32_soft(value)
}
}
#[inline]
#[must_use]
pub const fn trailing_zeros_u32(value: u32) -> u32 {
#[cfg(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_feature = "thumb2",
target_feature = "zbb"
))]
{
value.trailing_zeros()
}
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_feature = "thumb2",
target_feature = "zbb"
)))]
{
leading_zeros_u32_soft(value.reverse_bits())
}
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn leading_zeros_u64(value: u64) -> u32 {
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
{
value.leading_zeros()
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
{
let hi = (value >> 32) as u32;
let lo = value as u32;
let mask = Choice::from_u32_nz(hi).not().to_u32_mask();
leading_zeros_u32(hi).wrapping_add(mask & leading_zeros_u32(lo))
}
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn trailing_zeros_u64(value: u64) -> u32 {
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
{
value.trailing_zeros()
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
{
let hi = (value >> 32) as u32;
let lo = value as u32;
let mask = Choice::from_u32_nz(lo).not().to_u32_mask();
trailing_zeros_u32(lo).wrapping_add(mask & trailing_zeros_u32(hi))
}
}
#[cfg(any(
test,
not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_feature = "thumb2",
target_feature = "zbb"
))
))]
#[inline]
#[allow(clippy::arithmetic_side_effects, reason = "shift is between 1 and 16")]
const fn leading_zeros_u32_soft(mut value: u32) -> u32 {
let mut count = 0u32;
let mut shift = 16;
while shift != 0 {
let mask = Choice::from_u32_nz(value >> (u32::BITS - shift))
.not()
.to_u32_mask();
value ^= mask & (value ^ (value << shift));
count = count.wrapping_add(mask & shift);
shift >>= 1;
}
count.wrapping_add(Choice::from_u32_nz(value).not().to_u32_mask() & 1)
}
#[inline]
#[must_use]
pub const fn overflowing_add_u64(lhs: u64, rhs: u64) -> (u64, Choice) {
let sum = lhs.wrapping_add(rhs);
let carry = ((lhs & rhs) | ((lhs | rhs) & !sum)) >> (u64::BITS - 1);
(sum, Choice::from_u64_lsb(carry))
}
#[inline]
#[must_use]
pub const fn overflowing_add_u128(lhs: u128, rhs: u128) -> (u128, Choice) {
#[cfg(target_arch = "riscv32")]
{
overflowing_add_u128_halves(lhs, rhs)
}
#[cfg(not(target_arch = "riscv32"))]
{
let sum = lhs.wrapping_add(rhs);
let carry = ((lhs & rhs) | ((lhs | rhs) & !sum)) >> (u128::BITS - 1);
(sum, Choice::from_u128_lsb(carry))
}
}
#[cfg(any(target_arch = "riscv32", test))]
#[inline]
#[allow(clippy::cast_possible_truncation)]
const fn overflowing_add_u128_halves(lhs: u128, rhs: u128) -> (u128, Choice) {
let (lo, lo_carry) = overflowing_add_u64(lhs as u64, rhs as u64);
let (hi, hi_carry) = overflowing_add_u64((lhs >> 64) as u64, (rhs >> 64) as u64);
let (hi, carry) = overflowing_add_u64(hi, lo_carry.to_u8() as u64);
(((hi as u128) << 64) | lo as u128, hi_carry.or(carry))
}
#[cfg(test)]
mod tests {
use super::*;
const _: () = {
assert!(borrowing_sub_u32(0, 0, Choice::TRUE).0 == u32::MAX);
assert!(borrowing_sub_u32(0, 0, Choice::TRUE).1.to_bool());
assert!(borrowing_sub_u64(0, 0, Choice::TRUE).0 == u64::MAX);
assert!(borrowing_sub_u64(0, 0, Choice::TRUE).1.to_bool());
assert!(!borrowing_sub_u32(1, 0, Choice::TRUE).1.to_bool());
assert!(!borrowing_sub_u64(1, 0, Choice::TRUE).1.to_bool());
assert!(borrowing_sub_mask_u32(0, 0, u32::MAX).0 == u32::MAX);
assert!(borrowing_sub_mask_u32(0, 0, u32::MAX).1 == u32::MAX);
assert!(borrowing_sub_mask_u64(0, 0, u64::MAX).0 == u64::MAX);
assert!(borrowing_sub_mask_u64(0, 0, u64::MAX).1 == u64::MAX);
assert!(borrowing_sub_mask_u32(1, 0, u32::MAX).1 == 0);
assert!(borrowing_sub_mask_u64(1, 0, u64::MAX).1 == 0);
assert!(leading_zeros_u32(0) == 32);
assert!(trailing_zeros_u32(0) == 32);
assert!(leading_zeros_u64(0) == 64);
assert!(trailing_zeros_u64(0) == 64);
assert!(overflowing_add_u64(u64::MAX, 1).1.to_bool());
assert!(overflowing_add_u128(u128::MAX, 1).1.to_bool());
};
#[test]
fn zero_counts_u32() {
for bit in 0..u32::BITS {
for value in [0, 1 << bit, !(1 << bit), u32::MAX >> bit] {
assert_eq!(leading_zeros_u32(value), value.leading_zeros());
assert_eq!(trailing_zeros_u32(value), value.trailing_zeros());
assert_eq!(leading_zeros_u32_soft(value), value.leading_zeros());
assert_eq!(
leading_zeros_u32_soft(value.reverse_bits()),
value.trailing_zeros()
);
}
}
}
#[test]
fn zero_counts_u64() {
for bit in 0..u64::BITS {
for value in [0, 1 << bit, !(1 << bit), u64::MAX >> bit] {
assert_eq!(leading_zeros_u64(value), value.leading_zeros());
assert_eq!(trailing_zeros_u64(value), value.trailing_zeros());
}
}
}
macro_rules! subtraction_tests {
($test:ident, $ty:ty, $sub:ident, $sub_mask:ident) => {
#[test]
fn $test() {
let half = 1 << (<$ty>::BITS >> 1);
let high = 1 << (<$ty>::BITS - 1);
let values = [0, 1, half - 1, half, half + 1, high - 1, high, <$ty>::MAX];
for lhs in values {
for rhs in values {
for mask in [0, 1, high - 1, high, <$ty>::MAX - 1, <$ty>::MAX] {
let incoming = mask >> (<$ty>::BITS - 1) != 0;
let (expected, first_borrow) = lhs.overflowing_sub(rhs);
let (expected, second_borrow) =
expected.overflowing_sub(<$ty>::from(incoming));
let (difference, borrow) =
$sub(lhs, rhs, Choice::from_u8_lsb(u8::from(incoming)));
assert_eq!(
(difference, borrow.to_bool()),
(expected, first_borrow | second_borrow)
);
assert_eq!(
$sub_mask(lhs, rhs, mask),
(
expected,
<$ty>::from(first_borrow | second_borrow).wrapping_neg()
)
);
}
}
}
}
};
}
subtraction_tests!(sub_u32, u32, borrowing_sub_u32, borrowing_sub_mask_u32);
subtraction_tests!(sub_u64, u64, borrowing_sub_u64, borrowing_sub_mask_u64);
#[test]
fn sub_mask_propagates_borrow() {
let (lo, borrow) = borrowing_sub_mask_u32(0, 1, 0);
let (mid, borrow) = borrowing_sub_mask_u32(0, 0, borrow);
assert_eq!((lo, mid, borrow), (u32::MAX, u32::MAX, u32::MAX));
assert_eq!(borrowing_sub_mask_u32(1, 0, borrow), (0, 0));
let (lo, borrow) = borrowing_sub_mask_u64(0, 1, 0);
let (mid, borrow) = borrowing_sub_mask_u64(0, 0, borrow);
assert_eq!((lo, mid, borrow), (u64::MAX, u64::MAX, u64::MAX));
assert_eq!(borrowing_sub_mask_u64(1, 0, borrow), (0, 0));
}
macro_rules! addition_tests {
($test:ident, $ty:ty, $add:ident) => {
#[test]
fn $test() {
let half = 1 << (<$ty>::BITS >> 1);
let high = 1 << (<$ty>::BITS - 1);
let values = [0, 1, half - 1, half, half + 1, high - 1, high, <$ty>::MAX];
for lhs in values {
for rhs in values {
let (sum, carry) = $add(lhs, rhs);
assert_eq!((sum, carry.to_bool()), lhs.overflowing_add(rhs));
}
}
}
};
}
addition_tests!(add_u64, u64, overflowing_add_u64);
addition_tests!(add_u128, u128, overflowing_add_u128);
addition_tests!(add_u128_halves, u128, overflowing_add_u128_halves);
}