Skip to content

Commit

Permalink
[reland][libc] Remove UB specializations of type traits for BigInt (#…
Browse files Browse the repository at this point in the history
…84299)

Note: This is a reland of #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.
  • Loading branch information
gchatelet authored Mar 7, 2024
1 parent c40146c commit 245d669
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 125 deletions.
1 change: 1 addition & 0 deletions libc/src/__support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
112 changes: 69 additions & 43 deletions libc/src/__support/CPP/bit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}

Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

Expand All @@ -147,26 +153,29 @@ 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);
}

/// Returns the number of bits needed to represent value if value is nonzero.
/// 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);
}

/// Returns the largest integral power of two no greater than value if value is
/// 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);
Expand All @@ -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);
Expand All @@ -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));
}

Expand All @@ -226,33 +239,44 @@ 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) {
// TODO: remove from 'bit.h' as it is not a standard function.
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) {
// TODO: remove from 'bit.h' as it is not a standard function.
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) {
// TODO: remove from 'bit.h' as it is not a standard function.
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) {
// TODO: remove from 'bit.h' as it is not a standard function.
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) {
// TODO: rename as 'popcount' to follow the standard
// https://en.cppreference.com/w/cpp/numeric/popcount
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)
Expand All @@ -272,8 +296,10 @@ 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) {
// TODO: remove from 'bit.h' as it is not a standard function.
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));
}

Expand Down
Loading

0 comments on commit 245d669

Please sign in to comment.