Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Comment thread
jtschuster marked this conversation as resolved.
Outdated
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,14 @@ protected override int CompareToImpl(MethodDesc other, TypeSystemComparer compar
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);
}
}
}
197 changes: 192 additions & 5 deletions src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;
using ILLocalVariable = Internal.IL.Stubs.ILLocalVariable;

namespace ILCompiler
{
Expand Down Expand Up @@ -42,13 +43,199 @@ private MethodSignature InitializeSignature()

public override MethodIL EmitIL()
{
var emitter = new ILEmitter();
ILCodeStream codeStream = emitter.NewCodeStream();
ILEmitter ilEmitter = new ILEmitter();
ILCodeStream ilStream = ilEmitter.NewCodeStream();

// TODO: match getAsyncResumptionStub from CoreCLR VM
codeStream.EmitCallThrowHelper(emitter, Context.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowNotSupportedException"u8));
// Ported from jitinterface.cpp CEEJitInfo::getAsyncResumptionStub
if (!_targetMethod.Signature.IsStatic)
{
if (_targetMethod.OwningType.IsValueType)
{
ilStream.EmitLdc(0);
ilStream.Emit(ILOpcode.conv_u);
}
else
{
ilStream.Emit(ILOpcode.ldnull);
}
}

return emitter.Link(this);
if (Context.Target.Architecture != TargetArchitecture.X86)
{
if (_targetMethod.RequiresInstArg())
{
ilStream.EmitLdc(0);
ilStream.Emit(ILOpcode.conv_i);
}
ilStream.EmitLdArg(0);
Comment thread
jtschuster marked this conversation as resolved.
}

foreach (var param in _targetMethod.Signature)
{
var local = ilEmitter.NewLocal(param);
ilStream.EmitLdLoca(local);
ilStream.Emit(ILOpcode.initobj, ilEmitter.NewToken(param));
ilStream.EmitLdLoc(local);
}

if (Context.Target.Architecture == TargetArchitecture.X86)
{
ilStream.EmitLdArg(0);
Comment thread
jtschuster marked this conversation as resolved.
if (_targetMethod.RequiresInstArg())
{
ilStream.EmitLdc(0);
ilStream.Emit(ILOpcode.conv_i);
}
}

MethodDesc resumingMethod = new ExplicitContinuationAsyncMethod(_targetMethod);
ilStream.Emit(ILOpcode.call, ilEmitter.NewToken(resumingMethod));

bool returnsVoid = resumingMethod.Signature.ReturnType.IsVoid;
ILLocalVariable resultLocal = default;
if (!returnsVoid)
{
resultLocal = ilEmitter.NewLocal(resumingMethod.Signature.ReturnType);
ilStream.EmitStLoc(resultLocal);
}

MethodDesc asyncCallContinuation = Context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8)
.GetKnownMethod("AsyncCallContinuation"u8, null);
Comment thread
jkotas marked this conversation as resolved.
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);

if (!returnsVoid)
{
var doneResult = ilEmitter.NewCodeLabel();
ilStream.EmitLdLoc(newContinuationLocal);
ilStream.Emit(ILOpcode.brtrue, doneResult);
ilStream.EmitLdArg(1);
ilStream.EmitLdLoc(resultLocal);
ilStream.Emit(ILOpcode.stobj, ilEmitter.NewToken(resumingMethod.Signature.ReturnType));
ilStream.EmitLabel(doneResult);
}
ilStream.EmitLdLoc(newContinuationLocal);
ilStream.Emit(ILOpcode.ret);

return ilEmitter.Link(this);
}
}

/// <summary>
/// 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.
/// </summary>
internal sealed partial class ExplicitContinuationAsyncMethod : MethodDesc

Copilot AI Nov 18, 2025

Copy link

Choose a reason for hiding this comment

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

The ExplicitContinuationAsyncMethod class lacks a mangling partial class file. Based on the codebase pattern (AsyncResumptionStub, AsyncMethodVariant, etc.), internal call methods that can be referenced should implement IPrefixMangledMethod. Consider adding an AsyncResumptionStub.Mangling.cs partial class for ExplicitContinuationAsyncMethod to provide proper name mangling support.

Copilot uses AI. Check for mistakes.
{
private MethodSignature _signature;
private MethodDesc _wrappedMethod;

public ExplicitContinuationAsyncMethod(MethodDesc target)
{
_wrappedMethod = target;
}

public MethodDesc Target => _wrappedMethod;

/// <summary>
/// To explicitly pass the hidden parameters for async resumption methods,
/// we need to add explicit Continuation and generic context parameters to the signature.
/// </summary>
private MethodSignature InitializeSignature()
{
var _methodRepresented = _wrappedMethod;
Comment thread
jtschuster marked this conversation as resolved.
Outdated

// 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)
Comment thread
jtschuster marked this conversation as resolved.
Outdated
{
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)
{
int i = 0;
for (; i < _methodRepresented.Signature.Length; i++)
{
signature[i] = _methodRepresented.Signature[i];
}
signature[i++] = continuation;
if (_wrappedMethod.RequiresInstArg())
{
signature[i] = Context.GetWellKnownType(WellKnownType.Void).MakePointerType();
}
}
else
{
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
&& variant.Target.Signature.ReturnType is {HasInstantiation: true } returnType)
{
signature.ReturnType = returnType.Instantiation[0];
}
else
{
signature.ReturnType = Context.GetWellKnownType(WellKnownType.Void);
}
Comment thread
jtschuster marked this conversation as resolved.

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;
Comment on lines +220 to +223

Copilot AI Nov 18, 2025

Copy link

Choose a reason for hiding this comment

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

The Signature property should follow the null-coalescing pattern like other properties in the codebase. Use return _signature ??= InitializeSignature(); instead of explicit null check and separate assignment.

Suggested change
if (_signature is null)
return InitializeSignature();
return _signature;
return _signature ??= InitializeSignature();

Copilot uses AI. Check for mistakes.
}
}
Comment thread
jtschuster marked this conversation as resolved.

public override string DiagnosticName => $"ExplicitContinuationAsyncMethod({_wrappedMethod.DiagnosticName})";

public override TypeDesc OwningType => _wrappedMethod.OwningType;

public override TypeSystemContext Context => _wrappedMethod.Context;

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
<Compile Include="..\..\Common\System\FormattingHelpers.cs" Link="Common\FormattingHelpers.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\ILProvider.cs" Link="IL\ILProvider.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\ILReader.cs" Link="IL\ILReader.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\Stubs\AsyncResumptionStub.cs" Link="IL\Stubs\AsyncResumptionStub.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\Stubs\AsyncResumptionStub.Sorting.cs" Link="IL\Stubs\AsyncResumptionStub.Sorting.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\Stubs\AsyncResumptionStub.Mangling.cs" Link="IL\Stubs\AsyncResumptionStub.Mangling.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\Stubs\ComparerIntrinsics.cs" Link="IL\Stubs\ComparerIntrinsics.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\Stubs\InterlockedIntrinsics.cs" Link="IL\Stubs\InterlockedIntrinsics.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\Stubs\RuntimeHelpersIntrinsics.cs" Link="IL\Stubs\RuntimeHelpersIntrinsics.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Loading