Add ImmutableVector and ImmutableVectorView templates.
These templates allow creating and using heap-allocated but
immutable arrays of items. They will be used by future CLs
in a few key classes to reduce GN RAM usage drastically.
Here's how an ImmutableVector compares to an std::vector<>
in terms of memory usage:
- An empty instance is just an 8-byte null pointer, vs 24 bytes
for an empty vector.
- Its heap block is sized precisely to fit the instance's size,
while the std::vector<> will generally over-allocate its
storage space (calling reserve() does not guarantee minimal
heap usage).
Note that constructing an non-empty ImmutableVector requires a
source container (std::vector<> or otherwise), or a callable
that can produce a fixed number of items for in-place
construction (see source code for details).
An ImmutableVectorView is a non-owning reference to an
ImmutableVectorView, it is copyable and movable since the
vector is immutable.
Bug: None
Change-Id: I2cb669b7b0fab777bc7067ee7529690b300ea735
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/13621
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: David Turner <digit@google.com>
diff --git a/build/gen.py b/build/gen.py
index 8b833d0..f695ccd 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -761,6 +761,7 @@
'src/gn/functions_unittest.cc',
'src/gn/hash_table_base_unittest.cc',
'src/gn/header_checker_unittest.cc',
+ 'src/gn/immutable_vector_unittest.cc',
'src/gn/inherited_libraries_unittest.cc',
'src/gn/input_conversion_unittest.cc',
'src/gn/json_project_writer_unittest.cc',
diff --git a/src/gn/immutable_vector.h b/src/gn/immutable_vector.h
new file mode 100644
index 0000000..306819b
--- /dev/null
+++ b/src/gn/immutable_vector.h
@@ -0,0 +1,244 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_IMMUTABLE_VECTOR_H_
+#define TOOLS_GN_IMMUTABLE_VECTOR_H_
+
+#include <cstdlib>
+#include <type_traits>
+#include <utility>
+
+#include "util/aligned_alloc.h"
+
+// An ImmutableVector<T> represents a fixed-size vector of constant items of
+// type T. The in-memory representation is more efficient, only using one
+// pointer to a single heap-allocated memory block that also contains the size.
+//
+// An ImmutableVectorView<T> acts as a copyable and movable reference to another
+// ImmutableVector<T> instance. They both point to the same memory in the heap,
+// but the view is not owning and is invalidated when the instance it points to
+// is destroyed.
+//
+// Apart from that, they can be used with the same methods as a const
+// std::vector<T>.
+//
+template <typename T>
+class ImmutableVector;
+
+template <typename T>
+class ImmutableVectorView {
+ public:
+ // Default constructor
+ ImmutableVectorView() = default;
+
+ // Constructor from an ImmutableVector.
+ ImmutableVectorView(const ImmutableVector<T>& vector)
+ : header_(vector.header_) {}
+
+ // Usual methods to access items.
+ const T* data() const { return begin(); }
+ size_t size() const { return header_ ? header_->size : 0u; }
+ bool empty() const { return size() == 0; }
+ const T& operator[](size_t offset) const { return begin()[offset]; };
+
+ const T* begin() const { return header_ ? &header_->item0 : nullptr; }
+ const T* end() const {
+ return header_ ? &header_->item0 + header_->size : nullptr;
+ }
+
+ const T& front() const { return begin()[0]; }
+ const T& back() const { return end()[-1]; }
+
+ // For use with standard algorithms and templates.
+ using element_type = T;
+ using value_type = std::remove_cv_t<T>;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+ using iterator = const T*;
+ using const_iterator = const T*;
+
+ const_iterator find(const T& item) const {
+ auto it = begin();
+ auto it_end = end();
+ for (; it != it_end; ++it) {
+ if (*it == item)
+ break;
+ }
+ return it;
+ }
+
+ bool contains(const T& item) const {
+ for (const auto& cur_item : *this)
+ if (cur_item == item)
+ return true;
+
+ return false;
+ }
+
+ protected:
+ struct Header {
+ size_t size;
+ T item0;
+ };
+
+ ImmutableVectorView(Header* header) : header_(header) {}
+
+ Header* header_ = nullptr;
+};
+
+template <typename T>
+class ImmutableVector : public ImmutableVectorView<T> {
+ public:
+ // Default constructor.
+ ImmutableVector() = default;
+
+ // Range constructors.
+ ImmutableVector(const T* begin, size_t size)
+ : ImmutableVectorView<T>(AllocateAndCopyFrom(begin, size)) {}
+
+ ImmutableVector(const T* begin, const T* end)
+ : ImmutableVector(begin, end - begin) {}
+
+ // In-place constructor
+ // |producer| must be a callable that generates a T instance that will
+ // be used to construct items in place in the allocated vector.
+ template <typename P,
+ typename = std::void_t<
+ decltype(static_cast<const T&>(std::declval<P>()()))>>
+ ImmutableVector(P producer, size_t size) {
+ if (size) {
+ this->header_ = AllocateFor(size);
+ auto* dst = &(this->header_->item0);
+ auto* dst_limit = dst + size;
+ for (; dst != dst_limit; ++dst)
+ new (dst) T(producer());
+ }
+ }
+
+ // Container constructor
+ //
+ // This uses std::void_t<> to select container types whose values
+ // are convertible to |const T&|, and which have |begin()| and |size()|
+ // methods.
+ //
+ // This constructor copies items from the constructor into the
+ // ImmutableVector.
+ template <typename C,
+ typename = std::void_t<
+ decltype(static_cast<const T>(*std::declval<C>().begin())),
+ decltype(std::declval<C>().size())>>
+ ImmutableVector(const C& container) {
+ size_t size = container.size();
+ if (size) {
+ this->header_ = AllocateFor(size);
+ auto src = container.begin();
+ auto* dst = &(this->header_->item0);
+ auto* dst_limit = dst + size;
+ for (; dst != dst_limit; ++dst, ++src) {
+ new (dst) T(*src);
+ }
+ }
+ }
+
+ // Another container constructor where the items can be moved into
+ // the resulting ImmutableVector.
+ template <typename C,
+ typename = std::void_t<
+ decltype(static_cast<const T>(*std::declval<C>().begin())),
+ decltype(std::declval<C>().size())>>
+ ImmutableVector(C&& container) {
+ size_t size = container.size();
+ if (size) {
+ this->header_ = AllocateFor(size);
+ auto src = container.begin();
+ auto* dst = &(this->header_->item0);
+ auto* dst_limit = dst + size;
+ for (; dst != dst_limit; ++dst, ++src)
+ new (dst) T(std::move(*src));
+ }
+ }
+
+ // Initializer-list container.
+ ImmutableVector(std::initializer_list<T> list)
+ : ImmutableVector(list.begin(), list.size()) {}
+
+ // Copy operations
+ ImmutableVector(const ImmutableVector& other)
+ : ImmutableVectorView<T>(
+ AllocateAndCopyFrom(other.begin(), other.size())) {}
+
+ ImmutableVector& operator=(const ImmutableVector& other) {
+ if (this != &other) {
+ this->~ImmutableVector();
+ new (this) ImmutableVector(other);
+ }
+ return *this;
+ }
+
+ // Move operations
+ ImmutableVector(ImmutableVector&& other) noexcept {
+ this->header_ = other.header_;
+ other.header_ = nullptr;
+ }
+
+ ImmutableVector& operator=(ImmutableVector&& other) noexcept {
+ if (this != &other) {
+ this->~ImmutableVector();
+ new (this) ImmutableVector(std::move(other));
+ }
+ return *this;
+ }
+
+ // Destructor
+ ~ImmutableVector() {
+ if (this->header_) {
+ auto* cur = &(this->header_->item0);
+ auto* limit = cur + this->size();
+ while (cur != limit) {
+ (*cur).~T();
+ cur++;
+ }
+ Allocator::Free(this->header_);
+ }
+ }
+
+ protected:
+ friend class ImmutableVectorView<T>;
+
+ using Header = typename ImmutableVectorView<T>::Header;
+
+ // Don't use std::max() here to avoid including <algorithm> which is massive.
+ static constexpr size_t kAlignment = alignof(T) > alignof(Header)
+ ? alignof(T)
+ : alignof(Header);
+
+ using Allocator = AlignedAlloc<kAlignment>;
+
+ static Header* AllocateFor(size_t count) {
+ if (!count)
+ return nullptr;
+
+ auto* header = reinterpret_cast<Header*>(
+ Allocator::Alloc(sizeof(Header) + (count - 1u) * sizeof(T)));
+ header->size = count;
+ return header;
+ }
+
+ static Header* AllocateAndCopyFrom(const T* begin, size_t size) {
+ Header* header = AllocateFor(size);
+ if (size) {
+ T* dst = &(header->item0);
+ T* limit = dst + size;
+ for (; dst != limit; ++dst, ++begin)
+ new (dst) T(*begin);
+ }
+ return header;
+ }
+};
+
+#endif // TOOLS_GN_IMMUTABLE_VECTOR_H_
diff --git a/src/gn/immutable_vector_unittest.cc b/src/gn/immutable_vector_unittest.cc
new file mode 100644
index 0000000..3561ed2
--- /dev/null
+++ b/src/gn/immutable_vector_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/immutable_vector.h"
+#include "util/test/test.h"
+
+#include <set>
+#include <vector>
+
+TEST(ImmutableVector, CreationDestruction) {
+ ImmutableVector<int> empty;
+ EXPECT_TRUE(empty.empty());
+ EXPECT_EQ(0u, empty.size());
+
+ ImmutableVector<int> vec1 = {100, 42};
+ EXPECT_FALSE(vec1.empty());
+ EXPECT_EQ(2u, vec1.size());
+ EXPECT_EQ(100, vec1.front());
+ EXPECT_EQ(42, vec1.back());
+ EXPECT_EQ(100, vec1[0]);
+ EXPECT_EQ(42, vec1[1]);
+ EXPECT_TRUE(vec1.begin());
+ EXPECT_TRUE(vec1.end());
+ EXPECT_NE(vec1.begin(), vec1.end());
+ EXPECT_EQ(vec1.begin() + 2, vec1.end());
+
+ std::vector<int> input;
+ input.push_back(100);
+ input.push_back(42);
+ input.push_back(-12);
+ ImmutableVector<int> vec2(input);
+ EXPECT_FALSE(vec2.empty());
+ EXPECT_EQ(3u, vec2.size());
+ EXPECT_EQ(100, vec2.front());
+ EXPECT_EQ(100, vec2[0]);
+ EXPECT_EQ(42, vec2[1]);
+ EXPECT_EQ(-12, vec2[2]);
+ EXPECT_NE(vec2.begin(), &input[0]);
+ EXPECT_NE(vec2.end(), &input[0] + 3);
+}
+
+TEST(ImmutableVetor, InPlaceConstruction) {
+ size_t count = 0;
+ auto count_producer = [&count]() { return count++; };
+ ImmutableVector<int> vec(count_producer, 5u);
+ EXPECT_EQ(5u, vec.size());
+ EXPECT_EQ(0, vec[0]);
+ EXPECT_EQ(1, vec[1]);
+ EXPECT_EQ(2, vec[2]);
+ EXPECT_EQ(3, vec[3]);
+ EXPECT_EQ(4, vec[4]);
+}
+
+TEST(ImmutableVector, CopyAndMoveOperations) {
+ ImmutableVector<int> vec1 = {1, 2, 3, 4};
+ ImmutableVector<int> vec2 = vec1;
+ ImmutableVector<int> vec3 = std::move(vec1);
+
+ EXPECT_TRUE(vec1.empty());
+ EXPECT_EQ(4u, vec2.size());
+ EXPECT_EQ(4u, vec3.size());
+ EXPECT_NE(vec2.begin(), vec3.begin());
+ EXPECT_NE(vec2.end(), vec3.end());
+ EXPECT_TRUE(std::equal(vec2.begin(), vec2.end(), vec3.begin(), vec3.end()));
+}
+
+TEST(ImmutableVectorView, Creation) {
+ ImmutableVector<int> vec1 = {1, 3, 5, 7};
+ ImmutableVectorView<int> view1 = vec1;
+ ImmutableVectorView<int> view2(view1);
+
+ EXPECT_EQ(vec1.size(), view1.size());
+ EXPECT_EQ(vec1.size(), view2.size());
+
+ EXPECT_EQ(vec1.begin(), view1.begin());
+ EXPECT_EQ(vec1.end(), view1.end());
+
+ EXPECT_EQ(vec1.begin(), view2.begin());
+ EXPECT_EQ(vec1.end(), view2.end());
+}