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