1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // This approach to arenas overcomes many of the limitations described
18 // in the "Specialized allocators" section of
19 // http://www.pdos.lcs.mit.edu/~dm/c++-new.html
20 //
21 // A somewhat similar approach to Gladiator, but for heap-detection, was
22 // suggested by Ron van der Wal and Scott Meyers at
23 // http://www.aristeia.com/BookErrata/M27Comments_frames.html
24
25 #include "utils/base/arena.h"
26
27 #include "utils/base/logging.h"
28 #include "utils/base/macros.h"
29
30 namespace libtextclassifier3 {
31
aligned_malloc(size_t size,int minimum_alignment)32 static void *aligned_malloc(size_t size, int minimum_alignment) {
33 void *ptr = nullptr;
34 // posix_memalign requires that the requested alignment be at least
35 // sizeof(void*). In this case, fall back on malloc which should return memory
36 // aligned to at least the size of a pointer.
37 const int required_alignment = sizeof(void*);
38 if (minimum_alignment < required_alignment)
39 return malloc(size);
40 if (posix_memalign(&ptr, static_cast<size_t>(minimum_alignment), size) != 0)
41 return nullptr;
42 else
43 return ptr;
44 }
45
46 // The value here doesn't matter until page_aligned_ is supported.
47 static const int kPageSize = 8192; // should be getpagesize()
48
49 // We used to only keep track of how much space has been allocated in
50 // debug mode. Now we track this for optimized builds, as well. If you
51 // want to play with the old scheme to see if this helps performance,
52 // change this TC3_ARENASET() macro to a NOP. However, NOTE: some
53 // applications of arenas depend on this space information (exported
54 // via bytes_allocated()).
55 #define TC3_ARENASET(x) (x)
56
57 namespace {
58
59 #ifdef __cpp_aligned_new
60
AllocateBytes(size_t size)61 char* AllocateBytes(size_t size) {
62 return static_cast<char*>(::operator new(size));
63 }
64
65 // REQUIRES: alignment > __STDCPP_DEFAULT_NEW_ALIGNMENT__
66 //
67 // For alignments <=__STDCPP_DEFAULT_NEW_ALIGNMENT__, AllocateBytes() will
68 // provide the correct alignment.
AllocateAlignedBytes(size_t size,size_t alignment)69 char* AllocateAlignedBytes(size_t size, size_t alignment) {
70 TC3_CHECK_GT(alignment, __STDCPP_DEFAULT_NEW_ALIGNMENT__);
71 return static_cast<char*>(::operator new(size, std::align_val_t(alignment)));
72 }
73
DeallocateBytes(void * ptr,size_t size,size_t alignment)74 void DeallocateBytes(void* ptr, size_t size, size_t alignment) {
75 if (alignment > __STDCPP_DEFAULT_NEW_ALIGNMENT__) {
76 #ifdef __cpp_sized_deallocation
77 ::operator delete(ptr, size, std::align_val_t(alignment));
78 #else // !__cpp_sized_deallocation
79 ::operator delete(ptr, std::align_val_t(alignment));
80 #endif // !__cpp_sized_deallocation
81 } else {
82 #ifdef __cpp_sized_deallocation
83 ::operator delete(ptr, size);
84 #else // !__cpp_sized_deallocation
85 ::operator delete(ptr);
86 #endif // !__cpp_sized_deallocation
87 }
88 }
89
90 #else // !__cpp_aligned_new
91
92 char* AllocateBytes(size_t size) {
93 return static_cast<char*>(malloc(size));
94 }
95
96 char* AllocateAlignedBytes(size_t size, size_t alignment) {
97 return static_cast<char*>(aligned_malloc(size, alignment));
98 }
99
100 void DeallocateBytes(void* ptr, size_t size, size_t alignment) {
101 free(ptr);
102 }
103
104 #endif // !__cpp_aligned_new
105
106 } // namespace
107
108 const int BaseArena::kDefaultAlignment;
109
110 // ----------------------------------------------------------------------
111 // BaseArena::BaseArena()
112 // BaseArena::~BaseArena()
113 // Destroying the arena automatically calls Reset()
114 // ----------------------------------------------------------------------
115
BaseArena(char * first,const size_t orig_block_size,bool align_to_page)116 BaseArena::BaseArena(char* first, const size_t orig_block_size,
117 bool align_to_page)
118 : remaining_(0),
119 block_size_(orig_block_size),
120 freestart_(nullptr), // set for real in Reset()
121 last_alloc_(nullptr),
122 overflow_blocks_(nullptr),
123 first_block_externally_owned_(first != nullptr),
124 page_aligned_(align_to_page),
125 blocks_alloced_(1) {
126 // Trivial check that aligned objects can actually be allocated.
127 TC3_CHECK_GT(block_size_, kDefaultAlignment)
128 << "orig_block_size = " << orig_block_size;
129 if (page_aligned_) {
130 // kPageSize must be power of 2, so make sure of this.
131 TC3_CHECK(kPageSize > 0 && 0 == (kPageSize & (kPageSize - 1)))
132 << "kPageSize[ " << kPageSize << "] is not "
133 << "correctly initialized: not a power of 2.";
134 }
135
136 if (first) {
137 TC3_CHECK(!page_aligned_ ||
138 (reinterpret_cast<uintptr_t>(first) & (kPageSize - 1)) == 0);
139 first_blocks_[0].mem = first;
140 first_blocks_[0].size = orig_block_size;
141 } else {
142 if (page_aligned_) {
143 // Make sure the blocksize is page multiple, as we need to end on a page
144 // boundary.
145 TC3_CHECK_EQ(block_size_ & (kPageSize - 1), 0) << "block_size is not a"
146 << "multiple of kPageSize";
147 first_blocks_[0].mem = AllocateAlignedBytes(block_size_, kPageSize);
148 first_blocks_[0].alignment = kPageSize;
149 TC3_CHECK(nullptr != first_blocks_[0].mem);
150 } else {
151 first_blocks_[0].mem = AllocateBytes(block_size_);
152 first_blocks_[0].alignment = 0;
153 }
154 first_blocks_[0].size = block_size_;
155 }
156
157 Reset();
158 }
159
~BaseArena()160 BaseArena::~BaseArena() {
161 FreeBlocks();
162 assert(overflow_blocks_ == nullptr); // FreeBlocks() should do that
163 #ifdef ADDRESS_SANITIZER
164 if (first_block_externally_owned_) {
165 ASAN_UNPOISON_MEMORY_REGION(first_blocks_[0].mem, first_blocks_[0].size);
166 }
167 #endif
168 // The first X blocks stay allocated always by default. Delete them now.
169 for (int i = first_block_externally_owned_ ? 1 : 0;
170 i < blocks_alloced_; ++i) {
171 DeallocateBytes(first_blocks_[i].mem, first_blocks_[i].size,
172 first_blocks_[i].alignment);
173 }
174 }
175
176 // ----------------------------------------------------------------------
177 // BaseArena::block_count()
178 // Only reason this is in .cc file is because it involves STL.
179 // ----------------------------------------------------------------------
180
block_count() const181 int BaseArena::block_count() const {
182 return (blocks_alloced_ +
183 (overflow_blocks_ ? static_cast<int>(overflow_blocks_->size()) : 0));
184 }
185
186 // Returns true iff it advances freestart_ to the first position
187 // satisfying alignment without exhausting the current block.
SatisfyAlignment(size_t alignment)188 bool BaseArena::SatisfyAlignment(size_t alignment) {
189 const size_t overage =
190 reinterpret_cast<size_t>(freestart_) & (alignment - 1);
191 if (overage > 0) {
192 const size_t waste = alignment - overage;
193 if (waste >= remaining_) {
194 return false;
195 }
196 freestart_ += waste;
197 remaining_ -= waste;
198 }
199 TC3_DCHECK_EQ(0, reinterpret_cast<size_t>(freestart_) & (alignment - 1));
200 return true;
201 }
202
203 // ----------------------------------------------------------------------
204 // BaseArena::Reset()
205 // Clears all the memory an arena is using.
206 // ----------------------------------------------------------------------
207
Reset()208 void BaseArena::Reset() {
209 FreeBlocks();
210 freestart_ = first_blocks_[0].mem;
211 remaining_ = first_blocks_[0].size;
212 last_alloc_ = nullptr;
213 #ifdef ADDRESS_SANITIZER
214 ASAN_POISON_MEMORY_REGION(freestart_, remaining_);
215 #endif
216
217 TC3_ARENASET(status_.bytes_allocated_ = block_size_);
218
219 // There is no guarantee the first block is properly aligned, so
220 // enforce that now.
221 TC3_CHECK(SatisfyAlignment(kDefaultAlignment));
222
223 freestart_when_empty_ = freestart_;
224 }
225
226 // ----------------------------------------------------------------------
227 // BaseArena::MakeNewBlock()
228 // Our sbrk() equivalent. We always make blocks of the same size
229 // (though GetMemory() can also make a new block for really big
230 // data.
231 // ----------------------------------------------------------------------
232
MakeNewBlock(const uint32 alignment)233 void BaseArena::MakeNewBlock(const uint32 alignment) {
234 AllocatedBlock *block = AllocNewBlock(block_size_, alignment);
235 freestart_ = block->mem;
236 remaining_ = block->size;
237 TC3_CHECK(SatisfyAlignment(alignment));
238 }
239
240 // The following simple numeric routines also exist in util/math/mathutil.h
241 // but we don't want to depend on that library.
242
243 // Euclid's algorithm for Greatest Common Denominator.
GCD(uint32 x,uint32 y)244 static uint32 GCD(uint32 x, uint32 y) {
245 while (y != 0) {
246 uint32 r = x % y;
247 x = y;
248 y = r;
249 }
250 return x;
251 }
252
LeastCommonMultiple(uint32 a,uint32 b)253 static uint32 LeastCommonMultiple(uint32 a, uint32 b) {
254 if (a > b) {
255 return (a / GCD(a, b)) * b;
256 } else if (a < b) {
257 return (b / GCD(b, a)) * a;
258 } else {
259 return a;
260 }
261 }
262
263 // -------------------------------------------------------------
264 // BaseArena::AllocNewBlock()
265 // Adds and returns an AllocatedBlock.
266 // The returned AllocatedBlock* is valid until the next call
267 // to AllocNewBlock or Reset. (i.e. anything that might
268 // affect overflow_blocks_).
269 // -------------------------------------------------------------
270
AllocNewBlock(const size_t block_size,const uint32 alignment)271 BaseArena::AllocatedBlock* BaseArena::AllocNewBlock(const size_t block_size,
272 const uint32 alignment) {
273 AllocatedBlock *block;
274 // Find the next block.
275 if (blocks_alloced_ < TC3_ARRAYSIZE(first_blocks_)) {
276 // Use one of the pre-allocated blocks
277 block = &first_blocks_[blocks_alloced_++];
278 } else { // oops, out of space, move to the vector
279 if (overflow_blocks_ == nullptr)
280 overflow_blocks_ = new std::vector<AllocatedBlock>;
281 // Adds another block to the vector.
282 overflow_blocks_->resize(overflow_blocks_->size()+1);
283 // block points to the last block of the vector.
284 block = &overflow_blocks_->back();
285 }
286
287 // NOTE(tucker): this utility is made slightly more complex by
288 // not disallowing the case where alignment > block_size.
289 // Can we, without breaking existing code?
290
291 // If page_aligned_, then alignment must be a multiple of page size.
292 // Otherwise, must be a multiple of kDefaultAlignment, unless
293 // requested alignment is 1, in which case we don't care at all.
294 const uint32 adjusted_alignment =
295 page_aligned_ ? LeastCommonMultiple(kPageSize, alignment)
296 : (alignment > 1 ? LeastCommonMultiple(alignment, kDefaultAlignment) : 1);
297 TC3_CHECK_LE(adjusted_alignment, 1 << 20)
298 << "Alignment on boundaries greater than 1MB not supported.";
299
300 // If block_size > alignment we force block_size to be a multiple
301 // of alignment; if block_size < alignment we make no adjustment, unless
302 // page_aligned_ is true, in which case it must be a multiple of
303 // kPageSize because SetProtect() will assume that.
304 size_t adjusted_block_size = block_size;
305 #ifdef __STDCPP_DEFAULT_NEW_ALIGNMENT__
306 if (adjusted_alignment > __STDCPP_DEFAULT_NEW_ALIGNMENT__) {
307 #else
308 if (adjusted_alignment > 1) {
309 #endif
310 if (adjusted_block_size > adjusted_alignment) {
311 const uint32 excess = adjusted_block_size % adjusted_alignment;
312 adjusted_block_size += (excess > 0 ? adjusted_alignment - excess : 0);
313 }
314 if (page_aligned_) {
315 size_t num_pages = ((adjusted_block_size - 1)/kPageSize) + 1;
316 adjusted_block_size = num_pages * kPageSize;
317 }
318 block->mem = AllocateAlignedBytes(adjusted_block_size, adjusted_alignment);
319 } else {
320 block->mem = AllocateBytes(adjusted_block_size);
321 }
322 block->size = adjusted_block_size;
323 block->alignment = adjusted_alignment;
324 TC3_CHECK(nullptr != block->mem)
325 << "block_size=" << block_size
326 << " adjusted_block_size=" << adjusted_block_size
327 << " alignment=" << alignment
328 << " adjusted_alignment=" << adjusted_alignment;
329
330 TC3_ARENASET(status_.bytes_allocated_ += adjusted_block_size);
331
332 #ifdef ADDRESS_SANITIZER
333 ASAN_POISON_MEMORY_REGION(block->mem, block->size);
334 #endif
335 return block;
336 }
337
338 // ----------------------------------------------------------------------
339 // BaseArena::IndexToBlock()
340 // Index encoding is as follows:
341 // For blocks in the first_blocks_ array, we use index of the block in
342 // the array.
343 // For blocks in the overflow_blocks_ vector, we use the index of the
344 // block in iverflow_blocks_, plus the size of the first_blocks_ array.
345 // ----------------------------------------------------------------------
346
347 const BaseArena::AllocatedBlock *BaseArena::IndexToBlock(int index) const {
348 if (index < TC3_ARRAYSIZE(first_blocks_)) {
349 return &first_blocks_[index];
350 }
351 TC3_CHECK(overflow_blocks_ != nullptr);
352 int index_in_overflow_blocks = index - TC3_ARRAYSIZE(first_blocks_);
353 TC3_CHECK_GE(index_in_overflow_blocks, 0);
354 TC3_CHECK_LT(static_cast<size_t>(index_in_overflow_blocks),
355 overflow_blocks_->size());
356 return &(*overflow_blocks_)[index_in_overflow_blocks];
357 }
358
359 // ----------------------------------------------------------------------
360 // BaseArena::GetMemoryFallback()
361 // We take memory out of our pool, aligned on the byte boundary
362 // requested. If we don't have space in our current pool, we
363 // allocate a new block (wasting the remaining space in the
364 // current block) and give you that. If your memory needs are
365 // too big for a single block, we make a special your-memory-only
366 // allocation -- this is equivalent to not using the arena at all.
367 // ----------------------------------------------------------------------
368
369 void* BaseArena::GetMemoryFallback(const size_t size, const int alignment) {
370 if (0 == size) {
371 return nullptr; // stl/stl_alloc.h says this is okay
372 }
373
374 // alignment must be a positive power of 2.
375 TC3_CHECK(alignment > 0 && 0 == (alignment & (alignment - 1)));
376
377 // If the object is more than a quarter of the block size, allocate
378 // it separately to avoid wasting too much space in leftover bytes.
379 if (block_size_ == 0 || size > block_size_/4) {
380 // Use a block separate from all other allocations; in particular
381 // we don't update last_alloc_ so you can't reclaim space on this block.
382 AllocatedBlock* b = AllocNewBlock(size, alignment);
383 #ifdef ADDRESS_SANITIZER
384 ASAN_UNPOISON_MEMORY_REGION(b->mem, b->size);
385 #endif
386 return b->mem;
387 }
388
389 // Enforce alignment on freestart_ then check for adequate space,
390 // which may require starting a new block.
391 if (!SatisfyAlignment(alignment) || size > remaining_) {
392 MakeNewBlock(alignment);
393 }
394 TC3_CHECK_LE(size, remaining_);
395
396 remaining_ -= size;
397 last_alloc_ = freestart_;
398 freestart_ += size;
399
400 #ifdef ADDRESS_SANITIZER
401 ASAN_UNPOISON_MEMORY_REGION(last_alloc_, size);
402 #endif
403 return reinterpret_cast<void*>(last_alloc_);
404 }
405
406 // ----------------------------------------------------------------------
407 // BaseArena::ReturnMemoryFallback()
408 // BaseArena::FreeBlocks()
409 // Unlike GetMemory(), which does actual work, ReturnMemory() is a
410 // no-op: we don't "free" memory until Reset() is called. We do
411 // update some stats, though. Note we do no checking that the
412 // pointer you pass in was actually allocated by us, or that it
413 // was allocated for the size you say, so be careful here!
414 // FreeBlocks() does the work for Reset(), actually freeing all
415 // memory allocated in one fell swoop.
416 // ----------------------------------------------------------------------
417
418 void BaseArena::FreeBlocks() {
419 for ( int i = 1; i < blocks_alloced_; ++i ) { // keep first block alloced
420 DeallocateBytes(first_blocks_[i].mem, first_blocks_[i].size,
421 first_blocks_[i].alignment);
422 first_blocks_[i].mem = nullptr;
423 first_blocks_[i].size = 0;
424 }
425 blocks_alloced_ = 1;
426 if (overflow_blocks_ != nullptr) {
427 std::vector<AllocatedBlock>::iterator it;
428 for (it = overflow_blocks_->begin(); it != overflow_blocks_->end(); ++it) {
429 DeallocateBytes(it->mem, it->size, it->alignment);
430 }
431 delete overflow_blocks_; // These should be used very rarely
432 overflow_blocks_ = nullptr;
433 }
434 }
435
436 // ----------------------------------------------------------------------
437 // BaseArena::AdjustLastAlloc()
438 // If you realize you didn't want your last alloc to be for
439 // the size you asked, after all, you can fix it by calling
440 // this. We'll grow or shrink the last-alloc region if we
441 // can (we can always shrink, but we might not be able to
442 // grow if you want to grow too big.
443 // RETURNS true if we successfully modified the last-alloc
444 // region, false if the pointer you passed in wasn't actually
445 // the last alloc or if you tried to grow bigger than we could.
446 // ----------------------------------------------------------------------
447
448 bool BaseArena::AdjustLastAlloc(void *last_alloc, const size_t newsize) {
449 // It's only legal to call this on the last thing you alloced.
450 if (last_alloc == nullptr || last_alloc != last_alloc_) return false;
451 // last_alloc_ should never point into a "big" block, w/ size >= block_size_
452 assert(freestart_ >= last_alloc_ && freestart_ <= last_alloc_ + block_size_);
453 assert(remaining_ >= 0); // should be: it's a size_t!
454 if (newsize > (freestart_ - last_alloc_) + remaining_)
455 return false; // not enough room, even after we get back last_alloc_ space
456 const char* old_freestart = freestart_; // where last alloc used to end
457 freestart_ = last_alloc_ + newsize; // where last alloc ends now
458 remaining_ -= (freestart_ - old_freestart); // how much new space we've taken
459
460 #ifdef ADDRESS_SANITIZER
461 ASAN_UNPOISON_MEMORY_REGION(last_alloc_, newsize);
462 ASAN_POISON_MEMORY_REGION(freestart_, remaining_);
463 #endif
464 return true;
465 }
466
467 // ----------------------------------------------------------------------
468 // UnsafeArena::Realloc()
469 // SafeArena::Realloc()
470 // If you decide you want to grow -- or shrink -- a memory region,
471 // we'll do it for you here. Typically this will involve copying
472 // the existing memory to somewhere else on the arena that has
473 // more space reserved. But if you're reallocing the last-allocated
474 // block, we may be able to accommodate you just by updating a
475 // pointer. In any case, we return a pointer to the new memory
476 // location, which may be the same as the pointer you passed in.
477 // Here's an example of how you might use Realloc():
478 //
479 // compr_buf = arena->Alloc(uncompr_size); // get too-much space
480 // int compr_size;
481 // zlib.Compress(uncompr_buf, uncompr_size, compr_buf, &compr_size);
482 // compr_buf = arena->Realloc(compr_buf, uncompr_size, compr_size);
483 // ----------------------------------------------------------------------
484
485 char* UnsafeArena::Realloc(char* original, size_t oldsize, size_t newsize) {
486 assert(oldsize >= 0 && newsize >= 0);
487 // if original happens to be the last allocation we can avoid fragmentation.
488 if (AdjustLastAlloc(original, newsize)) {
489 return original;
490 }
491
492 char* resized = original;
493 if (newsize > oldsize) {
494 resized = Alloc(newsize);
495 memcpy(resized, original, oldsize);
496 } else {
497 // no need to do anything; we're ain't reclaiming any memory!
498 }
499
500 #ifdef ADDRESS_SANITIZER
501 // Alloc already returns unpoisoned memory, but handling both cases here
502 // allows us to poison the old memory without worrying about whether or not it
503 // overlaps with the new memory. Thus, we must poison the old memory first.
504 ASAN_POISON_MEMORY_REGION(original, oldsize);
505 ASAN_UNPOISON_MEMORY_REGION(resized, newsize);
506 #endif
507 return resized;
508 }
509
510 // Avoid weak vtables by defining a dummy key method.
511 void UnsafeArena::UnusedKeyMethod() {}
512
513 } // namespace libtextclassifier3
514