* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef sktext_gpu_SubRunAllocator_DEFINED
#define sktext_gpu_SubRunAllocator_DEFINED
#include "include/core/SkSpan.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkMath.h"
#include "include/private/base/SkTLogic.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkArenaAlloc.h"
#include <algorithm>
#include <array>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <new>
#include <tuple>
#include <utility>
namespace sktext::gpu {
class BagOfBytes {
public:
BagOfBytes(char* block, size_t blockSize, size_t firstHeapAllocation);
explicit BagOfBytes(size_t firstHeapAllocation = 0);
BagOfBytes(const BagOfBytes&) = delete;
BagOfBytes& operator=(const BagOfBytes&) = delete;
BagOfBytes(BagOfBytes&& that)
: fEndByte{std::exchange(that.fEndByte, nullptr)}
, fCapacity{that.fCapacity}
, fFibProgression{that.fFibProgression} {}
BagOfBytes& operator=(BagOfBytes&& that) {
this->~BagOfBytes();
new (this) BagOfBytes{std::move(that)};
return *this;
}
~BagOfBytes();
static constexpr int PlatformMinimumSizeWithOverhead(int requestedSize, int assumedAlignment) {
return MinimumSizeWithOverhead(
requestedSize, assumedAlignment, sizeof(Block), kMaxAlignment);
}
static constexpr int MinimumSizeWithOverhead(
int requestedSize, int assumedAlignment, int blockSize, int maxAlignment) {
SkASSERT_RELEASE(0 <= requestedSize && requestedSize < kMaxByteSize);
SkASSERT_RELEASE(SkIsPow2(assumedAlignment) && SkIsPow2(maxAlignment));
const int minAlignment = std::min(maxAlignment, assumedAlignment);
int minimumSize = SkToInt(AlignUp(requestedSize, minAlignment))
+ blockSize
+ maxAlignment - minAlignment;
constexpr int k32K = (1 << 15);
if (minimumSize >= k32K && minimumSize < std::numeric_limits<int>::max() - k4K) {
minimumSize = SkToInt(AlignUp(minimumSize, k4K));
}
return minimumSize;
}
template <int size>
using Storage = std::array<char, PlatformMinimumSizeWithOverhead(size, 1)>;
template <typename T>
static bool WillCountFit(int n) {
constexpr int kMaxN = kMaxByteSize / sizeof(T);
return 0 <= n && n < kMaxN;
}
template <typename T> char* allocateBytesFor(int n = 1) {
static_assert(alignof(T) <= kMaxAlignment, "Alignment is too big for arena");
static_assert(sizeof(T) < kMaxByteSize, "Size is too big for arena");
SkASSERT_RELEASE(WillCountFit<T>(n));
int size = n ? n * sizeof(T) : 1;
return this->allocateBytes(size, alignof(T));
}
void* alignedBytes(int unsafeSize, int unsafeAlignment);
private:
inline static constexpr int kMaxAlignment = std::max(16, (int)alignof(std::max_align_t));
inline static constexpr int k4K = (1 << 12);
inline static constexpr int kMaxByteSize = std::numeric_limits<int>::max() - k4K;
#if !defined(SK_FORCE_8_BYTE_ALIGNMENT)
static constexpr int kAllocationAlignment = alignof(std::max_align_t);
#else
static constexpr int kAllocationAlignment = 8;
#endif
static constexpr size_t AlignUp(int size, int alignment) {
return (size + (alignment - 1)) & -alignment;
}
struct Block {
Block(char* previous, char* startOfBlock);
char* const fBlockStart;
Block* const fPrevious;
};
char* allocateBytes(int size, int alignment) {
fCapacity = fCapacity & -alignment;
if (fCapacity < size) {
this->needMoreBytes(size, alignment);
}
char* const ptr = fEndByte - fCapacity;
SkASSERT(((intptr_t)ptr & (alignment - 1)) == 0);
SkASSERT(fCapacity >= size);
fCapacity -= size;
return ptr;
}
void setupBytesAndCapacity(char* bytes, int size);
void needMoreBytes(int size, int alignment);
char* fEndByte{nullptr};
int fCapacity{0};
SkFibBlockSizes<kMaxByteSize> fFibProgression;
};
template <typename T>
class SubRunInitializer {
public:
SubRunInitializer(void* memory) : fMemory{memory} { SkASSERT(memory != nullptr); }
~SubRunInitializer() {
::operator delete(fMemory);
}
template <typename... Args>
T* initialize(Args&&... args) {
SkASSERT(fMemory != nullptr);
return new (std::exchange(fMemory, nullptr)) T(std::forward<Args>(args)...);
}
private:
void* fMemory;
};
class SubRunAllocator {
public:
struct Destroyer {
template <typename T>
void operator()(T* ptr) { ptr->~T(); }
};
struct ArrayDestroyer {
int n;
template <typename T>
void operator()(T* ptr) {
for (int i = 0; i < n; i++) { ptr[i].~T(); }
}
};
template<class T>
inline static constexpr bool HasNoDestructor = std::is_trivially_destructible<T>::value;
SubRunAllocator(char* block, int blockSize, int firstHeapAllocation);
explicit SubRunAllocator(int firstHeapAllocation = 0);
SubRunAllocator(const SubRunAllocator&) = delete;
SubRunAllocator& operator=(const SubRunAllocator&) = delete;
SubRunAllocator(SubRunAllocator&&) = default;
SubRunAllocator& operator=(SubRunAllocator&&) = default;
template <typename T>
static std::tuple<SubRunInitializer<T>, int, SubRunAllocator>
AllocateClassMemoryAndArena(int allocSizeHint) {
SkASSERT_RELEASE(allocSizeHint >= 0);
int extraSize = BagOfBytes::PlatformMinimumSizeWithOverhead(allocSizeHint, alignof(T));
SkASSERT_RELEASE(INT_MAX - SkTo<int>(sizeof(T)) > extraSize);
int totalMemorySize = sizeof(T) + extraSize;
void* memory = ::operator new (totalMemorySize);
SubRunAllocator alloc{SkTAddOffset<char>(memory, sizeof(T)), extraSize, extraSize/2};
return {memory, totalMemorySize, std::move(alloc)};
}
template <typename T, typename... Args> T* makePOD(Args&&... args) {
static_assert(HasNoDestructor<T>, "This is not POD. Use makeUnique.");
char* bytes = fAlloc.template allocateBytesFor<T>();
return new (bytes) T(std::forward<Args>(args)...);
}
template <typename T, typename... Args>
std::unique_ptr<T, Destroyer> makeUnique(Args&&... args) {
static_assert(!HasNoDestructor<T>, "This is POD. Use makePOD.");
char* bytes = fAlloc.template allocateBytesFor<T>();
return std::unique_ptr<T, Destroyer>{new (bytes) T(std::forward<Args>(args)...)};
}
template<typename T> T* makePODArray(int n) {
static_assert(HasNoDestructor<T>, "This is not POD. Use makeUniqueArray.");
return reinterpret_cast<T*>(fAlloc.template allocateBytesFor<T>(n));
}
template<typename T>
SkSpan<T> makePODSpan(SkSpan<const T> s) {
static_assert(HasNoDestructor<T>, "This is not POD. Use makeUniqueArray.");
if (s.empty()) {
return SkSpan<T>{};
}
T* result = this->makePODArray<T>(SkTo<int>(s.size()));
memcpy(result, s.data(), s.size_bytes());
return {result, s.size()};
}
template<typename T, typename Src, typename Map>
SkSpan<T> makePODArray(const Src& src, Map map) {
static_assert(HasNoDestructor<T>, "This is not POD. Use makeUniqueArray.");
int size = SkTo<int>(src.size());
T* result = this->template makePODArray<T>(size);
for (int i = 0; i < size; i++) {
new (&result[i]) T(map(src[i]));
}
return {result, src.size()};
}
template<typename T>
std::unique_ptr<T[], ArrayDestroyer> makeUniqueArray(int n) {
static_assert(!HasNoDestructor<T>, "This is POD. Use makePODArray.");
T* array = reinterpret_cast<T*>(fAlloc.template allocateBytesFor<T>(n));
for (int i = 0; i < n; i++) {
new (&array[i]) T{};
}
return std::unique_ptr<T[], ArrayDestroyer>{array, ArrayDestroyer{n}};
}
template<typename T, typename I>
std::unique_ptr<T[], ArrayDestroyer> makeUniqueArray(int n, I initializer) {
static_assert(!HasNoDestructor<T>, "This is POD. Use makePODArray.");
T* array = reinterpret_cast<T*>(fAlloc.template allocateBytesFor<T>(n));
for (int i = 0; i < n; i++) {
new (&array[i]) T(initializer(i));
}
return std::unique_ptr<T[], ArrayDestroyer>{array, ArrayDestroyer{n}};
}
template<typename T, typename U, typename Map>
std::unique_ptr<T[], ArrayDestroyer> makeUniqueArray(SkSpan<const U> src, Map map) {
static_assert(!HasNoDestructor<T>, "This is POD. Use makePODArray.");
int count = SkCount(src);
T* array = reinterpret_cast<T*>(fAlloc.template allocateBytesFor<T>(src.size()));
for (int i = 0; i < count; ++i) {
new (&array[i]) T(map(src[i]));
}
return std::unique_ptr<T[], ArrayDestroyer>{array, ArrayDestroyer{count}};
}
void* alignedBytes(int size, int alignment);
private:
BagOfBytes fAlloc;
};
template <size_t InlineStorageSize, size_t InlineStorageAlignment>
class STSubRunAllocator : private std::array<char,
BagOfBytes::PlatformMinimumSizeWithOverhead(
InlineStorageSize, InlineStorageAlignment)>,
public SubRunAllocator {
public:
explicit STSubRunAllocator(size_t firstHeapAllocation = InlineStorageSize)
: SubRunAllocator{this->data(), SkToInt(this->size()), SkToInt(firstHeapAllocation)} {}
};
}
#endif