blob: 0a07c8c16fb81caa55f29801f7fb7f48270ecd87 [file] [log] [blame]
/*
* 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_