/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Sometimes it is necessary to allocate a large number of small // objects. Doing this the usual way (malloc, new) is slow, // especially for multithreaded programs. A BaseArena provides a // mark/release method of memory management: it asks for a large chunk // from the operating system and doles it out bit by bit as required. // Then you free all the memory at once by calling BaseArena::Reset(). // // // --Example Uses Of UnsafeArena // This is the simplest way. Just create an arena, and whenever you // need a block of memory to put something in, call BaseArena::Alloc(). eg // s = arena.Alloc(100); // snprintf(s, 100, "%s:%d", host, port); // arena.Shrink(strlen(s)+1); // optional; see below for use // // You'll probably use the convenience routines more often: // s = arena.Strdup(host); // a copy of host lives in the arena // s = arena.Strndup(host, 100); // we guarantee to NUL-terminate! // s = arena.Memdup(protobuf, sizeof(protobuf); // // If you go the Alloc() route, you'll probably allocate too-much-space. // You can reclaim the extra space by calling Shrink() before the next // Alloc() (or Strdup(), or whatever), with the #bytes you actually used. // If you use this method, memory management is easy: just call Alloc() // and friends a lot, and call Reset() when you're done with the data. // // FOR STRINGS: --Uses UnsafeArena // This is a special case of STL (below), but is simpler. Use an // astring, which acts like a string but allocates from the passed-in // arena: // astring s(arena); // or "sastring" to use a SafeArena // s.assign(host); // astring s2(host, hostlen, arena); #ifndef LIBTEXTCLASSIFIER_UTILS_BASE_ARENA_H_ #define LIBTEXTCLASSIFIER_UTILS_BASE_ARENA_H_ #include #include #include #ifdef ADDRESS_SANITIZER #include #endif #include "utils/base/integral_types.h" #include "utils/base/logging.h" namespace libtextclassifier3 { // This class is "thread-compatible": different threads can access the // arena at the same time without locking, as long as they use only // const methods. class BaseArena { protected: // You can't make an arena directly; only a subclass of one BaseArena(char* first_block, const size_t block_size, bool align_to_page); public: virtual ~BaseArena(); virtual void Reset(); // they're "slow" only 'cause they're virtual (subclasses define "fast" ones) virtual char* SlowAlloc(size_t size) = 0; virtual void SlowFree(void* memory, size_t size) = 0; virtual char* SlowRealloc(char* memory, size_t old_size, size_t new_size) = 0; class Status { private: friend class BaseArena; size_t bytes_allocated_; public: Status() : bytes_allocated_(0) {} size_t bytes_allocated() const { return bytes_allocated_; } }; // Accessors and stats counters // This accessor isn't so useful here, but is included so we can be // type-compatible with ArenaAllocator (in arena_allocator.h). That is, // we define arena() because ArenaAllocator does, and that way you // can template on either of these and know it's safe to call arena(). virtual BaseArena* arena() { return this; } size_t block_size() const { return block_size_; } int block_count() const; bool is_empty() const { // must check block count in case we allocated a block larger than blksize return freestart_ == freestart_when_empty_ && 1 == block_count(); } // The alignment that ArenaAllocator uses except for 1-byte objects. static constexpr int kDefaultAlignment = 8; protected: bool SatisfyAlignment(const size_t alignment); void MakeNewBlock(const uint32 alignment); void* GetMemoryFallback(const size_t size, const int align); void* GetMemory(const size_t size, const int align) { assert(remaining_ <= block_size_); // an invariant if (size > 0 && size <= remaining_ && align == 1) { // common case last_alloc_ = freestart_; freestart_ += size; remaining_ -= size; #ifdef ADDRESS_SANITIZER ASAN_UNPOISON_MEMORY_REGION(last_alloc_, size); #endif return reinterpret_cast(last_alloc_); } return GetMemoryFallback(size, align); } // This doesn't actually free any memory except for the last piece allocated void ReturnMemory(void* memory, const size_t size) { if (memory == last_alloc_ && size == static_cast(freestart_ - last_alloc_)) { remaining_ += size; freestart_ = last_alloc_; } #ifdef ADDRESS_SANITIZER ASAN_POISON_MEMORY_REGION(memory, size); #endif } // This is used by Realloc() -- usually we Realloc just by copying to a // bigger space, but for the last alloc we can realloc by growing the region. bool AdjustLastAlloc(void* last_alloc, const size_t newsize); Status status_; size_t remaining_; private: struct AllocatedBlock { char* mem; size_t size; size_t alignment; }; // Allocate new new block of at least block_size, with the specified // alignment. // The returned AllocatedBlock* is valid until the next call to AllocNewBlock // or Reset (i.e. anything that might affect overflow_blocks_). AllocatedBlock* AllocNewBlock(const size_t block_size, const uint32 alignment); const AllocatedBlock* IndexToBlock(int index) const; const size_t block_size_; char* freestart_; // beginning of the free space in most recent block char* freestart_when_empty_; // beginning of the free space when we're empty char* last_alloc_; // used to make sure ReturnBytes() is safe // if the first_blocks_ aren't enough, expand into overflow_blocks_. std::vector* overflow_blocks_; // STL vector isn't as efficient as it could be, so we use an array at first const bool first_block_externally_owned_; // true if they pass in 1st block const bool page_aligned_; // when true, all blocks need to be page aligned int8_t blocks_alloced_; // how many of the first_blocks_ have been allocated AllocatedBlock first_blocks_[16]; // the length of this array is arbitrary void FreeBlocks(); // Frees all except first block BaseArena(const BaseArena&) = delete; BaseArena& operator=(const BaseArena&) = delete; }; class UnsafeArena : public BaseArena { public: // Allocates a thread-compatible arena with the specified block size. explicit UnsafeArena(const size_t block_size) : BaseArena(nullptr, block_size, false) {} UnsafeArena(const size_t block_size, bool align) : BaseArena(nullptr, block_size, align) {} // Allocates a thread-compatible arena with the specified block // size. "first_block" must have size "block_size". Memory is // allocated from "first_block" until it is exhausted; after that // memory is allocated by allocating new blocks from the heap. UnsafeArena(char* first_block, const size_t block_size) : BaseArena(first_block, block_size, false) {} UnsafeArena(char* first_block, const size_t block_size, bool align) : BaseArena(first_block, block_size, align) {} char* Alloc(const size_t size) { return reinterpret_cast(GetMemory(size, 1)); } void* AllocAligned(const size_t size, const int align) { return GetMemory(size, align); } // Allocates and initializes an object on the arena. template T* AllocAndInit(Args&&... args) { return new (reinterpret_cast(AllocAligned(sizeof(T), alignof(T)))) T(std::forward(args)...); } char* Calloc(const size_t size) { void* return_value = Alloc(size); memset(return_value, 0, size); return reinterpret_cast(return_value); } void* CallocAligned(const size_t size, const int align) { void* return_value = AllocAligned(size, align); memset(return_value, 0, size); return return_value; } // Free does nothing except for the last piece allocated. void Free(void* memory, size_t size) { ReturnMemory(memory, size); } char* SlowAlloc(size_t size) override { // "slow" 'cause it's virtual return Alloc(size); } void SlowFree(void* memory, size_t size) override { // "slow" 'cause it's virt Free(memory, size); } char* SlowRealloc(char* memory, size_t old_size, size_t new_size) override { return Realloc(memory, old_size, new_size); } char* Memdup(const char* s, size_t bytes) { char* newstr = Alloc(bytes); memcpy(newstr, s, bytes); return newstr; } char* MemdupPlusNUL(const char* s, size_t bytes) { // like "string(s, len)" char* newstr = Alloc(bytes + 1); memcpy(newstr, s, bytes); newstr[bytes] = '\0'; return newstr; } char* Strdup(const char* s) { return Memdup(s, strlen(s) + 1); } // Unlike libc's strncpy, I always NUL-terminate. libc's semantics are dumb. // This will allocate at most n+1 bytes (+1 is for the nul terminator). char* Strndup(const char* s, size_t n) { // Use memchr so we don't walk past n. // We can't use the one in //strings since this is the base library, // so we have to reinterpret_cast from the libc void*. const char* eos = reinterpret_cast(memchr(s, '\0', n)); // if no null terminator found, use full n const size_t bytes = (eos == nullptr) ? n : eos - s; return MemdupPlusNUL(s, bytes); } // You can realloc a previously-allocated string either bigger or smaller. // We can be more efficient if you realloc a string right after you allocate // it (eg allocate way-too-much space, fill it, realloc to just-big-enough) char* Realloc(char* original, size_t oldsize, size_t newsize); // If you know the new size is smaller (or equal), you don't need to know // oldsize. We don't check that newsize is smaller, so you'd better be sure! char* Shrink(char* s, size_t newsize) { AdjustLastAlloc(s, newsize); // reclaim space if we can return s; // never need to move if we go smaller } // We make a copy so you can keep track of status at a given point in time Status status() const { return status_; } // Number of bytes remaining before the arena has to allocate another block. size_t bytes_until_next_allocation() const { return remaining_; } private: UnsafeArena(const UnsafeArena&) = delete; UnsafeArena& operator=(const UnsafeArena&) = delete; virtual void UnusedKeyMethod(); // Dummy key method to avoid weak vtable. }; } // namespace libtextclassifier3 #endif // LIBTEXTCLASSIFIER_UTILS_BASE_ARENA_H_