Skip to content

[libcxx] adds size-based __split_buffer representation to unstable ABI #139632

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

cjdb
Copy link
Contributor

@cjdb cjdb commented May 12, 2025

tl;dr We can significantly improve the runtime performance of std::vector by changing its representation from three pointers to one pointer and two integers. This document explains the details of this change, along with the justifications for making it. See the RFC for more information.

vector depends on __split_buffer for inserting elements. Changing __split_buffer to match vector's representation simplifies the model, as it eliminates the need to convert between two different representations of a contiguous buffer in the same configuration of libc++.

**tl;dr** We can significantly improve the runtime performance of
`std::vector` by changing its representation from three pointers to one
pointer and two integers. This document explains the details of this
change, along with the justifications for making it. See the [RFC] for
more information.

`vector` depends on `__split_buffer` for inserting elements. Changing
`__split_buffer` to match `vector`'s representation simplifies the
model, as it eliminates the need to convert between two different
representations of a contiguous buffer in the same configuration of
libc++.

[RFC]: TODO
@cjdb cjdb requested a review from a team as a code owner May 12, 2025 22:22
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label May 12, 2025
@llvmbot
Copy link
Member

llvmbot commented May 12, 2025

@llvm/pr-subscribers-lldb

@llvm/pr-subscribers-libcxx

Author: Christopher Di Bella (cjdb)

Changes

tl;dr We can significantly improve the runtime performance of std::vector by changing its representation from three pointers to one pointer and two integers. This document explains the details of this change, along with the justifications for making it. See the RFC for more information.

vector depends on __split_buffer for inserting elements. Changing __split_buffer to match vector's representation simplifies the model, as it eliminates the need to convert between two different representations of a contiguous buffer in the same configuration of libc++.


Patch is 41.17 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/139632.diff

3 Files Affected:

  • (modified) libcxx/include/__split_buffer (+377-172)
  • (modified) libcxx/include/__vector/vector.h (+25-23)
  • (modified) libcxx/include/deque (+14-26)
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index 21e58f4abc6b3..9710dfec774f7 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -13,10 +13,12 @@
 #include <__algorithm/max.h>
 #include <__algorithm/move.h>
 #include <__algorithm/move_backward.h>
+#include <__assert>
 #include <__config>
 #include <__iterator/distance.h>
 #include <__iterator/iterator_traits.h>
 #include <__iterator/move_iterator.h>
+#include <__memory/addressof.h>
 #include <__memory/allocate_at_least.h>
 #include <__memory/allocator.h>
 #include <__memory/allocator_traits.h>
@@ -78,23 +80,260 @@ public:
                       __split_buffer,
                       void>;
 
-  pointer __first_;
-  pointer __begin_;
-  pointer __end_;
-  _LIBCPP_COMPRESSED_PAIR(pointer, __cap_, allocator_type, __alloc_);
+  struct __data {
+    pointer __first_ = nullptr;
+    pointer __begin_ = nullptr;
+#ifndef _LIBCPP_ABI_SIZE_BASED_VECTOR
+    pointer __end_ = nullptr;
+    _LIBCPP_COMPRESSED_PAIR(pointer, __cap_ = nullptr, allocator_type, __alloc_);
+#else
+    size_type __size_ = 0;
+    _LIBCPP_COMPRESSED_PAIR(size_type, __cap_ = 0, allocator_type, __alloc_);
+#endif
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __data() = default;
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit __data(const allocator_type& __alloc)
+    : __alloc_(__alloc)
+    {}
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer first() noexcept {
+      return __first_;
+    }
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_pointer first() const noexcept {
+      return __first_;
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer begin() noexcept {
+      return __begin_;
+    }
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_pointer begin() const noexcept {
+      return __begin_;
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer end() noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __begin_ + __size_;
+#else
+      return __end_;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer end() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __begin_ + __size_;
+#else
+      return __end_;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type size() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __size_;
+#else
+      return static_cast<size_type>(__end_ - __begin_);
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool empty() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __size_ == 0;
+#else
+      return __begin_ == __end_;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type capacity() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __cap_;
+#else
+      return static_cast<size_type>(__cap_ - __first_);
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer __capacity_as_pointer() noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __first_ + __cap_;
+#else
+      return __cap_;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer __capacity_as_pointer() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __first_ + __cap_;
+#else
+      return __cap_;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __update_begin(pointer __new_begin) noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      __size_ -= __new_begin - __begin_;
+#else
+      // TODO: explain why there isn't a pointer-based analogue
+#endif
+
+      __begin_ = __new_begin;
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type __front_spare() const noexcept {
+      return static_cast<size_type>(__begin_ - __first_);
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __update_sentinel(pointer __new_end) noexcept {
+      _LIBCPP_ASSERT(__first_ <= __new_end, "__new_end cannot precede __first_");
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      __size_ += __new_end - end();
+#else
+      __end_ = __new_end;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __update_sentinel(size_type __new_size) noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      __size_ = __new_size;
+#else
+      __end_ = __begin_ + __new_size;
+#endif
+    }
 
-  __split_buffer(const __split_buffer&)            = delete;
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __update_capacity(size_type __new_capacity) noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      __cap_ = __new_capacity;
+#else
+      __cap_ = __first_ + __new_capacity;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type __back_spare() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      // `__cap_ - __end_` tells us the total number of spares when in size-mode. We need to remove
+      // the __front_spare from the count.
+      return __cap_ - __size_ - __front_spare();
+#else
+      return static_cast<size_type>(__cap_ - __end_);
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference back() noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __begin_[__size_ - 1];
+#else
+      return *(__end_ - 1);
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference back() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __begin_[__size_ - 1];
+#else
+      return *(__end_ - 1);
+#endif
+    }
+
+    template<class _Data2>
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __swap_without_allocator(_Data2& __other) noexcept {
+      std::swap(__first_, __other.__first_);
+      std::swap(__begin_, __other.__begin_);
+      std::swap(__cap_, __other.__cap_);
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      std::swap(__size_, __other.__size_);
+#else
+      std::swap(__end_, __other.__end_);
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void swap(__data& __other) noexcept {
+      __swap_without_allocator(__other);
+      std::__swap_allocator(__alloc_, __other.__alloc_);
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const noexcept {
+      if (__first_ == nullptr) {
+        if (__begin_ != nullptr) {
+          return false;
+        }
+
+        if (!empty()) {
+          return false;
+        }
+
+        if (capacity() != 0) {
+          return false;
+        }
+
+        return true;
+      }
+
+      if (__begin_ < __first_) {
+        return false;
+      }
+
+      if (capacity() < size()) {
+        return false;
+      }
+
+      if (end() < __begin_) {
+        return false;
+      }
+
+      return true;
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __is_full() const noexcept {
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      return __size_ == __cap_;
+#else
+      return __end_ == __cap_;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __reset() noexcept {
+      __first_ = nullptr;
+      __begin_ = nullptr;
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      __size_ = 0;
+      __cap_ = 0;
+#else
+      __end_ = nullptr;
+      __cap_ = nullptr;
+#endif
+    }
+
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __copy_without_alloc(__data const& __other)
+      noexcept(is_nothrow_copy_assignable<pointer>::value)
+    {
+      __first_ = __other.__first_;
+      __begin_ = __other.__begin_;
+      __cap_ = __other.__cap_;
+#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
+      __size_ = __other.__size_;
+#else
+      __end_ = __other.__end_;
+#endif
+    }
+  };
+
+  __data __data_;
+
+  __split_buffer(const __split_buffer&) = delete;
   __split_buffer& operator=(const __split_buffer&) = delete;
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __split_buffer()
-      _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
-      : __first_(nullptr), __begin_(nullptr), __end_(nullptr), __cap_(nullptr) {}
+    _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
+  : __data_{}
+  {}
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit __split_buffer(__alloc_rr& __a)
-      : __first_(nullptr), __begin_(nullptr), __end_(nullptr), __cap_(nullptr), __alloc_(__a) {}
+    _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
+  : __data_(__a)
+  {}
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit __split_buffer(const __alloc_rr& __a)
-      : __first_(nullptr), __begin_(nullptr), __end_(nullptr), __cap_(nullptr), __alloc_(__a) {}
+    _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
+  : __data_(__a)
+  {}
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
   __split_buffer(size_type __cap, size_type __start, __alloc_rr& __a);
@@ -111,36 +350,22 @@ public:
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~__split_buffer();
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator begin() _NOEXCEPT { return __begin_; }
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator begin() const _NOEXCEPT { return __begin_; }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator begin() _NOEXCEPT { return __data_.begin(); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator begin() const _NOEXCEPT { return __data_.begin(); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator end() _NOEXCEPT { return __data_.end(); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator end() const _NOEXCEPT { return __data_.end(); }
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator end() _NOEXCEPT { return __end_; }
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator end() const _NOEXCEPT { return __end_; }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT { __destruct_at_end(__data_.__begin_); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type size() const { return __data_.size(); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool empty() const { return __data_.empty(); }
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT { __destruct_at_end(__begin_); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type capacity() const { return __data_.capacity(); }
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type size() const {
-    return static_cast<size_type>(__end_ - __begin_);
-  }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference front() { return *__data_.__begin_; }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference front() const { return *__data_.__begin_; }
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool empty() const { return __end_ == __begin_; }
-
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type capacity() const {
-    return static_cast<size_type>(__cap_ - __first_);
-  }
-
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type __front_spare() const {
-    return static_cast<size_type>(__begin_ - __first_);
-  }
-
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type __back_spare() const {
-    return static_cast<size_type>(__cap_ - __end_);
-  }
-
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference front() { return *__begin_; }
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference front() const { return *__begin_; }
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference back() { return *(__end_ - 1); }
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference back() const { return *(__end_ - 1); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference back() { return __data_.back(); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference back() const { return __data_.back(); }
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void shrink_to_fit() _NOEXCEPT;
 
@@ -149,8 +374,8 @@ public:
   template <class... _Args>
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void emplace_back(_Args&&... __args);
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void pop_front() { __destruct_at_begin(__begin_ + 1); }
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void pop_back() { __destruct_at_end(__end_ - 1); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void pop_front() { __destruct_at_begin(__data_.begin() + 1); }
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void pop_back() { __destruct_at_end(__data_.end() - 1); }
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __construct_at_end(size_type __n);
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __construct_at_end(size_type __n, const_reference __x);
@@ -185,66 +410,52 @@ public:
       _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__alloc_rr>);
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const;
-
 private:
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__split_buffer& __c, true_type)
       _NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value) {
-    __alloc_ = std::move(__c.__alloc_);
+    __data_.__alloc_ = std::move(__c.__data_.__alloc_);
   }
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__split_buffer&, false_type) _NOEXCEPT {}
 
   struct _ConstructTransaction {
     _LIBCPP_CONSTEXPR_SINCE_CXX20
-    _LIBCPP_HIDE_FROM_ABI explicit _ConstructTransaction(pointer* __p, size_type __n) _NOEXCEPT
-        : __pos_(*__p),
-          __end_(*__p + __n),
-          __dest_(__p) {}
+    _LIBCPP_HIDE_FROM_ABI explicit _ConstructTransaction(__split_buffer* __parent, pointer __p, size_type __n) noexcept
+    : __pos_(__p),
+      __end_(__p + __n),
+      __parent_(__parent) {}
 
-    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~_ConstructTransaction() { *__dest_ = __pos_; }
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~_ConstructTransaction() {
+      __parent_->__data_.__update_sentinel(__pos_);
+    }
 
     pointer __pos_;
     const pointer __end_;
 
   private:
-    pointer* __dest_;
+    __split_buffer* __parent_;
   };
 };
 
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __split_buffer<_Tp, _Allocator>::__invariants() const {
-  if (__first_ == nullptr) {
-    if (__begin_ != nullptr)
-      return false;
-    if (__end_ != nullptr)
-      return false;
-    if (__cap_ != nullptr)
-      return false;
-  } else {
-    if (__begin_ < __first_)
-      return false;
-    if (__end_ < __begin_)
-      return false;
-    if (__cap_ < __end_)
-      return false;
-  }
-  return true;
+  return __data_.__invariants();
 }
 
-//  Default constructs __n objects starting at __end_
+//  Default constructs __n objects starting at `__begin_ + size()`
 //  throws if construction throws
 //  Precondition:  __n > 0
 //  Precondition:  size() + __n <= capacity()
 //  Postcondition:  size() == size() + __n
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::__construct_at_end(size_type __n) {
-  _ConstructTransaction __tx(std::addressof(this->__end_), __n);
+  _ConstructTransaction __tx(this, __data_.end(), __n);
   for (; __tx.__pos_ != __tx.__end_; ++__tx.__pos_) {
-    __alloc_traits::construct(__alloc_, std::__to_address(__tx.__pos_));
+    __alloc_traits::construct(__data_.__alloc_, std::__to_address(__tx.__pos_));
   }
 }
 
-//  Copy constructs __n objects starting at __end_ from __x
+//  Copy constructs __n objects starting at `__begin_ + size()` from __x
 //  throws if construction throws
 //  Precondition:  __n > 0
 //  Precondition:  size() + __n <= capacity()
@@ -253,30 +464,35 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::__construct_
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void
 __split_buffer<_Tp, _Allocator>::__construct_at_end(size_type __n, const_reference __x) {
-  _ConstructTransaction __tx(std::addressof(this->__end_), __n);
+  _ConstructTransaction __tx(this, __data_.end(), __n);
   for (; __tx.__pos_ != __tx.__end_; ++__tx.__pos_) {
-    __alloc_traits::construct(__alloc_, std::__to_address(__tx.__pos_), __x);
+    __alloc_traits::construct(__data_.__alloc_, std::__to_address(__tx.__pos_), __x);
   }
 }
 
-template <class _Tp, class _Allocator>
-template <class _Iterator, class _Sentinel>
+template<class _Tp, class _Allocator>
+template<class _Iterator, class _Sentinel>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void
 __split_buffer<_Tp, _Allocator>::__construct_at_end_with_sentinel(_Iterator __first, _Sentinel __last) {
-  __alloc_rr& __a = __alloc_;
+  __alloc_rr& __a = __data_.__alloc_;
   for (; __first != __last; ++__first) {
-    if (__end_ == __cap_) {
-      size_type __old_cap = __cap_ - __first_;
+    if (__data_.__back_spare() == 0) {
+      size_type __old_cap = __data_.capacity();
       size_type __new_cap = std::max<size_type>(2 * __old_cap, 8);
       __split_buffer __buf(__new_cap, 0, __a);
-      for (pointer __p = __begin_; __p != __end_; ++__p, (void)++__buf.__end_)
-        __alloc_traits::construct(__buf.__alloc_, std::__to_address(__buf.__end_), std::move(*__p));
+      pointer __buf_end = __buf.__data_.end();
+      pointer __end = __data_.end();
+      for (pointer __p = __data_.__begin_; __p != __end; ++__p, (void)++__buf_end)
+        __alloc_traits::construct(__buf.__data_.__alloc_, std::__to_address(__buf_end), std::move(*__p));
+      __buf.__data_.__update_sentinel(__buf_end);
       swap(__buf);
     }
-    __alloc_traits::construct(__a, std::__to_address(this->__end_), *__first);
-    ++this->__end_;
+
+    __alloc_traits::construct(__a, std::__to_address(__data_.end()), *__first);
+    __data_.__update_sentinel(size() + 1);
   }
 }
+
 template <class _Tp, class _Allocator>
 template <class _ForwardIterator, __enable_if_t<__has_forward_iterator_category<_ForwardIterator>::value, int> >
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void
@@ -288,92 +504,82 @@ template <class _Tp, class _Allocator>
 template <class _ForwardIterator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void
 __split_buffer<_Tp, _Allocator>::__construct_at_end_with_size(_ForwardIterator __first, size_type __n) {
-  _ConstructTransaction __tx(std::addressof(this->__end_), __n);
+  _ConstructTransaction __tx(this, __data_.end(), __n);
   for (; __tx.__pos_ != __tx.__end_; ++__tx.__pos_, (void)++__first) {
-    __alloc_traits::construct(__alloc_, std::__to_address(__tx.__pos_), *__first);
+    __alloc_traits::construct(__data_.__alloc_, std::__to_address(__tx.__pos_), *__first);
   }
 }
 
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 inline void
 __split_buffer<_Tp, _Allocator>::__destruct_at_begin(pointer __new_begin, false_type) {
-  while (__begin_ != __new_begin)
-    __alloc_traits::destroy(__alloc_, std::__to_address(__begin_++));
+  pointer __begin = __data_.__begin_;
+  while (__begin != __new_begin)
+    __alloc_traits::destroy(__data_.__alloc_, std::__to_address(__begin++));
+  __data_.__update_begin(__begin);
 }
 
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 inline void
 __split_buffer<_Tp, _Allocator>::__destruct_at_begin(pointer __new_begin, true_type) {
-  __begin_ = __new_begin;
+  __data_.__update_begin(__new_begin);
 }
 
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI void
 __split_buffer<_Tp, _Allocator>::__destruct_at_end(pointer __new_last, false_type) _NOEXCEPT {
-  while (__new_last != __end_)
-    __alloc_traits::destroy(__alloc_, std::__to_address(--__end_));
-}
-
-template <class _Tp, class _Allocator>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI void
-__split_buffer<_Tp, _Allocator>::__destruct_at_end(pointer __new_last, true_type) _NOEXCEPT {
-  __end_ = __new_last;
+  pointer __end = __data_.end();
+  while (__new_last != __end)
+    __alloc_traits::destroy(__data_.__alloc_, std::__to_address(--__end));
+  __data_.__update_sentinel(__end);
 }
 
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20
 __split_buffer<_Tp, _Allocator>::__split_buffer(size_type __cap, size_type __start, __alloc_rr...
[truncated]

Copy link

github-actions bot commented May 12, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions ,h -- libcxx/include/__split_buffer libcxx/include/__vector/vector.h libcxx/include/deque
View the diff from clang-format here.
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index 1e7d33fd4..2ea945588 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -85,7 +85,7 @@ public:
     pointer __begin_ = nullptr;
 #ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
     size_type __size_ = 0;
-    size_type __cap_ = 0;
+    size_type __cap_  = 0;
     allocator_type __alloc_;
 #else
     pointer __end_ = nullptr;
@@ -95,15 +95,10 @@ public:
     _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __data() = default;
 
     _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit __data(const allocator_type& __alloc)
-    : __alloc_(__alloc)
-    {}
+        : __alloc_(__alloc) {}
 
-    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer begin() _NOEXCEPT {
-      return __begin_;
-    }
-    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_pointer begin() const _NOEXCEPT {
-      return __begin_;
-    }
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer begin() _NOEXCEPT { return __begin_; }
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_pointer begin() const _NOEXCEPT { return __begin_; }
 
     _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer end() _NOEXCEPT {
 #ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
@@ -210,7 +205,7 @@ public:
 #endif
     }
 
-    template<class _Data2>
+    template <class _Data2>
     _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __swap_without_allocator(_Data2& __other) _NOEXCEPT {
       std::swap(__first_, __other.__first_);
       std::swap(__begin_, __other.__begin_);
@@ -266,7 +261,7 @@ public:
       __begin_ = nullptr;
 #ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
       __size_ = 0;
-      __cap_ = 0;
+      __cap_  = 0;
 #else
       __end_ = nullptr;
       __cap_ = nullptr;
@@ -274,11 +269,10 @@ public:
     }
 
     _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __copy_without_alloc(__data const& __other)
-      _NOEXCEPT_(is_nothrow_copy_assignable<pointer>::value)
-    {
+        _NOEXCEPT_(is_nothrow_copy_assignable<pointer>::value) {
       __first_ = __other.__first_;
       __begin_ = __other.__begin_;
-      __cap_ = __other.__cap_;
+      __cap_   = __other.__cap_;
 #ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
       __size_ = __other.__size_;
 #else
@@ -289,23 +283,20 @@ public:
 
   __data __data_;
 
-  __split_buffer(const __split_buffer&) = delete;
+  __split_buffer(const __split_buffer&)            = delete;
   __split_buffer& operator=(const __split_buffer&) = delete;
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __split_buffer()
-    _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
-  : __data_()
-  {}
+      _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
+      : __data_() {}
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit __split_buffer(__alloc_rr& __a)
-    _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
-  : __data_(__a)
-  {}
+      _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
+      : __data_(__a) {}
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit __split_buffer(const __alloc_rr& __a)
-    _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
-  : __data_(__a)
-  {}
+      _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
+      : __data_(__a) {}
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
   __split_buffer(size_type __cap, size_type __start, __alloc_rr& __a);
@@ -382,6 +373,7 @@ public:
       _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__alloc_rr>);
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const;
+
 private:
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__split_buffer& __c, true_type)
       _NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value) {
@@ -393,9 +385,9 @@ private:
   struct _ConstructTransaction {
     _LIBCPP_CONSTEXPR_SINCE_CXX20
     _LIBCPP_HIDE_FROM_ABI explicit _ConstructTransaction(__split_buffer* __parent, pointer __p, size_type __n) _NOEXCEPT
-    : __pos_(__p),
-      __end_(__p + __n),
-      __parent_(__parent) {}
+        : __pos_(__p),
+          __end_(__p + __n),
+          __parent_(__parent) {}
 
     _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~_ConstructTransaction() {
       __parent_->__data_.__update_sentinel(__pos_);
@@ -442,8 +434,8 @@ __split_buffer<_Tp, _Allocator>::__construct_at_end(size_type __n, const_referen
   }
 }
 
-template<class _Tp, class _Allocator>
-template<class _Iterator, class _Sentinel>
+template <class _Tp, class _Allocator>
+template <class _Iterator, class _Sentinel>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void
 __split_buffer<_Tp, _Allocator>::__construct_at_end_with_sentinel(_Iterator __first, _Sentinel __last) {
   __alloc_rr& __a = __data_.__alloc_;
@@ -453,7 +445,7 @@ __split_buffer<_Tp, _Allocator>::__construct_at_end_with_sentinel(_Iterator __fi
       size_type __new_cap = std::max<size_type>(2 * __old_cap, 8);
       __split_buffer __buf(__new_cap, 0, __a);
       pointer __buf_end = __buf.__data_.end();
-      pointer __end = __data_.end();
+      pointer __end     = __data_.end();
       for (pointer __p = __data_.__begin_; __p != __end; ++__p, (void)++__buf_end)
         __alloc_traits::construct(__buf.__data_.__alloc_, std::__to_address(__buf_end), std::move(*__p));
       __buf.__data_.__update_sentinel(__buf_end);
@@ -509,12 +501,12 @@ __split_buffer<_Tp, _Allocator>::__destruct_at_end(pointer __new_last, false_typ
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20
 __split_buffer<_Tp, _Allocator>::__split_buffer(size_type __cap, size_type __start, __alloc_rr& __a)
-  : __data_(__a) {
+    : __data_(__a) {
   _LIBCPP_ASSERT(__cap >= __start, "can't have a start point outside the capacity");
   if (__cap > 0) {
     auto __allocation = std::__allocate_at_least(__data_.__alloc_, __cap);
-    __data_.__first_ = __allocation.ptr;
-    __cap            = __allocation.count;
+    __data_.__first_  = __allocation.ptr;
+    __cap             = __allocation.count;
   }
 
   __data_.__begin_ = __data_.__first_ + __start;
@@ -548,8 +540,8 @@ __split_buffer<_Tp, _Allocator>::__split_buffer(__split_buffer&& __c, const __al
     __c.__data_.__reset();
   } else {
     auto __allocation = std::__allocate_at_least(__data_.__alloc_, __c.size());
-    __data_.__first_ = __allocation.ptr;
-    __data_.__begin_ = __data_.__first_;
+    __data_.__first_  = __allocation.ptr;
+    __data_.__begin_  = __data_.__first_;
     __data_.__update_sentinel(__data_.__first_);
     __data_.__update_capacity(__allocation.count);
     typedef move_iterator<iterator> _Ip;
@@ -627,8 +619,8 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::emplace_fron
 #endif
 }
 
-template<class _Tp, class _Allocator>
-template<class... _Args>
+template <class _Tp, class _Allocator>
+template <class... _Args>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::emplace_back(_Args&&... __args) {
   pointer __end = __data_.end();
   if (__data_.__back_spare() == 0) {
@@ -636,7 +628,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::emplace_back
       difference_type __d = __data_.__begin_ - __data_.__first_;
       __d                 = (__d + 1) / 2;
       __end               = std::move(__data_.__begin_, __end, __data_.__begin_ - __d);
-      __data_.__begin_   -= __d;
+      __data_.__begin_ -= __d;
       __data_.__update_sentinel(__end);
     } else {
       size_type __c = std::max<size_type>(2 * capacity(), 1);
@@ -650,10 +642,9 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator>::emplace_back
   __data_.__update_sentinel(++__end);
 }
 
-template<class _Tp, class _Allocator>
+template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI void
-swap(__split_buffer<_Tp, _Allocator>& __x, __split_buffer<_Tp, _Allocator>& __y) _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y)))
-{
+swap(__split_buffer<_Tp, _Allocator>& __x, __split_buffer<_Tp, _Allocator>& __y) _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) {
   __x.swap(__y);
 }
 

_LIBCPP_COMPRESSED_PAIR(pointer, __cap_, allocator_type, __alloc_);
struct __data {
pointer __first_ = nullptr;
pointer __begin_ = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should __begin_ be represented as an offset as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, or at least, not while vector uses it. vector uses __begin_ for its operations, and only refers to __first in two places (both swaps). It would be an interesting thing to explore as a potential deque optimisation, if we were to drop the vector dependency.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that a problem? We can just introduce a __begin() helper. This layout change is also (arguably mostly) affecting deque, so we might as well go all the way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very quick index of our code shows that vector has more than 1.3M uses, whereas deque has less than 20k. I haven't done any benchmarks for this, but I don't expect any improvements to be above the noise for deque. My concern is that there might be a small (but noticeable) pessimisation for vector, simply due to its overwhelming usage.

Perhaps we could investigate the impact of __begin() after the size-based vector PR lands?

Comment on lines 235 to 245
template<class _Data2>
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __swap_without_allocator(_Data2& __other) _NOEXCEPT {
std::swap(__first_, __other.__first_);
std::swap(__begin_, __other.__begin_);
std::swap(__cap_, __other.__cap_);
#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
std::swap(__size_, __other.__size_);
#else
std::swap(__end_, __other.__end_);
#endif
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a quite unfortunate function. Doesn't this result in an invalid state after the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We swap without the allocator in quite a few places. I'm happy to replace it with a regular swap, but this is faithful to those call sites.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd like that a lot more if it's feasible. Maybe as a separate patch to land before this one?

Edit: Actually, are these possibly the places where we have a reference as an allocator? If that's the case, maybe a function like __swap_buffer would be good that static_asserts that we've got a reference. (And possibly a _LIBCPP_ASSERT_INTERNAL(__alloc_ == destination_alloc)?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these are all called when the allocator is a reference, except for swap immediately below. My only aversion to adding the static assert is that it means we need to duplicate this code in swap, which isn't the worst thing, but it's a bit strange to repeat it immediately after this function's definition.

Comment on lines 252 to 282
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const _NOEXCEPT {
if (__first_ == nullptr) {
if (__begin_ != nullptr) {
return false;
}

if (!empty()) {
return false;
}

if (capacity() != 0) {
return false;
}

return true;
}

if (__begin_ < __first_) {
return false;
}

if (capacity() < size()) {
return false;
}

if (end() < __begin_) {
return false;
}

return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const _NOEXCEPT {
if (__first_ == nullptr) {
if (__begin_ != nullptr) {
return false;
}
if (!empty()) {
return false;
}
if (capacity() != 0) {
return false;
}
return true;
}
if (__begin_ < __first_) {
return false;
}
if (capacity() < size()) {
return false;
}
if (end() < __begin_) {
return false;
}
return true;
}
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const _NOEXCEPT {
if (__first_ == nullptr) {
if (__begin_ != nullptr)
return false;
if (!empty())
return false;
if (capacity() != 0)
return false;
return true;
} else {
if (__begin_ < __first_)
return false;
if (capacity() < size())
return false;
if (end() < __begin_)
return false;
return true;
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Braces have been removed. I generally agree with the LLVM style guide's position on not using else-clauses after unconditional returns, so I've left that as-is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tend to not follow that part of the style guide in libc++.

_LIBCPP_COMPRESSED_PAIR(pointer, __cap_, allocator_type, __alloc_);
struct __data {
pointer __first_ = nullptr;
pointer __begin_ = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that a problem? We can just introduce a __begin() helper. This layout change is also (arguably mostly) affecting deque, so we might as well go all the way.

Comment on lines 235 to 245
template<class _Data2>
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __swap_without_allocator(_Data2& __other) _NOEXCEPT {
std::swap(__first_, __other.__first_);
std::swap(__begin_, __other.__begin_);
std::swap(__cap_, __other.__cap_);
#ifdef _LIBCPP_ABI_SIZE_BASED_VECTOR
std::swap(__size_, __other.__size_);
#else
std::swap(__end_, __other.__end_);
#endif
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd like that a lot more if it's feasible. Maybe as a separate patch to land before this one?

Edit: Actually, are these possibly the places where we have a reference as an allocator? If that's the case, maybe a function like __swap_buffer would be good that static_asserts that we've got a reference. (And possibly a _LIBCPP_ASSERT_INTERNAL(__alloc_ == destination_alloc)?)

Comment on lines 252 to 282
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const _NOEXCEPT {
if (__first_ == nullptr) {
if (__begin_ != nullptr) {
return false;
}

if (!empty()) {
return false;
}

if (capacity() != 0) {
return false;
}

return true;
}

if (__begin_ < __first_) {
return false;
}

if (capacity() < size()) {
return false;
}

if (end() < __begin_) {
return false;
}

return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tend to not follow that part of the style guide in libc++.

Comment on lines +108 to +122
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer end() _NOEXCEPT {
#ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
return __begin_ + __size_;
#else
return __end_;
#endif
}

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer end() const _NOEXCEPT {
#ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
return __begin_ + __size_;
#else
return __end_;
#endif
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove these and instead implement __split_buffers end() as begin() + size()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code preserves what's currently in the main branch. Changing end() and empty() to use size() in the stable ABI would force pointer subtraction, which results in additional codegen.

Comment on lines +132 to +138
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool empty() const _NOEXCEPT {
#ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
return __size_ == 0;
#else
return __begin_ == __end_;
#endif
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same idea here: implement __split_buffers empty() as size() == 0?

Comment on lines +256 to +262
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __is_full() const _NOEXCEPT {
#ifdef _LIBCPP_ABI_SIZE_BASED_CONTAINERS
return __size_ == __cap_;
#else
return __end_ == __cap_;
#endif
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we can't do size() == capacity() in the places this is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for end(), except that we're doing pointer subtraction twice.

@cjdb cjdb requested a review from JDevlieghere as a code owner June 6, 2025 17:21
@llvmbot llvmbot added the lldb label Jun 6, 2025
Copy link

github-actions bot commented Jun 6, 2025

⚠️ Python code formatter, darker found issues in your code. ⚠️

You can test this locally with the following command:
darker --check --diff -r HEAD~1...HEAD libcxx/utils/gdb/libcxx/printers.py lldb/examples/synthetic/libcxx.py
View the diff from darker here.
--- lldb/examples/synthetic/libcxx.py	2025-06-06 17:20:27.000000 +0000
+++ lldb/examples/synthetic/libcxx.py	2025-06-06 17:23:34.402934 +0000
@@ -799,11 +799,13 @@
             first = map_.GetChildMemberWithName("__first_")
             map_first = first.GetValueAsUnsigned(0)
             self.map_begin = map_.GetChildMemberWithName("__begin_")
             map_begin = self.map_begin.GetValueAsUnsigned(0)
             map_end = get_buffer_end(map_, map_begin)
-            map_endcap = get_buffer_endcap(self, map_, map_begin, has_compressed_pair_layout, is_size_based)
+            map_endcap = get_buffer_endcap(
+                self, map_, map_begin, has_compressed_pair_layout, is_size_based
+            )
 
             # check consistency
             if not map_first <= map_begin <= map_end <= map_endcap:
                 logger.write("map pointers are not monotonic")
                 return

@cjdb cjdb requested a review from ldionne June 6, 2025 17:29
@cjdb
Copy link
Contributor Author

cjdb commented Jun 6, 2025

Ping. I would appreciate it if this PR could be merged in time for my presentation at Asia LLVM, which is at the start of next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. lldb
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants