[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
Thu Sep 3 07:03:51 PDT 2026
https://github.com/Rajveer100 updated https://github.com/llvm/llvm-project/pull/213260
>From 013d178054951eb273c17d7c7a786106624f6c07 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 1/3] [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/RISCVISelDAGToDAG.cpp | 89 ++++++++++++++++++
llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h | 1 +
...-p-extension-narrow-unsigned-arithmetic.ll | 91 +++++++++++++++++++
3 files changed, 181 insertions(+)
create mode 100644 llvm/test/CodeGen/RISCV/experimental-p-extension-narrow-unsigned-arithmetic.ll
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index f36f27769a353..3b948b566cade 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -24,6 +24,7 @@
#include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
+#include <queue>
using namespace llvm;
@@ -558,6 +559,91 @@ void RISCVDAGToDAGISel::selectXSfmmVSET(SDNode *Node) {
}
}
+bool RISCVDAGToDAGISel::tryPExtNarrowUnsigned(SDNode *Node) {
+ using namespace SDPatternMatch;
+
+ EVT VT = Node->getValueType(0);
+ SDLoc DL(Node);
+
+ APInt ConstMask;
+
+ // auto m_AddOrSub = [](Value_match N0 = m_Value(), Value_match N1 =
+ // m_Value(),
+ // Value_match N2 = m_Value(), Value_match N3 =
+ // m_Value()) {
+ // return m_AnyOf(m_Add(N0, N1), m_Sub(N2, N3));
+ // };
+
+ if (sd_match(Node,
+ m_And(m_Add(m_Value(), m_Value()), m_ConstInt(ConstMask)))) {
+ uint64_t ConstMaskVal = ConstMask.getZExtValue();
+ if (ConstMaskVal != 255 && ConstMaskVal != 65535)
+ return false;
+
+ uint64_t OpCode = (ConstMaskVal == 255) ? RISCV::PADD_B : RISCV::PADD_H;
+
+ std::queue<SDNode *> Worklist;
+ Worklist.push(Node->getOperand(0).getNode());
+
+ std::vector<SDNode *> Ops;
+ while (!Worklist.empty()) {
+ SDNode *CurNode = Worklist.front();
+ Worklist.pop();
+
+ SDValue N0, N1, N2, N3;
+ SDValue A0, A1;
+ 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_Add(m_Node(ISD::AssertZext, m_Value(A0), m_Value()),
+ m_Node(ISD::AssertZext, m_Value(A1), m_Value())))) {
+ Worklist.push(CurNode->getOperand(0).getNode());
+ Worklist.push(CurNode->getOperand(1).getNode());
+ } else if (sd_match(CurNode,
+ m_Node(ISD::AssertZext, m_Value(A0), m_Value()))) {
+ Ops.push_back(A0.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(CurDAG->getMachineNode(OpCode, DL, VT, SDValue(Op0, 0),
+ SDValue(Op1, 0)),
+ 0);
+ IsFirst = false;
+ } else {
+ PAddNode = SDValue(
+ CurDAG->getMachineNode(OpCode, DL, VT, LastOp, SDValue(Op0, 0)), 0);
+ }
+ LastOp = PAddNode;
+ }
+
+ ReplaceNode(Node, LastOp.getNode());
+ return true;
+ }
+
+ return false;
+}
+
bool RISCVDAGToDAGISel::tryShrinkShlLogicImm(SDNode *Node) {
MVT VT = Node->getSimpleValueType(0);
unsigned Opcode = Node->getOpcode();
@@ -1816,6 +1902,9 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
if (tryShrinkShlLogicImm(Node))
return;
+ if (Subtarget->hasStdExtP() && tryPExtNarrowUnsigned(Node))
+ return;
+
break;
}
case ISD::MUL: {
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index fabd32750a73e..9b95a6a3d62b2 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -99,6 +99,7 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
bool SelectAddrRegReg(SDValue Addr, SDValue &Base, SDValue &Offset);
+ bool tryPExtNarrowUnsigned(SDNode *Node);
bool tryShrinkShlLogicImm(SDNode *Node);
bool trySignedBitfieldExtract(SDNode *Node);
bool trySignedBitfieldInsertInSign(SDNode *Node);
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: {{.*}}
>From 07ddde196230bef9d50b8a3c85ca84fa001f0656 Mon Sep 17 00:00:00 2001
From: Rajveer <rajveer.developer at icloud.com>
Date: Thu, 3 Sep 2026 18:31:07 +0530
Subject: [PATCH 2/3] Check for enough zero bits in AssertZext, multi-use
checks, flags for size/performance
---
llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 62 ++++++++++++-------
...-p-extension-narrow-unsigned-arithmetic.ll | 17 +++++
2 files changed, 58 insertions(+), 21 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 3b948b566cade..3b4ce30b35c61 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -567,20 +567,21 @@ bool RISCVDAGToDAGISel::tryPExtNarrowUnsigned(SDNode *Node) {
APInt ConstMask;
- // auto m_AddOrSub = [](Value_match N0 = m_Value(), Value_match N1 =
- // m_Value(),
- // Value_match N2 = m_Value(), Value_match N3 =
- // m_Value()) {
- // return m_AnyOf(m_Add(N0, N1), m_Sub(N2, N3));
- // };
-
if (sd_match(Node,
m_And(m_Add(m_Value(), m_Value()), m_ConstInt(ConstMask)))) {
uint64_t ConstMaskVal = ConstMask.getZExtValue();
if (ConstMaskVal != 255 && ConstMaskVal != 65535)
return false;
- uint64_t OpCode = (ConstMaskVal == 255) ? RISCV::PADD_B : RISCV::PADD_H;
+ uint64_t PAddOpcode = (ConstMaskVal == 255) ? RISCV::PADD_B : RISCV::PADD_H;
+ uint64_t MinZeroBits = (ConstMaskVal == 255) ? 24 : 16;
+
+ auto hasEnoughZeroBits = [MinZeroBits](SDNode *Node, SDValue Op) -> bool {
+ VTSDNode *ChainedNode = dyn_cast<VTSDNode>(Op);
+ EVT FromTy = ChainedNode->getVT();
+ EVT ToTy = Node->getValueType(0);
+ return (ToTy.getSizeInBits() - FromTy.getSizeInBits()) >= MinZeroBits;
+ };
std::queue<SDNode *> Worklist;
Worklist.push(Node->getOperand(0).getNode());
@@ -591,28 +592,45 @@ bool RISCVDAGToDAGISel::tryPExtNarrowUnsigned(SDNode *Node) {
Worklist.pop();
SDValue N0, N1, N2, N3;
- SDValue A0, A1;
+ SDValue A0, A1, V0, V1;
if (sd_match(CurNode, m_Add(m_Add(m_Value(N0), m_Value(N1)),
m_Add(m_Value(N2), m_Value(N3))))) {
+ SDValue Inner0 = CurNode->getOperand(0);
+ SDValue Inner1 = CurNode->getOperand(1);
+ if (!Inner0.hasOneUse() || !Inner1.hasOneUse())
+ return false;
+
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())))) {
+ m_Node(ISD::AssertZext, m_Value(V1),
+ m_Value(A1))))) {
+ SDValue Inner0 = CurNode->getOperand(0);
+ if (!Inner0.hasOneUse())
+ return false;
+
+ if (!hasEnoughZeroBits(V1.getNode(), A1))
+ return false;
+
Worklist.push(N1.getNode());
Worklist.push(N0.getNode());
Worklist.push(CurNode->getOperand(1).getNode());
- } else if (sd_match(
- CurNode,
- m_Add(m_Node(ISD::AssertZext, m_Value(A0), m_Value()),
- m_Node(ISD::AssertZext, m_Value(A1), m_Value())))) {
+ } else if (sd_match(CurNode, m_Add(m_Node(ISD::AssertZext, m_Value(V0),
+ m_Value(A0)),
+ m_Node(ISD::AssertZext, m_Value(V1),
+ m_Value(A1))))) {
+ if (!hasEnoughZeroBits(V1.getNode(), A1) ||
+ !hasEnoughZeroBits(V0.getNode(), A0))
+ return false;
Worklist.push(CurNode->getOperand(0).getNode());
Worklist.push(CurNode->getOperand(1).getNode());
} else if (sd_match(CurNode,
- m_Node(ISD::AssertZext, m_Value(A0), m_Value()))) {
- Ops.push_back(A0.getNode());
+ m_Node(ISD::AssertZext, m_Value(V0), m_Value(A1)))) {
+ if (!hasEnoughZeroBits(V0.getNode(), A1))
+ return false;
+ Ops.push_back(V0.getNode());
}
}
@@ -626,13 +644,14 @@ bool RISCVDAGToDAGISel::tryPExtNarrowUnsigned(SDNode *Node) {
if (IsFirst) {
SDNode *Op1 = Ops[Idx++];
PAddNode =
- SDValue(CurDAG->getMachineNode(OpCode, DL, VT, SDValue(Op0, 0),
+ SDValue(CurDAG->getMachineNode(PAddOpcode, DL, VT, SDValue(Op0, 0),
SDValue(Op1, 0)),
0);
IsFirst = false;
} else {
PAddNode = SDValue(
- CurDAG->getMachineNode(OpCode, DL, VT, LastOp, SDValue(Op0, 0)), 0);
+ CurDAG->getMachineNode(PAddOpcode, DL, VT, LastOp, SDValue(Op0, 0)),
+ 0);
}
LastOp = PAddNode;
}
@@ -1902,8 +1921,9 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
if (tryShrinkShlLogicImm(Node))
return;
- if (Subtarget->hasStdExtP() && tryPExtNarrowUnsigned(Node))
- return;
+ if (!CurDAG->getMachineFunction().getFunction().hasMinSize())
+ if (Subtarget->hasStdExtP() && tryPExtNarrowUnsigned(Node))
+ return;
break;
}
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
index f2862bad87652..27c7f35b1351a 100644
--- a/llvm/test/CodeGen/RISCV/experimental-p-extension-narrow-unsigned-arithmetic.ll
+++ b/llvm/test/CodeGen/RISCV/experimental-p-extension-narrow-unsigned-arithmetic.ll
@@ -87,5 +87,22 @@ entry:
ret i16 %add3
}
+define zeroext i16 @zexti16_paddh2_neg_minsize(i16 zeroext %a, i16 zeroext %b, i16 zeroext %c, i16 zeroext %d) #0 {
+; CHECK-LABEL: zexti16_paddh2_neg_minsize:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: add a0, a1, a0
+; CHECK-NEXT: add a2, a2, a3
+; CHECK-NEXT: add a0, a2, a0
+; CHECK-NEXT: zext.h a0, a0
+; CHECK-NEXT: ret
+entry:
+ %add = add i16 %b, %a
+ %add2 = add i16 %c, %d
+ %add3 = add i16 %add2, %add
+ ret i16 %add3
+}
+
+attributes #0 = { minsize }
+
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
; RV32: {{.*}}
>From 129e7d4e63d15319dc8a1e2beef7bc0dc29fe94e Mon Sep 17 00:00:00 2001
From: Rajveer Singh Bharadwaj <rajveer.developer at icloud.com>
Date: Thu, 3 Sep 2026 19:33:17 +0530
Subject: [PATCH 3/3] Fix SDPatternMatch namespace
---
llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 3b4ce30b35c61..7274c174768df 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -560,7 +560,7 @@ void RISCVDAGToDAGISel::selectXSfmmVSET(SDNode *Node) {
}
bool RISCVDAGToDAGISel::tryPExtNarrowUnsigned(SDNode *Node) {
- using namespace SDPatternMatch;
+ using namespace llvm::SDPatternMatch;
EVT VT = Node->getValueType(0);
SDLoc DL(Node);
More information about the llvm-commits
mailing list