[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