[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:
Guillaume Chatelet
2024-04-04 11:41:27 +02:00
committed by GitHub
parent cca9115b1c
commit 71c3f5d617
13 changed files with 1034 additions and 782 deletions

View File

@@ -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)

View File

@@ -0,0 +1,7 @@
add_libc_fuzzer(
uint_fuzz
SRCS
uint_fuzz.cpp
DEPENDS
libc.src.__support.uint
)

View 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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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",