Re-add LIFO stack optimizations.
1 /* vim: set ts=4 sw=4 tw=99 et:
3 * Copyright (C) 2012 David Anderson
5 * This file is part of SourcePawn.
7 * SourcePawn is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License as published by the Free
9 * Software Foundation, either version 3 of the License, or (at your option)
12 * SourcePawn is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along with
17 * SourcePawn. If not, see http://www.gnu.org/licenses/.
19 #ifndef _INCLUDE_KEIMA_HASHTABLE_H_
20 #define _INCLUDE_KEIMA_HASHTABLE_H_
35 static const uint32 kFreeHash = 0;
36 static const uint32 kRemovedHash = 1;
39 void setHash(uint32 hash) {
56 bool removed() const {
57 return hash_ == kRemovedHash;
60 return hash_ == kFreeHash;
63 return hash_ > kRemovedHash;
68 bool sameHash(uint32 hash) const {
73 template <typename HashPolicy, typename AllocPolicy>
74 class HashTable : public AllocPolicy
76 friend class iterator;
78 typedef typename HashPolicy::Payload Payload;
79 typedef HashTableEntry<Payload> Entry;
88 static const uint32 kMinCapacity = 16;
89 static const uint32 kMaxCapacity = INT_MAX / sizeof(Entry);
91 template <typename Key>
92 uint32 computeHash(const Key &key) {
93 // Multiply by golden ratio.
94 uint32 hash = HashPolicy::hash(key) * 0x9E3779B9;
95 if (hash == Entry::kFreeHash || hash == Entry::kRemovedHash)
100 Entry *createTable(uint32 capacity) {
101 assert(capacity <= kMaxCapacity);
103 Entry *table = (Entry *)this->malloc(capacity * sizeof(Entry));
107 for (size_t i = 0; i < capacity; i++)
108 table[i].construct();
116 friend class HashTable;
129 Payload * operator ->() {
130 return &entry_->payload();
132 Payload & operator *() {
133 return entry_->payload();
137 return entry_->isLive();
141 class Insert : public Result
146 Insert(Entry *entry, uint32 hash)
152 uint32 hash() const {
163 Probulator(uint32 hash, uint32 capacity)
167 assert(IsPowerOfTwo(capacity_));
170 uint32 entry() const {
171 return hash_ & (capacity_ - 1);
179 bool underloaded() const {
180 // Check if the table is underloaded: < 25% entries used.
181 return (capacity_ > kMinCapacity) && (nelements_ + ndeleted_ < capacity_ / 4);
183 bool overloaded() const {
184 // Grow if the table is overloaded: > 75% entries used.
185 return (nelements_ + ndeleted_) > ((capacity_ / 2) + (capacity_ / 4));
189 if ((capacity_ >> 1) < minCapacity_)
191 return changeCapacity(capacity_ >> 1);
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 this->reportAllocationOverflow();
349 minCapacity_ = capacity;
351 assert(IsPowerOfTwo(capacity));
352 capacity_ = capacity;
354 table_ = createTable(capacity_);
361 template <typename Key>
362 Result find(const Key &key) {
366 template <typename Key>
367 Insert findForAdd(const Key &key) {
368 return lookupForAdd(key);
371 bool add(Insert &i, const Payload &payload) {
378 bool checkDensity() {
394 iterator(HashTable *table)
397 end_(table->table_ + table->capacity_)
399 while (i_ < end_ && !i_->isLive())
412 const Payload &operator *() const {
413 return i_->payload();
419 } while (i_ < end_ && !i_->isLive());
428 #endif // _INCLUDE_KEIMA_HASHTABLE_H_