Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove JitGenericHandleCache #106843

Merged
merged 12 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\TypeNameResolver.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\RuntimeTypeMetadataUpdateHandler.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\GenericsHelpers.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
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.Runtime.CompilerServices;

[StackTraceHidden]
[DebuggerStepThrough]
internal static unsafe partial class GenericsHelpers
{
[LibraryImport(RuntimeHelpers.QCall)]
private static partial IntPtr GenericHandleWorker(IntPtr pMD, IntPtr pMT, IntPtr signature, uint dictionaryIndexAndSlot, IntPtr pModule);

public struct GenericHandleArgs
{
public IntPtr signature;
public IntPtr module;
public uint dictionaryIndexAndSlot;
};

[DebuggerHidden]
public static IntPtr Method(IntPtr methodHnd, IntPtr signature)
{
return GenericHandleWorker(methodHnd, IntPtr.Zero, signature, 0xFFFFFFFF, IntPtr.Zero);
}

[DebuggerHidden]
public static unsafe IntPtr MethodWithSlotAndModule(IntPtr methodHnd, GenericHandleArgs * pArgs)
{
return GenericHandleWorker(methodHnd, IntPtr.Zero, pArgs->signature, pArgs->dictionaryIndexAndSlot, pArgs->module);
}

[DebuggerHidden]
public static IntPtr Class(IntPtr classHnd, IntPtr signature)
{
return GenericHandleWorker(IntPtr.Zero, classHnd, signature, 0xFFFFFFFF, IntPtr.Zero);
}

[DebuggerHidden]
public static unsafe IntPtr ClassWithSlotAndModule(IntPtr classHnd, GenericHandleArgs * pArgs)
{
return GenericHandleWorker(IntPtr.Zero, classHnd, pArgs->signature, pArgs->dictionaryIndexAndSlot, pArgs->module);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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 int _hashCode;
public MethodTable* _objectMethodTable;
public IntPtr _classHandle;
public IntPtr _methodHandle;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public VirtualResolutionData(MethodTable* objectMethodTable, IntPtr classHandle, IntPtr methodHandle)
{
_hashCode = (int) ((uint)objectMethodTable + (BitOperations.RotateLeft((uint)classHandle, 5)) + (BitOperations.RotateRight((uint)methodHandle, 5)));
_objectMethodTable = objectMethodTable;
_classHandle = classHandle;
_methodHandle = methodHandle;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(VirtualResolutionData other) =>
_hashCode == other._hashCode &&
(((nint)_objectMethodTable - (nint)other._objectMethodTable) |
(_classHandle - other._classHandle) |
(_methodHandle - other._methodHandle)) == 0;

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

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => _hashCode;
}

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);

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

[MethodImpl(MethodImplOptions.NoInlining)]
[DebuggerHidden]
private static unsafe IntPtr VirtualFunctionPointerSlow(object obj, IntPtr classHandle, IntPtr methodHandle)
{
IntPtr result = ResolveVirtualFunctionPointer(ObjectHandleOnStack.Create(ref obj), classHandle, methodHandle);
s_virtualFunctionPointerCache.TrySet(new VirtualResolutionData(RuntimeHelpers.GetMethodTable(obj), classHandle, methodHandle), result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks a bit suspect that we are passing obj into the resolve worker, but then only use RuntimeHelpers.GetMethodTable(obj) as the cache key. Do we have correct behavior for dynamic castable here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NativeAOT follows this rule around not caching results (for interface dispatch, but appears to have the same problem for virtual function resolution) CoreCLR does not appear to me to have logic in its dispatch behavior which avoids caching. Possibly we intended to build it, but I'm pretty sure we didn't. I can build a test case to validate my expectation if you'd like. The performance of the existing IDynamicInterfaceCastable implementation would likely be highly impacted if we change the rules here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result of this is a useable model, although it's not well defined in behavior. The result is that if any type uses IDynamicInterfaceCastable ... the implementor of IDynamicInterfaceCastable.GetInterfaceImplementaiton really should provide an idempotent implementation for any case where it will successfully return an implementation, or they may have surprising behavior. However, we DO have the correct cache avoidance at cast time. This means that users of types which use IDynamicInterfaceCastable experience the expected behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NativeAOT follows this rule around not caching results (for interface dispatch

We may want to fix NativeAOT to match CoreCLR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I've opened #107999 to track this issue

GC.KeepAlive(obj);
return result;
}

[DebuggerHidden]
private static unsafe IntPtr VirtualFunctionPointer(object obj, IntPtr classHandle, IntPtr methodHandle)
{
if (s_virtualFunctionPointerCache.TryGet(new VirtualResolutionData(RuntimeHelpers.GetMethodTable(obj), classHandle, methodHandle), out IntPtr result))
{
return result;
}
return VirtualFunctionPointerSlow(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(RuntimeHelpers.GetMethodTable(obj), classHandle, methodHandle), out IntPtr result))
{
return result;
}
return VirtualFunctionPointerSlow(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
6 changes: 3 additions & 3 deletions src/coreclr/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,16 @@
JITHELPER(CORINFO_HELP_NATIVE_MEMSET, Jit_NativeMemSet, METHOD__NIL)

// Generics
JITHELPER(CORINFO_HELP_RUNTIMEHANDLE_METHOD, JIT_GenericHandleMethod, METHOD__NIL)
JITHELPER(CORINFO_HELP_RUNTIMEHANDLE_CLASS, JIT_GenericHandleClass, METHOD__NIL)
DYNAMICJITHELPER(CORINFO_HELP_RUNTIMEHANDLE_METHOD, NULL, METHOD__GENERICSHELPERS__METHOD)
DYNAMICJITHELPER(CORINFO_HELP_RUNTIMEHANDLE_CLASS, NULL, METHOD__GENERICSHELPERS__CLASS)
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE, JIT_GetRuntimeType, METHOD__NIL)
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL, JIT_GetRuntimeType_MaybeNull, METHOD__NIL)
JITHELPER(CORINFO_HELP_METHODDESC_TO_STUBRUNTIMEMETHOD, JIT_GetRuntimeMethodStub,METHOD__NIL)
JITHELPER(CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD, JIT_GetRuntimeFieldStub, METHOD__NIL)
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
24 changes: 24 additions & 0 deletions src/coreclr/vm/JitQCallHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/*============================================================
**
** Header: JitQCallHelpers.h
**
**
===========================================================*/

#ifndef _JITQCALLHELPERS_H
#define _JITQCALLHELPERS_H

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

class Module;
class MethodTable;
class MethodDesc;

extern "C" void * QCALLTYPE ResolveVirtualFunctionPointer(QCall::ObjectHandleOnStack obj, CORINFO_CLASS_HANDLE classHnd, CORINFO_METHOD_HANDLE methodHnd);
extern "C" CORINFO_GENERIC_HANDLE QCALLTYPE GenericHandleWorker(MethodDesc * pMD, MethodTable * pMT, LPVOID signature, DWORD dictionaryIndexAndSlot, Module* pModule);

#endif //_JITQCALLHELPERS_H
4 changes: 1 addition & 3 deletions src/coreclr/vm/amd64/cgenamd64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -992,9 +992,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
{
STANDARD_VM_CONTRACT;

PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ?
GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) :
GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule));
PCODE helperAddress = GetDictionaryLookupHelper(pLookup->helper);

GenericHandleArgs * pArgs = (GenericHandleArgs *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(sizeof(GenericHandleArgs), DYNAMIC_HELPER_ALIGNMENT);
ExecutableWriterHolder<GenericHandleArgs> argsWriterHolder(pArgs, sizeof(GenericHandleArgs));
Expand Down
4 changes: 1 addition & 3 deletions src/coreclr/vm/arm/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2018,9 +2018,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
{
STANDARD_VM_CONTRACT;

PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ?
GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) :
GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule));
PCODE helperAddress = GetDictionaryLookupHelper(pLookup->helper);

GenericHandleArgs * pArgs = (GenericHandleArgs *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(sizeof(GenericHandleArgs), DYNAMIC_HELPER_ALIGNMENT);
ExecutableWriterHolder<GenericHandleArgs> argsWriterHolder(pArgs, sizeof(GenericHandleArgs));
Expand Down
4 changes: 1 addition & 3 deletions src/coreclr/vm/arm64/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1946,9 +1946,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
{
STANDARD_VM_CONTRACT;

PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ?
GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) :
GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule));
PCODE helperAddress = GetDictionaryLookupHelper(pLookup->helper);

GenericHandleArgs * pArgs = (GenericHandleArgs *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(sizeof(GenericHandleArgs), DYNAMIC_HELPER_ALIGNMENT);
ExecutableWriterHolder<GenericHandleArgs> argsWriterHolder(pArgs, sizeof(GenericHandleArgs));
Expand Down
Loading