[libc-commits] [libc] [llvm] [libc] Remove UB specializations of type_traits (PR #84035)
Guillaume Chatelet via libc-commits
libc-commits at lists.llvm.org
Tue Mar 5 08:05:10 PST 2024
https://github.com/gchatelet created https://github.com/llvm/llvm-project/pull/84035
The standard specifies that it it UB to specialize the following traits:
- std::is_integral
- std::is_unsigned
- std::make_unsigned
- std::make_signed
This patch:
- Removes specializations for BigInt
- Transforms SFINAE for bit.h functions from template parameter to
return type (This makes specialization easier).
- Adds BigInt specialization for bit.h functions.
- Fixes code depending on previous specializations.
>From 528b7ce023c477ddf070a1613f0fad23ec07e5c9 Mon Sep 17 00:00:00 2001
From: Guillaume Chatelet <gchatelet at google.com>
Date: Tue, 5 Mar 2024 15:39:34 +0000
Subject: [PATCH] [libc] Remove UB specializations of type_traits
The standard specifies that it it UB to specialize the following traits:
- std::is_integral
- std::is_unsigned
- std::make_unsigned
- std::make_signed
This patch:
- Removes specializations for BigInt
- Transforms SFINAE for bit.h functions from template parameter to
return type (This makes specialization easier).
- Adds BigInt specialization for bit.h functions.
- Fixes code depending on previous specializations.
---
libc/src/__support/CMakeLists.txt | 1 +
libc/src/__support/CPP/bit.h | 105 ++++----
libc/src/__support/UInt.h | 238 +++++++++++++-----
libc/src/__support/float_to_string.h | 2 +-
libc/src/__support/integer_to_string.h | 19 +-
libc/test/UnitTest/CMakeLists.txt | 1 +
libc/test/UnitTest/LibcTest.cpp | 10 +-
libc/test/UnitTest/LibcTest.h | 1 +
libc/test/UnitTest/TestLogger.cpp | 8 +-
.../llvm-project-overlay/libc/BUILD.bazel | 1 +
.../libc/test/UnitTest/BUILD.bazel | 1 +
11 files changed, 275 insertions(+), 112 deletions(-)
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index 1a4b3e9a2145c0..17c04aa57e6fd6 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -95,6 +95,7 @@ add_header_library(
HDRS
integer_to_string.h
DEPENDS
+ .uint
libc.src.__support.common
libc.src.__support.CPP.algorithm
libc.src.__support.CPP.limits
diff --git a/libc/src/__support/CPP/bit.h b/libc/src/__support/CPP/bit.h
index 7d11e7d5c497e0..496ca16b6d6017 100644
--- a/libc/src/__support/CPP/bit.h
+++ b/libc/src/__support/CPP/bit.h
@@ -27,13 +27,14 @@ namespace LIBC_NAMESPACE::cpp {
// This implementation of bit_cast requires trivially-constructible To, to avoid
// UB in the implementation.
-template <
- typename To, typename From,
- typename = cpp::enable_if_t<sizeof(To) == sizeof(From) &&
- cpp::is_trivially_constructible<To>::value &&
- cpp::is_trivially_copyable<To>::value &&
- cpp::is_trivially_copyable<From>::value>>
-LIBC_INLINE constexpr To bit_cast(const From &from) {
+template <typename To, typename From>
+LIBC_INLINE constexpr cpp::enable_if_t<
+ (sizeof(To) == sizeof(From)) &&
+ cpp::is_trivially_constructible<To>::value &&
+ cpp::is_trivially_copyable<To>::value &&
+ cpp::is_trivially_copyable<From>::value,
+ To>
+bit_cast(const From &from) {
MSAN_UNPOISON(&from, sizeof(From));
#if LIBC_HAS_BUILTIN(__builtin_bit_cast)
return __builtin_bit_cast(To, from);
@@ -51,8 +52,10 @@ LIBC_INLINE constexpr To bit_cast(const From &from) {
#endif // LIBC_HAS_BUILTIN(__builtin_bit_cast)
}
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr bool has_single_bit(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>,
+ bool>
+has_single_bit(T value) {
return (value != 0) && ((value & (value - 1)) == 0);
}
@@ -70,8 +73,9 @@ template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
/// Only unsigned integral types are allowed.
///
/// Returns cpp::numeric_limits<T>::digits on an input of 0.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int countr_zero(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
+countr_zero(T value) {
if (!value)
return cpp::numeric_limits<T>::digits;
if (value & 0x1)
@@ -103,8 +107,9 @@ ADD_SPECIALIZATION(countr_zero, unsigned long long, __builtin_ctzll)
/// Only unsigned integral types are allowed.
///
/// Returns cpp::numeric_limits<T>::digits on an input of 0.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int countl_zero(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
+countl_zero(T value) {
if (!value)
return cpp::numeric_limits<T>::digits;
// Bisection method.
@@ -135,8 +140,9 @@ ADD_SPECIALIZATION(countl_zero, unsigned long long, __builtin_clzll)
/// Only unsigned integral types are allowed.
///
/// Returns cpp::numeric_limits<T>::digits on an input of all ones.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int countl_one(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
+countl_one(T value) {
return cpp::countl_zero<T>(~value);
}
@@ -147,8 +153,9 @@ template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
/// Only unsigned integral types are allowed.
///
/// Returns cpp::numeric_limits<T>::digits on an input of all ones.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int countr_one(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
+countr_one(T value) {
return cpp::countr_zero<T>(~value);
}
@@ -156,8 +163,9 @@ template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
/// Returns 0 otherwise.
///
/// Ex. bit_width(5) == 3.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int bit_width(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
+bit_width(T value) {
return cpp::numeric_limits<T>::digits - cpp::countl_zero(value);
}
@@ -165,8 +173,9 @@ template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
/// nonzero. Returns 0 otherwise.
///
/// Ex. bit_floor(5) == 4.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr T bit_floor(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
+bit_floor(T value) {
if (!value)
return 0;
return T(1) << (cpp::bit_width(value) - 1);
@@ -179,8 +188,9 @@ template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
///
/// The return value is undefined if the input is larger than the largest power
/// of two representable in T.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr T bit_ceil(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
+bit_ceil(T value) {
if (value < 2)
return 1;
return T(1) << cpp::bit_width<T>(value - 1u);
@@ -190,28 +200,31 @@ template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
// from https://blog.regehr.org/archives/1063.
// Forward-declare rotr so that rotl can use it.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr T rotr(T value, int rotate);
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
+rotr(T value, int rotate);
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr T rotl(T value, int rotate) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
+rotl(T value, int rotate) {
constexpr unsigned N = cpp::numeric_limits<T>::digits;
rotate = rotate % N;
if (!rotate)
return value;
if (rotate < 0)
- return cpp::rotr(value, -rotate);
+ return cpp::rotr<T>(value, -rotate);
return (value << rotate) | (value >> (N - rotate));
}
-template <typename T, typename>
-[[nodiscard]] LIBC_INLINE constexpr T rotr(T value, int rotate) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
+rotr(T value, int rotate) {
constexpr unsigned N = cpp::numeric_limits<T>::digits;
rotate = rotate % N;
if (!rotate)
return value;
if (rotate < 0)
- return cpp::rotl(value, -rotate);
+ return cpp::rotl<T>(value, -rotate);
return (value >> rotate) | (value << (N - rotate));
}
@@ -226,33 +239,38 @@ LIBC_INLINE constexpr To bit_or_static_cast(const From &from) {
}
}
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int first_leading_zero(T value) {
+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 : countl_one(value) + 1;
}
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int first_leading_one(T value) {
+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 <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int first_trailing_zero(T value) {
+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
: countr_zero(static_cast<T>(~value)) + 1;
}
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int first_trailing_one(T value) {
+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 : countr_zero(value) + 1;
}
/// Count number of 1's aka population count or hamming weight.
///
/// Only unsigned integral types are allowed.
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int count_ones(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
+count_ones(T value) {
int count = 0;
for (int i = 0; i != cpp::numeric_limits<T>::digits; ++i)
if ((value >> i) & 0x1)
@@ -272,8 +290,9 @@ ADD_SPECIALIZATION(unsigned long long, __builtin_popcountll)
// TODO: 128b specializations?
#undef ADD_SPECIALIZATION
-template <typename T, typename = cpp::enable_if_t<cpp::is_unsigned_v<T>>>
-[[nodiscard]] LIBC_INLINE constexpr int count_zeros(T value) {
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
+count_zeros(T value) {
return count_ones<T>(static_cast<T>(~value));
}
diff --git a/libc/src/__support/UInt.h b/libc/src/__support/UInt.h
index ae1fe7aaa18287..69cb0b5d1ce765 100644
--- a/libc/src/__support/UInt.h
+++ b/libc/src/__support/UInt.h
@@ -43,6 +43,9 @@ struct BigInt {
static_assert(is_integral_v<WordType> && is_unsigned_v<WordType>,
"WordType must be unsigned integer.");
+ using word_type = WordType;
+ LIBC_INLINE_VAR static constexpr bool SIGNED = Signed;
+ LIBC_INLINE_VAR static constexpr size_t BITS = Bits;
LIBC_INLINE_VAR
static constexpr size_t WORD_SIZE = sizeof(WordType) * CHAR_BIT;
@@ -50,6 +53,10 @@ struct BigInt {
"Number of bits in BigInt should be a multiple of WORD_SIZE.");
LIBC_INLINE_VAR static constexpr size_t WORD_COUNT = Bits / WORD_SIZE;
+
+ using unsigned_type = BigInt<BITS, false, word_type>;
+ using signed_type = BigInt<BITS, true, word_type>;
+
cpp::array<WordType, WORD_COUNT> val{};
LIBC_INLINE constexpr BigInt() = default;
@@ -579,19 +586,33 @@ struct BigInt {
return *this;
}
- LIBC_INLINE constexpr uint64_t clz() {
- uint64_t leading_zeroes = 0;
- for (size_t i = WORD_COUNT; i > 0; --i) {
- if (val[i - 1] == 0) {
- leading_zeroes += WORD_SIZE;
- } else {
- leading_zeroes += countl_zero(val[i - 1]);
+ // TODO: remove and use cpp::countl_zero below.
+ [[nodiscard]] LIBC_INLINE constexpr int clz() const {
+ constexpr int word_digits = cpp::numeric_limits<word_type>::digits;
+ int leading_zeroes = 0;
+ for (auto i = val.size(); i > 0;) {
+ --i;
+ const int zeroes = countl_zero(val[i]);
+ leading_zeroes += zeroes;
+ if (zeroes != word_digits)
break;
- }
}
return leading_zeroes;
}
+ // TODO: remove and use cpp::countr_zero below.
+ [[nodiscard]] LIBC_INLINE constexpr int ctz() const {
+ constexpr int word_digits = cpp::numeric_limits<word_type>::digits;
+ int trailing_zeroes = 0;
+ for (auto word : val) {
+ const int zeroes = countr_zero(word);
+ trailing_zeroes += zeroes;
+ if (zeroes != word_digits)
+ break;
+ }
+ return trailing_zeroes;
+ }
+
LIBC_INLINE constexpr void shift_left(size_t s) {
if constexpr (Bits == WORD_SIZE) {
// Use native types if possible.
@@ -912,66 +933,169 @@ template <> class numeric_limits<Int<128>> {
LIBC_INLINE_VAR static constexpr int digits = 128;
};
-// Provides is_integral of U/Int<128>, U/Int<192>, U/Int<256>.
-template <size_t Bits, bool Signed, typename T>
-struct is_integral<BigInt<Bits, Signed, T>> : cpp::true_type {};
-
-// Provides is_unsigned of UInt<128>, UInt<192>, UInt<256>.
-template <size_t Bits, bool Signed, typename T>
-struct is_unsigned<BigInt<Bits, Signed, T>> : cpp::bool_constant<!Signed> {};
-
-template <size_t Bits, bool Signed, typename T>
-struct make_unsigned<BigInt<Bits, Signed, T>>
- : type_identity<BigInt<Bits, false, T>> {};
-
-template <size_t Bits, bool Signed, typename T>
-struct make_signed<BigInt<Bits, Signed, T>>
- : type_identity<BigInt<Bits, true, T>> {};
-
-namespace internal {
-template <typename T> struct is_custom_uint : cpp::false_type {};
+// type traits to determine whether a T is a cpp::BigInt.
+template <typename T> struct is_big_int : cpp::false_type {};
template <size_t Bits, bool Signed, typename T>
-struct is_custom_uint<BigInt<Bits, Signed, T>> : cpp::true_type {};
-} // namespace internal
-
-// bit_cast to UInt
-// Note: The standard scheme for SFINAE selection is to have exactly one
-// function instanciation valid at a time. This is usually done by having a
-// predicate in one function and the negated predicate in the other one.
-// e.g.
-// template<typename = cpp::enable_if_t< is_custom_uint<To>::value == true> ...
-// template<typename = cpp::enable_if_t< is_custom_uint<To>::value == false> ...
-//
-// Unfortunately this would make the default 'cpp::bit_cast' aware of
-// 'is_custom_uint' (or any other customization). To prevent exposing all
-// customizations in the original function, we create a different function with
-// four 'typename's instead of three - otherwise it would be considered as a
-// redeclaration of the same function leading to "error: template parameter
-// redefines default argument".
-template <typename To, typename From,
- typename = cpp::enable_if_t<sizeof(To) == sizeof(From) &&
- cpp::is_trivially_copyable<To>::value &&
- cpp::is_trivially_copyable<From>::value>,
- typename = cpp::enable_if_t<internal::is_custom_uint<To>::value>>
-LIBC_INLINE constexpr To bit_cast(const From &from) {
+struct is_big_int<BigInt<Bits, Signed, T>> : cpp::true_type {};
+
+template <class T>
+LIBC_INLINE_VAR constexpr bool is_big_int_v = is_big_int<T>::value;
+
+// Specialization of cpp::bit_cast ('bit.h') from T to BigInt.
+template <typename To, typename From>
+LIBC_INLINE constexpr cpp::enable_if_t<
+ (sizeof(To) == sizeof(From)) && cpp::is_trivially_copyable<To>::value &&
+ cpp::is_trivially_copyable<From>::value && is_big_int<To>::value,
+ To>
+bit_cast(const From &from) {
To out;
using Storage = decltype(out.val);
out.val = cpp::bit_cast<Storage>(from);
return out;
}
-// bit_cast from UInt
-template <
- typename To, size_t Bits,
- typename = cpp::enable_if_t<sizeof(To) == sizeof(UInt<Bits>) &&
- cpp::is_trivially_constructible<To>::value &&
- cpp::is_trivially_copyable<To>::value &&
- cpp::is_trivially_copyable<UInt<Bits>>::value>>
-LIBC_INLINE constexpr To bit_cast(const UInt<Bits> &from) {
+// Specialization of cpp::bit_cast ('bit.h') from BigInt to T.
+template <typename To, size_t Bits>
+LIBC_INLINE constexpr cpp::enable_if_t<
+ sizeof(To) == sizeof(UInt<Bits>) &&
+ cpp::is_trivially_constructible<To>::value &&
+ cpp::is_trivially_copyable<To>::value &&
+ cpp::is_trivially_copyable<UInt<Bits>>::value,
+ To>
+bit_cast(const UInt<Bits> &from) {
return cpp::bit_cast<To>(from.val);
}
+// Specialization of cpp::has_single_bit ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, bool>
+has_single_bit(T value) {
+ for (auto word : value.val)
+ if (cpp::has_single_bit(word))
+ return true;
+ return false;
+}
+
+// Specialization of cpp::countr_zero ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+countr_zero(const T &value) {
+ return value.ctz();
+}
+
+// Specialization of cpp::countl_zero ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+countl_zero(const T &value) {
+ return value.clz();
+}
+
+// Specialization of cpp::countl_one ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+countl_one(T value) {
+ // TODO : Implement a faster version.
+ return cpp::countl_zero<T>(~value);
+}
+
+// Specialization of cpp::countr_one ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+countr_one(T value) {
+ // TODO : Implement a faster version.
+ return cpp::countr_zero<T>(~value);
+}
+
+// Specialization of cpp::bit_width ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+bit_width(T value) {
+ return cpp::numeric_limits<T>::digits - cpp::countl_zero(value);
+}
+
+// Forward-declare rotr so that rotl can use it.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, T>
+rotr(T value, int rotate);
+
+// Specialization of cpp::rotl ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, T>
+rotl(T value, int rotate) {
+ // TODO : Implement a faster version.
+ constexpr unsigned N = cpp::numeric_limits<T>::digits;
+ rotate = rotate % N;
+ if (!rotate)
+ return value;
+ if (rotate < 0)
+ return cpp::rotr<T>(value, -rotate);
+ return (value << rotate) | (value >> (N - rotate));
+}
+
+// Specialization of cpp::rotr ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, T>
+rotr(T value, int rotate) {
+ // TODO : Implement a faster version.
+ constexpr unsigned N = cpp::numeric_limits<T>::digits;
+ rotate = rotate % N;
+ if (!rotate)
+ return value;
+ if (rotate < 0)
+ return cpp::rotl<T>(value, -rotate);
+ return (value >> rotate) | (value << (N - rotate));
+}
+
+// Specialization of cpp::first_leading_zero ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+first_leading_zero(T value) {
+ return value == cpp::numeric_limits<T>::max() ? 0 : countl_one(value) + 1;
+}
+
+// Specialization of cpp::first_leading_one ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+first_leading_one(T value) {
+ return first_leading_zero(static_cast<T>(~value));
+}
+
+// Specialization of cpp::first_trailing_zero ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+first_trailing_zero(T value) {
+ return value == cpp::numeric_limits<T>::max()
+ ? 0
+ : countr_zero(static_cast<T>(~value)) + 1;
+}
+
+// Specialization of cpp::first_trailing_one ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+first_trailing_one(T value) {
+ return value == cpp::numeric_limits<T>::max() ? 0 : countr_zero(value) + 1;
+}
+
+// Specialization of cpp::count_ones ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+count_ones(T value) {
+ // TODO : Implement a faster version.
+ int count = 0;
+ for (int i = 0; i != cpp::numeric_limits<T>::digits; ++i)
+ if ((value >> i) & 0x1)
+ ++count;
+ return count;
+}
+
+// Specialization of cpp::count_zeros ('bit.h') for BigInt.
+template <typename T>
+[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_big_int_v<T>, int>
+count_zeros(T value) {
+ return count_ones<T>(static_cast<T>(~value));
+}
+
} // namespace LIBC_NAMESPACE::cpp
#endif // LLVM_LIBC_SRC___SUPPORT_UINT_H
diff --git a/libc/src/__support/float_to_string.h b/libc/src/__support/float_to_string.h
index 744842ced8d772..27476433a94575 100644
--- a/libc/src/__support/float_to_string.h
+++ b/libc/src/__support/float_to_string.h
@@ -713,7 +713,7 @@ template <> class FloatToString<long double> {
float_as_fixed.shift_left(SHIFT_AMOUNT);
// If there are still digits above the decimal point, handle those.
- if (float_as_fixed.clz() < EXTRA_INT_WIDTH) {
+ if (float_as_fixed.clz() < static_cast<int>(EXTRA_INT_WIDTH)) {
cpp::UInt<EXTRA_INT_WIDTH> above_decimal_point =
float_as_fixed >> FLOAT_AS_INT_WIDTH;
diff --git a/libc/src/__support/integer_to_string.h b/libc/src/__support/integer_to_string.h
index 8d3859c8eb0ca5..a9e7935e8e4660 100644
--- a/libc/src/__support/integer_to_string.h
+++ b/libc/src/__support/integer_to_string.h
@@ -67,6 +67,7 @@
#include "src/__support/CPP/span.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/CPP/type_traits.h"
+#include "src/__support/UInt.h" // is_big_int
#include "src/__support/common.h"
namespace LIBC_NAMESPACE {
@@ -149,6 +150,18 @@ template <bool forward> class StringBufferWriterImpl {
using StringBufferWriter = StringBufferWriterImpl<true>;
using BackwardStringBufferWriter = StringBufferWriterImpl<false>;
+template <typename T, class = void> struct IntegerWriterUnsigned {};
+
+template <typename T>
+struct IntegerWriterUnsigned<T, cpp::enable_if_t<cpp::is_integral_v<T>>> {
+ using type = cpp::make_unsigned_t<T>;
+};
+
+template <typename T>
+struct IntegerWriterUnsigned<T, cpp::enable_if_t<cpp::is_big_int_v<T>>> {
+ using type = typename T::unsigned_type;
+};
+
} // namespace details
namespace radix {
@@ -163,7 +176,7 @@ template <size_t radix> using Custom = details::Fmt<radix>;
// See file header for documentation.
template <typename T, typename Fmt = radix::Dec> class IntegerToString {
- static_assert(cpp::is_integral_v<T>);
+ static_assert(cpp::is_integral_v<T> || cpp::is_big_int_v<T>);
LIBC_INLINE static constexpr size_t compute_buffer_size() {
constexpr auto max_digits = []() -> size_t {
@@ -208,8 +221,8 @@ template <typename T, typename Fmt = radix::Dec> class IntegerToString {
// An internal stateless structure that handles the number formatting logic.
struct IntegerWriter {
- static_assert(cpp::is_integral_v<T>);
- using UNSIGNED_T = cpp::make_unsigned_t<T>;
+ static_assert(cpp::is_integral_v<T> || cpp::is_big_int_v<T>);
+ using UNSIGNED_T = typename details::IntegerWriterUnsigned<T>::type;
LIBC_INLINE static char digit_char(uint8_t digit) {
if (digit < 10)
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index 4668f0061975f8..36837c553efce1 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -74,6 +74,7 @@ add_unittest_framework_library(
libc.src.__support.CPP.type_traits
libc.src.__support.fixed_point.fx_rep
libc.src.__support.OSUtil.osutil
+ libc.src.__support.uint
libc.src.__support.uint128
)
diff --git a/libc/test/UnitTest/LibcTest.cpp b/libc/test/UnitTest/LibcTest.cpp
index 7b0e4fca83683b..0340f7ed37100e 100644
--- a/libc/test/UnitTest/LibcTest.cpp
+++ b/libc/test/UnitTest/LibcTest.cpp
@@ -38,7 +38,8 @@ TestLogger &operator<<(TestLogger &logger, Location Loc) {
// When the value is UInt128, __uint128_t or wider, show its hexadecimal
// digits.
template <typename T>
-cpp::enable_if_t<cpp::is_integral_v<T> && (sizeof(T) > sizeof(uint64_t)),
+cpp::enable_if_t<(cpp::is_integral_v<T> && (sizeof(T) > sizeof(uint64_t))) ||
+ cpp::is_big_int_v<T>,
cpp::string>
describeValue(T Value) {
static_assert(sizeof(T) % 8 == 0, "Unsupported size of UInt");
@@ -47,11 +48,10 @@ describeValue(T Value) {
}
// When the value is of a standard integral type, just display it as normal.
-template <typename ValType>
-cpp::enable_if_t<cpp::is_integral_v<ValType> &&
- sizeof(ValType) <= sizeof(uint64_t),
+template <typename T>
+cpp::enable_if_t<cpp::is_integral_v<T> && (sizeof(T) <= sizeof(uint64_t)),
cpp::string>
-describeValue(ValType Value) {
+describeValue(T Value) {
return cpp::to_string(Value);
}
diff --git a/libc/test/UnitTest/LibcTest.h b/libc/test/UnitTest/LibcTest.h
index 639f6005832576..d26d6490bcb572 100644
--- a/libc/test/UnitTest/LibcTest.h
+++ b/libc/test/UnitTest/LibcTest.h
@@ -127,6 +127,7 @@ class Test {
// of type promotion.
template <typename ValType,
cpp::enable_if_t<cpp::is_integral_v<ValType> ||
+ cpp::is_big_int_v<ValType> ||
cpp::is_fixed_point_v<ValType>,
int> = 0>
bool test(TestCond Cond, ValType LHS, ValType RHS, const char *LHSStr,
diff --git a/libc/test/UnitTest/TestLogger.cpp b/libc/test/UnitTest/TestLogger.cpp
index 6bb0e17dc3888e..469b3a11d57d9b 100644
--- a/libc/test/UnitTest/TestLogger.cpp
+++ b/libc/test/UnitTest/TestLogger.cpp
@@ -2,6 +2,7 @@
#include "src/__support/CPP/string.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/OSUtil/io.h" // write_to_stderr
+#include "src/__support/UInt.h" // is_big_int
#include "src/__support/UInt128.h"
#include <stdint.h>
@@ -47,8 +48,9 @@ template <> TestLogger &TestLogger::operator<<(void *addr) {
}
template <typename T> TestLogger &TestLogger::operator<<(T t) {
- if constexpr (cpp::is_integral_v<T> && cpp::is_unsigned_v<T> &&
- sizeof(T) > sizeof(uint64_t)) {
+ if constexpr (cpp::is_big_int_v<T> ||
+ (cpp::is_integral_v<T> && cpp::is_unsigned_v<T> &&
+ (sizeof(T) > sizeof(uint64_t)))) {
static_assert(sizeof(T) % 8 == 0, "Unsupported size of UInt");
const IntegerToString<T, radix::Hex::WithPrefix> buffer(t);
return *this << buffer.view();
@@ -68,7 +70,7 @@ template TestLogger &TestLogger::operator<< <unsigned short>(unsigned short);
template TestLogger &TestLogger::operator<< <unsigned int>(unsigned int);
template TestLogger &TestLogger::operator<< <unsigned long>(unsigned long);
template TestLogger &
-TestLogger::operator<< <unsigned long long>(unsigned long long);
+ TestLogger::operator<< <unsigned long long>(unsigned long long);
#ifdef __SIZEOF_INT128__
template TestLogger &TestLogger::operator<< <__uint128_t>(__uint128_t);
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 16ceaadf276fa5..e055b848a54cc1 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -507,6 +507,7 @@ libc_support_library(
":__support_cpp_span",
":__support_cpp_string_view",
":__support_cpp_type_traits",
+ ":__support_uint",
],
)
diff --git a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
index a5c18fbb68b398..44692947af7c08 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
@@ -18,6 +18,7 @@ libc_support_library(
"//libc:__support_cpp_string",
"//libc:__support_cpp_string_view",
"//libc:__support_osutil_io",
+ "//libc:__support_uint",
"//libc:__support_uint128",
],
)
More information about the libc-commits
mailing list