diff options
Diffstat (limited to 'src/__support/math_extras.h')
-rw-r--r-- | src/__support/math_extras.h | 254 |
1 files changed, 109 insertions, 145 deletions
diff --git a/src/__support/math_extras.h b/src/__support/math_extras.h index 8ec30396ffdb..4bd871957406 100644 --- a/src/__support/math_extras.h +++ b/src/__support/math_extras.h @@ -10,187 +10,151 @@ #ifndef LLVM_LIBC_SRC___SUPPORT_MATH_EXTRAS_H #define LLVM_LIBC_SRC___SUPPORT_MATH_EXTRAS_H -#include "src/__support/CPP/limits.h" // CHAR_BIT -#include "src/__support/CPP/type_traits.h" // is_unsigned_v +#include "src/__support/CPP/bit.h" // countl_one, countr_zero +#include "src/__support/CPP/limits.h" // CHAR_BIT, numeric_limits +#include "src/__support/CPP/type_traits.h" // is_unsigned_v, is_constant_evaluated #include "src/__support/macros/attributes.h" // LIBC_INLINE -#include "src/__support/macros/config.h" // LIBC_HAS_BUILTIN namespace LIBC_NAMESPACE { // Create a bitmask with the count right-most bits set to 1, and all other bits // set to 0. Only unsigned types are allowed. template <typename T, size_t count> -LIBC_INLINE constexpr T mask_trailing_ones() { - static_assert(cpp::is_unsigned_v<T>); - constexpr unsigned t_bits = CHAR_BIT * sizeof(T); - static_assert(count <= t_bits && "Invalid bit index"); - // It's important not to initialize T with -1, since T may be BigInt which - // will take -1 as a uint64_t and only initialize the low 64 bits. - constexpr T all_zeroes(0); - constexpr T all_ones(~all_zeroes); // bitwise NOT performs integer promotion. - return count == 0 ? 0 : (all_ones >> (t_bits - count)); +LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T> +mask_trailing_ones() { + constexpr unsigned T_BITS = CHAR_BIT * sizeof(T); + static_assert(count <= T_BITS && "Invalid bit index"); + return count == 0 ? 0 : (T(-1) >> (T_BITS - count)); } // Create a bitmask with the count left-most bits set to 1, and all other bits // set to 0. Only unsigned types are allowed. template <typename T, size_t count> -LIBC_INLINE constexpr T mask_leading_ones() { - constexpr T mask(mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>()); - return T(~mask); // bitwise NOT performs integer promotion. +LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T> +mask_leading_ones() { + return T(~mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>()); } -// Add with carry -template <typename T> struct SumCarry { - T sum; - T carry; -}; - -// This version is always valid for constexpr. -template <typename T> -LIBC_INLINE constexpr cpp::enable_if_t< - cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, SumCarry<T>> -add_with_carry_const(T a, T b, T carry_in) { - T tmp = a + carry_in; - T sum = b + tmp; - T carry_out = (sum < b) + (tmp < a); - return {sum, carry_out}; -} - -// This version is not always valid for constepxr because it's overriden below -// if builtins are available. -template <typename T> -LIBC_INLINE cpp::enable_if_t<cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, - SumCarry<T>> -add_with_carry(T a, T b, T carry_in) { - return add_with_carry_const<T>(a, b, carry_in); -} - -#if LIBC_HAS_BUILTIN(__builtin_addc) -// https://clang.llvm.org/docs/LanguageExtensions.html#multiprecision-arithmetic-builtins - -template <> -LIBC_INLINE SumCarry<unsigned char> -add_with_carry<unsigned char>(unsigned char a, unsigned char b, - unsigned char carry_in) { - SumCarry<unsigned char> result{0, 0}; - result.sum = __builtin_addcb(a, b, carry_in, &result.carry); - return result; -} - -template <> -LIBC_INLINE SumCarry<unsigned short> -add_with_carry<unsigned short>(unsigned short a, unsigned short b, - unsigned short carry_in) { - SumCarry<unsigned short> result{0, 0}; - result.sum = __builtin_addcs(a, b, carry_in, &result.carry); - return result; +// Create a bitmask with the count right-most bits set to 0, and all other bits +// set to 1. Only unsigned types are allowed. +template <typename T, size_t count> +LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T> +mask_trailing_zeros() { + return mask_leading_ones<T, CHAR_BIT * sizeof(T) - count>(); } -template <> -LIBC_INLINE SumCarry<unsigned int> -add_with_carry<unsigned int>(unsigned int a, unsigned int b, - unsigned int carry_in) { - SumCarry<unsigned int> result{0, 0}; - result.sum = __builtin_addc(a, b, carry_in, &result.carry); - return result; +// Create a bitmask with the count left-most bits set to 0, and all other bits +// set to 1. Only unsigned types are allowed. +template <typename T, size_t count> +LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T> +mask_leading_zeros() { + return mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>(); } -template <> -LIBC_INLINE SumCarry<unsigned long> -add_with_carry<unsigned long>(unsigned long a, unsigned long b, - unsigned long carry_in) { - SumCarry<unsigned long> result{0, 0}; - result.sum = __builtin_addcl(a, b, carry_in, &result.carry); - return result; +// Returns whether 'a + b' overflows, the result is stored in 'res'. +template <typename T> +[[nodiscard]] LIBC_INLINE constexpr bool add_overflow(T a, T b, T &res) { + return __builtin_add_overflow(a, b, &res); } -template <> -LIBC_INLINE SumCarry<unsigned long long> -add_with_carry<unsigned long long>(unsigned long long a, unsigned long long b, - unsigned long long carry_in) { - SumCarry<unsigned long long> result{0, 0}; - result.sum = __builtin_addcll(a, b, carry_in, &result.carry); - return result; +// Returns whether 'a - b' overflows, the result is stored in 'res'. +template <typename T> +[[nodiscard]] LIBC_INLINE constexpr bool sub_overflow(T a, T b, T &res) { + return __builtin_sub_overflow(a, b, &res); } -#endif // LIBC_HAS_BUILTIN(__builtin_addc) - -// Subtract with borrow -template <typename T> struct DiffBorrow { - T diff; - T borrow; -}; +#define RETURN_IF(TYPE, BUILTIN) \ + if constexpr (cpp::is_same_v<T, TYPE>) \ + return BUILTIN(a, b, carry_in, carry_out); -// This version is always valid for constexpr. +// Returns the result of 'a + b' taking into account 'carry_in'. +// The carry out is stored in 'carry_out' it not 'nullptr', dropped otherwise. +// We keep the pass by pointer interface for consistency with the intrinsic. template <typename T> -LIBC_INLINE constexpr cpp::enable_if_t< - cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, DiffBorrow<T>> -sub_with_borrow_const(T a, T b, T borrow_in) { - T tmp = a - b; - T diff = tmp - borrow_in; - T borrow_out = (diff > tmp) + (tmp > a); - return {diff, borrow_out}; -} - -// This version is not always valid for constepxr because it's overriden below -// if builtins are available. +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T> +add_with_carry(T a, T b, T carry_in, T &carry_out) { + if constexpr (!cpp::is_constant_evaluated()) { +#if __has_builtin(__builtin_addcb) + RETURN_IF(unsigned char, __builtin_addcb) +#elif __has_builtin(__builtin_addcs) + RETURN_IF(unsigned short, __builtin_addcs) +#elif __has_builtin(__builtin_addc) + RETURN_IF(unsigned int, __builtin_addc) +#elif __has_builtin(__builtin_addcl) + RETURN_IF(unsigned long, __builtin_addcl) +#elif __has_builtin(__builtin_addcll) + RETURN_IF(unsigned long long, __builtin_addcll) +#endif + } + T sum = {}; + T carry1 = add_overflow(a, b, sum); + T carry2 = add_overflow(sum, carry_in, sum); + carry_out = carry1 | carry2; + return sum; +} + +// Returns the result of 'a - b' taking into account 'carry_in'. +// The carry out is stored in 'carry_out' it not 'nullptr', dropped otherwise. +// We keep the pass by pointer interface for consistency with the intrinsic. template <typename T> -LIBC_INLINE cpp::enable_if_t<cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, - DiffBorrow<T>> -sub_with_borrow(T a, T b, T borrow_in) { - return sub_with_borrow_const<T>(a, b, borrow_in); -} - -#if LIBC_HAS_BUILTIN(__builtin_subc) -// https://clang.llvm.org/docs/LanguageExtensions.html#multiprecision-arithmetic-builtins +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T> +sub_with_borrow(T a, T b, T carry_in, T &carry_out) { + if constexpr (!cpp::is_constant_evaluated()) { +#if __has_builtin(__builtin_subcb) + RETURN_IF(unsigned char, __builtin_subcb) +#elif __has_builtin(__builtin_subcs) + RETURN_IF(unsigned short, __builtin_subcs) +#elif __has_builtin(__builtin_subc) + RETURN_IF(unsigned int, __builtin_subc) +#elif __has_builtin(__builtin_subcl) + RETURN_IF(unsigned long, __builtin_subcl) +#elif __has_builtin(__builtin_subcll) + RETURN_IF(unsigned long long, __builtin_subcll) +#endif + } + T sub = {}; + T carry1 = sub_overflow(a, b, sub); + T carry2 = sub_overflow(sub, carry_in, sub); + carry_out = carry1 | carry2; + return sub; +} + +#undef RETURN_IF -template <> -LIBC_INLINE DiffBorrow<unsigned char> -sub_with_borrow<unsigned char>(unsigned char a, unsigned char b, - unsigned char borrow_in) { - DiffBorrow<unsigned char> result{0, 0}; - result.diff = __builtin_subcb(a, b, borrow_in, &result.borrow); - return result; +template <typename T> +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int> +first_leading_zero(T value) { + return value == cpp::numeric_limits<T>::max() ? 0 + : cpp::countl_one(value) + 1; } -template <> -LIBC_INLINE DiffBorrow<unsigned short> -sub_with_borrow<unsigned short>(unsigned short a, unsigned short b, - unsigned short borrow_in) { - DiffBorrow<unsigned short> result{0, 0}; - result.diff = __builtin_subcs(a, b, borrow_in, &result.borrow); - return result; +template <typename T> +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int> +first_leading_one(T value) { + return first_leading_zero(static_cast<T>(~value)); } -template <> -LIBC_INLINE DiffBorrow<unsigned int> -sub_with_borrow<unsigned int>(unsigned int a, unsigned int b, - unsigned int borrow_in) { - DiffBorrow<unsigned int> result{0, 0}; - result.diff = __builtin_subc(a, b, borrow_in, &result.borrow); - return result; +template <typename T> +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int> +first_trailing_zero(T value) { + return value == cpp::numeric_limits<T>::max() + ? 0 + : cpp::countr_zero(static_cast<T>(~value)) + 1; } -template <> -LIBC_INLINE DiffBorrow<unsigned long> -sub_with_borrow<unsigned long>(unsigned long a, unsigned long b, - unsigned long borrow_in) { - DiffBorrow<unsigned long> result{0, 0}; - result.diff = __builtin_subcl(a, b, borrow_in, &result.borrow); - return result; +template <typename T> +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int> +first_trailing_one(T value) { + return value == cpp::numeric_limits<T>::max() ? 0 + : cpp::countr_zero(value) + 1; } -template <> -LIBC_INLINE DiffBorrow<unsigned long long> -sub_with_borrow<unsigned long long>(unsigned long long a, unsigned long long b, - unsigned long long borrow_in) { - DiffBorrow<unsigned long long> result{0, 0}; - result.diff = __builtin_subcll(a, b, borrow_in, &result.borrow); - return result; +template <typename T> +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int> +count_zeros(T value) { + return cpp::popcount<T>(static_cast<T>(~value)); } -#endif // LIBC_HAS_BUILTIN(__builtin_subc) - } // namespace LIBC_NAMESPACE #endif // LLVM_LIBC_SRC___SUPPORT_MATH_EXTRAS_H |