[libcxx-commits] [libcxx] [libc++] Don't assume alignment of empty ranges (PR #213752)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Aug 7 06:35:21 PDT 2026
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/213752
>From e3071d46f3fdf834fcae94b2aac981c60812bbcb Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 3 Aug 2026 15:38:41 -0400
Subject: [PATCH 1/2] [libc++] Don't assume alignment of empty ranges
Iterators for empty ranges are sometimes implemented using a non-aligned
sentinel value. Requiring proper alignment in that case causes a hardening
failure. In reality, there is no benefit to requiring proper alignment for
an empty range.
---
libcxx/include/__memory/valid_range.h | 14 ++--
.../algorithms/unaligned_empty_range.pass.cpp | 82 +++++++++++++++++++
2 files changed, 91 insertions(+), 5 deletions(-)
create mode 100644 libcxx/test/libcxx/algorithms/unaligned_empty_range.pass.cpp
diff --git a/libcxx/include/__memory/valid_range.h b/libcxx/include/__memory/valid_range.h
index 32c98588fe39a..6edd68d53c49c 100644
--- a/libcxx/include/__memory/valid_range.h
+++ b/libcxx/include/__memory/valid_range.h
@@ -50,7 +50,9 @@ __is_valid_range(const _Tp* __first, const _Tp* __last) {
// This function allows the compiler to assume that [__first, __last) is a valid range as defined above.
//
// In practice, we only add explicit assumptions for bullets (1) and (3). These assumptions allow (currently only
-// clang-based compilers) to auto-vectorize algorithms that contain early returns.
+// clang-based compilers) to auto-vectorize algorithms that contain early returns. Note that we don't enforce
+// alignment assumptions for empty ranges since it is a common occurence to use unaligned sentinel values to
+// implement iterators of empty ranges.
template <class _Iter, class _Sent>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
__assume_valid_range([[__maybe_unused__]] _Iter&& __first, [[__maybe_unused__]] _Sent&& __last) {
@@ -60,10 +62,12 @@ __assume_valid_range([[__maybe_unused__]] _Iter&& __first, [[__maybe_unused__]]
_LIBCPP_ASSERT_INTERNAL(std::__is_valid_range(std::__to_address(__first), std::__to_address(__last)),
"Valid range assumption does not hold");
if (!__libcpp_is_constant_evaluated()) {
- using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type;
- __builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type));
- (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first));
- (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last));
+ if (__first != __last) {
+ using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type;
+ __builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type));
+ (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first));
+ (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last));
+ }
}
}
#endif
diff --git a/libcxx/test/libcxx/algorithms/unaligned_empty_range.pass.cpp b/libcxx/test/libcxx/algorithms/unaligned_empty_range.pass.cpp
new file mode 100644
index 0000000000000..8cd9bcd67f870
--- /dev/null
+++ b/libcxx/test/libcxx/algorithms/unaligned_empty_range.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Some algorithms make alignment assumptions for optimization purposes. However, it is
+// fairly common for data structures to implement iterators for empty ranges using
+// sentinel values, which may not be properly aligned. This test ensures that we don't
+// make alignment assumptions for empty ranges, which would break that use case.
+
+// UNSUPPORTED: c++03
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <ranges>
+
+#include "test_macros.h"
+
+// Returns a pointer that doesn't point to any object at all and that is not suitably
+// aligned for T. This is the sort of sentinel value that user code sometimes uses to
+// represent an empty range.
+template <class T>
+T* unaligned_sentinel() {
+ return reinterpret_cast<T*>(static_cast<std::uintptr_t>(1));
+}
+
+template <class T>
+void test() {
+ T* first = unaligned_sentinel<T>();
+ T* last = first;
+ T value{};
+ auto pred = [](T const&) { return true; };
+
+ assert(std::find(first, last, value) == last);
+ assert(std::find_if(first, last, pred) == last);
+ assert(std::find_if_not(first, last, pred) == last);
+ assert(std::any_of(first, last, pred) == false);
+ assert(std::all_of(first, last, pred) == true);
+ assert(std::none_of(first, last, pred) == true);
+ assert(std::remove(first, last, value) == last);
+ assert(std::remove_if(first, last, pred) == last);
+
+#if TEST_STD_VER >= 20
+ // (iterator, sentinel) overloads
+ assert(std::ranges::find(first, last, value) == last);
+ assert(std::ranges::find_if(first, last, pred) == last);
+ assert(std::ranges::find_if_not(first, last, pred) == last);
+ assert(std::ranges::any_of(first, last, pred) == false);
+ assert(std::ranges::all_of(first, last, pred) == true);
+ assert(std::ranges::none_of(first, last, pred) == true);
+ assert(std::ranges::remove(first, last, value).begin() == last);
+ assert(std::ranges::remove_if(first, last, pred).begin() == last);
+
+ // (range) overloads
+ std::ranges::subrange range(first, last);
+ assert(std::ranges::find(range, value) == last);
+ assert(std::ranges::find_if(range, pred) == last);
+ assert(std::ranges::find_if_not(range, pred) == last);
+ assert(std::ranges::any_of(range, pred) == false);
+ assert(std::ranges::all_of(range, pred) == true);
+ assert(std::ranges::none_of(range, pred) == true);
+ assert(std::ranges::remove(range, value).begin() == last);
+ assert(std::ranges::remove_if(range, pred).begin() == last);
+#endif
+}
+
+struct alignas(16) Overaligned {
+ int value = 0;
+ friend bool operator==(Overaligned const& x, Overaligned const& y) { return x.value == y.value; }
+};
+
+int main(int, char**) {
+ test<int>();
+ test<double>(); // not trivially equality comparable
+ test<Overaligned>();
+
+ return 0;
+}
>From b9a710fd57d981a10bf63887f01272d171e7bcb7 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 7 Aug 2026 09:35:03 -0400
Subject: [PATCH 2/2] TEMPORARY: remove alignment assumption to check if our
benchmarks detect it
---
libcxx/include/__algorithm/find_if.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libcxx/include/__algorithm/find_if.h b/libcxx/include/__algorithm/find_if.h
index 2a96e13ce3546..2222ee3071dfa 100644
--- a/libcxx/include/__algorithm/find_if.h
+++ b/libcxx/include/__algorithm/find_if.h
@@ -24,7 +24,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <class _Iter, class _Sent, class _Pred, class _Proj>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 _Iter
__find_if(_Iter __first, _Sent __last, _Pred&& __pred, _Proj&& __proj) {
- std::__assume_valid_range(__first, __last);
+ // std::__assume_valid_range(__first, __last);
for (; __first != __last; ++__first)
if (std::__invoke(__pred, std::__invoke(__proj, *__first)))
More information about the libcxx-commits
mailing list