diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
index ac544ed02e3fea..a8a83cedec3bb5 100644
--- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
+++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
@@ -148,13 +148,9 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
                                 return null;
 
                             case DefaultInterfaceMethodResolution.DefaultImplementation:
-                                if (dimMethod.OwningType.HasInstantiation || (declMethod != defaultInterfaceDispatchDeclMethod))
+                                if (declMethod != defaultInterfaceDispatchDeclMethod)
                                 {
-                                    // If we devirtualized into a default interface method on a generic type, we should actually return an
-                                    // instantiating stub but this is not happening.
-                                    // Making this work is tracked by https://github.com/dotnet/runtime/issues/9588
-
-                                    // In addition, we fail here for variant default interface dispatch
+                                    // Fail for variant default interface dispatch
                                     devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DIM;
                                     return null;
                                 }
@@ -219,13 +215,6 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
                 }
             }
 
-            if (impl != null && impl.HasInstantiation && impl.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific))
-            {
-                // We don't support devirtualization of shared generic virtual methods yet.
-                devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
-                impl = null;
-            }
-
             return impl;
         }
 
diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
index 5fc46db5a8978b..907eb19889a0c2 100644
--- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
+++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@@ -1482,6 +1482,64 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
                 info->resolvedTokenDevirtualizedUnboxedMethod = default(CORINFO_RESOLVED_TOKEN);
             }
 
+            bool isArray = decl.OwningType.IsInterface && objType.IsArray;
+            bool isGenericVirtual = !isArray && decl.HasInstantiation;
+
+            if (!impl.AcquiresInstMethodTableFromThis())
+            {
+                if (originalImpl.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any))
+                {
+                    // If we end up with a shared MethodTable that is not exact,
+                    // we can't devirtualize since it's not possible to compute the instantiation argument even as a runtime lookup.
+                    info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
+                    return false;
+                }
+
+                if (originalImpl.IsSharedByGenericInstantiations)
+                {
+                    // TODO: Support for runtime lookup
+                    info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
+                    return false;
+                }
+            }
+
+            if (isArray)
+            {
+#if READYTORUN
+                // Array interface devirt is not yet supported by R2R.
+                info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
+                return false;
+#else
+                // NativeAOT handles arrays in a different way that doesn't require an instantiating stub.
+#endif
+            }
+
+            if (impl.RequiresInstMethodDescArg())
+            {
+                if (unboxingStub)
+                {
+                    // Bail out for now. We need an unboxing stub that points to an instantiated method.
+                    info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
+                    return false;
+                }
+#if READYTORUN
+                MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null);
+                info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodDictionary, originalImplWithToken));
+
+#else
+                info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodGenericDictionary(originalImpl));
+#endif
+            }
+            else if (impl.RequiresInstMethodTableArg())
+            {
+#if READYTORUN
+                info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeDictionary, originalImpl.OwningType));
+
+#else
+                info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ConstructedTypeSymbol(originalImpl.OwningType));
+#endif
+            }
+
 #if READYTORUN
             // Testing has not shown that concerns about virtual matching are significant
             // Only generate verification for builds with the stress mode enabled
@@ -1496,7 +1554,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
 #endif
             info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_SUCCESS;
             info->devirtualizedMethod = ObjectToHandle(impl);
-            info->tokenLookupContext = contextFromType(owningType);
+            info->tokenLookupContext = (isArray || isGenericVirtual) ? contextFromMethod(originalImpl) : contextFromType(owningType);
 
             return true;
 
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DevirtualizationManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DevirtualizationManager.cs
index 7d0275ff2eaaee..e6e329e5b32663 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DevirtualizationManager.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DevirtualizationManager.cs
@@ -45,9 +45,7 @@ protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefTyp
             //
             // Result method checking
             // 1. Ensure that the resolved result versions with the code, or is the decl method
-            // 2. Devirtualizing to a default interface method is not currently considered to be useful, and how to check for version
-            //    resilience has not yet been analyzed.
-            // 3. When checking that the resolved result versions with the code, validate that all of the types
+            // 2. When checking that the resolved result versions with the code, validate that all of the types
             //    From implType to the owning type of resolved result method also version with the code.
 
             bool declMethodCheckFailed;
diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp
index c0ec22f3e75a8f..e4e59c1ed21ad7 100644
--- a/src/coreclr/vm/jitinterface.cpp
+++ b/src/coreclr/vm/jitinterface.cpp
@@ -8762,15 +8762,6 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
             info->detail = CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP;
             return false;
         }
-
-        // If we devirtualized into a default interface method on a generic type, we should actually return an
-        // instantiating stub but this is not happening.
-        // Making this work is tracked by https://github.com/dotnet/runtime/issues/9588
-        if (pDevirtMD->GetMethodTable()->IsInterface() && pDevirtMD->HasClassInstantiation())
-        {
-            info->detail = CORINFO_DEVIRTUALIZATION_FAILED_DIM;
-            return false;
-        }
     }
     else
     {
@@ -8832,53 +8823,67 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
     MethodTable* pExactMT = pApproxMT;
     bool isArray = false;
     bool isGenericVirtual = false;
-
-    if (pApproxMT->IsInterface())
-    {
-        // As noted above, we can't yet handle generic interfaces
-        // with default methods.
-        _ASSERTE(!pDevirtMD->HasClassInstantiation());
-
-    }
-    else if (pBaseMT->IsInterface() && pObjMT->IsArray())
+    
+    if (pBaseMT->IsInterface() && pObjMT->IsArray())
     {
         isArray = true;
     }
-    else
+    else if (!pApproxMT->IsInterface())
     {
         pExactMT = pDevirtMD->GetExactDeclaringType(pObjMT);
     }
 
+    MethodDesc* pInstantiatedMD = pDevirtMD;
+
     // This is generic virtual method devirtualization.
     if (!isArray && pBaseMD->HasMethodInstantiation())
     {
         MethodDesc* pPrimaryMD = pDevirtMD;
+
         pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
             pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true);
-        if (pDevirtMD->IsSharedByGenericMethodInstantiations())
-        {
-            info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
-            return false;
-        }
+        
+        pInstantiatedMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
+            pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false);
 
         isGenericVirtual = true;
     }
 
-    if (isArray || isGenericVirtual)
+    MethodDesc* pUnboxedDevirtMD = pDevirtMD->IsUnboxingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;
+
+    if (!pUnboxedDevirtMD->AcquiresInstMethodTableFromThis())
     {
-        if (pDevirtMD->IsInstantiatingStub())
+        if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetClassInstantiation()))
         {
-            info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pDevirtMD;
-            info->instParamLookup.constLookup.accessType = IAT_VALUE;
+            // If we end up with a shared MethodTable that is not exact,
+            // we can't devirtualize since it's not possible to compute the instantiation argument even as a runtime lookup.
+            info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
+            return false;
         }
 
-        info->tokenLookupContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD);
-        pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;
+        if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation()))
+        {
+            // TODO: Support for runtime lookup
+            info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
+            return false;
+        }
     }
-    else
+
+    if (pUnboxedDevirtMD->RequiresInstMethodDescArg())
     {
-        info->tokenLookupContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT);
+        info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstantiatedMD;
+        info->instParamLookup.constLookup.accessType = IAT_VALUE;
     }
+    else if (pUnboxedDevirtMD->RequiresInstMethodTableArg())
+    {
+        info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pExactMT;
+        info->instParamLookup.constLookup.accessType = IAT_VALUE;
+    }
+
+    pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;
+    info->tokenLookupContext = (isArray || isGenericVirtual)
+        ? MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pInstantiatedMD)
+        : MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT);
 
     // Success! Pass back the results.
     //
diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs
index 7aba5abe26bab3..cab401999094bb 100644
--- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs
+++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs
@@ -4,6 +4,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Reflection;
 using System.Runtime.CompilerServices;
 using Xunit;
 
@@ -219,6 +220,11 @@ internal static void Test<T>()
         Delegate m1 = test2.Foo<List<T>>();
         Delegate m2 = test2.Foo<List<List<T>>>;
         Assert.Equal(m1, m2);
+        
+        IBase foo = new DerivedStruct();
+        Delegate d = foo.Foo<List<T>>();
+        MethodInfo expected = typeof(DerivedStruct).GetMethod(nameof(DerivedStruct.Foo))!.MakeGenericMethod(typeof(List<T>));
+        Assert.Equal(expected, d.Method);
     }
 }
 
@@ -245,6 +251,19 @@ public override Delegate Foo<U1>()
     }
 }
 
+internal interface IBase
+{
+    Delegate Foo<U>();
+}
+
+internal struct DerivedStruct : IBase
+{
+    public Delegate Foo<U>()
+    {
+        return Foo<U>;
+    }
+}
+
 internal static class IconContextBridgeNonShared<TMethod>
 {
     public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value)
diff --git a/src/tests/Loader/classloader/DefaultInterfaceMethods/devirtualization/dim_devirtualization.cs b/src/tests/Loader/classloader/DefaultInterfaceMethods/devirtualization/dim_devirtualization.cs
new file mode 100644
index 00000000000000..cb67af966d11cd
--- /dev/null
+++ b/src/tests/Loader/classloader/DefaultInterfaceMethods/devirtualization/dim_devirtualization.cs
@@ -0,0 +1,275 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Xunit;
+
+public class Program
+{
+    [Fact]
+    public static void TestEntryPoint()
+    {
+        GitHubIssue39419RegressionCase.Run();
+        ConstrainedGenericInlineCase.Run();
+        MultiInterfaceDefaultMethodCases.Run();
+        NonGenericDefaultMethodCase.Run();
+        OverriddenDefaultMethodCases.Run();
+        ValueTypeArgumentDefaultMethodCase.Run();
+        StructNonDefaultImplementationCase.Run();
+        ContravariantDefaultMethodCase.Run();
+        DirectStructMethodCase.Run();
+        StaticGenericMethodCase.Run();
+    }
+}
+
+static class GitHubIssue39419RegressionCase
+{
+    interface IM<T>
+    {
+        bool UseDefaultM
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get => true;
+        }
+
+        ValueTask M(T instance) => throw new NotImplementedException("M must be implemented if UseDefaultM is false");
+
+        static ValueTask DefaultM(T instance)
+        {
+            return default;
+        }
+    }
+
+    struct M : IM<int>
+    {
+    }
+
+    public static void Run()
+    {
+        var m = new M();
+        if (((IM<int>)m).UseDefaultM)
+        {
+            IM<int>.DefaultM(42);
+            return;
+        }
+
+        ((IM<int>)m).M(42);
+        throw new UnreachableException();
+    }
+}
+
+static class ConstrainedGenericInlineCase
+{
+    interface I<T> where T : IComparable<T>
+    {
+        T GetAt(int i, T[] tx) => tx[i];
+    }
+
+    class C : I<string>
+    {
+    }
+
+    private static readonly string[] s_values = new string[] { "test" };
+
+    public static void Run()
+    {
+        I<string> c = new C();
+        var dcs = c.GetAt(0, s_values);
+        Assert.Equal("test", dcs);
+    }
+}
+
+static class MultiInterfaceDefaultMethodCases
+{
+    interface I<T>
+    {
+        string DefaultTypeOf() => typeof(T).Name;
+    }
+
+    class Dummy
+    {
+    }
+
+    class ClassImplementation : I<string>, I<object>, I<Dummy>
+    {
+        string I<Dummy>.DefaultTypeOf() => "C.Dummy";
+    }
+
+    struct StructImplementation : I<string>, I<object>, I<Dummy>
+    {
+        string I<Dummy>.DefaultTypeOf() => "C.Dummy";
+    }
+
+    public static void Run()
+    {
+        RunClassImplementation();
+        RunStructImplementation();
+    }
+
+    static void RunClassImplementation()
+    {
+        var c = new ClassImplementation();
+        var dcs = ((I<string>)c).DefaultTypeOf();
+        Assert.Equal("String", dcs);
+        var dos = ((I<object>)c).DefaultTypeOf();
+        Assert.Equal("Object", dos);
+        var dds = ((I<Dummy>)c).DefaultTypeOf();
+        Assert.Equal("C.Dummy", dds);
+    }
+
+    static void RunStructImplementation()
+    {
+        var c = new StructImplementation();
+        var dcs = ((I<string>)c).DefaultTypeOf();
+        Assert.Equal("String", dcs);
+        var dos = ((I<object>)c).DefaultTypeOf();
+        Assert.Equal("Object", dos);
+        var dds = ((I<Dummy>)c).DefaultTypeOf();
+        Assert.Equal("C.Dummy", dds);
+    }
+}
+
+static class NonGenericDefaultMethodCase
+{
+    interface I
+    {
+        string DefaultTypeOf() => typeof(string).Name;
+    }
+
+    class C : I
+    {
+    }
+
+    public static void Run()
+    {
+        var c = new C();
+        var dcs = ((I)c).DefaultTypeOf();
+        Assert.Equal("String", dcs);
+    }
+}
+
+static class OverriddenDefaultMethodCases
+{
+    interface I<T>
+    {
+        string DefaultTypeOf() => typeof(T).Name;
+    }
+    class C : I<string>
+    {
+        public string DefaultTypeOf() => "C.String";
+    }
+
+    class GenericC<T> : I<T>
+    {
+        public string DefaultTypeOf() => "C." + typeof(T).Name;
+    }
+
+    public static void Run()
+    {
+        RunNonGenericOverride();
+        RunGenericOverride();
+    }
+
+    static void RunNonGenericOverride()
+    {
+        var c = new C();
+        var dcs = ((I<string>)c).DefaultTypeOf();
+        Assert.Equal("C.String", dcs);
+    }
+
+    static void RunGenericOverride()
+    {
+        var c = new GenericC<string>();
+        var dcs = ((I<string>)c).DefaultTypeOf();
+        Assert.Equal("C.String", dcs);
+    }
+}
+
+static class ValueTypeArgumentDefaultMethodCase
+{
+    interface I<T>
+    {
+        string DefaultTypeOf() => typeof(T).Name;
+    }
+
+    class C : I<int>
+    {
+    }
+
+    public static void Run()
+    {
+        var c = new C();
+        var dcs = ((I<int>)c).DefaultTypeOf();
+        Assert.Equal("Int32", dcs);
+    }
+}
+
+static class StructNonDefaultImplementationCase
+{
+    interface I<T>
+    {
+        string DefaultTypeOf();
+    }
+
+    struct C<T> : I<T>
+    {
+        public string DefaultTypeOf() => "C." + typeof(T).Name;
+    }
+
+    public static void Run()
+    {
+        var c = new C<string>();
+        var dcs = ((I<string>)c).DefaultTypeOf();
+        Assert.Equal("C.String", dcs);
+    }
+}
+
+static class ContravariantDefaultMethodCase
+{
+    interface I<in T>
+    {
+        string DefaultTypeOf() => typeof(T).Name;
+    }
+
+    class C : I<object>
+    {
+    }
+
+    public static void Run()
+    {
+        var c = new C();
+        var dcs = ((I<string>)c).DefaultTypeOf();
+        Assert.Equal("Object", dcs);
+    }
+}
+
+static class DirectStructMethodCase
+{
+    struct C<T>
+    {
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public string DefaultTypeOf() => typeof(T).Name;
+    }
+
+    public static void Run()
+    {
+        var c = new C<string>();
+        var dcs = c.DefaultTypeOf();
+        Assert.Equal("String", dcs);
+    }
+}
+
+static class StaticGenericMethodCase
+{
+    class C<T>
+    {
+        public static string DefaultTypeOf() => typeof(T).Name;
+    }
+
+    public static void Run()
+    {
+        var dcs = C<string>.DefaultTypeOf();
+        Assert.Equal("String", dcs);
+    }
+}
+
diff --git a/src/tests/Loader/classloader/DefaultInterfaceMethods/devirtualization/dim_devirtualization.csproj b/src/tests/Loader/classloader/DefaultInterfaceMethods/devirtualization/dim_devirtualization.csproj
new file mode 100644
index 00000000000000..30d4804022360c
--- /dev/null
+++ b/src/tests/Loader/classloader/DefaultInterfaceMethods/devirtualization/dim_devirtualization.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="$(TestLibraryProjectPath)" />
+  </ItemGroup>
+</Project>
