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 7a242497f30826..9a1b1ad7ef985a 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs @@ -82,14 +82,46 @@ private static IntPtr RhResolveDispatch(object pObject, MethodTable* interfaceTy } [RuntimeExport("RhResolveDispatchOnType")] - private static IntPtr RhResolveDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot, MethodTable** ppGenericContext) + private static IntPtr RhResolveDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot) { return DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType, pInterfaceType, slot, + flags: default, + ppGenericContext: null); + } + + [RuntimeExport("RhResolveStaticDispatchOnType")] + private static IntPtr RhResolveStaticDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot, MethodTable** ppGenericContext) + { + return DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType, + pInterfaceType, + slot, + DispatchResolve.ResolveFlags.Static, ppGenericContext); } + [RuntimeExport("RhResolveDynamicInterfaceCastableDispatchOnType")] + private static IntPtr RhResolveDynamicInterfaceCastableDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot, MethodTable** ppGenericContext) + { + IntPtr result = DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType, + pInterfaceType, + slot, + DispatchResolve.ResolveFlags.IDynamicInterfaceCastable, + ppGenericContext); + + if ((result & (nint)DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag) != 0) + { + result &= ~(nint)DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag; + } + else + { + *ppGenericContext = null; + } + + return result; + } + private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell, ref DispatchCellInfo cellInfo) { // Type of object we're dispatching on. @@ -97,13 +129,10 @@ private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell, if (cellInfo.CellType == DispatchCellType.InterfaceAndSlot) { - // Type whose DispatchMap is used. Usually the same as the above but for types which implement IDynamicInterfaceCastable - // we may repeat this process with an alternate type. - MethodTable* pResolvingInstanceType = pInstanceType; - - IntPtr pTargetCode = DispatchResolve.FindInterfaceMethodImplementationTarget(pResolvingInstanceType, + IntPtr pTargetCode = DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType, cellInfo.InterfaceType, cellInfo.InterfaceSlot, + flags: default, ppGenericContext: null); if (pTargetCode == IntPtr.Zero && pInstanceType->IsIDynamicInterfaceCastable) { 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 349415c743aa9a..271fdd231c611a 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/DispatchResolve.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/DispatchResolve.cs @@ -13,21 +13,21 @@ internal static unsafe class DispatchResolve public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtType, MethodTable* pItfType, ushort itfSlotNumber, + ResolveFlags flags, /* out */ MethodTable** ppGenericContext) { + // We set this bit below during second pass, callers should not set it. + Debug.Assert((flags & ResolveFlags.DefaultInterfaceImplementation) == 0); + // Start at the current type and work up the inheritance chain MethodTable* pCur = pTgtType; - // We first look at non-default implementation. Default implementations are only considered - // if the "old algorithm" didn't come up with an answer. - bool fDoDefaultImplementationLookup = false; - again: while (pCur != null) { ushort implSlotNumber; if (FindImplSlotForCurrentType( - pCur, pItfType, itfSlotNumber, fDoDefaultImplementationLookup, &implSlotNumber, ppGenericContext)) + pCur, pItfType, itfSlotNumber, flags, &implSlotNumber, ppGenericContext)) { IntPtr targetMethod; if (implSlotNumber < pCur->NumVtableSlots) @@ -58,9 +58,9 @@ public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtTy } // If we haven't found an implementation, do a second pass looking for a default implementation. - if (!fDoDefaultImplementationLookup) + if ((flags & ResolveFlags.DefaultInterfaceImplementation) == 0) { - fDoDefaultImplementationLookup = true; + flags |= ResolveFlags.DefaultInterfaceImplementation; pCur = pTgtType; goto again; } @@ -72,10 +72,13 @@ public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtTy private static bool FindImplSlotForCurrentType(MethodTable* pTgtType, MethodTable* pItfType, ushort itfSlotNumber, - bool fDoDefaultImplementationLookup, + ResolveFlags flags, ushort* pImplSlotNumber, MethodTable** ppGenericContext) { + // We set this below during second pass, callers should not set this. + Debug.Assert((flags & ResolveFlags.Variant) == 0); + bool fRes = false; // If making a call and doing virtual resolution don't look into the dispatch map, @@ -96,16 +99,14 @@ private static bool FindImplSlotForCurrentType(MethodTable* pTgtType, // result in interesting behavior such as a derived type only overriding one particular instantiation // and funneling all the dispatches to it, but its the algorithm. - bool fDoVariantLookup = false; // do not check variance for first scan of dispatch map - fRes = FindImplSlotInSimpleMap( - pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, fDoVariantLookup, fDoDefaultImplementationLookup); + pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, flags); if (!fRes) { - fDoVariantLookup = true; // check variance for second scan of dispatch map + flags |= ResolveFlags.Variant; // check variance for second scan of dispatch map fRes = FindImplSlotInSimpleMap( - pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, fDoVariantLookup, fDoDefaultImplementationLookup); + pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, flags); } } @@ -117,8 +118,7 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, uint itfSlotNumber, ushort* pImplSlotNumber, MethodTable** ppGenericContext, - bool actuallyCheckVariance, - bool checkDefaultImplementations) + ResolveFlags flags) { Debug.Assert(pTgtType->HasDispatchMap, "Missing dispatch map"); @@ -130,7 +130,7 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, bool fCheckVariance = false; bool fArrayCovariance = false; - if (actuallyCheckVariance) + if ((flags & ResolveFlags.Variant) != 0) { fCheckVariance = pItfType->HasGenericVariance; fArrayCovariance = pTgtType->IsArray; @@ -166,8 +166,8 @@ 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; + bool fStaticDispatch = (flags & ResolveFlags.Static) != 0; + bool checkDefaultImplementations = (flags & ResolveFlags.DefaultInterfaceImplementation) != 0; // We either scan the instance or static portion of the dispatch map. Depends on what the caller wants. DispatchMap* pMap = pTgtType->DispatchMap; @@ -190,8 +190,11 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, // If this is a static method, the entry point is not usable without generic context. // (Instance methods acquire the generic context from their `this`.) + // Same for IDynamicInterfaceCastable (that has a `this` but it's not useful) if (fStaticDispatch) *ppGenericContext = GetGenericContextSource(pTgtType, i); + else if ((flags & ResolveFlags.IDynamicInterfaceCastable) != 0) + *ppGenericContext = pTgtType; return true; } @@ -231,8 +234,11 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, // If this is a static method, the entry point is not usable without generic context. // (Instance methods acquire the generic context from their `this`.) + // Same for IDynamicInterfaceCastable (that has a `this` but it's not useful) if (fStaticDispatch) *ppGenericContext = GetGenericContextSource(pTgtType, i); + else if ((flags & ResolveFlags.IDynamicInterfaceCastable) != 0) + *ppGenericContext = pTgtType; return true; } @@ -253,5 +259,13 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType, _ => pTgtType->InterfaceMap[usEncodedValue - StaticVirtualMethodContextSource.ContextFromFirstInterface] }; } + + public enum ResolveFlags + { + Variant = 0x1, + DefaultInterfaceImplementation = 0x2, + Static = 0x4, + IDynamicInterfaceCastable = 0x8, + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 507b4c14936921..6bfe8b6b9ffdda 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -825,6 +825,10 @@ CP0001 T:System.Runtime.CompilerServices.StaticClassConstructionContext + + CP0001 + T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 + CP0002 M:System.Reflection.MethodBase.GetParametersAsSpan 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 7db11a82041778..4e376c5b803922 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 @@ -493,7 +493,7 @@ public static bool IsDynamicType(RuntimeTypeHandle typeHandle) public static unsafe IntPtr ResolveStaticDispatchOnType(RuntimeTypeHandle instanceType, RuntimeTypeHandle interfaceType, int slot, out RuntimeTypeHandle genericContext) { MethodTable* genericContextPtr = default; - IntPtr result = RuntimeImports.RhResolveDispatchOnType(instanceType.ToMethodTable(), interfaceType.ToMethodTable(), checked((ushort)slot), &genericContextPtr); + IntPtr result = RuntimeImports.RhResolveStaticDispatchOnType(instanceType.ToMethodTable(), interfaceType.ToMethodTable(), checked((ushort)slot), &genericContextPtr); if (result != IntPtr.Zero) genericContext = new RuntimeTypeHandle(genericContextPtr); else diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SharedCodeHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SharedCodeHelpers.cs index 482a9a7b357a7e..172f7a6590e35d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SharedCodeHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SharedCodeHelpers.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime; + using Debug = System.Diagnostics.Debug; namespace Internal.Runtime.CompilerHelpers @@ -12,15 +14,13 @@ internal static class SharedCodeHelpers { public static unsafe MethodTable* GetOrdinalInterface(MethodTable* pType, ushort interfaceIndex) { - Debug.Assert(interfaceIndex <= pType->NumInterfaces); + Debug.Assert(interfaceIndex < pType->NumInterfaces); return pType->InterfaceMap[interfaceIndex]; } public static unsafe MethodTable* GetCurrentSharedThunkContext() { - // TODO: We should return the current context from the ThunkPool - // https://github.com/dotnet/runtimelab/issues/1442 - return null; + return (MethodTable*)RuntimeImports.GetCurrentInteropThunkContext(); } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/IDynamicInterfaceCastableSupport.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/IDynamicInterfaceCastableSupport.cs index 54878128ff9823..a1e146fb1b0a8c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/IDynamicInterfaceCastableSupport.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/IDynamicInterfaceCastableSupport.cs @@ -4,6 +4,12 @@ using System; using System.Runtime; using System.Runtime.InteropServices; +using System.Threading; + +using Internal.Runtime.Augments; +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; namespace Internal.Runtime { @@ -15,6 +21,8 @@ internal static bool IDynamicCastableIsInterfaceImplemented(IDynamicInterfaceCas return instance.IsInterfaceImplemented(new RuntimeTypeHandle(interfaceType), throwIfNotImplemented); } + private static readonly object s_thunkPoolHeap = RuntimeAugments.CreateThunksHeap(RuntimeImports.GetInteropCommonStubAddress()); + [RuntimeExport("IDynamicCastableGetInterfaceImplementation")] internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot) { @@ -28,11 +36,30 @@ internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterf { ThrowInvalidOperationException(implType); } - IntPtr result = RuntimeImports.RhResolveDispatchOnType(implType, interfaceType, slot); + + MethodTable* genericContext = null; + IntPtr result = RuntimeImports.RhResolveDynamicInterfaceCastableDispatchOnType(implType, interfaceType, slot, &genericContext); if (result == IntPtr.Zero) { IDynamicCastableGetInterfaceImplementationFailure(instance, interfaceType, implType); } + + if (genericContext != null) + { + if (!s_thunkHashtable.TryGetValue(new InstantiatingThunkKey(result, (nint)genericContext), out nint thunk)) + { + thunk = RuntimeAugments.AllocateThunk(s_thunkPoolHeap); + RuntimeAugments.SetThunkData(s_thunkPoolHeap, thunk, (nint)genericContext, result); + nint thunkInHashtable = s_thunkHashtable.AddOrGetExisting(thunk); + if (thunkInHashtable != thunk) + { + RuntimeAugments.FreeThunk(s_thunkPoolHeap, thunk); + thunk = thunkInHashtable; + } + } + + result = thunk; + } return result; } @@ -67,5 +94,46 @@ private static void IDynamicCastableGetInterfaceImplementationFailure(object ins throw new EntryPointNotFoundException(); } + + private static readonly InstantiatingThunkHashtable s_thunkHashtable = new InstantiatingThunkHashtable(); + + private class InstantiatingThunkHashtable : LockFreeReaderHashtableOfPointers + { + protected override bool CompareKeyToValue(InstantiatingThunkKey key, nint value) + { + bool result = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value, out nint context, out nint target); + Debug.Assert(result); + return key.Target == target && key.Context == context; + } + + protected override bool CompareValueToValue(nint value1, nint value2) + { + bool result1 = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value1, out nint context1, out nint target1); + Debug.Assert(result1); + + bool result2 = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value2, out nint context2, out nint target2); + Debug.Assert(result2); + return context1 == context2 && target1 == target2; + } + + protected override nint ConvertIntPtrToValue(nint pointer) => pointer; + protected override nint ConvertValueToIntPtr(nint value) => value; + protected override nint CreateValueFromKey(InstantiatingThunkKey key) => throw new NotImplementedException(); + protected override int GetKeyHashCode(InstantiatingThunkKey key) => HashCode.Combine(key.Target, key.Context); + + protected override int GetValueHashCode(nint value) + { + bool result = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value, out nint context, out nint target); + Debug.Assert(result); + return HashCode.Combine(target, context); + } + } + + private struct InstantiatingThunkKey + { + public readonly nint Target; + public readonly nint Context; + public InstantiatingThunkKey(nint target, nint context) => (Target, Context) = (target, context); + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index fc97632716fd2a..06d92c3b8b5d5e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -332,6 +332,9 @@ Utilities\LockFreeReaderHashtable.cs + + Utilities\LockFreeReaderHashtableOfPointers.cs + System\Collections\Generic\LowLevelList.cs 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 7f833e613e5c4b..52fe3bbaa5ad6f 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 @@ -469,12 +469,15 @@ internal static unsafe int RhCompatibleReentrantWaitAny(bool alertable, int time [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhResolveDispatchOnType")] - internal static extern unsafe IntPtr RhResolveDispatchOnType(MethodTable* instanceType, MethodTable* interfaceType, ushort slot, MethodTable** pGenericContext); + internal static extern unsafe IntPtr RhResolveDispatchOnType(MethodTable* instanceType, MethodTable* interfaceType, ushort slot); - internal static unsafe IntPtr RhResolveDispatchOnType(MethodTable* instanceType, MethodTable* interfaceType, ushort slot) - { - return RhResolveDispatchOnType(instanceType, interfaceType, slot, null); - } + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhResolveStaticDispatchOnType")] + internal static extern unsafe IntPtr RhResolveStaticDispatchOnType(MethodTable* instanceType, MethodTable* interfaceType, ushort slot, MethodTable** pGenericContext); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhResolveDynamicInterfaceCastableDispatchOnType")] + internal static extern unsafe IntPtr RhResolveDynamicInterfaceCastableDispatchOnType(MethodTable* instanceType, MethodTable* interfaceType, ushort slot, MethodTable** pGenericContext); [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhGetRuntimeHelperForType")] diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj b/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj index b00904e556f2ca..09881516e2950c 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj @@ -200,9 +200,6 @@ Internal\TypeSystem\ThrowHelper.Common.cs - - LockFreeReaderHashtableOfPointers.cs - Internal\TypeSystem\Utilities\ExceptionTypeNameFormatter.cs diff --git a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs index 8ce74a03072b4d..9d356cb066813d 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs @@ -43,6 +43,11 @@ internal static class SpecialDispatchMapSlot public const ushort Reabstraction = 0xFFFF; } + internal static class DispatchMapCodePointerFlags + { + public const int RequiresInstantiatingThunkFlag = 2; + } + internal static class SpecialGVMInterfaceEntry { public const uint Diamond = 0xFFFFFFFF; diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/LockFreeReaderHashtableOfPointers.cs b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/LockFreeReaderHashtableOfPointers.cs index 0decdff50a7a0d..af6aac636146ed 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/LockFreeReaderHashtableOfPointers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/LockFreeReaderHashtableOfPointers.cs @@ -9,6 +9,8 @@ using System.Threading.Tasks; using Debug = System.Diagnostics.Debug; +#nullable disable + namespace Internal.TypeSystem { /// diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.InterfaceThunks.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.InterfaceThunks.cs index 51bf6a6888e2ff..b9c8f7aa3566bb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.InterfaceThunks.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.InterfaceThunks.cs @@ -62,13 +62,11 @@ namespace ILCompiler // Contains functionality related to instantiating thunks for default interface methods public partial class CompilerTypeSystemContext { - private const int UseContextFromRuntime = -1; - /// /// For a shared (canonical) default interface method, gets a method that can be used to call the /// method on a specific implementing class. /// - public MethodDesc GetDefaultInterfaceMethodImplementationThunk(MethodDesc targetMethod, TypeDesc implementingClass, DefType interfaceOnDefinition) + public MethodDesc GetDefaultInterfaceMethodImplementationThunk(MethodDesc targetMethod, TypeDesc implementingClass, DefType interfaceOnDefinition, out int interfaceIndex) { Debug.Assert(targetMethod.IsSharedByGenericInstantiations); Debug.Assert(!targetMethod.Signature.IsStatic); @@ -76,11 +74,16 @@ public MethodDesc GetDefaultInterfaceMethodImplementationThunk(MethodDesc target Debug.Assert(interfaceOnDefinition.GetTypeDefinition() == targetMethod.OwningType.GetTypeDefinition()); Debug.Assert(targetMethod.OwningType.IsInterface); - int interfaceIndex; + bool useContextFromRuntime = false; if (implementingClass.IsInterface) { Debug.Assert(((MetadataType)implementingClass).IsDynamicInterfaceCastableImplementation()); - interfaceIndex = UseContextFromRuntime; + useContextFromRuntime = true; + } + + if (useContextFromRuntime && targetMethod.OwningType == implementingClass) + { + interfaceIndex = -1; } else { @@ -90,7 +93,7 @@ public MethodDesc GetDefaultInterfaceMethodImplementationThunk(MethodDesc target // Get a method that will inject the appropriate instantiation context to the // target default interface method. - var methodKey = new DefaultInterfaceMethodImplementationInstantiationThunkHashtableKey(targetMethod, interfaceIndex); + var methodKey = new DefaultInterfaceMethodImplementationInstantiationThunkHashtableKey(targetMethod, interfaceIndex, useContextFromRuntime); MethodDesc thunk = _dimThunkHashtable.GetOrCreateValue(methodKey); return thunk; @@ -117,11 +120,13 @@ private struct DefaultInterfaceMethodImplementationInstantiationThunkHashtableKe { public readonly MethodDesc TargetMethod; public readonly int InterfaceIndex; + public bool UseContextFromRuntime; - public DefaultInterfaceMethodImplementationInstantiationThunkHashtableKey(MethodDesc targetMethod, int interfaceIndex) + public DefaultInterfaceMethodImplementationInstantiationThunkHashtableKey(MethodDesc targetMethod, int interfaceIndex, bool useContextFromRuntime) { TargetMethod = targetMethod; InterfaceIndex = interfaceIndex; + UseContextFromRuntime = useContextFromRuntime; } } @@ -138,17 +143,19 @@ protected override int GetValueHashCode(DefaultInterfaceMethodImplementationInst protected override bool CompareKeyToValue(DefaultInterfaceMethodImplementationInstantiationThunkHashtableKey key, DefaultInterfaceMethodImplementationInstantiationThunk value) { return ReferenceEquals(key.TargetMethod, value.TargetMethod) && - key.InterfaceIndex == value.InterfaceIndex; + key.InterfaceIndex == value.InterfaceIndex && + key.UseContextFromRuntime == value.UseContextFromRuntime; } protected override bool CompareValueToValue(DefaultInterfaceMethodImplementationInstantiationThunk value1, DefaultInterfaceMethodImplementationInstantiationThunk value2) { return ReferenceEquals(value1.TargetMethod, value2.TargetMethod) && - value1.InterfaceIndex == value2.InterfaceIndex; + value1.InterfaceIndex == value2.InterfaceIndex && + value1.UseContextFromRuntime == value2.UseContextFromRuntime; } protected override DefaultInterfaceMethodImplementationInstantiationThunk CreateValueFromKey(DefaultInterfaceMethodImplementationInstantiationThunkHashtableKey key) { TypeDesc owningTypeOfThunks = ((CompilerTypeSystemContext)key.TargetMethod.Context).GeneratedAssembly.GetGlobalModuleType(); - return new DefaultInterfaceMethodImplementationInstantiationThunk(owningTypeOfThunks, key.TargetMethod, key.InterfaceIndex); + return new DefaultInterfaceMethodImplementationInstantiationThunk(owningTypeOfThunks, key.TargetMethod, key.InterfaceIndex, key.UseContextFromRuntime); } } private DefaultInterfaceMethodImplementationInstantiationThunkHashtable _dimThunkHashtable = new DefaultInterfaceMethodImplementationInstantiationThunkHashtable(); @@ -162,8 +169,9 @@ private sealed partial class DefaultInterfaceMethodImplementationInstantiationTh private readonly DefaultInterfaceMethodImplementationWithHiddenParameter _nakedTargetMethod; private readonly TypeDesc _owningType; private readonly int _interfaceIndex; + private readonly bool _useContextFromRuntime; - public DefaultInterfaceMethodImplementationInstantiationThunk(TypeDesc owningType, MethodDesc targetMethod, int interfaceIndex) + public DefaultInterfaceMethodImplementationInstantiationThunk(TypeDesc owningType, MethodDesc targetMethod, int interfaceIndex, bool useContextFromRuntime) { Debug.Assert(targetMethod.OwningType.IsInterface); Debug.Assert(!targetMethod.Signature.IsStatic); @@ -172,6 +180,7 @@ public DefaultInterfaceMethodImplementationInstantiationThunk(TypeDesc owningTyp _targetMethod = targetMethod; _nakedTargetMethod = new DefaultInterfaceMethodImplementationWithHiddenParameter(targetMethod, owningType); _interfaceIndex = interfaceIndex; + _useContextFromRuntime = useContextFromRuntime; } public override TypeSystemContext Context => _targetMethod.Context; @@ -180,6 +189,8 @@ public DefaultInterfaceMethodImplementationInstantiationThunk(TypeDesc owningTyp public int InterfaceIndex => _interfaceIndex; + public bool UseContextFromRuntime => _useContextFromRuntime; + public override MethodSignature Signature => _targetMethod.Signature; public MethodDesc TargetMethod => _targetMethod; @@ -202,7 +213,7 @@ public override string DiagnosticName public MethodDesc BaseMethod => _targetMethod; - public string Prefix => $"__InstantiatingStub_{_interfaceIndex}_"; + public string Prefix => $"__InstantiatingStub_{(uint)_interfaceIndex}_{(_useContextFromRuntime ? "_FromRuntime" : "")}_"; public override MethodIL EmitIL() { @@ -230,7 +241,7 @@ public override MethodIL EmitIL() } // Load the instantiating argument. - if (_interfaceIndex == UseContextFromRuntime) + if (_useContextFromRuntime) { codeStream.Emit(ILOpcode.call, emit.NewToken(getCurrentContext)); } @@ -238,6 +249,10 @@ public override MethodIL EmitIL() { codeStream.EmitLdArg(0); codeStream.Emit(ILOpcode.ldfld, emit.NewToken(eeTypeField)); + } + + if (_interfaceIndex >= 0) + { codeStream.EmitLdc(_interfaceIndex); codeStream.Emit(ILOpcode.call, emit.NewToken(getOrdinalInterfaceMethod)); } 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 a27a77882ab130..722618c759d562 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -539,10 +539,10 @@ public sealed override IEnumerable GetConditionalSt 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, defType.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType); + defaultIntfMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(defaultIntfMethod, defType.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType, out int providingInterfaceIndex); // The above thunk will index into interface list to find the right context. Make sure to keep all interfaces prior to this one - for (int i = 0; i < interfaceIndex; i++) + for (int i = 0; i <= providingInterfaceIndex; i++) { result.Add(new CombinedDependencyListEntry( factory.InterfaceUse(defTypeRuntimeInterfaces[i].GetTypeDefinition()), 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 dfa6c0967282e2..f0b9fbf532457e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Internal.Runtime; using Internal.Text; using Internal.TypeSystem; @@ -218,10 +219,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly) { // 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. - canonImplMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(canonImplMethod, declType.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType); + canonImplMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(canonImplMethod, declType.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType, out int providingInterfaceIndex); // The above thunk will index into interface list to find the right context. Make sure to keep all interfaces prior to this one - for (int i = 0; i < interfaceIndex; i++) + for (int i = 0; i <= providingInterfaceIndex; i++) { _nonRelocationDependencies ??= new DependencyList(); _nonRelocationDependencies.Add(factory.InterfaceUse(declTypeRuntimeInterfaces[i].GetTypeDefinition()), "Interface with shared default methods folows this"); @@ -267,14 +268,20 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly) if (BuildSealedVTableSlots(factory, relocsOnly)) { + bool isSharedDynamicInterfaceCastableImpl = _type.IsInterface + && _type.IsCanonicalSubtype(CanonicalFormKind.Any) + && ((MetadataType)_type).IsDynamicInterfaceCastableImplementation(); + for (int i = 0; i < _sealedVTableEntries.Count; i++) { IMethodNode relocTarget = _sealedVTableEntries[i].Target; + int delta = isSharedDynamicInterfaceCastableImpl ? DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag : 0; + if (factory.Target.SupportsRelativePointers) - objData.EmitReloc(relocTarget, RelocType.IMAGE_REL_BASED_RELPTR32); + objData.EmitReloc(relocTarget, RelocType.IMAGE_REL_BASED_RELPTR32, delta); else - objData.EmitPointerReloc(relocTarget); + objData.EmitPointerReloc(relocTarget, delta); } } diff --git a/src/tests/Interop/IDynamicInterfaceCastable/Program.cs b/src/tests/Interop/IDynamicInterfaceCastable/Program.cs index dc3e35d0d48129..ccf91480faaa3f 100644 --- a/src/tests/Interop/IDynamicInterfaceCastable/Program.cs +++ b/src/tests/Interop/IDynamicInterfaceCastable/Program.cs @@ -380,7 +380,6 @@ public static void ValidateBasicInterface() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtimelab/issues/1442", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] public static void ValidateGenericInterface() { Console.WriteLine($"Running {nameof(ValidateGenericInterface)}"); @@ -394,26 +393,38 @@ public static void ValidateGenericInterface() Console.WriteLine(" -- Validate cast"); // ITestGeneric -> ITestGenericIntImpl - Assert.True(castableObj is ITestGeneric, $"Should be castable to {nameof(ITestGeneric)} via is"); - Assert.NotNull(castableObj as ITestGeneric); + if (!TestLibrary.Utilities.IsNativeAot) // https://github.com/dotnet/runtime/issues/108229 + { + Assert.True(castableObj is ITestGeneric, $"Should be castable to {nameof(ITestGeneric)} via is"); + Assert.NotNull(castableObj as ITestGeneric); + } ITestGeneric testInt = (ITestGeneric)castableObj; // ITestGeneric -> ITestGenericImpl - Assert.True(castableObj is ITestGeneric, $"Should be castable to {nameof(ITestGeneric)} via is"); - Assert.NotNull(castableObj as ITestGeneric); + if (!TestLibrary.Utilities.IsNativeAot) // https://github.com/dotnet/runtime/issues/108229 + { + Assert.True(castableObj is ITestGeneric, $"Should be castable to {nameof(ITestGeneric)} via is"); + Assert.NotNull(castableObj as ITestGeneric); + } ITestGeneric testStr = (ITestGeneric)castableObj; // Validate Variance // ITestGeneric -> ITestGenericImpl - Assert.True(castableObj is ITestGeneric, $"Should be castable to {nameof(ITestGeneric)} via is"); - Assert.NotNull(castableObj as ITestGeneric); + if (!TestLibrary.Utilities.IsNativeAot) // https://github.com/dotnet/runtime/issues/108229 + { + Assert.True(castableObj is ITestGeneric, $"Should be castable to {nameof(ITestGeneric)} via is"); + Assert.NotNull(castableObj as ITestGeneric); + } ITestGeneric testVar = (ITestGeneric)castableObj; - // ITestGeneric is not recognized - Assert.False(castableObj is ITestGeneric, $"Should not be castable to {nameof(ITestGeneric)} via is"); - Assert.Null(castableObj as ITestGeneric); - var ex = Assert.Throws(() => { var _ = (ITestGeneric)castableObj; }); - Assert.Equal(string.Format(DynamicInterfaceCastableException.ErrorFormat, typeof(ITestGeneric)), ex.Message); + if (!TestLibrary.Utilities.IsNativeAot) // https://github.com/dotnet/runtime/issues/108229 + { + // ITestGeneric is not recognized + Assert.False(castableObj is ITestGeneric, $"Should not be castable to {nameof(ITestGeneric)} via is"); + Assert.Null(castableObj as ITestGeneric); + var ex = Assert.Throws(() => { var _ = (ITestGeneric)castableObj; }); + Assert.Equal(string.Format(DynamicInterfaceCastableException.ErrorFormat, typeof(ITestGeneric)), ex.Message); + } int expectedInt = 42; string expectedStr = "str"; @@ -423,13 +434,16 @@ public static void ValidateGenericInterface() Assert.Equal(expectedStr, testStr.ReturnArg(expectedStr)); Assert.Equal(expectedStr, testVar.ReturnArg(expectedStr)); - Console.WriteLine(" -- Validate generic method call"); - Assert.Equal(expectedInt * 2, testInt.DoubleGenericArg(42)); - Assert.Equal(expectedStr + expectedStr, testInt.DoubleGenericArg("str")); - Assert.Equal(expectedInt * 2, testStr.DoubleGenericArg(42)); - Assert.Equal(expectedStr + expectedStr, testStr.DoubleGenericArg("str")); - Assert.Equal(expectedInt * 2, testVar.DoubleGenericArg(42)); - Assert.Equal(expectedStr + expectedStr, testVar.DoubleGenericArg("str")); + if (!TestLibrary.Utilities.IsNativeAot) // https://github.com/dotnet/runtime/issues/108228 + { + Console.WriteLine(" -- Validate generic method call"); + Assert.Equal(expectedInt * 2, testInt.DoubleGenericArg(42)); + Assert.Equal(expectedStr + expectedStr, testInt.DoubleGenericArg("str")); + Assert.Equal(expectedInt * 2, testStr.DoubleGenericArg(42)); + Assert.Equal(expectedStr + expectedStr, testStr.DoubleGenericArg("str")); + Assert.Equal(expectedInt * 2, testVar.DoubleGenericArg(42)); + Assert.Equal(expectedStr + expectedStr, testVar.DoubleGenericArg("str")); + } Console.WriteLine(" -- Validate delegate call"); Func funcInt = new Func(testInt.ReturnArg); diff --git a/src/tests/issues.targets b/src/tests/issues.targets index ec449e4620b91a..7a2cff6628af3a 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -1067,9 +1067,6 @@ https://github.com/dotnet/runtimelab/issues/861 - - https://github.com/dotnet/runtimelab/issues/1442 - https://github.com/dotnet/runtimelab/issues/306 diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs index b5023e8c84163e..5b2189b336a5b5 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs @@ -15,6 +15,8 @@ public class Interfaces public static int Run() { + TestRuntime109893Regression.Run(); + if (TestInterfaceCache() == Fail) return Fail; @@ -61,6 +63,37 @@ public static int Run() return Pass; } + class TestRuntime109893Regression + { + class Type : IType; + + class MyVisitor : IVisitor + { + public object? Visit(IType _) => typeof(T); + } + + interface IType + { + object? Accept(IVisitor visitor); + } + + interface IType : IType + { + object? IType.Accept(IVisitor visitor) => visitor.Visit(this); + } + + interface IVisitor + { + object? Visit(IType type); + } + + public static void Run() + { + IType type = new Type(); + type.Accept(new MyVisitor()); + } + } + private static MyInterface[] MakeInterfaceArray() { MyInterface[] itfs = new MyInterface[50]; @@ -886,6 +919,16 @@ interface IInterfaceImpl : IInterface [DynamicInterfaceCastableImplementation] interface IInterfaceIndirectCastableImpl : IInterfaceImpl { } + interface IInterfaceImpl : IInterface + { + string IInterface.GetCookie() => typeof(T).Name; + } + + [DynamicInterfaceCastableImplementation] + interface IInterfaceIndirectCastableImpl : IInterfaceImpl { } + + class Atom { } + public static void Run() { Console.WriteLine("Testing IDynamicInterfaceCastable..."); @@ -922,6 +965,18 @@ public static void Run() if (o.GetCookie() != "Int32") throw new Exception(); } + + { + IInterface o = (IInterface)new CastableClass>(); + if (o.GetCookie() != "Atom") + throw new Exception(); + } + + { + IInterface o = (IInterface)new CastableClass>(); + if (o.GetCookie() != "Atom") + throw new Exception(); + } } }