Skip to content

Conversation

@artagnon
Copy link
Contributor

@artagnon artagnon commented Nov 18, 2025

In line with a std proposal to introduce std::clmul, and in preparation to introduce a clmul intrinsic, implement carry-less multiply primitives for APIntOps, clmul[rh].

Ref: https://isocpp.org/files/papers/P3642R3.html

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
@llvmbot
Copy link
Member

llvmbot commented Nov 18, 2025

@llvm/pr-subscribers-llvm-support

Author: Ramkumar Ramachandra (artagnon)

Changes

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


Full diff: https://github.com/llvm/llvm-project/pull/168527.diff

3 Files Affected:

  • (modified) llvm/include/llvm/ADT/APInt.h (+10)
  • (modified) llvm/lib/Support/APInt.cpp (+9-1)
  • (modified) llvm/unittests/ADT/APIntTest.cpp (+28)
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<unsigned>(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

@llvmbot
Copy link
Member

llvmbot commented Nov 18, 2025

@llvm/pr-subscribers-llvm-adt

Author: Ramkumar Ramachandra (artagnon)

Changes

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


Full diff: https://github.com/llvm/llvm-project/pull/168527.diff

3 Files Affected:

  • (modified) llvm/include/llvm/ADT/APInt.h (+10)
  • (modified) llvm/lib/Support/APInt.cpp (+9-1)
  • (modified) llvm/unittests/ADT/APIntTest.cpp (+28)
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<unsigned>(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

@eisenwave
Copy link

https://isocpp.org/files/papers/P3642R3.html#possible-implementation There's a more recent version of the proposal by the way. The naive implementation there is a bit better than the one in R2.

@artagnon
Copy link
Contributor Author

https://isocpp.org/files/papers/P3642R3.html#possible-implementation There's a more recent version of the proposal by the way. The naive implementation there is a bit better than the one in R2.

I get a 500 error on that page: can you kindly post the text if you have access?

@dtcxzyw
Copy link
Member

dtcxzyw commented Nov 18, 2025

https://isocpp.org/files/papers/P3642R3.html#possible-implementation There's a more recent version of the proposal by the way. The naive implementation there is a bit better than the one in R2.

I get a 500 error on that page: can you kindly post the text if you have access?

Cloudflare is down :)

@eisenwave
Copy link

eisenwave commented Nov 18, 2025

https://eisenwave.github.io/cpp-proposals/clmul.html also has the latest version, and seems to work despite cloudflare outage.

The solution there is only suitable for small integers though; it relies on multiplication. If the concern is efficiency for large integers, we have a problem.

@github-actions
Copy link

github-actions bot commented Nov 18, 2025

🐧 Linux x64 Test Results

  • 186295 tests passed
  • 4853 tests skipped

@artagnon artagnon changed the title [APInt] Introduce carry-less multiply operation [APInt] Introduce carry-less multiply primitives Nov 18, 2025
Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks.

@artagnon artagnon merged commit 727ee7e into llvm:main Nov 18, 2025
10 checks passed
@artagnon artagnon deleted the apint-clmul branch November 18, 2025 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants