[PATCH] riscv: Add RVV implementation of wcschr
Zhang Jinhan
zhangjinhan@isrc.iscas.ac.cn
Sat Sep 19 15:10:18 GMT 2026
Add an RVV implementation of wcschr and select it through the RISC-V
multiarch IFUNC resolver when the V extension is available. Retain the
generic implementation as the fallback and register both implementations
in the IFUNC implementation list.
Check the first element before setting up the search loops. Use
four-way-unrolled scalar loops for the first 32 elements when searching
for a nonzero character and the first 40 elements when searching for NUL.
Then probe with e32, LMUL=1 before switching to an LMUL=8 loop. Use
ordinary vector loads within a 4 KiB boundary and fault-only-first loads
when crossing that boundary. The NUL search uses a dedicated path that
only checks for zero elements.
On a SpacemiT K3/X100 with VLEN=256, bench-wcschr shows an overall
2.5036x speedup (+150.36%) over the generic implementation, equivalent
to a 60.06% reduction in execution time. All 498 case medians improve;
the smallest individual speedup is 1.2161x (+21.61%).
The source baseline is glibc commit
afc3228df04b64d94519bd00b61907560208bbb1.
The measurements compare __wcschr_generic and __wcschr_vector in the
same patched build, paired in each run. Execution was pinned to CPU 2,
with the userspace governor and an observed frequency of 2.2 GHz.
One warmup and six measured runs were performed, each covering 498
cases. For each case, take the median of its six timings for each
implementation, then compute the geometric mean across cases.
The following table gives those geometric means. Timing values use
the benchmark output scale; lower is better. Speedup is the ratio
of generic to vector, and gain is (generic / vector - 1) * 100%.
Position Cases Generic Vector Speedup Gain
All 498 97.362814 38.889854 2.5036x +150.36%
0-7 42 12.029031 6.635501 1.8128x +81.28%
8-15 64 23.475281 15.036105 1.5613x +56.13%
16-31 84 35.789723 21.191702 1.6889x +68.89%
>=32 308 228.633868 71.158150 3.2130x +221.30%
The per-run geometric means across all 498 cases are:
Run Generic Vector Gain
1 96.712354 38.672368 +150.08%
2 97.209984 38.629858 +151.64%
3 96.683859 38.656297 +150.11%
4 106.445429 47.093983 +126.03%
5 105.557872 46.824549 +125.43%
6 96.895320 38.721093 +150.24%
The smallest individual gain is for length=16, pos=15, rand=1,
branch=1, perc-zero=1, alignment=0: the case median falls from
27.2750 to 22.4280 (+21.61%). This is an individual case, not a
position-group geometric mean. The largest individual gain is
+510.65%. The two immediate-NUL cases improve by +58.82% and +49.95%,
and the two target-at-position-31 cases by +39.60% and +24.49%.
No case meets the repeatable-regression criterion: at least a 5%
slowdown in the case median and at least a 5% slowdown in four or
more of the six paired runs.
Validation on the test system:
- Patch application: PASS.
- Incremental RISC-V build: PASS.
- make test t=wcsmbs/test-wcschr: PASS, enumerating __wcschr_default,
__wcschr_vector, and __wcschr_generic.
- benchtests/bench-wcschr: 498/498 cases covered.
Co-authored-by: Ning Tian <tianning24@iscas.ac.cn>
Co-authored-by: Yuansheng <yuansheng@isrc.iscas.ac.cn>
Signed-off-by: Ning Tian <tianning24@iscas.ac.cn>
Signed-off-by: Yuansheng <yuansheng@isrc.iscas.ac.cn>
Signed-off-by: Zhang Jinhan <zhangjinhan@isrc.iscas.ac.cn>
---
sysdeps/riscv/multiarch/wcschr-generic.c | 17 ++
sysdeps/riscv/multiarch/wcschr-vector.S | 13 ++
sysdeps/riscv/rvv/wcschr.S | 200 ++++++++++++++++++
.../unix/sysv/linux/riscv/multiarch/Makefile | 7 +
.../linux/riscv/multiarch/ifunc-impl-list.c | 6 +
.../unix/sysv/linux/riscv/multiarch/wcschr.c | 44 ++++
6 files changed, 287 insertions(+)
create mode 100644 sysdeps/riscv/multiarch/wcschr-generic.c
create mode 100644 sysdeps/riscv/multiarch/wcschr-vector.S
create mode 100644 sysdeps/riscv/rvv/wcschr.S
create mode 100644 sysdeps/unix/sysv/linux/riscv/multiarch/wcschr.c
diff --git a/sysdeps/riscv/multiarch/wcschr-generic.c b/sysdeps/riscv/multiarch/wcschr-generic.c
new file mode 100644
index 0000000000..ee5810baa3
--- /dev/null
+++ b/sysdeps/riscv/multiarch/wcschr-generic.c
@@ -0,0 +1,17 @@
+/* Re-include the default wcschr implementation.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Licensed under the GNU Lesser General Public License v2.1+; see COPYING.LESSER. */
+
+#include <wchar.h>
+
+#if IS_IN(libc)
+# define WCSCHR __wcschr_generic
+# undef libc_hidden_def
+# define libc_hidden_def(name)
+# undef libc_hidden_weak
+# define libc_hidden_weak(name)
+# undef static_weak_alias
+# define static_weak_alias(name, alias)
+# include <wcsmbs/wcschr.c>
+#endif
diff --git a/sysdeps/riscv/multiarch/wcschr-vector.S b/sysdeps/riscv/multiarch/wcschr-vector.S
new file mode 100644
index 0000000000..a4b1cd215e
--- /dev/null
+++ b/sysdeps/riscv/multiarch/wcschr-vector.S
@@ -0,0 +1,13 @@
+/* RISC-V RVV based wcschr.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Licensed under the GNU Lesser General Public License v2.1+; see COPYING.LESSER. */
+
+#if IS_IN(libc)
+# define WCSCHR __wcschr_vector
+# undef libc_hidden_builtin_def
+# define libc_hidden_builtin_def(name)
+# undef weak_alias
+# define weak_alias(name, alias)
+# include <sysdeps/riscv/rvv/wcschr.S>
+#endif
diff --git a/sysdeps/riscv/rvv/wcschr.S b/sysdeps/riscv/rvv/wcschr.S
new file mode 100644
index 0000000000..e3c1908714
--- /dev/null
+++ b/sysdeps/riscv/rvv/wcschr.S
@@ -0,0 +1,200 @@
+/* RISC-V RVV based wcschr.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Licensed under the GNU Lesser General Public License v2.1+; see COPYING.LESSER. */
+
+#include <sysdep.h>
+#include <sys/asm.h>
+
+#ifndef WCSCHR
+# define WCSCHR __wcschr
+#endif
+
+#define wcs a0
+#define wc a1
+#define vl_reg a2
+#define step a3
+#define first a4
+#define post_vl a5
+#define page_size a6
+#define page_end a7
+#define scalar t0
+#define page_mask t1
+
+#define vchunk v16
+#define vhit v0
+#define vtmp v1
+
+ENTRY (WCSCHR)
+.option push
+.option arch, +v
+ /* Check the first element before selecting the target-specific path. It
+ keeps the most latency-sensitive match and NUL cases out of both loop
+ setup and vector startup. */
+ lw scalar, 0(wcs)
+ beq scalar, wc, L(ret)
+ beqz scalar, L(not_found)
+ addi wcs, wcs, 4
+ beqz wc, L(zero_scalar)
+
+ /* Scan 32 elements in total before paying vector startup. Process four
+ elements per iteration to reduce pointer updates and code footprint,
+ with only one loop backedge per group. */
+ lw scalar, 0(wcs)
+ beq scalar, wc, L(ret)
+ beqz scalar, L(not_found)
+ lw scalar, 4(wcs)
+ beq scalar, wc, L(ret4)
+ beqz scalar, L(not_found)
+ lw scalar, 8(wcs)
+ beq scalar, wc, L(ret8)
+ beqz scalar, L(not_found)
+ addi wcs, wcs, 12
+ li step, 7
+L(scalar_loop):
+ lw scalar, 0(wcs)
+ beq scalar, wc, L(ret)
+ beqz scalar, L(not_found)
+ lw scalar, 4(wcs)
+ beq scalar, wc, L(ret4)
+ beqz scalar, L(not_found)
+ lw scalar, 8(wcs)
+ beq scalar, wc, L(ret8)
+ beqz scalar, L(not_found)
+ lw scalar, 12(wcs)
+ beq scalar, wc, L(ret12)
+ beqz scalar, L(not_found)
+ addi wcs, wcs, 16
+ addi step, step, -1
+ bnez step, L(scalar_loop)
+
+ vsetvli vl_reg, zero, e32, m1, ta, ma
+ vle32ff.v vchunk, (wcs)
+ vmseq.vx vtmp, vchunk, wc
+ vmseq.vi vhit, vchunk, 0
+ vmor.mm vhit, vhit, vtmp
+ vfirst.m first, vhit
+ bgez first, L(found_nonzero)
+ /* A fault-only-first load may shorten vl. Read it only on the miss
+ path, where it is needed to advance the input pointer. */
+ csrr post_vl, vl
+ slli post_vl, post_vl, 2
+ add wcs, wcs, post_vl
+ vsetvli vl_reg, zero, e32, m8, ta, ma
+ slli step, vl_reg, 2
+ li page_size, 4096
+ addi page_mask, page_size, -1
+
+L(long_loop):
+ and page_end, wcs, page_mask
+ add page_end, page_end, step
+ bgtu page_end, page_size, L(long_page_cross)
+ vle32.v vchunk, (wcs)
+ vmseq.vx vtmp, vchunk, wc
+ vmseq.vi vhit, vchunk, 0
+ vmor.mm vhit, vhit, vtmp
+ vfirst.m first, vhit
+ bgez first, L(found_nonzero)
+ add wcs, wcs, step
+ j L(long_loop)
+
+L(long_page_cross):
+ vle32ff.v vchunk, (wcs)
+ vmseq.vx vtmp, vchunk, wc
+ vmseq.vi vhit, vchunk, 0
+ vmor.mm vhit, vhit, vtmp
+ vfirst.m first, vhit
+ bgez first, L(found_nonzero)
+ csrr post_vl, vl
+ slli post_vl, post_vl, 2
+ add wcs, wcs, post_vl
+ vsetvli zero, zero, e32, m8, ta, ma
+ j L(long_loop)
+
+L(found_nonzero):
+ slli first, first, 2
+ add wcs, wcs, first
+ lw scalar, 0(wcs)
+ beq scalar, wc, L(ret)
+L(not_found):
+ li wcs, 0
+L(ret):
+ ret
+L(ret4):
+ addi wcs, wcs, 4
+ ret
+L(ret8):
+ addi wcs, wcs, 8
+ ret
+L(ret12):
+ addi wcs, wcs, 12
+ ret
+
+ /* Searching for L'\0' needs only the NUL mask and does not require a
+ scalar reload after vfirst.m. */
+L(zero_scalar):
+ /* The first element was already checked. Scan elements 1..39 with the
+ same four-way unrolling before entering the NUL-only vector path. */
+ lw scalar, 0(wcs)
+ beqz scalar, L(ret)
+ lw scalar, 4(wcs)
+ beqz scalar, L(ret4)
+ lw scalar, 8(wcs)
+ beqz scalar, L(ret8)
+ addi wcs, wcs, 12
+ li step, 9
+L(zero_scalar_loop):
+ lw scalar, 0(wcs)
+ beqz scalar, L(ret)
+ lw scalar, 4(wcs)
+ beqz scalar, L(ret4)
+ lw scalar, 8(wcs)
+ beqz scalar, L(ret8)
+ lw scalar, 12(wcs)
+ beqz scalar, L(ret12)
+ addi wcs, wcs, 16
+ addi step, step, -1
+ bnez step, L(zero_scalar_loop)
+
+L(zero_first_vector):
+ vsetvli vl_reg, zero, e32, m1, ta, ma
+ vle32ff.v vchunk, (wcs)
+ vmseq.vi vhit, vchunk, 0
+ vfirst.m first, vhit
+ bgez first, L(found_zero)
+ csrr post_vl, vl
+ slli post_vl, post_vl, 2
+ add wcs, wcs, post_vl
+ vsetvli vl_reg, zero, e32, m8, ta, ma
+ slli step, vl_reg, 2
+ li page_size, 4096
+ addi page_mask, page_size, -1
+
+L(zero_long_loop):
+ and page_end, wcs, page_mask
+ add page_end, page_end, step
+ bgtu page_end, page_size, L(zero_long_page_cross)
+ vle32.v vchunk, (wcs)
+ vmseq.vi vhit, vchunk, 0
+ vfirst.m first, vhit
+ bgez first, L(found_zero)
+ add wcs, wcs, step
+ j L(zero_long_loop)
+
+L(zero_long_page_cross):
+ vle32ff.v vchunk, (wcs)
+ vmseq.vi vhit, vchunk, 0
+ vfirst.m first, vhit
+ bgez first, L(found_zero)
+ csrr post_vl, vl
+ slli post_vl, post_vl, 2
+ add wcs, wcs, post_vl
+ vsetvli zero, zero, e32, m8, ta, ma
+ j L(zero_long_loop)
+
+L(found_zero):
+ slli first, first, 2
+ add wcs, wcs, first
+ ret
+.option pop
+END (WCSCHR)
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
index bdbd12a2b6..4039604951 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
@@ -59,3 +59,10 @@ CFLAGS-strlen-generic.op = $(no-stack-protector)
CFLAGS-memcpy_noalignment.c += -mno-strict-align
endif
+
+ifeq ($(subdir),wcsmbs)
+sysdep_routines += \
+ wcschr-generic \
+ wcschr-vector \
+ # sysdep_routines
+endif
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c b/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
index ac2b25de74..ebcf24f677 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
@@ -18,6 +18,7 @@
#include <ifunc-impl-list.h>
#include <string.h>
+#include <wchar.h>
#include <sys/hwprobe.h>
size_t
@@ -120,5 +121,10 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
__strncpy_vector)
IFUNC_IMPL_ADD (array, i, strncpy, 1, __strncpy_generic))
+ IFUNC_IMPL (i, name, wcschr,
+ IFUNC_IMPL_ADD (array, i, wcschr, rvv_enabled,
+ __wcschr_vector)
+ IFUNC_IMPL_ADD (array, i, wcschr, 1, __wcschr_generic))
+
return 0;
}
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/wcschr.c b/sysdeps/unix/sysv/linux/riscv/multiarch/wcschr.c
new file mode 100644
index 0000000000..a66474598d
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/wcschr.c
@@ -0,0 +1,44 @@
+/* Multiple versions of wcschr.
+ All versions must be listed in ifunc-impl-list.c.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Licensed under the GNU Lesser General Public License v2.1+; see COPYING.LESSER. */
+
+#if IS_IN (libc)
+# undef wcschr
+# define wcschr __redirect_wcschr
+# include <stdint.h>
+# include <wchar.h>
+# include <ifunc-init.h>
+# include <riscv-ifunc.h>
+# include <sys/hwprobe.h>
+
+extern __typeof (__redirect_wcschr) __libc_wcschr;
+
+extern __typeof (__redirect_wcschr) __wcschr_generic attribute_hidden;
+extern __typeof (__redirect_wcschr) __wcschr_vector attribute_hidden;
+
+static inline __typeof (__redirect_wcschr) *
+select_wcschr_ifunc (uint64_t dl_hwcap, __riscv_hwprobe_t hwprobe_func)
+{
+ unsigned long long v;
+
+ if (__riscv_hwprobe_one (hwprobe_func, RISCV_HWPROBE_KEY_IMA_EXT_0, &v) == 0
+ && (v & RISCV_HWPROBE_IMA_V) == RISCV_HWPROBE_IMA_V)
+ return __wcschr_vector;
+
+ return __wcschr_generic;
+}
+
+riscv_libc_ifunc (__libc_wcschr, select_wcschr_ifunc);
+
+# undef wcschr
+strong_alias (__libc_wcschr, wcschr);
+strong_alias (wcschr, __wcschr)
+# ifdef SHARED
+__hidden_ver1 (wcschr, __GI_wcschr, __redirect_wcschr)
+ __attribute__ ((visibility ("hidden"))) __attribute_copy__ (wcschr);
+# endif
+#else
+# include <wcsmbs/wcschr.c>
+#endif
base-commit: d5bd0f22305277fa0d431bb9d733aa9456706573
--
2.43.0
More information about the Libc-alpha
mailing list