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..46d70a144cc1d0 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,22 @@ 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. + // 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. + // 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 +1502,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..c2900ffc1269a6 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,28 @@ 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) + { + 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 +2072,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..9330d03e8fe81d 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)) @@ -245,12 +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 && 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) 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/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) 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(); + } + } + } + }