1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
6 /*++---------------------------------------------------------------------------------------
14 Fast hash table classes,
21 #define ASSERT _ASSERTE
27 // #define HASHTABLE_PROFILE
29 // Track collision chains of up to length X
30 const unsigned int HASHTABLE_LOOKUP_PROBES_DATA = 20;
32 //-------------------------------------------------------
33 // enums for special Key values used in hash table
42 typedef ULONG_PTR UPTR;
44 //------------------------------------------------------------------------------
46 //------------------------------------------------------------------------------
50 //-------------------------------------------------------
52 // used by hash table implementation
54 typedef DPTR(class Bucket) PTR_Bucket;
61 #define VALUE_MASK (sizeof(LPVOID) == 4 ? 0x7FFFFFFF : I64(0x7FFFFFFFFFFFFFFF))
63 void SetValue (UPTR value, UPTR i)
65 LIMITED_METHOD_CONTRACT;
67 ASSERT(value <= VALUE_MASK);
68 m_rgValues[i] = (UPTR) ((m_rgValues[i] & ~VALUE_MASK) | value);
71 UPTR GetValue (UPTR i)
73 LIMITED_METHOD_DAC_CONTRACT;
75 return (UPTR)(m_rgValues[i] & VALUE_MASK);
78 UPTR IsCollision() // useful sentinel for fast fail of lookups
80 LIMITED_METHOD_CONTRACT;
82 return (UPTR) (m_rgValues[0] & ~VALUE_MASK);
87 LIMITED_METHOD_CONTRACT;
89 m_rgValues[0] |= ~VALUE_MASK; // set collision bit
90 m_rgValues[1] &= VALUE_MASK; // reset has free slots bit
97 // check for free slots available in the bucket
98 // either there is no collision or a free slot has been during
100 return (!IsCollision() || (m_rgValues[1] & ~VALUE_MASK));
105 LIMITED_METHOD_CONTRACT;
107 m_rgValues[1] |= ~VALUE_MASK; // set has free slots bit
110 BOOL InsertValue(const UPTR key, const UPTR value);
114 //------------------------------------------------------------------------------
115 // bool (*CompareFnPtr)(UPTR,UPTR); pointer to a function that takes 2 UPTRs
116 // and returns a boolean, provide a function with this signature to the HashTable
117 // to use for comparing Values during lookup
118 //------------------------------------------------------------------------------
119 typedef BOOL (*CompareFnPtr)(UPTR,UPTR);
126 LIMITED_METHOD_CONTRACT;
133 Compare(CompareFnPtr ptr)
135 LIMITED_METHOD_CONTRACT;
137 _ASSERTE(ptr != NULL);
143 LIMITED_METHOD_CONTRACT;
146 virtual UPTR CompareHelper(UPTR val1, UPTR storedval)
153 DISABLED(THROWS); // This is not a bug, we cannot decide, since the function ptr called may be either.
154 DISABLED(GC_NOTRIGGER); // This is not a bug, we cannot decide, since the function ptr called may be either.
159 return (*m_ptr)(val1,storedval);
163 class ComparePtr : public Compare
166 ComparePtr (CompareFnPtr ptr)
168 LIMITED_METHOD_CONTRACT;
170 _ASSERTE(ptr != NULL);
174 virtual UPTR CompareHelper(UPTR val1, UPTR storedval)
181 DISABLED(THROWS); // This is not a bug, we cannot decide, since the function ptr called may be either.
182 DISABLED(GC_NOTRIGGER); // This is not a bug, we cannot decide, since the function ptr called may be either.
188 return (*m_ptr)(val1,storedval);
192 //------------------------------------------------------------------------------
194 // Fast Hash table, for concurrent use,
195 // stores a 4 byte Key and a 4 byte Value for each slot.
196 // Duplicate keys are allowed, (keys are compared as 4 byte UPTRs)
197 // Duplicate values are allowed,(values are compared using comparison fn. provided)
198 // but if no comparison function is provided then the values should be unique
200 // Lookup's don't require to take locks, unless you specify fAsyncMode.
201 // Insert and Delete operations require locks
202 // Inserting a duplicate value will assert in DEBUG mode, the PROPER way to perform inserts
203 // is to take a lock, do a lookup and if the lookup fails then Insert
205 // In async mode, deleted slots are not immediately reclaimed (until a rehash), and
206 // accesses to the hash table cause a transition to cooperative GC mode, and reclamation of old
207 // hash maps (after a rehash) are deferred until GC time.
208 // In sync mode, none of this is necessary; however calls to LookupValue must be synchronized as well.
211 // The Hash table is an array of buckets, each bucket can contain 4 key/value pairs
212 // Special key values are used to identify EMPTY and DELETED slots
213 // Hash function uses the current size of the hash table and a SEED based on the key
214 // to choose the bucket, seed starts of being the key and gets refined every time
215 // the hash function is re-applied.
217 // Inserts choose an empty slot in the current bucket for new entries, if the current bucket
218 // is full, then the seed is refined and a new bucket is choosen, if an empty slot is not found
219 // after 8 retries, the hash table is expanded, this causes the current array of buckets to
220 // be put in a free list and a new array of buckets is allocated and all non-deleted entries
221 // from the old hash table are rehashed to the new array
222 // The old arrays are reclaimed during Compact phase, which should only be called during GC or
223 // any other time it is guaranteed that no Lookups are taking place.
224 // Concurrent Insert and Delete operations need to be serialized
226 // Delete operations, mark the Key in the slot as DELETED, the value is not removed and inserts
227 // don't reuse these slots, they get reclaimed during expansion and compact phases.
229 //------------------------------------------------------------------------------
236 HashMap() DAC_EMPTY();
238 ~HashMap() DAC_EMPTY();
241 void Init(BOOL fAsyncMode, LockOwner *pLock)
245 Init(0, (Compare *)NULL,fAsyncMode, pLock);
248 void Init(DWORD cbInitialSize, BOOL fAsyncMode, LockOwner *pLock)
252 Init(cbInitialSize, (Compare*)NULL, fAsyncMode, pLock);
255 void Init(CompareFnPtr ptr, BOOL fAsyncMode, LockOwner *pLock)
259 Init(0, ptr, fAsyncMode, pLock);
263 void Init(DWORD cbInitialSize, CompareFnPtr ptr, BOOL fAsyncMode, LockOwner *pLock);
267 void Init(DWORD cbInitialSize, Compare* pCompare, BOOL fAsyncMode, LockOwner *pLock);
269 // check to see if the value is already in the Hash Table
270 // key should be > DELETED
271 // if provided, uses the comparison function ptr to compare values
272 // returns INVALIDENTRY if not found
273 UPTR LookupValue(UPTR key, UPTR value);
275 // Insert if the value is not already present
276 // it is illegal to insert duplicate values in the hash map
277 // do a lookup to verify the value is not already present
279 void InsertValue(UPTR key, UPTR value);
281 // Replace the value if present
282 // returns the previous value, or INVALIDENTRY if not present
283 // does not insert a new value under any circumstances
285 UPTR ReplaceValue(UPTR key, UPTR value);
287 // mark the entry as deleted and return the stored value
288 // returns INVALIDENTRY, if not found
289 UPTR DeleteValue (UPTR key, UPTR value);
291 // for unique keys, use this function to get the value that is
292 // stored in the hash table, returns INVALIDENTRY if key not found
293 UPTR Gethash(UPTR key);
295 // Called only when all threads are frozed, like during GC
296 // for a SINGLE user mode, call compact after every delete
297 // operation on the hash table
300 // Remove all entries from the hash tablex
303 #ifdef DACCESS_COMPILE
304 void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
307 // inline helper, in non HASHTABLE_PROFILE mode becomes a NO-OP
308 void ProfileLookup(UPTR ntry, UPTR retValue);
309 // data members used for profiling
310 #ifdef HASHTABLE_PROFILE
311 unsigned m_cbRehash; // number of times rehashed
312 unsigned m_cbRehashSlots; // number of slots that were rehashed
313 unsigned m_cbObsoleteTables;
314 unsigned m_cbTotalBuckets;
315 unsigned m_cbInsertProbesGt8; // inserts that needed more than 8 probes
316 LONG m_rgLookupProbes[HASHTABLE_LOOKUP_PROBES_DATA]; // lookup probes
317 UPTR maxFailureProbe; // cost of failed lookup
319 void DumpStatistics();
320 #endif // HASHTABLE_PROFILE
322 #if 0 // Test-only code for debugging this class.
323 #ifndef DACCESS_COMPILE
324 static void LookupPerfTest(HashMap * table, const unsigned int MinThreshold);
325 static void HashMapTest();
326 #endif // !DACCESS_COMPILE
327 #endif // 0 // Test-only code for debugging this class.
330 // static helper function
331 static UPTR PutEntry (Bucket* rgBuckets, UPTR key, UPTR value);
334 DWORD GetNearestIndex(DWORD cbInitialSize);
337 static void Enter(HashMap *); // check valid to enter
338 static void Leave(HashMap *); // check valid to leave
340 typedef Holder<HashMap *, HashMap::Enter, HashMap::Leave> SyncAccessHolder;
341 BOOL m_fInSyncCode; // test for non-synchronus access
343 // in non DEBUG mode use a no-op helper
344 typedef NoOpBaseHolder<HashMap *> SyncAccessHolder;
347 // compute the new size, based on the number of free slots
348 // available, compact or expand
350 // create a new bucket array and rehash the non-deleted entries
352 static DWORD GetSize(PTR_Bucket rgBuckets);
353 static void SetSize(Bucket* rgBuckets, size_t size);
354 PTR_Bucket Buckets();
355 UPTR CompareValues(const UPTR value1, const UPTR value2);
357 // For double hashing, compute the second hash function once, then add.
358 // H(key, i) = H1(key) + i * H2(key), where 0 <= i < numBuckets
359 static void HashFunction(const UPTR key, const UINT numBuckets, UINT &seed, UINT &incr);
361 Compare* m_pCompare; // compare object to be used in lookup
362 SIZE_T m_iPrimeIndex; // current size (index into prime array)
363 PTR_Bucket m_rgBuckets; // array of buckets
365 // track the number of inserts and deletes
366 SIZE_T m_cbPrevSlotsInUse;
369 // mode of operation, synchronus or single user
374 FnLockOwner m_pfnLockOwner;
375 EEThreadId m_writerThreadId;
379 // A thread must own a lock for a hash if it is a writer.
384 ///---------Iterator----------------
389 PTR_Bucket m_pBucket;
390 PTR_Bucket m_pSentinel;
397 Iterator(Bucket* pBucket) :
398 m_pBucket(dac_cast<PTR_Bucket>(pBucket)),
399 m_id(-1), m_fEnd(false)
409 size_t cbSize = (PTR_size_t(m_pBucket))[0];
411 m_pSentinel = m_pBucket+cbSize;
415 Iterator(const Iterator& iter)
417 LIMITED_METHOD_CONTRACT;
419 m_pBucket = iter.m_pBucket;
420 m_pSentinel = iter.m_pSentinel;
422 m_fEnd = iter.m_fEnd;
427 ~Iterator(){ LIMITED_METHOD_DAC_CONTRACT; };
430 friend bool operator == (const Iterator& lhs, const Iterator& rhs)
432 LIMITED_METHOD_CONTRACT;
434 return (lhs.m_pBucket == rhs.m_pBucket && lhs.m_id == rhs.m_id);
437 inline Iterator& operator= (const Iterator& iter)
439 LIMITED_METHOD_CONTRACT;
441 m_pBucket = iter.m_pBucket;
442 m_pSentinel = iter.m_pSentinel;
444 m_fEnd = iter.m_fEnd;
449 inline void operator++ ()
454 _ASSERTE(!m_fEnd); // check we are not alredy at end
461 //accessors : GetDisc() , returns the discriminator
464 LIMITED_METHOD_CONTRACT;
466 _ASSERTE(!m_fEnd); // check we are not alredy at end
467 return m_pBucket->m_rgKeys[m_id];
469 //accessors : SetDisc() , sets the discriminator
472 //accessors : GetValue(),
473 // returns the pointer that corresponds to the discriminator
474 inline UPTR GetValue()
479 _ASSERTE(!m_fEnd); // check we are not alredy at end
480 return m_pBucket->GetValue(m_id);
484 // end(), check if the iterator is at the end of the bucket
485 inline BOOL end() const
487 LIMITED_METHOD_DAC_CONTRACT;
496 LIMITED_METHOD_DAC_CONTRACT;
498 for (;m_pBucket < m_pSentinel; m_pBucket++)
499 { //loop thru all buckets
500 for (m_id = m_id+1; m_id < 4; m_id++)
501 { //loop through all slots
502 if (m_pBucket->m_rgKeys[m_id] > DELETED)
514 inline Bucket* firstBucket()
522 // return an iterator, positioned at the beginning of the bucket
523 inline Iterator begin()
527 return Iterator(m_rgBuckets);
530 inline SIZE_T GetCount()
532 LIMITED_METHOD_CONTRACT;
534 return m_cbInserts-m_cbDeletes;
538 //---------------------------------------------------------------------------------------
540 // Wrapper class for using Hash table to store pointer values
541 // HashMap class requires that high bit is always reset
542 // The allocator used within the runtime, always allocates objects 8 byte aligned
543 // so we can shift right one bit, and store the result in the hash table
548 // key really acts as a hash code. Sanitize it from special values used by the underlying HashMap.
549 inline static UPTR SanitizeKey(UPTR key)
551 return (key > DELETED) ? key : (key + 100);
555 #ifndef DACCESS_COMPILE
556 void *operator new(size_t size, LoaderHeap *pHeap);
557 void operator delete(void *p);
558 #endif // !DACCESS_COMPILE
561 void Init(BOOL fAsyncMode, LockOwner *pLock)
565 Init(0,NULL,fAsyncMode,pLock);
568 void Init(DWORD cbInitialSize, BOOL fAsyncMode, LockOwner *pLock)
572 Init(cbInitialSize, NULL, fAsyncMode,pLock);
575 void Init(CompareFnPtr ptr, BOOL fAsyncMode, LockOwner *pLock)
579 Init(0, ptr, fAsyncMode,pLock);
583 void Init(DWORD cbInitialSize, CompareFnPtr ptr, BOOL fAsyncMode, LockOwner *pLock);
585 // check to see if the value is already in the Hash Table
586 LPVOID LookupValue(UPTR key, LPVOID pv)
590 key = SanitizeKey(key);
592 // gmalloc allocator, always allocates 8 byte aligned
593 // so we can shift out the lowest bit
594 // ptr right shift by 1
595 UPTR value = (UPTR)pv;
596 _ASSERTE((value & 0x1) == 0);
598 UPTR val = m_HashMap.LookupValue (key, value);
599 if (val != (UPTR) INVALIDENTRY)
606 // Insert if the value is not already present
607 // it is illegal to insert duplicate values in the hash map
608 // users should do a lookup to verify the value is not already present
610 void InsertValue(UPTR key, LPVOID pv)
614 key = SanitizeKey(key);
616 // gmalloc allocator, always allocates 8 byte aligned
617 // so we can shift out the lowest bit
618 // ptr right shift by 1
619 UPTR value = (UPTR)pv;
620 _ASSERTE((value & 0x1) == 0);
622 m_HashMap.InsertValue (key, value);
625 // Replace the value if present
626 // returns the previous value, or INVALIDENTRY if not present
627 // does not insert a new value under any circumstances
629 LPVOID ReplaceValue(UPTR key, LPVOID pv)
633 key = SanitizeKey(key);
635 // gmalloc allocator, always allocates 8 byte aligned
636 // so we can shift out the lowest bit
637 // ptr right shift by 1
638 UPTR value = (UPTR)pv;
639 _ASSERTE((value & 0x1) == 0);
641 UPTR val = m_HashMap.ReplaceValue (key, value);
642 if (val != (UPTR) INVALIDENTRY)
649 // mark the entry as deleted and return the stored value
650 // returns INVALIDENTRY if not found
651 LPVOID DeleteValue (UPTR key,LPVOID pv)
655 key = SanitizeKey(key);
657 UPTR value = (UPTR)pv;
658 _ASSERTE((value & 0x1) == 0);
660 UPTR val = m_HashMap.DeleteValue(key, value);
661 if (val != (UPTR) INVALIDENTRY)
668 // for unique keys, use this function to get the value that is
669 // stored in the hash table, returns INVALIDENTRY if key not found
670 LPVOID Gethash(UPTR key)
674 key = SanitizeKey(key);
676 UPTR val = m_HashMap.Gethash(key);
677 if (val != (UPTR) INVALIDENTRY)
700 HashMap::Iterator iter;
703 PtrIterator(HashMap& hashMap) : iter(hashMap.begin())
705 LIMITED_METHOD_DAC_CONTRACT;
707 PtrIterator(Bucket* bucket) : iter(bucket)
709 LIMITED_METHOD_DAC_CONTRACT;
714 LIMITED_METHOD_DAC_CONTRACT;
730 return iter.GetKey();
738 UPTR val = iter.GetValue();
739 if (val != (UPTR) INVALIDENTRY)
743 return PTR_VOID(val);
755 inline Bucket* firstBucket()
760 return m_HashMap.firstBucket();
763 // return an iterator, positioned at the beginning of the bucket
764 inline PtrIterator begin()
768 return PtrIterator(m_HashMap);
771 inline SIZE_T GetCount()
773 LIMITED_METHOD_CONTRACT;
775 return m_HashMap.GetCount();
778 #ifdef DACCESS_COMPILE
779 void EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
782 m_HashMap.EnumMemoryRegions(flags);
784 #endif // DACCESS_COMPILE
787 //---------------------------------------------------------------------
788 // inline Bucket*& NextObsolete (Bucket* rgBuckets)
789 // get the next obsolete bucket in the chain
791 Bucket*& NextObsolete (Bucket* rgBuckets)
793 LIMITED_METHOD_CONTRACT;
795 return *(Bucket**)&((size_t*)rgBuckets)[1];