Skip to content

Commit b811d06

Browse files
First part of async support in native AOT
Following works with runtime async enabled in Roslyn on native AOT: ```csharp public class Async2Void { public static void Main() { var t = AsyncTestEntryPoint(123, 456); t.Wait(); Console.WriteLine(t.Result); } private static async Task<int> AsyncTestEntryPoint(int x, int y) { int result = await OtherAsync(x, y); return result; } private static async Task<int> OtherAsync(int x, int y) { Console.WriteLine(x); Console.WriteLine(y); return x + y; } } ``` The implementation strategy is as follows: * In the compiler, whenever someone holds an `EcmaMethod` of a asyncv2 method, it means they're holding the `RuntimeAsync` flavor of the method (in CoreCLR VM parlance). The return value is a `Task`, `MethodDesc.IsAsync` is true, `MethodSignature.IsAsyncCallConv` is false. The IL is a thunk to the `AsyncVariantImplMethod` flavor. * In the compiler, we have a new `MethodDesc` descendant: `AsyncVariantImplMethod`. This is the actual method with async calling convention that corresponds to some `EcmaMethod` with `IsAsync`==true. For this one: the return value is unwrapped from `Task`, `MethodDesc.IsAsync` is true, `MethodSignature.IsAsyncCallConv` is true. The IL is the actual IL that corresponds to the wrapped `EcmaMethod` on disk.
1 parent e8812e7 commit b811d06

File tree

12 files changed

+291
-30
lines changed

12 files changed

+291
-30
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using Internal.TypeSystem;
6+
7+
using Debug = System.Diagnostics.Debug;
8+
9+
namespace ILCompiler
10+
{
11+
public partial class AsyncVariantImplMethod : MethodDelegator, IPrefixMangledMethod
12+
{
13+
MethodDesc IPrefixMangledMethod.BaseMethod => _wrappedMethod;
14+
15+
string IPrefixMangledMethod.Prefix => "AsyncCallable";
16+
}
17+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using Internal.TypeSystem;
6+
7+
using Debug = System.Diagnostics.Debug;
8+
9+
namespace ILCompiler
10+
{
11+
public partial class AsyncVariantImplMethod : MethodDelegator
12+
{
13+
protected override int ClassCode => 0xfa6c81;
14+
protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer)
15+
{
16+
return comparer.Compare(_wrappedMethod, ((AsyncVariantImplMethod)other)._wrappedMethod);
17+
}
18+
}
19+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using Internal.TypeSystem;
6+
using Internal.TypeSystem.Ecma;
7+
8+
using Debug = System.Diagnostics.Debug;
9+
10+
namespace ILCompiler
11+
{
12+
/// <summary>
13+
/// Represents an async method callable with <see cref="MethodSignatureFlags.AsyncCallConv"/> calling convention.
14+
/// The IL method body for this method comes from <see cref="MetadataDefinition"/> method that was provided in the constructor
15+
/// (typically, from user provided assemblies). This MethodDesc corresponds to AsyncVariantImpl in the CoreCLR VM parlance.
16+
/// The difference between this method and the method provided in the constructor to this class is that: method signature
17+
/// unwraps Task parameters and the calling convention changes to async.
18+
/// </summary>
19+
public partial class AsyncVariantImplMethod : MethodDelegator
20+
{
21+
private MethodSignature _signature;
22+
23+
public EcmaMethod MetadataDefinition => (EcmaMethod)_wrappedMethod;
24+
25+
public AsyncVariantImplMethod(EcmaMethod wrappedMethod)
26+
: base(wrappedMethod)
27+
{
28+
Debug.Assert(wrappedMethod.IsAsync);
29+
Debug.Assert(wrappedMethod.IsTaskReturning());
30+
Debug.Assert(wrappedMethod.IsTypicalMethodDefinition);
31+
}
32+
33+
private MethodSignature InitializeSignature()
34+
{
35+
MethodSignature sig = _wrappedMethod.Signature;
36+
MethodSignatureBuilder builder = new MethodSignatureBuilder(sig);
37+
TypeDesc returnType = sig.ReturnType;
38+
builder.ReturnType = returnType.HasInstantiation ? returnType.Instantiation[0] : Context.GetWellKnownType(WellKnownType.Void);
39+
40+
Debug.Assert((_wrappedMethod.Signature.Flags & MethodSignatureFlags.AsyncCallConv) == 0);
41+
builder.Flags = sig.Flags | MethodSignatureFlags.AsyncCallConv;
42+
43+
return _signature = builder.ToSignature();
44+
}
45+
46+
public override MethodSignature Signature => _signature ?? InitializeSignature();
47+
48+
public override MethodDesc GetMethodDefinition() => this;
49+
public override MethodDesc GetTypicalMethodDefinition() => this;
50+
51+
public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind)
52+
{
53+
// We should not be calling GetCanonMethodTarget on generic definitions of anything
54+
// and this MethodDesc is a generic definition.
55+
Debug.Assert(!HasInstantiation && !OwningType.HasInstantiation);
56+
return this;
57+
}
58+
59+
public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation) => throw new NotImplementedException();
60+
}
61+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Internal.IL;
5+
using Internal.TypeSystem;
6+
using Internal.TypeSystem.Ecma;
7+
8+
using Debug = System.Diagnostics.Debug;
9+
10+
namespace ILCompiler
11+
{
12+
public partial class CompilerTypeSystemContext
13+
{
14+
public MethodDesc GetAsyncVariantImplMethod(MethodDesc asyncMetadataMethod)
15+
{
16+
MethodDesc asyncMetadataMethodDef = asyncMetadataMethod.GetTypicalMethodDefinition();
17+
MethodDesc result = _asyncVariantImplHashtable.GetOrCreateValue((EcmaMethod)asyncMetadataMethodDef);
18+
19+
if (asyncMetadataMethodDef != asyncMetadataMethod)
20+
{
21+
TypeDesc owningType = asyncMetadataMethod.OwningType;
22+
if (owningType.HasInstantiation)
23+
result = GetMethodForInstantiatedType(result, (InstantiatedType)owningType);
24+
25+
if (asyncMetadataMethod.HasInstantiation)
26+
result = GetInstantiatedMethod(result, asyncMetadataMethod.Instantiation);
27+
}
28+
29+
return result;
30+
}
31+
32+
private sealed class AsyncVariantImplHashtable : LockFreeReaderHashtable<EcmaMethod, AsyncVariantImplMethod>
33+
{
34+
protected override int GetKeyHashCode(EcmaMethod key) => key.GetHashCode();
35+
protected override int GetValueHashCode(AsyncVariantImplMethod value) => value.MetadataDefinition.GetHashCode();
36+
protected override bool CompareKeyToValue(EcmaMethod key, AsyncVariantImplMethod value) => key == value.MetadataDefinition;
37+
protected override bool CompareValueToValue(AsyncVariantImplMethod value1, AsyncVariantImplMethod value2)
38+
=> value1.MetadataDefinition == value2.MetadataDefinition;
39+
protected override AsyncVariantImplMethod CreateValueFromKey(EcmaMethod key) => new AsyncVariantImplMethod(key);
40+
}
41+
private AsyncVariantImplHashtable _asyncVariantImplHashtable = new AsyncVariantImplHashtable();
42+
}
43+
}

src/coreclr/tools/Common/Compiler/MethodExtensions.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
5+
46
using ILCompiler.DependencyAnalysis;
57

68
using Internal.TypeSystem;
@@ -77,6 +79,27 @@ public static string GetUnmanagedCallersOnlyExportName(this EcmaMethod This)
7779
return null;
7880
}
7981

82+
/// <summary>
83+
/// Returns true if the method returns Task, Task&lt;T&gt;, ValueTask, or ValueTask&lt;T&gt;, otherwise false.
84+
/// </summary>
85+
public static bool IsTaskReturning(this MethodDesc method)
86+
{
87+
TypeDesc ret = method.GetTypicalMethodDefinition().Signature.ReturnType;
88+
89+
if (ret is MetadataType md
90+
&& md.Module == method.Context.SystemModule
91+
&& md.Namespace.SequenceEqual("System.Threading.Tasks"u8))
92+
{
93+
ReadOnlySpan<byte> name = md.Name;
94+
if (name.SequenceEqual("Task"u8) || name.SequenceEqual("Task`1"u8)
95+
|| name.SequenceEqual("ValueTask"u8) || name.SequenceEqual("ValueTask`1"u8))
96+
{
97+
return true;
98+
}
99+
}
100+
return false;
101+
}
102+
80103
#if !READYTORUN
81104
/// <summary>
82105
/// Determine whether a method can go into the sealed vtable of a type. Such method must be a sealed virtual

src/coreclr/tools/Common/JitInterface/AsyncMethodDesc.cs

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
using System;
55
using System.Diagnostics;
6+
7+
using ILCompiler;
8+
69
using Internal.TypeSystem;
710

811
namespace Internal.JitInterface
@@ -82,28 +85,4 @@ public override MethodSignature Signature
8285
int IJitHashableOnly.GetJitVisibleHashCode() => _jitVisibleHashCode;
8386
#endif
8487
}
85-
86-
internal static class AsyncMethodDescExtensions
87-
{
88-
/// <summary>
89-
/// Returns true if the method returns Task, Task&lt;T&gt;, ValueTask, or ValueTask&lt;T&gt;, otherwise false.
90-
/// </summary>
91-
public static bool IsTaskReturning(this MethodDesc method)
92-
{
93-
TypeDesc ret = method.GetTypicalMethodDefinition().Signature.ReturnType;
94-
95-
if (ret is MetadataType md
96-
&& md.Module == method.Context.SystemModule
97-
&& md.Namespace.SequenceEqual("System.Threading.Tasks"u8))
98-
{
99-
ReadOnlySpan<byte> name = md.Name;
100-
if (name.SequenceEqual("Task"u8) || name.SequenceEqual("Task`1"u8)
101-
|| name.SequenceEqual("ValueTask"u8) || name.SequenceEqual("ValueTask`1"u8))
102-
{
103-
return true;
104-
}
105-
}
106-
return false;
107-
}
108-
}
10988
}

src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,7 @@ private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* s
877877

878878
if (!signature.IsStatic) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_HASTHIS;
879879
if (signature.IsExplicitThis) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_EXPLICITTHIS;
880+
if (signature.IsAsyncCallConv) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL;
880881

881882
TypeDesc returnType = signature.ReturnType;
882883

@@ -1827,11 +1828,6 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
18271828

18281829
if (result is MethodDesc method)
18291830
{
1830-
pResolvedToken.hMethod = ObjectToHandle(method);
1831-
1832-
TypeDesc owningClass = method.OwningType;
1833-
pResolvedToken.hClass = ObjectToHandle(owningClass);
1834-
18351831
#if !SUPPORT_JIT
18361832
_compilation.TypeSystemContext.EnsureLoadableMethod(method);
18371833
#endif
@@ -1847,6 +1843,34 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
18471843
#else
18481844
_compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method);
18491845
#endif
1846+
1847+
if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await)
1848+
{
1849+
Debug.Assert(method.IsAsync);
1850+
1851+
#if !READYTORUN
1852+
// in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T).
1853+
// we cannot resolve to an Async variant in such case.
1854+
// return NULL, so that caller would re-resolve as a regular method call
1855+
method = method.IsTaskReturning()
1856+
? _compilation.TypeSystemContext.GetAsyncVariantImplMethod(method)
1857+
: null;
1858+
#else
1859+
// Above ifdef might be usable as-is, but GetAsyncVariantImplMethod is currently not part of the project
1860+
throw new NotImplementedException();
1861+
#endif
1862+
}
1863+
1864+
if (method != null)
1865+
{
1866+
pResolvedToken.hMethod = ObjectToHandle(method);
1867+
pResolvedToken.hClass = ObjectToHandle(method.OwningType);
1868+
}
1869+
else
1870+
{
1871+
pResolvedToken.hMethod = null;
1872+
pResolvedToken.hClass = null;
1873+
}
18501874
}
18511875
else
18521876
if (result is FieldDesc)
@@ -4315,7 +4339,7 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes)
43154339
flags.Set(CorJitFlag.CORJIT_FLAG_SOFTFP_ABI);
43164340
}
43174341

4318-
if (this.MethodBeingCompiled.IsAsync)
4342+
if (this.MethodBeingCompiled.Signature.IsAsyncCallConv)
43194343
{
43204344
flags.Set(CorJitFlag.CORJIT_FLAG_ASYNC);
43214345
}

src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ public enum CorInfoCallConv
377377
CORINFO_CALLCONV_HASTHIS = 0x20,
378378
CORINFO_CALLCONV_EXPLICITTHIS = 0x40,
379379
CORINFO_CALLCONV_PARAMTYPE = 0x80, // Passed last. Same as CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
380+
CORINFO_CALLCONV_ASYNCCALL = 0x100, // Is this a call to an async function?
380381
}
381382

382383
// Represents the calling conventions supported with the extensible calling convention syntax
@@ -1364,6 +1365,9 @@ public enum CorInfoTokenKind
13641365

13651366
// token comes from resolved static virtual method
13661367
CORINFO_TOKENKIND_ResolvedStaticVirtualMethod = 0x1000 | CORINFO_TOKENKIND_Method,
1368+
1369+
// token comes from runtime async awaiting pattern
1370+
CORINFO_TOKENKIND_Await = 0x2000 | CORINFO_TOKENKIND_Method,
13671371
};
13681372

13691373
// These are error codes returned by CompileMethod

src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55

6+
using ILCompiler;
7+
68
using Internal.TypeSystem;
79
using Internal.TypeSystem.Ecma;
810

@@ -309,6 +311,20 @@ public override MethodIL GetMethodIL(MethodDesc method)
309311
return result;
310312
}
311313

314+
if (ecmaMethod.IsAsync)
315+
{
316+
if (ecmaMethod.IsTaskReturning())
317+
{
318+
return AsyncThunkILEmitter.EmitTaskReturningThunk(ecmaMethod, ((CompilerTypeSystemContext)ecmaMethod.Context).GetAsyncVariantImplMethod(ecmaMethod));
319+
}
320+
else
321+
{
322+
// We only allow non-Task returning runtime async methods in CoreLib
323+
if (ecmaMethod.OwningType.Module != ecmaMethod.Context.SystemModule)
324+
ThrowHelper.ThrowBadImageFormatException();
325+
}
326+
}
327+
312328
MethodIL methodIL = EcmaMethodIL.Create(ecmaMethod);
313329
if (methodIL != null)
314330
return methodIL;
@@ -354,6 +370,11 @@ public override MethodIL GetMethodIL(MethodDesc method)
354370
return ArrayMethodILEmitter.EmitIL((ArrayMethod)method);
355371
}
356372
else
373+
if (method is AsyncVariantImplMethod asyncVariantImpl)
374+
{
375+
return EcmaMethodIL.Create(asyncVariantImpl.MetadataDefinition);
376+
}
377+
else
357378
{
358379
Debug.Assert(!(method is PInvokeTargetNativeMethod), "Who is asking for IL of PInvokeTargetNativeMethod?");
359380
return null;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Internal.TypeSystem;
5+
6+
namespace Internal.IL.Stubs
7+
{
8+
public static class AsyncThunkILEmitter
9+
{
10+
public static MethodIL EmitTaskReturningThunk(MethodDesc taskReturningMethod, MethodDesc asyncMethod)
11+
{
12+
TypeSystemContext context = taskReturningMethod.Context;
13+
14+
var emitter = new ILEmitter();
15+
var codestream = emitter.NewCodeStream();
16+
17+
// TODO: match EmitTaskReturningThunk in CoreCLR VM
18+
19+
MethodSignature sig = asyncMethod.Signature;
20+
int numParams = (sig.IsStatic || sig.IsExplicitThis) ? sig.Length : sig.Length + 1;
21+
for (int i = 0; i < numParams; i++)
22+
codestream.EmitLdArg(i);
23+
24+
codestream.Emit(ILOpcode.call, emitter.NewToken(asyncMethod));
25+
26+
if (sig.ReturnType.IsVoid)
27+
{
28+
codestream.Emit(ILOpcode.call, emitter.NewToken(context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8).GetKnownMethod("get_CompletedTask"u8, null)));
29+
}
30+
else
31+
{
32+
codestream.Emit(ILOpcode.call, emitter.NewToken(context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8).GetKnownMethod("FromResult"u8, null).MakeInstantiatedMethod(sig.ReturnType)));
33+
}
34+
35+
codestream.Emit(ILOpcode.ret);
36+
37+
return emitter.Link(taskReturningMethod);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)