https://github.com/sihuan created 
https://github.com/llvm/llvm-project/pull/224377

First batch of the P extension `Scalar Intrinsics` group, covering the 
operations that map onto a single existing LLVM IR intrinsic (llvm.sadd.sat, 
llvm.uadd.sat, llvm.ssub.sat, llvm.usub.sat, llvm.abs, llvm.bitreverse) that 
the backend already selects, so no codegen changes are needed.

>From 17cf4a584403941d5c7c95a974429d4313d41e33 Mon Sep 17 00:00:00 2001
From: SiHuaN <[email protected]>
Date: Mon, 14 Sep 2026 07:47:13 +0000
Subject: [PATCH] [Clang][RISCV] Add scalar saturating add/sub, absolute value
 and rev intrinsics

First batch of the P extension `Scalar Intrinsics` group, covering the
operations that map onto a single existing LLVM IR intrinsic (llvm.sadd.sat,
llvm.uadd.sat, llvm.ssub.sat, llvm.usub.sat, llvm.abs, llvm.bitreverse) that
the backend already selects, so no codegen changes are needed.

The 64-bit forms are RV64 only, guarded with __riscv_xlen as in
riscv_bitmanip.h. On RV64 the 32-bit saturating operations select the packed
psadd.w/psaddu.w/pssub.w/pssubu.w forms and rev_32 expands to rev+srai, both
as required by the intrinsic spec.
---
 clang/lib/Headers/riscv_packed_simd.h         |  18 +++
 clang/test/CodeGen/RISCV/rvp-intrinsics.c     | 126 ++++++++++++++++++
 .../riscv_packed_simd.c                       |  48 +++++++
 3 files changed, 192 insertions(+)

diff --git a/clang/lib/Headers/riscv_packed_simd.h 
b/clang/lib/Headers/riscv_packed_simd.h
index 8c20d00e68652..cfa673e365d9f 100644
--- a/clang/lib/Headers/riscv_packed_simd.h
+++ b/clang/lib/Headers/riscv_packed_simd.h
@@ -365,6 +365,24 @@ typedef uint32_t uint32x2_t 
__attribute__((__vector_size__(8)));
 // clang-format off: macro call sites have no trailing semicolons, which
 // confuses clang-format into a deeply nested expression.
 
+/* Scalar Bitmanip */
+__packed_unary_builtin(rev_32, uint32_t, __builtin_bitreverse32)
+#if __riscv_xlen == 64
+__packed_unary_builtin(rev_64, uint64_t, __builtin_bitreverse64)
+#endif
+
+/* Scalar Saturating Addition and Subtraction */
+__packed_binary_builtin(sadd_i32, int32_t, __builtin_elementwise_add_sat)
+__packed_binary_builtin(saddu_u32, uint32_t, __builtin_elementwise_add_sat)
+__packed_binary_builtin(ssub_i32, int32_t, __builtin_elementwise_sub_sat)
+__packed_binary_builtin(ssubu_u32, uint32_t, __builtin_elementwise_sub_sat)
+
+/* Scalar Absolute Value */
+__packed_pabs(abs_u32, int32_t, uint32_t)
+#if __riscv_xlen == 64
+__packed_pabs(abs_u64, int64_t, uint64_t)
+#endif
+
 /* Packed Splat (32-bit) */
 __packed_splat(pmv_s_u8x4, uint8x4_t, uint8_t, __packed_splat4)
 __packed_splat(pmv_s_i8x4, int8x4_t, int8_t, __packed_splat4)
diff --git a/clang/test/CodeGen/RISCV/rvp-intrinsics.c 
b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
index b2b2b1b9a777b..412c63a182b11 100644
--- a/clang/test/CodeGen/RISCV/rvp-intrinsics.c
+++ b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
@@ -8,6 +8,132 @@
 
 #include <riscv_packed_simd.h>
 
+/* Scalar Bitmanip */
+
+// RV32-LABEL: define dso_local i32 @test_rev_32(
+// RV32-SAME: i32 noundef [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = call i32 @llvm.bitreverse.i32(i32 [[A]])
+// RV32-NEXT:    ret i32 [[TMP0]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_rev_32(
+// RV64-SAME: i32 noundef signext [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = call i32 @llvm.bitreverse.i32(i32 [[A]])
+// RV64-NEXT:    ret i32 [[TMP0]]
+//
+uint32_t test_rev_32(uint32_t a) {
+  return __riscv_rev_32(a);
+}
+
+#if __riscv_xlen == 64
+// RV64-LABEL: define dso_local i64 @test_rev_64(
+// RV64-SAME: i64 noundef [[A:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = call i64 @llvm.bitreverse.i64(i64 [[A]])
+// RV64-NEXT:    ret i64 [[TMP0]]
+//
+uint64_t test_rev_64(uint64_t a) {
+  return __riscv_rev_64(a);
+}
+#endif
+
+/* Scalar Saturating Addition and Subtraction */
+
+// RV32-LABEL: define dso_local i32 @test_sadd_i32(
+// RV32-SAME: i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.sadd.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV32-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_sadd_i32(
+// RV64-SAME: i32 noundef signext [[A:%.*]], i32 noundef signext [[B:%.*]]) 
#[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.sadd.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV64-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+int32_t test_sadd_i32(int32_t a, int32_t b) {
+  return __riscv_sadd_i32(a, b);
+}
+
+// RV32-LABEL: define dso_local i32 @test_saddu_u32(
+// RV32-SAME: i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.uadd.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV32-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_saddu_u32(
+// RV64-SAME: i32 noundef signext [[A:%.*]], i32 noundef signext [[B:%.*]]) 
#[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.uadd.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV64-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+uint32_t test_saddu_u32(uint32_t a, uint32_t b) {
+  return __riscv_saddu_u32(a, b);
+}
+
+// RV32-LABEL: define dso_local i32 @test_ssub_i32(
+// RV32-SAME: i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.ssub.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV32-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_ssub_i32(
+// RV64-SAME: i32 noundef signext [[A:%.*]], i32 noundef signext [[B:%.*]]) 
#[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.ssub.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV64-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+int32_t test_ssub_i32(int32_t a, int32_t b) {
+  return __riscv_ssub_i32(a, b);
+}
+
+// RV32-LABEL: define dso_local i32 @test_ssubu_u32(
+// RV32-SAME: i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.usub.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV32-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_ssubu_u32(
+// RV64-SAME: i32 noundef signext [[A:%.*]], i32 noundef signext [[B:%.*]]) 
#[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[ELT_SAT_I:%.*]] = call i32 @llvm.usub.sat.i32(i32 [[A]], 
i32 [[B]])
+// RV64-NEXT:    ret i32 [[ELT_SAT_I]]
+//
+uint32_t test_ssubu_u32(uint32_t a, uint32_t b) {
+  return __riscv_ssubu_u32(a, b);
+}
+
+/* Scalar Absolute Value */
+
+// RV32-LABEL: define dso_local i32 @test_abs_u32(
+// RV32-SAME: i32 noundef [[A:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[ELT_ABS_I:%.*]] = call i32 @llvm.abs.i32(i32 [[A]], i1 
false)
+// RV32-NEXT:    ret i32 [[ELT_ABS_I]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_abs_u32(
+// RV64-SAME: i32 noundef signext [[A:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[ELT_ABS_I:%.*]] = call i32 @llvm.abs.i32(i32 [[A]], i1 
false)
+// RV64-NEXT:    ret i32 [[ELT_ABS_I]]
+//
+uint32_t test_abs_u32(int32_t a) {
+  return __riscv_abs_u32(a);
+}
+
+#if __riscv_xlen == 64
+// RV64-LABEL: define dso_local i64 @test_abs_u64(
+// RV64-SAME: i64 noundef [[A:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[ELT_ABS_I:%.*]] = call i64 @llvm.abs.i64(i64 [[A]], i1 
false)
+// RV64-NEXT:    ret i64 [[ELT_ABS_I]]
+//
+uint64_t test_abs_u64(int64_t a) {
+  return __riscv_abs_u64(a);
+}
+#endif
+
 /* Packed Splat (32-bit) */
 
 // RV32-LABEL: define dso_local i32 @test_pmv_s_u8x4(
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..e68c9156311ba 100644
--- a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
+++ b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
@@ -12,6 +12,54 @@
 
 #include <riscv_packed_simd.h>
 
+// Note: RV64 has no 32-bit `rev`; the spec expands it to `rev`+`srai`.
+// CHECK-LABEL: test_rev_32:
+// RV32:        rev{{[[:space:]]}}
+// RV64:        rev{{[[:space:]]}}
+// RV64-NEXT:   srai
+uint32_t test_rev_32(uint32_t a) { return __riscv_rev_32(a); }
+
+#if __riscv_xlen == 64
+// RV64-LABEL: test_rev_64:
+// RV64:        rev{{[[:space:]]}}
+uint64_t test_rev_64(uint64_t a) { return __riscv_rev_64(a); }
+#endif
+
+// CHECK-LABEL: test_sadd_i32:
+// RV32:        sadd{{[[:space:]]}}
+// RV64:        psadd.w
+int32_t test_sadd_i32(int32_t a, int32_t b) { return __riscv_sadd_i32(a, b); }
+
+// CHECK-LABEL: test_saddu_u32:
+// RV32:        saddu{{[[:space:]]}}
+// RV64:        psaddu.w
+uint32_t test_saddu_u32(uint32_t a, uint32_t b) {
+  return __riscv_saddu_u32(a, b);
+}
+
+// CHECK-LABEL: test_ssub_i32:
+// RV32:        ssub{{[[:space:]]}}
+// RV64:        pssub.w
+int32_t test_ssub_i32(int32_t a, int32_t b) { return __riscv_ssub_i32(a, b); }
+
+// CHECK-LABEL: test_ssubu_u32:
+// RV32:        ssubu{{[[:space:]]}}
+// RV64:        pssubu.w
+uint32_t test_ssubu_u32(uint32_t a, uint32_t b) {
+  return __riscv_ssubu_u32(a, b);
+}
+
+// CHECK-LABEL: test_abs_u32:
+// RV32:        abs{{[[:space:]]}}
+// RV64:        absw
+uint32_t test_abs_u32(int32_t a) { return __riscv_abs_u32(a); }
+
+#if __riscv_xlen == 64
+// RV64-LABEL: test_abs_u64:
+// RV64:        abs{{[[:space:]]}}
+uint64_t test_abs_u64(int64_t a) { return __riscv_abs_u64(a); }
+#endif
+
 // CHECK-LABEL: test_pmv_s_u8x4:
 // CHECK:       pmv.bs
 uint8x4_t test_pmv_s_u8x4(uint8_t x) { return __riscv_pmv_s_u8x4(x); }

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

Reply via email to