From 04cafd7eb3b01ceba0c3e206da12a49a5166ddca Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 19:47:46 +0200 Subject: [PATCH 1/8] Handle Synchronized async versions Non-async Task-returning methods can be Synchronized. We were missing handling for that scenario in the JIT and interpreter. --- src/coreclr/interpreter/compiler.cpp | 79 +++++++++++++------ src/coreclr/interpreter/compiler.h | 4 + src/coreclr/jit/flowgraph.cpp | 18 +++-- src/coreclr/jit/importer.cpp | 21 ++++- .../regression/synchronized-async-version.cs | 64 +++++++++++++++ .../synchronized-async-version.csproj | 5 ++ 6 files changed, 159 insertions(+), 32 deletions(-) create mode 100644 src/tests/async/regression/synchronized-async-version.cs create mode 100644 src/tests/async/regression/synchronized-async-version.csproj diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 28ef2438eb1d4b..e17370e431cee6 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -4870,21 +4870,9 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re if (!m_isSynchronized && !m_isAsyncMethodWithContextSaveRestore) NO_WAY("INTERP_LOAD_RETURN_VALUE_FOR_SYNCHRONIZED_OR_ASYNC used in a non-synchronized or async method"); - if (m_synchronizedOrAsyncRetValVarIndex == -1) - { - // Code inside for-loops, for example, is not emitted in the first pass, so we can reach the async - // epilog with m_synchronizedOrAsyncRetValVarIndex uninitialized. - CORINFO_SIG_INFO sig = m_methodInfo->args; - InterpType retType = GetInterpType(sig.retType); - if (retType != InterpTypeVoid) - { - CORINFO_CLASS_HANDLE retClsHnd = (retType == InterpTypeVT) ? sig.retTypeClass : NULL; - PushInterpType(retType, retClsHnd); - m_synchronizedOrAsyncRetValVarIndex = m_pStackPointer[-1].var; - m_pStackPointer--; - INTERP_DUMP("Created ret val var V%d (pre-created in epilog)\n", m_synchronizedOrAsyncRetValVarIndex); - } - } + // Code inside for-loops, for example, is not emitted in the first pass, so we can reach the async + // epilog with m_synchronizedOrAsyncRetValVarIndex uninitialized. + CreateSynchronizedRetValVar(); INTERP_DUMP("INTERP_LOAD_RETURN_VALUE_FOR_SYNCHRONIZED_OR_ASYNC with ret val var V%d\n", (int)m_synchronizedOrAsyncRetValVarIndex); if (m_synchronizedOrAsyncRetValVarIndex != -1) @@ -5762,7 +5750,9 @@ void InterpCompiler::EmitRet(CORINFO_METHOD_INFO* methodInfo) CORINFO_SIG_INFO sig = methodInfo->args; InterpType retType = GetInterpType(sig.retType); - if (m_isAsyncVersionOfSyncMethod) + // Wrap top of stack in await. For async versions that are synchronized, + // only wrap the outer most return in await. + if (m_isAsyncVersionOfSyncMethod && (!m_isSynchronized || m_currentILOffset >= m_ILCodeSizeFromILHeader)) { if (m_asyncVersionIsTailCalling) { @@ -5798,16 +5788,11 @@ void InterpCompiler::EmitRet(CORINFO_METHOD_INFO* methodInfo) int32_t ilOffset = (int32_t)(m_ip - m_pILCode); int32_t target = m_synchronizedOrAsyncPostFinallyOffset; - if (retType != InterpTypeVoid) + CreateSynchronizedRetValVar(); + + if ((retType != InterpTypeVoid) || m_isAsyncVersionOfSyncMethod) { CheckStackExact(1); - if (m_synchronizedOrAsyncRetValVarIndex == -1) - { - PushInterpType(retType, m_pVars[m_pStackPointer[-1].var].clsHnd); - m_synchronizedOrAsyncRetValVarIndex = m_pStackPointer[-1].var; - m_pStackPointer--; - INTERP_DUMP("Created ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); - } INTERP_DUMP("Store to ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); EmitStoreVar(m_synchronizedOrAsyncRetValVarIndex); } @@ -7591,7 +7576,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip bool InterpCompiler::IsRuntimeAsyncCallRetOrJmpInAsyncVersion(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) { - if (!m_isAsyncVersionOfSyncMethod) + if (!m_isAsyncVersionOfSyncMethod || m_isSynchronized) { return false; } @@ -8320,6 +8305,50 @@ int InterpCompiler::ApplyTypeValueTypePeep(const uint8_t* ip, OpcodePeepElement* return -1; } +void InterpCompiler::CreateSynchronizedRetValVar() +{ + if (m_synchronizedOrAsyncRetValVarIndex != -1) + { + return; + } + + if (m_methodInfo->args.retType == InterpTypeVoid && !m_isAsyncVersionOfSyncMethod) + { + return; + } + + InterpType retType; + CORINFO_CLASS_HANDLE retClsHnd = NULL; + if (m_isAsyncVersionOfSyncMethod) + { + // The actual type of the return value will be the Task or + // ValueTask type, so get it from the await call signature + CORINFO_LOOKUP instArg; + CORINFO_METHOD_HANDLE awaitCall = m_compHnd->getAwaitReturnCall(m_methodInfo->ftn, &instArg); + CORINFO_SIG_INFO awaitCallSig; + m_compHnd->getMethodSig(awaitCall, &awaitCallSig); + + CORINFO_CLASS_HANDLE vcTypeRet; + CorInfoType paramType = strip(m_compHnd->getArgType(&awaitCallSig, awaitCallSig.args, &vcTypeRet)); + + retType = GetInterpType(paramType); + retClsHnd = (retType == InterpTypeVT) ? vcTypeRet : NULL; + } + else + { + retType = GetInterpType(m_methodInfo->args.retType); + retClsHnd = (retType == InterpTypeVT) ? m_methodInfo->args.retTypeClass : NULL; + } + + if (retType != InterpTypeVoid) + { + PushInterpType(retType, retClsHnd); + m_synchronizedOrAsyncRetValVarIndex = m_pStackPointer[-1].var; + m_pStackPointer--; + INTERP_DUMP("Created ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); + } +} + void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) { bool readonly = false; diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index dad66ff5e2e111..9bb2f5a94e6630 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -760,6 +760,8 @@ class InterpCompiler int32_t GetMethodDataItemIndex(CORINFO_METHOD_HANDLE mHandle); int32_t GetDataForHelperFtn(CorInfoHelpFunc ftn); + void CreateSynchronizedRetValVar(); + void GenerateCode(CORINFO_METHOD_INFO* methodInfo); InterpBasicBlock* GenerateCodeForLeaveChainIslands(InterpBasicBlock *pNewBB, InterpBasicBlock *pPrevBB); void PatchInitLocals(CORINFO_METHOD_INFO* methodInfo); @@ -910,6 +912,8 @@ class InterpCompiler int32_t m_synchronizedFlagVarIndex = -1; // If the method is synchronized, this is the index of the argument that flag indicating if the lock was taken int32_t m_synchronizedOrAsyncRetValVarIndex = -1; // If the method is synchronized, ret instructions are replaced with a store to this var and a leave to an epilog instruction. int32_t m_synchronizedFinallyStartOffset = -1; // If the method is synchronized, this is the offset of the start of the finally epilog + InterpType m_synchronizedRetValType = InterpType::InterpTypeVoid; + CORINFO_CLASS_HANDLE m_synchronizedRetValClsHnd = NULL; int32_t m_threadObjVarIndex = -1; // If the method is async, this is the var index of the Thread local int32_t m_execContextVarIndex = -1; // If the method is async, this is the var index of the ExecutionContext local diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 69e10441a264d6..984022eeb991a4 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -1614,9 +1614,11 @@ void Compiler::fgAddSyncMethodEnterExit() // For EnC this is part of the frame header. Furthermore, this is allocated above PSP on ARM64. // To avoid complicated reasoning about alignment we always allocate a full pointer sized slot for this. var_types typeMonAcquired = TYP_I_IMPL; - this->lvaMonAcquired = lvaGrabTemp(true DEBUGARG("Synchronized method monitor acquired boolean")); - - lvaTable[lvaMonAcquired].lvType = typeMonAcquired; + if (lvaMonAcquired == BAD_VAR_NUM) + { + lvaMonAcquired = lvaGrabTemp(true DEBUGARG("Synchronized method monitor acquired boolean")); + lvaTable[lvaMonAcquired].lvType = typeMonAcquired; + } if (opts.IsOSR()) { @@ -1670,11 +1672,15 @@ void Compiler::fgAddSyncMethodEnterExit() false /*exit*/); // non-exceptional cases - for (BasicBlock* const block : Blocks()) + // For async versions we created these directly in import + if (!compIsAsyncVersion()) { - if (block->KindIs(BBJ_RETURN)) + for (BasicBlock* const block : Blocks()) { - fgCreateMonitorTree(lvaMonAcquired, info.compThisArg, block, false /*exit*/); + if (block->KindIs(BBJ_RETURN)) + { + fgCreateMonitorTree(lvaMonAcquired, info.compThisArg, block, false /*exit*/); + } } } } diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 1b811f91084775..0b8e27ac821631 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9052,7 +9052,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (compIsAsyncVersion()) { - if ((codeAddr + sz < codeEndp) && (getU1LittleEndian(codeAddr + sz) == CEE_RET)) + if ((codeAddr + sz < codeEndp) && (getU1LittleEndian(codeAddr + sz) == CEE_RET) && + ((info.compFlags & CORINFO_FLG_SYNCH) == 0)) { JITDUMP("\nRecognized tail-call in async version\n"); awaitOffset = (IL_OFFSET)(codeAddr - 1 - info.compCode); @@ -11706,6 +11707,24 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) // bool Compiler::impWrapTopOfStackInAwait() { + if ((info.compFlags & CORINFO_FLG_SYNCH) != 0) + { + assert(!compIsForInlining()); + + if (lvaMonAcquired == BAD_VAR_NUM) + { + lvaMonAcquired = lvaGrabTemp(true DEBUGARG("Synchronized method monitor acquired boolean")); + } + + lvaGetDesc(lvaMonAcquired)->lvType = TYP_I_IMPL; + + GenTree* varAddrNode = gtNewLclVarAddrNode(lvaMonAcquired); + GenTree* lockObject = + info.compIsStatic ? fgGetCritSectOfStaticMethod() : gtNewLclvNode(info.compThisArg, TYP_REF); + GenTree* exitMon = gtNewHelperCallNode(CORINFO_HELP_MON_EXIT, TYP_VOID, lockObject, varAddrNode); + impAppendTree(exitMon, CHECK_SPILL_ALL, impCurStmtDI); + } + if (impFoldAwaitedTopOfStack()) { return true; diff --git a/src/tests/async/regression/synchronized-async-version.cs b/src/tests/async/regression/synchronized-async-version.cs new file mode 100644 index 00000000000000..d5f3cd46417d7e --- /dev/null +++ b/src/tests/async/regression/synchronized-async-version.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +public class Async2Synchronized +{ + [Fact] + public static void TestEntryPoint() + { + TestEntryPointAsync().GetAwaiter().GetResult(); + } + + private static async Task TestEntryPointAsync() + { + Async2Synchronized p = new(); + Task t = p.Foo(); + // Returning the task from the [MethodImpl(MethodImplOptions.Synchronized)] + // method Bar must release the lock it acquired, so it should not be held here. + Assert.False(Monitor.IsEntered(typeof(Async2Synchronized))); + await t; + } + + private async Task Foo() + { + await Bar(Task.Delay(500)); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private Task Bar(Task t) + { + return t; + } + + [Fact] + public static void TestEntryPointValueTask() + { + TestEntryPointValueTaskAsync().GetAwaiter().GetResult(); + } + + private static async ValueTask TestEntryPointValueTaskAsync() + { + Async2Synchronized p = new(); + ValueTask t = p.FooValueTask(); + // Returning the ValueTask from the [MethodImpl(MethodImplOptions.Synchronized)] + // method Bar must release the lock it acquired, so it should not be held here. + Assert.False(Monitor.IsEntered(typeof(Async2Synchronized))); + await t; + } + + private async ValueTask FooValueTask() + { + await Bar(new ValueTask(Task.Delay(500))); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private ValueTask Bar(ValueTask t) + { + return t; + } +} diff --git a/src/tests/async/regression/synchronized-async-version.csproj b/src/tests/async/regression/synchronized-async-version.csproj new file mode 100644 index 00000000000000..3fc50cde4b3443 --- /dev/null +++ b/src/tests/async/regression/synchronized-async-version.csproj @@ -0,0 +1,5 @@ + + + + + From cff394d7b40e82f78aa4f56043e8eb19773342be Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 19:57:35 +0200 Subject: [PATCH 2/8] Nit --- src/coreclr/jit/importer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 0b8e27ac821631..9b6e1636dcd11e 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -11714,10 +11714,9 @@ bool Compiler::impWrapTopOfStackInAwait() if (lvaMonAcquired == BAD_VAR_NUM) { lvaMonAcquired = lvaGrabTemp(true DEBUGARG("Synchronized method monitor acquired boolean")); + lvaGetDesc(lvaMonAcquired)->lvType = TYP_I_IMPL; } - lvaGetDesc(lvaMonAcquired)->lvType = TYP_I_IMPL; - GenTree* varAddrNode = gtNewLclVarAddrNode(lvaMonAcquired); GenTree* lockObject = info.compIsStatic ? fgGetCritSectOfStaticMethod() : gtNewLclvNode(info.compThisArg, TYP_REF); From 703fc87b8dec0c29ff6dc3544ff665094c4d27fc Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 20:05:54 +0200 Subject: [PATCH 3/8] Fix test --- src/tests/async/regression/synchronized-async-version.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/async/regression/synchronized-async-version.cs b/src/tests/async/regression/synchronized-async-version.cs index d5f3cd46417d7e..441ec28777b060 100644 --- a/src/tests/async/regression/synchronized-async-version.cs +++ b/src/tests/async/regression/synchronized-async-version.cs @@ -20,7 +20,7 @@ private static async Task TestEntryPointAsync() Task t = p.Foo(); // Returning the task from the [MethodImpl(MethodImplOptions.Synchronized)] // method Bar must release the lock it acquired, so it should not be held here. - Assert.False(Monitor.IsEntered(typeof(Async2Synchronized))); + Assert.False(Monitor.IsEntered(p)); await t; } @@ -47,7 +47,7 @@ private static async ValueTask TestEntryPointValueTaskAsync() ValueTask t = p.FooValueTask(); // Returning the ValueTask from the [MethodImpl(MethodImplOptions.Synchronized)] // method Bar must release the lock it acquired, so it should not be held here. - Assert.False(Monitor.IsEntered(typeof(Async2Synchronized))); + Assert.False(Monitor.IsEntered(p)); await t; } From 4cc4d836bee4578104b5420f58b96b82466332af Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 20:19:39 +0200 Subject: [PATCH 4/8] Use TCS --- .../regression/synchronized-async-version.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/tests/async/regression/synchronized-async-version.cs b/src/tests/async/regression/synchronized-async-version.cs index 441ec28777b060..b032dd17eaef89 100644 --- a/src/tests/async/regression/synchronized-async-version.cs +++ b/src/tests/async/regression/synchronized-async-version.cs @@ -17,16 +17,18 @@ public static void TestEntryPoint() private static async Task TestEntryPointAsync() { Async2Synchronized p = new(); - Task t = p.Foo(); + TaskCompletionSource tcs = new(); + Task t = p.Foo(tcs.Task); // Returning the task from the [MethodImpl(MethodImplOptions.Synchronized)] // method Bar must release the lock it acquired, so it should not be held here. Assert.False(Monitor.IsEntered(p)); + tcs.SetResult(); await t; } - private async Task Foo() + private async Task Foo(Task task) { - await Bar(Task.Delay(500)); + await Bar(task); } [MethodImpl(MethodImplOptions.Synchronized)] @@ -44,16 +46,18 @@ public static void TestEntryPointValueTask() private static async ValueTask TestEntryPointValueTaskAsync() { Async2Synchronized p = new(); - ValueTask t = p.FooValueTask(); + TaskCompletionSource tcs = new(); + ValueTask t = p.FooValueTask(new ValueTask(tcs.Task)); // Returning the ValueTask from the [MethodImpl(MethodImplOptions.Synchronized)] // method Bar must release the lock it acquired, so it should not be held here. Assert.False(Monitor.IsEntered(p)); + tcs.SetResult(); await t; } - private async ValueTask FooValueTask() + private async ValueTask FooValueTask(ValueTask task) { - await Bar(new ValueTask(Task.Delay(500))); + await Bar(task); } [MethodImpl(MethodImplOptions.Synchronized)] From ea72ff60818a4f83bfaaa8f2659dc2edb886237b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 20:22:08 +0200 Subject: [PATCH 5/8] Feedback --- src/coreclr/interpreter/compiler.cpp | 2 +- src/coreclr/interpreter/compiler.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index e17370e431cee6..02b593ab1ca78a 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -8312,7 +8312,7 @@ void InterpCompiler::CreateSynchronizedRetValVar() return; } - if (m_methodInfo->args.retType == InterpTypeVoid && !m_isAsyncVersionOfSyncMethod) + if (m_methodInfo->args.retType == CORINFO_TYPE_VOID && !m_isAsyncVersionOfSyncMethod) { return; } diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 9bb2f5a94e6630..a459090f1df827 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -912,8 +912,6 @@ class InterpCompiler int32_t m_synchronizedFlagVarIndex = -1; // If the method is synchronized, this is the index of the argument that flag indicating if the lock was taken int32_t m_synchronizedOrAsyncRetValVarIndex = -1; // If the method is synchronized, ret instructions are replaced with a store to this var and a leave to an epilog instruction. int32_t m_synchronizedFinallyStartOffset = -1; // If the method is synchronized, this is the offset of the start of the finally epilog - InterpType m_synchronizedRetValType = InterpType::InterpTypeVoid; - CORINFO_CLASS_HANDLE m_synchronizedRetValClsHnd = NULL; int32_t m_threadObjVarIndex = -1; // If the method is async, this is the var index of the Thread local int32_t m_execContextVarIndex = -1; // If the method is async, this is the var index of the ExecutionContext local From 4fe6d49e4d3336c63d9cea27a3aaee65eac562dd Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 20:23:38 +0200 Subject: [PATCH 6/8] Unnecessary check --- src/coreclr/interpreter/compiler.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 02b593ab1ca78a..e9cbb086495b9c 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -8340,13 +8340,10 @@ void InterpCompiler::CreateSynchronizedRetValVar() retClsHnd = (retType == InterpTypeVT) ? m_methodInfo->args.retTypeClass : NULL; } - if (retType != InterpTypeVoid) - { - PushInterpType(retType, retClsHnd); - m_synchronizedOrAsyncRetValVarIndex = m_pStackPointer[-1].var; - m_pStackPointer--; - INTERP_DUMP("Created ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); - } + PushInterpType(retType, retClsHnd); + m_synchronizedOrAsyncRetValVarIndex = m_pStackPointer[-1].var; + m_pStackPointer--; + INTERP_DUMP("Created ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); } void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) From af7bc8dac1ba787dc5c3b88412dbad6661b79fd6 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 20:25:32 +0200 Subject: [PATCH 7/8] Fix project SDK --- src/tests/async/regression/synchronized-async-version.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/async/regression/synchronized-async-version.csproj b/src/tests/async/regression/synchronized-async-version.csproj index 3fc50cde4b3443..197767e2c4e249 100644 --- a/src/tests/async/regression/synchronized-async-version.csproj +++ b/src/tests/async/regression/synchronized-async-version.csproj @@ -1,4 +1,4 @@ - + From 0efe24002607277899ac2ee3d22965499e38c05b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Fri, 26 Jun 2026 20:45:01 +0200 Subject: [PATCH 8/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/interpreter/compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index e9cbb086495b9c..02ac3c0c730654 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -8324,7 +8324,7 @@ void InterpCompiler::CreateSynchronizedRetValVar() // The actual type of the return value will be the Task or // ValueTask type, so get it from the await call signature CORINFO_LOOKUP instArg; - CORINFO_METHOD_HANDLE awaitCall = m_compHnd->getAwaitReturnCall(m_methodInfo->ftn, &instArg); + CORINFO_METHOD_HANDLE awaitCall = m_compHnd->getAwaitReturnCall(m_methodHnd, &instArg); CORINFO_SIG_INFO awaitCallSig; m_compHnd->getMethodSig(awaitCall, &awaitCallSig);