Skip to content

Commit 386cb8d

Browse files
authored
JIT: create inferred GDVs for enumerator var uses that lack PGO (#118461)
Long-running enumerator loops at Tier0+instr will see their executions transition over to a non-instrumented OSR version of the code. This can cause loss of PGO data in the portions of the method that execute after the leaving the loop that inspires OSR. For enumerator vars we can safely deduce the likely classes from probes made earlier in the method. So when we see a class profile for an enumerator var, remember it and use it for subsequent calls that lack their own profile data. Addresses part of #118420.
1 parent 8b08265 commit 386cb8d

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

src/coreclr/jit/compiler.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4488,9 +4488,16 @@ class Compiler
44884488

44894489
// Enumerator de-abstraction support
44904490
//
4491+
struct InferredGdvEntry
4492+
{
4493+
CORINFO_CLASS_HANDLE m_classHandle;
4494+
unsigned m_likelihood;
4495+
};
4496+
44914497
typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, unsigned> NodeToUnsignedMap;
4498+
typedef JitHashTable<unsigned, JitSmallPrimitiveKeyFuncs<unsigned>, InferredGdvEntry> VarToLikelyClassMap;
44924499

4493-
// Map is only set on the root instance.
4500+
// Maps are only set on the root instance.
44944501
//
44954502
NodeToUnsignedMap* impEnumeratorGdvLocalMap = nullptr;
44964503
bool hasImpEnumeratorGdvLocalMap() { return impInlineRoot()->impEnumeratorGdvLocalMap != nullptr; }
@@ -4506,6 +4513,20 @@ class Compiler
45064513
return compiler->impEnumeratorGdvLocalMap;
45074514
}
45084515

4516+
VarToLikelyClassMap* impEnumeratorLikelyTypeMap = nullptr;
4517+
bool hasEnumeratorLikelyTypeMap() { return impInlineRoot()->impEnumeratorLikelyTypeMap != nullptr; }
4518+
VarToLikelyClassMap* getImpEnumeratorLikelyTypeMap()
4519+
{
4520+
Compiler* compiler = impInlineRoot();
4521+
if (compiler->impEnumeratorLikelyTypeMap == nullptr)
4522+
{
4523+
CompAllocator alloc(compiler->getAllocator(CMK_Generic));
4524+
compiler->impEnumeratorLikelyTypeMap = new (alloc) VarToLikelyClassMap(alloc);
4525+
}
4526+
4527+
return compiler->impEnumeratorLikelyTypeMap;
4528+
}
4529+
45094530
bool hasUpdatedTypeLocals = false;
45104531

45114532
#define SMALL_STACK_SIZE 16 // number of elements in impSmallStack

src/coreclr/jit/importercalls.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6986,6 +6986,7 @@ void Compiler::pickGDV(GenTreeCall* call,
69866986
const int maxLikelyMethods = MAX_GDV_TYPE_CHECKS;
69876987
LikelyClassMethodRecord likelyMethods[maxLikelyMethods];
69886988
unsigned numberOfMethods = 0;
6989+
bool isInferredGDV = false;
69896990

69906991
// TODO-GDV: R2R support requires additional work to reacquire the
69916992
// entrypoint, similar to what happens at the end of impDevirtualizeCall.
@@ -7000,6 +7001,39 @@ void Compiler::pickGDV(GenTreeCall* call,
70007001
pgoInfo.PgoData, ilOffset);
70017002
}
70027003

7004+
if ((numberOfClasses < 1) && (numberOfMethods < 1) && hasEnumeratorLikelyTypeMap())
7005+
{
7006+
// See if we can infer a GDV here for enumerator var uses
7007+
//
7008+
CallArg* const thisArg = call->gtArgs.FindWellKnownArg(WellKnownArg::ThisPointer);
7009+
7010+
if (thisArg != nullptr)
7011+
{
7012+
GenTree* const thisNode = thisArg->GetEarlyNode();
7013+
if (thisNode->OperIs(GT_LCL_VAR))
7014+
{
7015+
GenTreeLclVarCommon* thisLclNode = thisNode->AsLclVarCommon();
7016+
LclVarDsc* const thisVarDsc = lvaGetDesc(thisLclNode);
7017+
unsigned const thisLclNum = thisLclNode->GetLclNum();
7018+
7019+
if (thisVarDsc->lvIsEnumerator)
7020+
{
7021+
VarToLikelyClassMap* const map = getImpEnumeratorLikelyTypeMap();
7022+
InferredGdvEntry e;
7023+
if (map->Lookup(thisLclNum, &e))
7024+
{
7025+
JITDUMP("Recalling that V%02u has %u%% likely class %s\n", thisLclNum, e.m_likelihood,
7026+
eeGetClassName(e.m_classHandle));
7027+
numberOfClasses = 1;
7028+
likelyClasses[0].handle = (INT_PTR)e.m_classHandle;
7029+
likelyClasses[0].likelihood = e.m_likelihood;
7030+
isInferredGDV = true;
7031+
}
7032+
}
7033+
}
7034+
}
7035+
}
7036+
70037037
if ((numberOfClasses < 1) && (numberOfMethods < 1))
70047038
{
70057039
if (verboseLogging)
@@ -7186,6 +7220,58 @@ void Compiler::pickGDV(GenTreeCall* call,
71867220
JITDUMP("Accepting type %s with likelihood %u as a candidate\n",
71877221
eeGetClassName(classGuesses[guessIdx]), likelihoods[guessIdx])
71887222
}
7223+
7224+
// If the 'this' arg to the call is an enumerator var, record any
7225+
// dominant likely class so we can possibly infer a GDV at places where we
7226+
// never observed the var's value. (eg an unreached Dispose call if
7227+
// control is hijacked out of Tier0+i by OSR).
7228+
//
7229+
// Note enumerator vars are special as they are generally not redefined
7230+
// and we want to ensure all methods called on them get inlined to enable
7231+
// escape analysis to kick in, if possible.
7232+
//
7233+
const unsigned dominantLikelihood = 50;
7234+
7235+
if (!isInferredGDV && (likelihoods[guessIdx] >= dominantLikelihood))
7236+
{
7237+
CallArg* const thisArg = call->gtArgs.FindWellKnownArg(WellKnownArg::ThisPointer);
7238+
7239+
if (thisArg != nullptr)
7240+
{
7241+
GenTree* const thisNode = thisArg->GetEarlyNode();
7242+
if (thisNode->OperIs(GT_LCL_VAR))
7243+
{
7244+
GenTreeLclVarCommon* thisLclNode = thisNode->AsLclVarCommon();
7245+
LclVarDsc* const thisVarDsc = lvaGetDesc(thisLclNode);
7246+
unsigned const thisLclNum = thisLclNode->GetLclNum();
7247+
7248+
if (thisVarDsc->lvIsEnumerator)
7249+
{
7250+
VarToLikelyClassMap* const map = getImpEnumeratorLikelyTypeMap();
7251+
7252+
// If we have multiple type observations, we just use the first.
7253+
//
7254+
// Note importation order is somewhat reverse-post-orderish;
7255+
// a block is only imported if one of its imported preds is imported.
7256+
//
7257+
// Enumerator vars tend to have a dominating MoveNext call that will
7258+
// be the one subsequent uses will see, if they lack their own
7259+
// type observations.
7260+
//
7261+
if (!map->Lookup(thisLclNum))
7262+
{
7263+
InferredGdvEntry e;
7264+
e.m_classHandle = classGuesses[guessIdx];
7265+
e.m_likelihood = likelihoods[guessIdx];
7266+
7267+
JITDUMP("Remembering that V%02u has %u%% likely class %s\n", thisLclNum,
7268+
e.m_likelihood, eeGetClassName(e.m_classHandle));
7269+
map->Set(thisLclNum, e);
7270+
}
7271+
}
7272+
}
7273+
}
7274+
}
71897275
}
71907276
else
71917277
{

0 commit comments

Comments
 (0)