From 2abb2202207514b69f7cc353c415b492723838e1 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:33:36 -0800 Subject: [PATCH 01/15] Emit IL for AsyncResumptionStub --- .../IL/Stubs/AsyncResumptionStub.cs | 113 ++++++++++++++++-- 1 file changed, 105 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 29504bb6834fcf..76b04deb481e35 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -15,11 +15,11 @@ public partial class AsyncResumptionStub : ILStubMethod { private readonly MethodDesc _owningMethod; private MethodSignature _signature; + private MethodIL _methodIL; public AsyncResumptionStub(MethodDesc owningMethod) { - Debug.Assert(owningMethod.IsAsyncVariant() - || (owningMethod.IsAsync && !owningMethod.Signature.ReturnsTaskOrValueTask())); + Debug.Assert(owningMethod.IsAsyncCall()); _owningMethod = owningMethod; } @@ -32,6 +32,8 @@ public AsyncResumptionStub(MethodDesc owningMethod) public override TypeSystemContext Context => _owningMethod.Context; + protected override int ClassCode => unchecked((int)0xa91ac565); + private MethodSignature InitializeSignature() { TypeDesc objectType = Context.GetWellKnownType(WellKnownType.Object); @@ -39,15 +41,110 @@ private MethodSignature InitializeSignature() return _signature = new MethodSignature(0, 0, objectType, [objectType, byrefByte]); } - public override MethodIL EmitIL() + private MethodIL InitializeMethodIL() { - var emitter = new ILEmitter(); - ILCodeStream codeStream = emitter.NewCodeStream(); + Debug.Assert(_methodIL == null); + ILEmitter ilEmitter = new ILEmitter(); + ILCodeStream ilStream = ilEmitter.NewCodeStream(); + + // Ported from jitinterface.cpp CEEJitInfo::getAsyncResumptionStub + // Emitted IL: + // if (!_owningMethod.Signature.IsStatic) + // { + // if (_owningMethod.OwningType.IsValueType) + // ldc.i4.0 + // conv.u + // else + // ldnull + // } + // foreach (param in _owningMethod.Signature) + // { + // ldloca.s + // initobj + // ldloc.s + // } + // ldftn <_owningMethod> + // calli + // if (!returnsVoid) + // stloc.s + // call System.StubHelpers.StubHelpers::AsyncCallContinuation() + // stloc.s + // if (!returnsVoid) + // { + // ldloca.s + // brtrue.s done_result + // ldarg.1 + // ldloc.s + // stobj + // done_result: + // } + // ldloc.s + // ret + + // if it has this pointer + if (!_owningMethod.Signature.IsStatic) + { + if (_owningMethod.OwningType.IsValueType) + { + ilStream.EmitLdc(0); + ilStream.Emit(ILOpcode.conv_u); + } + else + { + ilStream.Emit(ILOpcode.ldnull); + } + } + + foreach (var param in _owningMethod.Signature) + { + var local = ilEmitter.NewLocal(param); + ilStream.EmitLdLoca(local); + ilStream.Emit(ILOpcode.initobj, ilEmitter.NewToken(param)); + ilStream.EmitLdLoc(local); + } + ilStream.Emit(ILOpcode.ldftn, ilEmitter.NewToken(_owningMethod)); + ilStream.Emit(ILOpcode.calli, ilEmitter.NewToken(this.Signature)); + + bool returnsVoid = _owningMethod.Signature.ReturnType != Context.GetWellKnownType(WellKnownType.Void); + Internal.IL.Stubs.ILLocalVariable resultLocal = default; + if (!returnsVoid) + { + resultLocal = ilEmitter.NewLocal(_owningMethod.Signature.ReturnType); + ilStream.EmitStLoc(resultLocal); + } + + MethodDesc asyncCallContinuation = Context.SystemModule.GetKnownType("System.StubHelpers"u8, "StubHelpers"u8) + .GetKnownMethod("AsyncCallContinuation"u8, null); + TypeDesc continuation = Context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "Continuation"u8); + var newContinuationLocal = ilEmitter.NewLocal(continuation); + ilStream.Emit(ILOpcode.call, ilEmitter.NewToken(asyncCallContinuation)); + ilStream.EmitStLoc(newContinuationLocal); - // TODO: match getAsyncResumptionStub from CoreCLR VM - codeStream.EmitCallThrowHelper(emitter, Context.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowNotSupportedException"u8)); + if (!returnsVoid) + { + var doneResult = ilEmitter.NewCodeLabel(); + ilStream.EmitLdLoca(newContinuationLocal); + ilStream.Emit(ILOpcode.brtrue, doneResult); + ilStream.EmitLdArg(1); + ilStream.EmitLdLoc(resultLocal); + ilStream.Emit(ILOpcode.stobj, ilEmitter.NewToken(_owningMethod.Signature.ReturnType)); + ilStream.EmitLabel(doneResult); + } + ilStream.EmitLdLoc(newContinuationLocal); + ilStream.Emit(ILOpcode.ret); - return emitter.Link(this); + return _methodIL = ilEmitter.Link(this); + } + + public override MethodIL EmitIL() + { + return _methodIL ?? InitializeMethodIL(); + } + + protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + var othr = (AsyncResumptionStub)other; + return comparer.Compare(_owningMethod, othr._owningMethod); } } } From 6449d073fd30e5f5a382f76c6ccbe200dd027577 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:54:21 -0800 Subject: [PATCH 02/15] Remove comment describing IL --- .../IL/Stubs/AsyncResumptionStub.cs | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 76b04deb481e35..19e013287a877d 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -48,40 +48,6 @@ private MethodIL InitializeMethodIL() ILCodeStream ilStream = ilEmitter.NewCodeStream(); // Ported from jitinterface.cpp CEEJitInfo::getAsyncResumptionStub - // Emitted IL: - // if (!_owningMethod.Signature.IsStatic) - // { - // if (_owningMethod.OwningType.IsValueType) - // ldc.i4.0 - // conv.u - // else - // ldnull - // } - // foreach (param in _owningMethod.Signature) - // { - // ldloca.s - // initobj - // ldloc.s - // } - // ldftn <_owningMethod> - // calli - // if (!returnsVoid) - // stloc.s - // call System.StubHelpers.StubHelpers::AsyncCallContinuation() - // stloc.s - // if (!returnsVoid) - // { - // ldloca.s - // brtrue.s done_result - // ldarg.1 - // ldloc.s - // stobj - // done_result: - // } - // ldloc.s - // ret - - // if it has this pointer if (!_owningMethod.Signature.IsStatic) { if (_owningMethod.OwningType.IsValueType) From a9aead45ee65e1c6ec03f7958d6e36d5dcf3d196 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:00:28 -0800 Subject: [PATCH 03/15] Don't cache methodIL on the MethodDesc object. --- .../Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 19e013287a877d..ccfd83da2d1830 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -15,7 +15,6 @@ public partial class AsyncResumptionStub : ILStubMethod { private readonly MethodDesc _owningMethod; private MethodSignature _signature; - private MethodIL _methodIL; public AsyncResumptionStub(MethodDesc owningMethod) { @@ -41,9 +40,8 @@ private MethodSignature InitializeSignature() return _signature = new MethodSignature(0, 0, objectType, [objectType, byrefByte]); } - private MethodIL InitializeMethodIL() + public override MethodIL EmitIL() { - Debug.Assert(_methodIL == null); ILEmitter ilEmitter = new ILEmitter(); ILCodeStream ilStream = ilEmitter.NewCodeStream(); @@ -99,12 +97,7 @@ private MethodIL InitializeMethodIL() ilStream.EmitLdLoc(newContinuationLocal); ilStream.Emit(ILOpcode.ret); - return _methodIL = ilEmitter.Link(this); - } - - public override MethodIL EmitIL() - { - return _methodIL ?? InitializeMethodIL(); + return ilEmitter.Link(this); } protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) From c18bc5d839c50b9784bb5c9c092ad5c7233ac11e Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:14:16 -0800 Subject: [PATCH 04/15] Move StubHelpers.AsyncCallContinuation to AsyncHelpers, fix partial implementations of AsyncResumptionStub --- .../System.Private.CoreLib/src/System/StubHelpers.cs | 3 --- .../Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs | 10 +--------- .../ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj | 3 +++ .../System/Runtime/CompilerServices/AsyncHelpers.cs | 3 +++ 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index acb629e66e3662..6d61edd19ebc4a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1610,9 +1610,6 @@ internal static void MulticastDebuggerTraceHelper(object o, int count) [Intrinsic] internal static IntPtr NextCallReturnAddress() => throw new UnreachableException(); // Unconditionally expanded intrinsic - - [Intrinsic] - internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic } // class StubHelpers #if FEATURE_COMINTEROP diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index ccfd83da2d1830..fd1bd5d29a0253 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -31,8 +31,6 @@ public AsyncResumptionStub(MethodDesc owningMethod) public override TypeSystemContext Context => _owningMethod.Context; - protected override int ClassCode => unchecked((int)0xa91ac565); - private MethodSignature InitializeSignature() { TypeDesc objectType = Context.GetWellKnownType(WellKnownType.Object); @@ -77,7 +75,7 @@ public override MethodIL EmitIL() ilStream.EmitStLoc(resultLocal); } - MethodDesc asyncCallContinuation = Context.SystemModule.GetKnownType("System.StubHelpers"u8, "StubHelpers"u8) + MethodDesc asyncCallContinuation = Context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8) .GetKnownMethod("AsyncCallContinuation"u8, null); TypeDesc continuation = Context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "Continuation"u8); var newContinuationLocal = ilEmitter.NewLocal(continuation); @@ -99,11 +97,5 @@ public override MethodIL EmitIL() return ilEmitter.Link(this); } - - protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) - { - var othr = (AsyncResumptionStub)other; - return comparer.Compare(_owningMethod, othr._owningMethod); - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index d326121cb517ee..8a8e116c8f5c0f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -45,6 +45,9 @@ + + + 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 167aca683121e2..87cd2d1de21f3f 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 @@ -237,5 +237,8 @@ public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) [RequiresPreviewFeatures] public static T Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } #endif + [Intrinsic] + [BypassReadyToRun] + internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic } } From 85ff8c702fd47919a1fea61633371c935817bf35 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:00:04 -0800 Subject: [PATCH 05/15] Update usages of AsyncCallContinuation to be in AsyncHelpers --- .../Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 4 ++++ src/coreclr/jit/gentree.cpp | 2 +- src/coreclr/jit/importercalls.cpp | 10 +++++----- src/coreclr/jit/namedintrinsiclist.h | 2 +- src/coreclr/vm/corelib.h | 6 +++--- .../System/Runtime/CompilerServices/AsyncHelpers.cs | 3 --- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 19ea178f6bf1f9..f8aaacc4a49740 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -150,6 +150,10 @@ public static partial class AsyncHelpers [Intrinsic] private static void AsyncSuspend(Continuation continuation) => throw new UnreachableException(); + [Intrinsic] + [BypassReadyToRun] + internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic + // Used during suspensions to hold the continuation chain and on what we are waiting. // Methods like FinalizeTaskReturningThunk will unlink the state and wrap into a Task. private struct RuntimeAsyncAwaitState diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index fe620f6f332b87..b9d49d5655f500 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -2265,7 +2265,7 @@ bool GenTreeCall::HasSideEffects(Compiler* compiler, bool ignoreExceptions, bool // those cases the JIT does not know (and does not need to know) which arg is // the async continuation. // -// The VM also uses the StubHelpers.AsyncCallContinuation() intrinsic in the +// The VM also uses the AsyncHelpers.AsyncCallContinuation() intrinsic in the // stubs discussed above. The JIT must take care in those cases to still mark // the preceding call as an async call; this is required for correct LSRA // behavior and GC reporting around the returned async continuation. This is diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 42dbb08ee7043f..f7915a7b98ef61 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -3323,7 +3323,7 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, return new (this, GT_LABEL) GenTree(GT_LABEL, TYP_I_IMPL); } - if (ni == NI_System_StubHelpers_AsyncCallContinuation) + if (ni == NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncCallContinuation) { GenTree* node = new (this, GT_ASYNC_CONTINUATION) GenTree(GT_ASYNC_CONTINUATION, TYP_REF); node->SetHasOrderingSideEffect(); @@ -10872,6 +10872,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_Runtime_CompilerServices_AsyncHelpers_Await; } + else if (strcmp(methodName, "AsyncCallContinuation") == 0) + { + result = NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncCallContinuation; + } } else if (strcmp(className, "StaticsHelpers") == 0) { @@ -11129,10 +11133,6 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_StubHelpers_NextCallReturnAddress; } - else if (strcmp(methodName, "AsyncCallContinuation") == 0) - { - result = NI_System_StubHelpers_AsyncCallContinuation; - } } } else if (strcmp(namespaceName, "Text") == 0) diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index aa56003f25acc1..e0c346870de084 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -107,7 +107,6 @@ enum NamedIntrinsic : unsigned short NI_System_RuntimeType_get_TypeHandle, NI_System_StubHelpers_GetStubContext, NI_System_StubHelpers_NextCallReturnAddress, - NI_System_StubHelpers_AsyncCallContinuation, NI_Array_Address, NI_Array_Get, @@ -126,6 +125,7 @@ enum NamedIntrinsic : unsigned short NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncSuspend, NI_System_Runtime_CompilerServices_AsyncHelpers_Await, + NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncCallContinuation, NI_System_Runtime_CompilerServices_StaticsHelpers_VolatileReadAsByref, diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index e0cde61d373d8e..b2dae8c9ed9e6b 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -747,8 +747,9 @@ DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK, CompletedTask, NoSi DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_EXECUTION_CONTEXT, CaptureExecutionContext, NoSig) DEFINE_METHOD(ASYNC_HELPERS, RESTORE_EXECUTION_CONTEXT, RestoreExecutionContext, NoSig) DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_CONTINUATION_CONTEXT, CaptureContinuationContext, NoSig) -DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_CONTEXTS, CaptureContexts, NoSig) -DEFINE_METHOD(ASYNC_HELPERS, RESTORE_CONTEXTS, RestoreContexts, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_CONTEXTS, CaptureContexts, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, RESTORE_CONTEXTS, RestoreContexts, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, ASYNC_CALL_CONTINUATION, AsyncCallContinuation, SM_RetContinuation) #ifdef TARGET_BROWSER DEFINE_METHOD(ASYNC_HELPERS, HANDLE_ASYNC_ENTRYPOINT, HandleAsyncEntryPoint, SM_TaskOfInt_RetInt) @@ -1098,7 +1099,6 @@ DEFINE_METHOD(STUBHELPERS, VALIDATE_BYREF, Validate DEFINE_METHOD(STUBHELPERS, GET_STUB_CONTEXT, GetStubContext, SM_RetIntPtr) DEFINE_METHOD(STUBHELPERS, LOG_PINNED_ARGUMENT, LogPinnedArgument, SM_IntPtr_IntPtr_RetVoid) DEFINE_METHOD(STUBHELPERS, NEXT_CALL_RETURN_ADDRESS, NextCallReturnAddress, SM_RetIntPtr) -DEFINE_METHOD(STUBHELPERS, ASYNC_CALL_CONTINUATION, AsyncCallContinuation, SM_RetContinuation) DEFINE_METHOD(STUBHELPERS, SAFE_HANDLE_ADD_REF, SafeHandleAddRef, SM_SafeHandle_RefBool_RetIntPtr) DEFINE_METHOD(STUBHELPERS, SAFE_HANDLE_RELEASE, SafeHandleRelease, SM_SafeHandle_RetVoid) 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 87cd2d1de21f3f..167aca683121e2 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 @@ -237,8 +237,5 @@ public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) [RequiresPreviewFeatures] public static T Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } #endif - [Intrinsic] - [BypassReadyToRun] - internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic } } From 3da6325c13e2696e10910599efabd751ea5bd1e6 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:42:12 -0800 Subject: [PATCH 06/15] Update src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs Co-authored-by: Vladimir Sadov --- .../tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index fd1bd5d29a0253..724420eac5004d 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -85,7 +85,7 @@ public override MethodIL EmitIL() if (!returnsVoid) { var doneResult = ilEmitter.NewCodeLabel(); - ilStream.EmitLdLoca(newContinuationLocal); + ilStream.EmitLdLoc(newContinuationLocal); ilStream.Emit(ILOpcode.brtrue, doneResult); ilStream.EmitLdArg(1); ilStream.EmitLdLoc(resultLocal); From 8a05815385c417e2fffed44455765679bf0a5856 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 10 Nov 2025 15:46:56 +0100 Subject: [PATCH 07/15] Add AsyncHelpers.SetAsyncCallContinuationArg intrinsic --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 6 +++++ src/coreclr/jit/async.cpp | 5 ++++ src/coreclr/jit/compiler.h | 2 ++ src/coreclr/jit/importercalls.cpp | 26 +++++++++++++++++-- src/coreclr/jit/namedintrinsiclist.h | 1 + 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index f8aaacc4a49740..60f08f9149815f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -154,6 +154,12 @@ public static partial class AsyncHelpers [BypassReadyToRun] internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic + // Set call continuation argument for an upcoming call with runtime async calling convention. + // Can only be used shortly before the call (with no interfering control flow instructions before the call). + // If used anywhere in the method it must be present before _all_ calls with runtime async calling convention. + [Intrinsic] + internal static void SetAsyncCallContinuationArg(Continuation continuation) => throw new UnreachableException(); + // Used during suspensions to hold the continuation chain and on what we are waiting. // Methods like FinalizeTaskReturningThunk will unlink the state and wrap into a Task. private struct RuntimeAsyncAwaitState diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index f1eb605e1e951c..fc8f731ee8d1f5 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -484,6 +484,11 @@ bool AsyncLiveness::IsLocalCaptureUnnecessary(unsigned lclNum) return true; } + if (lclNum == m_comp->lvaNextAsyncCallContArgVar) + { + return true; + } + return false; } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 5f51bd68e138db..ed485796cb519e 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3976,6 +3976,8 @@ class Compiler bool lvaInlineeReturnSpillTempFreshlyCreated = false; // True if the temp was freshly created for the inlinee return + unsigned lvaNextAsyncCallContArgVar = BAD_VAR_NUM; + #if FEATURE_FIXED_OUT_ARGS unsigned lvaOutgoingArgSpaceVar = BAD_VAR_NUM; // var that represents outgoing argument space PhasedVar lvaOutgoingArgSpaceSize; // size of fixed outgoing argument space diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index f7915a7b98ef61..f706bba22efc77 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -875,7 +875,10 @@ var_types Compiler::impImportCall(OPCODE opcode, // stubs and the VM inserts the arg itself. if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI)) { - call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) + GenTree* arg = lvaNextAsyncCallContArgVar != BAD_VAR_NUM + ? (GenTree*)gtNewLclVarNode(lvaNextAsyncCallContArgVar) + : gtNewNull(); + call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(arg, TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); } @@ -891,7 +894,10 @@ var_types Compiler::impImportCall(OPCODE opcode, // stubs and the VM inserts the arg itself. if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI)) { - call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) + GenTree* arg = lvaNextAsyncCallContArgVar != BAD_VAR_NUM + ? (GenTree*)gtNewLclVarNode(lvaNextAsyncCallContArgVar) + : gtNewNull(); + call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(arg, TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); } @@ -3332,6 +3338,18 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, return node; } + if (ni == NI_System_Runtime_CompilerServices_AsyncHelpers_SetAsyncCallContinuationArg) + { + if (lvaNextAsyncCallContArgVar == BAD_VAR_NUM) + { + lvaNextAsyncCallContArgVar = lvaGrabTemp(false DEBUGARG("Async call continuation arg")); + lvaGetDesc(lvaNextAsyncCallContArgVar)->lvType = TYP_REF; + } + + GenTree* node = gtNewStoreLclVarNode(lvaNextAsyncCallContArgVar, impPopStack().val); + return node; + } + if (ni == NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncSuspend) { GenTree* node = gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, impPopStack().val); @@ -10876,6 +10894,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncCallContinuation; } + else if (strcmp(methodName, "SetAsyncCallContinuationArg") == 0) + { + result = NI_System_Runtime_CompilerServices_AsyncHelpers_SetAsyncCallContinuationArg; + } } else if (strcmp(className, "StaticsHelpers") == 0) { diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index e0c346870de084..dd884ac5e575f4 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -126,6 +126,7 @@ enum NamedIntrinsic : unsigned short NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncSuspend, NI_System_Runtime_CompilerServices_AsyncHelpers_Await, NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncCallContinuation, + NI_System_Runtime_CompilerServices_AsyncHelpers_SetAsyncCallContinuationArg, NI_System_Runtime_CompilerServices_StaticsHelpers_VolatileReadAsByref, From 9288b46fe3fb681a54d1b155b960a520ca1d7073 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:43:13 -0800 Subject: [PATCH 08/15] Revert "Add AsyncHelpers.SetAsyncCallContinuationArg intrinsic" This reverts commit 8a05815385c417e2fffed44455765679bf0a5856. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 3 ++- src/coreclr/jit/async.cpp | 5 ----- src/coreclr/jit/compiler.h | 2 -- src/coreclr/jit/importercalls.cpp | 22 ++----------------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index e4d4d92e2b1e45..4c9a699e04a0b1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -156,7 +156,8 @@ public static partial class AsyncHelpers // * a continuation object if the call requires suspension. // In this case the formal result of the call is undefined. [Intrinsic] - private static Continuation? AsyncCallContinuation() => throw new UnreachableException(); + [BypassReadyToRun] + internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic // Used during suspensions to hold the continuation chain and on what we are waiting. // Methods like FinalizeTaskReturningThunk will unlink the state and wrap into a Task. diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 55494ee63db204..f208bd4a50003a 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -499,11 +499,6 @@ bool AsyncLiveness::IsLocalCaptureUnnecessary(unsigned lclNum) return true; } - if (lclNum == m_comp->lvaNextAsyncCallContArgVar) - { - return true; - } - return false; } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 30cd9ddcf37205..e8c4cc28b30db2 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3980,8 +3980,6 @@ class Compiler bool lvaInlineeReturnSpillTempFreshlyCreated = false; // True if the temp was freshly created for the inlinee return - unsigned lvaNextAsyncCallContArgVar = BAD_VAR_NUM; - #if FEATURE_FIXED_OUT_ARGS unsigned lvaOutgoingArgSpaceVar = BAD_VAR_NUM; // var that represents outgoing argument space PhasedVar lvaOutgoingArgSpaceSize; // size of fixed outgoing argument space diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 45fc1bd66f1f2b..51e0424fe58d8d 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -881,10 +881,7 @@ var_types Compiler::impImportCall(OPCODE opcode, // stubs and the VM inserts the arg itself. if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI)) { - GenTree* arg = lvaNextAsyncCallContArgVar != BAD_VAR_NUM - ? (GenTree*)gtNewLclVarNode(lvaNextAsyncCallContArgVar) - : gtNewNull(); - call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(arg, TYP_REF) + call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); } @@ -900,10 +897,7 @@ var_types Compiler::impImportCall(OPCODE opcode, // stubs and the VM inserts the arg itself. if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI)) { - GenTree* arg = lvaNextAsyncCallContArgVar != BAD_VAR_NUM - ? (GenTree*)gtNewLclVarNode(lvaNextAsyncCallContArgVar) - : gtNewNull(); - call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(arg, TYP_REF) + call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); } @@ -3319,18 +3313,6 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, return node; } - if (ni == NI_System_Runtime_CompilerServices_AsyncHelpers_SetAsyncCallContinuationArg) - { - if (lvaNextAsyncCallContArgVar == BAD_VAR_NUM) - { - lvaNextAsyncCallContArgVar = lvaGrabTemp(false DEBUGARG("Async call continuation arg")); - lvaGetDesc(lvaNextAsyncCallContArgVar)->lvType = TYP_REF; - } - - GenTree* node = gtNewStoreLclVarNode(lvaNextAsyncCallContArgVar, impPopStack().val); - return node; - } - if (ni == NI_System_Runtime_CompilerServices_AsyncHelpers_AsyncSuspend) { GenTree* node = gtNewOperNode(GT_RETURN_SUSPEND, TYP_VOID, impPopStack().val); From fc4443ccd0d83f0b2521ef5800dfa4a88b7a31bf Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 13 Nov 2025 23:11:34 -0800 Subject: [PATCH 09/15] Create dummy resume method with explicit continuation param --- .../IL/Stubs/AsyncResumptionStub.Mangling.cs | 2 +- .../IL/Stubs/AsyncResumptionStub.Sorting.cs | 2 +- .../IL/Stubs/AsyncResumptionStub.cs | 135 ++++++++++++++++-- .../DependencyAnalysis/ILScanNodeFactory.cs | 4 + .../DependencyAnalysis/RyuJitNodeFactory.cs | 4 + 5 files changed, 131 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs index e38e47a176566e..898a483159ae21 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs @@ -8,7 +8,7 @@ namespace ILCompiler { public partial class AsyncResumptionStub : ILStubMethod, IPrefixMangledMethod { - MethodDesc IPrefixMangledMethod.BaseMethod => _targetMethod; + MethodDesc IPrefixMangledMethod.BaseMethod => _owningMethod; string IPrefixMangledMethod.Prefix => "Resume"; } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs index e62e414c99f2e5..2043c2f8922535 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs @@ -12,7 +12,7 @@ public partial class AsyncResumptionStub : ILStubMethod protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) { - return comparer.Compare(_targetMethod, ((AsyncResumptionStub)other)._targetMethod); + return comparer.Compare(_owningMethod, ((AsyncResumptionStub)other)._owningMethod); } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 7bf8f6dcf09bcd..70efa7df79647c 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -13,25 +13,23 @@ namespace ILCompiler { public partial class AsyncResumptionStub : ILStubMethod { - private readonly MethodDesc _targetMethod; - private readonly TypeDesc _owningType; + private readonly MethodDesc _owningMethod; private MethodSignature _signature; - public AsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType) + public AsyncResumptionStub(MethodDesc targetMethod) { Debug.Assert(targetMethod.IsAsyncCall()); - _targetMethod = targetMethod; - _owningType = owningType; + _owningMethod = targetMethod; } - public override ReadOnlySpan Name => _targetMethod.Name; - public override string DiagnosticName => _targetMethod.DiagnosticName; + public override ReadOnlySpan Name => _owningMethod.Name; + public override string DiagnosticName => _owningMethod.DiagnosticName; - public override TypeDesc OwningType => _owningType; + public override TypeDesc OwningType => _owningMethod.OwningType; public override MethodSignature Signature => _signature ??= InitializeSignature(); - public override TypeSystemContext Context => _targetMethod.Context; + public override TypeSystemContext Context => _owningMethod.Context; private MethodSignature InitializeSignature() { @@ -59,6 +57,11 @@ public override MethodIL EmitIL() } } + if (Context.Target.Architecture != TargetArchitecture.X86) + { + ilStream.EmitLdArg(0); + } + foreach (var param in _owningMethod.Signature) { var local = ilEmitter.NewLocal(param); @@ -66,14 +69,20 @@ public override MethodIL EmitIL() ilStream.Emit(ILOpcode.initobj, ilEmitter.NewToken(param)); ilStream.EmitLdLoc(local); } - ilStream.Emit(ILOpcode.ldftn, ilEmitter.NewToken(_owningMethod)); - ilStream.Emit(ILOpcode.calli, ilEmitter.NewToken(this.Signature)); - bool returnsVoid = _owningMethod.Signature.ReturnType != Context.GetWellKnownType(WellKnownType.Void); + if (Context.Target.Architecture == TargetArchitecture.X86) + { + ilStream.EmitLdArg(0); + } + + MethodDesc resumingMethod = new ExplicitContinuationAsyncMethod(_owningMethod); + ilStream.Emit(ILOpcode.call, ilEmitter.NewToken(resumingMethod)); + + bool returnsVoid = resumingMethod.Signature.ReturnType.IsWellKnownType(WellKnownType.Void); Internal.IL.Stubs.ILLocalVariable resultLocal = default; if (!returnsVoid) { - resultLocal = ilEmitter.NewLocal(_owningMethod.Signature.ReturnType); + resultLocal = ilEmitter.NewLocal(resumingMethod.Signature.ReturnType); ilStream.EmitStLoc(resultLocal); } @@ -91,7 +100,7 @@ public override MethodIL EmitIL() ilStream.Emit(ILOpcode.brtrue, doneResult); ilStream.EmitLdArg(1); ilStream.EmitLdLoc(resultLocal); - ilStream.Emit(ILOpcode.stobj, ilEmitter.NewToken(_owningMethod.Signature.ReturnType)); + ilStream.Emit(ILOpcode.stobj, ilEmitter.NewToken(resumingMethod.Signature.ReturnType)); ilStream.EmitLabel(doneResult); } ilStream.EmitLdLoc(newContinuationLocal); @@ -100,4 +109,102 @@ public override MethodIL EmitIL() return ilEmitter.Link(this); } } + + internal sealed partial class ExplicitContinuationAsyncMethod : MethodDesc + { + private MethodSignature _signature; + private MethodDesc _wrappedMethod; + + public ExplicitContinuationAsyncMethod(MethodDesc target) + { + _wrappedMethod = target; + } + + public MethodDesc Target => _wrappedMethod; + + private MethodSignature InitializeSignature() + { + var _methodRepresented = _wrappedMethod; + + // Async methods have an implicit Continuation parameter + // The order of parameters depends on the architecture + // non-x86: this?, genericCtx?, continuation, params... + // x86: this?, params, continuation, genericCtx? + // To make the jit pass arguments in this order, we can add the continuation parameter + // at the end for x86 and at the beginning for other architectures. + // The 'this' parameter and generic context parameter (if any) can be handled by the jit. + + var signature = new MethodSignatureBuilder(_wrappedMethod.Signature) + { + Length = _wrappedMethod.Signature.Length + 1, + }; + + TypeDesc continuation = Context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "Continuation"u8); + if (Context.Target.Architecture == TargetArchitecture.X86) + { + for (int i = 0; i < _methodRepresented.Signature.Length; i++) + signature[i] = _methodRepresented.Signature[i]; + signature[_methodRepresented.Signature.Length] = continuation; + } + else + { + signature[0] = continuation; + for (int i = 0; i < _methodRepresented.Signature.Length; i++) + signature[i + 1] = _methodRepresented.Signature[i]; + } + // Get the return type from the Task-returning variant + if (_wrappedMethod is AsyncMethodVariant variant + && variant.Target.Signature.ReturnType is {HasInstantiation: true } returnType) + { + signature.ReturnType = returnType.Instantiation[0]; + } + else + { + signature.ReturnType = Context.GetWellKnownType(WellKnownType.Void); + } + + return _signature = signature.ToSignature(); + } + + public override bool HasCustomAttribute(string attributeNamespace, string attributeName) => throw new NotImplementedException(); + + public override MethodSignature Signature + { + get + { + if (_signature is null) + return InitializeSignature(); + + return _signature; + } + } + + public override string DiagnosticName => $"ExplicitContinuationAsyncMethod({_wrappedMethod.DiagnosticName})"; + + public override TypeDesc OwningType => _wrappedMethod.OwningType; + + public override TypeSystemContext Context => _wrappedMethod.Context; + + protected override int ClassCode => 0xd076659; + + protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + var otherMethod = (ExplicitContinuationAsyncMethod)other; + return comparer.Compare(_wrappedMethod, otherMethod._wrappedMethod); + } + + public override bool IsInternalCall => true; + } + + public static class AsyncResumptionStubExtensions + { + public static bool IsExplicitContinuationAsyncMethod(this MethodDesc method) + { + return method is ExplicitContinuationAsyncMethod; + } + public static MethodDesc GetExplicitContinuationAsyncMethodTarget(this MethodDesc method) + { + return ((ExplicitContinuationAsyncMethod)method).Target; + } + } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs index d780d5e99891cf..00496eba03a6de 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs @@ -30,6 +30,10 @@ protected override IMethodNode CreateMethodEntrypointNode(MethodDesc method) { return MethodEntrypoint(TypeSystemContext.GetRealDefaultInterfaceMethodImplementationThunkTargetMethod(method)); } + else if (method.IsExplicitContinuationAsyncMethod()) + { + return MethodEntrypoint(method.GetExplicitContinuationAsyncMethodTarget()); + } else if (method.IsArrayAddressMethod()) { return new ScannedMethodNode(((ArrayType)method.OwningType).GetArrayMethod(ArrayMethodKind.AddressWithHiddenArg)); diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs index 1e75766b8f5ed3..dc9bef2b66c87c 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs @@ -28,6 +28,10 @@ protected override IMethodNode CreateMethodEntrypointNode(MethodDesc method) { return MethodEntrypoint(TypeSystemContext.GetRealDefaultInterfaceMethodImplementationThunkTargetMethod(method)); } + else if (method.IsExplicitContinuationAsyncMethod()) + { + return MethodEntrypoint(method.GetExplicitContinuationAsyncMethodTarget()); + } else if (method.IsArrayAddressMethod()) { return MethodEntrypoint(((ArrayType)method.OwningType).GetArrayMethod(ArrayMethodKind.AddressWithHiddenArg)); From 690b6b3a788ba29d86ab4b1669716960f7136d40 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 13 Nov 2025 23:16:56 -0800 Subject: [PATCH 10/15] Renaming and code organizing --- .../IL/Stubs/AsyncResumptionStub.Mangling.cs | 2 +- .../IL/Stubs/AsyncResumptionStub.Sorting.cs | 12 ++++++- .../IL/Stubs/AsyncResumptionStub.cs | 31 +++++++------------ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs index 898a483159ae21..e38e47a176566e 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Mangling.cs @@ -8,7 +8,7 @@ namespace ILCompiler { public partial class AsyncResumptionStub : ILStubMethod, IPrefixMangledMethod { - MethodDesc IPrefixMangledMethod.BaseMethod => _owningMethod; + MethodDesc IPrefixMangledMethod.BaseMethod => _targetMethod; string IPrefixMangledMethod.Prefix => "Resume"; } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs index 2043c2f8922535..3117ad5e742319 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.Sorting.cs @@ -12,7 +12,17 @@ public partial class AsyncResumptionStub : ILStubMethod protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) { - return comparer.Compare(_owningMethod, ((AsyncResumptionStub)other)._owningMethod); + return comparer.Compare(_targetMethod, ((AsyncResumptionStub)other)._targetMethod); + } + } + + internal sealed partial class ExplicitContinuationAsyncMethod : MethodDesc + { + protected override int ClassCode => 0xd076659; + + protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + return comparer.Compare(_wrappedMethod, ((ExplicitContinuationAsyncMethod)other)._wrappedMethod); } } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 70efa7df79647c..0b48882f82b0fa 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -13,23 +13,24 @@ namespace ILCompiler { public partial class AsyncResumptionStub : ILStubMethod { - private readonly MethodDesc _owningMethod; + private readonly MethodDesc _targetMethod; + private readonly TypeDesc _owningType; private MethodSignature _signature; - public AsyncResumptionStub(MethodDesc targetMethod) + public AsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType) { Debug.Assert(targetMethod.IsAsyncCall()); - _owningMethod = targetMethod; + _targetMethod = targetMethod; } - public override ReadOnlySpan Name => _owningMethod.Name; - public override string DiagnosticName => _owningMethod.DiagnosticName; + public override ReadOnlySpan Name => _targetMethod.Name; + public override string DiagnosticName => _targetMethod.DiagnosticName; - public override TypeDesc OwningType => _owningMethod.OwningType; + public override TypeDesc OwningType => _owningType; public override MethodSignature Signature => _signature ??= InitializeSignature(); - public override TypeSystemContext Context => _owningMethod.Context; + public override TypeSystemContext Context => _targetMethod.Context; private MethodSignature InitializeSignature() { @@ -44,9 +45,9 @@ public override MethodIL EmitIL() ILCodeStream ilStream = ilEmitter.NewCodeStream(); // Ported from jitinterface.cpp CEEJitInfo::getAsyncResumptionStub - if (!_owningMethod.Signature.IsStatic) + if (!_targetMethod.Signature.IsStatic) { - if (_owningMethod.OwningType.IsValueType) + if (_targetMethod.OwningType.IsValueType) { ilStream.EmitLdc(0); ilStream.Emit(ILOpcode.conv_u); @@ -62,7 +63,7 @@ public override MethodIL EmitIL() ilStream.EmitLdArg(0); } - foreach (var param in _owningMethod.Signature) + foreach (var param in _targetMethod.Signature) { var local = ilEmitter.NewLocal(param); ilStream.EmitLdLoca(local); @@ -75,7 +76,7 @@ public override MethodIL EmitIL() ilStream.EmitLdArg(0); } - MethodDesc resumingMethod = new ExplicitContinuationAsyncMethod(_owningMethod); + MethodDesc resumingMethod = new ExplicitContinuationAsyncMethod(_targetMethod); ilStream.Emit(ILOpcode.call, ilEmitter.NewToken(resumingMethod)); bool returnsVoid = resumingMethod.Signature.ReturnType.IsWellKnownType(WellKnownType.Void); @@ -185,14 +186,6 @@ public override MethodSignature Signature public override TypeSystemContext Context => _wrappedMethod.Context; - protected override int ClassCode => 0xd076659; - - protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) - { - var otherMethod = (ExplicitContinuationAsyncMethod)other; - return comparer.Compare(_wrappedMethod, otherMethod._wrappedMethod); - } - public override bool IsInternalCall => true; } From a83aa2f43eafe8efc0463d745c1c09bfcecf317a Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:58:27 -0800 Subject: [PATCH 11/15] Add Instantiation arg to resumption stub and resuming method --- .../IL/Stubs/AsyncResumptionStub.cs | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 0b48882f82b0fa..6105ddd53b6462 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -21,6 +21,7 @@ public AsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType) { Debug.Assert(targetMethod.IsAsyncCall()); _targetMethod = targetMethod; + _owningType = owningType; } public override ReadOnlySpan Name => _targetMethod.Name; @@ -60,6 +61,10 @@ public override MethodIL EmitIL() if (Context.Target.Architecture != TargetArchitecture.X86) { + if (_targetMethod.RequiresInstArg()) + { + ilStream.EmitLdc(0); + } ilStream.EmitLdArg(0); } @@ -74,12 +79,16 @@ public override MethodIL EmitIL() if (Context.Target.Architecture == TargetArchitecture.X86) { ilStream.EmitLdArg(0); + if (_targetMethod.RequiresInstArg()) + { + ilStream.EmitLdc(0); + } } MethodDesc resumingMethod = new ExplicitContinuationAsyncMethod(_targetMethod); ilStream.Emit(ILOpcode.call, ilEmitter.NewToken(resumingMethod)); - bool returnsVoid = resumingMethod.Signature.ReturnType.IsWellKnownType(WellKnownType.Void); + bool returnsVoid = resumingMethod.Signature.ReturnType.IsVoid; Internal.IL.Stubs.ILLocalVariable resultLocal = default; if (!returnsVoid) { @@ -111,6 +120,12 @@ public override MethodIL EmitIL() } } + /// + /// A dummy method used to tell the jit that we want to explicitly pass the hidden Continuation parameter + /// as well as the generic context parameter (if any) for async resumption methods. + /// This method should be marked IsAsync=false and HasInstantiation=false. These are defaults + /// for MethodDesc and so aren't explicitly set in the code below. + /// internal sealed partial class ExplicitContinuationAsyncMethod : MethodDesc { private MethodSignature _signature; @@ -123,6 +138,10 @@ public ExplicitContinuationAsyncMethod(MethodDesc target) public MethodDesc Target => _wrappedMethod; + /// + /// To explicitly pass the hidden parameters for async resumption methods, + /// we need to add explicit Continuation and generic context parameters to the signature. + /// private MethodSignature InitializeSignature() { var _methodRepresented = _wrappedMethod; @@ -137,21 +156,37 @@ private MethodSignature InitializeSignature() var signature = new MethodSignatureBuilder(_wrappedMethod.Signature) { - Length = _wrappedMethod.Signature.Length + 1, + Length = _wrappedMethod.Signature.Length + + 1 // Continuation + + (_wrappedMethod.RequiresInstArg() ? 1 : 0), // Generic context }; TypeDesc continuation = Context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "Continuation"u8); if (Context.Target.Architecture == TargetArchitecture.X86) { - for (int i = 0; i < _methodRepresented.Signature.Length; i++) + int i = 0; + for (; i < _methodRepresented.Signature.Length; i++) + { signature[i] = _methodRepresented.Signature[i]; - signature[_methodRepresented.Signature.Length] = continuation; + } + signature[i++] = continuation; + if (_wrappedMethod.RequiresInstArg()) + { + signature[i] = Context.GetWellKnownType(WellKnownType.Void).MakePointerType(); + } } else { - signature[0] = continuation; - for (int i = 0; i < _methodRepresented.Signature.Length; i++) - signature[i + 1] = _methodRepresented.Signature[i]; + int i = 0; + if (_wrappedMethod.RequiresInstArg()) + { + signature[i++] = Context.GetWellKnownType(WellKnownType.Void).MakePointerType(); + } + signature[i++] = continuation; + foreach (var param in _methodRepresented.Signature) + { + signature[i++] = param; + } } // Get the return type from the Task-returning variant if (_wrappedMethod is AsyncMethodVariant variant From d2683301c2e86b23a33a2d8b9ad4ae562b345574 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:14:18 -0800 Subject: [PATCH 12/15] Emit conv.i if not compiling for 32 bit target --- .../tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 6105ddd53b6462..b5cb958642f9dd 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -64,6 +64,8 @@ public override MethodIL EmitIL() if (_targetMethod.RequiresInstArg()) { ilStream.EmitLdc(0); + if (Context.Target.PointerSize != 4) + ilStream.Emit(ILOpcode.conv_i); } ilStream.EmitLdArg(0); } @@ -82,6 +84,8 @@ public override MethodIL EmitIL() if (_targetMethod.RequiresInstArg()) { ilStream.EmitLdc(0); + if (Context.Target.PointerSize != 4) + ilStream.Emit(ILOpcode.conv_i); } } From 48f0d984d7e6243ffd9577365ce63b5c5ac8da12 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:09:29 -0800 Subject: [PATCH 13/15] Always emit conv_i --- .../tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index b5cb958642f9dd..3df34f962b67bb 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -64,8 +64,7 @@ public override MethodIL EmitIL() if (_targetMethod.RequiresInstArg()) { ilStream.EmitLdc(0); - if (Context.Target.PointerSize != 4) - ilStream.Emit(ILOpcode.conv_i); + ilStream.Emit(ILOpcode.conv_i); } ilStream.EmitLdArg(0); } @@ -84,8 +83,7 @@ public override MethodIL EmitIL() if (_targetMethod.RequiresInstArg()) { ilStream.EmitLdc(0); - if (Context.Target.PointerSize != 4) - ilStream.Emit(ILOpcode.conv_i); + ilStream.Emit(ILOpcode.conv_i); } } From c3685dc8bfd5cbdb30467e1a4cbe8fb5ba415df6 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:52:51 -0800 Subject: [PATCH 14/15] Add using for ILLocalVariable --- .../tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index 3df34f962b67bb..75805556d7e8bd 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -8,6 +8,7 @@ using Internal.TypeSystem; using Debug = System.Diagnostics.Debug; +using ILLocalVariable = Internal.IL.Stubs.ILLocalVariable; namespace ILCompiler { @@ -91,7 +92,7 @@ public override MethodIL EmitIL() ilStream.Emit(ILOpcode.call, ilEmitter.NewToken(resumingMethod)); bool returnsVoid = resumingMethod.Signature.ReturnType.IsVoid; - Internal.IL.Stubs.ILLocalVariable resultLocal = default; + ILLocalVariable resultLocal = default; if (!returnsVoid) { resultLocal = ilEmitter.NewLocal(resumingMethod.Signature.ReturnType); From f579e66da72037fbd7ba89841bb1fa7df49646d3 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:45:27 -0800 Subject: [PATCH 15/15] Revert AsyncHelpers diff --- .../System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 4c9a699e04a0b1..e4d4d92e2b1e45 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -156,8 +156,7 @@ public static partial class AsyncHelpers // * a continuation object if the call requires suspension. // In this case the formal result of the call is undefined. [Intrinsic] - [BypassReadyToRun] - internal static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Unconditionally expanded intrinsic + private static Continuation? AsyncCallContinuation() => throw new UnreachableException(); // Used during suspensions to hold the continuation chain and on what we are waiting. // Methods like FinalizeTaskReturningThunk will unlink the state and wrap into a Task.