libs/libc/risc-v: Add optimized strlcpy.

Add word-at-a-time strlcpy using DETECTNULL for both the copy phase
and the strlen tail when truncated.  The copy loop aligns src and
processes a register at a time, falling to bytewise for the last word
containing the terminator.  When truncated, the remaining src length
is measured with a second word-at-a-time loop.

strlcpy has 46 call sites in a typical kernel image (more than strcpy)
and is not covered by newlib OPTSPEED, making it a high-value target.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: ganjing <ganjing@xiaomi.com>
This commit is contained in:
ganjing 2026-08-11 14:22:35 +08:00 committed by Xiang Xiao
parent ad9c9d41b0
commit 931d5f50d4
4 changed files with 218 additions and 0 deletions

View file

@ -84,6 +84,10 @@ if(CONFIG_RISCV_STRNCMP)
list(APPEND SRCS arch_strncmp.S)
endif()
if(CONFIG_RISCV_STRLCPY)
list(APPEND SRCS arch_strlcpy.S)
endif()
if(CONFIG_RISCV_STRCAT)
list(APPEND SRCS arch_strcat.S)
endif()

View file

@ -24,6 +24,7 @@ config RISCV_STRING_FUNCTION
select RISCV_STRRCHR
select RISCV_STRNCMP
select RISCV_STRCAT
select RISCV_STRLCPY
config RISCV_MEMCPY
bool "Enable optimized memcpy() for RISC-V"
@ -159,3 +160,11 @@ config RISCV_STRCAT
depends on ARCH_TOOLCHAIN_GNU
---help---
Enable optimized RISC-V specific strcat() library function
config RISCV_STRLCPY
bool "Enable optimized strlcpy() for RISC-V"
default n
select LIBC_ARCH_STRLCPY
depends on ARCH_TOOLCHAIN_GNU
---help---
Enable optimized RISC-V specific strlcpy() library function

View file

@ -84,6 +84,10 @@ ifeq ($(CONFIG_RISCV_STRNCMP),y)
ASRCS += arch_strncmp.S
endif
ifeq ($(CONFIG_RISCV_STRLCPY),y)
ASRCS += arch_strlcpy.S
endif
ifeq ($(CONFIG_RISCV_STRCAT),y)
ASRCS += arch_strcat.S
endif

View file

@ -0,0 +1,201 @@
/****************************************************************************
* libs/libc/machine/risc-v/arch_strlcpy.S
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#include "libc.h"
#ifdef LIBC_BUILD_STRLCPY
#include "asm.h"
.text
.global ARCH_LIBCFUN(strlcpy)
.type ARCH_LIBCFUN(strlcpy), @function
.align 2
/************************************************************************************
* Name: strlcpy
*
* size_t strlcpy(char *dst, const char *src, size_t size)
*
* Copy at most size-1 bytes from src to dst, null-terminate (if size > 0),
* and return strlen(src). Word-at-a-time DETECTNULL for both the copy
* phase and the strlen tail when truncated.
************************************************************************************/
/* DETECTNULL constants loaded from .srodata */
.section .srodata, "a"
.align SZREG
.Lmask_01:
#if SZREG == 8
.dword 0x0101010101010101
#else
.word 0x01010101
#endif
.Lmask_80:
#if SZREG == 8
.dword 0x8080808080808080
#else
.word 0x80808080
#endif
.text
ARCH_LIBCFUN(strlcpy):
.cfi_sections .debug_frame
.cfi_startproc
mv t6, a1 /* save src start for return value */
/* size == 0: skip copy, just measure src */
beqz a2, .Lmeasure
addi a2, a2, -1 /* reserve space for null terminator */
/* Bytewise copy head: align src to SZREG boundary */
.Lcopy_head:
beqz a2, .Ltruncated
andi t0, a1, SZREG-1
beqz t0, .Lcopy_word_setup
lbu t0, 0(a1)
sb t0, 0(a0)
beqz t0, .Ldone
addi a0, a0, 1
addi a1, a1, 1
addi a2, a2, -1
j .Lcopy_head
.Lcopy_word_setup:
/* Load DETECTNULL masks */
lla t2, .Lmask_01
lla t3, .Lmask_80
REG_L t2, 0(t2) /* t2 = 0x0101...01 */
REG_L t3, 0(t3) /* t3 = 0x8080...80 */
/* Word copy loop: need at least SZREG bytes remaining in size */
li t4, SZREG
.Lcopy_word:
bltu a2, t4, .Lcopy_tail
REG_L t0, 0(a1)
/* DETECTNULL: (word - 0x0101..01) & ~word & 0x8080..80 */
sub t1, t0, t2
not t5, t0
and t1, t1, t5
and t1, t1, t3
bnez t1, .Lcopy_last_word
/* No null in this word, store it */
REG_S t0, 0(a0)
addi a0, a0, SZREG
addi a1, a1, SZREG
sub a2, a2, t4
j .Lcopy_word
.Lcopy_last_word:
/* Null found within word: copy bytes until null */
lbu t0, 0(a1)
sb t0, 0(a0)
beqz t0, .Ldone
addi a0, a0, 1
addi a1, a1, 1
j .Lcopy_last_word
.Lcopy_tail:
/* Less than SZREG bytes of size left: bytewise copy */
beqz a2, .Ltruncated
lbu t0, 0(a1)
sb t0, 0(a0)
beqz t0, .Ldone
addi a0, a0, 1
addi a1, a1, 1
addi a2, a2, -1
j .Lcopy_tail
.Ltruncated:
/* Null-terminate dst */
sb zero, 0(a0)
/* Measure remaining src length with word-at-a-time */
.Lmeasure:
/* Bytewise to align src */
andi t0, a1, SZREG-1
beqz t0, .Lmeasure_word_setup
.Lmeasure_head:
lbu t0, 0(a1)
beqz t0, .Lreturn
addi a1, a1, 1
andi t0, a1, SZREG-1
bnez t0, .Lmeasure_head
.Lmeasure_word_setup:
/* Load masks if not already loaded (from truncated path) */
lla t2, .Lmask_01
lla t3, .Lmask_80
REG_L t2, 0(t2)
REG_L t3, 0(t3)
.Lmeasure_word:
REG_L t0, 0(a1)
sub t1, t0, t2
not t5, t0
and t1, t1, t5
and t1, t1, t3
bnez t1, .Lmeasure_tail
addi a1, a1, SZREG
j .Lmeasure_word
.Lmeasure_tail:
/* Find exact null position */
lbu t0, 0(a1)
beqz t0, .Lreturn
addi a1, a1, 1
j .Lmeasure_tail
.Ldone:
/* Normal completion: null found during copy */
.Lreturn:
/* Return strlen(src) = current src pos - original start */
sub a0, a1, t6
ret
.cfi_endproc
.size ARCH_LIBCFUN(strlcpy), .-ARCH_LIBCFUN(strlcpy)
#endif