Home | History | Annotate | Download | only in private
      1 /*
      2  * Copyright 2016 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #ifndef SkArenaAlloc_DEFINED
      9 #define SkArenaAlloc_DEFINED
     10 
     11 #include "../private/SkTFitsIn.h"
     12 
     13 #include <cassert>
     14 #include <cstddef>
     15 #include <cstdint>
     16 #include <cstdlib>
     17 #include <cstring>
     18 #include <limits>
     19 #include <new>
     20 #include <type_traits>
     21 #include <utility>
     22 #include <vector>
     23 
     24 // SkArenaAlloc allocates object and destroys the allocated objects when destroyed. It's designed
     25 // to minimize the number of underlying block allocations. SkArenaAlloc allocates first out of an
     26 // (optional) user-provided block of memory, and when that's exhausted it allocates on the heap,
     27 // starting with an allocation of firstHeapAllocation bytes.  If your data (plus a small overhead)
     28 // fits in the user-provided block, SkArenaAlloc never uses the heap, and if it fits in
     29 // firstHeapAllocation bytes, it'll use the heap only once. If 0 is specified for
     30 // firstHeapAllocation, then blockSize is used unless that too is 0, then 1024 is used.
     31 //
     32 // Examples:
     33 //
     34 //   char block[mostCasesSize];
     35 //   SkArenaAlloc arena(block, mostCasesSize);
     36 //
     37 // If mostCasesSize is too large for the stack, you can use the following pattern.
     38 //
     39 //   std::unique_ptr<char[]> block{new char[mostCasesSize]};
     40 //   SkArenaAlloc arena(block.get(), mostCasesSize, almostAllCasesSize);
     41 //
     42 // If the program only sometimes allocates memory, use the following pattern.
     43 //
     44 //   SkArenaAlloc arena(nullptr, 0, almostAllCasesSize);
     45 //
     46 // The storage does not necessarily need to be on the stack. Embedding the storage in a class also
     47 // works.
     48 //
     49 //   class Foo {
     50 //       char storage[mostCasesSize];
     51 //       SkArenaAlloc arena (storage, mostCasesSize);
     52 //   };
     53 //
     54 // In addition, the system is optimized to handle POD data including arrays of PODs (where
     55 // POD is really data with no destructors). For POD data it has zero overhead per item, and a
     56 // typical per block overhead of 8 bytes. For non-POD objects there is a per item overhead of 4
     57 // bytes. For arrays of non-POD objects there is a per array overhead of typically 8 bytes. There
     58 // is an addition overhead when switching from POD data to non-POD data of typically 8 bytes.
     59 //
     60 // If additional blocks are needed they are increased exponentially. This strategy bounds the
     61 // recursion of the RunDtorsOnBlock to be limited to O(log size-of-memory). Block size grow using
     62 // the Fibonacci sequence which means that for 2^32 memory there are 48 allocations, and for 2^48
     63 // there are 71 allocations.
     64 class SkArenaAlloc {
     65 public:
     66     SkArenaAlloc(char* block, size_t blockSize, size_t firstHeapAllocation);
     67 
     68     explicit SkArenaAlloc(size_t firstHeapAllocation)
     69         : SkArenaAlloc(nullptr, 0, firstHeapAllocation)
     70     {}
     71 
     72     ~SkArenaAlloc();
     73 
     74     template <typename T, typename... Args>
     75     T* make(Args&&... args) {
     76         uint32_t size      = ToU32(sizeof(T));
     77         uint32_t alignment = ToU32(alignof(T));
     78         char* objStart;
     79         if (std::is_trivially_destructible<T>::value) {
     80             objStart = this->allocObject(size, alignment);
     81             fCursor = objStart + size;
     82         } else {
     83             objStart = this->allocObjectWithFooter(size + sizeof(Footer), alignment);
     84             // Can never be UB because max value is alignof(T).
     85             uint32_t padding = ToU32(objStart - fCursor);
     86 
     87             // Advance to end of object to install footer.
     88             fCursor = objStart + size;
     89             FooterAction* releaser = [](char* objEnd) {
     90                 char* objStart = objEnd - (sizeof(T) + sizeof(Footer));
     91                 ((T*)objStart)->~T();
     92                 return objStart;
     93             };
     94             this->installFooter(releaser, padding);
     95         }
     96 
     97         // This must be last to make objects with nested use of this allocator work.
     98         return new(objStart) T(std::forward<Args>(args)...);
     99     }
    100 
    101     template <typename T>
    102     T* makeArrayDefault(size_t count) {
    103         AssertRelease(SkTFitsIn<uint32_t>(count));
    104         uint32_t safeCount = ToU32(count);
    105         T* array = (T*)this->commonArrayAlloc<T>(safeCount);
    106 
    107         // If T is primitive then no initialization takes place.
    108         for (size_t i = 0; i < safeCount; i++) {
    109             new (&array[i]) T;
    110         }
    111         return array;
    112     }
    113 
    114     template <typename T>
    115     T* makeArray(size_t count) {
    116         AssertRelease(SkTFitsIn<uint32_t>(count));
    117         uint32_t safeCount = ToU32(count);
    118         T* array = (T*)this->commonArrayAlloc<T>(safeCount);
    119 
    120         // If T is primitive then the memory is initialized. For example, an array of chars will
    121         // be zeroed.
    122         for (size_t i = 0; i < safeCount; i++) {
    123             new (&array[i]) T();
    124         }
    125         return array;
    126     }
    127 
    128     // Only use makeBytesAlignedTo if none of the typed variants are impractical to use.
    129     void* makeBytesAlignedTo(size_t size, size_t align) {
    130         AssertRelease(SkTFitsIn<uint32_t>(size));
    131         auto objStart = this->allocObject(ToU32(size), ToU32(align));
    132         fCursor = objStart + size;
    133         return objStart;
    134     }
    135 
    136     // Destroy all allocated objects, free any heap allocations.
    137     void reset();
    138 
    139 private:
    140     static void AssertRelease(bool cond) { if (!cond) { ::abort(); } }
    141     static uint32_t ToU32(size_t v) {
    142         assert(SkTFitsIn<uint32_t>(v));
    143         return (uint32_t)v;
    144     }
    145 
    146     using Footer = int64_t;
    147     using FooterAction = char* (char*);
    148 
    149     static char* SkipPod(char* footerEnd);
    150     static void RunDtorsOnBlock(char* footerEnd);
    151     static char* NextBlock(char* footerEnd);
    152 
    153     void installFooter(FooterAction* releaser, uint32_t padding);
    154     void installUint32Footer(FooterAction* action, uint32_t value, uint32_t padding);
    155     void installPtrFooter(FooterAction* action, char* ptr, uint32_t padding);
    156 
    157     void ensureSpace(uint32_t size, uint32_t alignment);
    158 
    159     char* allocObject(uint32_t size, uint32_t alignment) {
    160         uintptr_t mask = alignment - 1;
    161         uintptr_t alignedOffset = (~reinterpret_cast<uintptr_t>(fCursor) + 1) & mask;
    162         uintptr_t totalSize = size + alignedOffset;
    163         AssertRelease(totalSize >= size);
    164         if (totalSize > static_cast<uintptr_t>(fEnd - fCursor)) {
    165             this->ensureSpace(size, alignment);
    166             alignedOffset = (~reinterpret_cast<uintptr_t>(fCursor) + 1) & mask;
    167         }
    168         return fCursor + alignedOffset;
    169     }
    170 
    171     char* allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment);
    172 
    173     template <typename T>
    174     char* commonArrayAlloc(uint32_t count) {
    175         char* objStart;
    176         AssertRelease(count <= std::numeric_limits<uint32_t>::max() / sizeof(T));
    177         uint32_t arraySize = ToU32(count * sizeof(T));
    178         uint32_t alignment = ToU32(alignof(T));
    179 
    180         if (std::is_trivially_destructible<T>::value) {
    181             objStart = this->allocObject(arraySize, alignment);
    182             fCursor = objStart + arraySize;
    183         } else {
    184             constexpr uint32_t overhead = sizeof(Footer) + sizeof(uint32_t);
    185             AssertRelease(arraySize <= std::numeric_limits<uint32_t>::max() - overhead);
    186             uint32_t totalSize = arraySize + overhead;
    187             objStart = this->allocObjectWithFooter(totalSize, alignment);
    188 
    189             // Can never be UB because max value is alignof(T).
    190             uint32_t padding = ToU32(objStart - fCursor);
    191 
    192             // Advance to end of array to install footer.?
    193             fCursor = objStart + arraySize;
    194             this->installUint32Footer(
    195                 [](char* footerEnd) {
    196                     char* objEnd = footerEnd - (sizeof(Footer) + sizeof(uint32_t));
    197                     uint32_t count;
    198                     memmove(&count, objEnd, sizeof(uint32_t));
    199                     char* objStart = objEnd - count * sizeof(T);
    200                     T* array = (T*) objStart;
    201                     for (uint32_t i = 0; i < count; i++) {
    202                         array[i].~T();
    203                     }
    204                     return objStart;
    205                 },
    206                 ToU32(count),
    207                 padding);
    208         }
    209 
    210         return objStart;
    211     }
    212 
    213     char*          fDtorCursor;
    214     char*          fCursor;
    215     char*          fEnd;
    216     char* const    fFirstBlock;
    217     const uint32_t fFirstSize;
    218     const uint32_t fFirstHeapAllocationSize;
    219 
    220     // Use the Fibonacci sequence as the growth factor for block size. The size of the block
    221     // allocated is fFib0 * fFirstHeapAllocationSize. Using 2 ^ n * fFirstHeapAllocationSize
    222     // had too much slop for Android.
    223     uint32_t       fFib0 {1}, fFib1 {1};
    224 };
    225 
    226 // Helper for defining allocators with inline/reserved storage.
    227 // For argument declarations, stick to the base type (SkArenaAlloc).
    228 template <size_t InlineStorageSize>
    229 class SkSTArenaAlloc : public SkArenaAlloc {
    230 public:
    231     explicit SkSTArenaAlloc(size_t firstHeapAllocation = InlineStorageSize)
    232         : INHERITED(fInlineStorage, InlineStorageSize, firstHeapAllocation) {}
    233 
    234 private:
    235     char fInlineStorage[InlineStorageSize];
    236 
    237     using INHERITED = SkArenaAlloc;
    238 };
    239 
    240 #endif  // SkArenaAlloc_DEFINED
    241