Skip to content

Commit 3e5e118

Browse files
authored
Change CrossLoaderAllocatorHash to not use the GC (#67160)
* Change `CrossLoaderAllocatorHash` to not use the GC `GCHeapHash`: - Replaced `GCHeapHash` usage with `SHash` - Added `s_supports_autoremove` to `SHash` to simulate `GCHeapHash`'s ability to remove elements while iterating them. When enabled, `SHash` calls `ShouldDelete()` on elements it walks during various operations and removes them if requested - Added an optional on-remove cleanup action for elements in `SHash` to ease cleanup of heap-allocated elements `CrossLoaderAllocatorHash`: - Added formal native types for internal data structures and hash table elements. The layout of the internal data structures is mostly the same as before. - Trackers are ref-counted since they may be inserted into multiple hash tables, and get deleted once their ref count reaches zero - Translated the current implementation. Used holders where necessary to handle OOMs gracefully. Miscellaneous: - Removed cooperative GC mode usage in the relevant places in slot backpatching and inline tracking, and removed usage of the forbid-suspend-for-debugger region in those places - When deleting call counters, reordered such that the runtime is suspended before the slot backpatching lock is acquired, to prevent the suspension code from suspending for the debugger while the lock is held and leading to FuncEval deadlocks Testing: - Memory usage is roughly the same as before, if anything it seems to be a bit lower - Perf is similar to before, no significant change - Ran a slot backpatching test including collectible assemblies in stress mode to look for issues
1 parent 322ea09 commit 3e5e118

29 files changed

+1140
-1926
lines changed

src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,6 @@
201201
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimePropertyInfo.cs" />
202202
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\RuntimeTypeMetadataUpdateHandler.cs" />
203203
<Compile Include="$(BclSourcesRoot)\System\Resources\ManifestBasedResourceGroveler.CoreCLR.cs" />
204-
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CrossLoaderAllocatorHashHelpers.cs" />
205-
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\GCHeapHash.cs" />
206204
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
207205
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\ICastableHelpers.cs" />
208206
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeFeature.CoreCLR.cs" />

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CrossLoaderAllocatorHashHelpers.cs

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/GCHeapHash.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/coreclr/inc/CrstTypes.def

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ Crst InlineTrackingMap
544544
End
545545

546546
Crst JitInlineTrackingMap
547-
AcquiredBefore CodeVersioning ThreadStore LoaderAllocator
547+
AcquiredBefore CodeVersioning ThreadStore MethodDescBackpatchInfoTracker
548548
End
549549

550550
Crst EventPipe
@@ -568,8 +568,8 @@ Crst COMCallWrapper
568568
End
569569

570570
Crst MethodDescBackpatchInfoTracker
571-
AcquiredBefore FuncPtrStubs ThreadStore SystemDomain
572-
AcquiredAfter ReJITGlobalRequest
571+
AcquiredBefore FuncPtrStubs
572+
AcquiredAfter ReJITGlobalRequest ThreadStore SystemDomain
573573
End
574574

575575
Crst NativeImageEagerFixups

src/coreclr/inc/crsttypes.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ int g_rgCrstLevelMap[] =
200200
7, // CrstISymUnmanagedReader
201201
11, // CrstJit
202202
0, // CrstJitGenericHandleCache
203-
16, // CrstJitInlineTrackingMap
203+
13, // CrstJitInlineTrackingMap
204204
4, // CrstJitPatchpoint
205205
-1, // CrstJitPerf
206206
6, // CrstJumpStubCache
@@ -210,7 +210,7 @@ int g_rgCrstLevelMap[] =
210210
16, // CrstLoaderAllocatorReferences
211211
3, // CrstLoaderHeap
212212
3, // CrstManagedObjectWrapperMap
213-
14, // CrstMethodDescBackpatchInfoTracker
213+
10, // CrstMethodDescBackpatchInfoTracker
214214
5, // CrstModule
215215
15, // CrstModuleFixup
216216
4, // CrstModuleLookupTable
@@ -231,7 +231,7 @@ int g_rgCrstLevelMap[] =
231231
0, // CrstRCWCleanupList
232232
10, // CrstReadyToRunEntryPointToMethodDescMap
233233
8, // CrstReflection
234-
17, // CrstReJITGlobalRequest
234+
14, // CrstReJITGlobalRequest
235235
4, // CrstRetThunkCache
236236
3, // CrstSavedExceptionInfo
237237
0, // CrstSaveModuleProfileData

src/coreclr/inc/shash.h

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,14 @@
7171
// default traits if it can be assigned from 0.
7272
// static const bool IsDeleted(const ELEMENT &e) Compare element with Deleted sentinal value. May be inherited from the
7373
// default traits if it can be assigned from -1.
74+
// static bool ShouldDelete(const ELEMENT &e) Called in addition to IsDeleted() when s_supports_autoremove is true, see more
75+
// information there.
7476
//
7577
// static void OnDestructPerEntryCleanupAction(ELEMENT& e) Called on every element when in hashtable destructor.
7678
// s_DestructPerEntryCleanupAction must be set to true if implemented.
79+
// static void OnRemovePerEntryCleanupAction(ELEMENT& e) Called when an element is removed from the hashtable, including when
80+
// the hashtable is destructed. s_RemovePerEntryCleanupAction must be set to true
81+
// if implemented.
7782
//
7883
// s_growth_factor_numerator
7984
// s_growth_factor_denominator Factor to grow allocation (numerator/denominator).
@@ -92,7 +97,19 @@
9297
// in that there may be more copies of the template instantiated through
9398
// the system as different variants are used.
9499
//
100+
// s_supports_autoremove When autoremove is supported, ShouldDelete() is called in addition to
101+
// IsDeleted() in any situation that involves walking the table's elements, to
102+
// determine if an element should be deleted. It enables the hash table to
103+
// self-clean elements whose underlying lifetime may be controlled externally. Note
104+
// that since some lookup/iteration operations are const (can operate on a
105+
// "const SHash"), when autoremove is supported, any such const operation may still
106+
// modify the hash table. If this is set to true, s_supports_remove must also be
107+
// true.
108+
//
95109
// s_DestructPerEntryCleanupAction Set to true if OnDestructPerEntryCleanupAction has non-empty implementation.
110+
// s_RemovePerEntryCleanupAction Set to true if OnRemovePerEntryCleanupAction has non-empty implementation.
111+
// Only one of s_DestructPerEntryCleanupAction and s_RemovePerEntryCleanupAction
112+
// may be set to true.
96113
//
97114
// DefaultHashTraits provides defaults for seldomly customized values in traits classes.
98115

@@ -115,15 +132,20 @@ class DefaultSHashTraits
115132
static const COUNT_T s_minimum_allocation = 7;
116133

117134
static const bool s_supports_remove = true;
135+
static const bool s_supports_autoremove = false;
118136

119137
static ELEMENT Null() { return (ELEMENT)(TADDR)0; }
120138
static ELEMENT Deleted() { return (ELEMENT)(TADDR)-1; }
121139
static bool IsNull(const ELEMENT &e) { return e == (ELEMENT)(TADDR)0; }
122140
static bool IsDeleted(const ELEMENT &e) { return e == (ELEMENT)(TADDR)-1; }
141+
static bool ShouldDelete(const ELEMENT &e) { return false; }
123142

124143
static inline void OnDestructPerEntryCleanupAction(const ELEMENT& e) { /* Do nothing */ }
125144
static const bool s_DestructPerEntryCleanupAction = false;
126145

146+
static void OnRemovePerEntryCleanupAction(const ELEMENT &e) {}
147+
static const bool s_RemovePerEntryCleanupAction = false;
148+
127149
static const bool s_NoThrow = true;
128150

129151
// No defaults - must specify:
@@ -177,6 +199,10 @@ class EMPTY_BASES_DECL SHash : public TRAITS
177199

178200
const element_t* LookupPtr(key_t key) const;
179201

202+
// Pointer-based flavor to replace an existing element (allows efficient access to tables of structures)
203+
204+
void ReplacePtr(const element_t *elementPtr, const element_t &newElement, bool invokeCleanupAction = true);
205+
180206
// Add an element to the hash table. This will never replace an element; multiple
181207
// elements may be stored with the same key.
182208

@@ -298,7 +324,7 @@ class EMPTY_BASES_DECL SHash : public TRAITS
298324
// it is perfectly fine for the element to be a duplicate - if so it
299325
// is added an additional time. Returns TRUE if a new empty spot was used;
300326
// FALSE if an existing deleted slot.
301-
static BOOL Add(element_t *table, count_t tableSize, const element_t &element);
327+
BOOL Add(element_t *table, count_t tableSize, const element_t &element);
302328

303329
// Utility function to add a new element to the hash table, if no element with the same key
304330
// is already there. Otherwise, it will replace the existing element. This has the effect of
@@ -308,7 +334,7 @@ class EMPTY_BASES_DECL SHash : public TRAITS
308334
// Utility function to find the first element with the given key in
309335
// the hash table.
310336

311-
static const element_t* Lookup(PTR_element_t table, count_t tableSize, key_t key);
337+
const element_t* Lookup(PTR_element_t table, count_t tableSize, key_t key) const;
312338

313339
// Utility function to remove the first element with the given key
314340
// in the hash table.
@@ -334,13 +360,17 @@ class EMPTY_BASES_DECL SHash : public TRAITS
334360
// Some compilers won't compile the separate implementation in shash.inl
335361
protected:
336362

363+
SHash *m_hash;
337364
PTR_element_t m_table;
338365
count_t m_tableSize;
339366
count_t m_index;
340367

341-
368+
// Iteration may modify the hash table if s_supports_autoremove is true. Since it typically does not modify the hash
369+
// table, it should be possible to iterate over a "const SHash". So this takes a "const SHash *" and casts away the
370+
// const to support autoremove.
342371
Index(const SHash *hash, BOOL begin)
343-
: m_table(hash->m_table),
372+
: m_hash(const_cast<SHash *>(hash)),
373+
m_table(hash->m_table),
344374
m_tableSize(hash->m_tableSize),
345375
m_index(begin ? 0 : m_tableSize)
346376
{
@@ -358,9 +388,24 @@ class EMPTY_BASES_DECL SHash : public TRAITS
358388
{
359389
LIMITED_METHOD_CONTRACT;
360390

361-
if (m_index < m_tableSize)
362-
if (TRAITS::IsNull(m_table[m_index]) || TRAITS::IsDeleted(m_table[m_index]))
363-
Next();
391+
if (m_index >= m_tableSize)
392+
{
393+
return;
394+
}
395+
396+
if (!TRAITS::IsNull(m_table[m_index]) && !TRAITS::IsDeleted(m_table[m_index]))
397+
{
398+
if (TRAITS::s_supports_autoremove && TRAITS::ShouldDelete(m_table[m_index]))
399+
{
400+
m_hash->RemoveElement(m_table, m_tableSize, &m_table[m_index]);
401+
}
402+
else
403+
{
404+
return;
405+
}
406+
}
407+
408+
Next();
364409
}
365410

366411
void Next()
@@ -376,7 +421,16 @@ class EMPTY_BASES_DECL SHash : public TRAITS
376421
if (m_index >= m_tableSize)
377422
break;
378423
if (!TRAITS::IsNull(m_table[m_index]) && !TRAITS::IsDeleted(m_table[m_index]))
379-
break;
424+
{
425+
if (TRAITS::s_supports_autoremove && TRAITS::ShouldDelete(m_table[m_index]))
426+
{
427+
m_hash->RemoveElement(m_table, m_tableSize, &m_table[m_index]);
428+
}
429+
else
430+
{
431+
break;
432+
}
433+
}
380434
}
381435
}
382436

@@ -442,11 +496,26 @@ class EMPTY_BASES_DECL SHash : public TRAITS
442496
m_increment = (hash % (this->m_tableSize-1)) + 1;
443497

444498
// Find first valid element
499+
445500
if (TRAITS::IsNull(this->m_table[this->m_index]))
501+
{
446502
this->m_index = this->m_tableSize;
447-
else if (TRAITS::IsDeleted(this->m_table[this->m_index])
448-
|| !TRAITS::Equals(m_key, TRAITS::GetKey(this->m_table[this->m_index])))
449-
Next();
503+
return;
504+
}
505+
506+
if (!TRAITS::IsDeleted(this->m_table[this->m_index]))
507+
{
508+
if (TRAITS::s_supports_autoremove && TRAITS::ShouldDelete(this->m_table[this->m_index]))
509+
{
510+
this->m_hash->RemoveElement(this->m_table, this->m_tableSize, &this->m_table[this->m_index]);
511+
}
512+
else if (TRAITS::Equals(m_key, TRAITS::GetKey(this->m_table[this->m_index])))
513+
{
514+
return;
515+
}
516+
}
517+
518+
Next();
450519
}
451520
}
452521

@@ -466,8 +535,18 @@ class EMPTY_BASES_DECL SHash : public TRAITS
466535
break;
467536
}
468537

469-
if (!TRAITS::IsDeleted(this->m_table[this->m_index])
470-
&& TRAITS::Equals(m_key, TRAITS::GetKey(this->m_table[this->m_index])))
538+
if (TRAITS::IsDeleted(this->m_table[this->m_index]))
539+
{
540+
continue;
541+
}
542+
543+
if (TRAITS::s_supports_autoremove && TRAITS::ShouldDelete(this->m_table[this->m_index]))
544+
{
545+
this->m_hash->RemoveElement(this->m_table, this->m_tableSize, &this->m_table[this->m_index]);
546+
continue;
547+
}
548+
549+
if (TRAITS::Equals(m_key, TRAITS::GetKey(this->m_table[this->m_index])))
471550
{
472551
break;
473552
}

0 commit comments

Comments
 (0)