-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Instantiating stub for devirtualized default interface method on a generic type #43668
Changes from all commits
618cfb2
88e6885
f64d641
d5d91e8
88d5e9f
a129821
380351c
9162917
1a54185
286f606
46fce53
ddbed14
561ed32
43d13cb
e5e1002
5302c51
1adb465
df791f1
e0a002e
73f12da
bc8ecd3
08b9b07
c49efef
aa89550
e91c55b
d302057
fad2020
c5245c5
2c7bf8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7762,7 +7762,7 @@ getMethodInfoHelper( | |
ftn, | ||
false); | ||
|
||
// Shared generic or static per-inst methods and shared methods on generic structs | ||
// Shared generic or static per-inst methods, shared methods on generic structs and default interface methods | ||
// take an extra argument representing their instantiation | ||
if (ftn->RequiresInstArg()) | ||
methInfo->args.callConv = (CorInfoCallConv)(methInfo->args.callConv | CORINFO_CALLCONV_PARAMTYPE); | ||
|
@@ -8898,25 +8898,28 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) | |
// Don't try and devirtualize com interface calls. | ||
if (pObjMT->IsComObjectType()) | ||
{ | ||
return nullptr; | ||
return false; | ||
} | ||
#endif // FEATURE_COMINTEROP | ||
|
||
// Interface call devirtualization. | ||
// | ||
// We must ensure that pObjMT actually implements the | ||
// interface corresponding to pBaseMD. | ||
if (!pObjMT->CanCastToInterface(pBaseMT)) | ||
{ | ||
return false; | ||
} | ||
bool canCastStraightForward = pObjMT->CanCastToInterface(pBaseMT); | ||
|
||
// For generic interface methods we must have context to | ||
// safely devirtualize. | ||
MethodTable* pOwnerMT = nullptr; | ||
if (info->context != nullptr) | ||
{ | ||
TypeHandle OwnerClsHnd = GetTypeFromContext(info->context); | ||
MethodTable* pOwnerMT = OwnerClsHnd.GetMethodTable(); | ||
pOwnerMT = OwnerClsHnd.GetMethodTable(); | ||
|
||
if (!canCastStraightForward && !(pOwnerMT->IsInterface() && pObjMT->CanCastToInterface(pOwnerMT))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What scenario are you addressing here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I found your explanation above. If pOwnerMT isn't an interface, why don't we return false here? In reply to: 570649611 [](ancestors = 570649611) |
||
{ | ||
return false; | ||
} | ||
|
||
// If the derived class is a shared class, make sure the | ||
// owner class is too. | ||
|
@@ -8927,6 +8930,10 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) | |
|
||
pDevirtMD = pObjMT->GetMethodDescForInterfaceMethod(TypeHandle(pOwnerMT), pBaseMD, FALSE /* throwOnConflict */); | ||
} | ||
else if (!canCastStraightForward) | ||
{ | ||
return false; | ||
} | ||
else if (!pBaseMD->HasClassOrMethodInstantiation()) | ||
{ | ||
pDevirtMD = pObjMT->GetMethodDescForInterfaceMethod(pBaseMD, FALSE /* throwOnConflict */); | ||
|
@@ -8937,12 +8944,27 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) | |
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 | ||
// default interface method | ||
// generics over value types do not share code, so we should do nothing on caller site | ||
// when `requiresInstMethodTableArg == false` | ||
if (pDevirtMD->GetMethodTable()->IsInterface() && pDevirtMD->HasClassInstantiation()) | ||
{ | ||
return false; | ||
// since we are in DIM branch, that means | ||
// call MethodTable::GetMethodDescForInterfaceMethod above has returned | ||
// either instantiating stub ie `pDevirtMD->IsWrapperStub() == true` | ||
// or non shared generic instantiation ie <T> is <Int32> | ||
_ASSERTE(pDevirtMD->IsWrapperStub() || !(pDevirtMD->GetMethodTable()->IsSharedByGenericInstantiations() || pDevirtMD->IsSharedByGenericMethodInstantiations())); | ||
info->exactContext = MAKE_CLASSCONTEXT(pDevirtMD->GetMethodTable()); | ||
if (pDevirtMD->IsWrapperStub()) | ||
{ | ||
info->requiresInstMethodTableArg = true; | ||
pDevirtMD = pDevirtMD->GetExistingWrappedMethodDesc(); | ||
} | ||
else | ||
{ | ||
info->requiresInstMethodTableArg = false; | ||
} | ||
_ASSERTE(pDevirtMD->IsRestored() && pDevirtMD->GetMethodTable()->IsFullyLoaded()); | ||
} | ||
} | ||
else | ||
|
@@ -8999,18 +9021,24 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) | |
// interface method. If so, we'll use the method's class. | ||
// | ||
MethodTable* pApproxMT = pDevirtMD->GetMethodTable(); | ||
MethodTable* pExactMT = pApproxMT; | ||
|
||
if (pApproxMT->IsInterface()) | ||
{ | ||
// As noted above, we can't yet handle generic interfaces | ||
// with default methods. | ||
_ASSERTE(!pDevirtMD->HasClassInstantiation()); | ||
if (pDevirtMD->HasClassInstantiation()) | ||
{ | ||
// DIMs are handled above | ||
_ASSERTE(info->exactContext != NULL); | ||
} | ||
else | ||
{ | ||
info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pApproxMT); | ||
} | ||
|
||
} | ||
else | ||
{ | ||
pExactMT = pDevirtMD->GetExactDeclaringType(pObjMT); | ||
MethodTable* pExactMT = pDevirtMD->GetExactDeclaringType(pObjMT); | ||
info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); | ||
} | ||
|
||
#ifdef FEATURE_READYTORUN_COMPILER | ||
|
@@ -9034,8 +9062,6 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) | |
// Success! Pass back the results. | ||
// | ||
info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) pDevirtMD; | ||
info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); | ||
info->requiresInstMethodTableArg = false; | ||
|
||
return true; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -581,7 +581,7 @@ class MethodDesc | |
// | ||
// RequiresInstMethodDescArg() | ||
// The method is itself generic and is shared between generic | ||
// instantiations but is not itself generic. Furthermore | ||
// instantiations but is not itself generic. WTF LOL. Furthermore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Words are hard, and copy/pasting is easy. The "but is not itself generic" is just wrong and should be deleted from this comment. |
||
// no "this" pointer is given (e.g. a value type method), so we pass in the | ||
// exact-instantiation method table as an extra argument. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This should read "MethodDesc" |
||
// i.e. shared-code instantiated generic methods | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2094,6 +2094,7 @@ class MethodTable | |
|
||
MethodDesc *GetMethodDescForInterfaceMethod(TypeHandle ownerType, MethodDesc *pInterfaceMD, BOOL throwOnConflict); | ||
MethodDesc *GetMethodDescForInterfaceMethod(MethodDesc *pInterfaceMD, BOOL throwOnConflict); // You can only use this one for non-generic interfaces | ||
// ^-- I don't believe this is correct statement, we have PRECONDITION(!pInterfaceMD->HasClassOrMethodInstantiation()); which implies it can be used with generic interfaces | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is not the meaning of the precondition. Its actually the opposite. If the precondition isn't true, the method can't be used, and if the method is on a generic interface then HasClassOrMethodInstantiation will return true, so !HasClassOrMethodInstantiation will be false. |
||
|
||
//------------------------------------------------------------------- | ||
// INTERFACE MAP. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
All of the test files need the copyright attribution comment. |
||
using System.Runtime.CompilerServices; | ||
using System.Threading.Tasks; | ||
|
||
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 | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveOptimization)] | ||
static int Main() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should find a way to test this without AggressiveOptimization. Currently that will disable the Crossgen compiler and so we won't test the behavior in ahead of time compile scenarios. |
||
{ | ||
var c = new C(); | ||
var dcs = ((I<string>)c).DefaultTypeOf(); | ||
if (dcs != "String") return 200; | ||
var dos = ((I<object>)c).DefaultTypeOf(); | ||
if (dos != "Object") return 300; | ||
var dds = ((I<Dummy>)c).DefaultTypeOf(); | ||
if (dds != "C.Dummy") return 300; | ||
return 100; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<CLRTestKind>BuildAndRun</CLRTestKind> | ||
<CLRTestPriority>0</CLRTestPriority> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="devirttest.cs" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Threading.Tasks; | ||
|
||
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) | ||
{ | ||
Console.WriteLine("Default Behaviour"); | ||
return default; | ||
} | ||
} | ||
|
||
struct M : IM<int> { } | ||
|
||
public static class Program | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveOptimization)] | ||
static int Main() | ||
{ | ||
var m = new M(); | ||
if (((IM<int>)m).UseDefaultM) | ||
{ | ||
IM<int>.DefaultM(42); | ||
return 100; | ||
} | ||
else | ||
{ | ||
((IM<int>)m).M(42); | ||
} | ||
return 200; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good comment change.