diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 096a9e4fb1120d..e78190f99ed43b 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4488,9 +4488,16 @@ class Compiler // Enumerator de-abstraction support // + struct InferredGdvEntry + { + CORINFO_CLASS_HANDLE m_classHandle; + unsigned m_likelihood; + }; + typedef JitHashTable, unsigned> NodeToUnsignedMap; + typedef JitHashTable, InferredGdvEntry> VarToLikelyClassMap; - // Map is only set on the root instance. + // Maps are only set on the root instance. // NodeToUnsignedMap* impEnumeratorGdvLocalMap = nullptr; bool hasImpEnumeratorGdvLocalMap() { return impInlineRoot()->impEnumeratorGdvLocalMap != nullptr; } @@ -4506,6 +4513,20 @@ class Compiler return compiler->impEnumeratorGdvLocalMap; } + VarToLikelyClassMap* impEnumeratorLikelyTypeMap = nullptr; + bool hasEnumeratorLikelyTypeMap() { return impInlineRoot()->impEnumeratorLikelyTypeMap != nullptr; } + VarToLikelyClassMap* getImpEnumeratorLikelyTypeMap() + { + Compiler* compiler = impInlineRoot(); + if (compiler->impEnumeratorLikelyTypeMap == nullptr) + { + CompAllocator alloc(compiler->getAllocator(CMK_Generic)); + compiler->impEnumeratorLikelyTypeMap = new (alloc) VarToLikelyClassMap(alloc); + } + + return compiler->impEnumeratorLikelyTypeMap; + } + bool hasUpdatedTypeLocals = false; #define SMALL_STACK_SIZE 16 // number of elements in impSmallStack diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index bc7f0433287a10..dbc0a7f46eb61e 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -6986,6 +6986,7 @@ void Compiler::pickGDV(GenTreeCall* call, const int maxLikelyMethods = MAX_GDV_TYPE_CHECKS; LikelyClassMethodRecord likelyMethods[maxLikelyMethods]; unsigned numberOfMethods = 0; + bool isInferredGDV = false; // TODO-GDV: R2R support requires additional work to reacquire the // entrypoint, similar to what happens at the end of impDevirtualizeCall. @@ -7000,6 +7001,39 @@ void Compiler::pickGDV(GenTreeCall* call, pgoInfo.PgoData, ilOffset); } + if ((numberOfClasses < 1) && (numberOfMethods < 1) && hasEnumeratorLikelyTypeMap()) + { + // See if we can infer a GDV here for enumerator var uses + // + CallArg* const thisArg = call->gtArgs.FindWellKnownArg(WellKnownArg::ThisPointer); + + if (thisArg != nullptr) + { + GenTree* const thisNode = thisArg->GetEarlyNode(); + if (thisNode->OperIs(GT_LCL_VAR)) + { + GenTreeLclVarCommon* thisLclNode = thisNode->AsLclVarCommon(); + LclVarDsc* const thisVarDsc = lvaGetDesc(thisLclNode); + unsigned const thisLclNum = thisLclNode->GetLclNum(); + + if (thisVarDsc->lvIsEnumerator) + { + VarToLikelyClassMap* const map = getImpEnumeratorLikelyTypeMap(); + InferredGdvEntry e; + if (map->Lookup(thisLclNum, &e)) + { + JITDUMP("Recalling that V%02u has %u%% likely class %s\n", thisLclNum, e.m_likelihood, + eeGetClassName(e.m_classHandle)); + numberOfClasses = 1; + likelyClasses[0].handle = (INT_PTR)e.m_classHandle; + likelyClasses[0].likelihood = e.m_likelihood; + isInferredGDV = true; + } + } + } + } + } + if ((numberOfClasses < 1) && (numberOfMethods < 1)) { if (verboseLogging) @@ -7186,6 +7220,58 @@ void Compiler::pickGDV(GenTreeCall* call, JITDUMP("Accepting type %s with likelihood %u as a candidate\n", eeGetClassName(classGuesses[guessIdx]), likelihoods[guessIdx]) } + + // If the 'this' arg to the call is an enumerator var, record any + // dominant likely class so we can possibly infer a GDV at places where we + // never observed the var's value. (eg an unreached Dispose call if + // control is hijacked out of Tier0+i by OSR). + // + // Note enumerator vars are special as they are generally not redefined + // and we want to ensure all methods called on them get inlined to enable + // escape analysis to kick in, if possible. + // + const unsigned dominantLikelihood = 50; + + if (!isInferredGDV && (likelihoods[guessIdx] >= dominantLikelihood)) + { + CallArg* const thisArg = call->gtArgs.FindWellKnownArg(WellKnownArg::ThisPointer); + + if (thisArg != nullptr) + { + GenTree* const thisNode = thisArg->GetEarlyNode(); + if (thisNode->OperIs(GT_LCL_VAR)) + { + GenTreeLclVarCommon* thisLclNode = thisNode->AsLclVarCommon(); + LclVarDsc* const thisVarDsc = lvaGetDesc(thisLclNode); + unsigned const thisLclNum = thisLclNode->GetLclNum(); + + if (thisVarDsc->lvIsEnumerator) + { + VarToLikelyClassMap* const map = getImpEnumeratorLikelyTypeMap(); + + // If we have multiple type observations, we just use the first. + // + // Note importation order is somewhat reverse-post-orderish; + // a block is only imported if one of its imported preds is imported. + // + // Enumerator vars tend to have a dominating MoveNext call that will + // be the one subsequent uses will see, if they lack their own + // type observations. + // + if (!map->Lookup(thisLclNum)) + { + InferredGdvEntry e; + e.m_classHandle = classGuesses[guessIdx]; + e.m_likelihood = likelihoods[guessIdx]; + + JITDUMP("Remembering that V%02u has %u%% likely class %s\n", thisLclNum, + e.m_likelihood, eeGetClassName(e.m_classHandle)); + map->Set(thisLclNum, e); + } + } + } + } + } } else {