Skip to content

Conversation

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Nov 3, 2025

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

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 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

Copy link
Contributor

Copilot AI left a comment

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
File Description
AsyncHelpers.cs Added NativeAOT-specific await implementations for Task/Task that handle completed tasks synchronously
ILCompiler.Compiler.csproj Registered new async variant method source files in the compiler project
AsyncThunks.cs Added IL emitter for generating Task-returning thunks that wrap async methods
NativeAotILProvider.cs Integrated async method detection and thunk generation into IL provider
CorInfoTypes.cs Added CORINFO_CALLCONV_ASYNCCALL and CORINFO_TOKENKIND_Await enum values
CorInfoImpl.cs Implemented async token resolution and propagated AsyncCallConv flag to JIT
AsyncMethodDesc.cs Removed IsTaskReturning extension method (moved to MethodExtensions)
MethodExtensions.cs Added IsTaskReturning helper method for identifying Task-returning methods
CompilerTypeSystemContext.Async.cs Added async variant method hashtable and instantiation support
AsyncVariantImplMethod.cs Core implementation of async variant method with unwrapped signature
AsyncVariantImplMethod.Sorting.cs Added sorting/comparison support for async variant methods
AsyncVariantImplMethod.Mangling.cs Added name mangling with "AsyncCallable" prefix

@MichalStrehovsky
Copy link
Member Author

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

@jkotas
Copy link
Member

jkotas commented Nov 4, 2025

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
Member Author

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
Member Author

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.

@MichalStrehovsky
Copy link
Member Author

/ba-g widespread Apple and Android infra issues

@MichalStrehovsky MichalStrehovsky merged commit 96d2c26 into dotnet:main Nov 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants