Skip to content
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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
618cfb2
added FindOrCreateAssociatedMethodDesc call to resolveVirtualMethodHe…
Rattenkrieg Oct 20, 2020
88e6885
added assertion
Rattenkrieg Oct 20, 2020
f64d641
beware of valuetypes
Rattenkrieg Oct 21, 2020
d5d91e8
fix copypaste
Rattenkrieg Oct 21, 2020
88d5e9f
fix assertion
Rattenkrieg Oct 21, 2020
a129821
don't create instantiating stubs for value types
Rattenkrieg Oct 22, 2020
380351c
callsite stub generic type handle parameter passing for devirtualized…
Rattenkrieg Oct 27, 2020
9162917
param order swap; formatting; cleanup
Rattenkrieg Oct 27, 2020
1a54185
ownerType as in-out param
Rattenkrieg Oct 28, 2020
286f606
fixed conditionals
Rattenkrieg Oct 28, 2020
46fce53
csharp jitinterface patch
Rattenkrieg Oct 28, 2020
ddbed14
return shared method desc instead of stub; refactor arg construction
Rattenkrieg Oct 28, 2020
561ed32
cleanup and typos
Rattenkrieg Oct 31, 2020
43d13cb
tests
Rattenkrieg Nov 3, 2020
e5e1002
tests cleanup
Rattenkrieg Nov 3, 2020
5302c51
formatting and tests fixes
Rattenkrieg Nov 3, 2020
1adb465
more tests fixes
Rattenkrieg Nov 3, 2020
df791f1
args for resolveVirtualMethod in struct
Rattenkrieg Nov 8, 2020
e0a002e
fixed order of fields in csharp struct
Rattenkrieg Nov 21, 2020
73f12da
conflicts fixed
Rattenkrieg Nov 21, 2020
bc8ecd3
nonblittable return fixed
Rattenkrieg Nov 21, 2020
08b9b07
fix bad merge
Rattenkrieg Nov 28, 2020
c49efef
cleanup build artifacts
Rattenkrieg Nov 28, 2020
aa89550
more build artifacts removed
Rattenkrieg Nov 28, 2020
e91c55b
rebased on master
Rattenkrieg Dec 12, 2020
d302057
fix rebase artifacts
Rattenkrieg Dec 12, 2020
fad2020
bool casing fixes
Rattenkrieg Dec 12, 2020
c5245c5
formatting; tests cleanup
Rattenkrieg Dec 17, 2020
2c7bf8a
fixed exact context lookup for non DIMs
Rattenkrieg Dec 20, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -1273,7 +1273,7 @@ void MethodContext::recResolveToken(CORINFO_RESOLVED_TOKEN* pResolvedToken, DWOR
key = SpmiRecordsHelper::CreateAgnostic_CORINFO_RESOLVED_TOKENin(pResolvedToken);

ResolveTokenValue value;
value.tokenOut = SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKENout(pResolvedToken, ResolveToken);
value.tokenOut = SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKENout(pResolvedToken, TryResolveToken);
value.exceptionCode = (DWORD)exceptionCode;

ResolveToken->Add(key, value);
Expand All @@ -1296,7 +1296,7 @@ void MethodContext::repResolveToken(CORINFO_RESOLVED_TOKEN* pResolvedToken, DWOR

ResolveTokenValue value = ResolveToken->Get(key);

SpmiRecordsHelper::Restore_CORINFO_RESOLVED_TOKENout(pResolvedToken, value.tokenOut, ResolveToken);
SpmiRecordsHelper::Restore_CORINFO_RESOLVED_TOKENout(pResolvedToken, value.tokenOut, TryResolveToken);
*exceptionCode = (DWORD)value.exceptionCode;
DEBUG_REP(dmpResolveToken(key, value));
}
Expand Down
79 changes: 60 additions & 19 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8592,13 +8592,18 @@ var_types Compiler::impImportCall(OPCODE opcode,
// (b) To shared-code instance methods in generic structs; the extra parameter
// is the struct's type handle (a vtable ptr)
// (c) To shared-code per-instantiation non-generic static methods in generic
// classes and structs; the extra parameter is the type handle
// classes, structs and default interface methods; the extra parameter is the type handle
// (d) To shared-code generic methods; the extra parameter is an
// exact-instantiation MethodDesc
//
// We also set the exact type context associated with the call so we can
// inline the call correctly later on.

// after devirtulization it may appear that devirtualized method
// would require extra type handle arg
// it's tempting to reorder this branch two "ifs" below
// and generalize its logic for devirtualized method case
// but I'm afraid to break stuff
if (sig->callConv & CORINFO_CALLCONV_PARAMTYPE)
{
assert(call->AsCall()->gtCallType == CT_USER_FUNC);
Expand Down Expand Up @@ -20915,7 +20920,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
return;
}

// Ask the runtime to determine the method that would be called based on the likely type.
// Ask the runtime to determine the method that would be called based on the guessed-for type.
//
CORINFO_DEVIRTUALIZATION_INFO dvInfo;
dvInfo.virtualMethod = baseMethod;
Expand Down Expand Up @@ -21160,20 +21165,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
// stubs)
call->gtInlineCandidateInfo = nullptr;

#if defined(DEBUG)
if (verbose)
{
printf("... after devirt...\n");
gtDispTree(call);
}

if (doPrint)
{
printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]\n", callKind, baseClassName,
baseMethodName, derivedClassName, derivedMethodName, note);
}
#endif // defined(DEBUG)

bool passExtraArgForValueType = false;
// If the 'this' object is a box, see if we can find the unboxed entry point for the call.
if (thisObj->IsBoxedValue())
{
Expand All @@ -21186,9 +21178,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
}

// Note for some shared methods the unboxed entry point requires an extra parameter.
bool requiresInstMethodTableArg = false;
bool requiresInstMethodTableArgForUnboxedEntry = false;
CORINFO_METHOD_HANDLE unboxedEntryMethod =
info.compCompHnd->getUnboxedEntry(derivedMethod, &requiresInstMethodTableArg);
info.compCompHnd->getUnboxedEntry(derivedMethod, &requiresInstMethodTableArgForUnboxedEntry);

if (unboxedEntryMethod != nullptr)
{
Expand All @@ -21200,7 +21192,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
//
// Ideally, we then inline the boxed method and and if it turns out not to modify
// the copy, we can undo the copy too.
if (requiresInstMethodTableArg)
if (requiresInstMethodTableArgForUnboxedEntry)
{
// Perform a trial box removal and ask for the type handle tree.
JITDUMP("Unboxed entry needs method table arg...\n");
Expand Down Expand Up @@ -21286,6 +21278,11 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
}
}
}
// there is no unboxed entry when we got devirtualized to DIM
else if (dvInfo.requiresInstMethodTableArg)
{
passExtraArgForValueType = true;
}
else
{
// Many of the low-level methods on value classes won't have unboxed entries,
Expand All @@ -21296,6 +21293,50 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
JITDUMP("Sorry, failed to find unboxed entry point\n");
}
}
// check wheter we have returned an instantiating stub for generic DIM
if ((isInterface && dvInfo.requiresInstMethodTableArg) || passExtraArgForValueType)
{
assert(((SIZE_T)dvInfo.context & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS);
CORINFO_CLASS_HANDLE exactClassHandle = eeGetClassFromContext(dvInfo.context);
GenTree* instParam = gtNewIconEmbClsHndNode(exactClassHandle);
call->gtCallMethHnd = derivedMethod;
if ((Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) || (call->gtCallArgs == nullptr))
{
call->gtCallArgs = gtPrependNewCallArg(instParam, call->gtCallArgs);
}
// Append for non-empty L2R
else
{
GenTreeCall::Use* beforeArg = call->gtCallArgs;
while (beforeArg->GetNext() != nullptr)
{
beforeArg = beforeArg->GetNext();
}

beforeArg->SetNext(gtNewCallArgs(instParam));
}
// do we need this?
for (GenTreeCall::Use& use : call->AsCall()->Args())
{
call->gtFlags |= use.GetNode()->gtFlags & GTF_GLOB_EFFECT;
}

// should we patch call->gtCallMoreFlags ?
}

#if defined(DEBUG)
if (verbose)
{
printf("... after devirt...\n");
gtDispTree(call);
}

if (doPrint)
{
printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]\n", callKind, baseClassName,
baseMethodName, derivedClassName, derivedMethodName, note);
}
#endif // defined(DEBUG)

// Need to update call info too.
//
Expand Down
6 changes: 5 additions & 1 deletion src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,8 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
{
Debug.Assert(ownerTypeDesc is InstantiatedType);
decl = _compilation.TypeSystemContext.GetMethodForInstantiatedType(decl.GetTypicalMethodDefinition(), (InstantiatedType)ownerTypeDesc);
info->exactContext = contextFromType(decl.OwningType);
info->requiresInstMethodTableArg = true;
}
}

Expand All @@ -984,9 +986,11 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info)
impl = getUnboxingThunk(impl);
}

MethodDesc exactImpl = TypeSystemHelpers.FindMethodOnTypeWithMatchingTypicalMethod(objType, impl);

info->devirtualizedMethod = ObjectToHandle(impl);
info->requiresInstMethodTableArg = false;
info->exactContext = contextFromType(impl.OwningType);
info->exactContext = contextFromType(exactImpl.OwningType);

return true;
}
Expand Down
62 changes: 44 additions & 18 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

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.

// take an extra argument representing their instantiation
if (ftn->RequiresInstArg())
methInfo->args.callConv = (CorInfoCallConv)(methInfo->args.callConv | CORINFO_CALLCONV_PARAMTYPE);
Expand Down Expand Up @@ -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)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What scenario are you addressing here?

Copy link
Member

Choose a reason for hiding this comment

The 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.
Expand All @@ -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 */);
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/method.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method table [](start = 27, length = 12)

This should read "MethodDesc"

// i.e. shared-code instantiated generic methods
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/methodtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u [](start = 0, length = 1)

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()
Copy link
Member

Choose a reason for hiding this comment

The 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;
}
}
Loading