Skip to content

Commit 87b470c

Browse files
authored
JIT: Devirtualize non-shared generic virtual methods (#122023)
Enable devirtualization support for generic virtual methods. When we see a base method having a method instantiation, we use `FindOrCreateAssociatedMethodDesc` to obtain the devirted method. Also introduced a jit knob so that it can be turned off at any time. AOT support is not included in this PR, which needs additional work in managed type system. Also, if we end up with an instantiating stub (i.e. a shared generic method that requires runtime lookup), we don't have the correct generic context so we need to bail out for now. Codegen example: ```cs public class IntProcessor : VirtualGenericClass, IVritualGenericInterface { public override void Process<T>(T item) { Console.WriteLine(item.ToString()); } } public interface IVritualGenericInterface { void Process<T>(T item) where T : notnull; } public static void Test<T>(IVritualGenericInterface ifce, T item) where T : notnull { ifce.Process(item); } public static void Test<T>(VirtualGenericClass baseClass, T item) where T : notnull { baseClass.Process(item); } static void Test() { IVritualGenericInterface i = new IntProcessor(); Test(i, 42); VirtualGenericClass c = new IntProcessor(); Test(c, 42); } ``` Codegen diff: ```diff G_M27646_IG01: ;; offset=0x0000 - push rsi push rbx - sub rsp, 40 + sub rsp, 32 - ;; size=6 bbWeight=1 PerfScore 2.25 + ;; size=5 bbWeight=1 PerfScore 1.25 -G_M27646_IG02: ;; offset=0x0006 +G_M27646_IG02: ;; offset=0x0005 - mov rbx, 0x7FFE0803F318 ; Program+IntProcessor + mov rbx, 0x221CA429C38 ; 'System.Int32' mov rcx, rbx - call CORINFO_HELP_NEWSFAST + call [System.Console:WriteLine(System.Object)] - mov rsi, rax + mov ecx, 42 - mov rcx, rsi + call [System.Number:Int32ToDecStr(int):System.String] - mov rdx, 0x7FFE0803F100 ; Program+IVritualGenericInterface + mov rcx, rax - mov r8, 0x7FFE0803F6A8 ; token handle + call [System.Console:WriteLine(System.String)] - call CORINFO_HELP_VIRTUAL_FUNC_PTR - mov rcx, rsi - mov edx, 42 - call rax mov rcx, rbx - call CORINFO_HELP_NEWSFAST + call [System.Console:WriteLine(System.Object)] - mov rbx, rax + mov ecx, 42 - mov rcx, rbx + call [System.Number:Int32ToDecStr(int):System.String] - mov rdx, 0x7FFE0803EF30 ; Program+VirtualGenericClass + mov rcx, rax - mov r8, 0x7FFE0803F8A8 ; token handle + call [System.Console:WriteLine(System.String)] - call CORINFO_HELP_VIRTUAL_FUNC_PTR - mov rcx, rbx - mov edx, 42 - call rax nop - ;; size=109 bbWeight=1 PerfScore 14.00 + ;; size=69 bbWeight=1 PerfScore 20.00 -G_M27646_IG03: ;; offset=0x0073 +G_M27646_IG03: ;; offset=0x004A - add rsp, 40 + add rsp, 32 pop rbx - pop rsi ret - ;; size=7 bbWeight=1 PerfScore 2.25 + ;; size=6 bbWeight=1 PerfScore 1.75 ``` Contributes to #112596 cc: @dotnet/jit-contrib
1 parent 34c3fee commit 87b470c

File tree

18 files changed

+888
-101
lines changed

18 files changed

+888
-101
lines changed

src/coreclr/inc/corinfo.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,7 +1540,7 @@ enum CORINFO_DEVIRTUALIZATION_DETAIL
15401540
{
15411541
CORINFO_DEVIRTUALIZATION_UNKNOWN, // no details available
15421542
CORINFO_DEVIRTUALIZATION_SUCCESS, // devirtualization was successful
1543-
CORINFO_DEVIRTUALIZATION_FAILED_CANON, // object class was canonical
1543+
CORINFO_DEVIRTUALIZATION_FAILED_CANON, // object class or method was canonical
15441544
CORINFO_DEVIRTUALIZATION_FAILED_COM, // object class was com
15451545
CORINFO_DEVIRTUALIZATION_FAILED_CAST, // object class could not be cast to interface class
15461546
CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP, // interface method could not be found
@@ -1556,6 +1556,7 @@ enum CORINFO_DEVIRTUALIZATION_DETAIL
15561556
CORINFO_DEVIRTUALIZATION_FAILED_DUPLICATE_INTERFACE, // crossgen2 virtual method algorithm and runtime algorithm differ in the presence of duplicate interface implementations
15571557
CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE, // Decl method cannot be represented in R2R image
15581558
CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE, // Support for type equivalence in devirtualization is not yet implemented in crossgen2
1559+
CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL, // Devirtualization of generic virtual methods is not yet implemented in crossgen2
15591560
CORINFO_DEVIRTUALIZATION_COUNT, // sentinel for maximum value
15601561
};
15611562

@@ -1578,7 +1579,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO
15781579
// - If pResolvedTokenDevirtualizedMethod is not set to NULL and targeting an R2R image
15791580
// use it as the parameter to getCallInfo
15801581
// - isInstantiatingStub is set to TRUE if the devirtualized method is a generic method instantiating stub
1581-
// - wasArrayInterfaceDevirt is set TRUE for array interface method devirtualization
1582+
// - needsMethodContext is set TRUE if the devirtualized method may require a method context
15821583
// (in which case the method handle and context will be a generic method)
15831584
//
15841585
CORINFO_METHOD_HANDLE devirtualizedMethod;
@@ -1587,7 +1588,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO
15871588
CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod;
15881589
CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod;
15891590
bool isInstantiatingStub;
1590-
bool wasArrayInterfaceDevirt;
1591+
bool needsMethodContext;
15911592
};
15921593

15931594
//----------------------------------------------------------------------------

src/coreclr/inc/jiteeversionguid.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@
3737

3838
#include <minipal/guid.h>
3939

40-
constexpr GUID JITEEVersionIdentifier = { /* 693c76d4-6555-49b4-bcb4-db73cb87e8d2 */
41-
0x693c76d4,
42-
0x6555,
43-
0x49b4,
44-
{0xbc, 0xb4, 0xdb, 0x73, 0xcb, 0x87, 0xe8, 0xd2}
40+
constexpr GUID JITEEVersionIdentifier = { /* cc48149b-7cb0-404a-9e9a-3d1623bb6194 */
41+
0xcc48149b,
42+
0x7cb0,
43+
0x404a,
44+
{0x9e, 0x9a, 0x3d, 0x16, 0x23, 0xbb, 0x61, 0x94}
4545
};
4646

4747
#endif // JIT_EE_VERSIONING_GUID_H

src/coreclr/jit/compiler.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10510,6 +10510,8 @@ const char* Compiler::devirtualizationDetailToString(CORINFO_DEVIRTUALIZATION_DE
1051010510
return "Decl method cannot be represented in R2R image";
1051110511
case CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE:
1051210512
return "Support for type equivalence in devirtualization is not yet implemented in crossgen2";
10513+
case CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL:
10514+
return "Devirtualization of generic virtual methods is not yet implemented in crossgen2";
1051310515
default:
1051410516
return "undefined";
1051510517
}

src/coreclr/jit/fginline.cpp

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -544,32 +544,6 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitor<Substi
544544
}
545545
#endif // FEATURE_MULTIREG_RET
546546

547-
CORINFO_METHOD_HANDLE GetMethodHandle(GenTreeCall* call)
548-
{
549-
assert(call->IsDevirtualizationCandidate(m_compiler));
550-
if (call->IsVirtual())
551-
{
552-
return call->gtCallMethHnd;
553-
}
554-
else
555-
{
556-
GenTree* runtimeMethHndNode =
557-
call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle)->GetNode();
558-
assert(runtimeMethHndNode != nullptr);
559-
switch (runtimeMethHndNode->OperGet())
560-
{
561-
case GT_RUNTIMELOOKUP:
562-
return runtimeMethHndNode->AsRuntimeLookup()->GetMethodHandle();
563-
case GT_CNS_INT:
564-
return CORINFO_METHOD_HANDLE(runtimeMethHndNode->AsIntCon()->IconValue());
565-
default:
566-
assert(!"Unexpected type in RuntimeMethodHandle arg.");
567-
return nullptr;
568-
}
569-
return nullptr;
570-
}
571-
}
572-
573547
//------------------------------------------------------------------------
574548
// LateDevirtualization: re-examine calls after inlining to see if we
575549
// can do more devirtualization
@@ -612,9 +586,9 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitor<Substi
612586

613587
if (tree->OperIs(GT_CALL))
614588
{
615-
GenTreeCall* call = tree->AsCall();
616-
// TODO-CQ: Drop `call->gtCallType == CT_USER_FUNC` once we have GVM devirtualization
617-
bool tryLateDevirt = call->IsDevirtualizationCandidate(m_compiler) && (call->gtCallType == CT_USER_FUNC);
589+
GenTreeCall* call = tree->AsCall();
590+
CORINFO_METHOD_HANDLE method = NO_METHOD_HANDLE;
591+
bool tryLateDevirt = call->IsDevirtualizationCandidate(m_compiler, &method);
618592

619593
#ifdef DEBUG
620594
tryLateDevirt = tryLateDevirt && (JitConfig.JitEnableLateDevirtualization() == 1);
@@ -632,11 +606,12 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitor<Substi
632606

633607
CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd;
634608
InlineContext* inlinersContext = call->gtLateDevirtualizationInfo->inlinersContext;
635-
CORINFO_METHOD_HANDLE method = GetMethodHandle(call);
636609
unsigned methodFlags = 0;
637610
const bool isLateDevirtualization = true;
638611
const bool explicitTailCall = call->IsTailPrefixedCall();
639612

613+
assert(method != NO_METHOD_HANDLE);
614+
640615
CORINFO_CONTEXT_HANDLE contextInput = context;
641616
context = nullptr;
642617
m_compiler->impDevirtualizeCall(call, nullptr, &method, &methodFlags, &contextInput, &context,

src/coreclr/jit/gentree.cpp

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,16 +2367,50 @@ int GenTreeCall::GetNonStandardAddedArgCount(Compiler* compiler) const
23672367
// is devirtualized.
23682368
//
23692369
// Arguments:
2370-
// compiler - the compiler instance so that we can call eeFindHelper
2370+
// compiler - [In] the compiler instance so that we can call eeFindHelper
2371+
// pMethHandle - [Out] the method handle if the call is a devirtualization candidate
23712372
//
23722373
// Return Value:
23732374
// Returns true if this GT_CALL node is a devirtualization candidate.
23742375
//
2375-
bool GenTreeCall::IsDevirtualizationCandidate(Compiler* compiler) const
2376+
bool GenTreeCall::IsDevirtualizationCandidate(Compiler* compiler, CORINFO_METHOD_HANDLE* pMethHandle) const
23762377
{
2377-
return IsVirtual() ||
2378-
(gtCallType == CT_INDIRECT && (gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_VIRTUAL_FUNC_PTR) ||
2379-
gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_GVMLOOKUP_FOR_SLOT)));
2378+
CORINFO_METHOD_HANDLE methHandleToDevirt = NO_METHOD_HANDLE;
2379+
bool isDevirtCandidate = false;
2380+
2381+
if (IsVirtual() && gtCallType == CT_USER_FUNC)
2382+
{
2383+
methHandleToDevirt = gtCallMethHnd;
2384+
isDevirtCandidate = true;
2385+
}
2386+
else if (IsGenericVirtual(compiler) && (JitConfig.JitEnableGenericVirtualDevirtualization() != 0))
2387+
{
2388+
GenTree* runtimeMethHndNode =
2389+
gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle)->GetNode();
2390+
assert(runtimeMethHndNode != nullptr);
2391+
switch (runtimeMethHndNode->OperGet())
2392+
{
2393+
case GT_RUNTIMELOOKUP:
2394+
methHandleToDevirt = runtimeMethHndNode->AsRuntimeLookup()->GetMethodHandle();
2395+
isDevirtCandidate = true;
2396+
break;
2397+
case GT_CNS_INT:
2398+
methHandleToDevirt = CORINFO_METHOD_HANDLE(runtimeMethHndNode->AsIntCon()->gtCompileTimeHandle);
2399+
isDevirtCandidate = true;
2400+
break;
2401+
default:
2402+
// Unable to get method handle for devirtualization.
2403+
// This can happen if the method handle is not an RUNTIMELOOKUP or CNS_INT for generic virtuals,
2404+
break;
2405+
}
2406+
}
2407+
2408+
if (pMethHandle)
2409+
{
2410+
*pMethHandle = methHandleToDevirt;
2411+
}
2412+
2413+
return isDevirtCandidate;
23802414
}
23812415

23822416
//-------------------------------------------------------------------------

src/coreclr/jit/gentree.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5262,8 +5262,13 @@ struct GenTreeCall final : public GenTree
52625262
{
52635263
return (gtFlags & GTF_CALL_VIRT_KIND_MASK) == GTF_CALL_VIRT_VTABLE;
52645264
}
5265+
bool IsGenericVirtual(Compiler* compiler) const
5266+
{
5267+
return (gtCallType == CT_INDIRECT && (gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_VIRTUAL_FUNC_PTR) ||
5268+
gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_GVMLOOKUP_FOR_SLOT)));
5269+
}
52655270

5266-
bool IsDevirtualizationCandidate(Compiler* compiler) const;
5271+
bool IsDevirtualizationCandidate(Compiler* compiler, CORINFO_METHOD_HANDLE* pMethHandle = nullptr) const;
52675272

52685273
bool IsInlineCandidate() const
52695274
{

0 commit comments

Comments
 (0)