diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 51cf6d642b1f0eee8bb4c21b70f4285547237622..1f9cbb6bfd431d0f72dd8dcc66239226eabf7190 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6799,6 +6799,14 @@ public: optMethodFlags &= ~OMF_HAS_GUARDEDDEVIRT; } + void considerGuardedDevirtualization(GenTreeCall* call, + IL_OFFSETX iloffset, + bool isInterface, + CORINFO_METHOD_HANDLE baseMethod, + CORINFO_CLASS_HANDLE baseClass, + CORINFO_CONTEXT_HANDLE* pContextHandle DEBUGARG(CORINFO_CLASS_HANDLE objClass) + DEBUGARG(const char* objClassName)); + void addGuardedDevirtualizationCandidate(GenTreeCall* call, CORINFO_METHOD_HANDLE methodHandle, CORINFO_CLASS_HANDLE classHandle, diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 21cda6aad331f5911294be1890e4e18fb4242b76..78cc8326370f570bf1c79fd9de4c522bc4e9c137 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -20975,65 +20975,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - JITDUMP("Considering guarded devirt (interface)...\n"); - - // See if the runtime can provide a class to guess for. - // - const unsigned interfaceLikelihoodThreshold = 25; - unsigned likelihood = 0; - unsigned numberOfClasses = 0; - CORINFO_CLASS_HANDLE likelyClass = - info.compCompHnd->getLikelyClass(info.compMethodHnd, baseClass, ilOffset, &likelihood, &numberOfClasses); - - if (likelyClass == NO_CLASS_HANDLE) - { - JITDUMP("No likely implementor of interface %p (%s), sorry\n", dspPtr(objClass), objClassName); - return; - } - - JITDUMP("Likely implementor of interface %p (%s) is %p (%s) [likelihood:%u classes seen:%u]\n", - dspPtr(objClass), objClassName, likelyClass, eeGetClassName(likelyClass), likelihood, numberOfClasses); - - // Todo: a more advanced heuristic using likelihood, number of - // classes, and the profile count for this block. - // - // For now we will guess if the likelihood is 25% or more, as studies - // have shown this should pay off for interface calls. - // - if (likelihood < interfaceLikelihoodThreshold) - { - JITDUMP("Not guessing for class; likelihood is below interface call threshold %u\n", - interfaceLikelihoodThreshold); - return; - } - - // Ask the runtime to determine the method that would be called based on the likely type. - // - CORINFO_DEVIRTUALIZATION_INFO dvInfo; - dvInfo.virtualMethod = baseMethod; - dvInfo.objClass = likelyClass; - dvInfo.context = *pContextHandle; - - bool canResolve = info.compCompHnd->resolveVirtualMethod(&dvInfo); - - if (!canResolve) - { - JITDUMP("Can't figure out which method would be invoked, sorry\n"); - return; - } - - CORINFO_METHOD_HANDLE likelyMethod = dvInfo.devirtualizedMethod; - JITDUMP("%s call would invoke method %s\n", callKind, eeGetMethodName(likelyMethod, nullptr)); - - // Some of these may be redundant - // - DWORD likelyMethodAttribs = info.compCompHnd->getMethodAttribs(likelyMethod); - DWORD likelyClassAttribs = info.compCompHnd->getClassAttribs(likelyClass); - - // Try guarded devirtualization. - // - addGuardedDevirtualizationCandidate(call, likelyMethod, likelyClass, likelyMethodAttribs, likelyClassAttribs, - likelihood); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, + pContextHandle DEBUGARG(objClass) DEBUGARG(objClassName)); return; } @@ -21135,65 +21078,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - JITDUMP("Consdering guarded devirt (virtual)...\n"); - - // See if there's a likely guess for the class. - // - const unsigned likelihoodThreshold = isInterface ? 25 : 30; - unsigned likelihood = 0; - unsigned numberOfClasses = 0; - CORINFO_CLASS_HANDLE likelyClass = - info.compCompHnd->getLikelyClass(info.compMethodHnd, baseClass, ilOffset, &likelihood, &numberOfClasses); - - if (likelyClass == NO_CLASS_HANDLE) - { - JITDUMP("No likely class, sorry\n"); - return; - } - - JITDUMP("Likely class for %p (%s) is %p (%s) [likelihood:%u classes seen:%u]\n", dspPtr(objClass), objClassName, - likelyClass, eeGetClassName(likelyClass), likelihood, numberOfClasses); - - // Todo: a more advanced heuristic using likelihood, number of - // classes, and the profile count for this block. - // - // For now we will guess if the likelihood is at least 25%/30% (intfc/virt), as studies - // have shown this transformation should pay off even if we guess wrong sometimes. - // - if (likelihood < likelihoodThreshold) - { - JITDUMP("Not guessing for class; likelihood is below %s call threshold %u\n", callKind, - likelihoodThreshold); - return; - } - - // Figure out which method will be called. - // - CORINFO_DEVIRTUALIZATION_INFO dvInfo; - dvInfo.virtualMethod = baseMethod; - dvInfo.objClass = likelyClass; - dvInfo.context = *pContextHandle; - - bool canResolve = info.compCompHnd->resolveVirtualMethod(&dvInfo); - - if (!canResolve) - { - JITDUMP("Can't figure out which method would be invoked, sorry\n"); - return; - } - - CORINFO_METHOD_HANDLE likelyMethod = dvInfo.devirtualizedMethod; - JITDUMP("%s call would invoke method %s\n", callKind, eeGetMethodName(likelyMethod, nullptr)); - - // Some of these may be redundant - // - DWORD likelyMethodAttribs = info.compCompHnd->getMethodAttribs(likelyMethod); - DWORD likelyClassAttribs = info.compCompHnd->getClassAttribs(likelyClass); - - // Try guarded devirtualization. - // - addGuardedDevirtualizationCandidate(call, likelyMethod, likelyClass, likelyMethodAttribs, likelyClassAttribs, - likelihood); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, + pContextHandle DEBUGARG(objClass) DEBUGARG(objClassName)); return; } @@ -21584,25 +21470,109 @@ void Compiler::addFatPointerCandidate(GenTreeCall* call) helper.StoreRetExprResultsInArgs(call); } +//------------------------------------------------------------------------ +// considerGuardedDevirtualization: see if we can profitably guess at the +// class involved in an interface or virtual call. +// +// Arguments: +// +// call - potential guarded devirtualization candidate +// ilOffset - IL offset of the call instruction +// isInterface - true if this is an interface call +// baseMethod - target method of the call +// baseClass - class that introduced the target method +// pContextHandle - context handle for the call +// objClass - class of 'this' in the call +// objClassName - name of the obj Class +// +// Notes: +// Consults with VM to see if there's a likely class at runtime, +// if so, adds a candidate for guarded devirtualization. +// +void Compiler::considerGuardedDevirtualization( + GenTreeCall* call, + IL_OFFSETX ilOffset, + bool isInterface, + CORINFO_METHOD_HANDLE baseMethod, + CORINFO_CLASS_HANDLE baseClass, + CORINFO_CONTEXT_HANDLE* pContextHandle DEBUGARG(CORINFO_CLASS_HANDLE objClass) DEBUGARG(const char* objClassName)) +{ +#if defined(DEBUG) + const char* callKind = isInterface ? "interface" : "virtual"; +#endif + + JITDUMP("Considering guarded devirtualization\n"); + + // See if there's a likely guess for the class. + // + const unsigned likelihoodThreshold = isInterface ? 25 : 30; + unsigned likelihood = 0; + unsigned numberOfClasses = 0; + CORINFO_CLASS_HANDLE likelyClass = + info.compCompHnd->getLikelyClass(info.compMethodHnd, baseClass, ilOffset, &likelihood, &numberOfClasses); + + if (likelyClass == NO_CLASS_HANDLE) + { + JITDUMP("No likely class, sorry\n"); + return; + } + + JITDUMP("Likely class for %p (%s) is %p (%s) [likelihood:%u classes seen:%u]\n", dspPtr(objClass), objClassName, + likelyClass, eeGetClassName(likelyClass), likelihood, numberOfClasses); + + // Todo: a more advanced heuristic using likelihood, number of + // classes, and the profile count for this block. + // + // For now we will guess if the likelihood is at least 25%/30% (intfc/virt), as studies + // have shown this transformation should pay off even if we guess wrong sometimes. + // + if (likelihood < likelihoodThreshold) + { + JITDUMP("Not guessing for class; likelihood is below %s call threshold %u\n", callKind, likelihoodThreshold); + return; + } + + // Figure out which method will be called. + // + CORINFO_DEVIRTUALIZATION_INFO dvInfo; + dvInfo.virtualMethod = baseMethod; + dvInfo.objClass = likelyClass; + dvInfo.context = *pContextHandle; + + const bool canResolve = info.compCompHnd->resolveVirtualMethod(&dvInfo); + + if (!canResolve) + { + JITDUMP("Can't figure out which method would be invoked, sorry\n"); + return; + } + + CORINFO_METHOD_HANDLE likelyMethod = dvInfo.devirtualizedMethod; + JITDUMP("%s call would invoke method %s\n", callKind, eeGetMethodName(likelyMethod, nullptr)); + + // Add this as a potential candidate. + // + uint32_t const likelyMethodAttribs = info.compCompHnd->getMethodAttribs(likelyMethod); + uint32_t const likelyClassAttribs = info.compCompHnd->getClassAttribs(likelyClass); + addGuardedDevirtualizationCandidate(call, likelyMethod, likelyClass, likelyMethodAttribs, likelyClassAttribs, + likelihood); +} + //------------------------------------------------------------------------ // addGuardedDevirtualizationCandidate: potentially mark the call as a guarded // devirtualization candidate // // Notes: // -// We currently do not mark calls as candidates when prejitting. This was done -// to simplify bringing up the associated transformation. It is worth revisiting -// if we think we can come up with a good guess for the class when prejitting. -// // Call sites in rare or unoptimized code, and calls that require cookies are -// also not marked as candidates. +// not marked as candidates. // // As part of marking the candidate, the code spills GT_RET_EXPRs anywhere in any // child tree, because and we need to clone all these trees when we clone the call // as part of guarded devirtualization, and these IR nodes can't be cloned. // // Arguments: -// call - potentual guarded devirtialization candidate +// call - potential guarded devirtualization candidate // methodHandle - method that will be invoked if the class test succeeds // classHandle - class that will be tested for at runtime // methodAttr - attributes of the method @@ -21629,14 +21599,6 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, return; } - // Bail when prejitting. We only do this for jitted code. - // We shoud revisit this if we think we can come up with good class guesses when prejitting. - if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT)) - { - JITDUMP("NOT Marking call [%06u] as guarded devirtualization candidate -- prejitting", dspTreeID(call)); - return; - } - // Bail if not optimizing or the call site is very likely cold if (compCurBB->isRunRarely() || opts.OptimizationDisabled()) { @@ -21651,6 +21613,8 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, // we save the stub address below. if ((call->gtCallType == CT_INDIRECT) && (call->AsCall()->gtCallCookie != nullptr)) { + JITDUMP("NOT Marking call [%06u] as guarded devirtualization candidate -- CT_INDIRECT with cookie\n", + dspTreeID(call)); return; }