aboutsummaryrefslogtreecommitdiff
path: root/util/arena.cc
blob: 25753c5df5cca24eead3ca9e3f4527797e24b895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright 2000 The RE2 Authors.  All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "util/util.h"

namespace re2 {

// ----------------------------------------------------------------------
// UnsafeArena::UnsafeArena()
// UnsafeArena::~UnsafeArena()
//    Destroying the arena automatically calls Reset()
// ----------------------------------------------------------------------


UnsafeArena::UnsafeArena(const size_t block_size)
  : block_size_(block_size),
    freestart_(NULL),                   // set for real in Reset()
    last_alloc_(NULL),
    remaining_(0),
    blocks_alloced_(1),
    overflow_blocks_(NULL) {
  assert(block_size > kDefaultAlignment);

  first_blocks_[0].mem = reinterpret_cast<char*>(malloc(block_size_));
  first_blocks_[0].size = block_size_;

  Reset();
}

UnsafeArena::~UnsafeArena() {
  FreeBlocks();
  assert(overflow_blocks_ == NULL);    // FreeBlocks() should do that
  // The first X blocks stay allocated always by default.  Delete them now.
  for (int i = 0; i < blocks_alloced_; i++)
    free(first_blocks_[i].mem);
}

// ----------------------------------------------------------------------
// UnsafeArena::Reset()
//    Clears all the memory an arena is using.
// ----------------------------------------------------------------------

void UnsafeArena::Reset() {
  FreeBlocks();
  freestart_ = first_blocks_[0].mem;
  remaining_ = first_blocks_[0].size;
  last_alloc_ = NULL;

  // We do not know for sure whether or not the first block is aligned,
  // so we fix that right now.
  const int overage = reinterpret_cast<uintptr_t>(freestart_) &
                      (kDefaultAlignment-1);
  if (overage > 0) {
    const int waste = kDefaultAlignment - overage;
    freestart_ += waste;
    remaining_ -= waste;
  }
  freestart_when_empty_ = freestart_;
  assert(!(reinterpret_cast<uintptr_t>(freestart_)&(kDefaultAlignment-1)));
}

// -------------------------------------------------------------
// UnsafeArena::AllocNewBlock()
//    Adds and returns an AllocatedBlock.
//    The returned AllocatedBlock* is valid until the next call
//    to AllocNewBlock or Reset.  (i.e. anything that might
//    affect overflow_blocks_).
// -------------------------------------------------------------

UnsafeArena::AllocatedBlock* UnsafeArena::AllocNewBlock(const size_t block_size) {
  AllocatedBlock *block;
  // Find the next block.
  if ( blocks_alloced_ < arraysize(first_blocks_) ) {
    // Use one of the pre-allocated blocks
    block = &first_blocks_[blocks_alloced_++];
  } else {                   // oops, out of space, move to the vector
    if (overflow_blocks_ == NULL) overflow_blocks_ = new vector<AllocatedBlock>;
    // Adds another block to the vector.
    overflow_blocks_->resize(overflow_blocks_->size()+1);
    // block points to the last block of the vector.
    block = &overflow_blocks_->back();
  }

  block->mem = reinterpret_cast<char*>(malloc(block_size));
  block->size = block_size;

  return block;
}

// ----------------------------------------------------------------------
// UnsafeArena::GetMemoryFallback()
//    We take memory out of our pool, aligned on the byte boundary
//    requested.  If we don't have space in our current pool, we
//    allocate a new block (wasting the remaining space in the
//    current block) and give you that.  If your memory needs are
//    too big for a single block, we make a special your-memory-only
//    allocation -- this is equivalent to not using the arena at all.
// ----------------------------------------------------------------------

void* UnsafeArena::GetMemoryFallback(const size_t size, const int align) {
  if (size == 0)
    return NULL;             // stl/stl_alloc.h says this is okay

  assert(align > 0 && 0 == (align & (align - 1)));  // must be power of 2

  // If the object is more than a quarter of the block size, allocate
  // it separately to avoid wasting too much space in leftover bytes
  if (block_size_ == 0 || size > block_size_/4) {
    // then it gets its own block in the arena
    assert(align <= kDefaultAlignment);   // because that's what new gives us
    // This block stays separate from the rest of the world; in particular
    // we don't update last_alloc_ so you can't reclaim space on this block.
    return AllocNewBlock(size)->mem;
  }

  const int overage =
    (reinterpret_cast<uintptr_t>(freestart_) & (align-1));
  if (overage) {
    const int waste = align - overage;
    freestart_ += waste;
    if (waste < remaining_) {
      remaining_ -= waste;
    } else {
      remaining_ = 0;
    }
  }
  if (size > remaining_) {
    AllocatedBlock *block = AllocNewBlock(block_size_);
    freestart_ = block->mem;
    remaining_ = block->size;
  }
  remaining_ -= size;
  last_alloc_ = freestart_;
  freestart_ += size;
  assert((reinterpret_cast<uintptr_t>(last_alloc_) & (align-1)) == 0);
  return reinterpret_cast<void*>(last_alloc_);
}

// ----------------------------------------------------------------------
// UnsafeArena::FreeBlocks()
//    Unlike GetMemory(), which does actual work, ReturnMemory() is a
//    no-op: we don't "free" memory until Reset() is called.  We do
//    update some stats, though.  Note we do no checking that the
//    pointer you pass in was actually allocated by us, or that it
//    was allocated for the size you say, so be careful here!
//       FreeBlocks() does the work for Reset(), actually freeing all
//    memory allocated in one fell swoop.
// ----------------------------------------------------------------------

void UnsafeArena::FreeBlocks() {
  for ( int i = 1; i < blocks_alloced_; ++i ) {  // keep first block alloced
    free(first_blocks_[i].mem);
    first_blocks_[i].mem = NULL;
    first_blocks_[i].size = 0;
  }
  blocks_alloced_ = 1;
  if (overflow_blocks_ != NULL) {
    vector<AllocatedBlock>::iterator it;
    for (it = overflow_blocks_->begin(); it != overflow_blocks_->end(); ++it) {
      free(it->mem);
    }
    delete overflow_blocks_;             // These should be used very rarely
    overflow_blocks_ = NULL;
  }
}

}  // namespace re2