| /* |
| * Copyright 2011 Google Inc. |
| * |
| * 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. |
| */ |
| |
| // Author: morlovich@google.com (Maksim Orlovich) |
| |
| #ifndef PAGESPEED_KERNEL_BASE_ARENA_H_ |
| #define PAGESPEED_KERNEL_BASE_ARENA_H_ |
| |
| #include <vector> |
| #include <cstddef> |
| |
| #include "base/logging.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| |
| namespace net_instaweb { |
| |
| // This template keeps a packed set of objects inheriting from the same base |
| // type (which must have a virtual destructor) where all of the |
| // objects in the same arena are expected to be destroyed at once. |
| template<typename T> |
| class Arena { |
| public: |
| // All allocations we make will be aligned to this. We will also reserve |
| // this much room for our work area, as it keeps things simple. |
| static const size_t kAlign = 8; |
| |
| Arena() { |
| InitEmpty(); |
| } |
| |
| ~Arena() { |
| CHECK(chunks_.empty()); |
| } |
| |
| void* Allocate(size_t size) { |
| size += kAlign; // Need room to link the next object. |
| size = ExpandToAlign(size); |
| |
| DCHECK(sizeof(void*) <= kAlign); |
| DCHECK(size < Chunk::kSize); |
| |
| if (next_alloc_ + size > chunk_end_) { |
| AddChunk(); |
| } |
| |
| char* base = next_alloc_; |
| |
| // Update the links -- the previous object should point to our |
| // chunk's base, our base should point to NULL, and last_link_ |
| // should point to our base |
| char** our_last_link_field = reinterpret_cast<char**>(base); |
| *last_link_ = base; |
| *our_last_link_field = NULL; |
| last_link_ = our_last_link_field; |
| |
| next_alloc_ += size; |
| |
| char* out = base + kAlign; |
| // Warning: the following line is very type-sensitive and can't |
| // easily be turned into a DCHECK_EQ as lint would like. |
| DCHECK((reinterpret_cast<uintptr_t>(out) & (kAlign - 1)) == 0); |
| return out; |
| } |
| |
| // Cleans up all the objects in the arena. You must call this explicitly. |
| void DestroyObjects(); |
| |
| // Rounds block size up to 8; we always align to it, even on 32-bit. |
| static size_t ExpandToAlign(size_t in) { |
| return (in + kAlign - 1) & ~(kAlign - 1); |
| } |
| |
| private: |
| struct Chunk { |
| // Representation: the arena is a vector of fixed-size (kSize, 8k) chunks; |
| // we allocate objects from the end of the most recently created chunk until |
| // an allocation doesn't fit (in which case we make a fresh chunk) |
| // |
| // Each chunk is independently organized into a singly-linked list, where |
| // we precede each object with a pointer to the next allocated block, e.g.: |
| // |
| // /-----------------| |
| // | \|/ |
| // -----------------|---------------|----|-----------| |
| // | | object 1 | | | object 2 | NU | object 3 | |
| // | | | | | | LL | | |
| // |_|_|----------|---|-------------|----|-----------| |
| // | ^ |
| // \--------------/ |
| // We need this because objects may have different sizes, and we'll need |
| // to find everyone to call their destructor |
| static const size_t kSize = 8192; |
| char buf[kSize]; |
| }; |
| |
| // Adds in a new chunk and initializes all the fields below to refer to it |
| void AddChunk(); |
| |
| // Sets up all the pointers below to denote us being empty. |
| void InitEmpty(); |
| |
| // First free byte of the current chunk |
| char* next_alloc_; |
| |
| // The point where to link in a new object we allocate. |
| // We need this so that the last object in a chunk has |
| // a null 'next' link. |
| char** last_link_; |
| |
| // First address after the last byte of the currently active chunk |
| char* chunk_end_; |
| |
| // Scratch location in case we want a write to go nowhere. |
| // Does not point anywhere, just bitbuckets writes |
| char* scratch_; |
| |
| std::vector<Chunk*> chunks_; |
| }; |
| |
| template<typename T> |
| void Arena<T>::AddChunk() { |
| Chunk* chunk = new Chunk(); |
| chunks_.push_back(chunk); |
| next_alloc_ = chunk->buf; |
| chunk_end_ = next_alloc_ + Chunk::kSize; |
| last_link_ = &scratch_; |
| } |
| |
| template<typename T> |
| void Arena<T>::DestroyObjects() { |
| for (int i = 0; i < static_cast<int>(chunks_.size()); ++i) { |
| // Walk through objects in this chunk. |
| char* base = chunks_[i]->buf; |
| while (base != NULL) { |
| reinterpret_cast<T*>(base + kAlign)->~T(); |
| base = *reinterpret_cast<char**>(base); |
| } |
| delete chunks_[i]; |
| } |
| chunks_.clear(); |
| InitEmpty(); |
| } |
| |
| template<typename T> |
| void Arena<T>::InitEmpty() { |
| // The way this is initialized ensures that the next call to allocate |
| // will call AddChunk(). Doing it this way rather than calling NewChunk() |
| // from here establishes the invariant that all the chunks are non-empty, |
| // which helps in DestroyObjects() |
| next_alloc_ = NULL; |
| last_link_ = NULL; |
| chunk_end_ = NULL; |
| } |
| |
| } // namespace net_instaweb |
| |
| #endif // PAGESPEED_KERNEL_BASE_ARENA_H_ |