diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index d49ec0a1de4b06..521e29f77daa77 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1591,17 +1591,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. // 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 c65beca168e933..9816fd7d1744fa 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 868b8ae2-0000-410b-a9dc-38d9eda37d66 */ - 0x868b8ae2, - 0x0000, - 0x410b, - {0xa9, 0xdc, 0x38, 0xd9, 0xed, 0xa3, 0x7d, 0x66} +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 c44692252ff971..ed718965ba07bf 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5204,6 +5204,8 @@ class Compiler methodPointerInfo* impAllocateMethodPointerInfo(const CORINFO_RESOLVED_TOKEN& token, mdToken tokenConstrained); + static bool impDevirtualizedCallHasConstInstParam(const CORINFO_DEVIRTUALIZATION_INFO& dvInfo); + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index e742e683f4de4d..95f3df80106229 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -603,16 +603,17 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtLateDevirtualizationInfo->methodHnd; - CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd; - InlineContext* inlinersContext = call->gtLateDevirtualizationInfo->inlinersContext; - unsigned methodFlags = 0; - const bool isLateDevirtualization = true; - const bool explicitTailCall = call->IsTailPrefixedCall(); + CORINFO_METHOD_HANDLE method = call->gtLateDevirtualizationInfo->methodHnd; + CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd; + InlineContext* inlinersContext = call->gtLateDevirtualizationInfo->inlinersContext; + CORINFO_RESOLVED_TOKEN* pResolvedToken = &call->gtLateDevirtualizationInfo->resolvedToken; + unsigned methodFlags = 0; + const bool isLateDevirtualization = true; + const bool explicitTailCall = call->IsTailPrefixedCall(); CORINFO_CONTEXT_HANDLE contextInput = context; context = nullptr; - m_compiler->impDevirtualizeCall(call, nullptr, &method, &methodFlags, &contextInput, &context, + m_compiler->impDevirtualizeCall(call, pResolvedToken, &method, &methodFlags, &contextInput, &context, isLateDevirtualization, explicitTailCall); if (!call->IsDevirtualizationCandidate(m_compiler)) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 25b4d6de057570..2f9147e1bf88fd 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1305,6 +1305,7 @@ var_types Compiler::impImportCall(OPCODE opcode, info->methodHnd = callInfo->hMethod; info->exactContextHnd = exactContextHnd; info->inlinersContext = compInlineContext; + info->resolvedToken = *pResolvedToken; call->AsCall()->gtLateDevirtualizationInfo = info; } } @@ -7510,6 +7511,29 @@ bool Compiler::isCompatibleMethodGDV(GenTreeCall* call, CORINFO_METHOD_HANDLE gd return true; } +//------------------------------------------------------------------------ +// impDevirtualizedCallHasConstInstParam: check if the instantiation argument +// is a compile-time lookup. +// +// Arguments: +// dvInfo - Devirtualization information returned by resolveVirtualMethod. +// +// Returns: +// true if instParamLookup describes a valid constant handle/address lookup. +// +bool Compiler::impDevirtualizedCallHasConstInstParam(const CORINFO_DEVIRTUALIZATION_INFO& dvInfo) +{ + if (dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) + { + return false; + } + + return ((dvInfo.instParamLookup.constLookup.accessType == IAT_VALUE && + dvInfo.instParamLookup.constLookup.handle != nullptr) || + (dvInfo.instParamLookup.constLookup.accessType == IAT_PVALUE && + dvInfo.instParamLookup.constLookup.addr != nullptr)); +} + //------------------------------------------------------------------------ // considerGuardedDevirtualization: see if we can profitably guess at the // class involved in an interface or virtual call. @@ -7629,6 +7653,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, CORINFO_CONTEXT_HANDLE exactContext = dvInfo.exactContext; CORINFO_METHOD_HANDLE exactMethod = dvInfo.devirtualizedMethod; uint32_t exactMethodAttrs = info.compCompHnd->getMethodAttribs(exactMethod); + const bool exactNeedsMethodContext = + ((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; // 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. @@ -7643,8 +7669,9 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, } addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs, - clsAttrs, likelyHood, dvInfo.needsMethodContext, - dvInfo.isInstantiatingStub, baseMethod, originalContext); + clsAttrs, likelyHood, exactNeedsMethodContext, + impDevirtualizedCallHasConstInstParam(dvInfo), baseMethod, + originalContext); } if (call->GetInlineCandidatesCount() == numExactClasses) @@ -7716,8 +7743,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, likelyContext = dvInfo.exactContext; likelyMethod = dvInfo.devirtualizedMethod; - needsMethodContext = dvInfo.needsMethodContext; - instantiatingStub = dvInfo.isInstantiatingStub; + needsMethodContext = ((size_t)likelyContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; + instantiatingStub = impDevirtualizedCallHasConstInstParam(dvInfo); } else { @@ -8721,25 +8748,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) @@ -8788,10 +8813,13 @@ 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 needsCompileTimeLookup = impDevirtualizedCallHasConstInstParam(dvInfo); + const bool needsInstParam = needsRuntimeLookup || needsCompileTimeLookup; if (derivedMethod != nullptr) { @@ -8799,7 +8827,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS) { - assert(!dvInfo.needsMethodContext); + assert(!needsInstParam); derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } else @@ -8807,7 +8835,7 @@ 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(call->IsGenericVirtual(this) || needsInstParam || ((objClassAttribs & CORINFO_FLG_ARRAY) != 0)); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } @@ -8818,54 +8846,30 @@ 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; - if (dvInfo.isInstantiatingStub) + if (derivedMethod != nullptr && needsInstParam) { - // 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. - // - CallArg* const instParam = call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam); - if (instParam != nullptr) + if (needsCompileTimeLookup) { - assert(!"unexpected inst param in virtual/interface call"); - return; - } + // We should only end up with generic methods that need a method context (eg. array interface, GVM). + // + 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) - { - JITDUMP("Array interface devirt: array type is inexact, sorry.\n"); - return; + // 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()); + + instantiatingStub = (CORINFO_METHOD_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } - // 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)); + assert(!needsCompileTimeLookup || (instantiatingStub != NO_METHOD_HANDLE)); } // If we failed to get a method handle, we can't directly devirtualize. @@ -8896,18 +8900,18 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, { note = "final method"; } - if (dvInfo.isInstantiatingStub) + if (needsInstParam) { - instArg = " [instantiating stub]"; + instArg = needsCompileTimeLookup ? eeGetMethodFullName(instantiatingStub) : "runtime lookup"; } 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 ? ", instantiating stub: " : "", instArg); gtDispTree(call); } } @@ -8946,6 +8950,39 @@ 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 (needsCompileTimeLookup) + { + compileTimeHandle = instantiatingStub; + } + + CORINFO_RESOLVED_TOKEN* lookupToken = + (pDerivedResolvedToken->tokenScope != nullptr) ? pDerivedResolvedToken : pResolvedToken; + + if ((lookupToken == nullptr) && dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) + { + JITDUMP("Cannot perform runtime instantiation lookup without a resolved token, sorry.\n"); + return; + } + + GenTree* instParam = + getLookupTree(lookupToken, &dvInfo.instParamLookup, GTF_ICON_METHOD_HDL, compileTimeHandle); + + if (instParam == nullptr) + { + // If we're inlining, impLookupToTree can return nullptr after recording a fatal observation. + JITDUMP("Failed to materialize instantiation argument for devirtualized call, sorry.\n"); + return; + } + + call->gtArgs.InsertInstParam(this, instParam); + } + // All checks done. Time to transform the call. // assert(canDevirtualize); @@ -8953,17 +8990,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; @@ -8993,8 +9019,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 ? ", instantiating stub: " : "", instArg); } // If we successfully devirtualized based on an exact or final class, diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index 08b1b5a3515013..07175f4588027b 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -637,6 +637,7 @@ struct LateDevirtualizationInfo CORINFO_METHOD_HANDLE methodHnd; CORINFO_CONTEXT_HANDLE exactContextHnd; InlineContext* inlinersContext; + CORINFO_RESOLVED_TOKEN resolvedToken; }; // InlArgInfo describes inline candidate argument properties. diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 5d8075ec7236d3..55d000ecedaad3 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5580,21 +5580,29 @@ GenTree* Compiler::getRuntimeLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_LOOKUP* pLookup, void* compileTimeHandle) { - assert(!compIsForInlining()); - CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; + GenTree* result = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); + // If pRuntimeLookup->indirections is equal to CORINFO_USEHELPER, it specifies that a run-time helper should be // used; otherwise, it specifies the number of indirections via pRuntimeLookup->offsets array. if ((pRuntimeLookup->indirections == CORINFO_USEHELPER) || (pRuntimeLookup->indirections == CORINFO_USENULL) || pRuntimeLookup->testForNull) { - return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, - getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind), - compileTimeHandle); - } +#ifdef FEATURE_READYTORUN + if (pRuntimeLookup->indirections == CORINFO_USENULL) + { + return gtNewIconNode(0, TYP_I_IMPL); + } - GenTree* result = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); + if (IsAot()) + { + return impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE, TYP_I_IMPL, + &pLookup->lookupKind, result); + } +#endif + return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, result, compileTimeHandle); + } ArrayStack stmts(getAllocator(CMK_ArrayStack)); diff --git a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs index ac544ed02e3fea..0ceb7197625faf 100644 --- a/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs +++ b/src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs @@ -219,13 +219,6 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType } } - if (impl != null && impl.HasInstantiation && impl.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Specific)) - { - // We don't support devirtualization of shared generic virtual methods yet. - devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; - impl = null; - } - return impl; } diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index f678e38bb1cf0b..6fb4ba44ba855b 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -121,6 +121,7 @@ public enum DictionaryEntryKind DispatchStubAddrSlot = 5, FieldDescSlot = 6, DeclaringTypeHandleSlot = 7, + DevirtualizedMethodDescSlot = 8, } public enum ReadyToRunFixupKind diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index c532ddb8564778..9d933e789eb647 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1339,14 +1339,27 @@ private void getEHinfo(CORINFO_METHOD_STRUCT_* ftn, uint EHnumber, ref CORINFO_E return ObjectToHandle(m.OwningType); } + private static bool IsCanonicalSubtypeInstantiation(Instantiation instantiation) + { + foreach (TypeDesc type in instantiation) + { + if (type.IsCanonicalSubtype(CanonicalFormKind.Specific)) + { + return true; + } + } + + return false; + } + + private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) { // Initialize OUT fields 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); @@ -1477,6 +1490,50 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) info->resolvedTokenDevirtualizedUnboxedMethod = default(CORINFO_RESOLVED_TOKEN); } + bool isArrayInterfaceDevirtualization = objType.IsArray && decl.OwningType.IsInterface; + bool isGenericVirtual = decl.HasInstantiation; + + if (isGenericVirtual) + { + bool requiresRuntimeLookup = IsCanonicalSubtypeInstantiation(originalImpl.Instantiation); + if (requiresRuntimeLookup) + { + if (info->pResolvedTokenVirtualMethod == null) + { + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + return false; + } + +#if READYTORUN + ComputeRuntimeLookupForSharedGenericToken( + Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, + ref info->resolvedTokenDevirtualizedMethod, + null, + originalImpl, + MethodBeingCompiled, + ref info->instParamLookup); +#else + // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP; + return false; +#endif + } + } + + if (!info->instParamLookup.lookupKind.needsRuntimeLookup && + (isArrayInterfaceDevirtualization || isGenericVirtual) && + impl.IsCanonicalMethod(CanonicalFormKind.Specific)) + { +#if READYTORUN + MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodHandle, originalImplWithToken)); +#else + // TODO: Implement generic virtual method devirtualization constant lookup for NativeAOT + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; +#endif + } + #if READYTORUN // Testing has not shown that concerns about virtual matching are significant // Only generate verification for builds with the stress mode enabled @@ -1491,8 +1548,7 @@ 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); + info->exactContext = (isArrayInterfaceDevirtualization || isGenericVirtual) ? contextFromMethod(originalImpl) : contextFromType(owningType); return true; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index e72852d5a14ce0..32652fe9afa19a 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1176,19 +1176,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/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 42fb1148659daf..9c482c268e6ea9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -919,17 +919,30 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref constrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *(CORINFO_RESOLVED_TOKEN*)pGenericLookupKind.runtimeLookupArgs); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, constrainedType); } - object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + ref CORINFO_RESOLVED_TOKEN helperArgToken = ref pResolvedToken; + if (helperId == ReadyToRunHelperId.MethodHandle && pGenericLookupKind.runtimeLookupArgs != null) + { + helperArgToken = ref *(CORINFO_RESOLVED_TOKEN*)pGenericLookupKind.runtimeLookupArgs; + } + + object helperArg = GetRuntimeDeterminedObjectForToken(ref helperArgToken); if (helperArg is MethodDesc methodDesc) { - var methodIL = HandleToObject(pResolvedToken.tokenScope); + var methodIL = HandleToObject(helperArgToken.tokenScope); MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + if (helperArgToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod) + { + helperArg = ComputeMethodWithToken(HandleToObject(pResolvedToken.hMethod), ref helperArgToken, constrainedType, false); + } + else + { + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref helperArgToken), constrainedType, unboxing: false, context: sharedMethod); + } } else if (helperArg is FieldDesc fieldDesc) { - helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref helperArgToken)); } var methodContext = new GenericContext(HandleToObject(callerHandle)); @@ -2627,7 +2640,7 @@ private void ComputeRuntimeLookupForSharedGenericToken( MethodDesc contextMethod = callerHandle; - // 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 (!contextMethod.IsSharedByGenericInstantiations) { ThrowHelper.ThrowInvalidProgramException(); @@ -2660,18 +2673,26 @@ private void ComputeRuntimeLookupForSharedGenericToken( break; case DictionaryEntryKind.MethodDescSlot: + case DictionaryEntryKind.DevirtualizedMethodDescSlot: case DictionaryEntryKind.MethodEntrySlot: case DictionaryEntryKind.ConstrainedMethodEntrySlot: case DictionaryEntryKind.DispatchStubAddrSlot: { - if (entryKind == DictionaryEntryKind.MethodDescSlot) + if (entryKind == DictionaryEntryKind.MethodDescSlot || entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) pResultLookup.lookupKind.runtimeLookupFlags = (ushort)ReadyToRunHelperId.MethodHandle; else if (entryKind == DictionaryEntryKind.MethodEntrySlot || entryKind == DictionaryEntryKind.ConstrainedMethodEntrySlot) pResultLookup.lookupKind.runtimeLookupFlags = (ushort)ReadyToRunHelperId.MethodEntry; else pResultLookup.lookupKind.runtimeLookupFlags = (ushort)ReadyToRunHelperId.VirtualDispatchCell; - pResultLookup.lookupKind.runtimeLookupArgs = pConstrainedResolvedToken; + if (entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) + { + pResultLookup.lookupKind.runtimeLookupArgs = Unsafe.AsPointer(ref pResolvedToken); + } + else + { + pResultLookup.lookupKind.runtimeLookupArgs = pConstrainedResolvedToken; + } break; } diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 9bd169804c0003..82fa638e6e4c6d 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -686,12 +686,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 bfc8aeecfaa774..24ee1d79fb8b7b 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3250,10 +3250,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) { @@ -3278,15 +3277,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) @@ -3306,10 +3304,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/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index e9d15c0fa6bb63..412a1f411facb9 100644 --- a/src/coreclr/vm/genericdict.cpp +++ b/src/coreclr/vm/genericdict.cpp @@ -864,6 +864,7 @@ Dictionary::PopulateEntry( } case MethodDescSlot: + case DevirtualizedMethodDescSlot: case DispatchStubAddrSlot: case MethodEntrySlot: { @@ -1202,7 +1203,7 @@ Dictionary::PopulateEntry( } else { - _ASSERTE(kind == MethodDescSlot); + _ASSERTE((kind == MethodDescSlot) || (kind == DevirtualizedMethodDescSlot)); result = (CORINFO_GENERIC_HANDLE)pMethod; } break; diff --git a/src/coreclr/vm/genericdict.h b/src/coreclr/vm/genericdict.h index be2a0af5569029..16bc515eb19998 100644 --- a/src/coreclr/vm/genericdict.h +++ b/src/coreclr/vm/genericdict.h @@ -58,6 +58,7 @@ enum DictionaryEntryKind DispatchStubAddrSlot = 5, FieldDescSlot = 6, DeclaringTypeHandleSlot = 7, + DevirtualizedMethodDescSlot = 8, }; enum DictionaryEntrySignatureSource : BYTE diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index f4a17d840d5f51..5357c6b0cfd8f6 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3010,7 +3010,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); @@ -3202,11 +3202,19 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr FALLTHROUGH; case MethodDescSlot: + case DevirtualizedMethodDescSlot: case MethodEntrySlot: case DispatchStubAddrSlot: { // Encode containing type - if (pResolvedToken->pTypeSpec != NULL) + if (entryKind == DevirtualizedMethodDescSlot) + { + // For shared GVM devirtualization use the devirtualized method owner type from pTemplateMD. + _ASSERTE(pTemplateMD != NULL); + sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + sigBuilder.AppendPointer(pTemplateMD->GetMethodTable()); + } + else if (pResolvedToken->pTypeSpec != NULL) { SigPointer sigptr(pResolvedToken->pTypeSpec, pResolvedToken->cbTypeSpec); sigptr.ConvertToInternalExactlyOne(pModule, NULL, &sigBuilder); @@ -8585,8 +8593,7 @@ 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)); MethodDesc* pBaseMD = GetMethod(info->virtualMethod); MethodTable* pBaseMT = pBaseMD->GetMethodTable(); @@ -8822,47 +8829,53 @@ 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; - return false; + pDevirtMD = MethodDesc::FindOrCreateAssociatedMethodDesc( + pPrimaryMD, pExactMT, pExactMT->IsValueType() && !pPrimaryMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); + + const bool requiresRuntimeLookup = pDevirtMD->IsWrapperStub() && TypeHandle::IsCanonicalSubtypeInstantiation(pDevirtMD->GetMethodInstantiation()); + if (requiresRuntimeLookup) + { + if (info->pResolvedTokenVirtualMethod == nullptr) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + + ComputeRuntimeLookupForSharedGenericToken(DevirtualizedMethodDescSlot, + info->pResolvedTokenVirtualMethod, + nullptr, + pDevirtMD, + m_pMethodBeingCompiled, + &info->instParamLookup); + } } 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..689531b2512a42 100644 --- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -355,96 +355,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); } }