From 2289c30eaf4a7691cf3d981a6cd9fd81806057e9 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 11 Feb 2026 17:27:12 +0100 Subject: [PATCH 1/6] Avoid resolving direct awaits to thunks This just results in extra jitting and the thunks are harder for the JIT to see through and optimize. --- src/coreclr/inc/corinfo.h | 1 + src/coreclr/jit/importer.cpp | 2 +- .../tools/Common/JitInterface/CorInfoImpl.cs | 38 ++++++++++++++++++- .../tools/Common/JitInterface/CorInfoTypes.cs | 1 + .../JitInterface/CorInfoImpl.RyuJit.cs | 25 +++++------- src/coreclr/vm/jitinterface.cpp | 32 ++++++++++++++-- 6 files changed, 77 insertions(+), 22 deletions(-) diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index d49ec0a1de4b06..33a985f31e7c4a 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -1476,6 +1476,7 @@ 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 diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 2fa1023a1587d0..67124ad042333c 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9020,7 +9020,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (isAwait) { - _impResolveToken(CORINFO_TOKENKIND_Await); + _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. diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index c532ddb8564778..636f37c18d69ec 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1776,12 +1776,46 @@ private object GetRuntimeDeterminedObjectForToken(ref CORINFO_RESOLVED_TOKEN pRe if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Newarr) result = ((TypeDesc)result).MakeArrayType(); - if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await) - result = _compilation.TypeSystemContext.GetAsyncVariantMethod((MethodDesc)result); + if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.CORINFO_TOKENKIND_AwaitVirtual) + { + bool isDirect = pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await || IsCallEffectivelyDirect((MethodDesc)result); + if (isDirect && !((MethodDesc)result).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. + } + else + { + result = _compilation.TypeSystemContext.GetAsyncVariantMethod((MethodDesc)result); + } + } return result; } + private bool IsCallEffectivelyDirect(MethodDesc method) + { + if (!method.IsVirtual) + { + return true; + } + + // Final/sealed has no meaning for interfaces, but might let us devirtualize otherwise + if (method.OwningType.IsInterface) + { + return false; + } + + // Check if we can devirt per metadata + if (method.IsFinal || method.OwningType.IsSealed()) + { + return true; + } + + return false; + } + private static object GetRuntimeDeterminedObjectForToken(MethodILScope methodIL, object typeOrMethodContext, mdToken token) { object result = ResolveTokenInScope(methodIL, typeOrMethodContext, token); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index e72852d5a14ce0..ee21fa8da783fd 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1469,6 +1469,7 @@ 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/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 38dec180ef6361..2f2228a2d6fa28 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -1363,23 +1363,16 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO else { // We can devirtualize the callvirt if the method is not virtual to begin with - bool canDevirt = !targetMethod.IsVirtual; - - // Final/sealed has no meaning for interfaces, but might let us devirtualize otherwise - if (!canDevirt && !targetMethod.OwningType.IsInterface) + bool canDevirt = IsCallEffectivelyDirect(method); + + // We might be able to devirt based on whole program view + if (!canDevirt + // Do not devirt if devirtualization would need a generic dictionary entry that we didn't predict + // during scanning (i.e. compiling a shared method body and we need to call another shared body + // with a method generic dictionary argument). + && (!pResult->exactContextNeedsRuntimeLookup || !targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg())) { - // Check if we can devirt per metadata - canDevirt = targetMethod.IsFinal || targetMethod.OwningType.IsSealed(); - - // We might be able to devirt based on whole program view - if (!canDevirt - // Do not devirt if devirtualization would need a generic dictionary entry that we didn't predict - // during scanning (i.e. compiling a shared method body and we need to call another shared body - // with a method generic dictionary argument). - && (!pResult->exactContextNeedsRuntimeLookup || !targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg())) - { - canDevirt = _compilation.IsEffectivelySealed(targetMethod); - } + canDevirt = _compilation.IsEffectivelySealed(method); } if (canDevirt) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index f4a17d840d5f51..0373b1d566828d 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1081,12 +1081,38 @@ 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 - pMD = pMD->ReturnsTaskOrValueTask() ? - pMD->GetAsyncVariant(/*allowInstParam*/FALSE): - NULL; + if (pMD->ReturnsTaskOrValueTask()) + { + 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. + pMD = NULL; + } + else + { + pMD = pMD->GetAsyncVariant(/*allowInstParam*/FALSE); + } + } break; From c92ba114b2d001f8b3f19abfd23d57e149e7cc30 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 11 Feb 2026 18:29:41 +0100 Subject: [PATCH 2/6] Copilot feedback --- .../aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 2f2228a2d6fa28..a0c3129a49241f 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -1363,7 +1363,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO else { // We can devirtualize the callvirt if the method is not virtual to begin with - bool canDevirt = IsCallEffectivelyDirect(method); + bool canDevirt = IsCallEffectivelyDirect(targetMethod); // We might be able to devirt based on whole program view if (!canDevirt @@ -1372,7 +1372,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO // with a method generic dictionary argument). && (!pResult->exactContextNeedsRuntimeLookup || !targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg())) { - canDevirt = _compilation.IsEffectivelySealed(method); + canDevirt = _compilation.IsEffectivelySealed(targetMethod); } if (canDevirt) From b178ea05a0a588a6de722d0376a62f08386cedf5 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 11 Feb 2026 18:58:15 +0100 Subject: [PATCH 3/6] Run jit-format --- src/coreclr/jit/importer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 67124ad042333c..af081c6b70d08f 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9020,7 +9020,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (isAwait) { - _impResolveToken(opcode == CEE_CALLVIRT ? CORINFO_TOKENKIND_AwaitVirtual : CORINFO_TOKENKIND_Await); + _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. From fa0d8a67d238c53c054eec126f984d19fd040480 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 12 Feb 2026 13:58:29 +0100 Subject: [PATCH 4/6] Refactor, fix bugs, address feedback --- src/coreclr/interpreter/compiler.cpp | 16 +++++++--- src/coreclr/jit/importer.cpp | 13 ++++++-- .../tools/Common/JitInterface/CorInfoImpl.cs | 30 ++++++++++--------- src/coreclr/vm/jitinterface.cpp | 10 +++---- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index eed9b25e922ba5..ba5c7f5b74a0c8 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -6749,7 +6749,9 @@ bool InterpCompiler::IsRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* pa return false; } - ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken); + CorInfoTokenKind tokenKind = + ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; + ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); if (m_resolvedAsyncCallToken.hMethod == NULL) { return false; @@ -6818,7 +6820,9 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, Opc return false; } - ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken); + CorInfoTokenKind tokenKind = + ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; + ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); if (m_resolvedAsyncCallToken.hMethod == NULL) { return false; @@ -6893,7 +6897,9 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const u return false; } - ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken); + CorInfoTokenKind tokenKind = + ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; + ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); if (m_resolvedAsyncCallToken.hMethod == NULL) { return false; @@ -6931,7 +6937,9 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip return false; } - ResolveToken(getU4LittleEndian(ip + 1), CORINFO_TOKENKIND_Await, &m_resolvedAsyncCallToken); + CorInfoTokenKind tokenKind = + ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; + ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); if (m_resolvedAsyncCallToken.hMethod == NULL) { return false; diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index af081c6b70d08f..c6342e5833ac84 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9009,6 +9009,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) codeAddrAfterMatch = impMatchTaskAwaitPattern(codeAddr, codeEndp, &configVal, &awaitOffset); if (codeAddrAfterMatch != nullptr) { + JITDUMP("Recognized await%s\n", configVal == 0 ? " (with ConfigureAwait(false))" : ""); + isAwait = true; prefixFlags |= PREFIX_IS_TASK_AWAIT; if (configVal != 0) @@ -9031,10 +9033,15 @@ void Compiler::impImportBlockCode(BasicBlock* block) } else { - // This can happen in rare 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. Treat - // that as a regular call that is Awaited + // 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. + // It can also happen generally if the VM does not think using the async entry point + // is worth it. Treat these as a regular call that is Awaited. _impResolveToken(CORINFO_TOKENKIND_Method); + prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT); + isAwait = false; + + JITDUMP("No async variant provided by VM, treating as regular call that is awaited\n"); } } else diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 636f37c18d69ec..057daa71b9f736 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1777,19 +1777,7 @@ private object GetRuntimeDeterminedObjectForToken(ref CORINFO_RESOLVED_TOKEN pRe result = ((TypeDesc)result).MakeArrayType(); if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.CORINFO_TOKENKIND_AwaitVirtual) - { - bool isDirect = pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await || IsCallEffectivelyDirect((MethodDesc)result); - if (isDirect && !((MethodDesc)result).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. - } - else - { - result = _compilation.TypeSystemContext.GetAsyncVariantMethod((MethodDesc)result); - } - } + result = _compilation.TypeSystemContext.GetAsyncVariantMethod((MethodDesc)result); return result; } @@ -1900,7 +1888,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method); #endif - if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await) + if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await or CorInfoTokenKind.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. @@ -1910,6 +1898,20 @@ 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 || IsCallEffectivelyDirect(method); + 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/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 0373b1d566828d..2b3f9a4d751c97 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1085,7 +1085,8 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken // 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 - if (pMD->ReturnsTaskOrValueTask()) + bool allowAsyncVariant = pMD->ReturnsTaskOrValueTask(); + if (allowAsyncVariant) { bool isDirect = tokenType == CORINFO_TOKENKIND_Await || pMD->IsStatic(); if (!isDirect) @@ -1106,14 +1107,11 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken // 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. - pMD = NULL; - } - else - { - pMD = pMD->GetAsyncVariant(/*allowInstParam*/FALSE); + allowAsyncVariant = false; } } + pMD = allowAsyncVariant ? pMD->GetAsyncVariant(/*allowInstParam*/FALSE) : NULL; break; default: From 7b2f07b8dbd32c7d01a89d65d384201b575d5d4c Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 12 Feb 2026 15:29:23 +0100 Subject: [PATCH 5/6] Fix build --- src/coreclr/vm/jitinterface.cpp | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 2b3f9a4d751c97..3d41d163e97684 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1082,36 +1082,38 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken 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) + { + // 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) { - DWORD attrs = pMD->GetAttrs(); - if (pMD->GetMethodTable()->IsInterface()) + bool isDirect = tokenType == CORINFO_TOKENKIND_Await || pMD->IsStatic(); + if (!isDirect) { - isDirect = !IsMdVirtual(attrs); + DWORD attrs = pMD->GetAttrs(); + if (pMD->GetMethodTable()->IsInterface()) + { + isDirect = !IsMdVirtual(attrs); + } + else + { + isDirect = !IsMdVirtual(attrs) || IsMdFinal(attrs) || pMD->GetMethodTable()->IsSealed(); + } } - else + + if (isDirect && !pMD->IsAsyncThunkMethod()) { - isDirect = !IsMdVirtual(attrs) || IsMdFinal(attrs) || pMD->GetMethodTable()->IsSealed(); + // 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 (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 = allowAsyncVariant ? pMD->GetAsyncVariant(/*allowInstParam*/FALSE) : NULL; break; default: From 5016fed866458537e820c90cccd22ea05e5d75fe Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 13 Feb 2026 11:11:12 +0100 Subject: [PATCH 6/6] Address feedback --- src/coreclr/interpreter/compiler.cpp | 40 +++++++++------------------- src/coreclr/interpreter/compiler.h | 1 + 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index ba5c7f5b74a0c8..b5924423564ee6 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -6739,6 +6739,14 @@ int InterpCompiler::ApplyLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElem return -1; } +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); + return m_resolvedAsyncCallToken.hMethod != NULL; +} + bool InterpCompiler::IsRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) { CORINFO_RESOLVED_TOKEN awaitResolvedToken; @@ -6749,10 +6757,7 @@ bool InterpCompiler::IsRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* pa return false; } - CorInfoTokenKind tokenKind = - ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; - ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); - if (m_resolvedAsyncCallToken.hMethod == NULL) + if (!ResolveAsyncCallToken(ip)) { return false; } @@ -6820,14 +6825,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, Opc return false; } - CorInfoTokenKind tokenKind = - ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; - ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); - if (m_resolvedAsyncCallToken.hMethod == NULL) - { - return false; - } - return true; + return ResolveAsyncCallToken(ip); } bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) @@ -6897,14 +6895,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const u return false; } - CorInfoTokenKind tokenKind = - ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; - ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); - if (m_resolvedAsyncCallToken.hMethod == NULL) - { - return false; - } - return true; + return ResolveAsyncCallToken(ip); } bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) @@ -6937,14 +6928,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip return false; } - CorInfoTokenKind tokenKind = - ip[0] == CEE_CALL ? CORINFO_TOKENKIND_Await : CORINFO_TOKENKIND_AwaitVirtual; - ResolveToken(getU4LittleEndian(ip + 1), tokenKind, &m_resolvedAsyncCallToken); - if (m_resolvedAsyncCallToken.hMethod == NULL) - { - return false; - } - return true; + return ResolveAsyncCallToken(ip); } int InterpCompiler::ApplyConvRUnR4Peep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo) diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 5ab165b770757e..896642e275e3fe 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -927,6 +927,7 @@ class InterpCompiler bool IsLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElement* peep, void** outComputedInfo); int ApplyLdftnDelegateCtorPeep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo); + bool ResolveAsyncCallToken(const uint8_t* ip); enum class ContinuationContextHandling : uint8_t { ContinueOnCapturedContext,