Skip to content

Commit

Permalink
Jit: Conditional Escape Analysis and Cloning
Browse files Browse the repository at this point in the history
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 dotnet#108913
  • Loading branch information
AndyAyersMS committed Jan 15, 2025
1 parent cea112c commit 95154f1
Show file tree
Hide file tree
Showing 11 changed files with 2,848 additions and 47 deletions.
800 changes: 800 additions & 0 deletions docs/design/coreclr/jit/DeabstractionAndConditionalEscapeAnalysis.md

Large diffs are not rendered by default.

25 changes: 23 additions & 2 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,9 @@ class LclVarDsc

unsigned char lvRedefinedInEmbeddedStatement : 1; // Local has redefinitions inside embedded statements that
// disqualify it from local copy prop.

unsigned char lvIsEnumerator : 1; // Local is assigned exact class where : IEnumerable<T> via GDV

private:
unsigned char lvIsNeverNegative : 1; // The local is known to be never negative

Expand Down Expand Up @@ -4559,6 +4562,26 @@ class Compiler

unsigned impStkSize; // Size of the full stack

// Enumerator de-abstraction support
//
typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, unsigned> NodeToUnsignedMap;

// Map is only set on the root instance.
//
NodeToUnsignedMap* impEnumeratorGdvLocalMap = nullptr;
bool hasImpEnumeratorGdvLocalMap() { return impInlineRoot()->impEnumeratorGdvLocalMap != nullptr; }
NodeToUnsignedMap* getImpEnumeratorGdvLocalMap()
{
Compiler* compiler = impInlineRoot();
if (compiler->impEnumeratorGdvLocalMap == nullptr)
{
CompAllocator alloc(compiler->getAllocator(CMK_Generic));
compiler->impEnumeratorGdvLocalMap = new (alloc) NodeToUnsignedMap(alloc);
}

return compiler->impEnumeratorGdvLocalMap;
}

#define SMALL_STACK_SIZE 16 // number of elements in impSmallStack

struct SavedStack // used to save/restore stack contents.
Expand Down Expand Up @@ -11589,8 +11612,6 @@ class Compiler
return compRoot->m_fieldSeqStore;
}

typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, unsigned> NodeToUnsignedMap;

NodeToUnsignedMap* m_memorySsaMap[MemoryKindCount];

// In some cases, we want to assign intermediate SSA #'s to memory states, and know what nodes create those memory
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/fgehopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2761,6 +2761,8 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
// all the EH indicies at or above insertBeforeIndex will shift,
// and the EH table may reallocate.
//
// This addition may also fail, if the table would become too large...
//
EHblkDsc* const clonedOutermostEbd =
fgTryAddEHTableEntries(insertBeforeIndex, regionCount, /* deferAdding */ deferCloning);

Expand Down
52 changes: 52 additions & 0 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3445,6 +3445,21 @@ void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken)
{
GenTreeCall* const call = exprToBox->AsRetExpr()->gtInlineCandidate->AsCall();

// If the call was flagged for possible enumerator cloning, flag the allocation as well.
//
if (compIsForInlining() && hasImpEnumeratorGdvLocalMap())
{
NodeToUnsignedMap* const map = getImpEnumeratorGdvLocalMap();
unsigned enumeratorLcl = BAD_VAR_NUM;
GenTreeCall* const call = impInlineInfo->iciCall;
if (map->Lookup(call, &enumeratorLcl))
{
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", dspTreeID(op1), enumeratorLcl);
map->Remove(call);
map->Set(op1, enumeratorLcl);
}
}

if (call->ShouldHaveRetBufArg())
{
JITDUMP("Must insert newobj stmts for box before call [%06u]\n", dspTreeID(call));
Expand Down Expand Up @@ -6740,6 +6755,26 @@ void Compiler::impImportBlockCode(BasicBlock* block)
{
lvaUpdateClass(lclNum, op1, tiRetVal.GetClassHandleForObjRef());
}

// If we see a local being assigned the result of a GDV-inlineable
// IEnumerable<T>.GetEnumerator, keep track of both the local and the call.
//
if (op1->OperIs(GT_RET_EXPR))
{
JITDUMP(".... checking for GDV of IEnumerable<T>...\n");

GenTreeCall* const call = op1->AsRetExpr()->gtInlineCandidate;
NamedIntrinsic const ni = lookupNamedIntrinsic(call->gtCallMethHnd);

if (ni == NI_System_Collections_Generic_IEnumerable_GetEnumerator)
{
JITDUMP("V%02u value is GDV of IEnumerable<T>.GetEnumerator\n", lclNum);
lvaTable[lclNum].lvIsEnumerator = true;
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", dspTreeID(call), lclNum);
getImpEnumeratorGdvLocalMap()->Set(call, lclNum);
Metrics.EnumeratorGDV++;
}
}
}

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

// If the method being imported is an inlinee, and the original call was flagged
// for possible enumerator cloning, flag the allocation as well.
//
if (compIsForInlining() && hasImpEnumeratorGdvLocalMap())
{
NodeToUnsignedMap* const map = getImpEnumeratorGdvLocalMap();
unsigned enumeratorLcl = BAD_VAR_NUM;
GenTreeCall* const call = impInlineInfo->iciCall;
if (map->Lookup(call, &enumeratorLcl))
{
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", dspTreeID(op1),
enumeratorLcl);
map->Remove(call);
map->Set(op1, enumeratorLcl);
}
}

// Remember that this basic block contains 'new' of an object
block->SetFlags(BBF_HAS_NEWOBJ);
optMethodFlags |= OMF_HAS_NEWOBJ;
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10562,6 +10562,13 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
result = NI_System_Collections_Generic_EqualityComparer_get_Default;
}
}
else if (strcmp(className, "IEnumerable`1") == 0)
{
if (strcmp(methodName, "GetEnumerator") == 0)
{
result = NI_System_Collections_Generic_IEnumerable_GetEnumerator;
}
}
}
else if (strcmp(namespaceName, "Numerics") == 0)
{
Expand Down
16 changes: 16 additions & 0 deletions src/coreclr/jit/indirectcalltransformer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,22 @@ class IndirectCallTransformer
GenTreeCall* call = compiler->gtCloneCandidateCall(origCall);
call->gtArgs.GetThisArg()->SetEarlyNode(compiler->gtNewLclvNode(thisTemp, TYP_REF));

// If the original call was flagged as one that might inspire enumerator de-abstraction
// cloning, move the flag to the devirtualized call.
//
if (compiler->hasImpEnumeratorGdvLocalMap())
{
Compiler::NodeToUnsignedMap* const map = compiler->getImpEnumeratorGdvLocalMap();
unsigned enumeratorLcl = BAD_VAR_NUM;
if (map->Lookup(origCall, &enumeratorLcl))
{
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", compiler->dspTreeID(call),
enumeratorLcl);
map->Remove(origCall);
map->Set(call, enumeratorLcl);
}
}

INDEBUG(call->SetIsGuarded());

JITDUMP("Direct call [%06u] in block " FMT_BB "\n", compiler->dspTreeID(call), block->bbNum);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ CONFIG_STRING(JitObjectStackAllocationRange, "JitObjectStackAllocationRange")
RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, "JitObjectStackAllocation", 1)
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationRefClass, "JitObjectStackAllocationRefClass", 1)
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationBoxedValueClass, "JitObjectStackAllocationBoxedValueClass", 1)
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationConditionalEscape, "JitObjectStackAllocationConditoinalEscape", 1)

RELEASE_CONFIG_INTEGER(JitEECallTimingInfo, "JitEECallTimingInfo", 0)

Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/jitmetadatalist.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ JITMETADATAMETRIC(ClassGDV, int, 0)
JITMETADATAMETRIC(MethodGDV, int, 0)
JITMETADATAMETRIC(MultiGuessGDV, int, 0)
JITMETADATAMETRIC(ChainedGDV, int, 0)
JITMETADATAMETRIC(EnumeratorGDV, int, 0)
JITMETADATAMETRIC(InlinerBranchFold, int, 0)
JITMETADATAMETRIC(InlineAttempt, int, 0)
JITMETADATAMETRIC(InlineCount, int, 0)
Expand Down Expand Up @@ -90,6 +91,8 @@ JITMETADATAMETRIC(LocalAssertionCount, int, 0)
JITMETADATAMETRIC(LocalAssertionOverflow, int, 0)
JITMETADATAMETRIC(MorphTrackedLocals, int, 0)
JITMETADATAMETRIC(MorphLocals, int, 0)
JITMETADATAMETRIC(EnumeratorGDVProvisionalNoEscape, int, 0)
JITMETADATAMETRIC(EnumeratorGDVCanCloneToEnsureNoEscape, int, 0)

#undef JITMETADATA
#undef JITMETADATAINFO
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ enum NamedIntrinsic : unsigned short
//
NI_System_SZArrayHelper_GetEnumerator,
NI_System_Array_T_GetEnumerator,
NI_System_Collections_Generic_IEnumerable_GetEnumerator,
};

#endif // _NAMEDINTRINSICLIST_H_
Loading

0 comments on commit 95154f1

Please sign in to comment.