Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down
46 changes: 45 additions & 1 deletion src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,50 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
info->resolvedTokenDevirtualizedUnboxedMethod = default(CORINFO_RESOLVED_TOKEN);
}

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;
}
Comment thread
hez2010 marked this conversation as resolved.

if (originalImpl.IsSharedByGenericInstantiations)
{
// TODO: Support for runtime lookup
info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}
}

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;
}
Comment thread
hez2010 marked this conversation as resolved.
#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
Expand All @@ -1496,7 +1540,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 = impl.RequiresInstMethodDescArg() ? contextFromMethod(originalImpl) : contextFromType(owningType);

return true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
50 changes: 35 additions & 15 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -8835,10 +8826,19 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)

if (pApproxMT->IsInterface())
{
// As noted above, we can't yet handle generic interfaces
// with default methods.
_ASSERTE(!pDevirtMD->HasClassInstantiation());
if (pDevirtMD->HasClassInstantiation())
{
if (TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetClassInstantiation()))
{
// 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->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pExactMT;
info->instParamLookup.constLookup.accessType = IAT_VALUE;
}
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
}
Comment thread
hez2010 marked this conversation as resolved.
else if (pBaseMT->IsInterface() && pObjMT->IsArray())
{
Expand All @@ -8855,10 +8855,29 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
MethodDesc* pPrimaryMD = pDevirtMD;
pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true);

if (pDevirtMD->IsSharedByGenericMethodInstantiations())
{
Comment thread
hez2010 marked this conversation as resolved.
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false);
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.

if (TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetClassInstantiation()))
Comment thread
hez2010 marked this conversation as resolved.
{
// 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;
}

// We are dealing with a generic virtual method whose owning type is exact.
// If the method is not exact, a runtime lookup would be required.
const bool requiresRuntimeLookup = TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation());
if (requiresRuntimeLookup)
{
// TODO: Support for runtime lookup
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
return false;
}
}

isGenericVirtual = true;
Expand All @@ -8873,13 +8892,14 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
}

info->tokenLookupContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD);
pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;
}
else
{
info->tokenLookupContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT);
}

pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD;
Comment thread
hez2010 marked this conversation as resolved.

// Success! Pass back the results.
//
info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) pDevirtMD;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;

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 class Program
{
[Fact]
public static void TestEntryPoint()
{
var m = new M();
if (((IM<int>)m).UseDefaultM)
{
IM<int>.DefaultM(42);
return;
}
else
{
((IM<int>)m).M(42);
}
throw new UnreachableException();
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
}
Comment thread
hez2010 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;

interface I<T> where T : IComparable<T>
{
T GetAt(int i, T[] tx) => tx[i];
}

class C : I<string>
{
}

public static class Program
{
private static string[] tx = new string[] { "test" };

[Fact]
public static void TestEntryPoint()
{
I<string> c = new C();
var dcs = c.GetAt(0, tx);
Assert.Equal("test", dcs);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.
Comment thread
hez2010 marked this conversation as resolved.

interface I<T>
{
string DefaultTypeOf() => typeof(T).Name;
}

class Dummy { }

class C : I<string>, I<object>, I<Dummy>
{
string I<Dummy>.DefaultTypeOf() => "C.Dummy";
}

public static class Program
{
[Fact]
public static void TestEntryPoint()
{
var c = new C();
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);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;

interface I
{
string DefaultTypeOf() => typeof(string).Name;
}

class C : I
{
}

public static class Program
{
[Fact]
public static void TestEntryPoint()
{
var c = new C();
var dcs = ((I)c).DefaultTypeOf();
Assert.Equal("String", dcs);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;

interface I<T>
{
string DefaultTypeOf() => typeof(T).Name;
}

class C : I<string>
{
public string DefaultTypeOf() => "C.String";
}

public static class Program
{
[Fact]
public static void TestEntryPoint()
{
var c = new C();
var dcs = ((I<string>)c).DefaultTypeOf();
Assert.Equal("C.String", dcs);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Loading
Loading