jitcraft/HashTable.h
author David Anderson <dvander@alliedmods.net>
Tue Jul 03 00:21:37 2012 -0700 (2012-07-03)
changeset 29 c0e27b4f0672
parent 0 c455ebb9a5dc
child 54 8e22792a3921
permissions -rw-r--r--
Added RootScopes for AST roots.
     1 // vim: set ts=4 sw=4 tw=99 et:
     2 //
     3 //  
     4 // (C) Copyright 2010-2011 AlliedModders LLC
     5 //
     6 // Initial Author: David Anderson
     7 // Contributors:
     8 //
     9 //
    10 
    11 #ifndef _INCLUDE_KEIMA_HASHTABLE_H_
    12 #define _INCLUDE_KEIMA_HASHTABLE_H_
    13 
    14 #include <new>
    15 #include <limits.h>
    16 #include "Object.h"
    17 #include "vm/Array.h"
    18 #include "Environment.h"
    19 
    20 namespace ke {
    21 
    22 template <typename T>
    23 class HashTableEntry
    24 {
    25     uint32 hash_;
    26     T t_;
    27 
    28   public:
    29     static const uint32 kFreeHash = 0;
    30     static const uint32 kRemovedHash = 1;
    31 
    32   public:
    33     void setHash(uint32 hash) {
    34         hash_ = hash;
    35     }
    36     uint32 hash() const {
    37         return hash_;
    38     }
    39     void setRemoved() {
    40         hash_ = kRemovedHash;
    41         destruct();
    42     }
    43     void construct() {
    44         new (&t_) T();
    45     }
    46     void destruct() {
    47         t_.~T();
    48     }
    49     bool removed() const {
    50         return hash_ == kRemovedHash;
    51     }
    52     bool free() const {
    53         return hash_ == kFreeHash;
    54     }
    55     bool isLive() const {
    56         return hash_ > kRemovedHash;
    57     }
    58     T &payload() {
    59         return t_;
    60     }
    61     bool sameHash(uint32 hash) const {
    62         return hash_ == hash;
    63     }
    64 };
    65 
    66 template <typename HashPolicy, typename AllocPolicy>
    67 class HashTable : public AllocPolicy
    68 {
    69     typedef typename HashPolicy::Payload Payload;
    70     typedef HashTableEntry<Payload> Entry;
    71 
    72     uint32 capacity_;
    73     uint32 nelements_;
    74     uint32 ndeleted_;
    75     Entry *table_;
    76 
    77   private:
    78     static const uint32 kMinCapacity = 16;
    79     static const uint32 kMaxCapacity = INT_MAX / sizeof(Entry);
    80 
    81     template <typename Key>
    82     uint32 computeHash(const Key &key) {
    83         // Multiply by golden ratio.
    84         uint32 hash = HashPolicy::hash(key) * 0x9E3779B9;
    85         if (hash == Entry::kFreeHash || hash == Entry::kRemovedHash)
    86             hash += 2;
    87         return hash;
    88     }
    89 
    90     Entry *createTable(uint32 capacity) {
    91         KE_ASSERT(capacity <= kMaxCapacity);
    92 
    93         Entry *table = (Entry *)this->malloc(capacity * sizeof(Entry));
    94         if (!table)
    95             return NULL;
    96 
    97         for (size_t i = 0; i < capacity; i++)
    98             table[i].construct();
    99 
   100         return table;
   101     }
   102 
   103   public:
   104     class Result
   105     {
   106         friend class HashTable;
   107 
   108         Entry *entry_;
   109 
   110         Entry &entry() {
   111             return *entry_;
   112         }
   113 
   114       public:
   115         Result(Entry *entry)
   116           : entry_(entry)
   117         { }
   118 
   119         Payload * operator ->() {
   120             return entry_->payload();
   121         }
   122         Payload & operator *() {
   123             return entry_->payload();
   124         }
   125 
   126         bool found() const {
   127             return entry_->isLive();
   128         }
   129     };
   130 
   131     class Insert : public Result
   132     {
   133         uint32 hash_;
   134 
   135       public:
   136         Insert(Entry *entry, uint32 hash)
   137           : Result(entry),
   138             hash_(hash)
   139         {
   140         }
   141 
   142         uint32 hash() const {
   143             return hash_;
   144         }
   145     };
   146 
   147   private:
   148     class Probulator {
   149         uint32 hash_;
   150         uint32 capacity_;
   151 
   152       public:
   153         Probulator(uint32 hash, uint32 capacity)
   154           : hash_(hash),
   155             capacity_(capacity)
   156         {
   157             KE_ASSERT(IsPowerOfTwo(capacity_)); 
   158         }
   159 
   160         uint32 entry() const {
   161             return hash_ & (capacity_ - 1);
   162         }
   163         uint32 next() {
   164             hash_++;
   165             return entry();
   166         }
   167     };
   168 
   169     bool underloaded() const {
   170         // Check if the table is underloaded: < 25% entries used.
   171         return (capacity_ > kMinCapacity) && (nelements_ + ndeleted_ < capacity_ / 4);
   172     }
   173     bool overloaded() const {
   174         // Grow if the table is overloaded: > 75% entries used.
   175         return (nelements_ + ndeleted_) > ((capacity_ / 2) + (capacity_ / 4));
   176     }
   177 
   178     bool shrink() {
   179         return changeCapacity(capacity_ >> 1);
   180     }
   181     
   182     bool grow() {
   183         KE_ASSERT(IsPowerOfTwo(kMaxCapacity));
   184         if (capacity_ >= kMaxCapacity) {
   185             this->reportAllocationOverflow();
   186             return false;
   187         }
   188         return changeCapacity(capacity_ << 1);
   189     }
   190 
   191     bool checkDensity() {
   192         if (underloaded())
   193             return shrink(); 
   194         if (overloaded())
   195             return grow();
   196         return true;
   197     }
   198 
   199     bool changeCapacity(uint32 newCapacity) {
   200         KE_ASSERT(newCapacity <= kMaxCapacity);
   201 
   202         Entry *newTable = createTable(newCapacity);
   203         if (!newTable)
   204             return false;
   205 
   206         Entry *oldTable = table_;
   207         uint32 oldCapacity = capacity_;
   208 
   209         table_ = newTable;
   210         capacity_ = newCapacity;
   211         ndeleted_ = 0;
   212 
   213         for (uint32 i = 0; i < oldCapacity; i++) {
   214             Entry &oldEntry = oldTable[i];
   215             if (oldEntry.isLive()) {
   216                 Insert p = insertUnique(oldEntry.hash());
   217                 p.entry().setHash(p.hash());
   218                 p.entry().payload() = oldEntry.payload();
   219             }
   220             oldEntry.destruct();
   221         }
   222         this->free(table_);
   223 
   224         return true;
   225     }
   226 
   227     // For use when the key is known to be unique.
   228     Insert insertUnique(uint32 hash) {
   229         Probulator probulator(hash, capacity_);
   230 
   231         Entry *e = &table_[probulator.entry()];
   232         for (;;) {
   233             if (e->free() || e->removed())
   234                 break;
   235             e = &table_[probulator.next()];
   236         }
   237 
   238         return Insert(e, hash);
   239     }
   240 
   241     template <typename Key>
   242     Result lookup(const Key &key) {
   243         uint32 hash = computeHash(key);
   244         Probulator probulator(hash, capacity_);
   245 
   246         Entry *e = &table_[probulator.entry()];
   247         for (;;) {
   248             if (e->free())
   249                 break;
   250             if (e->isLive() &&
   251                 e->sameHash(hash) &&
   252                 HashPolicy::matches(key, e->payload()))
   253             {
   254                 return Result(e);
   255             }
   256             e = &table_[probulator.next()];
   257         }
   258 
   259         return Result(e);
   260     }
   261 
   262     template <typename Key>
   263     Insert lookupForAdd(const Key &key) {
   264         uint32 hash = computeHash(key);
   265         Probulator probulator(hash, capacity_);
   266 
   267         Entry *e = &table_[probulator.entry()];
   268         for (;;) {
   269             if (!e->isLive())
   270                 break;
   271             if (e->sameHash(hash) && HashPolicy::matches(key, e->payload()))
   272                 break;
   273             e = &table_[probulator.next()];
   274         }
   275 
   276         return Insert(e, hash);
   277     }
   278 
   279     bool add(Insert &i) {
   280         KE_ASSERT(!i.found());
   281 
   282         // If the entry is deleted, just re-use the slot.
   283         if (i.entry().removed()) {
   284             ndeleted_--;
   285             return true;
   286         }
   287 
   288         // Otherwise, see if we're at max capacity.
   289         if (nelements_ == kMaxCapacity) {
   290             this->reportAllocationOverflow();
   291             return false;
   292         }
   293 
   294         // Check if the table is over or underloaded. The table is always at
   295         // least 25% free, so this check is enough to guarantee one free slot.
   296         // (Without one free slot, insertion search could infinite loop.)
   297         uint32 oldCapacity = capacity_;
   298         if (!checkDensity())
   299             return false;
   300 
   301         // If the table changed size, we need to find a new insertion point.
   302         // Note that a removed entry is impossible: either we caught it above,
   303         // or we just resized and no entries are removed.
   304         if (capacity_ != oldCapacity)
   305             i = insertUnique(i.hash());
   306 
   307         i.entry().setHash(i.hash());
   308         nelements_++;
   309 
   310         return true;
   311     }
   312 
   313   public:
   314     HashTable(AllocPolicy ap)
   315       : AllocPolicy(ap),
   316         capacity_(0),
   317         nelements_(0),
   318         ndeleted_(0),
   319         table_(NULL)
   320     {
   321     }
   322 
   323     ~HashTable()
   324     {
   325         for (uint32 i = 0; i < capacity_; i++)
   326             table_[i].destruct();
   327         this->free(table_);
   328     }
   329 
   330     bool init(uint32 capacity = 0) {
   331         if (capacity < kMinCapacity)
   332             capacity = kMinCapacity;
   333         else if (capacity > kMaxCapacity)
   334             capacity = kMaxCapacity;
   335 
   336         KE_ASSERT(IsPowerOfTwo(capacity));
   337         capacity_ = capacity;
   338 
   339         table_ = createTable(capacity_);
   340         if (!table_)
   341             return false;
   342 
   343         return true;
   344     }
   345 
   346     template <typename Key>
   347     Result find(const Key &key) {
   348         return lookup(key);
   349     }
   350 
   351     template <typename Key>
   352     Insert findForAdd(const Key &key) {
   353         return lookupForAdd(key);
   354     }
   355 
   356     bool add(Insert &i, const Payload &payload) {
   357         if (!add(i))
   358             return false;
   359         *i = payload;
   360         return true;
   361     }
   362 };
   363 
   364 }
   365 
   366 #endif // _INCLUDE_KEIMA_HASHTABLE_H_
   367