blob: f8a59dc794d6ea3e119fc527711b5ccbd3efa2b4 [file] [log] [blame]
// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef SRC_TINT_UTILS_MEMORY_BLOCK_ALLOCATOR_H_
#define SRC_TINT_UTILS_MEMORY_BLOCK_ALLOCATOR_H_
#include <array>
#include <cstring>
#include <utility>
#include "src/tint/utils/math/math.h"
#include "src/tint/utils/memory/bitcast.h"
namespace tint {
/// A container and allocator of objects of (or deriving from) the template type `T`.
/// Objects are allocated by calling Create(), and are owned by the BlockAllocator.
/// When the BlockAllocator is destructed, all constructed objects are automatically destructed and
/// freed.
///
/// Objects held by the BlockAllocator can be iterated over using a View.
template <typename T, size_t BLOCK_SIZE = 64 * 1024, size_t BLOCK_ALIGNMENT = 16>
class BlockAllocator {
/// Pointers is a chunk of T* pointers, forming a linked list.
/// The list of Pointers are used to maintain the list of allocated objects.
/// Pointers are allocated out of the block memory.
struct Pointers {
static constexpr size_t kMax = 32;
std::array<T*, kMax> ptrs;
Pointers* next;
Pointers* prev;
size_t count;
};
/// Block is linked list of memory blocks.
/// Blocks are allocated out of heap memory.
///
/// Note: We're not using std::aligned_storage here as this warns / errors on MSVC.
struct alignas(BLOCK_ALIGNMENT) Block {
uint8_t data[BLOCK_SIZE];
Block* next = nullptr;
};
// Forward declaration
template <bool IS_CONST>
class TView;
/// An iterator for the objects owned by the BlockAllocator.
template <bool IS_CONST>
class TIterator {
using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
public:
/// Equality operator
/// @param other the iterator to compare this iterator to
/// @returns true if this iterator is equal to other
bool operator==(const TIterator& other) const {
return ptrs == other.ptrs && idx == other.idx;
}
/// Inequality operator
/// @param other the iterator to compare this iterator to
/// @returns true if this iterator is not equal to other
bool operator!=(const TIterator& other) const { return !(*this == other); }
/// Progress the iterator forward one element
/// @returns this iterator
TIterator& operator++() {
if (ptrs != nullptr) {
++idx;
if (idx >= ptrs->count) {
idx = 0;
ptrs = ptrs->next;
}
}
return *this;
}
/// Progress the iterator backwards one element
/// @returns this iterator
TIterator& operator--() {
if (ptrs != nullptr) {
if (idx == 0) {
ptrs = ptrs->prev;
idx = ptrs->count - 1;
}
--idx;
}
return *this;
}
/// @returns the pointer to the object at the current iterator position
PointerTy operator*() const { return ptrs->ptrs[idx]; }
private:
friend TView<IS_CONST>; // Keep internal iterator impl private.
explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
/// The current Pointers
const Pointers* ptrs = nullptr;
/// The current index within #ptrs
size_t idx = 0;
};
/// View provides begin() and end() methods for looping over the objects owned by the
/// BlockAllocator.
template <bool IS_CONST>
class TView {
public:
/// The iterator type
using iterator = TIterator<IS_CONST>;
/// The const iterator type
using const_iterator = TIterator<true>;
/// @returns an iterator to the beginning of the view
iterator begin() const { return iterator{allocator_->data.pointers.root, 0}; }
/// @returns an iterator to the end of the view
iterator end() const { return iterator{nullptr, 0}; }
private:
friend BlockAllocator; // For BlockAllocator::operator View()
explicit TView(BlockAllocator const* allocator) : allocator_(allocator) {}
BlockAllocator const* const allocator_;
};
public:
/// A forward-iterator type over the objects of the BlockAllocator
using Iterator = TIterator</* const */ false>;
/// An immutable forward-iterator type over the objects of the BlockAllocator
using ConstIterator = TIterator</* const */ true>;
/// View provides begin() and end() methods for looping over the objects owned by the
/// BlockAllocator.
using View = TView<false>;
/// ConstView provides begin() and end() methods for looping over the objects owned by the
/// BlockAllocator.
using ConstView = TView<true>;
/// Constructor
BlockAllocator() = default;
/// Move constructor
/// @param rhs the BlockAllocator to move
BlockAllocator(BlockAllocator&& rhs) { std::swap(data, rhs.data); }
/// Move assignment operator
/// @param rhs the BlockAllocator to move
/// @return this BlockAllocator
BlockAllocator& operator=(BlockAllocator&& rhs) {
if (this != &rhs) {
Reset();
std::swap(data, rhs.data);
}
return *this;
}
/// Destructor
~BlockAllocator() { Reset(); }
/// @return a View of all objects owned by this BlockAllocator
View Objects() { return View(this); }
/// @return a ConstView of all objects owned by this BlockAllocator
ConstView Objects() const { return ConstView(this); }
/// Creates a new `TYPE` owned by the BlockAllocator.
/// When the BlockAllocator is destructed the object will be destructed and freed.
/// @param args the arguments to pass to the constructor
/// @returns the pointer to the constructed object
template <typename TYPE = T, typename... ARGS>
TYPE* Create(ARGS&&... args) {
static_assert(std::is_same<T, TYPE>::value || std::is_base_of<T, TYPE>::value,
"TYPE does not derive from T");
static_assert(std::is_same<T, TYPE>::value || std::has_virtual_destructor<T>::value,
"TYPE requires a virtual destructor when calling Create() for a type "
"that is not T");
auto* ptr = Allocate<TYPE>();
new (ptr) TYPE(std::forward<ARGS>(args)...);
AddObjectPointer(ptr);
data.count++;
return ptr;
}
/// Frees all allocations from the allocator.
void Reset() {
for (auto ptr : Objects()) {
ptr->~T();
}
auto* block = data.block.root;
while (block != nullptr) {
auto* next = block->next;
delete block;
block = next;
}
data = {};
}
/// @returns the total number of allocated objects.
size_t Count() const { return data.count; }
private:
BlockAllocator(const BlockAllocator&) = delete;
BlockAllocator& operator=(const BlockAllocator&) = delete;
/// Allocates an instance of TYPE from the current block, or from a newly allocated block if the
/// current block is full.
template <typename TYPE>
TYPE* Allocate() {
static_assert(sizeof(TYPE) <= BLOCK_SIZE,
"Cannot construct TYPE with size greater than BLOCK_SIZE");
static_assert(alignof(TYPE) <= BLOCK_ALIGNMENT, "alignof(TYPE) is greater than ALIGNMENT");
auto& block = data.block;
block.current_offset = tint::RoundUp(alignof(TYPE), block.current_offset);
if (block.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
// Allocate a new block from the heap
auto* prev_block = block.current;
block.current = new Block;
if (!block.current) {
return nullptr; // out of memory
}
block.current->next = nullptr;
block.current_offset = 0;
if (prev_block) {
prev_block->next = block.current;
} else {
block.root = block.current;
}
}
auto* base = &block.current->data[0];
auto* ptr = tint::Bitcast<TYPE*>(base + block.current_offset);
block.current_offset += sizeof(TYPE);
return ptr;
}
/// Adds `ptr` to the linked list of objects owned by this BlockAllocator.
/// Once added, `ptr` will be tracked for destruction when the BlockAllocator is destructed.
void AddObjectPointer(T* ptr) {
auto& pointers = data.pointers;
if (!pointers.current || pointers.current->count == Pointers::kMax) {
auto* prev_pointers = pointers.current;
pointers.current = Allocate<Pointers>();
if (!pointers.current) {
return; // out of memory
}
pointers.current->next = nullptr;
pointers.current->prev = prev_pointers;
pointers.current->count = 0;
if (prev_pointers) {
prev_pointers->next = pointers.current;
} else {
pointers.root = pointers.current;
}
}
pointers.current->ptrs[pointers.current->count++] = ptr;
}
struct {
struct {
/// The root block of the block linked list
Block* root = nullptr;
/// The current (end) block of the blocked linked list.
/// New allocations come from this block
Block* current = nullptr;
/// The byte offset in #current for the next allocation.
/// Initialized with BLOCK_SIZE so that the first allocation triggers a block
/// allocation.
size_t current_offset = BLOCK_SIZE;
} block;
struct {
/// The root Pointers structure of the pointers linked list
Pointers* root = nullptr;
/// The current (end) Pointers structure of the pointers linked list.
/// AddObjectPointer() adds to this structure.
Pointers* current = nullptr;
} pointers;
size_t count = 0;
} data;
};
} // namespace tint
#endif // SRC_TINT_UTILS_MEMORY_BLOCK_ALLOCATOR_H_