From 0194065d3a1be9af655f5ec2e6afb9c9b0017a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 27 Jun 2022 16:43:26 +0900 Subject: [PATCH 1/3] Type loader support for static virtual methods Fixes #67745. Support for static virtual methods that was added in #66084 was enough for compile-time resolution of static virtual methods, but didn't cover dynamic code. For example, given following code: ```csharp interface IFoo { static virtual void Frob(); } class SomeCaller where T : IFoo { ... T.Frob(); } class SomeClass : IFoo { ... } ``` If we do `typeof(SomeCaller<>).MakeGenericType(typeof(SomeClass)` at runtime, the runtime has to find what method implements `IFoo.Frob` on `SomeClass` and ensure proper data structures are generated for `SomeCaller` so that the call lands in the right spot at runtime. On a high level, what we need: * Change to the compiler to generate extra data ("interface dispatch maps") that lets us find an implementation of interface method X on a given type Y. * Change to the runtime to read the new data structure. * Change to the compiler to generate extra method bodies for types that can potentially be used with MakeGeneric at runtime. This is an overapproximation since we don't know the set of types that will really be used. * Change to type loader data structures to capture when shared generic code needs to do this mapping, and change the code in the compiler that emits it, and to the type loader that reads it. I've made it so that the dispatch logic between instance and static methods is shared. It's not strictly necessary for both to go into the same data structure, but it prevents duplicating the code on the emission and reading side. The side effect of that is that static virtual methods now go into the sealed vtable. We have to put them somewhere. This spot is as good as any. I've also had to make a small change to the ordering of data structure generation within the type loader. I've made is so that EEType/MethodTable structures are fully populated before we start filling out generic dictionaries. This prevents us from calling into the runtime dispatch logic with EETypes/MethodTables that are not actually built yet. I really didn't want to duplicate the dispatch logic into the type loader. --- .../src/Internal/Runtime/MethodTable.cs | 51 ++++- .../System/Runtime/CachedInterfaceDispatch.cs | 8 +- .../src/System/Runtime/DispatchResolve.cs | 50 ++++- .../Runtime/Augments/RuntimeAugments.cs | 11 + .../src/System/Runtime/RuntimeImports.cs | 9 +- .../Runtime/TypeLoader/EETypeCreator.cs | 17 +- .../TypeLoader/GenericDictionaryCell.cs | 53 +++++ .../Runtime/TypeLoader/TypeBuilder.cs | 13 +- .../tools/Common/Compiler/MethodExtensions.cs | 13 +- .../Internal/NativeFormat/NativeFormat.cs | 2 + .../Internal/Runtime/RuntimeConstants.cs | 7 + .../Compiler/DependencyAnalysis/EETypeNode.cs | 30 ++- .../DependencyAnalysis/GenericLookupResult.cs | 18 +- .../InterfaceDispatchMapNode.cs | 101 +++++++-- .../NativeLayoutVertexNode.cs | 29 ++- .../DependencyAnalysis/SealedVTableNode.cs | 11 +- .../DependencyAnalysis/VTableSliceNode.cs | 8 - .../VirtualMethodUseNode.cs | 1 - .../DeadCodeElimination.cs | 37 ++++ .../SmokeTests/UnitTests/Interfaces.cs | 194 ++++++++++++++++++ 20 files changed, 596 insertions(+), 67 deletions(-) diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs index 6a56d48790efc9..348f0878a4bb87 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs @@ -68,9 +68,18 @@ internal unsafe struct DispatchMapEntry internal ushort _usInterfaceMethodSlot; internal ushort _usImplMethodSlot; } + [StructLayout(LayoutKind.Sequential)] + internal struct StaticDispatchMapEntry + { + // Do not put any other fields before this one. We need StaticDispatchMapEntry* be castable to DispatchMapEntry*. + internal DispatchMapEntry _entry; + internal ushort _usContextMapSource; + } private ushort _standardEntryCount; // Implementations on the class private ushort _defaultEntryCount; // Default implementations + private ushort _standardStaticEntryCount; // Implementations on the class (static virtuals) + private ushort _defaultStaticEntryCount; // Default implementations (static virtuals) private DispatchMapEntry _dispatchMap; // at least one entry if any interfaces defined public uint NumStandardEntries @@ -101,20 +110,54 @@ public uint NumDefaultEntries #endif } - public int Size + public uint NumStandardStaticEntries { get { - return sizeof(ushort) + sizeof(ushort) + sizeof(DispatchMapEntry) * ((int)_standardEntryCount + (int)_defaultEntryCount); + return _standardStaticEntryCount; + } +#if TYPE_LOADER_IMPLEMENTATION + set + { + _standardStaticEntryCount = checked((ushort)value); } +#endif } - public DispatchMapEntry* this[int index] + public uint NumDefaultStaticEntries { get { - return (DispatchMapEntry*)Unsafe.AsPointer(ref Unsafe.Add(ref _dispatchMap, index)); + return _defaultStaticEntryCount; } +#if TYPE_LOADER_IMPLEMENTATION + set + { + _defaultStaticEntryCount = checked((ushort)value); + } +#endif + } + + public int Size + { + get + { + return sizeof(ushort) + sizeof(ushort) + sizeof(ushort) + sizeof(ushort) + + sizeof(DispatchMapEntry) * ((int)_standardEntryCount + (int)_defaultEntryCount) + + sizeof(StaticDispatchMapEntry) * ((int)_standardStaticEntryCount + (int)_defaultStaticEntryCount); + } + } + + public DispatchMapEntry* GetEntry(int index) + { + Debug.Assert(index <= _defaultEntryCount + _standardEntryCount); + return (DispatchMapEntry*)Unsafe.AsPointer(ref Unsafe.Add(ref _dispatchMap, index)); + } + + public DispatchMapEntry* GetStaticEntry(int index) + { + Debug.Assert(index <= _defaultStaticEntryCount + _standardStaticEntryCount); + return (DispatchMapEntry*)(((StaticDispatchMapEntry*)Unsafe.AsPointer(ref Unsafe.Add(ref _dispatchMap, _standardEntryCount + _defaultEntryCount))) + index); } } diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs index 8c36e8312e4856..841f0cd145643b 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs @@ -82,7 +82,7 @@ private static IntPtr RhResolveDispatch(object pObject, EETypePtr interfaceType, } [RuntimeExport("RhResolveDispatchOnType")] - private static IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot) + private static IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot, EETypePtr* pGenericContext) { // Type of object we're dispatching on. MethodTable* pInstanceType = instanceType.ToPointer(); @@ -92,7 +92,8 @@ private static IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr return DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType, pInterfaceType, - slot); + slot, + (MethodTable**)pGenericContext); } private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell, ref DispatchCellInfo cellInfo) @@ -108,7 +109,8 @@ private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell, IntPtr pTargetCode = DispatchResolve.FindInterfaceMethodImplementationTarget(pResolvingInstanceType, cellInfo.InterfaceType.ToPointer(), - cellInfo.InterfaceSlot); + cellInfo.InterfaceSlot, + ppGenericContext: null); if (pTargetCode == IntPtr.Zero && pInstanceType->IsIDynamicInterfaceCastable) { // Dispatch not resolved through normal dispatch map, try using the IDynamicInterfaceCastable diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/DispatchResolve.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/DispatchResolve.cs index aa661feaf6cd1b..d46aaa96e3a017 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/DispatchResolve.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/DispatchResolve.cs @@ -12,7 +12,8 @@ internal static unsafe class DispatchResolve { public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtType, MethodTable* pItfType, - ushort itfSlotNumber) + ushort itfSlotNumber, + /* out */ MethodTable** ppGenericContext) { DynamicModule* dynamicModule = pTgtType->DynamicModule; @@ -39,7 +40,7 @@ public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtTy { ushort implSlotNumber; if (FindImplSlotForCurrentType( - pCur, pItfType, itfSlotNumber, fDoDefaultImplementationLookup, &implSlotNumber)) + pCur, pItfType, itfSlotNumber, fDoDefaultImplementationLookup, &implSlotNumber, ppGenericContext)) { IntPtr targetMethod; if (implSlotNumber < pCur->NumVtableSlots) @@ -85,7 +86,8 @@ private static bool FindImplSlotForCurrentType(MethodTable* pTgtType, MethodTable* pItfType, ushort itfSlotNumber, bool fDoDefaultImplementationLookup, - ushort* pImplSlotNumber) + ushort* pImplSlotNumber, + MethodTable** ppGenericContext) { bool fRes = false; @@ -110,13 +112,13 @@ private static bool FindImplSlotForCurrentType(MethodTable* pTgtType, bool fDoVariantLookup = false; // do not check variance for first scan of dispatch map fRes = FindImplSlotInSimpleMap( - pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, fDoVariantLookup, fDoDefaultImplementationLookup); + pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, fDoVariantLookup, fDoDefaultImplementationLookup); if (!fRes) { fDoVariantLookup = true; // check variance for second scan of dispatch map fRes = FindImplSlotInSimpleMap( - pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, fDoVariantLookup, fDoDefaultImplementationLookup); + pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, fDoVariantLookup, fDoDefaultImplementationLookup); } } @@ -127,6 +129,7 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, MethodTable* pItfType, uint itfSlotNumber, ushort* pImplSlotNumber, + MethodTable** ppGenericContext, bool actuallyCheckVariance, bool checkDefaultImplementations) { @@ -176,10 +179,18 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, } } + // It only makes sense to ask for generic context if we're asking about a static method + bool fStaticDispatch = ppGenericContext != null; + + // We either scan the instance or static portion of the dispatch map. Depends on what the caller wants. DispatchMap* pMap = pTgtType->DispatchMap; - DispatchMap.DispatchMapEntry* i = (*pMap)[checkDefaultImplementations ? (int)pMap->NumStandardEntries : 0]; - DispatchMap.DispatchMapEntry* iEnd = (*pMap)[checkDefaultImplementations ? (int)(pMap->NumStandardEntries + pMap->NumDefaultEntries) : (int)pMap->NumStandardEntries]; - for (; i != iEnd; ++i) + DispatchMap.DispatchMapEntry* i = fStaticDispatch ? + pMap->GetStaticEntry(checkDefaultImplementations ? (int)pMap->NumStandardStaticEntries : 0) : + pMap->GetEntry(checkDefaultImplementations ? (int)pMap->NumStandardEntries : 0); + DispatchMap.DispatchMapEntry* iEnd = fStaticDispatch ? + pMap->GetStaticEntry(checkDefaultImplementations ? (int)(pMap->NumStandardStaticEntries + pMap->NumDefaultStaticEntries) : (int)pMap->NumStandardStaticEntries) : + pMap->GetEntry(checkDefaultImplementations ? (int)(pMap->NumStandardEntries + pMap->NumDefaultEntries) : (int)pMap->NumStandardEntries); + for (; i != iEnd; i = fStaticDispatch ? (DispatchMap.DispatchMapEntry*)(((DispatchMap.StaticDispatchMapEntry*)i) + 1) : i + 1) { if (i->_usInterfaceMethodSlot == itfSlotNumber) { @@ -192,6 +203,12 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, if (pCurEntryType == pItfType) { *pImplSlotNumber = i->_usImplMethodSlot; + + // If this is a static method, the entry point is not usable without generic context. + // (Instance methods acquire the generic context from their `this`.) + if (fStaticDispatch) + *ppGenericContext = GetGenericContextSource(pTgtType, i); + return true; } else if (fCheckVariance && ((fArrayCovariance && pCurEntryType->IsGeneric) || pCurEntryType->HasGenericVariance)) @@ -227,6 +244,12 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, if (TypeCast.TypeParametersAreCompatible(itfArity, pCurEntryInstantiation, pItfInstantiation, pItfVarianceInfo, fArrayCovariance, null)) { *pImplSlotNumber = i->_usImplMethodSlot; + + // If this is a static method, the entry point is not usable without generic context. + // (Instance methods acquire the generic context from their `this`.) + if (fStaticDispatch) + *ppGenericContext = GetGenericContextSource(pTgtType, i); + return true; } } @@ -235,5 +258,16 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, return false; } + + private static unsafe MethodTable* GetGenericContextSource(MethodTable* pTgtType, DispatchMap.DispatchMapEntry* pEntry) + { + ushort usEncodedValue = ((DispatchMap.StaticDispatchMapEntry*)pEntry)->_usContextMapSource; + return usEncodedValue switch + { + StaticVirtualMethodContextSource.None => null, + StaticVirtualMethodContextSource.ContextFromThisClass => pTgtType, + _ => pTgtType->InterfaceMap[usEncodedValue - StaticVirtualMethodContextSource.ContextFromFirstInterface].InterfaceType + }; + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 03e4fffed6a990..09ee25f38db8ef 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -641,6 +641,17 @@ public static IntPtr ResolveDispatchOnType(RuntimeTypeHandle instanceType, Runti return RuntimeImports.RhResolveDispatchOnType(CreateEETypePtr(instanceType), CreateEETypePtr(interfaceType), checked((ushort)slot)); } + public static unsafe IntPtr ResolveStaticDispatchOnType(RuntimeTypeHandle instanceType, RuntimeTypeHandle interfaceType, int slot, out RuntimeTypeHandle genericContext) + { + EETypePtr genericContextPtr = default; + IntPtr result = RuntimeImports.RhResolveDispatchOnType(CreateEETypePtr(instanceType), CreateEETypePtr(interfaceType), checked((ushort)slot), &genericContextPtr); + if (result != IntPtr.Zero) + genericContext = new RuntimeTypeHandle(genericContextPtr); + else + genericContext = default; + return result; + } + public static IntPtr ResolveDispatch(object instance, RuntimeTypeHandle interfaceType, int slot) { return RuntimeImports.RhResolveDispatch(instance, CreateEETypePtr(interfaceType), checked((ushort)slot)); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index aedbe429fd00fb..ff23d238e10eb7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -454,7 +454,14 @@ internal static unsafe int RhCompatibleReentrantWaitAny(bool alertable, int time [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhResolveDispatchOnType")] - internal static extern IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot); + // For my life cannot figure out the ordering of modifiers this is expecting. +#pragma warning disable IDE0036 + internal static extern unsafe IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot, EETypePtr* pGenericContext); + + internal static unsafe IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot) + { + return RhResolveDispatchOnType(instanceType, interfaceType, slot, null); + } [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhGetRuntimeHelperForType")] diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs index c16a125c3474e4..b31abb9f70d49b 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs @@ -583,14 +583,25 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo DispatchMap* pDynamicDispatchMap = (DispatchMap*)dynamicDispatchMapPtr; pDynamicDispatchMap->NumStandardEntries = pTemplateDispatchMap->NumStandardEntries; pDynamicDispatchMap->NumDefaultEntries = pTemplateDispatchMap->NumDefaultEntries; + pDynamicDispatchMap->NumStandardStaticEntries = pTemplateDispatchMap->NumStandardStaticEntries; + pDynamicDispatchMap->NumDefaultStaticEntries = pTemplateDispatchMap->NumDefaultStaticEntries; - for (int i = 0; i < pTemplateDispatchMap->NumStandardEntries + pTemplateDispatchMap->NumDefaultEntries; i++) + uint numInstanceEntries = pTemplateDispatchMap->NumStandardEntries + pTemplateDispatchMap->NumDefaultEntries; + for (uint i = 0; i < numInstanceEntries + pTemplateDispatchMap->NumStandardStaticEntries + pTemplateDispatchMap->NumDefaultStaticEntries; i++) { - DispatchMap.DispatchMapEntry* pTemplateEntry = (*pTemplateDispatchMap)[i]; - DispatchMap.DispatchMapEntry* pDynamicEntry = (*pDynamicDispatchMap)[i]; + DispatchMap.DispatchMapEntry* pTemplateEntry = i < numInstanceEntries ? + pTemplateDispatchMap->GetEntry((int)i) : + pTemplateDispatchMap->GetStaticEntry((int)(i - numInstanceEntries)); + DispatchMap.DispatchMapEntry* pDynamicEntry = i < numInstanceEntries ? + pDynamicDispatchMap->GetEntry((int)i) : + pDynamicDispatchMap->GetStaticEntry((int)(i - numInstanceEntries)); pDynamicEntry->_usInterfaceIndex = pTemplateEntry->_usInterfaceIndex; pDynamicEntry->_usInterfaceMethodSlot = pTemplateEntry->_usInterfaceMethodSlot; + if (i >= numInstanceEntries) + { + ((DispatchMap.StaticDispatchMapEntry*)pDynamicEntry)->_usContextMapSource = ((DispatchMap.StaticDispatchMapEntry*)pTemplateEntry)->_usContextMapSource; + } if (pTemplateEntry->_usImplMethodSlot < pTemplateEEType->NumVtableSlots) { pDynamicEntry->_usImplMethodSlot = (ushort)state.VTableSlotsMapping.GetVTableSlotInTargetType(pTemplateEntry->_usImplMethodSlot); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/GenericDictionaryCell.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/GenericDictionaryCell.cs index 69df4c61d68df1..3aa25ab4816abd 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/GenericDictionaryCell.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/GenericDictionaryCell.cs @@ -239,6 +239,43 @@ internal override IntPtr Create(TypeBuilder builder) } #endif + /// + /// Used for non-generic static constrained Methods + /// + private class NonGenericStaticConstrainedMethodCell : GenericDictionaryCell + { + internal TypeDesc ConstraintType; + internal TypeDesc ConstrainedMethodType; + internal int ConstrainedMethodSlot; + + internal override void Prepare(TypeBuilder builder) + { + if (ConstraintType.IsCanonicalSubtype(CanonicalFormKind.Any) || ConstrainedMethodType.IsCanonicalSubtype(CanonicalFormKind.Any)) + Environment.FailFast("Unable to compute call information for a canonical type/method."); + + builder.RegisterForPreparation(ConstraintType); + builder.RegisterForPreparation(ConstrainedMethodType); + } + + internal override IntPtr Create(TypeBuilder builder) + { + IntPtr result = RuntimeAugments.ResolveStaticDispatchOnType( + builder.GetRuntimeTypeHandle(ConstraintType), + builder.GetRuntimeTypeHandle(ConstrainedMethodType), + ConstrainedMethodSlot, + out RuntimeTypeHandle genericContext); + + Debug.Assert(result != IntPtr.Zero); + + if (!genericContext.IsNull()) + { + result = FunctionPointerOps.GetGenericMethodFunctionPointer(result, genericContext.ToIntPtr()); + } + + return result; + } + } + private class StaticDataCell : GenericDictionaryCell { internal StaticDataKind DataKind; @@ -1749,6 +1786,21 @@ internal static GenericDictionaryCell ParseAndCreateCell(NativeLayoutInfoLoadCon } break; #endif + case FixupSignatureKind.NonGenericStaticConstrainedMethod: + { + var constraintType = nativeLayoutInfoLoadContext.GetType(ref parser); + var constrainedMethodType = nativeLayoutInfoLoadContext.GetType(ref parser); + var constrainedMethodSlot = parser.GetUnsigned(); + TypeLoaderLogger.WriteLine("NonGenericStaticConstrainedMethod: " + constraintType.ToString() + " Method " + constrainedMethodType.ToString() + ", slot #" + constrainedMethodSlot.LowLevelToString()); + + cell = new NonGenericStaticConstrainedMethodCell() + { + ConstraintType = constraintType, + ConstrainedMethodType = constrainedMethodType, + ConstrainedMethodSlot = (int)constrainedMethodSlot + }; + } + break; case FixupSignatureKind.IsInst: case FixupSignatureKind.CastClass: @@ -1793,6 +1845,7 @@ internal static GenericDictionaryCell ParseAndCreateCell(NativeLayoutInfoLoadCon case FixupSignatureKind.NotYetSupported: case FixupSignatureKind.ThreadStaticIndex: + case FixupSignatureKind.GenericStaticConstrainedMethod: TypeLoaderLogger.WriteLine("Valid dictionary entry, but not yet supported by the TypeLoader!"); throw new TypeBuilder.MissingTemplateException(); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs index ab1bbfba737a46..a0c7bc7324639c 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs @@ -1152,8 +1152,10 @@ private void FinishInterfaces(TypeDesc type, TypeBuilderState state) } } - private unsafe void FinishTypeDictionary(TypeDesc type, TypeBuilderState state) + private unsafe void FinishTypeDictionary(TypeDesc type) { + TypeBuilderState state = type.GetTypeBuilderState(); + if (state.Dictionary != null) { // First, update the dictionary slot in the type's vtable to point to the created dictionary when applicable @@ -1348,8 +1350,6 @@ private void FinishRuntimeType(TypeDesc type) FinishInterfaces(type, state); - FinishTypeDictionary(type, state); - FinishClassConstructor(type, state); #if FEATURE_UNIVERSAL_GENERICS @@ -1371,8 +1371,6 @@ private void FinishRuntimeType(TypeDesc type) if (typeAsSzArrayType.IsSzArray && !typeAsSzArrayType.ElementType.IsPointer) { - FinishTypeDictionary(type, state); - #if FEATURE_UNIVERSAL_GENERICS // For types that were allocated from universal canonical templates, patch their vtables with // pointers to calling convention conversion thunks @@ -1522,6 +1520,11 @@ private void FinishTypeAndMethodBuilding() FinishRuntimeType(_typesThatNeedTypeHandles[i]); } + for (int i = 0; i < _typesThatNeedTypeHandles.Count; i++) + { + FinishTypeDictionary(_typesThatNeedTypeHandles[i]); + } + for (int i = 0; i < _methodsThatNeedDictionaries.Count; i++) { FinishMethodDictionary(_methodsThatNeedDictionaries[i]); diff --git a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs index af23ea53fb8110..b8a92a276f859f 100644 --- a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs +++ b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs @@ -87,11 +87,22 @@ public static string GetUnmanagedCallersOnlyExportName(this EcmaMethod This) /// public static bool CanMethodBeInSealedVTable(this MethodDesc method) { + bool isInterfaceMethod = method.OwningType.IsInterface; + // Methods on interfaces never go into sealed vtable // We would hit this code path for default implementations of interface methods (they are newslot+final). // Inteface types don't get physical slots, but they have logical slot numbers and that logic shouldn't // attempt to place final+newslot methods differently. - return method.IsFinal && method.IsNewSlot && !method.OwningType.IsInterface; + if (method.IsFinal && method.IsNewSlot && !isInterfaceMethod) + return true; + + // Implementations of static virtual method also go into the sealed vtable. + // Again, we don't let that happen for interface methods because the slot numbers are only logical, + // not physical. + if (method.Signature.IsStatic && !isInterfaceMethod) + return true; + + return false; } public static bool NotCallableWithoutOwningEEType(this MethodDesc method) diff --git a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs index 785c7a97ca8350..5fd89c6d66b8e8 100644 --- a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs +++ b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs @@ -100,6 +100,8 @@ enum FixupSignatureKind : uint NonGenericDirectConstrainedMethod = 0x18, PointerToOtherSlot = 0x19, IntValue = 0x20, + NonGenericStaticConstrainedMethod = 0x21, + GenericStaticConstrainedMethod = 0x22, NotYetSupported = 0xee, } diff --git a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs index fc05a42e6435eb..f01bd3f30fe0b0 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs @@ -64,6 +64,13 @@ internal static class SpecialGVMInterfaceEntry public const uint Reabstraction = 0xFFFFFFFE; } + internal static class StaticVirtualMethodContextSource + { + public const ushort None = 0; + public const ushort ContextFromThisClass = 1; + public const ushort ContextFromFirstInterface = 2; + } + internal enum RuntimeHelperKind { AllocateObject, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs index 54b6424032c5b2..07e9d4b972f887 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -371,14 +371,25 @@ public sealed override IEnumerable GetConditionalSt if (interfaceMethod.HasInstantiation) continue; - // Static virtual methods are resolved at compile time - if (interfaceMethod.Signature.IsStatic) - continue; + bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic; - MethodDesc implMethod = defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod); + MethodDesc implMethod = isStaticInterfaceMethod ? + defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) : + defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod); if (implMethod != null) { - result.Add(new CombinedDependencyListEntry(factory.VirtualMethodUse(implMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method")); + if (isStaticInterfaceMethod) + { + Debug.Assert(!implMethod.IsVirtual); + + // If the interface method is used virtually, the implementation body is used + result.Add(new CombinedDependencyListEntry(factory.CanonicalEntrypoint(implMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method")); + } + else + { + // If the interface method is used virtually, the slot is used virtually + result.Add(new CombinedDependencyListEntry(factory.VirtualMethodUse(implMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method")); + } // If any of the implemented interfaces have variance, calls against compatible interface methods // could result in interface methods of this type being used (e.g. IEnumerable.GetEnumerator() @@ -386,7 +397,11 @@ public sealed override IEnumerable GetConditionalSt if (isVariantInterfaceImpl) { MethodDesc typicalInterfaceMethod = interfaceMethod.GetTypicalMethodDefinition(); - result.Add(new CombinedDependencyListEntry(factory.VirtualMethodUse(implMethod), factory.VariantInterfaceMethodUse(typicalInterfaceMethod), "Interface method")); + + object implMethodUseNode = isStaticInterfaceMethod ? + factory.CanonicalEntrypoint(implMethod) : factory.VirtualMethodUse(implMethod); + + result.Add(new CombinedDependencyListEntry(implMethodUseNode, factory.VariantInterfaceMethodUse(typicalInterfaceMethod), "Interface method")); result.Add(new CombinedDependencyListEntry(factory.VirtualMethodUse(interfaceMethod), factory.VariantInterfaceMethodUse(typicalInterfaceMethod), "Interface method")); } @@ -411,8 +426,9 @@ public sealed override IEnumerable GetConditionalSt implMethod = implMethod.InstantiateSignature(defType.Instantiation, Instantiation.Empty); MethodDesc defaultIntfMethod = implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); - if (defaultIntfMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + if (!isStaticInterfaceMethod && defaultIntfMethod.IsCanonicalMethod(CanonicalFormKind.Any)) { + // Canonical instance default methods need to go through a thunk that adds the right generic context defaultIntfMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(defaultIntfMethod, _type.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType); } result.Add(new CombinedDependencyListEntry(factory.MethodEntrypoint(defaultIntfMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method")); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs index 88f726c26d0424..bd906e595bd6c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs @@ -1427,6 +1427,21 @@ public ConstrainedMethodUseLookupResult(MethodDesc constrainedMethod, TypeDesc c Debug.Assert(!_constrainedMethod.HasInstantiation || !_directCall, "Direct call to constrained generic method isn't supported"); } + public override IEnumerable> NonRelocDependenciesFromUsage(NodeFactory factory) + { + MethodDesc canonMethod = _constrainedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); + + // If we're producing a full vtable for the type, we don't need to report virtual method use. + if (!factory.VTable(canonMethod.OwningType).HasFixedSlots) + { + // Report the method as virtually used so that types that could be used here at runtime + // have the appropriate implementations generated. + // This covers instantiations created at runtime (MakeGeneric*). The statically present generic dictionaries + // are already covered by the dependency analysis within the compiler because we call GetTarget for those. + yield return factory.VirtualMethodUse(canonMethod); + } + } + public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultContext dictionary) { MethodDesc instantiatedConstrainedMethod = _constrainedMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation); @@ -1486,8 +1501,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde public override NativeLayoutVertexNode TemplateDictionaryNode(NodeFactory factory) { - return factory.NativeLayout.NotSupportedDictionarySlot; - //return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall); + return factory.NativeLayout.ConstrainedMethodUse(_constrainedMethod, _constraintType, _directCall); } public override void WriteDictionaryTocData(NodeFactory factory, IGenericLookupResultTocWriter writer) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs index 151ab641a091ff..dd183cd1b54f38 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs @@ -111,18 +111,16 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact foreach (MethodDesc slotMethod in slots) { - // Static interface methods don't go in the dispatch map - if (slotMethod.Signature.IsStatic) - continue; - MethodDesc declMethod = slotMethod; - Debug.Assert(!declMethod.Signature.IsStatic && declMethod.IsVirtual); + Debug.Assert(declMethod.IsVirtual); if (interfaceOnDefinitionType != null) declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType); - var implMethod = declType.GetTypeDefinition().ResolveInterfaceMethodToVirtualMethodOnType(declMethod); + var implMethod = declMethod.Signature.IsStatic ? + declType.GetTypeDefinition().ResolveInterfaceMethodToStaticVirtualMethodOnType(declMethod) : + declType.GetTypeDefinition().ResolveInterfaceMethodToVirtualMethodOnType(declMethod); if (implMethod != null) { return true; @@ -143,6 +141,8 @@ void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) { var entryCountReservation = builder.ReserveShort(); var defaultEntryCountReservation = builder.ReserveShort(); + var staticEntryCountReservation = builder.ReserveShort(); + var defaultStaticEntryCountReservation = builder.ReserveShort(); int entryCount = 0; TypeDesc declType = _type.GetClosestDefType(); @@ -154,8 +154,10 @@ void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) Debug.Assert(declTypeRuntimeInterfaces.Length == declTypeDefinitionRuntimeInterfaces.Length); var defaultImplementations = new List<(int InterfaceIndex, int InterfaceMethodSlot, int ImplMethodSlot)>(); + var staticImplementations = new List<(int InterfaceIndex, int InterfaceMethodSlot, int ImplMethodSlot, int Context)>(); + var staticDefaultImplementations = new List<(int InterfaceIndex, int InterfaceMethodSlot, int ImplMethodSlot, int Context)>(); - // Resolve all the interfaces, but only emit non-default implementations + // Resolve all the interfaces, but only emit non-static and non-default implementations for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++) { var interfaceType = declTypeRuntimeInterfaces[interfaceIndex]; @@ -170,7 +172,9 @@ void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) if(!interfaceType.IsTypeDefinition) declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType); - var implMethod = declTypeDefinition.ResolveInterfaceMethodToVirtualMethodOnType(declMethod); + var implMethod = declMethod.Signature.IsStatic ? + declTypeDefinition.ResolveInterfaceMethodToStaticVirtualMethodOnType(declMethod) : + declTypeDefinition.ResolveInterfaceMethodToVirtualMethodOnType(declMethod); // Interface methods first implemented by a base type in the hierarchy will return null for the implMethod (runtime interface // dispatch will walk the inheritance chain). @@ -184,10 +188,26 @@ void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) if (!implType.IsTypeDefinition) targetMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(implMethod.GetTypicalMethodDefinition(), (InstantiatedType)implType); - builder.EmitShort((short)checked((ushort)interfaceIndex)); - builder.EmitShort((short)checked((ushort)(interfaceMethodSlot + (interfaceType.HasGenericDictionarySlot() ? 1 : 0)))); - builder.EmitShort((short)checked((ushort)VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, declType))); - entryCount++; + int emittedInterfaceSlot = interfaceMethodSlot + (interfaceType.HasGenericDictionarySlot() ? 1 : 0); + int emittedImplSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, declType); + if (targetMethod.Signature.IsStatic) + { + // If this is a static virtual, also remember whether we need generic context. + // The implementation is not callable without the generic context. + // Instance methods acquire the generic context from `this` and don't need it. + // The pointer to the generic context is stored in the owning type's vtable. + int genericContext = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstArg() + ? StaticVirtualMethodContextSource.ContextFromThisClass + : StaticVirtualMethodContextSource.None; + staticImplementations.Add((interfaceIndex, emittedInterfaceSlot, emittedImplSlot, genericContext)); + } + else + { + builder.EmitShort((short)checked((ushort)interfaceIndex)); + builder.EmitShort((short)checked((ushort)emittedInterfaceSlot)); + builder.EmitShort((short)checked((ushort)emittedImplSlot)); + entryCount++; + } } else { @@ -196,9 +216,10 @@ void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) int? implSlot = null; DefaultInterfaceMethodResolution result = declTypeDefinition.ResolveInterfaceMethodToDefaultImplementationOnType(declMethod, out implMethod); + DefType providingInterfaceDefinitionType = null; if (result == DefaultInterfaceMethodResolution.DefaultImplementation) { - DefType providingInterfaceDefinitionType = (DefType)implMethod.OwningType; + providingInterfaceDefinitionType = (DefType)implMethod.OwningType; implMethod = implMethod.InstantiateSignature(declType.Instantiation, Instantiation.Empty); implSlot = VirtualMethodSlotHelper.GetDefaultInterfaceMethodSlot(factory, implMethod, declType, providingInterfaceDefinitionType); } @@ -213,10 +234,36 @@ void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) if (implSlot.HasValue) { - defaultImplementations.Add(( - interfaceIndex, - interfaceMethodSlot + (interfaceType.HasGenericDictionarySlot() ? 1 : 0), - implSlot.Value)); + int emittedInterfaceSlot = interfaceMethodSlot + (interfaceType.HasGenericDictionarySlot() ? 1 : 0); + if (declMethod.Signature.IsStatic) + { + int genericContext = StaticVirtualMethodContextSource.None; + if (result == DefaultInterfaceMethodResolution.DefaultImplementation && + implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstArg()) + { + // If this is a static virtual, also remember whether we need generic context. + // The implementation is not callable without the generic context. + // Instance methods acquire the generic context from `this` and don't need it. + // For default interface methods, the generic context is acquired by indexing + // into the interface list of the owning type. + Debug.Assert(providingInterfaceDefinitionType != null); + int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType); + Debug.Assert(indexOfInterface >= 0); + genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface; + } + staticDefaultImplementations.Add(( + interfaceIndex, + emittedInterfaceSlot, + implSlot.Value, + genericContext)); + } + else + { + defaultImplementations.Add(( + interfaceIndex, + emittedInterfaceSlot, + implSlot.Value)); + } } } } @@ -230,9 +277,29 @@ void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) builder.EmitShort((short)checked((ushort)defaultImplementation.ImplMethodSlot)); } + // Now emit the static implementations + foreach (var staticImplementation in staticImplementations) + { + builder.EmitShort((short)checked((ushort)staticImplementation.InterfaceIndex)); + builder.EmitShort((short)checked((ushort)staticImplementation.InterfaceMethodSlot)); + builder.EmitShort((short)checked((ushort)staticImplementation.ImplMethodSlot)); + builder.EmitShort((short)checked((ushort)staticImplementation.Context)); + } + + // Now emit the static default implementations + foreach (var staticImplementation in staticDefaultImplementations) + { + builder.EmitShort((short)checked((ushort)staticImplementation.InterfaceIndex)); + builder.EmitShort((short)checked((ushort)staticImplementation.InterfaceMethodSlot)); + builder.EmitShort((short)checked((ushort)staticImplementation.ImplMethodSlot)); + builder.EmitShort((short)checked((ushort)staticImplementation.Context)); + } + // Update the header builder.EmitShort(entryCountReservation, (short)checked((ushort)entryCount)); builder.EmitShort(defaultEntryCountReservation, (short)checked((ushort)defaultImplementations.Count)); + builder.EmitShort(staticEntryCountReservation, (short)checked((ushort)staticImplementations.Count)); + builder.EmitShort(defaultStaticEntryCountReservation, (short)checked((ushort)staticDefaultImplementations.Count)); } public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index b05aa924f69831..822c3ad37b0de3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -2013,8 +2013,29 @@ protected sealed override string GetName(NodeFactory factory) => + "," + factory.NameMangler.GetMangledTypeName(_constraintType); - protected sealed override FixupSignatureKind SignatureKind => _constrainedMethod.HasInstantiation ? FixupSignatureKind.GenericConstrainedMethod : - (_directCall ? FixupSignatureKind.NonGenericDirectConstrainedMethod : FixupSignatureKind.NonGenericConstrainedMethod); + protected sealed override FixupSignatureKind SignatureKind + { + get + { + if (_constrainedMethod.Signature.IsStatic) + { + Debug.Assert(_directCall); + if (_constrainedMethod.HasInstantiation) + return FixupSignatureKind.GenericStaticConstrainedMethod; + else + return FixupSignatureKind.NonGenericStaticConstrainedMethod; + } + else + { + if (_constrainedMethod.HasInstantiation) + return FixupSignatureKind.GenericConstrainedMethod; + else if (_directCall) + return FixupSignatureKind.NonGenericDirectConstrainedMethod; + else + return FixupSignatureKind.NonGenericConstrainedMethod; + } + } + } public sealed override IEnumerable GetStaticDependencies(NodeFactory factory) { @@ -2052,14 +2073,14 @@ protected sealed override Vertex WriteSignatureVertex(NativeWriter writer, NodeF Vertex constraintType = factory.NativeLayout.TypeSignatureVertex(_constraintType).WriteVertex(factory); if (_constrainedMethod.HasInstantiation) { - Debug.Assert(SignatureKind == FixupSignatureKind.GenericConstrainedMethod); + Debug.Assert(SignatureKind is FixupSignatureKind.GenericConstrainedMethod or FixupSignatureKind.GenericStaticConstrainedMethod); Vertex constrainedMethodVertex = factory.NativeLayout.MethodLdTokenVertex(_constrainedMethod).WriteVertex(factory); Vertex relativeOffsetVertex = GetNativeWriter(factory).GetRelativeOffsetSignature(constrainedMethodVertex); return writer.GetTuple(constraintType, relativeOffsetVertex); } else { - Debug.Assert((SignatureKind == FixupSignatureKind.NonGenericConstrainedMethod) || (SignatureKind == FixupSignatureKind.NonGenericDirectConstrainedMethod)); + Debug.Assert(SignatureKind is FixupSignatureKind.NonGenericConstrainedMethod or FixupSignatureKind.NonGenericDirectConstrainedMethod or FixupSignatureKind.NonGenericStaticConstrainedMethod); Vertex methodType = factory.NativeLayout.TypeSignatureVertex(_constrainedMethod.OwningType).WriteVertex(factory); var canonConstrainedMethod = _constrainedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); int interfaceSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, canonConstrainedMethod, canonConstrainedMethod.OwningType); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs index 3e82ac868213e9..3a557591c66ac5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs @@ -147,13 +147,16 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly) if (!interfaceType.IsTypeDefinition) declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType); - var implMethod = declTypeDefinition.ResolveInterfaceMethodToVirtualMethodOnType(declMethod); + var implMethod = declMethod.Signature.IsStatic ? + declTypeDefinition.ResolveInterfaceMethodToStaticVirtualMethodOnType(declMethod) : + declTypeDefinition.ResolveInterfaceMethodToVirtualMethodOnType(declMethod); // Interface methods first implemented by a base type in the hierarchy will return null for the implMethod (runtime interface // dispatch will walk the inheritance chain). if (implMethod != null) { - if (implMethod.CanMethodBeInSealedVTable() && !implMethod.OwningType.HasSameTypeDefinition(declType)) + if (implMethod.Signature.IsStatic || + (implMethod.CanMethodBeInSealedVTable() && !implMethod.OwningType.HasSameTypeDefinition(declType))) { TypeDesc implType = declType; while (!implType.HasSameTypeDefinition(implMethod.OwningType)) @@ -246,8 +249,10 @@ public static SealedVTableEntry FromDefaultInterfaceMethod(MethodDesc method, De public IMethodNode GetTarget(NodeFactory factory, TypeDesc implementingClass) { MethodDesc implMethod = _method.GetCanonMethodTarget(CanonicalFormKind.Specific); - if (_interfaceDefinition != null && implMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + if (_interfaceDefinition != null && !_method.Signature.IsStatic && implMethod.IsCanonicalMethod(CanonicalFormKind.Any)) { + // Canonical instance default interface methods need to go through a thunk that acquires the generic context from `this`. + // Static methods have their generic context passed explicitly. implMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(implMethod, implementingClass.ConvertToCanonForm(CanonicalFormKind.Specific), _interfaceDefinition); } return factory.MethodEntrypoint(implMethod, _method.OwningType.IsValueType); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs index 0e91c5bd552de0..8d80ea2f684e3b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VTableSliceNode.cs @@ -114,10 +114,6 @@ private static IReadOnlyList ComputeSlots(TypeDesc type) foreach (var method in allSlots) { - // Static virtual methods don't go in vtables - if (method.Signature.IsStatic) - continue; - // GVMs are not emitted in the type's vtable. if (method.HasInstantiation) continue; @@ -225,10 +221,6 @@ public override IEnumerable GetConditionalStaticDep if (method.HasInstantiation) continue; - // Static interface methods don't go into vtables - if (method.Signature.IsStatic) - continue; - // Current type doesn't define this slot. Another VTableSlice will take care of this. if (method.OwningType != defType) continue; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs index 1240dc82df38cf..7ca2bb591ad165 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs @@ -26,7 +26,6 @@ public VirtualMethodUseNode(MethodDesc decl) { Debug.Assert(!decl.IsRuntimeDeterminedExactMethod); Debug.Assert(decl.IsVirtual); - Debug.Assert(!decl.Signature.IsStatic); // Virtual method use always represents the slot defining method of the virtual. // Places that might see virtual methods being used through an override need to normalize diff --git a/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs index b371e2469c24b6..ad1b6753909770 100644 --- a/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs @@ -18,6 +18,7 @@ static int Main() TestAbstractDerivedByUnrelatedTypeWithDevirtualizedCall.Run(); TestUnusedDefaultInterfaceMethod.Run(); TestArrayElementTypeOperations.Run(); + TestStaticVirtualMethodOptimizations.Run(); return 100; } @@ -273,6 +274,42 @@ class NeverAllocated3 } } + class TestStaticVirtualMethodOptimizations + { + interface IFoo + { + static abstract Type Frob(); + } + + struct StructWithReachableStaticVirtual : IFoo + { + public static Type Frob() => typeof(Marker1); + } + + class ClassWithUnreachableStaticVirtual : IFoo + { + public static Type Frob() => typeof(Marker2); + } + + class Marker1 { } + class Marker2 { } + + static Type Call() where T : IFoo => T.Frob(); + + public static void Run() + { + Console.WriteLine("Testing unused static virtual method optimization"); + + // No shared generic code - we should not see IFoo.Frob as "virtually used" + Call(); + + // Implements IFoo.Frob, but there's no consumption place, so won't be generated. + new ClassWithUnreachableStaticVirtual().ToString(); + + ThrowIfNotPresent(typeof(TestStaticVirtualMethodOptimizations), nameof(Marker1)); + ThrowIfPresent(typeof(TestStaticVirtualMethodOptimizations), nameof(Marker2)); + } + } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "That's the point")] diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs index 3ff1cd039af5f6..9ef60373f0414c 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs @@ -44,6 +44,9 @@ public static int Run() TestStaticInterfaceMethodsAnalysis.Run(); TestStaticInterfaceMethods.Run(); TestSimpleStaticDefaultInterfaceMethods.Run(); + TestSimpleDynamicStaticVirtualMethods.Run(); + TestGenericDynamicStaticVirtualMethods.Run(); + TestVariantGenericDynamicStaticVirtualMethods.Run(); return Pass; } @@ -1094,4 +1097,195 @@ public static void Run() throw new Exception(); } } + + class TestSimpleDynamicStaticVirtualMethods + { + public interface IFoo + { + static abstract int CallMeDirect(); + static abstract int CallMeIndirect(); + static virtual int DefaultImplemented() => 42; + } + + class Foo : IFoo + { + public static int CallMeDirect() => 2019; + public static int CallMeIndirect() => 2022; + } + + class FrobCaller where T : IFoo + { + public static int CallDirect() => T.CallMeDirect(); + public static int CallIndirect() + { + Func d = T.CallMeIndirect; + return d(); + } + public static int CallDefault() => T.DefaultImplemented(); + } + + static Type s_fooType = typeof(Foo); + + public static void Run() + { + Type t = typeof(FrobCaller<>).MakeGenericType(s_fooType); + + { + var mi = t.GetMethod("CallDirect"); + int result = (int)mi.Invoke(null, Array.Empty()); + if (result != 2019) + throw new Exception(); + } + + { + var mi = t.GetMethod("CallIndirect"); + int result = (int)mi.Invoke(null, Array.Empty()); + if (result != 2022) + throw new Exception(); + } + + { + var mi = t.GetMethod("CallDefault"); + int result = (int)mi.Invoke(null, Array.Empty()); + if (result != 42) + throw new Exception(); + } + } + } + + class TestGenericDynamicStaticVirtualMethods + { + public interface IFoo + { + static abstract (int, Type) CallMeDirect(); + static abstract (int, Type) CallMeIndirect(); + static virtual (int, Type) DefaultImplemented() => (42, typeof(T[])); + } + + class Foo : IFoo + { + public static (int, Type) CallMeDirect() => (2019, typeof(T[,])); + public static (int, Type) CallMeIndirect() => (2022, typeof(T[,,])); + } + + class FrobCaller where T : IFoo + { + public static (int, Type) CallDirect() => T.CallMeDirect(); + public static (int, Type) CallIndirect() + { + Func<(int, Type)> d = T.CallMeIndirect; + return d(); + } + public static (int, Type) CallDefault() => T.DefaultImplemented(); + } + + class Wrapper + { + public static (int, Type) CallDirect() => FrobCaller, T>.CallDirect(); + public static (int, Type) CallIndirect() => FrobCaller, T>.CallIndirect(); + public static (int, Type) CallDefault() => FrobCaller, T>.CallDefault(); + } + + class Atom { } + + static Type s_atomType = typeof(Atom); + + public static void Run() + { + Type t = typeof(Wrapper<>).MakeGenericType(s_atomType); + + { + var mi = t.GetMethod("CallDirect"); + var result = ((int, Type))mi.Invoke(null, Array.Empty()); + if (result.Item1 != 2019) + throw new Exception(); + if (result.Item2.GetArrayRank() != 2 || result.Item2.GetElementType() != s_atomType) + throw new Exception(); + } + + { + var mi = t.GetMethod("CallIndirect"); + var result = ((int, Type))mi.Invoke(null, Array.Empty()); + if (result.Item1 != 2022) + throw new Exception(); + if (result.Item2.GetArrayRank() != 3 || result.Item2.GetElementType() != s_atomType) + throw new Exception(); + } + + { + var mi = t.GetMethod("CallDefault"); + var result = ((int, Type))mi.Invoke(null, Array.Empty()); + if (result.Item1 != 42) + throw new Exception(); + if (result.Item2.GetArrayRank() != 1 || result.Item2.GetElementType() != s_atomType) + throw new Exception(); + } + } + } + + class TestVariantGenericDynamicStaticVirtualMethods + { + public interface IFoo + { + static abstract (int, Type) CallMeDirect(); + static abstract (int, Type) CallMeIndirect(); + static virtual (int, Type) DefaultImplemented() => (42, typeof(T[])); + } + + class AbjectFail : IFoo + { + public static (int, Type) CallMeDirect() => (2019, typeof(T[,])); + public static (int, Type) CallMeIndirect() => (2022, typeof(T[,,])); + } + + class FrobCaller where T : IFoo + { + public static (int, Type) CallDirect() => T.CallMeDirect(); + public static (int, Type) CallIndirect() + { + Func<(int, Type)> d = T.CallMeIndirect; + return d(); + } + public static (int, Type) CallDefault() => T.DefaultImplemented(); + } + + class AtomBase { } + class Atom : AtomBase { } + + static Type s_atomType = typeof(Atom); + static Type s_atomBaseType = typeof(AtomBase); + + public static void Run() + { + Type t = typeof(FrobCaller<,>).MakeGenericType(typeof(AbjectFail), s_atomType); + + { + var mi = t.GetMethod("CallDirect"); + var result = ((int, Type))mi.Invoke(null, Array.Empty()); + if (result.Item1 != 2019) + throw new Exception(); + if (result.Item2.GetArrayRank() != 2 || result.Item2.GetElementType() != s_atomBaseType) + throw new Exception(); + } + + { + var mi = t.GetMethod("CallIndirect"); + var result = ((int, Type))mi.Invoke(null, Array.Empty()); + if (result.Item1 != 2022) + throw new Exception(); + if (result.Item2.GetArrayRank() != 3 || result.Item2.GetElementType() != s_atomBaseType) + throw new Exception(); + } + + { + var mi = t.GetMethod("CallDefault"); + var result = ((int, Type))mi.Invoke(null, Array.Empty()); + if (result.Item1 != 42) + throw new Exception(); + if (result.Item2.GetArrayRank() != 1 || result.Item2.GetElementType() != s_atomBaseType) + throw new Exception(); + } + } + } + } From c184d391821a3170d445bfe3754c4421a3166452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 27 Jun 2022 17:46:22 +0900 Subject: [PATCH 2/3] Facepalm --- .../Compiler/DependencyAnalysis/GenericLookupResult.cs | 3 ++- .../Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs | 1 - .../Compiler/DependencyAnalysis/SealedVTableNode.cs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs index bd906e595bd6c9..46d70a144cc1d0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs @@ -1432,7 +1432,8 @@ public override IEnumerable> NonRelocDependencie MethodDesc canonMethod = _constrainedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); // If we're producing a full vtable for the type, we don't need to report virtual method use. - if (!factory.VTable(canonMethod.OwningType).HasFixedSlots) + // We also don't report virtual method use for generic virtual methods - tracking those is orthogonal. + if (!factory.VTable(canonMethod.OwningType).HasFixedSlots && !canonMethod.HasInstantiation) { // Report the method as virtually used so that types that could be used here at runtime // have the appropriate implementations generated. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index 822c3ad37b0de3..c2900ffc1269a6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -2019,7 +2019,6 @@ protected sealed override FixupSignatureKind SignatureKind { if (_constrainedMethod.Signature.IsStatic) { - Debug.Assert(_directCall); if (_constrainedMethod.HasInstantiation) return FixupSignatureKind.GenericStaticConstrainedMethod; else diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs index 3a557591c66ac5..9330d03e8fe81d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs @@ -248,14 +248,15 @@ public static SealedVTableEntry FromDefaultInterfaceMethod(MethodDesc method, De public IMethodNode GetTarget(NodeFactory factory, TypeDesc implementingClass) { + bool isStaticVirtualMethod = _method.Signature.IsStatic; MethodDesc implMethod = _method.GetCanonMethodTarget(CanonicalFormKind.Specific); - if (_interfaceDefinition != null && !_method.Signature.IsStatic && implMethod.IsCanonicalMethod(CanonicalFormKind.Any)) + if (_interfaceDefinition != null && !isStaticVirtualMethod && implMethod.IsCanonicalMethod(CanonicalFormKind.Any)) { // Canonical instance default interface methods need to go through a thunk that acquires the generic context from `this`. // Static methods have their generic context passed explicitly. implMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(implMethod, implementingClass.ConvertToCanonForm(CanonicalFormKind.Specific), _interfaceDefinition); } - return factory.MethodEntrypoint(implMethod, _method.OwningType.IsValueType); + return factory.MethodEntrypoint(implMethod, unboxingStub: !isStaticVirtualMethod && _method.OwningType.IsValueType); } public bool Matches(MethodDesc method) From fd89b2691d7079a35a3f9142b52e65d241cc1888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 28 Jun 2022 09:58:29 +0900 Subject: [PATCH 3/3] Relax assert --- .../ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index c2845403c2a55b..bfdae7869892ba 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -1306,7 +1306,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO if (directCall && resolvedConstraint && pResult->exactContextNeedsRuntimeLookup) { - // We want to do a direct call to a shared method on a valuetype. We need to provide + // We want to do a direct call to a shared method on a type. We need to provide // a generic context, but the JitInterface doesn't provide a way for us to do it from here. // So we do the next best thing and ask RyuJIT to look up a fat pointer. @@ -1315,8 +1315,8 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO pResult->nullInstanceCheck = !targetMethod.Signature.IsStatic; // We have the canonical version of the method - find the runtime determined version. - // This is simplified because we know the method is on a valuetype. - Debug.Assert(targetMethod.OwningType.IsValueType); + // For now simplify it by assuming the resolved method is on the same type as the constraint. + Debug.Assert(targetMethod.OwningType.GetTypeDefinition() == constrainedType.GetTypeDefinition()); TypeDesc runtimeDeterminedConstrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *pConstrainedResolvedToken); if (forceUseRuntimeLookup)