[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