//===- llvm/ADT/PagedVector.h - 'Lazily allocated' vectors --*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// // // This file defines the PagedVector class. // //===----------------------------------------------------------------------===// #ifndef LLVM_ADT_PAGEDVECTOR_H #define LLVM_ADT_PAGEDVECTOR_H #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Allocator.h" #include #include namespace llvm { /// A vector that allocates memory in pages. /// /// Order is kept, but memory is allocated only when one element of the page is /// accessed. This introduces a level of indirection, but it is useful when you /// have a sparsely initialised vector where the full size is allocated upfront. /// /// As a side effect the elements are initialised later than in a normal vector. /// On the first access to one of the elements of a given page, all the elements /// of the page are initialised. This also means that the elements of the page /// are initialised beyond the size of the vector. /// /// Similarly on destruction the elements are destroyed only when the page is /// not needed anymore, delaying invoking the destructor of the elements. /// /// Notice that this has iterators only on materialized elements. This /// is deliberately done under the assumption you would dereference the elements /// while iterating, therefore materialising them and losing the gains in terms /// of memory usage this container provides. If you have such a use case, you /// probably want to use a normal std::vector or a llvm::SmallVector. template class PagedVector { static_assert(PageSize > 1, "PageSize must be greater than 0. Most likely " "you want it to be greater than 16."); /// The actual number of elements in the vector which can be accessed. size_t Size = 0; /// The position of the initial element of the page in the Data vector. /// Pages are allocated contiguously in the Data vector. mutable SmallVector PageToDataPtrs; /// Actual page data. All the page elements are allocated on the /// first access of any of the elements of the page. Elements are default /// constructed and elements of the page are stored contiguously. PointerIntPair Allocator; public: using value_type = T; /// Default constructor. We build our own allocator and mark it as such with /// `true` in the second pair element. PagedVector() : Allocator(new BumpPtrAllocator, true) {} explicit PagedVector(BumpPtrAllocator *A) : Allocator(A, false) { assert(A && "Allocator cannot be nullptr"); } ~PagedVector() { clear(); // If we own the allocator, delete it. if (Allocator.getInt()) delete Allocator.getPointer(); } // Forbid copy and move as we do not need them for the current use case. PagedVector(const PagedVector &) = delete; PagedVector(PagedVector &&) = delete; PagedVector &operator=(const PagedVector &) = delete; PagedVector &operator=(PagedVector &&) = delete; /// Look up an element at position `Index`. /// If the associated page is not filled, it will be filled with default /// constructed elements. T &operator[](size_t Index) const { assert(Index < Size); assert(Index / PageSize < PageToDataPtrs.size()); T *&PagePtr = PageToDataPtrs[Index / PageSize]; // If the page was not yet allocated, allocate it. if (!PagePtr) { PagePtr = Allocator.getPointer()->template Allocate(PageSize); // We need to invoke the default constructor on all the elements of the // page. std::uninitialized_value_construct_n(PagePtr, PageSize); } // Dereference the element in the page. return PagePtr[Index % PageSize]; } /// Return the capacity of the vector. I.e. the maximum size it can be /// expanded to with the resize method without allocating more pages. [[nodiscard]] size_t capacity() const { return PageToDataPtrs.size() * PageSize; } /// Return the size of the vector. [[nodiscard]] size_t size() const { return Size; } /// Resize the vector. Notice that the constructor of the elements will not /// be invoked until an element of a given page is accessed, at which point /// all the elements of the page will be constructed. /// /// If the new size is smaller than the current size, the elements of the /// pages that are not needed anymore will be destroyed, however, elements of /// the last page will not be destroyed. /// /// For these reason the usage of this vector is discouraged if you rely /// on the construction / destructor of the elements to be invoked. void resize(size_t NewSize) { if (NewSize == 0) { clear(); return; } // Handle shrink case: destroy the elements in the pages that are not // needed any more and deallocate the pages. // // On the other hand, we do not destroy the extra elements in the last page, // because we might need them later and the logic is simpler if we do not // destroy them. This means that elements are only destroyed when the // page they belong to is destroyed. This is similar to what happens on // access of the elements of a page, where all the elements of the page are // constructed not only the one effectively needed. size_t NewLastPage = (NewSize - 1) / PageSize; if (NewSize < Size) { for (size_t I = NewLastPage + 1, N = PageToDataPtrs.size(); I < N; ++I) { T *Page = PageToDataPtrs[I]; if (!Page) continue; // We need to invoke the destructor on all the elements of the page. std::destroy_n(Page, PageSize); Allocator.getPointer()->Deallocate(Page); } } Size = NewSize; PageToDataPtrs.resize(NewLastPage + 1); } [[nodiscard]] bool empty() const { return Size == 0; } /// Clear the vector, i.e. clear the allocated pages, the whole page /// lookup index and reset the size. void clear() { Size = 0; for (T *Page : PageToDataPtrs) { if (Page == nullptr) continue; std::destroy_n(Page, PageSize); // If we do not own the allocator, deallocate the pages one by one. if (!Allocator.getInt()) Allocator.getPointer()->Deallocate(Page); } // If we own the allocator, simply reset it. if (Allocator.getInt()) Allocator.getPointer()->Reset(); PageToDataPtrs.clear(); } /// Iterator on all the elements of the vector /// which have actually being constructed. class MaterializedIterator { const PagedVector *PV; size_t ElementIdx; public: using iterator_category = std::forward_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T *; using reference = T &; MaterializedIterator(PagedVector const *PV, size_t ElementIdx) : PV(PV), ElementIdx(ElementIdx) {} /// Pre-increment operator. /// /// When incrementing the iterator, we skip the elements which have not /// been materialized yet. MaterializedIterator &operator++() { ++ElementIdx; if (ElementIdx % PageSize == 0) { while (ElementIdx < PV->Size && !PV->PageToDataPtrs[ElementIdx / PageSize]) ElementIdx += PageSize; if (ElementIdx > PV->Size) ElementIdx = PV->Size; } return *this; } MaterializedIterator operator++(int) { MaterializedIterator Copy = *this; ++*this; return Copy; } T const &operator*() const { assert(ElementIdx < PV->Size); assert(PV->PageToDataPtrs[ElementIdx / PageSize]); T *PagePtr = PV->PageToDataPtrs[ElementIdx / PageSize]; return PagePtr[ElementIdx % PageSize]; } /// Equality operator. friend bool operator==(const MaterializedIterator &LHS, const MaterializedIterator &RHS) { return LHS.equals(RHS); } [[nodiscard]] size_t getIndex() const { return ElementIdx; } friend bool operator!=(const MaterializedIterator &LHS, const MaterializedIterator &RHS) { return !(LHS == RHS); } private: void verify() const { assert( ElementIdx == PV->Size || (ElementIdx < PV->Size && PV->PageToDataPtrs[ElementIdx / PageSize])); } bool equals(const MaterializedIterator &Other) const { assert(PV == Other.PV); verify(); Other.verify(); return ElementIdx == Other.ElementIdx; } }; /// Iterators over the materialized elements of the vector. /// /// This includes all the elements belonging to allocated pages, /// even if they have not been accessed yet. It's enough to access /// one element of a page to materialize all the elements of the page. MaterializedIterator materialized_begin() const { // Look for the first valid page. for (size_t ElementIdx = 0; ElementIdx < Size; ElementIdx += PageSize) if (PageToDataPtrs[ElementIdx / PageSize]) return MaterializedIterator(this, ElementIdx); return MaterializedIterator(this, Size); } MaterializedIterator materialized_end() const { return MaterializedIterator(this, Size); } [[nodiscard]] llvm::iterator_range materialized() const { return {materialized_begin(), materialized_end()}; } }; } // namespace llvm #endif // LLVM_ADT_PAGEDVECTOR_H