// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // PoolAlloc.cpp: // Implements the class methods for PoolAllocator and Allocation classes. // #include "common/PoolAlloc.h" #include #include #include #include "common/angleutils.h" #include "common/debug.h" #include "common/mathutil.h" #include "common/platform.h" #include "common/tls.h" namespace angle { // // Implement the functionality of the PoolAllocator class, which // is documented in PoolAlloc.h. // PoolAllocator::PoolAllocator(int growthIncrement, int allocationAlignment) : mAlignment(allocationAlignment), #if !defined(ANGLE_DISABLE_POOL_ALLOC) mPageSize(growthIncrement), mFreeList(0), mInUseList(0), mNumCalls(0), mTotalBytes(0), #endif mLocked(false) { #if !defined(ANGLE_DISABLE_POOL_ALLOC) if (mAlignment == 1) { // This is a special fast-path where fastAllocation() is enabled mAlignmentMask = 0; mHeaderSkip = sizeof(Header); } else { #endif // // Adjust mAlignment to be at least pointer aligned and // power of 2. // size_t minAlign = sizeof(void *); mAlignment &= ~(minAlign - 1); if (mAlignment < minAlign) mAlignment = minAlign; mAlignment = gl::ceilPow2(static_cast(mAlignment)); mAlignmentMask = mAlignment - 1; #if !defined(ANGLE_DISABLE_POOL_ALLOC) // // Align header skip // mHeaderSkip = minAlign; if (mHeaderSkip < sizeof(Header)) { mHeaderSkip = rx::roundUpPow2(sizeof(Header), mAlignment); } } // // Don't allow page sizes we know are smaller than all common // OS page sizes. // if (mPageSize < 4 * 1024) mPageSize = 4 * 1024; // // A large mCurrentPageOffset indicates a new page needs to // be obtained to allocate memory. // mCurrentPageOffset = mPageSize; #else // !defined(ANGLE_DISABLE_POOL_ALLOC) mStack.push_back({}); #endif } PoolAllocator::~PoolAllocator() { #if !defined(ANGLE_DISABLE_POOL_ALLOC) while (mInUseList) { Header *next = mInUseList->nextPage; mInUseList->~Header(); delete[] reinterpret_cast(mInUseList); mInUseList = next; } // We should not check the guard blocks // here, because we did it already when the block was // placed into the free list. // while (mFreeList) { Header *next = mFreeList->nextPage; delete[] reinterpret_cast(mFreeList); mFreeList = next; } #else // !defined(ANGLE_DISABLE_POOL_ALLOC) for (auto &allocs : mStack) { for (auto alloc : allocs) { free(alloc); } } mStack.clear(); #endif } // // Check a single guard block for damage // void Allocation::checkGuardBlock(unsigned char *blockMem, unsigned char val, const char *locText) const { #if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS) for (size_t x = 0; x < kGuardBlockSize; x++) { if (blockMem[x] != val) { char assertMsg[80]; // We don't print the assert message. It's here just to be helpful. snprintf(assertMsg, sizeof(assertMsg), "PoolAlloc: Damage %s %zu byte allocation at 0x%p\n", locText, mSize, data()); assert(0 && "PoolAlloc: Damage in guard block"); } } #endif } void PoolAllocator::push() { #if !defined(ANGLE_DISABLE_POOL_ALLOC) AllocState state = {mCurrentPageOffset, mInUseList}; mStack.push_back(state); // // Indicate there is no current page to allocate from. // mCurrentPageOffset = mPageSize; #else // !defined(ANGLE_DISABLE_POOL_ALLOC) mStack.push_back({}); #endif } // // Do a mass-deallocation of all the individual allocations // that have occurred since the last push(), or since the // last pop(), or since the object's creation. // // The deallocated pages are saved for future allocations. // void PoolAllocator::pop() { if (mStack.size() < 1) return; #if !defined(ANGLE_DISABLE_POOL_ALLOC) Header *page = mStack.back().page; mCurrentPageOffset = mStack.back().offset; while (mInUseList != page) { // invoke destructor to free allocation list mInUseList->~Header(); Header *nextInUse = mInUseList->nextPage; if (mInUseList->pageCount > 1) delete[] reinterpret_cast(mInUseList); else { mInUseList->nextPage = mFreeList; mFreeList = mInUseList; } mInUseList = nextInUse; } mStack.pop_back(); #else // !defined(ANGLE_DISABLE_POOL_ALLOC) for (auto &alloc : mStack.back()) { free(alloc); } mStack.pop_back(); #endif } // // Do a mass-deallocation of all the individual allocations // that have occurred. // void PoolAllocator::popAll() { while (mStack.size() > 0) pop(); } void *PoolAllocator::allocate(size_t numBytes) { ASSERT(!mLocked); #if !defined(ANGLE_DISABLE_POOL_ALLOC) // // Just keep some interesting statistics. // ++mNumCalls; mTotalBytes += numBytes; // If we are using guard blocks, all allocations are bracketed by // them: [guardblock][allocation][guardblock]. numBytes is how // much memory the caller asked for. allocationSize is the total // size including guard blocks. In release build, // kGuardBlockSize=0 and this all gets optimized away. size_t allocationSize = Allocation::AllocationSize(numBytes) + mAlignment; // Detect integer overflow. if (allocationSize < numBytes) return 0; // // Do the allocation, most likely case first, for efficiency. // This step could be moved to be inline sometime. // if (allocationSize <= mPageSize - mCurrentPageOffset) { // // Safe to allocate from mCurrentPageOffset. // unsigned char *memory = reinterpret_cast(mInUseList) + mCurrentPageOffset; mCurrentPageOffset += allocationSize; mCurrentPageOffset = (mCurrentPageOffset + mAlignmentMask) & ~mAlignmentMask; return initializeAllocation(mInUseList, memory, numBytes); } if (allocationSize > mPageSize - mHeaderSkip) { // // Do a multi-page allocation. Don't mix these with the others. // The OS is efficient in allocating and freeing multiple pages. // size_t numBytesToAlloc = allocationSize + mHeaderSkip; // Detect integer overflow. if (numBytesToAlloc < allocationSize) return 0; Header *memory = reinterpret_cast
(::new char[numBytesToAlloc]); if (memory == 0) return 0; // Use placement-new to initialize header new (memory) Header(mInUseList, (numBytesToAlloc + mPageSize - 1) / mPageSize); mInUseList = memory; mCurrentPageOffset = mPageSize; // make next allocation come from a new page // No guard blocks for multi-page allocations (yet) void *unalignedPtr = reinterpret_cast(reinterpret_cast(memory) + mHeaderSkip); return std::align(mAlignment, numBytes, unalignedPtr, allocationSize); } unsigned char *newPageAddr = static_cast(allocateNewPage(numBytes, allocationSize)); return initializeAllocation(mInUseList, newPageAddr, numBytes); #else // !defined(ANGLE_DISABLE_POOL_ALLOC) void *alloc = malloc(numBytes + mAlignmentMask); mStack.back().push_back(alloc); intptr_t intAlloc = reinterpret_cast(alloc); intAlloc = (intAlloc + mAlignmentMask) & ~mAlignmentMask; return reinterpret_cast(intAlloc); #endif } #if !defined(ANGLE_DISABLE_POOL_ALLOC) void *PoolAllocator::allocateNewPage(size_t numBytes, size_t allocationSize) { // // Need a simple page to allocate from. // Header *memory; if (mFreeList) { memory = mFreeList; mFreeList = mFreeList->nextPage; } else { memory = reinterpret_cast
(::new char[mPageSize]); if (memory == 0) return 0; } // Use placement-new to initialize header new (memory) Header(mInUseList, 1); mInUseList = memory; unsigned char *ret = reinterpret_cast(mInUseList) + mHeaderSkip; mCurrentPageOffset = (mHeaderSkip + allocationSize + mAlignmentMask) & ~mAlignmentMask; return ret; } #endif void PoolAllocator::lock() { ASSERT(!mLocked); mLocked = true; } void PoolAllocator::unlock() { ASSERT(mLocked); mLocked = false; } // // Check all allocations in a list for damage by calling check on each. // void Allocation::checkAllocList() const { for (const Allocation *alloc = this; alloc != 0; alloc = alloc->mPrevAlloc) alloc->check(); } } // namespace angle