[libc-commits] [libc] [libc][CPP] Add vector container and unit tests (PR #222972)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Fri Sep 11 11:57:46 PDT 2026
https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/222972
>From c1239cf45c96df7b1b31d3deec0a3750b0f83287 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 11 Sep 2026 07:39:45 +0100
Subject: [PATCH 1/2] [libc][CPP] Add vector container and unit tests
Sequential group database entrypoints require dynamically resizable
buffers to parse arbitrary-length records and member lists reliably.
Rather than managing raw heap reallocations manually within subsystem
parsers, dynamic growth is encapsulated in a dedicated cpp::vector
container.
Add cpp::vector in libc/src/__support/CPP/vector.h with move semantics,
safe fallible allocation (returning bool for out-of-memory handling
without C++ exceptions), and RAII clean-up for trivially and
non-trivially destructible types.
* Add vector header library in libc/src/__support/CPP/CMakeLists.txt
* Implement vector container in libc/src/__support/CPP/vector.h
* Add comprehensive unit tests in libc/test/src/__support/CPP/vector_test.cpp
* Register vector_test in libc/test/src/__support/CPP/CMakeLists.txt
Assisted-by: Automated tooling, human reviewed.
---
libc/src/__support/CPP/CMakeLists.txt | 16 +
libc/src/__support/CPP/vector.h | 321 ++++++++++++++++++++
libc/test/src/__support/CPP/CMakeLists.txt | 12 +
libc/test/src/__support/CPP/vector_test.cpp | 302 ++++++++++++++++++
4 files changed, 651 insertions(+)
create mode 100644 libc/src/__support/CPP/vector.h
create mode 100644 libc/test/src/__support/CPP/vector_test.cpp
diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index 6777c6a27c0cc..3a68411e39392 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -253,3 +253,19 @@ add_header_library(
.utility
.tuple
)
+
+add_header_library(
+ vector
+ HDRS
+ vector.h
+ DEPENDS
+ .new
+ .type_traits
+ .utility
+ libc.hdr.func.free
+ libc.hdr.func.realloc
+ libc.hdr.types.size_t
+ libc.src.__support.common
+ libc.src.__support.libc_assert
+ libc.src.__support.macros.config
+)
diff --git a/libc/src/__support/CPP/vector.h b/libc/src/__support/CPP/vector.h
new file mode 100644
index 0000000000000..ceffe208342a1
--- /dev/null
+++ b/libc/src/__support/CPP/vector.h
@@ -0,0 +1,321 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of vector container.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
+#define LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
+
+#include "hdr/func/free.h"
+#include "hdr/func/realloc.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/CPP/utility.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/config.h"
+
+#include <stddef.h> // For max_align_t, ptrdiff_t
+
+namespace LIBC_NAMESPACE_DECL {
+namespace cpp {
+
+template <typename T> class vector {
+ static_assert(alignof(T) <= alignof(max_align_t),
+ "Overaligned types are not supported by cpp::vector");
+
+public:
+ using value_type = T;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using reference = T &;
+ using const_reference = const T &;
+ using pointer = T *;
+ using const_pointer = const T *;
+ using iterator = T *;
+ using const_iterator = const T *;
+
+ [[nodiscard]] LIBC_INLINE constexpr size_type max_size() const noexcept {
+ return static_cast<size_type>(-1) / sizeof(T);
+ }
+
+private:
+ T *data_ = nullptr;
+ size_t size_ = 0;
+ size_t capacity_ = 0;
+
+ static constexpr size_t DEFAULT_INITIAL_CAPACITY = 16;
+
+ LIBC_INLINE void destroy_elements(size_t from, size_t to) {
+ if constexpr (!is_trivially_destructible_v<T>) {
+ while (to > from) {
+ --to;
+ data_[to].~T();
+ }
+ }
+ }
+
+ LIBC_INLINE void deallocate() {
+ if (data_) {
+ destroy_elements(0, size_);
+ ::free(data_);
+ data_ = nullptr;
+ size_ = 0;
+ capacity_ = 0;
+ }
+ }
+
+public:
+ LIBC_INLINE constexpr vector() = default;
+
+ LIBC_INLINE ~vector() { deallocate(); }
+
+ // Move constructor
+ LIBC_INLINE vector(vector &&other) noexcept
+ : data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
+ other.data_ = nullptr;
+ other.size_ = 0;
+ other.capacity_ = 0;
+ }
+
+ // Move assignment
+ LIBC_INLINE vector &operator=(vector &&other) noexcept {
+ if (this != &other) {
+ deallocate();
+ data_ = other.data_;
+ size_ = other.size_;
+ capacity_ = other.capacity_;
+ other.data_ = nullptr;
+ other.size_ = 0;
+ other.capacity_ = 0;
+ }
+ return *this;
+ }
+
+ // Copy operations are deleted to prevent accidental implicit allocations.
+ vector(const vector &) = delete;
+ vector &operator=(const vector &) = delete;
+
+ // Element access
+ LIBC_INLINE reference operator[](size_t i) {
+ LIBC_ASSERT(i < size_);
+ return data_[i];
+ }
+
+ LIBC_INLINE const_reference operator[](size_t i) const {
+ LIBC_ASSERT(i < size_);
+ return data_[i];
+ }
+
+ LIBC_INLINE pointer data() { return data_; }
+ LIBC_INLINE const_pointer data() const { return data_; }
+
+ LIBC_INLINE reference front() {
+ LIBC_ASSERT(size_ > 0);
+ return data_[0];
+ }
+
+ LIBC_INLINE const_reference front() const {
+ LIBC_ASSERT(size_ > 0);
+ return data_[0];
+ }
+
+ LIBC_INLINE reference back() {
+ LIBC_ASSERT(size_ > 0);
+ return data_[size_ - 1];
+ }
+
+ LIBC_INLINE const_reference back() const {
+ LIBC_ASSERT(size_ > 0);
+ return data_[size_ - 1];
+ }
+
+ // Iterators
+ LIBC_INLINE iterator begin() { return data_; }
+ LIBC_INLINE const_iterator begin() const { return data_; }
+ LIBC_INLINE const_iterator cbegin() const { return data_; }
+ LIBC_INLINE iterator end() { return data_ + size_; }
+ LIBC_INLINE const_iterator end() const { return data_ + size_; }
+ LIBC_INLINE const_iterator cend() const { return data_ + size_; }
+
+ // Capacity
+ [[nodiscard]] LIBC_INLINE bool empty() const { return size_ == 0; }
+ LIBC_INLINE size_t size() const { return size_; }
+ LIBC_INLINE size_t capacity() const { return capacity_; }
+
+ // Reserves at least new_cap elements. Returns true on success, false on OOM.
+ [[nodiscard]] LIBC_INLINE bool reserve(size_t new_cap) {
+ if (new_cap <= capacity_)
+ return true;
+ if (new_cap > max_size())
+ return false;
+
+ if constexpr (is_trivially_copyable_v<T>) {
+ void *new_data = ::realloc(data_, new_cap * sizeof(T));
+ if (!new_data)
+ return false;
+ data_ = static_cast<T *>(new_data);
+ capacity_ = new_cap;
+ return true;
+ } else {
+ void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
+ if (!new_raw)
+ return false;
+ T *new_data = static_cast<T *>(new_raw);
+ for (size_t i = 0; i < size_; ++i) {
+ new (new_data + i) T(cpp::move(data_[i]));
+ data_[i].~T();
+ }
+ ::free(data_);
+ data_ = new_data;
+ capacity_ = new_cap;
+ return true;
+ }
+ }
+
+ LIBC_INLINE void shrink_to_fit() {
+ if (size_ == capacity_)
+ return;
+ if (size_ == 0) {
+ reset();
+ return;
+ }
+ if constexpr (is_trivially_copyable_v<T>) {
+ void *new_data = ::realloc(data_, size_ * sizeof(T));
+ if (new_data) {
+ data_ = static_cast<T *>(new_data);
+ capacity_ = size_;
+ }
+ } else {
+ void *new_raw = ::realloc(nullptr, size_ * sizeof(T));
+ if (!new_raw)
+ return;
+ T *new_data = static_cast<T *>(new_raw);
+ for (size_t i = 0; i < size_; ++i) {
+ new (new_data + i) T(cpp::move(data_[i]));
+ data_[i].~T();
+ }
+ ::free(data_);
+ data_ = new_data;
+ capacity_ = size_;
+ }
+ }
+
+ // Modifiers
+ template <typename... Args>
+ [[nodiscard]] LIBC_INLINE bool emplace_back(Args &&...args) {
+ if (size_ < capacity_) {
+ new (data_ + size_) T(cpp::forward<Args>(args)...);
+ ++size_;
+ return true;
+ }
+
+ size_t new_cap = DEFAULT_INITIAL_CAPACITY;
+ if (capacity_ > 0) {
+ if (capacity_ > max_size() / 2)
+ return false;
+ new_cap = capacity_ * 2;
+ }
+
+ void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
+ if (!new_raw)
+ return false;
+ T *new_data = static_cast<T *>(new_raw);
+
+ // Construct the new element before moving/freeing old storage so that
+ // arguments referencing the vector's own buffer remain valid.
+ new (new_data + size_) T(cpp::forward<Args>(args)...);
+
+ for (size_t i = 0; i < size_; ++i) {
+ new (new_data + i) T(cpp::move(data_[i]));
+ data_[i].~T();
+ }
+ ::free(data_);
+ data_ = new_data;
+ capacity_ = new_cap;
+ ++size_;
+ return true;
+ }
+
+ [[nodiscard]] LIBC_INLINE bool push_back(const T &val) {
+ return emplace_back(val);
+ }
+
+ [[nodiscard]] LIBC_INLINE bool push_back(T &&val) {
+ return emplace_back(cpp::move(val));
+ }
+
+ LIBC_INLINE void pop_back() {
+ LIBC_ASSERT(size_ > 0);
+ --size_;
+ if constexpr (!is_trivially_destructible_v<T>)
+ data_[size_].~T();
+ }
+
+ LIBC_INLINE void clear() {
+ destroy_elements(0, size_);
+ size_ = 0;
+ }
+
+ // Deallocates backing memory and resets size and capacity to 0.
+ LIBC_INLINE void reset() { deallocate(); }
+
+ [[nodiscard]] LIBC_INLINE bool resize(size_t new_size) {
+ if (new_size < size_) {
+ destroy_elements(new_size, size_);
+ size_ = new_size;
+ return true;
+ }
+ if (new_size > size_) {
+ if (!reserve(new_size))
+ return false;
+ for (size_t i = size_; i < new_size; ++i)
+ new (data_ + i) T();
+ size_ = new_size;
+ }
+ return true;
+ }
+
+ [[nodiscard]] LIBC_INLINE bool resize(size_t new_size, const T &value) {
+ if (new_size < size_) {
+ destroy_elements(new_size, size_);
+ size_ = new_size;
+ return true;
+ }
+ if (new_size > size_) {
+ // Copy value before reserve in case value references this vector's data.
+ T val_copy(value);
+ if (!reserve(new_size))
+ return false;
+ for (size_t i = size_; i < new_size; ++i)
+ new (data_ + i) T(val_copy);
+ size_ = new_size;
+ }
+ return true;
+ }
+
+ LIBC_INLINE void swap(vector &other) noexcept {
+ cpp::swap(data_, other.data_);
+ cpp::swap(size_, other.size_);
+ cpp::swap(capacity_, other.capacity_);
+ }
+};
+
+template <typename T>
+LIBC_INLINE void swap(vector<T> &lhs, vector<T> &rhs) noexcept {
+ lhs.swap(rhs);
+}
+
+} // namespace cpp
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
diff --git a/libc/test/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt
index e962487a87a43..7931da87fca14 100644
--- a/libc/test/src/__support/CPP/CMakeLists.txt
+++ b/libc/test/src/__support/CPP/CMakeLists.txt
@@ -207,6 +207,18 @@ add_libc_test(
libc.src.__support.CPP.type_traits
)
+add_libc_test(
+ vector_test
+ SUITE
+ libc-cpp-utils-tests
+ SRCS
+ vector_test.cpp
+ DEPENDS
+ libc.src.__support.CPP.type_traits
+ libc.src.__support.CPP.utility
+ libc.src.__support.CPP.vector
+)
+
if(LIBC_COMPILER_HAS_EXT_VECTOR_TYPE)
add_libc_test(
simd_test
diff --git a/libc/test/src/__support/CPP/vector_test.cpp b/libc/test/src/__support/CPP/vector_test.cpp
new file mode 100644
index 0000000000000..036e64b9156ea
--- /dev/null
+++ b/libc/test/src/__support/CPP/vector_test.cpp
@@ -0,0 +1,302 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unit tests for vector.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/vector.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::cpp::vector;
+
+TEST(LlvmLibcVectorTest, InitializeEmpty) {
+ vector<int> v;
+ EXPECT_TRUE(v.empty());
+ EXPECT_EQ(v.size(), size_t(0));
+ EXPECT_EQ(v.capacity(), size_t(0));
+ EXPECT_EQ(v.data(), static_cast<int *>(nullptr));
+}
+
+TEST(LlvmLibcVectorTest, PushBackAndAccess) {
+ vector<int> v;
+ ASSERT_TRUE(v.push_back(10));
+ ASSERT_TRUE(v.push_back(20));
+ ASSERT_TRUE(v.push_back(30));
+
+ EXPECT_FALSE(v.empty());
+ EXPECT_EQ(v.size(), size_t(3));
+ EXPECT_GE(v.capacity(), size_t(3));
+
+ EXPECT_EQ(v[0], 10);
+ EXPECT_EQ(v[1], 20);
+ EXPECT_EQ(v[2], 30);
+ EXPECT_EQ(v.front(), 10);
+ EXPECT_EQ(v.back(), 30);
+ ASSERT_NE(v.data(), nullptr);
+ EXPECT_EQ(v.data()[1], 20);
+}
+
+TEST(LlvmLibcVectorTest, RangeBasedForLoop) {
+ vector<int> v;
+ for (int i = 0; i < 5; ++i)
+ ASSERT_TRUE(v.push_back(i * 10));
+
+ int expected = 0;
+ for (int val : v) {
+ EXPECT_EQ(val, expected);
+ expected += 10;
+ }
+ EXPECT_EQ(expected, 50);
+
+ // Test cbegin / cend
+ int const_expected = 0;
+ for (auto it = v.cbegin(); it != v.cend(); ++it) {
+ EXPECT_EQ(*it, const_expected);
+ const_expected += 10;
+ }
+ EXPECT_EQ(const_expected, 50);
+}
+
+TEST(LlvmLibcVectorTest, MemberTypesAndMaxSize) {
+ static_assert(LIBC_NAMESPACE::cpp::is_same_v<vector<int>::value_type, int>);
+ static_assert(LIBC_NAMESPACE::cpp::is_same_v<vector<int>::size_type, size_t>);
+ static_assert(
+ LIBC_NAMESPACE::cpp::is_same_v<vector<int>::difference_type, ptrdiff_t>);
+ static_assert(LIBC_NAMESPACE::cpp::is_same_v<vector<int>::pointer, int *>);
+ static_assert(
+ LIBC_NAMESPACE::cpp::is_same_v<vector<int>::const_pointer, const int *>);
+
+ vector<int> v;
+ EXPECT_GT(v.max_size(), size_t(0));
+ EXPECT_EQ(v.max_size(), static_cast<size_t>(-1) / sizeof(int));
+}
+
+TEST(LlvmLibcVectorTest, PopBack) {
+ vector<int> v;
+ ASSERT_TRUE(v.push_back(1));
+ ASSERT_TRUE(v.push_back(2));
+ EXPECT_EQ(v.size(), size_t(2));
+
+ v.pop_back();
+ EXPECT_EQ(v.size(), size_t(1));
+ EXPECT_EQ(v.back(), 1);
+
+ v.pop_back();
+ EXPECT_TRUE(v.empty());
+ EXPECT_EQ(v.size(), size_t(0));
+}
+
+TEST(LlvmLibcVectorTest, ReserveAndShrinkToFit) {
+ vector<int> v;
+ ASSERT_TRUE(v.reserve(100));
+ EXPECT_GE(v.capacity(), size_t(100));
+ EXPECT_EQ(v.size(), size_t(0));
+
+ ASSERT_TRUE(v.push_back(42));
+ EXPECT_EQ(v.size(), size_t(1));
+ EXPECT_GE(v.capacity(), size_t(100));
+
+ v.shrink_to_fit();
+ EXPECT_EQ(v.size(), size_t(1));
+ EXPECT_EQ(v.capacity(), size_t(1));
+ EXPECT_EQ(v[0], 42);
+}
+
+TEST(LlvmLibcVectorTest, ClearAndReset) {
+ vector<int> v;
+ for (int i = 0; i < 10; ++i)
+ ASSERT_TRUE(v.push_back(i));
+
+ size_t old_cap = v.capacity();
+ v.clear();
+ EXPECT_TRUE(v.empty());
+ EXPECT_EQ(v.size(), size_t(0));
+ EXPECT_EQ(v.capacity(), old_cap);
+
+ // Can reuse buffer after clear
+ ASSERT_TRUE(v.push_back(99));
+ EXPECT_EQ(v.size(), size_t(1));
+ EXPECT_EQ(v[0], 99);
+
+ // Reset frees buffer
+ v.reset();
+ EXPECT_TRUE(v.empty());
+ EXPECT_EQ(v.size(), size_t(0));
+ EXPECT_EQ(v.capacity(), size_t(0));
+ EXPECT_EQ(v.data(), static_cast<int *>(nullptr));
+}
+
+TEST(LlvmLibcVectorTest, Resize) {
+ vector<int> v;
+ ASSERT_TRUE(v.resize(5, 7));
+ EXPECT_EQ(v.size(), size_t(5));
+ for (size_t i = 0; i < 5; ++i)
+ EXPECT_EQ(v[i], 7);
+
+ ASSERT_TRUE(v.resize(2));
+ EXPECT_EQ(v.size(), size_t(2));
+ EXPECT_EQ(v[0], 7);
+ EXPECT_EQ(v[1], 7);
+
+ ASSERT_TRUE(v.resize(4, 9));
+ EXPECT_EQ(v.size(), size_t(4));
+ EXPECT_EQ(v[0], 7);
+ EXPECT_EQ(v[1], 7);
+ EXPECT_EQ(v[2], 9);
+ EXPECT_EQ(v[3], 9);
+}
+
+TEST(LlvmLibcVectorTest, MoveOperations) {
+ vector<int> v1;
+ ASSERT_TRUE(v1.push_back(100));
+ ASSERT_TRUE(v1.push_back(200));
+
+ // Move constructor
+ vector<int> v2(LIBC_NAMESPACE::cpp::move(v1));
+ EXPECT_EQ(v2.size(), size_t(2));
+ EXPECT_EQ(v2[0], 100);
+ EXPECT_EQ(v2[1], 200);
+ EXPECT_TRUE(v1.empty());
+ EXPECT_EQ(v1.data(), static_cast<int *>(nullptr));
+
+ // Move assignment
+ vector<int> v3;
+ v3 = LIBC_NAMESPACE::cpp::move(v2);
+ EXPECT_EQ(v3.size(), size_t(2));
+ EXPECT_EQ(v3[0], 100);
+ EXPECT_EQ(v3[1], 200);
+ EXPECT_TRUE(v2.empty());
+ EXPECT_EQ(v2.data(), static_cast<int *>(nullptr));
+}
+
+TEST(LlvmLibcVectorTest, SwapOperations) {
+ vector<int> a;
+ ASSERT_TRUE(a.push_back(1));
+ ASSERT_TRUE(a.push_back(2));
+
+ vector<int> b;
+ ASSERT_TRUE(b.push_back(30));
+ ASSERT_TRUE(b.push_back(40));
+ ASSERT_TRUE(b.push_back(50));
+
+ // Member swap
+ a.swap(b);
+ EXPECT_EQ(a.size(), size_t(3));
+ EXPECT_EQ(a[0], 30);
+ EXPECT_EQ(a[1], 40);
+ EXPECT_EQ(a[2], 50);
+ EXPECT_EQ(b.size(), size_t(2));
+ EXPECT_EQ(b[0], 1);
+ EXPECT_EQ(b[1], 2);
+
+ // Free swap
+ LIBC_NAMESPACE::cpp::swap(a, b);
+ EXPECT_EQ(a.size(), size_t(2));
+ EXPECT_EQ(a[0], 1);
+ EXPECT_EQ(a[1], 2);
+ EXPECT_EQ(b.size(), size_t(3));
+ EXPECT_EQ(b[0], 30);
+ EXPECT_EQ(b[1], 40);
+ EXPECT_EQ(b[2], 50);
+}
+
+struct LifetimeTracker {
+ static int constructed;
+ static int destructed;
+ static int destroy_log[64];
+ static int destroy_log_len;
+ int value = 0;
+ bool moved_from = false;
+
+ LifetimeTracker() : value(0) { ++constructed; }
+ explicit LifetimeTracker(int val) : value(val) { ++constructed; }
+ LifetimeTracker(const LifetimeTracker &other) : value(other.value) {
+ ++constructed;
+ }
+ LifetimeTracker(LifetimeTracker &&other) noexcept : value(other.value) {
+ other.moved_from = true;
+ ++constructed;
+ }
+ ~LifetimeTracker() {
+ ++destructed;
+ if (!moved_from && destroy_log_len < 64)
+ destroy_log[destroy_log_len++] = value;
+ }
+};
+
+int LifetimeTracker::constructed = 0;
+int LifetimeTracker::destructed = 0;
+int LifetimeTracker::destroy_log[64] = {};
+int LifetimeTracker::destroy_log_len = 0;
+
+TEST(LlvmLibcVectorTest, NonTrivialGrowthAndDestructionOrder) {
+ LifetimeTracker::constructed = 0;
+ LifetimeTracker::destructed = 0;
+ LifetimeTracker::destroy_log_len = 0;
+
+ {
+ vector<LifetimeTracker> v;
+ // Insert 20 elements to force reallocation past DEFAULT_INITIAL_CAPACITY
+ // (16)
+ for (int i = 0; i < 20; ++i)
+ ASSERT_TRUE(v.emplace_back(i));
+
+ EXPECT_EQ(v.size(), size_t(20));
+ EXPECT_GE(v.capacity(), size_t(20));
+ for (size_t i = 0; i < 20; ++i)
+ EXPECT_EQ(v[i].value, int(i));
+
+ v.shrink_to_fit();
+ EXPECT_EQ(v.capacity(), size_t(20));
+ for (size_t i = 0; i < 20; ++i)
+ EXPECT_EQ(v[i].value, int(i));
+
+ v.pop_back();
+ EXPECT_EQ(v.size(), size_t(19));
+ }
+
+ EXPECT_EQ(LifetimeTracker::constructed, LifetimeTracker::destructed);
+ // First destroyed was element 19 (via pop_back), then 18 down to 0 (LIFO)
+ ASSERT_EQ(LifetimeTracker::destroy_log_len, 20);
+ EXPECT_EQ(LifetimeTracker::destroy_log[0], 19);
+ for (int i = 1; i < 20; ++i)
+ EXPECT_EQ(LifetimeTracker::destroy_log[i], 19 - i);
+}
+
+TEST(LlvmLibcVectorTest, SelfReferentialPushBackAndResize) {
+ vector<int> v;
+ // Fill up to initial capacity (16) so the next push_back reallocates.
+ for (int i = 0; i < 16; ++i)
+ ASSERT_TRUE(v.push_back(i + 100));
+
+ EXPECT_EQ(v.size(), v.capacity());
+ // Self-referential push_back across reallocation boundary
+ ASSERT_TRUE(v.push_back(v[0]));
+ EXPECT_EQ(v.size(), size_t(17));
+ EXPECT_EQ(v.back(), 100);
+
+ // Self-referential resize across reallocation boundary
+ ASSERT_TRUE(v.resize(40, v[1]));
+ EXPECT_EQ(v.size(), size_t(40));
+ EXPECT_EQ(v[39], 101);
+}
+
+TEST(LlvmLibcVectorTest, StringPointerVector) {
+ vector<const char *> ptrs;
+ ASSERT_TRUE(ptrs.push_back("first"));
+ ASSERT_TRUE(ptrs.push_back("second"));
+ ASSERT_TRUE(ptrs.push_back(nullptr));
+
+ EXPECT_EQ(ptrs.size(), size_t(3));
+ EXPECT_STREQ(ptrs[0], "first");
+ EXPECT_STREQ(ptrs[1], "second");
+ EXPECT_EQ(ptrs[2], nullptr);
+ EXPECT_EQ(ptrs.data()[2], nullptr);
+}
>From f680235f7ab1452bbfad8102586012e9e5a1ee91 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 11 Sep 2026 19:57:34 +0100
Subject: [PATCH 2/2] [libc][CPP] Address review feedback on vector
* Factor out reallocate() private helper shared by reserve() and shrink_to_fit()
* Use ::malloc instead of ::realloc(nullptr, ...) for initial/non-trivial allocations
* Add [[nodiscard]] to pure accessors/observers matching libc++
Assisted-by: Automated tooling, human reviewed.
---
libc/src/__support/CPP/CMakeLists.txt | 1 +
libc/src/__support/CPP/vector.h | 104 ++++++++++++--------------
2 files changed, 47 insertions(+), 58 deletions(-)
diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index 3a68411e39392..16d9db567abf4 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -263,6 +263,7 @@ add_header_library(
.type_traits
.utility
libc.hdr.func.free
+ libc.hdr.func.malloc
libc.hdr.func.realloc
libc.hdr.types.size_t
libc.src.__support.common
diff --git a/libc/src/__support/CPP/vector.h b/libc/src/__support/CPP/vector.h
index ceffe208342a1..839a937505671 100644
--- a/libc/src/__support/CPP/vector.h
+++ b/libc/src/__support/CPP/vector.h
@@ -15,6 +15,7 @@
#define LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
#include "hdr/func/free.h"
+#include "hdr/func/malloc.h"
#include "hdr/func/realloc.h"
#include "hdr/types/size_t.h"
#include "src/__support/CPP/new.h"
@@ -74,6 +75,30 @@ template <typename T> class vector {
}
}
+ [[nodiscard]] LIBC_INLINE bool reallocate(size_t new_cap) {
+ if constexpr (is_trivially_copyable_v<T>) {
+ void *new_data = ::realloc(data_, new_cap * sizeof(T));
+ if (!new_data)
+ return false;
+ data_ = static_cast<T *>(new_data);
+ capacity_ = new_cap;
+ return true;
+ } else {
+ void *new_raw = ::malloc(new_cap * sizeof(T));
+ if (!new_raw)
+ return false;
+ T *new_data = static_cast<T *>(new_raw);
+ for (size_t i = 0; i < size_; ++i) {
+ new (new_data + i) T(cpp::move(data_[i]));
+ data_[i].~T();
+ }
+ ::free(data_);
+ data_ = new_data;
+ capacity_ = new_cap;
+ return true;
+ }
+ }
+
public:
LIBC_INLINE constexpr vector() = default;
@@ -106,51 +131,53 @@ template <typename T> class vector {
vector &operator=(const vector &) = delete;
// Element access
- LIBC_INLINE reference operator[](size_t i) {
+ [[nodiscard]] LIBC_INLINE reference operator[](size_t i) {
LIBC_ASSERT(i < size_);
return data_[i];
}
- LIBC_INLINE const_reference operator[](size_t i) const {
+ [[nodiscard]] LIBC_INLINE const_reference operator[](size_t i) const {
LIBC_ASSERT(i < size_);
return data_[i];
}
- LIBC_INLINE pointer data() { return data_; }
- LIBC_INLINE const_pointer data() const { return data_; }
+ [[nodiscard]] LIBC_INLINE pointer data() { return data_; }
+ [[nodiscard]] LIBC_INLINE const_pointer data() const { return data_; }
- LIBC_INLINE reference front() {
+ [[nodiscard]] LIBC_INLINE reference front() {
LIBC_ASSERT(size_ > 0);
return data_[0];
}
- LIBC_INLINE const_reference front() const {
+ [[nodiscard]] LIBC_INLINE const_reference front() const {
LIBC_ASSERT(size_ > 0);
return data_[0];
}
- LIBC_INLINE reference back() {
+ [[nodiscard]] LIBC_INLINE reference back() {
LIBC_ASSERT(size_ > 0);
return data_[size_ - 1];
}
- LIBC_INLINE const_reference back() const {
+ [[nodiscard]] LIBC_INLINE const_reference back() const {
LIBC_ASSERT(size_ > 0);
return data_[size_ - 1];
}
// Iterators
- LIBC_INLINE iterator begin() { return data_; }
- LIBC_INLINE const_iterator begin() const { return data_; }
- LIBC_INLINE const_iterator cbegin() const { return data_; }
- LIBC_INLINE iterator end() { return data_ + size_; }
- LIBC_INLINE const_iterator end() const { return data_ + size_; }
- LIBC_INLINE const_iterator cend() const { return data_ + size_; }
+ [[nodiscard]] LIBC_INLINE iterator begin() { return data_; }
+ [[nodiscard]] LIBC_INLINE const_iterator begin() const { return data_; }
+ [[nodiscard]] LIBC_INLINE const_iterator cbegin() const { return data_; }
+ [[nodiscard]] LIBC_INLINE iterator end() { return data_ + size_; }
+ [[nodiscard]] LIBC_INLINE const_iterator end() const { return data_ + size_; }
+ [[nodiscard]] LIBC_INLINE const_iterator cend() const {
+ return data_ + size_;
+ }
// Capacity
[[nodiscard]] LIBC_INLINE bool empty() const { return size_ == 0; }
- LIBC_INLINE size_t size() const { return size_; }
- LIBC_INLINE size_t capacity() const { return capacity_; }
+ [[nodiscard]] LIBC_INLINE size_t size() const { return size_; }
+ [[nodiscard]] LIBC_INLINE size_t capacity() const { return capacity_; }
// Reserves at least new_cap elements. Returns true on success, false on OOM.
[[nodiscard]] LIBC_INLINE bool reserve(size_t new_cap) {
@@ -158,28 +185,7 @@ template <typename T> class vector {
return true;
if (new_cap > max_size())
return false;
-
- if constexpr (is_trivially_copyable_v<T>) {
- void *new_data = ::realloc(data_, new_cap * sizeof(T));
- if (!new_data)
- return false;
- data_ = static_cast<T *>(new_data);
- capacity_ = new_cap;
- return true;
- } else {
- void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
- if (!new_raw)
- return false;
- T *new_data = static_cast<T *>(new_raw);
- for (size_t i = 0; i < size_; ++i) {
- new (new_data + i) T(cpp::move(data_[i]));
- data_[i].~T();
- }
- ::free(data_);
- data_ = new_data;
- capacity_ = new_cap;
- return true;
- }
+ return reallocate(new_cap);
}
LIBC_INLINE void shrink_to_fit() {
@@ -189,25 +195,7 @@ template <typename T> class vector {
reset();
return;
}
- if constexpr (is_trivially_copyable_v<T>) {
- void *new_data = ::realloc(data_, size_ * sizeof(T));
- if (new_data) {
- data_ = static_cast<T *>(new_data);
- capacity_ = size_;
- }
- } else {
- void *new_raw = ::realloc(nullptr, size_ * sizeof(T));
- if (!new_raw)
- return;
- T *new_data = static_cast<T *>(new_raw);
- for (size_t i = 0; i < size_; ++i) {
- new (new_data + i) T(cpp::move(data_[i]));
- data_[i].~T();
- }
- ::free(data_);
- data_ = new_data;
- capacity_ = size_;
- }
+ static_cast<void>(reallocate(size_));
}
// Modifiers
@@ -226,7 +214,7 @@ template <typename T> class vector {
new_cap = capacity_ * 2;
}
- void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
+ void *new_raw = ::malloc(new_cap * sizeof(T));
if (!new_raw)
return false;
T *new_data = static_cast<T *>(new_raw);
More information about the libc-commits
mailing list