Skip to content

Commit

Permalink
Remove JitGenericHandleCache
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
davidwrighton committed Aug 26, 2024
1 parent 56435ad commit 7de3762
Show file tree
Hide file tree
Showing 15 changed files with 195 additions and 374 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\RuntimeTypeMetadataUpdateHandler.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\VirtualDispatchHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Common\src\System\Runtime\RhFailFastReason.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ internal sealed partial class LoaderAllocatorScout
if (m_nativeLoaderAllocator == IntPtr.Zero)
return;

VirtualDispatchHelpers.ClearCache();

// Destroy returns false if the managed LoaderAllocator is still alive.
if (!Destroy(m_nativeLoaderAllocator))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.InteropServices;

namespace System.Runtime.CompilerServices;

[StackTraceHidden]
[DebuggerStepThrough]
internal static unsafe partial class VirtualDispatchHelpers
{
private struct VirtualResolutionData : IEquatable<VirtualResolutionData>
{
public MethodTable* MethodTable;
public IntPtr ClassHandleTargetMethod;
public IntPtr MethodHandle;

public bool Equals(VirtualResolutionData other) =>
MethodTable == other.MethodTable &&
ClassHandleTargetMethod == other.ClassHandleTargetMethod &&
MethodHandle == other.MethodHandle;

public override bool Equals(object? obj) => obj is VirtualResolutionData other && Equals(other);

public override int GetHashCode() => (int) ((uint)MethodTable ^ (BitOperations.RotateLeft((uint)ClassHandleTargetMethod, 5)) ^ (BitOperations.RotateRight((uint)MethodHandle, 5)));
}

private struct VirtualFunctionPointerArgs
{
public IntPtr classHnd;
public IntPtr methodHnd;
};

#if DEBUG
// use smaller numbers to hit resizing/preempting logic in debug
private const int InitialCacheSize = 8; // MUST BE A POWER OF TWO
private const int MaximumCacheSize = 512;
#else
private const int InitialCacheSize = 128; // MUST BE A POWER OF TWO
private const int MaximumCacheSize = 8 * 1024;
#endif // DEBUG

private static GenericCache<VirtualResolutionData, IntPtr> s_virtualFunctionPointerCache = new GenericCache<VirtualResolutionData, IntPtr>(InitialCacheSize, MaximumCacheSize);

internal static void ClearCache()
{
s_virtualFunctionPointerCache.FlushCurrentCache();
}

[LibraryImport(RuntimeHelpers.QCall)]
private static unsafe partial IntPtr JIT_ResolveVirtualFunctionPointer(ObjectHandleOnStack obj, IntPtr classHandle, IntPtr methodHandle);

[MethodImpl(MethodImplOptions.NoInlining)]
[DebuggerHidden]
private static unsafe IntPtr VirtualFunctionPointerSlowpath(object obj, IntPtr classHandle, IntPtr methodHandle)
{
IntPtr result = JIT_ResolveVirtualFunctionPointer(ObjectHandleOnStack.Create(ref obj), classHandle, methodHandle);
s_virtualFunctionPointerCache.TrySet(new VirtualResolutionData { MethodTable = RuntimeHelpers.GetMethodTable(obj), ClassHandleTargetMethod = classHandle, MethodHandle = methodHandle }, result);
GC.KeepAlive(obj);
return result;
}

[DebuggerHidden]
private static unsafe IntPtr VirtualFunctionPointer(object obj, IntPtr classHandle, IntPtr methodHandle)
{
if (s_virtualFunctionPointerCache.TryGet(new VirtualResolutionData { MethodTable = RuntimeHelpers.GetMethodTable(obj), ClassHandleTargetMethod = classHandle, MethodHandle = methodHandle }, out IntPtr result))
{
return result;
}
return VirtualFunctionPointerSlowpath(obj, classHandle, methodHandle);
}

[DebuggerHidden]
private static unsafe IntPtr VirtualFunctionPointer_Dynamic(object obj, ref VirtualFunctionPointerArgs virtualFunctionPointerArgs)
{
IntPtr classHandle = virtualFunctionPointerArgs.classHnd;
IntPtr methodHandle = virtualFunctionPointerArgs.methodHnd;

if (s_virtualFunctionPointerCache.TryGet(new VirtualResolutionData { MethodTable = RuntimeHelpers.GetMethodTable(obj), ClassHandleTargetMethod = classHandle, MethodHandle = methodHandle }, out IntPtr result))
{
return result;
}
return VirtualFunctionPointerSlowpath(obj, classHandle, methodHandle);
}
}
7 changes: 2 additions & 5 deletions src/coreclr/inc/CrstTypes.def
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ End

Crst Interop
AcquiredBefore PinnedHeapHandleTable AvailableParamTypes ClassInit DeadlockDetection GenericDictionaryExpansion
HandleTable InstMethodHashTable InteropData JitGenericHandleCache LoaderHeap SigConvert
HandleTable InstMethodHashTable InteropData LoaderHeap SigConvert
StubDispatchCache StubUnwindInfoHeapSegments SyncBlockCache TypeIDMap UnresolvedClassLock
PendingTypeLoadEntry
End
Expand All @@ -276,9 +276,6 @@ Crst Jit
SameLevelAs ClassInit
End

Crst JitGenericHandleCache
End

Crst JitPatchpoint
AcquiredBefore LoaderHeap
End
Expand Down Expand Up @@ -446,7 +443,7 @@ End
Crst ThreadStore
AcquiredBefore AvailableParamTypes DeadlockDetection DebuggerController
DebuggerHeapLock DebuggerJitInfo DynamicIL HandleTable IbcProfile
JitGenericHandleCache JumpStubCache LoaderHeap ModuleLookupTable ProfilerGCRefDataFreeList
JumpStubCache LoaderHeap ModuleLookupTable ProfilerGCRefDataFreeList
SingleUseLock SyncBlockCache SystemDomainDelayedUnloadList ThreadIdDispenser DebuggerMutex
JitInlineTrackingMap
End
Expand Down
115 changes: 56 additions & 59 deletions src/coreclr/inc/crsttypes_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,63 +65,62 @@ enum CrstType
CrstIsJMCMethod = 47,
CrstISymUnmanagedReader = 48,
CrstJit = 49,
CrstJitGenericHandleCache = 50,
CrstJitInlineTrackingMap = 51,
CrstJitPatchpoint = 52,
CrstJumpStubCache = 53,
CrstLeafLock = 54,
CrstListLock = 55,
CrstLoaderAllocator = 56,
CrstLoaderAllocatorReferences = 57,
CrstLoaderHeap = 58,
CrstManagedObjectWrapperMap = 59,
CrstMethodDescBackpatchInfoTracker = 60,
CrstMethodTableExposedObject = 61,
CrstModule = 62,
CrstModuleLookupTable = 63,
CrstMulticoreJitHash = 64,
CrstMulticoreJitManager = 65,
CrstNativeImageEagerFixups = 66,
CrstNativeImageLoad = 67,
CrstNotifyGdb = 68,
CrstPEImage = 69,
CrstPendingTypeLoadEntry = 70,
CrstPerfMap = 71,
CrstPgoData = 72,
CrstPinnedByrefValidation = 73,
CrstPinnedHeapHandleTable = 74,
CrstProfilerGCRefDataFreeList = 75,
CrstProfilingAPIStatus = 76,
CrstRCWCache = 77,
CrstRCWCleanupList = 78,
CrstReadyToRunEntryPointToMethodDescMap = 79,
CrstReflection = 80,
CrstReJITGlobalRequest = 81,
CrstRetThunkCache = 82,
CrstSigConvert = 83,
CrstSingleUseLock = 84,
CrstStressLog = 85,
CrstStubCache = 86,
CrstStubDispatchCache = 87,
CrstStubUnwindInfoHeapSegments = 88,
CrstSyncBlockCache = 89,
CrstSyncHashLock = 90,
CrstSystemDomain = 91,
CrstSystemDomainDelayedUnloadList = 92,
CrstThreadIdDispenser = 93,
CrstThreadLocalStorageLock = 94,
CrstThreadStore = 95,
CrstTieredCompilation = 96,
CrstTypeEquivalenceMap = 97,
CrstTypeIDMap = 98,
CrstUMEntryThunkCache = 99,
CrstUMEntryThunkFreeListLock = 100,
CrstUniqueStack = 101,
CrstUnresolvedClassLock = 102,
CrstUnwindInfoTableLock = 103,
CrstVSDIndirectionCellLock = 104,
CrstWrapperTemplate = 105,
kNumberOfCrstTypes = 106
CrstJitInlineTrackingMap = 50,
CrstJitPatchpoint = 51,
CrstJumpStubCache = 52,
CrstLeafLock = 53,
CrstListLock = 54,
CrstLoaderAllocator = 55,
CrstLoaderAllocatorReferences = 56,
CrstLoaderHeap = 57,
CrstManagedObjectWrapperMap = 58,
CrstMethodDescBackpatchInfoTracker = 59,
CrstMethodTableExposedObject = 60,
CrstModule = 61,
CrstModuleLookupTable = 62,
CrstMulticoreJitHash = 63,
CrstMulticoreJitManager = 64,
CrstNativeImageEagerFixups = 65,
CrstNativeImageLoad = 66,
CrstNotifyGdb = 67,
CrstPEImage = 68,
CrstPendingTypeLoadEntry = 69,
CrstPerfMap = 70,
CrstPgoData = 71,
CrstPinnedByrefValidation = 72,
CrstPinnedHeapHandleTable = 73,
CrstProfilerGCRefDataFreeList = 74,
CrstProfilingAPIStatus = 75,
CrstRCWCache = 76,
CrstRCWCleanupList = 77,
CrstReadyToRunEntryPointToMethodDescMap = 78,
CrstReflection = 79,
CrstReJITGlobalRequest = 80,
CrstRetThunkCache = 81,
CrstSigConvert = 82,
CrstSingleUseLock = 83,
CrstStressLog = 84,
CrstStubCache = 85,
CrstStubDispatchCache = 86,
CrstStubUnwindInfoHeapSegments = 87,
CrstSyncBlockCache = 88,
CrstSyncHashLock = 89,
CrstSystemDomain = 90,
CrstSystemDomainDelayedUnloadList = 91,
CrstThreadIdDispenser = 92,
CrstThreadLocalStorageLock = 93,
CrstThreadStore = 94,
CrstTieredCompilation = 95,
CrstTypeEquivalenceMap = 96,
CrstTypeIDMap = 97,
CrstUMEntryThunkCache = 98,
CrstUMEntryThunkFreeListLock = 99,
CrstUniqueStack = 100,
CrstUnresolvedClassLock = 101,
CrstUnwindInfoTableLock = 102,
CrstVSDIndirectionCellLock = 103,
CrstWrapperTemplate = 104,
kNumberOfCrstTypes = 105
};

#endif // __CRST_TYPES_INCLUDED
Expand Down Expand Up @@ -182,7 +181,6 @@ int g_rgCrstLevelMap[] =
0, // CrstIsJMCMethod
6, // CrstISymUnmanagedReader
10, // CrstJit
0, // CrstJitGenericHandleCache
11, // CrstJitInlineTrackingMap
3, // CrstJitPatchpoint
5, // CrstJumpStubCache
Expand Down Expand Up @@ -293,7 +291,6 @@ LPCSTR g_rgCrstNameMap[] =
"CrstIsJMCMethod",
"CrstISymUnmanagedReader",
"CrstJit",
"CrstJitGenericHandleCache",
"CrstJitInlineTrackingMap",
"CrstJitPatchpoint",
"CrstJumpStubCache",
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE, JIT_GetRuntimeType, METHOD__NIL)
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL, JIT_GetRuntimeType_MaybeNull, METHOD__NIL)

JITHELPER(CORINFO_HELP_VIRTUAL_FUNC_PTR, JIT_VirtualFunctionPointer, METHOD__NIL)
DYNAMICJITHELPER(CORINFO_HELP_VIRTUAL_FUNC_PTR, NULL, METHOD__VIRTUALDISPATCHHELPERS__VIRTUALFUNCTIONPOINTER)

JITHELPER(CORINFO_HELP_READYTORUN_NEW, NULL, METHOD__NIL)
JITHELPER(CORINFO_HELP_READYTORUN_NEWARR_1, NULL, METHOD__NIL)
Expand Down
19 changes: 19 additions & 0 deletions src/coreclr/vm/VirtualFunctionHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/*============================================================
**
** Header: VirtualFunctionHelpers.h
**
**
===========================================================*/

#ifndef _VIRTUALFUNCTIONHELPERS_H
#define _VIRTUALFUNCTIONHELPERS_H

#include "qcall.h"
#include "corinfo.h"

extern "C" void * QCALLTYPE JIT_ResolveVirtualFunctionPointer(QCall::ObjectHandleOnStack obj, CORINFO_CLASS_HANDLE classHnd, CORINFO_METHOD_HANDLE methodHnd);

#endif //_VIRTUALFUNCTIONHELPERS_H
4 changes: 4 additions & 0 deletions src/coreclr/vm/corelib.h
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,10 @@ DEFINE_METHOD(CASTHELPERS, STELEMREF, StelemRef, SM_Arr
DEFINE_METHOD(CASTHELPERS, LDELEMAREF, LdelemaRef, SM_ArrObject_IntPtr_PtrVoid_RetRefObj)
DEFINE_METHOD(CASTHELPERS, ARRAYTYPECHECK, ArrayTypeCheck, SM_Obj_Array_RetVoid)

DEFINE_CLASS(VIRTUALDISPATCHHELPERS, CompilerServices, VirtualDispatchHelpers)
DEFINE_METHOD(VIRTUALDISPATCHHELPERS, VIRTUALFUNCTIONPOINTER, VirtualFunctionPointer, NoSig)
DEFINE_METHOD(VIRTUALDISPATCHHELPERS, VIRTUALFUNCTIONPOINTER_DYNAMIC, VirtualFunctionPointer_Dynamic, NoSig)

#ifdef FEATURE_EH_FUNCLETS
DEFINE_CLASS(EH, Runtime, EH)
DEFINE_METHOD(EH, RH_THROW_EX, RhThrowEx, SM_Obj_RefExInfo_RetVoid)
Expand Down
18 changes: 7 additions & 11 deletions src/coreclr/vm/genericdict.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,15 +280,13 @@ DictionaryLayout* DictionaryLayout::ExpandDictionaryLayout(LoaderAllocator*

#ifdef _DEBUG
// Stress debug mode by increasing size by only 1 slot for the first 10 slots.
DWORD newSize = pCurrentDictLayout->m_numSlots > 10 ? (DWORD)pCurrentDictLayout->m_numSlots * 2 : pCurrentDictLayout->m_numSlots + 1;
if (!FitsIn<WORD>(newSize))
return NULL;
DictionaryLayout* pNewDictionaryLayout = Allocate((WORD)newSize, pAllocator, NULL);
DWORD newSize = pCurrentDictLayout->m_numSlots > 10 ? ((DWORD)pCurrentDictLayout->m_numSlots) * 2 : pCurrentDictLayout->m_numSlots + 1;
#else
if (!FitsIn<WORD>((DWORD)pCurrentDictLayout->m_numSlots * 2))
return NULL;
DictionaryLayout* pNewDictionaryLayout = Allocate(pCurrentDictLayout->m_numSlots * 2, pAllocator, NULL);
DWORD newSize = ((DWORD)pCurrentDictLayout->m_numSlots) * 2;
#endif
if (!FitsIn<WORD>(newSize + static_cast<WORD>(numGenericArgs)))
return NULL;
DictionaryLayout* pNewDictionaryLayout = Allocate((WORD)newSize, pAllocator, NULL);

pNewDictionaryLayout->m_numInitialSlots = pCurrentDictLayout->m_numInitialSlots;

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

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

MemoryBarrier();

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

0 comments on commit 7de3762

Please sign in to comment.