[clang] [llvm] [Clang][RISCV] Add scalar saturating add/sub, absolute value and rev intrinsics (PR #224377)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 17 10:59:28 PDT 2026
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 <liyongtai at iscas.ac.cn>
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); }
More information about the llvm-commits
mailing list