diff --git a/app/src/main/cpp/skyline/common/linear_allocator.h b/app/src/main/cpp/skyline/common/linear_allocator.h new file mode 100644 index 00000000..719321de --- /dev/null +++ b/app/src/main/cpp/skyline/common/linear_allocator.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include + +namespace skyline { + /** + * @brief Typeless allocation state holder for LinearAllocator + * @tparam NewChunkSize Step size in bytes for overflow chunk allocations + */ + template // Default to 1MB + class LinearAllocatorState { + private: + std::vector mainChunk; //!< The primary backing for the allocator, will grow during `Reset` calls if the previous set of allocations overflowed + std::list> overflowChunks; //!< Overflow backing chunks used to hold allocations that can't fit in `mainChunk` until it can be resized + u8 *ptr{}; //!< Points to a free region of memory of size `chunkRemainingBytes` + size_t chunkRemainingBytes{NewChunkSize}; //!< Remaining bytes left in the current chunk + + size_t allocCount{}; //!< The number of currently unfreed allocations + + public: + LinearAllocatorState() { + mainChunk.reserve(NewChunkSize); + ptr = mainChunk.data(); + } + + /** + * @brief Allocates a contiguous chunk of memory of size `size` aligned to the maximum possible required alignment + */ + u8 *Allocate(size_t unalignedSize) { + // Align the size to the maximum required alignment for standard types + size_t size{util::AlignUp(unalignedSize, alignof(std::max_align_t))}; + + // Allocated memory cannot span across multiple chunks + if (size > NewChunkSize) + throw std::bad_alloc(); + + if (chunkRemainingBytes < size) { + // If there is no space left in the current chunk allocate a new overflow one + auto &newChunk{overflowChunks.emplace_back()}; + newChunk.reserve(NewChunkSize); + ptr = newChunk.data(); + chunkRemainingBytes = NewChunkSize; + } + + auto allocatedPtr{ptr}; + + // Move ourselves along + chunkRemainingBytes -= size; + ptr += size; + + allocCount++; + + return allocatedPtr; + } + + /** + * @brief Decreases allocation count, should be called an equal number of times to `Allocate` + */ + void Deallocate() { + allocCount--; + } + + /** + * @brief Resizes the main chunk to fit any previously needed overflow chunks and allows memory to be reused again for further allocations + * @note There **must** be no allocations leftover when this is called + */ + void Reset() { + if (allocCount) + // If we still have allocations remaining then throw + throw std::bad_alloc(); + + if (size_t overflowSize{overflowChunks.size() * NewChunkSize}) { + // Expand the main chunk so that it can fit any previously needed overflow chunks + overflowChunks.clear(); + mainChunk.reserve(overflowSize + mainChunk.capacity()); + } + + ptr = mainChunk.data(); + chunkRemainingBytes = mainChunk.capacity(); + } + }; + + /** + * @brief Linear allocator conforming to the C++ 'Allocator' named requirement, forwards all allocation requests to the passed in state + */ + template> + class LinearAllocator { + private: + State &state; //!< Backing allocation state holder + + public: + using value_type = T; + + template friend class LinearAllocator; //!< Required for copy ctor from other types + + /** + * @note This is explicitly not explicit to avoid the need to repeat the allocator type when constructing a new object + */ + LinearAllocator(State &state) : state(state) {} + + template + LinearAllocator(const LinearAllocator &other) : state(other.state) {}; + + template + LinearAllocator(LinearAllocator &&other) : state(other.state) {}; + + [[nodiscard]] T *allocate(size_t n) { + return reinterpret_cast(state.Allocate(sizeof(T) * n)); + } + + void deallocate(T *obj, size_t n) noexcept { + state.Deallocate(); + } + + bool operator==(const LinearAllocator &other) const noexcept { + return &state == &other.state; + } + + bool operator!=(const LinearAllocator &other) const noexcept { + return &state != &other.state; + } + }; +}