Re: [PATCH 1/1] riscv: add vectorized memset, memcpy and memmove
Pincheng Wang <[email protected]> Tue, 16 Dec 2025 21:22:40 +0800
| Newsgroups | gmane.comp.lib.newlib |
|---|---|
| Message-ID | <[email protected]> |
Hi Christian, I think the answer depends quite a bit on the current state of the RISC-V toolchain. For RVV intrinsics specifically, a pratical consideration is compiler availability and compatibility. RVV intrinsics based on the v0.11 spec are supported by Clang 16 and GCC 13, while support for RVV v1.0 intrinsics only became available starting with Clang 19 and GCC 14. From that perspective, using hand-written assembly avoids introducing a hard dependency on newer compiler versions. That said, intrinsics are clearly preferable from a maintainability and readability standpoint, and are likely the better long-term direction once RVV intrinsics are consistently available across toolchains. Given these considerations, I'd be interested to hear the community's thoughts on the preferred direction here. If there is interest, I would be happy to also come up an intrinsics-based implementation for comparison. Best regards, Pincheng Wang On 2025/12/16 17:36, Christian Herber (OSS) wrote: > Hi Pincheng, > > is there a clear advantage for using assembly over using intrinsics? > > Christian > >> -----Original Message----- >> From: Pincheng Wang <[email protected]> >> Sent: Tuesday, 16 December 2025 03:20 >> To: Kito Cheng <[email protected]> >> Cc: [email protected] >> Subject: Re: [PATCH 1/1] riscv: add vectorized memset, memcpy and memmove >> >> Hi Kito, >> >> Sorry for the late reply. >> >> On 2025/12/12 16:33, Kito Cheng wrote: >>>> diff --git a/newlib/libc/machine/riscv/memcpy-asm.S >>>> b/newlib/libc/machine/riscv/memcpy-asm.S >>>> index 2771285f9..9d1d2d4bd 100644 >>>> --- a/newlib/libc/machine/riscv/memcpy-asm.S >>>> +++ b/newlib/libc/machine/riscv/memcpy-asm.S >>>> @@ -9,11 +9,11 @@ >>>> http://www.opensource.org/licenses. >>>> */ >>>> >>>> -#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) >>>> .text >>>> .global memcpy >>>> .type memcpy, @function >>>> memcpy: >>>> +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) >>> >>> This seems not right change to me, memcpy-asm.S is NOT conditional >>> compile in the Makefile, so that mean if we didn't defined >>> PREFER_SIZE_OVER_SPEED, __OPTIMIZE_SIZE__ or __riscv_v, we will have a >>> empty memcpy in memcpy-asm.o and then this will included in libc.a >>> >>> Same issue for memmove-asm.S >>> >> >> My apologies for not implementing and testing the changes thoroughly enough. >> I'll move the guard back to its original position in the next revision. >> >>>> mv a3, a0 >>>> beqz a2, 2f >>>> >>>> @@ -29,4 +29,25 @@ memcpy: >>>> ret >>>> >>>> .size memcpy, .-memcpy >>>> +#elif defined(__riscv_v) >>> >>> Suggest use __riscv_vector rather than __riscv_v, so that we can also >>> use that logic for zve* extensions. >>> >>>> + .option push >>>> + .option arch, +v >>> >>> and arch, +zve32x here rather than +v >>> >> >> Will replace macro and arch,+v to support both full V and Zve* extensions. >> >>>> + mv t0, a0 /* running dst */ >>>> + mv t1, a1 /* running src */ >>>> + beqz a2, .Ldone_copy /* n == 0 then return */ >>>> + >>>> +.Lbulk_copy: >>>> + vsetvli t2, a2, e8, m8, ta, ma /* t2 = vl (bytes) */ >>>> + vle8.v v0, (t1) >>>> + vse8.v v0, (t0) >>>> + add t0, t0, t2 >>>> + add t1, t1, t2 >>>> + sub a2, a2, t2 >>> >>> This sub can be drop >>> >>>> + bnez a2, .Lbulk_copy >>> >>> You can use either src(a1)+len(a2) or dst(a0)+len(a2) for loop condition: >>> >>> something like: >>> >>> void * >>> memcpy(unsigned char *dst, const unsigned char *src, >>> const size_t sz) { >>> const unsigned char *end = dst + sz; >>> while (dst != end) >>> *dst++ = *src++; >>> return dst; >>> } >>> >>> This optimization could be applied on other function as well >>> >> >> Thanks for the suggestion. Will restructure the loop conditions as you suggested. >> >>>> + /* fallthrough */ >>>> + >>>> +.Ldone_copy: >>>> + ret >>>> +.size memcpy, .-memcpy >>>> +.option pop >>>> #endif >>>> diff --git a/newlib/libc/machine/riscv/memcpy.c >>>> b/newlib/libc/machine/riscv/memcpy.c >>>> index a27e0ecb1..cd58c30a5 100644 >>>> --- a/newlib/libc/machine/riscv/memcpy.c >>>> +++ b/newlib/libc/machine/riscv/memcpy.c >>>> @@ -10,7 +10,7 @@ >>>> http://www.opensource.org/licenses. >>>> */ >>>> >>>> -#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) >>>> +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) || >>>> +defined(__riscv_v) >>>> // memcpy defined in memcpy-asm.S >>>> #else >>>> >>>> diff --git a/newlib/libc/machine/riscv/memmove-asm.S >>>> b/newlib/libc/machine/riscv/memmove-asm.S >>>> index 061472ca2..5cc2e5143 100644 >>>> --- a/newlib/libc/machine/riscv/memmove-asm.S >>>> +++ b/newlib/libc/machine/riscv/memmove-asm.S >>>> @@ -9,11 +9,11 @@ >>>> http://www.opensource.org/licenses. >>>> */ >>>> >>>> -#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) >>>> .text >>>> .global memmove >>>> .type memmove, @function >>>> memmove: >>>> +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) >>>> beqz a2, .Ldone /* in case there are 0 bytes to be copied, return >> immediately */ >>>> >>>> mv a4, a0 /* copy the destination address over to a4, since >> memmove should return that address in a0 at the end */ >>>> @@ -37,4 +37,49 @@ memmove: >>>> ret >>>> >>>> .size memmove, .-memmove >>>> +#elif defined(__riscv_v) >>>> + .option push >>>> + .option arch, +v >>>> + beqz a2, .Ldone_move /* n == 0 */ >>>> + beq a0, a1, .Ldone_move /* dst == src */ >>>> + >>>> + /* overlap check */ >>>> + bgeu a1, a0, .Lforward_move /* src >= dst then forward move*/ >>>> + >>>> + sub t2, a0, a1 /* t2 = dst - src */ >>>> + bgeu t2, a2, .Lforward_move /* no overlap then forward move */ >>>> + >>>> + /* backward move */ >>>> + add t0, a0, a2 /* running dst_end */ >>>> + add t1, a1, a2 /* running src_end */ >>>> + >>>> +.Lbackward_loop: >>>> + vsetvli t3, a2, e8, m8, ta, ma /* t3 = vl (bytes) */ >>>> + sub t0, t0, t3 >>>> + sub t1, t1, t3 >>>> + vle8.v v0, (t1) >>>> + vse8.v v0, (t0) >>>> + sub a2, a2, t3 >>>> + bnez a2, .Lbackward_loop >>>> + j .Ldone_move >>> >>> `ret` rather than `j .Ldone_move` here, ret and j are both one >>> instruction, so let just return to save one more jump :) >> >> Absolutely.I'll change in the next revision. >> >> Thank you very much for the detailed and thoughtful feedback. I really appreciate >> your guidance. I'll post v2 of the patch shortly. >> >> Best regards, >> Pincheng Wang >