From a66bd547418c93c03afe7487d4c5d097a494ed44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 25 Sep 2024 14:27:52 +0200 Subject: [PATCH 1/3] Fix IDynamicInterfaceCastable with shared generic code Fixes #72909. Internal team ran into this. Turns out CsWinRT also needs this, but they're were working around instead pushing on a fix. The big problem with this one is that we have an interface call to a default interface method that requires generic context. This means we need some kind of instantiating thunk (since callsite didn't provide generic context because it didn't know it). The normal default interface case uses an instantiating thunk that simply indexes into the interface list of `this`. We know the index of the interface (we don't know the concrete type because `T`s could be involved), but we can easily compute it at runtime from `this`. The problem with `IDynamicInterfaceCastable` is that `this` is useless (the class doesn't know anything about the interface). So we need to get the generic context from somewhere else. In this PR, I'm using the thunkpool as "somewhere else". When we finish interface lookup and find out `IDynamicInterfaceCastable` provided a shared method, we create a thunkpool thunk that stashes away the context. We then call the "default interface method instantiating thunk" and instead of indexing into interface list of `this`, we index into interface list of whatever was stashed away. So there are two thunks before we reach the point of executing the method body. --- .../System/Runtime/CachedInterfaceDispatch.cs | 41 ++++++++++++--- .../src/System/Runtime/DispatchResolve.cs | 50 +++++++++++------- .../Runtime/Augments/RuntimeAugments.cs | 2 +- .../CompilerHelpers/SharedCodeHelpers.cs | 6 +-- .../IDynamicInterfaceCastableSupport.cs | 30 ++++++++++- .../src/System/Runtime/RuntimeImports.cs | 13 +++-- .../Internal/Runtime/RuntimeConstants.cs | 14 +++++ ...mpilerTypeSystemContext.InterfaceThunks.cs | 41 ++++++++++----- .../Compiler/DependencyAnalysis/EETypeNode.cs | 4 +- .../DependencyAnalysis/SealedVTableNode.cs | 16 ++++-- .../IDynamicInterfaceCastable/Program.cs | 52 ++++++++++++------- src/tests/issues.targets | 3 -- 12 files changed, 197 insertions(+), 75 deletions(-) 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..a931d82fc468a6 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 & DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag) != 0) + { + result &= ~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/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..db207ce34782f7 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 @@ -18,9 +20,7 @@ internal static class SharedCodeHelpers 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..38b5beb7e4a7e1 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,11 @@ using System; using System.Runtime; using System.Runtime.InteropServices; +using System.Threading; + +using Internal.Runtime.Augments; + +using Debug = System.Diagnostics.Debug; namespace Internal.Runtime { @@ -15,6 +20,8 @@ internal static bool IDynamicCastableIsInterfaceImplemented(IDynamicInterfaceCas return instance.IsInterfaceImplemented(new RuntimeTypeHandle(interfaceType), throwIfNotImplemented); } + private static object s_thunkPoolHeap; + [RuntimeExport("IDynamicCastableGetInterfaceImplementation")] internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot) { @@ -28,11 +35,32 @@ 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_thunkPoolHeap == null) + { + // TODO: Free s_thunkPoolHeap if the thread lose the race + Interlocked.CompareExchange( + ref s_thunkPoolHeap, + RuntimeAugments.CreateThunksHeap(RuntimeImports.GetInteropCommonStubAddress()), + null + ); + Debug.Assert(s_thunkPoolHeap != null); + } + + nint thunk = RuntimeAugments.AllocateThunk(s_thunkPoolHeap); + RuntimeAugments.SetThunkData(s_thunkPoolHeap, thunk, (nint)genericContext, result); + + result = thunk; + } return result; } 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/tools/Common/Internal/Runtime/RuntimeConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs index 8ce74a03072b4d..8ec09915c28012 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs @@ -43,6 +43,20 @@ internal static class SpecialDispatchMapSlot public const ushort Reabstraction = 0xFFFF; } + internal static class DispatchMapCodePointerFlags + { + public const int WasmRequiresInstantiatingThunkFlag = 1 << 31; + public const int NonWasmRequiresInstantiatingThunkFlag = 2; + +#if NATIVEAOT +#if TARGET_WASM + public const nint RequiresInstantiatingThunkFlag = WasmRequiresInstantiatingThunkFlag; +#else + public const nint RequiresInstantiatingThunkFlag = NonWasmRequiresInstantiatingThunkFlag; +#endif +#endif + } + internal static class SpecialGVMInterfaceEntry { public const uint Diamond = 0xFFFFFFFF; 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..b48ca497acd9ce 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,21 @@ 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 ? + (factory.TypeSystemContext.Target.Architecture == TargetArchitecture.Wasm32 ? DispatchMapCodePointerFlags.WasmRequiresInstantiatingThunkFlag : DispatchMapCodePointerFlags.NonWasmRequiresInstantiatingThunkFlag) : 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 From d8a2cd769ee6ca5852621e383e4e69b8caf2c19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 26 Sep 2024 08:15:26 +0200 Subject: [PATCH 2/3] Finishing touches --- .../System/Runtime/CachedInterfaceDispatch.cs | 4 +- .../src/CompatibilitySuppressions.xml | 4 ++ .../CompilerHelpers/SharedCodeHelpers.cs | 2 +- .../IDynamicInterfaceCastableSupport.cs | 64 +++++++++++++++---- .../src/System.Private.CoreLib.csproj | 3 + .../src/System.Private.TypeLoader.csproj | 3 - .../Internal/Runtime/RuntimeConstants.cs | 11 +--- .../LockFreeReaderHashtableOfPointers.cs | 2 + .../DependencyAnalysis/SealedVTableNode.cs | 3 +- .../SmokeTests/UnitTests/Interfaces.cs | 22 +++++++ 10 files changed, 88 insertions(+), 30 deletions(-) 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 a931d82fc468a6..9a1b1ad7ef985a 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs @@ -110,9 +110,9 @@ private static IntPtr RhResolveDynamicInterfaceCastableDispatchOnType(MethodTabl DispatchResolve.ResolveFlags.IDynamicInterfaceCastable, ppGenericContext); - if ((result & DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag) != 0) + if ((result & (nint)DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag) != 0) { - result &= ~DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag; + result &= ~(nint)DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag; } else { 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/CompilerHelpers/SharedCodeHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/SharedCodeHelpers.cs index db207ce34782f7..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 @@ -14,7 +14,7 @@ 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]; } 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 38b5beb7e4a7e1..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 @@ -7,6 +7,7 @@ using System.Threading; using Internal.Runtime.Augments; +using Internal.TypeSystem; using Debug = System.Diagnostics.Debug; @@ -20,7 +21,7 @@ internal static bool IDynamicCastableIsInterfaceImplemented(IDynamicInterfaceCas return instance.IsInterfaceImplemented(new RuntimeTypeHandle(interfaceType), throwIfNotImplemented); } - private static object s_thunkPoolHeap; + private static readonly object s_thunkPoolHeap = RuntimeAugments.CreateThunksHeap(RuntimeImports.GetInteropCommonStubAddress()); [RuntimeExport("IDynamicCastableGetInterfaceImplementation")] internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot) @@ -45,20 +46,18 @@ internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterf if (genericContext != null) { - if (s_thunkPoolHeap == null) + if (!s_thunkHashtable.TryGetValue(new InstantiatingThunkKey(result, (nint)genericContext), out nint thunk)) { - // TODO: Free s_thunkPoolHeap if the thread lose the race - Interlocked.CompareExchange( - ref s_thunkPoolHeap, - RuntimeAugments.CreateThunksHeap(RuntimeImports.GetInteropCommonStubAddress()), - null - ); - Debug.Assert(s_thunkPoolHeap != null); + 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; + } } - nint thunk = RuntimeAugments.AllocateThunk(s_thunkPoolHeap); - RuntimeAugments.SetThunkData(s_thunkPoolHeap, thunk, (nint)genericContext, result); - result = thunk; } return result; @@ -95,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.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 8ec09915c28012..9d356cb066813d 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs @@ -45,16 +45,7 @@ internal static class SpecialDispatchMapSlot internal static class DispatchMapCodePointerFlags { - public const int WasmRequiresInstantiatingThunkFlag = 1 << 31; - public const int NonWasmRequiresInstantiatingThunkFlag = 2; - -#if NATIVEAOT -#if TARGET_WASM - public const nint RequiresInstantiatingThunkFlag = WasmRequiresInstantiatingThunkFlag; -#else - public const nint RequiresInstantiatingThunkFlag = NonWasmRequiresInstantiatingThunkFlag; -#endif -#endif + public const int RequiresInstantiatingThunkFlag = 2; } internal static class SpecialGVMInterfaceEntry 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/DependencyAnalysis/SealedVTableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs index b48ca497acd9ce..f0b9fbf532457e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/SealedVTableNode.cs @@ -276,8 +276,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly) { IMethodNode relocTarget = _sealedVTableEntries[i].Target; - int delta = isSharedDynamicInterfaceCastableImpl ? - (factory.TypeSystemContext.Target.Architecture == TargetArchitecture.Wasm32 ? DispatchMapCodePointerFlags.WasmRequiresInstantiatingThunkFlag : DispatchMapCodePointerFlags.NonWasmRequiresInstantiatingThunkFlag) : 0; + int delta = isSharedDynamicInterfaceCastableImpl ? DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag : 0; if (factory.Target.SupportsRelativePointers) objData.EmitReloc(relocTarget, RelocType.IMAGE_REL_BASED_RELPTR32, delta); diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs index b5023e8c84163e..7f79a9ef2e7f94 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs @@ -886,6 +886,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 +932,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(); + } } } From d07c0bfeeba0d9930310caaaeb0f0840d43fb3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 18 Nov 2024 23:26:22 +0100 Subject: [PATCH 3/3] Regression test --- .../SmokeTests/UnitTests/Interfaces.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs index 7f79a9ef2e7f94..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];