aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/SkSmallAllocator.h
blob: efe0bc7b4026de198d3b3c258188e482f2395bcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 * Copyright 2014 Google, Inc
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkSmallAllocator_DEFINED
#define SkSmallAllocator_DEFINED

#include "SkTArray.h"
#include "SkTypes.h"

#include <functional>
#include <type_traits>
#include <utility>


// max_align_t is needed to calculate the alignment for createWithIniterT when the T used is an
// abstract type. The complication with max_align_t is that it is defined differently for
// different builds.
namespace {
#if defined(SK_BUILD_FOR_WIN32) || defined(SK_BUILD_FOR_MAC)
    // Use std::max_align_t for compiles that follow the standard.
    #include <cstddef>
    using SystemAlignment = std::max_align_t;
#else
    // Ubuntu compiles don't have std::max_align_t defined, but MSVC does not define max_align_t.
    #include <stddef.h>
    using SystemAlignment = max_align_t;
#endif
}

/*
 *  Template class for allocating small objects without additional heap memory
 *  allocations.
 *
 *  kTotalBytes is the total number of bytes provided for storage for all
 *  objects created by this allocator. If an object to be created is larger
 *  than the storage (minus storage already used), it will be allocated on the
 *  heap. This class's destructor will handle calling the destructor for each
 *  object it allocated and freeing its memory.
 */
template<uint32_t kExpectedObjects, size_t kTotalBytes>
class SkSmallAllocator : SkNoncopyable {
public:
    ~SkSmallAllocator() {
        // Destruct in reverse order, in case an earlier object points to a
        // later object.
        while (fRecs.count() > 0) {
            this->deleteLast();
        }
    }

    /*
     *  Create a new object of type T. Its lifetime will be handled by this
     *  SkSmallAllocator.
     */
    template<typename T, typename... Args>
    T* createT(Args&&... args) {
        void* buf = this->reserve(sizeof(T), DefaultDestructor<T>);
        return new (buf) T(std::forward<Args>(args)...);
    }

    /*
     * Create a new object of size using initer to initialize the memory. The initer function has
     * the signature T* initer(void* storage). If initer is unable to initialize the memory it
     * should return nullptr where SkSmallAllocator will free the memory.
     */
    template <typename Initer>
    auto createWithIniter(size_t size, Initer initer) -> decltype(initer(nullptr)) {
        using ObjType = typename std::remove_pointer<decltype(initer(nullptr))>::type;
        SkASSERT(size >= sizeof(ObjType));

        void* storage = this->reserve(size, DefaultDestructor<ObjType>);
        auto candidate = initer(storage);
        if (!candidate) {
            // Initializing didn't workout so free the memory.
            this->freeLast();
        }

        return candidate;
    }

    /*
     * Free the last object allocated and call its destructor. This can be called multiple times
     * removing objects from the pool in reverse order.
     */
    void deleteLast() {
        SkASSERT(fRecs.count() > 0);
        Rec& rec = fRecs.back();
        rec.fDestructor(rec.fObj);
        this->freeLast();
    }

private:
    using Destructor = void(*)(void*);
    struct Rec {
        char*      fObj;
        Destructor fDestructor;
    };

    // Used to call the destructor for allocated objects.
    template<typename T>
    static void DefaultDestructor(void* ptr) {
        static_cast<T*>(ptr)->~T();
    }

    static constexpr size_t kAlignment = alignof(SystemAlignment);

    static constexpr size_t AlignSize(size_t size) {
        return (size + kAlignment - 1) & ~(kAlignment - 1);
    }

    // Reserve storageRequired from fStorage if possible otherwise allocate on the heap.
    void* reserve(size_t storageRequired, Destructor destructor) {
        // Make sure that all allocations stay aligned by rounding the storageRequired up to the
        // aligned value.
        char* objectStart = fStorageEnd;
        char* objectEnd = objectStart + AlignSize(storageRequired);
        Rec& rec = fRecs.push_back();
        if (objectEnd > &fStorage[kTotalBytes]) {
            // Allocate on the heap. Ideally we want to avoid this situation.
            rec.fObj = new char [storageRequired];
        } else {
            // There is space in fStorage.
            rec.fObj = objectStart;
            fStorageEnd = objectEnd;
        }
        rec.fDestructor = destructor;
        return rec.fObj;
    }

    void freeLast() {
        Rec& rec = fRecs.back();
        if (std::less<char*>()(rec.fObj, fStorage)
            || !std::less<char*>()(rec.fObj, &fStorage[kTotalBytes])) {
            delete [] rec.fObj;
        } else {
            fStorageEnd = rec.fObj;
        }
        fRecs.pop_back();
    }

    SkSTArray<kExpectedObjects, Rec, true> fRecs;
    char*                                  fStorageEnd {fStorage};
    // Since char have an alignment of 1, it should be forced onto an alignment the compiler
    // expects which is the alignment of std::max_align_t.
    alignas (kAlignment) char              fStorage[kTotalBytes];
};

#endif // SkSmallAllocator_DEFINED