[llvm] [KnownBits] Make `clmul` optimal (PR #177916)

Jay Foad via llvm-commits llvm-commits at lists.llvm.org
Mon Jan 26 03:32:07 PST 2026


https://github.com/jayfoad updated https://github.com/llvm/llvm-project/pull/177916

>From f6433f56ea3182aef6b55c1fbe6fff4fb7d79045 Mon Sep 17 00:00:00 2001
From: Jay Foad <jay.foad at amd.com>
Date: Mon, 26 Jan 2026 09:44:23 +0000
Subject: [PATCH 1/3] [KnownBits] Make `clmul` optimal

Normally I would push back on implementations that take O(BitWidth)
steps, but since the corresponding APInt function already takes
O(BitWidth) steps, this KnownBits function is no worse in that regard.
---
 llvm/lib/Support/KnownBits.cpp           | 51 ++++++++++--------------
 llvm/unittests/Support/KnownBitsTest.cpp |  3 +-
 2 files changed, 21 insertions(+), 33 deletions(-)

diff --git a/llvm/lib/Support/KnownBits.cpp b/llvm/lib/Support/KnownBits.cpp
index b5d7006c8de2a..e2cde1851cde6 100644
--- a/llvm/lib/Support/KnownBits.cpp
+++ b/llvm/lib/Support/KnownBits.cpp
@@ -11,6 +11,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "llvm/ADT/Sequence.h"
 #include "llvm/Support/KnownBits.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
@@ -603,38 +604,26 @@ KnownBits KnownBits::ashr(const KnownBits &LHS, const KnownBits &RHS,
 }
 
 KnownBits KnownBits::clmul(const KnownBits &LHS, const KnownBits &RHS) {
-  unsigned BitWidth = LHS.getBitWidth();
-
-  // An m*n result will always fit in m+n-1 bits since there are no carries.
-  // If either input is to be zero, the result is zero.
-  unsigned ActiveBitsLHS = LHS.countMaxActiveBits();
-  unsigned ActiveBitsRHS = RHS.countMaxActiveBits();
-  unsigned ActiveBits;
-  if (ActiveBitsLHS == 0 || ActiveBitsRHS == 0)
-    ActiveBits = 0;
-  else
-    ActiveBits = std::min(BitWidth, ActiveBitsLHS + ActiveBitsRHS - 1);
-
-  // The result of the bottom bits of a clmul can be inferred by looking at the
-  // bottom bits of both operands and carryless multiplying them together. The
-  // number of bits we can determine follows the same logic as KnownBits::mul.
-  unsigned TrailBitsKnownLHS = (LHS.Zero | LHS.One).countr_one();
-  unsigned TrailBitsKnownRHS = (RHS.Zero | RHS.One).countr_one();
-  unsigned TrailZeroLHS = LHS.countMinTrailingZeros();
-  unsigned TrailZeroRHS = RHS.countMinTrailingZeros();
-  unsigned TrailZ = TrailZeroLHS + TrailZeroRHS;
-
-  // Figure out the fewest known-bits operand.
-  unsigned SmallestOperand = std::min(TrailBitsKnownLHS - TrailZeroLHS,
-                                      TrailBitsKnownRHS - TrailZeroRHS);
-  unsigned ResultBitsKnown = std::min(SmallestOperand + TrailZ, BitWidth);
-
-  APInt BottomKnown = APIntOps::clmul(LHS.One, RHS.One);
+  KnownBits Res = makeConstant(APIntOps::clmul(LHS.getMinValue(),
+                                               RHS.getMinValue()));
+
+  // This is the same operation as clmul except it accumulates the result with
+  // an OR instead of an XOR.
+  auto ClMulOr = [](const APInt &LHS, const APInt &RHS){
+    APInt Res(LHS.getBitWidth(), 0);
+    for (unsigned I : seq(LHS.getBitWidth())) {
+      if (LHS[I])
+        Res |= RHS << I;
+    }
+    return Res;
+  };
 
-  KnownBits Res(BitWidth);
-  Res.Zero.setBitsFrom(ActiveBits);
-  Res.Zero |= (~BottomKnown).getLoBits(ResultBitsKnown);
-  Res.One = BottomKnown.getLoBits(ResultBitsKnown);
+  // Bits in the result are known if, for every corresponding pair of input
+  // bits, both input bits are known or either input bit is known to be zero.
+  APInt Known = ~(ClMulOr(~LHS.Zero & ~LHS.One, ~RHS.Zero) |
+                  ClMulOr(~LHS.Zero, ~RHS.Zero & ~RHS.One));
+  Res.Zero &= Known;
+  Res.One &= Known;
 
   return Res;
 }
diff --git a/llvm/unittests/Support/KnownBitsTest.cpp b/llvm/unittests/Support/KnownBitsTest.cpp
index 539b698551437..b2c557062d38b 100644
--- a/llvm/unittests/Support/KnownBitsTest.cpp
+++ b/llvm/unittests/Support/KnownBitsTest.cpp
@@ -568,8 +568,7 @@ TEST(KnownBitsTest, BinaryExhaustive) {
 
   testBinaryOpExhaustive("avgCeilS", KnownBits::avgCeilS, APIntOps::avgCeilS);
 
-  testBinaryOpExhaustive("clmul", KnownBits::clmul, APIntOps::clmul,
-                         /*CheckOptimality=*/false);
+  testBinaryOpExhaustive("clmul", KnownBits::clmul, APIntOps::clmul);
 }
 
 TEST(KnownBitsTest, UnaryExhaustive) {

>From 5eb5ccc592e6b1ae3230ff12d3c4a30153b5711f Mon Sep 17 00:00:00 2001
From: Jay Foad <jay.foad at amd.com>
Date: Mon, 26 Jan 2026 10:52:36 +0000
Subject: [PATCH 2/3] clang-format

---
 llvm/lib/Support/KnownBits.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Support/KnownBits.cpp b/llvm/lib/Support/KnownBits.cpp
index e2cde1851cde6..48c9b4b6fa1e4 100644
--- a/llvm/lib/Support/KnownBits.cpp
+++ b/llvm/lib/Support/KnownBits.cpp
@@ -11,8 +11,8 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "llvm/ADT/Sequence.h"
 #include "llvm/Support/KnownBits.h"
+#include "llvm/ADT/Sequence.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cassert>
@@ -604,12 +604,12 @@ KnownBits KnownBits::ashr(const KnownBits &LHS, const KnownBits &RHS,
 }
 
 KnownBits KnownBits::clmul(const KnownBits &LHS, const KnownBits &RHS) {
-  KnownBits Res = makeConstant(APIntOps::clmul(LHS.getMinValue(),
-                                               RHS.getMinValue()));
+  KnownBits Res =
+      makeConstant(APIntOps::clmul(LHS.getMinValue(), RHS.getMinValue()));
 
   // This is the same operation as clmul except it accumulates the result with
   // an OR instead of an XOR.
-  auto ClMulOr = [](const APInt &LHS, const APInt &RHS){
+  auto ClMulOr = [](const APInt &LHS, const APInt &RHS) {
     APInt Res(LHS.getBitWidth(), 0);
     for (unsigned I : seq(LHS.getBitWidth())) {
       if (LHS[I])

>From cd233c080fcb474104aa97b44b4d6748d17855c7 Mon Sep 17 00:00:00 2001
From: Jay Foad <jay.foad at amd.com>
Date: Mon, 26 Jan 2026 11:31:47 +0000
Subject: [PATCH 3/3] Early-out in ClMulOr and APInt::clmul

---
 llvm/lib/Support/APInt.cpp     |  4 ++--
 llvm/lib/Support/KnownBits.cpp | 14 ++++++++------
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp
index c35abf725dbe0..25081b9b2a0e5 100644
--- a/llvm/lib/Support/APInt.cpp
+++ b/llvm/lib/Support/APInt.cpp
@@ -3203,9 +3203,9 @@ APInt llvm::APIntOps::clmul(const APInt &LHS, const APInt &RHS) {
   assert(LHS.getBitWidth() == RHS.getBitWidth());
   unsigned BW = LHS.getBitWidth();
   APInt Result(BW, 0);
-  for (unsigned I : seq<unsigned>(BW))
+  for (unsigned I : seq(std::min(RHS.getActiveBits(), BW - LHS.countr_zero())))
     if (RHS[I])
-      Result ^= LHS.shl(I);
+      Result ^= LHS << I;
   return Result;
 }
 
diff --git a/llvm/lib/Support/KnownBits.cpp b/llvm/lib/Support/KnownBits.cpp
index 48c9b4b6fa1e4..cc2d848abf7c9 100644
--- a/llvm/lib/Support/KnownBits.cpp
+++ b/llvm/lib/Support/KnownBits.cpp
@@ -610,12 +610,14 @@ KnownBits KnownBits::clmul(const KnownBits &LHS, const KnownBits &RHS) {
   // This is the same operation as clmul except it accumulates the result with
   // an OR instead of an XOR.
   auto ClMulOr = [](const APInt &LHS, const APInt &RHS) {
-    APInt Res(LHS.getBitWidth(), 0);
-    for (unsigned I : seq(LHS.getBitWidth())) {
-      if (LHS[I])
-        Res |= RHS << I;
-    }
-    return Res;
+    assert(LHS.getBitWidth() == RHS.getBitWidth());
+    unsigned BW = LHS.getBitWidth();
+    APInt Result(BW, 0);
+    for (unsigned I :
+         seq(std::min(RHS.getActiveBits(), BW - LHS.countr_zero())))
+      if (RHS[I])
+        Result |= LHS << I;
+    return Result;
   };
 
   // Bits in the result are known if, for every corresponding pair of input



More information about the llvm-commits mailing list