-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Long-lived AsyncMethodVariant in MethodWithGCInfo/MethodWithToken #121357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MichalStrehovsky
merged 18 commits into
dotnet:main
from
jtschuster:LongAsyncMethodVariant
Nov 5, 2025
Merged
Changes from 5 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
8ed26f2
Checkout AsyncThunkMethodDescs branch
jtschuster a7e3d25
Make AsyncMethodVariant long-lived
jtschuster d94de0c
Implement GetMethodDefinition, etc on AsyncMethodVariant
jtschuster ca5829b
Undo changes to InstantiatedMethod.cs
jtschuster 8a0e756
Remove unnecessary API surfaces, undo deopt
jtschuster 14d29fe
Remove AsyncMethodKind, use different AsyncMethodVariantFactory
jtschuster a1901e4
Other misc cleanup
jtschuster 2dc6b2b
Undo extra change in MethodForInstantiatedType.cs
jtschuster d013145
Undo extra changes
jtschuster bc82b74
Remove extra newline
jtschuster fc9532d
Undo extra changes
jtschuster 3cf7d3b
PR Feedback
jtschuster 4a6dda7
Revert extra changes
jtschuster 846d99e
Update src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs
jkotas 11eb284
Merge branch 'LongAsyncMethodVariant' of https://github.com/jtschuste…
jtschuster f3729ae
Forgot to save this file, revert sln changes.
jtschuster dd7380d
isAsyncCallConv -> isAsyncCall
jtschuster 975aacd
Update comments for CORINFO_CALLCONV_ASYNCCALL
jtschuster File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
src/coreclr/tools/Common/TypeSystem/Common/AsyncMethodVariant.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| // 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 System.Collections.Concurrent; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using Internal.TypeSystem.Ecma; | ||
|
|
||
| namespace Internal.TypeSystem | ||
jtschuster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| /// <summary> | ||
| /// Either the AsyncMethodImplVariant or AsyncMethodThunkVariant of a method marked .IsAsync. | ||
jtschuster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| public partial class AsyncMethodVariant : MethodDelegator | ||
| { | ||
| private readonly int _jitVisibleHashCode; | ||
| private MethodSignature _asyncSignature; | ||
|
|
||
| public AsyncMethodVariant(MethodDesc wrappedMethod) | ||
| : base(wrappedMethod) | ||
| { | ||
| Debug.Assert(wrappedMethod.Signature.ReturnsTaskOrValueTask()); | ||
| _jitVisibleHashCode = HashCode.Combine(wrappedMethod.GetHashCode(), 0x310bb74f); | ||
| } | ||
|
|
||
| public MethodDesc Target => _wrappedMethod; | ||
|
|
||
| public override AsyncMethodKind AsyncMethodKind => _wrappedMethod.IsAsync ? AsyncMethodKind.AsyncVariantImpl : AsyncMethodKind.AsyncVariantThunk; | ||
|
|
||
| public override MethodSignature Signature | ||
| { | ||
| get | ||
| { | ||
| return _asyncSignature ??= CreateAsyncSignature(_wrappedMethod.Signature); | ||
| } | ||
| } | ||
|
|
||
| private MethodSignature CreateAsyncSignature(MethodSignature signature) | ||
| { | ||
| Debug.Assert(!signature.IsAsyncCallConv); | ||
| Debug.Assert(signature.ReturnsTaskOrValueTask()); | ||
| TypeDesc md = signature.ReturnType; | ||
| MethodSignatureBuilder builder = new MethodSignatureBuilder(signature); | ||
| builder.ReturnType = md.HasInstantiation ? md.Instantiation[0] : this.Context.GetWellKnownType(WellKnownType.Void); | ||
| builder.Flags = signature.Flags | MethodSignatureFlags.AsyncCallingConvention; | ||
| return builder.ToSignature(); | ||
| } | ||
|
|
||
| public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind) | ||
| { | ||
| return this; | ||
| } | ||
|
|
||
| public override MethodDesc GetMethodDefinition() | ||
| { | ||
| var real = _wrappedMethod.GetMethodDefinition(); | ||
| if (real == _wrappedMethod) | ||
| return this; | ||
|
|
||
| return _wrappedMethod.Context.GetAsyncVariant(real); | ||
| } | ||
|
|
||
| public override MethodDesc GetTypicalMethodDefinition() | ||
| { | ||
| var real = _wrappedMethod.GetTypicalMethodDefinition(); | ||
| if (real == _wrappedMethod) | ||
| return this; | ||
|
|
||
| return _wrappedMethod.Context.GetAsyncVariant(real); | ||
| } | ||
|
|
||
| public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation) | ||
| { | ||
| var real = _wrappedMethod.InstantiateSignature(typeInstantiation, methodInstantiation); | ||
| if (real == _wrappedMethod) | ||
| return this; | ||
|
|
||
| return _wrappedMethod.Context.GetAsyncVariant(real); | ||
| } | ||
|
|
||
| public override string ToString() => $"Async variant ({AsyncMethodKind}): " + _wrappedMethod.ToString(); | ||
|
|
||
| protected internal override int ClassCode => unchecked((int)0xd0fd1c1fu); | ||
|
|
||
| protected internal override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) | ||
| { | ||
| var asyncOther = (AsyncMethodVariant)other; | ||
| return comparer.Compare(this._wrappedMethod, asyncOther._wrappedMethod); | ||
| } | ||
|
|
||
| protected override int ComputeHashCode() | ||
| { | ||
| return _jitVisibleHashCode; | ||
| } | ||
| } | ||
|
|
||
| public sealed class AsyncMethodVariantFactory : ConcurrentDictionary<MethodDesc, AsyncMethodVariant> | ||
| { | ||
| public AsyncMethodVariant GetOrCreateAsyncMethodImplVariant(MethodDesc wrappedMethod) | ||
| { | ||
| return GetOrAdd(wrappedMethod, static (x) => new AsyncMethodVariant(x)); | ||
| } | ||
| } | ||
|
|
||
| public static class AsyncMethodVariantExtensions | ||
| { | ||
| /// <summary> | ||
| /// Returns true if this MethodDesc is an AsyncMethodVariant, which should not escape the jit interface. | ||
| /// </summary> | ||
| public static bool IsAsyncVariant(this MethodDesc method) | ||
| { | ||
| return method is AsyncMethodVariant; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the wrapped method of the AsyncMethodVariant. This method is Task-returning. | ||
| /// </summary> | ||
| public static MethodDesc GetAsyncVariantDefinition(this MethodDesc method) | ||
| { | ||
| return ((AsyncMethodVariant)method).Target; | ||
| } | ||
| } | ||
| } | ||
19 changes: 19 additions & 0 deletions
19
src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.Async.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Diagnostics; | ||
| using System.Threading; | ||
|
|
||
| namespace Internal.TypeSystem | ||
| { | ||
| public sealed partial class InstantiatedMethod | ||
| { | ||
| public override AsyncMethodKind AsyncMethodKind | ||
| { | ||
| get | ||
| { | ||
| return _methodDef.AsyncMethodKind; | ||
| } | ||
| } | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/coreclr/tools/Common/TypeSystem/Common/MethodDelegator.Async.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
|
|
||
| namespace Internal.TypeSystem | ||
| { | ||
| /// <summary> | ||
| /// Wraps a <see cref="MethodDesc"/> object and delegates methods to that <see cref="MethodDesc"/>. | ||
| /// </summary> | ||
| public abstract partial class MethodDelegator : MethodDesc | ||
| { | ||
| public abstract override AsyncMethodKind AsyncMethodKind { get; } | ||
| } | ||
| } |
114 changes: 114 additions & 0 deletions
114
src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.Async.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Diagnostics; | ||
| using System; | ||
|
|
||
| namespace Internal.TypeSystem | ||
| { | ||
| public sealed partial class MethodSignature | ||
| { | ||
| public bool ReturnsTaskOrValueTask() | ||
jtschuster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| TypeDesc ret = this.ReturnType; | ||
|
|
||
| if (ret is MetadataType md | ||
| && md.Module == this.Context.SystemModule | ||
| && md.Namespace.SequenceEqual("System.Threading.Tasks"u8)) | ||
| { | ||
| ReadOnlySpan<byte> name = md.Name; | ||
| if (name.SequenceEqual("Task"u8) || name.SequenceEqual("Task`1"u8) | ||
| || name.SequenceEqual("ValueTask"u8) || name.SequenceEqual("ValueTask`1"u8)) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| public enum AsyncMethodKind | ||
| { | ||
| // Regular methods not returning tasks | ||
| // These are "normal" methods that do not get other variants. | ||
| // Note: Generic T-returning methods are NotAsync, even if T could be a Task. | ||
| NotAsync, | ||
|
|
||
| // Regular methods that return Task/ValueTask | ||
| // Such method has its actual IL body and there also a synthetic variant that is an | ||
| // Async-callable thunk. (AsyncVariantThunk) | ||
| TaskReturning, | ||
|
|
||
| // Task-returning methods marked as MethodImpl::Async in metadata. | ||
| // Such method has a body that is a thunk that forwards to an Async implementation variant | ||
| // which owns the original IL. (AsyncVariantImpl) | ||
| RuntimeAsync, | ||
|
|
||
| //============================================================= | ||
| // On {TaskReturning, AsyncVariantThunk} and {RuntimeAsync, AsyncVariantImpl} pairs: | ||
| // | ||
| // When we see a Task-returning method we create 2 method variants that logically match the same method definition. | ||
| // One variant has the same signature/callconv as the defining method and another is a matching Async variant. | ||
| // Depending on whether the definition was a runtime async method or an ordinary Task-returning method, | ||
| // the IL body belongs to one of the variants and another variant is a synthetic thunk. | ||
| // | ||
| // The signature of the Async variant is derived from the original signature by replacing Task return type with | ||
| // modreq'd element type: | ||
| // Example: "Task<int> Foo();" ===> "modreq(Task`) int Foo();" | ||
| // Example: "ValueTask Bar();" ===> "modreq(ValueTask) void Bar();" | ||
| // | ||
| // The reason for this encoding is that: | ||
| // - it uses parts of original signature, as-is, thus does not need to look for or construct anything | ||
| // - it "unwraps" the element type. | ||
| // - it is reversible. In particular nonconflicting signatures will map to nonconflicting ones. | ||
| // | ||
| // Async methods are called with CORINFO_CALLCONV_ASYNCCALL call convention. | ||
| // | ||
| // It is possible to get from one variant to another via GetAsyncOtherVariant. | ||
| // | ||
| // NOTE: not all Async methods are "variants" from a pair, see AsyncExplicitImpl below. | ||
| //============================================================= | ||
|
|
||
| // The following methods use special calling convention (CORINFO_CALLCONV_ASYNCCALL) | ||
| // These methods are emitted by the JIT as resumable state machines and also take an extra | ||
| // parameter and extra return - the continuation object. | ||
|
|
||
| // Async methods with actual IL implementation of a MethodImpl::Async method. | ||
| AsyncVariantImpl, | ||
|
|
||
| // Async methods with synthetic bodies that forward to a TaskReturning method. | ||
| AsyncVariantThunk, | ||
|
|
||
| // Methods that are explicitly declared as Async in metadata while not Task returning. | ||
| // This is a special case used in a few infrastructure methods like `Await`. | ||
| // Such methods do not get non-Async variants/thunks and can only be called from another Async method. | ||
| // NOTE: These methods have the original signature and it is not possible to tell if the method is Async | ||
| // from the signature alone, thus all these methods are also JIT intrinsics. | ||
| AsyncExplicitImpl, | ||
| } | ||
|
|
||
| public abstract partial class MethodDesc : TypeSystemEntity | ||
| { | ||
| public virtual AsyncMethodKind AsyncMethodKind | ||
| { | ||
| get | ||
| { | ||
| return AsyncMethodKind.NotAsync; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Is this synthetic Task/async adapter to an async/Task implementation? | ||
| /// If yes, the method has another variant, which has the actual user-defined method body. | ||
| /// </summary> | ||
| public bool IsAsyncThunk | ||
| { | ||
| get | ||
| { | ||
| return AsyncMethodKind is | ||
| AsyncMethodKind.AsyncVariantThunk or | ||
| AsyncMethodKind.RuntimeAsync; | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.Async.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // 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 System.Threading; | ||
| using Debug = System.Diagnostics.Debug; | ||
|
|
||
| namespace Internal.TypeSystem | ||
| { | ||
| public sealed partial class MethodForInstantiatedType | ||
| { | ||
| public override AsyncMethodKind AsyncMethodKind | ||
| { | ||
| get | ||
| { | ||
| return _typicalMethodDef.AsyncMethodKind; | ||
| } | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.