-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[APInt] Introduce carry-less multiply primitives #168527
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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/pr-subscribers-llvm-support Author: Ramkumar Ramachandra (artagnon) ChangesIn 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:
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
|
|
@llvm/pr-subscribers-llvm-adt Author: Ramkumar Ramachandra (artagnon) ChangesIn 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:
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
|
|
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 :) |
|
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. |
🐧 Linux x64 Test Results
|
dtcxzyw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks.
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