From 128fdfd1fd982e277e969d5c7ca1710345f4f649 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 18 Sep 2024 16:52:02 -0700 Subject: [PATCH] Remove JitGenericHandleCache (#106843) - 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 - Use the GenericCache type which was previously only used on NativeAOT for Generic Virtual method resolution paths. - Just remove the pointless checks from within the various normal generic lookup paths, and since they no longer do anything interesting in their hot paths except erect a helper method frame and call a worker routine, move those jit helpers to managed as well. --- .../System.Private.CoreLib.csproj | 2 + .../CompilerServices/GenericsHelpers.cs | 47 ++ .../VirtualDispatchHelpers.cs | 96 ++++ src/coreclr/inc/CrstTypes.def | 7 +- src/coreclr/inc/crsttypes_generated.h | 115 +++-- src/coreclr/inc/jithelpers.h | 6 +- src/coreclr/vm/JitQCallHelpers.h | 24 + src/coreclr/vm/amd64/cgenamd64.cpp | 4 +- src/coreclr/vm/arm/stubs.cpp | 4 +- src/coreclr/vm/arm64/stubs.cpp | 4 +- src/coreclr/vm/binder.cpp | 32 +- src/coreclr/vm/corelib.h | 31 ++ src/coreclr/vm/genericdict.cpp | 18 +- src/coreclr/vm/i386/cgenx86.cpp | 4 +- src/coreclr/vm/jithelpers.cpp | 473 ++++-------------- src/coreclr/vm/jitinterface.h | 15 +- src/coreclr/vm/loaderallocator.cpp | 4 +- src/coreclr/vm/loongarch64/stubs.cpp | 4 +- src/coreclr/vm/method.cpp | 1 + src/coreclr/vm/object.cpp | 29 ++ src/coreclr/vm/object.h | 58 +++ src/coreclr/vm/prestub.cpp | 5 +- src/coreclr/vm/qcallentrypoints.cpp | 3 + src/coreclr/vm/readytoruninfo.cpp | 32 ++ src/coreclr/vm/readytoruninfo.h | 2 + src/coreclr/vm/riscv64/stubs.cpp | 4 +- .../Runtime/CompilerServices/GenericCache.cs | 14 +- src/tests/readytorun/tests/main.cs | 21 + src/tests/readytorun/tests/test.cs | 25 + 29 files changed, 582 insertions(+), 502 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericsHelpers.cs create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/VirtualDispatchHelpers.cs create mode 100644 src/coreclr/vm/JitQCallHelpers.h diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index e027d8d7d757a..57b52c6513924 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -196,7 +196,9 @@ + + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericsHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericsHelpers.cs new file mode 100644 index 0000000000000..aeba3370b3c0b --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericsHelpers.cs @@ -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); + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/VirtualDispatchHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/VirtualDispatchHelpers.cs new file mode 100644 index 0000000000000..131a39d4a816b --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/VirtualDispatchHelpers.cs @@ -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 + { + 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 s_virtualFunctionPointerCache = new GenericCache(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); + 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); + } +} diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index ccf226105e9f3..0e45e17285ed6 100644 --- a/src/coreclr/inc/CrstTypes.def +++ b/src/coreclr/inc/CrstTypes.def @@ -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 @@ -276,9 +276,6 @@ Crst Jit SameLevelAs ClassInit End -Crst JitGenericHandleCache -End - Crst JitPatchpoint AcquiredBefore LoaderHeap End @@ -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 diff --git a/src/coreclr/inc/crsttypes_generated.h b/src/coreclr/inc/crsttypes_generated.h index 9db4f694103cd..1d7fa67870104 100644 --- a/src/coreclr/inc/crsttypes_generated.h +++ b/src/coreclr/inc/crsttypes_generated.h @@ -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 @@ -182,7 +181,6 @@ int g_rgCrstLevelMap[] = 0, // CrstIsJMCMethod 6, // CrstISymUnmanagedReader 10, // CrstJit - 0, // CrstJitGenericHandleCache 11, // CrstJitInlineTrackingMap 3, // CrstJitPatchpoint 5, // CrstJumpStubCache @@ -293,7 +291,6 @@ LPCSTR g_rgCrstNameMap[] = "CrstIsJMCMethod", "CrstISymUnmanagedReader", "CrstJit", - "CrstJitGenericHandleCache", "CrstJitInlineTrackingMap", "CrstJitPatchpoint", "CrstJumpStubCache", diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 66999d89d82f6..1c89a6e70ff7b 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -221,8 +221,8 @@ 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) @@ -230,7 +230,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) diff --git a/src/coreclr/vm/JitQCallHelpers.h b/src/coreclr/vm/JitQCallHelpers.h new file mode 100644 index 0000000000000..191a5746a802a --- /dev/null +++ b/src/coreclr/vm/JitQCallHelpers.h @@ -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 diff --git a/src/coreclr/vm/amd64/cgenamd64.cpp b/src/coreclr/vm/amd64/cgenamd64.cpp index a14a1eed650fb..a365d0d662804 100644 --- a/src/coreclr/vm/amd64/cgenamd64.cpp +++ b/src/coreclr/vm/amd64/cgenamd64.cpp @@ -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 argsWriterHolder(pArgs, sizeof(GenericHandleArgs)); diff --git a/src/coreclr/vm/arm/stubs.cpp b/src/coreclr/vm/arm/stubs.cpp index 9589cff63d401..18ccde9888fb7 100644 --- a/src/coreclr/vm/arm/stubs.cpp +++ b/src/coreclr/vm/arm/stubs.cpp @@ -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 argsWriterHolder(pArgs, sizeof(GenericHandleArgs)); diff --git a/src/coreclr/vm/arm64/stubs.cpp b/src/coreclr/vm/arm64/stubs.cpp index 7d1a91b97894b..0bc1ac2797606 100644 --- a/src/coreclr/vm/arm64/stubs.cpp +++ b/src/coreclr/vm/arm64/stubs.cpp @@ -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 argsWriterHolder(pArgs, sizeof(GenericHandleArgs)); diff --git a/src/coreclr/vm/binder.cpp b/src/coreclr/vm/binder.cpp index 7805f1f37d9b5..7627e5a96c81a 100644 --- a/src/coreclr/vm/binder.cpp +++ b/src/coreclr/vm/binder.cpp @@ -599,7 +599,37 @@ void CoreLibBinder::Check() if (currentTypeTrimmed) continue; - pMT = ClassLoader::LoadTypeByNameThrowing(GetModule()->GetAssembly(), p->classNameSpace, p->className).AsMethodTable(); + LPCUTF8 nameSpace = p->classNameSpace; + LPCUTF8 name = p->className; + + LPCUTF8 nestedTypeMaybe = strchr(name, '+'); + if (nestedTypeMaybe == NULL) + { + NameHandle nameHandle = NameHandle(nameSpace, name); + pMT = ClassLoader::LoadTypeByNameThrowing(GetModule()->GetAssembly(), &nameHandle).AsMethodTable(); + } + else + { + // Handle the nested type scenario. + // The same NameHandle must be used to retain the scope to look for the nested type. + NameHandle nameHandle(GetModule(), mdtBaseType); + + SString splitName(SString::Utf8, name, (COUNT_T)(nestedTypeMaybe - name)); + nameHandle.SetName(nameSpace, splitName.GetUTF8()); + + // The side-effect of updating the scope in the NameHandle is the point of the call. + (void)ClassLoader::LoadTypeByNameThrowing(GetModule()->GetAssembly(), &nameHandle); + + // Now load the nested type. + nameHandle.SetName("", nestedTypeMaybe + 1); + + // We don't support nested types in nested types. + _ASSERTE(strchr(nameHandle.GetName(), '+') == NULL); + + // We don't support nested types with explicit namespaces + _ASSERTE(strchr(nameHandle.GetName(), '.') == NULL); + pMT = ClassLoader::LoadTypeByNameThrowing(GetModule()->GetAssembly(), &nameHandle).AsMethodTable(); + } if (p->expectedClassSize == sizeof(NoClass)) continue; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index ec5ee0795a504..138e3d51399e6 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1142,6 +1142,17 @@ DEFINE_CLASS(INATTRIBUTE, Interop, InAttribute) DEFINE_CLASS(CASTCACHE, CompilerServices, CastHelpers) DEFINE_FIELD(CASTCACHE, TABLE, s_table) +#ifdef DEBUG +// GenericCache is generic, so we can't check layout with DEFINE_FIELD_U +// Instead we do an ad hoc check +DEFINE_CLASS(GENERICCACHE, CompilerServices, GenericCache`2) +DEFINE_FIELD(GENERICCACHE, TABLE, _table) +DEFINE_FIELD(GENERICCACHE, SENTINEL_TABLE, _sentinelTable) +DEFINE_FIELD(GENERICCACHE, LAST_FLUSH_SIZE, _lastFlushSize) +DEFINE_FIELD(GENERICCACHE, INITIAL_CACHE_SIZE, _initialCacheSize) +DEFINE_FIELD(GENERICCACHE, MAX_CACHE_SIZE, _maxCacheSize) +#endif + DEFINE_CLASS(CASTHELPERS, CompilerServices, CastHelpers) DEFINE_METHOD(CASTHELPERS, ISINSTANCEOFANY, IsInstanceOfAny, SM_PtrVoid_Obj_RetObj) DEFINE_METHOD(CASTHELPERS, ISINSTANCEOFCLASS,IsInstanceOfClass, SM_PtrVoid_Obj_RetObj) @@ -1155,6 +1166,26 @@ 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) +DEFINE_FIELD(VIRTUALDISPATCHHELPERS, CACHE, s_virtualFunctionPointerCache) + +DEFINE_CLASS_U(CompilerServices, VirtualDispatchHelpers+VirtualFunctionPointerArgs, VirtualFunctionPointerArgs) +DEFINE_FIELD_U(classHnd, VirtualFunctionPointerArgs, classHnd) +DEFINE_FIELD_U(methodHnd, VirtualFunctionPointerArgs, methodHnd) + +DEFINE_CLASS(GENERICSHELPERS, CompilerServices, GenericsHelpers) +DEFINE_METHOD(GENERICSHELPERS, METHOD, Method, NoSig) +DEFINE_METHOD(GENERICSHELPERS, CLASS, Class, NoSig) +DEFINE_METHOD(GENERICSHELPERS, METHODWITHSLOTANDMODULE, MethodWithSlotAndModule, NoSig) +DEFINE_METHOD(GENERICSHELPERS, CLASSWITHSLOTANDMODULE, ClassWithSlotAndModule, NoSig) + +DEFINE_CLASS_U(CompilerServices, GenericsHelpers+GenericHandleArgs, GenericHandleArgs) +DEFINE_FIELD_U(signature, GenericHandleArgs, signature) +DEFINE_FIELD_U(module, GenericHandleArgs, module) +DEFINE_FIELD_U(dictionaryIndexAndSlot, GenericHandleArgs, dictionaryIndexAndSlot) + #ifdef FEATURE_EH_FUNCLETS DEFINE_CLASS(EH, Runtime, EH) DEFINE_METHOD(EH, RH_THROW_EX, RhThrowEx, SM_Obj_RefExInfo_RetVoid) diff --git a/src/coreclr/vm/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index 2dc5a013dcf4f..9d335522ca1ee 100644 --- a/src/coreclr/vm/genericdict.cpp +++ b/src/coreclr/vm/genericdict.cpp @@ -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(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((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(newSize + static_cast(numGenericArgs))) + return NULL; + DictionaryLayout* pNewDictionaryLayout = Allocate((WORD)newSize, pAllocator, NULL); pNewDictionaryLayout->m_numInitialSlots = pCurrentDictLayout->m_numInitialSlots; @@ -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); @@ -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; diff --git a/src/coreclr/vm/i386/cgenx86.cpp b/src/coreclr/vm/i386/cgenx86.cpp index 878405e0db1af..773c1881ae746 100644 --- a/src/coreclr/vm/i386/cgenx86.cpp +++ b/src/coreclr/vm/i386/cgenx86.cpp @@ -1289,9 +1289,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 argsWriterHolder(pArgs, sizeof(GenericHandleArgs)); diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 5777d7feea582..553385adb8611 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1925,192 +1925,19 @@ HCIMPLEND // //======================================================================== -/***********************************************************************/ -// JIT_GenericHandle and its cache -// -// Perform a "polytypic" operation related to shared generic code at runtime, possibly filling in an entry in -// either a generic dictionary cache associated with a descriptor or placing an entry in the global -// JitGenericHandle cache. -// -// A polytypic operation is one such as -// * new List -// * castclass List -// where the code being executed is shared generic code. In these cases the outcome of the operation depends -// on the exact value for T, which is acquired from a dynamic parameter. -// -// The actual operation always boils down to finding a "handle" (TypeHandle, MethodDesc, call address, -// dispatch stub address etc.) based on some static information (passed as tokens) and on the exact runtime -// type context (passed as one or two parameters classHnd and methodHnd). -// -// The static information specifies which polytypic operation (and thus which kind of handle) we're -// interested in. -// -// The dynamic information (the type context, i.e. the exact instantiation of class and method type -// parameters is specified in one of two ways: -// * If classHnd is null then the methodHnd should be an exact method descriptor wrapping shared code that -// satisfies SharedByGenericMethodInstantiations(). -// -// For example: -// * We may be running the shared code for a generic method instantiation C::m. The methodHnd -// will carry the exact instantiation, e.g. C::m -// -// * If classHnd is non-null (e.g. a type D) then: -// * methodHnd will indicate the representative code being run (which will be -// !SharedByGenericMethodInstantiations but will be SharedByGenericClassInstantiations). Let's say -// this code is C::m(). -// * the type D will be a descendent of type C. In particular D will relate to some type C -// where C is the representative instantiation of C' -// * the relevant dictionary will be the one attached to C. -// -// The JitGenericHandleCache is a global data structure shared across all application domains. It is only -// used if generic dictionaries have overflowed. It is flushed each time an application domain is unloaded. - -struct JitGenericHandleCacheKey -{ - JitGenericHandleCacheKey(CORINFO_CLASS_HANDLE classHnd, CORINFO_METHOD_HANDLE methodHnd, void *signature) - { - LIMITED_METHOD_CONTRACT; - m_Data1 = (size_t)classHnd; - m_Data2 = (size_t)methodHnd; - m_Data3 = (size_t)signature; - m_type = 0; - } - - JitGenericHandleCacheKey(MethodTable* pMT, CORINFO_CLASS_HANDLE classHnd, CORINFO_METHOD_HANDLE methodHnd) - { - LIMITED_METHOD_CONTRACT; - m_Data1 = (size_t)pMT; - m_Data2 = (size_t)classHnd; - m_Data3 = (size_t)methodHnd; - m_type = 1; - } - - size_t m_Data1; - size_t m_Data2; - size_t m_Data3; - - // The type of the entry: - // 0 - JIT_GenericHandle entry - // 1 - JIT_VirtualFunctionPointer entry - unsigned char m_type; -}; - -class JitGenericHandleCacheTraits -{ -public: - static EEHashEntry_t *AllocateEntry(const JitGenericHandleCacheKey *pKey, BOOL bDeepCopy, AllocationHeap pHeap = 0) - { - LIMITED_METHOD_CONTRACT; - EEHashEntry_t *pEntry = (EEHashEntry_t *) new (nothrow) BYTE[SIZEOF_EEHASH_ENTRY + sizeof(JitGenericHandleCacheKey)]; - if (!pEntry) - return NULL; - *((JitGenericHandleCacheKey*)pEntry->Key) = *pKey; - return pEntry; - } - - static void DeleteEntry(EEHashEntry_t *pEntry, AllocationHeap pHeap = 0) - { - LIMITED_METHOD_CONTRACT; - delete [] (BYTE*)pEntry; - } - - static BOOL CompareKeys(EEHashEntry_t *pEntry, const JitGenericHandleCacheKey *e2) - { - LIMITED_METHOD_CONTRACT; - const JitGenericHandleCacheKey *e1 = (const JitGenericHandleCacheKey*)&pEntry->Key; - return (e1->m_Data1 == e2->m_Data1) && (e1->m_Data2 == e2->m_Data2) && (e1->m_Data3 == e2->m_Data3) && - (e1->m_type == e2->m_type); - } - - static DWORD Hash(const JitGenericHandleCacheKey *k) - { - LIMITED_METHOD_CONTRACT; - return (DWORD)k->m_Data1 + _rotl((DWORD)k->m_Data2,5) + _rotr((DWORD)k->m_Data3,5); - } - - static const JitGenericHandleCacheKey *GetKey(EEHashEntry_t *pEntry) - { - LIMITED_METHOD_CONTRACT; - return (const JitGenericHandleCacheKey*)&pEntry->Key; - } -}; - -typedef EEHashTable JitGenericHandleCache; - -JitGenericHandleCache *g_pJitGenericHandleCache = NULL; //cache of calls to JIT_GenericHandle -CrstStatic g_pJitGenericHandleCacheCrst; - -void AddToGenericHandleCache(JitGenericHandleCacheKey* pKey, HashDatum datum) -{ - CONTRACTL { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(pKey)); - PRECONDITION(CheckPointer(datum)); - } CONTRACTL_END; - - EX_TRY - { - GCX_COOP(); - - CrstHolder lock(&g_pJitGenericHandleCacheCrst); - - HashDatum entry; - if (!g_pJitGenericHandleCache->GetValue(pKey,&entry)) - g_pJitGenericHandleCache->InsertValue(pKey,datum); - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions) // Swallow OOM -} - -/* static */ -void ClearJitGenericHandleCache() +extern "C" CORINFO_GENERIC_HANDLE QCALLTYPE GenericHandleWorker(MethodDesc * pMD, MethodTable * pMT, LPVOID signature, DWORD dictionaryIndexAndSlot, Module* pModule) { - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - } CONTRACTL_END; + QCALL_CONTRACT; + CORINFO_GENERIC_HANDLE result = NULL; - // We call this on every ALC unload, because entries in the cache might include - // pointers into the ALC being unloaded. We would prefer to - // only flush entries that have that are no longer valid, but the entries don't yet contain - // enough information to do that. However everything in the cache can be found again by calling - // loader functions, and the total number of entries in the cache is typically very small (indeed - // normally the cache is not used at all - it is only used when the generic dictionaries overflow). - if (g_pJitGenericHandleCache) - { - // It's not necessary to take the lock here because this function should only be called when EE is suspended, - // the lock is only taken to fulfill the threadsafety check and to be consistent. If the lock becomes a problem, we - // could put it in a "ifdef _DEBUG" block - CrstHolder lock(&g_pJitGenericHandleCacheCrst); - EEHashTableIteration iter; - g_pJitGenericHandleCache->IterateStart(&iter); - BOOL keepGoing = g_pJitGenericHandleCache->IterateNext(&iter); - while(keepGoing) - { - const JitGenericHandleCacheKey *key = g_pJitGenericHandleCache->IterateGetKey(&iter); - // Advance the iterator before we delete!! See notes in EEHash.h - keepGoing = g_pJitGenericHandleCache->IterateNext(&iter); - g_pJitGenericHandleCache->DeleteValue(key); - } - } -} + BEGIN_QCALL; -// Factored out most of the body of JIT_GenericHandle so it could be called easily from the CER reliability code to pre-populate the -// cache. -CORINFO_GENERIC_HANDLE JIT_GenericHandleWorker(MethodDesc * pMD, MethodTable * pMT, LPVOID signature, DWORD dictionaryIndexAndSlot, Module* pModule) -{ - CONTRACTL { - THROWS; - GC_TRIGGERS; - } CONTRACTL_END; + _ASSERTE(pMT != NULL || pMD != NULL); + _ASSERTE(pMT == NULL || pMD == NULL); - uint32_t dictionaryIndex = 0; - MethodTable * pDeclaringMT = NULL; + uint32_t dictionaryIndex = 0; + MethodTable * pDeclaringMT = NULL; if (pMT != NULL) { @@ -2149,33 +1976,10 @@ CORINFO_GENERIC_HANDLE JIT_GenericHandleWorker(MethodDesc * pMD, MethodTable * p break; pDeclaringMT = pParentMT; } - - if (pDeclaringMT != pMT) - { - JitGenericHandleCacheKey key((CORINFO_CLASS_HANDLE)pDeclaringMT, NULL, signature); - HashDatum res; - if (g_pJitGenericHandleCache->GetValue(&key,&res)) - { - // Add the denormalized key for faster lookup next time. This is not a critical entry - no need - // to specify appdomain affinity. - JitGenericHandleCacheKey denormKey((CORINFO_CLASS_HANDLE)pMT, NULL, signature); - AddToGenericHandleCache(&denormKey, res); - return (CORINFO_GENERIC_HANDLE) (DictionaryEntry) res; - } - } } DictionaryEntry * pSlot; - CORINFO_GENERIC_HANDLE result = (CORINFO_GENERIC_HANDLE)Dictionary::PopulateEntry(pMD, pDeclaringMT, signature, FALSE, &pSlot, dictionaryIndexAndSlot, pModule); - - if (pSlot == NULL) - { - // If we've overflowed the dictionary write the result to the cache. - // Add the normalized key (pDeclaringMT) here so that future lookups of any - // inherited types are faster next time rather than just just for this specific pMT. - JitGenericHandleCacheKey key((CORINFO_CLASS_HANDLE)pDeclaringMT, (CORINFO_METHOD_HANDLE)pMD, signature); - AddToGenericHandleCache(&key, (HashDatum)result); - } + result = (CORINFO_GENERIC_HANDLE)Dictionary::PopulateEntry(pMD, pDeclaringMT, signature, FALSE, &pSlot, dictionaryIndexAndSlot, pModule); if (pMT != NULL && pDeclaringMT != pMT) { @@ -2191,149 +1995,97 @@ CORINFO_GENERIC_HANDLE JIT_GenericHandleWorker(MethodDesc * pMD, MethodTable * p } } - return result; -} // JIT_GenericHandleWorker - -/*********************************************************************/ -// slow helper to tail call from the fast one -NOINLINE HCIMPL5(CORINFO_GENERIC_HANDLE, JIT_GenericHandle_Framed, - CORINFO_CLASS_HANDLE classHnd, - CORINFO_METHOD_HANDLE methodHnd, - LPVOID signature, - DWORD dictionaryIndexAndSlot, - CORINFO_MODULE_HANDLE moduleHnd) -{ - CONTRACTL { - FCALL_CHECK; - PRECONDITION(classHnd != NULL || methodHnd != NULL); - PRECONDITION(classHnd == NULL || methodHnd == NULL); - } CONTRACTL_END; - - // Result is a generic handle (in fact, a CORINFO_CLASS_HANDLE, CORINFO_METHOD_HANDLE, or a code pointer) - CORINFO_GENERIC_HANDLE result = NULL; - - MethodDesc * pMD = GetMethod(methodHnd); - MethodTable * pMT = TypeHandle(classHnd).AsMethodTable(); - Module * pModule = GetModule(moduleHnd); - - // Set up a frame - HELPER_METHOD_FRAME_BEGIN_RET_0(); + END_QCALL; - result = JIT_GenericHandleWorker(pMD, pMT, signature, dictionaryIndexAndSlot, pModule); - - HELPER_METHOD_FRAME_END(); - - _ASSERTE(result != NULL); - - // Return the handle return result; -} -HCIMPLEND +} // GenericHandleWorker -/*********************************************************************/ -#include -HCIMPL2(CORINFO_GENERIC_HANDLE, JIT_GenericHandleMethod, CORINFO_METHOD_HANDLE methodHnd, LPVOID signature) -{ - CONTRACTL { - FCALL_CHECK; - PRECONDITION(CheckPointer(methodHnd)); - PRECONDITION(CheckPointer(signature)); - } CONTRACTL_END; +FieldDesc* g_pVirtualFunctionPointerCache; - JitGenericHandleCacheKey key(NULL, methodHnd, signature); - HashDatum res; - if (g_pJitGenericHandleCache->GetValueSpeculative(&key,&res)) - return (CORINFO_GENERIC_HANDLE) (DictionaryEntry) res; - - // Tailcall to the slow helper - ENDFORBIDGC(); - return HCCALL5(JIT_GenericHandle_Framed, NULL, methodHnd, signature, -1, NULL); -} -HCIMPLEND - -HCIMPL2(CORINFO_GENERIC_HANDLE, JIT_GenericHandleMethodWithSlotAndModule, CORINFO_METHOD_HANDLE methodHnd, GenericHandleArgs * pArgs) +void FlushGenericCache(PTR_GenericCacheStruct genericCache) { - CONTRACTL{ - FCALL_CHECK; - PRECONDITION(CheckPointer(methodHnd)); - PRECONDITION(CheckPointer(pArgs)); - } CONTRACTL_END; + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; - JitGenericHandleCacheKey key(NULL, methodHnd, pArgs->signature); - HashDatum res; - if (g_pJitGenericHandleCache->GetValueSpeculative(&key, &res)) - return (CORINFO_GENERIC_HANDLE)(DictionaryEntry)res; + int32_t lastSize = genericCache->CacheElementCount(); + if (lastSize < genericCache->GetInitialCacheSize()) + { + lastSize = genericCache->GetInitialCacheSize(); + } - // Tailcall to the slow helper - ENDFORBIDGC(); - return HCCALL5(JIT_GenericHandle_Framed, NULL, methodHnd, pArgs->signature, pArgs->dictionaryIndexAndSlot, pArgs->module); + // store the last size to use when creating a new table + // it is just a hint, not needed for correctness, so no synchronization + // with the writing of the table + genericCache->SetLastFlushSize(lastSize); + // flushing is just replacing the table with a sentinel. + genericCache->SetTable(genericCache->GetSentinelTable()); } -HCIMPLEND -#include -/*********************************************************************/ -#include -HCIMPL2(CORINFO_GENERIC_HANDLE, JIT_GenericHandleClass, CORINFO_CLASS_HANDLE classHnd, LPVOID signature) +void FlushVirtualFunctionPointerCaches() { - CONTRACTL { - FCALL_CHECK; - PRECONDITION(CheckPointer(classHnd)); - PRECONDITION(CheckPointer(signature)); - } CONTRACTL_END; + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; - JitGenericHandleCacheKey key(classHnd, NULL, signature); - HashDatum res; - if (g_pJitGenericHandleCache->GetValueSpeculative(&key,&res)) - return (CORINFO_GENERIC_HANDLE) (DictionaryEntry) res; + FieldDesc *virtualCache = VolatileLoad(&g_pVirtualFunctionPointerCache); - // Tailcall to the slow helper - ENDFORBIDGC(); - return HCCALL5(JIT_GenericHandle_Framed, classHnd, NULL, signature, -1, NULL); + if (virtualCache != NULL) + { + // We can't use GetCurrentStaticAddress, as that may throw, since it will attempt to + // allocate memory for statics if that hasn't happened yet. But, since we force the + // statics memory to be allocated before initializing g_pVirtualFunctionPointerCache + // we can safely use the combo of GetBase and GetStaticAddress here. + FlushGenericCache((PTR_GenericCacheStruct)virtualCache->GetStaticAddress(virtualCache->GetBase())); + } } -HCIMPLEND - -HCIMPL2(CORINFO_GENERIC_HANDLE, JIT_GenericHandleClassWithSlotAndModule, CORINFO_CLASS_HANDLE classHnd, GenericHandleArgs * pArgs) -{ - CONTRACTL{ - FCALL_CHECK; - PRECONDITION(CheckPointer(classHnd)); - PRECONDITION(CheckPointer(pArgs)); - } CONTRACTL_END; - - JitGenericHandleCacheKey key(classHnd, NULL, pArgs->signature); - HashDatum res; - if (g_pJitGenericHandleCache->GetValueSpeculative(&key, &res)) - return (CORINFO_GENERIC_HANDLE)(DictionaryEntry)res; - - // Tailcall to the slow helper - ENDFORBIDGC(); - return HCCALL5(JIT_GenericHandle_Framed, classHnd, NULL, pArgs->signature, pArgs->dictionaryIndexAndSlot, pArgs->module); -} -HCIMPLEND -#include /*********************************************************************/ // Resolve a virtual method at run-time, either because of // aggressive backpatching or because the call is to a generic // method which is itself virtual. // -// classHnd is the actual run-time type for the call is made. +// classHnd is the actual run-time type for the call is made. (May be NULL for cases where methodHnd describes an interface) // methodHnd is the exact (instantiated) method descriptor corresponding to the // static method signature (i.e. might be for a superclass of classHnd) -// slow helper to tail call from the fast one -NOINLINE HCIMPL3(CORINFO_MethodPtr, JIT_VirtualFunctionPointer_Framed, Object * objectUNSAFE, +// slow helper to call from the fast one +extern "C" void* QCALLTYPE ResolveVirtualFunctionPointer(QCall::ObjectHandleOnStack obj, CORINFO_CLASS_HANDLE classHnd, CORINFO_METHOD_HANDLE methodHnd) { - FCALL_CONTRACT; + QCALL_CONTRACT; - // The address of the method that's returned. + // The address of the method that's returned. CORINFO_MethodPtr addr = NULL; - OBJECTREF objRef = ObjectToOBJECTREF(objectUNSAFE); + BEGIN_QCALL; - HELPER_METHOD_FRAME_BEGIN_RET_1(objRef); // Set up a frame + if (VolatileLoadWithoutBarrier(&g_pVirtualFunctionPointerCache) == NULL) + { + { + GCX_COOP(); + CoreLibBinder::GetClass(CLASS__VIRTUALDISPATCHHELPERS)->CheckRunClassInitThrowing(); + } + + VolatileStore(&g_pVirtualFunctionPointerCache, CoreLibBinder::GetField(FIELD__VIRTUALDISPATCHHELPERS__CACHE)); +#ifdef DEBUG + FieldDesc *virtualCache = VolatileLoad(&g_pVirtualFunctionPointerCache); + GenericCacheStruct::ValidateLayout(virtualCache->GetApproxFieldTypeHandleThrowing().GetMethodTable()); +#endif + } + + GCX_COOP(); + OBJECTREF objRef = obj.Get(); + GCPROTECT_BEGIN(objRef); if (objRef == NULL) COMPlusThrow(kNullReferenceException); @@ -2343,13 +2095,24 @@ NOINLINE HCIMPL3(CORINFO_MethodPtr, JIT_VirtualFunctionPointer_Framed, Object * MethodDesc* pStaticMD = (MethodDesc*) methodHnd; TypeHandle staticTH(classHnd); + if (staticTH.IsNull()) + { + // This may be NULL on input for cases where the methodHnd is not an interface method, or if getting the method table from the + // MethodDesc will return an exact type. + if (pStaticMD->IsInterface()) + { + staticTH = pStaticMD->GetMethodTable(); + _ASSERTE(!staticTH.IsCanonicalSubtype()); + } + } + pStaticMD->CheckRestore(); - // MDIL: If IL specifies callvirt/ldvirtftn it remains a "virtual" instruction - // even if the target is an instance method at MDIL generation time because - // we want to keep MDIL as resilient as IL. Right now we can end up here with - // non-virtual generic methods called from a "shared generic code". - // As soon as this deficiency is fixed in the binder we can get rid of this test. + // ReadyToRun: If the method was compiled using ldvirtftn to reference a non-virtual method + // resolve without using the VirtualizedCode call path here. + // This can happen if the method was converted from virtual to non-virtual after the R2R image was created. + // While this is not a common scenario and is documented as a breaking change, we should still handle it + // as we have no good scheme for reporting an actionable error here. if (!pStaticMD->IsVtableMethod()) { addr = (CORINFO_MethodPtr) pStaticMD->GetMultiCallableAddrOfCode(); @@ -2361,17 +2124,13 @@ NOINLINE HCIMPL3(CORINFO_MethodPtr, JIT_VirtualFunctionPointer_Framed, Object * // The code is now also used by reflection, remoting etc. addr = (CORINFO_MethodPtr) pStaticMD->GetMultiCallableAddrOfVirtualizedCode(&objRef, staticTH); _ASSERTE(addr); - - // This is not a critical entry - no need to specify appdomain affinity - JitGenericHandleCacheKey key(objRef->GetMethodTable(), classHnd, methodHnd); - AddToGenericHandleCache(&key, (HashDatum)addr); } - HELPER_METHOD_FRAME_END(); + GCPROTECT_END(); + END_QCALL; return addr; } -HCIMPLEND HCIMPL3(void, Jit_NativeMemSet, void* pDest, int value, size_t length) { @@ -2470,51 +2229,6 @@ HCIMPL1(Object*, JIT_GetRuntimeType_MaybeNull, CORINFO_CLASS_HANDLE type) HCIMPLEND #include -/*********************************************************************/ -#include -HCIMPL3(CORINFO_MethodPtr, JIT_VirtualFunctionPointer, Object * objectUNSAFE, - CORINFO_CLASS_HANDLE classHnd, - CORINFO_METHOD_HANDLE methodHnd) -{ - FCALL_CONTRACT; - - OBJECTREF objRef = ObjectToOBJECTREF(objectUNSAFE); - - if (objRef != NULL) - { - JitGenericHandleCacheKey key(objRef->GetMethodTable(), classHnd, methodHnd); - HashDatum res; - if (g_pJitGenericHandleCache->GetValueSpeculative(&key,&res)) - return (CORINFO_GENERIC_HANDLE)res; - } - - // Tailcall to the slow helper - ENDFORBIDGC(); - return HCCALL3(JIT_VirtualFunctionPointer_Framed, OBJECTREFToObject(objRef), classHnd, methodHnd); -} -HCIMPLEND - -HCIMPL2(CORINFO_MethodPtr, JIT_VirtualFunctionPointer_Dynamic, Object * objectUNSAFE, VirtualFunctionPointerArgs * pArgs) -{ - FCALL_CONTRACT; - - OBJECTREF objRef = ObjectToOBJECTREF(objectUNSAFE); - - if (objRef != NULL) - { - JitGenericHandleCacheKey key(objRef->GetMethodTable(), pArgs->classHnd, pArgs->methodHnd); - HashDatum res; - if (g_pJitGenericHandleCache->GetValueSpeculative(&key,&res)) - return (CORINFO_GENERIC_HANDLE)res; - } - - // Tailcall to the slow helper - ENDFORBIDGC(); - return HCCALL3(JIT_VirtualFunctionPointer_Framed, OBJECTREFToObject(objRef), pArgs->classHnd, pArgs->methodHnd); -} -HCIMPLEND - -#include // Helper for synchronized static methods in shared generics code #include @@ -5039,15 +4753,6 @@ void InitJITHelpers2() #endif // TARGET_X86 || TARGET_ARM InitJitHelperLogging(); - - g_pJitGenericHandleCacheCrst.Init(CrstJitGenericHandleCache, CRST_UNSAFE_COOPGC); - - // Allocate and initialize the generic handle cache - NewHolder tempGenericHandleCache (new JitGenericHandleCache()); - LockOwner sLock = {&g_pJitGenericHandleCacheCrst, IsOwnerOfCrst}; - if (!tempGenericHandleCache->Init(59, &sLock)) - COMPlusThrowOM(); - g_pJitGenericHandleCache = tempGenericHandleCache.Extract(); } //======================================================================== diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index d65d41e4662ff..10682c64736c6 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -47,6 +47,8 @@ class CalledMethod; #include "genericdict.h" +void FlushVirtualFunctionPointerCaches(); + inline FieldDesc* GetField(CORINFO_FIELD_HANDLE fieldHandle) { LIMITED_METHOD_CONTRACT; @@ -1092,8 +1094,6 @@ struct VirtualFunctionPointerArgs CORINFO_METHOD_HANDLE methodHnd; }; -FCDECL2(CORINFO_MethodPtr, JIT_VirtualFunctionPointer_Dynamic, Object * objectUNSAFE, VirtualFunctionPointerArgs * pArgs); - typedef HCCALL1_PTR(TADDR, FnStaticBaseHelper, TADDR arg0); struct StaticFieldAddressArgs @@ -1113,17 +1113,6 @@ struct GenericHandleArgs DWORD dictionaryIndexAndSlot; }; -FCDECL2(CORINFO_GENERIC_HANDLE, JIT_GenericHandleMethodWithSlotAndModule, CORINFO_METHOD_HANDLE methodHnd, GenericHandleArgs * pArgs); -FCDECL2(CORINFO_GENERIC_HANDLE, JIT_GenericHandleClassWithSlotAndModule, CORINFO_CLASS_HANDLE classHnd, GenericHandleArgs * pArgs); - -CORINFO_GENERIC_HANDLE JIT_GenericHandleWorker(MethodDesc *pMD, - MethodTable *pMT, - LPVOID signature, - DWORD dictionaryIndexAndSlot = -1, - Module * pModule = NULL); - -void ClearJitGenericHandleCache(); - CORJIT_FLAGS GetDebuggerCompileFlags(Module* pModule, CORJIT_FLAGS flags); bool __stdcall TrackAllocationsEnabled(); diff --git a/src/coreclr/vm/loaderallocator.cpp b/src/coreclr/vm/loaderallocator.cpp index 6ad3a8457f512..3611ee834d8c4 100644 --- a/src/coreclr/vm/loaderallocator.cpp +++ b/src/coreclr/vm/loaderallocator.cpp @@ -595,8 +595,9 @@ void LoaderAllocator::GCLoaderAllocators(LoaderAllocator* pOriginalLoaderAllocat // is inappropriate, we can introduce a new flag or hijack an unused one. ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_FOR_APPDOMAIN_SHUTDOWN); - // drop the cast cache while still in COOP mode. + // drop the cast and virtual resolution caches while still in COOP mode. CastCache::FlushCurrentCache(); + FlushVirtualFunctionPointerCaches(); } ExecutionManager::Unload(pDomainLoaderAllocatorDestroyIterator); @@ -604,7 +605,6 @@ void LoaderAllocator::GCLoaderAllocators(LoaderAllocator* pOriginalLoaderAllocat // TODO: Do we really want to perform this on each LoaderAllocator? MethodTable::ClearMethodDataCache(); - ClearJitGenericHandleCache(); if (!IsAtProcessExit()) { diff --git a/src/coreclr/vm/loongarch64/stubs.cpp b/src/coreclr/vm/loongarch64/stubs.cpp index ac1b170ebec9a..17457d8c33d06 100644 --- a/src/coreclr/vm/loongarch64/stubs.cpp +++ b/src/coreclr/vm/loongarch64/stubs.cpp @@ -1772,9 +1772,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 argsWriterHolder(pArgs, sizeof(GenericHandleArgs)); diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index d944116216523..79dc923931f4b 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2069,6 +2069,7 @@ PCODE MethodDesc::GetMultiCallableAddrOfVirtualizedCode(OBJECTREF *orThis, TypeH GC_TRIGGERS; PRECONDITION(IsVtableMethod()); + PRECONDITION(!staticTH.IsNull() || !IsInterface()); // If this is a non-interface method, staticTH may be null POSTCONDITION(RETVAL != NULL); } CONTRACT_END; diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index 477b256578ab4..050f16e09ad04 100644 --- a/src/coreclr/vm/object.cpp +++ b/src/coreclr/vm/object.cpp @@ -2109,4 +2109,33 @@ INT32 LoaderAllocatorObject::GetSlotsUsed() return m_slotsUsed; } + +#ifdef DEBUG +static void CheckOffsetOfFieldInInstantiation(MethodTable *pMTOfInstantiation, FieldDesc* pField, size_t offset) +{ + STANDARD_VM_CONTRACT; + + MethodTable* pGenericFieldMT = pField->GetApproxEnclosingMethodTable(); + DWORD index = pGenericFieldMT->GetIndexForFieldDesc(pField); + FieldDesc *pFieldOnInstantiation = pMTOfInstantiation->GetFieldDescByIndex(index); + + if (pFieldOnInstantiation->GetOffset() != offset) + { + _ASSERTE(!"Field offset mismatch"); + } +} + +/*static*/ void GenericCacheStruct::ValidateLayout(MethodTable* pMTOfInstantiation) +{ + STANDARD_VM_CONTRACT; + + CheckOffsetOfFieldInInstantiation(pMTOfInstantiation, CoreLibBinder::GetField(FIELD__GENERICCACHE__TABLE), offsetof(GenericCacheStruct, _table)); + CheckOffsetOfFieldInInstantiation(pMTOfInstantiation, CoreLibBinder::GetField(FIELD__GENERICCACHE__SENTINEL_TABLE), offsetof(GenericCacheStruct, _sentinelTable)); + CheckOffsetOfFieldInInstantiation(pMTOfInstantiation, CoreLibBinder::GetField(FIELD__GENERICCACHE__LAST_FLUSH_SIZE), offsetof(GenericCacheStruct, _lastFlushSize)); + CheckOffsetOfFieldInInstantiation(pMTOfInstantiation, CoreLibBinder::GetField(FIELD__GENERICCACHE__INITIAL_CACHE_SIZE), offsetof(GenericCacheStruct, _initialCacheSize)); + CheckOffsetOfFieldInInstantiation(pMTOfInstantiation, CoreLibBinder::GetField(FIELD__GENERICCACHE__MAX_CACHE_SIZE), offsetof(GenericCacheStruct, _maxCacheSize)); + // Validate the layout of the Generic +} +#endif + #endif // DACCESS_COMPILE diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 91e38c5ac9496..c9abe58b8e731 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -2162,6 +2162,64 @@ typedef PTR_LoaderAllocatorObject LOADERALLOCATORREF; #endif // FEATURE_COLLECTIBLE_TYPES +typedef DPTR(class GenericCacheStruct) PTR_GenericCacheStruct; +class GenericCacheStruct +{ + friend class CoreLibBinder; + public: + + ARRAYBASEREF GetTable() const + { + LIMITED_METHOD_CONTRACT; + return _table; + } + + int32_t CacheElementCount() const + { + LIMITED_METHOD_CONTRACT; + return GetTable()->GetNumComponents() - 1; + } + + ARRAYBASEREF GetSentinelTable() const + { + LIMITED_METHOD_CONTRACT; + return _sentinelTable; + } + + void SetTable(ARRAYBASEREF table) + { + WRAPPER_NO_CONTRACT; + SetObjectReference((OBJECTREF*)&_table, (OBJECTREF)table); + } + + void SetLastFlushSize(int32_t lastFlushSize) + { + LIMITED_METHOD_CONTRACT; + _lastFlushSize = lastFlushSize; + } + + int32_t GetInitialCacheSize() const + { + LIMITED_METHOD_CONTRACT; + return _initialCacheSize; + } + +#ifdef DEBUG + static void ValidateLayout(MethodTable* pMTOfInstantiation); +#endif + + private: + // README: + // If you modify the order of these fields, make sure to update the definition in + // BCL for this object. + + ARRAYBASEREF _table; + ARRAYBASEREF _sentinelTable; + int32_t _lastFlushSize; + int32_t _initialCacheSize; + int32_t _maxCacheSize; +}; + // This class corresponds to Exception on the managed side. typedef DPTR(class ExceptionObject) PTR_ExceptionObject; #include "pshpack4.h" diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 63189a4780e70..af2d2c8ebc060 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -3925,8 +3925,11 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR pArgs->classHnd = (CORINFO_CLASS_HANDLE)th.AsPtr(); pArgs->methodHnd = (CORINFO_METHOD_HANDLE)pMD; + MethodDesc *pVirtualDispatchHelper = CoreLibBinder::GetMethod(METHOD__VIRTUALDISPATCHHELPERS__VIRTUALFUNCTIONPOINTER_DYNAMIC); + PCODE pHelperCode = pVirtualDispatchHelper->GetMultiCallableAddrOfCode(); + pHelper = DynamicHelpers::CreateHelperWithArg(pModule->GetLoaderAllocator(), (TADDR)pArgs, - GetEEFuncEntryPoint(JIT_VirtualFunctionPointer_Dynamic)); + pHelperCode); amTracker.SuppressRelease(); } diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 4181a027e669c..bd4f396de4959 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -67,6 +67,7 @@ #endif //FEATURE_PERFTRACING #include "tailcallhelp.h" +#include "JitQCallHelpers.h" #include @@ -476,6 +477,8 @@ static const Entry s_QCall[] = DllImportEntry(EHEnumNext) DllImportEntry(AppendExceptionStackFrame) #endif // FEATURE_EH_FUNCLETS + DllImportEntry(ResolveVirtualFunctionPointer) + DllImportEntry(GenericHandleWorker) }; const void* QCallResolveDllImport(const char* name) diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 19ab4b621469b..eed2bf26e94b0 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -17,6 +17,38 @@ #include "wellknownattributes.h" #include "nativeimage.h" +#ifndef DACCESS_COMPILE +static PCODE g_pMethodWithSlotAndModule = (PCODE)NULL; +static PCODE g_pClassWithSlotAndModule = (PCODE)NULL; + +PCODE DynamicHelpers::GetDictionaryLookupHelper(CorInfoHelpFunc jitHelper) +{ + _ASSERTE(jitHelper == CORINFO_HELP_RUNTIMEHANDLE_METHOD || jitHelper == CORINFO_HELP_RUNTIMEHANDLE_CLASS); + if (jitHelper == CORINFO_HELP_RUNTIMEHANDLE_METHOD) + { + PCODE pFunc = VolatileLoadWithoutBarrier(&g_pMethodWithSlotAndModule); + if (pFunc == (PCODE)NULL) + { + pFunc = CoreLibBinder::GetMethod(METHOD__GENERICSHELPERS__METHODWITHSLOTANDMODULE)->GetMultiCallableAddrOfCode(); + VolatileStore(&g_pMethodWithSlotAndModule, pFunc); + } + + return pFunc; + } + else + { + PCODE pFunc = VolatileLoadWithoutBarrier(&g_pClassWithSlotAndModule); + if (pFunc == (PCODE)NULL) + { + pFunc = CoreLibBinder::GetMethod(METHOD__GENERICSHELPERS__CLASSWITHSLOTANDMODULE)->GetMultiCallableAddrOfCode(); + VolatileStore(&g_pClassWithSlotAndModule, pFunc); + } + + return pFunc; + } +} +#endif // DACCESS_COMPILE + using namespace NativeFormat; ReadyToRunCoreInfo::ReadyToRunCoreInfo() diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 68882845f95e9..a58bbb41b9009 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -343,6 +343,8 @@ class DynamicHelpers { private: static void EmitHelperWithArg(BYTE*& pCode, size_t rxOffset, LoaderAllocator * pAllocator, TADDR arg, PCODE target); + + static PCODE GetDictionaryLookupHelper(CorInfoHelpFunc jitHelper); public: static PCODE CreateHelper(LoaderAllocator * pAllocator, TADDR arg, PCODE target); static PCODE CreateHelperWithArg(LoaderAllocator * pAllocator, TADDR arg, PCODE target); diff --git a/src/coreclr/vm/riscv64/stubs.cpp b/src/coreclr/vm/riscv64/stubs.cpp index 9e7a97d4dcf81..1273bb6e739e7 100644 --- a/src/coreclr/vm/riscv64/stubs.cpp +++ b/src/coreclr/vm/riscv64/stubs.cpp @@ -1839,9 +1839,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, STANDARD_VM_CONTRACT; const IntReg RegR0 = 0, RegA0 = 10, RegT2 = 7, RegT4 = 29, RegT5 = 30; - 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 argsWriterHolder(pArgs, sizeof(GenericHandleArgs)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericCache.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericCache.cs index 9f48295e4ba9f..3e81c92168f17 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericCache.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericCache.cs @@ -57,12 +57,14 @@ private struct Entry private const uint VERSION_NUM_MASK = (1 << VERSION_NUM_SIZE) - 1; private const int BUCKET_SIZE = 8; - // nothing is ever stored into this, so we can use a static instance. - private static Entry[]? s_sentinelTable; + // The fields of this structure are known to coreclr, so if they are updated, you must also update object.h // The actual storage. private Entry[] _table; + // Sentinel table used to flush the cache + private Entry[] _sentinelTable; + // when flushing, remember the last size. private int _lastFlushSize; @@ -81,14 +83,14 @@ public GenericCache(int initialCacheSize, int maxCacheSize) // A trivial 2-elements table used for "flushing" the cache. // Nothing is ever stored in such a small table and identity of the sentinel is not important. // It is required that we are able to allocate this, we may need this in OOM cases. - s_sentinelTable ??= CreateCacheTable(2, throwOnFail: true); + _sentinelTable = CreateCacheTable(2, throwOnFail: true)!; _table = #if !DEBUG // Initialize to the sentinel in DEBUG as if just flushed, to ensure the sentinel can be handled in Set. CreateCacheTable(initialCacheSize) ?? #endif - s_sentinelTable!; + _sentinelTable!; _lastFlushSize = initialCacheSize; } @@ -365,11 +367,13 @@ internal void TrySet(TKey key, TValue value) private static int CacheElementCount(Entry[] table) { + // Do not update this function without also updating the implementation of this in object.h in CoreCLR return table.Length - 1; } private void FlushCurrentCache() { + // Do not update this function without also updating the implementation of this in jithelpers.cpp in CoreCLR Entry[] table = _table; int lastSize = CacheElementCount(table); if (lastSize < _initialCacheSize) @@ -380,7 +384,7 @@ private void FlushCurrentCache() // with the writing of the table _lastFlushSize = lastSize; // flushing is just replacing the table with a sentinel. - _table = s_sentinelTable!; + _table = _sentinelTable!; } private bool MaybeReplaceCacheWithLarger(int size) diff --git a/src/tests/readytorun/tests/main.cs b/src/tests/readytorun/tests/main.cs index 9f0e7172ab601..75a4adf951a92 100644 --- a/src/tests/readytorun/tests/main.cs +++ b/src/tests/readytorun/tests/main.cs @@ -80,6 +80,12 @@ static void TestThrowHelpers() catch (InvalidOperationException) {} } + [MethodImpl(MethodImplOptions.NoInlining)] + static Func GetChangedToNonVirtualDelegate(MyClass o) + { + return o.ChangedToNonVirtual; + } + static void TestMovedVirtualMethods() { var o = new MyChildClass(); @@ -87,6 +93,9 @@ static void TestMovedVirtualMethods() Assert.AreEqual(o.MovedToBaseClass(), "MovedToBaseClass"); Assert.AreEqual(o.ChangedToVirtual(), "ChangedToVirtual"); + // Test that changing a virtual to a non-virtual doesn't cause a crash. (Behavior is somewhat undefined, as this change is explicitly defined as a breaking change.) + Assert.AreEqual(GetChangedToNonVirtualDelegate(o)(), "ChangedToNonVirtual"); + o = null; try @@ -185,6 +194,12 @@ static void TestGenericVirtualMethod() "System.StringSystem.ObjectProgramSystem.Collections.Generic.IEnumerable`1[System.String]"); } + [MethodImpl(MethodImplOptions.NoInlining)] + static Func GetChangedToNonVirtualDelegate(MyGeneric o) + { + return o.ChangedToNonVirtual; + } + static void TestMovedGenericVirtualMethod() { var o = new MyChildGeneric(); @@ -192,6 +207,9 @@ static void TestMovedGenericVirtualMethod() Assert.AreEqual(o.MovedToBaseClass(), typeof(List).ToString()); Assert.AreEqual(o.ChangedToVirtual(), typeof(List).ToString()); + // Test that changing a virtual to a non-virtual doesn't cause a crash. (Behavior is somewhat undefined, as this change is explicitly defined as a breaking change.) + Assert.AreEqual(GetChangedToNonVirtualDelegate(o)(), typeof(List).ToString()); + o = null; try @@ -235,6 +253,9 @@ static void TestGenericOverStruct() var o2 = new MyChildGeneric(); Assert.AreEqual(o2.MovedToBaseClass(), typeof(List).ToString()); Assert.AreEqual(o2.ChangedToVirtual(), typeof(List).ToString()); + + // Test that changing a virtual to a non-virtual doesn't cause a crash. (Behavior is somewhat undefined, as this change is explicitly defined as a breaking change.) + Assert.AreEqual(GetChangedToNonVirtualDelegate(o2)(), typeof(List).ToString()); } static void TestInstanceFields() diff --git a/src/tests/readytorun/tests/test.cs b/src/tests/readytorun/tests/test.cs index 0f736073e9911..b6b74a0dff2cf 100644 --- a/src/tests/readytorun/tests/test.cs +++ b/src/tests/readytorun/tests/test.cs @@ -175,6 +175,18 @@ public string ChangedToVirtual() } #endif +#if V2 + public string ChangedToNonVirtual() + { + return "ChangedToNonVirtual"; + } +#else + public virtual string ChangedToNonVirtual() + { + return "ChangedToNonVirtual"; + } +#endif + public static void ThrowIOE() { #if !V2 @@ -279,6 +291,19 @@ public string ChangedToVirtual() } #endif +#if V2 + public string ChangedToNonVirtual() + { + return typeof(List).ToString(); + } +#else + public virtual string ChangedToNonVirtual() + { + return typeof(List).ToString(); + } +#endif + + public string NonVirtualMethod() { return "MyGeneric.NonVirtualMethod";