diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 08970ec4102e9b..ffeca0ab1af6fb 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1512,8 +1512,8 @@ 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 - // (in which case the method handle and context will be a generic method) + // - hasGenericMethodHandleContext is set TRUE for cases where the method handle and context will be a generic method + // (for array interface method or generic virtual method devirtualization) // CORINFO_METHOD_HANDLE devirtualizedMethod; CORINFO_CONTEXT_HANDLE exactContext; @@ -1521,7 +1521,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedMethod; CORINFO_RESOLVED_TOKEN resolvedTokenDevirtualizedUnboxedMethod; bool isInstantiatingStub; - bool wasArrayInterfaceDevirt; + bool hasGenericMethodHandleContext; }; //---------------------------------------------------------------------------- diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e8d9e66cd91363..dbab9bf3506686 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3063,6 +3063,9 @@ class Compiler GenTreeCall* gtNewHelperCallNode( unsigned helper, var_types type, GenTree* arg1 = nullptr, GenTree* arg2 = nullptr, GenTree* arg3 = nullptr); + + GenTreeCall* gtNewVirtualFunctionLookupHelperCallNode( + unsigned helper, var_types type, GenTree* thisPtr, GenTree* methHnd, GenTree* clsHnd = nullptr); GenTreeCall* gtNewRuntimeLookupHelperCallNode(CORINFO_RUNTIME_LOOKUP* pRuntimeLookup, GenTree* ctxTree, diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index a1e706fde6083e..fc1a48ed0d36b1 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -1568,6 +1568,60 @@ inline GenTreeCall* Compiler::gtNewHelperCallNode( return result; } +/*****************************************************************************/ + +//------------------------------------------------------------------------------ +// gtNewHelperCallNode : Helper to create a call helper node. +// +// +// Arguments: +// helper - Call helper +// type - Type of the node +// thisPtr - 'this' argument +// methHnd - Runtime method handle argument +// clsHnd - Class handle argument +// +// Return Value: +// New CT_HELPER node +// +inline GenTreeCall* Compiler::gtNewVirtualFunctionLookupHelperCallNode( + unsigned helper, var_types type, GenTree* thisPtr, GenTree* methHnd, GenTree* clsHnd) +{ + GenTreeCall* const result = gtNewCallNode(CT_HELPER, eeFindHelper(helper), type); + + if (!s_helperCallProperties.NoThrow((CorInfoHelpFunc)helper)) + { + result->gtFlags |= GTF_EXCEPT; + + if (s_helperCallProperties.AlwaysThrow((CorInfoHelpFunc)helper)) + { + setCallDoesNotReturn(result); + } + } +#if DEBUG + // Helper calls are never candidates. + + result->gtInlineObservation = InlineObservation::CALLSITE_IS_CALL_TO_HELPER; +#endif + + assert(methHnd != nullptr); + result->gtArgs.PushFront(this, NewCallArg::Primitive(methHnd).WellKnown(WellKnownArg::RuntimeMethodHandle)); + result->gtFlags |= methHnd->gtFlags & GTF_ALL_EFFECT; + + if (clsHnd != nullptr) + { + result->gtArgs.PushFront(this, NewCallArg::Primitive(clsHnd)); + result->gtFlags |= clsHnd->gtFlags & GTF_ALL_EFFECT; + } + + assert(thisPtr != nullptr); + + result->gtArgs.PushFront(this, NewCallArg::Primitive(thisPtr).WellKnown(WellKnownArg::ThisPointer)); + result->gtFlags |= thisPtr->gtFlags & GTF_ALL_EFFECT; + + return result; +} + //------------------------------------------------------------------------ // gtNewAllocObjNode: A little helper to create an object allocation node. // diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 5b9052b28c6699..26e3a0e35fd74c 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -530,6 +530,34 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorIsDevirtualizationCandidate(m_compiler)); + if (call->IsVirtual()) + { + assert(call->gtCallType == CT_USER_FUNC); + return call->gtCallMethHnd; + } + else + { + assert(call->gtCallType == CT_INDIRECT); + GenTree* runtimeMethHndNode = + call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle)->GetNode(); + assert(runtimeMethHndNode != nullptr); + switch (runtimeMethHndNode->OperGet()) + { + case GT_RUNTIMELOOKUP: + return runtimeMethHndNode->AsRuntimeLookup()->GetMethodHandle(); + case GT_CNS_INT: + return CORINFO_METHOD_HANDLE(runtimeMethHndNode->AsIntCon()->IconValue()); + default: + assert(!"Unexpected type in RuntimeMethodHandle arg."); + return nullptr; + } + return nullptr; + } + } + //------------------------------------------------------------------------ // LateDevirtualization: re-examine calls after inlining to see if we // can do more devirtualization @@ -573,7 +601,7 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorOperGet() == GT_CALL) { GenTreeCall* call = tree->AsCall(); - bool tryLateDevirt = call->IsVirtual() && (call->gtCallType == CT_USER_FUNC); + bool tryLateDevirt = call->IsDevirtualizationCandidate(m_compiler); #ifdef DEBUG tryLateDevirt = tryLateDevirt && (JitConfig.JitEnableLateDevirtualization() == 1); @@ -591,7 +619,7 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtLateDevirtualizationInfo->exactContextHnd; InlineContext* inlinersContext = call->gtLateDevirtualizationInfo->inlinersContext; - CORINFO_METHOD_HANDLE method = call->gtCallMethHnd; + CORINFO_METHOD_HANDLE method = GetMethodHandle(call); unsigned methodFlags = 0; const bool isLateDevirtualization = true; const bool explicitTailCall = call->IsTailPrefixedCall(); @@ -601,7 +629,7 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorimpDevirtualizeCall(call, nullptr, &method, &methodFlags, &contextInput, &context, isLateDevirtualization, explicitTailCall); - if (!call->IsVirtual()) + if (!call->IsDevirtualizationCandidate(m_compiler)) { assert(context != nullptr); assert(inlinersContext != nullptr); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index fbcc431a081e30..4da933bdb94691 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -2304,6 +2304,25 @@ int GenTreeCall::GetNonStandardAddedArgCount(Compiler* compiler) const return 0; } +//------------------------------------------------------------------------- +// IsDevirtualizationCandidate: Determine if this GT_CALL node is a devirtualization candidate. +// A call will be unmarked from devirtualization candidate if it +// is devirtualized. +// +// Arguments: +// compiler - the compiler instance so that we can call eeFindHelper +// +// Return Value: +// Returns true if this GT_CALL node is a devirtualization candidate. +// +bool GenTreeCall::IsDevirtualizationCandidate(Compiler* compiler) const +{ + return (IsVirtual() && gtCallType == CT_USER_FUNC) || + // TODO: Support R2R and NativeAOT (CORINFO_HELP_GVMLOOKUP_FOR_SLOT) + (!compiler->opts.IsReadyToRun() && gtCallType == CT_INDIRECT && + gtCallAddr->IsHelperCall(compiler, CORINFO_HELP_VIRTUAL_FUNC_PTR)); +} + //------------------------------------------------------------------------- // IsHelperCall: Determine if this GT_CALL node is a specific helper call. // @@ -13093,6 +13112,8 @@ const char* Compiler::gtGetWellKnownArgNameForArgMsg(WellKnownArg arg) return "tail call"; case WellKnownArg::StackArrayLocal: return "&lcl arr"; + case WellKnownArg::RuntimeMethodHandle: + return "meth hnd"; default: return nullptr; } diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index e01d9705eb0d4a..bc34eb1d1240a3 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -4564,6 +4564,7 @@ enum class WellKnownArg : unsigned SwiftSelf, X86TailCallSpecialArg, StackArrayLocal, + RuntimeMethodHandle, }; #ifdef DEBUG @@ -5178,6 +5179,9 @@ struct GenTreeCall final : public GenTree { return (gtFlags & GTF_CALL_VIRT_KIND_MASK) == GTF_CALL_VIRT_VTABLE; } + + bool IsDevirtualizationCandidate(Compiler* compiler) const; + bool IsInlineCandidate() const { return (gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0; diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 323951bf64ba91..c8bfe62d2815e6 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -2780,7 +2780,8 @@ GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr, { GenTree* runtimeMethodHandle = impLookupToTree(pResolvedToken, &pCallInfo->codePointerLookup, GTF_ICON_METHOD_HDL, pCallInfo->hMethod); - call = gtNewHelperCallNode(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, TYP_I_IMPL, thisPtr, runtimeMethodHandle); + call = gtNewVirtualFunctionLookupHelperCallNode(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, TYP_I_IMPL, thisPtr, + runtimeMethodHandle); } #ifdef FEATURE_READYTORUN @@ -2821,7 +2822,8 @@ GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr, // Call helper function. This gets the target address of the final destination callsite. // - call = gtNewHelperCallNode(CORINFO_HELP_VIRTUAL_FUNC_PTR, TYP_I_IMPL, thisPtr, exactTypeDesc, exactMethodDesc); + call = gtNewVirtualFunctionLookupHelperCallNode(CORINFO_HELP_VIRTUAL_FUNC_PTR, TYP_I_IMPL, thisPtr, + exactMethodDesc, exactTypeDesc); } assert(call != nullptr); diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 413932546ae51e..2685e814071c66 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -185,12 +185,6 @@ var_types Compiler::impImportCall(OPCODE opcode, compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_MANAGED_VARARGS); return TYP_UNDEF; } - - if ((mflags & CORINFO_FLG_VIRTUAL) && (sig->sigInst.methInstCount != 0) && (opcode == CEE_CALLVIRT)) - { - compInlineResult->NoteFatal(InlineObservation::CALLEE_IS_GENERIC_VIRTUAL); - return TYP_UNDEF; - } } clsHnd = pResolvedToken->hClass; @@ -384,12 +378,6 @@ var_types Compiler::impImportCall(OPCODE opcode, case CORINFO_VIRTUALCALL_LDVIRTFTN: { - if (compIsForInlining()) - { - compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_CALL_VIA_LDVIRTFTN); - return TYP_UNDEF; - } - assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); // OK, We've been told to call via LDVIRTFTN, so just @@ -402,11 +390,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->TypeIs(TYP_REF) && (origThisPtr != thisPtr)) + { + lvaGetDesc(thisPtr->AsLclVar())->lvSingleDef = 1; + lvaSetClass(thisPtr->AsLclVar()->GetLclNum(), origThisPtr); + } + GenTree* fptr = impImportLdvirtftn(thisPtr, pResolvedToken, callInfo); assert(fptr != nullptr); @@ -415,10 +412,6 @@ var_types Compiler::impImportCall(OPCODE opcode, // Now make an indirect call through the function pointer - unsigned lclNum = lvaGrabTemp(true DEBUGARG("VirtualCall through function pointer")); - impStoreToTemp(lclNum, fptr, CHECK_SPILL_ALL); - fptr = gtNewLclvNode(lclNum, TYP_I_IMPL); - call->AsCall()->gtCallAddr = fptr; call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); @@ -439,7 +432,7 @@ var_types Compiler::impImportCall(OPCODE opcode, // Sine 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: @@ -933,13 +926,15 @@ var_types Compiler::impImportCall(OPCODE opcode, } } +DEVIRT: + bool probing; probing = impConsiderCallProbe(call->AsCall(), rawILOffset); // See if we can devirt if we aren't probing. if (!probing && opts.OptimizationEnabled()) { - if (call->AsCall()->IsVirtual()) + if (call->AsCall()->IsDevirtualizationCandidate(this)) { // only true object pointers can be virtual assert(call->AsCall()->gtArgs.HasThisPointer() && @@ -955,7 +950,7 @@ var_types Compiler::impImportCall(OPCODE opcode, // inlinees. rawILOffset); - const bool wasDevirtualized = !call->AsCall()->IsVirtual(); + const bool wasDevirtualized = !call->AsCall()->IsDevirtualizationCandidate(this); if (wasDevirtualized) { @@ -1232,7 +1227,7 @@ var_types Compiler::impImportCall(OPCODE opcode, // If the call is virtual, record the inliner's context for possible use during late devirt inlining. // Also record the generics context if there is any. // - if (call->AsCall()->IsVirtual() && (call->AsCall()->gtCallType != CT_INDIRECT)) + if (call->AsCall()->IsDevirtualizationCandidate(this)) { JITDUMP("\nSaving generic context %p and inline context %p for call [%06u]\n", dspPtr(exactContextHnd), dspPtr(compInlineContext), dspTreeID(call->AsCall())); @@ -1449,8 +1444,7 @@ var_types Compiler::impImportCall(OPCODE opcode, varDsc->lvType = call->AsCall()->gtReturnType; } - // TODO-Bug: CHECK_SPILL_NONE here looks wrong. - impStoreToTemp(calliSlot, call, CHECK_SPILL_NONE); + impStoreToTemp(calliSlot, call, CHECK_SPILL_ALL); // impStoreToTemp can change src arg list and return type for call that returns struct. var_types type = genActualType(lvaTable[calliSlot].TypeGet()); call = gtNewLclvNode(calliSlot, type); @@ -7136,7 +7130,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, } addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs, - clsAttrs, likelyHood, dvInfo.wasArrayInterfaceDevirt, + clsAttrs, likelyHood, dvInfo.hasGenericMethodHandleContext, dvInfo.isInstantiatingStub, originalContext); } @@ -7208,7 +7202,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, likelyContext = dvInfo.exactContext; likelyMethod = dvInfo.devirtualizedMethod; - arrayInterface = dvInfo.wasArrayInterfaceDevirt; + arrayInterface = dvInfo.hasGenericMethodHandleContext; instantiatingStub = dvInfo.isInstantiatingStub; } else @@ -8027,9 +8021,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, assert(methodFlags != nullptr); assert(pContextHandle != nullptr); - // This should be a virtual vtable or virtual stub call. + // This should be a devirtualization candidate. // - assert(call->IsVirtual()); + assert(call->IsDevirtualizationCandidate(this)); assert(opts.OptimizationEnabled()); #if defined(DEBUG) @@ -8088,7 +8082,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // if ((baseMethodAttribs & CORINFO_FLG_VIRTUAL) == 0) { - assert(call->IsVirtualStub()); assert(opts.IsReadyToRun()); JITDUMP("\nimpDevirtualizeCall: [R2R] base method not virtual, sorry\n"); return; @@ -8180,7 +8173,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // It may or may not know enough to devirtualize... if (isInterface) { - assert(call->IsVirtualStub()); JITDUMP("--- base class is interface\n"); } @@ -8210,14 +8202,14 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS) { - assert(!dvInfo.wasArrayInterfaceDevirt); + assert(!dvInfo.hasGenericMethodHandleContext); 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(dvInfo.hasGenericMethodHandleContext); assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD); derivedClass = info.compCompHnd->getMethodClass(derivedMethod); } @@ -8238,17 +8230,17 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, if (dvInfo.isInstantiatingStub) { - // We should only end up with generic methods for array interface devirt. + // We should only end up with generic methods for array interface or GVM devirt. // - assert(dvInfo.wasArrayInterfaceDevirt); + assert(dvInfo.hasGenericMethodHandleContext); // We don't expect NAOT to end up here, since it has Array - // and normal devirtualization. + // and normal devirtualization. For GVM devirt we don't support NAOT yet. // assert(!IsTargetAbi(CORINFO_NATIVEAOT_ABI)); // We don't expect R2R to end up here, since it does not (yet) support - // array interface devirtualization. + // array interface or GVM devirtualization. // assert(!opts.IsReadyToRun()); @@ -8354,14 +8346,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP(" %s; can devirtualize\n", note); - // Make the updates. - call->gtFlags &= ~GTF_CALL_VIRT_VTABLE; - call->gtFlags &= ~GTF_CALL_VIRT_STUB; - call->gtCallMethHnd = derivedMethod; - call->gtCallType = CT_USER_FUNC; - call->gtControlExpr = nullptr; - INDEBUG(call->gtCallDebugFlags |= GTF_CALL_MD_DEVIRTUALIZED); - if (dvInfo.isInstantiatingStub) { // Pass the instantiating stub method desc as the inst param arg. @@ -8369,11 +8353,36 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Note different embedding would be needed for NAOT/R2R, // but we have ruled those out above. // - GenTree* const instParam = - gtNewIconEmbHndNode(instantiatingStub, nullptr, GTF_ICON_METHOD_HDL, instantiatingStub); + GenTree* instParam = nullptr; + // See if this is a generic virtual method call. + if (call->gtCallType == CT_INDIRECT && call->gtCallAddr->IsHelperCall(this, CORINFO_HELP_VIRTUAL_FUNC_PTR)) + { + // We need to pass the RUNTIMELOOKUP helper call as the inst param arg + // instead of the instantiating stub if there is one. + // + GenTree* const methHndNode = + call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle)->GetEarlyNode(); + if (methHndNode->OperIs(GT_RUNTIMELOOKUP)) + { + instParam = methHndNode; + } + } + // For cases where we don't have a RUNTIMELOOKUP helper call, we pass the instantiating stub. + if (instParam == nullptr) + { + instParam = gtNewIconEmbHndNode(instantiatingStub, nullptr, GTF_ICON_METHOD_HDL, instantiatingStub); + } call->gtArgs.InsertInstParam(this, instParam); } + // Make the updates. + call->gtFlags &= ~GTF_CALL_VIRT_VTABLE; + call->gtFlags &= ~GTF_CALL_VIRT_STUB; + call->gtCallMethHnd = derivedMethod; + call->gtCallType = CT_USER_FUNC; + call->gtControlExpr = nullptr; + INDEBUG(call->gtCallDebugFlags |= GTF_CALL_MD_DEVIRTUALIZED); + // Virtual calls include an implicit null check, which we may // now need to make explicit. if (!objIsNonNull) diff --git a/src/coreclr/jit/inline.def b/src/coreclr/jit/inline.def index 2b045ad5d20009..91ad0a5f5f5596 100644 --- a/src/coreclr/jit/inline.def +++ b/src/coreclr/jit/inline.def @@ -39,7 +39,6 @@ INLINE_OBSERVATION(HAS_NO_BODY, bool, "has no body", INLINE_OBSERVATION(HAS_NULL_FOR_LDELEM, bool, "has null pointer for ldelem", FATAL, CALLEE) INLINE_OBSERVATION(HAS_UNMANAGED_CALLCONV, bool, "has unmanaged calling convention", FATAL, CALLEE) INLINE_OBSERVATION(IS_ARRAY_METHOD, bool, "is array method", FATAL, CALLEE) -INLINE_OBSERVATION(IS_GENERIC_VIRTUAL, bool, "generic virtual", FATAL, CALLEE) INLINE_OBSERVATION(IS_JIT_NOINLINE, bool, "noinline per JitNoinline", FATAL, CALLEE) INLINE_OBSERVATION(IS_NOINLINE, bool, "noinline per IL/cached result", FATAL, CALLEE) INLINE_OBSERVATION(IS_SYNCHRONIZED, bool, "is synchronized", FATAL, CALLEE) @@ -134,7 +133,6 @@ INLINE_OBSERVATION(COMPILATION_ERROR, bool, "compilation error", INLINE_OBSERVATION(COMPILATION_FAILURE, bool, "failed to compile", FATAL, CALLSITE) INLINE_OBSERVATION(EXPLICIT_TAIL_PREFIX, bool, "explicit tail prefix", FATAL, CALLSITE) INLINE_OBSERVATION(GENERIC_DICTIONARY_LOOKUP, bool, "runtime dictionary lookup", FATAL, CALLSITE) -INLINE_OBSERVATION(HAS_CALL_VIA_LDVIRTFTN, bool, "call via ldvirtftn", FATAL, CALLSITE) INLINE_OBSERVATION(HAS_COMPLEX_HANDLE, bool, "complex handle access", FATAL, CALLSITE) INLINE_OBSERVATION(IMPLICIT_REC_TAIL_CALL, bool, "implicit recursive tail call", FATAL, CALLSITE) INLINE_OBSERVATION(IS_CALL_TO_HELPER, bool, "target is helper", FATAL, CALLSITE) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index d0bdd858ef7f9d..e9ea8b669d4602 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -739,6 +739,8 @@ const char* getWellKnownArgName(WellKnownArg arg) return "X86TailCallSpecialArg"; case WellKnownArg::StackArrayLocal: return "StackArrayLocal"; + case WellKnownArg::RuntimeMethodHandle: + return "RuntimeMethodHandle"; } return "N/A"; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 70dde2b702ea3b..4daed1775bc60d 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1303,7 +1303,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->hasGenericMethodHandleContext = false; TypeDesc objType = HandleToObject(info->objClass); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 08d7df78c946d8..291564e866e430 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1082,8 +1082,8 @@ 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 - // (in which case the method handle and context will be a generic method) + // - hasGenericMethodHandleContext is set TRUE for cases where the method handle and context will be a generic method + // (for array interface method or generic virtual method devirtualization) // public CORINFO_METHOD_STRUCT_* devirtualizedMethod; public CORINFO_CONTEXT_STRUCT* exactContext; @@ -1092,8 +1092,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 _hasGenericMethodHandleContext; + public bool hasGenericMethodHandleContext { get { return _hasGenericMethodHandleContext != 0; } set { _hasGenericMethodHandleContext = 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 18fd918a7a5cea..3db51f7dcef99d 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -653,7 +653,7 @@ struct Agnostic_ResolveVirtualMethodResult bool returnValue; DWORDLONG devirtualizedMethod; bool isInstantiatingStub; - bool wasArrayInterfaceDevirt; + bool hasGenericMethodHandleContext; 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 0621f2bcd1eb23..71a968993a58ab 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3293,12 +3293,12 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info key.pResolvedTokenVirtualMethod = SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKEN(info->pResolvedTokenVirtualMethod, ResolveToken); 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.wasArrayInterfaceDevirt = info->wasArrayInterfaceDevirt; + result.returnValue = returnValue; + result.devirtualizedMethod = CastHandle(info->devirtualizedMethod); + result.isInstantiatingStub = info->isInstantiatingStub; + result.exactContext = CastHandle(info->exactContext); + result.detail = (DWORD)info->detail; + result.hasGenericMethodHandleContext = info->hasGenericMethodHandleContext; if (returnValue) { @@ -3323,11 +3323,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, hasGenericMethodHandleContext-%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.hasGenericMethodHandleContext ? "true" : "false", result.exactContext, result.detail, result.returnValue ? SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(result.resolvedTokenDevirtualizedMethod).c_str() : "???", @@ -3352,7 +3352,7 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) result.devirtualizedMethod; info->isInstantiatingStub = result.isInstantiatingStub; - info->wasArrayInterfaceDevirt = result.wasArrayInterfaceDevirt; + info->hasGenericMethodHandleContext = result.hasGenericMethodHandleContext; 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 87c0b89e593d04..1e248ca0e9644a 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7709,7 +7709,7 @@ CEEInfo::getMethodInfo( getMethodInfoHelper(cxt, methInfo, context); result = true; } - else if (!ftn->IsWrapperStub() && ftn->HasILHeader()) + else if (!ftn->IsUnboxingStub() && ftn->HasILHeader()) { COR_ILMETHOD_DECODER header(ftn->GetILHeader(), ftn->GetMDImport(), NULL); cxt.Header = &header; @@ -8578,7 +8578,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->hasGenericMethodHandleContext = false; MethodDesc* pBaseMD = GetMethod(info->virtualMethod); MethodTable* pBaseMT = pBaseMD->GetMethodTable(); @@ -8586,9 +8586,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()); @@ -8762,6 +8759,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) MethodTable* pApproxMT = pDevirtMD->GetMethodTable(); MethodTable* pExactMT = pApproxMT; bool isArray = false; + bool isGenericVirtual = false; if (pApproxMT->IsInterface()) { @@ -8779,22 +8777,40 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) pExactMT = pDevirtMD->GetExactDeclaringType(pObjMT); } + // This is generic virtual method devirtualization. + if (!isArray && pBaseMD->HasMethodInstantiation()) + { + // If we're in a shared context we'll devirt to a shared + // generic method and won't be able to inline, so just bail. + // + if (pExactMT->IsSharedByGenericInstantiations()) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + + pDevirtMD = pDevirtMD->FindOrCreateAssociatedMethodDesc( + pDevirtMD, pExactMT, pExactMT->IsValueType() && !pDevirtMD->IsStatic(), pBaseMD->GetMethodInstantiation(), false); + + isGenericVirtual = true; + } + // Success! Pass back the results. // - if (isArray) + if (isArray || isGenericVirtual) { // 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->wasArrayInterfaceDevirt = true; + info->hasGenericMethodHandleContext = true; } else { info->exactContext = MAKE_CLASSCONTEXT((CORINFO_CLASS_HANDLE) pExactMT); info->isInstantiatingStub = false; - info->wasArrayInterfaceDevirt = false; + info->hasGenericMethodHandleContext = false; } info->devirtualizedMethod = (CORINFO_METHOD_HANDLE) pDevirtMD;