/* * Copyright (C) 2005, 2006, 2007, 2008, 2011, 2012 Apple Inc. All rights reserved. * Copyright (C) 2008 David Levin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef WTF_HashTable_h #define WTF_HashTable_h #include "wtf/Alignment.h" #include "wtf/Assertions.h" #include "wtf/DefaultAllocator.h" #include "wtf/HashTraits.h" #include "wtf/WTF.h" #define DUMP_HASHTABLE_STATS 0 #define DUMP_HASHTABLE_STATS_PER_TABLE 0 #if DUMP_HASHTABLE_STATS_PER_TABLE #include "wtf/DataLog.h" #endif #if DUMP_HASHTABLE_STATS #if DUMP_HASHTABLE_STATS_PER_TABLE #define UPDATE_PROBE_COUNTS() \ ++probeCount; \ HashTableStats::recordCollisionAtCount(probeCount); \ ++perTableProbeCount; \ m_stats->recordCollisionAtCount(perTableProbeCount) #define UPDATE_ACCESS_COUNTS() \ atomicIncrement(&HashTableStats::numAccesses); \ int probeCount = 0; \ ++m_stats->numAccesses; \ int perTableProbeCount = 0 #else #define UPDATE_PROBE_COUNTS() \ ++probeCount; \ HashTableStats::recordCollisionAtCount(probeCount) #define UPDATE_ACCESS_COUNTS() \ atomicIncrement(&HashTableStats::numAccesses); \ int probeCount = 0 #endif #else #if DUMP_HASHTABLE_STATS_PER_TABLE #define UPDATE_PROBE_COUNTS() \ ++perTableProbeCount; \ m_stats->recordCollisionAtCount(perTableProbeCount) #define UPDATE_ACCESS_COUNTS() \ ++m_stats->numAccesses; \ int perTableProbeCount = 0 #else #define UPDATE_PROBE_COUNTS() do { } while (0) #define UPDATE_ACCESS_COUNTS() do { } while (0) #endif #endif namespace WTF { #if DUMP_HASHTABLE_STATS struct HashTableStats { // The following variables are all atomically incremented when modified. static int numAccesses; static int numRehashes; static int numRemoves; static int numReinserts; // The following variables are only modified in the recordCollisionAtCount method within a mutex. static int maxCollisions; static int numCollisions; static int collisionGraph[4096]; static void recordCollisionAtCount(int count); static void dumpStats(); }; #endif template class HashTable; template class HashTableIterator; template class HashTableConstIterator; template class LinkedHashSet; template struct WeakProcessingHashTableHelper; typedef enum { HashItemKnownGood } HashItemKnownGoodTag; template class HashTableConstIterator { private: typedef HashTable HashTableType; typedef HashTableIterator iterator; typedef HashTableConstIterator const_iterator; typedef Value ValueType; typedef typename Traits::IteratorConstGetType GetType; typedef const ValueType* PointerType; friend class HashTable; friend class HashTableIterator; void skipEmptyBuckets() { while (m_position != m_endPosition && HashTableType::isEmptyOrDeletedBucket(*m_position)) ++m_position; } HashTableConstIterator(PointerType position, PointerType endPosition, const HashTableType* container) : m_position(position) , m_endPosition(endPosition) #ifdef ASSERT_ENABLED , m_container(container) , m_containerModifications(container->modifications()) #endif { skipEmptyBuckets(); } HashTableConstIterator(PointerType position, PointerType endPosition, const HashTableType* container, HashItemKnownGoodTag) : m_position(position) , m_endPosition(endPosition) #ifdef ASSERT_ENABLED , m_container(container) , m_containerModifications(container->modifications()) #endif { ASSERT(m_containerModifications == m_container->modifications()); } void checkModifications() const { // HashTable and collections that build on it do not support // modifications while there is an iterator in use. The exception // is ListHashSet, which has its own iterators that tolerate // modification of the underlying set. ASSERT(m_containerModifications == m_container->modifications()); } public: HashTableConstIterator() { } GetType get() const { checkModifications(); return m_position; } typename Traits::IteratorConstReferenceType operator*() const { return Traits::getToReferenceConstConversion(get()); } GetType operator->() const { return get(); } const_iterator& operator++() { ASSERT(m_position != m_endPosition); checkModifications(); ++m_position; skipEmptyBuckets(); return *this; } // postfix ++ intentionally omitted // Comparison. bool operator==(const const_iterator& other) const { return m_position == other.m_position; } bool operator!=(const const_iterator& other) const { return m_position != other.m_position; } bool operator==(const iterator& other) const { return *this == static_cast(other); } bool operator!=(const iterator& other) const { return *this != static_cast(other); } private: PointerType m_position; PointerType m_endPosition; #ifdef ASSERT_ENABLED const HashTableType* m_container; int64_t m_containerModifications; #endif }; template class HashTableIterator { private: typedef HashTable HashTableType; typedef HashTableIterator iterator; typedef HashTableConstIterator const_iterator; typedef Value ValueType; typedef typename Traits::IteratorGetType GetType; typedef ValueType* PointerType; friend class HashTable; HashTableIterator(PointerType pos, PointerType end, const HashTableType* container) : m_iterator(pos, end, container) { } HashTableIterator(PointerType pos, PointerType end, const HashTableType* container, HashItemKnownGoodTag tag) : m_iterator(pos, end, container, tag) { } public: HashTableIterator() { } // default copy, assignment and destructor are OK GetType get() const { return const_cast(m_iterator.get()); } typename Traits::IteratorReferenceType operator*() const { return Traits::getToReferenceConversion(get()); } GetType operator->() const { return get(); } iterator& operator++() { ++m_iterator; return *this; } // postfix ++ intentionally omitted // Comparison. bool operator==(const iterator& other) const { return m_iterator == other.m_iterator; } bool operator!=(const iterator& other) const { return m_iterator != other.m_iterator; } bool operator==(const const_iterator& other) const { return m_iterator == other; } bool operator!=(const const_iterator& other) const { return m_iterator != other; } operator const_iterator() const { return m_iterator; } private: const_iterator m_iterator; }; using std::swap; // Work around MSVC's standard library, whose swap for pairs does not swap by component. template inline void hashTableSwap(T& a, T& b) { swap(a, b); } template inline void hashTableSwap(KeyValuePair& a, KeyValuePair& b) { swap(a.key, b.key); swap(a.value, b.value); } template struct Mover; template struct Mover { static void move(T& from, T& to) { hashTableSwap(from, to); } }; template struct Mover { static void move(T& from, T& to) { to = from; } }; template class IdentityHashTranslator { public: template static unsigned hash(const T& key) { return HashFunctions::hash(key); } template static bool equal(const T& a, const U& b) { return HashFunctions::equal(a, b); } template static void translate(T& location, const U&, const V& value) { location = value; } }; template struct HashTableAddResult { HashTableAddResult(const HashTableType* container, ValueType* storedValue, bool isNewEntry) : storedValue(storedValue) , isNewEntry(isNewEntry) #if SECURITY_ASSERT_ENABLED , m_container(container) , m_containerModifications(container->modifications()) #endif { ASSERT_UNUSED(container, container); } ~HashTableAddResult() { // If rehash happened before accessing storedValue, it's // use-after-free. Any modification may cause a rehash, so we check // for modifications here. // Rehash after accessing storedValue is harmless but will assert if // the AddResult destructor takes place after a modification. You // may need to limit the scope of the AddResult. ASSERT_WITH_SECURITY_IMPLICATION(m_containerModifications == m_container->modifications()); } ValueType* storedValue; bool isNewEntry; #if SECURITY_ASSERT_ENABLED private: const HashTableType* m_container; const int64_t m_containerModifications; #endif }; template struct HashTableHelper { static bool isEmptyBucket(const Value& value) { return isHashTraitsEmptyValue(Extractor::extract(value)); } static bool isDeletedBucket(const Value& value) { return KeyTraits::isDeletedValue(Extractor::extract(value)); } static bool isEmptyOrDeletedBucket(const Value& value) { return isEmptyBucket(value) || isDeletedBucket(value); } }; template struct HashTableKeyChecker { // There's no simple generic way to make this check if safeToCompareToEmptyOrDeleted is false, // so the check always passes. template static bool checkKey(const T&) { return true; } }; template struct HashTableKeyChecker { template static bool checkKey(const T& key) { // FIXME : Check also equality to the deleted value. return !HashTranslator::equal(KeyTraits::emptyValue(), key); } }; // Don't declare a destructor for HeapAllocated hash tables. template class HashTableDestructorBase; template class HashTableDestructorBase { }; template class HashTableDestructorBase { public: ~HashTableDestructorBase() { static_cast(this)->finalize(); } }; // Note: empty or deleted key values are not allowed, using them may lead to undefined behavior. // For pointer keys this means that null pointers are not allowed unless you supply custom key traits. template class HashTable : public HashTableDestructorBase, Allocator::isGarbageCollected> { public: typedef HashTableIterator iterator; typedef HashTableConstIterator const_iterator; typedef Traits ValueTraits; typedef Key KeyType; typedef typename KeyTraits::PeekInType KeyPeekInType; typedef typename KeyTraits::PassInType KeyPassInType; typedef Value ValueType; typedef Extractor ExtractorType; typedef KeyTraits KeyTraitsType; typedef typename Traits::PassInType ValuePassInType; typedef IdentityHashTranslator IdentityTranslatorType; typedef HashTableAddResult AddResult; #if DUMP_HASHTABLE_STATS_PER_TABLE struct Stats { Stats() : numAccesses(0) , numRehashes(0) , numRemoves(0) , numReinserts(0) , maxCollisions(0) , numCollisions(0) , collisionGraph() { } int numAccesses; int numRehashes; int numRemoves; int numReinserts; int maxCollisions; int numCollisions; int collisionGraph[4096]; void recordCollisionAtCount(int count) { if (count > maxCollisions) maxCollisions = count; numCollisions++; collisionGraph[count]++; } void dumpStats() { dataLogF("\nWTF::HashTable::Stats dump\n\n"); dataLogF("%d accesses\n", numAccesses); dataLogF("%d total collisions, average %.2f probes per access\n", numCollisions, 1.0 * (numAccesses + numCollisions) / numAccesses); dataLogF("longest collision chain: %d\n", maxCollisions); for (int i = 1; i <= maxCollisions; i++) { dataLogF(" %d lookups with exactly %d collisions (%.2f%% , %.2f%% with this many or more)\n", collisionGraph[i], i, 100.0 * (collisionGraph[i] - collisionGraph[i+1]) / numAccesses, 100.0 * collisionGraph[i] / numAccesses); } dataLogF("%d rehashes\n", numRehashes); dataLogF("%d reinserts\n", numReinserts); } }; #endif HashTable(); void finalize() { ASSERT(!Allocator::isGarbageCollected); if (LIKELY(!m_table)) return; deleteAllBucketsAndDeallocate(m_table, m_tableSize); m_table = 0; } HashTable(const HashTable&); void swap(HashTable&); HashTable& operator=(const HashTable&); // When the hash table is empty, just return the same iterator for end as for begin. // This is more efficient because we don't have to skip all the empty and deleted // buckets, and iterating an empty table is a common case that's worth optimizing. iterator begin() { return isEmpty() ? end() : makeIterator(m_table); } iterator end() { return makeKnownGoodIterator(m_table + m_tableSize); } const_iterator begin() const { return isEmpty() ? end() : makeConstIterator(m_table); } const_iterator end() const { return makeKnownGoodConstIterator(m_table + m_tableSize); } unsigned size() const { return m_keyCount; } unsigned capacity() const { return m_tableSize; } bool isEmpty() const { return !m_keyCount; } AddResult add(ValuePassInType value) { return add(Extractor::extract(value), value); } // A special version of add() that finds the object by hashing and comparing // with some other type, to avoid the cost of type conversion if the object is already // in the table. template AddResult add(const T& key, const Extra&); template AddResult addPassingHashCode(const T& key, const Extra&); iterator find(KeyPeekInType key) { return find(key); } const_iterator find(KeyPeekInType key) const { return find(key); } bool contains(KeyPeekInType key) const { return contains(key); } template iterator find(const T&); template const_iterator find(const T&) const; template bool contains(const T&) const; void remove(KeyPeekInType); void remove(iterator); void remove(const_iterator); void clear(); static bool isEmptyBucket(const ValueType& value) { return isHashTraitsEmptyValue(Extractor::extract(value)); } static bool isDeletedBucket(const ValueType& value) { return KeyTraits::isDeletedValue(Extractor::extract(value)); } static bool isEmptyOrDeletedBucket(const ValueType& value) { return HashTableHelper:: isEmptyOrDeletedBucket(value); } ValueType* lookup(KeyPeekInType key) { return lookup(key); } template ValueType* lookup(T); template const ValueType* lookup(T) const; void trace(typename Allocator::Visitor*); #ifdef ASSERT_ENABLED int64_t modifications() const { return m_modifications; } void registerModification() { m_modifications++; } // HashTable and collections that build on it do not support // modifications while there is an iterator in use. The exception is // ListHashSet, which has its own iterators that tolerate modification // of the underlying set. void checkModifications(int64_t mods) const { ASSERT(mods == m_modifications); } #else int64_t modifications() const { return 0; } void registerModification() { } void checkModifications(int64_t mods) const { } #endif private: static ValueType* allocateTable(unsigned size); static void deleteAllBucketsAndDeallocate(ValueType* table, unsigned size); typedef std::pair LookupType; typedef std::pair FullLookupType; LookupType lookupForWriting(const Key& key) { return lookupForWriting(key); }; template FullLookupType fullLookupForWriting(const T&); template LookupType lookupForWriting(const T&); void remove(ValueType*); bool shouldExpand() const { return (m_keyCount + m_deletedCount) * m_maxLoad >= m_tableSize; } bool mustRehashInPlace() const { return m_keyCount * m_minLoad < m_tableSize * 2; } bool shouldShrink() const { return m_keyCount * m_minLoad < m_tableSize && m_tableSize > KeyTraits::minimumTableSize; } ValueType* expand(ValueType* entry = 0); void shrink() { rehash(m_tableSize / 2, 0); } ValueType* rehash(unsigned newTableSize, ValueType* entry); ValueType* reinsert(ValueType&); static void initializeBucket(ValueType& bucket); static void deleteBucket(ValueType& bucket) { bucket.~ValueType(); Traits::constructDeletedValue(bucket); } FullLookupType makeLookupResult(ValueType* position, bool found, unsigned hash) { return FullLookupType(LookupType(position, found), hash); } iterator makeIterator(ValueType* pos) { return iterator(pos, m_table + m_tableSize, this); } const_iterator makeConstIterator(ValueType* pos) const { return const_iterator(pos, m_table + m_tableSize, this); } iterator makeKnownGoodIterator(ValueType* pos) { return iterator(pos, m_table + m_tableSize, this, HashItemKnownGood); } const_iterator makeKnownGoodConstIterator(ValueType* pos) const { return const_iterator(pos, m_table + m_tableSize, this, HashItemKnownGood); } static const unsigned m_maxLoad = 2; static const unsigned m_minLoad = 6; unsigned tableSizeMask() const { size_t mask = m_tableSize - 1; ASSERT((mask & m_tableSize) == 0); return mask; } ValueType* m_table; unsigned m_tableSize; unsigned m_keyCount; unsigned m_deletedCount; #ifdef ASSERT_ENABLED unsigned m_modifications; #endif #if DUMP_HASHTABLE_STATS_PER_TABLE public: mutable OwnPtr m_stats; #endif template friend struct WeakProcessingHashTableHelper; template friend class LinkedHashSet; }; // Set all the bits to one after the most significant bit: 00110101010 -> 00111111111. template struct OneifyLowBits; template<> struct OneifyLowBits<0> { static const unsigned value = 0; }; template struct OneifyLowBits { static const unsigned value = number | OneifyLowBits<(number >> 1)>::value; }; // Compute the first power of two integer that is an upper bound of the parameter 'number'. template struct UpperPowerOfTwoBound { static const unsigned value = (OneifyLowBits::value + 1) * 2; }; // Because power of two numbers are the limit of maxLoad, their capacity is twice the // UpperPowerOfTwoBound, or 4 times their values. template struct HashTableCapacityForSizeSplitter; template struct HashTableCapacityForSizeSplitter { static const unsigned value = size * 4; }; template struct HashTableCapacityForSizeSplitter { static const unsigned value = UpperPowerOfTwoBound::value; }; // HashTableCapacityForSize computes the upper power of two capacity to hold the size parameter. // This is done at compile time to initialize the HashTraits. template struct HashTableCapacityForSize { static const unsigned value = HashTableCapacityForSizeSplitter::value; COMPILE_ASSERT(size > 0, HashTableNonZeroMinimumCapacity); COMPILE_ASSERT(!static_cast(value >> 31), HashTableNoCapacityOverflow); COMPILE_ASSERT(value > (2 * size), HashTableCapacityHoldsContentSize); }; template inline HashTable::HashTable() : m_table(0) , m_tableSize(0) , m_keyCount(0) , m_deletedCount(0) #ifdef ASSERT_ENABLED , m_modifications(0) #endif #if DUMP_HASHTABLE_STATS_PER_TABLE , m_stats(adoptPtr(new Stats)) #endif { } inline unsigned doubleHash(unsigned key) { key = ~key + (key >> 23); key ^= (key << 12); key ^= (key >> 7); key ^= (key << 2); key ^= (key >> 20); return key; } template template inline Value* HashTable::lookup(T key) { return const_cast(const_cast(this)->lookup(key)); } template template inline const Value* HashTable::lookup(T key) const { ASSERT((HashTableKeyChecker::checkKey(key))); const ValueType* table = m_table; if (!table) return 0; size_t k = 0; size_t sizeMask = tableSizeMask(); unsigned h = HashTranslator::hash(key); size_t i = h & sizeMask; UPDATE_ACCESS_COUNTS(); while (1) { const ValueType* entry = table + i; if (HashFunctions::safeToCompareToEmptyOrDeleted) { if (HashTranslator::equal(Extractor::extract(*entry), key)) return entry; if (isEmptyBucket(*entry)) return 0; } else { if (isEmptyBucket(*entry)) return 0; if (!isDeletedBucket(*entry) && HashTranslator::equal(Extractor::extract(*entry), key)) return entry; } UPDATE_PROBE_COUNTS(); if (!k) k = 1 | doubleHash(h); i = (i + k) & sizeMask; } } template template inline typename HashTable::LookupType HashTable::lookupForWriting(const T& key) { ASSERT(m_table); registerModification(); ValueType* table = m_table; size_t k = 0; size_t sizeMask = tableSizeMask(); unsigned h = HashTranslator::hash(key); size_t i = h & sizeMask; UPDATE_ACCESS_COUNTS(); ValueType* deletedEntry = 0; while (1) { ValueType* entry = table + i; if (isEmptyBucket(*entry)) return LookupType(deletedEntry ? deletedEntry : entry, false); if (HashFunctions::safeToCompareToEmptyOrDeleted) { if (HashTranslator::equal(Extractor::extract(*entry), key)) return LookupType(entry, true); if (isDeletedBucket(*entry)) deletedEntry = entry; } else { if (isDeletedBucket(*entry)) deletedEntry = entry; else if (HashTranslator::equal(Extractor::extract(*entry), key)) return LookupType(entry, true); } UPDATE_PROBE_COUNTS(); if (!k) k = 1 | doubleHash(h); i = (i + k) & sizeMask; } } template template inline typename HashTable::FullLookupType HashTable::fullLookupForWriting(const T& key) { ASSERT(m_table); registerModification(); ValueType* table = m_table; size_t k = 0; size_t sizeMask = tableSizeMask(); unsigned h = HashTranslator::hash(key); size_t i = h & sizeMask; UPDATE_ACCESS_COUNTS(); ValueType* deletedEntry = 0; while (1) { ValueType* entry = table + i; if (isEmptyBucket(*entry)) return makeLookupResult(deletedEntry ? deletedEntry : entry, false, h); if (HashFunctions::safeToCompareToEmptyOrDeleted) { if (HashTranslator::equal(Extractor::extract(*entry), key)) return makeLookupResult(entry, true, h); if (isDeletedBucket(*entry)) deletedEntry = entry; } else { if (isDeletedBucket(*entry)) deletedEntry = entry; else if (HashTranslator::equal(Extractor::extract(*entry), key)) return makeLookupResult(entry, true, h); } UPDATE_PROBE_COUNTS(); if (!k) k = 1 | doubleHash(h); i = (i + k) & sizeMask; } } template struct HashTableBucketInitializer; template<> struct HashTableBucketInitializer { template static void initialize(Value& bucket) { new (NotNull, &bucket) Value(Traits::emptyValue()); } }; template<> struct HashTableBucketInitializer { template static void initialize(Value& bucket) { // This initializes the bucket without copying the empty value. // That makes it possible to use this with types that don't support copying. // The memset to 0 looks like a slow operation but is optimized by the compilers. memset(&bucket, 0, sizeof(bucket)); } }; template inline void HashTable::initializeBucket(ValueType& bucket) { HashTableBucketInitializer::template initialize(bucket); } template template typename HashTable::AddResult HashTable::add(const T& key, const Extra& extra) { if (!m_table) expand(); ASSERT(m_table); ValueType* table = m_table; size_t k = 0; size_t sizeMask = tableSizeMask(); unsigned h = HashTranslator::hash(key); size_t i = h & sizeMask; UPDATE_ACCESS_COUNTS(); ValueType* deletedEntry = 0; ValueType* entry; while (1) { entry = table + i; if (isEmptyBucket(*entry)) break; if (HashFunctions::safeToCompareToEmptyOrDeleted) { if (HashTranslator::equal(Extractor::extract(*entry), key)) return AddResult(this, entry, false); if (isDeletedBucket(*entry)) deletedEntry = entry; } else { if (isDeletedBucket(*entry)) deletedEntry = entry; else if (HashTranslator::equal(Extractor::extract(*entry), key)) return AddResult(this, entry, false); } UPDATE_PROBE_COUNTS(); if (!k) k = 1 | doubleHash(h); i = (i + k) & sizeMask; } registerModification(); if (deletedEntry) { initializeBucket(*deletedEntry); entry = deletedEntry; --m_deletedCount; } HashTranslator::translate(*entry, key, extra); ASSERT(!isEmptyOrDeletedBucket(*entry)); ++m_keyCount; if (shouldExpand()) entry = expand(entry); return AddResult(this, entry, true); } template template typename HashTable::AddResult HashTable::addPassingHashCode(const T& key, const Extra& extra) { if (!m_table) expand(); FullLookupType lookupResult = fullLookupForWriting(key); ValueType* entry = lookupResult.first.first; bool found = lookupResult.first.second; unsigned h = lookupResult.second; if (found) return AddResult(this, entry, false); registerModification(); if (isDeletedBucket(*entry)) { initializeBucket(*entry); --m_deletedCount; } HashTranslator::translate(*entry, key, extra, h); ASSERT(!isEmptyOrDeletedBucket(*entry)); ++m_keyCount; if (shouldExpand()) entry = expand(entry); return AddResult(this, entry, true); } template Value* HashTable::reinsert(ValueType& entry) { ASSERT(m_table); registerModification(); ASSERT(!lookupForWriting(Extractor::extract(entry)).second); ASSERT(!isDeletedBucket(*(lookupForWriting(Extractor::extract(entry)).first))); #if DUMP_HASHTABLE_STATS atomicIncrement(&HashTableStats::numReinserts); #endif #if DUMP_HASHTABLE_STATS_PER_TABLE ++m_stats->numReinserts; #endif Value* newEntry = lookupForWriting(Extractor::extract(entry)).first; Mover::move(entry, *newEntry); return newEntry; } template template inline typename HashTable::iterator HashTable::find(const T& key) { ValueType* entry = lookup(key); if (!entry) return end(); return makeKnownGoodIterator(entry); } template template inline typename HashTable::const_iterator HashTable::find(const T& key) const { ValueType* entry = const_cast(this)->lookup(key); if (!entry) return end(); return makeKnownGoodConstIterator(entry); } template template bool HashTable::contains(const T& key) const { return const_cast(this)->lookup(key); } template void HashTable::remove(ValueType* pos) { registerModification(); #if DUMP_HASHTABLE_STATS atomicIncrement(&HashTableStats::numRemoves); #endif #if DUMP_HASHTABLE_STATS_PER_TABLE ++m_stats->numRemoves; #endif deleteBucket(*pos); ++m_deletedCount; --m_keyCount; if (shouldShrink()) shrink(); } template inline void HashTable::remove(iterator it) { if (it == end()) return; remove(const_cast(it.m_iterator.m_position)); } template inline void HashTable::remove(const_iterator it) { if (it == end()) return; remove(const_cast(it.m_position)); } template inline void HashTable::remove(KeyPeekInType key) { remove(find(key)); } template Value* HashTable::allocateTable(unsigned size) { typedef typename Allocator::template HashTableBackingHelper::Type HashTableBacking; size_t allocSize = size * sizeof(ValueType); ValueType* result; COMPILE_ASSERT(!Traits::emptyValueIsZero || !IsPolymorphic::value, EmptyValueCannotBeZeroForThingsWithAVtable); if (Traits::emptyValueIsZero) { result = Allocator::template zeroedBackingMalloc(allocSize); } else { result = Allocator::template backingMalloc(allocSize); for (unsigned i = 0; i < size; i++) initializeBucket(result[i]); } return result; } template void HashTable::deleteAllBucketsAndDeallocate(ValueType* table, unsigned size) { if (Traits::needsDestruction) { for (unsigned i = 0; i < size; ++i) { // This code is called when the hash table is cleared or // resized. We have allocated a new backing store and we need // to run the destructors on the old backing store, as it is // being freed. If we are GCing we need to both call the // destructor and mark the bucket as deleted, otherwise the // destructor gets called again when the GC finds the backing // store. With the default allocator it's enough to call the // destructor, since we will free the memory explicitly and // we won't see the memory with the bucket again. if (!isEmptyOrDeletedBucket(table[i])) { if (Allocator::isGarbageCollected) deleteBucket(table[i]); else table[i].~ValueType(); } } } Allocator::backingFree(table); } template Value* HashTable::expand(Value* entry) { unsigned newSize; if (!m_tableSize) { newSize = KeyTraits::minimumTableSize; } else if (mustRehashInPlace()) { newSize = m_tableSize; } else { newSize = m_tableSize * 2; RELEASE_ASSERT(newSize > m_tableSize); } return rehash(newSize, entry); } template Value* HashTable::rehash(unsigned newTableSize, Value* entry) { unsigned oldTableSize = m_tableSize; ValueType* oldTable = m_table; #if DUMP_HASHTABLE_STATS if (oldTableSize != 0) atomicIncrement(&HashTableStats::numRehashes); #endif #if DUMP_HASHTABLE_STATS_PER_TABLE if (oldTableSize != 0) ++m_stats->numRehashes; #endif m_table = allocateTable(newTableSize); m_tableSize = newTableSize; Value* newEntry = 0; for (unsigned i = 0; i != oldTableSize; ++i) { if (isEmptyOrDeletedBucket(oldTable[i])) { ASSERT(&oldTable[i] != entry); continue; } Value* reinsertedEntry = reinsert(oldTable[i]); if (&oldTable[i] == entry) { ASSERT(!newEntry); newEntry = reinsertedEntry; } } m_deletedCount = 0; deleteAllBucketsAndDeallocate(oldTable, oldTableSize); return newEntry; } template void HashTable::clear() { registerModification(); if (!m_table) return; deleteAllBucketsAndDeallocate(m_table, m_tableSize); m_table = 0; m_tableSize = 0; m_keyCount = 0; } template HashTable::HashTable(const HashTable& other) : m_table(0) , m_tableSize(0) , m_keyCount(0) , m_deletedCount(0) #ifdef ASSERT_ENABLED , m_modifications(0) #endif #if DUMP_HASHTABLE_STATS_PER_TABLE , m_stats(adoptPtr(new Stats(*other.m_stats))) #endif { // Copy the hash table the dumb way, by adding each element to the new table. // It might be more efficient to copy the table slots, but it's not clear that efficiency is needed. const_iterator end = other.end(); for (const_iterator it = other.begin(); it != end; ++it) add(*it); } template void HashTable::swap(HashTable& other) { std::swap(m_table, other.m_table); std::swap(m_tableSize, other.m_tableSize); std::swap(m_keyCount, other.m_keyCount); std::swap(m_deletedCount, other.m_deletedCount); #ifdef ASSERT_ENABLED std::swap(m_modifications, other.m_modifications); #endif #if DUMP_HASHTABLE_STATS_PER_TABLE m_stats.swap(other.m_stats); #endif } template HashTable& HashTable::operator=(const HashTable& other) { HashTable tmp(other); swap(tmp); return *this; } template struct WeakProcessingHashTableHelper; template struct WeakProcessingHashTableHelper { static void process(typename Allocator::Visitor* visitor, void* closure) { } }; template struct WeakProcessingHashTableHelper { static void process(typename Allocator::Visitor* visitor, void* closure) { typedef HashTable HashTableType; HashTableType* table = reinterpret_cast(closure); if (table->m_table) { // This just marks it live and does not push anything onto the // marking stack. Allocator::markNoTracing(visitor, table->m_table); // Now perform weak processing (this is a no-op if the backing // was accessible through an iterator and was already marked // strongly). for (typename HashTableType::ValueType* element = table->m_table + table->m_tableSize - 1; element >= table->m_table; element--) { if (!HashTableType::isEmptyOrDeletedBucket(*element)) { if (Traits::shouldRemoveFromCollection(visitor, *element)) { table->registerModification(); HashTableType::deleteBucket(*element); // Also calls the destructor. table->m_deletedCount++; table->m_keyCount--; // We don't rehash the backing until the next add // or delete, because that would cause allocation // during GC. } } } } } }; template void HashTable::trace(typename Allocator::Visitor* visitor) { // If someone else already marked the backing and queued up the trace // and/or weak callback then we are done. This optimization does not // happen for ListHashSet since its iterator does not point at the // backing. if (!m_table || visitor->isAlive(m_table)) return; // Normally, we mark the backing store without performing trace. This // means it is marked live, but the pointers inside it are not marked. // Instead we will mark the pointers below. However, for backing // stores that contain weak pointers the handling is rather different. // We don't mark the backing store here, so the marking GC will leave // the backing unmarked. If the backing is found in any other way than // through its HashTable (ie from an iterator) then the mark bit will // be set and the pointers will be marked strongly, avoiding problems // with iterating over things that disappear due to weak processing // while we are iterating over them. The weakProcessing callback will // mark the backing as a void pointer, and will perform weak processing // if needed. if (Traits::weakHandlingFlag == NoWeakHandlingInCollections) Allocator::markNoTracing(visitor, m_table); else Allocator::registerWeakMembers(visitor, this, m_table, WeakProcessingHashTableHelper::process); if (ShouldBeTraced::value) { for (ValueType* element = m_table + m_tableSize - 1; element >= m_table; element--) { if (!isEmptyOrDeletedBucket(*element)) Allocator::template trace(visitor, *element); } } } // iterator adapters template struct HashTableConstIteratorAdapter { HashTableConstIteratorAdapter() {} HashTableConstIteratorAdapter(const typename HashTableType::const_iterator& impl) : m_impl(impl) {} typedef typename Traits::IteratorConstGetType GetType; typedef typename HashTableType::ValueTraits::IteratorConstGetType SourceGetType; GetType get() const { return const_cast(SourceGetType(m_impl.get())); } typename Traits::IteratorConstReferenceType operator*() const { return Traits::getToReferenceConstConversion(get()); } GetType operator->() const { return get(); } HashTableConstIteratorAdapter& operator++() { ++m_impl; return *this; } // postfix ++ intentionally omitted typename HashTableType::const_iterator m_impl; }; template struct HashTableIteratorAdapter { typedef typename Traits::IteratorGetType GetType; typedef typename HashTableType::ValueTraits::IteratorGetType SourceGetType; HashTableIteratorAdapter() {} HashTableIteratorAdapter(const typename HashTableType::iterator& impl) : m_impl(impl) {} GetType get() const { return const_cast(SourceGetType(m_impl.get())); } typename Traits::IteratorReferenceType operator*() const { return Traits::getToReferenceConversion(get()); } GetType operator->() const { return get(); } HashTableIteratorAdapter& operator++() { ++m_impl; return *this; } // postfix ++ intentionally omitted operator HashTableConstIteratorAdapter() { typename HashTableType::const_iterator i = m_impl; return i; } typename HashTableType::iterator m_impl; }; template inline bool operator==(const HashTableConstIteratorAdapter& a, const HashTableConstIteratorAdapter& b) { return a.m_impl == b.m_impl; } template inline bool operator!=(const HashTableConstIteratorAdapter& a, const HashTableConstIteratorAdapter& b) { return a.m_impl != b.m_impl; } template inline bool operator==(const HashTableIteratorAdapter& a, const HashTableIteratorAdapter& b) { return a.m_impl == b.m_impl; } template inline bool operator!=(const HashTableIteratorAdapter& a, const HashTableIteratorAdapter& b) { return a.m_impl != b.m_impl; } // All 4 combinations of ==, != and Const,non const. template inline bool operator==(const HashTableConstIteratorAdapter& a, const HashTableIteratorAdapter& b) { return a.m_impl == b.m_impl; } template inline bool operator!=(const HashTableConstIteratorAdapter& a, const HashTableIteratorAdapter& b) { return a.m_impl != b.m_impl; } template inline bool operator==(const HashTableIteratorAdapter& a, const HashTableConstIteratorAdapter& b) { return a.m_impl == b.m_impl; } template inline bool operator!=(const HashTableIteratorAdapter& a, const HashTableConstIteratorAdapter& b) { return a.m_impl != b.m_impl; } template inline void removeAll(Collection1& collection, const Collection2& toBeRemoved) { if (collection.isEmpty() || toBeRemoved.isEmpty()) return; typedef typename Collection2::const_iterator CollectionIterator; CollectionIterator end(toBeRemoved.end()); for (CollectionIterator it(toBeRemoved.begin()); it != end; ++it) collection.remove(*it); } } // namespace WTF #include "wtf/HashIterators.h" #endif // WTF_HashTable_h