diff --git a/source/mijin/memory/stack_allocator.hpp b/source/mijin/memory/stack_allocator.hpp new file mode 100644 index 0000000..33b8ca7 --- /dev/null +++ b/source/mijin/memory/stack_allocator.hpp @@ -0,0 +1,192 @@ + +#pragma once + +#if !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED) +#define MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED 1 + +#include "../debug/assert.hpp" +#include "../internal/common.hpp" +#include "../util/align.hpp" +#include "../util/traits.hpp" + +namespace mijin +{ +template +struct StlStackAllocator +{ +public: + using value_type = TValue; +private: + TStackAllocator* base_; +public: + explicit StlStackAllocator(TStackAllocator& base) MIJIN_NOEXCEPT : base_(&base) {} + template + StlStackAllocator(const StlStackAllocator& other) MIJIN_NOEXCEPT : base_(other.base) {} + + template + StlStackAllocator& operator=(const StlStackAllocator& other) MIJIN_NOEXCEPT + { + base_ = other.base_; + return *this; + } + + auto operator<=>(const StlStackAllocator&) const noexcept = default; + + [[nodiscard]] + TValue* allocate(std::size_t count) const + { + return static_cast(base_->allocate(alignof(TValue), count * sizeof(TValue))); + } + + void deallocate(TValue* /* ptr */, std::size_t /* count */) const MIJIN_NOEXCEPT {} +}; + +template typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl) +class StackAllocator +{ +public: + using backing_t = TBacking; + static constexpr std::size_t ACTUAL_CHUNK_SIZE = chunkSize - sizeof(void*) - sizeof(std::size_t); + + template + using stl_allocator_t = StlStackAllocator>; +private: + struct Chunk + { + std::array data; + Chunk* next; + std::size_t allocated; + }; + [[no_unique_address]] TBacking backing_; + Chunk* firstChunk_ = nullptr; +public: + StackAllocator() MIJIN_NOEXCEPT_IF(std::is_nothrow_default_constructible_v) = default; + explicit StackAllocator(backing_t backing) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v, backing_t&&>)) + : backing_(std::move(backing)) {} + StackAllocator(const StackAllocator&) = delete; + StackAllocator(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) + : firstChunk_(std::exchange(other.firstChunk_, nullptr)) {} + ~StackAllocator() noexcept + { + Chunk* chunk = firstChunk_; + while (chunk != nullptr) + { + Chunk* nextChunk = firstChunk_->next; + backing_.deallocate(chunk, 1); + chunk = nextChunk; + } + } + StackAllocator& operator=(const StackAllocator&) = delete; + StackAllocator& operator=(StackAllocator&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v>) + { + if (this != &other) + { + backing_ = std::move(other.backing_); + firstChunk_ = std::exchange(other.firstChunk_, nullptr); + } + return *this; + } + + void* allocate(std::size_t alignment, std::size_t size) + { + // first check if this can ever fit + if (size > ACTUAL_CHUNK_SIZE) + { + return nullptr; + } + + // then try to find space in the current chunks + for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next) + { + const std::size_t remaining = ACTUAL_CHUNK_SIZE - chunk->allocated; + if (remaining < size) + { + continue; + } + + std::byte* start = &chunk->data[chunk->allocated]; + std::byte* pos = mijin::alignUp(start, alignment); + + const std::ptrdiff_t alignmentBytes = pos - start; + const std::size_t combinedSize = size + alignmentBytes; + + if (remaining < combinedSize) + { + continue; + } + + chunk->allocated += combinedSize; + return pos; + } + + // no free space in any chunk? allocate a new one + Chunk* newChunk = backing_.allocate(1); + if (newChunk == nullptr) + { + return nullptr; + } + initAndAddChunk(newChunk); + + // now try with the new chunk + std::byte* start = newChunk->data.data(); + std::byte* pos = mijin::alignUp(start, alignment); + + const std::ptrdiff_t alignmentBytes = pos - start; + const std::size_t combinedSize = size + alignmentBytes; + + // doesn't fit (due to alignment), time to give up + if (ACTUAL_CHUNK_SIZE < combinedSize) + { + return nullptr; + } + + newChunk->allocated = combinedSize; + return pos; + } + + void reset() noexcept + { + for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next) + { + chunk->allocated = 0; + } + } + + bool createChunks(std::size_t count) + { + if (count == 0) + { + return true; + } + Chunk* newChunks = backing_.allocate(count); + if (newChunks == nullptr) + { + return false; + } + + // reverse so the chunks are chained from 0 to count (new chunks are inserted in front) + for (std::size_t pos = count; pos > 0; --pos) + { + initAndAddChunk(&newChunks[pos-1]); + } + return true; + } + + template + stl_allocator_t makeStlAllocator() MIJIN_NOEXCEPT + { + return stl_allocator_t(*this); + } +private: + void initAndAddChunk(Chunk* newChunk) noexcept + { + ::new (newChunk) Chunk(); + + // put it in the front + newChunk->next = firstChunk_; + firstChunk_ = newChunk; + } +}; +} // namespace mijin + +#endif // !defined(MIJIN_MEMORY_STACK_ALLOCATOR_HPP_INCLUDED)