* Copyright 2020 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/base/SkBlockAllocator.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkTo.h"
#ifdef SK_DEBUG
#include <vector>
#endif
SkBlockAllocator::SkBlockAllocator(GrowthPolicy policy, size_t blockIncrementBytes,
size_t additionalPreallocBytes)
: fTail(&fHead)
, fBlockIncrement(SkTo<uint16_t>(
std::min(SkAlignTo(blockIncrementBytes, kAddressAlign) / kAddressAlign,
(size_t) std::numeric_limits<uint16_t>::max())))
, fGrowthPolicy(static_cast<uint64_t>(policy))
, fN0((policy == GrowthPolicy::kLinear || policy == GrowthPolicy::kExponential) ? 1 : 0)
, fN1(1)
, fHead(nullptr, additionalPreallocBytes + BaseHeadBlockSize()) {
SkASSERT(fBlockIncrement >= 1);
SkASSERT(additionalPreallocBytes <= kMaxAllocationSize);
}
SkBlockAllocator::Block::Block(Block* prev, int allocationSize)
: fNext(nullptr)
, fPrev(prev)
, fSize(allocationSize)
, fCursor(kDataStart)
, fMetadata(0)
, fAllocatorMetadata(0) {
SkASSERT(allocationSize >= (int) sizeof(Block));
SkDEBUGCODE(fSentinel = kAssignedMarker;)
this->poisonRange(kDataStart, fSize);
}
SkBlockAllocator::Block::~Block() {
this->unpoisonRange(kDataStart, fSize);
SkASSERT(fSentinel == kAssignedMarker);
SkDEBUGCODE(fSentinel = kFreedMarker;)
}
size_t SkBlockAllocator::totalSize() const {
size_t size = offsetof(SkBlockAllocator, fHead) + this->scratchBlockSize();
for (const Block* b : this->blocks()) {
size += b->fSize;
}
SkASSERT(size >= this->preallocSize());
return size;
}
size_t SkBlockAllocator::totalUsableSpace() const {
size_t size = this->scratchBlockSize();
if (size > 0) {
size -= kDataStart;
}
for (const Block* b : this->blocks()) {
size += (b->fSize - kDataStart);
}
SkASSERT(size >= this->preallocUsableSpace());
return size;
}
size_t SkBlockAllocator::totalSpaceInUse() const {
size_t size = 0;
for (const Block* b : this->blocks()) {
size += (b->fCursor - kDataStart);
}
SkASSERT(size <= this->totalUsableSpace());
return size;
}
SkBlockAllocator::Block* SkBlockAllocator::findOwningBlock(const void* p) {
uintptr_t ptr = reinterpret_cast<uintptr_t>(p);
for (Block* b : this->rblocks()) {
uintptr_t lowerBound = reinterpret_cast<uintptr_t>(b) + kDataStart;
uintptr_t upperBound = reinterpret_cast<uintptr_t>(b) + b->fSize;
if (lowerBound <= ptr && ptr < upperBound) {
SkASSERT(b->fSentinel == kAssignedMarker);
return b;
}
}
return nullptr;
}
void SkBlockAllocator::releaseBlock(Block* block) {
if (block == &fHead) {
block->fCursor = kDataStart;
block->fMetadata = 0;
block->poisonRange(kDataStart, block->fSize);
} else {
SkASSERT(block->fPrev);
block->fPrev->fNext = block->fNext;
if (block->fNext) {
SkASSERT(fTail != block);
block->fNext->fPrev = block->fPrev;
} else {
SkASSERT(fTail == block);
fTail = block->fPrev;
}
if (this->scratchBlockSize() < block->fSize) {
SkASSERT(block != fHead.fPrev);
if (fHead.fPrev) {
delete fHead.fPrev;
}
block->markAsScratch();
fHead.fPrev = block;
} else {
delete block;
}
}
GrowthPolicy gp = static_cast<GrowthPolicy>(fGrowthPolicy);
if (fN0 > 0 && (fN1 > 1 || gp == GrowthPolicy::kFibonacci)) {
SkASSERT(gp != GrowthPolicy::kFixed);
if (gp == GrowthPolicy::kLinear) {
fN1 = fN1 - fN0;
} else if (gp == GrowthPolicy::kFibonacci) {
int temp = fN1 - fN0;
fN1 = fN1 - temp;
fN0 = temp;
} else {
SkASSERT(gp == GrowthPolicy::kExponential);
fN1 = fN1 >> 1;
fN0 = fN1;
}
}
SkASSERT(fN1 >= 1 && fN0 >= 0);
}
void SkBlockAllocator::stealHeapBlocks(SkBlockAllocator* other) {
Block* toSteal = other->fHead.fNext;
if (toSteal) {
SkASSERT(other->fTail != &other->fHead);
toSteal->fPrev = fTail;
fTail->fNext = toSteal;
fTail = other->fTail;
other->fTail = &other->fHead;
other->fHead.fNext = nullptr;
}
}
void SkBlockAllocator::reset() {
for (Block* b : this->rblocks()) {
if (b == &fHead) {
fTail = b;
b->fNext = nullptr;
b->fCursor = kDataStart;
b->fMetadata = 0;
b->fAllocatorMetadata = 0;
b->poisonRange(kDataStart, b->fSize);
this->resetScratchSpace();
} else {
delete b;
}
}
SkASSERT(fTail == &fHead && fHead.fNext == nullptr && fHead.fPrev == nullptr &&
fHead.metadata() == 0 && fHead.fCursor == kDataStart);
GrowthPolicy gp = static_cast<GrowthPolicy>(fGrowthPolicy);
fN0 = (gp == GrowthPolicy::kLinear || gp == GrowthPolicy::kExponential) ? 1 : 0;
fN1 = 1;
}
void SkBlockAllocator::resetScratchSpace() {
if (fHead.fPrev) {
delete fHead.fPrev;
fHead.fPrev = nullptr;
}
}
void SkBlockAllocator::addBlock(int minSize, int maxSize) {
SkASSERT(minSize > (int) sizeof(Block) && minSize <= maxSize);
static constexpr int kMaxN = (1 << 23) - 1;
static_assert(2 * kMaxN <= std::numeric_limits<int32_t>::max());
auto alignAllocSize = [](int size) {
int mask = size > (1 << 15) ? ((1 << 12) - 1) : (kAddressAlign - 1);
return (size + mask) & ~mask;
};
int allocSize;
void* mem = nullptr;
if (this->scratchBlockSize() >= minSize) {
SkASSERT(fHead.fPrev->isScratch());
allocSize = fHead.fPrev->fSize;
mem = fHead.fPrev;
fHead.fPrev = nullptr;
} else if (minSize < maxSize) {
GrowthPolicy gp = static_cast<GrowthPolicy>(fGrowthPolicy);
int nextN1 = fN0 + fN1;
int nextN0;
if (gp == GrowthPolicy::kFixed || gp == GrowthPolicy::kLinear) {
nextN0 = fN0;
} else if (gp == GrowthPolicy::kFibonacci) {
nextN0 = fN1;
} else {
SkASSERT(gp == GrowthPolicy::kExponential);
nextN0 = nextN1;
}
fN0 = std::min(kMaxN, nextN0);
fN1 = std::min(kMaxN, nextN1);
int sizeIncrement = fBlockIncrement * kAddressAlign;
if (maxSize / sizeIncrement < nextN1) {
allocSize = maxSize;
} else {
allocSize = std::min(alignAllocSize(std::max(minSize, sizeIncrement * nextN1)),
maxSize);
}
} else {
SkASSERT(minSize == maxSize);
allocSize = alignAllocSize(minSize);
}
if (!mem) {
mem = operator new(allocSize);
}
fTail->fNext = new (mem) Block(fTail, allocSize);
fTail = fTail->fNext;
}
#ifdef SK_DEBUG
void SkBlockAllocator::validate() const {
std::vector<const Block*> blocks;
const Block* prev = nullptr;
for (const Block* block : this->blocks()) {
blocks.push_back(block);
SkASSERT(kAssignedMarker == block->fSentinel);
if (block == &fHead) {
SkASSERT(!prev && (!fHead.fPrev || fHead.fPrev->isScratch()));
} else {
SkASSERT(prev == block->fPrev);
}
if (prev) {
SkASSERT(prev->fNext == block);
}
SkASSERT(block->fSize >= (int) sizeof(Block));
SkASSERT(block->fCursor >= kDataStart);
SkASSERT(block->fCursor <= block->fSize);
prev = block;
}
SkASSERT(prev == fTail);
SkASSERT(!blocks.empty());
SkASSERT(blocks[0] == &fHead);
size_t j = blocks.size();
for (const Block* b : this->rblocks()) {
SkASSERT(b == blocks[j - 1]);
j--;
}
SkASSERT(j == 0);
}
#endif