[llvm] [RISCV][P-ext] Add packed sign and zero extend intrinsics (PR #208685)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 22:21:34 PDT 2026


https://github.com/sihuan updated https://github.com/llvm/llvm-project/pull/208685

>From a88d76a3ee069205521878f66e38508410cc1645 Mon Sep 17 00:00:00 2001
From: SiHuaN <liyongtai at iscas.ac.cn>
Date: Thu, 9 Jul 2026 08:56:31 +0000
Subject: [PATCH 1/5] [RISCV][P-ext] Add packed sign and zero extend combines

---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 40 +++++++++--
 llvm/lib/Target/RISCV/RISCVInstrInfoP.td    | 29 ++++++--
 llvm/test/CodeGen/RISCV/rvp-simd-32.ll      | 20 ++++++
 llvm/test/CodeGen/RISCV/rvp-simd-64.ll      | 74 +++++++++++++++++++++
 4 files changed, 155 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 96b6f391863c4..c15e96f1aed3b 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -721,11 +721,11 @@ RISCVTargetLowering::RISCVTargetLowering(const TargetMachine &TM,
                          {MVT::v4i16, MVT::v2i32}, Legal);
       setOperationAction(ISD::ANY_EXTEND_VECTOR_INREG, {MVT::v4i16, MVT::v2i32},
                          Custom);
-      // LegalizeVectorOps uses result VT, LegalizeDAG uses ExtVT.
-      setOperationAction(ISD::SIGN_EXTEND_INREG,
-                         {MVT::v2i16, MVT::v4i8, MVT::v2i32, MVT::v4i16},
-                         Legal);
     }
+    // LegalizeVectorOps uses result VT, LegalizeDAG uses ExtVT.
+    setOperationAction(
+        ISD::SIGN_EXTEND_INREG,
+        {MVT::v2i8, MVT::v4i8, MVT::v2i16, MVT::v4i16, MVT::v2i32}, Legal);
   }
 
   if (Subtarget.hasStdExtZfbfmin()) {
@@ -17771,6 +17771,36 @@ static SDValue combineNarrowableShiftedLoad(SDNode *N, SelectionDAG &DAG) {
                      DAG.getShiftAmountConstant(ShiftAmt, VT, DL));
 }
 
+static SDValue combinePZExt(SDNode *N, SelectionDAG &DAG,
+                            const RISCVSubtarget &Subtarget) {
+  EVT VT = N->getValueType(0);
+  if (!VT.isSimple())
+    return SDValue();
+
+  MVT SimpleVT = VT.getSimpleVT();
+  if (!Subtarget.isPExtPackedType(SimpleVT))
+    return SDValue();
+
+  APInt SplatVal;
+  if (!ISD::isConstantSplatVector(N->getOperand(1).getNode(), SplatVal))
+    return SDValue();
+
+  MVT EltVT = SimpleVT.getVectorElementType();
+  bool IsByteToHalf = EltVT == MVT::i16 && SplatVal == 0xff;
+  bool IsHalfToWord = EltVT == MVT::i32 && SplatVal == 0xffff;
+  if (!IsByteToHalf && !IsHalfToWord)
+    return SDValue();
+
+  MVT PPairVT = MVT::getVectorVT(
+      MVT::getIntegerVT(SimpleVT.getScalarSizeInBits() / 2),
+      SimpleVT.getVectorNumElements() * 2);
+  SDLoc DL(N);
+  SDValue Src = DAG.getBitcast(PPairVT, N->getOperand(0));
+  SDValue Res = DAG.getNode(RISCVISD::PPAIRE, DL, PPairVT, Src,
+                            DAG.getConstant(0, DL, PPairVT));
+  return DAG.getBitcast(SimpleVT, Res);
+}
+
 // 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.
@@ -17779,6 +17809,8 @@ static SDValue performANDCombine(SDNode *N,
                                  const RISCVSubtarget &Subtarget) {
   SelectionDAG &DAG = DCI.DAG;
   SDValue N0 = N->getOperand(0);
+  if (SDValue V = combinePZExt(N, DAG, Subtarget))
+    return V;
 
   // Pre-promote (i32 (and (srl X, Y), 1)) on RV64 with Zbs without zero
   // extending X. This is safe since we only need the LSB after the shift and
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
index e49c861ae800b..c0e89c6d440f4 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
@@ -2512,6 +2512,15 @@ let append Predicates = [IsRV32] in {
   def : PatGprPair<riscv_psabs, PSABS_DB, v8i8>;
   def : PatGprPair<riscv_psabs, PSABS_DH, v4i16>;
 
+  // Packed sign extend patterns
+  // Sign extend inreg patterns using psext. sext_invec is lowered to
+  // zext_invec+sext_inreg.
+  def : Pat<(v2i16 (sext_inreg GPR:$rs1, v2i8)), (PSEXT_H_B GPR:$rs1)>;
+  def : Pat<(v4i16 (sext_inreg GPRPair:$rs1, v4i8)), (PSEXT_DH_B GPRPair:$rs1)>;
+  def : Pat<(v2i32 (sext_inreg GPRPair:$rs1, v2i8)), (PSEXT_DW_B GPRPair:$rs1)>;
+  def : Pat<(v2i32 (sext_inreg GPRPair:$rs1, v2i16)),
+            (PSEXT_DW_H GPRPair:$rs1)>;
+
   // 16-bit bswap patterns
   def : Pat<(v4i16 (bswap GPRPair:$rs)),
             (PPAIROE_DB GPRPair:$rs, GPRPair:$rs)>;
@@ -2558,8 +2567,12 @@ let append Predicates = [IsRV32] in {
   def : Pat<(v2i32 (build_vector (XLenVT GPR:$a), (XLenVT GPR:$b))),
             (BuildGPRPair GPR:$a, GPR:$b)>;
 
+  def : Pat<(v4i8 (riscv_ppaire GPR:$rs1, GPR:$rs2)),
+            (PPAIRE_B GPR:$rs1, GPR:$rs2)>;
   def : Pat<(v8i8 (riscv_ppaire GPRPair:$rs1, GPRPair:$rs2)),
             (PPAIRE_DB GPRPair:$rs1, GPRPair:$rs2)>;
+  def : Pat<(v4i16 (riscv_ppaire GPRPair:$rs1, GPRPair:$rs2)),
+            (PPAIRE_DH GPRPair:$rs1, GPRPair:$rs2)>;
 
   // Concat vector patterns
   def : Pat<(v8i8 (concat_vectors (v4i8 GPR:$a), (v4i8 GPR:$b))),
@@ -2589,6 +2602,11 @@ let append Predicates = [IsRV64] in {
 
   def : PatGpr<bitreverse, REV_RV64>;
 
+  def : Pat<(v8i8 (riscv_ppaire GPR:$rs1, GPR:$rs2)),
+            (PPAIRE_B GPR:$rs1, GPR:$rs2)>;
+  def : Pat<(v4i16 (riscv_ppaire GPR:$rs1, GPR:$rs2)),
+            (PPAIRE_H GPR:$rs1, GPR:$rs2)>;
+
   def : Pat<(XLenVT (riscv_sati GPR:$rs1, timm:$imm)),
             (SATI_RV64 GPR:$rs1, (IncImm timm:$imm))>;
   def : Pat<(XLenVT (riscv_usati GPR:$rs1, timm:$imm)),
@@ -2751,6 +2769,13 @@ let append Predicates = [IsRV64] in {
   def : Pat<(v2i32 (vselect (v2i32 GPR:$mask), GPR:$true_v, GPR:$false_v)),
             (MERGE GPR:$mask, GPR:$false_v, GPR:$true_v)>;
 
+  // Packed sign extend patterns
+  // Sign extend inreg patterns using psext. sext_invec is lowered to
+  // zext_invec+sext_inreg.
+  def : Pat<(v4i16 (sext_inreg GPR:$rs1, v4i8)), (PSEXT_H_B GPR:$rs1)>;
+  def : Pat<(v2i32 (sext_inreg GPR:$rs1, v2i8)), (PSEXT_W_B GPR:$rs1)>;
+  def : Pat<(v2i32 (sext_inreg GPR:$rs1, v2i16)), (PSEXT_W_H GPR:$rs1)>;
+
   // 32-bit bswap patterns
   def : Pat<(v2i32 (bswap GPR:$rs)),
             (PPAIROE_W (REV8_RV64 GPR:$rs), (REV8_RV64 GPR:$rs))>;
@@ -2825,9 +2850,5 @@ let append Predicates = [IsRV64] in {
             (ZIP8P GPR:$rs1, GPR:$rs2)>;
   def : Pat<(v4i16 (riscv_pzip (v4i16 GPR:$rs1), (v4i16 GPR:$rs2))),
             (ZIP16P GPR:$rs1, GPR:$rs2)>;
-  // Sign extend inreg patterns using psext. sext_invec is lowered to
-  // zext_invec+sext_inreg.
-  def : Pat<(v4i16 (sext_inreg GPR:$rs1, v4i8)), (PSEXT_H_B GPR:$rs1)>;
-  def : Pat<(v2i32 (sext_inreg GPR:$rs1, v2i16)), (PSEXT_W_H GPR:$rs1)>;
 } // append Predicates = [IsRV64]
 } // Predicates = [HasStdExtP]
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
index 6d1bd5a336d38..b3ac71405bea5 100644
--- a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
@@ -2681,6 +2681,26 @@ define <2 x i16> @test_pmerge_mvmn_i16x2(<2 x i16> %rs2, <2 x i16> %rs1, <2 x i1
   ret <2 x i16> %res
 }
 
+; Packed sign and zero extend
+define <2 x i16> @test_psext_b_v2i16(<2 x i16> %a) {
+; CHECK-LABEL: test_psext_b_v2i16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    psext.h.b a0, a0
+; CHECK-NEXT:    ret
+  %shl = shl <2 x i16> %a, splat (i16 8)
+  %res = ashr <2 x i16> %shl, splat (i16 8)
+  ret <2 x i16> %res
+}
+
+define <2 x i16> @test_pzext_b_v2i16(<2 x i16> %a) {
+; CHECK-LABEL: test_pzext_b_v2i16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pzext.h.b a0, a0
+; CHECK-NEXT:    ret
+  %res = and <2 x i16> %a, splat (i16 255)
+  ret <2 x i16> %res
+}
+
 ; Packed absolute difference sum
 define i32 @test_pabdsumu_u8x4_u32(<4 x i8> %a, <4 x i8> %b) {
 ; RV32-LABEL: test_pabdsumu_u8x4_u32:
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-64.ll b/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
index 164996a746c23..d30bbe9154957 100644
--- a/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
@@ -5892,6 +5892,80 @@ define <2 x i32> @test_pmerge_mvmn_i32x2(<2 x i32> %rs2, <2 x i32> %rs1, <2 x i3
   ret <2 x i32> %res
 }
 
+; Packed sign and zero extend
+define <4 x i16> @test_psext_b_v4i16(<4 x i16> %a) {
+; RV32-LABEL: test_psext_b_v4i16:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psext.dh.b a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psext_b_v4i16:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psext.h.b a0, a0
+; RV64-NEXT:    ret
+  %shl = shl <4 x i16> %a, splat (i16 8)
+  %res = ashr <4 x i16> %shl, splat (i16 8)
+  ret <4 x i16> %res
+}
+
+define <2 x i32> @test_psext_b_v2i32(<2 x i32> %a) {
+; RV32-LABEL: test_psext_b_v2i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psext.dw.b a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psext_b_v2i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psext.w.b a0, a0
+; RV64-NEXT:    ret
+  %shl = shl <2 x i32> %a, splat (i32 24)
+  %res = ashr <2 x i32> %shl, splat (i32 24)
+  ret <2 x i32> %res
+}
+
+define <2 x i32> @test_psext_h_v2i32(<2 x i32> %a) {
+; RV32-LABEL: test_psext_h_v2i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psext.dw.h a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psext_h_v2i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psext.w.h a0, a0
+; RV64-NEXT:    ret
+  %shl = shl <2 x i32> %a, splat (i32 16)
+  %res = ashr <2 x i32> %shl, splat (i32 16)
+  ret <2 x i32> %res
+}
+
+define <4 x i16> @test_pzext_b_v4i16(<4 x i16> %a) {
+; RV32-LABEL: test_pzext_b_v4i16:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pzext.dh.b a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pzext_b_v4i16:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pzext.h.b a0, a0
+; RV64-NEXT:    ret
+  %res = and <4 x i16> %a, splat (i16 255)
+  ret <4 x i16> %res
+}
+
+define <2 x i32> @test_pzext_h_v2i32(<2 x i32> %a) {
+; RV32-LABEL: test_pzext_h_v2i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pzext.dw.h a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pzext_h_v2i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pzext.w.h a0, a0
+; RV64-NEXT:    ret
+  %res = and <2 x i32> %a, splat (i32 65535)
+  ret <2 x i32> %res
+}
+
 ; Packed absolute difference sum
 define i32 @test_pabdsumu_u8x8_u32(<8 x i8> %a, <8 x i8> %b) {
 ; RV32-LABEL: test_pabdsumu_u8x8_u32:

>From 92b8109d0b8d463f7816980a10892a427678e377 Mon Sep 17 00:00:00 2001
From: SiHuaN <liyongtai at iscas.ac.cn>
Date: Tue, 14 Jul 2026 15:11:25 +0000
Subject: [PATCH 2/5] [RISCV][P-ext] Add packed sign and zero extend intrinsics

---
 llvm/include/llvm/IR/IntrinsicsRISCV.td       | 10 +++
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   | 79 +++++++++++++++++--
 llvm/test/CodeGen/RISCV/rvp-simd-32.ll        | 18 +++++
 llvm/test/CodeGen/RISCV/rvp-simd-64.ll        | 70 ++++++++++++++++
 .../RISCV/rvp-simd-intrinsic-invalid.ll       | 39 +++++++++
 5 files changed, 208 insertions(+), 8 deletions(-)
 create mode 100644 llvm/test/CodeGen/RISCV/rvp-simd-intrinsic-invalid.ll

diff --git a/llvm/include/llvm/IR/IntrinsicsRISCV.td b/llvm/include/llvm/IR/IntrinsicsRISCV.td
index fca28455f2117..b35c555af6a6e 100644
--- a/llvm/include/llvm/IR/IntrinsicsRISCV.td
+++ b/llvm/include/llvm/IR/IntrinsicsRISCV.td
@@ -2095,6 +2095,16 @@ class RVPBinaryIntrinsic
                             [IntrNoMem, IntrSpeculatable]>;
   def int_riscv_pmerge : RVPTernaryIntrinsic;
 
+  // Packed Sign and Zero Extend.
+  class RVPExtIntrinsic
+      : DefaultAttrsIntrinsic<[llvm_anyvector_ty],
+                              [LLVMMatchType<0>],
+                              [IntrNoMem, IntrSpeculatable]>;
+  def int_riscv_psext_b : RVPExtIntrinsic;
+  def int_riscv_psext_h : RVPExtIntrinsic;
+  def int_riscv_pzext_b : RVPExtIntrinsic;
+  def int_riscv_pzext_h : RVPExtIntrinsic;
+
   // Packed Absolute Difference Sum.
   def int_riscv_pabdsumu
       : DefaultAttrsIntrinsic<[llvm_anyint_ty],
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index c15e96f1aed3b..c9ee773330821 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -11961,6 +11961,21 @@ static unsigned getRVPShiftOpcode(Intrinsic::ID IntNo) {
   }
 }
 
+static SDValue lowerPZExt(SDValue Src, const SDLoc &DL, SelectionDAG &DAG,
+                          const RISCVSubtarget &Subtarget) {
+  MVT VT = Src.getSimpleValueType();
+  MVT PPairVT =
+      MVT::getVectorVT(MVT::getIntegerVT(VT.getScalarSizeInBits() / 2),
+                       VT.getVectorNumElements() * 2);
+  Src = DAG.getBitcast(PPairVT, Src);
+  unsigned ZeroReg = !Subtarget.is64Bit() && PPairVT.getSizeInBits() == 64
+                         ? RISCV::X0_Pair
+                         : RISCV::X0;
+  SDValue Res = DAG.getNode(RISCVISD::PPAIRE, DL, PPairVT, Src,
+                            DAG.getRegister(ZeroReg, PPairVT));
+  return DAG.getBitcast(VT, Res);
+}
+
 SDValue RISCVTargetLowering::LowerINTRINSIC_WO_CHAIN(SDValue Op,
                                                      SelectionDAG &DAG) const {
   unsigned IntNo = Op.getConstantOperandVal(0);
@@ -12138,6 +12153,35 @@ SDValue RISCVTargetLowering::LowerINTRINSIC_WO_CHAIN(SDValue Op,
     return DAG.getNode(getRVPShiftOpcode(IntNo), DL, Op.getValueType(),
                        Op.getOperand(1), ShAmt);
   }
+  case Intrinsic::riscv_psext_b:
+  case Intrinsic::riscv_psext_h: {
+    EVT VT = Op.getValueType();
+    if (!VT.isSimple() || !Subtarget.isPExtPackedType(VT.getSimpleVT()))
+      reportFatalUsageError("unsupported llvm.riscv.psext intrinsic");
+
+    MVT SimpleVT = VT.getSimpleVT();
+    unsigned SrcEltBits = IntNo == Intrinsic::riscv_psext_b ? 8 : 16;
+    if (SrcEltBits >= SimpleVT.getScalarSizeInBits())
+      reportFatalUsageError("unsupported llvm.riscv.psext intrinsic");
+
+    MVT ExtVT = MVT::getVectorVT(MVT::getIntegerVT(SrcEltBits),
+                                 SimpleVT.getVectorElementCount());
+    return DAG.getNode(ISD::SIGN_EXTEND_INREG, DL, SimpleVT, Op.getOperand(1),
+                       DAG.getValueType(ExtVT));
+  }
+  case Intrinsic::riscv_pzext_b:
+  case Intrinsic::riscv_pzext_h: {
+    EVT VT = Op.getValueType();
+    if (!VT.isSimple() || !Subtarget.isPExtPackedType(VT.getSimpleVT()))
+      reportFatalUsageError("unsupported llvm.riscv.pzext intrinsic");
+
+    MVT SimpleVT = VT.getSimpleVT();
+    unsigned SrcEltBits = IntNo == Intrinsic::riscv_pzext_b ? 8 : 16;
+    if (SimpleVT.getScalarSizeInBits() != SrcEltBits * 2)
+      reportFatalUsageError("unsupported llvm.riscv.pzext intrinsic");
+
+    return lowerPZExt(Op.getOperand(1), DL, DAG, Subtarget);
+  }
   case Intrinsic::riscv_pabdsumu:
   case Intrinsic::riscv_pabdsumau: {
     // On RV32 an i32-result absolute difference sum over a 64-bit (GPRPair)
@@ -16178,6 +16222,32 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
       Results.push_back(DAG.getNode(ISD::TRUNCATE, DL, MVT::i32, Res));
       return;
     }
+    case Intrinsic::riscv_psext_h:
+      reportFatalUsageError("unsupported llvm.riscv.psext intrinsic");
+    case Intrinsic::riscv_pzext_h:
+      reportFatalUsageError("unsupported llvm.riscv.pzext intrinsic");
+    case Intrinsic::riscv_psext_b:
+    case Intrinsic::riscv_pzext_b: {
+      bool IsSExt = IntNo == Intrinsic::riscv_psext_b;
+      const char *UnsupportedMsg =
+          IsSExt ? "unsupported llvm.riscv.psext intrinsic"
+                 : "unsupported llvm.riscv.pzext intrinsic";
+      EVT VT = N->getValueType(0);
+      if (!Subtarget.is64Bit() || VT != MVT::v2i16)
+        reportFatalUsageError(UnsupportedMsg);
+
+      SDValue Src = DAG.getNode(ISD::CONCAT_VECTORS, DL, MVT::v4i16,
+                                N->getOperand(1), DAG.getUNDEF(VT));
+      SDValue Res;
+      if (IsSExt)
+        Res = DAG.getNode(ISD::SIGN_EXTEND_INREG, DL, MVT::v4i16, Src,
+                          DAG.getValueType(MVT::v4i8));
+      else
+        Res = lowerPZExt(Src, DL, DAG, Subtarget);
+      Results.push_back(DAG.getNode(ISD::EXTRACT_SUBVECTOR, DL, VT, Res,
+                                    DAG.getVectorIdxConstant(0, DL)));
+      return;
+    }
     case Intrinsic::riscv_paadd:
     case Intrinsic::riscv_paaddu:
     case Intrinsic::riscv_pasub:
@@ -17791,14 +17861,7 @@ static SDValue combinePZExt(SDNode *N, SelectionDAG &DAG,
   if (!IsByteToHalf && !IsHalfToWord)
     return SDValue();
 
-  MVT PPairVT = MVT::getVectorVT(
-      MVT::getIntegerVT(SimpleVT.getScalarSizeInBits() / 2),
-      SimpleVT.getVectorNumElements() * 2);
-  SDLoc DL(N);
-  SDValue Src = DAG.getBitcast(PPairVT, N->getOperand(0));
-  SDValue Res = DAG.getNode(RISCVISD::PPAIRE, DL, PPairVT, Src,
-                            DAG.getConstant(0, DL, PPairVT));
-  return DAG.getBitcast(SimpleVT, Res);
+  return lowerPZExt(N->getOperand(0), SDLoc(N), DAG, Subtarget);
 }
 
 // Combines two comparison operation and logic operation to one selection
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
index b3ac71405bea5..4fdb87c9db517 100644
--- a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
@@ -2701,6 +2701,24 @@ define <2 x i16> @test_pzext_b_v2i16(<2 x i16> %a) {
   ret <2 x i16> %res
 }
 
+define <2 x i16> @test_riscv_psext_b_v2i16(<2 x i16> %a) {
+; CHECK-LABEL: test_riscv_psext_b_v2i16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    psext.h.b a0, a0
+; CHECK-NEXT:    ret
+  %res = call <2 x i16> @llvm.riscv.psext.b.v2i16(<2 x i16> %a)
+  ret <2 x i16> %res
+}
+
+define <2 x i16> @test_riscv_pzext_b_v2i16(<2 x i16> %a) {
+; CHECK-LABEL: test_riscv_pzext_b_v2i16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pzext.h.b a0, a0
+; CHECK-NEXT:    ret
+  %res = call <2 x i16> @llvm.riscv.pzext.b.v2i16(<2 x i16> %a)
+  ret <2 x i16> %res
+}
+
 ; Packed absolute difference sum
 define i32 @test_pabdsumu_u8x4_u32(<4 x i8> %a, <4 x i8> %b) {
 ; RV32-LABEL: test_pabdsumu_u8x4_u32:
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-64.ll b/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
index d30bbe9154957..6d3c7d4e584db 100644
--- a/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
@@ -5966,6 +5966,76 @@ define <2 x i32> @test_pzext_h_v2i32(<2 x i32> %a) {
   ret <2 x i32> %res
 }
 
+define <4 x i16> @test_riscv_psext_b_v4i16(<4 x i16> %a) {
+; RV32-LABEL: test_riscv_psext_b_v4i16:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psext.dh.b a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_riscv_psext_b_v4i16:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psext.h.b a0, a0
+; RV64-NEXT:    ret
+  %res = call <4 x i16> @llvm.riscv.psext.b.v4i16(<4 x i16> %a)
+  ret <4 x i16> %res
+}
+
+define <2 x i32> @test_riscv_psext_b_v2i32(<2 x i32> %a) {
+; RV32-LABEL: test_riscv_psext_b_v2i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psext.dw.b a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_riscv_psext_b_v2i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psext.w.b a0, a0
+; RV64-NEXT:    ret
+  %res = call <2 x i32> @llvm.riscv.psext.b.v2i32(<2 x i32> %a)
+  ret <2 x i32> %res
+}
+
+define <2 x i32> @test_riscv_psext_h_v2i32(<2 x i32> %a) {
+; RV32-LABEL: test_riscv_psext_h_v2i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psext.dw.h a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_riscv_psext_h_v2i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psext.w.h a0, a0
+; RV64-NEXT:    ret
+  %res = call <2 x i32> @llvm.riscv.psext.h.v2i32(<2 x i32> %a)
+  ret <2 x i32> %res
+}
+
+define <4 x i16> @test_riscv_pzext_b_v4i16(<4 x i16> %a) {
+; RV32-LABEL: test_riscv_pzext_b_v4i16:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pzext.dh.b a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_riscv_pzext_b_v4i16:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pzext.h.b a0, a0
+; RV64-NEXT:    ret
+  %res = call <4 x i16> @llvm.riscv.pzext.b.v4i16(<4 x i16> %a)
+  ret <4 x i16> %res
+}
+
+define <2 x i32> @test_riscv_pzext_h_v2i32(<2 x i32> %a) {
+; RV32-LABEL: test_riscv_pzext_h_v2i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pzext.dw.h a0, a0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_riscv_pzext_h_v2i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pzext.w.h a0, a0
+; RV64-NEXT:    ret
+  %res = call <2 x i32> @llvm.riscv.pzext.h.v2i32(<2 x i32> %a)
+  ret <2 x i32> %res
+}
+
 ; Packed absolute difference sum
 define i32 @test_pabdsumu_u8x8_u32(<8 x i8> %a, <8 x i8> %b) {
 ; RV32-LABEL: test_pabdsumu_u8x8_u32:
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-intrinsic-invalid.ll b/llvm/test/CodeGen/RISCV/rvp-simd-intrinsic-invalid.ll
new file mode 100644
index 0000000000000..28b1d7c53a6e7
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-intrinsic-invalid.ll
@@ -0,0 +1,39 @@
+; RUN: sed -n '/^; BEGIN-PSEXT-LEGAL$/,/^; END-PSEXT-LEGAL$/p' %s \
+; RUN:   | not llc -mtriple=riscv64 -mattr=+experimental-p,+m,+zbb \
+; RUN:   -o /dev/null 2>&1 | FileCheck %s --check-prefix=PSEXT
+; RUN: sed -n '/^; BEGIN-PSEXT-WIDEN$/,/^; END-PSEXT-WIDEN$/p' %s \
+; RUN:   | not llc -mtriple=riscv64 -mattr=+experimental-p,+m,+zbb \
+; RUN:   -o /dev/null 2>&1 | FileCheck %s --check-prefix=PSEXT
+; RUN: sed -n '/^; BEGIN-PZEXT$/,/^; END-PZEXT$/p' %s \
+; RUN:   | not llc -mtriple=riscv64 -mattr=+experimental-p,+m,+zbb \
+; RUN:   -o /dev/null 2>&1 | FileCheck %s --check-prefix=PZEXT
+
+; PSEXT: LLVM ERROR: unsupported llvm.riscv.psext intrinsic
+; PZEXT: LLVM ERROR: unsupported llvm.riscv.pzext intrinsic
+
+; BEGIN-PSEXT-LEGAL
+define <4 x i16> @bad_psext_legal_type(<4 x i16> %a) {
+  %res = call <4 x i16> @llvm.riscv.psext.h.v4i16(<4 x i16> %a)
+  ret <4 x i16> %res
+}
+
+declare <4 x i16> @llvm.riscv.psext.h.v4i16(<4 x i16>)
+; END-PSEXT-LEGAL
+
+; BEGIN-PSEXT-WIDEN
+define <2 x i16> @bad_psext_widen_type(<2 x i16> %a) {
+  %res = call <2 x i16> @llvm.riscv.psext.h.v2i16(<2 x i16> %a)
+  ret <2 x i16> %res
+}
+
+declare <2 x i16> @llvm.riscv.psext.h.v2i16(<2 x i16>)
+; END-PSEXT-WIDEN
+
+; BEGIN-PZEXT
+define <2 x i32> @bad_pzext(<2 x i32> %a) {
+  %res = call <2 x i32> @llvm.riscv.pzext.b.v2i32(<2 x i32> %a)
+  ret <2 x i32> %res
+}
+
+declare <2 x i32> @llvm.riscv.pzext.b.v2i32(<2 x i32>)
+; END-PZEXT

>From e39773a5e73859687bf0a8b87f2ef276d42e70bd Mon Sep 17 00:00:00 2001
From: SiHuaN <liyongtai at iscas.ac.cn>
Date: Wed, 15 Jul 2026 14:15:00 +0000
Subject: [PATCH 3/5] [RISCV][P-ext] Use a zero splat vector for packed zero
 extend

---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index c9ee773330821..afc8d02a777a7 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -11968,11 +11968,9 @@ static SDValue lowerPZExt(SDValue Src, const SDLoc &DL, SelectionDAG &DAG,
       MVT::getVectorVT(MVT::getIntegerVT(VT.getScalarSizeInBits() / 2),
                        VT.getVectorNumElements() * 2);
   Src = DAG.getBitcast(PPairVT, Src);
-  unsigned ZeroReg = !Subtarget.is64Bit() && PPairVT.getSizeInBits() == 64
-                         ? RISCV::X0_Pair
-                         : RISCV::X0;
-  SDValue Res = DAG.getNode(RISCVISD::PPAIRE, DL, PPairVT, Src,
-                            DAG.getRegister(ZeroReg, PPairVT));
+  SDValue Zero = DAG.getNode(ISD::SPLAT_VECTOR, DL, PPairVT,
+                             DAG.getConstant(0, DL, Subtarget.getXLenVT()));
+  SDValue Res = DAG.getNode(RISCVISD::PPAIRE, DL, PPairVT, Src, Zero);
   return DAG.getBitcast(VT, Res);
 }
 

>From d51323eeb8193e2c5fc4d73b321221a087c6c19f Mon Sep 17 00:00:00 2001
From: SiHuaN <liyongtai at iscas.ac.cn>
Date: Thu, 16 Jul 2026 13:36:39 +0000
Subject: [PATCH 4/5] [RISCV][P-ext] Lower packed zero extend through
 shuffle-with-zero

Replace the bespoke combinePZExt with the generic shuffle-with-zero path:
implement isVectorClearMaskLegal so DAGCombiner::XformToShuffleWithZero
rewrites the packed zero-extend `and` into shuffle(src, zero, ...), and
lower that shuffle to RISCVISD::PPAIRE in lowerVECTOR_SHUFFLE.

The shuffle is formed before type legalization, so on RV64 the illegal
32-bit view widens to the legal 64-bit view; PPAIRE refines the undef
lanes the widening introduces.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 65 +++++++++++++--------
 llvm/lib/Target/RISCV/RISCVISelLowering.h   |  2 +
 2 files changed, 42 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index afc8d02a777a7..65026a9964ffe 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -6484,6 +6484,23 @@ static SDValue lowerVECTOR_SHUFFLEAsPUnzip(ShuffleVectorSDNode *SVN,
   return DAG.getNode(Opc, DL, VT, V1, V2);
 }
 
+// Match the shuffle mask <0, N, 2, N+2, ...>: even result lanes take the even
+// source lanes (operand 0) and odd result lanes come from the second operand.
+// XformToShuffleWithZero forms this from `(and src, low-half-mask)` with a zero
+// second operand, which is a packed zero-extend, i.e. RISCVISD::PPAIRE(src, 0).
+// Undef lanes (e.g. from widening the 32-bit view to a legal 64-bit type on
+// RV64) always match, since PPAIRE refines them.
+static bool isPackedZExtShuffleMask(ArrayRef<int> Mask) {
+  unsigned NumElts = Mask.size();
+  if (NumElts % 2 != 0)
+    return false;
+  for (unsigned I = 0; I != NumElts / 2; ++I)
+    if ((Mask[2 * I] >= 0 && Mask[2 * I] != (int)(2 * I)) ||
+        (Mask[2 * I + 1] >= 0 && Mask[2 * I + 1] < (int)NumElts))
+      return false;
+  return true;
+}
+
 SDValue RISCVTargetLowering::lowerVECTOR_SHUFFLE(SDValue Op,
                                                  SelectionDAG &DAG) const {
   SDValue V1 = Op.getOperand(0);
@@ -6542,6 +6559,15 @@ SDValue RISCVTargetLowering::lowerVECTOR_SHUFFLE(SDValue Op,
       }
     }
 
+    // shuffle(src, zero, <0, N, 2, N+2, ...>) is a packed zero-extend:
+    // PPAIRE(src, 0). XformToShuffleWithZero forms this from an `and` with a
+    // low-half mask (see isVectorClearMaskLegal).
+    if (ISD::isConstantSplatVectorAllZeros(V2.getNode()) &&
+        isPackedZExtShuffleMask(Mask))
+      return DAG.getNode(RISCVISD::PPAIRE, DL, VT, V1,
+                         DAG.getNode(ISD::SPLAT_VECTOR, DL, VT,
+                                     DAG.getConstant(0, DL, XLenVT)));
+
     // Select an element reverse shuffle to VECTOR_REVERSE. The tablegen
     // patterns select rev8/rev16/ppairoe.* from VECTOR_REVERSE.
     // Reverse of the low L lanes, higher lanes poison. L == NumElts is a plain
@@ -7204,6 +7230,20 @@ SDValue RISCVTargetLowering::lowerVECTOR_SHUFFLE(SDValue Op,
   return DAG.getNode(ISD::VSELECT, DL, VT, SelectMask, V2, V1);
 }
 
+bool RISCVTargetLowering::isVectorClearMaskLegal(ArrayRef<int> M,
+                                                 EVT VT) const {
+  // Enable DAGCombiner::XformToShuffleWithZero to rewrite a packed zero-extend
+  // `and` into shuffle(src, zero, ...), which lowerVECTOR_SHUFFLE turns into
+  // RISCVISD::PPAIRE. Accept the packed byte/halfword views that lowering
+  // handles; the 32-bit views are illegal on RV64 but reachable before type
+  // legalization widens them to the legal 64-bit view.
+  if (!Subtarget.hasStdExtP() || !VT.isSimple())
+    return false;
+  MVT SVT = VT.getSimpleVT();
+  return (SVT == MVT::v4i8 || SVT == MVT::v8i8 || SVT == MVT::v4i16) &&
+         isPackedZExtShuffleMask(M);
+}
+
 bool RISCVTargetLowering::isShuffleMaskLegal(ArrayRef<int> M, EVT VT) const {
   // Only support legal VTs for other shuffles for now.
   if (!isTypeLegal(VT) || !Subtarget.hasVInstructions())
@@ -17839,29 +17879,6 @@ static SDValue combineNarrowableShiftedLoad(SDNode *N, SelectionDAG &DAG) {
                      DAG.getShiftAmountConstant(ShiftAmt, VT, DL));
 }
 
-static SDValue combinePZExt(SDNode *N, SelectionDAG &DAG,
-                            const RISCVSubtarget &Subtarget) {
-  EVT VT = N->getValueType(0);
-  if (!VT.isSimple())
-    return SDValue();
-
-  MVT SimpleVT = VT.getSimpleVT();
-  if (!Subtarget.isPExtPackedType(SimpleVT))
-    return SDValue();
-
-  APInt SplatVal;
-  if (!ISD::isConstantSplatVector(N->getOperand(1).getNode(), SplatVal))
-    return SDValue();
-
-  MVT EltVT = SimpleVT.getVectorElementType();
-  bool IsByteToHalf = EltVT == MVT::i16 && SplatVal == 0xff;
-  bool IsHalfToWord = EltVT == MVT::i32 && SplatVal == 0xffff;
-  if (!IsByteToHalf && !IsHalfToWord)
-    return SDValue();
-
-  return lowerPZExt(N->getOperand(0), SDLoc(N), DAG, Subtarget);
-}
-
 // 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.
@@ -17870,8 +17887,6 @@ static SDValue performANDCombine(SDNode *N,
                                  const RISCVSubtarget &Subtarget) {
   SelectionDAG &DAG = DCI.DAG;
   SDValue N0 = N->getOperand(0);
-  if (SDValue V = combinePZExt(N, DAG, Subtarget))
-    return V;
 
   // Pre-promote (i32 (and (srl X, Y), 1)) on RV64 with Zbs without zero
   // extending X. This is safe since we only need the LSB after the shift and
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.h b/llvm/lib/Target/RISCV/RISCVISelLowering.h
index f8a99d38e2691..2ca7c392639f7 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.h
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.h
@@ -100,6 +100,8 @@ class RISCVTargetLowering : public TargetLowering {
   /// should be stack expanded.
   bool isShuffleMaskLegal(ArrayRef<int> M, EVT VT) const override;
 
+  bool isVectorClearMaskLegal(ArrayRef<int> M, EVT VT) const override;
+
   bool isMultiStoresCheaperThanBitsMerge(EVT LTy, EVT HTy) const override {
     // If the pair to store is a mixture of float and int values, we will
     // save two bitwise instructions and one float-to-int instruction and

>From 180920769d3a72c77af60b38e6fd92b2ae736e33 Mon Sep 17 00:00:00 2001
From: SiHuaN <liyongtai at iscas.ac.cn>
Date: Fri, 17 Jul 2026 05:07:01 +0000
Subject: [PATCH 5/5] [RISCV][P-ext] Use getExtractSubvector helper

---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 65026a9964ffe..7316f9c713b79 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -16282,8 +16282,7 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
                           DAG.getValueType(MVT::v4i8));
       else
         Res = lowerPZExt(Src, DL, DAG, Subtarget);
-      Results.push_back(DAG.getNode(ISD::EXTRACT_SUBVECTOR, DL, VT, Res,
-                                    DAG.getVectorIdxConstant(0, DL)));
+      Results.push_back(DAG.getExtractSubvector(DL, VT, Res, 0));
       return;
     }
     case Intrinsic::riscv_paadd:



More information about the llvm-commits mailing list