Edit

kc3-lang/angle/src/common/PoolAlloc.h

Branch :

  • Show log

    Commit

  • Author : Tobin Ehlis
    Date : 2019-01-17 12:25:54
    Hash : 5546fb4f
    Message : Vulkan:Adding custom pool allocator Migrated pool allocator used by compiler to common. Planning to use this for ANGLE custom command buffers so this some refactoring in preparation for that work. Added a unit test to check PoolAllocator functionality. Bug: angleproject:2951 Reviewed-on: https://chromium-review.googlesource.com/c/1476953 Reviewed-by: Jamie Madill <jmadill@google.com> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Commit-Queue: Tobin Ehlis <tobine@google.com> Change-Id: I0b4f3d55ea1799e35c9799c221f7129233f30b24 Reviewed-on: https://chromium-review.googlesource.com/c/1492972

  • src/common/PoolAlloc.h
  • //
    // 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"
    
    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;
        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);
    
        //
        // 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>;
    
        // 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_