[reland][libc] Refactor BigInt (#87613)
This is a reland of #86137 with a fix for platforms / compiler that do not support trivially constructible int128 types.
This commit is contained in:
committed by
GitHub
parent
cca9115b1c
commit
71c3f5d617
@@ -1,6 +1,7 @@
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=fuzzer")
|
||||
add_custom_target(libc-fuzzer)
|
||||
|
||||
add_subdirectory(__support)
|
||||
# TODO(#85680): Re-enable math fuzzing after headers are sorted out
|
||||
# add_subdirectory(math)
|
||||
add_subdirectory(stdlib)
|
||||
|
||||
7
libc/fuzzing/__support/CMakeLists.txt
Normal file
7
libc/fuzzing/__support/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
add_libc_fuzzer(
|
||||
uint_fuzz
|
||||
SRCS
|
||||
uint_fuzz.cpp
|
||||
DEPENDS
|
||||
libc.src.__support.uint
|
||||
)
|
||||
70
libc/fuzzing/__support/uint_fuzz.cpp
Normal file
70
libc/fuzzing/__support/uint_fuzz.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "src/__support/CPP/bit.h"
|
||||
#include "src/__support/UInt.h"
|
||||
#include "src/string/memory_utils/inline_memcpy.h"
|
||||
|
||||
using namespace LIBC_NAMESPACE;
|
||||
|
||||
// Helper function when using gdb / lldb to set a breakpoint and inspect values.
|
||||
template <typename T> void debug_and_trap(const char *msg, T a, T b) {
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
#define DEBUG_AND_TRAP()
|
||||
|
||||
#define TEST_BINOP(OP) \
|
||||
if ((a OP b) != (static_cast<T>(BigInt(a) OP BigInt(b)))) \
|
||||
debug_and_trap(#OP, a, b);
|
||||
|
||||
#define TEST_SHIFTOP(OP) \
|
||||
if ((a OP b) != (static_cast<T>(BigInt(a) OP b))) \
|
||||
debug_and_trap(#OP, a, b);
|
||||
|
||||
#define TEST_FUNCTION(FUN) \
|
||||
if (FUN(a) != FUN(BigInt(a))) \
|
||||
debug_and_trap(#FUN, a, b);
|
||||
|
||||
// Test that basic arithmetic operations of BigInt behave like their scalar
|
||||
// counterparts.
|
||||
template <typename T, typename BigInt> void run_tests(T a, T b) {
|
||||
TEST_BINOP(+)
|
||||
TEST_BINOP(-)
|
||||
TEST_BINOP(*)
|
||||
if (b != 0)
|
||||
TEST_BINOP(/)
|
||||
if (b >= 0 && b < cpp::numeric_limits<T>::digits) {
|
||||
TEST_SHIFTOP(<<)
|
||||
TEST_SHIFTOP(>>)
|
||||
}
|
||||
if constexpr (!BigInt::SIGNED) {
|
||||
TEST_FUNCTION(cpp::has_single_bit)
|
||||
TEST_FUNCTION(cpp::countr_zero)
|
||||
TEST_FUNCTION(cpp::countl_zero)
|
||||
TEST_FUNCTION(cpp::countl_one)
|
||||
TEST_FUNCTION(cpp::countr_one)
|
||||
}
|
||||
}
|
||||
|
||||
// Reads a T from libfuzzer data.
|
||||
template <typename T> T read(const uint8_t *data, size_t &remainder) {
|
||||
T out = 0;
|
||||
constexpr size_t T_SIZE = sizeof(T);
|
||||
const size_t copy_size = remainder < T_SIZE ? remainder : T_SIZE;
|
||||
inline_memcpy(&out, data, copy_size);
|
||||
remainder -= copy_size;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T, typename BigInt>
|
||||
void run_tests(const uint8_t *data, size_t size) {
|
||||
const auto a = read<T>(data, size);
|
||||
const auto b = read<T>(data, size);
|
||||
run_tests<T, BigInt>(a, b);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// unsigned
|
||||
run_tests<uint64_t, BigInt<64, false, uint16_t>>(data, size);
|
||||
// signed
|
||||
run_tests<int64_t, BigInt<64, true, uint16_t>>(data, size);
|
||||
return 0;
|
||||
}
|
||||
@@ -58,9 +58,9 @@ template <size_t Bits> struct DyadicFloat {
|
||||
// significant bit.
|
||||
LIBC_INLINE constexpr DyadicFloat &normalize() {
|
||||
if (!mantissa.is_zero()) {
|
||||
int shift_length = static_cast<int>(mantissa.clz());
|
||||
int shift_length = cpp::countl_zero(mantissa);
|
||||
exponent -= shift_length;
|
||||
mantissa.shift_left(static_cast<size_t>(shift_length));
|
||||
mantissa <<= static_cast<size_t>(shift_length);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -233,7 +233,7 @@ LIBC_INLINE constexpr DyadicFloat<Bits> quick_add(DyadicFloat<Bits> a,
|
||||
result.sign = a.sign;
|
||||
result.exponent = a.exponent;
|
||||
result.mantissa = a.mantissa;
|
||||
if (result.mantissa.add(b.mantissa)) {
|
||||
if (result.mantissa.add_overflow(b.mantissa)) {
|
||||
// Mantissa addition overflow.
|
||||
result.shift_right(1);
|
||||
result.mantissa.val[DyadicFloat<Bits>::MantissaType::WORD_COUNT - 1] |=
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -689,7 +689,7 @@ template <> class FloatToString<long double> {
|
||||
|
||||
wide_int float_as_int = mantissa;
|
||||
|
||||
float_as_int.shift_left(exponent);
|
||||
float_as_int <<= exponent;
|
||||
int_block_index = 0;
|
||||
|
||||
while (float_as_int > 0) {
|
||||
@@ -708,10 +708,11 @@ template <> class FloatToString<long double> {
|
||||
|
||||
const int SHIFT_AMOUNT = FLOAT_AS_INT_WIDTH + exponent;
|
||||
static_assert(EXTRA_INT_WIDTH >= sizeof(long double) * 8);
|
||||
float_as_fixed.shift_left(SHIFT_AMOUNT);
|
||||
float_as_fixed <<= SHIFT_AMOUNT;
|
||||
|
||||
// If there are still digits above the decimal point, handle those.
|
||||
if (float_as_fixed.clz() < static_cast<int>(EXTRA_INT_WIDTH)) {
|
||||
if (cpp::countl_zero(float_as_fixed) <
|
||||
static_cast<int>(EXTRA_INT_WIDTH)) {
|
||||
UInt<EXTRA_INT_WIDTH> above_decimal_point =
|
||||
float_as_fixed >> FLOAT_AS_INT_WIDTH;
|
||||
|
||||
|
||||
@@ -151,12 +151,15 @@ template <size_t N> struct Parser<LIBC_NAMESPACE::UInt<N>> {
|
||||
template <typename T>
|
||||
LIBC_INLINE constexpr T parse_with_prefix(const char *ptr) {
|
||||
using P = Parser<T>;
|
||||
if (ptr[0] == '0' && ptr[1] == 'x')
|
||||
return P::template parse<16>(ptr + 2);
|
||||
else if (ptr[0] == '0' && ptr[1] == 'b')
|
||||
return P::template parse<2>(ptr + 2);
|
||||
else
|
||||
return P::template parse<10>(ptr);
|
||||
if (ptr == nullptr)
|
||||
return T();
|
||||
if (ptr[0] == '0') {
|
||||
if (ptr[1] == 'b')
|
||||
return P::template parse<2>(ptr + 2);
|
||||
if (ptr[1] == 'x')
|
||||
return P::template parse<16>(ptr + 2);
|
||||
}
|
||||
return P::template parse<10>(ptr);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
@@ -169,6 +172,16 @@ LIBC_INLINE constexpr auto operator""_u256(const char *x) {
|
||||
return internal::parse_with_prefix<UInt<256>>(x);
|
||||
}
|
||||
|
||||
template <typename T> LIBC_INLINE constexpr T parse_bigint(const char *ptr) {
|
||||
if (ptr == nullptr)
|
||||
return T();
|
||||
if (ptr[0] == '-' || ptr[0] == '+') {
|
||||
auto positive = internal::parse_with_prefix<T>(ptr + 1);
|
||||
return ptr[0] == '-' ? -positive : positive;
|
||||
}
|
||||
return internal::parse_with_prefix<T>(ptr);
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE
|
||||
|
||||
#endif // LLVM_LIBC_SRC___SUPPORT_INTEGER_LITERALS_H
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
#ifndef LLVM_LIBC_SRC___SUPPORT_MATH_EXTRAS_H
|
||||
#define LLVM_LIBC_SRC___SUPPORT_MATH_EXTRAS_H
|
||||
|
||||
#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
|
||||
#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
|
||||
|
||||
namespace LIBC_NAMESPACE {
|
||||
@@ -32,199 +32,94 @@ mask_trailing_ones() {
|
||||
template <typename T, size_t count>
|
||||
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
|
||||
mask_leading_ones() {
|
||||
constexpr T MASK(mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>());
|
||||
return T(~MASK); // bitwise NOT performs integer promotion.
|
||||
return T(~mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>());
|
||||
}
|
||||
|
||||
// Add with carry
|
||||
template <typename T> struct SumCarry {
|
||||
// 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>();
|
||||
}
|
||||
|
||||
// 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>();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#define RETURN_IF(TYPE, BUILTIN) \
|
||||
if constexpr (cpp::is_same_v<T, TYPE>) \
|
||||
return BUILTIN(a, b, carry_in, carry_out);
|
||||
|
||||
// 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>
|
||||
[[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 carry;
|
||||
};
|
||||
T carry1 = add_overflow(a, b, sum);
|
||||
T carry2 = add_overflow(sum, carry_in, sum);
|
||||
carry_out = carry1 | carry2;
|
||||
return sum;
|
||||
}
|
||||
|
||||
// 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>, 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};
|
||||
}
|
||||
|
||||
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(T a, T b, T carry_in) {
|
||||
return add_with_carry_const<T>(a, b, carry_in);
|
||||
}
|
||||
|
||||
#if __has_builtin(__builtin_addc)
|
||||
// https://clang.llvm.org/docs/LanguageExtensions.html#multiprecision-arithmetic-builtins
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr SumCarry<unsigned char>
|
||||
add_with_carry<unsigned char>(unsigned char a, unsigned char b,
|
||||
unsigned char carry_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return add_with_carry_const<unsigned char>(a, b, carry_in);
|
||||
} else {
|
||||
SumCarry<unsigned char> result{0, 0};
|
||||
result.sum = __builtin_addcb(a, b, carry_in, &result.carry);
|
||||
return result;
|
||||
[[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;
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr SumCarry<unsigned short>
|
||||
add_with_carry<unsigned short>(unsigned short a, unsigned short b,
|
||||
unsigned short carry_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return add_with_carry_const<unsigned short>(a, b, carry_in);
|
||||
} else {
|
||||
SumCarry<unsigned short> result{0, 0};
|
||||
result.sum = __builtin_addcs(a, b, carry_in, &result.carry);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr SumCarry<unsigned int>
|
||||
add_with_carry<unsigned int>(unsigned int a, unsigned int b,
|
||||
unsigned int carry_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return add_with_carry_const<unsigned int>(a, b, carry_in);
|
||||
} else {
|
||||
SumCarry<unsigned int> result{0, 0};
|
||||
result.sum = __builtin_addc(a, b, carry_in, &result.carry);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr SumCarry<unsigned long>
|
||||
add_with_carry<unsigned long>(unsigned long a, unsigned long b,
|
||||
unsigned long carry_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return add_with_carry_const<unsigned long>(a, b, carry_in);
|
||||
} else {
|
||||
SumCarry<unsigned long> result{0, 0};
|
||||
result.sum = __builtin_addcl(a, b, carry_in, &result.carry);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr SumCarry<unsigned long long>
|
||||
add_with_carry<unsigned long long>(unsigned long long a, unsigned long long b,
|
||||
unsigned long long carry_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return add_with_carry_const<unsigned long long>(a, b, carry_in);
|
||||
} else {
|
||||
SumCarry<unsigned long long> result{0, 0};
|
||||
result.sum = __builtin_addcll(a, b, carry_in, &result.carry);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __has_builtin(__builtin_addc)
|
||||
|
||||
// Subtract with borrow
|
||||
template <typename T> struct DiffBorrow {
|
||||
T diff;
|
||||
T borrow;
|
||||
};
|
||||
|
||||
// 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>, 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.
|
||||
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(T a, T b, T borrow_in) {
|
||||
return sub_with_borrow_const<T>(a, b, borrow_in);
|
||||
}
|
||||
|
||||
#if __has_builtin(__builtin_subc)
|
||||
// https://clang.llvm.org/docs/LanguageExtensions.html#multiprecision-arithmetic-builtins
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr DiffBorrow<unsigned char>
|
||||
sub_with_borrow<unsigned char>(unsigned char a, unsigned char b,
|
||||
unsigned char borrow_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return sub_with_borrow_const<unsigned char>(a, b, borrow_in);
|
||||
} else {
|
||||
DiffBorrow<unsigned char> result{0, 0};
|
||||
result.diff = __builtin_subcb(a, b, borrow_in, &result.borrow);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr DiffBorrow<unsigned short>
|
||||
sub_with_borrow<unsigned short>(unsigned short a, unsigned short b,
|
||||
unsigned short borrow_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return sub_with_borrow_const<unsigned short>(a, b, borrow_in);
|
||||
} else {
|
||||
DiffBorrow<unsigned short> result{0, 0};
|
||||
result.diff = __builtin_subcs(a, b, borrow_in, &result.borrow);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr DiffBorrow<unsigned int>
|
||||
sub_with_borrow<unsigned int>(unsigned int a, unsigned int b,
|
||||
unsigned int borrow_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return sub_with_borrow_const<unsigned int>(a, b, borrow_in);
|
||||
} else {
|
||||
DiffBorrow<unsigned int> result{0, 0};
|
||||
result.diff = __builtin_subc(a, b, borrow_in, &result.borrow);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr DiffBorrow<unsigned long>
|
||||
sub_with_borrow<unsigned long>(unsigned long a, unsigned long b,
|
||||
unsigned long borrow_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return sub_with_borrow_const<unsigned long>(a, b, borrow_in);
|
||||
} else {
|
||||
DiffBorrow<unsigned long> result{0, 0};
|
||||
result.diff = __builtin_subcl(a, b, borrow_in, &result.borrow);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
LIBC_INLINE constexpr DiffBorrow<unsigned long long>
|
||||
sub_with_borrow<unsigned long long>(unsigned long long a, unsigned long long b,
|
||||
unsigned long long borrow_in) {
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
return sub_with_borrow_const<unsigned long long>(a, b, borrow_in);
|
||||
} else {
|
||||
DiffBorrow<unsigned long long> result{0, 0};
|
||||
result.diff = __builtin_subcll(a, b, borrow_in, &result.borrow);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __has_builtin(__builtin_subc)
|
||||
#undef RETURN_IF
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
|
||||
|
||||
@@ -20,17 +20,6 @@ template <typename T> struct NumberPair {
|
||||
T hi = T(0);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
cpp::enable_if_t<cpp::is_integral_v<T> && cpp::is_unsigned_v<T>,
|
||||
NumberPair<T>> constexpr split(T a) {
|
||||
constexpr size_t HALF_BIT_WIDTH = sizeof(T) * 4;
|
||||
constexpr T LOWER_HALF_MASK = (T(1) << HALF_BIT_WIDTH) - T(1);
|
||||
NumberPair<T> result;
|
||||
result.lo = a & LOWER_HALF_MASK;
|
||||
result.hi = a >> HALF_BIT_WIDTH;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE
|
||||
|
||||
#endif // LLVM_LIBC_SRC___SUPPORT_NUMBER_PAIR_H
|
||||
|
||||
@@ -133,3 +133,24 @@ TEST(LlvmLibcIntegerLiteralTest, u256) {
|
||||
U256_MAX,
|
||||
0xFFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF_u256);
|
||||
}
|
||||
|
||||
TEST(LlvmLibcIntegerLiteralTest, parse_bigint) {
|
||||
using T = LIBC_NAMESPACE::Int<128>;
|
||||
struct {
|
||||
const char *str;
|
||||
T expected;
|
||||
} constexpr TEST_CASES[] = {
|
||||
{"0", 0}, {"-1", -1}, {"+1", 1}, {"-0xFF", -255}, {"-0b11", -3},
|
||||
};
|
||||
for (auto tc : TEST_CASES) {
|
||||
T actual = LIBC_NAMESPACE::parse_bigint<T>(tc.str);
|
||||
EXPECT_EQ(actual, tc.expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LlvmLibcIntegerLiteralTest, parse_bigint_invalid) {
|
||||
using T = LIBC_NAMESPACE::Int<128>;
|
||||
const T expected; // default construction
|
||||
EXPECT_EQ(LIBC_NAMESPACE::parse_bigint<T>(nullptr), expected);
|
||||
EXPECT_EQ(LIBC_NAMESPACE::parse_bigint<T>(""), expected);
|
||||
}
|
||||
|
||||
@@ -101,4 +101,61 @@ TYPED_TEST(LlvmLibcBitTest, CountZeros, UnsignedTypesNoBigInt) {
|
||||
EXPECT_EQ(count_zeros<T>(cpp::numeric_limits<T>::max() >> i), i);
|
||||
}
|
||||
|
||||
using UnsignedTypes = testing::TypeList<
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
__uint128_t,
|
||||
#endif
|
||||
unsigned char, unsigned short, unsigned int, unsigned long,
|
||||
unsigned long long>;
|
||||
|
||||
TYPED_TEST(LlvmLibcBlockMathExtrasTest, add_overflow, UnsignedTypes) {
|
||||
constexpr T ZERO = cpp::numeric_limits<T>::min();
|
||||
constexpr T ONE(1);
|
||||
constexpr T MAX = cpp::numeric_limits<T>::max();
|
||||
constexpr T BEFORE_MAX = MAX - 1;
|
||||
|
||||
const struct {
|
||||
T lhs;
|
||||
T rhs;
|
||||
T sum;
|
||||
bool carry;
|
||||
} TESTS[] = {
|
||||
{ZERO, ONE, ONE, false}, // 0x00 + 0x01 = 0x01
|
||||
{BEFORE_MAX, ONE, MAX, false}, // 0xFE + 0x01 = 0xFF
|
||||
{MAX, ONE, ZERO, true}, // 0xFF + 0x01 = 0x00 (carry)
|
||||
{MAX, MAX, BEFORE_MAX, true}, // 0xFF + 0xFF = 0xFE (carry)
|
||||
};
|
||||
for (auto tc : TESTS) {
|
||||
T sum;
|
||||
bool carry = add_overflow<T>(tc.lhs, tc.rhs, sum);
|
||||
EXPECT_EQ(sum, tc.sum);
|
||||
EXPECT_EQ(carry, tc.carry);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(LlvmLibcBlockMathExtrasTest, sub_overflow, UnsignedTypes) {
|
||||
constexpr T ZERO = cpp::numeric_limits<T>::min();
|
||||
constexpr T ONE(1);
|
||||
constexpr T MAX = cpp::numeric_limits<T>::max();
|
||||
constexpr T BEFORE_MAX = MAX - 1;
|
||||
|
||||
const struct {
|
||||
T lhs;
|
||||
T rhs;
|
||||
T sub;
|
||||
bool carry;
|
||||
} TESTS[] = {
|
||||
{ONE, ZERO, ONE, false}, // 0x01 - 0x00 = 0x01
|
||||
{MAX, MAX, ZERO, false}, // 0xFF - 0xFF = 0x00
|
||||
{ZERO, ONE, MAX, true}, // 0x00 - 0x01 = 0xFF (carry)
|
||||
{BEFORE_MAX, MAX, MAX, true}, // 0xFE - 0xFF = 0xFF (carry)
|
||||
};
|
||||
for (auto tc : TESTS) {
|
||||
T sub;
|
||||
bool carry = sub_overflow<T>(tc.lhs, tc.rhs, sub);
|
||||
EXPECT_EQ(sub, tc.sub);
|
||||
EXPECT_EQ(carry, tc.carry);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "src/__support/CPP/optional.h"
|
||||
#include "src/__support/UInt.h"
|
||||
#include "src/__support/integer_literals.h" // parse_unsigned_bigint
|
||||
#include "src/__support/macros/properties/types.h" // LIBC_TYPES_HAS_INT128
|
||||
|
||||
#include "include/llvm-libc-macros/math-macros.h" // HUGE_VALF, HUGE_VALF
|
||||
@@ -15,6 +16,195 @@
|
||||
|
||||
namespace LIBC_NAMESPACE {
|
||||
|
||||
enum Value { ZERO, ONE, TWO, MIN, MAX };
|
||||
|
||||
template <typename T> auto create(Value value) {
|
||||
switch (value) {
|
||||
case ZERO:
|
||||
return T(0);
|
||||
case ONE:
|
||||
return T(1);
|
||||
case TWO:
|
||||
return T(2);
|
||||
case MIN:
|
||||
return T::min();
|
||||
case MAX:
|
||||
return T::max();
|
||||
}
|
||||
}
|
||||
|
||||
using Types = testing::TypeList< //
|
||||
#ifdef LIBC_TYPES_HAS_INT64
|
||||
BigInt<64, false, uint64_t>, // 64-bits unsigned (1 x uint64_t)
|
||||
BigInt<64, true, uint64_t>, // 64-bits signed (1 x uint64_t)
|
||||
#endif
|
||||
#ifdef LIBC_TYPES_HAS_INT128
|
||||
BigInt<128, false, __uint128_t>, // 128-bits unsigned (1 x __uint128_t)
|
||||
BigInt<128, true, __uint128_t>, // 128-bits signed (1 x __uint128_t)
|
||||
#endif
|
||||
BigInt<16, false, uint16_t>, // 16-bits unsigned (1 x uint16_t)
|
||||
BigInt<16, true, uint16_t>, // 16-bits signed (1 x uint16_t)
|
||||
BigInt<64, false, uint16_t>, // 64-bits unsigned (4 x uint16_t)
|
||||
BigInt<64, true, uint16_t> // 64-bits signed (4 x uint16_t)
|
||||
>;
|
||||
|
||||
#define ASSERT_SAME(A, B) ASSERT_TRUE((A) == (B))
|
||||
|
||||
TYPED_TEST(LlvmLibcUIntClassTest, Additions, Types) {
|
||||
ASSERT_SAME(create<T>(ZERO) + create<T>(ZERO), create<T>(ZERO));
|
||||
ASSERT_SAME(create<T>(ONE) + create<T>(ZERO), create<T>(ONE));
|
||||
ASSERT_SAME(create<T>(ZERO) + create<T>(ONE), create<T>(ONE));
|
||||
ASSERT_SAME(create<T>(ONE) + create<T>(ONE), create<T>(TWO));
|
||||
// 2's complement addition works for signed and unsigned types.
|
||||
// - unsigned : 0xff + 0x01 = 0x00 (255 + 1 = 0)
|
||||
// - signed : 0xef + 0x01 = 0xf0 (127 + 1 = -128)
|
||||
ASSERT_SAME(create<T>(MAX) + create<T>(ONE), create<T>(MIN));
|
||||
}
|
||||
|
||||
TYPED_TEST(LlvmLibcUIntClassTest, Subtraction, Types) {
|
||||
ASSERT_SAME(create<T>(ZERO) - create<T>(ZERO), create<T>(ZERO));
|
||||
ASSERT_SAME(create<T>(ONE) - create<T>(ONE), create<T>(ZERO));
|
||||
ASSERT_SAME(create<T>(ONE) - create<T>(ZERO), create<T>(ONE));
|
||||
// 2's complement subtraction works for signed and unsigned types.
|
||||
// - unsigned : 0x00 - 0x01 = 0xff ( 0 - 1 = 255)
|
||||
// - signed : 0xf0 - 0x01 = 0xef (-128 - 1 = 127)
|
||||
ASSERT_SAME(create<T>(MIN) - create<T>(ONE), create<T>(MAX));
|
||||
}
|
||||
|
||||
TYPED_TEST(LlvmLibcUIntClassTest, Multiplication, Types) {
|
||||
ASSERT_SAME(create<T>(ZERO) * create<T>(ZERO), create<T>(ZERO));
|
||||
ASSERT_SAME(create<T>(ZERO) * create<T>(ONE), create<T>(ZERO));
|
||||
ASSERT_SAME(create<T>(ONE) * create<T>(ZERO), create<T>(ZERO));
|
||||
ASSERT_SAME(create<T>(ONE) * create<T>(ONE), create<T>(ONE));
|
||||
ASSERT_SAME(create<T>(ONE) * create<T>(TWO), create<T>(TWO));
|
||||
ASSERT_SAME(create<T>(TWO) * create<T>(ONE), create<T>(TWO));
|
||||
// - unsigned : 0xff x 0xff = 0x01 (mod 0xff)
|
||||
// - signed : 0xef x 0xef = 0x01 (mod 0xff)
|
||||
ASSERT_SAME(create<T>(MAX) * create<T>(MAX), create<T>(ONE));
|
||||
}
|
||||
|
||||
template <typename T> void print(const char *msg, T value) {
|
||||
testing::tlog << msg;
|
||||
IntegerToString<T, radix::Hex> buffer(value);
|
||||
testing::tlog << buffer.view() << "\n";
|
||||
}
|
||||
|
||||
TEST(LlvmLibcUIntClassTest, SignedAddSub) {
|
||||
// Computations performed by https://www.wolframalpha.com/
|
||||
using T = BigInt<128, true, uint32_t>;
|
||||
const T a = parse_bigint<T>("1927508279017230597");
|
||||
const T b = parse_bigint<T>("278789278723478925");
|
||||
const T s = parse_bigint<T>("2206297557740709522");
|
||||
// Addition
|
||||
ASSERT_SAME(a + b, s);
|
||||
ASSERT_SAME(b + a, s); // commutative
|
||||
// Subtraction
|
||||
ASSERT_SAME(a - s, -b);
|
||||
ASSERT_SAME(s - a, b);
|
||||
}
|
||||
|
||||
TEST(LlvmLibcUIntClassTest, SignedMulDiv) {
|
||||
// Computations performed by https://www.wolframalpha.com/
|
||||
using T = BigInt<128, true, uint16_t>;
|
||||
struct {
|
||||
const char *a;
|
||||
const char *b;
|
||||
const char *mul;
|
||||
} const test_cases[] = {{"-4", "3", "-12"},
|
||||
{"-3", "-3", "9"},
|
||||
{"1927508279017230597", "278789278723478925",
|
||||
"537368642840747885329125014794668225"}};
|
||||
for (auto tc : test_cases) {
|
||||
const T a = parse_bigint<T>(tc.a);
|
||||
const T b = parse_bigint<T>(tc.b);
|
||||
const T mul = parse_bigint<T>(tc.mul);
|
||||
// Multiplication
|
||||
ASSERT_SAME(a * b, mul);
|
||||
ASSERT_SAME(b * a, mul); // commutative
|
||||
ASSERT_SAME(a * -b, -mul); // sign
|
||||
ASSERT_SAME(-a * b, -mul); // sign
|
||||
ASSERT_SAME(-a * -b, mul); // sign
|
||||
// Division
|
||||
ASSERT_SAME(mul / a, b);
|
||||
ASSERT_SAME(mul / b, a);
|
||||
ASSERT_SAME(-mul / a, -b); // sign
|
||||
ASSERT_SAME(mul / -a, -b); // sign
|
||||
ASSERT_SAME(-mul / -a, b); // sign
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(LlvmLibcUIntClassTest, Division, Types) {
|
||||
ASSERT_SAME(create<T>(ZERO) / create<T>(ONE), create<T>(ZERO));
|
||||
ASSERT_SAME(create<T>(MAX) / create<T>(ONE), create<T>(MAX));
|
||||
ASSERT_SAME(create<T>(MAX) / create<T>(MAX), create<T>(ONE));
|
||||
ASSERT_SAME(create<T>(ONE) / create<T>(ONE), create<T>(ONE));
|
||||
if constexpr (T::SIGNED) {
|
||||
// Special case found by fuzzing.
|
||||
ASSERT_SAME(create<T>(MIN) / create<T>(MIN), create<T>(ONE));
|
||||
}
|
||||
// - unsigned : 0xff / 0x02 = 0x7f
|
||||
// - signed : 0xef / 0x02 = 0x77
|
||||
ASSERT_SAME(create<T>(MAX) / create<T>(TWO), (create<T>(MAX) >> 1));
|
||||
|
||||
using word_type = typename T::word_type;
|
||||
const T zero_one_repeated = T::all_ones() / T(0xff);
|
||||
const word_type pattern = word_type(~0) / word_type(0xff);
|
||||
for (const word_type part : zero_one_repeated.val) {
|
||||
if constexpr (T::SIGNED == false) {
|
||||
EXPECT_EQ(part, pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(LlvmLibcUIntClassTest, is_neg, Types) {
|
||||
EXPECT_FALSE(create<T>(ZERO).is_neg());
|
||||
EXPECT_FALSE(create<T>(ONE).is_neg());
|
||||
EXPECT_FALSE(create<T>(TWO).is_neg());
|
||||
EXPECT_EQ(create<T>(MIN).is_neg(), T::SIGNED);
|
||||
EXPECT_FALSE(create<T>(MAX).is_neg());
|
||||
}
|
||||
|
||||
TYPED_TEST(LlvmLibcUIntClassTest, Masks, Types) {
|
||||
if constexpr (!T::SIGNED) {
|
||||
constexpr size_t BITS = T::BITS;
|
||||
// mask_trailing_ones
|
||||
ASSERT_SAME((mask_trailing_ones<T, 0>()), T::zero());
|
||||
ASSERT_SAME((mask_trailing_ones<T, 1>()), T::one());
|
||||
ASSERT_SAME((mask_trailing_ones<T, BITS - 1>()), T::all_ones() >> 1);
|
||||
ASSERT_SAME((mask_trailing_ones<T, BITS>()), T::all_ones());
|
||||
// mask_leading_ones
|
||||
ASSERT_SAME((mask_leading_ones<T, 0>()), T::zero());
|
||||
ASSERT_SAME((mask_leading_ones<T, 1>()), T::one() << (BITS - 1));
|
||||
ASSERT_SAME((mask_leading_ones<T, BITS - 1>()), T::all_ones() - T::one());
|
||||
ASSERT_SAME((mask_leading_ones<T, BITS>()), T::all_ones());
|
||||
// mask_trailing_zeros
|
||||
ASSERT_SAME((mask_trailing_zeros<T, 0>()), T::all_ones());
|
||||
ASSERT_SAME((mask_trailing_zeros<T, 1>()), T::all_ones() - T::one());
|
||||
ASSERT_SAME((mask_trailing_zeros<T, BITS - 1>()), T::one() << (BITS - 1));
|
||||
ASSERT_SAME((mask_trailing_zeros<T, BITS>()), T::zero());
|
||||
// mask_trailing_zeros
|
||||
ASSERT_SAME((mask_leading_zeros<T, 0>()), T::all_ones());
|
||||
ASSERT_SAME((mask_leading_zeros<T, 1>()), T::all_ones() >> 1);
|
||||
ASSERT_SAME((mask_leading_zeros<T, BITS - 1>()), T::one());
|
||||
ASSERT_SAME((mask_leading_zeros<T, BITS>()), T::zero());
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(LlvmLibcUIntClassTest, CountBits, Types) {
|
||||
if constexpr (!T::SIGNED) {
|
||||
for (size_t i = 0; i <= T::BITS; ++i) {
|
||||
const auto l_one = T::all_ones() << i; // 0b111...000
|
||||
const auto r_one = T::all_ones() >> i; // 0b000...111
|
||||
const int zeros = i;
|
||||
const int ones = T::BITS - zeros;
|
||||
ASSERT_EQ(cpp::countr_one(r_one), ones);
|
||||
ASSERT_EQ(cpp::countl_one(l_one), ones);
|
||||
ASSERT_EQ(cpp::countr_zero(l_one), zeros);
|
||||
ASSERT_EQ(cpp::countl_zero(r_one), zeros);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using LL_UInt64 = UInt<64>;
|
||||
// We want to test UInt<128> explicitly. So, for
|
||||
// convenience, we use a sugar which does not conflict with the UInt128 type
|
||||
@@ -561,7 +751,7 @@ TEST(LlvmLibcUIntClassTest, FullMulTests) {
|
||||
LL_UInt##Bits a = ~LL_UInt##Bits(0); \
|
||||
LL_UInt##Bits hi = a.quick_mul_hi(a); \
|
||||
LL_UInt##Bits trunc = static_cast<LL_UInt##Bits>(a.ful_mul(a) >> Bits); \
|
||||
uint64_t overflow = trunc.sub(hi); \
|
||||
uint64_t overflow = trunc.sub_overflow(hi); \
|
||||
EXPECT_EQ(overflow, uint64_t(0)); \
|
||||
EXPECT_LE(uint64_t(trunc), uint64_t(Error)); \
|
||||
} while (0)
|
||||
|
||||
@@ -87,6 +87,7 @@ libc_test(
|
||||
srcs = ["uint_test.cpp"],
|
||||
deps = [
|
||||
"//libc:__support_cpp_optional",
|
||||
"//libc:__support_integer_literals",
|
||||
"//libc:__support_macros_properties_types",
|
||||
"//libc:__support_uint",
|
||||
"//libc:llvm_libc_macros_math_macros",
|
||||
|
||||
Reference in New Issue
Block a user