[libc-commits] [libc] [libc][string] Optimize inline_memmove routine for Aarch64 (PR #232494)
Paul Kirth via libc-commits
libc-commits at lists.llvm.org
Thu Oct 15 13:52:12 PDT 2026
https://github.com/ilovepi created https://github.com/llvm/llvm-project/pull/232494
When switching to llvm-libc, Fuchsia developers noticed a performance regression from their previous implementation of memmove. The existing implementation made several assumptions that fail when not using floating point hardware, e.g. through -mgeneral-regs-only.
This patch implements the same pattern used on x86_64 to make better choices when dispatching on smaller sizes. It also avoids a mandatory stack realignment when not using the vector registers, a mandatory check of the stack canary for the large stack adjustment, and a number of register spills.
Now, under -mgeneral-regs-only, memmove becomes a leaf function and shrinks from 332 to 173 instructions. For Cortex-A73 / Cortex-A53, llvm-mca estimated cycles per call drop by 37%–50% for 8-byte disjoint copies, 60%–79% for 8–64 byte overlapping copies, and 29%–40% for 96–256 byte overlapping copies. With NEON enabled, deduplicating the small-size dispatch reduces instruction count by ~35% and avoids the is_disjoint check for copies <= 128 bytes.
Assisted-by: Gemini
>From 778055fd02e8f8c2a07abcf96dc1026e49064b41 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Wed, 14 Oct 2026 16:07:25 -0700
Subject: [PATCH] [libc][string] Optimize inline_memmove routines
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When switching to llvm-libc, Fuchisa developers noticed a performance
regression from their previous implementation of memmove. The existing
implementation made several assumptions that fail when not using
floating point hardware, e.g. through -mgeneral-regs-only.
This patch implements the same pattern used on x86_64 to make better
choices when dispatching on smaller sizes. It also avoids a mandatory
stack realignment when not using the vector registers, a mandatory
check of the stack canary for the large stack adjustment, and a number
of register spills.
Under -mgeneral-regs-only, memmove becoems a leaf function and shrinks
from 332 to 173 instructions. In llvm-mca (Cortex-A73 / Cortex-A53),
estimated cycles per call drop by 37%–50% for 8-byte disjoint copies,
60%–79% for 8–64 byte overlapping copies, and 29%–40% for 96–256 byte
overlapping copies. With NEON enabled, deduplicating the small-size
dispatch reduces static instruction count by ~35% and avoids the
is_disjoint check for copies <= 128 bytes.
Assisted-by: Gemini
---
.../memory_utils/aarch64/inline_memmove.h | 71 +++++++++++++------
libc/src/string/memory_utils/inline_memmove.h | 5 +-
2 files changed, 54 insertions(+), 22 deletions(-)
diff --git a/libc/src/string/memory_utils/aarch64/inline_memmove.h b/libc/src/string/memory_utils/aarch64/inline_memmove.h
index 6f5f53029985a7..3ec74f1f8870e3 100644
--- a/libc/src/string/memory_utils/aarch64/inline_memmove.h
+++ b/libc/src/string/memory_utils/aarch64/inline_memmove.h
@@ -17,33 +17,64 @@
namespace LIBC_NAMESPACE_DECL {
+#if defined(__ARM_NEON)
using uint128_t = generic_v128;
using uint256_t = generic_v256;
using uint512_t = generic_v512;
+using AlignT = uint256_t;
+using LoopT = uint512_t;
+#else
+using uint128_t = cpp::array<uint64_t, 2>;
+using uint256_t = cpp::array<uint64_t, 4>;
+using AlignT = uint128_t;
+using LoopT = uint256_t;
+#endif
-LIBC_INLINE void inline_memmove_aarch64(Ptr dst, CPtr src, size_t count) {
+LIBC_INLINE bool inline_memmove_small_size_aarch64(Ptr dst, CPtr src,
+ size_t count) {
if (count == 0)
- return;
- if (count == 1)
- return generic::Memmove<uint8_t>::block(dst, src);
- if (count <= 4)
- return generic::Memmove<uint16_t>::head_tail(dst, src, count);
- if (count <= 8)
- return generic::Memmove<uint32_t>::head_tail(dst, src, count);
- if (count <= 16)
- return generic::Memmove<uint64_t>::head_tail(dst, src, count);
- if (count <= 32)
- return generic::Memmove<uint128_t>::head_tail(dst, src, count);
- if (count <= 64)
- return generic::Memmove<uint256_t>::head_tail(dst, src, count);
- if (count <= 128)
- return generic::Memmove<uint512_t>::head_tail(dst, src, count);
+ return true;
+ if (count == 1) {
+ generic::Memmove<uint8_t>::block(dst, src);
+ return true;
+ }
+ if (count <= 4) {
+ generic::Memmove<uint16_t>::head_tail(dst, src, count);
+ return true;
+ }
+ if (count <= 8) {
+ generic::Memmove<uint32_t>::head_tail(dst, src, count);
+ return true;
+ }
+ if (count <= 16) {
+ generic::Memmove<uint64_t>::head_tail(dst, src, count);
+ return true;
+ }
+ if (count <= 32) {
+ generic::Memmove<uint128_t>::head_tail(dst, src, count);
+ return true;
+ }
+ if (count <= 64) {
+ generic::Memmove<uint256_t>::head_tail(dst, src, count);
+ return true;
+ }
+#if defined(__ARM_NEON)
+ if (count <= 128) {
+ generic::Memmove<uint512_t>::head_tail(dst, src, count);
+ return true;
+ }
+#endif
+ return false;
+}
+
+LIBC_INLINE void inline_memmove_follow_up_aarch64(Ptr dst, CPtr src,
+ size_t count) {
if (dst < src) {
- generic::Memmove<uint256_t>::align_forward<Arg::Src>(dst, src, count);
- return generic::Memmove<uint512_t>::loop_and_tail_forward(dst, src, count);
+ generic::Memmove<AlignT>::align_forward<Arg::Src>(dst, src, count);
+ return generic::Memmove<LoopT>::loop_and_tail_forward(dst, src, count);
} else {
- generic::Memmove<uint256_t>::align_backward<Arg::Src>(dst, src, count);
- return generic::Memmove<uint512_t>::loop_and_tail_backward(dst, src, count);
+ generic::Memmove<AlignT>::align_backward<Arg::Src>(dst, src, count);
+ return generic::Memmove<LoopT>::loop_and_tail_backward(dst, src, count);
}
}
diff --git a/libc/src/string/memory_utils/inline_memmove.h b/libc/src/string/memory_utils/inline_memmove.h
index c75100b48cc95b..d34013a3ce8b1b 100644
--- a/libc/src/string/memory_utils/inline_memmove.h
+++ b/libc/src/string/memory_utils/inline_memmove.h
@@ -27,8 +27,9 @@
#elif defined(LIBC_TARGET_ARCH_IS_AARCH64)
#include "src/string/memory_utils/aarch64/inline_memmove.h"
#define LIBC_SRC_STRING_MEMORY_UTILS_MEMMOVE_SMALL_SIZE \
- inline_memmove_no_small_size
-#define LIBC_SRC_STRING_MEMORY_UTILS_MEMMOVE_FOLLOW_UP inline_memmove_aarch64
+ inline_memmove_small_size_aarch64
+#define LIBC_SRC_STRING_MEMORY_UTILS_MEMMOVE_FOLLOW_UP \
+ inline_memmove_follow_up_aarch64
#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
#include "src/string/memory_utils/riscv/inline_memmove.h"
#define LIBC_SRC_STRING_MEMORY_UTILS_MEMMOVE_SMALL_SIZE \
More information about the libc-commits
mailing list