From 92a7840a61ec89751441f18dba93413420869229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 15 Jul 2025 07:48:37 +0200 Subject: [PATCH 1/3] Allow caching IDynamicInterfaceCastable resuts Contributes to #107999. We want _some_ caching. It's unclear what the right caching is (the caching on CoreCLR VM side is [wild](https://github.com/dotnet/runtime/issues/107999#issuecomment-3072051079)), but given that we sometimes might need to purge caches to avoid turning them into memory leaks, I think the only conclusion is that whoever implements things like: ```csharp class Dyn : IDynamicInterfaceCastable { public Type InterfaceType { get; init; } public Type ImplType { get; init; } public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) => interfaceType.Equals(InterfaceType.TypeHandle) ? ImplType.TypeHandle : throw new Exception(); public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) => interfaceType.Equals(InterfaceType.TypeHandle) ? true : (throwIfNotImplemented ? throw new InvalidCastException() : false); } ``` Should feel bad, and we don't support their use case. --- .../src/System/Runtime/CachedInterfaceDispatch.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 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 7a6f07a01c0e19..d8ae1363c73af2 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs @@ -29,17 +29,7 @@ private static IntPtr RhpCidResolve_Worker(object pObject, IntPtr pCell) IntPtr pTargetCode = RhResolveDispatchWorker(pObject, (void*)pCell, ref cellInfo); if (pTargetCode != IntPtr.Zero) { - // We don't update the dispatch cell cache if this is IDynamicInterfaceCastable because this - // scenario is by-design dynamic. There is no guarantee that another instance with the same MethodTable - // as the one we just resolved would do the resolution the same way. We will need to ask again. - if (!pObject.GetMethodTable()->IsIDynamicInterfaceCastable) - { - return InternalCalls.RhpUpdateDispatchCellCache(pCell, pTargetCode, pObject.GetMethodTable(), ref cellInfo); - } - else - { - return pTargetCode; - } + return InternalCalls.RhpUpdateDispatchCellCache(pCell, pTargetCode, pObject.GetMethodTable(), ref cellInfo); } // "Valid method implementation was not found." From 6d71760bbbb3942825c60edb87354c05509911f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 16 Jul 2025 08:12:03 +0200 Subject: [PATCH 2/3] Fix test that broke core invariant --- .../IDynamicInterfaceCastable/Program.cs | 70 +++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/src/tests/Interop/IDynamicInterfaceCastable/Program.cs b/src/tests/Interop/IDynamicInterfaceCastable/Program.cs index 73c84920352d15..5a2b0b23f00b2b 100644 --- a/src/tests/Interop/IDynamicInterfaceCastable/Program.cs +++ b/src/tests/Interop/IDynamicInterfaceCastable/Program.cs @@ -213,7 +213,7 @@ public class DynamicInterfaceCastable : IDynamicInterfaceCastable, IDirectlyImpl { private Dictionary interfaceToImplMap; - public DynamicInterfaceCastable(Dictionary interfaceToImplMap) + protected DynamicInterfaceCastable(Dictionary interfaceToImplMap) { this.interfaceToImplMap = interfaceToImplMap; } @@ -347,14 +347,20 @@ public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceT [ActiveIssue("https://github.com/dotnet/runtime/issues/55742", TestRuntimes.Mono)] public class Program { + class DynamicInterfaceCastable_ValidateBasicInterface : DynamicInterfaceCastable + { + public DynamicInterfaceCastable_ValidateBasicInterface() + : base(new Dictionary { + { typeof(ITest), typeof(ITestImpl) } + }) { } + } + [Fact] public static void ValidateBasicInterface() { Console.WriteLine($"Running {nameof(ValidateBasicInterface)}"); - object castableObj = new DynamicInterfaceCastable(new Dictionary { - { typeof(ITest), typeof(ITestImpl) } - }); + object castableObj = new DynamicInterfaceCastable_ValidateBasicInterface(); Console.WriteLine(" -- Validate cast"); @@ -379,16 +385,22 @@ public static void ValidateBasicInterface() Assert.Same(castableObj, func()); } + class DynamicInterfaceCastable_ValidateGenericInterface : DynamicInterfaceCastable + { + public DynamicInterfaceCastable_ValidateGenericInterface() + : base(new Dictionary { + { typeof(ITestGeneric), typeof(ITestGenericIntImpl) }, + { typeof(ITestGeneric), typeof(ITestGenericImpl) }, + { typeof(ITestGeneric), typeof(ITestGenericImpl) }, + }) { } + } + [Fact] public static void ValidateGenericInterface() { Console.WriteLine($"Running {nameof(ValidateGenericInterface)}"); - object castableObj = new DynamicInterfaceCastable(new Dictionary { - { typeof(ITestGeneric), typeof(ITestGenericIntImpl) }, - { typeof(ITestGeneric), typeof(ITestGenericImpl) }, - { typeof(ITestGeneric), typeof(ITestGenericImpl) }, - }); + object castableObj = new DynamicInterfaceCastable_ValidateGenericInterface(); Console.WriteLine(" -- Validate cast"); @@ -442,15 +454,21 @@ public static void ValidateGenericInterface() Assert.Equal(expectedStr, funcVar(expectedStr)); } + class DynamicInterfaceCastable_ValidateOverriddenInterface : DynamicInterfaceCastable + { + public DynamicInterfaceCastable_ValidateOverriddenInterface() + : base(new Dictionary { + { typeof(ITest), typeof(IOverrideTestImpl) }, + { typeof(IOverrideTest), typeof(IOverrideTestImpl) }, + }) { } + } + [Fact] public static void ValidateOverriddenInterface() { Console.WriteLine($"Running {nameof(ValidateOverriddenInterface)}"); - object castableObj = new DynamicInterfaceCastable(new Dictionary { - { typeof(ITest), typeof(IOverrideTestImpl) }, - { typeof(IOverrideTest), typeof(IOverrideTestImpl) }, - }); + object castableObj = new DynamicInterfaceCastable_ValidateOverriddenInterface(); Console.WriteLine(" -- Validate cast"); @@ -476,14 +494,20 @@ public static void ValidateOverriddenInterface() Assert.Equal(IOverrideTestImpl.GetMyTypeReturnValue, funcGetType()); } + class DynamicInterfaceCastable_ValidateNotImplemented : DynamicInterfaceCastable + { + public DynamicInterfaceCastable_ValidateNotImplemented() + : base(new Dictionary { + { typeof(ITest), typeof(ITestImpl) } + }) { } + } + [Fact] public static void ValidateNotImplemented() { Console.WriteLine($"Running {nameof(ValidateNotImplemented)}"); - object castableObj = new DynamicInterfaceCastable(new Dictionary { - { typeof(ITest), typeof(ITestImpl) } - }); + object castableObj = new DynamicInterfaceCastable_ValidateNotImplemented(); Assert.False(castableObj is INotImplemented, $"Should not be castable to {nameof(INotImplemented)} via is"); Assert.Null(castableObj as INotImplemented); @@ -491,15 +515,21 @@ public static void ValidateNotImplemented() Assert.Equal(string.Format(DynamicInterfaceCastableException.ErrorFormat, typeof(INotImplemented)), ex.Message); } + class DynamicInterfaceCastable_ValidateDirectlyImplemented : DynamicInterfaceCastable + { + public DynamicInterfaceCastable_ValidateDirectlyImplemented() + : base(new Dictionary { + { typeof(ITest), typeof(ITestImpl) }, + { typeof(IDirectlyImplemented), typeof(IDirectlyImplementedImpl) }, + }) { } + } + [Fact] public static void ValidateDirectlyImplemented() { Console.WriteLine($"Running {nameof(ValidateDirectlyImplemented)}"); - object castableObj = new DynamicInterfaceCastable(new Dictionary { - { typeof(ITest), typeof(ITestImpl) }, - { typeof(IDirectlyImplemented), typeof(IDirectlyImplementedImpl) }, - }); + object castableObj = new DynamicInterfaceCastable_ValidateDirectlyImplemented(); Console.WriteLine(" -- Validate cast"); Assert.True(castableObj is IDirectlyImplemented, $"Should be castable to {nameof(IDirectlyImplemented)} via is"); From efe9da4a0dfb0623c68b890932b31f489b778aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 16 Jul 2025 23:19:58 +0200 Subject: [PATCH 3/3] Update Program.cs --- src/tests/Interop/IDynamicInterfaceCastable/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/Interop/IDynamicInterfaceCastable/Program.cs b/src/tests/Interop/IDynamicInterfaceCastable/Program.cs index 5a2b0b23f00b2b..94d56222a3675e 100644 --- a/src/tests/Interop/IDynamicInterfaceCastable/Program.cs +++ b/src/tests/Interop/IDynamicInterfaceCastable/Program.cs @@ -371,7 +371,7 @@ public static void ValidateBasicInterface() Console.WriteLine(" -- Validate method call"); Assert.Same(castableObj, testObj.ReturnThis()); - Assert.Equal(typeof(DynamicInterfaceCastable), testObj.GetMyType()); + Assert.Equal(typeof(DynamicInterfaceCastable_ValidateBasicInterface), testObj.GetMyType()); Console.WriteLine(" -- Validate method call which calls methods using 'this'"); Assert.Equal(DynamicInterfaceCastable.ImplementedMethodReturnValue, testObj.CallImplemented(ImplementationToCall.Class));