Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
074487c
Replace HashMap COOP transitions with Epoch-Based Reclamation (EBR)
AaronRobinsonMSFT Feb 11, 2026
55708d0
EBR cleanup: naming, scoping, and style improvements
AaronRobinsonMSFT Feb 12, 2026
73168b4
Refactor EbrPendingEntry structure: move definition from ebr.h to ebr…
AaronRobinsonMSFT Feb 12, 2026
d6acf4e
Update src/coreclr/vm/hash.cpp
AaronRobinsonMSFT Feb 12, 2026
cfedbdf
Fix EBR QueueForDeletion OOM safety and Rehash GetSize misuse
AaronRobinsonMSFT Feb 13, 2026
c7dfb9b
Address PR feedback: Shutdown guard, simplified asserts, comment fixes
AaronRobinsonMSFT Feb 13, 2026
fc4aece
Clean up per-thread EBR state on thread exit
AaronRobinsonMSFT Feb 13, 2026
4377813
Use thread_local value for EbrThreadData instead of heap allocation
AaronRobinsonMSFT Feb 13, 2026
6aa1f2c
Fix contracts
AaronRobinsonMSFT Feb 13, 2026
5c554da
Centralize bucket allocation with AllocateBuckets/FreeBuckets
AaronRobinsonMSFT Feb 14, 2026
a4f7ee6
Move free calls outside lock and add EbrPendingEntry constructor
AaronRobinsonMSFT Feb 14, 2026
98852e9
Move EBR reclamation to finalizer thread
AaronRobinsonMSFT Feb 14, 2026
958ce3a
Add STANDARD_VM_CONTRACT to NativeImage::Initialize and update Crst i…
AaronRobinsonMSFT Feb 17, 2026
6657f0c
Refactor EBR shutdown handling and improve bucket management in HashMap
AaronRobinsonMSFT Feb 17, 2026
ae84468
Enhance EBR thread detachment handling and add preemption for reclama…
AaronRobinsonMSFT Feb 17, 2026
a3040db
Make EBR thread list insert lock-free with lazy deletion
AaronRobinsonMSFT Feb 18, 2026
07473cc
Heap-allocate EbrThreadData with TLS destructor for cleanup
AaronRobinsonMSFT Feb 18, 2026
434ed79
Refactor EbrThreadData management to use thread-local instance and si…
AaronRobinsonMSFT Feb 18, 2026
48add32
Apply suggestions from code review
AaronRobinsonMSFT Feb 19, 2026
b575e02
Remove unused header
AaronRobinsonMSFT Feb 19, 2026
cdafcc1
Fix EBR comments and remove Ebr Crst types
AaronRobinsonMSFT Feb 19, 2026
2367317
Remove unnecessary full fence make all loads/stores explicit.
AaronRobinsonMSFT Feb 19, 2026
a6e1d92
Fix CrstFlags usage and reset thread EBR data in ThreadDetach
AaronRobinsonMSFT Feb 20, 2026
83f2176
Enhance EbrCollector: Add DetachedCollector, improve thread detach ha…
AaronRobinsonMSFT Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Centralize bucket allocation with AllocateBuckets/FreeBuckets
Introduce static AllocateBuckets and FreeBuckets helpers to ensure
consistent BYTE[] allocation and deallocation of bucket arrays. Move
GetSize/SetSize from HashMap members to file-static functions. Remove
vestigial NextObsolete and chain-traversal loop from DeleteObsoleteBuckets
since EBR queues each array independently.
  • Loading branch information
AaronRobinsonMSFT committed Feb 14, 2026
commit 5c554da97b6c5ae8fc9517b9f03e4c950a511798
88 changes: 37 additions & 51 deletions src/coreclr/vm/hash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,29 +91,48 @@ BOOL Bucket::InsertValue(const UPTR key, const UPTR value)
SetCollision(); // otherwise set the collision bit
return false;
}

#endif // !DACCESS_COMPILE

//---------------------------------------------------------------------
// inline Bucket*& NextObsolete (Bucket* rgBuckets)
// get the next obsolete bucket in the chain
static Bucket*& NextObsolete(Bucket* rgBuckets)
static DWORD GetSize(PTR_Bucket rgBuckets)
{
LIMITED_METHOD_DAC_CONTRACT;
PTR_size_t pSize = dac_cast<PTR_size_t>(rgBuckets - 1);
_ASSERTE(FitsIn<DWORD>(pSize[0]));
return static_cast<DWORD>(pSize[0]);
}

static void SetSize(Bucket* rgBuckets, size_t size)
{
LIMITED_METHOD_CONTRACT;
return *(Bucket**)&((size_t*)rgBuckets)[1];
((size_t*)rgBuckets)[0] = size;
}

// Allocate a zero-initialized bucket array with space for 'size' buckets
// plus a leading size_t header.
static Bucket* AllocateBuckets(DWORD size)
{
STATIC_CONTRACT_THROWS;
S_SIZE_T cbAlloc = (S_SIZE_T(size) + S_SIZE_T(1)) * S_SIZE_T(sizeof(Bucket));
if (cbAlloc.IsOverflow())
ThrowHR(COR_E_OVERFLOW);
Bucket* rgBuckets = (Bucket*) new BYTE[cbAlloc.Value()];
memset(rgBuckets, 0, cbAlloc.Value());
SetSize(rgBuckets, size);
return rgBuckets;
}

// Free a bucket array allocated by AllocateBuckets.
static void FreeBuckets(Bucket* rgBuckets)
{
LIMITED_METHOD_CONTRACT;
delete [] (BYTE*)rgBuckets;
}

// Static helper for EBR deferred deletion of obsolete bucket arrays.
static void DeleteObsoleteBuckets(void* p)
{
LIMITED_METHOD_CONTRACT;
Bucket* pBucket = (Bucket*)p;
while (pBucket)
{
Bucket* pNextBucket = NextObsolete(pBucket);
delete [] pBucket;
pBucket = pNextBucket;
}
FreeBuckets((Bucket*)p);
}

//---------------------------------------------------------------------
Expand All @@ -130,19 +149,6 @@ PTR_Bucket HashMap::Buckets()
return m_rgBuckets + 1;
}

//---------------------------------------------------------------------
// inline size_t HashMap::GetSize(PTR_Bucket rgBuckets)
// get the number of buckets
inline
DWORD HashMap::GetSize(PTR_Bucket rgBuckets)
{
LIMITED_METHOD_DAC_CONTRACT;
PTR_size_t pSize = dac_cast<PTR_size_t>(rgBuckets - 1);
_ASSERTE(FitsIn<DWORD>(pSize[0]));
return static_cast<DWORD>(pSize[0]);
}


//---------------------------------------------------------------------
// inline size_t HashMap::HashFunction(UPTR key, UINT numBuckets, UINT &seed, UINT &incr)
// get the first & second hash function.
Expand All @@ -166,16 +172,6 @@ void HashMap::HashFunction(const UPTR key, const UINT numBuckets, UINT &seed, UI

#ifndef DACCESS_COMPILE

//---------------------------------------------------------------------
// inline void HashMap::SetSize(Bucket *rgBuckets, size_t size)
// set the number of buckets
inline
void HashMap::SetSize(Bucket *rgBuckets, size_t size)
{
LIMITED_METHOD_CONTRACT;
((size_t*)rgBuckets)[0] = size;
}

//---------------------------------------------------------------------
// HashMap::HashMap()
// constructor, initialize all values
Expand Down Expand Up @@ -290,10 +286,7 @@ void HashMap::Init(DWORD cbInitialSize, Compare* pCompare, BOOL fAsyncMode, Lock
DWORD size = g_rgPrimes[m_iPrimeIndex];
_ASSERTE(size < 0x7fffffff);

m_rgBuckets = new Bucket[size+1];

memset (m_rgBuckets, 0, (size+1)*sizeof(Bucket));
SetSize(m_rgBuckets, size);
m_rgBuckets = AllocateBuckets(size);

m_pCompare = pCompare;

Expand Down Expand Up @@ -376,7 +369,7 @@ void HashMap::Clear()
STATIC_CONTRACT_FORBID_FAULT;

// free the current table
delete [] m_rgBuckets;
FreeBuckets(m_rgBuckets);

m_rgBuckets = NULL;
}
Expand Down Expand Up @@ -902,14 +895,7 @@ void HashMap::Rehash()
Bucket* rgBuckets = Buckets();
UPTR cbCurrSize = GetSize(rgBuckets);

S_SIZE_T cbNewBuckets = (S_SIZE_T(cbNewSize) + S_SIZE_T(1)) * S_SIZE_T(sizeof(Bucket));

if (cbNewBuckets.IsOverflow())
ThrowHR(COR_E_OVERFLOW);

Bucket* rgNewBuckets = (Bucket *) new BYTE[cbNewBuckets.Value()];
memset (rgNewBuckets, 0, cbNewBuckets.Value());
SetSize(rgNewBuckets, cbNewSize);
Bucket* rgNewBuckets = AllocateBuckets(cbNewSize);

// current valid slots
UPTR cbValidSlots = m_cbInserts-m_cbDeletes;
Expand Down Expand Up @@ -1241,10 +1227,10 @@ void HashMap::LookupPerfTest(HashMap * table, const unsigned int MinThreshold)
table->LookupValue(i, i);
//cout << "Lookup perf test (1000 * " << MinThreshold << ": " << (t1-t0) << " ms." << endl;
#ifdef HASHTABLE_PROFILE
minipal_log_print_info("Lookup perf test time: %d ms table size: %d max failure probe: %d longest collision chain: %d\n", (int) (t1-t0), (int) table->GetSize(table->Buckets()), (int) table->maxFailureProbe, (int) table->m_cbMaxCollisionLength);
minipal_log_print_info("Lookup perf test time: %d ms table size: %d max failure probe: %d longest collision chain: %d\n", (int) (t1-t0), (int) GetSize(table->Buckets()), (int) table->maxFailureProbe, (int) table->m_cbMaxCollisionLength);
table->DumpStatistics();
#else // !HASHTABLE_PROFILE
minipal_log_print_info("Lookup perf test time: %d ms table size: %d\n", (int) (t1-t0), table->GetSize(table->Buckets()));
minipal_log_print_info("Lookup perf test time: %d ms table size: %d\n", (int) (t1-t0), GetSize(table->Buckets()));
#endif // !HASHTABLE_PROFILE
}
#endif // !DACCESS_COMPILE
Expand Down
2 changes: 0 additions & 2 deletions src/coreclr/vm/hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,6 @@ class HashMap
UPTR NewSize();
// create a new bucket array and rehash the non-deleted entries
void Rehash();
static DWORD GetSize(PTR_Bucket rgBuckets);
static void SetSize(Bucket* rgBuckets, size_t size);
PTR_Bucket Buckets();
UPTR CompareValues(const UPTR value1, const UPTR value2);

Expand Down