//1// Copyright 2019 The ANGLE Project Authors. All rights reserved.2// Use of this source code is governed by a BSD-style license that can be3// found in the LICENSE file.4//5// PoolAlloc.h:6// Defines the class interface for PoolAllocator and the Allocation7// class that it uses internally.8//910#ifndef COMMON_POOLALLOC_H_11#define COMMON_POOLALLOC_H_1213#if !defined(NDEBUG)14# define ANGLE_POOL_ALLOC_GUARD_BLOCKS // define to enable guard block checking15#endif1617//18// This header defines an allocator that can be used to efficiently19// allocate a large number of small requests for heap memory, with the20// intention that they are not individually deallocated, but rather21// collectively deallocated at one time.22//23// This simultaneously24//25// * Makes each individual allocation much more efficient; the26// typical allocation is trivial.27// * Completely avoids the cost of doing individual deallocation.28// * Saves the trouble of tracking down and plugging a large class of leaks.29//30// Individual classes can use this allocator by supplying their own31// new and delete methods.32//3334#include <stddef.h>35#include <string.h>36#include <memory>37#include <vector>3839#include "angleutils.h"40#include "common/debug.h"4142namespace angle43{44// If we are using guard blocks, we must track each individual45// allocation. If we aren't using guard blocks, these46// never get instantiated, so won't have any impact.47//4849class Allocation50{51public:52Allocation(size_t size, unsigned char *mem, Allocation *prev = 0)53: mSize(size), mMem(mem), mPrevAlloc(prev)54{55// Allocations are bracketed:56// [allocationHeader][initialGuardBlock][userData][finalGuardBlock]57// This would be cleaner with if (kGuardBlockSize)..., but that58// makes the compiler print warnings about 0 length memsets,59// even with the if() protecting them.60#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)61memset(preGuard(), kGuardBlockBeginVal, kGuardBlockSize);62memset(data(), kUserDataFill, mSize);63memset(postGuard(), kGuardBlockEndVal, kGuardBlockSize);64#endif65}6667void checkAlloc() const68{69checkGuardBlock(preGuard(), kGuardBlockBeginVal, "before");70checkGuardBlock(postGuard(), kGuardBlockEndVal, "after");71}7273void checkAllocList() const;7475// Return total size needed to accommodate user buffer of 'size',76// plus our tracking data.77static size_t AllocationSize(size_t size) { return size + 2 * kGuardBlockSize + HeaderSize(); }7879// Offset from surrounding buffer to get to user data buffer.80static unsigned char *OffsetAllocation(unsigned char *m)81{82return m + kGuardBlockSize + HeaderSize();83}8485private:86void checkGuardBlock(unsigned char *blockMem, unsigned char val, const char *locText) const;8788// Find offsets to pre and post guard blocks, and user data buffer89unsigned char *preGuard() const { return mMem + HeaderSize(); }90unsigned char *data() const { return preGuard() + kGuardBlockSize; }91unsigned char *postGuard() const { return data() + mSize; }92size_t mSize; // size of the user data area93unsigned char *mMem; // beginning of our allocation (pts to header)94Allocation *mPrevAlloc; // prior allocation in the chain9596static constexpr unsigned char kGuardBlockBeginVal = 0xfb;97static constexpr unsigned char kGuardBlockEndVal = 0xfe;98static constexpr unsigned char kUserDataFill = 0xcd;99#if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)100static constexpr size_t kGuardBlockSize = 16;101static constexpr size_t HeaderSize() { return sizeof(Allocation); }102#else103static constexpr size_t kGuardBlockSize = 0;104static constexpr size_t HeaderSize() { return 0; }105#endif106};107108//109// There are several stacks. One is to track the pushing and popping110// of the user, and not yet implemented. The others are simply a111// repositories of free pages or used pages.112//113// Page stacks are linked together with a simple header at the beginning114// of each allocation obtained from the underlying OS. Multi-page allocations115// are returned to the OS. Individual page allocations are kept for future116// re-use.117//118// The "page size" used is not, nor must it match, the underlying OS119// page size. But, having it be about that size or equal to a set of120// pages is likely most optimal.121//122class PoolAllocator : angle::NonCopyable123{124public:125static const int kDefaultAlignment = 16;126//127// Create PoolAllocator. If alignment is set to 1 byte then fastAllocate()128// function can be used to make allocations with less overhead.129//130PoolAllocator(int growthIncrement = 8 * 1024, int allocationAlignment = kDefaultAlignment);131132//133// Don't call the destructor just to free up the memory, call pop()134//135~PoolAllocator();136137//138// Initialize page size and alignment after construction139//140void initialize(int pageSize, int alignment);141142//143// Call push() to establish a new place to pop memory to. Does not144// have to be called to get things started.145//146void push();147148//149// Call pop() to free all memory allocated since the last call to push(),150// or if no last call to push, frees all memory since first allocation.151//152void pop();153154//155// Call popAll() to free all memory allocated.156//157void popAll();158159//160// Call allocate() to actually acquire memory. Returns 0 if no memory161// available, otherwise a properly aligned pointer to 'numBytes' of memory.162//163void *allocate(size_t numBytes);164165//166// Call fastAllocate() for a faster allocate function that does minimal bookkeeping167// preCondition: Allocator must have been created w/ alignment of 1168ANGLE_INLINE uint8_t *fastAllocate(size_t numBytes)169{170#if defined(ANGLE_DISABLE_POOL_ALLOC)171return reinterpret_cast<uint8_t *>(allocate(numBytes));172#else173ASSERT(mAlignment == 1);174// No multi-page allocations175ASSERT(numBytes <= (mPageSize - mHeaderSkip));176//177// Do the allocation, most likely case inline first, for efficiency.178//179if (numBytes <= mPageSize - mCurrentPageOffset)180{181//182// Safe to allocate from mCurrentPageOffset.183//184uint8_t *memory = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;185mCurrentPageOffset += numBytes;186return memory;187}188return reinterpret_cast<uint8_t *>(allocateNewPage(numBytes, numBytes));189#endif190}191192//193// There is no deallocate. The point of this class is that194// deallocation can be skipped by the user of it, as the model195// of use is to simultaneously deallocate everything at once196// by calling pop(), and to not have to solve memory leak problems.197//198199// Catch unwanted allocations.200// TODO(jmadill): Remove this when we remove the global allocator.201void lock();202void unlock();203204private:205size_t mAlignment; // all returned allocations will be aligned at206// this granularity, which will be a power of 2207size_t mAlignmentMask;208#if !defined(ANGLE_DISABLE_POOL_ALLOC)209friend struct Header;210211struct Header212{213Header(Header *nextPage, size_t pageCount)214: nextPage(nextPage),215pageCount(pageCount)216# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)217,218lastAllocation(0)219# endif220{}221222~Header()223{224# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)225if (lastAllocation)226lastAllocation->checkAllocList();227# endif228}229230Header *nextPage;231size_t pageCount;232# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)233Allocation *lastAllocation;234# endif235};236237struct AllocState238{239size_t offset;240Header *page;241};242using AllocStack = std::vector<AllocState>;243244// Slow path of allocation when we have to get a new page.245void *allocateNewPage(size_t numBytes, size_t allocationSize);246// Track allocations if and only if we're using guard blocks247void *initializeAllocation(Header *block, unsigned char *memory, size_t numBytes)248{249# if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)250new (memory) Allocation(numBytes + mAlignment, memory, block->lastAllocation);251block->lastAllocation = reinterpret_cast<Allocation *>(memory);252# endif253// The OffsetAllocation() call is optimized away if !defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)254void *unalignedPtr = Allocation::OffsetAllocation(memory);255size_t alignedBytes = numBytes + mAlignment;256return std::align(mAlignment, numBytes, unalignedPtr, alignedBytes);257}258259size_t mPageSize; // granularity of allocation from the OS260size_t mHeaderSkip; // amount of memory to skip to make room for the261// header (basically, size of header, rounded262// up to make it aligned263size_t mCurrentPageOffset; // next offset in top of inUseList to allocate from264Header *mFreeList; // list of popped memory265Header *mInUseList; // list of all memory currently being used266AllocStack mStack; // stack of where to allocate from, to partition pool267268int mNumCalls; // just an interesting statistic269size_t mTotalBytes; // just an interesting statistic270271#else // !defined(ANGLE_DISABLE_POOL_ALLOC)272std::vector<std::vector<void *>> mStack;273#endif274275bool mLocked;276};277278} // namespace angle279280#endif // COMMON_POOLALLOC_H_281282283