Skip to content

Commit 39a31f0

Browse files
authored
Virtual stub indirect call profiling (#116453)
Add support for profiling indirect virtual stub calls and subsequent guarded devirtualization.
1 parent 5e1973b commit 39a31f0

File tree

8 files changed

+80
-34
lines changed

8 files changed

+80
-34
lines changed

src/coreclr/jit/compiler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11886,7 +11886,7 @@ class GenTreeVisitor
1188611886

1188711887
if (call->gtCallType == CT_INDIRECT)
1188811888
{
11889-
if (call->gtCallCookie != nullptr)
11889+
if (!call->IsVirtualStub() && (call->gtCallCookie != nullptr))
1189011890
{
1189111891
result = WalkTree(&call->gtCallCookie, call);
1189211892
if (result == fgWalkResult::WALK_ABORT)

src/coreclr/jit/compiler.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4683,7 +4683,8 @@ void GenTree::VisitOperands(TVisitor visitor)
46834683

46844684
if (call->gtCallType == CT_INDIRECT)
46854685
{
4686-
if ((call->gtCallCookie != nullptr) && (visitor(call->gtCallCookie) == VisitResult::Abort))
4686+
if (!call->IsVirtualStub() && (call->gtCallCookie != nullptr) &&
4687+
(visitor(call->gtCallCookie) == VisitResult::Abort))
46874688
{
46884689
return;
46894690
}

src/coreclr/jit/gentree.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5977,7 +5977,9 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
59775977
if (call->gtCallType == CT_INDIRECT)
59785978
{
59795979
// pinvoke-calli cookie is a constant, or constant indirection
5980-
assert(call->gtCallCookie == nullptr || call->gtCallCookie->OperIs(GT_CNS_INT, GT_IND));
5980+
// or a non-tree if this is a managed call.
5981+
assert(call->IsVirtualStub() || (call->gtCallCookie == nullptr) ||
5982+
call->gtCallCookie->OperIs(GT_CNS_INT, GT_IND));
59815983

59825984
GenTree* indirect = call->gtCallAddr;
59835985

@@ -6725,7 +6727,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse)
67256727
}
67266728
if (call->gtCallType == CT_INDIRECT)
67276729
{
6728-
if (operand == call->gtCallCookie)
6730+
if (!call->IsVirtualStub() && (operand == call->gtCallCookie))
67296731
{
67306732
*pUse = &call->gtCallCookie;
67316733
return true;
@@ -9899,16 +9901,23 @@ GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree)
98999901
/* Copy the union */
99009902
if (tree->gtCallType == CT_INDIRECT)
99019903
{
9902-
copy->gtCallCookie = tree->gtCallCookie ? gtCloneExpr(tree->gtCallCookie) : nullptr;
9903-
copy->gtCallAddr = tree->gtCallAddr ? gtCloneExpr(tree->gtCallAddr) : nullptr;
9904+
if (tree->IsVirtualStub())
9905+
{
9906+
copy->gtCallCookie = tree->gtCallCookie;
9907+
}
9908+
else
9909+
{
9910+
copy->gtCallCookie = tree->gtCallCookie ? gtCloneExpr(tree->gtCallCookie) : nullptr;
9911+
}
9912+
copy->gtCallAddr = tree->gtCallAddr ? gtCloneExpr(tree->gtCallAddr) : nullptr;
99049913
}
99059914
else
99069915
{
99079916
copy->gtCallMethHnd = tree->gtCallMethHnd;
99089917
copy->gtInlineCandidateInfo = tree->gtInlineCandidateInfo;
9909-
copy->gtInlineInfoCount = tree->gtInlineInfoCount;
99109918
}
99119919

9920+
copy->gtInlineInfoCount = tree->gtInlineInfoCount;
99129921
copy->gtLateDevirtualizationInfo = tree->gtLateDevirtualizationInfo;
99139922

99149923
copy->gtCallType = tree->gtCallType;
@@ -10641,7 +10650,7 @@ void GenTreeUseEdgeIterator::AdvanceCall()
1064110650
assert(call->gtCallType == CT_INDIRECT);
1064210651

1064310652
m_advance = &GenTreeUseEdgeIterator::AdvanceCall<CALL_ADDRESS>;
10644-
if (call->gtCallCookie != nullptr)
10653+
if (!call->IsVirtualStub() && (call->gtCallCookie != nullptr))
1064510654
{
1064610655
m_edge = &call->gtCallCookie;
1064710656
return;
@@ -10964,6 +10973,11 @@ void Compiler::gtDispNodeName(GenTree* tree)
1096410973
}
1096510974
else if (tree->AsCall()->gtCallType == CT_INDIRECT)
1096610975
{
10976+
if (tree->AsCall()->IsVirtual())
10977+
{
10978+
callType = "CALLV";
10979+
}
10980+
1096710981
ctType = " ind";
1096810982
}
1096910983
else

src/coreclr/jit/importer.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6935,8 +6935,20 @@ void Compiler::impImportBlockCode(BasicBlock* block)
69356935
{
69366936
JITDUMP(".... checking for GDV of IEnumerable<T>...\n");
69376937

6938-
GenTreeCall* const call = op1->AsRetExpr()->gtInlineCandidate;
6939-
NamedIntrinsic const ni = lookupNamedIntrinsic(call->gtCallMethHnd);
6938+
GenTreeCall* const call = op1->AsRetExpr()->gtInlineCandidate;
6939+
NamedIntrinsic ni = NI_Illegal;
6940+
6941+
// TODO -- handle CT_INDIRECT virtuals here too
6942+
// but we don't have the right method handle
6943+
//
6944+
if (call->gtCallType == CT_USER_FUNC)
6945+
{
6946+
ni = lookupNamedIntrinsic(call->gtCallMethHnd);
6947+
}
6948+
else if (call->IsGuardedDevirtualizationCandidate())
6949+
{
6950+
JITDUMP("No GDV IEnumerable<T> check for [%06u]\n", dspTreeID(call));
6951+
}
69406952

69416953
if (ni == NI_System_Collections_Generic_IEnumerable_GetEnumerator)
69426954
{

src/coreclr/jit/importercalls.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7276,7 +7276,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call,
72767276

72777277
if (!canResolve)
72787278
{
7279-
JITDUMP("Can't figure out which method would be invoked, sorry\n");
7279+
JITDUMP("Can't figure out which method would be invoked, sorry. [%s]\n",
7280+
devirtualizationDetailToString(dvInfo.detail));
72807281

72817282
// Continue checking other candidates, maybe some of them will succeed.
72827283
break;
@@ -7695,20 +7696,28 @@ void Compiler::impMarkInlineCandidateHelper(GenTreeCall* call,
76957696
}
76967697
}
76977698

7698-
/* Ignore helper calls */
7699-
7699+
// Ignore helper calls
7700+
//
77007701
if (call->IsHelperCall())
77017702
{
77027703
assert(!call->IsGuardedDevirtualizationCandidate());
77037704
inlineResult->NoteFatal(InlineObservation::CALLSITE_IS_CALL_TO_HELPER);
77047705
return;
77057706
}
77067707

7707-
/* Ignore indirect calls */
7708+
// Ignore indirect calls, unless they are indirect virtual stub calls with profile info.
7709+
//
77087710
if (call->gtCallType == CT_INDIRECT)
77097711
{
7710-
inlineResult->NoteFatal(InlineObservation::CALLSITE_IS_NOT_DIRECT_MANAGED);
7711-
return;
7712+
if (!call->IsGuardedDevirtualizationCandidate())
7713+
{
7714+
inlineResult->NoteFatal(InlineObservation::CALLSITE_IS_NOT_DIRECT_MANAGED);
7715+
return;
7716+
}
7717+
else
7718+
{
7719+
assert(call->IsVirtualStub());
7720+
}
77127721
}
77137722

77147723
// The inliner gets confused when the unmanaged convention reverses arg order (like x86).
@@ -8924,11 +8933,6 @@ bool Compiler::impConsiderCallProbe(GenTreeCall* call, IL_OFFSET ilOffset)
89248933
//
89258934
Compiler::GDVProbeType Compiler::compClassifyGDVProbeType(GenTreeCall* call)
89268935
{
8927-
if (call->gtCallType == CT_INDIRECT)
8928-
{
8929-
return GDVProbeType::None;
8930-
}
8931-
89328936
if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) || IsAot())
89338937
{
89348938
return GDVProbeType::None;

src/coreclr/jit/inline.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,9 +1344,7 @@ InlineContext* InlineStrategy::NewContext(InlineContext* parentContext, Statemen
13441344
// which becomes a single statement where the IL location points to the
13451345
// ldarg instruction.
13461346
context->m_Location = stmt->GetDebugInfo().GetLocation();
1347-
1348-
assert(call->gtCallType == CT_USER_FUNC);
1349-
context->m_Callee = call->gtCallMethHnd;
1347+
context->m_Callee = call->gtCallMethHnd;
13501348

13511349
#if defined(DEBUG)
13521350
context->m_Devirtualized = call->IsDevirtualized();

src/coreclr/jit/morph.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call
17731773
// add as a non-standard arg.
17741774
}
17751775
}
1776-
else if (call->gtCallType == CT_INDIRECT && (call->gtCallCookie != nullptr))
1776+
else if ((call->gtCallType == CT_INDIRECT) && !call->IsVirtualStub() && (call->gtCallCookie != nullptr))
17771777
{
17781778
assert(!call->IsUnmanaged());
17791779

src/coreclr/vm/jitinterface.cpp

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8638,7 +8638,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
86388638
return false;
86398639
}
86408640
}
8641-
else if (!pObjMT->CanCastToInterface(pBaseMT))
8641+
else if (!pBaseMT->IsSharedByGenericInstantiations() && !pObjMT->CanCastToInterface(pBaseMT))
86428642
{
86438643
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CAST;
86448644
return false;
@@ -8648,32 +8648,49 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
86488648
// safely devirtualize.
86498649
if (info->context != nullptr)
86508650
{
8651-
// If the derived class is a shared class, make sure the
8652-
// owner class is too.
8653-
if (pObjMT->IsSharedByGenericInstantiations())
8651+
MethodTable* interfaceMT = nullptr;
8652+
8653+
if (pObjMT->IsSharedByGenericInstantiations() || pBaseMT->IsSharedByGenericInstantiations())
86548654
{
86558655
MethodTable* pCanonBaseMT = pBaseMT->GetCanonicalMethodTable();
8656-
8656+
86578657
// Check to see if the derived class implements multiple variants of a matching interface.
86588658
// If so, we cannot predict exactly which implementation is in use here.
86598659
MethodTable::InterfaceMapIterator it = pObjMT->IterateInterfaceMap();
86608660
int canonicallyMatchingInterfacesFound = 0;
8661+
MethodTable* interfaceMT = nullptr;
86618662
while (it.Next())
86628663
{
8663-
if (it.GetInterface(pObjMT)->GetCanonicalMethodTable() == pCanonBaseMT)
8664+
MethodTable* mt = it.GetInterface(pObjMT);
8665+
if (mt->GetCanonicalMethodTable() == pCanonBaseMT)
86648666
{
8667+
interfaceMT = mt;
86658668
canonicallyMatchingInterfacesFound++;
8669+
86668670
if (canonicallyMatchingInterfacesFound > 1)
86678671
{
8668-
// Multiple canonically identical interfaces found when attempting to devirtualize an inexact interface dispatch
8672+
// Multiple canonically identical interfaces found.
8673+
//
86698674
info->detail = CORINFO_DEVIRTUALIZATION_MULTIPLE_IMPL;
86708675
return false;
86718676
}
86728677
}
86738678
}
8674-
}
8679+
8680+
if (canonicallyMatchingInterfacesFound == 0)
8681+
{
8682+
// The object doesn't implement the interface...
8683+
//
8684+
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CAST;
8685+
return false;
8686+
}
86758687

8676-
pDevirtMD = pObjMT->GetMethodDescForInterfaceMethod(TypeHandle(pBaseMT), pBaseMD, FALSE /* throwOnConflict */);
8688+
pDevirtMD = pObjMT->GetMethodDescForInterfaceMethod(TypeHandle(interfaceMT), pBaseMD, FALSE /* throwOnConflict */);
8689+
}
8690+
else
8691+
{
8692+
pDevirtMD = pObjMT->GetMethodDescForInterfaceMethod(TypeHandle(pBaseMT), pBaseMD, FALSE /* throwOnConflict */);
8693+
}
86778694
}
86788695
else if (!pBaseMD->HasClassOrMethodInstantiation())
86798696
{

0 commit comments

Comments
 (0)