xref: /aosp_15_r20/external/gemmlowp/internal/allocator.h (revision 5f39d1b313f0528e11bae88b3029b54b9e1033e7)
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