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

>From 3ba62d4a6ff409b9a7faf6b0382b2197678570da Mon Sep 17 00:00:00 2001
From: SiHuaN <[email protected]>
Date: Thu, 17 Sep 2026 20:40:14 +0000
Subject: [PATCH 1/6] [RISCV] Add OPERAND_UIMM4_PLUS1 to
 RISCVInstrInfo::verifyInstruction

It is used by psati.h and psati.dh.
---
 llvm/lib/Target/RISCV/RISCVInstrInfo.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp 
b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index 6d83480fa6c69..b057c6066516f 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -3046,6 +3046,9 @@ bool RISCVInstrInfo::verifyInstruction(const MachineInstr 
&MI,
         CASE_OPERAND_UIMM_LSB_ZEROS(8, 000)
         CASE_OPERAND_UIMM_LSB_ZEROS(9, 000)
         // clang-format on
+        case RISCVOp::OPERAND_UIMM4_PLUS1:
+          Ok = Imm >= 1 && Imm <= 16;
+          break;
         case RISCVOp::OPERAND_UIMM5_NONZERO:
           Ok = isUInt<5>(Imm) && (Imm != 0);
           break;

>From 50f06a52304b9a6bbf11f7021f818a9a085abf3e Mon Sep 17 00:00:00 2001
From: SiHuaN <[email protected]>
Date: Thu, 17 Sep 2026 20:21:16 +0000
Subject: [PATCH 2/6] [RISCV][P-ext] Add packed saturation intrinsics

Lower llvm.riscv.psati/pusati to psati.h/psati.w and the RV32 paired
psati.dh/psati.dw forms. On RV64 the 32-bit v2i16 type is widened with
zeroed upper lanes.
---
 llvm/include/llvm/IR/IntrinsicsRISCV.td     |   8 ++
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp |  24 ++++
 llvm/lib/Target/RISCV/RISCVInstrInfoP.td    |  23 ++++
 llvm/test/CodeGen/RISCV/rvp-simd-32.ll      |  75 ++++++++++++
 llvm/test/CodeGen/RISCV/rvp-simd-64.ll      | 126 ++++++++++++++++++++
 5 files changed, 256 insertions(+)

diff --git a/llvm/include/llvm/IR/IntrinsicsRISCV.td 
b/llvm/include/llvm/IR/IntrinsicsRISCV.td
index 09399b0ea3f36..cf8339c9a2a75 100644
--- a/llvm/include/llvm/IR/IntrinsicsRISCV.td
+++ b/llvm/include/llvm/IR/IntrinsicsRISCV.td
@@ -2076,6 +2076,14 @@ class RVPBinaryIntrinsic
   def int_riscv_psshl  : RVPShiftIntrinsic;
   def int_riscv_psshlr : RVPShiftIntrinsic;
 
+  // Packed Saturation.
+  class RVPSaturationIntrinsic
+      : DefaultAttrsIntrinsic<[llvm_anyvector_ty],
+                              [LLVMMatchType<0>, llvm_i32_ty],
+                              [IntrNoMem, IntrSpeculatable, 
ImmArg<ArgIndex<1>>]>;
+  def int_riscv_psati  : RVPSaturationIntrinsic;
+  def int_riscv_pusati : RVPSaturationIntrinsic;
+
   // Packed Exchanged Addition and Subtraction.
   def int_riscv_pas  : RVPBinaryIntrinsic;
   def int_riscv_psa  : RVPBinaryIntrinsic;
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp 
b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 69d813fbce12d..539fb6d31f37d 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -13086,6 +13086,13 @@ SDValue 
RISCVTargetLowering::LowerINTRINSIC_WO_CHAIN(SDValue Op,
     return DAG.getNode(getRVPShiftOpcode(IntNo), DL, Op.getValueType(),
                        Op.getOperand(1), ShAmt);
   }
+  case Intrinsic::riscv_psati:
+  case Intrinsic::riscv_pusati: {
+    unsigned Opc =
+        IntNo == Intrinsic::riscv_psati ? RISCVISD::PSATI : RISCVISD::PUSATI;
+    SDValue Width = DAG.getAnyExtOrTrunc(Op.getOperand(2), DL, XLenVT);
+    return DAG.getNode(Opc, DL, Op.getValueType(), Op.getOperand(1), Width);
+  }
   case Intrinsic::riscv_psext_b:
   case Intrinsic::riscv_psext_h: {
     EVT VT = Op.getValueType();
@@ -17628,6 +17635,23 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
       Results.push_back(DAG.getExtractSubvector(DL, VT, Res, 0));
       return;
     }
+    case Intrinsic::riscv_psati:
+    case Intrinsic::riscv_pusati: {
+      MVT VT = N->getSimpleValueType(0);
+      if (!Subtarget.is64Bit() || VT != MVT::v2i16)
+        return;
+
+      MVT WideVT = MVT::v4i16;
+      SDValue Rs1 =
+          widenPackedVectorWithZeros(DAG, DL, N->getOperand(1), WideVT);
+      SDValue Width =
+          DAG.getAnyExtOrTrunc(N->getOperand(2), DL, Subtarget.getXLenVT());
+      unsigned Opc =
+          IntNo == Intrinsic::riscv_psati ? RISCVISD::PSATI : RISCVISD::PUSATI;
+      SDValue Res = DAG.getNode(Opc, DL, WideVT, Rs1, Width);
+      Results.push_back(DAG.getExtractSubvector(DL, VT, Res, 0));
+      return;
+    }
     case Intrinsic::riscv_predsum:
     case Intrinsic::riscv_predsumu: {
       bool IsSigned = IntNo == Intrinsic::riscv_predsum;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td 
b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
index 831a591cf6283..8adce67d0c221 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
@@ -2044,6 +2044,13 @@ def riscv_psshar : RVSDNode<"PSSHAR", 
SDT_RISCVPackedShift>;
 def riscv_psshl : RVSDNode<"PSSHL", SDT_RISCVPackedShift>;
 def riscv_psshlr : RVSDNode<"PSSHLR", SDT_RISCVPackedShift>;
 
+// Packed saturation to an immediate width.
+def SDT_RISCVPackedSaturation
+    : SDTypeProfile<1, 2, [SDTCisVec<0>, SDTCisSameAs<0, 1>,
+                           SDTCisVT<2, XLenVT>]>;
+def riscv_psati  : RVSDNode<"PSATI", SDT_RISCVPackedSaturation>;
+def riscv_pusati : RVSDNode<"PUSATI", SDT_RISCVPackedSaturation>;
+
 // RV32 packed narrowing shift.
 def SDT_RISCVPackedNarrowingShift
     : SDTypeProfile<1, 2, [SDTCisVec<0>, SDTCisVec<1>,
@@ -2338,6 +2345,10 @@ let Predicates = [HasStdExtP] in {
   def : PatGprShift<riscv_psshl, PSSHL_HS, XLenVecI16VT>;
   def : PatGprShift<riscv_psshlr, PSSHLR_HS, XLenVecI16VT>;
 
+  // 16-bit packed saturation patterns
+  def : PatGprImm<riscv_psati, PSATI_H, uimm4_plus1, XLenVecI16VT>;
+  def : PatGprImm<riscv_pusati, PUSATI_H, uimm4, XLenVecI16VT>;
+
   // 8-bit logical shift left/right
   def : PatGprShiftMask<riscv_pshl, PSLL_BS, shiftMask32, XLenVecI8VT>;
   def : PatGprShiftMask<riscv_psrl, PSRL_BS, shiftMask32, XLenVecI8VT>;
@@ -2815,6 +2826,10 @@ let append Predicates = [IsRV32] in {
   def : PatGprPairShift<riscv_psshl, PSSHL_DHS, v4i16>;
   def : PatGprPairShift<riscv_psshlr, PSSHLR_DHS, v4i16>;
 
+  // 16-bit packed saturation patterns
+  def : PatGprPairImm<riscv_psati, PSATI_DH, uimm4_plus1, v4i16>;
+  def : PatGprPairImm<riscv_pusati, PUSATI_DH, uimm4, v4i16>;
+
   // 32-bit saturating shift patterns
   def : PatGprPairImm<riscv_pssha, PSSLAI_DW, uimm5, v2i32>;
   def : PatGprPairImm<riscv_psshar, PSSLAI_DW, uimm5, v2i32>;
@@ -2825,6 +2840,10 @@ let append Predicates = [IsRV32] in {
   def : PatGprPairShift<riscv_psshl, PSSHL_DWS, v2i32>;
   def : PatGprPairShift<riscv_psshlr, PSSHLR_DWS, v2i32>;
 
+  // 32-bit packed saturation patterns
+  def : PatGprPairImm<riscv_psati, PSATI_DW, uimm5_plus1, v2i32>;
+  def : PatGprPairImm<riscv_pusati, PUSATI_DW, uimm5, v2i32>;
+
   // 8-bit logical shift left/right
   def : PatGprPairShiftMask<riscv_pshl, PSLL_DBS, shiftMask32, v8i8>;
   def : PatGprPairShiftMask<riscv_psrl, PSRL_DBS, shiftMask32, v8i8>;
@@ -3274,6 +3293,10 @@ let append Predicates = [IsRV64] in {
   def : PatGprShift<riscv_psshl, PSSHL_WS, v2i32>;
   def : PatGprShift<riscv_psshlr, PSSHLR_WS, v2i32>;
 
+  // 32-bit packed saturation patterns
+  def : PatGprImm<riscv_psati, PSATI_W, uimm5_plus1, v2i32>;
+  def : PatGprImm<riscv_pusati, PUSATI_W, uimm5, v2i32>;
+
   // 32-bit logical shift left/right
   def : PatGprShiftMask<riscv_pshl, PSLL_WS, shiftMask32, v2i32>;
   def : PatGprShiftMask<riscv_psrl, PSRL_WS, shiftMask32, v2i32>;
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll 
b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
index e9e04eb6b5ebe..5adc48d167f6e 100644
--- a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
@@ -3827,3 +3827,78 @@ define i32 @test_maccsu_h11_i32(i32 %rd, <2 x i16> %a, 
<2 x i16> %b) {
   %r = call i32 @llvm.riscv.maccsu.11.i32.v2i16(i32 %rd, <2 x i16> %a, <2 x 
i16> %b)
   ret i32 %r
 }
+
+define <2 x i16> @test_psati_i16x2(<2 x i16> %a) {
+; RV32-LABEL: test_psati_i16x2:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.h a0, a0, 8
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i16x2:
+; RV64:       # %bb.0:
+; RV64-NEXT:    zext.w a0, a0
+; RV64-NEXT:    psati.h a0, a0, 8
+; RV64-NEXT:    ret
+  %res = call <2 x i16> @llvm.riscv.psati.v2i16.i32(<2 x i16> %a, i32 8)
+  ret <2 x i16> %res
+}
+
+define <2 x i16> @test_psati_i16x2_min_width(<2 x i16> %a) {
+; RV32-LABEL: test_psati_i16x2_min_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.h a0, a0, 1
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i16x2_min_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    zext.w a0, a0
+; RV64-NEXT:    psati.h a0, a0, 1
+; RV64-NEXT:    ret
+  %res = call <2 x i16> @llvm.riscv.psati.v2i16.i32(<2 x i16> %a, i32 1)
+  ret <2 x i16> %res
+}
+
+define <2 x i16> @test_psati_i16x2_max_width(<2 x i16> %a) {
+; RV32-LABEL: test_psati_i16x2_max_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.h a0, a0, 16
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i16x2_max_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    zext.w a0, a0
+; RV64-NEXT:    psati.h a0, a0, 16
+; RV64-NEXT:    ret
+  %res = call <2 x i16> @llvm.riscv.psati.v2i16.i32(<2 x i16> %a, i32 16)
+  ret <2 x i16> %res
+}
+
+define <2 x i16> @test_pusati_u16x2(<2 x i16> %a) {
+; RV32-LABEL: test_pusati_u16x2:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pusati.h a0, a0, 0
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pusati_u16x2:
+; RV64:       # %bb.0:
+; RV64-NEXT:    zext.w a0, a0
+; RV64-NEXT:    pusati.h a0, a0, 0
+; RV64-NEXT:    ret
+  %res = call <2 x i16> @llvm.riscv.pusati.v2i16.i32(<2 x i16> %a, i32 0)
+  ret <2 x i16> %res
+}
+
+define <2 x i16> @test_pusati_u16x2_max_width(<2 x i16> %a) {
+; RV32-LABEL: test_pusati_u16x2_max_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pusati.h a0, a0, 15
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pusati_u16x2_max_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    zext.w a0, a0
+; RV64-NEXT:    pusati.h a0, a0, 15
+; RV64-NEXT:    ret
+  %res = call <2 x i16> @llvm.riscv.pusati.v2i16.i32(<2 x i16> %a, i32 15)
+  ret <2 x i16> %res
+}
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-64.ll 
b/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
index 43107137fe1fa..0330870cb14bb 100644
--- a/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-64.ll
@@ -8116,3 +8116,129 @@ define i64 @test_maccsu_w11_i64(i64 %rd, <2 x i32> %a, 
<2 x i32> %b) {
   %r = call i64 @llvm.riscv.maccsu.11.i64.v2i32(i64 %rd, <2 x i32> %a, <2 x 
i32> %b)
   ret i64 %r
 }
+
+define <4 x i16> @test_psati_i16x4(<4 x i16> %a) {
+; RV32-LABEL: test_psati_i16x4:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.dh a0, a0, 8
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i16x4:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psati.h a0, a0, 8
+; RV64-NEXT:    ret
+  %res = call <4 x i16> @llvm.riscv.psati.v4i16.i32(<4 x i16> %a, i32 8)
+  ret <4 x i16> %res
+}
+
+define <4 x i16> @test_psati_i16x4_min_width(<4 x i16> %a) {
+; RV32-LABEL: test_psati_i16x4_min_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.dh a0, a0, 1
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i16x4_min_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psati.h a0, a0, 1
+; RV64-NEXT:    ret
+  %res = call <4 x i16> @llvm.riscv.psati.v4i16.i32(<4 x i16> %a, i32 1)
+  ret <4 x i16> %res
+}
+
+define <4 x i16> @test_psati_i16x4_max_width(<4 x i16> %a) {
+; RV32-LABEL: test_psati_i16x4_max_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.dh a0, a0, 16
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i16x4_max_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psati.h a0, a0, 16
+; RV64-NEXT:    ret
+  %res = call <4 x i16> @llvm.riscv.psati.v4i16.i32(<4 x i16> %a, i32 16)
+  ret <4 x i16> %res
+}
+
+define <2 x i32> @test_psati_i32x2(<2 x i32> %a) {
+; RV32-LABEL: test_psati_i32x2:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.dw a0, a0, 16
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i32x2:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psati.w a0, a0, 16
+; RV64-NEXT:    ret
+  %res = call <2 x i32> @llvm.riscv.psati.v2i32.i32(<2 x i32> %a, i32 16)
+  ret <2 x i32> %res
+}
+
+define <2 x i32> @test_psati_i32x2_max_width(<2 x i32> %a) {
+; RV32-LABEL: test_psati_i32x2_max_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    psati.dw a0, a0, 32
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_psati_i32x2_max_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    psati.w a0, a0, 32
+; RV64-NEXT:    ret
+  %res = call <2 x i32> @llvm.riscv.psati.v2i32.i32(<2 x i32> %a, i32 32)
+  ret <2 x i32> %res
+}
+
+define <4 x i16> @test_pusati_u16x4(<4 x i16> %a) {
+; RV32-LABEL: test_pusati_u16x4:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pusati.dh a0, a0, 8
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pusati_u16x4:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pusati.h a0, a0, 8
+; RV64-NEXT:    ret
+  %res = call <4 x i16> @llvm.riscv.pusati.v4i16.i32(<4 x i16> %a, i32 8)
+  ret <4 x i16> %res
+}
+
+define <4 x i16> @test_pusati_u16x4_max_width(<4 x i16> %a) {
+; RV32-LABEL: test_pusati_u16x4_max_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pusati.dh a0, a0, 15
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pusati_u16x4_max_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pusati.h a0, a0, 15
+; RV64-NEXT:    ret
+  %res = call <4 x i16> @llvm.riscv.pusati.v4i16.i32(<4 x i16> %a, i32 15)
+  ret <4 x i16> %res
+}
+
+define <2 x i32> @test_pusati_u32x2(<2 x i32> %a) {
+; RV32-LABEL: test_pusati_u32x2:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pusati.dw a0, a0, 16
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pusati_u32x2:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pusati.w a0, a0, 16
+; RV64-NEXT:    ret
+  %res = call <2 x i32> @llvm.riscv.pusati.v2i32.i32(<2 x i32> %a, i32 16)
+  ret <2 x i32> %res
+}
+
+define <2 x i32> @test_pusati_u32x2_max_width(<2 x i32> %a) {
+; RV32-LABEL: test_pusati_u32x2_max_width:
+; RV32:       # %bb.0:
+; RV32-NEXT:    pusati.dw a0, a0, 31
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_pusati_u32x2_max_width:
+; RV64:       # %bb.0:
+; RV64-NEXT:    pusati.w a0, a0, 31
+; RV64-NEXT:    ret
+  %res = call <2 x i32> @llvm.riscv.pusati.v2i32.i32(<2 x i32> %a, i32 31)
+  ret <2 x i32> %res
+}

>From 3e72ecd76c9388696d08767679e91586bd980439 Mon Sep 17 00:00:00 2001
From: SiHuaN <[email protected]>
Date: Thu, 17 Sep 2026 20:21:23 +0000
Subject: [PATCH 3/6] [Clang][RISCV] Add packed saturation intrinsics

Expose __riscv_psati_* and __riscv_pusati_* from riscv_packed_simd.h.
The saturation width must be a compile-time constant, so the wrappers are
macros around builtins that take a _Constant argument; Sema checks the
width range.
---
 clang/include/clang/Basic/BuiltinsRISCV.td    |  10 ++
 clang/lib/CodeGen/TargetBuiltins/RISCV.cpp    |  26 +++++
 clang/lib/Headers/riscv_packed_simd.h         |  13 +++
 clang/lib/Sema/SemaRISCV.cpp                  |  11 ++
 clang/test/CodeGen/RISCV/rvp-intrinsics.c     | 110 ++++++++++++++++++
 .../Sema/riscv-psati-width-out-of-range.c     |  41 +++++++
 .../riscv_packed_simd.c                       |  42 +++++++
 7 files changed, 253 insertions(+)
 create mode 100644 clang/test/Sema/riscv-psati-width-out-of-range.c

diff --git a/clang/include/clang/Basic/BuiltinsRISCV.td 
b/clang/include/clang/Basic/BuiltinsRISCV.td
index ee840e45a65ba..e97bb0e66da53 100644
--- a/clang/include/clang/Basic/BuiltinsRISCV.td
+++ b/clang/include/clang/Basic/BuiltinsRISCV.td
@@ -496,6 +496,16 @@ def psshl_s_u32x2 : RISCVBuiltin<"_Vector<2, unsigned 
int>(_Vector<2, unsigned i
 def psshlr_s_u16x4 : RISCVBuiltin<"_Vector<4, unsigned short>(_Vector<4, 
unsigned short>, int)">;
 def psshlr_s_u32x2 : RISCVBuiltin<"_Vector<2, unsigned int>(_Vector<2, 
unsigned int>, int)">;
 
+// Packed Saturation (32-bit)
+def pusati_u16x2 : RISCVBuiltin<"_Vector<2, unsigned short>(_Vector<2, short>, 
_Constant unsigned int)">;
+def psati_i16x2  : RISCVBuiltin<"_Vector<2, short>(_Vector<2, short>, 
_Constant unsigned int)">;
+
+// Packed Saturation (64-bit)
+def pusati_u16x4 : RISCVBuiltin<"_Vector<4, unsigned short>(_Vector<4, short>, 
_Constant unsigned int)">;
+def pusati_u32x2 : RISCVBuiltin<"_Vector<2, unsigned int>(_Vector<2, int>, 
_Constant unsigned int)">;
+def psati_i16x4  : RISCVBuiltin<"_Vector<4, short>(_Vector<4, short>, 
_Constant unsigned int)">;
+def psati_i32x2  : RISCVBuiltin<"_Vector<2, int>(_Vector<2, int>, _Constant 
unsigned int)">;
+
 } // Features = "experimental-p"
 
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp 
b/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
index f99a05ce673aa..4f7b9ceae6eaa 100644
--- a/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
@@ -1531,6 +1531,32 @@ Value *CodeGenFunction::EmitRISCVBuiltinExpr(unsigned 
BuiltinID,
     break;
   }
 
+  // Packed Saturation
+  case RISCV::BI__builtin_riscv_psati_i16x2:
+  case RISCV::BI__builtin_riscv_psati_i16x4:
+  case RISCV::BI__builtin_riscv_psati_i32x2:
+  case RISCV::BI__builtin_riscv_pusati_u16x2:
+  case RISCV::BI__builtin_riscv_pusati_u16x4:
+  case RISCV::BI__builtin_riscv_pusati_u32x2: {
+    switch (BuiltinID) {
+    default:
+      llvm_unreachable("unexpected builtin ID");
+    case RISCV::BI__builtin_riscv_psati_i16x2:
+    case RISCV::BI__builtin_riscv_psati_i16x4:
+    case RISCV::BI__builtin_riscv_psati_i32x2:
+      ID = Intrinsic::riscv_psati;
+      break;
+    case RISCV::BI__builtin_riscv_pusati_u16x2:
+    case RISCV::BI__builtin_riscv_pusati_u16x4:
+    case RISCV::BI__builtin_riscv_pusati_u32x2:
+      ID = Intrinsic::riscv_pusati;
+      break;
+    }
+
+    IntrinsicTypes = {ResultType};
+    break;
+  }
+
   // Packed Multiplication with Horizontal Addition
   case RISCV::BI__builtin_riscv_pm4add_i8x4:
   case RISCV::BI__builtin_riscv_pm4add_i8x8:
diff --git a/clang/lib/Headers/riscv_packed_simd.h 
b/clang/lib/Headers/riscv_packed_simd.h
index 8c20d00e68652..0b8c516d5fe3e 100644
--- a/clang/lib/Headers/riscv_packed_simd.h
+++ b/clang/lib/Headers/riscv_packed_simd.h
@@ -615,6 +615,19 @@ __packed_binary_builtin_mixed(psshl_s_u32x2, uint32x2_t, 
uint32x2_t, int, __buil
 __packed_binary_builtin_mixed(psshlr_s_u16x4, uint16x4_t, uint16x4_t, int, 
__builtin_riscv_psshlr_s_u16x4)
 __packed_binary_builtin_mixed(psshlr_s_u32x2, uint32x2_t, uint32x2_t, int, 
__builtin_riscv_psshlr_s_u32x2)
 
+/* Packed Saturation (32-bit) */
+#define __riscv_pusati_u16x2(rs1, width)                                       
\
+  __builtin_riscv_pusati_u16x2(rs1, width)
+#define __riscv_psati_i16x2(rs1, width) __builtin_riscv_psati_i16x2(rs1, width)
+
+/* Packed Saturation (64-bit) */
+#define __riscv_pusati_u16x4(rs1, width)                                       
\
+  __builtin_riscv_pusati_u16x4(rs1, width)
+#define __riscv_pusati_u32x2(rs1, width)                                       
\
+  __builtin_riscv_pusati_u32x2(rs1, width)
+#define __riscv_psati_i16x4(rs1, width) __builtin_riscv_psati_i16x4(rs1, width)
+#define __riscv_psati_i32x2(rs1, width) __builtin_riscv_psati_i32x2(rs1, width)
+
 /* Packed Element Insert (32-bit) */
 __packed_insert(pset_i8_i8x4, int8x4_t, int8_t, 3)
 __packed_insert(pset_u8_u8x4, uint8x4_t, uint8_t, 3)
diff --git a/clang/lib/Sema/SemaRISCV.cpp b/clang/lib/Sema/SemaRISCV.cpp
index be6bbe8e5f9eb..58a52091ee8b2 100644
--- a/clang/lib/Sema/SemaRISCV.cpp
+++ b/clang/lib/Sema/SemaRISCV.cpp
@@ -968,6 +968,17 @@ bool SemaRISCV::CheckBuiltinFunctionCall(const TargetInfo 
&TI,
   case RISCV::BI__builtin_riscv_sm4ks:
   case RISCV::BI__builtin_riscv_sm4ed:
     return SemaRef.BuiltinConstantArgRange(TheCall, 2, 0, 3);
+  // Check the saturation width for the packed saturating instructions.
+  case RISCV::BI__builtin_riscv_psati_i16x2:
+  case RISCV::BI__builtin_riscv_psati_i16x4:
+    return SemaRef.BuiltinConstantArgRange(TheCall, 1, 1, 16);
+  case RISCV::BI__builtin_riscv_psati_i32x2:
+    return SemaRef.BuiltinConstantArgRange(TheCall, 1, 1, 32);
+  case RISCV::BI__builtin_riscv_pusati_u16x2:
+  case RISCV::BI__builtin_riscv_pusati_u16x4:
+    return SemaRef.BuiltinConstantArgRange(TheCall, 1, 0, 15);
+  case RISCV::BI__builtin_riscv_pusati_u32x2:
+    return SemaRef.BuiltinConstantArgRange(TheCall, 1, 0, 31);
   // Check if rnum is in [0, 10]
   case RISCV::BI__builtin_riscv_aes64ks1i:
     return SemaRef.BuiltinConstantArgRange(TheCall, 1, 0, 10);
diff --git a/clang/test/CodeGen/RISCV/rvp-intrinsics.c 
b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
index b2b2b1b9a777b..815609577420d 100644
--- a/clang/test/CodeGen/RISCV/rvp-intrinsics.c
+++ b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
@@ -11864,3 +11864,113 @@ int16x4_t test_pjoin2_i16x4(int16x2_t lo, int16x2_t 
hi) {
 uint16x4_t test_pjoin2_u16x4(uint16x2_t lo, uint16x2_t hi) {
   return __riscv_pjoin2_u16x4(lo, hi);
 }
+
+/* Packed Saturation (32-bit) */
+// RV32-LABEL: define dso_local i32 @test_pusati_u16x2(
+// RV32-SAME: i32 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i32 [[A_COERCE]] to <2 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = call <2 x i16> @llvm.riscv.pusati.v2i16(<2 x 
i16> [[TMP0]], i32 8)
+// RV32-NEXT:    [[TMP2:%.*]] = bitcast <2 x i16> [[TMP1]] to i32
+// RV32-NEXT:    ret i32 [[TMP2]]
+//
+// RV64-LABEL: define dso_local i32 @test_pusati_u16x2(
+// RV64-SAME: i32 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i32 [[A_COERCE]] to <2 x i16>
+// RV64-NEXT:    [[TMP1:%.*]] = call <2 x i16> @llvm.riscv.pusati.v2i16(<2 x 
i16> [[TMP0]], i32 8)
+// RV64-NEXT:    [[TMP2:%.*]] = bitcast <2 x i16> [[TMP1]] to i32
+// RV64-NEXT:    ret i32 [[TMP2]]
+//
+uint16x2_t test_pusati_u16x2(int16x2_t a) { return __riscv_pusati_u16x2(a, 8); 
}
+
+// RV32-LABEL: define dso_local i32 @test_psati_i16x2(
+// RV32-SAME: i32 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i32 [[A_COERCE]] to <2 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = call <2 x i16> @llvm.riscv.psati.v2i16(<2 x 
i16> [[TMP0]], i32 8)
+// RV32-NEXT:    [[TMP2:%.*]] = bitcast <2 x i16> [[TMP1]] to i32
+// RV32-NEXT:    ret i32 [[TMP2]]
+//
+// RV64-LABEL: define dso_local i32 @test_psati_i16x2(
+// RV64-SAME: i32 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i32 [[A_COERCE]] to <2 x i16>
+// RV64-NEXT:    [[TMP1:%.*]] = call <2 x i16> @llvm.riscv.psati.v2i16(<2 x 
i16> [[TMP0]], i32 8)
+// RV64-NEXT:    [[TMP2:%.*]] = bitcast <2 x i16> [[TMP1]] to i32
+// RV64-NEXT:    ret i32 [[TMP2]]
+//
+int16x2_t test_psati_i16x2(int16x2_t a) { return __riscv_psati_i16x2(a, 8); }
+
+/* Packed Saturation (64-bit) */
+// RV32-LABEL: define dso_local i64 @test_pusati_u16x4(
+// RV32-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <4 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = call <4 x i16> @llvm.riscv.pusati.v4i16(<4 x 
i16> [[TMP0]], i32 8)
+// RV32-NEXT:    [[TMP2:%.*]] = bitcast <4 x i16> [[TMP1]] to i64
+// RV32-NEXT:    ret i64 [[TMP2]]
+//
+// RV64-LABEL: define dso_local i64 @test_pusati_u16x4(
+// RV64-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <4 x i16>
+// RV64-NEXT:    [[TMP1:%.*]] = call <4 x i16> @llvm.riscv.pusati.v4i16(<4 x 
i16> [[TMP0]], i32 8)
+// RV64-NEXT:    [[TMP2:%.*]] = bitcast <4 x i16> [[TMP1]] to i64
+// RV64-NEXT:    ret i64 [[TMP2]]
+//
+uint16x4_t test_pusati_u16x4(int16x4_t a) { return __riscv_pusati_u16x4(a, 8); 
}
+
+// RV32-LABEL: define dso_local i64 @test_pusati_u32x2(
+// RV32-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <2 x i32>
+// RV32-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.pusati.v2i32(<2 x 
i32> [[TMP0]], i32 16)
+// RV32-NEXT:    [[TMP2:%.*]] = bitcast <2 x i32> [[TMP1]] to i64
+// RV32-NEXT:    ret i64 [[TMP2]]
+//
+// RV64-LABEL: define dso_local i64 @test_pusati_u32x2(
+// RV64-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <2 x i32>
+// RV64-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.pusati.v2i32(<2 x 
i32> [[TMP0]], i32 16)
+// RV64-NEXT:    [[TMP2:%.*]] = bitcast <2 x i32> [[TMP1]] to i64
+// RV64-NEXT:    ret i64 [[TMP2]]
+//
+uint32x2_t test_pusati_u32x2(int32x2_t a) { return __riscv_pusati_u32x2(a, 
16); }
+
+// RV32-LABEL: define dso_local i64 @test_psati_i16x4(
+// RV32-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <4 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = call <4 x i16> @llvm.riscv.psati.v4i16(<4 x 
i16> [[TMP0]], i32 8)
+// RV32-NEXT:    [[TMP2:%.*]] = bitcast <4 x i16> [[TMP1]] to i64
+// RV32-NEXT:    ret i64 [[TMP2]]
+//
+// RV64-LABEL: define dso_local i64 @test_psati_i16x4(
+// RV64-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <4 x i16>
+// RV64-NEXT:    [[TMP1:%.*]] = call <4 x i16> @llvm.riscv.psati.v4i16(<4 x 
i16> [[TMP0]], i32 8)
+// RV64-NEXT:    [[TMP2:%.*]] = bitcast <4 x i16> [[TMP1]] to i64
+// RV64-NEXT:    ret i64 [[TMP2]]
+//
+int16x4_t test_psati_i16x4(int16x4_t a) { return __riscv_psati_i16x4(a, 8); }
+
+// RV32-LABEL: define dso_local i64 @test_psati_i32x2(
+// RV32-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <2 x i32>
+// RV32-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.psati.v2i32(<2 x 
i32> [[TMP0]], i32 16)
+// RV32-NEXT:    [[TMP2:%.*]] = bitcast <2 x i32> [[TMP1]] to i64
+// RV32-NEXT:    ret i64 [[TMP2]]
+//
+// RV64-LABEL: define dso_local i64 @test_psati_i32x2(
+// RV64-SAME: i64 noundef [[A_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[A_COERCE]] to <2 x i32>
+// RV64-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.psati.v2i32(<2 x 
i32> [[TMP0]], i32 16)
+// RV64-NEXT:    [[TMP2:%.*]] = bitcast <2 x i32> [[TMP1]] to i64
+// RV64-NEXT:    ret i64 [[TMP2]]
+//
+int32x2_t test_psati_i32x2(int32x2_t a) { return __riscv_psati_i32x2(a, 16); }
diff --git a/clang/test/Sema/riscv-psati-width-out-of-range.c 
b/clang/test/Sema/riscv-psati-width-out-of-range.c
new file mode 100644
index 0000000000000..694a294e77fa1
--- /dev/null
+++ b/clang/test/Sema/riscv-psati-width-out-of-range.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -triple riscv32 -target-feature +experimental-p \
+// RUN:   -fsyntax-only -verify -verify-ignore-unexpected=note %s
+// RUN: %clang_cc1 -triple riscv64 -target-feature +experimental-p \
+// RUN:   -fsyntax-only -verify -verify-ignore-unexpected=note %s
+
+#include <riscv_packed_simd.h>
+
+int16x2_t test_psati_i16x2_nonconstant(int16x2_t v, unsigned width) {
+  // expected-error@+1 {{argument to '__builtin_riscv_psati_i16x2' must be a 
constant integer}}
+  return __riscv_psati_i16x2(v, width);
+}
+
+int16x2_t test_psati_i16x2_out_of_range(int16x2_t v) {
+  // expected-error@+1 {{argument value 17 is outside the valid range [1, 16]}}
+  return __riscv_psati_i16x2(v, 17);
+}
+
+int16x4_t test_psati_i16x4_out_of_range(int16x4_t v) {
+  // expected-error@+1 {{argument value 0 is outside the valid range [1, 16]}}
+  return __riscv_psati_i16x4(v, 0);
+}
+
+int32x2_t test_psati_i32x2_out_of_range(int32x2_t v) {
+  // expected-error@+1 {{argument value 33 is outside the valid range [1, 32]}}
+  return __riscv_psati_i32x2(v, 33);
+}
+
+uint16x2_t test_pusati_u16x2_out_of_range(int16x2_t v) {
+  // expected-error@+1 {{argument value 16 is outside the valid range [0, 15]}}
+  return __riscv_pusati_u16x2(v, 16);
+}
+
+uint16x4_t test_pusati_u16x4_out_of_range(int16x4_t v) {
+  // expected-error@+1 {{argument value 16 is outside the valid range [0, 15]}}
+  return __riscv_pusati_u16x4(v, 16);
+}
+
+uint32x2_t test_pusati_u32x2_out_of_range(int32x2_t v) {
+  // expected-error@+1 {{argument value 32 is outside the valid range [0, 31]}}
+  return __riscv_pusati_u32x2(v, 32);
+}
diff --git a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c 
b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
index f3857cedc3b5a..4c5d01c1cda59 100644
--- a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
+++ b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
@@ -1239,6 +1239,48 @@ int32x2_t test_psshar_s_i32x2_neg_imm(int32x2_t a) {
   return __riscv_psshar_s_i32x2(a, -5);
 }
 
+/* Packed Saturation (32-bit) */
+// CHECK-LABEL: test_pusati_u16x2:
+// CHECK:       pusati.h{{[[:space:]]}}
+uint16x2_t test_pusati_u16x2(int16x2_t a) {
+  return __riscv_pusati_u16x2(a, 8);
+}
+
+// CHECK-LABEL: test_psati_i16x2:
+// CHECK:       psati.h{{[[:space:]]}}
+int16x2_t test_psati_i16x2(int16x2_t a) {
+  return __riscv_psati_i16x2(a, 8);
+}
+
+/* Packed Saturation (64-bit) */
+// CHECK-LABEL: test_pusati_u16x4:
+// RV32:        pusati.dh{{[[:space:]]}}
+// RV64:        pusati.h{{[[:space:]]}}
+uint16x4_t test_pusati_u16x4(int16x4_t a) {
+  return __riscv_pusati_u16x4(a, 8);
+}
+
+// CHECK-LABEL: test_pusati_u32x2:
+// RV32:        pusati.dw{{[[:space:]]}}
+// RV64:        pusati.w{{[[:space:]]}}
+uint32x2_t test_pusati_u32x2(int32x2_t a) {
+  return __riscv_pusati_u32x2(a, 16);
+}
+
+// CHECK-LABEL: test_psati_i16x4:
+// RV32:        psati.dh{{[[:space:]]}}
+// RV64:        psati.h{{[[:space:]]}}
+int16x4_t test_psati_i16x4(int16x4_t a) {
+  return __riscv_psati_i16x4(a, 8);
+}
+
+// CHECK-LABEL: test_psati_i32x2:
+// RV32:        psati.dw{{[[:space:]]}}
+// RV64:        psati.w{{[[:space:]]}}
+int32x2_t test_psati_i32x2(int32x2_t a) {
+  return __riscv_psati_i32x2(a, 16);
+}
+
 // CHECK-LABEL: test_pand_i8x4:
 // CHECK:       and{{[[:space:]]}}
 int8x4_t test_pand_i8x4(int8x4_t a, int8x4_t b) {

>From 9faa749a148a8fbb6d916458ed7585db0d222d40 Mon Sep 17 00:00:00 2001
From: SiHuaN <[email protected]>
Date: Thu, 17 Sep 2026 21:17:18 +0000
Subject: [PATCH 4/6] code format

---
 .../riscv_packed_simd.c                       | 20 +++++--------------
 1 file changed, 5 insertions(+), 15 deletions(-)

diff --git a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c 
b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
index 4c5d01c1cda59..3d2350bedb720 100644
--- a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
+++ b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
@@ -1242,23 +1242,17 @@ int32x2_t test_psshar_s_i32x2_neg_imm(int32x2_t a) {
 /* Packed Saturation (32-bit) */
 // CHECK-LABEL: test_pusati_u16x2:
 // CHECK:       pusati.h{{[[:space:]]}}
-uint16x2_t test_pusati_u16x2(int16x2_t a) {
-  return __riscv_pusati_u16x2(a, 8);
-}
+uint16x2_t test_pusati_u16x2(int16x2_t a) { return __riscv_pusati_u16x2(a, 8); 
}
 
 // CHECK-LABEL: test_psati_i16x2:
 // CHECK:       psati.h{{[[:space:]]}}
-int16x2_t test_psati_i16x2(int16x2_t a) {
-  return __riscv_psati_i16x2(a, 8);
-}
+int16x2_t test_psati_i16x2(int16x2_t a) { return __riscv_psati_i16x2(a, 8); }
 
 /* Packed Saturation (64-bit) */
 // CHECK-LABEL: test_pusati_u16x4:
 // RV32:        pusati.dh{{[[:space:]]}}
 // RV64:        pusati.h{{[[:space:]]}}
-uint16x4_t test_pusati_u16x4(int16x4_t a) {
-  return __riscv_pusati_u16x4(a, 8);
-}
+uint16x4_t test_pusati_u16x4(int16x4_t a) { return __riscv_pusati_u16x4(a, 8); 
}
 
 // CHECK-LABEL: test_pusati_u32x2:
 // RV32:        pusati.dw{{[[:space:]]}}
@@ -1270,16 +1264,12 @@ uint32x2_t test_pusati_u32x2(int32x2_t a) {
 // CHECK-LABEL: test_psati_i16x4:
 // RV32:        psati.dh{{[[:space:]]}}
 // RV64:        psati.h{{[[:space:]]}}
-int16x4_t test_psati_i16x4(int16x4_t a) {
-  return __riscv_psati_i16x4(a, 8);
-}
+int16x4_t test_psati_i16x4(int16x4_t a) { return __riscv_psati_i16x4(a, 8); }
 
 // CHECK-LABEL: test_psati_i32x2:
 // RV32:        psati.dw{{[[:space:]]}}
 // RV64:        psati.w{{[[:space:]]}}
-int32x2_t test_psati_i32x2(int32x2_t a) {
-  return __riscv_psati_i32x2(a, 16);
-}
+int32x2_t test_psati_i32x2(int32x2_t a) { return __riscv_psati_i32x2(a, 16); }
 
 // CHECK-LABEL: test_pand_i8x4:
 // CHECK:       and{{[[:space:]]}}

>From 6bea92e319b6a88848208939d93e62bf95596aab Mon Sep 17 00:00:00 2001
From: SiHuaN <[email protected]>
Date: Wed, 23 Sep 2026 13:31:58 +0000
Subject: [PATCH 5/6] [RISCV][P-ext] Reuse RISCVISD::SATI/USATI for packed
 saturation

Generalize the sati/usati type profile to also accept a packed vector instead
of adding separate PSATI/PUSATI nodes, so the packed forms reuse the existing
computeKnownBits/ComputeNumSignBits handling.

Build the width TargetConstant directly instead of retyping the intrinsic
operand with getAnyExtOrTrunc, which folds it to a plain Constant, and widen a
v2i16 result on RV64 with undef through the shared widening path rather than
zeroing the high lanes.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 35 ++++-------
 llvm/lib/Target/RISCV/RISCVInstrInfoP.td    | 42 +++++++------
 llvm/test/CodeGen/RISCV/rvp-simd-32.ll      | 70 ++++++---------------
 3 files changed, 55 insertions(+), 92 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp 
b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 539fb6d31f37d..863d1282e65f1 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -13088,10 +13088,12 @@ SDValue 
RISCVTargetLowering::LowerINTRINSIC_WO_CHAIN(SDValue Op,
   }
   case Intrinsic::riscv_psati:
   case Intrinsic::riscv_pusati: {
-    unsigned Opc =
-        IntNo == Intrinsic::riscv_psati ? RISCVISD::PSATI : RISCVISD::PUSATI;
-    SDValue Width = DAG.getAnyExtOrTrunc(Op.getOperand(2), DL, XLenVT);
-    return DAG.getNode(Opc, DL, Op.getValueType(), Op.getOperand(1), Width);
+    bool IsSigned = IntNo == Intrinsic::riscv_psati;
+    unsigned Opc = IsSigned ? RISCVISD::SATI : RISCVISD::USATI;
+    // psati's width counts the sign bit, RISCVISD::SATI's immediate does not.
+    unsigned Width = Op.getConstantOperandVal(2) - (IsSigned ? 1 : 0);
+    return DAG.getNode(Opc, DL, Op.getValueType(), Op.getOperand(1),
+                       DAG.getTargetConstant(Width, DL, XLenVT));
   }
   case Intrinsic::riscv_psext_b:
   case Intrinsic::riscv_psext_h: {
@@ -17529,7 +17531,9 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
     case Intrinsic::riscv_pmulhru:
     case Intrinsic::riscv_pmulhsu:
     case Intrinsic::riscv_pmulhrsu:
-    case Intrinsic::riscv_psabs: {
+    case Intrinsic::riscv_psabs:
+    case Intrinsic::riscv_psati:
+    case Intrinsic::riscv_pusati: {
       EVT VT = N->getValueType(0);
       if (!Subtarget.is64Bit() || (VT != MVT::v4i8 && VT != MVT::v2i16))
         return;
@@ -17572,8 +17576,8 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
         Opc = getRVPMulHighOpcode(IntNo);
         break;
       default:
-        // pas/psa/psas/pssa/paas/pasa and pmerge: re-emit at the widened type
-        // rather than lowering to a generic node.
+        // pas/psa/psas/pssa/paas/pasa, pmerge and psati/pusati: re-emit at the
+        // widened type rather than lowering to a generic node.
         Opc = ISD::INTRINSIC_WO_CHAIN;
         break;
       }
@@ -17635,23 +17639,6 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
       Results.push_back(DAG.getExtractSubvector(DL, VT, Res, 0));
       return;
     }
-    case Intrinsic::riscv_psati:
-    case Intrinsic::riscv_pusati: {
-      MVT VT = N->getSimpleValueType(0);
-      if (!Subtarget.is64Bit() || VT != MVT::v2i16)
-        return;
-
-      MVT WideVT = MVT::v4i16;
-      SDValue Rs1 =
-          widenPackedVectorWithZeros(DAG, DL, N->getOperand(1), WideVT);
-      SDValue Width =
-          DAG.getAnyExtOrTrunc(N->getOperand(2), DL, Subtarget.getXLenVT());
-      unsigned Opc =
-          IntNo == Intrinsic::riscv_psati ? RISCVISD::PSATI : RISCVISD::PUSATI;
-      SDValue Res = DAG.getNode(Opc, DL, WideVT, Rs1, Width);
-      Results.push_back(DAG.getExtractSubvector(DL, VT, Res, 0));
-      return;
-    }
     case Intrinsic::riscv_predsum:
     case Intrinsic::riscv_predsumu: {
       bool IsSigned = IntNo == Intrinsic::riscv_predsum;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td 
b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
index 8adce67d0c221..cb402a06452c9 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoP.td
@@ -2044,13 +2044,6 @@ def riscv_psshar : RVSDNode<"PSSHAR", 
SDT_RISCVPackedShift>;
 def riscv_psshl : RVSDNode<"PSSHL", SDT_RISCVPackedShift>;
 def riscv_psshlr : RVSDNode<"PSSHLR", SDT_RISCVPackedShift>;
 
-// Packed saturation to an immediate width.
-def SDT_RISCVPackedSaturation
-    : SDTypeProfile<1, 2, [SDTCisVec<0>, SDTCisSameAs<0, 1>,
-                           SDTCisVT<2, XLenVT>]>;
-def riscv_psati  : RVSDNode<"PSATI", SDT_RISCVPackedSaturation>;
-def riscv_pusati : RVSDNode<"PUSATI", SDT_RISCVPackedSaturation>;
-
 // RV32 packed narrowing shift.
 def SDT_RISCVPackedNarrowingShift
     : SDTypeProfile<1, 2, [SDTCisVec<0>, SDTCisVec<1>,
@@ -2107,9 +2100,12 @@ def riscv_pmqracc_h11 : RVSDNode<"PMQRACC_W_H11", 
SDT_RISCVQFormatAccP>;
 def riscv_mqwacc  : RVSDNode<"MQWACC",  SDT_RISCVWideningAddSubAccumulate>;
 def riscv_mqrwacc : RVSDNode<"MQRWACC", SDT_RISCVWideningAddSubAccumulate>;
 
-// The immediate for these is the number of trailing ones in the max value.
-def riscv_sati : RVSDNode<"SATI", SDTIntBinOp>;
-def riscv_usati : RVSDNode<"USATI", SDTIntBinOp>;
+// The immediate for these is the number of trailing ones in the max value. The
+// saturated operand is a scalar (sati/usati) or a packed vector 
(psati/pusati).
+def SDT_RISCVSaturate : SDTypeProfile<1, 2, [SDTCisInt<0>, SDTCisSameAs<0, 1>,
+                                             SDTCisVT<2, XLenVT>]>;
+def riscv_sati : RVSDNode<"SATI", SDT_RISCVSaturate>;
+def riscv_usati : RVSDNode<"USATI", SDT_RISCVSaturate>;
 
 // Bitwise merge: res = (~op0 & op1) | (op0 & op2)
 def SDT_RISCVMERGE : SDTypeProfile<1, 3, [SDTCisInt<0>,
@@ -2147,6 +2143,16 @@ def IncImm : SDNodeXForm<imm, [{
                                      N->getValueType(0));
 }]>;
 
+// Matches the immediate of RISCVISD::SATI/USATI. The signed instructions count
+// the sign bit in their width field, which IncImm adds back.
+class RISCVSatWidthLeaf<int bitsNum, SDNodeXForm xform = NOOP_SDNodeXForm>
+    : TImmLeaf<XLenVT, "return isUInt<" # bitsNum # ">(Imm);", xform>;
+
+def sati_width4  : RISCVSatWidthLeaf<4, IncImm>;
+def sati_width5  : RISCVSatWidthLeaf<5, IncImm>;
+def usati_width4 : RISCVSatWidthLeaf<4>;
+def usati_width5 : RISCVSatWidthLeaf<5>;
+
 def SDT_RISCVBuildPairGPRVec : SDTypeProfile<1, 2, [SDTCisVec<0>,
                                                     SDTCisVT<1, i32>,
                                                     SDTCisSameAs<1, 2>]>;
@@ -2346,8 +2352,8 @@ let Predicates = [HasStdExtP] in {
   def : PatGprShift<riscv_psshlr, PSSHLR_HS, XLenVecI16VT>;
 
   // 16-bit packed saturation patterns
-  def : PatGprImm<riscv_psati, PSATI_H, uimm4_plus1, XLenVecI16VT>;
-  def : PatGprImm<riscv_pusati, PUSATI_H, uimm4, XLenVecI16VT>;
+  def : PatGprImm<riscv_sati, PSATI_H, sati_width4, XLenVecI16VT>;
+  def : PatGprImm<riscv_usati, PUSATI_H, usati_width4, XLenVecI16VT>;
 
   // 8-bit logical shift left/right
   def : PatGprShiftMask<riscv_pshl, PSLL_BS, shiftMask32, XLenVecI8VT>;
@@ -2827,8 +2833,8 @@ let append Predicates = [IsRV32] in {
   def : PatGprPairShift<riscv_psshlr, PSSHLR_DHS, v4i16>;
 
   // 16-bit packed saturation patterns
-  def : PatGprPairImm<riscv_psati, PSATI_DH, uimm4_plus1, v4i16>;
-  def : PatGprPairImm<riscv_pusati, PUSATI_DH, uimm4, v4i16>;
+  def : PatGprPairImm<riscv_sati, PSATI_DH, sati_width4, v4i16>;
+  def : PatGprPairImm<riscv_usati, PUSATI_DH, usati_width4, v4i16>;
 
   // 32-bit saturating shift patterns
   def : PatGprPairImm<riscv_pssha, PSSLAI_DW, uimm5, v2i32>;
@@ -2841,8 +2847,8 @@ let append Predicates = [IsRV32] in {
   def : PatGprPairShift<riscv_psshlr, PSSHLR_DWS, v2i32>;
 
   // 32-bit packed saturation patterns
-  def : PatGprPairImm<riscv_psati, PSATI_DW, uimm5_plus1, v2i32>;
-  def : PatGprPairImm<riscv_pusati, PUSATI_DW, uimm5, v2i32>;
+  def : PatGprPairImm<riscv_sati, PSATI_DW, sati_width5, v2i32>;
+  def : PatGprPairImm<riscv_usati, PUSATI_DW, usati_width5, v2i32>;
 
   // 8-bit logical shift left/right
   def : PatGprPairShiftMask<riscv_pshl, PSLL_DBS, shiftMask32, v8i8>;
@@ -3294,8 +3300,8 @@ let append Predicates = [IsRV64] in {
   def : PatGprShift<riscv_psshlr, PSSHLR_WS, v2i32>;
 
   // 32-bit packed saturation patterns
-  def : PatGprImm<riscv_psati, PSATI_W, uimm5_plus1, v2i32>;
-  def : PatGprImm<riscv_pusati, PUSATI_W, uimm5, v2i32>;
+  def : PatGprImm<riscv_sati, PSATI_W, sati_width5, v2i32>;
+  def : PatGprImm<riscv_usati, PUSATI_W, usati_width5, v2i32>;
 
   // 32-bit logical shift left/right
   def : PatGprShiftMask<riscv_pshl, PSLL_WS, shiftMask32, v2i32>;
diff --git a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll 
b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
index 5adc48d167f6e..704726080593f 100644
--- a/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
+++ b/llvm/test/CodeGen/RISCV/rvp-simd-32.ll
@@ -3829,76 +3829,46 @@ define i32 @test_maccsu_h11_i32(i32 %rd, <2 x i16> %a, 
<2 x i16> %b) {
 }
 
 define <2 x i16> @test_psati_i16x2(<2 x i16> %a) {
-; RV32-LABEL: test_psati_i16x2:
-; RV32:       # %bb.0:
-; RV32-NEXT:    psati.h a0, a0, 8
-; RV32-NEXT:    ret
-;
-; RV64-LABEL: test_psati_i16x2:
-; RV64:       # %bb.0:
-; RV64-NEXT:    zext.w a0, a0
-; RV64-NEXT:    psati.h a0, a0, 8
-; RV64-NEXT:    ret
+; CHECK-LABEL: test_psati_i16x2:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    psati.h a0, a0, 8
+; CHECK-NEXT:    ret
   %res = call <2 x i16> @llvm.riscv.psati.v2i16.i32(<2 x i16> %a, i32 8)
   ret <2 x i16> %res
 }
 
 define <2 x i16> @test_psati_i16x2_min_width(<2 x i16> %a) {
-; RV32-LABEL: test_psati_i16x2_min_width:
-; RV32:       # %bb.0:
-; RV32-NEXT:    psati.h a0, a0, 1
-; RV32-NEXT:    ret
-;
-; RV64-LABEL: test_psati_i16x2_min_width:
-; RV64:       # %bb.0:
-; RV64-NEXT:    zext.w a0, a0
-; RV64-NEXT:    psati.h a0, a0, 1
-; RV64-NEXT:    ret
+; CHECK-LABEL: test_psati_i16x2_min_width:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    psati.h a0, a0, 1
+; CHECK-NEXT:    ret
   %res = call <2 x i16> @llvm.riscv.psati.v2i16.i32(<2 x i16> %a, i32 1)
   ret <2 x i16> %res
 }
 
 define <2 x i16> @test_psati_i16x2_max_width(<2 x i16> %a) {
-; RV32-LABEL: test_psati_i16x2_max_width:
-; RV32:       # %bb.0:
-; RV32-NEXT:    psati.h a0, a0, 16
-; RV32-NEXT:    ret
-;
-; RV64-LABEL: test_psati_i16x2_max_width:
-; RV64:       # %bb.0:
-; RV64-NEXT:    zext.w a0, a0
-; RV64-NEXT:    psati.h a0, a0, 16
-; RV64-NEXT:    ret
+; CHECK-LABEL: test_psati_i16x2_max_width:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    psati.h a0, a0, 16
+; CHECK-NEXT:    ret
   %res = call <2 x i16> @llvm.riscv.psati.v2i16.i32(<2 x i16> %a, i32 16)
   ret <2 x i16> %res
 }
 
 define <2 x i16> @test_pusati_u16x2(<2 x i16> %a) {
-; RV32-LABEL: test_pusati_u16x2:
-; RV32:       # %bb.0:
-; RV32-NEXT:    pusati.h a0, a0, 0
-; RV32-NEXT:    ret
-;
-; RV64-LABEL: test_pusati_u16x2:
-; RV64:       # %bb.0:
-; RV64-NEXT:    zext.w a0, a0
-; RV64-NEXT:    pusati.h a0, a0, 0
-; RV64-NEXT:    ret
+; CHECK-LABEL: test_pusati_u16x2:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pusati.h a0, a0, 0
+; CHECK-NEXT:    ret
   %res = call <2 x i16> @llvm.riscv.pusati.v2i16.i32(<2 x i16> %a, i32 0)
   ret <2 x i16> %res
 }
 
 define <2 x i16> @test_pusati_u16x2_max_width(<2 x i16> %a) {
-; RV32-LABEL: test_pusati_u16x2_max_width:
-; RV32:       # %bb.0:
-; RV32-NEXT:    pusati.h a0, a0, 15
-; RV32-NEXT:    ret
-;
-; RV64-LABEL: test_pusati_u16x2_max_width:
-; RV64:       # %bb.0:
-; RV64-NEXT:    zext.w a0, a0
-; RV64-NEXT:    pusati.h a0, a0, 15
-; RV64-NEXT:    ret
+; CHECK-LABEL: test_pusati_u16x2_max_width:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pusati.h a0, a0, 15
+; CHECK-NEXT:    ret
   %res = call <2 x i16> @llvm.riscv.pusati.v2i16.i32(<2 x i16> %a, i32 15)
   ret <2 x i16> %res
 }

>From 3b408fbf5468ce8aa11cf03e9b38214edcdd934c Mon Sep 17 00:00:00 2001
From: SiHuaN <[email protected]>
Date: Wed, 23 Sep 2026 13:31:58 +0000
Subject: [PATCH 6/6] [Clang][RISCV] Fold psati/pusati into the shared packed
 builtin lowering

They only need the intrinsic ID and IntrinsicTypes = {ResultType}, so handle
them in the case group that already does that instead of a switch of their own.

Also cover the accepted widths in the Sema test and drop its
-verify-ignore-unexpected=note: these diagnostics come without notes, unlike
the enable_if based pget/pset checks the RUN lines were copied from.
---
 clang/lib/CodeGen/TargetBuiltins/RISCV.cpp    | 25 ++++++-------------
 clang/lib/Sema/SemaRISCV.cpp                  |  2 +-
 .../Sema/riscv-psati-width-out-of-range.c     | 16 ++++++++++--
 3 files changed, 23 insertions(+), 20 deletions(-)

diff --git a/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp 
b/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
index 4f7b9ceae6eaa..c798f5fb68bd5 100644
--- a/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
@@ -1328,7 +1328,14 @@ Value *CodeGenFunction::EmitRISCVBuiltinExpr(unsigned 
BuiltinID,
   case RISCV::BI__builtin_riscv_psshl_s_u16x4:
   case RISCV::BI__builtin_riscv_psshl_s_u32x2:
   case RISCV::BI__builtin_riscv_psshlr_s_u16x4:
-  case RISCV::BI__builtin_riscv_psshlr_s_u32x2: {
+  case RISCV::BI__builtin_riscv_psshlr_s_u32x2:
+  // Packed Saturation
+  case RISCV::BI__builtin_riscv_pusati_u16x2:
+  case RISCV::BI__builtin_riscv_psati_i16x2:
+  case RISCV::BI__builtin_riscv_pusati_u16x4:
+  case RISCV::BI__builtin_riscv_pusati_u32x2:
+  case RISCV::BI__builtin_riscv_psati_i16x4:
+  case RISCV::BI__builtin_riscv_psati_i32x2: {
     switch (BuiltinID) {
     default:
       llvm_unreachable("unexpected builtin ID");
@@ -1525,22 +1532,6 @@ Value *CodeGenFunction::EmitRISCVBuiltinExpr(unsigned 
BuiltinID,
     case RISCV::BI__builtin_riscv_psshlr_s_u32x2:
       ID = Intrinsic::riscv_psshlr;
       break;
-    }
-
-    IntrinsicTypes = {ResultType};
-    break;
-  }
-
-  // Packed Saturation
-  case RISCV::BI__builtin_riscv_psati_i16x2:
-  case RISCV::BI__builtin_riscv_psati_i16x4:
-  case RISCV::BI__builtin_riscv_psati_i32x2:
-  case RISCV::BI__builtin_riscv_pusati_u16x2:
-  case RISCV::BI__builtin_riscv_pusati_u16x4:
-  case RISCV::BI__builtin_riscv_pusati_u32x2: {
-    switch (BuiltinID) {
-    default:
-      llvm_unreachable("unexpected builtin ID");
     case RISCV::BI__builtin_riscv_psati_i16x2:
     case RISCV::BI__builtin_riscv_psati_i16x4:
     case RISCV::BI__builtin_riscv_psati_i32x2:
diff --git a/clang/lib/Sema/SemaRISCV.cpp b/clang/lib/Sema/SemaRISCV.cpp
index 58a52091ee8b2..ee3858a6448d2 100644
--- a/clang/lib/Sema/SemaRISCV.cpp
+++ b/clang/lib/Sema/SemaRISCV.cpp
@@ -968,7 +968,7 @@ bool SemaRISCV::CheckBuiltinFunctionCall(const TargetInfo 
&TI,
   case RISCV::BI__builtin_riscv_sm4ks:
   case RISCV::BI__builtin_riscv_sm4ed:
     return SemaRef.BuiltinConstantArgRange(TheCall, 2, 0, 3);
-  // Check the saturation width for the packed saturating instructions.
+  // Check the psati/pusati saturation width.
   case RISCV::BI__builtin_riscv_psati_i16x2:
   case RISCV::BI__builtin_riscv_psati_i16x4:
     return SemaRef.BuiltinConstantArgRange(TheCall, 1, 1, 16);
diff --git a/clang/test/Sema/riscv-psati-width-out-of-range.c 
b/clang/test/Sema/riscv-psati-width-out-of-range.c
index 694a294e77fa1..93e72dcf7e9e7 100644
--- a/clang/test/Sema/riscv-psati-width-out-of-range.c
+++ b/clang/test/Sema/riscv-psati-width-out-of-range.c
@@ -1,7 +1,7 @@
 // RUN: %clang_cc1 -triple riscv32 -target-feature +experimental-p \
-// RUN:   -fsyntax-only -verify -verify-ignore-unexpected=note %s
+// RUN:   -fsyntax-only -verify %s
 // RUN: %clang_cc1 -triple riscv64 -target-feature +experimental-p \
-// RUN:   -fsyntax-only -verify -verify-ignore-unexpected=note %s
+// RUN:   -fsyntax-only -verify %s
 
 #include <riscv_packed_simd.h>
 
@@ -10,6 +10,18 @@ int16x2_t test_psati_i16x2_nonconstant(int16x2_t v, unsigned 
width) {
   return __riscv_psati_i16x2(v, width);
 }
 
+int16x2_t test_psati_i16x2_min_width(int16x2_t v) {
+  return __riscv_psati_i16x2(v, 1);
+}
+
+int16x2_t test_psati_i16x2_max_width(int16x2_t v) {
+  return __riscv_psati_i16x2(v, 16);
+}
+
+uint16x2_t test_pusati_u16x2_max_width(int16x2_t v) {
+  return __riscv_pusati_u16x2(v, 15);
+}
+
 int16x2_t test_psati_i16x2_out_of_range(int16x2_t v) {
   // expected-error@+1 {{argument value 17 is outside the valid range [1, 16]}}
   return __riscv_psati_i16x2(v, 17);

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to