riscv: avoid vle8ff in vector strcmp loop

Message ID 20260614105517.5201-1-daichengrong@iscas.ac.cn (mailing list archive)
State Changes Requested
Headers
Series riscv: avoid vle8ff in vector strcmp loop |

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-arm success Build passed
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

daichengrong June 14, 2026, 10:55 a.m. UTC
  Limit vl by the minimum distance from the input pointers to the next 4K
boundary, and use normal vle8.v loads in the main loop.  This keeps the
loads page-safe while avoiding the vl update dependency from vle8ff.v.

Signed-off-by: daichengrong <daichengrong@iscas.ac.cn>
---
 sysdeps/riscv/rvv/strcmp.S | 110 +++++++++++++++++++++----------------
 1 file changed, 64 insertions(+), 46 deletions(-)
  

Comments

Carlos O'Donell June 15, 2026, 1:06 p.m. UTC | #1
On 6/14/26 6:55 AM, daichengrong wrote:
> Limit vl by the minimum distance from the input pointers to the next 4K
> boundary, and use normal vle8.v loads in the main loop.  This keeps the
> loads page-safe while avoiding the vl update dependency from vle8ff.v.

Do you have microbenchmark data that supports this change?

Generally for such changes we would like to see "make bench" showing a difference.
  
> Signed-off-by: daichengrong <daichengrong@iscas.ac.cn>
> ---
>   sysdeps/riscv/rvv/strcmp.S | 110 +++++++++++++++++++++----------------
>   1 file changed, 64 insertions(+), 46 deletions(-)
> 
> diff --git a/sysdeps/riscv/rvv/strcmp.S b/sysdeps/riscv/rvv/strcmp.S
> index a2f6865581..d32d8f490a 100644
> --- a/sysdeps/riscv/rvv/strcmp.S
> +++ b/sysdeps/riscv/rvv/strcmp.S
> @@ -35,59 +35,77 @@
>   #define vstr1 v0
>   #define vstr2 v8
>   #define vmask1 v16
> -#define vmask2 v17
> +#define vmask2 v24
>   
>   ENTRY (STRCMP)
>   .option push
>   .option arch, +v
> -    /* lmul=1 */
> -L(Loop):
> -    vsetvli ivl, zero, e8, m1, ta, ma
> -    vle8ff.v vstr1, (str1)
> -    /* Check if vstr1[i] == 0 */
> -    vmseq.vx vmask1, vstr1, zero
> -
> -    vle8ff.v vstr2, (str2)
> -    /* Check if vstr1[i] != vstr2[i] */
> -    vmsne.vv vmask2, vstr1, vstr2
> -
> -    /* Find the index x for vstr1[x] == 0 */
> -    vfirst.m temp1, vmask1
> -    /* Find the index x for vstr1[x] != vstr2[x] */
> -    vfirst.m temp2, vmask2
> -
> -    bgez temp1, L(check1)
> -    bgez temp2, L(check2)
> -
> -    /* Get the current vl updated by vle8ff. */
> -    csrr ivl, vl
> -    add str1, str1, ivl
> -    add str2, str2, ivl
> -    j L(Loop)
> -
> -    /* temp1 >= 0 */
> -L(check1):
> -    bltz temp2, L(return_at_nul)
> -    blt temp2, temp1, L(check2)
> -L(return_at_nul):
> -    /* temp2 < 0 */
> -    /* temp2 >= 0 && temp1 < temp2 */
> -    add str1, str1, temp1
> -    add str2, str2, temp1
> -    lbu temp1, 0(str1)
> -    lbu temp2, 0(str2)
> -    sub result, temp1, temp2
> +    li          t0, 4095
> +    li          t3, 4096
> +L(loop):
> +    and         t1, str1, t0      /* t1 = str1 offset in 4K page */
> +    and         t2, str2, t0      /* t2 = str2 offset in 4K page */
> +    sub         t4, t3, t1        /* t4 = bytes from str1 to page end */
> +    sub         t5, t3, t2        /* t5 = bytes from str2 to page end */
> +    /*
> +     * t3 = min(dist1, dist2)
> +     */
> +    bltu        t5, t4, 1f
> +    mv          t5, t4
> +1:
> +    /*
> +     * Set vl to min(dist, VLMAX).
> +     *
> +     * If far from page boundary, vl = VLMAX.
> +     * If close to page boundary, vl = dist.
> +     */
> +
> +    vsetvli     ivl, t5, e8, m4, ta, ma
> +    /*
> +     * This load will not cross a 4K boundary for either pointer.
> +     */
> +    vle8.v      vstr1, (str1)
> +    vle8.v      vstr2, (str2)
> +
> +    /*
> +     * str1 == 0 means string ended.
> +     * str1 != str2 means strcmp result is decided.
> +     */
> +    vmseq.vx    vmask1, vstr1, zero
> +    vmsne.vv    vmask2, vstr1, vstr2
> +
> +    vfirst.m    temp2, vmask2
> +    vfirst.m    temp1, vmask1
> +
> +    bgez        temp2, L(check_nul)
> +    bgez        temp1, L(ret)
> +
> +    /*
> +     * No NUL and no difference in this chunk.
> +     * Advance by actual vl.
> +     *
> +     * If this was a page-boundary-limited load, one of str1/str2 now
> +     * reaches the next 4K boundary. The next loop recomputes distance.
> +     */
> +    add         str1, str1, ivl
> +    add         str2, str2, ivl
> +    j           L(loop)
> +
> +L(check_nul):
> +    bltz        temp1, L(found)
> +    blt         temp1, temp2, L(ret)
> +L(found):
> +    add         str1, str1, temp2
> +    add         str2, str2, temp2
> +    lbu         temp1, 0(str1)
> +    lbu         temp2, 0(str2)
> +    sub         result, temp1, temp2
>       ret
>   
> -    /* temp1 < 0 */
> -    /* temp2 >= 0 */
> -L(check2):
> -    add str1, str1, temp2
> -    add str2, str2, temp2
> -    lbu temp1, 0(str1)
> -    lbu temp2, 0(str2)
> -    sub result, temp1, temp2
> +L(ret):
> +    li          result, 0
>       ret
> +
>   .option pop
>   END (STRCMP)
>   libc_hidden_builtin_def (strcmp)
  
Florian Weimer June 15, 2026, 1:54 p.m. UTC | #2
> Limit vl by the minimum distance from the input pointers to the next 4K
> boundary, and use normal vle8.v loads in the main loop.  This keeps the
> loads page-safe while avoiding the vl update dependency from vle8ff.v.

Doesn't this make the implementation incompatible with the Zimt (memory
tagging) extension?  Or isn't this a problem today?

Thanks,
Florian
  
Florian Weimer June 15, 2026, 1:54 p.m. UTC | #3
* Carlos O'Donell:

> On 6/14/26 6:55 AM, daichengrong wrote:
>> Limit vl by the minimum distance from the input pointers to the next 4K
>> boundary, and use normal vle8.v loads in the main loop.  This keeps the
>> loads page-safe while avoiding the vl update dependency from vle8ff.v.
>
> Do you have microbenchmark data that supports this change?
>
> Generally for such changes we would like to see "make bench" showing a
> difference.

I think asking for performance data from unreleased CPUs is unfair.
I don't know if this applies in this instance, though.

Thanks,
Florian
  

Patch

diff --git a/sysdeps/riscv/rvv/strcmp.S b/sysdeps/riscv/rvv/strcmp.S
index a2f6865581..d32d8f490a 100644
--- a/sysdeps/riscv/rvv/strcmp.S
+++ b/sysdeps/riscv/rvv/strcmp.S
@@ -35,59 +35,77 @@ 
 #define vstr1 v0
 #define vstr2 v8
 #define vmask1 v16
-#define vmask2 v17
+#define vmask2 v24
 
 ENTRY (STRCMP)
 .option push
 .option arch, +v
-    /* lmul=1 */
-L(Loop):
-    vsetvli ivl, zero, e8, m1, ta, ma
-    vle8ff.v vstr1, (str1)
-    /* Check if vstr1[i] == 0 */
-    vmseq.vx vmask1, vstr1, zero
-
-    vle8ff.v vstr2, (str2)
-    /* Check if vstr1[i] != vstr2[i] */
-    vmsne.vv vmask2, vstr1, vstr2
-
-    /* Find the index x for vstr1[x] == 0 */
-    vfirst.m temp1, vmask1
-    /* Find the index x for vstr1[x] != vstr2[x] */
-    vfirst.m temp2, vmask2
-
-    bgez temp1, L(check1)
-    bgez temp2, L(check2)
-
-    /* Get the current vl updated by vle8ff. */
-    csrr ivl, vl
-    add str1, str1, ivl
-    add str2, str2, ivl
-    j L(Loop)
-
-    /* temp1 >= 0 */
-L(check1):
-    bltz temp2, L(return_at_nul)
-    blt temp2, temp1, L(check2)
-L(return_at_nul):
-    /* temp2 < 0 */
-    /* temp2 >= 0 && temp1 < temp2 */
-    add str1, str1, temp1
-    add str2, str2, temp1
-    lbu temp1, 0(str1)
-    lbu temp2, 0(str2)
-    sub result, temp1, temp2
+    li          t0, 4095
+    li          t3, 4096
+L(loop):
+    and         t1, str1, t0      /* t1 = str1 offset in 4K page */
+    and         t2, str2, t0      /* t2 = str2 offset in 4K page */
+    sub         t4, t3, t1        /* t4 = bytes from str1 to page end */
+    sub         t5, t3, t2        /* t5 = bytes from str2 to page end */
+    /*
+     * t3 = min(dist1, dist2)
+     */
+    bltu        t5, t4, 1f
+    mv          t5, t4
+1:
+    /*
+     * Set vl to min(dist, VLMAX).
+     *
+     * If far from page boundary, vl = VLMAX.
+     * If close to page boundary, vl = dist.
+     */
+
+    vsetvli     ivl, t5, e8, m4, ta, ma
+    /*
+     * This load will not cross a 4K boundary for either pointer.
+     */
+    vle8.v      vstr1, (str1)
+    vle8.v      vstr2, (str2)
+
+    /*
+     * str1 == 0 means string ended.
+     * str1 != str2 means strcmp result is decided.
+     */
+    vmseq.vx    vmask1, vstr1, zero
+    vmsne.vv    vmask2, vstr1, vstr2
+
+    vfirst.m    temp2, vmask2
+    vfirst.m    temp1, vmask1
+
+    bgez        temp2, L(check_nul)
+    bgez        temp1, L(ret)
+
+    /*
+     * No NUL and no difference in this chunk.
+     * Advance by actual vl.
+     *
+     * If this was a page-boundary-limited load, one of str1/str2 now
+     * reaches the next 4K boundary. The next loop recomputes distance.
+     */
+    add         str1, str1, ivl
+    add         str2, str2, ivl
+    j           L(loop)
+
+L(check_nul):
+    bltz        temp1, L(found)
+    blt         temp1, temp2, L(ret)
+L(found):
+    add         str1, str1, temp2
+    add         str2, str2, temp2
+    lbu         temp1, 0(str1)
+    lbu         temp2, 0(str2)
+    sub         result, temp1, temp2
     ret
 
-    /* temp1 < 0 */
-    /* temp2 >= 0 */
-L(check2):
-    add str1, str1, temp2
-    add str2, str2, temp2
-    lbu temp1, 0(str1)
-    lbu temp2, 0(str2)
-    sub result, temp1, temp2
+L(ret):
+    li          result, 0
     ret
+
 .option pop
 END (STRCMP)
 libc_hidden_builtin_def (strcmp)