Skip to content

Conversation

kazutakahirata
Copy link
Contributor

This patch implements llvm::countr_zero_constexpr, a constexpr version
of llvm::countr_zero, in terms of llvm::popcount while making
llvm::popcount a constexpr function at the same time.

The new function is intended to serve as a marker. When we switch to
C++20, we will most likely go through functions in llvm/ADT/bit.h and
replace them with their counterparts from . With
llvm::countr_zero_constexpr, we can easily replace its use with
std::countr_zero.

This patch reimplements ConstantLog2 in terms of the new function.

This patch implements llvm::countr_zero_constexpr, a constexpr version
of llvm::countr_zero, in terms of llvm::popcount while making
llvm::popcount a constexpr function at the same time.

The new function is intended to serve as a marker.  When we switch to
C++20, we will most likely go through functions in llvm/ADT/bit.h and
replace them with their counterparts from <bit>.  With
llvm::countr_zero_constexpr, we can easily replace its use with
std::countr_zero.

This patch reimplements ConstantLog2 in terms of the new function.
@llvmbot
Copy link
Member

llvmbot commented Oct 19, 2025

@llvm/pr-subscribers-llvm-adt

@llvm/pr-subscribers-llvm-support

Author: Kazu Hirata (kazutakahirata)

Changes

This patch implements llvm::countr_zero_constexpr, a constexpr version
of llvm::countr_zero, in terms of llvm::popcount while making
llvm::popcount a constexpr function at the same time.

The new function is intended to serve as a marker. When we switch to
C++20, we will most likely go through functions in llvm/ADT/bit.h and
replace them with their counterparts from <bit>. With
llvm::countr_zero_constexpr, we can easily replace its use with
std::countr_zero.

This patch reimplements ConstantLog2 in terms of the new function.


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

3 Files Affected:

  • (modified) llvm/include/llvm/ADT/bit.h (+19-4)
  • (modified) llvm/include/llvm/Support/MathExtras.h (+1-3)
  • (modified) llvm/unittests/ADT/BitTest.cpp (+22)
diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 8b60b6998ca0b..2ea6c2a7f0a1d 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -151,7 +151,8 @@ template <typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
 /// Count the number of set bits in a value.
 /// Ex. popcount(0xF000F000) = 8
 /// Returns 0 if Value is zero.
-template <typename T> [[nodiscard]] inline int popcount(T Value) noexcept {
+template <typename T>
+[[nodiscard]] constexpr inline int popcount(T Value) noexcept {
   static_assert(std::is_unsigned_v<T>, "T must be an unsigned integer type");
   static_assert(sizeof(T) <= 8, "T must be 8 bytes or less");
 
@@ -177,6 +178,22 @@ template <typename T> [[nodiscard]] inline int popcount(T Value) noexcept {
   }
 }
 
+/// Count number of 0's from the least significant bit to the most
+///   stopping at the first 1.
+///
+/// A constexpr version of countr_zero.
+///
+/// Only unsigned integral types are allowed.
+///
+/// Returns std::numeric_limits<T>::digits on an input of 0.
+template <typename T> [[nodiscard]] constexpr int countr_zero_constexpr(T Val) {
+  static_assert(std::is_unsigned_v<T>,
+                "Only unsigned integral types are allowed.");
+  // "(Val & -Val) - 1" is a bitmask with all bits below
+  // the least significant 1 set.
+  return llvm::popcount(static_cast<std::make_unsigned_t<T>>((Val & -Val) - 1));
+}
+
 /// Count number of 0's from the least significant bit to the most
 ///   stopping at the first 1.
 ///
@@ -208,9 +225,7 @@ template <typename T> [[nodiscard]] int countr_zero(T Val) {
 #endif
   }
 
-  // Fallback to popcount.  "(Val & -Val) - 1" is a bitmask with all bits below
-  // the least significant 1 set.
-  return llvm::popcount(static_cast<std::make_unsigned_t<T>>((Val & -Val) - 1));
+  return countr_zero_constexpr(Val);
 }
 
 /// Count number of 0's from the most significant bit to the least
diff --git a/llvm/include/llvm/Support/MathExtras.h b/llvm/include/llvm/Support/MathExtras.h
index 412323354525b..9bbb8a2a30541 100644
--- a/llvm/include/llvm/Support/MathExtras.h
+++ b/llvm/include/llvm/Support/MathExtras.h
@@ -316,11 +316,9 @@ inline bool isShiftedMask_64(uint64_t Value, unsigned &MaskIdx,
 /// Valid only for positive powers of two.
 template <size_t kValue> constexpr size_t ConstantLog2() {
   static_assert(llvm::isPowerOf2_64(kValue), "Value is not a valid power of 2");
-  return 1 + ConstantLog2<kValue / 2>();
+  return llvm::countr_zero_constexpr(kValue);
 }
 
-template <> constexpr size_t ConstantLog2<1>() { return 0; }
-
 template <size_t kValue>
 LLVM_DEPRECATED("Use ConstantLog2 instead", "ConstantLog2")
 constexpr size_t CTLog2() {
diff --git a/llvm/unittests/ADT/BitTest.cpp b/llvm/unittests/ADT/BitTest.cpp
index 5b3df918e62b0..e8041bb6f7f58 100644
--- a/llvm/unittests/ADT/BitTest.cpp
+++ b/llvm/unittests/ADT/BitTest.cpp
@@ -286,6 +286,28 @@ TEST(BitTest, BitCeilConstexpr) {
   static_assert(llvm::bit_ceil_constexpr(257u) == 512);
 }
 
+TEST(BitTest, CountrZeroConstexpr) {
+  static_assert(llvm::countr_zero_constexpr(0u) == 32);
+  static_assert(llvm::countr_zero_constexpr(1u) == 0);
+  static_assert(llvm::countr_zero_constexpr(2u) == 1);
+  static_assert(llvm::countr_zero_constexpr(3u) == 0);
+  static_assert(llvm::countr_zero_constexpr(4u) == 2);
+  static_assert(llvm::countr_zero_constexpr(8u) == 3);
+  static_assert(llvm::countr_zero_constexpr(0x80000000u) == 31);
+
+  static_assert(llvm::countr_zero_constexpr(0ull) == 64);
+  static_assert(llvm::countr_zero_constexpr(1ull) == 0);
+  static_assert(llvm::countr_zero_constexpr(0x100000000ull) == 32);
+  static_assert(llvm::countr_zero_constexpr(0x8000000000000000ull) == 63);
+
+  static_assert(
+      llvm::countr_zero_constexpr(std::numeric_limits<uint16_t>::max()) == 0);
+  static_assert(
+      llvm::countr_zero_constexpr(std::numeric_limits<uint32_t>::max()) == 0);
+  static_assert(
+      llvm::countr_zero_constexpr(std::numeric_limits<uint64_t>::max()) == 0);
+}
+
 TEST(BitTest, CountlZero) {
   uint8_t Z8 = 0;
   uint16_t Z16 = 0;

@kazutakahirata
Copy link
Contributor Author

@kuhar Please take a look again. Thanks!

@github-actions
Copy link

github-actions bot commented Oct 20, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@kazutakahirata kazutakahirata merged commit 6284041 into llvm:main Oct 20, 2025
9 of 10 checks passed
@kazutakahirata kazutakahirata deleted the cleanup_20251019_ADT_bit_countr_zero_constexpr_ConstantLog2 branch October 20, 2025 19:36
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.

3 participants