Index: lib/esan/esan_hashtable.h =================================================================== --- lib/esan/esan_hashtable.h +++ lib/esan/esan_hashtable.h @@ -46,20 +46,54 @@ // If the table is internally-synchronized, this lock must not be held // while a hashtable function is called as it will deadlock: the lock // is not recursive. This is meant for use with externally-synchronized - // tables. + // tables or with an iterator. void lock(); void unlock(); private: - void resize(); - static size_t hashDefault(KeyTy Key); - struct HashEntry { KeyTy Key; DataTy Payload; HashEntry *Next; }; + public: + struct HashPair { + HashPair(KeyTy Key, DataTy Data): Key(Key), Data(Data) {} + KeyTy Key; + DataTy Data; + }; + + // This iterator does not perform any synchronization. + // It expects the caller to lock the table across the whole iteration. + // Calling HashTable functions while using the iterator is not supported. + // The iterator returns copies of the keys and data. + class iterator { + public: + iterator(HashTable* Table); + iterator(const iterator& Src) = default; + iterator& operator=(const iterator& Src) = default; + HashPair operator*(); + iterator& operator++(); + iterator& operator++(int); + bool operator==(const iterator& Cmp) const; + bool operator!=(const iterator& Cmp) const; + private: + iterator(HashTable* Table, int Idx); + friend HashTable; + HashTable* Table; + int Idx; + HashTable::HashEntry *Entry; + }; + + // No erase or insert iterator supported + iterator begin(); + iterator end(); + + private: + void resize(); + static size_t hashDefault(KeyTy Key); + HashEntry **Table; u32 Capacity; u32 Entries; @@ -240,4 +274,75 @@ Mutex.Unlock(); } +//===----------------------------------------------------------------------===// +// Iterator implementation +//===----------------------------------------------------------------------===// + +template +HashTable::iterator::iterator(HashTable* Table) : + Table(Table), Idx(-1), Entry(nullptr) { + operator++(); +} + +template +HashTable::iterator::iterator(HashTable* Table, + int Idx) : + Table(Table), Idx(Idx), Entry(nullptr) +{ + CHECK(Idx >= (int)Table->Capacity); // Only used to create end(). +} + +template +typename HashTable::HashPair +HashTable::iterator::operator*() { + CHECK(Idx >= 0 && Idx < (int)Table->Capacity); + CHECK(Entry != nullptr); + return HashPair(Entry->Key, Entry->Payload); +} + +template +typename HashTable::iterator& +HashTable::iterator::operator++() { + if (Entry != nullptr) + Entry = Entry->Next; + while (Entry == nullptr) { + ++Idx; + if (Idx >= (int)Table->Capacity) + break; // At end(). + Entry = Table->Table[Idx]; + } + return *this; +} + +template +typename HashTable::iterator& +HashTable::iterator::operator++(int) { + iterator Temp(*this); + operator++(); + return Temp; +} + +template +bool HashTable::iterator::operator==(const iterator& Cmp) const { + return Cmp.Table == Table && Cmp.Idx == Idx && Cmp.Entry == Entry; +} + +template +bool HashTable::iterator::operator!=(const iterator& Cmp) const { + return Cmp.Table != Table || Cmp.Idx != Idx || Cmp.Entry != Entry; +} + +template +typename HashTable::iterator +HashTable::begin() { + return iterator(this); +} + +template +typename HashTable::iterator +HashTable::end() { + return iterator(this, Capacity); +} + + } // namespace __esan Index: test/esan/Unit/hashtable.cpp =================================================================== --- test/esan/Unit/hashtable.cpp +++ test/esan/Unit/hashtable.cpp @@ -30,6 +30,15 @@ { __esan::HashTable IntTable; assert(IntTable.size() == 0); + + // Test iteration on an empty table. + int Count = 0; + for (auto Iter = IntTable.begin(); Iter != IntTable.end(); + ++Iter, ++Count) { + // Empty. + } + assert(Count == 0); + bool Added = IntTable.add(4, 42); assert(Added); assert(!IntTable.add(4, 42)); @@ -37,9 +46,21 @@ int Value; bool Found = IntTable.lookup(4, Value); assert(Found && Value == 42); + + // Test iterator. + IntTable.lock(); + for (auto Iter = IntTable.begin(); Iter != IntTable.end(); + ++Iter, ++Count) { + assert((*Iter).Key == 4); + assert((*Iter).Data == 42); + } + IntTable.unlock(); + assert(Count == 1); + assert(Count == IntTable.size()); assert(!IntTable.remove(5)); assert(IntTable.remove(4)); + // Test a more complex payload. __esan::HashTable DataTable(4, 50, false, freeData); MyData *NewData = new MyData("mystring"); Added = DataTable.add(4, NewData); @@ -60,6 +81,30 @@ Found = DataTable.lookup(i+1, FoundData); assert(Found && strcmp(FoundData->Buf, "delete-at-end") == 0); } + DataTable.lock(); + Count = 0; + for (auto Iter = DataTable.begin(); Iter != DataTable.end(); + ++Iter, ++Count) { + int Key = (*Iter).Key; + FoundData = (*Iter).Data; + assert(Key >= 1 && Key <= 4); + assert(strcmp(FoundData->Buf, "delete-at-end") == 0); + } + DataTable.unlock(); + assert(Count == 4); + assert(Count == DataTable.size()); + + // Ensure the iterator supports a range-based for loop. + DataTable.lock(); + Count = 0; + for (auto Pair : DataTable) { + assert(Pair.Key >= 1 && Pair.Key <= 4); + assert(strcmp(Pair.Data->Buf, "delete-at-end") == 0); + ++Count; + } + DataTable.unlock(); + assert(Count == 4); + assert(Count == DataTable.size()); fprintf(stderr, "All checks passed.\n"); return 0;