[libc-commits] [libc] [libc] Hide the object from the clang_vector string loads (PR #236612)
Mike Lothian via libc-commits
libc-commits at lists.llvm.org
Wed Oct 28 18:16:07 PDT 2026
https://github.com/FireBurn created https://github.com/llvm/llvm-project/pull/236612
string_length and find_first_character load the whole aligned vector around the string, which reads outside the object on purpose. When a call is inlined with a known array, as under LTO in the unit tests, the compiler sees the bytes outside the array as undefined and returns whatever it likes: strlen gave 30, 48 or 38 for a six character string built with -O3 -flto=thin
An empty asm on the aligned pointer hides where it came from, as inline_memcpy.h does for arm. It costs no instructions. The only thing lost is folding the length of a known array at compile time when a call is inlined
AI disclosure: this patch was written with Claude Code. I reviewed the change and ran the strlen, strcat, strncat and strstr unit tests built with -O3 -march=native -flto=thin; they fail without it and pass with it
Assisted-by: Claude
>From 94eba0cb4afbf1e2bb0de9880b1f34e6fafa60fb Mon Sep 17 00:00:00 2001
From: Mike Lothian <mike at fireburn.co.uk>
Date: Thu, 29 Oct 2026 00:34:06 +0000
Subject: [PATCH] [libc] Hide the object from the clang_vector string loads
string_length and find_first_character load the whole aligned vector
around the string, which reads outside the object on purpose. When a
call is inlined with a known array, as under LTO in the unit tests, the
compiler sees the bytes outside the array as undefined and returns
whatever it likes: strlen gave 30, 48 or 38 for a six character string
built with -O3 -flto=thin. An empty asm on the aligned pointer hides
where it came from, as inline_memcpy.h does for arm
Assisted-by: Claude
---
libc/src/string/memory_utils/generic/inline_strlen.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/libc/src/string/memory_utils/generic/inline_strlen.h b/libc/src/string/memory_utils/generic/inline_strlen.h
index 833ff9fbcbf80a..46030cb97d6676 100644
--- a/libc/src/string/memory_utils/generic/inline_strlen.h
+++ b/libc/src/string/memory_utils/generic/inline_strlen.h
@@ -31,6 +31,10 @@ LIBC_NO_SANITIZE_OOB_ACCESS LIBC_INLINE size_t string_length(const char *src) {
size_t alignment = alignof(cpp::simd<char>);
const cpp::simd<char> *aligned = reinterpret_cast<const cpp::simd<char> *>(
__builtin_align_down(src, alignment));
+ // The load below reads past the string on purpose. Hide which object it
+ // came from so that inlining a call on a known array does not make the
+ // compiler treat the extra bytes as undefined.
+ __asm__("" : "+r"(aligned));
cpp::simd<char> chars = cpp::load<cpp::simd<char>>(aligned, /*aligned=*/true);
cpp::simd_mask<char> mask = chars == null_byte;
@@ -64,6 +68,7 @@ find_first_character(const unsigned char *s, unsigned char c, size_t n) {
size_t alignment = alignof(Vector);
const Vector *aligned =
reinterpret_cast<const Vector *>(__builtin_align_down(s, alignment));
+ __asm__("" : "+r"(aligned));
Vector chars = cpp::load<Vector>(aligned, /*aligned=*/true);
Mask cmp_v = chars == c_byte;
More information about the libc-commits
mailing list