[libcxx-commits] [libcxx] [libc++] Assume separate storage in vector (PR #225325)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 6 05:29:17 PDT 2026


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/225325

>From f863bfc5edea81449c8ec1f0447ad19e0aad0354 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Tue, 22 Sep 2026 10:14:43 +0200
Subject: [PATCH] [libc++] Assume separate storage in vector

---
 libcxx/include/__utility/assume.h             | 16 ++++++++++++++
 libcxx/include/__vector/layout.h              |  9 ++++++++
 .../sequence/sequence_container_benchmarks.h  | 22 +++++++++++++++++++
 .../containers/sequence/vector.bench.cpp      |  1 +
 4 files changed, 48 insertions(+)

diff --git a/libcxx/include/__utility/assume.h b/libcxx/include/__utility/assume.h
index 72a5efbca7b50a9..4734bb6ca6af5f0 100644
--- a/libcxx/include/__utility/assume.h
+++ b/libcxx/include/__utility/assume.h
@@ -10,6 +10,7 @@
 #define _LIBCPP___UTILITY_ASSUME_H
 
 #include <__config>
+#include <__type_traits/is_constant_evaluated.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -25,6 +26,21 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 _LIBCPP_HIDE_FROM_ABI inline _LIBCPP_CONSTEXPR_SINCE_CXX14 void __assume(bool __cond) { [[__assume__(__cond)]]; }
 
+// This function allows the compiler to assume that `__a` and `__b` can never alias each other, similar to `__restrict`.
+// However, this assume must also hold if the function is inlined, so effectively the two pointers must never be part of
+// the same allocation according to the LLVM semantics. This is currently only the case for stack allocated objects as
+// well as pointers returned from the `malloc`-family of functions and `::operator new` family of functions. Other
+// pointers may be considered part of the same allocation, in which case this assume could lead to incorrect
+// optimizations.
+_LIBCPP_HIDE_FROM_ABI inline _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__assume_separate_storage([[__maybe_unused__]] const void* __a, [[__maybe_unused__]] const void* __b) {
+#if __has_builtin(__builtin_assume_separate_storage)
+  // __builtin_assume_separate_storage claims constexpr support, but it's broken. See https://llvm.org/PR225335.
+  if (!__libcpp_is_constant_evaluated())
+    __builtin_assume_separate_storage(__a, __b);
+#endif
+}
+
 _LIBCPP_END_NAMESPACE_STD
 
 _LIBCPP_POP_MACROS
diff --git a/libcxx/include/__vector/layout.h b/libcxx/include/__vector/layout.h
index af03556dc2636de..57ee30474f7113f 100644
--- a/libcxx/include/__vector/layout.h
+++ b/libcxx/include/__vector/layout.h
@@ -19,6 +19,7 @@
 #include <__memory/uninitialized_algorithms.h>
 #include <__split_buffer>
 #include <__type_traits/is_nothrow_constructible.h>
+#include <__utility/assume.h>
 #include <__utility/exchange.h>
 #include <__utility/move.h>
 #include <__utility/swap.h>
@@ -159,19 +160,27 @@ class __vector_layout {
   /// `__begin_ptr()` is not called `data()` because `vector::data()` returns `T*`, but `__begin_`
   /// is allowed to be a fancy pointer.
   [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer __begin_ptr() _NOEXCEPT {
+    if _LIBCPP_CONSTEXPR (__is_std_allocator_v<allocator_type>)
+      std::__assume_separate_storage(this, std::__to_address(__begin_));
     return __begin_;
   }
 
   [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_pointer __begin_ptr() const _NOEXCEPT {
+    if _LIBCPP_CONSTEXPR (__is_std_allocator_v<allocator_type>)
+      std::__assume_separate_storage(this, std::__to_address(__begin_));
     return __begin_;
   }
 
   /// Returns a built-in pointer to the beginning of the buffer.
   [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI _Tp* __data() _NOEXCEPT {
+    if _LIBCPP_CONSTEXPR (__is_std_allocator_v<allocator_type>)
+      std::__assume_separate_storage(this, std::__to_address(__begin_));
     return std::__to_address(__begin_);
   }
 
   [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI _Tp const* __data() const _NOEXCEPT {
+    if _LIBCPP_CONSTEXPR (__is_std_allocator_v<allocator_type>)
+      std::__assume_separate_storage(this, std::__to_address(__begin_));
     return std::__to_address(__begin_);
   }
 
diff --git a/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h b/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
index 07956ec0bea95de..432b7883d923a7e 100644
--- a/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
@@ -612,6 +612,28 @@ void sequence_container_benchmarks(std::string container) {
           c.insert(c.end(), value); // re-insert an element at the end to avoid needing a new container
         }
       });
+
+    if constexpr (std::is_integral_v<ValueType>) {
+      for (auto gen : generators)
+        bench("index-loop" + tostr(gen), [gen](auto& st) TEST_ALIGN_BENCHMARK {
+          const auto size = st.range(0);
+          std::vector<ValueType> in;
+          std::generate_n(std::back_inserter(in), size, gen);
+          DoNotOptimizeData(in);
+
+          Container c(in.begin(), in.end());
+
+          // Necessary to prevent the compiler from knowing that `c` and `c.begin()` don't alias purely based on how
+          // `c.begin()` came to be.
+          DoNotOptimize(c);
+
+          for (auto _ : st) {
+            for (size_t i = 0; i != c.size(); ++i)
+              c[i] = std::make_unsigned_t<ValueType>(c[i]) + 1; // Do something cheap and always defined
+            DoNotOptimize(c);
+          }
+        });
+    }
   }
 }
 
diff --git a/libcxx/test/benchmarks/containers/sequence/vector.bench.cpp b/libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
index a62a75b68b1bfa7..e9b0f23dc147388 100644
--- a/libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
+++ b/libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
@@ -27,6 +27,7 @@ struct Generate<NothrowMoveConstructible> {
 };
 
 int main(int argc, char** argv) {
+  support::sequence_container_benchmarks<std::vector<char>>("std::vector<char>");
   support::sequence_container_benchmarks<std::vector<int>>("std::vector<int>");
   support::sequence_container_benchmarks<std::vector<std::string>>("std::vector<std::string>");
   support::sequence_container_benchmarks<std::vector<NothrowMoveConstructible>>(



More information about the libcxx-commits mailing list