riscv: Implement Zbb based strlen and prefer it over the RVV based strlen implementation when Zbb is available
Checks
| Context |
Check |
Description |
| redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
| linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Build passed
|
| redhat-pt-bot/TryBot-32bit |
success
|
Build for i686
|
| linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_glibc_check--master-arm |
success
|
Test passed
|
Commit Message
So we've had Zbb variants for strlen, strcmp and a few other routines
sitting here in our local repositories for a long time. The original
implementations were done by the VRULL team, then adjusted for minor
bugs caught by the glibc testsuite and later wired into the hwprobe
mechanism.
Much like the RVV implementations that have been dropping into the tree,
I want to focus on one routine at a time to make sure we're happy with
the result, then move onto the next one. In this particular patch I'm
focused on strlen.
The implementation is largely derived from the bitmanip examples, just
cleaned up so that it ought to work for both rv32/rv64 and either big or
little endian (little endian is untested, I believe VRULL tested rv32 at
some point).
Neither the Zbb nor the RVV implementation seems at all sensitive to
data alignment concerns on the K3. So we can safely ignore that input
axis and focus on how many cycles it takes to handle a string of a
particular length.
I asked the LLM model to take the performance data, convert it to cycles
per byte, then get the average cycles per byte over a range of lengths
new buckets starting a power of 2 boundaries.
Bucket ZBB CPB Vector CBP Winner
1-1 4.227 17.312 ZBB is ~4.1x faster
2-3 1.714 7.232 ZBB is ~4.2x faster
4-7 0.870 3.287 ZBB is ~3.8x faster
8-15 0.563 1.950 ZBB is ~3.5x faster
16-31 0.446 0.954 ZBB is ~2.1x faster
32-63 0.299 0.477 ZBB is ~1.6x faster
64-127 0.190 0.414 ZBB is ~2.2x faster
And so-on with the cycles-per-byte dropping for both, but ZBB
consistently running ~2.1x faster than RVV up to a length of 8k.
We can see the Zbb is just better all around. There wasn't a single
case where RVV won. It's pretty obvious that the vector version has a
higher fixed overhead, but I really expected vector to overcome that
overhead as the strings got longer. As it stands the data says quite
clearly that we should be using Zbb on the K3 design and likely the K1
design (currently being tested).
Given the K1/K3 designs are what folks can get their hands on, I'd
recommend we make Zbb preferred over RVV. We'll likely have to adjust
that as newer designs come into the market, but the decision should be
data driven. I'm going to run this on our Veyron V2 design and Peter is
going to run on the Ascalon design, but neither of those are generally
available and probably shouldn't drive decisions, those are mostly for
informational purposes and to give a sense of whether or not higher
targeted designs are likely to benefit from the RVV variant when those
higher performance designs hit the market.
You could also legitimately ask what GCC should be doing here. Right now
GCC will inline the strlen call, generating RVV code that is nearly
identical to what's in glibc. So it's probably not a win for GCC to
inline an RVV strlen, though inlining does at least avoid the function
call overhead and allow for secondary optimization affects since there's
no call.
This has been built and regression tested on the c920 and K3, the K1 is
still running. The c920 is interesting because it has neither RVV nor
Zbb, so confirming I didn't do anything dumb in the resolver was useful.
OK for the trunk?
Jeff
Comments
On 6/10/2026 10:09 PM, Jeffrey Law wrote:
>
> So we've had Zbb variants for strlen, strcmp and a few other routines
> sitting here in our local repositories for a long time. The original
> implementations were done by the VRULL team, then adjusted for minor
> bugs caught by the glibc testsuite and later wired into the hwprobe
> mechanism.
[ ... ]
So a follow-up on the K1 performance behavior. Again Zbb wins across
the board, though the size of the win is much smaller. For really short
strings it's about 2.4x faster and the gap closes to 1.2x by the time
we're doing strings of > 64 bytes.
What's kind of interesting is as we get into the 32-63 length bucket the
performance of Zbb and RVV is nearly identical. That's also where RVV
made its best showing on the K3, though it never caught Zbb. Regardless
Zbb is a clear win on the K1 design.
jeff
On 6/10/26 11:09 PM, Jeffrey Law wrote:
> We can see the Zbb is just better all around. There wasn't a single
> case where RVV won. It's pretty obvious that the vector version has a
> higher fixed overhead, but I really expected vector to overcome that
> overhead as the strings got longer. As it stands the data says quite
> clearly that we should be using Zbb on the K3 design and likely the K1
> design (currently being tested).
>
> Given the K1/K3 designs are what folks can get their hands on, I'd
> recommend we make Zbb preferred over RVV. We'll likely have to adjust
> that as newer designs come into the market, but the decision should be
> data driven. I'm going to run this on our Veyron V2 design and Peter is
> going to run on the Ascalon design, but neither of those are generally
> available and probably shouldn't drive decisions, those are mostly for
> informational purposes and to give a sense of whether or not higher
> targeted designs are likely to benefit from the RVV variant when those
> higher performance designs hit the market.
Yes, I'll have someone run the tests on our Ascalon design and report
back.
That said, this is looking good for a normal multiarch build, but for
a --disable-multiarch build, we'll need some sysdeps/riscv/preconfigure*
and Implies file changes, specifying the subdirectory search priority.
I believe "as is" with this patch, if we build with --disable-multiarch,
we'll never look in the zbb subdir, meaning zbb isn't in the subdirectory
search list at all.
With your data, it would seem we'd want to search the new zbb subdir
before the rvv subdir...at least for strlen, but I doubt we'd want
that for all other functions, right? So do we go with, search rvv
first, followed by zbb and just take the perf hit for non-multiarch
builds? Your thoughts?
Peter
On 11/06/26 01:09, Jeffrey Law wrote:
>
> So we've had Zbb variants for strlen, strcmp and a few other routines sitting here in our local repositories for a long time. The original implementations were done by the VRULL team, then adjusted for minor bugs caught by the glibc testsuite and later wired into the hwprobe mechanism.
>
> Much like the RVV implementations that have been dropping into the tree, I want to focus on one routine at a time to make sure we're happy with the result, then move onto the next one. In this particular patch I'm focused on strlen.
>
> The implementation is largely derived from the bitmanip examples, just cleaned up so that it ought to work for both rv32/rv64 and either big or little endian (little endian is untested, I believe VRULL tested rv32 at some point).
>
> Neither the Zbb nor the RVV implementation seems at all sensitive to data alignment concerns on the K3. So we can safely ignore that input axis and focus on how many cycles it takes to handle a string of a particular length.
>
> I asked the LLM model to take the performance data, convert it to cycles per byte, then get the average cycles per byte over a range of lengths new buckets starting a power of 2 boundaries.
>
> Bucket ZBB CPB Vector CBP Winner
> 1-1 4.227 17.312 ZBB is ~4.1x faster
> 2-3 1.714 7.232 ZBB is ~4.2x faster
> 4-7 0.870 3.287 ZBB is ~3.8x faster
> 8-15 0.563 1.950 ZBB is ~3.5x faster
> 16-31 0.446 0.954 ZBB is ~2.1x faster
> 32-63 0.299 0.477 ZBB is ~1.6x faster
> 64-127 0.190 0.414 ZBB is ~2.2x faster
>
> And so-on with the cycles-per-byte dropping for both, but ZBB consistently running ~2.1x faster than RVV up to a length of 8k.
>
>
> We can see the Zbb is just better all around. There wasn't a single case where RVV won. It's pretty obvious that the vector version has a higher fixed overhead, but I really expected vector to overcome that overhead as the strings got longer. As it stands the data says quite clearly that we should be using Zbb on the K3 design and likely the K1 design (currently being tested).
>
> Given the K1/K3 designs are what folks can get their hands on, I'd recommend we make Zbb preferred over RVV. We'll likely have to adjust that as newer designs come into the market, but the decision should be data driven. I'm going to run this on our Veyron V2 design and Peter is going to run on the Ascalon design, but neither of those are generally available and probably shouldn't drive decisions, those are mostly for informational purposes and to give a sense of whether or not higher targeted designs are likely to benefit from the RVV variant when those higher performance designs hit the market.
>
>
> You could also legitimately ask what GCC should be doing here. Right now GCC will inline the strlen call, generating RVV code that is nearly identical to what's in glibc. So it's probably not a win for GCC to inline an RVV strlen, though inlining does at least avoid the function call overhead and allow for secondary optimization affects since there's no call.
>
> This has been built and regression tested on the c920 and K3, the K1 is still running. The c920 is interesting because it has neither RVV nor Zbb, so confirming I didn't do anything dumb in the resolver was useful.
Did you check the generic implementation? Both 3d6fcf1bd7f462d333c36a14efc0e03f2fdd3f9e
and 4c966c078036abe0e36bd86c9eaeb4501e552977 added zbb handling, so building with
-march=rv64imafdc_zbb -mabi=lp64d with gcc-16 I see:
$ riscv64-linux-gnu-objdump -d string/strlen-generic.os
[...]
0000000000000000 <__strlen_generic>:
0: ff857713 andi a4,a0,-8
4: 00757693 andi a3,a0,7
8: 0036969b slliw a3,a3,0x3
c: 631c ld a5,0(a4)
e: 2877d793 orc.b a5,a5
12: fff7c793 not a5,a5
16: 00d7d7b3 srl a5,a5,a3
1a: 56fd li a3,-1
1c: e38d bnez a5,3e <.L8>
000000000000001e <.L2>:
1e: 0721 addi a4,a4,8
20: 631c ld a5,0(a4)
22: 2877d793 orc.b a5,a5
26: fed78ce3 beq a5,a3,1e <.L2>
2a: fff7c793 not a5,a5
2e: 60179793 ctz a5,a5
32: 0037d79b srliw a5,a5,0x3
36: 973e add a4,a4,a5
38: 40a70533 sub a0,a4,a0
3c: 8082 ret
000000000000003e <.L8>:
3e: 60179793 ctz a5,a5
42: 0037d51b srliw a0,a5,0x3
46: 8082 ret
Which seems pretty much like:
$ riscv64-linux-gnu-objdump -d string/strlen-zbb.os
[...]
0000000000000000 <__strlen_zbb-0x2>:
0: 0001 nop
0000000000000002 <__strlen_zbb>:
2: 00757693 andi a3,a0,7
6: ff857593 andi a1,a0,-8
a: 4721 li a4,8
c: 8f15 sub a4,a4,a3
e: 068e slli a3,a3,0x3
10: 6190 ld a2,0(a1)
12: 00d65633 srl a2,a2,a3
16: 28765613 orc.b a2,a2
1a: fff64613 not a2,a2
1e: 60161613 ctz a2,a2
22: 00365513 srli a0,a2,0x3
26: 02e56763 bltu a0,a4,54 <.Ldone>
2a: 00858693 addi a3,a1,8
2e: 577d li a4,-1
30: 0001 nop
32: 00000013 nop
0000000000000036 <.Lloop>:
36: 6590 ld a2,8(a1)
38: 05a1 addi a1,a1,8
3a: 28765613 orc.b a2,a2
3e: fee60ce3 beq a2,a4,36 <.Lloop>
42: fff64613 not a2,a2
46: 60161613 ctz a2,a2
4a: 40d586b3 sub a3,a1,a3
4e: 9536 add a0,a0,a3
50: 820d srli a2,a2,0x3
52: 9532 add a0,a0,a2
0000000000000054 <.Ldone>:
54: 8082 ret
56: 0001 nop
The ASM version seems to have an extra cost of li/sub valid-bytes setup, longer
first-word path, and the alignment padding nops. I would guess both would have
similar performance profiles.
And there is another question if adding a zbb sysdep is really a good move here.
My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
which implies in zbb and thus you can select it without the need to add an
specific implementation.
On 6/11/2026 1:51 PM, Adhemerval Zanella Netto wrote:
> Did you check the generic implementation? Both 3d6fcf1bd7f462d333c36a14efc0e03f2fdd3f9e
> and 4c966c078036abe0e36bd86c9eaeb4501e552977 added zbb handling, so building with
> -march=rv64imafdc_zbb -mabi=lp64d with gcc-16 I see:
It included the generic, but I didn't build with flags that would have
flipped Zbb on.
[ ... ]
> The ASM version seems to have an extra cost of li/sub valid-bytes setup, longer
> first-word path, and the alignment padding nops. I would guess both would have
> similar performance profiles.
Yea, I would expect largely similar.
>
> And there is another question if adding a zbb sysdep is really a good move here.
> My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
> which implies in zbb and thus you can select it without the need to add an
> specific implementation.
While I would *love* to see everyone settle on rva23, but I'm highly
skeptical based on my conversations with distros and others.
Jeff
On 6/11/2026 11:41 AM, Peter Bergner wrote:
> On 6/10/26 11:09 PM, Jeffrey Law wrote:
>> We can see the Zbb is just better all around. There wasn't a single
>> case where RVV won. It's pretty obvious that the vector version has a
>> higher fixed overhead, but I really expected vector to overcome that
>> overhead as the strings got longer. As it stands the data says quite
>> clearly that we should be using Zbb on the K3 design and likely the K1
>> design (currently being tested).
>>
>> Given the K1/K3 designs are what folks can get their hands on, I'd
>> recommend we make Zbb preferred over RVV. We'll likely have to adjust
>> that as newer designs come into the market, but the decision should be
>> data driven. I'm going to run this on our Veyron V2 design and Peter is
>> going to run on the Ascalon design, but neither of those are generally
>> available and probably shouldn't drive decisions, those are mostly for
>> informational purposes and to give a sense of whether or not higher
>> targeted designs are likely to benefit from the RVV variant when those
>> higher performance designs hit the market.
> Yes, I'll have someone run the tests on our Ascalon design and report
> back.
Thanks.
>
> That said, this is looking good for a normal multiarch build, but for
> a --disable-multiarch build, we'll need some sysdeps/riscv/preconfigure*
> and Implies file changes, specifying the subdirectory search priority.
> I believe "as is" with this patch, if we build with --disable-multiarch,
> we'll never look in the zbb subdir, meaning zbb isn't in the subdirectory
> search list at all.
I'm not at all familiar with the Implies stuff. I'll try some things
with --disable-multiarch to see what I get.
>
> With your data, it would seem we'd want to search the new zbb subdir
> before the rvv subdir...at least for strlen, but I doubt we'd want
> that for all other functions, right? So do we go with, search rvv
> first, followed by zbb and just take the perf hit for non-multiarch
> builds? Your thoughts?
I think we'd want to evaluate the other functions. str[n]cmp for
example before we make this decision.
jeff
On 11/06/26 18:30, Jeffrey Law wrote:
>
>
> On 6/11/2026 1:51 PM, Adhemerval Zanella Netto wrote:
>> Did you check the generic implementation? Both 3d6fcf1bd7f462d333c36a14efc0e03f2fdd3f9e
>> and 4c966c078036abe0e36bd86c9eaeb4501e552977 added zbb handling, so building with
>> -march=rv64imafdc_zbb -mabi=lp64d with gcc-16 I see:
> It included the generic, but I didn't build with flags that would have flipped Zbb on.
>
> [ ... ]
>
>> The ASM version seems to have an extra cost of li/sub valid-bytes setup, longer
>> first-word path, and the alignment padding nops. I would guess both would have
>> similar performance profiles.
> Yea, I would expect largely similar.
>
>>
>> And there is another question if adding a zbb sysdep is really a good move here.
>> My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
>> which implies in zbb and thus you can select it without the need to add an
>> specific implementation.
> While I would *love* to see everyone settle on rva23, but I'm highly skeptical based on my conversations with distros and others.
>
> Jeff
Sigh... then I think it would be better to have a way to build zbb optimized
implementations using the generic code, instead of adding zbb assembly routines.
Unfortunately __attribute__ ((target ("arch=+zbb"))) was only added on gcc-14;
and RISC-V lackk any option to add +zbb for the extensions for a translation unit.
One option would be filter out the compiler used zbb, check if zbb is being used
(by parsing the .attribute form the assembly), and compose a -march with the compiler
default *plus* zbb. I think someone has already suggested it, I don't recall exactly.
Something like the below, and you can go even further and add a config.make to avoid
building and selecting zbb if compiler already default to it (as for building to rva23).
I will let this exercise for the reader ;)
diff --git a/sysdeps/unix/sysv/linux/riscv/configure.ac b/sysdeps/unix/sysv/linux/riscv/configure.ac
index 9c736415f72..f84a1669e4a 100644
--- a/sysdeps/unix/sysv/linux/riscv/configure.ac
+++ b/sysdeps/unix/sysv/linux/riscv/configure.ac
@@ -32,6 +32,28 @@ fi
LIBC_CONFIG_VAR([default-abi], [$libc_cv_riscv_int_abi$libc_cv_riscv_float_abi])
+AC_CACHE_CHECK([for the compiler option to enable the Zbb extension],
+ [libc_cv_riscv_zbb_cflags], [dnl
+libc_cv_riscv_zbb_cflags=
+cat > conftest.c <<EOF
+int foo (void) { return 0; }
+EOF
+libc_cv_riscv_arch=`$CC $CFLAGS $CPPFLAGS -S -o - conftest.c 2>/dev/null \
+ | sed -n 's/.*\.attribute@<:@^,@:>@*,@<:@^"@:>@*"\(rv@<:@^"@:>@*\)".*/\1/p'`
+rm -f conftest*
+if test -n "$libc_cv_riscv_arch"; then
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -march=${libc_cv_riscv_arch}_zbb"
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
+#ifndef __riscv_zbb
+# error __riscv_zbb is not defined
+#endif
+]])],
+ [libc_cv_riscv_zbb_cflags="-march=${libc_cv_riscv_arch}_zbb"])
+ CFLAGS="$save_CFLAGS"
+fi])
+LIBC_CONFIG_VAR([riscv-zbb-cflags], [$libc_cv_riscv_zbb_cflags])
+
case $libc_cv_riscv_int_abi$libc_cv_riscv_float_abi-$machine in
lp64-riscv/rv64/*)
LIBC_SLIBDIR_RTLDDIR([lib64/lp64], [lib])
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
index 30d92c1ba92..df7fde2fd8d 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
@@ -31,6 +31,7 @@ sysdep_routines += \
strlen \
strlen-generic \
strlen-vector \
+ strlen-zbb \
strncmp \
strncmp-generic \
strncmp-vector \
@@ -40,6 +41,8 @@ sysdep_routines += \
# sysdep_routines
CFLAGS-memcpy_noalignment.c += -mno-strict-align
+CFLAGS-strlen-zbb.c += $(riscv-zbb-cflags)
+
# Called during static initialization
CFLAGS-memset-generic.c += $(no-stack-protector)
CFLAGS-memcpy-generic.c += $(no-stack-protector)
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 80275785293..e598f958487 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
@@ -28,6 +28,7 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
bool fast_unaligned = false;
bool rvv_enabled = false;
+ bool zbb_enabled = false;
struct riscv_hwprobe pairs[2] = {
{.key = RISCV_HWPROBE_KEY_CPUPERF_0},
@@ -41,6 +42,9 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
if (pairs[1].value & RISCV_HWPROBE_IMA_V)
rvv_enabled = true;
+
+ if (pairs[1].value & RISCV_HWPROBE_EXT_ZBB)
+ zbb_enabled = true;
}
IFUNC_IMPL (i, name, memcpy,
@@ -68,6 +72,8 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
IFUNC_IMPL (i, name, strlen,
IFUNC_IMPL_ADD (array, i, strlen, rvv_enabled,
__strlen_vector)
+ IFUNC_IMPL_ADD (array, i, strlen, zbb_enabled,
+ __strlen_zbb)
IFUNC_IMPL_ADD (array, i, strlen, 1, __strlen_generic))
IFUNC_IMPL (i, name, strcmp,
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c b/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c
index 9975286b85a..5ed08ac37e1 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c
@@ -32,14 +32,19 @@ extern __typeof (__redirect_strlen) __libc_strlen;
extern __typeof (__redirect_strlen) __strlen_generic attribute_hidden;
extern __typeof (__redirect_strlen) __strlen_vector attribute_hidden;
+extern __typeof (__redirect_strlen) __strlen_zbb attribute_hidden;
static inline __typeof (__redirect_strlen) *
select_strlen_ifunc (uint64_t dl_hwcap, __riscv_hwprobe_t hwprobe_func)
{
unsigned long long int 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 __strlen_vector;
+ if (__riscv_hwprobe_one (hwprobe_func, RISCV_HWPROBE_KEY_IMA_EXT_0, &v) == 0)
+ {
+ if (v & RISCV_HWPROBE_IMA_V)
+ return __strlen_vector;
+ if (v & RISCV_HWPROBE_EXT_ZBB)
+ return __strlen_zbb;
+ }
return __strlen_generic;
}
On 6/11/26 2:51 PM, Adhemerval Zanella Netto wrote:
> And there is another question if adding a zbb sysdep is really a good move here.
> My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
> which implies in zbb and thus you can select it without the need to add an
> specific implementation.
So there is an question here, which hopefully Adhemerval can correct me if
I'm wrong or confirm my memory is correct. For non-multiarch builds, it's
the sysdeps/riscv/preconfigure fragment that detects which sysdeps/riscv/*
subdirectory to start our subdirectory search priority with by setting the
"machine" variable. The Implies file in that subdirectory then points to
the next subdirectory to search in, etc. etc. I believe (Adhemerval correct
me if I'm wrong), is that if preconfigure says start at subdirectory "a" and
its Implies file points to "b" and "b"'s Implies points to "c", etc, then if
"a" is enabled via compiler flags, that implies "b", "c", etc. are all also
enabled via the compiler flags, correct? Meaning if "a" is enabled, then
it cannot be that case that any of "b", "c", etc. are disabled, correct?
If that is the case, then we have a slight problem, in that rvv and zbb are
orthogonal extensions, so one doesn't "imply" the other, so it wouldn't really
be "legal" to have one in the Implies file of the other.
However, from a practical standpoint, there are cores that have implemented zbb,
but have not implemented rvv, but I don't know of any cores that have rvv
without also implementing zbb, so from a purely practical standpoint, rvv
seems to "imply" zbb. That said, I'm not sure whether we should rely on that?
It's also opposite the order we want the strlen implementation to be picked up,
which is zbb first and then fallback to rvv.
I suppose one option is if we decide that rvv does (from a practical standpoint)
imply zbb, then we could create the zbb subdir, have it in rvv's Implies file
and then remove the strlen-vector.S and place your strlen-zbb.S in the zbb
subdir. That would make all zbb only and rvv+zbb cores use the zbb strlen.
Thoughts?
Peter
On 12/06/26 13:56, Peter Bergner wrote:
> On 6/11/26 2:51 PM, Adhemerval Zanella Netto wrote:
>> And there is another question if adding a zbb sysdep is really a good move here.
>> My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
>> which implies in zbb and thus you can select it without the need to add an
>> specific implementation.
>
> So there is an question here, which hopefully Adhemerval can correct me if
> I'm wrong or confirm my memory is correct. For non-multiarch builds, it's
> the sysdeps/riscv/preconfigure fragment that detects which sysdeps/riscv/*
> subdirectory to start our subdirectory search priority with by setting the
> "machine" variable. The Implies file in that subdirectory then points to
> the next subdirectory to search in, etc. etc. I believe (Adhemerval correct
> me if I'm wrong), is that if preconfigure says start at subdirectory "a" and
> its Implies file points to "b" and "b"'s Implies points to "c", etc, then if
> "a" is enabled via compiler flags, that implies "b", "c", etc. are all also
> enabled via the compiler flags, correct? Meaning if "a" is enabled, then
> it cannot be that case that any of "b", "c", etc. are disabled, correct?
>
> If that is the case, then we have a slight problem, in that rvv and zbb are
> orthogonal extensions, so one doesn't "imply" the other, so it wouldn't really
> be "legal" to have one in the Implies file of the other.
>
> However, from a practical standpoint, there are cores that have implemented zbb,
> but have not implemented rvv, but I don't know of any cores that have rvv
> without also implementing zbb, so from a purely practical standpoint, rvv
> seems to "imply" zbb. That said, I'm not sure whether we should rely on that?
Yes, the sysdep and Implies files were modeled back when new ISAs versions
were a subset of the previous one (like PowerPC POWERX, and 32 bit ARM armvX).
And even then, we had some outliers like power6x/arm thumb which has some
instructions not implemented in newer versions, and that required is own sysdeps
subfolder *without* Implies file.
Newer ISAs now to push for extensions that might or not be implemented, which
includes x86_64, aarch64, and RISCV. For these, the strategy will depend how
the glibc would be deployed, the ISA support for GNU extensions (like IFUNC),
and what kind of hardware the deployer aims for.
AArch64, for instance, is still setting a minimal ISA supported version of
ARMv8.0: all the sysdeps/aarch64/ optimizations assumes it. Nothing prevents
a deployer to build glibc with a -march=armvX.Y, although ARM did take some
care to avoid potential pitfalls like add atomic outliers (so armv8.1 LSE
can be used by libgcc) and defining PAC+BTI in the NOP space (so a glibc build
with --enable-standard-branch-protection still run on older hardware).
For x86_64 we have added the x86_64-vX microarchitectures, along with glibc-hwcaps.
It allows deploying glibc built for the x86_64 baseline, but it still allows
build and use libraries with a higher microarchitecture level. The glibc loader
avoids the SIGILL issue by using the PT_GNU_PROPERTY (if present). If ld.so sees
that libexample.so has GNU_PROPERTY_X86_ISA_1_NEEDED set to x86_64-v3 (AVX2), but
detects that you are running on an older x86_64-v2 CPU, the dynamic linker will
refuse to load it.
>
> It's also opposite the order we want the strlen implementation to be picked up,
> which is zbb first and then fallback to rvv.
>
> I suppose one option is if we decide that rvv does (from a practical standpoint)
> imply zbb, then we could create the zbb subdir, have it in rvv's Implies file
> and then remove the strlen-vector.S and place your strlen-zbb.S in the zbb
> subdir. That would make all zbb only and rvv+zbb cores use the zbb strlen.
>
Now for RISCV with its potentially dozens of extensions (plus the vendors ones),
I would suggest you to *avoid* adds extensions sysdeps (as this patch did), and
move any performance optimization only to multiarch - as aarch64 does for ISA
extensions one like SVE/MOPS. You already did for RVV, so I would suggest you
to circle back and only add them on the sysdeps/riscv/multiarch.
Maybe also re-evalute whether rvd/rvf/rv64/rv32 is really required here, or
whether we can consolidate the implementations. I would also explicit disable
configuring with some broken configurations, like float-only (rvf).
It does not help for --disable-multi-arch, but it is also no the usual mode
to deploy glibc.
For this, a sysdep *might* make sense for the RISC-V *profiles. It would selecte
a baseline of arch-specific implementation (like vector ones). I would also follow
AArch64 and try to provide *optimized baselines* implementation, so there is no
need to have this dance of which optimization each chip would benefit more.
You can have the over-complicated x86_64 way, to have multiple tuned implementation
for each specific chips (where it selects which vector instruction it use, in
which size range, etc); but again I would suggest you to follow AArch64 and
try to simplify things if possible.
On 6/12/2026 10:56 AM, Peter Bergner wrote:
> On 6/11/26 2:51 PM, Adhemerval Zanella Netto wrote:
>> And there is another question if adding a zbb sysdep is really a good move here.
>> My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
>> which implies in zbb and thus you can select it without the need to add an
>> specific implementation.
> So there is an question here, which hopefully Adhemerval can correct me if
> I'm wrong or confirm my memory is correct. For non-multiarch builds, it's
> the sysdeps/riscv/preconfigure fragment that detects which sysdeps/riscv/*
> subdirectory to start our subdirectory search priority with by setting the
> "machine" variable. The Implies file in that subdirectory then points to
> the next subdirectory to search in, etc. etc. I believe (Adhemerval correct
> me if I'm wrong), is that if preconfigure says start at subdirectory "a" and
> its Implies file points to "b" and "b"'s Implies points to "c", etc, then if
> "a" is enabled via compiler flags, that implies "b", "c", etc. are all also
> enabled via the compiler flags, correct? Meaning if "a" is enabled, then
> it cannot be that case that any of "b", "c", etc. are disabled, correct?
>
> If that is the case, then we have a slight problem, in that rvv and zbb are
> orthogonal extensions, so one doesn't "imply" the other, so it wouldn't really
> be "legal" to have one in the Implies file of the other.
>
> However, from a practical standpoint, there are cores that have implemented zbb,
> but have not implemented rvv, but I don't know of any cores that have rvv
> without also implementing zbb, so from a purely practical standpoint, rvv
> seems to "imply" zbb. That said, I'm not sure whether we should rely on that?
>
> It's also opposite the order we want the strlen implementation to be picked up,
> which is zbb first and then fallback to rvv.
>
> I suppose one option is if we decide that rvv does (from a practical standpoint)
> imply zbb, then we could create the zbb subdir, have it in rvv's Implies file
> and then remove the strlen-vector.S and place your strlen-zbb.S in the zbb
> subdir. That would make all zbb only and rvv+zbb cores use the zbb strlen.
While it's probably OK in practice right now, the fundamental nature of
the RISC-V ISA is a designer can mix and match extensions. As you note,
there is no implies relationship between Zbb and RVV. I'm not at all a
fan of how that works, but it is what it is and we should try to do the
right thing here.
I think that argues that Adhemerval's suggestion is probably the right
way to go. I need to look at it again, but the gist was to move the Zbb
stuff into the multiarch directory, dropping zbb as a distinct sysdep
directory. This is an aspect of glibc I don't know well at all and I
was largely trying to mimick what's being done elsewhere. But this is
fundamentally different. Adhemerval's suggestion is actually how this
stuff was implemented internally, it was only when I looked closely at
how the final RVV stuff went into the tree that I mirrored some of that
into the Zbb patch.
Note that I'm still generating and evaluating data on the other cases.
While strlen is a slam dunk right now for Zbb, the others aren't as
clear cut. That I think also argues that Zbb probably shouldn't be a
sysdep. I also would fully expect that as designs get better (let's
face it, while K3 is a huge improvement over the K1, it's not would I
would call a high performance design).
Jeff
On 13/06/26 01:46, Jeffrey Law wrote:
>
>
> On 6/12/2026 10:56 AM, Peter Bergner wrote:
>> On 6/11/26 2:51 PM, Adhemerval Zanella Netto wrote:
>>> And there is another question if adding a zbb sysdep is really a good move here.
>>> My understanding is chip produces and distros are moving to RVA23U64/RVA23S64,
>>> which implies in zbb and thus you can select it without the need to add an
>>> specific implementation.
>> So there is an question here, which hopefully Adhemerval can correct me if
>> I'm wrong or confirm my memory is correct. For non-multiarch builds, it's
>> the sysdeps/riscv/preconfigure fragment that detects which sysdeps/riscv/*
>> subdirectory to start our subdirectory search priority with by setting the
>> "machine" variable. The Implies file in that subdirectory then points to
>> the next subdirectory to search in, etc. etc. I believe (Adhemerval correct
>> me if I'm wrong), is that if preconfigure says start at subdirectory "a" and
>> its Implies file points to "b" and "b"'s Implies points to "c", etc, then if
>> "a" is enabled via compiler flags, that implies "b", "c", etc. are all also
>> enabled via the compiler flags, correct? Meaning if "a" is enabled, then
>> it cannot be that case that any of "b", "c", etc. are disabled, correct?
>>
>> If that is the case, then we have a slight problem, in that rvv and zbb are
>> orthogonal extensions, so one doesn't "imply" the other, so it wouldn't really
>> be "legal" to have one in the Implies file of the other.
>>
>> However, from a practical standpoint, there are cores that have implemented zbb,
>> but have not implemented rvv, but I don't know of any cores that have rvv
>> without also implementing zbb, so from a purely practical standpoint, rvv
>> seems to "imply" zbb. That said, I'm not sure whether we should rely on that?
>>
>> It's also opposite the order we want the strlen implementation to be picked up,
>> which is zbb first and then fallback to rvv.
>>
>> I suppose one option is if we decide that rvv does (from a practical standpoint)
>> imply zbb, then we could create the zbb subdir, have it in rvv's Implies file
>> and then remove the strlen-vector.S and place your strlen-zbb.S in the zbb
>> subdir. That would make all zbb only and rvv+zbb cores use the zbb strlen.
> While it's probably OK in practice right now, the fundamental nature of the RISC-V ISA is a designer can mix and match extensions. As you note, there is no implies relationship between Zbb and RVV. I'm not at all a fan of how that works, but it is what it is and we should try to do the right thing here.
>
> I think that argues that Adhemerval's suggestion is probably the right way to go. I need to look at it again, but the gist was to move the Zbb stuff into the multiarch directory, dropping zbb as a distinct sysdep directory. This is an aspect of glibc I don't know well at all and I was largely trying to mimick what's being done elsewhere. But this is fundamentally different. Adhemerval's suggestion is actually how this stuff was implemented internally, it was only when I looked closely at how the final RVV stuff went into the tree that I mirrored some of that into the Zbb patch.
>
> Note that I'm still generating and evaluating data on the other cases. While strlen is a slam dunk right now for Zbb, the others aren't as clear cut. That I think also argues that Zbb probably shouldn't be a sysdep. I also would fully expect that as designs get better (let's face it, while K3 is a huge improvement over the K1, it's not would I would call a high performance design).
Another advantage of having the optimized routines as ifunc options is you can
have the selection logic on the ifunc resolver, instead of relying on some
specific build option being used (as to tune glibc build for a specific chip).
But you can still have a sysdeps folder, but I think for RISCV it would make
sense more a profile one. It will then gather the preferred routines options,
and also disable ifunc selection if not required.
On 6/12/26 1:12 PM, Adhemerval Zanella Netto wrote:
> Now for RISCV with its potentially dozens of extensions (plus the vendors ones),
> I would suggest you to *avoid* adds extensions sysdeps (as this patch did), and
> move any performance optimization only to multiarch - as aarch64 does for ISA
> extensions one like SVE/MOPS. You already did for RVV, so I would suggest you
> to circle back and only add them on the sysdeps/riscv/multiarch.
[snip]
> For this, a sysdep *might* make sense for the RISC-V *profiles. It would selecte
> a baseline of arch-specific implementation (like vector ones). I would also follow
> AArch64 and try to provide *optimized baselines* implementation, so there is no
> need to have this dance of which optimization each chip would benefit more.
>
> You can have the over-complicated x86_64 way, to have multiple tuned implementation
> for each specific chips (where it selects which vector instruction it use, in
> which size range, etc); but again I would suggest you to follow AArch64 and
> try to simplify things if possible.
Talking with Jeff offline, I think we're both coming to the conclusion that
you are correct (not a surprise!) and that sysdep directories for a lot of
orthogonal extensions is going to be messy and unwieldy. I do like your
suggestion of having sysdep directories for riscv profiles as those can pull
in lots of extensions at once and riscv profiles somewhat being supersets of
previous profiles more closely matches the behavior expected by the Implies
files.
I'll study how AArch64 implements those optimized baselines to see if we
can use the same design. Thanks for the pointer. I'm sure I'll be back
with questions though! :-) ...and any pointers or gotchas to look out for
will be greatly appreciated.
Obviously, this would be a following release kind of thing though, given
where we are in the release process.
Peter
new file mode 100644
@@ -0,0 +1,26 @@
+/* Re-include the RISC-V Zbb based strlen implementation.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#if IS_IN(libc)
+# define STRLEN __strlen_zbb
+# undef libc_hidden_builtin_def
+# define libc_hidden_builtin_def(name)
+# undef weak_alias
+# define weak_alias(name, alias)
+# include <sysdeps/riscv/zbb/strlen.S>
+#endif
new file mode 100644
@@ -0,0 +1,117 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library. If not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <sysdep.h>
+#include <sys/asm.h>
+
+/* Assumptions: rvi_zbb. */
+/* Implementation from the Bitmanip specification. */
+
+#define src a0
+#define result a0
+#define addr a1
+#define data a2
+#define offset a3
+#define offset_bits a3
+#define valid_bytes a4
+#define m1 a4
+
+#if __riscv_xlen == 64
+# define REG_L ld
+# define SZREG 8
+# define PTRLOG 3
+#else
+# define REG_L lw
+# define SZREG 4
+# define PTRLOG 2
+#endif
+
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# define CZ clz
+# define SHIFT sll
+#else
+# define CZ ctz
+# define SHIFT srl
+#endif
+
+#ifndef STRLEN
+# define STRLEN __strlen_zbb
+#endif
+
+ENTRY (STRLEN)
+.option push
+.option arch,+zbb
+
+ /* Number of irrelevant bytes in the first word. */
+ andi offset, src, SZREG-1
+ /* Align pointer. */
+ andi addr, src, -SZREG
+
+ li valid_bytes, SZREG
+ sub valid_bytes, valid_bytes, offset
+ slli offset_bits, offset, PTRLOG
+
+ /* Get the first word. */
+ REG_L data, 0(addr)
+ /* Shift away the partial data we loaded to remove the irrelevant bytes
+ * preceeding the string with the effect of adding NUL bytes at the
+ * end of the string. */
+ SHIFT data, data, offset_bits
+ /* Convert non-NUL into 0xff and NUL into 0x00. */
+ orc.b data, data
+ /* Convert non-NUL into 0x00 and NUL into 0xff. */
+ not data, data
+ /* Search for the first set bit (corresponding to a NUL byte in the
+ * original chunk). */
+ CZ data, data
+ /* The first chunk is special: commpare against the number
+ * of valid bytes in this chunk. */
+ srli result, data, 3
+ bgtu valid_bytes, result, L(done)
+
+ /* Prepare for the word comparison loop. */
+ addi offset, addr, SZREG
+ li m1, -1
+
+ /* Our critical loop is 4 instructions and processes data in
+ * 4 byte or 8 byte chunks. */
+ .p2align 3
+L(loop):
+ REG_L data, SZREG(addr)
+ addi addr, addr, SZREG
+ orc.b data, data
+ beq data, m1, L(loop)
+
+L(epilogue):
+ not data, data
+ CZ data, data
+ /* Get number of processed words. */
+ sub offset, addr, offset
+ /* Add number of characters in the first word. */
+ add result, result, offset
+ srli data, data, 3
+ /* Add number of characters in the last word. */
+ add result, result, data
+L(done):
+ ret
+
+.option pop
+
+END (STRLEN)
+libc_hidden_builtin_def (STRLEN)
+weak_alias (STRLEN, strlen)
@@ -31,6 +31,7 @@ sysdep_routines += \
strlen \
strlen-generic \
strlen-vector \
+ strlen-zbb \
strncmp \
strncmp-generic \
strncmp-vector \
@@ -27,6 +27,7 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
size_t i = max;
bool fast_unaligned = false;
+ bool zbb_enabled = false;
bool rvv_enabled = false;
struct riscv_hwprobe pairs[2] = {
@@ -41,6 +42,9 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
if (pairs[1].value & RISCV_HWPROBE_IMA_V)
rvv_enabled = true;
+
+ if (pairs[1].value & RISCV_HWPROBE_EXT_ZBB)
+ zbb_enabled = true;
}
IFUNC_IMPL (i, name, memcpy,
@@ -66,6 +70,8 @@ __libc_ifunc_impl_list (const char *name, struct libc_ifunc_impl *array,
IFUNC_IMPL_ADD (array, i, strcpy, 1, __strcpy_generic))
IFUNC_IMPL (i, name, strlen,
+ IFUNC_IMPL_ADD (array, i, strlen, zbb_enabled,
+ __strlen_zbb)
IFUNC_IMPL_ADD (array, i, strlen, rvv_enabled,
__strlen_vector)
IFUNC_IMPL_ADD (array, i, strlen, 1, __strlen_generic))
@@ -32,11 +32,20 @@ extern __typeof (__redirect_strlen) __libc_strlen;
extern __typeof (__redirect_strlen) __strlen_generic attribute_hidden;
extern __typeof (__redirect_strlen) __strlen_vector attribute_hidden;
+extern __typeof (__redirect_strlen) __strlen_zbb attribute_hidden;
static inline __typeof (__redirect_strlen) *
select_strlen_ifunc (uint64_t dl_hwcap, __riscv_hwprobe_t hwprobe_func)
{
unsigned long long int v;
+
+ /* Testing has shown that on circa 2026 hardware a Zbb based strlen is
+ consistently faster than a V implementation. So we prefer Zbb for
+ now. I expect this will change over time. */
+ if (__riscv_hwprobe_one (hwprobe_func, RISCV_HWPROBE_KEY_IMA_EXT_0, &v) == 0
+ && (v & RISCV_HWPROBE_EXT_ZBB) == RISCV_HWPROBE_EXT_ZBB)
+ return __strlen_zbb;
+
if (__riscv_hwprobe_one (hwprobe_func, RISCV_HWPROBE_KEY_IMA_EXT_0, &v) == 0
&& (v & RISCV_HWPROBE_IMA_V) == RISCV_HWPROBE_IMA_V)
return __strlen_vector;