1*5f39d1b3SJooyung Han // Copyright 2015 The Gemmlowp Authors. All Rights Reserved.
2*5f39d1b3SJooyung Han //
3*5f39d1b3SJooyung Han // Licensed under the Apache License, Version 2.0 (the "License");
4*5f39d1b3SJooyung Han // you may not use this file except in compliance with the License.
5*5f39d1b3SJooyung Han // You may obtain a copy of the License at
6*5f39d1b3SJooyung Han //
7*5f39d1b3SJooyung Han // http://www.apache.org/licenses/LICENSE-2.0
8*5f39d1b3SJooyung Han //
9*5f39d1b3SJooyung Han // Unless required by applicable law or agreed to in writing, software
10*5f39d1b3SJooyung Han // distributed under the License is distributed on an "AS IS" BASIS,
11*5f39d1b3SJooyung Han // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*5f39d1b3SJooyung Han // See the License for the specific language governing permissions and
13*5f39d1b3SJooyung Han // limitations under the License.
14*5f39d1b3SJooyung Han
15*5f39d1b3SJooyung Han // allocator.h: a buffer allocator that allows avoiding most of the
16*5f39d1b3SJooyung Han // malloc/free overhead, by:
17*5f39d1b3SJooyung Han // 1. Requiring all N allocations to be reserved in advance, and
18*5f39d1b3SJooyung Han // then commited at once, turning N allocations into 1.
19*5f39d1b3SJooyung Han // 2. Being persistent, the allocated storage is reused across commits,
20*5f39d1b3SJooyung Han // and only reallocated as needed when the commit size gets larger.
21*5f39d1b3SJooyung Han //
22*5f39d1b3SJooyung Han // This is driven by Android-specific needs:
23*5f39d1b3SJooyung Han // 1. On Android, the default (Bionic) allocator tends to aggressively
24*5f39d1b3SJooyung Han // unmap pages, which means that malloc/free can be surprisingly expensive.
25*5f39d1b3SJooyung Han // 2. On Android, stack allocations with alloca() can't be as large as on
26*5f39d1b3SJooyung Han // desktop platforms.
27*5f39d1b3SJooyung Han //
28*5f39d1b3SJooyung Han // General usage:
29*5f39d1b3SJooyung Han // 1. Reserve blocks by calling Reserve(), which returns a Handle.
30*5f39d1b3SJooyung Han // 2. Call Commit() once.
31*5f39d1b3SJooyung Han // 3. Now it is possible to get pointers to allocated buffers by calling
32*5f39d1b3SJooyung Han // GetPointer().
33*5f39d1b3SJooyung Han // 4. Call Decommit() once.
34*5f39d1b3SJooyung Han // 5. The allocator is now reverted to its original state, except that
35*5f39d1b3SJooyung Han // it retained its allocated storage, so the next Commit() will be faster.
36*5f39d1b3SJooyung Han // The allocated storage is only freed when the Allocator object is
37*5f39d1b3SJooyung Han // destroyed.
38*5f39d1b3SJooyung Han
39*5f39d1b3SJooyung Han #ifndef GEMMLOWP_INTERNAL_ALLOCATOR_H_
40*5f39d1b3SJooyung Han #define GEMMLOWP_INTERNAL_ALLOCATOR_H_
41*5f39d1b3SJooyung Han
42*5f39d1b3SJooyung Han #include "common.h"
43*5f39d1b3SJooyung Han
44*5f39d1b3SJooyung Han namespace gemmlowp {
45*5f39d1b3SJooyung Han
46*5f39d1b3SJooyung Han enum class TypeId : std::uint8_t { Uint8, Int8, Uint16, Int16, Uint32, Int32 };
47*5f39d1b3SJooyung Han
48*5f39d1b3SJooyung Han template <typename T>
49*5f39d1b3SJooyung Han struct GetTypeIdImpl {};
50*5f39d1b3SJooyung Han
51*5f39d1b3SJooyung Han template <typename T>
GetTypeId()52*5f39d1b3SJooyung Han inline TypeId GetTypeId() {
53*5f39d1b3SJooyung Han return GetTypeIdImpl<T>::Value;
54*5f39d1b3SJooyung Han }
55*5f39d1b3SJooyung Han
56*5f39d1b3SJooyung Han template <typename T>
57*5f39d1b3SJooyung Han struct GetTypeIdImpl<const T> : GetTypeIdImpl<T> {};
58*5f39d1b3SJooyung Han
59*5f39d1b3SJooyung Han #define GEMMLOWP_REGISTER_TYPEID(type_, id) \
60*5f39d1b3SJooyung Han template <> \
61*5f39d1b3SJooyung Han struct GetTypeIdImpl<type_> { \
62*5f39d1b3SJooyung Han static const TypeId Value = TypeId::id; \
63*5f39d1b3SJooyung Han };
64*5f39d1b3SJooyung Han
65*5f39d1b3SJooyung Han GEMMLOWP_REGISTER_TYPEID(std::uint8_t, Uint8)
66*5f39d1b3SJooyung Han GEMMLOWP_REGISTER_TYPEID(std::int8_t, Int8)
67*5f39d1b3SJooyung Han GEMMLOWP_REGISTER_TYPEID(std::uint16_t, Uint16)
68*5f39d1b3SJooyung Han GEMMLOWP_REGISTER_TYPEID(std::int16_t, Int16)
69*5f39d1b3SJooyung Han GEMMLOWP_REGISTER_TYPEID(std::uint32_t, Uint32)
70*5f39d1b3SJooyung Han GEMMLOWP_REGISTER_TYPEID(std::int32_t, Int32)
71*5f39d1b3SJooyung Han
72*5f39d1b3SJooyung Han class Allocator {
73*5f39d1b3SJooyung Han public:
74*5f39d1b3SJooyung Han Allocator()
75*5f39d1b3SJooyung Han : committed_(false),
76*5f39d1b3SJooyung Han storage_size_(0),
77*5f39d1b3SJooyung Han storage_(nullptr),
78*5f39d1b3SJooyung Han reserved_blocks_(0),
79*5f39d1b3SJooyung Han reserved_bytes_(0),
80*5f39d1b3SJooyung Han generation_(0) {}
81*5f39d1b3SJooyung Han
82*5f39d1b3SJooyung Han ~Allocator() {
83*5f39d1b3SJooyung Han assert(!committed_);
84*5f39d1b3SJooyung Han assert(!reserved_blocks_);
85*5f39d1b3SJooyung Han DeallocateStorage();
86*5f39d1b3SJooyung Han }
87*5f39d1b3SJooyung Han
88*5f39d1b3SJooyung Han // Alignment of allocated blocks.
89*5f39d1b3SJooyung Han static constexpr std::size_t kAlignment = kDefaultCacheLineSize;
90*5f39d1b3SJooyung Han
91*5f39d1b3SJooyung Han // This is all we need so far, and since the usage pattern is fixed,
92*5f39d1b3SJooyung Han // there is no point in allowing more until we need to.
93*5f39d1b3SJooyung Han static constexpr std::size_t kMaxBlocks = 5;
94*5f39d1b3SJooyung Han
95*5f39d1b3SJooyung Han void Commit() {
96*5f39d1b3SJooyung Han assert(!committed_);
97*5f39d1b3SJooyung Han
98*5f39d1b3SJooyung Han if (reserved_bytes_ > storage_size_) {
99*5f39d1b3SJooyung Han DeallocateStorage();
100*5f39d1b3SJooyung Han storage_size_ = RoundUpToPowerOfTwo(reserved_bytes_);
101*5f39d1b3SJooyung Han storage_ = aligned_alloc(kAlignment, storage_size_);
102*5f39d1b3SJooyung Han }
103*5f39d1b3SJooyung Han
104*5f39d1b3SJooyung Han ReleaseBuildAssertion(!storage_size_ || storage_, "allocation failure");
105*5f39d1b3SJooyung Han committed_ = true;
106*5f39d1b3SJooyung Han }
107*5f39d1b3SJooyung Han
108*5f39d1b3SJooyung Han void Decommit() {
109*5f39d1b3SJooyung Han assert(committed_);
110*5f39d1b3SJooyung Han committed_ = false;
111*5f39d1b3SJooyung Han generation_++;
112*5f39d1b3SJooyung Han
113*5f39d1b3SJooyung Han reserved_blocks_ = 0;
114*5f39d1b3SJooyung Han reserved_bytes_ = 0;
115*5f39d1b3SJooyung Han }
116*5f39d1b3SJooyung Han
117*5f39d1b3SJooyung Han // See generation_
118*5f39d1b3SJooyung Han typedef std::size_t generation_t;
119*5f39d1b3SJooyung Han
120*5f39d1b3SJooyung Han // A handle on a reserved block. The user obtains
121*5f39d1b3SJooyung Han // one by calling Reserve() and, after committing,
122*5f39d1b3SJooyung Han // passes it to GetPointer().
123*5f39d1b3SJooyung Han class Handle {
124*5f39d1b3SJooyung Han std::uint8_t index_;
125*5f39d1b3SJooyung Han generation_t generation_;
126*5f39d1b3SJooyung Han TypeId type_;
127*5f39d1b3SJooyung Han
128*5f39d1b3SJooyung Han friend class Allocator;
129*5f39d1b3SJooyung Han };
130*5f39d1b3SJooyung Han
131*5f39d1b3SJooyung Han // Reserves a block sized for n elements of type T, and
132*5f39d1b3SJooyung Han // returns a handle to it. Must be called before committing.
133*5f39d1b3SJooyung Han template <typename T>
134*5f39d1b3SJooyung Han Handle Reserve(std::size_t n) {
135*5f39d1b3SJooyung Han assert(!committed_ && "can't reserve blocks while committed");
136*5f39d1b3SJooyung Han assert(reserved_blocks_ < kMaxBlocks &&
137*5f39d1b3SJooyung Han "didn't expect to allocate this many blocks");
138*5f39d1b3SJooyung Han const std::size_t bytes = RoundUp<kAlignment>(n * sizeof(T));
139*5f39d1b3SJooyung Han const std::size_t offset = reserved_bytes_;
140*5f39d1b3SJooyung Han const std::size_t index = reserved_blocks_;
141*5f39d1b3SJooyung Han
142*5f39d1b3SJooyung Han reserved_blocks_offsets_[index] = offset;
143*5f39d1b3SJooyung Han Handle h;
144*5f39d1b3SJooyung Han h.index_ = index;
145*5f39d1b3SJooyung Han h.generation_ = generation_;
146*5f39d1b3SJooyung Han h.type_ = GetTypeId<T>();
147*5f39d1b3SJooyung Han
148*5f39d1b3SJooyung Han reserved_blocks_++;
149*5f39d1b3SJooyung Han reserved_bytes_ += bytes;
150*5f39d1b3SJooyung Han
151*5f39d1b3SJooyung Han return h;
152*5f39d1b3SJooyung Han }
153*5f39d1b3SJooyung Han
154*5f39d1b3SJooyung Han // Returns the pointer to the allocated buffer for the given handle.
155*5f39d1b3SJooyung Han // Must be called after committing.
156*5f39d1b3SJooyung Han template <typename T>
157*5f39d1b3SJooyung Han T* GetPointer(const Handle& h) const {
158*5f39d1b3SJooyung Han assert(committed_ && "can't get block pointers unless committed");
159*5f39d1b3SJooyung Han assert(h.index_ < reserved_blocks_ &&
160*5f39d1b3SJooyung Han "bad handle, points to inexistant block");
161*5f39d1b3SJooyung Han assert(h.generation_ == generation_ &&
162*5f39d1b3SJooyung Han "handle from earlier generation, have decommitted since");
163*5f39d1b3SJooyung Han assert(h.type_ == GetTypeId<T>() && "type mismatch");
164*5f39d1b3SJooyung Han std::size_t offset = reserved_blocks_offsets_[h.index_];
165*5f39d1b3SJooyung Han std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(storage_) + offset;
166*5f39d1b3SJooyung Han return reinterpret_cast<T*>(addr);
167*5f39d1b3SJooyung Han }
168*5f39d1b3SJooyung Han
169*5f39d1b3SJooyung Han private:
170*5f39d1b3SJooyung Han void DeallocateStorage() {
171*5f39d1b3SJooyung Han assert(!committed_);
172*5f39d1b3SJooyung Han aligned_free(storage_);
173*5f39d1b3SJooyung Han storage_size_ = 0;
174*5f39d1b3SJooyung Han }
175*5f39d1b3SJooyung Han
176*5f39d1b3SJooyung Han // Set to true by Commit() and to false by Decommit(). Initially false.
177*5f39d1b3SJooyung Han bool committed_;
178*5f39d1b3SJooyung Han
179*5f39d1b3SJooyung Han // The actually allocated storage size and buffer pointer.
180*5f39d1b3SJooyung Han std::size_t storage_size_;
181*5f39d1b3SJooyung Han mutable void* storage_;
182*5f39d1b3SJooyung Han
183*5f39d1b3SJooyung Han // The number of blocks that have been reserved by Reserve().
184*5f39d1b3SJooyung Han std::size_t reserved_blocks_;
185*5f39d1b3SJooyung Han // The number of bytes that have been reserved by Reserve().
186*5f39d1b3SJooyung Han std::size_t reserved_bytes_;
187*5f39d1b3SJooyung Han // The offsets of reserved blocks into the storage buffer.
188*5f39d1b3SJooyung Han std::size_t reserved_blocks_offsets_[kMaxBlocks];
189*5f39d1b3SJooyung Han
190*5f39d1b3SJooyung Han // The 'generation' is incremented on Decommit() and allows catching
191*5f39d1b3SJooyung Han // bad GetPointer() calls still referring to a previous commit.
192*5f39d1b3SJooyung Han generation_t generation_;
193*5f39d1b3SJooyung Han };
194*5f39d1b3SJooyung Han
195*5f39d1b3SJooyung Han } // namespace gemmlowp
196*5f39d1b3SJooyung Han
197*5f39d1b3SJooyung Han #endif // GEMMLOWP_INTERNAL_ALLOCATOR_H_
198