[llvm] [ADT] implement countl_zero_constexpr and reuse it for countl_zero & bit_width_constexpr (PR #189111)

Max Graey via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 30 07:39:27 PDT 2026


https://github.com/MaxGraey updated https://github.com/llvm/llvm-project/pull/189111

>From 8459ed648e485798ac853616fc221099688dc168 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Fri, 27 Mar 2026 22:52:18 +0200
Subject: [PATCH 1/8] add countl_zero_constexpr and reuse it in countl_zero

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

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 5971b75045b6b..14244efc7e230 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -227,6 +227,33 @@ template <typename T> [[nodiscard]] int countr_zero(T Val) {
   return countr_zero_constexpr(Val);
 }
 
+/// Count number of 0's from the most significant bit to the least
+///   stopping at the first 1.
+///
+/// A constexpr version of countl_zero.
+///
+/// Only unsigned integral types are allowed.
+///
+/// Returns std::numeric_limits<T>::digits on an input of 0.
+template <typename T> [[nodiscard]] constexpr int countl_zero_constexpr(T Val) {
+  static_assert(std::is_unsigned_v<T>,
+                "Only unsigned integral types are allowed.");
+
+  constexpr int Digits = std::numeric_limits<T>::digits;
+
+  if (!Val)
+    return Digits;
+
+  Val |= (Val >> 1);
+  Val |= (Val >> 2);
+  Val |= (Val >> 4);
+  if constexpr (Digits > 8)  Val |= (Val >> 8);
+  if constexpr (Digits > 16) Val |= (Val >> 16);
+  if constexpr (Digits > 32) Val |= (Val >> 32);
+
+  return Digits - llvm::popcount(Val);
+}
+
 /// Count number of 0's from the most significant bit to the least
 ///   stopping at the first 1.
 ///
@@ -258,16 +285,7 @@ template <typename T> [[nodiscard]] int countl_zero(T Val) {
 #endif
   }
 
-  // Fall back to the bisection method.
-  unsigned ZeroBits = 0;
-  for (T Shift = std::numeric_limits<T>::digits >> 1; Shift; Shift >>= 1) {
-    T Tmp = Val >> Shift;
-    if (Tmp)
-      Val = Tmp;
-    else
-      ZeroBits |= Shift;
-  }
-  return ZeroBits;
+  return countl_zero_constexpr(Val);
 }
 
 /// Count the number of ones from the most significant bit to the first

>From 78f33f8a2088f7d8050a2e1ce338d678cd37f445 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Fri, 27 Mar 2026 22:59:10 +0200
Subject: [PATCH 2/8] fix format

---
 llvm/include/llvm/ADT/bit.h | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 14244efc7e230..f0f655bb55cf3 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -247,10 +247,15 @@ template <typename T> [[nodiscard]] constexpr int countl_zero_constexpr(T Val) {
   Val |= (Val >> 1);
   Val |= (Val >> 2);
   Val |= (Val >> 4);
-  if constexpr (Digits > 8)  Val |= (Val >> 8);
-  if constexpr (Digits > 16) Val |= (Val >> 16);
-  if constexpr (Digits > 32) Val |= (Val >> 32);
-
+  if constexpr (Digits > 8) {
+    Val |= (Val >> 8);
+  }
+  if constexpr (Digits > 16) {
+    Val |= (Val >> 16);
+  }
+  if constexpr (Digits > 32) {
+    Val |= (Val >> 32);
+  }
   return Digits - llvm::popcount(Val);
 }
 

>From b6253f2045ae980c6798ce64e0b2c7e94c54a6c3 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Fri, 27 Mar 2026 23:16:26 +0200
Subject: [PATCH 3/8] also reuse for bit_width_constexpr

---
 llvm/include/llvm/ADT/bit.h | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index f0f655bb55cf3..160231c12574f 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -338,12 +338,7 @@ template <typename T> [[nodiscard]] int bit_width(T Value) {
 template <typename T> [[nodiscard]] constexpr int bit_width_constexpr(T Value) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
-  int Width = 0;
-  while (Value > 0) {
-    Value >>= 1;
-    ++Width;
-  }
-  return Width;
+  return std::numeric_limits<T>::digits - llvm::countl_zero_constexpr(Value);
 }
 
 /// Returns the largest integral power of two no greater than Value if Value is

>From 7f790812cbce0f242cb9c616d40392c676c405a0 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Sat, 28 Mar 2026 02:45:02 +0200
Subject: [PATCH 4/8] also allow small types for countl_zero

---
 llvm/include/llvm/ADT/bit.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 160231c12574f..892a82eb2c405 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -272,7 +272,7 @@ template <typename T> [[nodiscard]] int countl_zero(T Val) {
     return std::numeric_limits<T>::digits;
 
   // Use the intrinsic if available.
-  if constexpr (sizeof(T) == 4) {
+  if constexpr (sizeof(T) <= 4) {
 #if __has_builtin(__builtin_clz) || defined(__GNUC__)
     return __builtin_clz(Val);
 #elif defined(_MSC_VER)

>From bf0d5d37075fbfd119cf75a54c6ebf3178a33989 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Sat, 28 Mar 2026 03:36:56 +0200
Subject: [PATCH 5/8] fix

---
 llvm/include/llvm/ADT/bit.h | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 892a82eb2c405..099cc03ae673a 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -273,12 +273,14 @@ template <typename T> [[nodiscard]] int countl_zero(T Val) {
 
   // Use the intrinsic if available.
   if constexpr (sizeof(T) <= 4) {
+    constexpr int ExtraBits =
+      std::numeric_limits<uint32_t>::digits - std::numeric_limits<T>::digits;
 #if __has_builtin(__builtin_clz) || defined(__GNUC__)
-    return __builtin_clz(Val);
+    return __builtin_clz(Val) - ExtraBits;
 #elif defined(_MSC_VER)
     unsigned long Index;
     _BitScanReverse(&Index, Val);
-    return Index ^ 31;
+    return static_cast<int>((std::numeric_limits<T>::digits - 1) - Index);
 #endif
   } else if constexpr (sizeof(T) == 8) {
 #if __has_builtin(__builtin_clzll) || defined(__GNUC__)

>From 0a5444bd515db86f521ab7121e101c19ce66a3d7 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Sat, 28 Mar 2026 03:41:26 +0200
Subject: [PATCH 6/8] fix format

---
 llvm/include/llvm/ADT/bit.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 099cc03ae673a..8b7dab5e4c576 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -274,7 +274,7 @@ template <typename T> [[nodiscard]] int countl_zero(T Val) {
   // Use the intrinsic if available.
   if constexpr (sizeof(T) <= 4) {
     constexpr int ExtraBits =
-      std::numeric_limits<uint32_t>::digits - std::numeric_limits<T>::digits;
+        std::numeric_limits<uint32_t>::digits - std::numeric_limits<T>::digits;
 #if __has_builtin(__builtin_clz) || defined(__GNUC__)
     return __builtin_clz(Val) - ExtraBits;
 #elif defined(_MSC_VER)

>From b41daff4babe4f232c286747e664b0aef18c2463 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Sat, 28 Mar 2026 04:04:56 +0200
Subject: [PATCH 7/8] refactoring

---
 llvm/include/llvm/ADT/bit.h | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 8b7dab5e4c576..2bbc4a03be57e 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -239,24 +239,24 @@ template <typename T> [[nodiscard]] constexpr int countl_zero_constexpr(T Val) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
 
-  constexpr int Digits = std::numeric_limits<T>::digits;
+  constexpr int BitWidth = std::numeric_limits<T>::digits;
 
   if (!Val)
-    return Digits;
+    return BitWidth;
 
   Val |= (Val >> 1);
   Val |= (Val >> 2);
   Val |= (Val >> 4);
-  if constexpr (Digits > 8) {
+  if constexpr (BitWidth > 8) {
     Val |= (Val >> 8);
   }
-  if constexpr (Digits > 16) {
+  if constexpr (BitWidth > 16) {
     Val |= (Val >> 16);
   }
-  if constexpr (Digits > 32) {
+  if constexpr (BitWidth > 32) {
     Val |= (Val >> 32);
   }
-  return Digits - llvm::popcount(Val);
+  return BitWidth - llvm::popcount(Val);
 }
 
 /// Count number of 0's from the most significant bit to the least
@@ -268,19 +268,21 @@ template <typename T> [[nodiscard]] constexpr int countl_zero_constexpr(T Val) {
 template <typename T> [[nodiscard]] int countl_zero(T Val) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
+
+  constexpr int BitWidth = std::numeric_limits<T>::digits;
+
   if (!Val)
-    return std::numeric_limits<T>::digits;
+    return BitWidth;
 
   // Use the intrinsic if available.
   if constexpr (sizeof(T) <= 4) {
-    constexpr int ExtraBits =
-        std::numeric_limits<uint32_t>::digits - std::numeric_limits<T>::digits;
 #if __has_builtin(__builtin_clz) || defined(__GNUC__)
-    return __builtin_clz(Val) - ExtraBits;
+    constexpr int Padding = std::numeric_limits<uint32_t>::digits - BitWidth;
+    return __builtin_clz(Val) - Padding;
 #elif defined(_MSC_VER)
     unsigned long Index;
     _BitScanReverse(&Index, Val);
-    return static_cast<int>((std::numeric_limits<T>::digits - 1) - Index);
+    return static_cast<int>((BitWidth - 1) - Index);
 #endif
   } else if constexpr (sizeof(T) == 8) {
 #if __has_builtin(__builtin_clzll) || defined(__GNUC__)

>From e08e3a13a8539d5e81f1d955771df2a5d58c48c2 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Mon, 30 Mar 2026 17:39:09 +0300
Subject: [PATCH 8/8] use bisection instead utilize popcount for
 countl_zero_constexpr

---
 llvm/include/llvm/ADT/bit.h | 26 ++++++++------------------
 1 file changed, 8 insertions(+), 18 deletions(-)

diff --git a/llvm/include/llvm/ADT/bit.h b/llvm/include/llvm/ADT/bit.h
index 2bbc4a03be57e..505be0f682678 100644
--- a/llvm/include/llvm/ADT/bit.h
+++ b/llvm/include/llvm/ADT/bit.h
@@ -238,25 +238,15 @@ template <typename T> [[nodiscard]] int countr_zero(T Val) {
 template <typename T> [[nodiscard]] constexpr int countl_zero_constexpr(T Val) {
   static_assert(std::is_unsigned_v<T>,
                 "Only unsigned integral types are allowed.");
-
-  constexpr int BitWidth = std::numeric_limits<T>::digits;
-
-  if (!Val)
-    return BitWidth;
-
-  Val |= (Val >> 1);
-  Val |= (Val >> 2);
-  Val |= (Val >> 4);
-  if constexpr (BitWidth > 8) {
-    Val |= (Val >> 8);
-  }
-  if constexpr (BitWidth > 16) {
-    Val |= (Val >> 16);
-  }
-  if constexpr (BitWidth > 32) {
-    Val |= (Val >> 32);
+  unsigned ZeroBits = 0;
+  for (T Shift = std::numeric_limits<T>::digits >> 1; Shift; Shift >>= 1) {
+    T Tmp = Val >> Shift;
+    if (Tmp)
+      Val = Tmp;
+    else
+      ZeroBits |= Shift;
   }
-  return BitWidth - llvm::popcount(Val);
+  return ZeroBits;
 }
 
 /// Count number of 0's from the most significant bit to the least



More information about the llvm-commits mailing list