diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 777f67f03aa393..7724b80d730a0e 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1558,6 +1558,7 @@ enum CORINFO_DEVIRTUALIZATION_DETAIL CORINFO_DEVIRTUALIZATION_FAILED_DUPLICATE_INTERFACE, // crossgen2 virtual method algorithm and runtime algorithm differ in the presence of duplicate interface implementations CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE, // Decl method cannot be represented in R2R image CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE, // Support for type equivalence in devirtualization is not yet implemented in crossgen2 + CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL, // Devirtualization of generic virtual methods is not yet implemented in crossgen2 CORINFO_DEVIRTUALIZATION_COUNT, // sentinel for maximum value }; @@ -1580,7 +1581,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO // - 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 - // - wasArrayInterfaceDevirt is set TRUE for array interface method devirtualization + // - mayNeedMethodContext 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) // CORINFO_METHOD_HANDLE devirtualizedMethod; @@ -1589,7 +1590,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; bool isInstantiatingStub; - bool wasArrayInterfaceDevirt; + bool mayNeedMethodContext; }; //---------------------------------------------------------------------------- diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index dc436ed0ff9e82..5432bdbc210453 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -10590,6 +10590,8 @@ const char* Compiler::devirtualizationDetailToString(CORINFO_DEVIRTUALIZATION_DE return "Decl method cannot be represented in R2R image"; case CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE: return "Support for type equivalence in devirtualization is not yet implemented in crossgen2"; + case CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL: + return "Devirtualization of generic virtual methods is not yet implemented in crossgen2"; default: return "undefined"; } diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index ec41df22af9671..5b1f058ea848b0 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -551,7 +551,7 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtCallMethHnd; } - else + else if (call->IsGenericVirtual(m_compiler)) { GenTree* runtimeMethHndNode = call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle)->GetNode(); @@ -563,11 +563,12 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorAsIntCon()->IconValue()); default: - assert(!"Unexpected type in RuntimeMethodHandle arg."); + // Unable to get method handle for devirtualization. + // This can happen if the method handle is not an RUNTIMELOOKUP or CNS_INT for generic virtuals, return nullptr; } - return nullptr; } + return nullptr; } //------------------------------------------------------------------------ @@ -612,9 +613,13 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorOperIs(GT_CALL)) { - GenTreeCall* call = tree->AsCall(); - // TODO-CQ: Drop `call->gtCallType == CT_USER_FUNC` once we have GVM devirtualization - bool tryLateDevirt = call->IsDevirtualizationCandidate(m_compiler) && (call->gtCallType == CT_USER_FUNC); + GenTreeCall* call = tree->AsCall(); + bool tryLateDevirt = call->IsDevirtualizationCandidate(m_compiler); + if (tryLateDevirt && call->gtCallType == CT_INDIRECT) + { + // For indirect calls, we can only late devirt if it's a generic virtual method for now. + tryLateDevirt = call->IsGenericVirtual(m_compiler); + } #ifdef DEBUG tryLateDevirt = tryLateDevirt && (JitConfig.JitEnableLateDevirtualization() == 1); @@ -637,6 +642,16 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorIsTailPrefixedCall(); + if (method == nullptr) + { + assert(!call->IsVirtual()); + // Unable to get method handle for devirtualization. + // This can happen if the method handle is not an RUNTIMELOOKUP or CNS_INT for generic virtuals, + // e.g. a local variable holding the method handle. + // Just bail out. + return; + } + CORINFO_CONTEXT_HANDLE contextInput = context; context = nullptr; m_compiler->impDevirtualizeCall(call, nullptr, &method, &methodFlags, &contextInput, &context, diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 9682ba4a42df8d..12783afa5023d1 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -2374,9 +2374,7 @@ int GenTreeCall::GetNonStandardAddedArgCount(Compiler* compiler) const // bool GenTreeCall::IsDevirtualizationCandidate(Compiler* compiler) const { - return IsVirtual() || - (gtCallType == CT_INDIRECT && (gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_VIRTUAL_FUNC_PTR) || - gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_GVMLOOKUP_FOR_SLOT))); + return IsVirtual() || IsGenericVirtual(compiler); } //------------------------------------------------------------------------- diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 0726b9b9f8846f..ac88f63b1ce317 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -5260,6 +5260,11 @@ struct GenTreeCall final : public GenTree { return (gtFlags & GTF_CALL_VIRT_KIND_MASK) == GTF_CALL_VIRT_VTABLE; } + bool IsGenericVirtual(Compiler* compiler) const + { + return (gtCallType == CT_INDIRECT && (gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_VIRTUAL_FUNC_PTR) || + gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_GVMLOOKUP_FOR_SLOT))); + } bool IsDevirtualizationCandidate(Compiler* compiler) const; diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 22478354a3ab8d..1f3d8f4829ede4 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -417,11 +417,20 @@ var_types Compiler::impImportCall(OPCODE opcode, thisPtr = impTransformThis(thisPtr, pConstrainedResolvedToken, callInfo->thisTransform); assert(thisPtr != nullptr); + GenTree* origThisPtr = thisPtr; + // Clone the (possibly transformed) "this" pointer GenTree* thisPtrCopy; thisPtr = impCloneExpr(thisPtr, &thisPtrCopy, CHECK_SPILL_ALL, nullptr DEBUGARG("LDVIRTFTN this pointer")); + // We cloned the "this" pointer, mark it as a single def and set the class for it + if (thisPtr->OperIsLocal() && thisPtr->TypeIs(TYP_REF) && (origThisPtr != thisPtr)) + { + lvaGetDesc(thisPtr->AsLclVarCommon())->lvSingleDef = 1; + lvaSetClass(thisPtr->AsLclVarCommon()->GetLclNum(), origThisPtr); + } + GenTree* fptr = impImportLdvirtftn(thisPtr, pResolvedToken, callInfo); assert(fptr != nullptr); @@ -448,10 +457,11 @@ var_types Compiler::impImportCall(OPCODE opcode, } #endif - // Sine we are jumping over some code, check that its OK to skip that code + // Since we are jumping over some code, check that its OK to skip that code assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG && (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG); - goto DONE; + + goto DEVIRT; } case CORINFO_CALL: @@ -968,6 +978,8 @@ var_types Compiler::impImportCall(OPCODE opcode, } } +DEVIRT: + bool probing; probing = impConsiderCallProbe(call->AsCall(), rawILOffset); @@ -7606,7 +7618,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, } addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs, - clsAttrs, likelyHood, dvInfo.wasArrayInterfaceDevirt, + clsAttrs, likelyHood, dvInfo.mayNeedMethodContext, dvInfo.isInstantiatingStub, baseMethod, originalContext); } @@ -7630,11 +7642,11 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // Iterate over the guesses for (int candidateId = 0; candidateId < candidatesCount; candidateId++) { - CORINFO_CLASS_HANDLE likelyClass = likelyClasses[candidateId]; - CORINFO_METHOD_HANDLE likelyMethod = likelyMethods[candidateId]; - unsigned likelihood = likelihoods[candidateId]; - bool arrayInterface = false; - bool instantiatingStub = false; + CORINFO_CLASS_HANDLE likelyClass = likelyClasses[candidateId]; + CORINFO_METHOD_HANDLE likelyMethod = likelyMethods[candidateId]; + unsigned likelihood = likelihoods[candidateId]; + bool mayNeedMethodContext = false; + bool instantiatingStub = false; CORINFO_CONTEXT_HANDLE likelyContext = originalContext; @@ -7677,10 +7689,10 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, break; } - likelyContext = dvInfo.exactContext; - likelyMethod = dvInfo.devirtualizedMethod; - arrayInterface = dvInfo.wasArrayInterfaceDevirt; - instantiatingStub = dvInfo.isInstantiatingStub; + likelyContext = dvInfo.exactContext; + likelyMethod = dvInfo.devirtualizedMethod; + mayNeedMethodContext = dvInfo.mayNeedMethodContext; + instantiatingStub = dvInfo.isInstantiatingStub; } else { @@ -7755,7 +7767,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // Add this as a potential candidate. // addGuardedDevirtualizationCandidate(call, likelyMethod, likelyClass, likelyContext, likelyMethodAttribs, - likelyClassAttribs, likelihood, arrayInterface, instantiatingStub, + likelyClassAttribs, likelihood, mayNeedMethodContext, instantiatingStub, baseMethod, originalContext); } } @@ -7781,7 +7793,7 @@ 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 -// arrayInterface - devirtualization of an array interface call +// mayNeedMethodContext - devirtualized method may need generic method context (e.g. array interfaces) // instantiatingStub - devirtualized method in an instantiating stub // originalMethodHandle - method handle of base method (before devirt) // originalContextHandle - context for the original call @@ -7793,7 +7805,7 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, unsigned methodAttr, unsigned classAttr, unsigned likelihood, - bool arrayInterface, + bool mayNeedMethodContext, bool instantiatingStub, CORINFO_METHOD_HANDLE originalMethodHandle, CORINFO_CONTEXT_HANDLE originalContextHandle) @@ -7872,7 +7884,7 @@ void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, pInfo->originalContextHandle = originalContextHandle; pInfo->likelihood = likelihood; pInfo->exactContextHandle = contextHandle; - pInfo->arrayInterface = arrayInterface; + pInfo->mayNeedMethodContext = mayNeedMethodContext; // If the guarded method is an instantiating stub, find the instantiated method // @@ -8606,6 +8618,12 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, &rootCompiler->info.compMethodInfo->args); #endif // DEBUG + if ((JitConfig.JitEnableGenericVirtualDevirtualization() == 0) && call->IsGenericVirtual(this)) + { + JITDUMP("\nimpDevirtualizeCall: generic virtual devirtualization disabled\n"); + return; + } + // Fetch information about the virtual method we're calling. CORINFO_METHOD_HANDLE baseMethod = *method; unsigned baseMethodAttribs = *methodFlags; @@ -8637,6 +8655,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, #endif // DEBUG } + // Is the call a generic virtual method call? + const bool isGenericVirtual = call->IsGenericVirtual(this); + // In R2R mode, we might see virtual stub calls to // non-virtuals. For instance cases where the non-virtual method // is in a different assembly but is called via CALLVIRT. For @@ -8646,7 +8667,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // In non-R2R modes CALLVIRT will be turned into a // regular call+nullcheck by normal call importation. // - if ((baseMethodAttribs & CORINFO_FLG_VIRTUAL) == 0) + if (!isGenericVirtual && ((baseMethodAttribs & CORINFO_FLG_VIRTUAL) == 0)) { assert(call->IsVirtualStub()); assert(IsAot()); @@ -8740,7 +8761,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // It may or may not know enough to devirtualize... if (isInterface) { - assert(call->IsVirtualStub()); + assert(call->IsVirtualStub() || isGenericVirtual); JITDUMP("--- base class is interface\n"); } @@ -8770,14 +8791,14 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS) { - assert(!dvInfo.wasArrayInterfaceDevirt); + assert(!dvInfo.mayNeedMethodContext); derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK); } else { // Array interface devirt can return a nonvirtual generic method of the non-generic SZArrayHelper class. // - assert(dvInfo.wasArrayInterfaceDevirt); + assert(isGenericVirtual || dvInfo.mayNeedMethodContext); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } @@ -8798,9 +8819,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (dvInfo.isInstantiatingStub) { - // We should only end up with generic methods that needs a method context (eg. array interface). + // We should only end up with generic methods that needs a method context (eg. array interface, GVM). // - assert(dvInfo.wasArrayInterfaceDevirt); + assert(dvInfo.mayNeedMethodContext); // We don't expect NAOT to end up here, since it has Array // and normal devirtualization. @@ -8923,6 +8944,42 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP(" %s; can devirtualize\n", note); + if (dvInfo.isInstantiatingStub) + { + if (isGenericVirtual) + { + // This generic method call requires a generic method context. + // Pass the method handle as the inst param arg. + // + assert(call->gtCallAddr->IsCall()); + CallArg* const methHndArg = + call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle); + assert(methHndArg != nullptr); + GenTree* methHnd = methHndArg->GetEarlyNode(); + if (methHnd->OperIs(GT_RUNTIMELOOKUP)) + { + // We need to do runtime lookup instead of using the instantiating stub directly. + call->gtArgs.InsertInstParam(this, methHnd); + } + else + { + // We can pass the instantiating stub directly as we don't need a runtime lookup. + GenTree* const instParam = gtNewIconEmbMethHndNode(instantiatingStub); + call->gtArgs.InsertInstParam(this, instParam); + } + } + else + { + // 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; @@ -8931,17 +8988,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, call->gtControlExpr = nullptr; INDEBUG(call->gtCallDebugFlags |= GTF_CALL_MD_DEVIRTUALIZED); - 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); - } - // Virtual calls include an implicit null check, which we may // now need to make explicit. if (!objIsNonNull) @@ -9801,7 +9847,7 @@ void Compiler::impCheckCanInline(GenTreeCall* call, pInfo->originalMethodHandle = nullptr; pInfo->originalContextHandle = nullptr; pInfo->likelihood = 0; - pInfo->arrayInterface = false; + pInfo->mayNeedMethodContext = false; } pInfo->methInfo = methInfo; diff --git a/src/coreclr/jit/indirectcalltransformer.cpp b/src/coreclr/jit/indirectcalltransformer.cpp index fd4ee8c5777908..314f2102c6c5ad 100644 --- a/src/coreclr/jit/indirectcalltransformer.cpp +++ b/src/coreclr/jit/indirectcalltransformer.cpp @@ -970,10 +970,9 @@ class IndirectCallTransformer CORINFO_CONTEXT_HANDLE context = inlineInfo->exactContextHandle; if (clsHnd != NO_CLASS_HANDLE) { - // If we devirtualized an array interface call, - // pass the original method handle and original context handle to the devirtualizer. + // Pass the original method handle and original context handle to the devirtualizer if needed. // - if (inlineInfo->arrayInterface) + if (inlineInfo->mayNeedMethodContext) { methodHnd = inlineInfo->originalMethodHandle; context = inlineInfo->originalContextHandle; diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index a89be81fa68895..fcada89e922cc5 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -598,7 +598,7 @@ struct InlineCandidateInfo : public HandleHistogramProfileCandidateInfo CORINFO_METHOD_HANDLE guardedMethodUnboxedEntryHandle; CORINFO_METHOD_HANDLE guardedMethodInstantiatedEntryHandle; unsigned likelihood; - bool arrayInterface; + bool mayNeedMethodContext; CORINFO_METHOD_INFO methInfo; diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 3a9188a3e1eb0f..15dd8f3dcd1e28 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -126,6 +126,9 @@ CONFIG_STRING(JitInlineMethodsWithEHRange, "JitInlineMethodsWithEHRange") CONFIG_INTEGER(JitLongAddress, "JitLongAddress", 0) // Force using the large pseudo instruction form for long address CONFIG_INTEGER(JitMaxUncheckedOffset, "JitMaxUncheckedOffset", 8) +// Enable devirtualization for generic virtual methods +RELEASE_CONFIG_INTEGER(JitEnableGenericVirtualDevirtualization, "JitEnableGenericVirtualDevirtualization", 1) + // // MinOpts // diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index f5c2a50d6f0a2c..97703389baee41 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1324,7 +1324,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) info->exactContext = null; info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; info->isInstantiatingStub = false; - info->wasArrayInterfaceDevirt = false; + info->mayNeedMethodContext = false; TypeDesc objType = HandleToObject(info->objClass); @@ -1340,7 +1340,12 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) // Transform from the unboxing thunk to the normal method decl = decl.IsUnboxingThunk() ? decl.GetUnboxedMethod() : decl; - Debug.Assert(!decl.HasInstantiation); + if (decl.HasInstantiation) + { + // We cannot devirtualize generic virtual methods in AOT yet + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL; + return false; + } if ((info->context != null) && decl.OwningType.IsInterface) { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 9ebd407d86c006..4d9e63dad9fc5a 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1138,6 +1138,7 @@ public enum CORINFO_DEVIRTUALIZATION_DETAIL CORINFO_DEVIRTUALIZATION_FAILED_DUPLICATE_INTERFACE, // crossgen2 virtual method algorithm and runtime algorithm differ in the presence of duplicate interface implementations CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE, // Decl method cannot be represented in R2R image CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE, // Support for type equivalence in devirtualization is not yet implemented in crossgen2 + CORINFO_DEVIRTUALIZATION_FAILED_GENERIC_VIRTUAL, // Devirtualization of generic virtual methods is not yet implemented in crossgen2 CORINFO_DEVIRTUALIZATION_COUNT, // sentinel for maximum value } @@ -1158,7 +1159,7 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO // - 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 - // - wasArrayInterfaceDevirt is set TRUE for array interface method devirtualization + // - mayNeedMethodContext 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) // public CORINFO_METHOD_STRUCT_* devirtualizedMethod; @@ -1168,8 +1169,8 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO public CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; public byte _isInstantiatingStub; public bool isInstantiatingStub { get { return _isInstantiatingStub != 0; } set { _isInstantiatingStub = value ? (byte)1 : (byte)0; } } - public byte _wasArrayInterfaceDevirt; - public bool wasArrayInterfaceDevirt { get { return _wasArrayInterfaceDevirt != 0; } set { _wasArrayInterfaceDevirt = value ? (byte)1 : (byte)0; } } + public byte _mayNeedMethodContext; + public bool mayNeedMethodContext { get { return _mayNeedMethodContext != 0; } set { _mayNeedMethodContext = value ? (byte)1 : (byte)0; } } } //---------------------------------------------------------------------------- diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 955f1253ee504c..a3670db34403ee 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -681,7 +681,7 @@ struct Agnostic_ResolveVirtualMethodResult bool returnValue; DWORDLONG devirtualizedMethod; bool isInstantiatingStub; - bool wasArrayInterfaceDevirt; + bool mayNeedMethodContext; DWORDLONG exactContext; DWORD detail; Agnostic_CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 9dbb8b8d6a40ff..e2f5090d4ad05f 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3276,7 +3276,7 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info result.isInstantiatingStub = info->isInstantiatingStub; result.exactContext = CastHandle(info->exactContext); result.detail = (DWORD) info->detail; - result.wasArrayInterfaceDevirt = info->wasArrayInterfaceDevirt; + result.mayNeedMethodContext = info->mayNeedMethodContext; if (returnValue) { @@ -3301,11 +3301,11 @@ 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, wasArrayInterfaceDevirt-%s, exactContext-%016" PRIX64 ", detail-%d, tokDvMeth{%s}, tokDvUnboxMeth{%s}", + printf(", value returnValue-%s, devirtMethod-%016" PRIX64 ", instantiatingStub-%s, mayNeedMethodContext-%s, exactContext-%016" PRIX64 ", detail-%d, tokDvMeth{%s}, tokDvUnboxMeth{%s}", result.returnValue ? "true" : "false", result.devirtualizedMethod, result.isInstantiatingStub ? "true" : "false", - result.wasArrayInterfaceDevirt ? "true" : "false", + result.mayNeedMethodContext ? "true" : "false", result.exactContext, result.detail, result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedMethod).c_str() : "???", @@ -3330,7 +3330,7 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) result.devirtualizedMethod; info->isInstantiatingStub = result.isInstantiatingStub; - info->wasArrayInterfaceDevirt = result.wasArrayInterfaceDevirt; + info->mayNeedMethodContext = result.mayNeedMethodContext; info->exactContext = (CORINFO_CONTEXT_HANDLE) result.exactContext; info->detail = (CORINFO_DEVIRTUALIZATION_DETAIL) result.detail; if (result.returnValue) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 520fba235a8aba..23fc88e88a06d3 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8568,7 +8568,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) memset(&info->resolvedTokenDevirtualizedMethod, 0, sizeof(info->resolvedTokenDevirtualizedMethod)); memset(&info->resolvedTokenDevirtualizedUnboxedMethod, 0, sizeof(info->resolvedTokenDevirtualizedUnboxedMethod)); info->isInstantiatingStub = false; - info->wasArrayInterfaceDevirt = false; + info->mayNeedMethodContext = false; MethodDesc* pBaseMD = GetMethod(info->virtualMethod); MethodTable* pBaseMT = pBaseMD->GetMethodTable(); @@ -8576,9 +8576,6 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) // Method better be from a fully loaded class _ASSERTE(pBaseMT->IsFullyLoaded()); - //@GENERICS: shouldn't be doing this for instantiated methods as they live elsewhere - _ASSERTE(!pBaseMD->HasMethodInstantiation()); - // Method better be virtual _ASSERTE(pBaseMD->IsVirtual()); @@ -8786,6 +8783,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) MethodTable* pApproxMT = pDevirtMD->GetMethodTable(); MethodTable* pExactMT = pApproxMT; bool isArray = false; + bool isGenericVirtual = false; if (pApproxMT->IsInterface()) { @@ -8802,6 +8800,31 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) { pExactMT = pDevirtMD->GetExactDeclaringType(pObjMT); } + + // This is generic virtual method devirtualization. + if (!isArray && pBaseMD->HasMethodInstantiation()) + { + pDevirtMD = pDevirtMD->FindOrCreateAssociatedMethodDesc( + pDevirtMD, pExactMT, pExactMT->IsValueType() && !pDevirtMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); + + // If the generic virtual method requires a runtime lookup, and we devirted to a class that also + // requires a runtime lookup, we can't handle this case yet because we don't have the right generic context. + // For example: Base.M devirted to Derived.M, where both T and U are shared generic. + // In this case we would need both the T and U be part of the context to be able to do the lookup. + // TODO-CQ: Fix this limitation. + if (pDevirtMD->IsInstantiatingStub()) + { + MethodDesc* instantiatingStub = pDevirtMD->GetWrappedMethodDesc(); + if (instantiatingStub->GetMethodTable()->IsSharedByGenericInstantiations() + && instantiatingStub->IsSharedByGenericMethodInstantiations()) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + } + + isGenericVirtual = true; + } // Success! Pass back the results. // @@ -8812,13 +8835,19 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) // info->isInstantiatingStub = pDevirtMD->IsInstantiatingStub(); info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); - info->wasArrayInterfaceDevirt = true; + info->mayNeedMethodContext = true; + } + else if (isGenericVirtual) + { + // We only need the method context when the devirted generic method is a shared generic method + info->mayNeedMethodContext = info->isInstantiatingStub = pDevirtMD->IsInstantiatingStub(); + info->exactContext = MAKE_METHODCONTEXT((CORINFO_METHOD_HANDLE) pDevirtMD); } else { info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); info->isInstantiatingStub = false; - info->wasArrayInterfaceDevirt = false; + info->mayNeedMethodContext = false; } info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) pDevirtMD; diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs new file mode 100644 index 00000000000000..c971d07f273a4e --- /dev/null +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -0,0 +1,706 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class GenericVirtualMethodTests +{ + [Fact] + public static void ClassBase_NonGenericDerived_AllVariants() + { + ValidateCaller("ClassBase_NonGenericDerived_Inlining", new ClassBaseCaller(new ClassBase_NonGenericDerived_Inlining())); + ValidateCaller("ClassBase_NonGenericDerived_NoInlining", new ClassBaseCaller(new ClassBase_NonGenericDerived_NoInlining())); + } + + [Fact] + public static void ClassBase_GenericDerived_AllVariants() + { + ValidateCaller("ClassBase_GenericDerived_Inlining_Int", new ClassBaseCaller(new ClassBase_GenericDerived_Inlining())); + ValidateCaller("ClassBase_GenericDerived_Inlining_String", new ClassBaseCaller(new ClassBase_GenericDerived_Inlining())); + ValidateCaller("ClassBase_GenericDerived_NoInlining_Int", new ClassBaseCaller(new ClassBase_GenericDerived_NoInlining())); + ValidateCaller("ClassBase_GenericDerived_NoInlining_String", new ClassBaseCaller(new ClassBase_GenericDerived_NoInlining())); + } + + [Fact] + public static void GenericClassBase_NonGenericDerived_AllVariants() + { + ValidateCaller("GenericClassBase_NonGenericDerived_Inlining_NonShared", new GenericClassBaseCaller(new GenericClassBase_NonGenericDerived_Inlining_NonShared())); + ValidateCaller("GenericClassBase_NonGenericDerived_Inlining_Shared", new GenericClassBaseCaller(new GenericClassBase_NonGenericDerived_Inlining_Shared())); + ValidateCaller("GenericClassBase_NonGenericDerived_NoInlining_NonShared", new GenericClassBaseCaller(new GenericClassBase_NonGenericDerived_NoInlining_NonShared())); + ValidateCaller("GenericClassBase_NonGenericDerived_NoInlining_Shared", new GenericClassBaseCaller(new GenericClassBase_NonGenericDerived_NoInlining_Shared())); + } + + [Fact] + public static void GenericClassBase_GenericDerived_InliningVariants() + { + ValidateCaller("GenericClassBase_GenericDerived_Inlining_Int_Int", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_Inlining())); + ValidateCaller("GenericClassBase_GenericDerived_Inlining_Int_String", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_Inlining())); + ValidateCaller("GenericClassBase_GenericDerived_Inlining_String_Int", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_Inlining())); + ValidateCaller("GenericClassBase_GenericDerived_Inlining_String_String", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_Inlining())); + } + + [Fact] + public static void GenericClassBase_GenericDerived_NoInliningVariants() + { + ValidateCaller("GenericClassBase_GenericDerived_NoInlining_Int_Int", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_NoInlining())); + ValidateCaller("GenericClassBase_GenericDerived_NoInlining_Int_String", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_NoInlining())); + ValidateCaller("GenericClassBase_GenericDerived_NoInlining_String_Int", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_NoInlining())); + ValidateCaller("GenericClassBase_GenericDerived_NoInlining_String_String", new GenericClassBaseCaller(new GenericClassBase_GenericDerived_NoInlining())); + } + + [Fact] + public static void InterfaceBase_NonGenericClassDerived_AllVariants() + { + ValidateCaller("InterfaceBase_NonGenericClassDerived_Inlining", new InterfaceBaseCaller(new InterfaceBase_NonGenericClassDerived_Inlining())); + ValidateCaller("InterfaceBase_NonGenericClassDerived_NoInlining", new InterfaceBaseCaller(new InterfaceBase_NonGenericClassDerived_NoInlining())); + } + + [Fact] + public static void InterfaceBase_NonGenericStructDerived_AllVariants() + { + ValidateCaller("InterfaceBase_NonGenericStructDerived_Inlining", new InterfaceBaseCaller(new InterfaceBase_NonGenericStructDerived_Inlining())); + ValidateCaller("InterfaceBase_NonGenericStructDerived_NoInlining", new InterfaceBaseCaller(new InterfaceBase_NonGenericStructDerived_NoInlining())); + } + + [Fact] + public static void InterfaceBase_GenericClassDerived_AllVariants() + { + ValidateCaller("InterfaceBase_GenericClassDerived_Inlining_Int", new InterfaceBaseCaller(new InterfaceBase_GenericClassDerived_Inlining())); + ValidateCaller("InterfaceBase_GenericClassDerived_Inlining_String", new InterfaceBaseCaller(new InterfaceBase_GenericClassDerived_Inlining())); + ValidateCaller("InterfaceBase_GenericClassDerived_NoInlining_Int", new InterfaceBaseCaller(new InterfaceBase_GenericClassDerived_NoInlining())); + ValidateCaller("InterfaceBase_GenericClassDerived_NoInlining_String", new InterfaceBaseCaller(new InterfaceBase_GenericClassDerived_NoInlining())); + } + + [Fact] + public static void InterfaceBase_GenericStructDerived_AllVariants() + { + ValidateCaller("InterfaceBase_GenericStructDerived_Inlining_Int", new InterfaceBaseCaller(new InterfaceBase_GenericStructDerived_Inlining())); + ValidateCaller("InterfaceBase_GenericStructDerived_Inlining_String", new InterfaceBaseCaller(new InterfaceBase_GenericStructDerived_Inlining())); + ValidateCaller("InterfaceBase_GenericStructDerived_NoInlining_Int", new InterfaceBaseCaller(new InterfaceBase_GenericStructDerived_NoInlining())); + ValidateCaller("InterfaceBase_GenericStructDerived_NoInlining_String", new InterfaceBaseCaller(new InterfaceBase_GenericStructDerived_NoInlining())); + } + + [Fact] + public static void GenericInterfaceBase_NonGenericClassDerived_AllVariants() + { + ValidateCaller("GenericInterfaceBase_NonGenericClassDerived_Inlining_NonShared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericClassDerived_Inlining_NonShared())); + ValidateCaller("GenericInterfaceBase_NonGenericClassDerived_Inlining_Shared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericClassDerived_Inlining_Shared())); + ValidateCaller("GenericInterfaceBase_NonGenericClassDerived_NoInlining_NonShared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericClassDerived_NoInlining_NonShared())); + ValidateCaller("GenericInterfaceBase_NonGenericClassDerived_NoInlining_Shared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericClassDerived_NoInlining_Shared())); + } + + [Fact] + public static void GenericInterfaceBase_NonGenericStructDerived_AllVariants() + { + ValidateCaller("GenericInterfaceBase_NonGenericStructDerived_Inlining_NonShared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericStructDerived_Inlining_NonShared())); + ValidateCaller("GenericInterfaceBase_NonGenericStructDerived_Inlining_Shared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericStructDerived_Inlining_Shared())); + ValidateCaller("GenericInterfaceBase_NonGenericStructDerived_NoInlining_NonShared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericStructDerived_NoInlining_NonShared())); + ValidateCaller("GenericInterfaceBase_NonGenericStructDerived_NoInlining_Shared", new GenericInterfaceBaseCaller(new GenericInterfaceBase_NonGenericStructDerived_NoInlining_Shared())); + } + + [Fact] + public static void GenericInterfaceBase_GenericClassDerived_InliningVariants() + { + ValidateCaller("GenericInterfaceBase_GenericClassDerived_Inlining_Int_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_Inlining())); + ValidateCaller("GenericInterfaceBase_GenericClassDerived_Inlining_Int_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_Inlining())); + ValidateCaller("GenericInterfaceBase_GenericClassDerived_Inlining_String_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_Inlining())); + ValidateCaller("GenericInterfaceBase_GenericClassDerived_Inlining_String_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_Inlining())); + } + + [Fact] + public static void GenericInterfaceBase_GenericClassDerived_NoInliningVariants() + { + ValidateCaller("GenericInterfaceBase_GenericClassDerived_NoInlining_Int_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_NoInlining())); + ValidateCaller("GenericInterfaceBase_GenericClassDerived_NoInlining_Int_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_NoInlining())); + ValidateCaller("GenericInterfaceBase_GenericClassDerived_NoInlining_String_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_NoInlining())); + ValidateCaller("GenericInterfaceBase_GenericClassDerived_NoInlining_String_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericClassDerived_NoInlining())); + } + + [Fact] + public static void GenericInterfaceBase_GenericStructDerived_InliningVariants() + { + ValidateCaller("GenericInterfaceBase_GenericStructDerived_Inlining_Int_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_Inlining())); + ValidateCaller("GenericInterfaceBase_GenericStructDerived_Inlining_Int_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_Inlining())); + ValidateCaller("GenericInterfaceBase_GenericStructDerived_Inlining_String_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_Inlining())); + ValidateCaller("GenericInterfaceBase_GenericStructDerived_Inlining_String_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_Inlining())); + } + + [Fact] + public static void GenericInterfaceBase_GenericStructDerived_NoInliningVariants() + { + ValidateCaller("GenericInterfaceBase_GenericStructDerived_NoInlining_Int_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_NoInlining())); + ValidateCaller("GenericInterfaceBase_GenericStructDerived_NoInlining_Int_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_NoInlining())); + ValidateCaller("GenericInterfaceBase_GenericStructDerived_NoInlining_String_Int", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_NoInlining())); + ValidateCaller("GenericInterfaceBase_GenericStructDerived_NoInlining_String_String", new GenericInterfaceBaseCaller(new GenericInterfaceBase_GenericStructDerived_NoInlining())); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateCaller(string scenarioName, IBaseMethodCaller caller) + { + ValidateNonSharedCall(scenarioName, caller); + ValidateContextNonSharedCall(scenarioName, caller); + ValidateContextSharedCall(scenarioName, caller); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateNonSharedCall(string scenarioName, IBaseMethodCaller caller) + { + // Avoid using interpolated strings to make the IL size smaller for inlining. + // This method must be inlined to properly test GVM devirtualization. + Console.WriteLine("Testing {0}: {1}...", nameof(ValidateNonSharedCall), scenarioName); + var value = scenarioName.Length; + Equal(value, caller.Invoke(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateContextNonSharedCall(string scenarioName, IBaseMethodCaller caller) + { + Console.WriteLine("Testing {0}: {1}...", nameof(ValidateContextNonSharedCall), scenarioName); + var value = scenarioName; + Equal(value, caller.Invoke(value)); + + Equal(value, IconContextBridgeNonShared.SameMethodSameClass(caller, value)); + Equal(value, IconContextBridgeNonShared.SameMethodDifferentClass(caller, value)); + Equal(value, IconContextBridgeNonShared.DifferentMethodSameClass(caller, value)); + Equal(value, IconContextBridgeNonShared.DifferentMethodDifferentClass(caller, value)); + + Equal(value, RuntimeLookupBridgeNonShared.SameClassSameMethod(caller, value)); + Equal(value, RuntimeLookupBridgeNonShared.SameClassDifferentMethod(caller, value)); + Equal(value, RuntimeLookupBridgeNonShared.DifferentClassSameMethod(caller, value)); + Equal(value, RuntimeLookupBridgeNonShared.DifferentClassDifferentMethod(caller, value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidateContextSharedCall(string scenarioName, IBaseMethodCaller caller) + { + Console.WriteLine("Testing {0}: {1}...", nameof(ValidateContextSharedCall), scenarioName); + var value = scenarioName; + Equal(value, caller.Invoke(value)); + + Equal(value, IconContextBridgeShared.SameMethodSameClass(caller, value)); + Equal(value, IconContextBridgeShared.SameMethodDifferentClass(caller, value)); + Equal(value, IconContextBridgeShared.DifferentMethodSameClass(caller, value)); + Equal(value, IconContextBridgeShared.DifferentMethodDifferentClass(caller, value)); + + Equal(value, RuntimeLookupBridgeShared.SameClassSameMethod(caller, value)); + Equal(value, RuntimeLookupBridgeShared.SameClassDifferentMethod(caller, value)); + Equal(value, RuntimeLookupBridgeShared.DifferentClassSameMethod(caller, value)); + Equal(value, RuntimeLookupBridgeShared.DifferentClassDifferentMethod(caller, value)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Equal(T expected, T actual, [CallerArgumentExpression(nameof(actual))] string testcase = "") + { + Console.WriteLine("Validating {0}...", testcase); + Assert.Equal(expected, actual); + } +} + +internal static class IconContextBridgeNonShared +{ + public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.SameMethodSameClass(caller, value); + } + + public static TMethod SameMethodDifferentClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.SameMethodDifferentClass(caller, value); + } + + public static TMethod DifferentMethodSameClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.DifferentMethodSameClass(caller, value); + } + + public static TMethod DifferentMethodDifferentClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.DifferentMethodDifferentClass(caller, value); + } +} + +internal static class IconContextBridgeShared +{ + public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.SameMethodSameClass(caller, value); + } + + public static TMethod SameMethodDifferentClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.SameMethodDifferentClass(caller, value); + } + + public static TMethod DifferentMethodSameClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.DifferentMethodSameClass(caller, value); + } + + public static TMethod DifferentMethodDifferentClass(IBaseMethodCaller caller, TMethod value) + { + return IconContextInvoker.DifferentMethodDifferentClass(caller, value); + } +} + +internal static class IconContextInvoker +{ + public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value) + { + return caller.Invoke(value); + } + + public static TMethod SameMethodDifferentClass(IBaseMethodCaller caller, TMethod value) + { + return IconDifferentClassHelper.SameMethod(caller, value); + } + + public static TMethod DifferentMethodSameClass(IBaseMethodCaller caller, TMethod value) + { + return DifferentMethodSameClassCore(caller, value); + } + + public static TMethod DifferentMethodDifferentClass(IBaseMethodCaller caller, TMethod value) + { + return IconDifferentClassHelper.DifferentMethod(caller, value); + } + + private static TMethod DifferentMethodSameClassCore(IBaseMethodCaller caller, TMethod value) + { + return caller.Invoke(value); + } +} + +internal static class IconDifferentClassHelper +{ + public static TMethod SameMethod(IBaseMethodCaller caller, TMethod value) + { + return IconDifferentClassHost.InvokeSameMethod(caller, value); + } + + public static TMethod DifferentMethod(IBaseMethodCaller caller, TMethod value) + { + return IconDifferentClassHost.InvokeDifferentMethod(caller, value); + } + + private static class IconDifferentClassHost + { + public static T InvokeSameMethod(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } + + public static T InvokeDifferentMethod(IBaseMethodCaller caller, T value) + { + return Inner.Invoke(caller, value); + } + + private static class Inner + { + public static T Invoke(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } + } + } +} + +internal static class RuntimeLookupBridgeNonShared +{ + public static TMethod SameClassSameMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.SameClassSameMethod(caller, value); + } + + public static TMethod SameClassDifferentMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.SameClassDifferentMethod(caller, value); + } + + public static TMethod DifferentClassSameMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.DifferentClassSameMethod(caller, value); + } + + public static TMethod DifferentClassDifferentMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.DifferentClassDifferentMethod(caller, value); + } +} + +internal static class RuntimeLookupBridgeShared +{ + public static TMethod SameClassSameMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.SameClassSameMethod(caller, value); + } + + public static TMethod SameClassDifferentMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.SameClassDifferentMethod(caller, value); + } + + public static TMethod DifferentClassSameMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.DifferentClassSameMethod(caller, value); + } + + public static TMethod DifferentClassDifferentMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupDispatcher.DifferentClassDifferentMethod(caller, value); + } +} + +internal static class RuntimeLookupDispatcher +{ + public static TMethod SameClassSameMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupThunks.InvokeSameClassSameMethod(caller, value); + } + + public static TMethod SameClassDifferentMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupThunks.InvokeSameClassDifferentMethod(caller, value); + } + + public static TMethod DifferentClassSameMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupThunks.InvokeDifferentClassSameMethod(caller, value); + } + + public static TMethod DifferentClassDifferentMethod(IBaseMethodCaller caller, TMethod value) + { + return RuntimeLookupThunks.InvokeDifferentClassDifferentMethod(caller, value); + } +} + +internal static class RuntimeLookupThunks +{ + public static T InvokeSameClassSameMethod(IBaseMethodCaller caller, T value) + { + return RuntimeLookupHost.SameClassSameMethod(caller, value); + } + + public static T InvokeDifferentClassSameMethod(IBaseMethodCaller caller, T value) + { + return RuntimeLookupHost.DifferentClassSameMethod(caller, value); + } + + public static T InvokeSameClassDifferentMethod(IBaseMethodCaller caller, T value) + { + return RuntimeLookupHost.SameClassDifferentMethod(caller, value); + } + + public static T InvokeDifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + { + return RuntimeLookupHost.DifferentClassDifferentMethod(caller, value); + } +} + +internal static class RuntimeLookupHost +{ + public static T SameClassSameMethod(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } + + public static T DifferentClassSameMethod(IBaseMethodCaller caller, T value) + { + return RuntimeLookupRemote.SameMethod(caller, value); + } + + public static T SameClassDifferentMethod(IBaseMethodCaller caller, T value) + { + return SameClassDifferentMethodCore(caller, value); + } + + public static T DifferentClassDifferentMethod(IBaseMethodCaller caller, T value) + { + return RuntimeLookupRemote.DifferentMethod(caller, value); + } + + private static T SameClassDifferentMethodCore(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } +} + +internal static class RuntimeLookupRemote +{ + public static T SameMethod(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } + + public static T DifferentMethod(IBaseMethodCaller caller, T value) + { + return RemoteInner.Invoke(caller, value); + } + + private static class RemoteInner + { + public static T Invoke(IBaseMethodCaller caller, T value) + { + return caller.Invoke(value); + } + } +} + +internal interface IBaseMethodCaller +{ + T Invoke(T value); +} + +internal sealed class ClassBaseCaller : IBaseMethodCaller +{ + private readonly NonGenericBaseClass _instance; + + public ClassBaseCaller(NonGenericBaseClass instance) + { + _instance = instance; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Invoke(T value) => _instance.Process(value); +} + +internal sealed class GenericClassBaseCaller : IBaseMethodCaller +{ + private readonly GenericBaseClass _instance; + + public GenericClassBaseCaller(GenericBaseClass instance) + { + _instance = instance; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Invoke(T value) => _instance.Process(value); +} + +internal sealed class InterfaceBaseCaller : IBaseMethodCaller +{ + private readonly INonGenericBaseInterface _instance; + + public InterfaceBaseCaller(INonGenericBaseInterface instance) + { + _instance = instance; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Invoke(T value) => _instance.Process(value); +} + +internal sealed class GenericInterfaceBaseCaller : IBaseMethodCaller +{ + private readonly IGenericBaseInterface _instance; + + public GenericInterfaceBaseCaller(IGenericBaseInterface instance) + { + _instance = instance; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Invoke(T value) => _instance.Process(value); +} + +internal abstract class NonGenericBaseClass +{ + public abstract T Process(T value); +} + +internal abstract class GenericBaseClass +{ + public abstract T Process(T value); +} + +internal interface INonGenericBaseInterface +{ + T Process(T value); +} + +internal interface IGenericBaseInterface +{ + T Process(T value); +} + +internal sealed class ClassBase_NonGenericDerived_Inlining : NonGenericBaseClass +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T Process(T value) => value; +} + +internal sealed class ClassBase_NonGenericDerived_NoInlining : NonGenericBaseClass +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public override T Process(T value) => value; +} + +internal sealed class ClassBase_GenericDerived_Inlining : NonGenericBaseClass +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T Process(T value) => value; +} + +internal sealed class ClassBase_GenericDerived_NoInlining : NonGenericBaseClass +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public override T Process(T value) => value; +} + +internal sealed class GenericClassBase_NonGenericDerived_Inlining_NonShared : GenericBaseClass +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T Process(T value) => value; +} + +internal sealed class GenericClassBase_NonGenericDerived_Inlining_Shared : GenericBaseClass +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T Process(T value) => value; +} + +internal sealed class GenericClassBase_NonGenericDerived_NoInlining_NonShared : GenericBaseClass +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public override T Process(T value) => value; +} + +internal sealed class GenericClassBase_NonGenericDerived_NoInlining_Shared : GenericBaseClass +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public override T Process(T value) => value; +} + +internal sealed class GenericClassBase_GenericDerived_Inlining : GenericBaseClass +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T Process(T value) => value; +} + +internal sealed class GenericClassBase_GenericDerived_NoInlining : GenericBaseClass +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public override T Process(T value) => value; +} + +internal sealed class InterfaceBase_NonGenericClassDerived_Inlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal sealed class InterfaceBase_NonGenericClassDerived_NoInlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal readonly struct InterfaceBase_NonGenericStructDerived_Inlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal readonly struct InterfaceBase_NonGenericStructDerived_NoInlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal sealed class InterfaceBase_GenericClassDerived_Inlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal sealed class InterfaceBase_GenericClassDerived_NoInlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal readonly struct InterfaceBase_GenericStructDerived_Inlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal readonly struct InterfaceBase_GenericStructDerived_NoInlining : INonGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal sealed class GenericInterfaceBase_NonGenericClassDerived_Inlining_NonShared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal sealed class GenericInterfaceBase_NonGenericClassDerived_Inlining_Shared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal sealed class GenericInterfaceBase_NonGenericClassDerived_NoInlining_NonShared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal sealed class GenericInterfaceBase_NonGenericClassDerived_NoInlining_Shared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal readonly struct GenericInterfaceBase_NonGenericStructDerived_Inlining_NonShared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal readonly struct GenericInterfaceBase_NonGenericStructDerived_Inlining_Shared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal readonly struct GenericInterfaceBase_NonGenericStructDerived_NoInlining_NonShared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal readonly struct GenericInterfaceBase_NonGenericStructDerived_NoInlining_Shared : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal sealed class GenericInterfaceBase_GenericClassDerived_Inlining : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal sealed class GenericInterfaceBase_GenericClassDerived_NoInlining : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} + +internal readonly struct GenericInterfaceBase_GenericStructDerived_Inlining : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Process(T value) => value; +} + +internal readonly struct GenericInterfaceBase_GenericStructDerived_NoInlining : IGenericBaseInterface +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public T Process(T value) => value; +} diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.csproj b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.csproj new file mode 100644 index 00000000000000..1376e43d6f15b0 --- /dev/null +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.csproj @@ -0,0 +1,9 @@ + + + PdbOnly + 1 + + + + + diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods_o.csproj b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods_o.csproj new file mode 100644 index 00000000000000..a879c984c4fd22 --- /dev/null +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods_o.csproj @@ -0,0 +1,10 @@ + + + PdbOnly + True + 1 + + + + +