| // Copyright 2016 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #pragma once |
| |
| #include "aemu/base/TypeTraits.h" |
| |
| #include <algorithm> |
| #include <initializer_list> |
| #include <memory> |
| #include <type_traits> |
| #include <utility> |
| |
| #include <stddef.h> |
| #include <stdlib.h> |
| |
| // |
| // SmallVector<T>, SmallFixedVector<T, SmallSize> |
| // |
| // This header defines a replacement for a std::vector<> that uses small buffer |
| // optimization technique - for some preset number of elements |SmallSize| it |
| // stores them inside of the object, and falls back to the dynamically allocated |
| // array only if one needs to add more elements. |
| // This is useful for the performance-critical places where common number of |
| // processed items is small, but it may still be quite large for a stack array. |
| // |
| // SmallFixedVector<> is the class you use to store elements, while |
| // SmallVector<> is its base class that erases the small size from the type. |
| // |
| // NOTE: SmallVector<> cannot guarantee std::vector<>'s iterator invalidation |
| // rules for move() and swap() operations - std::vector<>s just exchange |
| // their iterators on swap() and pass the moved ones over, while SmallVector<> |
| // may leave the iterators pointing to nowhere if they were for the in-place |
| // array storage. |
| // |
| // Currenly only a limited subset of std::vector<>'s operations is implemented, |
| // but fill free to add the ones you need. |
| // |
| |
| namespace android { |
| namespace base { |
| |
| // |
| // Forward-declare the 'real' small vector class. |
| template <class T, size_t S> |
| class SmallFixedVector; |
| |
| // |
| // SmallVector<T> - an interface for a small-buffer-optimized vector. |
| // It hides the fixed size from its type, so one can use it to pass small |
| // vectors around and not leak the buffer size to all callers: |
| // |
| // void process(SmallVector<Foo>& data); |
| // ... |
| // ... |
| // SmallFixedVector<Foo, 100> aLittleBitOfFoos = ...; |
| // process(aLittleBitOfFoos); |
| // ... |
| // SmallFixedVector<Foo, 1000> moreFoos = ...; |
| // process(moreFoos); |
| // |
| template <class T> |
| class SmallVector { |
| // Make them friends so SmallFixedVector is able to refer to SmallVector's |
| // protected members in static_assert()s. |
| template <class U, size_t S> |
| friend class SmallFixedVector; |
| |
| public: |
| // Common set of type aliases. |
| using value_type = T; |
| using iterator = T*; |
| using const_iterator = const T*; |
| using pointer = T*; |
| using const_pointer = const T*; |
| using reference = T&; |
| using const_reference = const T&; |
| using size_type = size_t; |
| |
| // It's ok to delete SmallVector<> through the base class - dtor() actually |
| // takes care of all living elements and the allocated memory. |
| ~SmallVector() { dtor(); } |
| |
| // std::vector<> interface operations. |
| iterator begin() { return mBegin; } |
| const_iterator begin() const { return mBegin; } |
| const_iterator cbegin() const { return mBegin; } |
| |
| iterator end() { return mEnd; } |
| const_iterator end() const { return mEnd; } |
| const_iterator cend() const { return mEnd; } |
| |
| size_type size() const { return end() - begin(); } |
| size_type capacity() const { return mCapacity; } |
| bool empty() const { return begin() == end(); } |
| |
| reference front() { return *begin(); } |
| const_reference front() const { return *cbegin(); } |
| reference back() { return *(end() - 1); } |
| const_reference back() const { return *(cend() - 1); } |
| |
| reference operator[](size_t i) { return *(begin() + i); } |
| const_reference operator[](size_t i) const { return *(cbegin() + i); } |
| |
| pointer data() { return mBegin; } |
| const_pointer data() const { return mBegin; } |
| const_pointer cdata() const { return mBegin; } |
| |
| template <class... Args> |
| void emplace_back(Args&&... args) { |
| grow_for_size(size() + 1); |
| new (mEnd) T(std::forward<Args>(args)...); |
| ++mEnd; |
| } |
| |
| void push_back(const T& t) { emplace_back(t); } |
| void push_back(T&& t) { emplace_back(std::move(t)); } |
| |
| void pop_back() { |
| destruct(mEnd - 1, mEnd); |
| --mEnd; |
| } |
| |
| void clear() { |
| destruct(begin(), end()); |
| mEnd = mBegin; |
| } |
| |
| void reserve(size_type newCap) { |
| if (newCap <= this->capacity()) { |
| return; |
| } |
| set_capacity(newCap); |
| } |
| |
| void resize(size_type newSize) { resize_impl<true>(newSize); } |
| |
| // This version of resizing doesn't initialize the newly allocated elements |
| // Useful for the cases when value-initialization is noticeably slow and |
| // one wants to directly construct or memcpy the elements into the resized |
| // place. |
| void resize_noinit(size_type newSize) { resize_impl<false>(newSize); } |
| |
| // Returns if the current vector's buffer is dynamically allocated. |
| bool isAllocated() const { return this->cbegin() != smallBufferStart(); } |
| |
| protected: |
| // Hide the default constructor so only SmallFixedVector can be |
| // instantiated. |
| SmallVector() = default; |
| |
| // Destroy all elements in the vector and free the array if it was allocated |
| // dynamically. |
| void dtor() { |
| this->destruct(this->begin(), this->end()); |
| if (isAllocated()) { |
| free(this->mBegin); |
| } |
| } |
| |
| // Just a convenience setter function to init all members at once. |
| void init(iterator begin, iterator end, size_type capacity) { |
| this->mBegin = begin; |
| this->mEnd = end; |
| this->mCapacity = capacity; |
| } |
| |
| // An implementation of different resizing versions. |
| template <bool init> |
| void resize_impl(size_type newSize) { |
| if (newSize < this->size()) { |
| const auto newEnd = this->begin() + newSize; |
| this->destruct(newEnd, this->end()); |
| this->mEnd = newEnd; |
| } else if (newSize > this->size()) { |
| grow_for_size(newSize); |
| const auto newEnd = this->begin() + newSize; |
| if (init) { |
| std::uninitialized_fill(this->end(), newEnd, T()); |
| } |
| this->mEnd = newEnd; |
| } |
| } |
| |
| // Templated append operation for a range of elements. |
| template <class Iter> |
| void insert_back(Iter b, Iter e) { |
| if (b == e) { |
| return; |
| } |
| const auto newSize = this->size() + (e - b); |
| grow_for_size(newSize); |
| this->mEnd = std::uninitialized_copy(b, e, this->mEnd); |
| } |
| |
| // Multiplicative grow for the internal array so it can hold |newSize| |
| // elements. |
| // Doesn't change size(), only capacity(). |
| void grow_for_size(size_type newSize) { |
| // Grow by 1.5x by default. |
| if (newSize > capacity()) { |
| set_capacity(std::max(newSize, capacity() + capacity() / 2)); |
| } |
| } |
| |
| // Sets the capacity() to be exacly |newCap|. Allocates the array |
| // dynamically, moves all elements over and (potentially) deallocates the |
| // old array. |
| // Doesn't change size(), only capacity(). |
| void set_capacity(size_type newCap) { |
| // Here we can only be switching to the dynamic vector, as static one |
| // always has its capacity on the maximum. |
| const auto newBegin = (T*)malloc(sizeof(T) * newCap); |
| if (!newBegin) { |
| abort(); // what else can we do here? |
| } |
| const auto newEnd = std::uninitialized_copy( |
| std::make_move_iterator(this->begin()), |
| std::make_move_iterator(this->end()), newBegin); |
| dtor(); |
| this->mBegin = newBegin; |
| this->mEnd = newEnd; |
| this->mCapacity = newCap; |
| } |
| |
| // A convenience function to call destructor for a range of elements. |
| static void destruct(T* b, T* e) { |
| if (!std::is_trivially_destructible<T>::value) { |
| for (; b != e; ++b) { |
| b->~T(); |
| } |
| } |
| } |
| |
| // By design of the class, SmallFixedVector<> will be inheriting from |
| // SmallVector<>, so its in-place storage array is going to be the very next |
| // member after the last one here. |
| // This function returns that address, and SmallFixedVector<> has a static |
| // assert to make sure it remains correct. |
| constexpr const void* smallBufferStart() const { |
| return (const void*)(&mCapacity + 1); |
| } |
| |
| // Standard set of members for a vector - begin, end and capacity. |
| // These point to the currently used chunk of memory, no matter if it's a |
| // heap-allocated one or an in-place array. |
| iterator mBegin; |
| iterator mEnd; |
| size_type mCapacity; |
| }; |
| |
| // The implementation of a SmallVector with a fixed in-place size, |SmallSize|. |
| template <class T, size_t SmallSize> |
| class SmallFixedVector : public SmallVector<T> { |
| using base = SmallVector<T>; |
| |
| public: |
| // Grab these from the base class. |
| using value_type = typename base::value_type; |
| using iterator = typename base::iterator; |
| using const_iterator = typename base::const_iterator; |
| using pointer = typename base::pointer; |
| using const_pointer = typename base::const_pointer; |
| using reference = typename base::reference; |
| using const_reference = typename base::const_reference; |
| using size_type = typename base::size_type; |
| |
| static constexpr size_type kSmallSize = SmallSize; |
| |
| // Default constructor - set up an empty vector with capacity at full |
| // internal array size. |
| SmallFixedVector() { |
| // Make sure that the small array starts exactly where base class |
| // expects it: right after the |mCapacity|. |
| |
| // We can't use a static_assert with offsetof() because in msvc, it uses |
| // reinterpret_cast. |
| // TODO: Add runtime assertion instead? |
| // https://developercommunity.visualstudio.com/content/problem/22196/static-assert-cannot-compile-constexprs-method-tha.html |
| #ifndef _MSC_VER |
| static_assert(offsetof(base, mCapacity) + sizeof(base::mCapacity) == |
| offsetof(SmallFixedVector, mData) && |
| offsetof(Data, array) == 0, |
| "SmallFixedVector<> class layout is wrong, " |
| "|mData| needs to follow |mCapacity|"); |
| #endif |
| |
| init_inplace(); |
| } |
| |
| // Ctor from a range of iterators |
| template <class Iter> |
| SmallFixedVector(Iter b, Iter e) : SmallFixedVector() { |
| this->insert_back(b, e); |
| } |
| |
| // Ctor from a range - anything that has begin and end. |
| // Note: template constructor is never a copy/move-ctor. |
| template <class Range, |
| class = enable_if_c<!std::is_same<Range, T>::value && |
| is_range<Range>::value>> |
| explicit SmallFixedVector(const Range& r) |
| : SmallFixedVector(std::begin(r), std::end(r)) {} |
| template <class Range, |
| class = enable_if_c<!std::is_same<Range, T>::value && |
| is_range<Range>::value>> |
| explicit SmallFixedVector(Range&& r) |
| : SmallFixedVector(std::make_move_iterator(std::begin(r)), |
| std::make_move_iterator(std::end(r))) {} |
| template <class U, class = enable_if_convertible<U, T>> |
| SmallFixedVector(std::initializer_list<U> list) |
| : SmallFixedVector(std::begin(list), std::end(list)) {} |
| |
| SmallFixedVector(const SmallFixedVector& other) |
| : SmallFixedVector(other.begin(), other.end()) {} |
| |
| SmallFixedVector(SmallFixedVector&& other) { |
| if (other.isAllocated()) { |
| // Just steal the allocated memory from the |other|. |
| this->mBegin = other.mBegin; |
| this->mEnd = other.mEnd; |
| this->mCapacity = other.mCapacity; |
| other.init_inplace(); |
| } else { |
| // Have to move individual elements. |
| this->mBegin = mData.array; |
| this->mEnd = std::uninitialized_copy( |
| std::make_move_iterator(other.begin()), |
| std::make_move_iterator(other.end()), this->begin()); |
| this->mCapacity = kSmallSize; |
| } |
| } |
| |
| SmallFixedVector& operator=(const SmallFixedVector& other) { |
| if (&other != this) { |
| this->clear(); |
| this->insert_back(other.begin(), other.end()); |
| } |
| return *this; |
| } |
| |
| SmallFixedVector& operator=(SmallFixedVector&& other) { |
| if (other.isAllocated()) { |
| // Steal it and we're done. |
| this->dtor(); |
| this->mBegin = other.mBegin; |
| this->mEnd = other.mEnd; |
| this->mCapacity = other.mCapacity; |
| other.init_inplace(); |
| return *this; |
| } |
| |
| if (this->isAllocated() && this->mCapacity < other.size()) { |
| // Not enough dynamic memory, switch to in-place. |
| this->dtor(); |
| init_inplace(); |
| } else { |
| // This could potentially be improved by move-assigning |
| // only needed items and destroying the rest, but |
| // destroy-all+construct-all is just simpler. For PODs it actually |
| // is even faster as it's always a single memcpy(). |
| this->destruct(this->begin(), this->end()); |
| } |
| |
| // Move the whole |other| into the pre-cleaned memory |
| const auto newEnd = std::uninitialized_copy( |
| std::make_move_iterator(other.begin()), |
| std::make_move_iterator(other.end()), this->mBegin); |
| this->mEnd = newEnd; |
| // |other| is valid as-is. |
| return *this; |
| } |
| |
| // Make sure we don't end up trying to move from an interface - it's just |
| // inefficient with the current code. |
| SmallFixedVector(base&& other) = delete; |
| SmallFixedVector& operator=(base&& other) = delete; |
| |
| private: |
| // A shortcut for initialization for in-place storage. |
| void init_inplace() { this->init(mData.array, mData.array, kSmallSize); } |
| |
| // A union with empty constructor and destructor makes sure that the array |
| // elements are not default-constructed in ctor and not destructed in dtor: |
| // the class needs to be able manage their lifetime more precisely. |
| union Data { |
| alignas(size_type) T array[kSmallSize]; |
| |
| Data() {} |
| ~Data() {} |
| } mData; |
| }; |
| |
| } // namespace base |
| } // namespace android |