diff --git a/src/coreclr/inc/corhdr.h b/src/coreclr/inc/corhdr.h index 0bd7755e3b0d5e..01a2d602b2befd 100644 --- a/src/coreclr/inc/corhdr.h +++ b/src/coreclr/inc/corhdr.h @@ -975,7 +975,8 @@ typedef enum CorCallingConvention IMAGE_CEE_CS_CALLCONV_UNMANAGED = 0x9, // Unmanaged calling convention encoded as modopts IMAGE_CEE_CS_CALLCONV_GENERICINST = 0xa, // generic method instantiation IMAGE_CEE_CS_CALLCONV_NATIVEVARARG = 0xb, // used ONLY for 64bit vararg PInvoke calls - IMAGE_CEE_CS_CALLCONV_MAX = 0xc, // first invalid calling convention + IMAGE_CEE_CS_CALLCONV_ASYNC = 0xc, // used for calli in IL stubs + IMAGE_CEE_CS_CALLCONV_MAX = 0xd, // first invalid calling convention // The high bits of the calling convention convey additional info diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index bc7f0433287a10..e2882c95ee13f5 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -719,6 +719,11 @@ var_types Compiler::impImportCall(OPCODE opcode, JITDUMP(" Continuation continues on thread pool\n"); } } + else if (opcode == CEE_CALLI) + { + // Used for unboxing/instantiating stubs + JITDUMP("Call is an async calli\n"); + } else { JITDUMP("Call is an async non-task await\n"); @@ -941,7 +946,9 @@ var_types Compiler::impImportCall(OPCODE opcode, .WellKnown(WellKnownArg::VarArgsCookie)); } - if (call->AsCall()->IsAsync()) + // Add async continuation arg. For calli these are used for IL + // stubs and the VM inserts the arg itself. + if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI)) { call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); @@ -955,7 +962,9 @@ var_types Compiler::impImportCall(OPCODE opcode, } else { - if (call->AsCall()->IsAsync()) + // Add async continuation arg. For calli these are used for IL + // stubs and the VM inserts the arg itself. + if (call->AsCall()->IsAsync() && (opcode != CEE_CALLI)) { call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF) .WellKnown(WellKnownArg::AsyncContinuation)); diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 9ae7d10df0c9c5..3d4abbee5358d4 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -676,6 +676,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE // explicit arguments have been scanned is platform dependent. void GetThisLoc(ArgLocDesc * pLoc) { WRAPPER_NO_CONTRACT; GetSimpleLoc(GetThisOffset(), pLoc); } void GetParamTypeLoc(ArgLocDesc * pLoc) { WRAPPER_NO_CONTRACT; GetSimpleLoc(GetParamTypeArgOffset(), pLoc); } + void GetAsyncContinuationLoc(ArgLocDesc * pLoc) { WRAPPER_NO_CONTRACT; GetSimpleLoc(GetAsyncContinuationArgOffset(), pLoc); } void GetVASigCookieLoc(ArgLocDesc * pLoc) { WRAPPER_NO_CONTRACT; GetSimpleLoc(GetVASigCookieOffset(), pLoc); } #ifndef CALLDESCR_RETBUFFARGREG diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 28b75bdf0d6932..cc9535ca13f6ed 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -475,6 +475,18 @@ BOOL GenerateShuffleArrayPortable(MethodDesc* pMethodSrc, MethodDesc *pMethodDst #endif // !defined(TARGET_ARM64) || !defined(CALLDESCR_RETBUFFARGREG) } + // Handle async continuation argument. + _ASSERTE(!!sArgPlacerDst.HasAsyncContinuation() == !!sArgPlacerSrc.HasAsyncContinuation()); + if (sArgPlacerDst.HasAsyncContinuation()) + { + // The async continuation is implicit in both signatures. + sArgPlacerSrc.GetAsyncContinuationLoc(&sArgSrc); + sArgPlacerDst.GetAsyncContinuationLoc(&sArgDst); + + if (!AddNextShuffleEntryToArray(sArgSrc, sArgDst, pShuffleEntryArray, shuffleType)) + return FALSE; + } + // Iterate all the regular arguments. mapping source registers and stack locations to the corresponding // destination locations. while ((ofsSrc = sArgPlacerSrc.GetNextOffset()) != TransitionBlock::InvalidOffset) diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index 5aeeb2776aed3e..a5558713e17ee9 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -70,7 +70,7 @@ void CreateModuleIndependentSignature(LoaderHeap* pCreationHeap, // static MethodDesc* ILStubCache::CreateAndLinkNewILStubMethodDesc(LoaderAllocator* pAllocator, MethodTable* pMT, DWORD dwStubFlags, Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, SigTypeContext *pTypeContext, - ILStubLinker* pStubLinker) + ILStubLinker* pStubLinker, BOOL isAsync /* = FALSE */) { CONTRACT (MethodDesc*) { @@ -86,6 +86,7 @@ MethodDesc* ILStubCache::CreateAndLinkNewILStubMethodDesc(LoaderAllocator* pAllo dwStubFlags, pSigModule, pSig, cbSig, + isAsync, pTypeContext, &amTracker); @@ -143,7 +144,7 @@ namespace // static MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTable* pMT, DWORD dwStubFlags, - Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, SigTypeContext *pTypeContext, + Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, BOOL isAsync, SigTypeContext *pTypeContext, AllocMemTracker* pamTracker) { CONTRACT (MethodDesc*) @@ -160,7 +161,7 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, - FALSE /* HasAsyncMethodData */, + isAsync /* HasAsyncMethodData */, pMT, pamTracker); @@ -175,6 +176,13 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa pMD->InitializeFlags(DynamicMethodDesc::FlagPublic | DynamicMethodDesc::FlagIsILStub); pMD->SetTemporaryEntryPoint(pamTracker); + if (isAsync) + { + pMD->SetHasAsyncMethodData(); + // Async stubs are standalone methods that do not form async/task-returning variant pairs. + pMD->GetAddrOfAsyncMethodData()->kind = AsyncMethodKind::AsyncExplicitImpl; + } + // // convert signature to a compatible signature if needed // @@ -531,7 +539,8 @@ MethodDesc* ILStubCache::GetStubMethodDesc( } MethodTable *pStubMT = GetOrCreateStubMethodTable(pSigLoaderModule); - pMD = ILStubCache::CreateNewMethodDesc(m_pAllocator->GetHighFrequencyHeap(), pStubMT, dwStubFlags, pSigModule, pSig, cbSig, pTypeContext, pamTracker); + BOOL isAsync = pTargetMD == NULL ? FALSE: pTargetMD->IsAsyncMethod(); + pMD = ILStubCache::CreateNewMethodDesc(m_pAllocator->GetHighFrequencyHeap(), pStubMT, dwStubFlags, pSigModule, pSig, cbSig, isAsync, pTypeContext, pamTracker); if (SF_IsSharedStub(dwStubFlags)) { diff --git a/src/coreclr/vm/ilstubcache.h b/src/coreclr/vm/ilstubcache.h index eb2b22952754df..52be99d9fb8ef1 100644 --- a/src/coreclr/vm/ilstubcache.h +++ b/src/coreclr/vm/ilstubcache.h @@ -73,7 +73,8 @@ class ILStubCache final PCCOR_SIGNATURE pSig, DWORD cbSig, SigTypeContext *pTypeContext, - ILStubLinker* pStubLinker); + ILStubLinker* pStubLinker, + BOOL isAsync = FALSE); MethodTable * GetStubMethodTable() { @@ -97,6 +98,7 @@ class ILStubCache final Module* pSigModule, PCCOR_SIGNATURE pSig, DWORD cbSig, + BOOL isAsync, SigTypeContext *pTypeContext, AllocMemTracker* pamTracker); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 3b569d3c36310d..e79dd8e55861ab 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -478,19 +478,26 @@ static void ConvToJitSig( uint32_t data; IfFailThrow(sig.GetCallingConvInfo(&data)); - sigRet->callConv = (CorInfoCallConv) data; #if defined(TARGET_UNIX) || defined(TARGET_ARM) - if ((isCallConv(sigRet->callConv, IMAGE_CEE_CS_CALLCONV_VARARG)) || - (isCallConv(sigRet->callConv, IMAGE_CEE_CS_CALLCONV_NATIVEVARARG))) + if ((isCallConv(data, IMAGE_CEE_CS_CALLCONV_VARARG)) || + (isCallConv(data, IMAGE_CEE_CS_CALLCONV_NATIVEVARARG))) { // This signature corresponds to a method that uses varargs, which are not supported. COMPlusThrow(kInvalidProgramException, IDS_EE_VARARG_NOT_SUPPORTED); } #endif // defined(TARGET_UNIX) || defined(TARGET_ARM) + // We have an internal calling convention for async used for signatures + // in IL stubs. Translate that to the flag representation in + // CorInfoCallConv. + if (isCallConv(data, IMAGE_CEE_CS_CALLCONV_ASYNC)) + sigRet->callConv = (CorInfoCallConv)(CORINFO_CALLCONV_DEFAULT | CORINFO_CALLCONV_ASYNCCALL | (data & ~IMAGE_CEE_CS_CALLCONV_MASK)); + else + sigRet->callConv = (CorInfoCallConv) data; + // Skip number of type arguments - if (sigRet->callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) + if (data & IMAGE_CEE_CS_CALLCONV_GENERIC) IfFailThrow(sig.GetData(NULL)); uint32_t numArgs; @@ -1796,7 +1803,6 @@ CEEInfo::findSig( if (IsDynamicScope(scopeHnd)) { sig = GetDynamicResolver(scopeHnd)->ResolveSignature(sigTok); - sigTok = mdTokenNil; } else { diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index f9da780c74c417..dccce9b985229c 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1394,9 +1394,9 @@ PrepareCodeConfigBuffer::PrepareCodeConfigBuffer(NativeCodeVersion codeVersion) // CreateDerivedTargetSigWithExtraParams: // This method is used to create the signature of the target of the ILStub for -// instantiating, unboxing, and async variant stubs, when/where we need to -// introduce a generic context/async continuation. -// And since the generic context/async continuations are hidden parameters, +// instantiating and unboxing stubs, when/where we need to +// introduce a generic context. +// And since the generic contexts are hidden parameters, // we're creating a signature that looks like non-generic but with additional // parameters right after the thisptr void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder *stubSigBuilder) @@ -1404,6 +1404,8 @@ void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder STANDARD_VM_CONTRACT; BYTE callingConvention = IMAGE_CEE_CS_CALLCONV_DEFAULT; + if (msig.HasAsyncContinuation()) + callingConvention = IMAGE_CEE_CS_CALLCONV_ASYNC; if (msig.HasThis()) callingConvention |= IMAGE_CEE_CS_CALLCONV_HASTHIS; // CallingConvention @@ -1475,9 +1477,6 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM _ASSERTE(msig.HasThis()); - // TODO: (async) instantiating/unboxing stubs https://github.com/dotnet/runtime/issues/117266 - _ASSERTE(!msig.HasAsyncContinuation()); - ILStubLinker sl(pTargetMD->GetModule(), pTargetMD->GetSignature(), &typeContext, @@ -1499,12 +1498,17 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM pCode->EmitLoadThis(); pCode->EmitLDFLDA(tokRawData); -#if defined(TARGET_X86) +#ifdef TARGET_X86 // Push the rest of the arguments for x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { pCode->EmitLDARG(i); } + + if (msig.HasAsyncContinuation()) + { + pCode->EmitLDNULL(); + } #endif // Push the hidden context param @@ -1515,7 +1519,12 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM pCode->EmitSUB(); pCode->EmitLDIND_I(); -#if !defined(TARGET_X86) +#ifndef TARGET_X86 + if (msig.HasAsyncContinuation()) + { + pCode->EmitLDNULL(); + } + // Push the rest of the arguments for not x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { @@ -1540,7 +1549,8 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM pTargetMD->GetModule(), pSig, cbSig, &typeContext, - &sl); + &sl, + pTargetMD->IsAsyncMethod()); ILStubResolver *pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver(); @@ -1593,9 +1603,6 @@ Stub * CreateInstantiatingILStub(MethodDesc* pTargetMD, void* pHiddenArg) ILCodeStream *pCode = sl.NewCodeStream(ILStubLinker::kDispatch); - // TODO: (async) instantiating/unboxing stubs https://github.com/dotnet/runtime/issues/117266 - _ASSERTE(!msig.HasAsyncContinuation()); - // Build the new signature SigBuilder stubSigBuilder; MethodDesc::CreateDerivedTargetSigWithExtraParams(msig, &stubSigBuilder); @@ -1607,25 +1614,37 @@ Stub * CreateInstantiatingILStub(MethodDesc* pTargetMD, void* pHiddenArg) pCode->EmitLoadThis(); } -#if defined(TARGET_X86) +#ifdef TARGET_X86 // Push the rest of the arguments for x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { pCode->EmitLDARG(i); } -#endif // TARGET_X86 + if (msig.HasAsyncContinuation()) + { + pCode->EmitLDNULL(); + } + + // Push the hidden context param + // InstantiatingStub + pCode->EmitLDC((TADDR)pHiddenArg); +#else // Push the hidden context param // InstantiatingStub pCode->EmitLDC((TADDR)pHiddenArg); -#if !defined(TARGET_X86) - // Push the rest of the arguments for not x86 + if (msig.HasAsyncContinuation()) + { + pCode->EmitLDNULL(); + } + + // Push the rest of the arguments for x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { pCode->EmitLDARG(i); } -#endif // !TARGET_X86 +#endif // Push the target address pCode->EmitLDC((TADDR)pTargetMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY)); @@ -1644,7 +1663,8 @@ Stub * CreateInstantiatingILStub(MethodDesc* pTargetMD, void* pHiddenArg) pTargetMD->GetModule(), pSig, cbSig, &typeContext, - &sl); + &sl, + pTargetMD->IsAsyncMethod()); ILStubResolver *pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver(); diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 70e0c9d4751ab7..e23a17418e89f1 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -185,7 +185,7 @@ void SigPointer::ConvertToInternalExactlyOne(Module* pSigModule, const SigTypeCo { mdToken tk; IfFailThrowBF(GetToken(&tk), BFA_BAD_COMPLUS_SIG, pSigModule); - TypeHandle th = ClassLoader::LoadTypeDefOrRefThrowing(pSigModule, tk); + TypeHandle th = ClassLoader::LoadTypeDefOrRefThrowing(pSigModule, tk, ClassLoader::ThrowIfNotFound, ClassLoader::PermitUninstDefOrRef); pSigBuilder->AppendElementType(ELEMENT_TYPE_CMOD_INTERNAL); pSigBuilder->AppendByte(typ == ELEMENT_TYPE_CMOD_REQD); // "is required" byte pSigBuilder->AppendPointer(th.AsPtr()); diff --git a/src/tests/async/inst-unbox-thunks/inst-unbox-thunks.cs b/src/tests/async/inst-unbox-thunks/inst-unbox-thunks.cs new file mode 100644 index 00000000000000..a3e82e3aa89d22 --- /dev/null +++ b/src/tests/async/inst-unbox-thunks/inst-unbox-thunks.cs @@ -0,0 +1,140 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Xunit; + +public class InstUnBoxThunks +{ + interface I0 + { + Task M0(); + Task M1(object a0, object a1, object a2, object a3, object a4, object a5, object a6, object a7, object a8); + } + + struct Struct0 : I0 + { + public async Task M0() + { + await Task.Yield(); + return "hi"; + } + + public async Task M1(object a0, object a1, object a2, object a3, object a4, object a5, object a6, object a7, object a8) + { + await Task.Yield(); + return "hello"; + } + } + + static I0 o00; + static async Task CallStruct0M0() + { + o00 = new Struct0(); + return await o00.M0(); + } + + static I0 o01; + static async Task CallStruct0M1() + { + o01 = new Struct0(); + return await o01.M1(default, default, default, default, default, default, default, default, default); + } + + struct Struct1 : I0 + { + public async Task M0() + { + await Task.Yield(); + return typeof(T).ToString(); + } + + public async Task M1(object a0, object a1, object a2, object a3, object a4, object a5, object a6, object a7, object a8) + { + await Task.Yield(); + return typeof(T).ToString(); + } + } + + static I0 o10; + static async Task CallStruct1M0() + { + o10 = new Struct1(); + return await o10.M0(); + } + + static I0 o11; + static async Task CallStruct1M1() + { + o11 = new Struct1(); + return await o11.M1(default, default, default, default, default, default, default, default, default); + } + + class Box where U : I0 + { + public U f; + } + + static async Task CallStruct1M0Field(Box arg) where T : I0 + { + return await arg.f.M0(); + } + + static async Task CallStruct1M1Field(Box arg) where T : I0 + { + return await arg.f.M1(default, default, default, default, default, default, default, default, default); + } + + static Box> b1 = new(); + static async Task CallStruct1M0b() + { + b1.f = new Struct1(); + return await CallStruct1M0Field(b1); + } + + static Box> b2 = new(); + static async Task CallStruct1M1b() + { + b2.f = new Struct1(); + return await CallStruct1M1Field(b2); + } + + + [Fact] + public static void NoArgUnbox() + { + Assert.Equal("hi", CallStruct0M0().Result); + } + + [Fact] + public static void ManyArgUnbox() + { + Assert.Equal("hello", CallStruct0M1().Result); + } + + [Fact] + public static void NoArgGenericUnbox() + { + Assert.Equal("System.String", CallStruct1M0().Result); + } + + [Fact] + public static void ManyArgGenericUnbox() + { + Assert.Equal("System.String", CallStruct1M1().Result); + } + + [Fact] + public static void NoArgGenericInstantiating() + { + Assert.Equal("System.String", CallStruct1M0b().Result); + } + + [Fact] + public static void ManyArgGenericInstantiating() + { + Assert.Equal("System.String", CallStruct1M1b().Result); + } +} diff --git a/src/tests/async/inst-unbox-thunks/inst-unbox-thunks.csproj b/src/tests/async/inst-unbox-thunks/inst-unbox-thunks.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/inst-unbox-thunks/inst-unbox-thunks.csproj @@ -0,0 +1,8 @@ + + + True + + + + +