diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 4dfa9d39bc10c7..31897588adf722 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1576,6 +1576,7 @@ struct CORINFO_DEVIRTUALIZATION_INFO // [In] arguments of resolveVirtualMethod // CORINFO_METHOD_HANDLE virtualMethod; + CORINFO_METHOD_HANDLE callerMethod; CORINFO_CLASS_HANDLE objClass; CORINFO_CONTEXT_HANDLE context; CORINFO_RESOLVED_TOKEN *pResolvedTokenVirtualMethod; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index e0177d76cd5826..a13c35e2f55004 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* fcb1b400-696c-4425-a8a7-bb082430a217 */ - 0xfcb1b400, - 0x696c, - 0x4425, - {0xa8, 0xa7, 0xbb, 0x08, 0x24, 0x30, 0xa2, 0x17} +constexpr GUID JITEEVersionIdentifier = { /* 01657eff-500b-4529-a977-df9eb508cd6a */ + 0x01657eff, + 0x500b, + 0x4529, + {0xa9, 0x77, 0xdf, 0x9e, 0xb5, 0x08, 0xcd, 0x6a} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e9937b323bf992..eb4b590e9e31bd 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5042,6 +5042,7 @@ class Compiler CORINFO_RESOLVED_TOKEN* pResolvedToken; // Resolved token for the target method, used by R2R. CORINFO_RESOLVED_TOKEN* pUnboxedResolvedToken; // Resolved token and method handle for the unboxed entry. CORINFO_LOOKUP* pInstParamLookup; // All the information needed for the instantiation parameter lookup. + GenTree* runtimeLookupContext; // Runtime context tree to use for pInstParamLookup, or nullptr. CORINFO_SIG_INFO* pMethSig; // The devirted method signature. bool objIsNonNull; // True if the receiver is known non-null. bool hadImplicitNullCheck; // True if the original call's null check was implicit. @@ -7213,9 +7214,11 @@ class Compiler CORINFO_METHOD_HANDLE dispatcherHnd); GenTree* getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFlags, - void* compileTimeHandle); + void* compileTimeHandle, + GenTree* runtimeLookupContext = nullptr); GenTree* getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, - void* compileTimeHandle); + void* compileTimeHandle, + GenTree* runtimeLookupContext = nullptr); GenTree* getVirtMethodPointerTree(GenTree* thisPtr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo); @@ -8108,7 +8111,8 @@ class Compiler bool isInterface, CORINFO_METHOD_HANDLE baseMethod, CORINFO_CLASS_HANDLE baseClass, - CORINFO_CONTEXT_HANDLE* pContextHandle); + CORINFO_CONTEXT_HANDLE* pContextHandle, + CORINFO_RESOLVED_TOKEN* pResolvedToken); bool isCompatibleMethodGDV(GenTreeCall* call, CORINFO_METHOD_HANDLE gdvTarget); diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 9c88f80329bbe3..04b86e0b4beee4 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -608,16 +608,17 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitorgtLateDevirtualizationInfo->methodHnd; - CORINFO_CONTEXT_HANDLE context = call->gtLateDevirtualizationInfo->exactContextHnd; - InlineContext* inlinersContext = call->gtInlineContext; - 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->gtInlineContext; + 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/importer.cpp b/src/coreclr/jit/importer.cpp index 0409edb99e6bbb..5e148476d644b7 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -1628,10 +1628,17 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_LOOKUP* pLookup, void* compile assert(pRuntimeLookup->indirections != 0); GenTreeCall* helperCall = gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, ctxTree, compileTimeHandle); - // Spilling it to a temp improves CQ (mainly in Tier0) - unsigned callLclNum = lvaGrabTemp(true DEBUGARG("spilling helperCall")); - impStoreToTemp(callLclNum, helperCall, CHECK_SPILL_NONE); - return gtNewLclvNode(callLclNum, helperCall->TypeGet()); + if (opts.OptimizationEnabled()) + { + return helperCall; + } + else + { + // Spilling it to a temp improves CQ (mainly in Tier0) + unsigned callLclNum = lvaGrabTemp(true DEBUGARG("spilling helperCall")); + impStoreToTemp(callLclNum, helperCall, CHECK_SPILL_NONE); + return gtNewLclvNode(callLclNum, helperCall->TypeGet()); + } } // Size-check is not expected without testForNull diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index d0bcf2a484eb4e..0ffe988ce9d644 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1086,7 +1086,7 @@ var_types Compiler::impImportCall(OPCODE opcode, else if (call->AsCall()->IsDelegateInvoke()) { considerGuardedDevirtualization(call->AsCall(), rawILOffset, false, call->AsCall()->gtCallMethHnd, - NO_CLASS_HANDLE, nullptr); + NO_CLASS_HANDLE, nullptr, pResolvedToken); } } @@ -1367,6 +1367,7 @@ var_types Compiler::impImportCall(OPCODE opcode, info->methodHnd = callInfo->hMethod; info->exactContextHnd = exactContextHnd; info->ilLocation = impCurStmtDI.GetLocation(); + info->resolvedToken = *pResolvedToken; call->AsCall()->gtLateDevirtualizationInfo = info; } } @@ -8013,7 +8014,8 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, bool isInterface, CORINFO_METHOD_HANDLE baseMethod, CORINFO_CLASS_HANDLE baseClass, - CORINFO_CONTEXT_HANDLE* pContextHandle) + CORINFO_CONTEXT_HANDLE* pContextHandle, + CORINFO_RESOLVED_TOKEN* pResolvedToken) { JITDUMP("Considering guarded devirtualization at IL offset %u (0x%x)\n", ilOffset, ilOffset); @@ -8092,9 +8094,10 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // CORINFO_DEVIRTUALIZATION_INFO dvInfo; dvInfo.virtualMethod = baseMethod; + dvInfo.callerMethod = call->gtInlineContext->GetCallee(); dvInfo.objClass = exactCls; dvInfo.context = originalContext; - dvInfo.pResolvedTokenVirtualMethod = nullptr; + dvInfo.pResolvedTokenVirtualMethod = pResolvedToken; JITDUMP("GDV exact: resolveVirtualMethod (method %p class %p context %p)\n", dvInfo.virtualMethod, dvInfo.objClass, dvInfo.context); @@ -8177,6 +8180,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call, // Figure out which method will be called. // dvInfo.virtualMethod = baseMethod; + dvInfo.callerMethod = call->gtInlineContext->GetCallee(); dvInfo.objClass = likelyClass; dvInfo.context = originalContext; dvInfo.pResolvedTokenVirtualMethod = nullptr; @@ -9229,7 +9233,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, + pResolvedToken); return; } @@ -9273,7 +9278,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, baseClass, pContextHandle, + pResolvedToken); return; } @@ -9285,11 +9291,9 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, JITDUMP("--- base class is interface\n"); } - // Fetch the method that would be called based on the declared type of 'this', - // and prepare to fetch the method attributes. - // CORINFO_DEVIRTUALIZATION_INFO dvInfo; dvInfo.virtualMethod = baseMethod; + dvInfo.callerMethod = call->gtInlineContext->GetCallee(); dvInfo.objClass = objClass; dvInfo.context = *pContextHandle; dvInfo.detail = CORINFO_DEVIRTUALIZATION_UNKNOWN; @@ -9300,6 +9304,56 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, info.compCompHnd->resolveVirtualMethod(&dvInfo); + GenTree* runtimeLookupContext = nullptr; + + if (isLateDevirtualization && dvInfo.instParamLookup.lookupKind.needsRuntimeLookup) + { + // Late devirtualization may revisit a call that was imported in an inlinee. + // If token context is not the current root context, runtime lookups can be rooted in the wrong generic context. + // + CORINFO_CONTEXT_HANDLE tokenContext = pResolvedToken->tokenContext; + const SIZE_T tokenContextHandle = (SIZE_T)tokenContext & ~CORINFO_CONTEXTFLAGS_MASK; + + const bool isMethodContext = ((SIZE_T)tokenContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD; + const bool isCurrentContext = + (tokenContext == METHOD_BEING_COMPILED_CONTEXT()) || + (isMethodContext ? ((CORINFO_METHOD_HANDLE)tokenContextHandle == info.compMethodHnd) + : ((CORINFO_CLASS_HANDLE)tokenContextHandle == info.compClassHnd)); + + // If we don't have the right context, try recover it from the tree. + // + if (!isCurrentContext) + { + CallArg* runtimeMethodHandleArg = nullptr; + if ((call->gtControlExpr != nullptr) && call->gtControlExpr->OperIs(GT_CALL)) + { + runtimeMethodHandleArg = + call->gtControlExpr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle); + } + + if (runtimeMethodHandleArg != nullptr && runtimeMethodHandleArg->GetNode()->OperIs(GT_RUNTIMELOOKUP)) + { + if (runtimeMethodHandleArg->GetNode()->AsRuntimeLookup()->Lookup()->OperIs(GT_CALL)) + { + GenTreeCall* const helperCall = + runtimeMethodHandleArg->GetNode()->AsRuntimeLookup()->Lookup()->AsCall(); + + if (helperCall->IsHelperCall(CORINFO_HELP_RUNTIMEHANDLE_METHOD) || + helperCall->IsHelperCall(CORINFO_HELP_RUNTIMEHANDLE_CLASS)) + { + runtimeLookupContext = helperCall->gtArgs.GetArgByIndex(0)->GetNode(); + } + } + } + + if (runtimeLookupContext == nullptr) + { + JITDUMP("Late devirt needs a runtime lookup context cannot be figured out. Bail out.\n"); + return; + } + } + } + CORINFO_METHOD_HANDLE derivedMethod = dvInfo.devirtualizedMethod; CORINFO_CONTEXT_HANDLE exactContext = dvInfo.tokenLookupContext; CORINFO_RESOLVED_TOKEN* pDerivedResolvedToken = &dvInfo.resolvedTokenDevirtualizedMethod; @@ -9395,7 +9449,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, return; } - considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, objClass, pContextHandle); + considerGuardedDevirtualization(call, ilOffset, isInterface, baseMethod, objClass, pContextHandle, + pResolvedToken); return; } @@ -9410,6 +9465,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, dcInfo.pInstParamLookup = &dvInfo.instParamLookup; dcInfo.pResolvedToken = pDerivedResolvedToken; dcInfo.pUnboxedResolvedToken = &dvInfo.resolvedTokenDevirtualizedUnboxedMethod; + dcInfo.runtimeLookupContext = runtimeLookupContext; dcInfo.pMethSig = &derivedSig; dcInfo.objIsNonNull = objIsNonNull; dcInfo.hadImplicitNullCheck = true; @@ -9628,7 +9684,8 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, CORINFO_METHOD_HANDLE exactMethodHandle = (CORINFO_METHOD_HANDLE)((SIZE_T)dcInfo->tokenLookupContext & ~CORINFO_CONTEXTFLAGS_MASK); - instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle); + instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle, + dcInfo->runtimeLookupContext); JITDUMP("revising call to invoke unboxed entry with additional method desc arg\n"); } else @@ -9710,7 +9767,8 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, CORINFO_METHOD_HANDLE exactMethodHandle = (CORINFO_METHOD_HANDLE)((SIZE_T)dcInfo->tokenLookupContext & ~CORINFO_CONTEXTFLAGS_MASK); - instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle); + instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle, + dcInfo->runtimeLookupContext); } else { @@ -9719,7 +9777,8 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, CORINFO_CLASS_HANDLE exactClassHandle = (CORINFO_CLASS_HANDLE)((SIZE_T)dcInfo->tokenLookupContext & ~CORINFO_CONTEXTFLAGS_MASK); - instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_CLASS_HDL, exactClassHandle); + instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_CLASS_HDL, exactClassHandle, + dcInfo->runtimeLookupContext); } } } diff --git a/src/coreclr/jit/indirectcalltransformer.cpp b/src/coreclr/jit/indirectcalltransformer.cpp index 13059d41d766c3..7e64346af0958f 100644 --- a/src/coreclr/jit/indirectcalltransformer.cpp +++ b/src/coreclr/jit/indirectcalltransformer.cpp @@ -987,6 +987,7 @@ class IndirectCallTransformer dcInfo.pUnboxedResolvedToken = (clsHnd != NO_CLASS_HANDLE) ? &inlineInfo->guardedMethodUnboxedResolvedToken : nullptr; + dcInfo.runtimeLookupContext = nullptr; dcInfo.objIsNonNull = objIsNonNull; dcInfo.hadImplicitNullCheck = m_origCall->IsVirtual(); dcInfo.isDelegateCall = m_origCall->IsDelegateInvoke(); diff --git a/src/coreclr/jit/inline.h b/src/coreclr/jit/inline.h index 90e79dbb3fc8e7..4d1357bb0f3db2 100644 --- a/src/coreclr/jit/inline.h +++ b/src/coreclr/jit/inline.h @@ -634,6 +634,7 @@ struct LateDevirtualizationInfo CORINFO_METHOD_HANDLE methodHnd; CORINFO_CONTEXT_HANDLE exactContextHnd; ILLocation ilLocation; + 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 7c99a66f80bc9f..0b42d1d27e34b8 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -5512,11 +5512,15 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig // pLookup - the lookup to get the tree for // handleFlags - flags to set on the result node // compileTimeHandle - compile-time handle corresponding to the lookup +// runtimeLookupContext - context tree to use for a runtime lookup, or nullptr to use the current context // // Return Value: // A node representing the lookup tree // -GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFlags, void* compileTimeHandle) +GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, + GenTreeFlags handleFlags, + void* compileTimeHandle, + GenTree* runtimeLookupContext) { if (!pLookup->lookupKind.needsRuntimeLookup) { @@ -5539,7 +5543,7 @@ GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFla return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, compileTimeHandle); } - return getRuntimeLookupTree(pLookup, compileTimeHandle); + return getRuntimeLookupTree(pLookup, compileTimeHandle, runtimeLookupContext); } //------------------------------------------------------------------------ @@ -5548,25 +5552,27 @@ GenTree* Compiler::getLookupTree(CORINFO_LOOKUP* pLookup, GenTreeFlags handleFla // Arguments: // pLookup - the lookup to get the tree for // compileTimeHandle - compile-time handle corresponding to the lookup +// runtimeLookupContext - context tree to use for the lookup, or nullptr to use the current context // // Return Value: // A node representing the runtime lookup tree // -GenTree* Compiler::getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, void* compileTimeHandle) +GenTree* Compiler::getRuntimeLookupTree(CORINFO_LOOKUP* pLookup, void* compileTimeHandle, GenTree* runtimeLookupContext) { CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; + GenTree* contextTree = runtimeLookupContext != nullptr + ? runtimeLookupContext + : 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); + return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, contextTree, compileTimeHandle); } - GenTree* result = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); + GenTree* result = contextTree; ArrayStack stmts(getAllocator(CMK_ArrayStack)); diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index f70b278f2de695..99ec54082ac19a 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -124,6 +124,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 fbcb9f1e4c2f93..8d9c2b999266b5 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1541,9 +1541,25 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) if (originalImpl.IsRuntimeDeterminedExactMethod || originalImpl.IsSharedByGenericInstantiations) { - // TODO: Support for runtime lookup + if (info->pResolvedTokenVirtualMethod == null || unboxingStub) + { + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + +#if READYTORUN + ComputeRuntimeLookupForSharedGenericToken( + Internal.ReadyToRunConstants.DictionaryEntryKind.DevirtualizedMethodDescSlot, + ref *info->pResolvedTokenVirtualMethod, + null, + originalImpl, + HandleToObject(info->callerMethod), + ref info->instParamLookup); +#else + // TODO: Implement generic virtual method devirtualization runtime lookup for NativeAOT info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; +#endif } } @@ -1556,32 +1572,34 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) } #endif - if (requiresInstMethodDescArg) + if (!info->instParamLookup.lookupKind.needsRuntimeLookup) { - if (unboxingStub) + if (requiresInstMethodDescArg) { - // Bail out for now. We need an unboxing stub that points to an instantiated method. - info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; - } + if (unboxingStub) + { + // Bail out for now. We need an unboxing stub that points to an instantiated method. + info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } #if READYTORUN - MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null); - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodDictionary, originalImplWithToken)); - + MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, methodWithTokenImpl.Token, null, false, null, null); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodDictionary, originalImplWithToken)); #else - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodGenericDictionary(originalImpl)); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodGenericDictionary(originalImpl)); #endif - } - else if (requiresInstMethodTableArg) - { - if (!unboxingStub) + } + else if (requiresInstMethodTableArg) { + if (!unboxingStub) + { #if READYTORUN - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeDictionary, originalImpl.OwningType)); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeDictionary, originalImpl.OwningType)); #else - info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ConstructedTypeSymbol(originalImpl.OwningType)); + info->instParamLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ConstructedTypeSymbol(originalImpl.OwningType)); #endif + } } } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 0c843d13aa1a7f..bc48fdd678cbb7 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1165,6 +1165,7 @@ public unsafe struct CORINFO_DEVIRTUALIZATION_INFO // [In] arguments of resolveVirtualMethod // public CORINFO_METHOD_STRUCT_* virtualMethod; + public CORINFO_METHOD_STRUCT_* callerMethod; public CORINFO_CLASS_STRUCT_* objClass; public CORINFO_CONTEXT_STRUCT* context; public CORINFO_RESOLVED_TOKEN* pResolvedTokenVirtualMethod; 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 aba1b7041cdd9c..84f4949d519248 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2738,11 +2738,12 @@ 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) { helperId = ReadyToRunHelperId.MethodHandle; } @@ -2770,21 +2771,38 @@ private void ComputeRuntimeLookupForSharedGenericToken( throw new NotImplementedException(entryKind.ToString()); } - object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); - if (helperArg is MethodDesc methodDesc) + object helperArg; + if (entryKind == DictionaryEntryKind.DevirtualizedMethodDescSlot) { - var methodIL = HandleToObject(pResolvedToken.tokenScope); - MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); - // We shouldn't be needing shared generics in resumption stubs - generics info should all be stored in the continuation - Debug.Assert(MethodBeingCompiled is not AsyncResumptionStub); - _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation), constrainedType, unboxing: false, genericContextObject: sharedMethod, forceOwningTypeFromMethodDesc: strippedInstantiation); + Debug.Assert(templateMethod != null); + MethodDesc tokenMethod = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + if (tokenMethod.HasInstantiation) + { + templateMethod = _compilation.TypeSystemContext.GetInstantiatedMethod(templateMethod.GetMethodDefinition(), tokenMethod.Instantiation); + } + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, templateMethod); + ModuleToken templateMethodToken = + _compilation.NodeFactory.Resolver.GetModuleTokenForMethod(templateMethod.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: true); + helperArg = new MethodWithToken(templateMethod, templateMethodToken, constrainedType: null, unboxing: false, genericContextObject: null); } - else if (helperArg is FieldDesc fieldDesc) + else { - ModuleToken fieldToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); - Debug.Assert(!strippedInstantiation); - helperArg = new FieldWithToken(fieldDesc, fieldToken, forceOwningTypeNotDerivedFromToken: strippedInstantiation); + helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken); + if (helperArg is MethodDesc methodDesc) + { + var methodIL = HandleToObject(pResolvedToken.tokenScope); + MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); + // We shouldn't be needing shared generics in resumption stubs - generics info should all be stored in the continuation + Debug.Assert(MethodBeingCompiled is not AsyncResumptionStub); + _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation), constrainedType, unboxing: false, genericContextObject: sharedMethod, forceOwningTypeFromMethodDesc: strippedInstantiation); + } + else if (helperArg is FieldDesc fieldDesc) + { + ModuleToken fieldToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); + Debug.Assert(!strippedInstantiation); + helperArg = new FieldWithToken(fieldDesc, fieldToken, forceOwningTypeNotDerivedFromToken: strippedInstantiation); + } } var methodContext = new GenericContext(callerHandle); diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 020112dbb4f40c..2e95102863f0ce 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -681,6 +681,7 @@ struct Agnostic_GetWasmTypeSymbol struct Agnostic_ResolveVirtualMethodKey { DWORDLONG virtualMethod; + DWORDLONG callerMethod; DWORDLONG objClass; DWORDLONG context; DWORD pResolvedTokenVirtualMethodNonNull; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index b9e8b4f17e81e8..65c5f807ade1e2 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3238,6 +3238,7 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info Agnostic_ResolveVirtualMethodKey key; ZeroMemory(&key, sizeof(key)); // Zero token including any struct padding key.virtualMethod = CastHandle(info->virtualMethod); + key.callerMethod = CastHandle(info->callerMethod); key.objClass = CastHandle(info->objClass); key.context = CastHandle(info->context); @@ -3269,8 +3270,10 @@ void MethodContext::recResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info void MethodContext::dmpResolveVirtualMethod(const Agnostic_ResolveVirtualMethodKey& key, const Agnostic_ResolveVirtualMethodResult& result) { - printf("ResolveVirtualMethod key virtMethod-%016" PRIX64 ", objClass-%016" PRIX64 ", context-%016" PRIX64 " pResolvedTokenVirtualMethodNonNull-%08X pResolvedTokenVirtualMethod{%s}", + printf("ResolveVirtualMethod key virtMethod-%016" PRIX64 ", callerMethod-%016" PRIX64 ", objClass-%016" PRIX64 + ", context-%016" PRIX64 " pResolvedTokenVirtualMethodNonNull-%08X pResolvedTokenVirtualMethod{%s}", key.virtualMethod, + key.callerMethod, key.objClass, key.context, key.pResolvedTokenVirtualMethodNonNull, @@ -3290,6 +3293,7 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info Agnostic_ResolveVirtualMethodKey key; ZeroMemory(&key, sizeof(key)); // Zero key including any struct padding key.virtualMethod = CastHandle(info->virtualMethod); + key.callerMethod = CastHandle(info->callerMethod); key.objClass = CastHandle(info->objClass); key.context = CastHandle(info->context); @@ -3297,7 +3301,7 @@ bool MethodContext::repResolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO * info if (key.pResolvedTokenVirtualMethodNonNull) key.pResolvedTokenVirtualMethod = SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKEN(info->pResolvedTokenVirtualMethod, ResolveToken); - Agnostic_ResolveVirtualMethodResult result = LookupByKeyOrMiss(ResolveVirtualMethod, key, ": %016" PRIX64 "-%016" PRIX64 "-%016" PRIX64 "-%08X", key.virtualMethod, key.objClass, key.context, key.pResolvedTokenVirtualMethodNonNull); + Agnostic_ResolveVirtualMethodResult result = LookupByKeyOrMiss(ResolveVirtualMethod, key, ": %016" PRIX64 "-%016" PRIX64 "-%016" PRIX64 "-%016" PRIX64 "-%08X", key.virtualMethod, key.callerMethod, key.objClass, key.context, key.pResolvedTokenVirtualMethodNonNull); DEBUG_REP(dmpResolveVirtualMethod(key, result)); diff --git a/src/coreclr/vm/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index e9d15c0fa6bb63..326f5500e53f52 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 f8fa996f3782aa..6237275af62ecd 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3209,11 +3209,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); @@ -8887,13 +8895,26 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) { - // TODO: Support for runtime lookup - info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; - return false; + if (info->pResolvedTokenVirtualMethod == nullptr) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + + ComputeRuntimeLookupForSharedGenericToken( + DevirtualizedMethodDescSlot, + info->pResolvedTokenVirtualMethod, + nullptr, + pInstantiatedMD, + GetMethod(info->callerMethod), + &info->instParamLookup); } - info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstantiatedMD; - info->instParamLookup.constLookup.accessType = IAT_VALUE; + if (!info->instParamLookup.lookupKind.needsRuntimeLookup) + { + info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstantiatedMD; + info->instParamLookup.constLookup.accessType = IAT_VALUE; + } } else if (pInstArgMD->RequiresInstMethodTableArg()) { @@ -8910,19 +8931,38 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) } else if (isUnboxingStubOfInstantiatingStub) { - if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetClassInstantiation()) || - TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) + if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetClassInstantiation())) { - // This is an unboxing stub that points to an instantiating stub that requires a runtime lookup. - // Bail out. + // If we end up with a shared MethodTable that is not exact, + // we can't devirtualize since it's not possible to compute the instantiation argument even as a runtime lookup. info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; } - // pInstArgMD is the wrapped instantiating stub in the unboxing stub. - // - info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstArgMD; - info->instParamLookup.constLookup.accessType = IAT_VALUE; + if (TypeHandle::IsCanonicalSubtypeInstantiation(pInstantiatedMD->GetMethodInstantiation())) + { + if (info->pResolvedTokenVirtualMethod == nullptr) + { + info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON; + return false; + } + + ComputeRuntimeLookupForSharedGenericToken( + DevirtualizedMethodDescSlot, + info->pResolvedTokenVirtualMethod, + nullptr, + pInstArgMD, + GetMethod(info->callerMethod), + &info->instParamLookup); + } + + if (!info->instParamLookup.lookupKind.needsRuntimeLookup) + { + // pInstArgMD is the wrapped instantiating stub in the unboxing stub. + // + info->instParamLookup.constLookup.handle = (CORINFO_GENERIC_HANDLE) pInstArgMD; + info->instParamLookup.constLookup.accessType = IAT_VALUE; + } } pDevirtMD = pDevirtMD->IsInstantiatingStub() ? pDevirtMD->GetWrappedMethodDesc() : pDevirtMD; @@ -8936,6 +8976,7 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info) if (pDevirtMD->IsUnboxingStub()) { MethodDesc* pUnboxedMD = pDevirtMD->GetMethodTable()->GetUnboxedEntryPointMD(pDevirtMD); + if (pUnboxedMD->IsInstantiatingStub()) pUnboxedMD = pUnboxedMD->GetWrappedMethodDesc(); info->resolvedTokenDevirtualizedUnboxedMethod.hMethod = (CORINFO_METHOD_HANDLE) pUnboxedMD; } diff --git a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs index c5e12ccf7f4609..10f6650ef18319 100644 --- a/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs +++ b/src/tests/JIT/Generics/VirtualMethods/generic_virtual_methods.cs @@ -151,6 +151,54 @@ public static void RuntimeLookupDelegate() RuntimeLookupDelegateGenericVirtual.TestGenericMethodOnStringType(); } + [Fact] + public static void RuntimeLookupInlining() + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static IDevirtTarget MakeTarget() + { + return new DevirtTarget(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Type LateInlinee() + { + return MakeTarget().GetTypeArg(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Type LateCaller() + { + return LateInlinee(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Type Inlinee(IDevirtTarget target) + { + return target.GetTypeArg(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Type Caller() + { + return Inlinee(new DevirtTarget()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static Type LateRootCaller() + { + return MakeTarget().GetTypeArg(); + } + + Type immediateActual = Caller(); + Type lateActual = LateCaller(); + Type lateRootActual = LateRootCaller(); + Type expected = typeof(object); + Assert.Equal(expected, immediateActual); + Assert.Equal(expected, lateActual); + Assert.Equal(expected, lateRootActual); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ValidateCaller(string scenarioName, IBaseMethodCaller caller) { @@ -492,6 +540,17 @@ public Delegate Foo() } } +interface IDevirtTarget +{ + Type GetTypeArg(); +} + +sealed class DevirtTarget : IDevirtTarget +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public Type GetTypeArg() => typeof(T); +} + internal static class IconContextBridgeNonShared { public static TMethod SameMethodSameClass(IBaseMethodCaller caller, TMethod value)