[llvm] 9526701 - [RISCV][GlobalISel] Add G_CLMULH support (#221686)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 10 19:02:51 PDT 2026
Author: Jian Li
Date: 2026-09-11T10:02:46+08:00
New Revision: 95267015df795c02d829b3545610a5f5e95609e8
URL: https://github.com/llvm/llvm-project/commit/95267015df795c02d829b3545610a5f5e95609e8
DIFF: https://github.com/llvm/llvm-project/commit/95267015df795c02d829b3545610a5f5e95609e8.diff
LOG: [RISCV][GlobalISel] Add G_CLMULH support (#221686)
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.
Added:
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/clmul-rv32.ll
llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll
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 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 d587b7090c6b4..1840d0b7c8ebf 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -237,6 +237,11 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
.legalFor(ST.hasStdExtZbkc(), {sXLen})
.unsupported();
+ getActionDefinitionsBuilder(G_CLMULH)
+ .legalFor(ST.hasStdExtZbkc(), {sXLen})
+ .customFor(ST.is64Bit() && ST.hasStdExtZbkc(), {s32})
+ .unsupported();
+
auto &CountZerosActions = getActionDefinitionsBuilder({G_CTLZ, G_CTTZ});
auto &CountZerosPoisonActions =
getActionDefinitionsBuilder({G_CTLZ_ZERO_POISON, G_CTTZ_ZERO_POISON});
@@ -858,6 +863,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.
@@ -1616,6 +1626,24 @@ bool RISCVLegalizerInfo::legalizeCustom(
return false;
case TargetOpcode::G_ABS:
return Helper.lowerAbsToMaxNeg(MI);
+ case TargetOpcode::G_CLMULH: {
+ 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.
+ 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},
+ {ShiftedLHS, ShiftedRHS});
+ auto High = MIRBuilder.buildLShr(sXLen, Product, Shift);
+ MIRBuilder.buildTrunc(MI.getOperand(0), High);
+ MI.eraseFromParent();
+ return true;
+ }
case TargetOpcode::G_FCONSTANT: {
const APFloat &FVal = MI.getOperand(1).getFPImm()->getValueAPF();
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir b/llvm/test/CodeGen/AArch64/GlobalISel/legalizer-info-validation.mir
index 2a310e903614d..a02e9ed4dff85 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..af54282ecc865 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/clmul-rv64.ll
@@ -7,6 +7,8 @@
; RUN: | FileCheck %s --check-prefix=RV64ZBKC
declare i64 @llvm.clmul.i64(i64, i64)
+declare i64 @llvm.riscv.clmulh.i64(i64, i64)
+declare i32 @llvm.riscv.clmulh.i32(i32, i32)
define i64 @clmul_i64(i64 %a, i64 %b) {
; RV64ZBC-LABEL: clmul_i64:
@@ -21,3 +23,61 @@ define i64 @clmul_i64(i64 %a, i64 %b) {
%r = call i64 @llvm.clmul.i64(i64 %a, i64 %b)
ret i64 %r
}
+
+define signext i32 @clmulh_i32(i32 signext %a, i32 signext %b) {
+; RV64ZBC-LABEL: clmulh_i32:
+; RV64ZBC: # %bb.0:
+; RV64ZBC-NEXT: slli a0, a0, 32
+; RV64ZBC-NEXT: slli a1, a1, 32
+; RV64ZBC-NEXT: clmulh a0, a0, a1
+; RV64ZBC-NEXT: srli a0, a0, 32
+; RV64ZBC-NEXT: sext.w a0, a0
+; RV64ZBC-NEXT: ret
+;
+; RV64ZBKC-LABEL: clmulh_i32:
+; RV64ZBKC: # %bb.0:
+; RV64ZBKC-NEXT: slli a0, a0, 32
+; RV64ZBKC-NEXT: slli a1, a1, 32
+; RV64ZBKC-NEXT: clmulh a0, a0, a1
+; RV64ZBKC-NEXT: srli a0, a0, 32
+; RV64ZBKC-NEXT: sext.w a0, a0
+; RV64ZBKC-NEXT: ret
+ %r = call i32 @llvm.riscv.clmulh.i32(i32 %a, i32 %b)
+ ret i32 %r
+}
+
+define signext i32 @clmulh_i32_zext(i32 zeroext %a, i32 zeroext %b) {
+; RV64ZBC-LABEL: clmulh_i32_zext:
+; RV64ZBC: # %bb.0:
+; RV64ZBC-NEXT: slli a0, a0, 32
+; RV64ZBC-NEXT: slli a1, a1, 32
+; RV64ZBC-NEXT: clmulh a0, a0, a1
+; RV64ZBC-NEXT: srli a0, a0, 32
+; RV64ZBC-NEXT: sext.w a0, a0
+; RV64ZBC-NEXT: ret
+;
+; RV64ZBKC-LABEL: clmulh_i32_zext:
+; RV64ZBKC: # %bb.0:
+; RV64ZBKC-NEXT: slli a0, a0, 32
+; RV64ZBKC-NEXT: slli a1, a1, 32
+; RV64ZBKC-NEXT: clmulh a0, a0, a1
+; RV64ZBKC-NEXT: srli a0, a0, 32
+; RV64ZBKC-NEXT: sext.w a0, a0
+; RV64ZBKC-NEXT: ret
+ %r = call i32 @llvm.riscv.clmulh.i32(i32 %a, i32 %b)
+ ret i32 %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 8e350e59123ca..5b75b68557d1e 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
@@ -773,6 +773,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: }
More information about the llvm-commits
mailing list