[llvm] [ADT] Add llvm::countr_zero_constexpr (PR #164188)

Kazu Hirata via llvm-commits llvm-commits at lists.llvm.org
Mon Oct 20 06:57:35 PDT 2025


https://github.com/kazutakahirata updated https://github.com/llvm/llvm-project/pull/164188

>From 06827d99c837cb142d83cc7f729b21315c2d5725 Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Wed, 10 Sep 2025 20:02:01 -0700
Subject: [PATCH 1/2] [ADT] Add llvm::countr_zero_constexpr

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.
---
 llvm/include/llvm/ADT/bit.h            | 23 +++++++++++++++++++----
 llvm/include/llvm/Support/MathExtras.h |  4 +---
 llvm/unittests/ADT/BitTest.cpp         | 22 ++++++++++++++++++++++
 3 files changed, 42 insertions(+), 7 deletions(-)

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;

>From 91f29bffb23eb2f8c4b1e945b6cb213bf9ca7ebd Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Mon, 20 Oct 2025 06:54:12 -0700
Subject: [PATCH 2/2] Address a comment.

---
 llvm/include/llvm/ADT/bit.h | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 2ea6c2a7f0a1d..2fcb46a337dc1 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -152,7 +152,7 @@ template <typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
 /// Ex. popcount(0xF000F000) = 8
 /// Returns 0 if Value is zero.
 template <typename T>
-[[nodiscard]] constexpr inline int popcount(T Value) noexcept {
+[[nodiscard]] constexpr 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");
 
@@ -179,7 +179,7 @@ template <typename T>
 }
 
 /// Count number of 0's from the least significant bit to the most
-///   stopping at the first 1.
+/// stopping at the first 1.
 ///
 /// A constexpr version of countr_zero.
 ///
@@ -189,13 +189,13 @@ template <typename T>
 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.
+  // "(Val & -Val) - 1" generates a mask with all bits set up to (but not
+  // including) the least significant set bit of Val.
   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.
+/// stopping at the first 1.
 ///
 /// Only unsigned integral types are allowed.
 ///



More information about the llvm-commits mailing list