[libc-commits] [libc] [libc] Read known objects bytewise in the clang_vector string loads (PR #236612)
Mike Lothian via libc-commits
libc-commits at lists.llvm.org
Tue Nov 3 04:46:15 PST 2026
https://github.com/FireBurn updated https://github.com/llvm/llvm-project/pull/236612
>From 7282642b6cb03b213fc66b3e31a8446292be3c9f 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] Keep the clang_vector string loads observable to the
compiler
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 treats the bytes outside the array as undefined and may drop
the stores it does not see being read: strlen gave 30, 48 or 38 for a
six character string built with -O3 -flto=thin.
Pass the pointer through an empty asm that clobbers memory, so the
object counts as read, and the aligned address through another so the
compiler cannot reason about the load. This is the idiom memset_explicit
uses, and it costs nothing at run time.
Assisted-by: Claude
---
.../memory_utils/generic/inline_strlen.h | 21 +++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/libc/src/string/memory_utils/generic/inline_strlen.h b/libc/src/string/memory_utils/generic/inline_strlen.h
index 833ff9fbcbf80a..1cb69fa3c40063 100644
--- a/libc/src/string/memory_utils/generic/inline_strlen.h
+++ b/libc/src/string/memory_utils/generic/inline_strlen.h
@@ -16,6 +16,19 @@
namespace LIBC_NAMESPACE_DECL {
namespace clang_vector {
+// The loads below start at the aligned address at or below the string, so they
+// read outside the object on purpose. The compiler may treat such a read as
+// undefined or drop the stores it cannot see being read, which happens once a
+// call is inlined on a known array. Passing the pointer through an asm that
+// clobbers memory makes the object observable and hides the aligned address.
+LIBC_INLINE const cpp::simd<char> *aligned_base(const void *src) {
+ asm("" : : "r"(src) : "memory");
+ const cpp::simd<char> *aligned = reinterpret_cast<const cpp::simd<char> *>(
+ __builtin_align_down(src, alignof(cpp::simd<char>)));
+ asm("" : "+r"(aligned));
+ return aligned;
+}
+
// Exploit the underlying integer representation to do a variable shift.
template <typename byte_ty>
LIBC_INLINE constexpr cpp::simd_mask<byte_ty> shift_mask(cpp::simd_mask<char> m,
@@ -28,9 +41,7 @@ LIBC_INLINE constexpr cpp::simd_mask<byte_ty> shift_mask(cpp::simd_mask<char> m,
LIBC_NO_SANITIZE_OOB_ACCESS LIBC_INLINE size_t string_length(const char *src) {
constexpr cpp::simd<char> null_byte = cpp::splat('\0');
- size_t alignment = alignof(cpp::simd<char>);
- const cpp::simd<char> *aligned = reinterpret_cast<const cpp::simd<char> *>(
- __builtin_align_down(src, alignment));
+ const cpp::simd<char> *aligned = aligned_base(src);
cpp::simd<char> chars = cpp::load<cpp::simd<char>>(aligned, /*aligned=*/true);
cpp::simd_mask<char> mask = chars == null_byte;
@@ -61,9 +72,7 @@ find_first_character(const unsigned char *s, unsigned char c, size_t n) {
using Mask = cpp::simd_mask<char>;
Vector c_byte = c;
- size_t alignment = alignof(Vector);
- const Vector *aligned =
- reinterpret_cast<const Vector *>(__builtin_align_down(s, alignment));
+ const Vector *aligned = aligned_base(s);
Vector chars = cpp::load<Vector>(aligned, /*aligned=*/true);
Mask cmp_v = chars == c_byte;
More information about the libc-commits
mailing list