tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PoolAlloc.cpp (14893B)


      1 //
      2 // Copyright 2019 The ANGLE Project Authors. All rights reserved.
      3 // Use of this source code is governed by a BSD-style license that can be
      4 // found in the LICENSE file.
      5 //
      6 // PoolAlloc.cpp:
      7 //    Implements the class methods for PoolAllocator and Allocation classes.
      8 //
      9 
     10 #include "common/PoolAlloc.h"
     11 
     12 #include <assert.h>
     13 #include <stdint.h>
     14 #include <stdio.h>
     15 
     16 #include "common/angleutils.h"
     17 #include "common/debug.h"
     18 #include "common/mathutil.h"
     19 #include "common/platform.h"
     20 #include "common/tls.h"
     21 
     22 namespace angle
     23 {
     24 // If we are using guard blocks, we must track each individual allocation.  If we aren't using guard
     25 // blocks, these never get instantiated, so won't have any impact.
     26 
     27 class Allocation
     28 {
     29  public:
     30    Allocation(size_t size, unsigned char *mem, Allocation *prev = 0)
     31        : mSize(size), mMem(mem), mPrevAlloc(prev)
     32    {
     33        // Allocations are bracketed:
     34        //
     35        //    [allocationHeader][initialGuardBlock][userData][finalGuardBlock]
     36        //
     37        // This would be cleaner with if (kGuardBlockSize)..., but that makes the compiler print
     38        // warnings about 0 length memsets, even with the if() protecting them.
     39 #if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
     40        memset(preGuard(), kGuardBlockBeginVal, kGuardBlockSize);
     41        memset(data(), kUserDataFill, mSize);
     42        memset(postGuard(), kGuardBlockEndVal, kGuardBlockSize);
     43 #endif
     44    }
     45 
     46    void checkAllocList() const;
     47 
     48    static size_t AlignedHeaderSize(uint8_t *allocationBasePtr, size_t alignment)
     49    {
     50        // Make sure that the data offset after the header is aligned to the given alignment.
     51        size_t base = reinterpret_cast<size_t>(allocationBasePtr);
     52        return rx::roundUpPow2(base + kGuardBlockSize + HeaderSize(), alignment) - base;
     53    }
     54 
     55    // Return total size needed to accommodate user buffer of 'size',
     56    // plus our tracking data and any necessary alignments.
     57    static size_t AllocationSize(uint8_t *allocationBasePtr,
     58                                 size_t size,
     59                                 size_t alignment,
     60                                 size_t *preAllocationPaddingOut)
     61    {
     62        // The allocation will be laid out as such:
     63        //
     64        //                      Aligned to |alignment|
     65        //                               ^
     66        //   preAllocationPaddingOut     |
     67        //        ___^___                |
     68        //       /       \               |
     69        //       <padding>[header][guard][data][guard]
     70        //       \___________ __________/
     71        //                   V
     72        //              dataOffset
     73        //
     74        // Note that alignment is at least as much as a pointer alignment, so the pointers in the
     75        // header are also necessarily aligned appropriately.
     76        //
     77        size_t dataOffset        = AlignedHeaderSize(allocationBasePtr, alignment);
     78        *preAllocationPaddingOut = dataOffset - HeaderSize() - kGuardBlockSize;
     79 
     80        return dataOffset + size + kGuardBlockSize;
     81    }
     82 
     83    // Given memory pointing to |header|, returns |data|.
     84    static uint8_t *GetDataPointer(uint8_t *memory, size_t alignment)
     85    {
     86        uint8_t *alignedPtr = memory + kGuardBlockSize + HeaderSize();
     87 
     88        // |memory| must be aligned already such that user data is aligned to |alignment|.
     89        ASSERT((reinterpret_cast<uintptr_t>(alignedPtr) & (alignment - 1)) == 0);
     90 
     91        return alignedPtr;
     92    }
     93 
     94  private:
     95    void checkGuardBlock(unsigned char *blockMem, unsigned char val, const char *locText) const;
     96 
     97    void checkAlloc() const
     98    {
     99        checkGuardBlock(preGuard(), kGuardBlockBeginVal, "before");
    100        checkGuardBlock(postGuard(), kGuardBlockEndVal, "after");
    101    }
    102 
    103    // Find offsets to pre and post guard blocks, and user data buffer
    104    unsigned char *preGuard() const { return mMem + HeaderSize(); }
    105    unsigned char *data() const { return preGuard() + kGuardBlockSize; }
    106    unsigned char *postGuard() const { return data() + mSize; }
    107    size_t mSize;            // size of the user data area
    108    unsigned char *mMem;     // beginning of our allocation (points to header)
    109    Allocation *mPrevAlloc;  // prior allocation in the chain
    110 
    111    static constexpr unsigned char kGuardBlockBeginVal = 0xfb;
    112    static constexpr unsigned char kGuardBlockEndVal   = 0xfe;
    113    static constexpr unsigned char kUserDataFill       = 0xcd;
    114 #if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
    115    static constexpr size_t kGuardBlockSize = 16;
    116    static constexpr size_t HeaderSize() { return sizeof(Allocation); }
    117 #else
    118    static constexpr size_t kGuardBlockSize = 0;
    119    static constexpr size_t HeaderSize() { return 0; }
    120 #endif
    121 };
    122 
    123 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    124 class PageHeader
    125 {
    126  public:
    127    PageHeader(PageHeader *nextPage, size_t pageCount)
    128        : nextPage(nextPage),
    129          pageCount(pageCount)
    130 #    if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
    131          ,
    132          lastAllocation(nullptr)
    133 #    endif
    134    {}
    135 
    136    ~PageHeader()
    137    {
    138 #    if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
    139        if (lastAllocation)
    140        {
    141            lastAllocation->checkAllocList();
    142        }
    143 #    endif
    144    }
    145 
    146    PageHeader *nextPage;
    147    size_t pageCount;
    148 #    if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
    149    Allocation *lastAllocation;
    150 #    endif
    151 };
    152 #endif
    153 
    154 //
    155 // Implement the functionality of the PoolAllocator class, which
    156 // is documented in PoolAlloc.h.
    157 //
    158 PoolAllocator::PoolAllocator(int growthIncrement, int allocationAlignment)
    159    : mAlignment(allocationAlignment),
    160 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    161      mPageSize(growthIncrement),
    162      mFreeList(nullptr),
    163      mInUseList(nullptr),
    164      mNumCalls(0),
    165      mTotalBytes(0),
    166 #endif
    167      mLocked(false)
    168 {
    169    initialize(growthIncrement, allocationAlignment);
    170 }
    171 
    172 void PoolAllocator::initialize(int pageSize, int alignment)
    173 {
    174    mAlignment = alignment;
    175 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    176    mPageSize       = pageSize;
    177    mPageHeaderSkip = sizeof(PageHeader);
    178 
    179    // Alignment == 1 is a special fast-path where fastAllocate() is enabled
    180    if (mAlignment != 1)
    181    {
    182 #endif
    183        // Adjust mAlignment to be at least pointer aligned and
    184        // power of 2.
    185        //
    186        size_t minAlign = sizeof(void *);
    187        if (mAlignment < minAlign)
    188        {
    189            mAlignment = minAlign;
    190        }
    191        mAlignment = gl::ceilPow2(static_cast<unsigned int>(mAlignment));
    192 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    193    }
    194    //
    195    // Don't allow page sizes we know are smaller than all common
    196    // OS page sizes.
    197    //
    198    if (mPageSize < 4 * 1024)
    199    {
    200        mPageSize = 4 * 1024;
    201    }
    202 
    203    //
    204    // A large mCurrentPageOffset indicates a new page needs to
    205    // be obtained to allocate memory.
    206    //
    207    mCurrentPageOffset = mPageSize;
    208 
    209 #else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
    210    mStack.push_back({});
    211 #endif
    212 }
    213 
    214 PoolAllocator::~PoolAllocator()
    215 {
    216 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    217    while (mInUseList)
    218    {
    219        PageHeader *next = mInUseList->nextPage;
    220        mInUseList->~PageHeader();
    221        delete[] reinterpret_cast<char *>(mInUseList);
    222        mInUseList = next;
    223    }
    224    // We should not check the guard blocks
    225    // here, because we did it already when the block was
    226    // placed into the free list.
    227    //
    228    while (mFreeList)
    229    {
    230        PageHeader *next = mFreeList->nextPage;
    231        delete[] reinterpret_cast<char *>(mFreeList);
    232        mFreeList = next;
    233    }
    234 #else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
    235    for (auto &allocs : mStack)
    236    {
    237        for (auto alloc : allocs)
    238        {
    239            free(alloc);
    240        }
    241    }
    242    mStack.clear();
    243 #endif
    244 }
    245 
    246 //
    247 // Check a single guard block for damage
    248 //
    249 void Allocation::checkGuardBlock(unsigned char *blockMem,
    250                                 unsigned char val,
    251                                 const char *locText) const
    252 {
    253 #if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
    254    for (size_t x = 0; x < kGuardBlockSize; x++)
    255    {
    256        if (blockMem[x] != val)
    257        {
    258            char assertMsg[80];
    259            // We don't print the assert message.  It's here just to be helpful.
    260            snprintf(assertMsg, sizeof(assertMsg),
    261                     "PoolAlloc: Damage %s %zu byte allocation at 0x%p\n", locText, mSize, data());
    262            assert(0 && "PoolAlloc: Damage in guard block");
    263        }
    264    }
    265 #endif
    266 }
    267 
    268 void PoolAllocator::push()
    269 {
    270 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    271    AllocState state = {mCurrentPageOffset, mInUseList};
    272 
    273    mStack.push_back(state);
    274 
    275    //
    276    // Indicate there is no current page to allocate from.
    277    //
    278    mCurrentPageOffset = mPageSize;
    279 #else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
    280    mStack.push_back({});
    281 #endif
    282 }
    283 
    284 // Do a mass-deallocation of all the individual allocations that have occurred since the last
    285 // push(), or since the last pop(), or since the object's creation.
    286 //
    287 // The deallocated pages are saved for future allocations.
    288 void PoolAllocator::pop()
    289 {
    290    if (mStack.size() < 1)
    291    {
    292        return;
    293    }
    294 
    295 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    296    PageHeader *page   = mStack.back().page;
    297    mCurrentPageOffset = mStack.back().offset;
    298 
    299    while (mInUseList != page)
    300    {
    301        // invoke destructor to free allocation list
    302        mInUseList->~PageHeader();
    303 
    304        PageHeader *nextInUse = mInUseList->nextPage;
    305        if (mInUseList->pageCount > 1)
    306        {
    307            delete[] reinterpret_cast<char *>(mInUseList);
    308        }
    309        else
    310        {
    311            mInUseList->nextPage = mFreeList;
    312            mFreeList            = mInUseList;
    313        }
    314        mInUseList = nextInUse;
    315    }
    316 
    317    mStack.pop_back();
    318 #else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
    319    for (auto &alloc : mStack.back())
    320    {
    321        free(alloc);
    322    }
    323    mStack.pop_back();
    324 #endif
    325 }
    326 
    327 //
    328 // Do a mass-deallocation of all the individual allocations
    329 // that have occurred.
    330 //
    331 void PoolAllocator::popAll()
    332 {
    333    while (mStack.size() > 0)
    334        pop();
    335 }
    336 
    337 void *PoolAllocator::allocate(size_t numBytes)
    338 {
    339    ASSERT(!mLocked);
    340 
    341 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    342    //
    343    // Just keep some interesting statistics.
    344    //
    345    ++mNumCalls;
    346    mTotalBytes += numBytes;
    347 
    348    uint8_t *currentPagePtr = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
    349 
    350    size_t preAllocationPadding = 0;
    351    size_t allocationSize =
    352        Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
    353 
    354    // Integer overflow is unexpected.
    355    ASSERT(allocationSize >= numBytes);
    356 
    357    // Do the allocation, most likely case first, for efficiency.
    358    if (allocationSize <= mPageSize - mCurrentPageOffset)
    359    {
    360        // There is enough room to allocate from the current page at mCurrentPageOffset.
    361        uint8_t *memory = currentPagePtr + preAllocationPadding;
    362        mCurrentPageOffset += allocationSize;
    363 
    364        return initializeAllocation(memory, numBytes);
    365    }
    366 
    367    if (allocationSize > mPageSize - mPageHeaderSkip)
    368    {
    369        // If the allocation is larger than a whole page, do a multi-page allocation.  These are not
    370        // mixed with the others.  The OS is efficient in allocating and freeing multiple pages.
    371 
    372        // We don't know what the alignment of the new allocated memory will be, so conservatively
    373        // allocate enough memory for up to alignment extra bytes being needed.
    374        allocationSize = Allocation::AllocationSize(reinterpret_cast<uint8_t *>(mPageHeaderSkip),
    375                                                    numBytes, mAlignment, &preAllocationPadding);
    376 
    377        size_t numBytesToAlloc = allocationSize + mPageHeaderSkip + mAlignment;
    378 
    379        // Integer overflow is unexpected.
    380        ASSERT(numBytesToAlloc >= allocationSize);
    381 
    382        PageHeader *memory = reinterpret_cast<PageHeader *>(::new char[numBytesToAlloc]);
    383        if (memory == nullptr)
    384        {
    385            return nullptr;
    386        }
    387 
    388        // Use placement-new to initialize header
    389        new (memory) PageHeader(mInUseList, (numBytesToAlloc + mPageSize - 1) / mPageSize);
    390        mInUseList = memory;
    391 
    392        // Make next allocation come from a new page
    393        mCurrentPageOffset = mPageSize;
    394 
    395        // Now that we actually have the pointer, make sure the data pointer will be aligned.
    396        currentPagePtr = reinterpret_cast<uint8_t *>(memory) + mPageHeaderSkip;
    397        Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
    398 
    399        return initializeAllocation(currentPagePtr + preAllocationPadding, numBytes);
    400    }
    401 
    402    uint8_t *newPageAddr = allocateNewPage(numBytes);
    403    return initializeAllocation(newPageAddr, numBytes);
    404 
    405 #else  // !defined(ANGLE_DISABLE_POOL_ALLOC)
    406 
    407    void *alloc = malloc(numBytes + mAlignment - 1);
    408    mStack.back().push_back(alloc);
    409 
    410    intptr_t intAlloc = reinterpret_cast<intptr_t>(alloc);
    411    intAlloc          = rx::roundUpPow2<intptr_t>(intAlloc, mAlignment);
    412    return reinterpret_cast<void *>(intAlloc);
    413 #endif
    414 }
    415 
    416 #if !defined(ANGLE_DISABLE_POOL_ALLOC)
    417 uint8_t *PoolAllocator::allocateNewPage(size_t numBytes)
    418 {
    419    // Need a simple page to allocate from.  Pick a page from the free list, if any.  Otherwise need
    420    // to make the allocation.
    421    PageHeader *memory;
    422    if (mFreeList)
    423    {
    424        memory    = mFreeList;
    425        mFreeList = mFreeList->nextPage;
    426    }
    427    else
    428    {
    429        memory = reinterpret_cast<PageHeader *>(::new char[mPageSize]);
    430        if (memory == nullptr)
    431        {
    432            return nullptr;
    433        }
    434    }
    435    // Use placement-new to initialize header
    436    new (memory) PageHeader(mInUseList, 1);
    437    mInUseList = memory;
    438 
    439    // Leave room for the page header.
    440    mCurrentPageOffset      = mPageHeaderSkip;
    441    uint8_t *currentPagePtr = reinterpret_cast<uint8_t *>(mInUseList) + mCurrentPageOffset;
    442 
    443    size_t preAllocationPadding = 0;
    444    size_t allocationSize =
    445        Allocation::AllocationSize(currentPagePtr, numBytes, mAlignment, &preAllocationPadding);
    446 
    447    mCurrentPageOffset += allocationSize;
    448 
    449    // The new allocation is made after the page header and any alignment required before it.
    450    return reinterpret_cast<uint8_t *>(mInUseList) + mPageHeaderSkip + preAllocationPadding;
    451 }
    452 
    453 void *PoolAllocator::initializeAllocation(uint8_t *memory, size_t numBytes)
    454 {
    455 #    if defined(ANGLE_POOL_ALLOC_GUARD_BLOCKS)
    456    new (memory) Allocation(numBytes, memory, mInUseList->lastAllocation);
    457    mInUseList->lastAllocation = reinterpret_cast<Allocation *>(memory);
    458 #    endif
    459 
    460    return Allocation::GetDataPointer(memory, mAlignment);
    461 }
    462 #endif
    463 
    464 void PoolAllocator::lock()
    465 {
    466    ASSERT(!mLocked);
    467    mLocked = true;
    468 }
    469 
    470 void PoolAllocator::unlock()
    471 {
    472    ASSERT(mLocked);
    473    mLocked = false;
    474 }
    475 
    476 //
    477 // Check all allocations in a list for damage by calling check on each.
    478 //
    479 void Allocation::checkAllocList() const
    480 {
    481    for (const Allocation *alloc = this; alloc != nullptr; alloc = alloc->mPrevAlloc)
    482    {
    483        alloc->checkAlloc();
    484    }
    485 }
    486 
    487 }  // namespace angle