498 lines
15 KiB
C++
498 lines
15 KiB
C++
//
|
|
// 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 <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
#include "common/angleutils.h"
|
|
#include "common/debug.h"
|
|
#include "common/mathutil.h"
|
|
#include "common/platform.h"
|
|
#include "common/tls.h"
|
|
|
|
#if defined(ANGLE_WITH_ASAN)
|
|
# include <sanitizer/asan_interface.h>
|
|
#endif
|
|
|
|
namespace angle
|
|
{
|
|
// If we are using guard blocks, we must track each individual allocation. If we aren't using guard
|
|
// blocks, these never get instantiated, so won't have any impact.
|
|
|
|
class Allocation
|
|
{
|
|
public:
|
|
Allocation(size_t size, unsigned char *mem, Allocation *prev = 0)
|
|
: mSize(size), mMem(mem), mPrevAlloc(prev)
|
|
{
|
|
// Allocations are bracketed:
|
|
//
|
|
// [allocationHeader][initialGuardBlock][userData][finalGuardBlock]
|
|
//
|
|
// This would be cleaner with if (kGuardBlockSize)..., but that makes the compiler print
|
|
// warnings about 0 length memsets, even with the if() protecting them.
|
|
#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
|
|
memset(preGuard(), kGuardBlockBeginVal, kGuardBlockSize);
|
|
memset(data(), kUserDataFill, mSize);
|
|
memset(postGuard(), kGuardBlockEndVal, kGuardBlockSize);
|
|
#endif
|
|
}
|
|
|
|
void checkAllocList() const;
|
|
|
|
static size_t AlignedHeaderSize(uint8_t *allocationBasePtr, size_t alignment)
|
|
{
|
|
// Make sure that the data offset after the header is aligned to the given alignment.
|
|
size_t base = reinterpret_cast<size_t>(allocationBasePtr);
|
|
return rx::roundUpPow2(base + kGuardBlockSize + HeaderSize(), alignment) - base;
|
|
}
|
|
|
|
// Return total size needed to accommodate user buffer of 'size',
|
|
// plus our tracking data and any necessary alignments.
|
|
static size_t AllocationSize(uint8_t *allocationBasePtr,
|
|
size_t size,
|
|
size_t alignment,
|
|
size_t *preAllocationPaddingOut)
|
|
{
|
|
// The allocation will be laid out as such:
|
|
//
|
|
// Aligned to |alignment|
|
|
// ^
|
|
// preAllocationPaddingOut |
|
|
// ___^___ |
|
|
// / \ |
|
|
// <padding>[header][guard][data][guard]
|
|
// \___________ __________/
|
|
// V
|
|
// dataOffset
|
|
//
|
|
// Note that alignment is at least as much as a pointer alignment, so the pointers in the
|
|
// header are also necessarily aligned appropriately.
|
|
//
|
|
size_t dataOffset = AlignedHeaderSize(allocationBasePtr, alignment);
|
|
*preAllocationPaddingOut = dataOffset - HeaderSize() - kGuardBlockSize;
|
|
|
|
return dataOffset + size + kGuardBlockSize;
|
|
}
|
|
|
|
// Given memory pointing to |header|, returns |data|.
|
|
static uint8_t *GetDataPointer(uint8_t *memory, size_t alignment)
|
|
{
|
|
uint8_t *alignedPtr = memory + kGuardBlockSize + HeaderSize();
|
|
|
|
// |memory| must be aligned already such that user data is aligned to |alignment|.
|
|
ASSERT((reinterpret_cast<uintptr_t>(alignedPtr) & (alignment - 1)) == 0);
|
|
|
|
return alignedPtr;
|
|
}
|
|
|
|
private:
|
|
void checkGuardBlock(unsigned char *blockMem, unsigned char val, const char *locText) const;
|
|
|
|
void checkAlloc() const
|
|
{
|
|
checkGuardBlock(preGuard(), kGuardBlockBeginVal, "before");
|
|
checkGuardBlock(postGuard(), kGuardBlockEndVal, "after");
|
|
}
|
|
|
|
// Find offsets to pre and post guard blocks, and user data buffer
|
|
unsigned char *preGuard() const { return mMem + HeaderSize(); }
|
|
unsigned char *data() const { return preGuard() + kGuardBlockSize; }
|
|
unsigned char *postGuard() const { return data() + mSize; }
|
|
size_t mSize; // size of the user data area
|
|
unsigned char *mMem; // beginning of our allocation (points to header)
|
|
Allocation *mPrevAlloc; // prior allocation in the chain
|
|
|
|
static constexpr unsigned char kGuardBlockBeginVal = 0xfb;
|
|
static constexpr unsigned char kGuardBlockEndVal = 0xfe;
|
|
static constexpr unsigned char kUserDataFill = 0xcd;
|
|
#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
|
|
static constexpr size_t kGuardBlockSize = 16;
|
|
static constexpr size_t HeaderSize() { return sizeof(Allocation); }
|
|
#else
|
|
static constexpr size_t kGuardBlockSize = 0;
|
|
static constexpr size_t HeaderSize() { return 0; }
|
|
#endif
|
|
};
|
|
|
|
#if !defined(ANGLE_DISABLE_POOL_ALLOC)
|
|
class PageHeader
|
|
{
|
|
public:
|
|
PageHeader(PageHeader *nextPage, size_t pageCount)
|
|
: nextPage(nextPage),
|
|
pageCount(pageCount)
|
|
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
|
|
,
|
|
lastAllocation(nullptr)
|
|
# endif
|
|
{}
|
|
|
|
~PageHeader()
|
|
{
|
|
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
|
|
if (lastAllocation)
|
|
{
|
|
lastAllocation->checkAllocList();
|
|
}
|
|
# endif
|
|
}
|
|
|
|
PageHeader *nextPage;
|
|
size_t pageCount;
|
|
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
|
|
Allocation *lastAllocation;
|
|
# endif
|
|
};
|
|
#endif
|
|
|
|
//
|
|
// 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(nullptr),
|
|
mInUseList(nullptr),
|
|
mNumCalls(0),
|
|
mTotalBytes(0),
|
|
#endif
|
|
mLocked(false)
|
|
{
|
|
initialize(growthIncrement, allocationAlignment);
|
|
}
|
|
|
|
void PoolAllocator::initialize(int pageSize, int alignment)
|
|
{
|
|
mAlignment = alignment;
|
|
#if !defined(ANGLE_DISABLE_POOL_ALLOC)
|
|
mPageSize = pageSize;
|
|
mPageHeaderSkip = sizeof(PageHeader);
|
|
|
|
// Alignment == 1 is a special fast-path where fastAllocate() is enabled
|
|
if (mAlignment != 1)
|
|
{
|
|
#endif
|
|
// Adjust mAlignment to be at least pointer aligned and
|
|
// power of 2.
|
|
//
|
|
size_t minAlign = sizeof(void *);
|
|
if (mAlignment < minAlign)
|
|
{
|
|
mAlignment = minAlign;
|
|
}
|
|
mAlignment = gl::ceilPow2(static_cast<unsigned int>(mAlignment));
|
|
#if !defined(ANGLE_DISABLE_POOL_ALLOC)
|
|
}
|
|
//
|
|
// 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)
|
|
{
|
|
PageHeader *next = mInUseList->nextPage;
|
|
mInUseList->~PageHeader();
|
|
delete[] reinterpret_cast<char *>(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)
|
|
{
|
|
PageHeader *next = mFreeList->nextPage;
|
|
delete[] reinterpret_cast<char *>(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)
|
|
PageHeader *page = mStack.back().page;
|
|
mCurrentPageOffset = mStack.back().offset;
|
|
|
|
while (mInUseList != page)
|
|
{
|
|
|
|
// invoke destructor to free allocation list
|
|
mInUseList->~PageHeader();
|
|
|
|
PageHeader *nextInUse = mInUseList->nextPage;
|
|
if (mInUseList->pageCount > 1)
|
|
{
|
|
delete[] reinterpret_cast<char *>(mInUseList);
|
|
}
|
|
else
|
|
{
|
|
# if defined(ANGLE_WITH_ASAN)
|
|
// Clear any container annotations left over from when the memory
|
|
// was last used. (crbug.com/1419798)
|
|
__asan_unpoison_memory_region(mInUseList, mPageSize);
|
|
# endif
|
|
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;
|
|
|
|
uint8_t *currentPagePtr = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
|
|
|
|
size_t preAllocationPadding = 0;
|
|
size_t allocationSize =
|
|
Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
|
|
|
|
// Integer overflow is unexpected.
|
|
ASSERT(allocationSize >= numBytes);
|
|
|
|
// Do the allocation, most likely case first, for efficiency.
|
|
if (allocationSize <= mPageSize - mCurrentPageOffset)
|
|
{
|
|
// There is enough room to allocate from the current page at mCurrentPageOffset.
|
|
uint8_t *memory = currentPagePtr + preAllocationPadding;
|
|
mCurrentPageOffset += allocationSize;
|
|
|
|
return initializeAllocation(memory, numBytes);
|
|
}
|
|
|
|
if (allocationSize > mPageSize - mPageHeaderSkip)
|
|
{
|
|
// If the allocation is larger than a whole page, do a multi-page allocation. These are not
|
|
// mixed with the others. The OS is efficient in allocating and freeing multiple pages.
|
|
|
|
// We don't know what the alignment of the new allocated memory will be, so conservatively
|
|
// allocate enough memory for up to alignment extra bytes being needed.
|
|
allocationSize = Allocation::AllocationSize(reinterpret_cast<uint8_t *>(mPageHeaderSkip),
|
|
numBytes, mAlignment, &preAllocationPadding);
|
|
|
|
size_t numBytesToAlloc = allocationSize + mPageHeaderSkip + mAlignment;
|
|
|
|
// Integer overflow is unexpected.
|
|
ASSERT(numBytesToAlloc >= allocationSize);
|
|
|
|
PageHeader *memory = reinterpret_cast<PageHeader *>(::new char[numBytesToAlloc]);
|
|
if (memory == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Use placement-new to initialize header
|
|
new (memory) PageHeader(mInUseList, (numBytesToAlloc + mPageSize - 1) / mPageSize);
|
|
mInUseList = memory;
|
|
|
|
// Make next allocation come from a new page
|
|
mCurrentPageOffset = mPageSize;
|
|
|
|
// Now that we actually have the pointer, make sure the data pointer will be aligned.
|
|
currentPagePtr = reinterpret_cast<uint8_t *>(memory) + mPageHeaderSkip;
|
|
Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
|
|
|
|
return initializeAllocation(currentPagePtr + preAllocationPadding, numBytes);
|
|
}
|
|
|
|
uint8_t *newPageAddr = allocateNewPage(numBytes);
|
|
return initializeAllocation(newPageAddr, numBytes);
|
|
|
|
#else // !defined(ANGLE_DISABLE_POOL_ALLOC)
|
|
|
|
void *alloc = malloc(numBytes + mAlignment - 1);
|
|
mStack.back().push_back(alloc);
|
|
|
|
intptr_t intAlloc = reinterpret_cast<intptr_t>(alloc);
|
|
intAlloc = rx::roundUpPow2<intptr_t>(intAlloc, mAlignment);
|
|
return reinterpret_cast<void *>(intAlloc);
|
|
#endif
|
|
}
|
|
|
|
#if !defined(ANGLE_DISABLE_POOL_ALLOC)
|
|
uint8_t *PoolAllocator::allocateNewPage(size_t numBytes)
|
|
{
|
|
// Need a simple page to allocate from. Pick a page from the free list, if any. Otherwise need
|
|
// to make the allocation.
|
|
PageHeader *memory;
|
|
if (mFreeList)
|
|
{
|
|
memory = mFreeList;
|
|
mFreeList = mFreeList->nextPage;
|
|
}
|
|
else
|
|
{
|
|
memory = reinterpret_cast<PageHeader *>(::new char[mPageSize]);
|
|
if (memory == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
// Use placement-new to initialize header
|
|
new (memory) PageHeader(mInUseList, 1);
|
|
mInUseList = memory;
|
|
|
|
// Leave room for the page header.
|
|
mCurrentPageOffset = mPageHeaderSkip;
|
|
uint8_t *currentPagePtr = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
|
|
|
|
size_t preAllocationPadding = 0;
|
|
size_t allocationSize =
|
|
Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
|
|
|
|
mCurrentPageOffset += allocationSize;
|
|
|
|
// The new allocation is made after the page header and any alignment required before it.
|
|
return reinterpret_cast<uint8_t *>(mInUseList) + mPageHeaderSkip + preAllocationPadding;
|
|
}
|
|
|
|
void *PoolAllocator::initializeAllocation(uint8_t *memory, size_t numBytes)
|
|
{
|
|
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
|
|
new (memory) Allocation(numBytes, memory, mInUseList->lastAllocation);
|
|
mInUseList->lastAllocation = reinterpret_cast<Allocation *>(memory);
|
|
# endif
|
|
|
|
return Allocation::GetDataPointer(memory, mAlignment);
|
|
}
|
|
#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 != nullptr; alloc = alloc->mPrevAlloc)
|
|
{
|
|
alloc->checkAlloc();
|
|
}
|
|
}
|
|
|
|
} // namespace angle
|