From 79e8a8db7f128d2ff490e02e2212bfe88287989d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?=
 <MichalStrehovsky@users.noreply.github.com>
Date: Wed, 5 Nov 2025 09:43:50 +0100
Subject: [PATCH 1/2] Rebased

---
 .../Compiler/AsyncMethodVariant.Mangling.cs   | 17 ++++++++
 .../tools/Common/JitInterface/CorInfoImpl.cs  | 28 ++++++++++---
 .../tools/Common/JitInterface/CorInfoTypes.cs |  3 ++
 .../TypeSystem/IL/NativeAotILProvider.cs      | 21 ++++++++++
 .../Common/TypeSystem/IL/Stubs/AsyncThunks.cs | 40 +++++++++++++++++++
 .../ILCompiler.Compiler.csproj                |  4 ++
 .../Runtime/CompilerServices/AsyncHelpers.cs  | 25 ++++++++++++
 7 files changed, 132 insertions(+), 6 deletions(-)
 create mode 100644 src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs
 create mode 100644 src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs

diff --git a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs
new file mode 100644
index 00000000000000..7d579757ab319b
--- /dev/null
+++ b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs
@@ -0,0 +1,17 @@
+// 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;
+
+using Debug = System.Diagnostics.Debug;
+
+namespace ILCompiler
+{
+    public partial class AsyncMethodVariant : MethodDelegator, IPrefixMangledMethod
+    {
+        MethodDesc IPrefixMangledMethod.BaseMethod => _wrappedMethod;
+
+        string IPrefixMangledMethod.Prefix => "AsyncCallable";
+    }
+}
diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
index f987e7e637c3e0..d6317a79eed721 100644
--- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
+++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@@ -1828,11 +1828,6 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
 
             if (result is MethodDesc method)
             {
-                pResolvedToken.hMethod = ObjectToHandle(method);
-
-                TypeDesc owningClass = method.OwningType;
-                pResolvedToken.hClass = ObjectToHandle(owningClass);
-
 #if !SUPPORT_JIT
                 _compilation.TypeSystemContext.EnsureLoadableMethod(method);
 #endif
@@ -1848,6 +1843,27 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)
 #else
                 _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method);
 #endif
+
+                if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Await)
+                {
+                    // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T).
+                    // we cannot resolve to an Async variant in such case.
+                    // return NULL, so that caller would re-resolve as a regular method call
+                    method = method.IsAsync && method.GetMethodDefinition().Signature.ReturnsTaskOrValueTask()
+                        ? _compilation.TypeSystemContext.GetAsyncVariantMethod(method)
+                        : null;
+                }
+
+                if (method != null)
+                {
+                    pResolvedToken.hMethod = ObjectToHandle(method);
+                    pResolvedToken.hClass = ObjectToHandle(method.OwningType);
+                }
+                else
+                {
+                    pResolvedToken.hMethod = null;
+                    pResolvedToken.hClass = null;
+                }
             }
             else
             if (result is FieldDesc)
@@ -4316,7 +4332,7 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes)
                 flags.Set(CorJitFlag.CORJIT_FLAG_SOFTFP_ABI);
             }
 
-            if (this.MethodBeingCompiled.IsAsync)
+            if (this.MethodBeingCompiled.Signature.IsAsyncCall)
             {
                 flags.Set(CorJitFlag.CORJIT_FLAG_ASYNC);
             }
diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs
index c82c51c4e7d257..628dc4974a262f 100644
--- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs
+++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs
@@ -1366,6 +1366,9 @@ public enum CorInfoTokenKind
 
         // token comes from resolved static virtual method
         CORINFO_TOKENKIND_ResolvedStaticVirtualMethod = 0x1000 | CORINFO_TOKENKIND_Method,
+
+        // token comes from runtime async awaiting pattern
+        CORINFO_TOKENKIND_Await = 0x2000 | CORINFO_TOKENKIND_Method,
     };
 
     // These are error codes returned by CompileMethod
diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs
index 373b2abd826af7..eef3dc45a205bc 100644
--- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs
+++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs
@@ -3,6 +3,8 @@
 
 using System;
 
+using ILCompiler;
+
 using Internal.TypeSystem;
 using Internal.TypeSystem.Ecma;
 
@@ -309,6 +311,20 @@ public override MethodIL GetMethodIL(MethodDesc method)
                         return result;
                 }
 
+                if (ecmaMethod.IsAsync)
+                {
+                    if (ecmaMethod.Signature.ReturnsTaskOrValueTask())
+                    {
+                        return AsyncThunkILEmitter.EmitTaskReturningThunk(ecmaMethod, ((CompilerTypeSystemContext)ecmaMethod.Context).GetAsyncVariantMethod(ecmaMethod));
+                    }
+                    else
+                    {
+                        // We only allow non-Task returning runtime async methods in CoreLib
+                        if (ecmaMethod.OwningType.Module != ecmaMethod.Context.SystemModule)
+                            ThrowHelper.ThrowBadImageFormatException();
+                    }
+                }
+
                 MethodIL methodIL = EcmaMethodIL.Create(ecmaMethod);
                 if (methodIL != null)
                     return methodIL;
@@ -354,6 +370,11 @@ public override MethodIL GetMethodIL(MethodDesc method)
                 return ArrayMethodILEmitter.EmitIL((ArrayMethod)method);
             }
             else
+            if (method is AsyncMethodVariant asyncVariantImpl)
+            {
+                return EcmaMethodIL.Create(asyncVariantImpl.Target);
+            }
+            else
             {
                 Debug.Assert(!(method is PInvokeTargetNativeMethod), "Who is asking for IL of PInvokeTargetNativeMethod?");
                 return null;
diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
new file mode 100644
index 00000000000000..0f048327cfdc4d
--- /dev/null
+++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Internal.TypeSystem;
+
+namespace Internal.IL.Stubs
+{
+    public static class AsyncThunkILEmitter
+    {
+        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);
+
+            codestream.Emit(ILOpcode.call, emitter.NewToken(asyncMethod));
+
+            if (sig.ReturnType.IsVoid)
+            {
+                codestream.Emit(ILOpcode.call, emitter.NewToken(context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8).GetKnownMethod("get_CompletedTask"u8, null)));
+            }
+            else
+            {
+                codestream.Emit(ILOpcode.call, emitter.NewToken(context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8).GetKnownMethod("FromResult"u8, null).MakeInstantiatedMethod(sig.ReturnType)));
+            }
+
+            codestream.Emit(ILOpcode.ret);
+
+            return emitter.Link(taskReturningMethod);
+        }
+    }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
index c1a26ce1951dc7..5cd4a3f196d577 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
@@ -33,6 +33,9 @@
   </ItemGroup>
 
   <ItemGroup>
+    <Compile Include="..\..\Common\Compiler\AsyncMethodVariant.cs" Link="Compiler\AsyncMethodVariant.cs" />
+    <Compile Include="..\..\Common\Compiler\AsyncMethodVariant.Mangling.cs" Link="Compiler\AsyncMethodVariant.Mangling.cs" />
+    <Compile Include="..\..\Common\Compiler\CompilerTypeSystemContext.Async.cs" Link="Compiler\CompilerTypeSystemContext.Async.cs" />
     <Compile Include="..\..\Common\Compiler\Win32Resources\ResourceData.cs" Link="Compiler\Win32Resources\ResourceData.cs" />
     <Compile Include="..\..\Common\Compiler\Win32Resources\ResourceData.Reader.cs" Link="Compiler\Win32Resources\ResourceData.Reader.cs" />
     <Compile Include="..\..\Common\Compiler\Win32Resources\ResourceData.ResourcesDataModel.cs" Link="Compiler\Win32Resources\ResourceData.ResourcesDataModel.cs" />
@@ -42,6 +45,7 @@
     <Compile Include="..\..\Common\TypeSystem\IL\DelegateInfo.cs">
       <Link>IL\DelegateInfo.cs</Link>
     </Compile>
+    <Compile Include="..\..\Common\TypeSystem\IL\Stubs\AsyncThunks.cs" Link="IL\Stubs\AsyncThunks.cs" />
     <Compile Include="..\..\Common\TypeSystem\IL\Stubs\DelegateThunks.Sorting.cs">
       <Link>IL\Stubs\DelegateThunks.Sorting.cs</Link>
     </Compile>
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs
index 8a7563f241aa24..ffd7b11516f138 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs
@@ -220,10 +220,35 @@ public static T Await<T>(ConfiguredValueTaskAwaitable<T> configuredAwaitable)
         public static void UnsafeAwaitAwaiter<TAwaiter>(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { throw new NotImplementedException(); }
         [RequiresPreviewFeatures]
         public static void AwaitAwaiter<TAwaiter>(TAwaiter awaiter) where TAwaiter : INotifyCompletion { throw new NotImplementedException(); }
+#if NATIVEAOT
+        [RequiresPreviewFeatures]
+        public static void Await(System.Threading.Tasks.Task task)
+        {
+            TaskAwaiter awaiter = task.GetAwaiter();
+            if (!awaiter.IsCompleted)
+            {
+                throw new NotImplementedException();
+            }
+
+            awaiter.GetResult();
+        }
+        [RequiresPreviewFeatures]
+        public static T Await<T>(System.Threading.Tasks.Task<T> task)
+        {
+            TaskAwaiter<T> awaiter = task.GetAwaiter();
+            if (!awaiter.IsCompleted)
+            {
+                throw new NotImplementedException();
+            }
+
+            return awaiter.GetResult();
+        }
+#else
         [RequiresPreviewFeatures]
         public static void Await(System.Threading.Tasks.Task task) { throw new NotImplementedException(); }
         [RequiresPreviewFeatures]
         public static T Await<T>(System.Threading.Tasks.Task<T> task) { throw new NotImplementedException(); }
+#endif
         [RequiresPreviewFeatures]
         public static void Await(System.Threading.Tasks.ValueTask task) { throw new NotImplementedException(); }
         [RequiresPreviewFeatures]

From a1ef43edddeddf64805cfe28642981a2c336f648 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?=
 <MichalStrehovsky@users.noreply.github.com>
Date: Wed, 5 Nov 2025 04:58:19 -0800
Subject: [PATCH 2/2] Update
 src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs

---
 .../tools/Common/TypeSystem/IL/NativeAotILProvider.cs  | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs
index eef3dc45a205bc..8446e56e85bfe2 100644
--- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs
+++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs
@@ -372,7 +372,15 @@ public override MethodIL GetMethodIL(MethodDesc method)
             else
             if (method is AsyncMethodVariant asyncVariantImpl)
             {
-                return EcmaMethodIL.Create(asyncVariantImpl.Target);
+                if (asyncVariantImpl.IsAsync)
+                {
+                    return EcmaMethodIL.Create(asyncVariantImpl.Target);
+                }
+                else
+                {
+                    // TODO: Emit thunk with async calling convention
+                    throw new NotImplementedException();
+                }
             }
             else
             {
