Skip to content

Commit 7de3762

Browse files
committed
Remove JitGenericHandleCache
- It was only used for overflow scenarios from the generic dictionary (which don't happen), virtual resolution scenarios for creating delegates, and a few other rare R2R scenarios - Replace the virtual resolution scenarios with a cache of the affected data in managed code, and move the helpers to managed - Just remove the pointless checks from within the various normal generic lookup paths Swap to using GenericCache - Make FlushCurrentCache public on GenericCache, as we need to clear this when we clean up a LoaderAllocator - Tweak algorithm for computing out of bound slots on the DictionaryLayout Update crsttypes
1 parent 56435ad commit 7de3762

File tree

15 files changed

+195
-374
lines changed

15 files changed

+195
-374
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\RuntimeTypeMetadataUpdateHandler.cs" />
198198
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
199199
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
200+
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\VirtualDispatchHelpers.cs" />
200201
<Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
201202
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
202203
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Common\src\System\Runtime\RhFailFastReason.cs" />

src/coreclr/System.Private.CoreLib/src/System/Reflection/LoaderAllocator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ internal sealed partial class LoaderAllocatorScout
3434
if (m_nativeLoaderAllocator == IntPtr.Zero)
3535
return;
3636

37+
VirtualDispatchHelpers.ClearCache();
38+
3739
// Destroy returns false if the managed LoaderAllocator is still alive.
3840
if (!Destroy(m_nativeLoaderAllocator))
3941
{
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Numerics;
7+
using System.Runtime.InteropServices;
8+
9+
namespace System.Runtime.CompilerServices;
10+
11+
[StackTraceHidden]
12+
[DebuggerStepThrough]
13+
internal static unsafe partial class VirtualDispatchHelpers
14+
{
15+
private struct VirtualResolutionData : IEquatable<VirtualResolutionData>
16+
{
17+
public MethodTable* MethodTable;
18+
public IntPtr ClassHandleTargetMethod;
19+
public IntPtr MethodHandle;
20+
21+
public bool Equals(VirtualResolutionData other) =>
22+
MethodTable == other.MethodTable &&
23+
ClassHandleTargetMethod == other.ClassHandleTargetMethod &&
24+
MethodHandle == other.MethodHandle;
25+
26+
public override bool Equals(object? obj) => obj is VirtualResolutionData other && Equals(other);
27+
28+
public override int GetHashCode() => (int) ((uint)MethodTable ^ (BitOperations.RotateLeft((uint)ClassHandleTargetMethod, 5)) ^ (BitOperations.RotateRight((uint)MethodHandle, 5)));
29+
}
30+
31+
private struct VirtualFunctionPointerArgs
32+
{
33+
public IntPtr classHnd;
34+
public IntPtr methodHnd;
35+
};
36+
37+
#if DEBUG
38+
// use smaller numbers to hit resizing/preempting logic in debug
39+
private const int InitialCacheSize = 8; // MUST BE A POWER OF TWO
40+
private const int MaximumCacheSize = 512;
41+
#else
42+
private const int InitialCacheSize = 128; // MUST BE A POWER OF TWO
43+
private const int MaximumCacheSize = 8 * 1024;
44+
#endif // DEBUG
45+
46+
private static GenericCache<VirtualResolutionData, IntPtr> s_virtualFunctionPointerCache = new GenericCache<VirtualResolutionData, IntPtr>(InitialCacheSize, MaximumCacheSize);
47+
48+
internal static void ClearCache()
49+
{
50+
s_virtualFunctionPointerCache.FlushCurrentCache();
51+
}
52+
53+
[LibraryImport(RuntimeHelpers.QCall)]
54+
private static unsafe partial IntPtr JIT_ResolveVirtualFunctionPointer(ObjectHandleOnStack obj, IntPtr classHandle, IntPtr methodHandle);
55+
56+
[MethodImpl(MethodImplOptions.NoInlining)]
57+
[DebuggerHidden]
58+
private static unsafe IntPtr VirtualFunctionPointerSlowpath(object obj, IntPtr classHandle, IntPtr methodHandle)
59+
{
60+
IntPtr result = JIT_ResolveVirtualFunctionPointer(ObjectHandleOnStack.Create(ref obj), classHandle, methodHandle);
61+
s_virtualFunctionPointerCache.TrySet(new VirtualResolutionData { MethodTable = RuntimeHelpers.GetMethodTable(obj), ClassHandleTargetMethod = classHandle, MethodHandle = methodHandle }, result);
62+
GC.KeepAlive(obj);
63+
return result;
64+
}
65+
66+
[DebuggerHidden]
67+
private static unsafe IntPtr VirtualFunctionPointer(object obj, IntPtr classHandle, IntPtr methodHandle)
68+
{
69+
if (s_virtualFunctionPointerCache.TryGet(new VirtualResolutionData { MethodTable = RuntimeHelpers.GetMethodTable(obj), ClassHandleTargetMethod = classHandle, MethodHandle = methodHandle }, out IntPtr result))
70+
{
71+
return result;
72+
}
73+
return VirtualFunctionPointerSlowpath(obj, classHandle, methodHandle);
74+
}
75+
76+
[DebuggerHidden]
77+
private static unsafe IntPtr VirtualFunctionPointer_Dynamic(object obj, ref VirtualFunctionPointerArgs virtualFunctionPointerArgs)
78+
{
79+
IntPtr classHandle = virtualFunctionPointerArgs.classHnd;
80+
IntPtr methodHandle = virtualFunctionPointerArgs.methodHnd;
81+
82+
if (s_virtualFunctionPointerCache.TryGet(new VirtualResolutionData { MethodTable = RuntimeHelpers.GetMethodTable(obj), ClassHandleTargetMethod = classHandle, MethodHandle = methodHandle }, out IntPtr result))
83+
{
84+
return result;
85+
}
86+
return VirtualFunctionPointerSlowpath(obj, classHandle, methodHandle);
87+
}
88+
}

src/coreclr/inc/CrstTypes.def

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ End
255255

256256
Crst Interop
257257
AcquiredBefore PinnedHeapHandleTable AvailableParamTypes ClassInit DeadlockDetection GenericDictionaryExpansion
258-
HandleTable InstMethodHashTable InteropData JitGenericHandleCache LoaderHeap SigConvert
258+
HandleTable InstMethodHashTable InteropData LoaderHeap SigConvert
259259
StubDispatchCache StubUnwindInfoHeapSegments SyncBlockCache TypeIDMap UnresolvedClassLock
260260
PendingTypeLoadEntry
261261
End
@@ -276,9 +276,6 @@ Crst Jit
276276
SameLevelAs ClassInit
277277
End
278278

279-
Crst JitGenericHandleCache
280-
End
281-
282279
Crst JitPatchpoint
283280
AcquiredBefore LoaderHeap
284281
End
@@ -446,7 +443,7 @@ End
446443
Crst ThreadStore
447444
AcquiredBefore AvailableParamTypes DeadlockDetection DebuggerController
448445
DebuggerHeapLock DebuggerJitInfo DynamicIL HandleTable IbcProfile
449-
JitGenericHandleCache JumpStubCache LoaderHeap ModuleLookupTable ProfilerGCRefDataFreeList
446+
JumpStubCache LoaderHeap ModuleLookupTable ProfilerGCRefDataFreeList
450447
SingleUseLock SyncBlockCache SystemDomainDelayedUnloadList ThreadIdDispenser DebuggerMutex
451448
JitInlineTrackingMap
452449
End

src/coreclr/inc/crsttypes_generated.h

Lines changed: 56 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -65,63 +65,62 @@ enum CrstType
6565
CrstIsJMCMethod = 47,
6666
CrstISymUnmanagedReader = 48,
6767
CrstJit = 49,
68-
CrstJitGenericHandleCache = 50,
69-
CrstJitInlineTrackingMap = 51,
70-
CrstJitPatchpoint = 52,
71-
CrstJumpStubCache = 53,
72-
CrstLeafLock = 54,
73-
CrstListLock = 55,
74-
CrstLoaderAllocator = 56,
75-
CrstLoaderAllocatorReferences = 57,
76-
CrstLoaderHeap = 58,
77-
CrstManagedObjectWrapperMap = 59,
78-
CrstMethodDescBackpatchInfoTracker = 60,
79-
CrstMethodTableExposedObject = 61,
80-
CrstModule = 62,
81-
CrstModuleLookupTable = 63,
82-
CrstMulticoreJitHash = 64,
83-
CrstMulticoreJitManager = 65,
84-
CrstNativeImageEagerFixups = 66,
85-
CrstNativeImageLoad = 67,
86-
CrstNotifyGdb = 68,
87-
CrstPEImage = 69,
88-
CrstPendingTypeLoadEntry = 70,
89-
CrstPerfMap = 71,
90-
CrstPgoData = 72,
91-
CrstPinnedByrefValidation = 73,
92-
CrstPinnedHeapHandleTable = 74,
93-
CrstProfilerGCRefDataFreeList = 75,
94-
CrstProfilingAPIStatus = 76,
95-
CrstRCWCache = 77,
96-
CrstRCWCleanupList = 78,
97-
CrstReadyToRunEntryPointToMethodDescMap = 79,
98-
CrstReflection = 80,
99-
CrstReJITGlobalRequest = 81,
100-
CrstRetThunkCache = 82,
101-
CrstSigConvert = 83,
102-
CrstSingleUseLock = 84,
103-
CrstStressLog = 85,
104-
CrstStubCache = 86,
105-
CrstStubDispatchCache = 87,
106-
CrstStubUnwindInfoHeapSegments = 88,
107-
CrstSyncBlockCache = 89,
108-
CrstSyncHashLock = 90,
109-
CrstSystemDomain = 91,
110-
CrstSystemDomainDelayedUnloadList = 92,
111-
CrstThreadIdDispenser = 93,
112-
CrstThreadLocalStorageLock = 94,
113-
CrstThreadStore = 95,
114-
CrstTieredCompilation = 96,
115-
CrstTypeEquivalenceMap = 97,
116-
CrstTypeIDMap = 98,
117-
CrstUMEntryThunkCache = 99,
118-
CrstUMEntryThunkFreeListLock = 100,
119-
CrstUniqueStack = 101,
120-
CrstUnresolvedClassLock = 102,
121-
CrstUnwindInfoTableLock = 103,
122-
CrstVSDIndirectionCellLock = 104,
123-
CrstWrapperTemplate = 105,
124-
kNumberOfCrstTypes = 106
68+
CrstJitInlineTrackingMap = 50,
69+
CrstJitPatchpoint = 51,
70+
CrstJumpStubCache = 52,
71+
CrstLeafLock = 53,
72+
CrstListLock = 54,
73+
CrstLoaderAllocator = 55,
74+
CrstLoaderAllocatorReferences = 56,
75+
CrstLoaderHeap = 57,
76+
CrstManagedObjectWrapperMap = 58,
77+
CrstMethodDescBackpatchInfoTracker = 59,
78+
CrstMethodTableExposedObject = 60,
79+
CrstModule = 61,
80+
CrstModuleLookupTable = 62,
81+
CrstMulticoreJitHash = 63,
82+
CrstMulticoreJitManager = 64,
83+
CrstNativeImageEagerFixups = 65,
84+
CrstNativeImageLoad = 66,
85+
CrstNotifyGdb = 67,
86+
CrstPEImage = 68,
87+
CrstPendingTypeLoadEntry = 69,
88+
CrstPerfMap = 70,
89+
CrstPgoData = 71,
90+
CrstPinnedByrefValidation = 72,
91+
CrstPinnedHeapHandleTable = 73,
92+
CrstProfilerGCRefDataFreeList = 74,
93+
CrstProfilingAPIStatus = 75,
94+
CrstRCWCache = 76,
95+
CrstRCWCleanupList = 77,
96+
CrstReadyToRunEntryPointToMethodDescMap = 78,
97+
CrstReflection = 79,
98+
CrstReJITGlobalRequest = 80,
99+
CrstRetThunkCache = 81,
100+
CrstSigConvert = 82,
101+
CrstSingleUseLock = 83,
102+
CrstStressLog = 84,
103+
CrstStubCache = 85,
104+
CrstStubDispatchCache = 86,
105+
CrstStubUnwindInfoHeapSegments = 87,
106+
CrstSyncBlockCache = 88,
107+
CrstSyncHashLock = 89,
108+
CrstSystemDomain = 90,
109+
CrstSystemDomainDelayedUnloadList = 91,
110+
CrstThreadIdDispenser = 92,
111+
CrstThreadLocalStorageLock = 93,
112+
CrstThreadStore = 94,
113+
CrstTieredCompilation = 95,
114+
CrstTypeEquivalenceMap = 96,
115+
CrstTypeIDMap = 97,
116+
CrstUMEntryThunkCache = 98,
117+
CrstUMEntryThunkFreeListLock = 99,
118+
CrstUniqueStack = 100,
119+
CrstUnresolvedClassLock = 101,
120+
CrstUnwindInfoTableLock = 102,
121+
CrstVSDIndirectionCellLock = 103,
122+
CrstWrapperTemplate = 104,
123+
kNumberOfCrstTypes = 105
125124
};
126125

127126
#endif // __CRST_TYPES_INCLUDED
@@ -182,7 +181,6 @@ int g_rgCrstLevelMap[] =
182181
0, // CrstIsJMCMethod
183182
6, // CrstISymUnmanagedReader
184183
10, // CrstJit
185-
0, // CrstJitGenericHandleCache
186184
11, // CrstJitInlineTrackingMap
187185
3, // CrstJitPatchpoint
188186
5, // CrstJumpStubCache
@@ -293,7 +291,6 @@ LPCSTR g_rgCrstNameMap[] =
293291
"CrstIsJMCMethod",
294292
"CrstISymUnmanagedReader",
295293
"CrstJit",
296-
"CrstJitGenericHandleCache",
297294
"CrstJitInlineTrackingMap",
298295
"CrstJitPatchpoint",
299296
"CrstJumpStubCache",

src/coreclr/inc/jithelpers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@
234234
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE, JIT_GetRuntimeType, METHOD__NIL)
235235
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL, JIT_GetRuntimeType_MaybeNull, METHOD__NIL)
236236

237-
JITHELPER(CORINFO_HELP_VIRTUAL_FUNC_PTR, JIT_VirtualFunctionPointer, METHOD__NIL)
237+
DYNAMICJITHELPER(CORINFO_HELP_VIRTUAL_FUNC_PTR, NULL, METHOD__VIRTUALDISPATCHHELPERS__VIRTUALFUNCTIONPOINTER)
238238

239239
JITHELPER(CORINFO_HELP_READYTORUN_NEW, NULL, METHOD__NIL)
240240
JITHELPER(CORINFO_HELP_READYTORUN_NEWARR_1, NULL, METHOD__NIL)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
4+
/*============================================================
5+
**
6+
** Header: VirtualFunctionHelpers.h
7+
**
8+
**
9+
===========================================================*/
10+
11+
#ifndef _VIRTUALFUNCTIONHELPERS_H
12+
#define _VIRTUALFUNCTIONHELPERS_H
13+
14+
#include "qcall.h"
15+
#include "corinfo.h"
16+
17+
extern "C" void * QCALLTYPE JIT_ResolveVirtualFunctionPointer(QCall::ObjectHandleOnStack obj, CORINFO_CLASS_HANDLE classHnd, CORINFO_METHOD_HANDLE methodHnd);
18+
19+
#endif //_VIRTUALFUNCTIONHELPERS_H

src/coreclr/vm/corelib.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,10 @@ DEFINE_METHOD(CASTHELPERS, STELEMREF, StelemRef, SM_Arr
11561156
DEFINE_METHOD(CASTHELPERS, LDELEMAREF, LdelemaRef, SM_ArrObject_IntPtr_PtrVoid_RetRefObj)
11571157
DEFINE_METHOD(CASTHELPERS, ARRAYTYPECHECK, ArrayTypeCheck, SM_Obj_Array_RetVoid)
11581158

1159+
DEFINE_CLASS(VIRTUALDISPATCHHELPERS, CompilerServices, VirtualDispatchHelpers)
1160+
DEFINE_METHOD(VIRTUALDISPATCHHELPERS, VIRTUALFUNCTIONPOINTER, VirtualFunctionPointer, NoSig)
1161+
DEFINE_METHOD(VIRTUALDISPATCHHELPERS, VIRTUALFUNCTIONPOINTER_DYNAMIC, VirtualFunctionPointer_Dynamic, NoSig)
1162+
11591163
#ifdef FEATURE_EH_FUNCLETS
11601164
DEFINE_CLASS(EH, Runtime, EH)
11611165
DEFINE_METHOD(EH, RH_THROW_EX, RhThrowEx, SM_Obj_RefExInfo_RetVoid)

src/coreclr/vm/genericdict.cpp

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,15 +280,13 @@ DictionaryLayout* DictionaryLayout::ExpandDictionaryLayout(LoaderAllocator*
280280

281281
#ifdef _DEBUG
282282
// Stress debug mode by increasing size by only 1 slot for the first 10 slots.
283-
DWORD newSize = pCurrentDictLayout->m_numSlots > 10 ? (DWORD)pCurrentDictLayout->m_numSlots * 2 : pCurrentDictLayout->m_numSlots + 1;
284-
if (!FitsIn<WORD>(newSize))
285-
return NULL;
286-
DictionaryLayout* pNewDictionaryLayout = Allocate((WORD)newSize, pAllocator, NULL);
283+
DWORD newSize = pCurrentDictLayout->m_numSlots > 10 ? ((DWORD)pCurrentDictLayout->m_numSlots) * 2 : pCurrentDictLayout->m_numSlots + 1;
287284
#else
288-
if (!FitsIn<WORD>((DWORD)pCurrentDictLayout->m_numSlots * 2))
289-
return NULL;
290-
DictionaryLayout* pNewDictionaryLayout = Allocate(pCurrentDictLayout->m_numSlots * 2, pAllocator, NULL);
285+
DWORD newSize = ((DWORD)pCurrentDictLayout->m_numSlots) * 2;
291286
#endif
287+
if (!FitsIn<WORD>(newSize + static_cast<WORD>(numGenericArgs)))
288+
return NULL;
289+
DictionaryLayout* pNewDictionaryLayout = Allocate((WORD)newSize, pAllocator, NULL);
292290

293291
pNewDictionaryLayout->m_numInitialSlots = pCurrentDictLayout->m_numInitialSlots;
294292

@@ -1062,10 +1060,6 @@ Dictionary::PopulateEntry(
10621060
// We indirect through a cell so that updates can take place atomically.
10631061
// The call stub and the indirection cell have the same lifetime as the dictionary itself, i.e.
10641062
// are allocated in the domain of the dicitonary.
1065-
//
1066-
// In the case of overflow (where there is no dictionary, just a global hash table) then
1067-
// the entry will be placed in the overflow hash table (JitGenericHandleCache). This
1068-
// is partitioned according to domain, i.e. is scraped each time an AppDomain gets unloaded.
10691063
PCODE addr = pMgr->GetCallStub(ownerType, methodSlot);
10701064

10711065
result = (CORINFO_GENERIC_HANDLE)pMgr->GenerateStubIndirection(addr);
@@ -1284,6 +1278,8 @@ Dictionary::PopulateEntry(
12841278

12851279
MemoryBarrier();
12861280

1281+
_ASSERTE(slotIndex != 0); // Technically this assert is invalid, but it will only happen if growing the dictionary layout attempts to grow beyond the capacity
1282+
// of a 16 bit unsigned integer. This is highly unlikely to happen in practice, but possible, and will result in extremely degraded performance.
12871283
if (slotIndex != 0)
12881284
{
12891285
Dictionary* pDictionary;

0 commit comments

Comments
 (0)