Convert Name and StringPool to a GC-safe symbol table.
2 * Copyright (C) 2012 David Anderson
4 * This file is part of JITCraft.
6 * JITCraft is free software: you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free
8 * Software Foundation, either version 3 of the License, or (at your option)
11 * Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * JITCraft. If not, see http://www.gnu.org/licenses/.
18 #ifndef _INCLUDE_KEIMA_HASHTABLE_H_
19 #define _INCLUDE_KEIMA_HASHTABLE_H_
34 static const uint32 kFreeHash = 0;
35 static const uint32 kRemovedHash = 1;
38 void setHash(uint32 hash) {
55 bool removed() const {
56 return hash_ == kRemovedHash;
59 return hash_ == kFreeHash;
62 return hash_ > kRemovedHash;
67 bool sameHash(uint32 hash) const {
72 template <typename HashPolicy, typename AllocPolicy>
73 class HashTable : public AllocPolicy
75 friend class iterator;
77 typedef typename HashPolicy::Payload Payload;
78 typedef HashTableEntry<Payload> Entry;
87 static const uint32 kMinCapacity = 16;
88 static const uint32 kMaxCapacity = INT_MAX / sizeof(Entry);
90 template <typename Key>
91 uint32 computeHash(const Key &key) {
92 // Multiply by golden ratio.
93 uint32 hash = HashPolicy::hash(key) * 0x9E3779B9;
94 if (hash == Entry::kFreeHash || hash == Entry::kRemovedHash)
99 Entry *createTable(uint32 capacity) {
100 assert(capacity <= kMaxCapacity);
102 Entry *table = (Entry *)this->malloc(capacity * sizeof(Entry));
106 for (size_t i = 0; i < capacity; i++)
107 table[i].construct();
115 friend class HashTable;
128 Payload * operator ->() {
129 return entry_->payload();
131 Payload & operator *() {
132 return entry_->payload();
136 return entry_->isLive();
140 class Insert : public Result
145 Insert(Entry *entry, uint32 hash)
151 uint32 hash() const {
162 Probulator(uint32 hash, uint32 capacity)
166 assert(IsPowerOfTwo(capacity_));
169 uint32 entry() const {
170 return hash_ & (capacity_ - 1);
178 bool underloaded() const {
179 // Check if the table is underloaded: < 25% entries used.
180 return (capacity_ > kMinCapacity) && (nelements_ + ndeleted_ < capacity_ / 4);
182 bool overloaded() const {
183 // Grow if the table is overloaded: > 75% entries used.
184 return (nelements_ + ndeleted_) > ((capacity_ / 2) + (capacity_ / 4));
188 if ((capacity_ >> 1) < minCapacity_)
190 return changeCapacity(capacity_ >> 1);
194 assert(IsPowerOfTwo(kMaxCapacity));
195 if (capacity_ >= kMaxCapacity) {
196 this->reportAllocationOverflow();
199 return changeCapacity(capacity_ << 1);
202 bool changeCapacity(uint32 newCapacity) {
203 assert(newCapacity <= kMaxCapacity);
205 Entry *newTable = createTable(newCapacity);
209 Entry *oldTable = table_;
210 uint32 oldCapacity = capacity_;
213 capacity_ = newCapacity;
216 for (uint32 i = 0; i < oldCapacity; i++) {
217 Entry &oldEntry = oldTable[i];
218 if (oldEntry.isLive()) {
219 Insert p = insertUnique(oldEntry.hash());
220 p.entry().setHash(p.hash());
221 p.entry().payload() = oldEntry.payload();
225 this->free(oldTable);
230 // For use when the key is known to be unique.
231 Insert insertUnique(uint32 hash) {
232 Probulator probulator(hash, capacity_);
234 Entry *e = &table_[probulator.entry()];
236 if (e->free() || e->removed())
238 e = &table_[probulator.next()];
241 return Insert(e, hash);
244 template <typename Key>
245 Result lookup(const Key &key) {
246 uint32 hash = computeHash(key);
247 Probulator probulator(hash, capacity_);
249 Entry *e = &table_[probulator.entry()];
255 HashPolicy::matches(key, e->payload()))
259 e = &table_[probulator.next()];
265 template <typename Key>
266 Insert lookupForAdd(const Key &key) {
267 uint32 hash = computeHash(key);
268 Probulator probulator(hash, capacity_);
270 Entry *e = &table_[probulator.entry()];
274 if (e->sameHash(hash) && HashPolicy::matches(key, e->payload()))
276 e = &table_[probulator.next()];
279 return Insert(e, hash);
282 bool add(Insert &i) {
285 // If the entry is deleted, just re-use the slot.
286 if (i.entry().removed()) {
291 // Otherwise, see if we're at max capacity.
292 if (nelements_ == kMaxCapacity) {
293 this->reportAllocationOverflow();
297 // Check if the table is over or underloaded. The table is always at
298 // least 25% free, so this check is enough to guarantee one free slot.
299 // (Without one free slot, insertion search could infinite loop.)
300 uint32 oldCapacity = capacity_;
304 // If the table changed size, we need to find a new insertion point.
305 // Note that a removed entry is impossible: either we caught it above,
306 // or we just resized and no entries are removed.
307 if (capacity_ != oldCapacity)
308 i = insertUnique(i.hash());
310 i.entry().setHash(i.hash());
316 void remove(Entry &e) {
324 HashTable(AllocPolicy ap)
330 minCapacity_(kMinCapacity)
336 for (uint32 i = 0; i < capacity_; i++)
337 table_[i].destruct();
341 bool init(uint32 capacity = 0) {
342 if (capacity < kMinCapacity)
343 capacity = kMinCapacity;
344 else if (capacity > kMaxCapacity)
345 capacity = kMaxCapacity;
347 minCapacity_ = capacity;
349 assert(IsPowerOfTwo(capacity));
350 capacity_ = capacity;
352 table_ = createTable(capacity_);
359 template <typename Key>
360 Result find(const Key &key) {
364 template <typename Key>
365 Insert findForAdd(const Key &key) {
366 return lookupForAdd(key);
369 bool add(Insert &i, const Payload &payload) {
376 bool checkDensity() {
392 iterator(HashTable *table)
395 end_(table->table_ + table->capacity_)
397 while (i_ < end_ && !i_->isLive())
410 const Payload &operator *() const {
411 return i_->payload();
415 while (i_ < end_ && !i_->isLive())
423 #endif // _INCLUDE_KEIMA_HASHTABLE_H_