From 79e8a8db7f128d2ff490e02e2212bfe88287989d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= 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 @@ + + + @@ -42,6 +45,7 @@ IL\DelegateInfo.cs + IL\Stubs\DelegateThunks.Sorting.cs 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(ConfiguredValueTaskAwaitable configuredAwaitable) public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { throw new NotImplementedException(); } [RequiresPreviewFeatures] public static void AwaitAwaiter(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(System.Threading.Tasks.Task task) + { + TaskAwaiter 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(System.Threading.Tasks.Task 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?= 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 {