diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerServices/MethodNameAndSignature.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerServices/MethodNameAndSignature.cs index fd125110cf748f..1f81afd1e620ac 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerServices/MethodNameAndSignature.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerServices/MethodNameAndSignature.cs @@ -55,6 +55,19 @@ public override bool Equals(object? compare) && thisMethod.Name.Equals(otherMethod.Name); } + public bool ReturnTypeHasInstantiation + { + get + { + Method method = Reader.GetMethod(Handle); + Handle returnType = method.Signature.GetMethodSignature(Reader).ReturnType; + if (returnType.HandleType != HandleType.TypeSpecification) + return false; + Handle inner = returnType.ToTypeSpecificationHandle(Reader).GetTypeSpecification(Reader).Signature; + return inner.HandleType == HandleType.TypeInstantiationSignature; + } + } + public override int GetHashCode() { Method method = Reader.GetMethod(Handle); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs index cb9725cd06e362..376834bbbac3d0 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs @@ -190,17 +190,18 @@ internal MethodDesc GetMethod(ref NativeParser parser) bool unboxingStub = (flags & MethodFlags.IsUnboxingStub) != 0; bool asyncVariant = (flags & MethodFlags.IsAsyncVariant) != 0; + bool returnDroppingAsyncThunk = (flags & MethodFlags.IsReturnDroppingAsyncThunk) != 0; MethodDesc retVal; if ((flags & MethodFlags.HasInstantiation) != 0) { TypeDesc[] typeArguments = GetTypeSequence(ref parser); Debug.Assert(typeArguments.Length > 0); - retVal = this._typeSystemContext.ResolveGenericMethodInstantiation(unboxingStub, asyncVariant, containingType, nameAndSignature, new Instantiation(typeArguments)); + retVal = this._typeSystemContext.ResolveGenericMethodInstantiation(unboxingStub, asyncVariant, returnDroppingAsyncThunk, containingType, nameAndSignature, new Instantiation(typeArguments)); } else { - retVal = this._typeSystemContext.ResolveRuntimeMethod(unboxingStub, asyncVariant, containingType, nameAndSignature); + retVal = this._typeSystemContext.ResolveRuntimeMethod(unboxingStub, asyncVariant, returnDroppingAsyncThunk, containingType, nameAndSignature); } if ((flags & MethodFlags.HasFunctionPointer) != 0) diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs index 9a564d5b8d8655..8c57c42f65bfbb 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs @@ -286,7 +286,7 @@ internal void ParseNativeLayoutInfo(InstantiatedMethod method) if (!method.UnboxingStub && method.OwningType.IsValueType && !TypeLoaderEnvironment.IsStaticMethodSignature(method.NameAndSignature)) { // Make it an unboxing stub, note the first parameter which is true - nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, method.AsyncVariant, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation); + nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, method.AsyncVariant, method.ReturnDroppingAsyncThunk, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation); } uint nativeLayoutInfoToken; @@ -803,6 +803,7 @@ private void FinishRuntimeType(TypeDesc type) _genericMethodArgumentHandles = GetRuntimeTypeHandles(method.Instantiation), _methodNameAndSignature = method.NameAndSignature, _isAsyncVariant = method.AsyncVariant, + _isReturnDroppingAsyncThunk = method.ReturnDroppingAsyncThunk, _methodDictionary = method.RuntimeMethodDictionary }; } diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericMethodsLookup.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericMethodsLookup.cs index cdf51142416350..4f3555016d4a0b 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericMethodsLookup.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericMethodsLookup.cs @@ -21,6 +21,7 @@ internal class GenericMethodEntry private int? _hashCode; public bool _isRegisteredSuccessfully; public bool _isAsyncVariant; + public bool _isReturnDroppingAsyncThunk; public IntPtr _methodDictionary; public RuntimeTypeHandle _declaringTypeHandle; public MethodNameAndSignature _methodNameAndSignature; @@ -50,6 +51,9 @@ public virtual bool IsEqualToEntryByComponentsComparison(GenericMethodEntry othe if (_isAsyncVariant != other._isAsyncVariant) return false; + if (_isReturnDroppingAsyncThunk != other._isReturnDroppingAsyncThunk) + return false; + if (!other._methodNameAndSignature.Equals(_methodNameAndSignature)) return false; @@ -157,10 +161,13 @@ internal override bool MatchParsedEntry(ref NativeParser entryParser, ref Extern int flagsAndToken = (int)entryParser.GetUnsigned(); bool isAsyncVariant = (flagsAndToken & GenericMethodsHashtableConstants.IsAsyncVariant) != 0; + bool isReturnDroppingAsyncThunk = (flagsAndToken & GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk) != 0; if (_methodToLookup.AsyncVariant != isAsyncVariant) return false; + if (_methodToLookup.ReturnDroppingAsyncThunk != isReturnDroppingAsyncThunk) + return false; - int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~GenericMethodsHashtableConstants.IsAsyncVariant); + int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~(GenericMethodsHashtableConstants.IsAsyncVariant | GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk)); MethodNameAndSignature nameAndSignature = TypeLoaderEnvironment.GetMethodNameAndSignatureFromToken(moduleHandle, (uint)token); if (!_methodToLookup.NameAndSignature.Equals(nameAndSignature)) @@ -192,6 +199,9 @@ internal override bool MatchGenericMethodEntry(GenericMethodEntry entry) if (_methodToLookup.AsyncVariant != entry._isAsyncVariant) return false; + if (_methodToLookup.ReturnDroppingAsyncThunk != entry._isReturnDroppingAsyncThunk) + return false; + if (!_methodToLookup.NameAndSignature.Equals(entry._methodNameAndSignature)) return false; @@ -300,7 +310,7 @@ public bool TryGetGenericVirtualMethodPointer(InstantiatedMethod method, out Int if (!method.UnboxingStub && method.OwningType.IsValueType && !IsStaticMethodSignature(method.NameAndSignature)) { // Make it an unboxing stub, note the first parameter which is true - nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, method.AsyncVariant, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation); + nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, method.AsyncVariant, method.ReturnDroppingAsyncThunk, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation); } // If we cannot find an exact method entry point, look for an equivalent template and compute the generic dictionary @@ -441,7 +451,7 @@ private static unsafe bool TryGetStaticGenericMethodComponents(IntPtr methodDict int flagsAndToken = (int)entryParser.GetUnsigned(); isAsyncVariant = (flagsAndToken & GenericMethodsHashtableConstants.IsAsyncVariant) != 0; - int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~GenericMethodsHashtableConstants.IsAsyncVariant); + int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~(GenericMethodsHashtableConstants.IsAsyncVariant | GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk)); nameAndSignature = new MethodNameAndSignature(module.MetadataReader, token.AsHandle().ToMethodHandle(module.MetadataReader)); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs index b73557318d9bf7..f5a1336bdbe829 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.GVMResolution.cs @@ -311,7 +311,12 @@ private static InstantiatedMethod FindMatchingInterfaceSlot(NativeFormatModuleIn Debug.Assert(interfaceImplType != null); } - return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, slotMethod.AsyncVariant, interfaceImplType, targetMethodNameAndSignature, slotMethod.Instantiation); + bool returnDroppingAsyncThunk = slotMethod.AsyncVariant + && !slotMethod.NameAndSignature.ReturnTypeHasInstantiation + && targetMethodNameAndSignature.ReturnTypeHasInstantiation; + bool asyncVariant = slotMethod.AsyncVariant && !returnDroppingAsyncThunk; + + return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, asyncVariant, returnDroppingAsyncThunk, interfaceImplType, targetMethodNameAndSignature, slotMethod.Instantiation); } } } @@ -502,7 +507,13 @@ private static InstantiatedMethod ResolveGenericVirtualMethodTarget(DefType targ Debug.Assert(targetMethodNameAndSignature != null); TypeSystemContext context = slotMethod.Context; - return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, slotMethod.AsyncVariant, targetType, targetMethodNameAndSignature, slotMethod.Instantiation); + + bool returnDroppingAsyncThunk = slotMethod.AsyncVariant + && !slotMethod.NameAndSignature.ReturnTypeHasInstantiation + && targetMethodNameAndSignature.ReturnTypeHasInstantiation; + bool asyncVariant = slotMethod.AsyncVariant && !returnDroppingAsyncThunk; + + return (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, asyncVariant, returnDroppingAsyncThunk, targetType, targetMethodNameAndSignature, slotMethod.Instantiation); } } diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.LdTokenResultLookup.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.LdTokenResultLookup.cs index 844e3fe9196a64..d75d03bbb6bd35 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.LdTokenResultLookup.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.LdTokenResultLookup.cs @@ -203,10 +203,10 @@ public MethodDesc GetMethodDescForRuntimeMethodHandle(TypeSystemContext context, if (genericMethodArgs != null) { Instantiation methodInst = context.ResolveRuntimeTypeHandles(genericMethodArgs); - return context.ResolveGenericMethodInstantiation(unboxingStub: false, isAsyncVariant, type, nameAndSignature, methodInst); + return context.ResolveGenericMethodInstantiation(unboxingStub: false, isAsyncVariant, returnDroppingAsyncThunk: false, type, nameAndSignature, methodInst); } - return context.ResolveRuntimeMethod(unboxingStub: false, isAsyncVariant, type, nameAndSignature); + return context.ResolveRuntimeMethod(unboxingStub: false, isAsyncVariant, returnDroppingAsyncThunk: false, type, nameAndSignature); } public unsafe bool TryGetRuntimeMethodHandleComponents(RuntimeMethodHandle runtimeMethodHandle, out RuntimeTypeHandle declaringTypeHandle, out QMethodDefinition handle, out RuntimeTypeHandle[] genericMethodArgs) diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs index 73835fe3883a4d..16ff076bfe5b3a 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs @@ -380,7 +380,7 @@ public bool TryGetGenericMethodDictionaryForComponents(RuntimeTypeHandle declari TypeSystemContext context = TypeSystemContextFactory.Create(); DefType declaringType = (DefType)context.ResolveRuntimeTypeHandle(declaringTypeHandle); - InstantiatedMethod methodBeingLoaded = (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, asyncVariant: false, declaringType, nameAndSignature, context.ResolveRuntimeTypeHandles(genericMethodArgHandles)); + InstantiatedMethod methodBeingLoaded = (InstantiatedMethod)context.ResolveGenericMethodInstantiation(false, asyncVariant: false, returnDroppingAsyncThunk: false, declaringType, nameAndSignature, context.ResolveRuntimeTypeHandles(genericMethodArgHandles)); if (TryLookupGenericMethodDictionary(new MethodDescBasedGenericMethodLookup(methodBeingLoaded), out methodDictionary)) { diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/InstantiatedMethod.Runtime.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/InstantiatedMethod.Runtime.cs index e8d2ebaf7b2b65..d298dc6261ec98 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/InstantiatedMethod.Runtime.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/InstantiatedMethod.Runtime.cs @@ -73,6 +73,14 @@ public override bool AsyncVariant } } + public override bool ReturnDroppingAsyncThunk + { + get + { + return _methodDef.ReturnDroppingAsyncThunk; + } + } + #if DEBUG public override string ToString() { diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/MethodDesc.Runtime.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/MethodDesc.Runtime.cs index 3a45b30c37be9f..9d183b9ff48383 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/MethodDesc.Runtime.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/MethodDesc.Runtime.cs @@ -67,5 +67,13 @@ public virtual bool AsyncVariant return false; } } + + public virtual bool ReturnDroppingAsyncThunk + { + get + { + return false; + } + } } } diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.Canon.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.Canon.cs index bd0902fb8fc971..490e97a8f7ef41 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.Canon.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.Canon.cs @@ -23,7 +23,7 @@ public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind) if (canonicalizedTypeOfTargetMethod == OwningType) return this; - return Context.ResolveRuntimeMethod(this.UnboxingStub, this.AsyncVariant, canonicalizedTypeOfTargetMethod, this.NameAndSignature); + return Context.ResolveRuntimeMethod(this.UnboxingStub, this.AsyncVariant, this.ReturnDroppingAsyncThunk, canonicalizedTypeOfTargetMethod, this.NameAndSignature); } } } diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.cs index 6483d49215eb26..d3a362efa88e65 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/RuntimeMethodDesc.cs @@ -14,13 +14,14 @@ namespace Internal.TypeSystem.NoMetadata /// internal sealed partial class RuntimeMethodDesc : NoMetadataMethodDesc { - public RuntimeMethodDesc(bool unboxingStub, bool asyncVariant, DefType owningType, + public RuntimeMethodDesc(bool unboxingStub, bool asyncVariant, bool returnDroppingAsyncThunk, DefType owningType, MethodNameAndSignature nameAndSignature, int hashcode) { _owningType = owningType; _nameAndSignature = nameAndSignature; _unboxingStub = unboxingStub; _asyncVariant = asyncVariant; + _returnDroppingAsyncThunk = returnDroppingAsyncThunk; SetHashCode(hashcode); #if DEBUG @@ -116,6 +117,15 @@ public override bool AsyncVariant } } + private bool _returnDroppingAsyncThunk; + public override bool ReturnDroppingAsyncThunk + { + get + { + return _returnDroppingAsyncThunk; + } + } + public override MethodDesc GetTypicalMethodDefinition() { TypeDesc owningTypeDefinition = OwningType.GetTypeDefinition(); @@ -127,7 +137,7 @@ public override MethodDesc GetTypicalMethodDefinition() } // Otherwise, find its equivalent on the type definition of the owning type - return Context.ResolveRuntimeMethod(UnboxingStub, AsyncVariant, (DefType)owningTypeDefinition, _nameAndSignature); + return Context.ResolveRuntimeMethod(UnboxingStub, AsyncVariant, ReturnDroppingAsyncThunk, (DefType)owningTypeDefinition, _nameAndSignature); } public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation) @@ -137,7 +147,7 @@ public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, TypeDesc owningType = method.OwningType; TypeDesc instantiatedOwningType = owningType.InstantiateSignature(typeInstantiation, methodInstantiation); if (owningType != instantiatedOwningType) - method = instantiatedOwningType.Context.ResolveRuntimeMethod(UnboxingStub, AsyncVariant, (DefType)instantiatedOwningType, _nameAndSignature); + method = instantiatedOwningType.Context.ResolveRuntimeMethod(UnboxingStub, AsyncVariant, ReturnDroppingAsyncThunk, (DefType)instantiatedOwningType, _nameAndSignature); Instantiation instantiation = method.Instantiation; TypeDesc[] clone = null; diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs index f4709a03231d0d..2574e2d05d6548 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs @@ -322,14 +322,16 @@ private struct RuntimeMethodKey { private bool _unboxingStub; private bool _asyncVariant; + private bool _returnDroppingAsyncThunk; private DefType _owningType; private MethodNameAndSignature _methodNameAndSignature; private int _hashCode; - public RuntimeMethodKey(bool unboxingStub, bool asyncVariant, DefType owningType, MethodNameAndSignature nameAndSignature) + public RuntimeMethodKey(bool unboxingStub, bool asyncVariant, bool returnDroppingAsyncThunk, DefType owningType, MethodNameAndSignature nameAndSignature) { _unboxingStub = unboxingStub; _asyncVariant = asyncVariant; + _returnDroppingAsyncThunk = returnDroppingAsyncThunk; _owningType = owningType; _methodNameAndSignature = nameAndSignature; @@ -358,6 +360,9 @@ protected override bool CompareKeyToValue(RuntimeMethodKey key, MethodDesc value if (key._asyncVariant != runtimeMethod.AsyncVariant) return false; + if (key._returnDroppingAsyncThunk != runtimeMethod.ReturnDroppingAsyncThunk) + return false; + if (!key._owningType.Equals(runtimeMethod.OwningType)) return false; @@ -369,7 +374,7 @@ protected override bool CompareKeyToValue(RuntimeMethodKey key, MethodDesc value else { // Only RuntimeMethodDesc can be an unboxing stub or async variant - if (key._unboxingStub || key._asyncVariant) + if (key._unboxingStub || key._asyncVariant || key._returnDroppingAsyncThunk) return false; if (!key._owningType.Equals(value.OwningType)) @@ -397,6 +402,9 @@ protected override bool CompareValueToValue(MethodDesc value1, MethodDesc value2 if (((RuntimeMethodDesc)value1).AsyncVariant != ((RuntimeMethodDesc)value2).AsyncVariant) return false; + if (((RuntimeMethodDesc)value1).ReturnDroppingAsyncThunk != ((RuntimeMethodDesc)value2).ReturnDroppingAsyncThunk) + return false; + if (!value1.OwningType.Equals(value2.OwningType)) return false; @@ -421,7 +429,7 @@ protected override MethodDesc CreateValueFromKey(RuntimeMethodKey key) // Instantiated Types always get their methods through GetMethodForInstantiatedType if (key._owningType is InstantiatedType) { - MethodDesc typicalMethod = key._owningType.Context.ResolveRuntimeMethod(key._unboxingStub, key._asyncVariant, (DefType)key._owningType.GetTypeDefinition(), key._methodNameAndSignature); + MethodDesc typicalMethod = key._owningType.Context.ResolveRuntimeMethod(key._unboxingStub, key._asyncVariant, key._returnDroppingAsyncThunk, (DefType)key._owningType.GetTypeDefinition(), key._methodNameAndSignature); return typicalMethod.Context.GetMethodForInstantiatedType(typicalMethod, (InstantiatedType)key._owningType); } } @@ -431,17 +439,17 @@ protected override MethodDesc CreateValueFromKey(RuntimeMethodKey key) Debug.Assert(key._owningType.IsValueType); } - return new RuntimeMethodDesc(key._unboxingStub, key._asyncVariant, key._owningType, key._methodNameAndSignature, key._hashCode); + return new RuntimeMethodDesc(key._unboxingStub, key._asyncVariant, key._returnDroppingAsyncThunk, key._owningType, key._methodNameAndSignature, key._hashCode); } } } private RuntimeMethodKey.RuntimeMethodKeyHashtable _runtimeMethods; - internal MethodDesc ResolveRuntimeMethod(bool unboxingStub, bool asyncVariant, DefType owningType, MethodNameAndSignature nameAndSignature) + internal MethodDesc ResolveRuntimeMethod(bool unboxingStub, bool asyncVariant, bool returnDroppingAsyncThunk, DefType owningType, MethodNameAndSignature nameAndSignature) { _runtimeMethods ??= new RuntimeMethodKey.RuntimeMethodKeyHashtable(); - return _runtimeMethods.GetOrCreateValue(new RuntimeMethodKey(unboxingStub, asyncVariant, owningType, nameAndSignature)); + return _runtimeMethods.GetOrCreateValue(new RuntimeMethodKey(unboxingStub, asyncVariant, returnDroppingAsyncThunk, owningType, nameAndSignature)); } private LowLevelDictionary _genericTypeInstances; @@ -475,9 +483,9 @@ public DefType ResolveGenericInstantiation(DefType typeDef, Instantiation argume /// /// Find a method based on owner type and nativelayout name, method instantiation, and signature. /// - public MethodDesc ResolveGenericMethodInstantiation(bool unboxingStub, bool asyncVariant, DefType owningType, MethodNameAndSignature nameAndSignature, Instantiation methodInstantiation) + public MethodDesc ResolveGenericMethodInstantiation(bool unboxingStub, bool asyncVariant, bool returnDroppingAsyncThunk, DefType owningType, MethodNameAndSignature nameAndSignature, Instantiation methodInstantiation) { - var uninstantiatedMethod = ResolveRuntimeMethod(unboxingStub, asyncVariant, owningType, nameAndSignature); + var uninstantiatedMethod = ResolveRuntimeMethod(unboxingStub, asyncVariant, returnDroppingAsyncThunk, owningType, nameAndSignature); MethodDesc returnedMethod; if (methodInstantiation.IsNull || (methodInstantiation.Length == 0)) diff --git a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs index 7a232d2023f866..63350e6590cbba 100644 --- a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs +++ b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.Mangling.cs @@ -14,4 +14,11 @@ public partial class AsyncMethodVariant : MethodDelegator, IPrefixMangledMethod ReadOnlySpan IPrefixMangledMethod.Prefix => "AsyncCallable"u8; } + + public partial class ReturnDroppingAsyncThunk : MethodDelegator, IPrefixMangledMethod + { + MethodDesc IPrefixMangledMethod.BaseMethod => _asyncVariant.Target; + + ReadOnlySpan IPrefixMangledMethod.Prefix => "ReturnDroppingAsync"u8; + } } diff --git a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs index c3abf7499c4315..8b68888e29e5f7 100644 --- a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs +++ b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs @@ -72,6 +72,72 @@ protected override int CompareToImpl(MethodDesc other, TypeSystemComparer compar } } + /// + /// A special void-returning async variant that calls the T returning async variant and drops the result. + /// Used when a base class method returns Task and the derived class overrides it with Task<T>. + /// The base's async variant is void-returning, while the derived's async variant is T-returning. + /// This thunk bridges the mismatch. + /// + public partial class ReturnDroppingAsyncThunk : MethodDelegator + { + private readonly AsyncMethodVariant _asyncVariant; + private MethodSignature _voidSignature; + + public ReturnDroppingAsyncThunk(AsyncMethodVariant asyncVariant) + : base(asyncVariant) + { + Debug.Assert(!asyncVariant.Signature.ReturnType.IsVoid); + _asyncVariant = asyncVariant; + } + + public AsyncMethodVariant AsyncVariantTarget => _asyncVariant; + + public override MethodSignature Signature + { + get + { + return _voidSignature ?? InitializeSignature(); + } + } + + private MethodSignature InitializeSignature() + { + MethodSignatureBuilder builder = new MethodSignatureBuilder(_asyncVariant.Signature); + builder.ReturnType = Context.GetWellKnownType(WellKnownType.Void); + return (_voidSignature = builder.ToSignature()); + } + + public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind) + { + return this; + } + + public override MethodDesc GetMethodDefinition() + { + return this; + } + + public override MethodDesc GetTypicalMethodDefinition() + { + return this; + } + + public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation) + { + return this; + } + + public override string ToString() => $"Return-dropping async variant: " + _asyncVariant.Target.ToString(); + + protected override int ClassCode => unchecked((int)0xa3c2b7e5u); + + protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + var rdOther = (ReturnDroppingAsyncThunk)other; + return comparer.Compare(_asyncVariant, rdOther._asyncVariant); + } + } + public static class AsyncMethodVariantExtensions { public static bool IsAsyncVariant(this MethodDesc method) @@ -79,9 +145,14 @@ public static bool IsAsyncVariant(this MethodDesc method) return method.GetTypicalMethodDefinition() is AsyncMethodVariant; } + public static bool IsReturnDroppingAsyncThunk(this MethodDesc method) + { + return method.GetTypicalMethodDefinition() is ReturnDroppingAsyncThunk; + } + public static bool IsAsyncThunk(this MethodDesc method) { - return method.IsAsyncVariant() ^ method.IsAsync; + return (method.IsAsyncVariant() ^ method.IsAsync) || method.IsReturnDroppingAsyncThunk(); } public static bool IsCompilerGeneratedILBodyForAsync(this MethodDesc method) diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs index 9930fd679920b4..e974d78ffb00ca 100644 --- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs @@ -34,12 +34,30 @@ private MethodDesc DecomposeAsyncVariant(MethodDesc method, out bool isAsyncVari return isAsyncVariant ? _context.GetTargetOfAsyncVariantMethod(method) : method; } + private MethodDesc WrapAsAsyncVariant(MethodDesc originalMethod, MethodDesc resolvedMethod) + { + Debug.Assert(originalMethod.Signature.ReturnsTaskOrValueTask()); + Debug.Assert(resolvedMethod.Signature.ReturnsTaskOrValueTask()); + + MethodDesc asyncVariant = _context.GetAsyncVariantMethod(resolvedMethod); + + // Check if the slot is void-returning but the resolved + // async variant is T-returning (base has Task, derived has Task). + if (!originalMethod.Signature.ReturnType.HasInstantiation + && !asyncVariant.Signature.ReturnType.IsVoid) + { + asyncVariant = _context.GetReturnDroppingAsyncVariantMethod(asyncVariant); + } + + return asyncVariant; + } + public override MethodDesc FindVirtualFunctionTargetMethodOnObjectType(MethodDesc targetMethod, TypeDesc objectType) { targetMethod = DecomposeAsyncVariant(targetMethod, out bool isAsyncSlot); MethodDesc result = base.FindVirtualFunctionTargetMethodOnObjectType(targetMethod, objectType); if (result != null && isAsyncSlot) - result = _context.GetAsyncVariantMethod(result); + result = WrapAsAsyncVariant(targetMethod, result); return result; } @@ -49,7 +67,7 @@ public override DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefault interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot); DefaultInterfaceMethodResolution result = base.ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, currentType, out impl); if (impl != null && isAsyncSlot) - impl = _context.GetAsyncVariantMethod(impl); + impl = WrapAsAsyncVariant(interfaceMethod, impl); return result; } @@ -59,7 +77,7 @@ public override MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(Met interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot); MethodDesc result = base.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, currentType); if (result != null && isAsyncSlot) - result = _context.GetAsyncVariantMethod(result); + result = WrapAsAsyncVariant(interfaceMethod, result); return result; } @@ -68,7 +86,7 @@ public override MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDes interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot); MethodDesc result = base.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod, currentType); if (result != null && isAsyncSlot) - result = _context.GetAsyncVariantMethod(result); + result = WrapAsAsyncVariant(interfaceMethod, result); return result; } @@ -77,7 +95,7 @@ public override DefaultInterfaceMethodResolution ResolveVariantInterfaceMethodTo interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot); DefaultInterfaceMethodResolution result = base.ResolveVariantInterfaceMethodToDefaultImplementationOnType(interfaceMethod, currentType, out impl); if (impl != null && isAsyncSlot) - impl = _context.GetAsyncVariantMethod(impl); + impl = WrapAsAsyncVariant(interfaceMethod, impl); return result; } @@ -86,7 +104,7 @@ public override MethodDesc ResolveVariantInterfaceMethodToStaticVirtualMethodOnT interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot); MethodDesc result = base.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod, currentType); if (result != null && isAsyncSlot) - result = _context.GetAsyncVariantMethod(result); + result = WrapAsAsyncVariant(interfaceMethod, result); return result; } @@ -95,7 +113,7 @@ public override MethodDesc ResolveVariantInterfaceMethodToVirtualMethodOnType(Me interfaceMethod = DecomposeAsyncVariant(interfaceMethod, out bool isAsyncSlot); MethodDesc result = base.ResolveVariantInterfaceMethodToVirtualMethodOnType(interfaceMethod, currentType); if (result != null && isAsyncSlot) - result = _context.GetAsyncVariantMethod(result); + result = WrapAsAsyncVariant(interfaceMethod, result); return result; } @@ -200,6 +218,38 @@ protected override bool CompareValueToValue(AsyncMethodVariant value1, AsyncMeth } private AsyncVariantHashtable _asyncVariantHashtable = new AsyncVariantHashtable(); + public MethodDesc GetReturnDroppingAsyncVariantMethod(MethodDesc asyncVariantMethod) + { + Debug.Assert(asyncVariantMethod.IsAsyncVariant()); + Debug.Assert(!asyncVariantMethod.Signature.ReturnType.IsVoid); + + MethodDesc typicalMethodDefinition = asyncVariantMethod.GetTypicalMethodDefinition(); + MethodDesc result = _returnDroppingHashtable.GetOrCreateValue((AsyncMethodVariant)typicalMethodDefinition); + + if (typicalMethodDefinition != asyncVariantMethod) + { + TypeDesc owningType = asyncVariantMethod.OwningType; + if (owningType != typicalMethodDefinition.OwningType) + result = GetMethodForInstantiatedType(result, (InstantiatedType)owningType); + + if (asyncVariantMethod.HasInstantiation && !asyncVariantMethod.IsMethodDefinition) + result = GetInstantiatedMethod(result, asyncVariantMethod.Instantiation); + } + + return result; + } + + private sealed class ReturnDroppingHashtable : LockFreeReaderHashtable + { + protected override int GetKeyHashCode(AsyncMethodVariant key) => key.GetHashCode(); + protected override int GetValueHashCode(ReturnDroppingAsyncThunk value) => value.AsyncVariantTarget.GetHashCode(); + protected override bool CompareKeyToValue(AsyncMethodVariant key, ReturnDroppingAsyncThunk value) => key == value.AsyncVariantTarget; + protected override bool CompareValueToValue(ReturnDroppingAsyncThunk value1, ReturnDroppingAsyncThunk value2) + => value1.AsyncVariantTarget == value2.AsyncVariantTarget; + protected override ReturnDroppingAsyncThunk CreateValueFromKey(AsyncMethodVariant key) => new ReturnDroppingAsyncThunk(key); + } + private ReturnDroppingHashtable _returnDroppingHashtable = new ReturnDroppingHashtable(); + public AsyncResumptionStub GetAsyncResumptionStub(MethodDesc targetMethod, TypeDesc owningType) { return _asyncResumptionStubHashtable.GetOrCreateValue(new AsyncResumptionStubKey(targetMethod, owningType)); diff --git a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs index 5949052cb51032..954bb9c01d6a21 100644 --- a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs +++ b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs @@ -178,9 +178,10 @@ public static bool IsCallEffectivelyDirect(this MethodDesc method) /// /// Determines whether a method uses the async calling convention. - /// Returns true for async variants (compiler-generated wrappers around Task-returning methods) - /// and for special async intrinsics that don't return Task/ValueTask but use async calling convention. + /// Returns true for async variants (compiler-generated wrappers around Task-returning methods), + /// return dropping async thunks, and for special async intrinsics that don't return Task/ValueTask + /// but use async calling convention. /// - public static bool IsAsyncCall(this MethodDesc method) => method.IsAsyncVariant() || (method.IsAsync && !method.Signature.ReturnsTaskOrValueTask()); + public static bool IsAsyncCall(this MethodDesc method) => method.IsAsyncVariant() || method.IsReturnDroppingAsyncThunk() || (method.IsAsync && !method.Signature.ReturnsTaskOrValueTask()); } } diff --git a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs index 68155dd5955b1a..aa9664b09c226a 100644 --- a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs +++ b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs @@ -179,6 +179,7 @@ enum MethodFlags : uint IsUnboxingStub = 0x2, HasFunctionPointer = 0x4, IsAsyncVariant = 0x8, + IsReturnDroppingAsyncThunk = 0x10, }; [Flags] diff --git a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs index a90a4e7756c5d9..629e738cb4022a 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/RuntimeConstants.cs @@ -86,5 +86,6 @@ internal static class RuntimeMethodHandleConstants internal static class GenericMethodsHashtableConstants { public const int IsAsyncVariant = unchecked((int)0x80000000); + public const int IsReturnDroppingAsyncThunk = unchecked((int)0x40000000); } } diff --git a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs index 82c2016b2a2099..84b4daec9d8c99 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs @@ -355,6 +355,11 @@ public override MethodIL GetMethodIL(MethodDesc method) return ArrayMethodILEmitter.EmitIL((ArrayMethod)method); } else + if (method is ReturnDroppingAsyncThunk returnDroppingVariant) + { + return AsyncThunkILEmitter.EmitReturnDroppingThunk(returnDroppingVariant, returnDroppingVariant.AsyncVariantTarget); + } + else if (method is AsyncMethodVariant asyncVariantImpl) { if (asyncVariantImpl.IsAsync) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs index 961a147e4dc661..2d9891ef57b874 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs @@ -376,5 +376,59 @@ public static MethodIL EmitAsyncMethodThunk(MethodDesc asyncMethod, MethodDesc t return emitter.Link(asyncMethod); } + + // Provided an async variant, emits an async wrapper that drops the returned value. + // Used in the covariant return scenario. + // The emitted code matches EmitReturnDroppingThunk in CoreCLR VM. + public static MethodIL EmitReturnDroppingThunk(MethodDesc returnDroppingMethod, MethodDesc asyncVariantTarget) + { + TypeSystemContext context = returnDroppingMethod.Context; + + var emitter = new ILEmitter(); + emitter.SetHasGeneratedTokens(); + + var codestream = emitter.NewCodeStream(); + + if (asyncVariantTarget.OwningType.HasInstantiation) + { + var instantiatedType = (InstantiatedType)TypeSystemHelpers.InstantiateAsOpen(asyncVariantTarget.OwningType); + asyncVariantTarget = context.GetMethodForInstantiatedType(asyncVariantTarget, instantiatedType); + } + + if (asyncVariantTarget.HasInstantiation) + { + var inst = new TypeDesc[asyncVariantTarget.Instantiation.Length]; + for (int i = 0; i < inst.Length; i++) + { + inst[i] = context.GetSignatureVariable(i, true); + } + asyncVariantTarget = asyncVariantTarget.MakeInstantiatedMethod(new Instantiation(inst)); + } + + MethodSignature sig = returnDroppingMethod.Signature; + + // Implement IL that is effectively the following: + // { + // this.other(arg); + // return; + // } + + int localArg = 0; + codestream.EmitLdArg(localArg++); + + for (int iArg = 0; iArg < sig.Length; iArg++) + { + codestream.EmitLdArg(localArg++); + } + + // Use 'call' not 'callvirt': in NativeAOT the target of this thunk is resolved + // per type at compile time, so there is no need to redispatch through the vtable. + // CoreCLR uses callvirt because thunks can be inherited by subtypes at runtime. + codestream.Emit(ILOpcode.call, emitter.NewToken(asyncVariantTarget)); + codestream.Emit(ILOpcode.pop); + codestream.Emit(ILOpcode.ret); + + return emitter.Link(returnDroppingMethod); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExactMethodInstantiationsNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExactMethodInstantiationsNode.cs index b0a94038ca5ff1..dc4e3dbf557b81 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExactMethodInstantiationsNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExactMethodInstantiationsNode.cs @@ -72,9 +72,11 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } int flags = 0; - MethodDesc methodForMetadata = GetMethodForMetadata(method, out bool isAsyncVariant); + MethodDesc methodForMetadata = GetMethodForMetadata(method, out bool isAsyncVariant, out bool isReturnDroppingAsyncThunk); if (isAsyncVariant) flags |= GenericMethodsHashtableConstants.IsAsyncVariant; // Same flag as the other hashtable! Readers are shared. + if (isReturnDroppingAsyncThunk) + flags |= GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk; int token = factory.MetadataManager.GetMetadataHandleForMethod(factory, methodForMetadata); @@ -115,18 +117,26 @@ public static void GetExactMethodInstantiationDependenciesForMethod(ref Dependen foreach (var arg in method.Instantiation) dependencies.Add(new DependencyListEntry(factory.NecessaryTypeSymbol(arg), "Exact method instantiation entry")); - factory.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, factory, GetMethodForMetadata(method, out _)); + factory.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, factory, GetMethodForMetadata(method, out _, out _)); } - private static MethodDesc GetMethodForMetadata(MethodDesc method, out bool isAsyncVariant) + private static MethodDesc GetMethodForMetadata(MethodDesc method, out bool isAsyncVariant, out bool isReturnDroppingAsyncThunk) { MethodDesc result = method.GetTypicalMethodDefinition(); + if (result is ReturnDroppingAsyncThunk rdThunk) + { + isAsyncVariant = false; + isReturnDroppingAsyncThunk = true; + return rdThunk.AsyncVariantTarget.Target; + } if (result is AsyncMethodVariant asyncVariant) { isAsyncVariant = true; + isReturnDroppingAsyncThunk = false; return asyncVariant.Target; } isAsyncVariant = false; + isReturnDroppingAsyncThunk = false; return result; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableNode.cs index 349254ee85d8c5..bfe71bfbb7a7f4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericMethodsHashtableNode.cs @@ -67,9 +67,11 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } int flags = 0; - MethodDesc methodForMetadata = GetMethodForMetadata(method, out bool isAsyncVariant); + MethodDesc methodForMetadata = GetMethodForMetadata(method, out bool isAsyncVariant, out bool isReturnDroppingAsyncThunk); if (isAsyncVariant) flags |= GenericMethodsHashtableConstants.IsAsyncVariant; + if (isReturnDroppingAsyncThunk) + flags |= GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk; int token = factory.MetadataManager.GetMetadataHandleForMethod(factory, methodForMetadata); @@ -108,18 +110,26 @@ public static void GetGenericMethodsHashtableDependenciesForMethod(ref Dependenc dependencies.Add(new DependencyListEntry(argNode, "GenericMethodsHashtable entry instantiation argument")); } - factory.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, factory, GetMethodForMetadata(method, out _)); + factory.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, factory, GetMethodForMetadata(method, out _, out _)); } - private static MethodDesc GetMethodForMetadata(MethodDesc method, out bool isAsyncVariant) + private static MethodDesc GetMethodForMetadata(MethodDesc method, out bool isAsyncVariant, out bool isReturnDroppingAsyncThunk) { MethodDesc result = method.GetTypicalMethodDefinition(); + if (result is ReturnDroppingAsyncThunk rdThunk) + { + isAsyncVariant = false; + isReturnDroppingAsyncThunk = true; + return rdThunk.AsyncVariantTarget.Target; + } if (result is AsyncMethodVariant asyncVariant) { isAsyncVariant = true; + isReturnDroppingAsyncThunk = false; return asyncVariant.Target; } isAsyncVariant = false; + isReturnDroppingAsyncThunk = false; return result; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index 87849e5a921f84..0953abf3ad6e7e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -153,7 +153,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto dependencies.Add(new DependencyListEntry(methodEntryPointNode, "NativeLayoutMethodEntryVertexNode entrypoint")); } - context.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, context, GetMethodForMetadata(_method, out _)); + context.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, context, GetMethodForMetadata(_method, out _, out _)); return dependencies; } @@ -191,9 +191,11 @@ public override Vertex WriteVertex(NodeFactory factory) if (IsUnboxingStub) flags |= MethodFlags.IsUnboxingStub; - MethodDesc methodForToken = GetMethodForMetadata(_method, out bool isAsyncVariant); + MethodDesc methodForToken = GetMethodForMetadata(_method, out bool isAsyncVariant, out bool isReturnDroppingAsyncThunk); if (isAsyncVariant) flags |= MethodFlags.IsAsyncVariant; + if (isReturnDroppingAsyncThunk) + flags |= MethodFlags.IsReturnDroppingAsyncThunk; uint fptrReferenceId = 0; if ((_flags & MethodEntryFlags.SaveEntryPoint) != 0) @@ -208,15 +210,23 @@ public override Vertex WriteVertex(NodeFactory factory) return GetNativeWriter(factory).GetMethodSignature((uint)flags, fptrReferenceId, containingType, token, args); } - private static MethodDesc GetMethodForMetadata(MethodDesc method, out bool isAsyncVariant) + private static MethodDesc GetMethodForMetadata(MethodDesc method, out bool isAsyncVariant, out bool isReturnDroppingAsyncThunk) { MethodDesc result = method.GetTypicalMethodDefinition(); + if (result is ReturnDroppingAsyncThunk rdThunk) + { + isAsyncVariant = false; + isReturnDroppingAsyncThunk = true; + return rdThunk.AsyncVariantTarget.Target; + } if (result is AsyncMethodVariant asyncVariant) { isAsyncVariant = true; + isReturnDroppingAsyncThunk = false; return asyncVariant.Target; } isAsyncVariant = false; + isReturnDroppingAsyncThunk = false; return result; } diff --git a/src/coreclr/vm/asyncthunks.cpp b/src/coreclr/vm/asyncthunks.cpp index 30541cc83b94d0..3fa631527cf29c 100644 --- a/src/coreclr/vm/asyncthunks.cpp +++ b/src/coreclr/vm/asyncthunks.cpp @@ -621,6 +621,7 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pTaskReturningVariant, MetaSig // Provided an async variant, emits an async wrapper that drops the returned value. // Used in the covariant return scenario. +// The emitted code matches EmitReturnDroppingThunk in the Managed Type System. void MethodDesc::EmitReturnDroppingThunk(MethodDesc* pAsyncOtherVariant, MetaSig& msig, ILStubLinker* pSL) { _ASSERTE(pAsyncOtherVariant->IsAsyncVariantMethod()); diff --git a/src/tests/async/covariant-return/covariant-returns.cs b/src/tests/async/covariant-return/covariant-returns.cs index 9aaef5f91f7122..4f6df8b95be60e 100644 --- a/src/tests/async/covariant-return/covariant-returns.cs +++ b/src/tests/async/covariant-return/covariant-returns.cs @@ -176,3 +176,122 @@ public override async Task Foo() } } } + +namespace CovariantReturnWithoutRuntimeAsync +{ + public class Program + { + internal static int Result; + + [Fact] + public static void TestCovariantReturnWithoutRuntimeAsync() + { + Result = 0; + CallInstance(new Derived()).GetAwaiter().GetResult(); + Assert.Equal(42, Result); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallInstance(Base b) => await b.InstanceMethod(); + + public class Base + { + public virtual async Task InstanceMethod() + { + } + } + + public class Derived : Base + { + [RuntimeAsyncMethodGenerationAttribute(false)] + public override async Task InstanceMethod() + { + await Task.Yield(); + Result = 42; + return 42; + } + } + } +} + +namespace GenericVirtualMethod +{ + public class Program + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallInstance(Base b) => await b.InstanceMethod(); + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task CallInstanceValueType(Base b) => await b.InstanceMethod(); + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/127197", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNotNativeAot))] + public static void TestGenericVirtualMethod() + { + CallInstance(new Derived()).GetAwaiter().GetResult(); + CallInstanceValueType(new Derived()).GetAwaiter().GetResult(); + } + public class Base + { + public virtual async Task InstanceMethod() + { + } + } + public class Mid : Base + { + public override async Task InstanceMethod() + { + throw new Exception(); + } + } + public class Derived : Mid + { + public override async Task InstanceMethod() + { + int result = typeof(T).FullName.Length; + await Task.Yield(); + return result; + } + } + } +} + +namespace AsyncInterfaceGenericMethod +{ + public class Program + { + interface IFoo + { + Task AsyncInterfaceMethod(); + } + + class Foo : IFoo + { + async Task IFoo.AsyncInterfaceMethod() + { + await Task.Yield(); + return typeof(T).FullName.Length; + } + } + + static async Task Run() + { + IFoo f = new Foo(); + int x = await f.AsyncInterfaceMethod(); + Assert.Equal(typeof(object).FullName.Length, x); + } + + static async Task RunValueType() + { + IFoo f = new Foo(); + int x = await f.AsyncInterfaceMethod(); + Assert.Equal(typeof(int).FullName.Length, x); + } + + [Fact] + public static void TestAsyncInterfaceGenericMethod() + { + Run().GetAwaiter().GetResult(); + RunValueType().GetAwaiter().GetResult(); + } + } +} diff --git a/src/tests/async/covariant-return/covariant-returns.csproj b/src/tests/async/covariant-return/covariant-returns.csproj index eaf76ccb482db5..5610ae90aa882c 100644 --- a/src/tests/async/covariant-return/covariant-returns.csproj +++ b/src/tests/async/covariant-return/covariant-returns.csproj @@ -1,11 +1,10 @@ - - true false +