[llvm] 5e14ef5 - [RISCV][GlobalISel] Add G_CLMULR support (#224525)

via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 09:52:09 PDT 2026


Author: Jian Li
Date: 2026-09-21T09:51:34-07:00
New Revision: 5e14ef5d10879eafe6b412278d6d851719e62c88

URL: https://github.com/llvm/llvm-project/commit/5e14ef5d10879eafe6b412278d6d851719e62c88
DIFF: https://github.com/llvm/llvm-project/commit/5e14ef5d10879eafe6b412278d6d851719e62c88.diff

LOG: [RISCV][GlobalISel] Add G_CLMULR support (#224525)

Add G_CLMULR to represent reversed carry-less multiplication in generic
Machine IR.

Translate llvm.riscv.clmulr to G_CLMULR and mark it legal for native
XLEN scalar types when Zbc is available.

Added: 
    llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv32.ll
    llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv64.ll

Modified: 
    llvm/docs/GlobalISel/GenericOpcode.md
    llvm/include/llvm/Support/TargetOpcodes.def
    llvm/include/llvm/Target/GenericOpcodes.td
    llvm/include/llvm/Target/GlobalISel/SelectionDAGCompat.td
    llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
    llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
    llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
    llvm/test/TableGen/get-named-operand-idx.td

Removed: 
    


################################################################################
diff  --git a/llvm/docs/GlobalISel/GenericOpcode.md b/llvm/docs/GlobalISel/GenericOpcode.md
index 39b11b1d76355..de316fcfb134d 100644
--- a/llvm/docs/GlobalISel/GenericOpcode.md
+++ b/llvm/docs/GlobalISel/GenericOpcode.md
@@ -321,6 +321,18 @@ return bits [2N-1:N]. The destination and both sources have the same type.
 %dst:_(s32) = G_CLMULH %src0:_(s32), %src1:_(s32)
 ```
 
+### G_CLMULR
+
+Perform reversed carry-less multiplication.
+For N-bit operands, compute the 2N-bit polynomial product over GF(2) and
+return bits [2N-2:N-1]. Equivalent to
+`bitreverse(clmul(bitreverse(x), bitreverse(y)))`.
+The destination and both sources have the same type.
+
+```none
+%dst:_(s32) = G_CLMULR %src0:_(s32), %src1:_(s32)
+```
+
 ### G_SDIVREM, G_UDIVREM
 
 Perform integer division and remainder thereby producing two results.

diff  --git a/llvm/include/llvm/Support/TargetOpcodes.def b/llvm/include/llvm/Support/TargetOpcodes.def
index 314c2b277a5f8..9e21793627c54 100644
--- a/llvm/include/llvm/Support/TargetOpcodes.def
+++ b/llvm/include/llvm/Support/TargetOpcodes.def
@@ -865,6 +865,9 @@ HANDLE_TARGET_OPCODE(G_CLMUL)
 // Generic carry-less multiply high instruction.
 HANDLE_TARGET_OPCODE(G_CLMULH)
 
+// Generic reversed carry-less multiply instruction.
+HANDLE_TARGET_OPCODE(G_CLMULR)
+
 /// Floating point ceil.
 HANDLE_TARGET_OPCODE(G_FCEIL)
 

diff  --git a/llvm/include/llvm/Target/GenericOpcodes.td b/llvm/include/llvm/Target/GenericOpcodes.td
index 6e1a2b7f50954..183abcff9a9ba 100644
--- a/llvm/include/llvm/Target/GenericOpcodes.td
+++ b/llvm/include/llvm/Target/GenericOpcodes.td
@@ -600,6 +600,16 @@ def G_CLMULH : GenericInstruction {
   let isCommutable = true;
 }
 
+// Generic reversed carry-less multiplication.
+// For N-bit operands, compute the 2N-bit polynomial product over GF(2) and
+// return bits [2N-2:N-1].
+def G_CLMULR : GenericInstruction {
+  let OutOperandList = (outs type0:$dst);
+  let InOperandList = (ins type0:$src1, type0:$src2);
+  let hasSideEffects = false;
+  let isCommutable = true;
+}
+
 
 //------------------------------------------------------------------------------
 // Overflow ops

diff  --git a/llvm/include/llvm/Target/GlobalISel/SelectionDAGCompat.td b/llvm/include/llvm/Target/GlobalISel/SelectionDAGCompat.td
index a3423d5019e68..8db92b0702584 100644
--- a/llvm/include/llvm/Target/GlobalISel/SelectionDAGCompat.td
+++ b/llvm/include/llvm/Target/GlobalISel/SelectionDAGCompat.td
@@ -74,6 +74,7 @@ def : GINodeEquiv<G_SUB, sub>;
 def : GINodeEquiv<G_MUL, mul>;
 def : GINodeEquiv<G_CLMUL, clmul>;
 def : GINodeEquiv<G_CLMULH, clmulh>;
+def : GINodeEquiv<G_CLMULR, clmulr>;
 def : GINodeEquiv<G_UMULH, mulhu>;
 def : GINodeEquiv<G_SMULH, mulhs>;
 def : GINodeEquiv<G_SDIV, sdiv>;

diff  --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index 1840d0b7c8ebf..451d21df4d95c 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -242,6 +242,12 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
       .customFor(ST.is64Bit() && ST.hasStdExtZbkc(), {s32})
       .unsupported();
 
+  // CLMULR is Zbc-only; Zbkc is a subset that has CLMUL/CLMULH but not CLMULR.
+  getActionDefinitionsBuilder(G_CLMULR)
+      .legalFor(ST.hasStdExtZbc(), {sXLen})
+      .customFor(ST.is64Bit() && ST.hasStdExtZbc(), {s32})
+      .unsupported();
+
   auto &CountZerosActions = getActionDefinitionsBuilder({G_CTLZ, G_CTTZ});
   auto &CountZerosPoisonActions =
       getActionDefinitionsBuilder({G_CTLZ_ZERO_POISON, G_CTTZ_ZERO_POISON});
@@ -868,6 +874,11 @@ bool RISCVLegalizerInfo::legalizeIntrinsic(LegalizerHelper &Helper,
                                  {MI.getOperand(2), MI.getOperand(3)});
     MI.eraseFromParent();
     return true;
+  case Intrinsic::riscv_clmulr:
+    Helper.MIRBuilder.buildInstr(TargetOpcode::G_CLMULR, {MI.getOperand(0)},
+                                 {MI.getOperand(2), MI.getOperand(3)});
+    MI.eraseFromParent();
+    return true;
   case Intrinsic::vacopy: {
     // vacopy arguments must be legal because of the intrinsic signature.
     // No need to check here.
@@ -1626,18 +1637,20 @@ bool RISCVLegalizerInfo::legalizeCustom(
     return false;
   case TargetOpcode::G_ABS:
     return Helper.lowerAbsToMaxNeg(MI);
-  case TargetOpcode::G_CLMULH: {
+  case TargetOpcode::G_CLMULH:
+  case TargetOpcode::G_CLMULR: {
     assert(STI.is64Bit() &&
            MRI.getType(MI.getOperand(0).getReg()) == LLT::scalar(32) &&
            "Unexpected custom legalization");
     // Shift both inputs by 32 so the full product has 64 trailing zeros.
-    // CLMULH then returns the original 64-bit product. Extract its high half.
+    // Perform CLMULH or CLMULR on the shifted inputs, then extract the upper
+    // 32 bits of the result.
     auto Shift = MIRBuilder.buildConstant(sXLen, 32);
     auto LHS = MIRBuilder.buildAnyExt(sXLen, MI.getOperand(1));
     auto RHS = MIRBuilder.buildAnyExt(sXLen, MI.getOperand(2));
     auto ShiftedLHS = MIRBuilder.buildShl(sXLen, LHS, Shift);
     auto ShiftedRHS = MIRBuilder.buildShl(sXLen, RHS, Shift);
-    auto Product = MIRBuilder.buildInstr(TargetOpcode::G_CLMULH, {sXLen},
+    auto Product = MIRBuilder.buildInstr(MI.getOpcode(), {sXLen},
                                          {ShiftedLHS, ShiftedRHS});
     auto High = MIRBuilder.buildLShr(sXLen, Product, Shift);
     MIRBuilder.buildTrunc(MI.getOperand(0), High);

diff  --git a/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir b/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
index a02e9ed4dff85..61b38ac3fdc93 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
@@ -781,6 +781,9 @@
 # DEBUG-NEXT: G_CLMULH (opcode {{[0-9]+}}): 1 type index, 0 imm indices
 # DEBUG-NEXT: .. type index coverage check SKIPPED: no rules defined
 # DEBUG-NEXT: .. imm index coverage check SKIPPED: no rules defined
+# DEBUG-NEXT: G_CLMULR (opcode {{[0-9]+}}): 1 type index, 0 imm indices
+# DEBUG-NEXT: .. type index coverage check SKIPPED: no rules defined
+# DEBUG-NEXT: .. imm index coverage check SKIPPED: no rules defined
 # DEBUG-NEXT: G_FCEIL (opcode {{[0-9]+}}): 1 type index, 0 imm indices
 # DEBUG-NEXT: .. opcode {{[0-9]+}} is aliased to {{[0-9]+}}
 # DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected

diff  --git a/llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv32.ll b/llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv32.ll
new file mode 100644
index 0000000000000..301140fee41b8
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv32.ll
@@ -0,0 +1,15 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv32 -mattr=+zbc -global-isel \
+; RUN:   -global-isel-abort=1 -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s --check-prefix=RV32ZBC
+
+declare i32 @llvm.riscv.clmulr.i32(i32, i32)
+
+define i32 @clmulr_i32(i32 %a, i32 %b) {
+; RV32ZBC-LABEL: clmulr_i32:
+; RV32ZBC:       # %bb.0:
+; RV32ZBC-NEXT:    clmulr a0, a0, a1
+; RV32ZBC-NEXT:    ret
+  %r = call i32 @llvm.riscv.clmulr.i32(i32 %a, i32 %b)
+  ret i32 %r
+}

diff  --git a/llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv64.ll b/llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv64.ll
new file mode 100644
index 0000000000000..9dee309f8575b
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/clmulr-rv64.ll
@@ -0,0 +1,42 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv64 -mattr=+zbc -global-isel \
+; RUN:   -global-isel-abort=1 -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s --check-prefix=RV64ZBC
+
+declare i64 @llvm.riscv.clmulr.i64(i64, i64)
+declare i32 @llvm.riscv.clmulr.i32(i32, i32)
+
+define i64 @clmulr_i64(i64 %a, i64 %b) {
+; RV64ZBC-LABEL: clmulr_i64:
+; RV64ZBC:       # %bb.0:
+; RV64ZBC-NEXT:    clmulr a0, a0, a1
+; RV64ZBC-NEXT:    ret
+  %r = call i64 @llvm.riscv.clmulr.i64(i64 %a, i64 %b)
+  ret i64 %r
+}
+
+define signext i32 @clmulr_i32(i32 signext %a, i32 signext %b) {
+; RV64ZBC-LABEL: clmulr_i32:
+; RV64ZBC:       # %bb.0:
+; RV64ZBC-NEXT:    slli a0, a0, 32
+; RV64ZBC-NEXT:    slli a1, a1, 32
+; RV64ZBC-NEXT:    clmulr a0, a0, a1
+; RV64ZBC-NEXT:    srli a0, a0, 32
+; RV64ZBC-NEXT:    sext.w a0, a0
+; RV64ZBC-NEXT:    ret
+  %r = call i32 @llvm.riscv.clmulr.i32(i32 %a, i32 %b)
+  ret i32 %r
+}
+
+define signext i32 @clmulr_i32_zext(i32 zeroext %a, i32 zeroext %b) {
+; RV64ZBC-LABEL: clmulr_i32_zext:
+; RV64ZBC:       # %bb.0:
+; RV64ZBC-NEXT:    slli a0, a0, 32
+; RV64ZBC-NEXT:    slli a1, a1, 32
+; RV64ZBC-NEXT:    clmulr a0, a0, a1
+; RV64ZBC-NEXT:    srli a0, a0, 32
+; RV64ZBC-NEXT:    sext.w a0, a0
+; RV64ZBC-NEXT:    ret
+  %r = call i32 @llvm.riscv.clmulr.i32(i32 %a, i32 %b)
+  ret i32 %r
+}

diff  --git a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
index 5b75b68557d1e..cc4d7977b3921 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
@@ -776,6 +776,9 @@
 # DEBUG-NEXT: G_CLMULH (opcode {{[0-9]+}}): 1 type index, 0 imm indices
 # DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected
 # DEBUG-NEXT: .. imm index coverage check SKIPPED: user-defined predicate detected
+# DEBUG-NEXT: G_CLMULR (opcode {{[0-9]+}}): 1 type index, 0 imm indices
+# DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected
+# DEBUG-NEXT: .. imm index coverage check SKIPPED: user-defined predicate detected
 # DEBUG-NEXT: G_FCEIL (opcode {{[0-9]+}}): 1 type index, 0 imm indices
 # DEBUG-NEXT: .. the first uncovered type index: 1, OK
 # DEBUG-NEXT: .. the first uncovered imm index: 0, OK

diff  --git a/llvm/test/TableGen/get-named-operand-idx.td b/llvm/test/TableGen/get-named-operand-idx.td
index fab74e2b6447e..165d3f705c04c 100644
--- a/llvm/test/TableGen/get-named-operand-idx.td
+++ b/llvm/test/TableGen/get-named-operand-idx.td
@@ -98,8 +98,8 @@ defm : RemapAllTargetPseudoPointerOperands<RegClass>;
 // CHECK-NEXT:      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 // CHECK-NEXT:      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 // CHECK-NEXT:      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-// CHECK-NEXT:      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
-// CHECK-NEXT:      2, 0,
+// CHECK-NEXT:      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+// CHECK-NEXT:      2, 2, 0,
 // CHECK-NEXT:    };
 // CHECK-NEXT:    return InstructionIndex[Opcode];
 // CHECK-NEXT:  }


        


More information about the llvm-commits mailing list