[llvm] [RISCV] Emit MULHU/MULHS/UMUL_LOHI/SMUL_LOHI from our custom XLen*2 expansion. (PR #180379)
Craig Topper via llvm-commits
llvm-commits at lists.llvm.org
Sun Feb 8 12:45:17 PST 2026
https://github.com/topperc updated https://github.com/llvm/llvm-project/pull/180379
>From 825ebfd54594fe22a3bca588edb9bb9789e06c3d Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Fri, 6 Feb 2026 22:53:52 -0800
Subject: [PATCH 1/3] Pre-commit tests
---
llvm/test/CodeGen/RISCV/rv32p.ll | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/llvm/test/CodeGen/RISCV/rv32p.ll b/llvm/test/CodeGen/RISCV/rv32p.ll
index e8dbefbce047d..84987a35cb102 100644
--- a/llvm/test/CodeGen/RISCV/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/rv32p.ll
@@ -551,6 +551,19 @@ define i64 @wmulu_i32(i32 %x, i32 %y) {
ret i64 %c
}
+define i64 @wmulsu_i32(i32 %x, i32 %y) {
+; CHECK-LABEL: wmulsu_i32:
+; CHECK: # %bb.0:
+; CHECK-NEXT: mul a2, a1, a0
+; CHECK-NEXT: mulhsu a1, a1, a0
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %a = zext i32 %x to i64
+ %b = sext i32 %y to i64
+ %c = mul i64 %a, %b
+ ret i64 %c
+}
+
; Test that mulh continues to be used with P.
define i32 @mulh_i32(i32 %x, i32 %y) {
; CHECK-LABEL: mulh_i32:
@@ -579,6 +592,20 @@ define i32 @mulhu_i32(i32 %x, i32 %y) {
ret i32 %e
}
+; Test that mulhsu continues to be used with P.
+define i32 @mulhsu_i32(i32 %x, i32 %y) {
+; CHECK-LABEL: mulhsu_i32:
+; CHECK: # %bb.0:
+; CHECK-NEXT: mulhsu a0, a1, a0
+; CHECK-NEXT: ret
+ %a = zext i32 %x to i64
+ %b = sext i32 %y to i64
+ %c = mul i64 %a, %b
+ %d = lshr i64 %c, 32
+ %e = trunc i64 %d to i32
+ ret i32 %e
+}
+
define i64 @add_i64(i64 %x, i64 %y) {
; CHECK-LABEL: add_i64:
; CHECK: # %bb.0:
@@ -588,8 +615,8 @@ define i64 @add_i64(i64 %x, i64 %y) {
ret i64 %a
}
-define i64 @usb_i64(i64 %x, i64 %y) {
-; CHECK-LABEL: usb_i64:
+define i64 @sub_i64(i64 %x, i64 %y) {
+; CHECK-LABEL: sub_i64:
; CHECK: # %bb.0:
; CHECK-NEXT: subd a0, a0, a2
; CHECK-NEXT: ret
>From 67d7fecb35fa3f66e01cdef9af350e1d99e9bb4c Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Fri, 6 Feb 2026 23:37:22 -0800
Subject: [PATCH 2/3] [RISCV] Add support for forming WMULSU during type
legalization.
Add a DAG combine to turn it into MULHSU if the lower half result
is unused.
---
llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 20 +++++++++++++++++---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 20 ++++++++++++++++++--
llvm/lib/Target/RISCV/RISCVInstrInfoP.td | 2 ++
llvm/test/CodeGen/RISCV/rv32p.ll | 4 +---
4 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 48e2f547b9fd7..a2c61973bbdb4 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1758,13 +1758,27 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
return;
}
case ISD::SMUL_LOHI:
- case ISD::UMUL_LOHI: {
+ case ISD::UMUL_LOHI:
+ case RISCVISD::WMULSU: {
// Custom select (S/U)MUL_LOHI to WMUL(U) for RV32P.
assert(Subtarget->hasStdExtP() && !Subtarget->is64Bit() && VT == MVT::i32 &&
"Unexpected opcode");
- unsigned Opc =
- Node->getOpcode() == ISD::SMUL_LOHI ? RISCV::WMUL : RISCV::WMULU;
+ unsigned Opc;
+ switch (Node->getOpcode()) {
+ default:
+ llvm_unreachable("Unexpected opcode");
+ case ISD::SMUL_LOHI:
+ Opc = RISCV::WMUL;
+ break;
+ case ISD::UMUL_LOHI:
+ Opc = RISCV::WMULU;
+ break;
+ case RISCVISD::WMULSU:
+ Opc = RISCV::WMULSU;
+ break;
+ }
+
SDNode *WMUL = CurDAG->getMachineNode(
Opc, DL, MVT::Untyped, Node->getOperand(0), Node->getOperand(1));
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index b1d88ce55d955..9ea4716d5b983 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -15122,8 +15122,15 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
MVT XLenVT = Subtarget.getXLenVT();
S = DAG.getNode(ISD::TRUNCATE, DL, XLenVT, S);
U = DAG.getNode(ISD::TRUNCATE, DL, XLenVT, U);
- SDValue Lo = DAG.getNode(ISD::MUL, DL, XLenVT, S, U);
- SDValue Hi = DAG.getNode(RISCVISD::MULHSU, DL, XLenVT, S, U);
+ SDValue Lo, Hi;
+ if (Subtarget.hasStdExtP() && !Subtarget.is64Bit()) {
+ Lo = DAG.getNode(RISCVISD::WMULSU, DL,
+ DAG.getVTList(MVT::i32, MVT::i32), S, U);
+ Hi = Lo.getValue(1);
+ } else {
+ Lo = DAG.getNode(ISD::MUL, DL, XLenVT, S, U);
+ Hi = DAG.getNode(RISCVISD::MULHSU, DL, XLenVT, S, U);
+ }
return DAG.getNode(ISD::BUILD_PAIR, DL, N->getValueType(0), Lo, Hi);
};
@@ -21115,6 +21122,15 @@ SDValue RISCVTargetLowering::PerformDAGCombine(SDNode *N,
return SDValue(N, 0);
break;
}
+ case RISCVISD::WMULSU: {
+ // Convert to MULHSU if only the upper half is used.
+ if (!N->hasAnyUseOfValue(0)) {
+ SDValue Res = DAG.getNode(RISCVISD::MULHSU, DL, N->getValueType(1),
+ N->getOperand(0), N->getOperand(1));
+ return DCI.CombineTo(N, Res, Res);
+ }
+ break;
+ }
case RISCVISD::FMV_W_X_RV64: {
// If the input to FMV_W_X_RV64 is just FMV_X_ANYEXTW_RV64 the the
// conversion is unnecessary and can be replaced with the
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
index d0008335ea9ea..12ac3ea1eeb6b 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
@@ -1493,6 +1493,8 @@ def riscv_addd : RVSDNode<"ADDD", SDT_RISCVIntBinOpD,
[SDNPCommutative, SDNPAssociative]>;
def riscv_subd : RVSDNode<"SUBD", SDT_RISCVIntBinOpD>;
+def riscv_wmulsu : RVSDNode<"WMULSU", SDTIntBinHiLoOp>;
+
// Averaging subtraction, (a - b) >> 2
def riscv_asub : RVSDNode<"ASUB", SDTIntBinOp>;
def riscv_asubu : RVSDNode<"ASUBU", SDTIntBinOp>;
diff --git a/llvm/test/CodeGen/RISCV/rv32p.ll b/llvm/test/CodeGen/RISCV/rv32p.ll
index 84987a35cb102..082e3008d4d0d 100644
--- a/llvm/test/CodeGen/RISCV/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/rv32p.ll
@@ -554,9 +554,7 @@ define i64 @wmulu_i32(i32 %x, i32 %y) {
define i64 @wmulsu_i32(i32 %x, i32 %y) {
; CHECK-LABEL: wmulsu_i32:
; CHECK: # %bb.0:
-; CHECK-NEXT: mul a2, a1, a0
-; CHECK-NEXT: mulhsu a1, a1, a0
-; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: wmulsu a0, a1, a0
; CHECK-NEXT: ret
%a = zext i32 %x to i64
%b = sext i32 %y to i64
>From 173245d67bc0d90837831c48260f85380b6a6257 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Sat, 7 Feb 2026 15:57:50 -0800
Subject: [PATCH 3/3] [RISCV] Emit MULHU/MULHS/UMUL_LOHI/SMUL_LOHI from our
custom XLen*2 expansion.
We already do all the checks necessary in order to prioritize
MULHU/MULHS/UMUL_LOHI/SMUL_LOHI over MULHSU/WMULSU. We might as
well just emit the nodes instead of letting generic type legalization
redo the checks.
Stacked on #180331.
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 57 ++++++++++++---------
1 file changed, 33 insertions(+), 24 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 9ea4716d5b983..03ab69407f426 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -15105,44 +15105,53 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
case ISD::MUL: {
unsigned Size = N->getSimpleValueType(0).getSizeInBits();
unsigned XLen = Subtarget.getXLen();
- // This multiply needs to be expanded, try to use MULHSU+MUL if possible.
if (Size > XLen) {
+ // This multiply needs to be expanded, try to use MULH+MUL or WMUL if
+ // possible. We duplicate the default legalization to
+ // MULHU/MULHS/UMUL_LOHI/SMUL_LOHI to minimize the number of calls to
+ // MaskedValueIsZero and ComputeNumSignBits
+ // FIXME: Should we have a target independent MULHSU/WMULSU node? Are
+ // there are other targets that could use it?
assert(Size == (XLen * 2) && "Unexpected custom legalisation");
- SDValue LHS = N->getOperand(0);
- SDValue RHS = N->getOperand(1);
- APInt HighMask = APInt::getHighBitsSet(Size, XLen);
- bool LHSIsU = DAG.MaskedValueIsZero(LHS, HighMask);
- bool RHSIsU = DAG.MaskedValueIsZero(RHS, HighMask);
- // We need exactly one side to be unsigned.
- if (LHSIsU == RHSIsU)
- return;
-
- auto MakeMULPair = [&](SDValue S, SDValue U) {
+ auto MakeMULPair = [&](SDValue L, SDValue R, unsigned HighOpc,
+ unsigned LoHiOpc) {
MVT XLenVT = Subtarget.getXLenVT();
- S = DAG.getNode(ISD::TRUNCATE, DL, XLenVT, S);
- U = DAG.getNode(ISD::TRUNCATE, DL, XLenVT, U);
+ L = DAG.getNode(ISD::TRUNCATE, DL, XLenVT, L);
+ R = DAG.getNode(ISD::TRUNCATE, DL, XLenVT, R);
SDValue Lo, Hi;
if (Subtarget.hasStdExtP() && !Subtarget.is64Bit()) {
- Lo = DAG.getNode(RISCVISD::WMULSU, DL,
- DAG.getVTList(MVT::i32, MVT::i32), S, U);
+ SDVTList VTs = DAG.getVTList(MVT::i32, MVT::i32);
+ Lo = DAG.getNode(LoHiOpc, DL, VTs, L, R);
Hi = Lo.getValue(1);
} else {
- Lo = DAG.getNode(ISD::MUL, DL, XLenVT, S, U);
- Hi = DAG.getNode(RISCVISD::MULHSU, DL, XLenVT, S, U);
+ Lo = DAG.getNode(ISD::MUL, DL, XLenVT, L, R);
+ Hi = DAG.getNode(HighOpc, DL, XLenVT, L, R);
}
return DAG.getNode(ISD::BUILD_PAIR, DL, N->getValueType(0), Lo, Hi);
};
+ SDValue LHS = N->getOperand(0);
+ SDValue RHS = N->getOperand(1);
+
+ APInt HighMask = APInt::getHighBitsSet(Size, XLen);
+ bool LHSIsU = DAG.MaskedValueIsZero(LHS, HighMask);
+ bool RHSIsU = DAG.MaskedValueIsZero(RHS, HighMask);
+ if (LHSIsU && RHSIsU) {
+ Results.push_back(MakeMULPair(LHS, RHS, ISD::MULHU, ISD::UMUL_LOHI));
+ return;
+ }
+
bool LHSIsS = DAG.ComputeNumSignBits(LHS) > XLen;
bool RHSIsS = DAG.ComputeNumSignBits(RHS) > XLen;
-
- // The other operand should be signed, but still prefer MULH when
- // possible.
- if (RHSIsU && LHSIsS && !RHSIsS)
- Results.push_back(MakeMULPair(LHS, RHS));
- else if (LHSIsU && RHSIsS && !LHSIsS)
- Results.push_back(MakeMULPair(RHS, LHS));
+ if (LHSIsS && RHSIsS)
+ Results.push_back(MakeMULPair(LHS, RHS, ISD::MULHS, ISD::SMUL_LOHI));
+ else if (RHSIsU && LHSIsS)
+ Results.push_back(
+ MakeMULPair(LHS, RHS, RISCVISD::MULHSU, RISCVISD::WMULSU));
+ else if (LHSIsU && RHSIsS)
+ Results.push_back(
+ MakeMULPair(RHS, LHS, RISCVISD::MULHSU, RISCVISD::WMULSU));
return;
}
More information about the llvm-commits
mailing list