Skip to content

Commit 02b7358

Browse files
authored
Expand Virtual Call targets earlier in Morph and allow CSE of the indirections (#47808)
* Added GTF_CALL_M_EXPANDED_EARLY to flag virtual calls that should be expanded early during fgMorph Added COMPlus_JitExpandCallsEarly variable to enable virtual calls to be expanded early on a per method basis Set opts.compExpandCallsEarly to true when we are optimizing and have COMPlus_JitExpandCallsEarly enabled Update gtSetEvalOrder to also include the call->gtControlExpr Update morph to call fgExpandVirtualVtableCallTarget when we are expanding early Update lower to not call LowerVirtualVtableCall when we have already expanded it early Modify CheckTreeId to print the duplicated gtTreeID before it asserts. All tests are passing when using COMPLUS_JitExpandCallsEarly=* Expand the Virtual Call target after we morph the args Fix an inadvertent change in the GT_CALL weights * Changed the default for Virtual calls to be expanded early in Morph Use COMPlus_JitExpandCallsEarly=0 to disable and use old behavior * Code Review feedback Added comment stating the the isRelative code path is never executed * Fixes for propagating gtControlExpr->gtFlags * Fix a few code size regressions when we perform a tail call * Tailcall lower fix * Code Review changes * Fixes for the TAILCALL_HELPER path for x86 * Address the Arm/Linux failure
1 parent 2e36f1b commit 02b7358

File tree

9 files changed

+293
-21
lines changed

9 files changed

+293
-21
lines changed

src/coreclr/jit/compiler.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3979,6 +3979,15 @@ void Compiler::compSetOptimizationLevel()
39793979
}
39803980
}
39813981

3982+
#if TARGET_ARM
3983+
// A single JitStress=1 Linux ARM32 test fails when we expand virtual calls early
3984+
// JIT\HardwareIntrinsics\General\Vector128_1\Vector128_1_ro
3985+
//
3986+
opts.compExpandCallsEarly = (JitConfig.JitExpandCallsEarly() == 2);
3987+
#else
3988+
opts.compExpandCallsEarly = (JitConfig.JitExpandCallsEarly() != 0);
3989+
#endif
3990+
39823991
fgCanRelocateEHRegions = true;
39833992
}
39843993

@@ -5514,7 +5523,7 @@ int Compiler::compCompile(CORINFO_MODULE_HANDLE classPtr,
55145523
#ifdef TARGET_UNIX
55155524
info.compMatchedVM = info.compMatchedVM && (eeInfo->osType == CORINFO_UNIX);
55165525
#else
5517-
info.compMatchedVM = info.compMatchedVM && (eeInfo->osType == CORINFO_WINNT);
5526+
info.compMatchedVM = info.compMatchedVM && (eeInfo->osType == CORINFO_WINNT);
55185527
#endif
55195528

55205529
// If we are not compiling for a matched VM, then we are getting JIT flags that don't match our target

src/coreclr/jit/compiler.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,6 +2120,10 @@ class fgArgInfo
21202120
noway_assert(!"GetArgEntry: argNum not found");
21212121
return nullptr;
21222122
}
2123+
void SetNeedsTemps()
2124+
{
2125+
needsTemps = true;
2126+
}
21232127

21242128
// Get the node for the arg at position argIndex.
21252129
// Caller must ensure that this index is a valid arg index.
@@ -5792,6 +5796,7 @@ class Compiler
57925796
Statement* paramAssignmentInsertionPoint);
57935797
static int fgEstimateCallStackSize(GenTreeCall* call);
57945798
GenTree* fgMorphCall(GenTreeCall* call);
5799+
GenTree* fgExpandVirtualVtableCallTarget(GenTreeCall* call);
57955800
void fgMorphCallInline(GenTreeCall* call, InlineResult* result);
57965801
void fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result);
57975802
#if DEBUG
@@ -9061,6 +9066,8 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
90619066
bool dspGCtbls; // Display the GC tables
90629067
#endif
90639068

9069+
bool compExpandCallsEarly; // True if we should expand virtual call targets early for this method
9070+
90649071
// Default numbers used to perform loop alignment. All the numbers are choosen
90659072
// based on experimenting with various benchmarks.
90669073

src/coreclr/jit/fgdiagnostic.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,12 @@ void Compiler::fgDebugCheckFlags(GenTree* tree)
24812481
chkFlags |= (call->gtCallAddr->gtFlags & GTF_SIDE_EFFECT);
24822482
}
24832483

2484+
if ((call->gtControlExpr != nullptr) && call->IsExpandedEarly() && call->IsVirtualVtable())
2485+
{
2486+
fgDebugCheckFlags(call->gtControlExpr);
2487+
chkFlags |= (call->gtControlExpr->gtFlags & GTF_SIDE_EFFECT);
2488+
}
2489+
24842490
if (call->IsUnmanaged() && (call->gtCallMoreFlags & GTF_CALL_M_UNMGD_THISCALL))
24852491
{
24862492
if (call->gtCallArgs->GetNode()->OperGet() == GT_NOP)
@@ -2968,15 +2974,28 @@ class UniquenessCheckWalker
29682974
}
29692975

29702976
//------------------------------------------------------------------------
2971-
// CheckTreeId: Check that this tree was not visit before and memorize it as visited.
2977+
// CheckTreeId: Check that this tree was not visited before and memorize it as visited.
29722978
//
29732979
// Arguments:
29742980
// gtTreeID - identificator of GenTree.
29752981
//
2982+
// Note:
2983+
// This method causes an assert failure when we find a duplicated node in our tree
2984+
//
29762985
void CheckTreeId(unsigned gtTreeID)
29772986
{
2978-
assert(!BitVecOps::IsMember(&nodesVecTraits, uniqueNodes, gtTreeID));
2979-
BitVecOps::AddElemD(&nodesVecTraits, uniqueNodes, gtTreeID);
2987+
if (BitVecOps::IsMember(&nodesVecTraits, uniqueNodes, gtTreeID))
2988+
{
2989+
if (comp->verbose)
2990+
{
2991+
printf("Duplicate gtTreeID was found: %d\n", gtTreeID);
2992+
}
2993+
assert(!"Duplicate gtTreeID was found");
2994+
}
2995+
else
2996+
{
2997+
BitVecOps::AddElemD(&nodesVecTraits, uniqueNodes, gtTreeID);
2998+
}
29802999
}
29813000

29823001
private:

src/coreclr/jit/gentree.cpp

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4442,6 +4442,9 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
44424442
costEx = 5;
44434443
costSz = 2;
44444444

4445+
GenTreeCall* call;
4446+
call = tree->AsCall();
4447+
44454448
/* Evaluate the 'this' argument, if present */
44464449

44474450
if (tree->AsCall()->gtCallThisArg != nullptr)
@@ -4459,10 +4462,10 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
44594462

44604463
/* Evaluate the arguments, right to left */
44614464

4462-
if (tree->AsCall()->gtCallArgs != nullptr)
4465+
if (call->gtCallArgs != nullptr)
44634466
{
44644467
const bool lateArgs = false;
4465-
lvl2 = gtSetCallArgsOrder(tree->AsCall()->Args(), lateArgs, &costEx, &costSz);
4468+
lvl2 = gtSetCallArgsOrder(call->Args(), lateArgs, &costEx, &costSz);
44664469
if (level < lvl2)
44674470
{
44684471
level = lvl2;
@@ -4473,23 +4476,23 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
44734476
* This is a "hidden" list and its only purpose is to
44744477
* extend the life of temps until we make the call */
44754478

4476-
if (tree->AsCall()->gtCallLateArgs != nullptr)
4479+
if (call->gtCallLateArgs != nullptr)
44774480
{
44784481
const bool lateArgs = true;
4479-
lvl2 = gtSetCallArgsOrder(tree->AsCall()->LateArgs(), lateArgs, &costEx, &costSz);
4482+
lvl2 = gtSetCallArgsOrder(call->LateArgs(), lateArgs, &costEx, &costSz);
44804483
if (level < lvl2)
44814484
{
44824485
level = lvl2;
44834486
}
44844487
}
44854488

4486-
if (tree->AsCall()->gtCallType == CT_INDIRECT)
4489+
if (call->gtCallType == CT_INDIRECT)
44874490
{
44884491
// pinvoke-calli cookie is a constant, or constant indirection
4489-
assert(tree->AsCall()->gtCallCookie == nullptr || tree->AsCall()->gtCallCookie->gtOper == GT_CNS_INT ||
4490-
tree->AsCall()->gtCallCookie->gtOper == GT_IND);
4492+
assert(call->gtCallCookie == nullptr || call->gtCallCookie->gtOper == GT_CNS_INT ||
4493+
call->gtCallCookie->gtOper == GT_IND);
44914494

4492-
GenTree* indirect = tree->AsCall()->gtCallAddr;
4495+
GenTree* indirect = call->gtCallAddr;
44934496

44944497
lvl2 = gtSetEvalOrder(indirect);
44954498
if (level < lvl2)
@@ -4501,13 +4504,27 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
45014504
}
45024505
else
45034506
{
4507+
if (call->IsVirtual())
4508+
{
4509+
GenTree* controlExpr = call->gtControlExpr;
4510+
if (controlExpr != nullptr)
4511+
{
4512+
lvl2 = gtSetEvalOrder(controlExpr);
4513+
if (level < lvl2)
4514+
{
4515+
level = lvl2;
4516+
}
4517+
costEx += controlExpr->GetCostEx();
4518+
costSz += controlExpr->GetCostSz();
4519+
}
4520+
}
45044521
#ifdef TARGET_ARM
4505-
if (tree->AsCall()->IsVirtualStub())
4522+
if (call->IsVirtualStub())
45064523
{
45074524
// We generate movw/movt/ldr
45084525
costEx += (1 + IND_COST_EX);
45094526
costSz += 8;
4510-
if (tree->AsCall()->gtCallMoreFlags & GTF_CALL_M_VIRTSTUB_REL_INDIRECT)
4527+
if (call->gtCallMoreFlags & GTF_CALL_M_VIRTSTUB_REL_INDIRECT)
45114528
{
45124529
// Must use R12 for the ldr target -- REG_JUMP_THUNK_PARAM
45134530
costSz += 2;
@@ -4520,6 +4537,7 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
45204537
}
45214538
costSz += 2;
45224539
#endif
4540+
45234541
#ifdef TARGET_XARCH
45244542
costSz += 3;
45254543
#endif
@@ -4528,7 +4546,7 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
45284546
level += 1;
45294547

45304548
/* Virtual calls are a bit more expensive */
4531-
if (tree->AsCall()->IsVirtual())
4549+
if (call->IsVirtual())
45324550
{
45334551
costEx += 2 * IND_COST_EX;
45344552
costSz += 2;
@@ -8112,7 +8130,7 @@ GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree, unsigned addFlag
81128130

81138131
copy->gtCallType = tree->gtCallType;
81148132
copy->gtReturnType = tree->gtReturnType;
8115-
copy->gtControlExpr = tree->gtControlExpr;
8133+
copy->gtControlExpr = gtCloneExpr(tree->gtControlExpr, addFlags, deepVarNum, deepVarVal);
81168134

81178135
/* Copy the union */
81188136
if (tree->gtCallType == CT_INDIRECT)
@@ -9820,7 +9838,7 @@ void Compiler::gtDispNodeName(GenTree* tree)
98209838
}
98219839
if (tree->AsCall()->IsVirtualVtable())
98229840
{
9823-
gtfType = " ind";
9841+
gtfType = " vt-ind";
98249842
}
98259843
else if (tree->AsCall()->IsVirtualStub())
98269844
{

src/coreclr/jit/gentree.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4224,6 +4224,7 @@ struct GenTreeCall final : public GenTree
42244224
#define GTF_CALL_M_SUPPRESS_GC_TRANSITION 0x00800000 // GT_CALL -- suppress the GC transition (i.e. during a pinvoke) but a separate GC safe point is required.
42254225
#define GTF_CALL_M_EXP_RUNTIME_LOOKUP 0x01000000 // GT_CALL -- this call needs to be tranformed into CFG for the dynamic dictionary expansion feature.
42264226
#define GTF_CALL_M_STRESS_TAILCALL 0x02000000 // GT_CALL -- the call is NOT "tail" prefixed but GTF_CALL_M_EXPLICIT_TAILCALL was added because of tail call stress mode
4227+
#define GTF_CALL_M_EXPANDED_EARLY 0x04000000 // GT_CALL -- the Virtual Call target address is expanded and placed in gtControlExpr in Morph rather than in Lower
42274228

42284229
// clang-format on
42294230

@@ -4530,6 +4531,21 @@ struct GenTreeCall final : public GenTree
45304531
return (gtCallMoreFlags & GTF_CALL_M_EXP_RUNTIME_LOOKUP) != 0;
45314532
}
45324533

4534+
void SetExpandedEarly()
4535+
{
4536+
gtCallMoreFlags |= GTF_CALL_M_EXPANDED_EARLY;
4537+
}
4538+
4539+
void ClearExpandedEarly()
4540+
{
4541+
gtCallMoreFlags &= ~GTF_CALL_M_EXPANDED_EARLY;
4542+
}
4543+
4544+
bool IsExpandedEarly() const
4545+
{
4546+
return (gtCallMoreFlags & GTF_CALL_M_EXPANDED_EARLY) != 0;
4547+
}
4548+
45334549
unsigned gtCallMoreFlags; // in addition to gtFlags
45344550

45354551
unsigned char gtCallType : 3; // value from the gtCallTypes enumeration

src/coreclr/jit/importer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8295,6 +8295,14 @@ var_types Compiler::impImportCall(OPCODE opcode,
82958295
assert(!(clsFlags & CORINFO_FLG_VALUECLASS));
82968296
call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset);
82978297
call->gtFlags |= GTF_CALL_VIRT_VTABLE;
8298+
8299+
// Should we expand virtual call targets early for this method?
8300+
//
8301+
if (opts.compExpandCallsEarly)
8302+
{
8303+
// Mark this method to expand the virtual call target early in fgMorpgCall
8304+
call->AsCall()->SetExpandedEarly();
8305+
}
82988306
break;
82998307
}
83008308

src/coreclr/jit/jitconfigvalues.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,10 @@ CONFIG_INTEGER(JitMinimalProfiling, W("JitMinimalProfiling"), 0)
458458
CONFIG_INTEGER(JitClassProfiling, W("JitClassProfiling"), 0)
459459
CONFIG_INTEGER(JitEdgeProfiling, W("JitEdgeProfiling"), 0)
460460

461+
// Control when Virtual Calls are expanded
462+
CONFIG_INTEGER(JitExpandCallsEarly, W("JitExpandCallsEarly"), 1) // Expand Call targets early (in the global morph
463+
// phase)
464+
461465
#if defined(DEBUG)
462466
// JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the
463467
// file, certain other JIT config variables will be active. If the currently compiled function is not in the file,

src/coreclr/jit/lower.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,7 +1560,8 @@ void Lowering::LowerCall(GenTree* node)
15601560
LowerArgsForCall(call);
15611561

15621562
// note that everything generated from this point on runs AFTER the outgoing args are placed
1563-
GenTree* controlExpr = nullptr;
1563+
GenTree* controlExpr = nullptr;
1564+
bool callWasExpandedEarly = false;
15641565

15651566
// for x86, this is where we record ESP for checking later to make sure stack is balanced
15661567

@@ -1581,8 +1582,17 @@ void Lowering::LowerCall(GenTree* node)
15811582
break;
15821583

15831584
case GTF_CALL_VIRT_VTABLE:
1584-
// stub dispatching is off or this is not a virtual call (could be a tailcall)
1585-
controlExpr = LowerVirtualVtableCall(call);
1585+
assert(call->IsVirtualVtable());
1586+
if (!call->IsExpandedEarly())
1587+
{
1588+
assert(call->gtControlExpr == nullptr);
1589+
controlExpr = LowerVirtualVtableCall(call);
1590+
}
1591+
else
1592+
{
1593+
callWasExpandedEarly = true;
1594+
controlExpr = call->gtControlExpr;
1595+
}
15861596
break;
15871597

15881598
case GTF_CALL_NONVIRT:
@@ -1619,7 +1629,9 @@ void Lowering::LowerCall(GenTree* node)
16191629
controlExpr = LowerTailCallViaJitHelper(call, controlExpr);
16201630
}
16211631

1622-
if (controlExpr != nullptr)
1632+
// Check if we need to thread a newly created controlExpr into the LIR
1633+
//
1634+
if ((controlExpr != nullptr) && !callWasExpandedEarly)
16231635
{
16241636
LIR::Range controlExprRange = LIR::SeqTree(comp, controlExpr);
16251637

0 commit comments

Comments
 (0)