Skip to content

First part of async support in native AOT - #121295

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:async1
Nov 5, 2025
Merged

First part of async support in native AOT#121295
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:async1

Conversation

@MichalStrehovsky

@MichalStrehovskyMichalStrehovsky commented Nov 3, 2025

Copy link
Copy Markdown
Member

Following works with runtime async enabled in Roslyn on native AOT:

publicclassAsync2Void{publicstaticvoidMain(){vart=AsyncTestEntryPoint(123,456);t.Wait();Console.WriteLine(t.Result);}privatestaticasyncTask<int>AsyncTestEntryPoint(intx,inty){intresult=awaitOtherAsync(x,y);returnresult;}privatestaticasyncTask<int>OtherAsync(intx,inty){Console.WriteLine(x);Console.WriteLine(y);returnx+y;}}

The implementation strategy is as follows:

  • In the compiler, whenever someone holds an EcmaMethod of an 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.

Cc @dotnet/ilc-contrib

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements support for NativeAOT async method compilation by introducing an async calling convention and variant method infrastructure. The key changes enable async methods to be compiled with special handling in NativeAOT, mirroring the CoreCLR VM's AsyncVariantImpl concept.

Key Changes:

  • Added NativeAOT-specific implementations for Task and Task<T> await methods in AsyncHelpers
  • Introduced AsyncVariantImplMethod infrastructure to represent async methods callable with async calling convention
  • Added support for unwrapping Task return types and marking methods with AsyncCallConv flag
  • Extended JIT interface to handle async token resolution and calling convention flags

Reviewed Changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
AsyncHelpers.csAdded NativeAOT-specific await implementations for Task/Task that handle completed tasks synchronously
ILCompiler.Compiler.csprojRegistered new async variant method source files in the compiler project
AsyncThunks.csAdded IL emitter for generating Task-returning thunks that wrap async methods
NativeAotILProvider.csIntegrated async method detection and thunk generation into IL provider
CorInfoTypes.csAdded CORINFO_CALLCONV_ASYNCCALL and CORINFO_TOKENKIND_Await enum values
CorInfoImpl.csImplemented async token resolution and propagated AsyncCallConv flag to JIT
AsyncMethodDesc.csRemoved IsTaskReturning extension method (moved to MethodExtensions)
MethodExtensions.csAdded IsTaskReturning helper method for identifying Task-returning methods
CompilerTypeSystemContext.Async.csAdded async variant method hashtable and instantiation support
AsyncVariantImplMethod.csCore implementation of async variant method with unwrapped signature
AsyncVariantImplMethod.Sorting.csAdded sorting/comparison support for async variant methods
AsyncVariantImplMethod.Mangling.csAdded name mangling with "AsyncCallable" prefix

Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

There are several shovel-ready workitems that can be started in parallel on the native AOT side after this merges (we can discuss on Teams).

I did a couple hacks on the crossgen side that makes things mostly compile in MichalStrehovsky@d395c1a (not part of this PR; was just looking around). I think the crossgen side will depend on what we want to compile. If we're fine with only precompiling the AsyncVariantImplMethod flavor and none of the thunks, the actual implementation could be different so as not to perturb the system too much. If we want to precompile the thunks as well, we'll have to take similar approach to my hack. The hacky part is that most of the crossgen2 codebase assumes that what we're working with are (potentially instantiated) EcmaMethods. It does not like when a method is not EcmaMethod (see the places I had to update, likely didn't hit them all yet, not sure if the fixes are correct). We cannot have EcmaMethod of the thunks.

Cc @dotnet/ilc-contrib

Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/tools/Common/Compiler/AsyncVariantImplMethod.cs Outdated
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs Outdated
@jkotas

Copy link
Copy Markdown
Member

There are several shovel-ready workitems that can be started in parallel on the native AOT side after this merges (we can discuss on Teams).

If you got a Teams channel going for this, would you mind adding me to it? I suspect that the questions that I have just asked might have been discussed there.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

There are several shovel-ready workitems that can be started in parallel on the native AOT side after this merges (we can discuss on Teams).

If you got a Teams channel going for this, would you mind adding me to it? I suspect that the questions that I have just asked might have been discussed there.

It was just an ad hoc video meeting between Jackson, Eduardo, David and me. I'll add you if we have something written.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Rebased this on top of Jackson's PR that added the MethodDesc, so this is now only 130 lines. I validated the repro in top-post still works.

This is ready for review now.

Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/ba-g widespread Apple and Android infra issues

@MichalStrehovsky
MichalStrehovsky merged commit 96d2c26 into dotnet:mainNov 5, 2025
125 of 145 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the async1 branch November 5, 2025 20:51
MichalStrehovsky added a commit to MichalStrehovsky/runtime that referenced this pull request Nov 5, 2025
The same program as in dotnet#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), however because we need to derive from `Continuation` and presumably need to have a working vtable (with Equals/GetHashCode/ToString), we can't use this. So we emit a normal `MethodTable` for a synthetic type. Maybe we could optimize this to the one without vtable in the future.
The synthetic type is a `MetadataType` that reports `Continuation` as the base type. It has instance fields of type `object` or `nint`, based on what we need. Because the standard type layout algorithm (the thing that assigns offsets to fields) would attempt to group GC fields together (it's a layout that works better for the GC), we also have a custom layouting algorithm that lays out these fields sequentially.
The resumption stub is just a stubbed out TODO for someone else to look into for now.
MichalStrehovsky added a commit that referenced this pull request Nov 7, 2025
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.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 6, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@MichalStrehovsky@jkotas@jakobbotsch@VSadov