From 097b378852af23f7da2aa0ec1b3688e900791d30 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 24 Feb 2026 15:25:36 +0100 Subject: [PATCH 1/5] JIT: Refactor async-to-sync call optimization - Introduce `getAsyncOtherVariant` JIT-EE API that returns the other async variant of an async/task-returning method - Remove the `AwaitVirtual` token kind that was used to allow the VM to tell the JIT to prefer a task-returning instead of async variant - Implement the same optimization on the JIT side instead using the `getAsyncOtherVariant`, when we notice that a call will be direct Fix #124545 --- src/coreclr/inc/corinfo.h | 7 ++- src/coreclr/inc/icorjitinfoimpl_generated.h | 4 ++ src/coreclr/inc/jiteeversionguid.h | 10 ++-- src/coreclr/interpreter/compiler.cpp | 4 +- src/coreclr/jit/ICorJitInfo_names_generated.h | 1 + .../jit/ICorJitInfo_wrapper_generated.hpp | 10 ++++ src/coreclr/jit/ee_il_dll.hpp | 9 ++- src/coreclr/jit/importer.cpp | 58 ++++++++++++------- .../tools/Common/JitInterface/CorInfoImpl.cs | 23 +++----- .../JitInterface/CorInfoImpl_generated.cs | 17 ++++++ .../tools/Common/JitInterface/CorInfoTypes.cs | 1 - .../ThunkGenerator/ThunkInput.txt | 1 + .../IL/ILImporter.Scanner.cs | 12 ---- src/coreclr/tools/aot/crossgen2.slnx | 3 + src/coreclr/tools/aot/ilc.slnx | 3 + .../aot/jitinterface/jitinterface_generated.h | 11 ++++ .../tools/superpmi/superpmi-shared/lwmlist.h | 1 + .../superpmi-shared/methodcontext.cpp | 43 ++++++++++++++ .../superpmi/superpmi-shared/methodcontext.h | 5 ++ .../superpmi-shim-collector/icorjitinfo.cpp | 13 +++++ .../icorjitinfo_generated.cpp | 8 +++ .../icorjitinfo_generated.cpp | 7 +++ .../tools/superpmi/superpmi/icorjitinfo.cpp | 7 +++ src/coreclr/vm/jitinterface.cpp | 52 ++++++++--------- 24 files changed, 222 insertions(+), 88 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index a52f1dcba32b0b..700b26285279be 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1487,7 +1487,6 @@ enum CorInfoTokenKind // token comes from runtime async awaiting pattern CORINFO_TOKENKIND_Await = 0x2000 | CORINFO_TOKENKIND_Method, - CORINFO_TOKENKIND_AwaitVirtual = 0x4000 | CORINFO_TOKENKIND_Method, }; struct CORINFO_RESOLVED_TOKEN @@ -2290,6 +2289,12 @@ class ICorStaticInfo CORINFO_CLASS_HANDLE* classArg ) = 0; + // Get the other variant of an async method, if possible. + virtual CORINFO_METHOD_HANDLE getAsyncOtherVariant( + CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk + ) = 0; + // Given T, return the type of the default Comparer. // Returns null if the type can't be determined exactly. virtual CORINFO_CLASS_HANDLE getDefaultComparerClass( diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index cd2823047fa2ec..7a67f9ea0ff3cd 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -104,6 +104,10 @@ CORINFO_METHOD_HANDLE getInstantiatedEntry( CORINFO_METHOD_HANDLE* methodArg, CORINFO_CLASS_HANDLE* classArg) override; +CORINFO_METHOD_HANDLE getAsyncOtherVariant( + CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk) override; + CORINFO_CLASS_HANDLE getDefaultComparerClass( CORINFO_CLASS_HANDLE elemType) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index e0da2147d7f6e4..73af122681ea79 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 4379bd40-8b3b-4643-a6da-cb62e55e2e6c */ - 0x4379bd40, - 0x8b3b, - 0x4643, - {0xa6, 0xda, 0xcb, 0x62, 0xe5, 0x5e, 0x2e, 0x6c} +constexpr GUID JITEEVersionIdentifier = { /* 22511e72-5ac8-4fc8-83ef-0b61688c68bb */ + 0x22511e72, + 0x5ac8, + 0x4fc8, + {0x83, 0xef, 0x0b, 0x61, 0x68, 0x8c, 0x68, 0xbb} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index b5924423564ee6..2ac5a1abcf418a 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -6741,9 +6741,7 @@ int InterpCompiler::ApplyLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElem bool InterpCompiler::ResolveAsyncCallToken(const uint8_t* ip) { - CorInfoTokenKind tokenKind = - ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; - ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); + ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken); return m_resolvedAsyncCallToken.hMethod != NULL; } diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index f6d1edf75d754e..d03d03b1007970 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -23,6 +23,7 @@ DEF_CLR_API(getMethodVTableOffset) DEF_CLR_API(resolveVirtualMethod) DEF_CLR_API(getUnboxedEntry) DEF_CLR_API(getInstantiatedEntry) +DEF_CLR_API(getAsyncOtherVariant) DEF_CLR_API(getDefaultComparerClass) DEF_CLR_API(getDefaultEqualityComparerClass) DEF_CLR_API(getSZArrayHelperEnumeratorClass) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index b9db37ba260055..80411912d6c9cc 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -202,6 +202,16 @@ CORINFO_METHOD_HANDLE WrapICorJitInfo::getInstantiatedEntry( return temp; } +CORINFO_METHOD_HANDLE WrapICorJitInfo::getAsyncOtherVariant( + CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk) +{ + API_ENTER(getAsyncOtherVariant); + CORINFO_METHOD_HANDLE temp = wrapHnd->getAsyncOtherVariant(ftn, variantIsThunk); + API_LEAVE(getAsyncOtherVariant); + return temp; +} + CORINFO_CLASS_HANDLE WrapICorJitInfo::getDefaultComparerClass( CORINFO_CLASS_HANDLE elemType) { diff --git a/src/coreclr/jit/ee_il_dll.hpp b/src/coreclr/jit/ee_il_dll.hpp index 303340a446612b..512384824d1a1d 100644 --- a/src/coreclr/jit/ee_il_dll.hpp +++ b/src/coreclr/jit/ee_il_dll.hpp @@ -339,7 +339,12 @@ inline var_types Compiler::TypeHandleToVarType(CorInfoType jitType, CORINFO_CLAS return type; } -inline CORINFO_CALLINFO_FLAGS combine(CORINFO_CALLINFO_FLAGS flag1, CORINFO_CALLINFO_FLAGS flag2) +constexpr CORINFO_CALLINFO_FLAGS operator|(CORINFO_CALLINFO_FLAGS a, CORINFO_CALLINFO_FLAGS b) { - return (CORINFO_CALLINFO_FLAGS)(flag1 | flag2); + return (CORINFO_CALLINFO_FLAGS)((uint32_t)a | (uint32_t)b); +} + +inline CORINFO_CALLINFO_FLAGS& operator|=(CORINFO_CALLINFO_FLAGS& a, CORINFO_CALLINFO_FLAGS b) +{ + return a = (CORINFO_CALLINFO_FLAGS)((uint32_t)a | (uint32_t)b); } diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index f04f516cf663da..2f7e37ad755e50 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -8556,7 +8556,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) JITDUMP(" %08X", resolvedToken.token); eeGetCallInfo(&resolvedToken, (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, - combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN), &callInfo); + CORINFO_CALLINFO_SECURITYCHECKS | CORINFO_CALLINFO_LDFTN, &callInfo); // This check really only applies to intrinsic Array.Address methods if (callInfo.sig.callConv & CORINFO_CALLCONV_PARAMTYPE) @@ -8595,8 +8595,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) JITDUMP(" %08X", resolvedToken.token); eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef */, - combine(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN), - CORINFO_CALLINFO_CALLVIRT), + CORINFO_CALLINFO_SECURITYCHECKS | CORINFO_CALLINFO_LDFTN | CORINFO_CALLINFO_CALLVIRT, &callInfo); // This check really only applies to intrinsic Array.Address methods @@ -8729,7 +8728,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) _impResolveToken(CORINFO_TOKENKIND_NewObj); eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef*/, - combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_ALLOWINSTPARAM), &callInfo); + CORINFO_CALLINFO_SECURITYCHECKS | CORINFO_CALLINFO_ALLOWINSTPARAM, &callInfo); mflags = callInfo.methodFlags; @@ -8982,16 +8981,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (isAwait) { - _impResolveToken(opcode == CEE_CALLVIRT ? CORINFO_TOKENKIND_AwaitVirtual - : CORINFO_TOKENKIND_Await); - if (resolvedToken.hMethod != nullptr) - { - // There is a runtime async variant that is implicitly awaitable, just call that. - // skip the await pattern to the last token. - codeAddr = codeAddrAfterMatch; - opcodeOffs = awaitOffset; - } - else + _impResolveToken(CORINFO_TOKENKIND_Await); + if (resolvedToken.hMethod == nullptr) { // This can happen in cases when the Task-returning method is not a runtime Async // function. For example "T M1(T arg) => arg" when called with a Task argument. @@ -9009,12 +9000,39 @@ void Compiler::impImportBlockCode(BasicBlock* block) _impResolveToken(CORINFO_TOKENKIND_Method); } - eeGetCallInfo(&resolvedToken, - (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, - // this is how impImportCall invokes getCallInfo - combine(combine(CORINFO_CALLINFO_ALLOWINSTPARAM, CORINFO_CALLINFO_SECURITYCHECKS), - (opcode == CEE_CALLVIRT) ? CORINFO_CALLINFO_CALLVIRT : CORINFO_CALLINFO_NONE), - &callInfo); + CORINFO_CALLINFO_FLAGS flags = CORINFO_CALLINFO_ALLOWINSTPARAM | CORINFO_CALLINFO_SECURITYCHECKS; + if (opcode == CEE_CALLVIRT) + { + flags |= CORINFO_CALLINFO_CALLVIRT; + } + + eeGetCallInfo(&resolvedToken, (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags, &callInfo); + + if (isAwait && (callInfo.kind == CORINFO_CALL)) + { + assert(callInfo.sig.isAsyncCall()); + bool isSyncCallThunk; + info.compCompHnd->getAsyncOtherVariant(callInfo.hMethod, &isSyncCallThunk); + if (!isSyncCallThunk) + { + // Otherwise the async variant that we got is a thunk. + // Switch back to the non-async task-returning call. There is no reason to go through the thunk. + _impResolveToken(CORINFO_TOKENKIND_Method); + prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT); + isAwait = false; + + JITDUMP("Async variant provided by VM is a thunk, switching direct call to synchronous task-returning method\n"); + eeGetCallInfo(&resolvedToken, (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags, &callInfo); + } + } + + if (isAwait) + { + // If the synchronous call is a thunk then it means the async variant is not a thunk and we prefer + // to directly call it. Skip the await pattern to the last token. + codeAddr = codeAddrAfterMatch; + opcodeOffs = awaitOffset; + } } else { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index f82c3914a4cf57..8d8ba5a3fe147e 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1558,6 +1558,11 @@ static CORINFO_RESOLVED_TOKEN CreateResolvedTokenFromMethod(CorInfoImpl jitInter return null; } + private CORINFO_METHOD_STRUCT_* getAsyncOtherVariant(CORINFO_METHOD_STRUCT_* ftn, ref bool variantIsThunk) + { + throw new NotImplementedException(); + } + private CORINFO_CLASS_STRUCT_* getDefaultComparerClass(CORINFO_CLASS_STRUCT_* elemType) { TypeDesc comparand = HandleToObject(elemType); @@ -1778,7 +1783,7 @@ private object GetRuntimeDeterminedObjectForToken(ref CORINFO_RESOLVED_TOKEN pRe if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Newarr) result = ((TypeDesc)result).MakeArrayType(); - if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.CORINFO_TOKENKIND_AwaitVirtual) + if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await) result = _compilation.TypeSystemContext.GetAsyncVariantMethod((MethodDesc)result); return result; @@ -1868,7 +1873,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method); #endif - if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.CORINFO_TOKENKIND_AwaitVirtual) + if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await) { // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). // we cannot resolve to an Async variant in such case. @@ -1878,20 +1883,6 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; -#if !READYTORUN - if (allowAsyncVariant) - { - bool isDirect = pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await || method.IsCallEffectivelyDirect(); - if (isDirect && !method.IsAsync) - { - // Async variant would be a thunk. Do not resolve direct calls - // to async thunks. That just creates and JITs unnecessary - // thunks, and the thunks are harder for the JIT to optimize. - allowAsyncVariant = false; - } - } -#endif - method = allowAsyncVariant ? _compilation.TypeSystemContext.GetAsyncVariantMethod(method) : null; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index c38588671117c5..a1eebc3762aaa1 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -39,6 +39,7 @@ static ICorJitInfoCallbacks() s_callbacks.resolveVirtualMethod = &_resolveVirtualMethod; s_callbacks.getUnboxedEntry = &_getUnboxedEntry; s_callbacks.getInstantiatedEntry = &_getInstantiatedEntry; + s_callbacks.getAsyncOtherVariant = &_getAsyncOtherVariant; s_callbacks.getDefaultComparerClass = &_getDefaultComparerClass; s_callbacks.getDefaultEqualityComparerClass = &_getDefaultEqualityComparerClass; s_callbacks.getSZArrayHelperEnumeratorClass = &_getSZArrayHelperEnumeratorClass; @@ -220,6 +221,7 @@ static ICorJitInfoCallbacks() public delegate* unmanaged resolveVirtualMethod; public delegate* unmanaged getUnboxedEntry; public delegate* unmanaged getInstantiatedEntry; + public delegate* unmanaged getAsyncOtherVariant; public delegate* unmanaged getDefaultComparerClass; public delegate* unmanaged getDefaultEqualityComparerClass; public delegate* unmanaged getSZArrayHelperEnumeratorClass; @@ -665,6 +667,21 @@ private static byte _resolveVirtualMethod(IntPtr thisHandle, IntPtr* ppException } } + [UnmanagedCallersOnly] + private static CORINFO_METHOD_STRUCT_* _getAsyncOtherVariant(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* ftn, bool* variantIsThunk) + { + var _this = GetThis(thisHandle); + try + { + return _this.getAsyncOtherVariant(ftn, ref *variantIsThunk); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static CORINFO_CLASS_STRUCT_* _getDefaultComparerClass(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* elemType) { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 03f6049219c934..4ba39d65fefc2c 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1476,7 +1476,6 @@ public enum CorInfoTokenKind // token comes from runtime async awaiting pattern CORINFO_TOKENKIND_Await = 0x2000 | CORINFO_TOKENKIND_Method, - CORINFO_TOKENKIND_AwaitVirtual = 0x4000 | CORINFO_TOKENKIND_Method, }; // These are error codes returned by CompileMethod diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 0b7d236841129f..8e605002f90174 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -192,6 +192,7 @@ FUNCTIONS bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info); CORINFO_METHOD_HANDLE getUnboxedEntry(CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg); CORINFO_METHOD_HANDLE getInstantiatedEntry(CORINFO_METHOD_HANDLE ftn, CORINFO_METHOD_HANDLE* methodArg, CORINFO_CLASS_HANDLE* classArg); + CORINFO_METHOD_HANDLE getAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk); CORINFO_CLASS_HANDLE getDefaultComparerClass(CORINFO_CLASS_HANDLE elemType); CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE elemType); CORINFO_CLASS_HANDLE getSZArrayHelperEnumeratorClass(CORINFO_CLASS_HANDLE elemType); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index 4394d08ad8240b..870ebf9b5adbac 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -484,18 +484,6 @@ private void ImportCall(ILOpcode opcode, int token) // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; - if (allowAsyncVariant) - { - bool isDirect = opcode == ILOpcode.call || method.IsCallEffectivelyDirect(); - if (isDirect && !method.IsAsync) - { - // Async variant would be a thunk. Do not resolve direct calls - // to async thunks. That just creates and JITs unnecessary - // thunks, and the thunks are harder for the JIT to optimize. - allowAsyncVariant = false; - } - } - if (allowAsyncVariant && MatchTaskAwaitPattern()) { runtimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); diff --git a/src/coreclr/tools/aot/crossgen2.slnx b/src/coreclr/tools/aot/crossgen2.slnx index f404d3a841f868..b79a67596c466f 100644 --- a/src/coreclr/tools/aot/crossgen2.slnx +++ b/src/coreclr/tools/aot/crossgen2.slnx @@ -7,6 +7,9 @@ + + + diff --git a/src/coreclr/tools/aot/ilc.slnx b/src/coreclr/tools/aot/ilc.slnx index ba4d00a6f349f9..9e797384a2645f 100644 --- a/src/coreclr/tools/aot/ilc.slnx +++ b/src/coreclr/tools/aot/ilc.slnx @@ -7,6 +7,9 @@ + + + diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index eef827200da7f4..dec68b1eb53d4f 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -30,6 +30,7 @@ struct JitInterfaceCallbacks bool (* resolveVirtualMethod)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_DEVIRTUALIZATION_INFO* info); CORINFO_METHOD_HANDLE (* getUnboxedEntry)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg); CORINFO_METHOD_HANDLE (* getInstantiatedEntry)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, CORINFO_METHOD_HANDLE* methodArg, CORINFO_CLASS_HANDLE* classArg); + CORINFO_METHOD_HANDLE (* getAsyncOtherVariant)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk); CORINFO_CLASS_HANDLE (* getDefaultComparerClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE elemType); CORINFO_CLASS_HANDLE (* getDefaultEqualityComparerClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE elemType); CORINFO_CLASS_HANDLE (* getSZArrayHelperEnumeratorClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE elemType); @@ -395,6 +396,16 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } + virtual CORINFO_METHOD_HANDLE getAsyncOtherVariant( + CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk) +{ + CorInfoExceptionClass* pException = nullptr; + CORINFO_METHOD_HANDLE temp = _callbacks->getAsyncOtherVariant(_thisHandle, &pException, ftn, variantIsThunk); + if (pException != nullptr) throw pException; + return temp; +} + virtual CORINFO_CLASS_HANDLE getDefaultComparerClass( CORINFO_CLASS_HANDLE elemType) { diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index 9240baa75f0d69..b464feef60b3f6 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -140,6 +140,7 @@ LWM(GetTypeForPrimitiveValueClass, DWORDLONG, DWORD) LWM(GetTypeForPrimitiveNumericClass, DWORDLONG, DWORD) LWM(GetUnboxedEntry, DWORDLONG, DLD); LWM(GetInstantiatedEntry, DWORDLONG, Agnostic_GetInstantiatedEntryResult); +LWM(GetAsyncOtherVariant, DWORDLONG, DLD); LWM(GetUnBoxHelper, DWORDLONG, DWORD) LWM(GetRuntimeTypePointer, DWORDLONG, DWORDLONG) LWM(IsObjectImmutable, DWORDLONG, DWORD) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index d16bb9aed25e3b..b070dec81ccb23 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3397,6 +3397,49 @@ CORINFO_METHOD_HANDLE MethodContext::repGetInstantiatedEntry(CORINFO_METHOD_HAND return (CORINFO_METHOD_HANDLE)(value.result); } +void MethodContext::recGetAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk, + CORINFO_METHOD_HANDLE result) +{ + if (GetAsyncOtherVariant == nullptr) + { + GetAsyncOtherVariant = new LightWeightMap(); + } + + DWORDLONG key = CastHandle(ftn); + DLD value; + value.A = CastHandle(result); + if (variantIsThunk != nullptr) + { + value.B = (DWORD)*variantIsThunk ? 1 : 0; + } + else + { + value.B = 0; + } + GetAsyncOtherVariant->Add(key, value); + DEBUG_REC(dmpGetAsyncOtherVariant(key, value)); +} + +void MethodContext::dmpGetAsyncOtherVariant(DWORDLONG key, DLD value) +{ + printf("GetAsyncOtherVariant ftn-%016" PRIX64 ", result-%016" PRIX64 ", variantIsThunk-%u", key, value.A, value.B); +} + +CORINFO_METHOD_HANDLE MethodContext::repGetAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk) +{ + DWORDLONG key = CastHandle(ftn); + + DLD value = LookupByKeyOrMiss(GetAsyncOtherVariant, key, ": key %016" PRIX64 "", key); + DEBUG_REP(dmpGetAsyncOtherVariant(key, value)); + + if (variantIsThunk != nullptr) + { + *variantIsThunk = (value.B == 1); + } + return (CORINFO_METHOD_HANDLE)(value.A); +} + void MethodContext::recGetDefaultComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result) { if (GetDefaultComparerClass == nullptr) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index 34cafcc5f986c6..5f39cdce3853e0 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -459,6 +459,10 @@ class MethodContext CORINFO_METHOD_HANDLE* methodHandle, CORINFO_CLASS_HANDLE* classHandle); + void recGetAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk, CORINFO_METHOD_HANDLE result); + void dmpGetAsyncOtherVariant(DWORDLONG key, DLD value); + CORINFO_METHOD_HANDLE repGetAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk); + void recGetDefaultComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result); void dmpGetDefaultComparerClass(DWORDLONG key, DWORDLONG value); CORINFO_CLASS_HANDLE repGetDefaultComparerClass(CORINFO_CLASS_HANDLE cls); @@ -1217,6 +1221,7 @@ enum mcPackets Packet_GetContinuationType = 234, Packet_GetWasmTypeSymbol = 235, Packet_GetWasmLowering = 236, + Packet_GetAsyncOtherVariant = 237, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 878d207c8ef34c..c5c5e47afbf684 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -263,6 +263,19 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::getInstantiatedEntry(CORINFO_METHOD_HAND return result; } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk) +{ + mc->cr->AddCall("getAsyncOtherVariant"); + bool localVariantIsThunk = false; + CORINFO_METHOD_HANDLE result = original_ICorJitInfo->getAsyncOtherVariant(ftn, &localVariantIsThunk); + mc->recGetAsyncOtherVariant(ftn, &localVariantIsThunk, result); + if (variantIsThunk != nullptr) + { + *variantIsThunk = localVariantIsThunk; + } + return result; +} + // Given T, return the type of the default Comparer. // Returns null if the type can't be determined exactly. CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultComparerClass(CORINFO_CLASS_HANDLE cls) diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index afadb096655c76..8058b6802159e8 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -171,6 +171,14 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::getInstantiatedEntry( return original_ICorJitInfo->getInstantiatedEntry(ftn, methodArg, classArg); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncOtherVariant( + CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk) +{ + mcs->AddCall("getAsyncOtherVariant"); + return original_ICorJitInfo->getAsyncOtherVariant(ftn, variantIsThunk); +} + CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultComparerClass( CORINFO_CLASS_HANDLE elemType) { diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index d90cfd56537b2c..852a318f83c225 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -152,6 +152,13 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::getInstantiatedEntry( return original_ICorJitInfo->getInstantiatedEntry(ftn, methodArg, classArg); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncOtherVariant( + CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk) +{ + return original_ICorJitInfo->getAsyncOtherVariant(ftn, variantIsThunk); +} + CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultComparerClass( CORINFO_CLASS_HANDLE elemType) { diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 589de2655a3d76..20372434076341 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -218,6 +218,13 @@ CORINFO_METHOD_HANDLE MyICJI::getInstantiatedEntry(CORINFO_METHOD_HANDLE ftn, CO return result; } +CORINFO_METHOD_HANDLE MyICJI::getAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, bool* variantIsThunk) +{ + jitInstance->mc->cr->AddCall("getAsyncOtherVariant"); + CORINFO_METHOD_HANDLE result = jitInstance->mc->repGetAsyncOtherVariant(ftn, variantIsThunk); + return result; +} + // Given T, return the type of the default Comparer. // Returns null if the type can't be determined exactly. CORINFO_CLASS_HANDLE MyICJI::getDefaultComparerClass(CORINFO_CLASS_HANDLE cls) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 33bea7016ae278..a7864115c92021 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1081,38 +1081,11 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken break; case CORINFO_TOKENKIND_Await: - case CORINFO_TOKENKIND_AwaitVirtual: { // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). // we cannot resolve to an Async variant in such case. // return NULL, so that caller would re-resolve as a regular method call - bool allowAsyncVariant = pMD->ReturnsTaskOrValueTask(); - if (allowAsyncVariant) - { - bool isDirect = tokenType == CORINFO_TOKENKIND_Await || pMD->IsStatic(); - if (!isDirect) - { - DWORD attrs = pMD->GetAttrs(); - if (pMD->GetMethodTable()->IsInterface()) - { - isDirect = !IsMdVirtual(attrs); - } - else - { - isDirect = !IsMdVirtual(attrs) || IsMdFinal(attrs) || pMD->GetMethodTable()->IsSealed(); - } - } - - if (isDirect && !pMD->IsAsyncThunkMethod()) - { - // Async variant would be a thunk. Do not resolve direct calls - // to async thunks. That just creates and JITs unnecessary - // thunks, and the thunks are harder for the JIT to optimize. - allowAsyncVariant = false; - } - } - - pMD = allowAsyncVariant ? pMD->GetAsyncVariant(/*allowInstParam*/FALSE) : NULL; + pMD = pMD->ReturnsTaskOrValueTask() ? pMD->GetAsyncVariant(/*allowInstParam*/FALSE) : NULL; } break; @@ -8995,6 +8968,29 @@ CORINFO_METHOD_HANDLE CEEInfo::getInstantiatedEntry( return result; } +CORINFO_METHOD_HANDLE CEEInfo::getAsyncOtherVariant( + CORINFO_METHOD_HANDLE ftn, + bool* variantIsThunk) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + CORINFO_METHOD_HANDLE result = NULL; + + JIT_TO_EE_TRANSITION(); + + MethodDesc* pMD = GetMethod(ftn); + MethodDesc* pAsyncOtherVariant = pMD->GetAsyncOtherVariant(); + *variantIsThunk = pAsyncOtherVariant != NULL && pAsyncOtherVariant->IsAsyncThunkMethod(); + + EE_TO_JIT_TRANSITION(); + + return result; +} + /*********************************************************************/ void CEEInfo::expandRawHandleIntrinsic( CORINFO_RESOLVED_TOKEN * pResolvedToken, From 8ef22c8ddfdcd6cdfeced66c558e6b36e93f975b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 24 Feb 2026 17:22:52 +0100 Subject: [PATCH 2/5] Managed type system impl --- .../tools/Common/JitInterface/CorInfoImpl.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 8d8ba5a3fe147e..dcf9977ec551b1 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1560,7 +1560,23 @@ static CORINFO_RESOLVED_TOKEN CreateResolvedTokenFromMethod(CorInfoImpl jitInter private CORINFO_METHOD_STRUCT_* getAsyncOtherVariant(CORINFO_METHOD_STRUCT_* ftn, ref bool variantIsThunk) { - throw new NotImplementedException(); + MethodDesc method = HandleToObject(ftn); + if (method.IsAsyncVariant()) + { + method = method.GetTargetOfAsyncVariant(); + } + else if (method.Signature.ReturnsTaskOrValueTask()) + { + method = method.GetAsyncVariant(); + } + else + { + variantIsThunk = false; + return null; + } + + variantIsThunk = method?.IsAsyncThunk() ?? false; + return ObjectToHandle(method); } private CORINFO_CLASS_STRUCT_* getDefaultComparerClass(CORINFO_CLASS_STRUCT_* elemType) From 1460458a70cf44e0c609fdcf030304f7638c1a9e Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 24 Feb 2026 17:25:38 +0100 Subject: [PATCH 3/5] Fix --- src/coreclr/vm/jitinterface.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index a7864115c92021..b87b75787bd4a6 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8983,7 +8983,12 @@ CORINFO_METHOD_HANDLE CEEInfo::getAsyncOtherVariant( JIT_TO_EE_TRANSITION(); MethodDesc* pMD = GetMethod(ftn); - MethodDesc* pAsyncOtherVariant = pMD->GetAsyncOtherVariant(); + MethodDesc* pAsyncOtherVariant = NULL; + if (pMD->HasAsyncMethodData()) + { + pAsyncOtherVariant = pMD->GetAsyncOtherVariant(); + } + result = (CORINFO_METHOD_HANDLE)pAsyncOtherVariant; *variantIsThunk = pAsyncOtherVariant != NULL && pAsyncOtherVariant->IsAsyncThunkMethod(); EE_TO_JIT_TRANSITION(); From d3f8649cf8dc10a3970e01c033e2dd456676c202 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 24 Feb 2026 17:31:07 +0100 Subject: [PATCH 4/5] Disallow null variantIsThunk --- .../superpmi/superpmi-shared/methodcontext.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index b070dec81ccb23..38149f1cb5a06c 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3409,14 +3409,7 @@ void MethodContext::recGetAsyncOtherVariant(CORINFO_METHOD_HANDLE ftn, DWORDLONG key = CastHandle(ftn); DLD value; value.A = CastHandle(result); - if (variantIsThunk != nullptr) - { - value.B = (DWORD)*variantIsThunk ? 1 : 0; - } - else - { - value.B = 0; - } + value.B = (DWORD)*variantIsThunk ? 1 : 0; GetAsyncOtherVariant->Add(key, value); DEBUG_REC(dmpGetAsyncOtherVariant(key, value)); } @@ -3432,11 +3425,7 @@ CORINFO_METHOD_HANDLE MethodContext::repGetAsyncOtherVariant(CORINFO_METHOD_HAND DLD value = LookupByKeyOrMiss(GetAsyncOtherVariant, key, ": key %016" PRIX64 "", key); DEBUG_REP(dmpGetAsyncOtherVariant(key, value)); - - if (variantIsThunk != nullptr) - { - *variantIsThunk = (value.B == 1); - } + *variantIsThunk = (value.B != 0); return (CORINFO_METHOD_HANDLE)(value.A); } From 2a664b17650de95e77b7fff91125e2eae8850b94 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 24 Feb 2026 17:34:14 +0100 Subject: [PATCH 5/5] Run jit-format --- src/coreclr/jit/importer.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 2f7e37ad755e50..43f775ae82e08b 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9006,7 +9006,9 @@ void Compiler::impImportBlockCode(BasicBlock* block) flags |= CORINFO_CALLINFO_CALLVIRT; } - eeGetCallInfo(&resolvedToken, (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags, &callInfo); + eeGetCallInfo(&resolvedToken, + (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags, + &callInfo); if (isAwait && (callInfo.kind == CORINFO_CALL)) { @@ -9016,20 +9018,24 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (!isSyncCallThunk) { // Otherwise the async variant that we got is a thunk. - // Switch back to the non-async task-returning call. There is no reason to go through the thunk. + // Switch back to the non-async task-returning call. There is no reason to go through the + // thunk. _impResolveToken(CORINFO_TOKENKIND_Method); prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT); isAwait = false; - JITDUMP("Async variant provided by VM is a thunk, switching direct call to synchronous task-returning method\n"); - eeGetCallInfo(&resolvedToken, (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags, &callInfo); + JITDUMP( + "Async variant provided by VM is a thunk, switching direct call to synchronous task-returning method\n"); + eeGetCallInfo(&resolvedToken, + (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, + flags, &callInfo); } } if (isAwait) { - // If the synchronous call is a thunk then it means the async variant is not a thunk and we prefer - // to directly call it. Skip the await pattern to the last token. + // If the synchronous call is a thunk then it means the async variant is not a thunk and we + // prefer to directly call it. Skip the await pattern to the last token. codeAddr = codeAddrAfterMatch; opcodeOffs = awaitOffset; }