From 0fd708d066c5fbad93b85e27acf205d054efd478 Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Tue, 18 Nov 2025 12:34:47 +0000 Subject: [PATCH 1/3] [APInt] Introduce carry-less multiply operation In line with a std proposal to introduce std::clmul, and in preparation to introduce a clmul intrinsic, implement a carry-less multiply for APIntOps. Ref: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3642r2.html --- llvm/include/llvm/ADT/APInt.h | 10 ++++++++++ llvm/lib/Support/APInt.cpp | 10 +++++++++- llvm/unittests/ADT/APIntTest.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/llvm/include/llvm/ADT/APInt.h b/llvm/include/llvm/ADT/APInt.h index fdb3b84b73a1f..0d40d360d96ac 100644 --- a/llvm/include/llvm/ADT/APInt.h +++ b/llvm/include/llvm/ADT/APInt.h @@ -2440,6 +2440,16 @@ LLVM_ABI APInt fshl(const APInt &Hi, const APInt &Lo, const APInt &Shift); /// (4) fshr(i8 255, i8 0, i8 9) = fshr(i8 255, i8 0, i8 1) // 9 % 8 LLVM_ABI APInt fshr(const APInt &Hi, const APInt &Lo, const APInt &Shift); +/// Perform a carry-less multiply, also known as XOR multiplication. All +/// arguments and result have the same bitwidth. +/// +/// Examples: +/// (1) clmul(i4 1, i4 2) = 2 +/// (2) clmul(i4 5, i4 6) = 14 +/// (3) clmul(i4 -4, i4 2) = -8 +/// (4) clmul(i4 -4, i4 -5) = 4 +LLVM_ABI APInt clmul(const APInt &LHS, const APInt &RHS); + } // namespace APIntOps // See friend declaration above. This additional declaration is required in diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp index f6fd5f9ddd633..de17758c8a1cd 100644 --- a/llvm/lib/Support/APInt.cpp +++ b/llvm/lib/Support/APInt.cpp @@ -15,10 +15,10 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/Hashing.h" +#include "llvm/ADT/Sequence.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/bit.h" -#include "llvm/Config/llvm-config.h" #include "llvm/Support/Alignment.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" @@ -3187,3 +3187,11 @@ APInt llvm::APIntOps::fshr(const APInt &Hi, const APInt &Lo, return Lo; return Hi.shl(Hi.getBitWidth() - ShiftAmt) | Lo.lshr(ShiftAmt); } + +APInt llvm::APIntOps::clmul(const APInt &LHS, const APInt &RHS) { + assert(LHS.getBitWidth() == RHS.getBitWidth()); + APInt Result(LHS.getBitWidth(), 0); + for (unsigned I : seq(Result.getBitWidth())) + Result ^= LHS.shl(I) * (RHS.lshr(I) & 1); + return Result; +} diff --git a/llvm/unittests/ADT/APIntTest.cpp b/llvm/unittests/ADT/APIntTest.cpp index ca9f9f17ee112..ddfaaba463a86 100644 --- a/llvm/unittests/ADT/APIntTest.cpp +++ b/llvm/unittests/ADT/APIntTest.cpp @@ -3823,4 +3823,32 @@ TEST(APIntTest, Fshr) { -8193); } +TEST(APIntTest, clmul) { + EXPECT_EQ(APIntOps::clmul(APInt(4, 1), APInt(4, 2)).getZExtValue(), 2U); + EXPECT_EQ(APIntOps::clmul(APInt(4, 5), APInt(4, 6)).getZExtValue(), 14U); + EXPECT_EQ(APIntOps::clmul(APInt(4, -4, /*isSigned*/ true), + APInt(4, 2, /*isSigned*/ false)) + .getSExtValue(), + -8); + EXPECT_EQ(APIntOps::clmul(APInt(4, -4, /*isSigned*/ true), + APInt(4, -5, /*isSigned*/ true)) + .getSExtValue(), + 4); + EXPECT_EQ(APIntOps::clmul(APInt(8, 0), APInt(8, 255)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmul(APInt(8, 15), APInt(8, 15)).getZExtValue(), 85U); + EXPECT_EQ(APIntOps::clmul(APInt(8, 1), APInt(8, 2)).getZExtValue(), 2U); + EXPECT_EQ(APIntOps::clmul(APInt(64, 0, /*isSigned*/ true), + APInt(64, 9223372036854775807, /*isSigned*/ true)) + .getSExtValue(), + 0); + EXPECT_EQ(APIntOps::clmul(APInt(64, 1, /*isSigned*/ true), + APInt(64, 2, /*isSigned*/ true)) + .getSExtValue(), + 2); + EXPECT_EQ(APIntOps::clmul(APInt(16, -2, /*isSigned*/ true), + APInt(16, -1, /*isSigned*/ true)) + .getSExtValue(), + -21846); +} + } // end anonymous namespace From a1ee9e02ee0f3d1d973def8d2b79ba4100bccc6a Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Tue, 18 Nov 2025 13:57:03 +0000 Subject: [PATCH 2/3] [APInt] Avoid a shift with a branch --- llvm/lib/Support/APInt.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp index de17758c8a1cd..3f422436b3262 100644 --- a/llvm/lib/Support/APInt.cpp +++ b/llvm/lib/Support/APInt.cpp @@ -3190,8 +3190,10 @@ APInt llvm::APIntOps::fshr(const APInt &Hi, const APInt &Lo, APInt llvm::APIntOps::clmul(const APInt &LHS, const APInt &RHS) { assert(LHS.getBitWidth() == RHS.getBitWidth()); - APInt Result(LHS.getBitWidth(), 0); - for (unsigned I : seq(Result.getBitWidth())) - Result ^= LHS.shl(I) * (RHS.lshr(I) & 1); + unsigned BW = LHS.getBitWidth(); + APInt Result(BW, 0); + for (unsigned I : seq(BW)) + if (RHS[I]) + Result ^= LHS.shl(I); return Result; } From 338998cf1be355940e7c0bb02f27b4455cda45cb Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Tue, 18 Nov 2025 14:09:12 +0000 Subject: [PATCH 3/3] [APInt] Also introduce clmul[rh] --- llvm/include/llvm/ADT/APInt.h | 15 +++++++-- llvm/lib/Support/APInt.cpp | 10 ++++++ llvm/unittests/ADT/APIntTest.cpp | 55 ++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/llvm/include/llvm/ADT/APInt.h b/llvm/include/llvm/ADT/APInt.h index 0d40d360d96ac..7e73cc1957c05 100644 --- a/llvm/include/llvm/ADT/APInt.h +++ b/llvm/include/llvm/ADT/APInt.h @@ -2440,8 +2440,8 @@ LLVM_ABI APInt fshl(const APInt &Hi, const APInt &Lo, const APInt &Shift); /// (4) fshr(i8 255, i8 0, i8 9) = fshr(i8 255, i8 0, i8 1) // 9 % 8 LLVM_ABI APInt fshr(const APInt &Hi, const APInt &Lo, const APInt &Shift); -/// Perform a carry-less multiply, also known as XOR multiplication. All -/// arguments and result have the same bitwidth. +/// Perform a carry-less multiply, also known as XOR multiplication, and return +/// low-bits. All arguments and result have the same bitwidth. /// /// Examples: /// (1) clmul(i4 1, i4 2) = 2 @@ -2450,6 +2450,17 @@ LLVM_ABI APInt fshr(const APInt &Hi, const APInt &Lo, const APInt &Shift); /// (4) clmul(i4 -4, i4 -5) = 4 LLVM_ABI APInt clmul(const APInt &LHS, const APInt &RHS); +/// Perform a reversed carry-less multiply. +/// +/// clmulr(a, b) = bitreverse(clmul(bitreverse(a), bitreverse(b))) +LLVM_ABI APInt clmulr(const APInt &LHS, const APInt &RHS); + +/// Perform a carry-less multiply, and return high-bits. All arguments and +/// result have the same bitwidth. +/// +/// clmulh(a, b) = clmulr(a, b) >> 1 +LLVM_ABI APInt clmulh(const APInt &LHS, const APInt &RHS); + } // namespace APIntOps // See friend declaration above. This additional declaration is required in diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp index 3f422436b3262..673cd867f0e45 100644 --- a/llvm/lib/Support/APInt.cpp +++ b/llvm/lib/Support/APInt.cpp @@ -3197,3 +3197,13 @@ APInt llvm::APIntOps::clmul(const APInt &LHS, const APInt &RHS) { Result ^= LHS.shl(I); return Result; } + +APInt llvm::APIntOps::clmulr(const APInt &LHS, const APInt &RHS) { + assert(LHS.getBitWidth() == RHS.getBitWidth()); + return clmul(LHS.reverseBits(), RHS.reverseBits()).reverseBits(); +} + +APInt llvm::APIntOps::clmulh(const APInt &LHS, const APInt &RHS) { + assert(LHS.getBitWidth() == RHS.getBitWidth()); + return clmulr(LHS, RHS).lshr(1); +} diff --git a/llvm/unittests/ADT/APIntTest.cpp b/llvm/unittests/ADT/APIntTest.cpp index ddfaaba463a86..4cb537da72e87 100644 --- a/llvm/unittests/ADT/APIntTest.cpp +++ b/llvm/unittests/ADT/APIntTest.cpp @@ -3851,4 +3851,59 @@ TEST(APIntTest, clmul) { -21846); } +TEST(APIntTest, clmulr) { + EXPECT_EQ(APIntOps::clmulr(APInt(4, 1), APInt(4, 2)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulr(APInt(4, 5), APInt(4, 6)).getZExtValue(), 3U); + EXPECT_EQ(APIntOps::clmulr(APInt(4, -4, /*isSigned*/ true), + APInt(4, 2, /*isSigned*/ false)) + .getSExtValue(), + 3); + EXPECT_EQ(APIntOps::clmulr(APInt(4, -4, /*isSigned*/ true), + APInt(4, -5, /*isSigned*/ true)) + .getSExtValue(), + -2); + EXPECT_EQ(APIntOps::clmulr(APInt(8, 0), APInt(8, 255)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulr(APInt(8, 15), APInt(8, 15)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulr(APInt(8, 1), APInt(8, 2)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulr(APInt(64, 0, /*isSigned*/ true), + APInt(64, 9223372036854775807, /*isSigned*/ true)) + .getSExtValue(), + 0); + EXPECT_EQ(APIntOps::clmulr(APInt(64, 1, /*isSigned*/ true), + APInt(64, 2, /*isSigned*/ true)) + .getSExtValue(), + 0); + EXPECT_EQ(APIntOps::clmulr(APInt(16, -2, /*isSigned*/ true), + APInt(16, -1, /*isSigned*/ true)) + .getSExtValue(), + -21845); +} + +TEST(APIntTest, clmulh) { + EXPECT_EQ(APIntOps::clmulh(APInt(4, 1), APInt(4, 2)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulh(APInt(4, 5), APInt(4, 6)).getZExtValue(), 1U); + EXPECT_EQ(APIntOps::clmulh(APInt(4, -4, /*isSigned*/ true), + APInt(4, 2, /*isSigned*/ false)) + .getSExtValue(), + 1); + EXPECT_EQ(APIntOps::clmulh(APInt(4, -4, /*isSigned*/ true), + APInt(4, -5, /*isSigned*/ true)) + .getSExtValue(), + 7); + EXPECT_EQ(APIntOps::clmulh(APInt(8, 0), APInt(8, 255)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulh(APInt(8, 15), APInt(8, 15)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulh(APInt(8, 1), APInt(8, 2)).getZExtValue(), 0U); + EXPECT_EQ(APIntOps::clmulh(APInt(64, 0, /*isSigned*/ true), + APInt(64, 9223372036854775807, /*isSigned*/ true)) + .getSExtValue(), + 0); + EXPECT_EQ(APIntOps::clmulh(APInt(64, 1, /*isSigned*/ true), + APInt(64, 2, /*isSigned*/ true)) + .getSExtValue(), + 0); + EXPECT_EQ(APIntOps::clmulh(APInt(16, -2, /*isSigned*/ true), + APInt(16, -1, /*isSigned*/ true)) + .getSExtValue(), + 21845); +} } // end anonymous namespace