/* * Copyright 2012 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrMemoryPool.h" #include "SkMalloc.h" #include "ops/GrOp.h" #ifdef SK_DEBUG #include <atomic> #endif #ifdef SK_DEBUG #define VALIDATE this->validate() #else #define VALIDATE #endif void GrOpMemoryPool::release(std::unique_ptr<GrOp> op) { GrOp* tmp = op.release(); SkASSERT(tmp); tmp->~GrOp(); fMemoryPool.release(tmp); } constexpr size_t GrMemoryPool::kSmallestMinAllocSize; GrMemoryPool::GrMemoryPool(size_t preallocSize, size_t minAllocSize) { SkDEBUGCODE(fAllocationCnt = 0); SkDEBUGCODE(fAllocBlockCnt = 0); minAllocSize = SkTMax<size_t>(GrSizeAlignUp(minAllocSize, kAlignment), kSmallestMinAllocSize); preallocSize = SkTMax<size_t>(GrSizeAlignUp(preallocSize, kAlignment), minAllocSize); fMinAllocSize = minAllocSize; fSize = 0; fHead = CreateBlock(preallocSize); fTail = fHead; fHead->fNext = nullptr; fHead->fPrev = nullptr; VALIDATE; }; GrMemoryPool::~GrMemoryPool() { VALIDATE; #ifdef SK_DEBUG int i = 0; int n = fAllocatedIDs.count(); fAllocatedIDs.foreach([&i, n] (int32_t id) { if (++i == 1) { SkDebugf("Leaked IDs (in no particular order): %d", id); } else if (i < 11) { SkDebugf(", %d%s", id, (n == i ? "\n" : "")); } else if (i == 11) { SkDebugf(", ...\n"); } }); #endif SkASSERT(0 == fAllocationCnt); SkASSERT(fHead == fTail); SkASSERT(0 == fHead->fLiveCount); DeleteBlock(fHead); }; void* GrMemoryPool::allocate(size_t size) { VALIDATE; size += kPerAllocPad; size = GrSizeAlignUp(size, kAlignment); if (fTail->fFreeSize < size) { size_t blockSize = size + kHeaderSize; blockSize = SkTMax<size_t>(blockSize, fMinAllocSize); BlockHeader* block = CreateBlock(blockSize); block->fPrev = fTail; block->fNext = nullptr; SkASSERT(nullptr == fTail->fNext); fTail->fNext = block; fTail = block; fSize += block->fSize; SkDEBUGCODE(++fAllocBlockCnt); } SkASSERT(kAssignedMarker == fTail->fBlockSentinal); SkASSERT(fTail->fFreeSize >= size); intptr_t ptr = fTail->fCurrPtr; // We stash a pointer to the block header, just before the allocated space, // so that we can decrement the live count on delete in constant time. AllocHeader* allocData = reinterpret_cast<AllocHeader*>(ptr); SkDEBUGCODE(allocData->fSentinal = kAssignedMarker); SkDEBUGCODE(allocData->fID = []{ static std::atomic<int32_t> nextID{1}; return nextID++; }()); // You can set a breakpoint here when a leaked ID is allocated to see the stack frame. SkDEBUGCODE(fAllocatedIDs.add(allocData->fID)); allocData->fHeader = fTail; ptr += kPerAllocPad; fTail->fPrevPtr = fTail->fCurrPtr; fTail->fCurrPtr += size; fTail->fFreeSize -= size; fTail->fLiveCount += 1; SkDEBUGCODE(++fAllocationCnt); VALIDATE; return reinterpret_cast<void*>(ptr); } void GrMemoryPool::release(void* p) { VALIDATE; intptr_t ptr = reinterpret_cast<intptr_t>(p) - kPerAllocPad; AllocHeader* allocData = reinterpret_cast<AllocHeader*>(ptr); SkASSERT(kAssignedMarker == allocData->fSentinal); SkDEBUGCODE(allocData->fSentinal = kFreedMarker); SkDEBUGCODE(fAllocatedIDs.remove(allocData->fID)); BlockHeader* block = allocData->fHeader; SkASSERT(kAssignedMarker == block->fBlockSentinal); if (1 == block->fLiveCount) { // the head block is special, it is reset rather than deleted if (fHead == block) { fHead->fCurrPtr = reinterpret_cast<intptr_t>(fHead) + kHeaderSize; fHead->fLiveCount = 0; fHead->fFreeSize = fHead->fSize - kHeaderSize; } else { BlockHeader* prev = block->fPrev; BlockHeader* next = block->fNext; SkASSERT(prev); prev->fNext = next; if (next) { next->fPrev = prev; } else { SkASSERT(fTail == block); fTail = prev; } fSize -= block->fSize; DeleteBlock(block); SkDEBUGCODE(fAllocBlockCnt--); } } else { --block->fLiveCount; // Trivial reclaim: if we're releasing the most recent allocation, reuse it if (block->fPrevPtr == ptr) { block->fFreeSize += (block->fCurrPtr - block->fPrevPtr); block->fCurrPtr = block->fPrevPtr; } } SkDEBUGCODE(--fAllocationCnt); VALIDATE; } GrMemoryPool::BlockHeader* GrMemoryPool::CreateBlock(size_t blockSize) { blockSize = SkTMax<size_t>(blockSize, kHeaderSize); BlockHeader* block = reinterpret_cast<BlockHeader*>(sk_malloc_throw(blockSize)); // we assume malloc gives us aligned memory SkASSERT(!(reinterpret_cast<intptr_t>(block) % kAlignment)); SkDEBUGCODE(block->fBlockSentinal = kAssignedMarker); block->fLiveCount = 0; block->fFreeSize = blockSize - kHeaderSize; block->fCurrPtr = reinterpret_cast<intptr_t>(block) + kHeaderSize; block->fPrevPtr = 0; // gcc warns on assigning nullptr to an intptr_t. block->fSize = blockSize; return block; } void GrMemoryPool::DeleteBlock(BlockHeader* block) { SkASSERT(kAssignedMarker == block->fBlockSentinal); SkDEBUGCODE(block->fBlockSentinal = kFreedMarker); // FWIW sk_free(block); } void GrMemoryPool::validate() { #ifdef SK_DEBUG BlockHeader* block = fHead; BlockHeader* prev = nullptr; SkASSERT(block); int allocCount = 0; do { SkASSERT(kAssignedMarker == block->fBlockSentinal); allocCount += block->fLiveCount; SkASSERT(prev == block->fPrev); if (prev) { SkASSERT(prev->fNext == block); } intptr_t b = reinterpret_cast<intptr_t>(block); size_t ptrOffset = block->fCurrPtr - b; size_t totalSize = ptrOffset + block->fFreeSize; intptr_t userStart = b + kHeaderSize; SkASSERT(!(b % kAlignment)); SkASSERT(!(totalSize % kAlignment)); SkASSERT(!(block->fCurrPtr % kAlignment)); if (fHead != block) { SkASSERT(block->fLiveCount); SkASSERT(totalSize >= fMinAllocSize); } else { SkASSERT(totalSize == block->fSize); } if (!block->fLiveCount) { SkASSERT(ptrOffset == kHeaderSize); SkASSERT(userStart == block->fCurrPtr); } else { AllocHeader* allocData = reinterpret_cast<AllocHeader*>(userStart); SkASSERT(allocData->fSentinal == kAssignedMarker || allocData->fSentinal == kFreedMarker); SkASSERT(block == allocData->fHeader); } prev = block; } while ((block = block->fNext)); SkASSERT(allocCount == fAllocationCnt); SkASSERT(fAllocationCnt == fAllocatedIDs.count()); SkASSERT(prev == fTail); SkASSERT(fAllocBlockCnt != 0 || fSize == 0); #endif }