[llvm] [RISCV][GlobalISel] Add G_CLMULH support (PR #221686)

Jian Li via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 7 05:44:15 PDT 2026


https://github.com/janearthur updated https://github.com/llvm/llvm-project/pull/221686

>From 289df580e1aba10802fde8494c26189ac987e09a Mon Sep 17 00:00:00 2001
From: Jian Li <hellothedreamer at 126.com>
Date: Mon, 7 Sep 2026 16:56:07 +0800
Subject: [PATCH 1/2] [RISCV][GlobalISel] Add G_CLMULH support

Add G_CLMULH to represent the high half of a carry-less
multiplication in generic Machine IR.

Translate llvm.riscv.clmulh to G_CLMULH and mark it legal for
native XLEN scalar types when Zbkc is available. Reuse the existing
SelectionDAG pattern to select the RISC-V CLMULH instruction.
---
 llvm/docs/GlobalISel/GenericOpcode.md             | 10 ++++++++++
 llvm/include/llvm/Support/TargetOpcodes.def       |  3 +++
 llvm/include/llvm/Target/GenericOpcodes.td        |  8 ++++++++
 .../llvm/Target/GlobalISel/SelectionDAGCompat.td  |  1 +
 .../lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp |  9 +++++++++
 .../GlobalISel/legalizer-info-validation.mir      |  3 +++
 llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv32.ll  | 15 +++++++++++++++
 llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll  | 15 +++++++++++++++
 .../GlobalISel/legalizer-info-validation.mir      |  3 +++
 llvm/test/TableGen/get-named-operand-idx.td       |  4 ++--
 10 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/llvm/docs/GlobalISel/GenericOpcode.md b/llvm/docs/GlobalISel/GenericOpcode.md
index 8e2a5be0022b5..39b11b1d76355 100644
--- a/llvm/docs/GlobalISel/GenericOpcode.md
+++ b/llvm/docs/GlobalISel/GenericOpcode.md
@@ -311,6 +311,16 @@ Perform integer carry-less multiplication.
 %dst:_(s32) = G_CLMUL %src_0:_(s32), %src1:_(32)
 ```
 
+### G_CLMULH
+
+Perform carry-less multiplication and return the high half of the result.
+For N-bit operands, compute the 2N-bit polynomial product over GF(2) and
+return bits [2N-1:N]. The destination and both sources have the same type.
+
+```none
+%dst:_(s32) = G_CLMULH %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 549a93b7bec3a..314c2b277a5f8 100644
--- a/llvm/include/llvm/Support/TargetOpcodes.def
+++ b/llvm/include/llvm/Support/TargetOpcodes.def
@@ -862,6 +862,9 @@ HANDLE_TARGET_OPCODE(G_BITREVERSE)
 // Generic carry-less multiply instruction.
 HANDLE_TARGET_OPCODE(G_CLMUL)
 
+// Generic carry-less multiply high instruction.
+HANDLE_TARGET_OPCODE(G_CLMULH)
+
 /// 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 b1a7a01f4bbe3..6e1a2b7f50954 100644
--- a/llvm/include/llvm/Target/GenericOpcodes.td
+++ b/llvm/include/llvm/Target/GenericOpcodes.td
@@ -592,6 +592,14 @@ def G_CLMUL : GenericInstruction {
   let isCommutable = true;
 }
 
+// Generic carry-less multiplication returning the high half of the result.
+def G_CLMULH : 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 a91ef6058b7e4..a3423d5019e68 100644
--- a/llvm/include/llvm/Target/GlobalISel/SelectionDAGCompat.td
+++ b/llvm/include/llvm/Target/GlobalISel/SelectionDAGCompat.td
@@ -73,6 +73,7 @@ def : GINodeEquiv<G_ADD, add>;
 def : GINodeEquiv<G_SUB, sub>;
 def : GINodeEquiv<G_MUL, mul>;
 def : GINodeEquiv<G_CLMUL, clmul>;
+def : GINodeEquiv<G_CLMULH, clmulh>;
 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 d0ee9bb39cc8b..587e34576f231 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -237,6 +237,10 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
       .legalFor(ST.hasStdExtZbkc(), {sXLen})
       .unsupported();
 
+  getActionDefinitionsBuilder(G_CLMULH)
+      .legalFor(ST.hasStdExtZbkc(), {sXLen})
+      .unsupported();
+
   auto &CountZerosActions = getActionDefinitionsBuilder({G_CTLZ, G_CTTZ});
   auto &CountZerosPoisonActions =
       getActionDefinitionsBuilder({G_CTLZ_ZERO_POISON, G_CTTZ_ZERO_POISON});
@@ -845,6 +849,11 @@ bool RISCVLegalizerInfo::legalizeIntrinsic(LegalizerHelper &Helper,
   switch (IntrinsicID) {
   default:
     return false;
+  case Intrinsic::riscv_clmulh:
+    Helper.MIRBuilder.buildInstr(TargetOpcode::G_CLMULH, {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.
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir b/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
index b99c06cba9208..2fd1291bd2f88 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
@@ -778,6 +778,9 @@
 # DEBUG-NEXT: G_CLMUL (opcode [[CLMUL_OPC:[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
+# 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_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/clmul-rv32.ll b/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv32.ll
index 316069fb06e1b..95a1bf586d879 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv32.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv32.ll
@@ -7,6 +7,7 @@
 ; RUN:   | FileCheck %s --check-prefix=RV32ZBKC
 
 declare i32 @llvm.clmul.i32(i32, i32)
+declare i32 @llvm.riscv.clmulh.i32(i32, i32)
 
 define i32 @clmul_i32(i32 %a, i32 %b) {
 ; RV32ZBC-LABEL: clmul_i32:
@@ -21,3 +22,17 @@ define i32 @clmul_i32(i32 %a, i32 %b) {
   %r = call i32 @llvm.clmul.i32(i32 %a, i32 %b)
   ret i32 %r
 }
+
+define i32 @clmulh_i32(i32 %a, i32 %b) {
+; RV32ZBC-LABEL: clmulh_i32:
+; RV32ZBC:       # %bb.0:
+; RV32ZBC-NEXT:    clmulh a0, a0, a1
+; RV32ZBC-NEXT:    ret
+;
+; RV32ZBKC-LABEL: clmulh_i32:
+; RV32ZBKC:       # %bb.0:
+; RV32ZBKC-NEXT:    clmulh a0, a0, a1
+; RV32ZBKC-NEXT:    ret
+  %r = call i32 @llvm.riscv.clmulh.i32(i32 %a, i32 %b)
+  ret i32 %r
+}
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll b/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll
index 6549e63ab9af2..4e97a02c3af6e 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll
@@ -7,6 +7,7 @@
 ; RUN:   | FileCheck %s --check-prefix=RV64ZBKC
 
 declare i64 @llvm.clmul.i64(i64, i64)
+declare i64 @llvm.riscv.clmulh.i64(i64, i64)
 
 define i64 @clmul_i64(i64 %a, i64 %b) {
 ; RV64ZBC-LABEL: clmul_i64:
@@ -21,3 +22,17 @@ define i64 @clmul_i64(i64 %a, i64 %b) {
   %r = call i64 @llvm.clmul.i64(i64 %a, i64 %b)
   ret i64 %r
 }
+
+define i64 @clmulh_i64(i64 %a, i64 %b) {
+; RV64ZBC-LABEL: clmulh_i64:
+; RV64ZBC:       # %bb.0:
+; RV64ZBC-NEXT:    clmulh a0, a0, a1
+; RV64ZBC-NEXT:    ret
+;
+; RV64ZBKC-LABEL: clmulh_i64:
+; RV64ZBKC:       # %bb.0:
+; RV64ZBKC-NEXT:    clmulh a0, a0, a1
+; RV64ZBKC-NEXT:    ret
+  %r = call i64 @llvm.riscv.clmulh.i64(i64 %a, i64 %b)
+  ret i64 %r
+}
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
index a9a045daaaa9f..944cadc863378 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
@@ -772,6 +772,9 @@
 # DEBUG-NEXT: G_CLMUL (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_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_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 31de6faec0da4..fab74e2b6447e 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, 1, 2, 2,
-// CHECK-NEXT:      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:    };
 // CHECK-NEXT:    return InstructionIndex[Opcode];
 // CHECK-NEXT:  }

>From cb0281b69dbca583a015ae3eab5894b3854313c6 Mon Sep 17 00:00:00 2001
From: Jian Li <hellothedreamer at 126.com>
Date: Mon, 7 Sep 2026 20:40:10 +0800
Subject: [PATCH 2/2] [RISCV][GlobalISel] Fix G_CLMULH formatting

---
 llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index 587e34576f231..3e28aa5184421 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -851,7 +851,7 @@ bool RISCVLegalizerInfo::legalizeIntrinsic(LegalizerHelper &Helper,
     return false;
   case Intrinsic::riscv_clmulh:
     Helper.MIRBuilder.buildInstr(TargetOpcode::G_CLMULH, {MI.getOperand(0)},
-                                {MI.getOperand(2), MI.getOperand(3)});
+                                 {MI.getOperand(2), MI.getOperand(3)});
     MI.eraseFromParent();
     return true;
   case Intrinsic::vacopy: {



More information about the llvm-commits mailing list