Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
240 changes: 230 additions & 10 deletions src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,257 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Internal.TypeSystem;

namespace Internal.IL.Stubs
{
public static class AsyncThunkILEmitter
{
// The emitted code matches method EmitTaskReturningThunk in CoreCLR VM.
public static MethodIL EmitTaskReturningThunk(MethodDesc taskReturningMethod, MethodDesc asyncMethod)
{
TypeSystemContext context = taskReturningMethod.Context;

var emitter = new ILEmitter();
var codestream = emitter.NewCodeStream();

// TODO: match EmitTaskReturningThunk in CoreCLR VM

MethodSignature sig = asyncMethod.Signature;
int numParams = (sig.IsStatic || sig.IsExplicitThis) ? sig.Length : sig.Length + 1;
for (int i = 0; i < numParams; i++)
codestream.EmitLdArg(i);
TypeDesc returnType = sig.ReturnType;

MetadataType md = taskReturningMethod.Signature.ReturnType as MetadataType;
ReadOnlySpan<byte> name = md.Name;
bool isValueTask = name.SequenceEqual("ValueTask"u8) || name.SequenceEqual("ValueTask`1"u8);

TypeDesc logicalReturnType = null;
ILLocalVariable logicalResultLocal = 0;
if (returnType.HasInstantiation)
{
// The return type is either Task<T> or ValueTask<T>, exactly one generic argument
logicalReturnType = returnType.Instantiation[0];
logicalResultLocal = emitter.NewLocal(logicalReturnType);
}

ILLocalVariable returnTaskLocal = emitter.NewLocal(returnType);

// TODO: Fix this (ExecutionAndSyncBlockStore is not available in Native AOT).

// TypeDesc executionAndSyncBlockStoreType = context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "ExecutionAndSyncBlockStore"u8);
// ILLocalVariable executionAndSyncBlockStoreLocal = emitter.NewLocal(executionAndSyncBlockStoreType);

ILCodeLabel returnTaskLabel = emitter.NewCodeLabel();
ILCodeLabel suspendedLabel = emitter.NewCodeLabel();
ILCodeLabel finishedLabel = emitter.NewCodeLabel();

codestream.Emit(ILOpcode.call, emitter.NewToken(asyncMethod));
// codestream.EmitLdLoca(executionAndSyncBlockStoreLocal);
// codestream.Emit(ILOpcode.call, emitter.NewToken(executionAndSyncBlockStoreType.GetKnownMethod("Push"u8, null)));

if (sig.ReturnType.IsVoid)
ILExceptionRegionBuilder tryFinallyRegion = emitter.NewFinallyRegion();
{
codestream.Emit(ILOpcode.call, emitter.NewToken(context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8).GetKnownMethod("get_CompletedTask"u8, null)));
codestream.BeginTry(tryFinallyRegion);
codestream.Emit(ILOpcode.nop);
ILExceptionRegionBuilder tryCatchRegion = emitter.NewCatchRegion();
{
codestream.BeginTry(tryCatchRegion);

int localArg = 0;
if (sig.IsExplicitThis)
{
codestream.EmitLdArg(localArg++);
}

for (int iArg = 0; iArg < sig.Length; iArg++)
{
codestream.EmitLdArg(localArg++);
}

if (asyncMethod.OwningType.HasInstantiation)
{
var inst = new TypeDesc[asyncMethod.OwningType.Instantiation.Length];
for (int i = 0; i < inst.Length; i++)
{
inst[i] = context.GetSignatureVariable(i, false);
}

var instantiatedType = context.GetInstantiatedType((MetadataType)asyncMethod.OwningType, new Instantiation(inst));
Comment on lines +74 to +80
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this use InstantiateAsOpen helper?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @MichalStrehovsky mentioned that asyncMethod.OwningType may not be a generic definition so we cannot use the helper

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, first thing InstantiateAsOpen currently does is that it asserts the method is not generic; we could have a generic method here:

Debug.Assert(method.IsMethodDefinition && !method.HasInstantiation);

But looking at it and all the callsites, it looks like it only asserts because we didn't have a need to do this with a generic method before and so it wasn't implemented. We could also just extend it to handle generic methods and call it from here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant InstantiateAsOpen(this TypeDesc type) that should just work.

Fixing InstantiateAsOpen(this MethodDesc type) sounds good to me as well. We may be able to use it in a few other places, e.g.

TypeSystemContext context = methodToInstantiate.Context;
var inst = new TypeDesc[methodToInstantiate.Instantiation.Length];
for (int i = 0; i < inst.Length; i++)
{
inst[i] = context.GetSignatureVariable(i, true);
}
methodToInstantiate = context.GetInstantiatedMethod(methodToInstantiate, new Instantiation(inst));

asyncMethod = context.GetMethodForInstantiatedType(asyncMethod, instantiatedType);
}

if (asyncMethod.HasInstantiation)
{
var inst = new TypeDesc[asyncMethod.Instantiation.Length];
for (int i = 0; i < inst.Length; i++)
{
inst[i] = context.GetSignatureVariable(i, true);
}
asyncMethod = asyncMethod.MakeInstantiatedMethod(new Instantiation(inst));
}

codestream.Emit(ILOpcode.call, emitter.NewToken(asyncMethod));

if (logicalReturnType != null)
{
codestream.EmitStLoc(logicalResultLocal);
}

// TODO: Fix this (AsyncCallContinuation is not available in Native AOT).

//MethodDesc asyncCallContinuationMd = context.SystemModule
// .GetKnownType("System.StubHelpers"u8, "StubHelpers"u8)
// .GetKnownMethod("AsyncCallContinuation"u8, null);

//codestream.Emit(ILOpcode.call, emitter.NewToken(asyncCallContinuationMd));

codestream.Emit(ILOpcode.brfalse, finishedLabel);
codestream.Emit(ILOpcode.leave, suspendedLabel);
codestream.EmitLabel(finishedLabel);

if (logicalReturnType != null)
{
codestream.EmitLdLoc(logicalResultLocal);

MethodDesc fromResultMethod;
if (isValueTask)
{
fromResultMethod = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "ValueTask`1"u8)
.GetKnownMethod("FromResult"u8, null)
.MakeInstantiatedMethod(new Instantiation(logicalReturnType));
}
else
{
fromResultMethod = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "Task"u8)
.GetKnownMethod("FromResult"u8, null)
.MakeInstantiatedMethod(new Instantiation(logicalReturnType));
}

codestream.Emit(ILOpcode.call, emitter.NewToken(fromResultMethod));
}
else
{
MethodDesc getCompletedTaskMethod;
if (isValueTask)
{
getCompletedTaskMethod = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "ValueTask"u8)
.GetKnownMethod("get_CompletedTask"u8, null);
}
else
{
getCompletedTaskMethod = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "Task"u8)
.GetKnownMethod("get_CompletedTask"u8, null);
}
codestream.Emit(ILOpcode.call, emitter.NewToken(getCompletedTaskMethod));
}

codestream.EmitLdLoc(returnTaskLocal);
codestream.Emit(ILOpcode.leave, returnTaskLabel);

codestream.EndTry(tryCatchRegion);
}
// Catch
{
codestream.BeginHandler(tryCatchRegion);

MethodDesc fromExceptionMd;
if (logicalReturnType != null)
{
// Generate: returnType.FromException<T>(Exception)
if (isValueTask)
{
fromExceptionMd = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "ValueTask`1"u8)
.GetKnownMethod("FromException"u8, null);
}
else
{
fromExceptionMd = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "Task`1"u8)
.GetKnownMethod("FromException"u8, null);
}
fromExceptionMd = fromExceptionMd.MakeInstantiatedMethod(new Instantiation(logicalReturnType));
}
else
{
// Generate: returnType.FromException(Exception)
if (isValueTask)
{
fromExceptionMd = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "ValueTask"u8)
.GetKnownMethod("FromException"u8, null);
}
else
{
fromExceptionMd = context.SystemModule
.GetKnownType("System.Threading.Tasks"u8, "Task"u8)
.GetKnownMethod("FromException"u8, null);
}
}

codestream.Emit(ILOpcode.call, emitter.NewToken(fromExceptionMd));
codestream.EmitStLoc(returnTaskLocal);
codestream.Emit(ILOpcode.leave, returnTaskLabel);
codestream.EndHandler(tryCatchRegion);
}

codestream.EmitLabel(suspendedLabel);

// TODO: Fix this (Finalize returning thunks are not available in Native AOT).

//MethodDesc finalizeTaskReturningThunkMd;

//if (logicalReturnType != null)
//{
// if (isValueTask)
// {
// finalizeTaskReturningThunkMd = context.SystemModule
// .GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8)
// .GetKnownMethod("FinalizeValueTaskReturningThunk`1"u8, null);
// }
// else
// {
// finalizeTaskReturningThunkMd = context.SystemModule
// .GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8)
// .GetKnownMethod("FinalizeTaskReturningThunk`1"u8, null);
// }
// finalizeTaskReturningThunkMd = finalizeTaskReturningThunkMd.MakeInstantiatedMethod(new Instantiation(logicalReturnType));
//}
//else
//{
// if (isValueTask)
// {
// finalizeTaskReturningThunkMd = context.SystemModule
// .GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8)
// .GetKnownMethod("FinalizeValueTaskReturningThunk"u8, null);
// }
// else
// {
// finalizeTaskReturningThunkMd = context.SystemModule
// .GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8)
// .GetKnownMethod("FinalizeTaskReturningThunk"u8, null);
// }
//}
//codestream.Emit(ILOpcode.call, emitter.NewToken(finalizeTaskReturningThunkMd));
codestream.EmitStLoc(returnTaskLocal);
codestream.Emit(ILOpcode.leave, returnTaskLabel);

codestream.EndTry(tryFinallyRegion);
}
else
//
{
codestream.Emit(ILOpcode.call, emitter.NewToken(context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8).GetKnownMethod("FromResult"u8, null).MakeInstantiatedMethod(sig.ReturnType)));
codestream.BeginHandler(tryFinallyRegion);

// TODO: Fix this (ExecutionAndSyncBlockStore is not available in Native AOT).

// codestream.EmitLdLoca(executionAndSyncBlockStoreLocal);
// codestream.Emit(ILOpcode.call, emitter.NewToken(executionAndSyncBlockStoreType.GetKnownMethod("Pop"u8, null)));
codestream.EndHandler(tryFinallyRegion);
}

codestream.EmitLabel(returnTaskLabel);
codestream.EmitLdLoc(returnTaskLocal);
codestream.Emit(ILOpcode.ret);

return emitter.Link(taskReturningMethod);
Expand Down
28 changes: 26 additions & 2 deletions src/coreclr/tools/Common/TypeSystem/IL/Stubs/ILEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ public class ILEmitter
private ArrayBuilder<ILCodeStream> _codeStreams;
private ArrayBuilder<LocalVariableDefinition> _locals;
private ArrayBuilder<object> _tokens;
private ArrayBuilder<ILExceptionRegionBuilder> _catchRegions;
private ArrayBuilder<ILExceptionRegionBuilder> _finallyRegions;

public ILEmitter()
Expand Down Expand Up @@ -727,6 +728,14 @@ public ILCodeLabel NewCodeLabel()
return newLabel;
}

// For now, only catches exceptions of type Exception.
public ILExceptionRegionBuilder NewCatchRegion()
{
var region = new ILExceptionRegionBuilder();
_catchRegions.Add(region);
return region;
}

public ILExceptionRegionBuilder NewFinallyRegion()
{
var region = new ILExceptionRegionBuilder();
Expand Down Expand Up @@ -782,18 +791,33 @@ public MethodIL Link(MethodDesc owningMethod)

ILExceptionRegion[] exceptionRegions = null;

int numberOfExceptionRegions = _finallyRegions.Count;
int numberOfExceptionRegions = _catchRegions.Count + _finallyRegions.Count;
if (numberOfExceptionRegions > 0)
{
exceptionRegions = new ILExceptionRegion[numberOfExceptionRegions];

TypeDesc exceptionType = owningMethod.Context.SystemModule.GetKnownType("System"u8, "Exception"u8);

int exceptionTypeToken = (int)NewToken(exceptionType);

for (int i = 0; i < _catchRegions.Count; i++)
{
ILExceptionRegionBuilder region = _catchRegions[i];

Debug.Assert(region.IsDefined);

exceptionRegions[i] = new ILExceptionRegion(ILExceptionRegionKind.Catch,
region.TryOffset, region.TryLength, region.HandlerOffset, region.HandlerLength,
classToken: exceptionTypeToken, filterOffset: 0);
}

for (int i = 0; i < _finallyRegions.Count; i++)
{
ILExceptionRegionBuilder region = _finallyRegions[i];

Debug.Assert(region.IsDefined);

exceptionRegions[i] = new ILExceptionRegion(ILExceptionRegionKind.Finally,
exceptionRegions[_catchRegions.Count + i] = new ILExceptionRegion(ILExceptionRegionKind.Finally,
region.TryOffset, region.TryLength, region.HandlerOffset, region.HandlerLength,
classToken: 0, filterOffset: 0);
}
Expand Down
5 changes: 3 additions & 2 deletions src/coreclr/vm/asyncthunks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_
return true;
}

// provided an async method, emits a Task-returning wrapper.
// Provided an async method, emits a Task-returning wrapper.
// The emitted code matches method EmitTaskReturningThunk in the Managed Type System.
void MethodDesc::EmitTaskReturningThunk(MethodDesc* pAsyncOtherVariant, MetaSig& thunkMsig, ILStubLinker* pSL)
{
_ASSERTE(!pAsyncOtherVariant->IsAsyncThunkMethod());
Expand Down Expand Up @@ -132,7 +133,7 @@ void MethodDesc::EmitTaskReturningThunk(MethodDesc* pAsyncOtherVariant, MetaSig&
}

int token;
_ASSERTE(!pAsyncOtherVariant->IsWrapperStub());
_ASSERTE(!pAsyncOtherVariant->IsWrapperStub()); // Would it be better to move the assertion to the top of this method?
if (pAsyncOtherVariant->HasClassOrMethodInstantiation())
{
// For generic code emit generic signatures.
Expand Down
Loading