riscv: Implement Zbb based strlen and prefer it over the RVV based strlen implementation when Zbb is available

Message ID f9a820a0-6bcc-41a0-b888-e81c6f48fb54@oss.qualcomm.com (mailing list archive)
State New
Headers
Series 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

Jeffrey Law June 11, 2026, 4:09 a.m. UTC
  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

Jeffrey Law June 11, 2026, 2 p.m. UTC | #1
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
  
Peter Bergner June 11, 2026, 5:41 p.m. UTC | #2
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
  
Adhemerval Zanella Netto June 11, 2026, 7:51 p.m. UTC | #3
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.
  
Jeffrey Law June 11, 2026, 9:30 p.m. UTC | #4
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
  
Jeffrey Law June 11, 2026, 9:32 p.m. UTC | #5
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
  
Adhemerval Zanella Netto June 12, 2026, 12:33 p.m. UTC | #6
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;
 }
  
Peter Bergner June 12, 2026, 4:56 p.m. UTC | #7
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
  
Adhemerval Zanella Netto June 12, 2026, 6:12 p.m. UTC | #8
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.
  
Jeffrey Law June 13, 2026, 4:46 a.m. UTC | #9
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
  
Adhemerval Zanella Netto June 15, 2026, 12:49 p.m. UTC | #10
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.
  
Peter Bergner June 19, 2026, 10:48 p.m. UTC | #11
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
  

Patch

diff --git a/sysdeps/riscv/multiarch/strlen-zbb.S b/sysdeps/riscv/multiarch/strlen-zbb.S
new file mode 100644
index 0000000000..ed449157a7
--- /dev/null
+++ b/sysdeps/riscv/multiarch/strlen-zbb.S
@@ -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
diff --git a/sysdeps/riscv/zbb/strlen.S b/sysdeps/riscv/zbb/strlen.S
new file mode 100644
index 0000000000..6fe1f85d26
--- /dev/null
+++ b/sysdeps/riscv/zbb/strlen.S
@@ -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)
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile b/sysdeps/unix/sysv/linux/riscv/multiarch/Makefile
index 30d92c1ba9..0e42022fab 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 \
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 8027578529..4b0ac4ea51 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/ifunc-impl-list.c
@@ -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))
diff --git a/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c b/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c
index 9975286b85..f2f0b6e0a4 100644
--- a/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c
+++ b/sysdeps/unix/sysv/linux/riscv/multiarch/strlen.c
@@ -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;