Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 54 additions & 25 deletions src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Comment thread
jakobbotsch marked this conversation as resolved.
{
if (m_asyncVersionIsTailCalling)
{
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated

InterpType retType;
CORINFO_CLASS_HANDLE retClsHnd = NULL;
if (m_isAsyncVersionOfSyncMethod)
{
// The actual type of the return value will be the Task<T> or
// ValueTask<T> type, so get it from the await call signature
CORINFO_LOOKUP instArg;
Comment thread
jakobbotsch marked this conversation as resolved.
CORINFO_METHOD_HANDLE awaitCall = m_compHnd->getAwaitReturnCall(m_methodInfo->ftn, &instArg);
CORINFO_SIG_INFO awaitCallSig;
Comment thread
jakobbotsch marked this conversation as resolved.
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;
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/interpreter/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated

int32_t m_threadObjVarIndex = -1; // If the method is async, this is the var index of the Thread local
Comment thread
jakobbotsch marked this conversation as resolved.
int32_t m_execContextVarIndex = -1; // If the method is async, this is the var index of the ExecutionContext local
Expand Down
18 changes: 12 additions & 6 deletions src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
{
Expand Down Expand Up @@ -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*/);
}
}
}
}
Expand Down
20 changes: 19 additions & 1 deletion src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -11706,6 +11707,23 @@ 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;
Expand Down
64 changes: 64 additions & 0 deletions src/tests/async/regression/synchronized-async-version.cs
Original file line number Diff line number Diff line change
@@ -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)));
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated
await t;
}

private async Task Foo()
{
await Bar(Task.Delay(500));
}
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated
Comment thread
jakobbotsch marked this conversation as resolved.

[MethodImpl(MethodImplOptions.Synchronized)]
private Task Bar(Task t)
{
return t;
}
Comment thread
jakobbotsch marked this conversation as resolved.

[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)));
Comment thread
jakobbotsch marked this conversation as resolved.
Outdated
await t;
}

private async ValueTask FooValueTask()
{
await Bar(new ValueTask(Task.Delay(500)));
}
Comment thread
jakobbotsch marked this conversation as resolved.

[MethodImpl(MethodImplOptions.Synchronized)]
private ValueTask Bar(ValueTask t)
{
return t;
}
Comment thread
jakobbotsch marked this conversation as resolved.
}
5 changes: 5 additions & 0 deletions src/tests/async/regression/synchronized-async-version.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.IL">
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
Loading