diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index 623422b7aef992..8cb62bd656e099 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -41,7 +41,7 @@ public enum ReadyToRunImportSectionFlags : ushort /// Constants for method and field encoding /// [Flags] - public enum ReadyToRunMethodSigFlags : byte + public enum ReadyToRunMethodSigFlags : ushort { READYTORUN_METHOD_SIG_None = 0x00, READYTORUN_METHOD_SIG_UnboxingStub = 0x01, @@ -52,6 +52,7 @@ public enum ReadyToRunMethodSigFlags : byte READYTORUN_METHOD_SIG_Constrained = 0x20, READYTORUN_METHOD_SIG_OwnerType = 0x40, READYTORUN_METHOD_SIG_UpdateContext = 0x80, + READYTORUN_METHOD_SIG_AsyncThunkVariant = 0x100, } [Flags] diff --git a/src/coreclr/tools/Common/JitInterface/AsyncMethodDesc.cs b/src/coreclr/tools/Common/JitInterface/AsyncMethodDesc.cs deleted file mode 100644 index b6584b5588eafb..00000000000000 --- a/src/coreclr/tools/Common/JitInterface/AsyncMethodDesc.cs +++ /dev/null @@ -1,109 +0,0 @@ -// 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.Diagnostics; -using Internal.TypeSystem; - -namespace Internal.JitInterface -{ - /// - /// Represents the async-callable (CORINFO_CALLCONV_ASYNCCALL) variant of a Task/ValueTask returning method. - /// The wrapper should be short‑lived and only used while interacting with the JIT interface. - /// - internal sealed class AsyncMethodDesc : MethodDelegator, IJitHashableOnly - { - private readonly AsyncMethodDescFactory _factory; - private readonly int _jitVisibleHashCode; - - public MethodDesc Target => _wrappedMethod; - - public AsyncMethodDesc(MethodDesc wrappedMethod, AsyncMethodDescFactory factory) - : base(wrappedMethod) - { - Debug.Assert(wrappedMethod.IsTaskReturning()); - _factory = factory; - // Salt with arbitrary constant so hash space differs from underlying method. - _jitVisibleHashCode = HashCode.Combine(wrappedMethod.GetHashCode(), 0x51C0A54); - } - - public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind) - { - MethodDesc realCanonTarget = _wrappedMethod.GetCanonMethodTarget(kind); - if (realCanonTarget != _wrappedMethod) - return _factory.GetAsyncMethod(realCanonTarget); - return this; - } - - public override MethodDesc GetMethodDefinition() - { - MethodDesc real = _wrappedMethod.GetMethodDefinition(); - if (real != _wrappedMethod) - return _factory.GetAsyncMethod(real); - return this; - } - - public override MethodDesc GetTypicalMethodDefinition() - { - MethodDesc real = _wrappedMethod.GetTypicalMethodDefinition(); - if (real != _wrappedMethod) - return _factory.GetAsyncMethod(real); - return this; - } - - public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation) - { - MethodDesc real = _wrappedMethod.InstantiateSignature(typeInstantiation, methodInstantiation); - if (real != _wrappedMethod) - return _factory.GetAsyncMethod(real); - return this; - } - - public override MethodSignature Signature - { - get - { - MethodSignature wrappedSignature = _wrappedMethod.Signature; - MetadataType md = (MetadataType)wrappedSignature.ReturnType; - MethodSignatureBuilder builder = new MethodSignatureBuilder(wrappedSignature); - builder.ReturnType = md.HasInstantiation ? md.Instantiation[0] : this.Context.GetWellKnownType(WellKnownType.Void); - builder.Flags = wrappedSignature.Flags | MethodSignatureFlags.AsyncCallConv; - return builder.ToSignature(); - } - } - -#if !SUPPORT_JIT - // Same pattern as UnboxingMethodDesc: these should not escape JIT hashing scope. - protected override int ClassCode => throw new NotImplementedException(); - protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) => throw new NotImplementedException(); - protected override int ComputeHashCode() => _jitVisibleHashCode; - int IJitHashableOnly.GetJitVisibleHashCode() => _jitVisibleHashCode; -#else - int IJitHashableOnly.GetJitVisibleHashCode() => _jitVisibleHashCode; -#endif - } - - internal static class AsyncMethodDescExtensions - { - /// - /// Returns true if the method returns Task, Task<T>, ValueTask, or ValueTask<T>, otherwise false. - /// - public static bool IsTaskReturning(this MethodDesc method) - { - TypeDesc ret = method.GetTypicalMethodDefinition().Signature.ReturnType; - - if (ret is MetadataType md - && md.Module == method.Context.SystemModule - && md.Namespace.SequenceEqual("System.Threading.Tasks"u8)) - { - ReadOnlySpan 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; - } - } -} diff --git a/src/coreclr/tools/Common/JitInterface/AsyncMethodDescFactory.cs b/src/coreclr/tools/Common/JitInterface/AsyncMethodDescFactory.cs deleted file mode 100644 index 5494807192e078..00000000000000 --- a/src/coreclr/tools/Common/JitInterface/AsyncMethodDescFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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.Generic; -using System.Diagnostics; -using Internal.TypeSystem; - -namespace Internal.JitInterface -{ - internal class AsyncMethodDescFactory : Dictionary - { - public AsyncMethodDesc GetAsyncMethod(MethodDesc method) - { - if (!TryGetValue(method, out AsyncMethodDesc result)) - { - result = new AsyncMethodDesc(method, this); - Add(method, result); - } - - return result; - } - } -} diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 4e574ed7641083..86677b0f548c3d 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -614,7 +614,11 @@ private MethodDesc MethodBeingCompiled { get { +#if READYTORUN + return _methodCodeNode.GetJitMethod(_asyncMethodFactory); +#else return _methodCodeNode.Method; +#endif } } @@ -877,6 +881,7 @@ private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* s if (!signature.IsStatic) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_HASTHIS; if (signature.IsExplicitThis) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_EXPLICITTHIS; + if (signature.IsAsyncCallConv) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL; TypeDesc returnType = signature.ReturnType; @@ -1395,7 +1400,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) if (info->pResolvedTokenVirtualMethod != null) { - methodWithTokenDecl = ComputeMethodWithToken(decl, ref *info->pResolvedTokenVirtualMethod, null, false); + methodWithTokenDecl = ComputeMethodWithToken(decl, ref *info->pResolvedTokenVirtualMethod, null, false, asyncVariant: false); } else { @@ -1410,7 +1415,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE; return false; } - methodWithTokenDecl = new MethodWithToken(decl, declToken, null, false, null, devirtualizedMethodOwner: decl.OwningType); + methodWithTokenDecl = new MethodWithToken(decl, declToken, null, false, asyncVariant: false, null, devirtualizedMethodOwner: decl.OwningType); } MethodWithToken methodWithTokenImpl; #endif @@ -1436,7 +1441,7 @@ private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) else { #if READYTORUN - methodWithTokenImpl = new MethodWithToken(nonUnboxingImpl, resolver.GetModuleTokenForMethod(nonUnboxingImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: true), null, unboxingStub, null, devirtualizedMethodOwner: impl.OwningType); + methodWithTokenImpl = new MethodWithToken(nonUnboxingImpl, resolver.GetModuleTokenForMethod(nonUnboxingImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: true), null, unboxingStub, asyncVariant: false, null, devirtualizedMethodOwner: impl.OwningType); #endif info->resolvedTokenDevirtualizedMethod = CreateResolvedTokenFromMethod(this, impl diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index ff37fc2f9bc60b..5a20a9dffcd3ab 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -119,6 +119,7 @@ public unsafe struct CORINFO_SIG_INFO private uint totalILArgs() { return (uint)(numArgs + (hasImplicitThis() ? 1 : 0)); } private bool isVarArg() { return ((getCallConv() == CorInfoCallConv.CORINFO_CALLCONV_VARARG) || (getCallConv() == CorInfoCallConv.CORINFO_CALLCONV_NATIVEVARARG)); } internal bool hasTypeArg() { return ((callConv & CorInfoCallConv.CORINFO_CALLCONV_PARAMTYPE) != 0); } + private bool isAsyncCallConv() { return ((callConv & CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL) != 0); } }; //---------------------------------------------------------------------------- @@ -377,6 +378,7 @@ public enum CorInfoCallConv CORINFO_CALLCONV_HASTHIS = 0x20, CORINFO_CALLCONV_EXPLICITTHIS = 0x40, CORINFO_CALLCONV_PARAMTYPE = 0x80, // Passed last. Same as CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG + CORINFO_CALLCONV_ASYNCCALL = 0x100, // Is this a call to an async function? } // Represents the calling conventions supported with the extensible calling convention syntax diff --git a/src/coreclr/tools/Common/JitInterface/UnboxingMethodDesc.cs b/src/coreclr/tools/Common/JitInterface/UnboxingMethodDesc.cs index eb1ba8a9d3bfc9..0d106949ae56bb 100644 --- a/src/coreclr/tools/Common/JitInterface/UnboxingMethodDesc.cs +++ b/src/coreclr/tools/Common/JitInterface/UnboxingMethodDesc.cs @@ -64,6 +64,14 @@ public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, return this; } + public override AsyncMethodKind AsyncMethodKind + { + get + { + return _wrappedMethod.AsyncMethodKind; + } + } + public override string ToString() { return "Unboxing MethodDesc: " + _wrappedMethod.ToString(); diff --git a/src/coreclr/tools/Common/TypeSystem/Common/AsyncMethodVariant.cs b/src/coreclr/tools/Common/TypeSystem/Common/AsyncMethodVariant.cs new file mode 100644 index 00000000000000..c15c138dffe87f --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/AsyncMethodVariant.cs @@ -0,0 +1,138 @@ +// 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.Generic; +using System.Diagnostics; +using Internal.JitInterface; + + +namespace Internal.TypeSystem +{ + /// + /// Either the AsyncMethodImplVariant or AsyncMethodThunkVariant of a method marked .IsAsync. + /// + public partial class AsyncMethodVariant : MethodDelegator, IJitHashableOnly + { + private readonly AsyncMethodVariantFactory _factory; + private readonly AsyncMethodKind _asyncMethodKind; + private readonly int _jitVisibleHashCode; + private MethodSignature _asyncSignature; + + public AsyncMethodVariant(MethodDesc wrappedMethod, AsyncMethodVariantFactory factory, AsyncMethodKind kind) + : base(wrappedMethod) + { + Debug.Assert(wrappedMethod.IsTaskReturning); + Debug.Assert(kind switch + { + AsyncMethodKind.AsyncVariantThunk => !wrappedMethod.IsAsync, + AsyncMethodKind.AsyncVariantImpl => wrappedMethod.IsAsync, + _ => false, + }); + _factory = factory; + _asyncMethodKind = kind; + _jitVisibleHashCode = HashCode.Combine(wrappedMethod.GetHashCode(), 0x310bb74f); + } + + public MethodDesc Target => _wrappedMethod; + + public override AsyncMethodKind AsyncMethodKind => _asyncMethodKind; + + public override MethodSignature Signature + { + get + { + return _asyncSignature ??= _wrappedMethod.Signature.CreateAsyncSignature(); + } + } + + public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind) + { + return _factory.GetOrCreateAsyncMethodImplVariant(_wrappedMethod.GetCanonMethodTarget(kind), _asyncMethodKind); + } + + public override MethodDesc GetMethodDefinition() + { + var real = _wrappedMethod.GetMethodDefinition(); + if (real == _wrappedMethod) + return this; + + return _factory.GetOrCreateAsyncMethodImplVariant(real, _asyncMethodKind); + } + + public override MethodDesc GetTypicalMethodDefinition() + { + var real = _wrappedMethod.GetTypicalMethodDefinition(); + if (real == _wrappedMethod) + return this; + return _factory.GetOrCreateAsyncMethodImplVariant(real, _asyncMethodKind); + } + + public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation) + { + var real = _wrappedMethod.InstantiateSignature(typeInstantiation, methodInstantiation); + if (real == _wrappedMethod) + return this; + return _factory.GetOrCreateAsyncMethodImplVariant(real, _asyncMethodKind); + } + + public override string ToString() => $"Async variant ({_asyncMethodKind}): " + _wrappedMethod.ToString(); + + protected override int ClassCode => throw new NotImplementedException(); + + protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + throw new NotImplementedException(); + } + + protected override int ComputeHashCode() + { + throw new NotSupportedException("This method may not be stored as it is expected to only be used transiently in the JIT"); + } + + int IJitHashableOnly.GetJitVisibleHashCode() => _jitVisibleHashCode; + } + + public sealed class AsyncMethodVariantFactory : Dictionary<(MethodDesc, AsyncMethodKind), AsyncMethodVariant> + { + public AsyncMethodVariant GetOrCreateAsyncMethodImplVariant(MethodDesc wrappedMethod, AsyncMethodKind kind) + { + Debug.Assert(wrappedMethod.IsAsync); + if (!TryGetValue((wrappedMethod, kind), out AsyncMethodVariant variant)) + { + variant = new AsyncMethodVariant(wrappedMethod, this, kind); + this[(wrappedMethod, kind)] = variant; + } + return variant; + } + + public AsyncMethodVariant GetOrCreateAsyncThunk(MethodDesc wrappedMethod) + { + return GetOrCreateAsyncMethodImplVariant(wrappedMethod, AsyncMethodKind.AsyncVariantThunk); + } + + public AsyncMethodVariant GetOrCreateAsyncImpl(MethodDesc wrappedMethod) + { + return GetOrCreateAsyncMethodImplVariant(wrappedMethod, AsyncMethodKind.AsyncVariantImpl); + } + } + + public static class AsyncMethodVariantExtensions + { + /// + /// Returns true if this MethodDesc is an AsyncMethodVariant, which should not escape the jit interface. + /// + public static bool IsAsyncVariant(this MethodDesc method) + { + return method is AsyncMethodVariant; + } + + /// + /// Gets the wrapped method of the AsyncMethodVariant. This method is Task-returning. + /// + public static MethodDesc GetAsyncVariantDefinition(this MethodDesc method) + { + return ((AsyncMethodVariant)method).Target; + } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.Async.cs b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.Async.cs new file mode 100644 index 00000000000000..2a1f28b36b138b --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.Async.cs @@ -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; + } + } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.cs b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.cs index 6024dcc6cc335d..fb5694f92fbd62 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedMethod.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Threading; using Internal.NativeFormat; namespace Internal.TypeSystem @@ -58,21 +59,27 @@ private TypeDesc Instantiate(TypeDesc type) return type.InstantiateSignature(default(Instantiation), _instantiation); } + private void InitializeSignature() + { + var template = _methodDef.Signature; + _signature = InstantiateSignature(template); + } + + private MethodSignature InstantiateSignature(MethodSignature template) + { + var builder = new MethodSignatureBuilder(template); + builder.ReturnType = Instantiate(template.ReturnType); + for (int i = 0; i < template.Length; i++) + builder[i] = Instantiate(template[i]); + return builder.ToSignature(); + } + public override MethodSignature Signature { get { if (_signature == null) - { - MethodSignature template = _methodDef.Signature; - MethodSignatureBuilder builder = new MethodSignatureBuilder(template); - - builder.ReturnType = Instantiate(template.ReturnType); - for (int i = 0; i < template.Length; i++) - builder[i] = Instantiate(template[i]); - - _signature = builder.ToSignature(); - } + InitializeSignature(); return _signature; } @@ -134,7 +141,6 @@ public override bool IsAsync } } - public override bool HasCustomAttribute(string attributeNamespace, string attributeName) { return _methodDef.HasCustomAttribute(attributeNamespace, attributeName); diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodDelegator.Async.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodDelegator.Async.cs new file mode 100644 index 00000000000000..58f780c8a3aa6e --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodDelegator.Async.cs @@ -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 +{ + /// + /// Wraps a object and delegates methods to that . + /// + public abstract partial class MethodDelegator : MethodDesc + { + public abstract override AsyncMethodKind AsyncMethodKind { get; } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.Async.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.Async.cs new file mode 100644 index 00000000000000..3d3356147cb6af --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.Async.cs @@ -0,0 +1,183 @@ +// 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 + { + // Flags that extend MethodSignatureFlags. + // These are not in metadata, just cached values. + // They must not conflict with MethodSignatureFlags. + private static class MethodSignatureFlagsExtensions + { + public const MethodSignatureFlags ReturnsTaskOrValueTask = (MethodSignatureFlags)(1 << 30); // Not a real flag, just a cached value + public const MethodSignatureFlags ReturnsTaskOrValueTaskMask = (MethodSignatureFlags)(1 << 31); // Not a real flag, just a cached value + } + + public bool ReturnsTaskOrValueTask + { + get + { + if ((_flags & MethodSignatureFlagsExtensions.ReturnsTaskOrValueTaskMask) == 0) + { + // Compute and cache the value + if (ReturnsTaskOrValueTaskCore()) + { + _flags |= MethodSignatureFlagsExtensions.ReturnsTaskOrValueTask; + } + _flags |= MethodSignatureFlagsExtensions.ReturnsTaskOrValueTaskMask; + } + return (_flags & MethodSignatureFlagsExtensions.ReturnsTaskOrValueTask) != 0; + } + } + + public MethodSignature CreateAsyncSignature() + { + Debug.Assert(!IsAsyncCallConv); + Debug.Assert(ReturnsTaskOrValueTask); + TypeDesc md = this.ReturnType; + MethodSignatureBuilder builder = new MethodSignatureBuilder(this); + builder.ReturnType = md.HasInstantiation ? md.Instantiation[0] : this.Context.GetWellKnownType(WellKnownType.Void); + builder.Flags = this.Flags | MethodSignatureFlags.AsyncCallingConvention; + return builder.ToSignature(); + } + + private bool ReturnsTaskOrValueTaskCore() + { + TypeDesc ret = this.ReturnType; + + if (ret is MetadataType md + && md.Module == this.Context.SystemModule + && md.Namespace.SequenceEqual("System.Threading.Tasks"u8)) + { + ReadOnlySpan 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 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; + } + } + + public bool IsTaskReturning + { + get + { + return Signature.ReturnsTaskOrValueTask; + } + } + + public bool IsAsyncVariant + { + get + { + return AsyncMethodKind is + AsyncMethodKind.AsyncVariantImpl or + AsyncMethodKind.AsyncVariantThunk; + } + } + + /// + /// 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. + /// + public bool IsAsyncThunk + { + get + { + return AsyncMethodKind is + AsyncMethodKind.AsyncVariantThunk or + AsyncMethodKind.RuntimeAsync; + } + } + + /// + /// Is this method callable as an async method? (i.e. uses Async calling convention) + /// + public bool IsAsyncCallConv + { + get + { + return AsyncMethodKind is + AsyncMethodKind.AsyncVariantImpl or + AsyncMethodKind.AsyncVariantThunk or + AsyncMethodKind.AsyncExplicitImpl; + } + } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs index 3f53e780d435e5..656f7b0c905bd9 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs @@ -20,10 +20,10 @@ public enum MethodSignatureFlags UnmanagedCallingConventionThisCall = 0x0003, CallingConventionVarargs = 0x0005, UnmanagedCallingConvention = 0x0009, + AsyncCallingConvention = 0x0100, Static = 0x0010, ExplicitThis = 0x0020, - AsyncCallConv = 0x0040, } public enum EmbeddedSignatureDataKind @@ -143,7 +143,7 @@ public bool IsAsyncCallConv { get { - return (_flags & MethodSignatureFlags.AsyncCallConv) != 0; + return (_flags & MethodSignatureFlags.AsyncCallingConvention) != 0; } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.Async.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.Async.cs new file mode 100644 index 00000000000000..cfe736142b9075 --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.Async.cs @@ -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; + } + } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.cs b/src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.cs index b5c7195b483e14..2c93bbcdfd0bf0 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MethodForInstantiatedType.cs @@ -2,7 +2,7 @@ // 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 @@ -51,21 +51,29 @@ private TypeDesc Instantiate(TypeDesc type) return type.InstantiateSignature(_instantiatedType.Instantiation, default(Instantiation)); } + private MethodSignature InstantiateSignature(MethodSignature template) + { + MethodSignatureBuilder builder = new MethodSignatureBuilder(template); + builder.ReturnType = Instantiate(template.ReturnType); + for (int i = 0; i < template.Length; i++) + builder[i] = Instantiate(template[i]); + + return builder.ToSignature(); + } + + private void InitializeSignature() + { + MethodSignature template = _typicalMethodDef.Signature; + _signature = InstantiateSignature(template); + + } + public override MethodSignature Signature { get { if (_signature == null) - { - MethodSignature template = _typicalMethodDef.Signature; - MethodSignatureBuilder builder = new MethodSignatureBuilder(template); - - builder.ReturnType = Instantiate(template.ReturnType); - for (int i = 0; i < template.Length; i++) - builder[i] = Instantiate(template[i]); - - _signature = builder.ToSignature(); - } + InitializeSignature(); return _signature; } diff --git a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaMethod.Async.cs b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaMethod.Async.cs new file mode 100644 index 00000000000000..a4ad0234e388d4 --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaMethod.Async.cs @@ -0,0 +1,34 @@ +// 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.Ecma +{ + public sealed partial class EcmaMethod + { + public override AsyncMethodKind AsyncMethodKind + { + get + { + // EcmaMethod always represents the Task-returning variant for Task-returning methods + // in order to match the signature, even though the IL in metadata may not match. + // Other MethodDescs represent the async variant. + bool returnsTask = Signature.ReturnsTaskOrValueTask; + if (returnsTask) + { + return IsAsync + ? AsyncMethodKind.RuntimeAsync + : AsyncMethodKind.TaskReturning; + } + else + { + return IsAsync + ? AsyncMethodKind.AsyncExplicitImpl + : AsyncMethodKind.NotAsync; + } + } + } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaMethod.cs b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaMethod.cs index 09fb19a982fd79..facce63d99e8d4 100644 --- a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaMethod.cs +++ b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaMethod.cs @@ -15,6 +15,7 @@ public sealed partial class EcmaMethod : MethodDesc, EcmaModule.IEntityHandleObj { private static class MethodFlags { +#pragma warning disable IDE0055 // Disable formatting to keep aligned public const int BasicMetadataCache = 0x00001; public const int Virtual = 0x00002; public const int NewSlot = 0x00004; @@ -28,11 +29,12 @@ private static class MethodFlags public const int AggressiveOptimization = 0x00400; public const int NoOptimization = 0x00800; public const int RequireSecObject = 0x01000; + public const int Async = 0x02000; - public const int AttributeMetadataCache = 0x02000; - public const int Intrinsic = 0x04000; - public const int UnmanagedCallersOnly = 0x08000; - public const int Async = 0x10000; + public const int AttributeMetadataCache = 0x04000; + public const int Intrinsic = 0x08000; + public const int UnmanagedCallersOnly = 0x10000; +#pragma warning restore IDE0055 }; private EcmaType _type; @@ -75,14 +77,13 @@ public override EcmaType OwningType } } - private MethodSignature InitializeSignature() + private void InitializeSignature() { var metadataReader = MetadataReader; BlobReader signatureReader = metadataReader.GetBlobReader(metadataReader.GetMethodDefinition(_handle).Signature); - EcmaSignatureParser parser = new EcmaSignatureParser(Module, signatureReader, NotFoundBehavior.Throw); var signature = parser.ParseMethodSignature(); - return (_signature = signature); + _signature = signature; } public override MethodSignature Signature @@ -90,7 +91,8 @@ public override MethodSignature Signature get { if (_signature == null) - return InitializeSignature(); + InitializeSignature(); + return _signature; } } diff --git a/src/coreclr/tools/Common/TypeSystem/RuntimeDetermined/MethodForRuntimeDeterminedType.cs b/src/coreclr/tools/Common/TypeSystem/RuntimeDetermined/MethodForRuntimeDeterminedType.cs index 5f97f7645ea76e..e9ff95f1d0e9e8 100644 --- a/src/coreclr/tools/Common/TypeSystem/RuntimeDetermined/MethodForRuntimeDeterminedType.cs +++ b/src/coreclr/tools/Common/TypeSystem/RuntimeDetermined/MethodForRuntimeDeterminedType.cs @@ -39,6 +39,8 @@ internal MethodForRuntimeDeterminedType(MethodDesc typicalMethodDef, RuntimeDete public override ReadOnlySpan Name => _typicalMethodDef.Name; public override MethodDesc GetTypicalMethodDefinition() => _typicalMethodDef; public override Instantiation Instantiation => _typicalMethodDef.Instantiation; + public override bool IsAsync => _typicalMethodDef.IsAsync; + public override AsyncMethodKind AsyncMethodKind => _typicalMethodDef.AsyncMethodKind; public override bool HasCustomAttribute(string attributeNamespace, string attributeName) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs index a89a3115cb102a..e96fb8036844c4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs @@ -43,7 +43,10 @@ public override IEnumerable GetStaticDependencies(NodeFacto try { context.DetectGenericCycles(Type, method); - dependencies.Add(context.CompiledMethodNode(method), $"Method on type {Type.ToString()}"); + dependencies.Add(context.CompiledMethodNode(method, false), $"Method on type {Type.ToString()}"); + if (method.IsTaskReturning) + dependencies.Add(context.CompiledMethodNode(method, true), $"Async Method on type {Type.ToString()}"); + } catch (TypeSystemException) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs index 4c73a0ab08bcab..f8e7b2c6dd242d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs @@ -59,7 +59,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto } if (useDependency) { - ISymbolNode canonMethodNode = factory.CompiledMethodNode(canonMethod); + ISymbolNode canonMethodNode = factory.CompiledMethodNode(canonMethod, _method.AsyncVariant); yield return new DependencyListEntry(canonMethodNode, "Canonical method for instantiating stub"); } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs index 45b39e8de77d72..f43dfd6dc73924 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs @@ -77,7 +77,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) dataBuilder.EmitTypeSignature(typeRef, innerContext); } - MethodWithToken method = new MethodWithToken(_method, moduleToken, null, unboxing: false, context: null); + MethodWithToken method = new MethodWithToken(_method, moduleToken, null, unboxing: false, asyncVariant: false, context: null); dataBuilder.EmitMethodSignature(method, enforceDefEncoding: false, enforceOwningType: false, innerContext, false); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs index db63a30fae67a5..c49afd4fb7fc67 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs @@ -51,7 +51,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde sb.Append("__ReadyToRunInstanceEntryPointTable"u8); } - public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, NodeFactory factory) + public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, NodeFactory factory, bool asyncVariant = false) { EcmaMethod typicalMethod = (EcmaMethod)method.GetTypicalMethodDefinition(); @@ -70,7 +70,7 @@ public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, N ArraySignatureBuilder signatureBuilder = new ArraySignatureBuilder(); signatureBuilder.EmitMethodSignature( - new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, context: null), + new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, asyncVariant: asyncVariant, context: null), enforceDefEncoding: true, enforceOwningType: moduleToken.Module is EcmaModule ? factory.CompilationModuleGroup.EnforceOwningType((EcmaModule)moduleToken.Module) : true, factory.SignatureContext, @@ -81,7 +81,7 @@ public static byte[] BuildSignatureForMethodDefinedInModule(MethodDesc method, N private byte[] BuildSignatureForMethod(MethodWithGCInfo method, NodeFactory factory) { - return BuildSignatureForMethodDefinedInModule(method.Method, factory); + return BuildSignatureForMethodDefinedInModule(method.Method, factory, method.AsyncVariant); } public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs index 323661407e311d..8290efe54bc7e1 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstrumentationDataTableNode.cs @@ -147,7 +147,7 @@ private int MethodToInt(TypeSystemEntityOrUnknown handle) EcmaMethod typicalMethod = (EcmaMethod)handle.AsMethod.GetTypicalMethodDefinition(); ModuleToken moduleToken = new ModuleToken(typicalMethod.Module, typicalMethod.Handle); - MethodWithToken tok = new MethodWithToken(handle.AsMethod, moduleToken, constrainedType: null, unboxing: false, context: null); + MethodWithToken tok = new MethodWithToken(handle.AsMethod, moduleToken, constrainedType: null, unboxing: false, asyncVariant:false, context: null); Import methodHandleImport = (Import)_symbolFactory.CreateReadyToRunHelper(ReadyToRunHelperId.MethodHandle, tok); _imports.Add(methodHandleImport); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs index c3f3324e0c6ba4..9bbc5f1ffe0a53 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs @@ -65,7 +65,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact try { factory.DetectGenericCycles(_method.Method, canonMethod); - list.Add(factory.CompiledMethodNode(canonMethod), "Virtual function dependency on cross module inlineable method"); + list.Add(factory.CompiledMethodNode(canonMethod, _method.AsyncVariant), "Virtual function dependency on cross module inlineable method"); } catch (TypeSystemException) { @@ -89,7 +89,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) // Optimize some of the fixups into a more compact form ReadyToRunFixupKind fixupKind = _fixupKind; bool optimized = false; - if (!_method.Unboxing && !IsInstantiatingStub && _method.ConstrainedType == null && + if (!_method.Unboxing && !IsInstantiatingStub && !_method.AsyncVariant && _method.ConstrainedType == null && fixupKind == ReadyToRunFixupKind.MethodEntry) { if (!_method.Method.HasInstantiation && !_method.Method.OwningType.HasInstantiation && !_method.Method.OwningType.IsArray) @@ -116,13 +116,13 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { if (method.Token.TokenType == CorTokenType.mdtMethodSpec) { - method = new MethodWithToken(method.Method, factory.SignatureContext.GetModuleTokenForMethod(method.Method), method.ConstrainedType, unboxing: _method.Unboxing, null); + method = new MethodWithToken(method.Method, factory.SignatureContext.GetModuleTokenForMethod(method.Method), method.ConstrainedType, unboxing: _method.Unboxing, asyncVariant: _method.AsyncVariant, null); } else if (!optimized && (method.Token.TokenType == CorTokenType.mdtMemberRef)) { if (method.Method.OwningType.GetTypeDefinition() is EcmaType) { - method = new MethodWithToken(method.Method, factory.SignatureContext.GetModuleTokenForMethod(method.Method), method.ConstrainedType, unboxing: _method.Unboxing, null); + method = new MethodWithToken(method.Method, factory.SignatureContext.GetModuleTokenForMethod(method.Method), method.ConstrainedType, unboxing: _method.Unboxing, asyncVariant: _method.AsyncVariant, null); } } } @@ -154,6 +154,10 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde { sb.Append(" [INST]"u8); } + if (_method.AsyncVariant) + { + sb.Append(" [ASYNC]"u8); + } sb.Append(": "u8); _method.AppendMangledName(nameMangler, sb); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs index 4b5f00c661c678..6162beef9387e5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs @@ -31,12 +31,17 @@ public class MethodWithGCInfo : ObjectNode, IMethodBodyNode, ISymbolDefinitionNo private MethodDesc[] _inlinedMethods; private bool _lateTriggeredCompilation; private DependencyList _nonRelocationDependencies; + private bool _isAsyncVariant; - public MethodWithGCInfo(MethodDesc methodDesc) + public MethodWithGCInfo(MethodDesc methodDesc, bool isAsyncVariant) { + Debug.Assert(!methodDesc.IsAsyncVariant()); + Debug.Assert(!methodDesc.IsUnboxingThunk()); GCInfoNode = new MethodGCInfoNode(this); _fixups = new List(); _method = methodDesc; + _isAsyncVariant = isAsyncVariant; + Debug.Assert(_isAsyncVariant ? methodDesc.IsTaskReturning : true); } protected override void OnMarked(NodeFactory context) @@ -91,6 +96,15 @@ public void SetCode(ObjectData data) public MethodDesc Method => _method; + public MethodDesc GetJitMethod(AsyncMethodVariantFactory factory) + { + if (!_isAsyncVariant) + return _method; + return _method.IsAsync ? + factory.GetOrCreateAsyncImpl(_method) + : factory.GetOrCreateAsyncThunk(_method); + } + public List Fixups => _fixups; public int Size => _methodCode.Data.Length; @@ -323,7 +337,7 @@ public void InitializeFrameInfos(FrameInfo[] frameInfos) else { // On x86, fake a single frame info representing the entire method - _frameInfos = new FrameInfo[] + _frameInfos = new FrameInfo[] { new FrameInfo((FrameInfoFlags)0, startOffset: 0, endOffset: 0, blobData: Array.Empty()) }; @@ -378,7 +392,13 @@ public void InitializeDebugEHClauseInfos(DebugEHClauseInfo[] debugEHClauseInfos) public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) { MethodWithGCInfo otherNode = (MethodWithGCInfo)other; - return comparer.Compare(_method, otherNode._method); + + var result = comparer.Compare(_method, otherNode._method); + if (result != 0) + return result; + if (_isAsyncVariant == otherNode._isAsyncVariant) + return 0; + return _isAsyncVariant ? -1 : 1; } public void InitializeInliningInfo(MethodDesc[] inlinedMethods, NodeFactory factory) @@ -396,6 +416,9 @@ public void InitializeNonRelocationDependencies(DependencyList dependencies) public int Offset => 0; public override bool IsShareable => throw new NotImplementedException(); + + public bool AsyncVariant => _isAsyncVariant; + public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => IsEmpty; public override string ToString() => _method.ToString(); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs index 2c4ca7cd004702..b259ae6d822b00 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs @@ -436,6 +436,10 @@ public void EmitMethodSignature( { flags |= (uint)ReadyToRunMethodSigFlags.READYTORUN_METHOD_SIG_OwnerType; } + if (method.AsyncVariant) + { + flags |= (uint)ReadyToRunMethodSigFlags.READYTORUN_METHOD_SIG_AsyncThunkVariant; + } EmitMethodSpecificationSignature(method, flags, enforceDefEncoding, enforceOwningType, context); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs index 4a45d024afcf89..31f0c40d409652 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs @@ -185,7 +185,7 @@ public static void AddDependenciesForAsyncStateMachineBox(ref DependencyList dep try { factory.DetectGenericCycles(type, method); - dependencies.Add(factory.CompiledMethodNode(method), $"AsyncStateMachineBox Method on type {type.ToString()}"); + dependencies.Add(factory.CompiledMethodNode(method, false), $"AsyncStateMachineBox Method on type {type.ToString()}"); } catch (TypeSystemException) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index e1c1e9a2448b10..fd3a079152fd70 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -112,13 +112,13 @@ public void AddMarkedILBodyFixupSignature(ILBodyFixupSignature sig) _markedILBodyFixupSignatures.Add(sig); } - private NodeCache _localMethodCache; + private NodeCache<(MethodDesc, bool), MethodWithGCInfo> _localMethodCache; - public MethodWithGCInfo CompiledMethodNode(MethodDesc method) + public MethodWithGCInfo CompiledMethodNode(MethodDesc method, bool asyncVariant) { Debug.Assert(CompilationModuleGroup.ContainsMethodBody(method, false)); Debug.Assert(method == method.GetCanonMethodTarget(CanonicalFormKind.Specific)); - return _localMethodCache.GetOrAdd(method); + return _localMethodCache.GetOrAdd((method, asyncVariant)); } private NodeCache _allMethodsOnType; @@ -281,9 +281,9 @@ private void CreateNodeCaches() _importMethods = new NodeCache(CreateMethodEntrypoint); - _localMethodCache = new NodeCache(key => + _localMethodCache = new NodeCache<(MethodDesc, bool), MethodWithGCInfo>(key => { - return new MethodWithGCInfo(key); + return new MethodWithGCInfo(key.Item1, key.Item2); }); _methodSignatures = new NodeCache(key => @@ -430,7 +430,7 @@ private IMethodNode CreateMethodEntrypoint(TypeAndMethod key) if (CompilationModuleGroup.ContainsMethodBody(compilableMethod, false)) { - methodWithGCInfo = CompiledMethodNode(compilableMethod); + methodWithGCInfo = CompiledMethodNode(compilableMethod, method.AsyncVariant); } if (isPrecodeImportRequired) @@ -479,7 +479,7 @@ public IEnumerable EnumerateCompiledMethods(EcmaModule moduleT EcmaModule module = ((EcmaMethod)method.GetTypicalMethodDefinition()).Module; ModuleToken moduleToken = Resolver.GetModuleTokenForMethod(method, allowDynamicallyCreatedReference: true, throwIfNotFound: true); - IMethodNode methodNodeDebug = MethodEntrypoint(new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, context: null), false, false, false); + IMethodNode methodNodeDebug = MethodEntrypoint(new MethodWithToken(method, moduleToken, constrainedType: null, unboxing: false, asyncVariant: methodCodeNode.AsyncVariant, context: null), false, false, false); MethodWithGCInfo methodCodeNodeDebug = methodNodeDebug as MethodWithGCInfo; if (methodCodeNodeDebug == null && methodNodeDebug is DelayLoadMethodImport DelayLoadMethodImport) { @@ -778,7 +778,7 @@ public void AttachToDependencyGraph(DependencyAnalyzerBase graph, I } } - InliningInfoNode crossModuleInliningInfoTable = new InliningInfoNode(null, + InliningInfoNode crossModuleInliningInfoTable = new InliningInfoNode(null, CompilationModuleGroup.IsCompositeBuildMode ? InliningInfoNode.InfoType.CrossModuleInliningForCrossModuleDataOnly : InliningInfoNode.InfoType.CrossModuleAllMethods); Header.Add(Internal.Runtime.ReadyToRunSectionType.CrossModuleInlineInfo, crossModuleInliningInfoTable, crossModuleInliningInfoTable); this.CrossModuleInlningInfo = crossModuleInliningInfoTable; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index e7e4039f95071f..4a6c947af022b9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -247,7 +247,7 @@ public void AddCompilationRoot(MethodDesc method, bool rootMinimalDependencies, MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific); if (_factory.CompilationModuleGroup.ContainsMethodBody(canonMethod, false)) { - IMethodNode methodEntryPoint = _factory.CompiledMethodNode(canonMethod); + IMethodNode methodEntryPoint = _factory.CompiledMethodNode(canonMethod, false); if (rootMinimalDependencies) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs index 93e3f3605cb03d..ddf005b8a7e6b7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs @@ -85,7 +85,7 @@ protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj) perModuleData = new PerModuleMethodsGenerated(module); _methodsGenerated[module] = perModuleData; } - if (method.HasInstantiation || method.OwningType.HasInstantiation) + if (method.HasInstantiation || method.OwningType.HasInstantiation || method.IsAsyncThunk) { perModuleData.GenericMethodsGenerated.Add(methodNode); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs index 6ea7cb2638fedf..bf97d6721ac908 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs @@ -165,6 +165,14 @@ public override MethodIL GetMethodIL(MethodDesc method) return result; } + if (method.IsAsync) + { + // We should not be creating any AsyncMethodVariants yet. + // This hasn't been implemented. + Debug.Assert(method.AsyncMethodKind is AsyncMethodKind.RuntimeAsync or AsyncMethodKind.AsyncExplicitImpl); + return null; + } + // Check to see if there is an override for the EcmaMethodIL. If there is not // then simply return the EcmaMethodIL. In theory this could call // CreateCrossModuleInlineableTokensForILBody, but we explicitly do not want diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 5139e35de8b69d..444533b810b62f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -354,11 +354,8 @@ JitInterface\UnboxingMethodDescFactory.cs - - JitInterface\AsyncMethodDesc.cs - - - JitInterface\AsyncMethodDesc.cs + + TypeSystem\Common\AsyncMethodVariant.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 2bde41e031f496..b23f695bb6443e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -130,16 +130,19 @@ public class MethodWithToken public readonly TypeDesc ConstrainedType; public readonly bool Unboxing; public readonly bool OwningTypeNotDerivedFromToken; + public readonly bool AsyncVariant; public readonly TypeDesc OwningType; - public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, object context, TypeDesc devirtualizedMethodOwner = null) + public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, bool asyncVariant, object context, TypeDesc devirtualizedMethodOwner = null) { Debug.Assert(!method.IsUnboxingThunk()); + Debug.Assert(!method.IsAsyncVariant()); Method = method; Token = token; ConstrainedType = constrainedType; Unboxing = unboxing; + AsyncVariant = asyncVariant; OwningType = GetMethodTokenOwningType(this, constrainedType, context, devirtualizedMethodOwner, out OwningTypeNotDerivedFromToken); } @@ -349,6 +352,8 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) } if (Unboxing) sb.Append("; UNBOXING"u8); + if (AsyncVariant) + sb.Append("; ASYNC"u8); } public override string ToString() @@ -365,6 +370,8 @@ public override string ToString() debuggingName.Append(Token.ToString()); if (Unboxing) debuggingName.Append("; UNBOXING"); + if (AsyncVariant) + debuggingName.Append("; ASYNC"); return debuggingName.ToString(); } @@ -396,6 +403,10 @@ public int CompareTo(MethodWithToken other, TypeSystemComparer comparer) if (result != 0) return result; + result = AsyncVariant.CompareTo(other.AsyncVariant); + if (result != 0) + return result; + // The OwningType/OwningTypeNotDerivedFromToken should be equivalent if the above conditions are equal. Debug.Assert(OwningTypeNotDerivedFromToken == other.OwningTypeNotDerivedFromToken); Debug.Assert(OwningTypeNotDerivedFromToken || (OwningType == other.OwningType)); @@ -465,6 +476,7 @@ unsafe partial class CorInfoImpl private NativeVarInfo[] _debugVarInfos; private HashSet _inlinedMethods; private UnboxingMethodDescFactory _unboxingThunkFactory = new UnboxingMethodDescFactory(); + private AsyncMethodVariantFactory _asyncMethodFactory = new AsyncMethodVariantFactory(); private List _precodeFixups; private List _ilBodiesNeeded; private Dictionary _preInitedTypes = new Dictionary(); @@ -906,7 +918,7 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref var methodIL = HandleToObject(pResolvedToken.tokenScope); MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod); - helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); + helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, asyncVariant: false, context: sharedMethod); } else if (helperArg is FieldDesc fieldDesc) { @@ -956,6 +968,7 @@ private void getReadyToRunDelegateCtorHelper(ref CORINFO_RESOLVED_TOKEN pTargetM HandleToModuleToken(ref pTargetMethod), constrainedType: constrainedType, unboxing: false, + asyncVariant: false, context: typeOrMethodContext); // runtime lookup is not needed, callerHandle is unused @@ -1329,7 +1342,7 @@ private FieldWithToken ComputeFieldWithToken(FieldDesc field, ref CORINFO_RESOLV return new FieldWithToken(field, token); } - private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing) + private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing, bool asyncVariant) { ModuleToken token = HandleToModuleToken(ref pResolvedToken, method, out object context, ref constrainedType); @@ -1339,7 +1352,7 @@ private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RE devirtualizedMethodOwner = HandleToObject(pResolvedToken.hClass); } - return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, context: context, devirtualizedMethodOwner: devirtualizedMethodOwner); + return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, asyncVariant: asyncVariant, context: context, devirtualizedMethodOwner: devirtualizedMethodOwner); } private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, MethodDesc methodDesc, out object context, ref TypeDesc constrainedType) @@ -2444,7 +2457,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol( _compilation.SymbolNodeFactory.InterfaceDispatchCell( - ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: null, unboxing: false), + ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: null, unboxing: false, asyncVariant: false), MethodBeingCompiled)); // If the abi of the method isn't stable, this will cause a usage of the RequiresRuntimeJitSymbol, which will trigger a RequiresRuntimeJitException @@ -2489,7 +2502,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO // READYTORUN: FUTURE: Direct calls if possible pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol( _compilation.NodeFactory.MethodEntrypoint( - ComputeMethodWithToken(nonUnboxingMethod, ref pResolvedToken, constrainedType, unboxing: isUnboxingStub), + ComputeMethodWithToken(nonUnboxingMethod, ref pResolvedToken, constrainedType, unboxing: isUnboxingStub, asyncVariant: false), isInstantiatingStub: useInstantiatingStub, isPrecodeImportRequired: (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0, isJumpableImportRequired: false)); @@ -2513,7 +2526,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO { pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol( _compilation.NodeFactory.DynamicHelperCell( - ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: null, unboxing: false), + ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: null, unboxing: false, asyncVariant: false), useInstantiatingStub)); Debug.Assert(!pResult->sig.hasTypeArg()); @@ -2540,7 +2553,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO { pResult->instParamLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper( ReadyToRunHelperId.MethodDictionary, - ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: constrainedType, unboxing: false))); + ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: constrainedType, unboxing: false, asyncVariant: false))); } else { @@ -2791,7 +2804,7 @@ private void embedGenericHandle(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool symbolNode = _compilation.SymbolNodeFactory.CreateReadyToRunHelper( ReadyToRunHelperId.MethodHandle, - ComputeMethodWithToken(md, ref pResolvedToken, constrainedType: null, unboxing: unboxingStub)); + ComputeMethodWithToken(md, ref pResolvedToken, constrainedType: null, unboxing: unboxingStub, asyncVariant: false /* ?? */)); } break; @@ -3033,7 +3046,7 @@ private void getAddressOfPInvokeTarget(CORINFO_METHOD_STRUCT_* method, ref CORIN Debug.Assert(_compilation.CompilationModuleGroup.VersionsWithMethodBody(methodDesc)); EcmaMethod ecmaMethod = (EcmaMethod)methodDesc; ModuleToken moduleToken = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle); - MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null, unboxing: false, context: null); + MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null, unboxing: false, asyncVariant: false, context: null); if ((ecmaMethod.GetPInvokeMethodCallingConventions() & UnmanagedCallingConventions.IsSuppressGcTransition) != 0) { diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj index d8c363424d831a..04a075b74820db 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj @@ -264,6 +264,9 @@ TypeSystem\Common\InstantiatedMethod.cs + + TypeSystem\Common\InstantiatedMethod.cs + TypeSystem\Common\InstantiatedMethod.Diagnostic.cs @@ -312,6 +315,9 @@ TypeSystem\Common\MethodForInstantiatedType.cs + + TypeSystem\Common\MethodForInstantiatedType.Async.cs + TypeSystem\Common\MethodForInstantiatedType.Diagnostic.cs @@ -378,12 +384,18 @@ TypeSystem\Common\MethodDelegator.cs + + TypeSystem\Common\MethodDelegator.Async.cs + TypeSystem\Common\MethodDelegator.Diagnostic.cs TypeSystem\Common\MethodDesc.cs + + TypeSystem\Common\MethodDesc.Async.cs + TypeSystem\Common\MethodDesc.Diagnostic.cs @@ -480,6 +492,9 @@ Ecma\EcmaMethod.cs + + Ecma\EcmaMethod.Async.cs + Ecma\EcmaMethod.cs