Skip to content

Commit f6c74b8

Browse files
Jit: Conditional Escape Analysis and Cloning (#111473)
Enhance escape analysis to determine if an object escapes only under failed GDV tests. If so, clone to create a path of code so that the object doesn't escape, and then stack allocate the object. More details in the included document. Contributes to #108913 Co-authored-by: Aman Khalid <[email protected]>
1 parent e567edb commit f6c74b8

16 files changed

+3048
-139
lines changed

docs/design/coreclr/jit/DeabstractionAndConditionalEscapeAnalysis.md

+804
Large diffs are not rendered by default.

src/coreclr/jit/compiler.h

+25-2
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,9 @@ class LclVarDsc
667667

668668
unsigned char lvRedefinedInEmbeddedStatement : 1; // Local has redefinitions inside embedded statements that
669669
// disqualify it from local copy prop.
670+
671+
unsigned char lvIsEnumerator : 1; // Local is assigned exact class where : IEnumerable<T> via GDV
672+
670673
private:
671674
unsigned char lvIsNeverNegative : 1; // The local is known to be never negative
672675

@@ -3711,6 +3714,8 @@ class Compiler
37113714
return gtNewStmt(exprClone, stmt->GetDebugInfo());
37123715
}
37133716

3717+
Statement* gtLatestStatement(Statement* stmt1, Statement* stmt2);
3718+
37143719
// Internal helper for cloning a call
37153720
GenTreeCall* gtCloneExprCallHelper(GenTreeCall* call);
37163721

@@ -4606,6 +4611,26 @@ class Compiler
46064611

46074612
unsigned impStkSize; // Size of the full stack
46084613

4614+
// Enumerator de-abstraction support
4615+
//
4616+
typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, unsigned> NodeToUnsignedMap;
4617+
4618+
// Map is only set on the root instance.
4619+
//
4620+
NodeToUnsignedMap* impEnumeratorGdvLocalMap = nullptr;
4621+
bool hasImpEnumeratorGdvLocalMap() { return impInlineRoot()->impEnumeratorGdvLocalMap != nullptr; }
4622+
NodeToUnsignedMap* getImpEnumeratorGdvLocalMap()
4623+
{
4624+
Compiler* compiler = impInlineRoot();
4625+
if (compiler->impEnumeratorGdvLocalMap == nullptr)
4626+
{
4627+
CompAllocator alloc(compiler->getAllocator(CMK_Generic));
4628+
compiler->impEnumeratorGdvLocalMap = new (alloc) NodeToUnsignedMap(alloc);
4629+
}
4630+
4631+
return compiler->impEnumeratorGdvLocalMap;
4632+
}
4633+
46094634
#define SMALL_STACK_SIZE 16 // number of elements in impSmallStack
46104635

46114636
struct SavedStack // used to save/restore stack contents.
@@ -11677,8 +11702,6 @@ class Compiler
1167711702
return compRoot->m_fieldSeqStore;
1167811703
}
1167911704

11680-
typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, unsigned> NodeToUnsignedMap;
11681-
1168211705
NodeToUnsignedMap* m_memorySsaMap[MemoryKindCount];
1168311706

1168411707
// In some cases, we want to assign intermediate SSA #'s to memory states, and know what nodes create those memory

src/coreclr/jit/fgdiagnostic.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -2860,7 +2860,14 @@ bool BBPredsChecker::CheckEHFinallyRet(BasicBlock* blockPred, BasicBlock* block)
28602860
}
28612861
}
28622862

2863-
assert(found && "BBJ_EHFINALLYRET predecessor of block that doesn't follow a BBJ_CALLFINALLY!");
2863+
if (!found)
2864+
{
2865+
JITDUMP(FMT_BB " is successor of finallyret " FMT_BB " but prev block is not a callfinally to " FMT_BB
2866+
" (search range was [" FMT_BB "..." FMT_BB "]\n",
2867+
block->bbNum, blockPred->bbNum, finBeg->bbNum, firstBlock->bbNum, lastBlock->bbNum);
2868+
assert(!"BBJ_EHFINALLYRET predecessor of block that doesn't follow a BBJ_CALLFINALLY!");
2869+
}
2870+
28642871
return found;
28652872
}
28662873

src/coreclr/jit/fgehopt.cpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -2552,6 +2552,7 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
25522552
auto addBlockToClone = [=, &blocks, &visited, &numberOfBlocksToClone](BasicBlock* block, const char* msg) {
25532553
if (!BitVecOps::TryAddElemD(traits, visited, block->bbID))
25542554
{
2555+
JITDUMP("[already seen] %s block " FMT_BB "\n", msg, block->bbNum);
25552556
return false;
25562557
}
25572558

@@ -2781,6 +2782,8 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
27812782
// all the EH indicies at or above insertBeforeIndex will shift,
27822783
// and the EH table may reallocate.
27832784
//
2785+
// This addition may also fail, if the table would become too large...
2786+
//
27842787
EHblkDsc* const clonedOutermostEbd =
27852788
fgTryAddEHTableEntries(insertBeforeIndex, regionCount, /* deferAdding */ deferCloning);
27862789

@@ -3000,8 +3003,6 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
30003003
cloneTryIndex += indexShift;
30013004
}
30023005

3003-
EHblkDsc* const originalEbd = ehGetDsc(originalTryIndex);
3004-
EHblkDsc* const clonedEbd = ehGetDsc(cloneTryIndex);
30053006
newBlock->setTryIndex(cloneTryIndex);
30063007
updateBlockReferences(cloneTryIndex);
30073008
}
@@ -3016,8 +3017,6 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
30163017
cloneHndIndex += indexShift;
30173018
}
30183019

3019-
EHblkDsc* const originalEbd = ehGetDsc(originalHndIndex);
3020-
EHblkDsc* const clonedEbd = ehGetDsc(cloneHndIndex);
30213020
newBlock->setHndIndex(cloneHndIndex);
30223021
updateBlockReferences(cloneHndIndex);
30233022

src/coreclr/jit/gentree.cpp

+39
Original file line numberDiff line numberDiff line change
@@ -32679,3 +32679,42 @@ void GenTree::SetMorphed(Compiler* compiler, bool doChildren /* = false */)
3267932679
}
3268032680
}
3268132681
#endif
32682+
32683+
//------------------------------------------------------------------------
32684+
// gtLatestStmt: determine which of two statements happens later
32685+
//
32686+
// Arguments:
32687+
// stmt1 - first statement to consider
32688+
// stmt2 - second statement to consider
32689+
//
32690+
// Returns:
32691+
// either stmt1 or stmt2, whichever happens later in the block
32692+
//
32693+
Statement* Compiler::gtLatestStatement(Statement* stmt1, Statement* stmt2)
32694+
{
32695+
if (stmt1 == stmt2)
32696+
{
32697+
return stmt1;
32698+
}
32699+
32700+
Statement* cursor1 = stmt1->GetNextStmt();
32701+
Statement* cursor2 = stmt2->GetNextStmt();
32702+
32703+
while (true)
32704+
{
32705+
if ((cursor1 == stmt2) || (cursor2 == nullptr))
32706+
{
32707+
return stmt2;
32708+
}
32709+
32710+
if ((cursor2 == stmt1) || (cursor1 == nullptr))
32711+
{
32712+
return stmt1;
32713+
}
32714+
32715+
cursor1 = cursor1->GetNextStmt();
32716+
cursor2 = cursor2->GetNextStmt();
32717+
}
32718+
32719+
assert(!"could not determine latest stmt");
32720+
}

src/coreclr/jit/importer.cpp

+52
Original file line numberDiff line numberDiff line change
@@ -3445,6 +3445,21 @@ void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken)
34453445
{
34463446
GenTreeCall* const call = exprToBox->AsRetExpr()->gtInlineCandidate->AsCall();
34473447

3448+
// If the call was flagged for possible enumerator cloning, flag the allocation as well.
3449+
//
3450+
if (compIsForInlining() && hasImpEnumeratorGdvLocalMap())
3451+
{
3452+
NodeToUnsignedMap* const map = getImpEnumeratorGdvLocalMap();
3453+
unsigned enumeratorLcl = BAD_VAR_NUM;
3454+
GenTreeCall* const call = impInlineInfo->iciCall;
3455+
if (map->Lookup(call, &enumeratorLcl))
3456+
{
3457+
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", dspTreeID(op1), enumeratorLcl);
3458+
map->Remove(call);
3459+
map->Set(op1, enumeratorLcl);
3460+
}
3461+
}
3462+
34483463
if (call->ShouldHaveRetBufArg())
34493464
{
34503465
JITDUMP("Must insert newobj stmts for box before call [%06u]\n", dspTreeID(call));
@@ -6740,6 +6755,26 @@ void Compiler::impImportBlockCode(BasicBlock* block)
67406755
{
67416756
lvaUpdateClass(lclNum, op1, tiRetVal.GetClassHandleForObjRef());
67426757
}
6758+
6759+
// If we see a local being assigned the result of a GDV-inlineable
6760+
// IEnumerable<T>.GetEnumerator, keep track of both the local and the call.
6761+
//
6762+
if (op1->OperIs(GT_RET_EXPR))
6763+
{
6764+
JITDUMP(".... checking for GDV of IEnumerable<T>...\n");
6765+
6766+
GenTreeCall* const call = op1->AsRetExpr()->gtInlineCandidate;
6767+
NamedIntrinsic const ni = lookupNamedIntrinsic(call->gtCallMethHnd);
6768+
6769+
if (ni == NI_System_Collections_Generic_IEnumerable_GetEnumerator)
6770+
{
6771+
JITDUMP("V%02u value is GDV of IEnumerable<T>.GetEnumerator\n", lclNum);
6772+
lvaTable[lclNum].lvIsEnumerator = true;
6773+
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", dspTreeID(call), lclNum);
6774+
getImpEnumeratorGdvLocalMap()->Set(call, lclNum);
6775+
Metrics.EnumeratorGDV++;
6776+
}
6777+
}
67436778
}
67446779

67456780
/* Filter out simple stores to itself */
@@ -8838,6 +8873,23 @@ void Compiler::impImportBlockCode(BasicBlock* block)
88388873
op1->gtFlags |= GTF_ALLOCOBJ_EMPTY_STATIC;
88398874
}
88408875

8876+
// If the method being imported is an inlinee, and the original call was flagged
8877+
// for possible enumerator cloning, flag the allocation as well.
8878+
//
8879+
if (compIsForInlining() && hasImpEnumeratorGdvLocalMap())
8880+
{
8881+
NodeToUnsignedMap* const map = getImpEnumeratorGdvLocalMap();
8882+
unsigned enumeratorLcl = BAD_VAR_NUM;
8883+
GenTreeCall* const call = impInlineInfo->iciCall;
8884+
if (map->Lookup(call, &enumeratorLcl))
8885+
{
8886+
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", dspTreeID(op1),
8887+
enumeratorLcl);
8888+
map->Remove(call);
8889+
map->Set(op1, enumeratorLcl);
8890+
}
8891+
}
8892+
88418893
// Remember that this basic block contains 'new' of an object
88428894
block->SetFlags(BBF_HAS_NEWOBJ);
88438895
optMethodFlags |= OMF_HAS_NEWOBJ;

src/coreclr/jit/importercalls.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -10570,6 +10570,13 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
1057010570
result = NI_System_Collections_Generic_EqualityComparer_get_Default;
1057110571
}
1057210572
}
10573+
else if (strcmp(className, "IEnumerable`1") == 0)
10574+
{
10575+
if (strcmp(methodName, "GetEnumerator") == 0)
10576+
{
10577+
result = NI_System_Collections_Generic_IEnumerable_GetEnumerator;
10578+
}
10579+
}
1057310580
}
1057410581
else if (strcmp(namespaceName, "Numerics") == 0)
1057510582
{

src/coreclr/jit/indirectcalltransformer.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,22 @@ class IndirectCallTransformer
907907
GenTreeCall* call = compiler->gtCloneCandidateCall(origCall);
908908
call->gtArgs.GetThisArg()->SetEarlyNode(compiler->gtNewLclvNode(thisTemp, TYP_REF));
909909

910+
// If the original call was flagged as one that might inspire enumerator de-abstraction
911+
// cloning, move the flag to the devirtualized call.
912+
//
913+
if (compiler->hasImpEnumeratorGdvLocalMap())
914+
{
915+
Compiler::NodeToUnsignedMap* const map = compiler->getImpEnumeratorGdvLocalMap();
916+
unsigned enumeratorLcl = BAD_VAR_NUM;
917+
if (map->Lookup(origCall, &enumeratorLcl))
918+
{
919+
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", compiler->dspTreeID(call),
920+
enumeratorLcl);
921+
map->Remove(origCall);
922+
map->Set(call, enumeratorLcl);
923+
}
924+
}
925+
910926
INDEBUG(call->SetIsGuarded());
911927

912928
JITDUMP("Direct call [%06u] in block " FMT_BB "\n", compiler->dspTreeID(call), block->bbNum);

src/coreclr/jit/inductionvariableopts.cpp

+1-40
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,6 @@ class StrengthReductionContext
13921392
BasicBlock* FindPostUseUpdateInsertionPoint(ArrayStack<CursorInfo>* cursors,
13931393
BasicBlock* backEdgeDominator,
13941394
Statement** afterStmt);
1395-
Statement* LatestStatement(Statement* stmt1, Statement* stmt2);
13961395
bool InsertionPointPostDominatesUses(BasicBlock* insertionPoint, ArrayStack<CursorInfo>* cursors);
13971396

13981397
bool StressProfitability()
@@ -2615,7 +2614,7 @@ BasicBlock* StrengthReductionContext::FindPostUseUpdateInsertionPoint(ArrayStack
26152614
}
26162615
else
26172616
{
2618-
latestStmt = LatestStatement(latestStmt, cursor.Stmt);
2617+
latestStmt = m_comp->gtLatestStatement(latestStmt, cursor.Stmt);
26192618
}
26202619
}
26212620

@@ -2632,44 +2631,6 @@ BasicBlock* StrengthReductionContext::FindPostUseUpdateInsertionPoint(ArrayStack
26322631
return nullptr;
26332632
}
26342633

2635-
//------------------------------------------------------------------------
2636-
// LatestStatement: Given two statements in the same basic block, return the
2637-
// latter of the two.
2638-
//
2639-
// Parameters:
2640-
// stmt1 - First statement
2641-
// stmt2 - Second statement
2642-
//
2643-
// Returns:
2644-
// Latter of the statements.
2645-
//
2646-
Statement* StrengthReductionContext::LatestStatement(Statement* stmt1, Statement* stmt2)
2647-
{
2648-
if (stmt1 == stmt2)
2649-
{
2650-
return stmt1;
2651-
}
2652-
2653-
Statement* cursor1 = stmt1->GetNextStmt();
2654-
Statement* cursor2 = stmt2->GetNextStmt();
2655-
2656-
while (true)
2657-
{
2658-
if ((cursor1 == stmt2) || (cursor2 == nullptr))
2659-
{
2660-
return stmt2;
2661-
}
2662-
2663-
if ((cursor2 == stmt1) || (cursor1 == nullptr))
2664-
{
2665-
return stmt1;
2666-
}
2667-
2668-
cursor1 = cursor1->GetNextStmt();
2669-
cursor2 = cursor2->GetNextStmt();
2670-
}
2671-
}
2672-
26732634
//------------------------------------------------------------------------
26742635
// InsertionPointPostDominatesUses: Check if a basic block post-dominates all
26752636
// locations specified by the cursors.

src/coreclr/jit/jitconfigvalues.h

+2
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,8 @@ CONFIG_STRING(JitObjectStackAllocationRange, "JitObjectStackAllocationRange")
670670
RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, "JitObjectStackAllocation", 1)
671671
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationRefClass, "JitObjectStackAllocationRefClass", 1)
672672
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationBoxedValueClass, "JitObjectStackAllocationBoxedValueClass", 1)
673+
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationConditionalEscape, "JitObjectStackAllocationConditionalEscape", 1)
674+
CONFIG_STRING(JitObjectStackAllocationConditionalEscapeRange, "JitObjectStackAllocationConditionalEscapeRange")
673675
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationArray, "JitObjectStackAllocationArray", 1)
674676
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationSize, "JitObjectStackAllocationSize", 528)
675677

src/coreclr/jit/jitmetadatalist.h

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ JITMETADATAMETRIC(ClassGDV, int, 0)
6363
JITMETADATAMETRIC(MethodGDV, int, 0)
6464
JITMETADATAMETRIC(MultiGuessGDV, int, 0)
6565
JITMETADATAMETRIC(ChainedGDV, int, 0)
66+
JITMETADATAMETRIC(EnumeratorGDV, int, 0)
6667
JITMETADATAMETRIC(InlinerBranchFold, int, 0)
6768
JITMETADATAMETRIC(InlineAttempt, int, 0)
6869
JITMETADATAMETRIC(InlineCount, int, 0)
@@ -92,6 +93,8 @@ JITMETADATAMETRIC(LocalAssertionCount, int, 0)
9293
JITMETADATAMETRIC(LocalAssertionOverflow, int, 0)
9394
JITMETADATAMETRIC(MorphTrackedLocals, int, 0)
9495
JITMETADATAMETRIC(MorphLocals, int, 0)
96+
JITMETADATAMETRIC(EnumeratorGDVProvisionalNoEscape, int, 0)
97+
JITMETADATAMETRIC(EnumeratorGDVCanCloneToEnsureNoEscape, int, 0)
9598

9699
#undef JITMETADATA
97100
#undef JITMETADATAINFO

src/coreclr/jit/namedintrinsiclist.h

+1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ enum NamedIntrinsic : unsigned short
248248
//
249249
NI_System_SZArrayHelper_GetEnumerator,
250250
NI_System_Array_T_GetEnumerator,
251+
NI_System_Collections_Generic_IEnumerable_GetEnumerator,
251252
};
252253

253254
#endif // _NAMEDINTRINSICLIST_H_

0 commit comments

Comments
 (0)