blob: 1c902bfdd75274e217a26496bcd4f4050660900d [file] [log] [blame]
//
// 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.h:
// Defines the class interface for PoolAllocator and the Allocation
// class that it uses internally.
//
#ifndef COMMON_POOLALLOC_H_
#define COMMON_POOLALLOC_H_
#if !defined(NDEBUG)
# define ANGLE_POOL_ALLOC_GUARD_BLOCKS // define to enable guard block sanity checking
#endif
//
// This header defines an allocator that can be used to efficiently
// allocate a large number of small requests for heap memory, with the
// intention that they are not individually deallocated, but rather
// collectively deallocated at one time.
//
// This simultaneously
//
// * Makes each individual allocation much more efficient; the
// typical allocation is trivial.
// * Completely avoids the cost of doing individual deallocation.
// * Saves the trouble of tracking down and plugging a large class of leaks.
//
// Individual classes can use this allocator by supplying their own
// new and delete methods.
//
#include <stddef.h>
#include <string.h>
#include <memory>
#include <vector>
#include "angleutils.h"
#include "common/debug.h"
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 check() const
{
checkGuardBlock(preGuard(), kGuardBlockBeginVal, "before");
checkGuardBlock(postGuard(), kGuardBlockEndVal, "after");
}
void checkAllocList() const;
// Return total size needed to accommodate user buffer of 'size',
// plus our tracking data.
static size_t AllocationSize(size_t size) { return size + 2 * kGuardBlockSize + HeaderSize(); }
// Offset from surrounding buffer to get to user data buffer.
static unsigned char *OffsetAllocation(unsigned char *m)
{
return m + kGuardBlockSize + HeaderSize();
}
private:
void checkGuardBlock(unsigned char *blockMem, unsigned char val, const char *locText) const;
// 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 (pts 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
};
//
// There are several stacks. One is to track the pushing and popping
// of the user, and not yet implemented. The others are simply a
// repositories of free pages or used pages.
//
// Page stacks are linked together with a simple header at the beginning
// of each allocation obtained from the underlying OS. Multi-page allocations
// are returned to the OS. Individual page allocations are kept for future
// re-use.
//
// The "page size" used is not, nor must it match, the underlying OS
// page size. But, having it be about that size or equal to a set of
// pages is likely most optimal.
//
class PoolAllocator : angle::NonCopyable
{
public:
static const int kDefaultAlignment = 16;
//
// Create PoolAllocator. If alignment is be set to 1 byte then fastAllocate()
// function can be used to make allocations with less overhead.
//
PoolAllocator(int growthIncrement = 8 * 1024, int allocationAlignment = kDefaultAlignment);
//
// Don't call the destructor just to free up the memory, call pop()
//
~PoolAllocator();
//
// Call push() to establish a new place to pop memory to. Does not
// have to be called to get things started.
//
void push();
//
// Call pop() to free all memory allocated since the last call to push(),
// or if no last call to push, frees all memory since first allocation.
//
void pop();
//
// Call popAll() to free all memory allocated.
//
void popAll();
//
// Call allocate() to actually acquire memory. Returns 0 if no memory
// available, otherwise a properly aligned pointer to 'numBytes' of memory.
//
void *allocate(size_t numBytes);
//
// Call fastAllocate() for a faster allocate function that does minimal bookkeeping
// preCondition: Allocator must have been created w/ alignment of 1
ANGLE_INLINE uint8_t *fastAllocate(size_t numBytes)
{
#if defined(ANGLE_DISABLE_POOL_ALLOC)
return reinterpret_cast<uint8_t *>(allocate(numBytes));
#else
ASSERT(mAlignment == 1);
// No multi-page allocations
ASSERT(numBytes <= (mPageSize - mHeaderSkip));
//
// Do the allocation, most likely case inline first, for efficiency.
//
if (numBytes <= mPageSize - mCurrentPageOffset)
{
//
// Safe to allocate from mCurrentPageOffset.
//
uint8_t *memory = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
mCurrentPageOffset += numBytes;
return memory;
}
return reinterpret_cast<uint8_t *>(allocateNewPage(numBytes, numBytes));
#endif
}
//
// There is no deallocate. The point of this class is that
// deallocation can be skipped by the user of it, as the model
// of use is to simultaneously deallocate everything at once
// by calling pop(), and to not have to solve memory leak problems.
//
// Catch unwanted allocations.
// TODO(jmadill): Remove this when we remove the global allocator.
void lock();
void unlock();
private:
size_t mAlignment; // all returned allocations will be aligned at
// this granularity, which will be a power of 2
size_t mAlignmentMask;
#if !defined(ANGLE_DISABLE_POOL_ALLOC)
friend struct Header;
struct Header
{
Header(Header *nextPage, size_t pageCount)
: nextPage(nextPage),
pageCount(pageCount)
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
,
lastAllocation(0)
# endif
{}
~Header()
{
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
if (lastAllocation)
lastAllocation->checkAllocList();
# endif
}
Header *nextPage;
size_t pageCount;
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
Allocation *lastAllocation;
# endif
};
struct AllocState
{
size_t offset;
Header *page;
};
using AllocStack = std::vector<AllocState>;
// Slow path of allocation when we have to get a new page.
void *allocateNewPage(size_t numBytes, size_t allocationSize);
// Track allocations if and only if we're using guard blocks
void *initializeAllocation(Header *block, unsigned char *memory, size_t numBytes)
{
# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
new (memory) Allocation(numBytes + mAlignment, memory, block->lastAllocation);
block->lastAllocation = reinterpret_cast<Allocation *>(memory);
# endif
// The OffsetAllocation() call is optimized away if !defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
void *unalignedPtr = Allocation::OffsetAllocation(memory);
size_t alignedBytes = numBytes + mAlignment;
return std::align(mAlignment, numBytes, unalignedPtr, alignedBytes);
}
size_t mPageSize; // granularity of allocation from the OS
size_t mHeaderSkip; // amount of memory to skip to make room for the
// header (basically, size of header, rounded
// up to make it aligned
size_t mCurrentPageOffset; // next offset in top of inUseList to allocate from
Header *mFreeList; // list of popped memory
Header *mInUseList; // list of all memory currently being used
AllocStack mStack; // stack of where to allocate from, to partition pool
int mNumCalls; // just an interesting statistic
size_t mTotalBytes; // just an interesting statistic
#else // !defined(ANGLE_DISABLE_POOL_ALLOC)
std::vector<std::vector<void *>> mStack;
#endif
bool mLocked;
};
} // namespace angle
#endif // COMMON_POOLALLOC_H_