diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index a7d1726a7614b4..62f70f68377be1 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1151,7 +1151,6 @@ struct CORINFO_CONST_LOOKUP // IAT_PVALUE --> "addr" stores a pointer to a location which will hold the real handle // IAT_RELPVALUE --> "addr" stores a relative pointer to a location which will hold the real handle // IAT_PPVALUE --> "addr" stores a double indirection to a location which will hold the real handle - InfoAccessType accessType; union { @@ -1598,17 +1597,15 @@ struct CORINFO_DEVIRTUALIZATION_INFO // - details on the computation done by the jit host // - If pResolvedTokenDevirtualizedMethod is not set to NULL and targeting an R2R image // use it as the parameter to getCallInfo - // - isInstantiatingStub is set to TRUE if the devirtualized method is a generic method instantiating stub - // - needsMethodContext is set TRUE if the devirtualized method may require a method context - // (in which case the method handle and context will be a generic method) + // - instParamLookup contains all the information necessary to pass the instantiation parameter for + // the devirtualized method. A constant lookup with IAT_VALUE with a nullptr handle indicates no instantiation parameter is needed. // CORINFO_METHOD_HANDLE devirtualizedMethod; CORINFO_CONTEXT_HANDLE exactContext; CORINFO_DEVIRTUALIZATION_DETAIL detail; CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; - bool isInstantiatingStub; - bool needsMethodContext; + CORINFO_LOOKUP instParamLookup; }; //---------------------------------------------------------------------------- diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index c18e0ddca37998..9816fd7d1744fa 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 1516acb8-ac41-4dcb-9840-f39ee25ffa73 */ - 0x1516acb8, - 0xac41, - 0x4dcb, - {0x98, 0x40, 0xf3, 0x9e, 0xe2, 0x5f, 0xfa, 0x73} +constexpr GUID JITEEVersionIdentifier = { /* fc5f63e7-921b-4091-b920-8df8d7b872c1 */ + 0xfc5f63e7, + 0x921b, + 0x4091, + {0xb9, 0x20, 0x8d, 0xf8, 0xd7, 0xb8, 0x72, 0xc1} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index f35b36e63a6253..e26b408f40046a 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6861,13 +6861,11 @@ class Compiler GenTree* fgCreateCallDispatcherAndGetResult(GenTreeCall* origCall, CORINFO_METHOD_HANDLE callTargetStubHnd, CORINFO_METHOD_HANDLE dispatcherHnd); - GenTree* getLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - GenTreeFlags handleFlags, - void* compileTimeHandle); - GenTree* getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - void* compileTimeHandle); + GenTree* getLookupTree(CORINFO_LOOKUP* pLookup, + GenTreeFlags handleFlags, + void* compileTimeHandle); + GenTree* getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, + void* compileTimeHandle); GenTree* getVirtMethodPointerTree(GenTree* thisPtr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo); @@ -7763,8 +7761,8 @@ class Compiler unsigned methodAttr, unsigned classAttr, unsigned likelihood, - bool arrayInterface, - bool instantiatingStub, + bool needsMethodContext, + CORINFO_METHOD_HANDLE instantiatingStub, CORINFO_METHOD_HANDLE originalMethodHandle, CORINFO_CONTEXT_HANDLE originalContextHandle); diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 6ef07513ee4b10..991f09ec9bef52 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7742,6 +7742,19 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; CORINFO_METHOD_HANDLE exactMethod = dvInfo.devirtualizedMethod; uint32_t exactMethodAttrs = info.compCompHnd->getMethodAttribs(exactMethod); + assert(!dvInfo.instParamLookup.lookupKind.needsRuntimeLookup); + + const bool needsMethodContext = + ((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; + const bool needsInstParam = (dvInfo.instParamLookup.constLookup.accessType != IAT_VALUE) || + (dvInfo.instParamLookup.constLookup.handle != nullptr); + CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; + if (needsInstParam) + { + assert(needsMethodContext); + instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); + assert(instantiatingStub != NO_METHOD_HANDLE); + } // NOTE: This is currently used only with NativeAOT. In theory, we could also check if we // have static PGO data to decide which class to guess first. Presumably, this is a rare case. @@ -7756,8 +7769,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, } addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs, - clsAttrs, likelyHood, dvInfo.needsMethodContext, - dvInfo.isInstantiatingStub, baseMethod, originalContext); + clsAttrs, likelyHood, needsMethodContext, instantiatingStub, + baseMethod, originalContext); } if (call->GetInlineCandidatesCount() == numExactClasses) @@ -7784,7 +7797,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, CORINFO_METHOD_HANDLE likelyMethod = likelyMethods[candidateId]; unsigned likelihood = likelihoods[candidateId]; bool needsMethodContext = false; - bool instantiatingStub = false; + CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; CORINFO_CONTEXT_HANDLE likelyContext = originalContext; @@ -7827,10 +7840,19 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, break; } - likelyContext = dvInfo.exactContext; - likelyMethod = dvInfo.devirtualizedMethod; - needsMethodContext = dvInfo.needsMethodContext; - instantiatingStub = dvInfo.isInstantiatingStub; + likelyContext = dvInfo.exactContext; + likelyMethod = dvInfo.devirtualizedMethod; + assert(!dvInfo.instParamLookup.lookupKind.needsRuntimeLookup); + + needsMethodContext = ((size_t)likelyContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; + const bool needsInstParam = (dvInfo.instParamLookup.constLookup.accessType != IAT_VALUE) || + (dvInfo.instParamLookup.constLookup.handle != nullptr); + if (needsInstParam) + { + assert(needsMethodContext); + instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)likelyContext & ~CORINFO_CONTEXTFLAGS_MASK); + assert(instantiatingStub != NO_METHOD_HANDLE); + } } else { @@ -7931,8 +7953,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // methodAttr - attributes of the method // classAttr - attributes of the class // likelihood - odds that this class is the class seen at runtime -// needsMethodContext - devirtualized method may need generic method context (e.g. array interfaces) -// instantiatingStub - devirtualized method in an instantiating stub +// needsMethodContext - devirtualized method's exact context is a method context +// instantiatingStub - instantiating stub to pass as an instantiation argument, if one is needed // originalMethodHandle - method handle of base method (before devirt) // originalContextHandle - context for the original call // @@ -7944,7 +7966,7 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, unsigned classAttr, unsigned likelihood, bool needsMethodContext, - bool instantiatingStub, + CORINFO_METHOD_HANDLE instantiatingStub, CORINFO_METHOD_HANDLE originalMethodHandle, CORINFO_CONTEXT_HANDLE originalContextHandle) { @@ -7999,9 +8021,22 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, // We're all set, proceed with candidate creation. // + if (needsMethodContext) + { + assert(((size_t)contextHandle & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); + } + + if (instantiatingStub != NO_METHOD_HANDLE) + { + assert(needsMethodContext); + } + JITDUMP("Marking call [%06u] as guarded devirtualization candidate; will guess for %s %s\n", dspTreeID(call), classHandle != NO_CLASS_HANDLE ? "class" : "method", - classHandle != NO_CLASS_HANDLE ? eeGetClassName(classHandle) : eeGetMethodFullName(methodHandle)); + classHandle != NO_CLASS_HANDLE + ? eeGetClassName(classHandle) + : eeGetMethodFullName(instantiatingStub != NO_METHOD_HANDLE ? instantiatingStub : methodHandle)); + setMethodHasGuardedDevirtualization(); // Spill off any GT_RET_EXPR subtrees so we can clone the call. @@ -8026,20 +8061,11 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, // If the guarded method is an instantiating stub, find the instantiated method // - if (instantiatingStub) + if (instantiatingStub != NO_METHOD_HANDLE) { - JITDUMP(" ... method is an instantiating stub, looking for instantiated entry\n"); - CORINFO_CLASS_HANDLE ignoredClass = NO_CLASS_HANDLE; - CORINFO_METHOD_HANDLE ignoredMethod = NO_METHOD_HANDLE; - CORINFO_METHOD_HANDLE instantiatedMethod = - info.compCompHnd->getInstantiatedEntry(methodHandle, &ignoredMethod, &ignoredClass); - assert(ignoredClass == NO_CLASS_HANDLE); - - if (instantiatedMethod != NO_METHOD_HANDLE) - { - JITDUMP(" ... updating GDV candidate with instantiated entry info\n"); - pInfo->guardedMethodInstantiatedEntryHandle = instantiatedMethod; - } + JITDUMP(" ... updating GDV candidate with instantiated entry info\n"); + pInfo->guardedMethodHandle = instantiatingStub; + pInfo->guardedMethodInstantiatedEntryHandle = methodHandle; } // If the guarded class is a value class, look for an unboxed entry point. @@ -8907,25 +8933,23 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, const bool objClassIsFinal = (objClassAttribs & CORINFO_FLG_FINAL) != 0; #if defined(DEBUG) - const char* callKind = isInterface ? "interface" : "virtual"; - const char* objClassNote = "[?]"; - const char* objClassName = "?objClass"; - const char* baseClassName = "?baseClass"; - const char* baseMethodName = "?baseMethod"; + const char* callKind = isInterface ? "interface" : "virtual"; + const char* objClassNote = "[?]"; + const char* objClassName = "?objClass"; + const char* baseMethodFullName = "?baseMethod"; if (verbose || doPrint) { - objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : ""; - objClassName = eeGetClassName(objClass); - baseClassName = eeGetClassName(baseClass); - baseMethodName = eeGetMethodName(baseMethod); + objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : ""; + objClassName = eeGetClassName(objClass); + baseMethodFullName = eeGetMethodFullName(baseMethod); if (verbose) { printf("\nimpDevirtualizeCall: Trying to devirtualize %s call:\n" " class for 'this' is %s%s (attrib %08x)\n" - " base method is %s::%s\n", - callKind, objClassName, objClassNote, objClassAttribs, baseClassName, baseMethodName); + " base method is %s\n", + callKind, objClassName, objClassNote, objClassAttribs, baseMethodFullName); } } #endif // defined(DEBUG) @@ -8974,10 +8998,15 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, info.compCompHnd->resolveVirtualMethod(&dvInfo); - CORINFO_METHOD_HANDLE derivedMethod = dvInfo.devirtualizedMethod; - CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; - CORINFO_CLASS_HANDLE derivedClass = NO_CLASS_HANDLE; - CORINFO_RESOLVED_TOKEN* pDerivedResolvedToken = &dvInfo.resolvedTokenDevirtualizedMethod; + CORINFO_METHOD_HANDLE derivedMethod = dvInfo.devirtualizedMethod; + CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; + CORINFO_CLASS_HANDLE derivedClass = NO_CLASS_HANDLE; + CORINFO_RESOLVED_TOKEN* pDerivedResolvedToken = &dvInfo.resolvedTokenDevirtualizedMethod; + const bool needsRuntimeLookup = dvInfo.instParamLookup.lookupKind.needsRuntimeLookup; + const bool isArrayInterfaceDevirt = (objClassAttribs & CORINFO_FLG_ARRAY) != 0; + + const bool needsInstParam = needsRuntimeLookup || !(dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && + dvInfo.instParamLookup.constLookup.handle == nullptr); if (derivedMethod != nullptr) { @@ -8985,7 +9014,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS) { - assert(!dvInfo.needsMethodContext); derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } else @@ -8993,7 +9021,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Array interface devirt can return a nonvirtual generic method of the non-generic SZArrayHelper class. // Generic virtual method devirt also returns a generic method. // - assert(call->IsGenericVirtual(this) || dvInfo.needsMethodContext); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } @@ -9004,54 +9031,36 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, bool canDevirtualize = false; #if defined(DEBUG) - const char* derivedClassName = "?derivedClass"; - const char* derivedMethodName = "?derivedMethod"; - const char* note = "inexact or not final"; - const char* instArg = ""; + const char* derivedMethodFullName = "?derivedMethod"; + const char* note = "inexact or not final"; + const char* instArg = ""; #endif - CORINFO_METHOD_HANDLE instantiatingStub = NO_METHOD_HANDLE; + CORINFO_METHOD_HANDLE instParam = NO_METHOD_HANDLE; - if (dvInfo.isInstantiatingStub) + if (derivedMethod != nullptr && needsInstParam && !needsRuntimeLookup) { - // We should only end up with generic methods that needs a method context (eg. array interface, GVM). - // - assert(dvInfo.needsMethodContext); - - // We don't expect NAOT to end up here, since it has Array - // and normal devirtualization. - // - assert(!IsTargetAbi(CORINFO_NATIVEAOT_ABI)); - - // We don't expect R2R to end up here, since it does not (yet) support - // array interface devirtualization. - // - assert(!IsAot()); - - // We don't expect there to be an existing inst param arg. + // We should only end up with generic methods that need a method context (eg. array interface, GVM). // - CallArg* const instParam = call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam); - if (instParam != nullptr) - { - assert(!"unexpected inst param in virtual/interface call"); - return; - } + assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); // If we don't know the array type exactly we may have the wrong interface type here. // Bail out. // - if (!isExact) + if (isArrayInterfaceDevirt && !isExact) { JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); return; } - // We want to inline the instantiating stub. Fetch the relevant info. - // - CORINFO_CLASS_HANDLE ignored = NO_CLASS_HANDLE; - derivedMethod = info.compCompHnd->getInstantiatedEntry(derivedMethod, &instantiatingStub, &ignored); - assert(ignored == NO_CLASS_HANDLE); - assert((derivedMethod == NO_METHOD_HANDLE) || (instantiatingStub != NO_METHOD_HANDLE)); + // We don't expect R2R/NAOT to end up here for array interface devirtualization. + // For NAOT, it has Array and normal devirtualization. + // For R2R, we don't (yet) support array interface devirtualization. + assert(call->IsGenericVirtual(this) || !IsAot()); + + instParam = (CORINFO_METHOD_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); + + assert(instParam != NO_METHOD_HANDLE); } // If we failed to get a method handle, we can't directly devirtualize. @@ -9082,18 +9091,18 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, { note = "final method"; } - if (dvInfo.isInstantiatingStub) + if (needsInstParam) { - instArg = " [instantiating stub]"; + instArg = needsRuntimeLookup ? "runtime lookup" : eeGetMethodFullName(instParam); } if (verbose || doPrint) { - derivedMethodName = eeGetMethodName(derivedMethod); - derivedClassName = eeGetClassName(derivedClass); + derivedMethodFullName = eeGetMethodFullName(derivedMethod); if (verbose) { - printf(" devirt to %s::%s -- %s%s\n", derivedClassName, derivedMethodName, note, instArg); + printf(" devirt to %s -- %s%s%s\n", derivedMethodFullName, note, + needsInstParam ? ", instantiation: " : "", instArg); gtDispTree(call); } } @@ -9132,6 +9141,29 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } + // Insert the instantiation argument when necessary. + if (needsInstParam) + { + assert(call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam) == nullptr); + + CORINFO_METHOD_HANDLE compileTimeHandle = derivedMethod; + if (!needsRuntimeLookup) + { + compileTimeHandle = instParam; + } + + GenTree* instParamNode = getLookupTree(&dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); + + if (instParamNode == nullptr) + { + // If we're inlining, impLookupToTree can return nullptr after recording a fatal observation. + JITDUMP("Failed to produce the lookup for devirtualized call, sorry.\n"); + return; + } + + call->gtArgs.InsertInstParam(this, instParamNode); + } + // All checks done. Time to transform the call. // assert(canDevirtualize); @@ -9139,17 +9171,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP(" %s; can devirtualize\n", note); - if (dvInfo.isInstantiatingStub) - { - // Pass the instantiating stub method desc as the inst param arg. - // - // Note different embedding would be needed for NAOT/R2R, - // but we have ruled those out above. - // - GenTree* const instParam = gtNewIconEmbMethHndNode(instantiatingStub); - call->gtArgs.InsertInstParam(this, instParam); - } - // Make the updates. call->gtFlags &= ~GTF_CALL_VIRT_VTABLE; call->gtFlags &= ~GTF_CALL_VIRT_STUB; @@ -9179,8 +9200,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (doPrint) { - printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]\n", callKind, baseClassName, - baseMethodName, derivedClassName, derivedMethodName, note); + printf("Devirtualized %s call to %s; now direct call to %s [%s]%s%s\n", callKind, baseMethodFullName, + derivedMethodFullName, note, needsInstParam ? ", instantiation: " : "", instArg); } // If we successfully devirtualized based on an exact or final class, diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 369f44207ba6d1..38faa6d2001887 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5463,7 +5463,6 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig // getLookupTree: get a lookup tree // // Arguments: -// pResolvedToken - resolved token of the call // pLookup - the lookup to get the tree for // handleFlags - flags to set on the result node // compileTimeHandle - compile-time handle corresponding to the lookup @@ -5471,10 +5470,7 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig // Return Value: // A node representing the lookup tree // -GenTree* Compiler::getLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - GenTreeFlags handleFlags, - void* compileTimeHandle) +GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFlags, void* compileTimeHandle) { if (!pLookup->lookupKind.needsRuntimeLookup) { @@ -5497,26 +5493,21 @@ GenTree* Compiler::getLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, compileTimeHandle); } - return getRuntimeLookupTree(pResolvedToken, pLookup, compileTimeHandle); + return getRuntimeLookupTree(pLookup, compileTimeHandle); } //------------------------------------------------------------------------ // getRuntimeLookupTree: get a tree for a runtime lookup // // Arguments: -// pResolvedToken - resolved token of the call // pLookup - the lookup to get the tree for // compileTimeHandle - compile-time handle corresponding to the lookup // // Return Value: // A node representing the runtime lookup tree // -GenTree* Compiler::getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, - CORINFO_LOOKUP* pLookup, - void* compileTimeHandle) +GenTree* Compiler::getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, void* compileTimeHandle) { - assert(!compIsForInlining()); - CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; // If pRuntimeLookup->indirections is equal to CORINFO_USEHELPER, it specifies that a run-time helper should be @@ -5637,8 +5628,8 @@ GenTree* Compiler::getTokenHandleTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, bo // info.compCompHnd->embedGenericHandle(pResolvedToken, parent, info.compMethodHnd, &embedInfo); - GenTree* result = getLookupTree(pResolvedToken, &embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), - embedInfo.compileTimeHandle); + GenTree* result = + getLookupTree(&embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), embedInfo.compileTimeHandle); // If we have a result and it requires runtime lookup, wrap it in a runtime lookup node. if ((result != nullptr) && embedInfo.lookup.lookupKind.needsRuntimeLookup) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index dcc065e8303292..f8c4d0184d2ace 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1351,8 +1351,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) info->devirtualizedMethod = null; info->exactContext = null; info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; - info->isInstantiatingStub = false; - info->needsMethodContext = false; + info->instParamLookup = default(CORINFO_LOOKUP); TypeDesc objType = HandleToObject(info->objClass); @@ -1497,7 +1496,6 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) #endif info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_SUCCESS; info->devirtualizedMethod = ObjectToHandle(impl); - info->isInstantiatingStub = false; info->exactContext = contextFromType(owningType); return true; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index a921abd0406ab1..d6476e6d2f99bb 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1189,19 +1189,15 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO // invariant is `resolveVirtualMethod(...) == (devirtualizedMethod != nullptr)`. // - exactContext is set to wrapped CORINFO_CLASS_HANDLE of devirt'ed method table. // - detail describes the computation done by the jit host - // - isInstantiatingStub is set to TRUE if the devirtualized method is a method instantiation stub - // - needsMethodContext is set TRUE if the devirtualized method may require a method context - // (in which case the method handle and context will be a generic method) + // - instParamLookup contains all the information necessary to pass the instantiation parameter for + // the devirtualized method. // public CORINFO_METHOD_STRUCT_* devirtualizedMethod; public CORINFO_CONTEXT_STRUCT* exactContext; public CORINFO_DEVIRTUALIZATION_DETAIL detail; public CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; public CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; - public byte _isInstantiatingStub; - public bool isInstantiatingStub { get { return _isInstantiatingStub != 0; } set { _isInstantiatingStub = value ? (byte)1 : (byte)0; } } - public byte _needsMethodContext; - public bool needsMethodContext { get { return _needsMethodContext != 0; } set { _needsMethodContext = value ? (byte)1 : (byte)0; } } + public CORINFO_LOOKUP instParamLookup; } //---------------------------------------------------------------------------- diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index f929bb75035cf7..4540e349a94cd2 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -688,12 +688,11 @@ struct Agnostic_ResolveVirtualMethodResult { bool returnValue; DWORDLONG devirtualizedMethod; - bool isInstantiatingStub; - bool needsMethodContext; DWORDLONG exactContext; DWORD detail; Agnostic_CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; Agnostic_CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; + Agnostic_CORINFO_LOOKUP instParamLookup; }; struct Agnostic_GetInstantiatedEntryResult diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 605cdfce3c4758..0f71d37aa51509 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3259,10 +3259,9 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info Agnostic_ResolveVirtualMethodResult result; result.returnValue = returnValue; result.devirtualizedMethod = CastHandle(info->devirtualizedMethod); - result.isInstantiatingStub = info->isInstantiatingStub; result.exactContext = CastHandle(info->exactContext); result.detail = (DWORD)info->detail; - result.needsMethodContext = info->needsMethodContext; + result.instParamLookup = SpmiRecordsHelper::StoreAgnostic_CORINFO_LOOKUP(&info->instParamLookup); if (returnValue) { @@ -3287,15 +3286,14 @@ void MethodContext::dmpResolveVirtualMethod(const Agnostic_ResolveVirtualMethodK key.context, key.pResolvedTokenVirtualMethodNonNull, key.pResolvedTokenVirtualMethodNonNull ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.pResolvedTokenVirtualMethod).c_str() : "???"); - printf(", value returnValue-%s, devirtMethod-%016" PRIX64 ", instantiatingStub-%s, needsMethodContext-%s, exactContext-%016" PRIX64 ", detail-%d, tokDvMeth{%s}, tokDvUnboxMeth{%s}", + printf(", value returnValue-%s, devirtMethod-%016" PRIX64 ", exactContext-%016" PRIX64 ", detail-%d, tokDvMeth{%s}, tokDvUnboxMeth{%s}, instParamLookup{%s}", result.returnValue ? "true" : "false", result.devirtualizedMethod, - result.isInstantiatingStub ? "true" : "false", - result.needsMethodContext ? "true" : "false", result.exactContext, result.detail, result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedMethod).c_str() : "???", - result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedUnboxedMethod).c_str() : "???"); + result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedUnboxedMethod).c_str() : "???", + SpmiDumpHelper::DumpAgnostic_CORINFO_LOOKUP(result.instParamLookup).c_str()); } bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info) @@ -3315,10 +3313,9 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info DEBUG_REP(dmpResolveVirtualMethod(key, result)); info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) result.devirtualizedMethod; - info->isInstantiatingStub = result.isInstantiatingStub; - info->needsMethodContext = result.needsMethodContext; info->exactContext = (CORINFO_CONTEXT_HANDLE) result.exactContext; info->detail = (CORINFO_DEVIRTUALIZATION_DETAIL) result.detail; + info->instParamLookup = SpmiRecordsHelper::RestoreCORINFO_LOOKUP(result.instParamLookup); if (result.returnValue) { info->resolvedTokenDevirtualizedMethod = SpmiRecordsHelper::Restore_CORINFO_RESOLVED_TOKEN(&result.resolvedTokenDevirtualizedMethod, ResolveToken); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index ebf0533da70904..91a128bbbdff66 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3017,7 +3017,7 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr MethodDesc* pContextMD = pCallerMD; MethodTable* pContextMT = pContextMD->GetMethodTable(); - // There is a pathological case where invalid IL refereces __Canon type directly, but there is no dictionary availabled to store the lookup. + // There is a pathological case where invalid IL references __Canon type directly, but there is no dictionary available to store the lookup. if (!pContextMD->IsSharedByGenericInstantiations()) COMPlusThrow(kInvalidProgramException); @@ -8615,8 +8615,10 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) info->detail = CORINFO_DEVIRTUALIZATION_UNKNOWN; memset(&info->resolvedTokenDevirtualizedMethod, 0, sizeof(info->resolvedTokenDevirtualizedMethod)); memset(&info->resolvedTokenDevirtualizedUnboxedMethod, 0, sizeof(info->resolvedTokenDevirtualizedUnboxedMethod)); - info->isInstantiatingStub = false; - info->needsMethodContext = false; + memset(&info->instParamLookup, 0, sizeof(info->instParamLookup)); + info->instParamLookup.lookupKind.needsRuntimeLookup = false; + info->instParamLookup.constLookup.accessType = IAT_VALUE; + info->instParamLookup.constLookup.handle = NULL; MethodDesc* pBaseMD = GetMethod(info->virtualMethod); MethodTable* pBaseMT = pBaseMD->GetMethodTable(); @@ -8852,12 +8854,9 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) // This is generic virtual method devirtualization. if (!isArray && pBaseMD->HasMethodInstantiation()) { - pDevirtMD = pDevirtMD->FindOrCreateAssociatedMethodDesc( - pDevirtMD, pExactMT, pExactMT->IsValueType() && !pDevirtMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true); - - // We still can't handle shared generic methods because we don't have - // the right generic context for runtime lookup. - // TODO: Remove this limitation. + MethodDesc* pPrimaryMD = pDevirtMD; + pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( + pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), true); if (pDevirtMD->IsSharedByGenericMethodInstantiations()) { info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; @@ -8867,32 +8866,24 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) isGenericVirtual = true; } - // Success! Pass back the results. - // - if (isArray) + if (!info->instParamLookup.lookupKind.needsRuntimeLookup && (isArray || isGenericVirtual) && pDevirtMD->IsInstantiatingStub()) { - // Note if array devirtualization produced an instantiation stub - // so jit can try and inline it. - // - info->isInstantiatingStub = pDevirtMD->IsInstantiatingStub(); - info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); - info->needsMethodContext = true; + info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE)pDevirtMD; + info->instParamLookup.constLookup.accessType = IAT_VALUE; } - else if (isGenericVirtual) + + if (isArray || isGenericVirtual) { - // We don't support shared generic methods yet so this should always be false - info->needsMethodContext = false; - // We don't produce an instantiating stub - info->isInstantiatingStub = false; info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); + pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD; } else { info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); - info->isInstantiatingStub = false; - info->needsMethodContext = false; } + // Success! Pass back the results. + // info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) pDevirtMD; info->detail = CORINFO_DEVIRTUALIZATION_SUCCESS; diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs index c971d07f273a4e..7aba5abe26bab3 100644 --- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -3,6 +3,7 @@ // using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; using Xunit; @@ -137,6 +138,12 @@ public static void GenericInterfaceBase_GenericStructDerived_NoInliningVariants( ValidateCaller("GenericInterfaceBase_GenericStructDerived_NoInlining_String_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_NoInlining())); } + [Fact] + public static void RuntimeLookupDelegate() + { + RuntimeLookupDelegateGenericVirtual.Test(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ValidateCaller(string scenarioName, IBaseMethodCaller caller) { @@ -199,6 +206,45 @@ private static void Equal(T expected, T actual, [CallerArgumentExpression(nam } } +internal class RuntimeLookupDelegateGenericVirtual +{ + internal static readonly List s_list = new(); + + internal static void Test() + { + var test = new Base(); + test.Foo>(); + + var test2 = new Derived(); + Delegate m1 = test2.Foo>(); + Delegate m2 = test2.Foo>>; + Assert.Equal(m1, m2); + } +} + +internal class Base +{ + public virtual Delegate Foo() + { + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(U)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>>>)); + RuntimeLookupDelegateGenericVirtual.s_list.Add(typeof(List>>>>>)); + return Foo; + } +} + +internal class Derived : Base +{ + public override Delegate Foo() + { + return Foo>; + } +} + internal static class IconContextBridgeNonShared { public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value) @@ -355,96 +401,164 @@ public static TMethod DifferentClassDifferentMethod(IBaseMethodCaller caller, TM internal static class RuntimeLookupDispatcher { + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod SameClassSameMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeSameClassSameMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.SameClassSameMethod(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod SameClassDifferentMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeSameClassDifferentMethod(caller, value); + return SameClassDifferentMethodCore(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod DifferentClassSameMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeDifferentClassSameMethod(caller, value); + return RuntimeLookupDifferentClass.SameMethod(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] public static TMethod DifferentClassDifferentMethod(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupThunks.InvokeDifferentClassDifferentMethod(caller, value); + return RuntimeLookupDifferentClass.DifferentMethod(caller, value); } -} -internal static class RuntimeLookupThunks -{ - public static T InvokeSameClassSameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + private static TMethod SameClassDifferentMethodCore(IBaseMethodCaller caller, TMethod value) { - return RuntimeLookupHost.SameClassSameMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.SameClassDifferentMethod(caller, value); } +} - public static T InvokeDifferentClassSameMethod(IBaseMethodCaller caller, T value) +internal static class RuntimeLookupDifferentClass +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static T SameMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupHost.DifferentClassSameMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.DifferentClassSameMethod(caller, value); } - public static T InvokeSameClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public static T DifferentMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupHost.SameClassDifferentMethod(caller, value); + return DifferentMethodCore(caller, value); } - public static T InvokeDifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + private static T DifferentMethodCore(IBaseMethodCaller caller, T value) { - return RuntimeLookupHost.DifferentClassDifferentMethod(caller, value); + RuntimeLookupVirtualInvoker invoker = new RuntimeLookupVirtualStage(); + return invoker.DifferentClassDifferentMethod(caller, value); } } -internal static class RuntimeLookupHost +internal abstract class RuntimeLookupVirtualInvoker +{ + public abstract T SameClassSameMethod(IBaseMethodCaller caller, T value); + public abstract T SameClassDifferentMethod(IBaseMethodCaller caller, T value); + public abstract T DifferentClassSameMethod(IBaseMethodCaller caller, T value); + public abstract T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value); +} + +internal sealed class RuntimeLookupVirtualStage : RuntimeLookupVirtualInvoker { - public static T SameClassSameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassSameMethod(IBaseMethodCaller caller, T value) { - return caller.Invoke(value); + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.SameClassSameMethod(caller, value); } - public static T DifferentClassSameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassDifferentMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupRemote.SameMethod(caller, value); + return SameClassDifferentMethodCore(caller, value); } - public static T SameClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassSameMethod(IBaseMethodCaller caller, T value) { - return SameClassDifferentMethodCore(caller, value); + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.DifferentClassSameMethod(caller, value); } - public static T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value) { - return RuntimeLookupRemote.DifferentMethod(caller, value); + return DifferentClassDifferentMethodCore(caller, value); } + [MethodImpl(MethodImplOptions.NoInlining)] private static T SameClassDifferentMethodCore(IBaseMethodCaller caller, T value) { - return caller.Invoke(value); + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.SameClassDifferentMethod(caller, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T DifferentClassDifferentMethodCore(IBaseMethodCaller caller, T value) + { + RuntimeLookupVirtualInvoker invoker = RuntimeLookupTerminalFactory.CreateInvoker(); + return invoker.DifferentClassDifferentMethod(caller, value); + } +} + +internal static class RuntimeLookupTerminalFactory +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RuntimeLookupVirtualInvoker CreateInvoker() + { + return new RuntimeLookupTerminalInvoker(); } } -internal static class RuntimeLookupRemote +internal sealed class RuntimeLookupTerminalInvoker : RuntimeLookupVirtualInvoker { - public static T SameMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassSameMethod(IBaseMethodCaller caller, T value) { return caller.Invoke(value); } - public static T DifferentMethod(IBaseMethodCaller caller, T value) + [MethodImpl(MethodImplOptions.NoInlining)] + public override T SameClassDifferentMethod(IBaseMethodCaller caller, T value) { - return RemoteInner.Invoke(caller, value); + return SameClassDifferentMethodCore(caller, value); } - private static class RemoteInner + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassSameMethod(IBaseMethodCaller caller, T value) { - public static T Invoke(IBaseMethodCaller caller, T value) - { - return caller.Invoke(value); - } + return caller.Invoke(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public override T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + { + return DifferentClassDifferentMethodCore(caller, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T SameClassDifferentMethodCore(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T DifferentClassDifferentMethodCore(IBaseMethodCaller caller, T value) + { + return DifferentClassDifferentMethodCoreInner.Invoke(caller, value); + } + + private static class DifferentClassDifferentMethodCoreInner + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static T Invoke(IBaseMethodCaller caller, T value) => caller.Invoke(value); } }