[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