mirror of
https://github.com/apache/nuttx.git
synced 2026-08-27 20:30:38 +00:00
memcmp, strncmp and strcmp reach their word loops only when both pointers
are already on a register boundary:
or t0, a0, a1
andi t0, t0, SZREG-1
That asks more than the loops need. They load from the two pointers at
the same boundary, so what matters is that the two agree about where a
boundary falls, not that either is already on one. A pair offset by the
same amount can be walked up to the boundary a byte at a time and
compared a register at a time from there.
The union also holds far less often than the difference. For arbitrary
pointers on RV64 it is true about one time in 64 against one in eight,
and the case it rejects, two strings carved out of the same buffer, is
the common one.
Test the difference of the pointers, and walk to the boundary first.
arch_strcpy.S and arch_memcpy.S already do this. Keeping every access
aligned is not only faster here: the base ISA does not require misaligned
loads and stores to be supported at all, so a routine in a machine
directory cannot assume one will work, whatever it costs.
Measured on a 1.4 GHz rv64, source and destination misaligned by one:
before after
memcmp 32K 34.4 458.0 MB/s
strncmp 32K 32.4 253.0 MB/s
strcmp 32K 41.0 280.0 MB/s
Each of those was the rate of the byte loop the word loop was meant to
replace. Pointers that genuinely disagree still take the byte loop, and
the aligned rates are unchanged.
The measurements come from the benchmark in apache/nuttx-apps#3706.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
232 lines
4.4 KiB
ArmAsm
232 lines
4.4 KiB
ArmAsm
/****************************************************************************
|
|
* libs/libc/machine/risc-v/arch_strcmp.S
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
* SPDX-FileCopyrightText: 2017 SiFive Inc. All rights reserved.
|
|
*
|
|
* This copyrighted material is made available to anyone wishing to use,
|
|
* modify, copy, or redistribute it subject to the terms and conditions
|
|
* of the FreeBSD License. This program is distributed in the hope that
|
|
* it will be useful, but WITHOUT ANY WARRANTY expressed or implied,
|
|
* including the implied warranties of MERCHANTABILITY or FITNESS FOR
|
|
* A PARTICULAR PURPOSE. A copy of this license is available at
|
|
* http://www.opensource.org/licenses.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "libc.h"
|
|
|
|
#ifdef LIBC_BUILD_STRCMP
|
|
|
|
#include "asm.h"
|
|
|
|
.text
|
|
.globl ARCH_LIBCFUN(strcmp)
|
|
.type ARCH_LIBCFUN(strcmp), @function
|
|
ARCH_LIBCFUN(strcmp):
|
|
.cfi_sections .debug_frame
|
|
.cfi_startproc
|
|
li t2, -1
|
|
|
|
/* Two pointers the same distance past a boundary can be compared
|
|
* a register at a time once both are walked up to it. Only
|
|
* pointers that disagree about where the boundary falls need the
|
|
* byte loop, since no single aligned load serves both. Test the
|
|
* difference of the pointers, not their union.
|
|
*/
|
|
|
|
xor a4, a0, a1
|
|
and a4, a4, SZREG-1
|
|
bnez a4, .Lmisaligned
|
|
|
|
/* Same offset: walk both up to the boundary a byte at a time,
|
|
* stopping early on a difference or a terminator.
|
|
*/
|
|
|
|
and a4, a0, SZREG-1
|
|
beqz a4, .Laligned
|
|
.Lhead:
|
|
lbu a2, 0(a0)
|
|
lbu a3, 0(a1)
|
|
bne a2, a3, .Lheaddiff
|
|
addi a0, a0, 1
|
|
addi a1, a1, 1
|
|
beqz a2, .Lheadeq
|
|
and a4, a0, SZREG-1
|
|
bnez a4, .Lhead
|
|
j .Laligned
|
|
|
|
.Lheaddiff:
|
|
sub a0, a2, a3
|
|
ret
|
|
|
|
.Lheadeq:
|
|
li a0, 0
|
|
ret
|
|
|
|
.Laligned:
|
|
|
|
#if SZREG == 4
|
|
li a5, 0x7f7f7f7f
|
|
#else
|
|
ld a5, mask
|
|
#endif
|
|
|
|
.macro check_one_word i n
|
|
REG_L a2, \i*SZREG(a0)
|
|
REG_L a3, \i*SZREG(a1)
|
|
|
|
and t0, a2, a5
|
|
or t1, a2, a5
|
|
add t0, t0, a5
|
|
or t0, t0, t1
|
|
|
|
bne t0, t2, .Lnull\i
|
|
.if \i+1-\n
|
|
bne a2, a3, .Lmismatch
|
|
.else
|
|
add a0, a0, \n*SZREG
|
|
add a1, a1, \n*SZREG
|
|
beq a2, a3, .Lloop
|
|
# fall through to .Lmismatch
|
|
.endif
|
|
.endm
|
|
|
|
.macro foundnull i n
|
|
.ifne \i
|
|
.Lnull\i:
|
|
add a0, a0, \i*SZREG
|
|
add a1, a1, \i*SZREG
|
|
.ifeq \i-1
|
|
.Lnull0:
|
|
.endif
|
|
bne a2, a3, .Lmisaligned
|
|
li a0, 0
|
|
ret
|
|
.endif
|
|
.endm
|
|
|
|
.Lloop:
|
|
# examine full words at a time, favoring strings of a couple dozen chars
|
|
#if __riscv_xlen == 32
|
|
check_one_word 0 5
|
|
check_one_word 1 5
|
|
check_one_word 2 5
|
|
check_one_word 3 5
|
|
check_one_word 4 5
|
|
#else
|
|
check_one_word 0 3
|
|
check_one_word 1 3
|
|
check_one_word 2 3
|
|
#endif
|
|
# backwards branch to .Lloop contained above
|
|
|
|
.Lmismatch:
|
|
# words don't match, but a2 has no null byte.
|
|
|
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
|
|
#if __riscv_xlen == 64
|
|
sll a4, a2, 48
|
|
sll a5, a3, 48
|
|
bne a4, a5, .Lmismatch_upper
|
|
sll a4, a2, 32
|
|
sll a5, a3, 32
|
|
bne a4, a5, .Lmismatch_upper
|
|
#endif
|
|
sll a4, a2, 16
|
|
sll a5, a3, 16
|
|
bne a4, a5, .Lmismatch_upper
|
|
|
|
srl a4, a2, 8*SZREG-16
|
|
srl a5, a3, 8*SZREG-16
|
|
sub a0, a4, a5
|
|
and a1, a0, 0xff
|
|
bnez a1, 1f
|
|
ret
|
|
|
|
.Lmismatch_upper:
|
|
srl a4, a4, 8*SZREG-16
|
|
srl a5, a5, 8*SZREG-16
|
|
sub a0, a4, a5
|
|
and a1, a0, 0xff
|
|
bnez a1, 1f
|
|
ret
|
|
|
|
1:
|
|
and a4, a4, 0xff
|
|
and a5, a5, 0xff
|
|
sub a0, a4, a5
|
|
ret
|
|
|
|
#else
|
|
|
|
#if __riscv_xlen == 64
|
|
srl a4, a2, 48
|
|
srl a5, a3, 48
|
|
bne a4, a5, .Lmismatch_lower
|
|
srl a4, a2, 32
|
|
srl a5, a3, 32
|
|
bne a4, a5, .Lmismatch_lower
|
|
#endif
|
|
srl a4, a2, 16
|
|
srl a5, a3, 16
|
|
bne a4, a5, .Lmismatch_lower
|
|
|
|
srl a4, a2, 8
|
|
srl a5, a3, 8
|
|
bne a4, a5, 1f
|
|
and a4, a2, 0xff
|
|
and a5, a3, 0xff
|
|
1:
|
|
sub a0, a4, a5
|
|
ret
|
|
|
|
.Lmismatch_lower:
|
|
srl a2, a4, 8
|
|
srl a3, a5, 8
|
|
bne a2, a3, 1f
|
|
and a2, a4, 0xff
|
|
and a3, a5, 0xff
|
|
1:
|
|
sub a0, a2, a3
|
|
ret
|
|
|
|
#endif
|
|
|
|
.Lmisaligned:
|
|
# misaligned
|
|
lbu a2, 0(a0)
|
|
lbu a3, 0(a1)
|
|
add a0, a0, 1
|
|
add a1, a1, 1
|
|
bne a2, a3, 1f
|
|
bnez a2, .Lmisaligned
|
|
|
|
1:
|
|
sub a0, a2, a3
|
|
ret
|
|
|
|
# cases in which a null byte was detected
|
|
#if __riscv_xlen == 32
|
|
foundnull 0 5
|
|
foundnull 1 5
|
|
foundnull 2 5
|
|
foundnull 3 5
|
|
foundnull 4 5
|
|
#else
|
|
foundnull 0 3
|
|
foundnull 1 3
|
|
foundnull 2 3
|
|
#endif
|
|
.cfi_endproc
|
|
.size ARCH_LIBCFUN(strcmp), .-ARCH_LIBCFUN(strcmp)
|
|
|
|
#if SZREG == 8
|
|
.section .srodata.cst8,"aM",@progbits,8
|
|
.align 3
|
|
mask:
|
|
.dword 0x7f7f7f7f7f7f7f7f
|
|
#endif
|
|
|
|
#endif
|