Skip to content

Make more math&bit functions constexpr, NFC #145856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

hokein
Copy link
Collaborator

@hokein hokein commented Jun 26, 2025

No description provided.

@hokein hokein marked this pull request as ready for review June 26, 2025 12:13
@hokein hokein requested a review from AaronBallman June 26, 2025 12:13
@llvmbot
Copy link
Member

llvmbot commented Jun 26, 2025

@llvm/pr-subscribers-llvm-support

@llvm/pr-subscribers-llvm-adt

Author: Haojian Wu (hokein)

Changes

Full diff: https://github.com/llvm/llvm-project/pull/145856.diff

2 Files Affected:

  • (modified) llvm/include/llvm/ADT/bit.h (+8-8)
  • (modified) llvm/include/llvm/Support/MathExtras.h (+25-22)
diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index d6e33c3e6133a..207653f2f8478 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -154,7 +154,7 @@ template <typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
 /// Only unsigned integral types are allowed.
 ///
 /// Returns std::numeric_limits<T>::digits on an input of 0.
-template <typename T> [[nodiscard]] int countr_zero(T Val) {
+template <typename T> [[nodiscard]] constexpr int countr_zero(T Val) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
   if (!Val)
@@ -200,7 +200,7 @@ template <typename T> [[nodiscard]] int countr_zero(T Val) {
 /// Only unsigned integral types are allowed.
 ///
 /// Returns std::numeric_limits<T>::digits on an input of 0.
-template <typename T> [[nodiscard]] int countl_zero(T Val) {
+template <typename T> [[nodiscard]] constexpr int countl_zero(T Val) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
   if (!Val)
@@ -244,7 +244,7 @@ template <typename T> [[nodiscard]] int countl_zero(T Val) {
 /// Only unsigned integral types are allowed.
 ///
 /// Returns std::numeric_limits<T>::digits on an input of all ones.
-template <typename T> [[nodiscard]] int countl_one(T Value) {
+template <typename T> [[nodiscard]] constexpr int countl_one(T Value) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
   return llvm::countl_zero<T>(~Value);
@@ -257,7 +257,7 @@ template <typename T> [[nodiscard]] int countl_one(T Value) {
 /// Only unsigned integral types are allowed.
 ///
 /// Returns std::numeric_limits<T>::digits on an input of all ones.
-template <typename T> [[nodiscard]] int countr_one(T Value) {
+template <typename T> [[nodiscard]] constexpr int countr_one(T Value) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
   return llvm::countr_zero<T>(~Value);
@@ -267,7 +267,7 @@ template <typename T> [[nodiscard]] int countr_one(T Value) {
 /// Returns 0 otherwise.
 ///
 /// Ex. bit_width(5) == 3.
-template <typename T> [[nodiscard]] int bit_width(T Value) {
+template <typename T> [[nodiscard]] constexpr int bit_width(T Value) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
   return std::numeric_limits<T>::digits - llvm::countl_zero(Value);
@@ -277,7 +277,7 @@ template <typename T> [[nodiscard]] int bit_width(T Value) {
 /// nonzero.  Returns 0 otherwise.
 ///
 /// Ex. bit_floor(5) == 4.
-template <typename T> [[nodiscard]] T bit_floor(T Value) {
+template <typename T> [[nodiscard]] constexpr T bit_floor(T Value) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
   if (!Value)
@@ -292,7 +292,7 @@ template <typename T> [[nodiscard]] T bit_floor(T Value) {
 ///
 /// The return value is undefined if the input is larger than the largest power
 /// of two representable in T.
-template <typename T> [[nodiscard]] T bit_ceil(T Value) {
+template <typename T> [[nodiscard]] constexpr T bit_ceil(T Value) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
   if (Value < 2)
@@ -304,7 +304,7 @@ template <typename T> [[nodiscard]] T bit_ceil(T Value) {
 /// Ex. popcount(0xF000F000) = 8
 /// Returns 0 if the word is zero.
 template <typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
-[[nodiscard]] inline int popcount(T Value) noexcept {
+[[nodiscard]] inline constexpr int popcount(T Value) noexcept {
   if constexpr (sizeof(T) <= 4) {
 #if defined(__GNUC__)
     return (int)__builtin_popcount(Value);
diff --git a/llvm/include/llvm/Support/MathExtras.h b/llvm/include/llvm/Support/MathExtras.h
index 7bbf1b3aab761..2cf0511231976 100644
--- a/llvm/include/llvm/Support/MathExtras.h
+++ b/llvm/include/llvm/Support/MathExtras.h
@@ -303,8 +303,8 @@ constexpr bool isPowerOf2_64(uint64_t Value) {
 /// If true, \p MaskIdx will specify the index of the lowest set bit and \p
 /// MaskLen is updated to specify the length of the mask, else neither are
 /// updated.
-inline bool isShiftedMask_32(uint32_t Value, unsigned &MaskIdx,
-                             unsigned &MaskLen) {
+inline constexpr bool isShiftedMask_32(uint32_t Value, unsigned &MaskIdx,
+                                       unsigned &MaskLen) {
   if (!isShiftedMask_32(Value))
     return false;
   MaskIdx = llvm::countr_zero(Value);
@@ -316,8 +316,8 @@ inline bool isShiftedMask_32(uint32_t Value, unsigned &MaskIdx,
 /// remainder zero (64 bit version.) If true, \p MaskIdx will specify the index
 /// of the lowest set bit and \p MaskLen is updated to specify the length of the
 /// mask, else neither are updated.
-inline bool isShiftedMask_64(uint64_t Value, unsigned &MaskIdx,
-                             unsigned &MaskLen) {
+inline constexpr bool isShiftedMask_64(uint64_t Value, unsigned &MaskIdx,
+                                       unsigned &MaskLen) {
   if (!isShiftedMask_64(Value))
     return false;
   MaskIdx = llvm::countr_zero(Value);
@@ -337,26 +337,26 @@ template <> constexpr size_t CTLog2<1>() { return 0; }
 /// Return the floor log base 2 of the specified value, -1 if the value is zero.
 /// (32 bit edition.)
 /// Ex. Log2_32(32) == 5, Log2_32(1) == 0, Log2_32(0) == -1, Log2_32(6) == 2
-inline unsigned Log2_32(uint32_t Value) {
+inline constexpr unsigned Log2_32(uint32_t Value) {
   return 31 - llvm::countl_zero(Value);
 }
 
 /// Return the floor log base 2 of the specified value, -1 if the value is zero.
 /// (64 bit edition.)
-inline unsigned Log2_64(uint64_t Value) {
+inline constexpr unsigned Log2_64(uint64_t Value) {
   return 63 - llvm::countl_zero(Value);
 }
 
 /// Return the ceil log base 2 of the specified value, 32 if the value is zero.
 /// (32 bit edition).
 /// Ex. Log2_32_Ceil(32) == 5, Log2_32_Ceil(1) == 0, Log2_32_Ceil(6) == 3
-inline unsigned Log2_32_Ceil(uint32_t Value) {
+inline constexpr unsigned Log2_32_Ceil(uint32_t Value) {
   return 32 - llvm::countl_zero(Value - 1);
 }
 
 /// Return the ceil log base 2 of the specified value, 64 if the value is zero.
 /// (64 bit edition.)
-inline unsigned Log2_64_Ceil(uint64_t Value) {
+inline constexpr unsigned Log2_64_Ceil(uint64_t Value) {
   return 64 - llvm::countl_zero(Value - 1);
 }
 
@@ -391,7 +391,7 @@ constexpr uint64_t NextPowerOf2(uint64_t A) {
 
 /// Returns the power of two which is greater than or equal to the given value.
 /// Essentially, it is a ceil operation across the domain of powers of two.
-inline uint64_t PowerOf2Ceil(uint64_t A) {
+inline constexpr uint64_t PowerOf2Ceil(uint64_t A) {
   if (!A || A > UINT64_MAX / 2)
     return 0;
   return UINT64_C(1) << Log2_64_Ceil(A);
@@ -569,7 +569,7 @@ template <unsigned B> constexpr int32_t SignExtend32(uint32_t X) {
 
 /// Sign-extend the number in the bottom B bits of X to a 32-bit integer.
 /// Requires B <= 32.
-inline int32_t SignExtend32(uint32_t X, unsigned B) {
+inline constexpr int32_t SignExtend32(uint32_t X, unsigned B) {
   assert(B <= 32 && "Bit width out of range.");
   if (B == 0)
     return 0;
@@ -587,7 +587,7 @@ template <unsigned B> constexpr int64_t SignExtend64(uint64_t x) {
 
 /// Sign-extend the number in the bottom B bits of X to a 64-bit integer.
 /// Requires B <= 64.
-inline int64_t SignExtend64(uint64_t X, unsigned B) {
+inline constexpr int64_t SignExtend64(uint64_t X, unsigned B) {
   assert(B <= 64 && "Bit width out of range.");
   if (B == 0)
     return 0;
@@ -614,9 +614,9 @@ constexpr T AbsoluteDifference(U X, V Y) {
 /// maximum representable value of T on overflow.  ResultOverflowed indicates if
 /// the result is larger than the maximum representable value of type T.
 template <typename T>
-std::enable_if_t<std::is_unsigned_v<T>, T>
+constexpr std::enable_if_t<std::is_unsigned_v<T>, T>
 SaturatingAdd(T X, T Y, bool *ResultOverflowed = nullptr) {
-  bool Dummy;
+  bool Dummy = 0;
   bool &Overflowed = ResultOverflowed ? *ResultOverflowed : Dummy;
   // Hacker's Delight, p. 29
   T Z = X + Y;
@@ -630,8 +630,8 @@ SaturatingAdd(T X, T Y, bool *ResultOverflowed = nullptr) {
 /// Add multiple unsigned integers of type T.  Clamp the result to the
 /// maximum representable value of T on overflow.
 template <class T, class... Ts>
-std::enable_if_t<std::is_unsigned_v<T>, T> SaturatingAdd(T X, T Y, T Z,
-                                                         Ts... Args) {
+constexpr std::enable_if_t<std::is_unsigned_v<T>, T>
+SaturatingAdd(T X, T Y, T Z, Ts... Args) {
   bool Overflowed = false;
   T XY = SaturatingAdd(X, Y, &Overflowed);
   if (Overflowed)
@@ -643,9 +643,9 @@ std::enable_if_t<std::is_unsigned_v<T>, T> SaturatingAdd(T X, T Y, T Z,
 /// maximum representable value of T on overflow.  ResultOverflowed indicates if
 /// the result is larger than the maximum representable value of type T.
 template <typename T>
-std::enable_if_t<std::is_unsigned_v<T>, T>
+constexpr std::enable_if_t<std::is_unsigned_v<T>, T>
 SaturatingMultiply(T X, T Y, bool *ResultOverflowed = nullptr) {
-  bool Dummy;
+  bool Dummy = 0;
   bool &Overflowed = ResultOverflowed ? *ResultOverflowed : Dummy;
 
   // Hacker's Delight, p. 30 has a different algorithm, but we don't use that
@@ -689,9 +689,9 @@ SaturatingMultiply(T X, T Y, bool *ResultOverflowed = nullptr) {
 /// overflow. ResultOverflowed indicates if the result is larger than the
 /// maximum representable value of type T.
 template <typename T>
-std::enable_if_t<std::is_unsigned_v<T>, T>
+constexpr std::enable_if_t<std::is_unsigned_v<T>, T>
 SaturatingMultiplyAdd(T X, T Y, T A, bool *ResultOverflowed = nullptr) {
-  bool Dummy;
+  bool Dummy = 0;
   bool &Overflowed = ResultOverflowed ? *ResultOverflowed : Dummy;
 
   T Product = SaturatingMultiply(X, Y, &Overflowed);
@@ -707,7 +707,8 @@ LLVM_ABI extern const float huge_valf;
 /// Add two signed integers, computing the two's complement truncated result,
 /// returning true if overflow occurred.
 template <typename T>
-std::enable_if_t<std::is_signed_v<T>, T> AddOverflow(T X, T Y, T &Result) {
+constexpr std::enable_if_t<std::is_signed_v<T>, T> AddOverflow(T X, T Y,
+                                                               T &Result) {
 #if __has_builtin(__builtin_add_overflow)
   return __builtin_add_overflow(X, Y, &Result);
 #else
@@ -733,7 +734,8 @@ std::enable_if_t<std::is_signed_v<T>, T> AddOverflow(T X, T Y, T &Result) {
 /// Subtract two signed integers, computing the two's complement truncated
 /// result, returning true if an overflow occurred.
 template <typename T>
-std::enable_if_t<std::is_signed_v<T>, T> SubOverflow(T X, T Y, T &Result) {
+constexpr std::enable_if_t<std::is_signed_v<T>, T> SubOverflow(T X, T Y,
+                                                               T &Result) {
 #if __has_builtin(__builtin_sub_overflow)
   return __builtin_sub_overflow(X, Y, &Result);
 #else
@@ -759,7 +761,8 @@ std::enable_if_t<std::is_signed_v<T>, T> SubOverflow(T X, T Y, T &Result) {
 /// Multiply two signed integers, computing the two's complement truncated
 /// result, returning true if an overflow occurred.
 template <typename T>
-std::enable_if_t<std::is_signed_v<T>, T> MulOverflow(T X, T Y, T &Result) {
+constexpr std::enable_if_t<std::is_signed_v<T>, T> MulOverflow(T X, T Y,
+                                                               T &Result) {
 #if __has_builtin(__builtin_mul_overflow)
   return __builtin_mul_overflow(X, Y, &Result);
 #else

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@@ -200,7 +200,7 @@ template <typename T> [[nodiscard]] int countr_zero(T Val) {
/// Only unsigned integral types are allowed.
///
/// Returns std::numeric_limits<T>::digits on an input of 0.
template <typename T> [[nodiscard]] int countl_zero(T Val) {
template <typename T> [[nodiscard]] constexpr int countl_zero(T Val) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if you are aware that not all code paths here (and some other functions) are actually available in constant expressions ? Like for MSVC it uses _BitScanReverse which itself isn't constexpr.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, this could be unit-tested

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing it out! I didn’t check all the code paths — I only checked at the __builtin_clz version.

Copy link
Member

@kuhar kuhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation / use case for making these constexpr?

@@ -200,7 +200,7 @@ template <typename T> [[nodiscard]] int countr_zero(T Val) {
/// Only unsigned integral types are allowed.
///
/// Returns std::numeric_limits<T>::digits on an input of 0.
template <typename T> [[nodiscard]] int countl_zero(T Val) {
template <typename T> [[nodiscard]] constexpr int countl_zero(T Val) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, this could be unit-tested

@hokein
Copy link
Collaborator Author

hokein commented Jun 30, 2025

What is the motivation / use case for making these constexpr?

This improves the developer experience — for example, when using clangd, you can see the value of the expression on hover.

That said, I'm closing this PR for now. Since C++20 provides std::countl_zero as a constexpr function, it makes more sense to adopt that once we start compiling LLVM with -std=c++20.

@hokein hokein closed this Jun 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants