Skip to content

Commit 621d670

Browse files
Async continuations for native AOT (#121398)
The same program as in #121295 still works, but we can newly report async helpers as Intrinsic/Async. This implements things around suspension/resumption. Most of this change is around handling continuation types. Continuation types are synthetic types (created in the compiler) that derive from `Continuation` in the CoreLib. The JIT requests these based on the shape of locals it needs to preserve. What we get from the JIT in CorInfoImpl is size of the type and a pointer map (1011001 - non-zero means "GC pointer is here"). What we need to generate is a `MethodTable` for a type that derives from `Continuation` and has the specified GC layout after fields inherited from `Continuation`. We already have a similar thing in the compiler ("`MethodTable`" we use for GC statics), so we're reusing the same approach. The `MethodTable` is pretty restricted and not the whole `MethodTable` API surface on it is available at runtime. However, it implements enough that the GC can operate on it. The two new types are `AsyncContinuationType`. This is a type system `MetadataType` that represents the continuation pointer map. Then we have `AsyncContinuationEETypeNode` which is the thing that gets emitted into the output file - the `MethodTable` itself. The resumption stub is just a stubbed out TODO for someone else to look into for now.
1 parent a1bc3b1 commit 621d670

File tree

16 files changed

+369
-58
lines changed

16 files changed

+369
-58
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
using System.Threading.Tasks;
1414
using System.Threading.Tasks.Sources;
1515

16+
#if NATIVEAOT
17+
using Internal.Runtime;
18+
#endif
19+
1620
namespace System.Runtime.CompilerServices
1721
{
1822
internal struct ExecutionAndSyncBlockStore
@@ -166,11 +170,16 @@ private struct RuntimeAsyncAwaitState
166170

167171
private static unsafe Continuation AllocContinuation(Continuation prevContinuation, MethodTable* contMT)
168172
{
173+
#if NATIVEAOT
174+
Continuation newContinuation = (Continuation)RuntimeImports.RhNewObject(contMT);
175+
#else
169176
Continuation newContinuation = (Continuation)RuntimeTypeHandle.InternalAllocNoChecks(contMT);
177+
#endif
170178
prevContinuation.Next = newContinuation;
171179
return newContinuation;
172180
}
173181

182+
#if !NATIVEAOT
174183
private static unsafe Continuation AllocContinuationMethod(Continuation prevContinuation, MethodTable* contMT, int keepAliveOffset, MethodDesc* method)
175184
{
176185
LoaderAllocator loaderAllocator = RuntimeMethodHandle.GetLoaderAllocator(new RuntimeMethodHandleInternal((IntPtr)method));
@@ -192,6 +201,7 @@ private static unsafe Continuation AllocContinuationClass(Continuation prevConti
192201
}
193202
return newContinuation;
194203
}
204+
#endif
195205

196206
/// <summary>
197207
/// Used by internal thunks that implement awaiting on Task or a ValueTask.

src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
<Link>ExceptionStringID.cs</Link>
5656
</Compile>
5757
</ItemGroup>
58+
<ItemGroup>
59+
<!-- TODO: (async) once we know which helpers can actually be shared, move those to libraries partition -->
60+
<Compile Include="$(CoreClrProjectRoot)System.Private.CoreLib\src\System\Runtime\CompilerServices\AsyncHelpers.CoreCLR.cs" />
61+
</ItemGroup>
5862
<ItemGroup>
5963
<Compile Include="Internal\Runtime\CompilerHelpers\DelegateHelpers.cs" />
6064
<Compile Include="Internal\Runtime\CompilerHelpers\LibraryInitializer.cs" />
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Text;
8+
9+
using Internal.TypeSystem;
10+
11+
namespace ILCompiler
12+
{
13+
/// <summary>
14+
/// An async continuation type. The code generator will request this to store local state
15+
/// through an async suspension/resumption. We only identify these using a <see cref="GCPointerMap"/>
16+
/// since that's all the code generator cares about - size of the type, and where the GC pointers are.
17+
/// </summary>
18+
public sealed partial class AsyncContinuationType : MetadataType
19+
{
20+
private readonly MetadataType _continuationBaseType;
21+
public GCPointerMap PointerMap { get; }
22+
23+
public override DefType[] ExplicitlyImplementedInterfaces => [];
24+
public override ReadOnlySpan<byte> Name => Encoding.UTF8.GetBytes(DiagnosticName);
25+
public override ReadOnlySpan<byte> Namespace => [];
26+
27+
// We don't lay these out using MetadataType metadata.
28+
// Autolayout (which we'd get due to GC pointers) would likely not match what codegen expects.
29+
public override bool IsExplicitLayout => throw new NotImplementedException();
30+
public override bool IsSequentialLayout => throw new NotImplementedException();
31+
public override bool IsExtendedLayout => throw new NotImplementedException();
32+
public override bool IsAutoLayout => throw new NotImplementedException();
33+
public override ClassLayoutMetadata GetClassLayout() => throw new NotImplementedException();
34+
35+
public override bool IsBeforeFieldInit => false;
36+
public override ModuleDesc Module => _continuationBaseType.Module;
37+
public override MetadataType BaseType => _continuationBaseType;
38+
public override bool IsSealed => true;
39+
public override bool IsAbstract => false;
40+
public override MetadataType ContainingType => null;
41+
public override PInvokeStringFormat PInvokeStringFormat => default;
42+
public override string DiagnosticName => $"ContinuationType_{PointerMap}";
43+
public override string DiagnosticNamespace => "";
44+
protected override int ClassCode => 0x528741a;
45+
public override TypeSystemContext Context => _continuationBaseType.Context;
46+
47+
public AsyncContinuationType(MetadataType continuationBaseType, GCPointerMap pointerMap)
48+
=> (_continuationBaseType, PointerMap) = (continuationBaseType, pointerMap);
49+
50+
public override bool HasCustomAttribute(string attributeNamespace, string attributeName) => false;
51+
public override IEnumerable<MetadataType> GetNestedTypes() => [];
52+
public override MetadataType GetNestedType(string name) => null;
53+
protected override MethodImplRecord[] ComputeVirtualMethodImplsForType() => [];
54+
public override MethodImplRecord[] FindMethodsImplWithMatchingDeclName(ReadOnlySpan<byte> name) => [];
55+
56+
protected override int CompareToImpl(TypeDesc other, TypeSystemComparer comparer)
57+
{
58+
Debug.Assert(_continuationBaseType == ((AsyncContinuationType)other)._continuationBaseType);
59+
GCPointerMap otherPointerMap = ((AsyncContinuationType)other).PointerMap;
60+
return PointerMap.CompareTo(otherPointerMap);
61+
}
62+
63+
public override int GetHashCode() => PointerMap.GetHashCode();
64+
65+
protected override TypeFlags ComputeTypeFlags(TypeFlags mask)
66+
{
67+
TypeFlags flags = 0;
68+
69+
if ((mask & TypeFlags.HasGenericVarianceComputed) != 0)
70+
{
71+
flags |= TypeFlags.HasGenericVarianceComputed;
72+
}
73+
74+
if ((mask & TypeFlags.CategoryMask) != 0)
75+
{
76+
flags |= TypeFlags.Class;
77+
}
78+
79+
flags |= TypeFlags.HasFinalizerComputed;
80+
flags |= TypeFlags.AttributeCacheComputed;
81+
82+
return flags;
83+
}
84+
}
85+
}

src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public MethodDesc GetAsyncVariantMethod(MethodDesc taskReturningMethod)
1515
{
1616
Debug.Assert(taskReturningMethod.Signature.ReturnsTaskOrValueTask());
1717
MethodDesc asyncMetadataMethodDef = taskReturningMethod.GetTypicalMethodDefinition();
18-
MethodDesc result = _asyncVariantImplHashtable.GetOrCreateValue((EcmaMethod)asyncMetadataMethodDef);
18+
MethodDesc result = _asyncVariantHashtable.GetOrCreateValue((EcmaMethod)asyncMetadataMethodDef);
1919

2020
if (asyncMetadataMethodDef != taskReturningMethod)
2121
{
@@ -30,7 +30,7 @@ public MethodDesc GetAsyncVariantMethod(MethodDesc taskReturningMethod)
3030
return result;
3131
}
3232

33-
private sealed class AsyncVariantImplHashtable : LockFreeReaderHashtable<EcmaMethod, AsyncMethodVariant>
33+
private sealed class AsyncVariantHashtable : LockFreeReaderHashtable<EcmaMethod, AsyncMethodVariant>
3434
{
3535
protected override int GetKeyHashCode(EcmaMethod key) => key.GetHashCode();
3636
protected override int GetValueHashCode(AsyncMethodVariant value) => value.Target.GetHashCode();
@@ -39,6 +39,32 @@ protected override bool CompareValueToValue(AsyncMethodVariant value1, AsyncMeth
3939
=> value1.Target == value2.Target;
4040
protected override AsyncMethodVariant CreateValueFromKey(EcmaMethod key) => new AsyncMethodVariant(key);
4141
}
42-
private AsyncVariantImplHashtable _asyncVariantImplHashtable = new AsyncVariantImplHashtable();
42+
private AsyncVariantHashtable _asyncVariantHashtable = new AsyncVariantHashtable();
43+
44+
public MetadataType GetContinuationType(GCPointerMap pointerMap)
45+
{
46+
return _continuationTypeHashtable.GetOrCreateValue(pointerMap);
47+
}
48+
49+
private sealed class ContinuationTypeHashtable : LockFreeReaderHashtable<GCPointerMap, AsyncContinuationType>
50+
{
51+
private readonly CompilerTypeSystemContext _parent;
52+
private MetadataType _continuationType;
53+
54+
public ContinuationTypeHashtable(CompilerTypeSystemContext parent)
55+
=> _parent = parent;
56+
57+
protected override int GetKeyHashCode(GCPointerMap key) => key.GetHashCode();
58+
protected override int GetValueHashCode(AsyncContinuationType value) => value.PointerMap.GetHashCode();
59+
protected override bool CompareKeyToValue(GCPointerMap key, AsyncContinuationType value) => key.Equals(value.PointerMap);
60+
protected override bool CompareValueToValue(AsyncContinuationType value1, AsyncContinuationType value2)
61+
=> value1.PointerMap.Equals(value2.PointerMap);
62+
protected override AsyncContinuationType CreateValueFromKey(GCPointerMap key)
63+
{
64+
_continuationType ??= _parent.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "Continuation"u8);
65+
return new AsyncContinuationType(_continuationType, key);
66+
}
67+
}
68+
private ContinuationTypeHashtable _continuationTypeHashtable;
4369
}
4470
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ private void CompileMethodCleanup()
685685

686686
#if !READYTORUN
687687
_debugInfo = null;
688+
_asyncResumptionStub = null;
688689
#endif
689690

690691
_debugLocInfos = null;
@@ -3401,7 +3402,19 @@ private void getAsyncInfo(ref CORINFO_ASYNC_INFO pAsyncInfoOut)
34013402
private CORINFO_CLASS_STRUCT_* getContinuationType(nuint dataSize, ref bool objRefs, nuint objRefsSize)
34023403
{
34033404
Debug.Assert(objRefsSize == (dataSize + (nuint)(PointerSize - 1)) / (nuint)PointerSize);
3405+
#if READYTORUN
34043406
throw new NotImplementedException("getContinuationType");
3407+
#else
3408+
GCPointerMapBuilder gcMapBuilder = new GCPointerMapBuilder((int)dataSize, PointerSize);
3409+
ReadOnlySpan<bool> bools = MemoryMarshal.CreateReadOnlySpan(ref objRefs, (int)objRefsSize);
3410+
for (int i = 0; i < bools.Length; i++)
3411+
{
3412+
if (bools[i])
3413+
gcMapBuilder.MarkGCPointer(i * PointerSize);
3414+
}
3415+
3416+
return ObjectToHandle(_compilation.TypeSystemContext.GetContinuationType(gcMapBuilder.ToGCMap()));
3417+
#endif
34053418
}
34063419

34073420
private mdToken getMethodDefFromMethod(CORINFO_METHOD_STRUCT_* hMethod)
@@ -3756,7 +3769,14 @@ private bool getTailCallHelpers(ref CORINFO_RESOLVED_TOKEN callToken, CORINFO_SI
37563769
private CORINFO_METHOD_STRUCT_* getAsyncResumptionStub(ref void* entryPoint)
37573770
#pragma warning restore CA1822 // Mark members as static
37583771
{
3772+
#if READYTORUN
37593773
throw new NotImplementedException("Crossgen2 does not support runtime-async yet");
3774+
#else
3775+
_asyncResumptionStub ??= new AsyncResumptionStub(MethodBeingCompiled);
3776+
3777+
entryPoint = (void*)ObjectToHandle(_compilation.NodeFactory.MethodEntrypoint(_asyncResumptionStub));
3778+
return ObjectToHandle(_asyncResumptionStub);
3779+
#endif
37603780
}
37613781

37623782
private byte[] _code;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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.Stubs;
5+
using Internal.TypeSystem;
6+
7+
namespace ILCompiler
8+
{
9+
public partial class AsyncResumptionStub : ILStubMethod, IPrefixMangledMethod
10+
{
11+
MethodDesc IPrefixMangledMethod.BaseMethod => _owningMethod;
12+
13+
string IPrefixMangledMethod.Prefix => "Resume";
14+
}
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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.Stubs;
5+
using Internal.TypeSystem;
6+
7+
namespace ILCompiler
8+
{
9+
public partial class AsyncResumptionStub : ILStubMethod
10+
{
11+
protected override int ClassCode => 0x773ab1;
12+
13+
protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer)
14+
{
15+
return comparer.Compare(_owningMethod, ((AsyncResumptionStub)other)._owningMethod);
16+
}
17+
}
18+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
6+
using Internal.IL;
7+
using Internal.IL.Stubs;
8+
using Internal.TypeSystem;
9+
10+
using Debug = System.Diagnostics.Debug;
11+
12+
namespace ILCompiler
13+
{
14+
public partial class AsyncResumptionStub : ILStubMethod
15+
{
16+
private readonly MethodDesc _owningMethod;
17+
private MethodSignature _signature;
18+
19+
public AsyncResumptionStub(MethodDesc owningMethod)
20+
{
21+
Debug.Assert(owningMethod.IsAsyncVariant()
22+
|| (owningMethod.IsAsync && !owningMethod.Signature.ReturnsTaskOrValueTask()));
23+
_owningMethod = owningMethod;
24+
}
25+
26+
public override ReadOnlySpan<byte> Name => _owningMethod.Name;
27+
public override string DiagnosticName => _owningMethod.DiagnosticName;
28+
29+
public override TypeDesc OwningType => _owningMethod.OwningType;
30+
31+
public override MethodSignature Signature => _signature ??= InitializeSignature();
32+
33+
public override TypeSystemContext Context => _owningMethod.Context;
34+
35+
private MethodSignature InitializeSignature()
36+
{
37+
TypeDesc objectType = Context.GetWellKnownType(WellKnownType.Object);
38+
TypeDesc byrefByte = Context.GetWellKnownType(WellKnownType.Byte).MakeByRefType();
39+
return _signature = new MethodSignature(0, 0, objectType, [objectType, byrefByte]);
40+
}
41+
42+
public override MethodIL EmitIL()
43+
{
44+
var emitter = new ILEmitter();
45+
ILCodeStream codeStream = emitter.NewCodeStream();
46+
47+
// TODO: match getAsyncResumptionStub from CoreCLR VM
48+
codeStream.EmitCallThrowHelper(emitter, Context.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowNotSupportedException"u8));
49+
50+
return emitter.Link(this);
51+
}
52+
}
53+
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode gener
5959
_typeWithRepeatedFieldsFieldLayoutAlgorithm = new TypeWithRepeatedFieldsFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm);
6060

6161
_delegateInfoHashtable = new DelegateInfoHashtable(delegateFeatures);
62+
_continuationTypeHashtable = new ContinuationTypeHashtable(this);
6263

6364
_genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(genericCycleDepthCutoff, genericCycleBreadthCutoff);
6465

0 commit comments

Comments
 (0)