https://github.com/Michael-Chen-NJU created 
https://github.com/llvm/llvm-project/pull/210389

Add RISC-V P-extension packed narrowing convert header APIs for `pncvt` and 
`pncvth`.

The new APIs lower through generic IR: RV32 uses truncate / logical shift plus 
truncate forms, while RV64 uses existing packed unzip shuffle forms. This 
reuses the existing backend lowering and TableGen aliases rather than adding 
intrinsic-specific lowering.

Tests cover Clang IR generation and cross-project header assembly checks for 
RV32/RV64 spec-listed mnemonics.

From c2419b5ce06f0af96637bee9c67b1015573fc04a Mon Sep 17 00:00:00 2001
From: Michael-Chen-NJU <[email protected]>
Date: Sat, 18 Jul 2026 01:43:28 +0800
Subject: [PATCH] [RISCV] Add packed narrowing convert intrinsics

---
 clang/lib/Headers/riscv_packed_simd.h         |  50 ++++++
 clang/test/CodeGen/RISCV/rvp-intrinsics.c     | 158 ++++++++++++++++++
 .../riscv_packed_simd.c                       |  42 +++++
 3 files changed, 250 insertions(+)

diff --git a/clang/lib/Headers/riscv_packed_simd.h 
b/clang/lib/Headers/riscv_packed_simd.h
index c0596aefe529a..b464bb66d66f5 100644
--- a/clang/lib/Headers/riscv_packed_simd.h
+++ b/clang/lib/Headers/riscv_packed_simd.h
@@ -134,6 +134,42 @@ typedef uint32_t uint32x2_t 
__attribute__((__vector_size__(8)));
                                         7);                                    
\
   }
 
+#if __riscv_xlen == 64
+#define __packed_narrow_even2(name, rty, ty, sty)                              
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_shufflevector((sty)__rs1, (sty)__rs1, 0, 2);              
\
+  }
+#define __packed_narrow_even4(name, rty, ty, sty)                              
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_shufflevector((sty)__rs1, (sty)__rs1, 0, 2, 4, 6);        
\
+  }
+#define __packed_narrow_odd2(name, rty, ty, sty, uty)                          
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_shufflevector((sty)__rs1, (sty)__rs1, 1, 3);              
\
+  }
+#define __packed_narrow_odd4(name, rty, ty, sty, uty)                          
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_shufflevector((sty)__rs1, (sty)__rs1, 1, 3, 5, 7);        
\
+  }
+#else
+#define __packed_narrow_even2(name, rty, ty, sty)                              
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_convertvector(__rs1, rty);                                
\
+  }
+#define __packed_narrow_even4(name, rty, ty, sty)                              
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_convertvector(__rs1, rty);                                
\
+  }
+#define __packed_narrow_odd2(name, rty, ty, sty, uty)                          
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_convertvector(((uty)__rs1) >> 16, rty);                   
\
+  }
+#define __packed_narrow_odd4(name, rty, ty, sty, uty)                          
\
+  static __inline__ rty __DEFAULT_FN_ATTRS __riscv_##name(ty __rs1) {          
\
+    return __builtin_convertvector(((uty)__rs1) >> 8, rty);                    
\
+  }
+#endif
+
 /* Packed Reverse: reverse the order of the elements. Lowered to a single
  * rev8/rev16/ppairoe.* by the backend's packed reverse-shuffle handling. */
 #define __packed_reverse2(name, ty)                                            
\
@@ -485,6 +521,16 @@ __packed_widen_high4(pwcvth_u16x4, uint16x4_t, uint8x4_t)
 __packed_widen_high2(pwcvth_i32x2, int32x2_t, int16x2_t)
 __packed_widen_high2(pwcvth_u32x2, uint32x2_t, uint16x2_t)
 
+/* Packed Narrowing Convert */
+__packed_narrow_even4(pncvt_i8x4, int8x4_t, int16x4_t, int8x8_t)
+__packed_narrow_even4(pncvt_u8x4, uint8x4_t, uint16x4_t, uint8x8_t)
+__packed_narrow_even2(pncvt_i16x2, int16x2_t, int32x2_t, int16x4_t)
+__packed_narrow_even2(pncvt_u16x2, uint16x2_t, uint32x2_t, uint16x4_t)
+__packed_narrow_odd4(pncvth_i8x4, int8x4_t, int16x4_t, int8x8_t, uint16x4_t)
+__packed_narrow_odd4(pncvth_u8x4, uint8x4_t, uint16x4_t, uint8x8_t, uint16x4_t)
+__packed_narrow_odd2(pncvth_i16x2, int16x2_t, int32x2_t, int16x4_t, uint32x2_t)
+__packed_narrow_odd2(pncvth_u16x2, uint16x2_t, uint32x2_t, uint16x4_t, 
uint32x2_t)
+
 /* Packed Reverse (32-bit) */
 __packed_reverse4(prev_i8x4, int8x4_t)
 __packed_reverse4(prev_u8x4, uint8x4_t)
@@ -720,6 +766,10 @@ __packed_reinterpret(u32x2_i32x2, int32x2_t, uint32x2_t)
 #undef __packed_widen_convert
 #undef __packed_widen_high2
 #undef __packed_widen_high4
+#undef __packed_narrow_even2
+#undef __packed_narrow_even4
+#undef __packed_narrow_odd2
+#undef __packed_narrow_odd4
 #undef __packed_reverse2
 #undef __packed_reverse4
 #undef __packed_reverse8
diff --git a/clang/test/CodeGen/RISCV/rvp-intrinsics.c 
b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
index a3eb670fe23c9..85135ba9abd99 100644
--- a/clang/test/CodeGen/RISCV/rvp-intrinsics.c
+++ b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
@@ -7194,6 +7194,164 @@ uint32x2_t test_pwcvth_u32x2(uint16x2_t rs1) {
   return __riscv_pwcvth_u32x2(rs1);
 }
 
+/* Packed Narrowing Convert */
+
+// RV32-LABEL: define dso_local i32 @test_pncvt_i8x4(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc <4 x i16> [[TMP0]] to <4 x i8>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvt_i8x4(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <8 x i8>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <8 x i8> [[TMP0]], <8 x i8> 
poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+int8x4_t test_pncvt_i8x4(int16x4_t rs1) { return __riscv_pncvt_i8x4(rs1); }
+
+// RV32-LABEL: define dso_local i32 @test_pncvt_u8x4(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc <4 x i16> [[TMP0]] to <4 x i8>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvt_u8x4(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <8 x i8>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <8 x i8> [[TMP0]], <8 x i8> 
poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+uint8x4_t test_pncvt_u8x4(uint16x4_t rs1) { return __riscv_pncvt_u8x4(rs1); }
+
+// RV32-LABEL: define dso_local i32 @test_pncvt_i16x2(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <2 x i32>
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc <2 x i32> [[TMP0]] to <2 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvt_i16x2(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <4 x i16> [[TMP0]], <4 x 
i16> poison, <2 x i32> <i32 0, i32 2>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+int16x2_t test_pncvt_i16x2(int32x2_t rs1) { return __riscv_pncvt_i16x2(rs1); }
+
+// RV32-LABEL: define dso_local i32 @test_pncvt_u16x2(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <2 x i32>
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc <2 x i32> [[TMP0]] to <2 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvt_u16x2(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <4 x i16> [[TMP0]], <4 x 
i16> poison, <2 x i32> <i32 0, i32 2>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+uint16x2_t test_pncvt_u16x2(uint32x2_t rs1) {
+  return __riscv_pncvt_u16x2(rs1);
+}
+
+// RV32-LABEL: define dso_local i32 @test_pncvth_i8x4(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV32-NEXT:    [[SHR_I:%.*]] = lshr <4 x i16> [[TMP0]], splat (i16 8)
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc nuw <4 x i16> [[SHR_I]] to <4 x i8>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvth_i8x4(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <8 x i8>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <8 x i8> [[TMP0]], <8 x i8> 
poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+int8x4_t test_pncvth_i8x4(int16x4_t rs1) { return __riscv_pncvth_i8x4(rs1); }
+
+// RV32-LABEL: define dso_local i32 @test_pncvth_u8x4(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV32-NEXT:    [[SHR_I:%.*]] = lshr <4 x i16> [[TMP0]], splat (i16 8)
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc nuw <4 x i16> [[SHR_I]] to <4 x i8>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvth_u8x4(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <8 x i8>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <8 x i8> [[TMP0]], <8 x i8> 
poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <4 x i8> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+uint8x4_t test_pncvth_u8x4(uint16x4_t rs1) {
+  return __riscv_pncvth_u8x4(rs1);
+}
+
+// RV32-LABEL: define dso_local i32 @test_pncvth_i16x2(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <2 x i32>
+// RV32-NEXT:    [[SHR_I:%.*]] = lshr <2 x i32> [[TMP0]], splat (i32 16)
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc nuw <2 x i32> [[SHR_I]] to <2 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvth_i16x2(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <4 x i16> [[TMP0]], <4 x 
i16> poison, <2 x i32> <i32 1, i32 3>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+int16x2_t test_pncvth_i16x2(int32x2_t rs1) {
+  return __riscv_pncvth_i16x2(rs1);
+}
+
+// RV32-LABEL: define dso_local i32 @test_pncvth_u16x2(
+// RV32-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <2 x i32>
+// RV32-NEXT:    [[SHR_I:%.*]] = lshr <2 x i32> [[TMP0]], splat (i32 16)
+// RV32-NEXT:    [[CONV_I:%.*]] = trunc nuw <2 x i32> [[SHR_I]] to <2 x i16>
+// RV32-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[CONV_I]] to i32
+// RV32-NEXT:    ret i32 [[TMP1]]
+//
+// RV64-LABEL: define dso_local i32 @test_pncvth_u16x2(
+// RV64-SAME: i64 noundef [[RS1_COERCE:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = bitcast i64 [[RS1_COERCE]] to <4 x i16>
+// RV64-NEXT:    [[SHUFFLE_I:%.*]] = shufflevector <4 x i16> [[TMP0]], <4 x 
i16> poison, <2 x i32> <i32 1, i32 3>
+// RV64-NEXT:    [[TMP1:%.*]] = bitcast <2 x i16> [[SHUFFLE_I]] to i32
+// RV64-NEXT:    ret i32 [[TMP1]]
+//
+uint16x2_t test_pncvth_u16x2(uint32x2_t rs1) {
+  return __riscv_pncvth_u16x2(rs1);
+}
+
 /* Reinterpret Casts, Packed <-> Scalar (32-bit) */
 // RV32-LABEL: define dso_local i32 @test_preinterpret_u8x4_u32(
 // RV32-SAME: i32 noundef [[X_COERCE:%.*]]) #[[ATTR0]] {
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 a73c0376db9d1..c647d3bd9732a 100644
--- a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
+++ b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
@@ -2133,6 +2133,48 @@ uint32x2_t test_pwcvth_u32x2(uint16x2_t rs1) {
   return __riscv_pwcvth_u32x2(rs1);
 }
 
+// CHECK-LABEL: test_pncvt_i8x4:
+// RV32:        pncvt.b
+// RV64:        pncvt.wb
+int8x4_t test_pncvt_i8x4(int16x4_t rs1) { return __riscv_pncvt_i8x4(rs1); }
+
+// CHECK-LABEL: test_pncvt_u8x4:
+// RV32:        pncvt.b
+// RV64:        pncvt.wb
+uint8x4_t test_pncvt_u8x4(uint16x4_t rs1) { return __riscv_pncvt_u8x4(rs1); }
+
+// CHECK-LABEL: test_pncvt_i16x2:
+// RV32:        pncvt.h
+// RV64:        pncvt.wh
+int16x2_t test_pncvt_i16x2(int32x2_t rs1) { return __riscv_pncvt_i16x2(rs1); }
+
+// CHECK-LABEL: test_pncvt_u16x2:
+// RV32:        pncvt.h
+// RV64:        pncvt.wh
+uint16x2_t test_pncvt_u16x2(uint32x2_t rs1) { return __riscv_pncvt_u16x2(rs1); 
}
+
+// CHECK-LABEL: test_pncvth_i8x4:
+// RV32:        pncvth.b
+// RV64:        pncvth.wb
+int8x4_t test_pncvth_i8x4(int16x4_t rs1) { return __riscv_pncvth_i8x4(rs1); }
+
+// CHECK-LABEL: test_pncvth_u8x4:
+// RV32:        pncvth.b
+// RV64:        pncvth.wb
+uint8x4_t test_pncvth_u8x4(uint16x4_t rs1) { return __riscv_pncvth_u8x4(rs1); }
+
+// CHECK-LABEL: test_pncvth_i16x2:
+// RV32:        pncvth.h
+// RV64:        pncvth.wh
+int16x2_t test_pncvth_i16x2(int32x2_t rs1) { return __riscv_pncvth_i16x2(rs1); 
}
+
+// CHECK-LABEL: test_pncvth_u16x2:
+// RV32:        pncvth.h
+// RV64:        pncvth.wh
+uint16x2_t test_pncvth_u16x2(uint32x2_t rs1) {
+  return __riscv_pncvth_u16x2(rs1);
+}
+
 // CHECK-LABEL: test_pmerge_merge_u8x4:
 // CHECK:        merge
 uint8x4_t test_pmerge_merge_u8x4(uint8x4_t rd, uint8x4_t rs1, uint8x4_t rs2) {

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

Reply via email to