[llvm] [RISCV] Use `experimental-p` extension for zero-extended narrow unsigned types (PR #213260)

Rajveer Singh Bharadwaj via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 10 04:24:45 PDT 2026


https://github.com/Rajveer100 updated https://github.com/llvm/llvm-project/pull/213260

>From 91cbbacbf004e56289f6c44470f81e7e43106036 Mon Sep 17 00:00:00 2001
From: Rajveer <rajveer.developer at icloud.com>
Date: Fri, 31 Jul 2026 17:41:45 +0530
Subject: [PATCH] [RISCV] Use `experimental-p` extension for zero-extended
 narrow unsigned types

Resolves #211361

The bits `31:8` remain zero after packed addition due to `padd.b`
carry-out as per ABI, hence we can generate more optimised code instead
of `zext.b` + `add`.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   | 78 ++++++++++++++++
 ...-p-extension-narrow-unsigned-arithmetic.ll | 91 +++++++++++++++++++
 2 files changed, 169 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/experimental-p-extension-narrow-unsigned-arithmetic.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index fe905f686d855..d0b99cecaea45 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -18027,6 +18027,79 @@ static SDValue combineNarrowableShiftedLoad(SDNode *N, SelectionDAG &DAG) {
                      DAG.getShiftAmountConstant(ShiftAmt, VT, DL));
 }
 
+static SDValue combinePExtNarrowUnsigned(SDNode *N, SelectionDAG &DAG,
+                                         const RISCVSubtarget &Subtarget) {
+  if (!Subtarget.hasStdExtP() || !Subtarget.isRV32())
+    return SDValue();
+
+  using namespace SDPatternMatch;
+
+  EVT VT = N->getValueType(0);
+  SDLoc DL(N);
+
+  APInt ConstMask;
+
+  if (sd_match(N, m_And(m_Add(m_Value(), m_Value()), m_ConstInt(ConstMask)))) {
+    uint64_t ConstMaskVal = ConstMask.getZExtValue();
+    if (ConstMaskVal != 255 && ConstMaskVal != 65535)
+      return SDValue();
+
+    uint64_t OpCode = (ConstMaskVal == 255) ? RISCV::PADD_B : RISCV::PADD_H;
+
+    std::queue<SDNode *> Worklist;
+    Worklist.push(N->getOperand(0).getNode());
+
+    std::vector<SDNode *> Ops;
+    while (!Worklist.empty()) {
+      SDNode *CurNode = Worklist.front();
+      Worklist.pop();
+
+      SDValue N0, N1, N2, N3;
+      SDValue A;
+      if (sd_match(CurNode, m_Add(m_Add(m_Value(N0), m_Value(N1)),
+                                  m_Add(m_Value(N2), m_Value(N3))))) {
+        Worklist.push(N3.getNode());
+        Worklist.push(N2.getNode());
+        Worklist.push(N1.getNode());
+        Worklist.push(N0.getNode());
+      } else if (sd_match(CurNode, m_Add(m_Add(m_Value(N0), m_Value(N1)),
+                                         m_Node(ISD::AssertZext, m_Value(),
+                                                m_Value())))) {
+        Worklist.push(N1.getNode());
+        Worklist.push(N0.getNode());
+        Worklist.push(CurNode->getOperand(1).getNode());
+      } else if (sd_match(CurNode,
+                          m_Node(ISD::AssertZext, m_Value(A), m_Value()))) {
+        Ops.push_back(A.getNode());
+      }
+    }
+
+    uint64_t Idx = 0, NumOps = Ops.size();
+    bool IsFirst = true;
+
+    SDValue LastOp;
+    while (Idx < NumOps) {
+      SDNode *Op0 = Ops[Idx++];
+      SDValue PAddNode;
+      if (IsFirst) {
+        SDNode *Op1 = Ops[Idx++];
+        PAddNode = SDValue(DAG.getMachineNode(OpCode, DL, VT, SDValue(Op0, 0),
+                                              SDValue(Op1, 0)),
+                           0);
+        IsFirst = false;
+      } else {
+        PAddNode = SDValue(
+            DAG.getMachineNode(OpCode, DL, VT, LastOp, SDValue(Op0, 0)), 0);
+      }
+      LastOp = PAddNode;
+    }
+
+    return LastOp;
+  }
+
+  return SDValue();
+}
+
 // Combines two comparison operation and logic operation to one selection
 // operation(min, max) and logic operation. Returns new constructed Node if
 // conditions for optimization are satisfied.
@@ -18068,6 +18141,11 @@ static SDValue performANDCombine(SDNode *N,
   if (SDValue V = reduceANDOfAtomicLoad(N, DCI))
     return V;
 
+  if (Subtarget.hasStdExtP()) {
+    if (SDValue V = combinePExtNarrowUnsigned(N, DAG, Subtarget))
+      return V;
+  }
+
   if (DCI.isAfterLegalizeDAG())
     if (SDValue V = combineDeMorganOfBoolean(N, DAG))
       return V;
diff --git a/llvm/test/CodeGen/RISCV/experimental-p-extension-narrow-unsigned-arithmetic.ll b/llvm/test/CodeGen/RISCV/experimental-p-extension-narrow-unsigned-arithmetic.ll
new file mode 100644
index 0000000000000..f2862bad87652
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/experimental-p-extension-narrow-unsigned-arithmetic.ll
@@ -0,0 +1,91 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv32 -mattr=+experimental-p \
+; RUN:   -verify-machineinstrs < %s | \
+; RUN:   FileCheck --check-prefixes=CHECK,RV32 %s
+
+define zeroext i8 @zexti8_paddb1(i8 zeroext %x, i8 zeroext %y) {
+; CHECK-LABEL: zexti8_paddb1:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    padd.b a0, a1, a0
+; CHECK-NEXT:    ret
+entry:
+  %add = add i8 %y, %x
+  ret i8 %add
+}
+
+define zeroext i32 @zexti32_padd_neg(i32 zeroext %x, i32 zeroext %y) {
+; CHECK-LABEL: zexti32_padd_neg:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    add a0, a1, a0
+; CHECK-NEXT:    ret
+entry:
+  %add = add i32 %y, %x
+  ret i32 %add
+}
+
+define zeroext i8 @zexti8_paddb2(i8 zeroext %x, i8 zeroext %y, i8 zeroext %z) {
+; CHECK-LABEL: zexti8_paddb2:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    padd.b a0, a0, a1
+; CHECK-NEXT:    padd.b a0, a0, a2
+; CHECK-NEXT:    ret
+entry:
+  %add = add i8 %y, %x
+  %add2 = add i8 %add, %z
+  ret i8 %add2
+}
+
+define zeroext i8 @zexti8_paddb3(i8 zeroext %a, i8 zeroext %b, i8 zeroext %c, i8 zeroext %d) {
+; CHECK-LABEL: zexti8_paddb3:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    padd.b a0, a0, a1
+; CHECK-NEXT:    padd.b a0, a0, a3
+; CHECK-NEXT:    padd.b a0, a0, a2
+; CHECK-NEXT:    ret
+entry:
+  %add = add i8 %b, %a
+  %add2 = add i8 %c, %d
+  %add3 = add i8 %add2, %add
+  ret i8 %add3
+}
+
+define zeroext i8 @zexti8_paddb4(i8 zeroext %a, i8 zeroext %b, i8 zeroext %c, i8 zeroext %d) {
+; CHECK-LABEL: zexti8_paddb4:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    padd.b a0, a0, a1
+; CHECK-NEXT:    padd.b a0, a0, a3
+; CHECK-NEXT:    padd.b a0, a0, a2
+; CHECK-NEXT:    ret
+entry:
+  %add = add i8 %b, %a
+  %add2 = add i8 %c, %d
+  %add3 = add i8 %add2, %add
+  ret i8 %add3
+}
+
+define zeroext i16 @zexti16_paddh1(i16 zeroext %x, i16 zeroext %y) {
+; CHECK-LABEL: zexti16_paddh1:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    padd.h a0, a1, a0
+; CHECK-NEXT:    ret
+entry:
+  %add = add i16 %y, %x
+  ret i16 %add
+}
+
+define zeroext i16 @zexti16_paddh2(i16 zeroext %a, i16 zeroext %b, i16 zeroext %c, i16 zeroext %d) {
+; CHECK-LABEL: zexti16_paddh2:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    padd.h a0, a0, a1
+; CHECK-NEXT:    padd.h a0, a0, a3
+; CHECK-NEXT:    padd.h a0, a0, a2
+; CHECK-NEXT:    ret
+entry:
+  %add = add i16 %b, %a
+  %add2 = add i16 %c, %d
+  %add3 = add i16 %add2, %add
+  ret i16 %add3
+}
+
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; RV32: {{.*}}



More information about the llvm-commits mailing list