diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 64169a699065f4..b1ff2b91809d3c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -664,6 +664,61 @@ private static unsafe T TransparentAwaitOfT(Task task) return default!; } + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + private static void TransparentAwaitWithResult(Task task) + { + if (!task.IsCompleted) + { + TailAwait(); + TransparentAwait(task); + return; + } + + TaskAwaiter.ValidateEnd(task); + } + + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + private static void TransparentAwaitWithResult(ValueTask task) + { + if (!task.IsCompleted) + { + TailAwait(); + TransparentAwaitValueTask(task); + return; + } + + task.ThrowIfCompletedUnsuccessfully(); + } + + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + private static T TransparentAwaitWithResult(Task task) + { + if (!task.IsCompleted) + { + TailAwait(); + return TransparentAwaitOfT(task); + } + + TaskAwaiter.ValidateEnd(task); + return task.ResultOnSuccess; + } + + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + private static T TransparentAwaitWithResult(ValueTask task) + { + if (!task.IsCompleted) + { + TailAwait(); + return TransparentAwaitValueTaskOfT(task); + } + + return task.Result; + } + // Represents execution of a chain of suspended and resuming runtime // async functions. private sealed class RuntimeAsyncTask : Task diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 5ac1524d19c6f1..aa0f7a1ed7b6a3 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -716,6 +716,7 @@ enum CorInfoOptions CORINFO_GENERICS_CTXT_FROM_METHODTABLE), CORINFO_GENERICS_CTXT_KEEP_ALIVE = 0x00000100, // Keep the generics context alive throughout the method even if there is no explicit use, and report its location to the CLR CORINFO_ASYNC_SAVE_CONTEXTS = 0x00000200, // Runtime async method must save and restore contexts + CORINFO_ASYNC_VERSION = 0x00000400, // This is an async version whose IL belongs to a non-async method }; // @@ -3139,6 +3140,10 @@ class ICorStaticInfo CORINFO_ASYNC_INFO* pAsyncInfoOut ) = 0; + // Get information about which await call to use to await the return type + // of the non-async version of an async call. + virtual CORINFO_METHOD_HANDLE getAwaitReturnCall(CORINFO_METHOD_HANDLE callerHandle, CORINFO_LOOKUP* instArg) = 0; + /*********************************************************************************/ // // Diagnostic methods diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 8fc80cda20018c..3c5a48ca5c3b3e 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -507,6 +507,10 @@ void getEEInfo( void getAsyncInfo( CORINFO_ASYNC_INFO* pAsyncInfoOut) override; +CORINFO_METHOD_HANDLE getAwaitReturnCall( + CORINFO_METHOD_HANDLE callerHandle, + CORINFO_LOOKUP* instArg) override; + mdMethodDef getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 3f1d8e566f18f4..12347eb227a951 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 31a04b06-915e-42a0-bbd2-c9c397677ae5 */ - 0x31a04b06, - 0x915e, - 0x42a0, - {0xbb, 0xd2, 0xc9, 0xc3, 0x97, 0x67, 0x7a, 0xe5} +constexpr GUID JITEEVersionIdentifier = { /* dcace1ba-8b1e-45ad-890f-055c4e190ddb */ + 0xdcace1ba, + 0x8b1e, + 0x45ad, + {0x89, 0x0f, 0x05, 0x5c, 0x4e, 0x19, 0x0d, 0xdb} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 7a1a9d7d84d1e1..18072ed212de8c 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2257,6 +2257,13 @@ bool InterpCompiler::CompileMethod() { INTERP_DUMP("Method is async with context save/restore\n"); } + + m_isAsyncVersionOfSyncMethod = m_methodInfo->options & CORINFO_ASYNC_VERSION; + if (m_isAsyncVersionOfSyncMethod) + { + INTERP_DUMP("Method is an async version of a synchronous method; IL belongs to synchronous method\n"); + } + CreateILVars(); GenerateCode(m_methodInfo); @@ -4605,7 +4612,13 @@ static OpcodePeepElement peepRuntimeAsyncCallConfigureAwaitValueTask_EXACT_L[] = // LDC_I4_0 or LDC_I4_1 goes here (at offset 10) { 11, CEE_CALL}, { 16, CEE_CALL }, - { 19, CEE_ILLEGAL } // End marker + { 21, CEE_ILLEGAL } // End marker +}; + +static OpcodePeepElement peepRuntimeAsyncCallRetInAsyncVersion[] = { + // call or callvirt at the start + { 5, CEE_RET }, + { 6, CEE_ILLEGAL } // End marker }; class InterpAsyncCallPeeps @@ -4618,9 +4631,10 @@ class InterpAsyncCallPeeps OpcodePeep peepCallConfigureAwaitValueTask_S_S = { peepRuntimeAsyncCallConfigureAwaitValueTask_S_S, &InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask, &InterpCompiler::ApplyRuntimeAsyncCall, "CallConfigureAwaitValueTask_S_S" }; OpcodePeep peepCallConfigureAwaitValueTask_EXACT_S = { peepRuntimeAsyncCallConfigureAwaitValueTask_EXACT_S, &InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc, &InterpCompiler::ApplyRuntimeAsyncCall, "CallConfigureAwaitValueTask_EXACT_S" }; OpcodePeep peepCallConfigureAwaitValueTask_EXACT_L = { peepRuntimeAsyncCallConfigureAwaitValueTask_EXACT_L, &InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc, &InterpCompiler::ApplyRuntimeAsyncCall, "CallConfigureAwaitValueTask_EXACT_L" }; + OpcodePeep peepCallRetInAsyncVersion = { peepRuntimeAsyncCallRetInAsyncVersion, &InterpCompiler::IsRuntimeAsyncCallRetInAsyncVersion, &InterpCompiler::ApplyRuntimeAsyncCallRetInAsyncVersion, "CallRetInAsyncVersion" }; public: - OpcodePeep* Peeps[9] = { + OpcodePeep* Peeps[10] = { &peepCall, &peepCallConfigureAwaitTask, &peepCallConfigureAwaitValueTask_L_L, @@ -4629,6 +4643,7 @@ class InterpAsyncCallPeeps &peepCallConfigureAwaitValueTask_S_S, &peepCallConfigureAwaitValueTask_EXACT_S, &peepCallConfigureAwaitValueTask_EXACT_L, + &peepCallRetInAsyncVersion, NULL }; @@ -4914,7 +4929,9 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re } else { - const uint8_t* origIP = m_ip; + // We expect this bool to be reset when we handle the RET instruction after it gets set. + assert(!m_matchedAsyncCallRetInAsyncVersion); + if (!newObj && m_methodInfo->args.isAsyncCall() && AsyncCallPeeps.FindAndApplyPeep(this)) { resolvedCallToken = m_resolvedAsyncCallToken; @@ -4945,26 +4962,6 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re BADCODE("We're trying to emit an async call, but the async resolved context didn't find one"); } - if (continuationContextHandling != ContinuationContextHandling::None && callInfo.kind == CORINFO_CALL) - { - bool isSyncCallThunk; - m_compHnd->getAsyncOtherVariant(callInfo.hMethod, &isSyncCallThunk); - if (!isSyncCallThunk) - { - // The async variant that we got is a thunk. Switch back to the - // non-async task-returning call. There is no reason to create - // and go through the thunk. - ResolveToken(token, CORINFO_TOKENKIND_Method, &resolvedCallToken); - - // Reset back to the IP after the original call instruction. - // FindAndApplyPeep above modified it to be after the "Await" - // call, but now we want to process the await separately. - m_ip = origIP + 5; - continuationContextHandling = ContinuationContextHandling::None; - m_compHnd->getCallInfo(&resolvedCallToken, pConstrainedToken, m_methodInfo->ftn, flags, &callInfo); - } - } - if (callInfo.sig.isVarArg()) { BADCODE("Vararg methods are not supported in interpreted code"); @@ -5333,7 +5330,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re if (continuationArgLocation != INT_MAX) { - PushStackType(StackTypeI, NULL); + PushStackType(StackTypeO, NULL); m_pStackPointer--; int32_t continuationArg = m_pStackPointer[0].var; @@ -5697,7 +5694,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re if (callInfo.sig.isAsyncCall() && m_methodInfo->args.isAsyncCall()) // Async2 functions may need to suspend { - EmitSuspend(callInfo, continuationContextHandling); + EmitSuspend(callInfo.sig.retType, continuationContextHandling); } } @@ -5706,6 +5703,30 @@ void InterpCompiler::EmitRet(CORINFO_METHOD_INFO* methodInfo) CORINFO_SIG_INFO sig = methodInfo->args; InterpType retType = GetInterpType(sig.retType); + if (m_isAsyncVersionOfSyncMethod) + { + if (m_matchedAsyncCallRetInAsyncVersion) + { + m_matchedAsyncCallRetInAsyncVersion = false; + + if (retType == InterpTypeVoid && m_pStackPointer > m_pStackBase) + { + INTERP_DUMP("Popping stack for Task in void-returning async version\n"); + // For covariant cases like a tailcall to a Task-returning + // callee from a Task-returning function we will have a + // superfluous value on the stack after the call. + m_pStackPointer--; + } + } + else + { + CheckStackExact(1); + + INTERP_DUMP("Wrapping top of stack in await\n"); + WrapTopOfStackInAwait(); + } + } + if ((retType != InterpTypeVoid) && (retType != InterpTypeByRef)) { CheckStackExact(1); @@ -5805,7 +5826,98 @@ void InterpCompiler::EmitRet(CORINFO_METHOD_INFO* methodInfo) } } -void SetSlotToTrue(TArray &gcRefMap, int32_t slotOffset) +void InterpCompiler::WrapTopOfStackInAwait() +{ + CORINFO_LOOKUP instArgLookup; + CORINFO_METHOD_HANDLE awaitMethod = m_compHnd->getAwaitReturnCall(m_methodHnd, &instArgLookup); + + CORINFO_SIG_INFO awaitSig; + m_compHnd->getMethodSig(awaitMethod, &awaitSig); + + assert(awaitSig.isAsyncCall()); + assert(awaitSig.numArgs == 1); + assert(!awaitSig.hasThis()); + + // Pop the awaitable off the stack; it becomes the last argument. + int32_t awaitableVar = m_pStackPointer[-1].var; + m_pStackPointer--; + + int extraParamArgLocation = awaitSig.hasTypeArg() ? 0 : INT_MAX; + int continuationArgLocation = awaitSig.hasTypeArg() ? 1 : 0; + int numArgs = 1 + (awaitSig.hasTypeArg() ? 1 : 0) + 1; // task + (optional inst) + continuation + + int32_t* callArgs = getAllocator(IMK_CallInfo).allocate(numArgs + 1); + + if (awaitSig.hasTypeArg()) + { + int32_t instParamVar; + if (instArgLookup.lookupKind.needsRuntimeLookup) + { + EmitPushCORINFO_LOOKUP(instArgLookup); + m_pStackPointer--; + instParamVar = m_pStackPointer[0].var; + } + else + { + assert(instArgLookup.constLookup.accessType == IAT_VALUE); + PushStackType(StackTypeI, NULL); + m_pStackPointer--; + instParamVar = m_pStackPointer[0].var; + AddIns(INTOP_LDPTR); + m_pLastNewIns->SetDVar(instParamVar); + m_pLastNewIns->data[0] = GetDataItemIndex(instArgLookup.constLookup.handle); + } + callArgs[extraParamArgLocation] = instParamVar; + } + + // Continuation arg: pass null; the suspend logic emitted below sets it up. + PushStackType(StackTypeO, NULL); + m_pStackPointer--; + int32_t continuationArg = m_pStackPointer[0].var; + AddIns(INTOP_LDNULL); + m_pLastNewIns->SetDVar(continuationArg); + callArgs[continuationArgLocation] = continuationArg; + + callArgs[numArgs - 1] = awaitableVar; + callArgs[numArgs] = CALL_ARGS_TERMINATOR; + + // Result var. Must be on top of the stack so EmitSuspend treats it as the + // return value of the call (and not as an unrelated live stack var). + int32_t dVar; + if (awaitSig.retType != CORINFO_TYPE_VOID) + { + InterpType interpType = GetInterpType(awaitSig.retType); + if (interpType == InterpTypeVT) + { + int32_t size = m_compHnd->getClassSize(awaitSig.retTypeClass); + PushTypeVT(awaitSig.retTypeClass, size); + } + else + { + PushInterpType(interpType, NULL); + } + dVar = m_pStackPointer[-1].var; + } + else + { + // Create a new dummy var to serve as the dVar of the call. + PushStackType(StackTypeI4, NULL); + m_pStackPointer--; + dVar = m_pStackPointer[0].var; + } + + AddIns(INTOP_CALL); + m_pLastNewIns->data[0] = GetMethodDataItemIndex(awaitMethod); + m_pLastNewIns->SetDVar(dVar); + m_pLastNewIns->SetSVar(CALL_ARGS_SVAR); + m_pLastNewIns->flags |= INTERP_INST_FLAG_CALL; + m_pLastNewIns->info.pCallInfo = new (getAllocator(IMK_CallInfo)) InterpCallInfo(); + m_pLastNewIns->info.pCallInfo->pCallArgs = callArgs; + + EmitSuspend(awaitSig.retType, ContinuationContextHandling::None); +} + +static void SetSlotToTrue(TArray &gcRefMap, int32_t slotOffset) { assert(slotOffset % sizeof(void*) == 0); int32_t slotIndex = slotOffset / sizeof(void*); @@ -5819,7 +5931,7 @@ void SetSlotToTrue(TArray &gcRefMap, int32_t slotOffset) gcRefMap.Set(slotIndex, true); } -void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, ContinuationContextHandling continuationContextHandling) +void InterpCompiler::EmitSuspend(CorInfoType callRetType, ContinuationContextHandling continuationContextHandling) { if (m_nextAwaitIsTail) { @@ -5869,7 +5981,7 @@ void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, Continuation // Step 2: Handle live stack vars (excluding return value) int32_t stackDepth = (int32_t)(m_pStackPointer - m_pStackBase); int32_t returnValueVar = -1; - if (stackDepth > 0 && callInfo.sig.retType != CORINFO_TYPE_VOID) + if (stackDepth > 0 && callRetType != CORINFO_TYPE_VOID) { // The return value var is written explicitly during resume, it is not included in the set of liveVars returnValueVar = m_pStackPointer[-1].var; @@ -6289,7 +6401,7 @@ void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, Continuation // Once we've resumed, if the return type is a primitive integral type which // isn't an I4/U4 but is represented as such on the evaluation stack, sign/zero // extend it to the proper size. - switch (callInfo.sig.retType) + switch (callRetType) { case CORINFO_TYPE_UBYTE: case CORINFO_TYPE_BOOL: @@ -7411,6 +7523,29 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip return ResolveAsyncCallToken(ip); } +bool InterpCompiler::IsRuntimeAsyncCallRetInAsyncVersion(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) +{ + if (!m_isAsyncVersionOfSyncMethod) + { + return false; + } + + if (!ResolveAsyncCallToken(ip)) + { + return false; + } + + m_currentContinuationContextHandling = ContinuationContextHandling::None; + m_matchedAsyncCallRetInAsyncVersion = true; + return true; +} + +int InterpCompiler::ApplyRuntimeAsyncCallRetInAsyncVersion(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo) +{ + // Only skip the call, not the RET we matched. + return 5; +} + int InterpCompiler::ApplyConvRUnR4Peep(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo) { // Replace with CONV_R4_UN diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 3b5acb11c47a3a..42b4d50b494270 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -694,6 +694,7 @@ class InterpCompiler // directly returns the continuation of the call instead of creating a new // suspension point. bool m_nextAwaitIsTail = false; + bool m_matchedAsyncCallRetInAsyncVersion = false; // Table of mappings of leave instructions to the first finally call island the leave // needs to execute. @@ -899,6 +900,7 @@ class InterpCompiler void *m_asyncResumeFuncPtr = NULL; bool m_isAsyncMethodWithContextSaveRestore = false; + bool m_isAsyncVersionOfSyncMethod = false; int32_t m_asyncFinallyStartOffset = -1; // If the method is async, this is the offset of the start of the fault handler bool m_shadowCopyOfThisPointerActuallyNeeded = false; @@ -983,8 +985,10 @@ class InterpCompiler bool IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo); bool IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo); bool IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo); + bool IsRuntimeAsyncCallRetInAsyncVersion(const uint8_t* ip, OpcodePeepElement* peep, void** computedInfo); int ApplyRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo) { return -1; } + int ApplyRuntimeAsyncCallRetInAsyncVersion(const uint8_t* ip, OpcodePeepElement* peep, void* computedInfo); ContinuationContextHandling m_currentContinuationContextHandling = ContinuationContextHandling::None; CORINFO_RESOLVED_TOKEN m_resolvedAsyncCallToken; @@ -997,8 +1001,9 @@ class InterpCompiler void EmitShiftOp(int32_t opBase); void EmitCompareOp(int32_t opBase); void EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool readonly, bool tailcall, bool newObj, bool isCalli); + void WrapTopOfStackInAwait(); void EmitRet(CORINFO_METHOD_INFO* methodInfo); - void EmitSuspend(const CORINFO_CALL_INFO &callInfo, ContinuationContextHandling ContinuationContextHandling); + void EmitSuspend(CorInfoType callRetType, ContinuationContextHandling ContinuationContextHandling); void EmitCalli(bool isTailCall, void* calliCookie, int callIFunctionPointerVar, CORINFO_SIG_INFO* callSiteSig); bool EmitNamedIntrinsicCall(NamedIntrinsic ni, bool nonVirtualCall, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig); void EmitLdind(InterpType type, CORINFO_CLASS_HANDLE clsHnd, int32_t offset); diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index 57781f560ce0d5..ea2e65c04ba71a 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -126,6 +126,7 @@ DEF_CLR_API(runWithErrorTrap) DEF_CLR_API(runWithSPMIErrorTrap) DEF_CLR_API(getEEInfo) DEF_CLR_API(getAsyncInfo) +DEF_CLR_API(getAwaitReturnCall) DEF_CLR_API(getMethodDefFromMethod) DEF_CLR_API(printMethodName) DEF_CLR_API(getMethodNameFromMetadata) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 87e541b608f155..e0d783dc6b0ccb 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1200,6 +1200,16 @@ void WrapICorJitInfo::getAsyncInfo( API_LEAVE(getAsyncInfo); } +CORINFO_METHOD_HANDLE WrapICorJitInfo::getAwaitReturnCall( + CORINFO_METHOD_HANDLE callerHandle, + CORINFO_LOOKUP* instArg) +{ + API_ENTER(getAwaitReturnCall); + CORINFO_METHOD_HANDLE temp = wrapHnd->getAwaitReturnCall(callerHandle, instArg); + API_LEAVE(getAwaitReturnCall); + return temp; +} + mdMethodDef WrapICorJitInfo::getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index a786daa966f13b..ade92b70e16707 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -3138,6 +3138,11 @@ void Compiler::compInitOptions(JitFlags* jitFlags) { printf("OPTIONS: compilation is an async state machine\n"); } + + if (compIsAsyncVersion()) + { + printf("OPTIONS: compilation IL belongs to synchronous version\n"); + } } #endif diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index b25ce125243fa1..cba74e4ce24880 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4938,6 +4938,7 @@ class Compiler // This call is a task await PREFIX_IS_TASK_AWAIT = 0x00000080, PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT = 0x00000100, + PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT = 0x00000200, }; static void impValidateMemoryAccessOpcode(const BYTE* codeAddr, const BYTE* codeEndp, bool volatilePrefix); @@ -5626,6 +5627,7 @@ class Compiler void impLoadArg(unsigned ilArgNum, IL_OFFSET offset); void impLoadLoc(unsigned ilLclNum, IL_OFFSET offset); bool impReturnInstruction(int prefixFlags, OPCODE& opcode); + void impWrapTopOfStackInAwait(); void impPoisonImplicitByrefsBeforeReturn(); // A free list of linked list nodes used to represent to-do stacks of basic blocks. @@ -11789,11 +11791,18 @@ class Compiler #endif // TARGET_AMD64 } + // Does this function have async calling convention and can have suspension points? bool compIsAsync() const { return opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ASYNC); } + // Is this the async version of a non-async method? IL belongs to non-async method. + bool compIsAsyncVersion() const + { + return (info.compMethodInfo->options & CORINFO_ASYNC_VERSION) != 0; + } + //------------------------------------------------------------------------ // compMethodReturnsMultiRegRetType: Does this method return a multi-reg value? // diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 082d86cedd93f8..ff78dd9a429810 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9029,22 +9029,41 @@ void Compiler::impImportBlockCode(BasicBlock* block) int configVal = -1; // -1 not configured, 0/1 configured to false/true const BYTE* codeAddrAfterMatch = nullptr; IL_OFFSET awaitOffset = BAD_IL_OFFSET; + + if (compIsAsyncVersion()) + { + if ((codeAddr + sz < codeEndp) && (getU1LittleEndian(codeAddr + sz) == CEE_RET)) + { + JITDUMP("\nRecognized tail-call in async version\n"); + awaitOffset = (IL_OFFSET)(codeAddr - 1 - info.compCode); + isAwait = true; + prefixFlags |= PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT; + + // Consume the ret opcode. Note `codeAddr` points at the unconsumed token; + // the main loop will still do `codeAddr += sz` (token size) after this case. + codeAddrAfterMatch = codeAddr + 1; + } + } + else + { #ifdef DEBUG - if (compIsAsync() && JitConfig.JitOptimizeAwait()) + if (compIsAsync() && JitConfig.JitOptimizeAwait()) #else - if (compIsAsync()) + if (compIsAsync()) #endif - { - codeAddrAfterMatch = impMatchTaskAwaitPattern(codeAddr, codeEndp, &configVal, &awaitOffset); - if (codeAddrAfterMatch != nullptr) { - JITDUMP("Recognized await%s\n", configVal == 0 ? " (with ConfigureAwait(false))" : ""); - - isAwait = true; - prefixFlags |= PREFIX_IS_TASK_AWAIT; - if (configVal != 0) + codeAddrAfterMatch = impMatchTaskAwaitPattern(codeAddr, codeEndp, &configVal, &awaitOffset); + if (codeAddrAfterMatch != nullptr) { - prefixFlags |= PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT; + JITDUMP("\nRecognized await%s\n", + configVal == 0 ? " (with ConfigureAwait(false))" : ""); + + isAwait = true; + prefixFlags |= PREFIX_IS_TASK_AWAIT; + if (configVal != 0) + { + prefixFlags |= PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT; + } } } } @@ -9059,10 +9078,11 @@ void Compiler::impImportBlockCode(BasicBlock* block) // It can also happen generally if the VM does not think using the async entry point // is worth it. Treat these as a regular call that is Awaited. _impResolveToken(CORINFO_TOKENKIND_Method); - prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT); + prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT | + PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT); isAwait = false; - JITDUMP("No async variant provided by VM, treating as regular call that is awaited\n"); + JITDUMP("\nNo async variant provided by VM, treating as regular call that is awaited\n"); } } else @@ -9080,7 +9100,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags, &callInfo); - if (isAwait && (callInfo.kind == CORINFO_CALL)) + // TODO: crossgen2 cannot handle us removing this + if (isAwait && IsReadyToRun() && (callInfo.kind == CORINFO_CALL)) { assert(callInfo.sig.isAsyncCall()); bool isSyncCallThunk; @@ -9091,11 +9112,10 @@ void Compiler::impImportBlockCode(BasicBlock* block) // back to the non-async task-returning call. There // is no reason to go through the thunk. _impResolveToken(CORINFO_TOKENKIND_Method); - prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT); + prefixFlags &= ~(PREFIX_IS_TASK_AWAIT | PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT | + PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT); isAwait = false; - JITDUMP( - "Async variant provided by VM is a thunk, switching direct call to synchronous task-returning method\n"); eeGetCallInfo(&resolvedToken, (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, flags, &callInfo); @@ -9274,9 +9294,31 @@ void Compiler::impImportBlockCode(BasicBlock* block) return; } - if (explicitTailCall || newBBcreatedForTailcallStress) // If newBBcreatedForTailcallStress is true, we - // have created a new BB after the "call" - // instruction in fgMakeBasicBlocks(). So we need to jump to RET regardless. + // For tail awaits we also import the ret right after. + // Also, we may have covariant cases like Task return from a Task method, + // and in those cases we end up with an extra IL stack entry here. + if ((prefixFlags & PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT) != 0) + { + if ((info.compRetType == TYP_VOID) && (stackState.esStackDepth > 0)) + { + JITDUMP("\nHave extra IL stack entry after tail await\n"); + GenTree* val = impPopStack().val; + if (varTypeIsStruct(val)) + { + val = impNormStructVal(val, CHECK_SPILL_ALL); + } + + impAppendTree(gtUnusedValNode(val), CHECK_SPILL_ALL, impCurStmtDI); + } + + goto RET; + } + + // For explicit tailcalls import the ret as part of it. + // If newBBcreatedForTailcallStress is true we have created a + // new BB after the "call" instruction in fgMakeBasicBlocks(). + // So we need to jump to RET regardless. + if (explicitTailCall || newBBcreatedForTailcallStress) { assert(!compIsForInlining()); goto RET; @@ -11219,7 +11261,7 @@ GenTree* Compiler::impStoreMultiRegValueToVar(GenTree* op, // // Arguments: // prefixFlags -- active IL prefixes -// opcode -- [in, out] IL opcode +// opcode -- [in, out] IL opcode // // Returns: // True if import was successful (may fail for some inlinees) @@ -11247,6 +11289,17 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) GenTree* op2 = nullptr; GenTree* op1 = nullptr; + if (((prefixFlags & PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT) == 0) && compIsAsyncVersion()) + { + JITDUMP("\nWrapping return value in await\n"); + impWrapTopOfStackInAwait(); + + if (info.compRetType == TYP_VOID) + { + impAppendTree(impPopStack().val, CHECK_SPILL_ALL, impCurStmtDI); + } + } + if (info.compRetType != TYP_VOID) { op2 = impPopStack().val; @@ -11593,6 +11646,106 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) return true; } +//------------------------------------------------------------------------ +// impWrapTopOfStackInAwait: +// Wrap the value on the top of the stack in AsyncHelpers.TransparentAwait. +// +// Remarks: +// Async versions of non-async task-returning methods are compiled with the +// exact same IL as the original method. This means the return value is +// mistyped; the original IL returns a Task or ValueTask, but the runtime +// async version expects to return the unwrapped result. This function +// accomplishes the unwrapping by inserting an async call to +// AsyncHelpers.TransparentAwait around the value on the top of the stack. +// +void Compiler::impWrapTopOfStackInAwait() +{ + CORINFO_LOOKUP instArgLookup; + CORINFO_METHOD_HANDLE awaitMethod = info.compCompHnd->getAwaitReturnCall(info.compMethodHnd, &instArgLookup); + + CORINFO_SIG_INFO awaitSig; + info.compCompHnd->getMethodSig(awaitMethod, &awaitSig); + + assert(awaitSig.isAsyncCall()); + + var_types callRetType = JITtype2varType(awaitSig.retType); + GenTreeCall* awaitCall = gtNewCallNode(CT_USER_FUNC, awaitMethod, callRetType); + CORINFO_CLASS_HANDLE taskTypeHnd; + CorInfoType taskType = strip(info.compCompHnd->getArgType(&awaitSig, awaitSig.args, &taskTypeHnd)); + + GenTree* awaitable = impPopStack().val; + var_types taskJitType = JITtype2varType(taskType); + NewCallArg taskArg; + if (taskJitType == TYP_STRUCT) + { + awaitable = impNormStructVal(awaitable, CHECK_SPILL_ALL); + taskArg = NewCallArg::Struct(awaitable, TYP_STRUCT, typGetObjLayout(taskTypeHnd)); + } + else + { + taskArg = NewCallArg::Primitive(awaitable, taskJitType); + } + + awaitCall->gtArgs.PushFront(this, taskArg); + + NewCallArg asyncContArg = NewCallArg::Primitive(gtNewNull()).WellKnown(WellKnownArg::AsyncContinuation); + + NewCallArg instArg; + if (awaitSig.hasTypeArg()) + { + GenTree* instArgTree = impLookupToTree(&instArgLookup, GTF_ICON_METHOD_HDL, awaitMethod); + instArg = NewCallArg::Primitive(instArgTree).WellKnown(WellKnownArg::InstParam); + } + + if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) + { + awaitCall->gtArgs.PushFront(this, asyncContArg); + + if (awaitSig.hasTypeArg()) + { + awaitCall->gtArgs.PushFront(this, instArg); + } + } + else + { + awaitCall->gtArgs.PushBack(this, asyncContArg); + + if (awaitSig.hasTypeArg()) + { + awaitCall->gtArgs.PushBack(this, instArg); + } + } + + GenTree* toPush = awaitCall; + if (varTypeIsStruct(callRetType)) + { + toPush = impFixupCallStructReturn(awaitCall, awaitSig.retTypeClass); + } + + AsyncCallInfo* asyncInfo = new (this, CMK_Async) AsyncCallInfo; + + if (impInlineRoot()->compIsAsyncVersion()) + { + asyncInfo->IsTailAwait = !compIsForInlining() || impInlineInfo->iciCall->GetAsyncInfo().IsTailAwait; + } + else + { + // We are inlining into an async method. This means we have a proper + // async await, and we require proper handling. + assert(compIsForInlining() && impInlineInfo->iciCall->IsAsync()); + GenTreeCall* inlCall = impInlineInfo->iciCall; + + JITDUMP("Inheriting continuation handling %d from caller [%06u]\n", + (unsigned)inlCall->GetAsyncInfo().ContinuationContextHandling, dspTreeID(inlCall)); + asyncInfo->ContinuationContextHandling = inlCall->GetAsyncInfo().ContinuationContextHandling; + impInheritAsyncContextsFromInliner(awaitCall); + } + + awaitCall->SetIsAsync(asyncInfo); + + impPushOnStack(toPush, makeTypeInfo(awaitSig.retType, awaitSig.retTypeClass)); +} + #ifdef DEBUG //------------------------------------------------------------------------ // impPoisonImplicitByrefsBeforeReturn: diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 851e08288b3d35..910df1350d802d 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -7062,23 +7062,37 @@ void Compiler::impSetupAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned pref if (compIsForInlining()) { - if (!m_nextAwaitIsTail) + if (!m_nextAwaitIsTail && !compIsAsyncVersion()) { compInlineResult->NoteFatal(InlineObservation::CALLEE_AWAIT); return; } + // For async versions of synchronous methods all async calls are in + // tail position. Inlining is simple for these cases: we can just + // inherit all context handling from the inlining call. + assert(!compIsAsyncVersion() || ((prefixFlags & PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT) != 0)); + GenTreeCall* inlCall = impInlineInfo->iciCall; - JITDUMP("Call [%06u] is to call with a tail async call [%06u]\n", dspTreeID(inlCall), dspTreeID(call)); + JITDUMP("Call [%06u] is to function with a tail async call [%06u]\n", dspTreeID(inlCall), dspTreeID(call)); assert(inlCall->IsAsync()); asyncInfo.ContinuationContextHandling = inlCall->GetAsyncInfo().ContinuationContextHandling; // Validate that below code won't override the handling assert((prefixFlags & PREFIX_IS_TASK_AWAIT) == 0); - m_nextAwaitIsTail = false; - asyncInfo.IsTailAwait = inlCall->GetAsyncInfo().IsTailAwait; + asyncInfo.IsTailAwait = + inlCall->GetAsyncInfo().IsTailAwait && (m_nextAwaitIsTail || (call->gtReturnType == info.compRetType)); + m_nextAwaitIsTail = false; + } + else + { + if (opts.OptimizationEnabled() && ((prefixFlags & PREFIX_IS_ASYNC_VERSION_TAIL_AWAIT) != 0)) + { + // We can only do an actual tail await if the caller and callee agree on return type. + asyncInfo.IsTailAwait = call->gtReturnType == info.compRetType; + } } unsigned newSourceTypes = ICorDebugInfo::ASYNC; diff --git a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs index 78b0c9c76b174b..b3c0996384f84c 100644 --- a/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs +++ b/src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs @@ -160,6 +160,11 @@ public static bool IsCompilerGeneratedILBodyForAsync(this MethodDesc method) return method.IsAsyncThunk() || method is AsyncResumptionStub; } + public static bool RequiresSaveRestoreOfAsyncContexts(this MethodDesc method) + { + return method.IsAsync && method.IsAsyncVariant(); + } + public static MethodDesc GetAsyncVariant(this MethodDesc method) { Debug.Assert(!method.IsAsyncVariant()); diff --git a/src/coreclr/tools/Common/Compiler/AsyncVersionMethodIL.cs b/src/coreclr/tools/Common/Compiler/AsyncVersionMethodIL.cs new file mode 100644 index 00000000000000..a69c7c82a55243 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/AsyncVersionMethodIL.cs @@ -0,0 +1,76 @@ +// 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.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler +{ + public sealed class AsyncVersionMethodIL : MethodIL + { + private readonly MethodDesc _variant; + private readonly MethodIL _ecmaIL; + private readonly MethodIL _unwrappedIL; + + public MethodIL WrappedIL => _ecmaIL; + + public AsyncVersionMethodIL(MethodDesc variant, MethodIL ecmaIL, MethodIL thunkIL) + => (_variant, _ecmaIL, _unwrappedIL) = (variant, ecmaIL, thunkIL); + + // This is the reason we need this class - the method that owns the IL is the variant. + public override MethodDesc OwningMethod => _variant; + + // Everything else dispatches to MethodIL + public override MethodDebugInformation GetDebugInfo() => _ecmaIL.GetDebugInfo(); + public override ILExceptionRegion[] GetExceptionRegions() => _ecmaIL.GetExceptionRegions(); + public override byte[] GetILBytes() => _ecmaIL.GetILBytes(); + public override LocalVariableDefinition[] GetLocals() => _ecmaIL.GetLocals(); + public override object GetObject(int token, NotFoundBehavior notFoundBehavior = NotFoundBehavior.Throw) => _ecmaIL.GetObject(token, notFoundBehavior); + public override bool IsInitLocals => _ecmaIL.IsInitLocals; + public override int MaxStack => _ecmaIL.MaxStack; + + public static MethodIL GetWrappedIfAsyncVersion(MethodDesc method, Compilation compilation) + { + if (method.SupportsAsyncVersionCodegen()) + { + MethodDesc targetMethod = method.GetTargetOfAsyncVariant(); + + MethodDesc methodDef = method.GetTypicalMethodDefinition(); + MethodDesc targetMethodDef = targetMethod.GetTypicalMethodDefinition(); + MethodIL methodIL = new AsyncVersionMethodIL( + methodDef, + compilation.GetMethodIL(targetMethodDef), + compilation.GetMethodIL(method)); + if (method != methodDef) + { + methodIL = new InstantiatedMethodIL(method, methodIL); + } + + return methodIL; + } + + return compilation.GetMethodIL(method); + } + + public static MethodIL UnwrapIfAsyncVersion(MethodIL il) + { + AsyncVersionMethodIL asIL = + il switch + { + InstantiatedMethodIL instIL => instIL.GetMethodILDefinition() as AsyncVersionMethodIL, + AsyncVersionMethodIL asyncIL => asyncIL, + _ => null + }; + + if (asIL != null) + { + return asIL._unwrappedIL; + } + + return il; + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs index f1a9aa8755fc89..887f429da13977 100644 --- a/src/coreclr/tools/Common/Compiler/MethodExtensions.cs +++ b/src/coreclr/tools/Common/Compiler/MethodExtensions.cs @@ -201,5 +201,12 @@ public static bool IsCallEffectivelyDirect(this MethodDesc method) /// but use async calling convention. /// public static bool IsAsyncCall(this MethodDesc method) => method.IsAsyncVariant() || method.IsReturnDroppingAsyncThunk() || (method.IsAsync && !method.Signature.ReturnsTaskOrValueTask()); + + /// + /// Check if this is an async method whose codegen supports passing it the IL of the original non-async method. + /// + /// + /// + public static bool SupportsAsyncVersionCodegen(this MethodDesc method) => method.IsAsyncVariant() && method.IsAsyncThunk(); } } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 5638e885771ee5..709dd59d285b4e 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -577,7 +577,7 @@ private void PublishCode() } #else var methodIL = (MethodIL)HandleToObject((void*)_methodScope); - CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _additionalDependencies, _compilation.NodeFactory, MethodBeingCompiled, methodIL); + CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _additionalDependencies, _compilation.NodeFactory, MethodBeingCompiled, AsyncVersionMethodIL.UnwrapIfAsyncVersion(methodIL)); _methodCodeNode.InitializeDebugInfo(_debugInfo); LocalVariableDefinition[] locals = methodIL.GetLocals(); @@ -831,11 +831,19 @@ private bool Get_CORINFO_METHOD_INFO(MethodDesc method, MethodIL methodIL, CORIN // of async contexts. Regular user implemented runtime async methods // require this behavior, but thunks should be transparent and should not // come with this behavior. - if (method.IsAsyncVariant() && method.IsAsync) + if (method.RequiresSaveRestoreOfAsyncContexts()) { methodInfo->options |= CorInfoOptions.CORINFO_ASYNC_SAVE_CONTEXTS; } +#if !READYTORUN + if (method.SupportsAsyncVersionCodegen()) + { + // This is an async version and the IL belongs to the sync version. + methodInfo->options |= CorInfoOptions.CORINFO_ASYNC_VERSION; + } +#endif + methodInfo->regionKind = CorInfoRegionKind.CORINFO_REGION_NONE; Get_CORINFO_SIG_INFO(method, sig: &methodInfo->args, methodIL); Get_CORINFO_SIG_INFO(methodIL.GetLocals(), &methodInfo->locals); @@ -1267,7 +1275,8 @@ private bool getMethodInfo(CORINFO_METHOD_STRUCT_* ftn, CORINFO_METHOD_INFO* inf if (!_compilation.CanInline(MethodBeingCompiled, method)) return false; - MethodIL methodIL = method.IsUnboxingThunk() ? null : _compilation.GetMethodIL(method); + + MethodIL methodIL = method.IsUnboxingThunk() ? null : AsyncVersionMethodIL.GetWrappedIfAsyncVersion(method, _compilation); return Get_CORINFO_METHOD_INFO(method, methodIL, info); } @@ -1296,7 +1305,7 @@ private CorInfoInline canInline(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHO { if (rootModule.IsWrapNonExceptionThrows != calleeModule.IsWrapNonExceptionThrows) { - var calleeIL = _compilation.GetMethodIL(calleeMethod); + var calleeIL = AsyncVersionMethodIL.GetWrappedIfAsyncVersion(calleeMethod, _compilation); if (calleeIL.GetExceptionRegions().Length != 0) { // Fail inlining if root method and callee have different exception wrapping behavior @@ -1329,7 +1338,7 @@ private void reportTailCallDecision(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_M private void getEHinfo(CORINFO_METHOD_STRUCT_* ftn, uint EHnumber, ref CORINFO_EH_CLAUSE clause) { - var methodIL = _compilation.GetMethodIL(HandleToObject(ftn)); + var methodIL = AsyncVersionMethodIL.GetWrappedIfAsyncVersion(HandleToObject(ftn), _compilation); var ehRegion = methodIL.GetExceptionRegions()[EHnumber]; @@ -1518,7 +1527,7 @@ static CORINFO_RESOLVED_TOKEN CreateResolvedTokenFromMethod(CorInfoImpl jitInter #endif CORINFO_RESOLVED_TOKEN result = default(CORINFO_RESOLVED_TOKEN); - MethodILScope scope = jitInterface._compilation.GetMethodIL(methodWithToken.Method); + MethodILScope scope = AsyncVersionMethodIL.GetWrappedIfAsyncVersion(methodWithToken.Method, jitInterface._compilation); scope ??= EcmaMethodILScope.Create((EcmaMethod)methodWithToken.Method.GetTypicalMethodDefinition()); result.tokenScope = jitInterface.ObjectToHandle(scope); result.tokenContext = jitInterface.contextFromMethod(method); @@ -1892,7 +1901,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) ValidateSafetyOfUsingTypeEquivalenceInSignature(method.Signature); } #else - _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method); + _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, AsyncVersionMethodIL.UnwrapIfAsyncVersion((MethodIL)methodIL), method); #endif if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await) @@ -3494,6 +3503,59 @@ private void getAsyncInfo(ref CORINFO_ASYNC_INFO pAsyncInfoOut) pAsyncInfoOut.finishSuspensionWithContinuationContextMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)); } + private CORINFO_METHOD_STRUCT_* getAwaitReturnCall(CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_LOOKUP instArg) + { + instArg.lookupKind.needsRuntimeLookup = false; + instArg.constLookup.accessType = InfoAccessType.IAT_VALUE; + instArg.constLookup.addr = null; + +#if READYTORUN + return null; +#else + MethodDesc caller = HandleToObject(callerHandle); + Debug.Assert(caller.IsAsyncVariant() && caller.IsAsyncThunk()); + + MethodDesc taskReturningMethod = caller.GetTargetOfAsyncVariant(); + TypeDesc taskReturnType = taskReturningMethod.Signature.ReturnType; + bool isValueTask = taskReturnType.IsValueType; + + CompilerTypeSystemContext context = _compilation.TypeSystemContext; + DefType asyncHelpers = context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); + + MethodDesc runtimeDeterminedCaller = caller.GetSharedRuntimeFormMethodTarget(); + + TypeDesc returnType = runtimeDeterminedCaller.Signature.ReturnType; + MethodDesc runtimeDeterminedResult; + if (returnType.IsVoid) + { + TypeDesc parameterType = isValueTask + ? context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "ValueTask"u8) + : context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8); + MethodSignature signature = new MethodSignature(MethodSignatureFlags.Static, 0, context.GetWellKnownType(WellKnownType.Void), [parameterType]); + runtimeDeterminedResult = asyncHelpers.GetKnownMethod("TransparentAwaitWithResult"u8, signature); + } + else + { + TypeDesc signatureVariable = context.GetSignatureVariable(0, method: true); + TypeDesc parameterType = isValueTask + ? context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "ValueTask`1"u8).MakeInstantiatedType(signatureVariable) + : context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task`1"u8).MakeInstantiatedType(signatureVariable); + MethodSignature signature = new MethodSignature(MethodSignatureFlags.Static, 1, signatureVariable, [parameterType]); + runtimeDeterminedResult = asyncHelpers.GetKnownMethod("TransparentAwaitWithResult"u8, signature).MakeInstantiatedMethod(returnType); + } + + MethodDesc result = runtimeDeterminedResult.GetCanonMethodTarget(CanonicalFormKind.Specific); + + if (result.RequiresInstArg()) + { + // Runtime lookup is needed + ComputeLookup(ref Unsafe.NullRef(), runtimeDeterminedResult, ReadyToRunHelperId.MethodDictionary, caller, ref instArg); + } + + return ObjectToHandle(result); +#endif + } + private CORINFO_CLASS_STRUCT_* getContinuationType(nuint dataSize, ref bool objRefs, nuint objRefsSize) { Debug.Assert(objRefsSize == (dataSize + (nuint)(PointerSize - 1)) / (nuint)PointerSize); diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 2ec1fc175a8d91..2e634fd9c606b9 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -142,6 +142,7 @@ static ICorJitInfoCallbacks() s_callbacks.runWithSPMIErrorTrap = &_runWithSPMIErrorTrap; s_callbacks.getEEInfo = &_getEEInfo; s_callbacks.getAsyncInfo = &_getAsyncInfo; + s_callbacks.getAwaitReturnCall = &_getAwaitReturnCall; s_callbacks.getMethodDefFromMethod = &_getMethodDefFromMethod; s_callbacks.printMethodName = &_printMethodName; s_callbacks.getMethodNameFromMetadata = &_getMethodNameFromMetadata; @@ -325,6 +326,7 @@ static ICorJitInfoCallbacks() public delegate* unmanaged runWithSPMIErrorTrap; public delegate* unmanaged getEEInfo; public delegate* unmanaged getAsyncInfo; + public delegate* unmanaged getAwaitReturnCall; public delegate* unmanaged getMethodDefFromMethod; public delegate* unmanaged printMethodName; public delegate* unmanaged getMethodNameFromMetadata; @@ -2191,6 +2193,21 @@ private static void _getAsyncInfo(IntPtr thisHandle, IntPtr* ppException, CORINF } } + [UnmanagedCallersOnly] + private static CORINFO_METHOD_STRUCT_* _getAwaitReturnCall(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_LOOKUP* instArg) + { + var _this = GetThis(thisHandle); + try + { + return _this.getAwaitReturnCall(callerHandle, ref *instArg); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static mdToken _getMethodDefFromMethod(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* hMethod) { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index ff3dcf406ac6c6..687552f4f9cb57 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -444,6 +444,7 @@ public enum CorInfoOptions CORINFO_GENERICS_CTXT_FROM_METHODTABLE), CORINFO_GENERICS_CTXT_KEEP_ALIVE = 0x00000100, // Keep the generics context alive throughout the method even if there is no explicit use, and report its location to the CLR CORINFO_ASYNC_SAVE_CONTEXTS = 0x00000200, // Runtime async method must save and restore contexts + CORINFO_ASYNC_VERSION = 0x00000400, // This is an async version whose IL belongs to a non-async method } // These are used to detect array methods as NamedIntrinsic in JIT importer, diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 384ca10f881504..2456fc811624ae 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -295,6 +295,7 @@ FUNCTIONS [ManualNativeWrapper] bool runWithSPMIErrorTrap(ICorJitInfo::errorTrapFunction function, void* parameter); void getEEInfo(CORINFO_EE_INFO* pEEInfoOut); void getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut); + CORINFO_METHOD_HANDLE getAwaitReturnCall(CORINFO_METHOD_HANDLE callerHandle, CORINFO_LOOKUP* instArg); mdMethodDef getMethodDefFromMethod(CORINFO_METHOD_HANDLE hMethod); size_t printMethodName(CORINFO_METHOD_HANDLE ftn, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize) const char* getMethodNameFromMetadata(CORINFO_METHOD_HANDLE ftn, const char **className, const char **namespaceName, const char **enclosingClassNames, size_t maxEnclosingClassNames); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs index c61590199c91f9..ce3db997cc7f94 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -70,7 +70,9 @@ public enum ImportState : byte private DependencyList _dependencies; private BasicBlock _lateBasicBlocks; + private int _prevMatchedAwaitTailCallRetPostOffset; private bool _asyncDependenciesReported; + private bool _wrappingAwaitReported; private sealed class ExceptionRegion { @@ -80,9 +82,11 @@ private sealed class ExceptionRegion public ILImporter(ILScanner compilation, MethodDesc method, MethodIL methodIL = null) { + _compilation = compilation; + if (methodIL == null) { - methodIL = compilation.GetMethodIL(method); + methodIL = AsyncVersionMethodIL.GetWrappedIfAsyncVersion(method, _compilation); } else { @@ -95,7 +99,6 @@ public ILImporter(ILScanner compilation, MethodDesc method, MethodIL methodIL = ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramSpecific, method); } - _compilation = compilation; _factory = (ILScanNodeFactory)compilation.NodeFactory; _ilBytes = methodIL.GetILBytes(); @@ -180,7 +183,7 @@ public ILImporter(ILScanner compilation, MethodDesc method, MethodIL methodIL = } } - if (_canonMethod.IsAsyncCall()) + if (_canonMethod.RequiresSaveRestoreOfAsyncContexts()) { const string reason = "Async state machine"; DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); @@ -202,7 +205,7 @@ public ILImporter(ILScanner compilation, MethodDesc method, MethodIL methodIL = conditionalDependencies.Add(new(dep.Node, bb.Condition, dep.Reason)); } - CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _unconditionalDependencies, _factory, _canonMethod, _canonMethodIL); + CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _unconditionalDependencies, _factory, _canonMethod, AsyncVersionMethodIL.UnwrapIfAsyncVersion(_canonMethodIL)); CodeBasedDependencyAlgorithm.AddConditionalDependenciesDueToMethodCodePresence(ref conditionalDependencies, _factory, _canonMethod); return (_unconditionalDependencies, conditionalDependencies); @@ -342,12 +345,7 @@ private bool MatchTaskAwaitPattern() // call // Find where this basic block ends - int nextBBOffset = _currentOffset; - while (nextBBOffset < _basicBlocks.Length && _basicBlocks[nextBBOffset] == null) - nextBBOffset++; - - // Create ILReader for what's left in the basic block - var reader = new ILReader(new ReadOnlySpan(_ilBytes, _currentOffset, nextBBOffset - _currentOffset)); + ILReader reader = GetRemainingBlockIL(); if (!reader.HasNext) return false; @@ -414,6 +412,34 @@ private bool MatchTaskAwaitPattern() && IsAsyncHelpersAwait((MethodDesc)_methodIL.GetObject(reader.ReadILToken())); } + private ILReader GetRemainingBlockIL() + { + int nextBBOffset = _currentOffset; + while (nextBBOffset < _basicBlocks.Length && _basicBlocks[nextBBOffset] == null) + nextBBOffset++; + + // Create ILReader for what's left in the basic block + return new ILReader(new ReadOnlySpan(_ilBytes, _currentOffset, nextBBOffset - _currentOffset)); + } + + private bool MatchTailCallAwait(MethodDesc method) + { + // Create ILReader for what's left in the basic block + var reader = GetRemainingBlockIL(); + + if (!reader.HasNext) + return false; + + ILOpcode opcode = reader.ReadILOpcode(); + if (opcode == ILOpcode.ret && method.Signature.ReturnsTaskOrValueTask()) + { + _prevMatchedAwaitTailCallRetPostOffset = _currentOffset + reader.Offset; + return true; + } + + return false; + } + private void ImportCall(ILOpcode opcode, int token) { // We get both the canonical and runtime determined form - JitInterface mostly operates @@ -425,7 +451,7 @@ private void ImportCall(ILOpcode opcode, int token) if ((method.Signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == MethodSignatureFlags.CallingConventionVarargs) ThrowHelper.ThrowBadImageFormatException(); - _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, method); + _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, AsyncVersionMethodIL.UnwrapIfAsyncVersion(_canonMethodIL), method); if (method.IsRawPInvoke()) { @@ -434,31 +460,8 @@ private void ImportCall(ILOpcode opcode, int token) } // Are we scanning a call within a state machine? - if (opcode is ILOpcode.call or ILOpcode.callvirt - && _canonMethod.IsAsyncCall()) + if (opcode is ILOpcode.call or ILOpcode.callvirt && _canonMethod.IsAsyncCall()) { - // Add dependencies on infra to do suspend/resume. We only need to do this once per method scanned. - if (!_asyncDependenciesReported && method.IsAsync) - { - _asyncDependenciesReported = true; - - const string asyncReason = "Async state machine"; - - AsyncResumptionStub resumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(_canonMethod, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType()); - _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(resumptionStub), asyncReason); - - _dependencies.Add(_factory.ConstructedTypeSymbol(_compilation.TypeSystemContext.ContinuationType), asyncReason); - - DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); - - _dependencies.Add(_compilation.GetHelperEntrypoint(ReadyToRunHelper.AllocContinuation), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)), asyncReason); - _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason); - } - // If this is the task await pattern, the JIT will first resolve the call to the // async variant, then may switch back to the original if the async variant is just // a thunk (for non-runtime-async methods). Report both variants as dependencies such @@ -471,7 +474,7 @@ private void ImportCall(ILOpcode opcode, int token) // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; - if (allowAsyncVariant && MatchTaskAwaitPattern()) + if (allowAsyncVariant && (_canonMethod.SupportsAsyncVersionCodegen() ? MatchTailCallAwait(method) : MatchTaskAwaitPattern())) { MethodDesc asyncVariantMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(method); MethodDesc asyncVariantRuntimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod); @@ -484,6 +487,12 @@ private void ImportCall(ILOpcode opcode, int token) private void ImportCall(ILOpcode opcode, MethodDesc method, MethodDesc runtimeDeterminedMethod) { + // Add dependencies on infra to do suspend/resume. We only need to do this once per method scanned. + if (!_asyncDependenciesReported && _canonMethod.IsAsyncCall() && method.IsAsyncCall()) + { + RegisterDependenciesOnAsyncCall(); + } + string reason = null; switch (opcode) { @@ -954,6 +963,28 @@ private void ImportCall(ILOpcode opcode, MethodDesc method, MethodDesc runtimeDe } } + private void RegisterDependenciesOnAsyncCall() + { + Debug.Assert(!_asyncDependenciesReported); + _asyncDependenciesReported = true; + + const string asyncReason = "Async state machine"; + + AsyncResumptionStub resumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(_canonMethod, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType()); + _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(resumptionStub), asyncReason); + + _dependencies.Add(_factory.ConstructedTypeSymbol(_compilation.TypeSystemContext.ContinuationType), asyncReason); + + DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); + + _dependencies.Add(_compilation.GetHelperEntrypoint(ReadyToRunHelper.AllocContinuation), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)), asyncReason); + _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason); + } + private void ImportLdFtn(int token, ILOpcode opCode) { ImportCall(opCode, token); @@ -1101,7 +1132,7 @@ private void ImportMkRefAny(int token) { _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.TypeHandleToRuntimeType), "mkrefany"); _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.TypeHandleToRuntimeTypeHandle), "mkrefany"); - _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token)); + _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, AsyncVersionMethodIL.UnwrapIfAsyncVersion(_methodIL), (TypeDesc)_canonMethodIL.GetObject(token)); ImportTypedRefOperationDependencies(token, "mkrefany"); } @@ -1141,7 +1172,7 @@ private void ImportLdToken(int token) } } - _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token)); + _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, AsyncVersionMethodIL.UnwrapIfAsyncVersion(_methodIL), (TypeDesc)_canonMethodIL.GetObject(token)); _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeTypeHandle), "ldtoken"); _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeType), "ldtoken"); @@ -1224,7 +1255,7 @@ private void ImportFieldAccess(int token, bool isStatic, bool? write, string rea if (field.IsLiteral) ThrowHelper.ThrowMissingFieldException(field.OwningType, field.GetName()); - _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, canonField); + _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, AsyncVersionMethodIL.UnwrapIfAsyncVersion(_canonMethodIL), canonField); // `write` will be null for ld(s)flda. Consider address loads write unless they were // for initonly static fields. We'll trust the initonly that this is not a write. @@ -1378,7 +1409,7 @@ private void AddBoxingDependencies(TypeDesc type, string reason) return; TypeDesc typeForAccessCheck = type.IsRuntimeDeterminedSubtype ? type.ConvertToCanonForm(CanonicalFormKind.Specific) : type; - _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, typeForAccessCheck); + _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, AsyncVersionMethodIL.UnwrapIfAsyncVersion(_methodIL), typeForAccessCheck); if (type.IsRuntimeDeterminedSubtype) { @@ -1407,7 +1438,7 @@ private void ImportLeave(BasicBlock target) private void ImportNewArray(int token) { var elementType = (TypeDesc)_methodIL.GetObject(token); - _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token)); + _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, AsyncVersionMethodIL.UnwrapIfAsyncVersion(_methodIL), (TypeDesc)_canonMethodIL.GetObject(token)); if (elementType.IsRuntimeDeterminedSubtype) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, elementType.MakeArrayType()), "newarr"); @@ -1754,6 +1785,69 @@ private DefType GetWellKnownType(WellKnownType wellKnownType) return _compilation.TypeSystemContext.GetWellKnownType(wellKnownType); } + private void ImportReturn() + { + if (_wrappingAwaitReported || !_canonMethod.SupportsAsyncVersionCodegen() || _prevMatchedAwaitTailCallRetPostOffset == _currentOffset) + { + return; + } + + _wrappingAwaitReported = true; + + MethodDesc taskReturningMethod = _canonMethod.GetTargetOfAsyncVariant(); + TypeDesc taskReturnType = taskReturningMethod.Signature.ReturnType; + bool isValueTask = taskReturnType.IsValueType; + + CompilerTypeSystemContext context = _compilation.TypeSystemContext; + DefType asyncHelpers = context.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); + + MethodDesc runtimeDeterminedCaller = _canonMethod.GetSharedRuntimeFormMethodTarget(); + + TypeDesc returnType = runtimeDeterminedCaller.Signature.ReturnType; + MethodDesc runtimeDeterminedResult; + if (returnType.IsVoid) + { + TypeDesc parameterType = isValueTask + ? context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "ValueTask"u8) + : context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8); + MethodSignature signature = new MethodSignature(MethodSignatureFlags.Static, 0, context.GetWellKnownType(WellKnownType.Void), [parameterType]); + runtimeDeterminedResult = asyncHelpers.GetKnownMethod("TransparentAwaitWithResult"u8, signature); + } + else + { + TypeDesc signatureVariable = context.GetSignatureVariable(0, method: true); + TypeDesc parameterType = isValueTask + ? context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "ValueTask`1"u8).MakeInstantiatedType(signatureVariable) + : context.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task`1"u8).MakeInstantiatedType(signatureVariable); + MethodSignature signature = new MethodSignature(MethodSignatureFlags.Static, 1, signatureVariable, [parameterType]); + runtimeDeterminedResult = asyncHelpers.GetKnownMethod("TransparentAwaitWithResult"u8, signature).MakeInstantiatedMethod(returnType); + } + + MethodDesc targetMethod = runtimeDeterminedResult.GetCanonMethodTarget(CanonicalFormKind.Specific); + + const string reason = "Wrapped Task return"; + + if (runtimeDeterminedResult.IsRuntimeDeterminedExactMethod) + { + _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodDictionary, runtimeDeterminedResult), reason); + _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); + } + else + { + if (targetMethod.RequiresInstArg()) + { + _dependencies.Add(_compilation.NodeFactory.MethodGenericDictionary(runtimeDeterminedResult), reason); + } + + _dependencies.Add(GetMethodEntrypoint(targetMethod), reason); + } + + if (!_asyncDependenciesReported) + { + RegisterDependenciesOnAsyncCall(); + } + } + private static void ImportNop() { } private static void ImportBreak() { } private static void ImportLoadVar(int index, bool argument) { } @@ -1762,7 +1856,6 @@ private static void ImportAddressOfVar(int index, bool argument) { } private static void ImportDup() { } private static void ImportPop() { } private static void ImportLoadNull() { } - private static void ImportReturn() { } private static void ImportLoadInt(long value, StackValueKind kind) { } private static void ImportLoadFloat(double value) { } private static void ImportLoadIndirect(int token) { } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 67602a68ac221c..608efd3429f5f4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -36,6 +36,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ExternalReferenceTokenManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ExternalReferenceTokenManager.cs index 1edea14b3f10a8..f046893f12f979 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ExternalReferenceTokenManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ExternalReferenceTokenManager.cs @@ -122,7 +122,7 @@ private void EnsureMethodDefTokensAreAvailableInVersionBubble(MethodDesc methodD AddTokenToMutableModule(ecmaMethod); return; } - if (methodDesc.HasInstantiation) + if (methodDesc.HasInstantiation || methodDesc.OwningType.HasInstantiation) { EnsureTypeDefTokensAreAvailableInVersionBubble(methodDesc.GetMethodDefinition().OwningType); foreach (TypeDesc instParam in methodDesc.Instantiation) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 215a6bf805d571..2133032274f279 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -64,6 +64,7 @@ + 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 78121953235e08..5c4485f245da5b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -584,6 +584,14 @@ public static bool ShouldSkipCompilation(InstructionSetSupport instructionSetSup return true; } + // Currently crossgen2 does not support compiling async versions of synchronous Task-returning functions. + // We would compile a wrapper thunk but that comes with different perf characteristics and diagnostics + // that we do not want to deal with. + if (methodNeedingCode.SupportsAsyncVersionCodegen()) + { + return true; + } + if (ShouldCodeNotBeCompiledIntoFinalImage(instructionSetSupport, methodNeedingCode)) { return true; diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 86a63a64bce39a..c3888acc9dd107 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -15,6 +15,7 @@ using ILCompiler; using ILCompiler.DependencyAnalysis; +using System.Runtime.CompilerServices; #if SUPPORT_JIT using MethodCodeNode = Internal.Runtime.JitSupport.JitMethodCodeNode; @@ -67,7 +68,7 @@ public void CompileMethod(MethodCodeNode methodCodeNodeNeedingCode, MethodIL met _methodCodeNode = methodCodeNodeNeedingCode; _isFallbackBodyCompilation = methodIL != null; - methodIL ??= _compilation.GetMethodIL(MethodBeingCompiled); + methodIL ??= AsyncVersionMethodIL.GetWrappedIfAsyncVersion(MethodBeingCompiled, _compilation); try { @@ -270,7 +271,7 @@ private void ComputeLookup(ref CORINFO_RESOLVED_TOKEN pResolvedToken, object ent // currently do it for an inline. This is not a big issue because ReadyToRun helpers // in optimized code only happen in special build configurations (such as // `-O --noscan` or multimodule build). - if (pResolvedToken.tokenContext != contextFromMethodBeingCompiled()) + if (!Unsafe.IsNullRef(ref pResolvedToken) && pResolvedToken.tokenContext != contextFromMethodBeingCompiled()) { lookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED; return; diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 90ec07a3b446bc..247b42cc0bc0b9 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -133,6 +133,7 @@ struct JitInterfaceCallbacks bool (* runWithSPMIErrorTrap)(void * thisHandle, CorInfoExceptionClass** ppException, ICorJitInfo::errorTrapFunction function, void* parameter); void (* getEEInfo)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_EE_INFO* pEEInfoOut); void (* getAsyncInfo)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_ASYNC_INFO* pAsyncInfoOut); + CORINFO_METHOD_HANDLE (* getAwaitReturnCall)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE callerHandle, CORINFO_LOOKUP* instArg); mdMethodDef (* getMethodDefFromMethod)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE hMethod); size_t (* printMethodName)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize); const char* (* getMethodNameFromMetadata)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, const char** className, const char** namespaceName, const char** enclosingClassNames, size_t maxEnclosingClassNames); @@ -1383,6 +1384,16 @@ class JitInterfaceWrapper : public ICorJitInfo if (pException != nullptr) throw pException; } + virtual CORINFO_METHOD_HANDLE getAwaitReturnCall( + CORINFO_METHOD_HANDLE callerHandle, + CORINFO_LOOKUP* instArg) +{ + CorInfoExceptionClass* pException = nullptr; + CORINFO_METHOD_HANDLE temp = _callbacks->getAwaitReturnCall(_thisHandle, &pException, callerHandle, instArg); + if (pException != nullptr) throw pException; + return temp; +} + virtual mdMethodDef getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 85a7a8bf540eed..0ec56054bb0d2f 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -30,6 +30,39 @@ struct Agnostic_CORINFO_SIG_INFO DWORD token; }; +struct Agnostic_CORINFO_CONST_LOOKUP +{ + DWORD accessType; + DWORDLONG handle; // actually a union of two pointer sized things +}; + +struct Agnostic_CORINFO_LOOKUP_KIND +{ + DWORD needsRuntimeLookup; + DWORD runtimeLookupKind; +}; + +struct Agnostic_CORINFO_RUNTIME_LOOKUP +{ + DWORDLONG signature; + DWORD helper; + DWORD indirections; + DWORD testForNull; + WORD sizeOffset; + DWORDLONG offsets[CORINFO_MAXINDIRECTIONS]; + DWORD indirectFirstOffset; + DWORD indirectSecondOffset; + Agnostic_CORINFO_CONST_LOOKUP helperEntryPoint; +}; + +struct Agnostic_CORINFO_LOOKUP +{ + Agnostic_CORINFO_LOOKUP_KIND lookupKind; + Agnostic_CORINFO_RUNTIME_LOOKUP runtimeLookup; // This and constLookup actually a union, but with different + // layouts.. :-| copy the right one based on lookupKinds value + Agnostic_CORINFO_CONST_LOOKUP constLookup; +}; + struct Agnostic_CORINFO_METHOD_INFO { DWORDLONG ftn; @@ -209,6 +242,12 @@ struct Agnostic_CORINFO_ASYNC_INFO DWORDLONG finishSuspensionWithContinuationContextMethHnd; }; +struct Agnostic_GetAwaitReturnCallResult +{ + DWORDLONG methodHnd; + Agnostic_CORINFO_LOOKUP instArg; +}; + struct Agnostic_GetOSRInfo { DWORD index; @@ -266,45 +305,12 @@ struct Agnostic_CORINFO_HELPER_DESC Agnostic_CORINFO_HELPER_ARG args[CORINFO_ACCESS_ALLOWED_MAX_ARGS]; }; -struct Agnostic_CORINFO_CONST_LOOKUP -{ - DWORD accessType; - DWORDLONG handle; // actually a union of two pointer sized things -}; - struct Agnostic_GetHelperFtn { Agnostic_CORINFO_CONST_LOOKUP helperLookup; DWORDLONG helperMethod; }; -struct Agnostic_CORINFO_LOOKUP_KIND -{ - DWORD needsRuntimeLookup; - DWORD runtimeLookupKind; -}; - -struct Agnostic_CORINFO_RUNTIME_LOOKUP -{ - DWORDLONG signature; - DWORD helper; - DWORD indirections; - DWORD testForNull; - WORD sizeOffset; - DWORDLONG offsets[CORINFO_MAXINDIRECTIONS]; - DWORD indirectFirstOffset; - DWORD indirectSecondOffset; - Agnostic_CORINFO_CONST_LOOKUP helperEntryPoint; -}; - -struct Agnostic_CORINFO_LOOKUP -{ - Agnostic_CORINFO_LOOKUP_KIND lookupKind; - Agnostic_CORINFO_RUNTIME_LOOKUP runtimeLookup; // This and constLookup actually a union, but with different - // layouts.. :-| copy the right one based on lookupKinds value - Agnostic_CORINFO_CONST_LOOKUP constLookup; -}; - struct Agnostic_CORINFO_FIELD_INFO { DWORD fieldAccessor; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index b464feef60b3f6..63cdb605f575d0 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -79,6 +79,7 @@ LWM(GetSZArrayHelperEnumeratorClass, DWORDLONG, DWORDLONG) LWM(GetDelegateCtor, Agnostic_GetDelegateCtorIn, Agnostic_GetDelegateCtorOut) LWM(GetEEInfo, DWORD, Agnostic_CORINFO_EE_INFO) LWM(GetAsyncInfo, DWORD, Agnostic_CORINFO_ASYNC_INFO) +LWM(GetAwaitReturnCall, DWORDLONG, Agnostic_GetAwaitReturnCallResult) LWM(GetEHinfo, DLD, Agnostic_CORINFO_EH_CLAUSE) LWM(GetStaticFieldContent, DLDDD, DD) LWM(GetObjectContent, DLDD, DD) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index ec6b3b9e1ef4ac..acc61ceeb04d75 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -4516,6 +4516,33 @@ void MethodContext::repGetAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut) DEBUG_REP(dmpGetAsyncInfo(0, value)); } +void MethodContext::recGetAwaitReturnCall(CORINFO_METHOD_HANDLE callerHnd, CORINFO_LOOKUP* instArg, CORINFO_METHOD_HANDLE methHnd) +{ + if (GetAwaitReturnCall == nullptr) + GetAwaitReturnCall = new LightWeightMap(); + + Agnostic_GetAwaitReturnCallResult value; + ZeroMemory(&value, sizeof(value)); + value.methodHnd = CastHandle(methHnd); + value.instArg = SpmiRecordsHelper::StoreAgnostic_CORINFO_LOOKUP(instArg); + + GetAwaitReturnCall->Add(CastHandle(callerHnd), value); + DEBUG_REC(dmpGetAwaitReturnCall(CastHandle(callerHnd), value)); +} +void MethodContext::dmpGetAwaitReturnCall(DWORDLONG key, Agnostic_GetAwaitReturnCallResult& value) +{ + printf("GetAwaitReturnCall key %016" PRIX64 " value methodHnd-%016" PRIX64 " instArg %s", + key, + value.methodHnd, + SpmiDumpHelper::DumpAgnostic_CORINFO_LOOKUP(value.instArg).c_str()); +} +CORINFO_METHOD_HANDLE MethodContext::repGetAwaitReturnCall(CORINFO_METHOD_HANDLE callerHnd, CORINFO_LOOKUP* instArg) +{ + const Agnostic_GetAwaitReturnCallResult& result = LookupByKeyOrMissNoMessage(GetAwaitReturnCall, CastHandle(callerHnd)); + *instArg = SpmiRecordsHelper::RestoreCORINFO_LOOKUP(result.instArg); + return (CORINFO_METHOD_HANDLE)result.methodHnd; +} + void MethodContext::recGetGSCookie(GSCookie* pCookieVal, GSCookie** ppCookieVal) { if (GetGSCookie == nullptr) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index 449a18a91caeb5..8956394957e6a1 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -572,6 +572,10 @@ class MethodContext void dmpGetAsyncInfo(DWORD key, const Agnostic_CORINFO_ASYNC_INFO& value); void repGetAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut); + void recGetAwaitReturnCall(CORINFO_METHOD_HANDLE callerHnd, CORINFO_LOOKUP* instArg, CORINFO_METHOD_HANDLE methHnd); + void dmpGetAwaitReturnCall(DWORDLONG key, Agnostic_GetAwaitReturnCallResult& value); + CORINFO_METHOD_HANDLE repGetAwaitReturnCall(CORINFO_METHOD_HANDLE callerHnd, CORINFO_LOOKUP* instArg); + void recGetGSCookie(GSCookie* pCookieVal, GSCookie** ppCookieVal); void dmpGetGSCookie(DWORD key, DLDL value); void repGetGSCookie(GSCookie* pCookieVal, GSCookie** ppCookieVal); @@ -1222,6 +1226,7 @@ enum mcPackets Packet_GetWasmTypeSymbol = 235, Packet_GetWasmLowering = 236, Packet_GetAsyncOtherVariant = 237, + Packet_GetAwaitReturnCall = 238, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h b/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h index 1a460b05b76e70..b2df4eded655e6 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/spmirecordhelper.h @@ -97,7 +97,7 @@ class SpmiRecordsHelper static Agnostic_CORINFO_LOOKUP_KIND CreateAgnostic_CORINFO_LOOKUP_KIND( const CORINFO_LOOKUP_KIND* pGenericLookupKind); - static CORINFO_LOOKUP_KIND RestoreCORINFO_LOOKUP_KIND(Agnostic_CORINFO_LOOKUP_KIND& lookupKind); + static CORINFO_LOOKUP_KIND RestoreCORINFO_LOOKUP_KIND(const Agnostic_CORINFO_LOOKUP_KIND& lookupKind); static Agnostic_CORINFO_CONST_LOOKUP StoreAgnostic_CORINFO_CONST_LOOKUP( const CORINFO_CONST_LOOKUP* pLookup); @@ -107,11 +107,11 @@ class SpmiRecordsHelper static Agnostic_CORINFO_RUNTIME_LOOKUP StoreAgnostic_CORINFO_RUNTIME_LOOKUP( CORINFO_RUNTIME_LOOKUP* pLookup); - static CORINFO_RUNTIME_LOOKUP RestoreCORINFO_RUNTIME_LOOKUP(Agnostic_CORINFO_RUNTIME_LOOKUP& Lookup); + static CORINFO_RUNTIME_LOOKUP RestoreCORINFO_RUNTIME_LOOKUP(const Agnostic_CORINFO_RUNTIME_LOOKUP& lookup); static Agnostic_CORINFO_LOOKUP StoreAgnostic_CORINFO_LOOKUP(CORINFO_LOOKUP* pLookup); - static CORINFO_LOOKUP RestoreCORINFO_LOOKUP(Agnostic_CORINFO_LOOKUP& agnosticLookup); + static CORINFO_LOOKUP RestoreCORINFO_LOOKUP(const Agnostic_CORINFO_LOOKUP& agnosticLookup); static Agnostic_CORINFO_TYPE_LAYOUT_NODE StoreAgnostic_CORINFO_TYPE_LAYOUT_NODE(const CORINFO_TYPE_LAYOUT_NODE& node); static CORINFO_TYPE_LAYOUT_NODE RestoreCORINFO_TYPE_LAYOUT_NODE(const Agnostic_CORINFO_TYPE_LAYOUT_NODE& node); @@ -454,7 +454,7 @@ inline Agnostic_CORINFO_LOOKUP_KIND SpmiRecordsHelper::CreateAgnostic_CORINFO_LO } inline CORINFO_LOOKUP_KIND SpmiRecordsHelper::RestoreCORINFO_LOOKUP_KIND( - Agnostic_CORINFO_LOOKUP_KIND& lookupKind) + const Agnostic_CORINFO_LOOKUP_KIND& lookupKind) { CORINFO_LOOKUP_KIND genericLookupKind; genericLookupKind.needsRuntimeLookup = lookupKind.needsRuntimeLookup != 0; @@ -500,7 +500,7 @@ inline Agnostic_CORINFO_RUNTIME_LOOKUP SpmiRecordsHelper::StoreAgnostic_CORINFO_ } inline CORINFO_RUNTIME_LOOKUP SpmiRecordsHelper::RestoreCORINFO_RUNTIME_LOOKUP( - Agnostic_CORINFO_RUNTIME_LOOKUP& lookup) + const Agnostic_CORINFO_RUNTIME_LOOKUP& lookup) { CORINFO_RUNTIME_LOOKUP runtimeLookup; runtimeLookup.signature = (LPVOID)lookup.signature; @@ -532,7 +532,7 @@ inline Agnostic_CORINFO_LOOKUP SpmiRecordsHelper::StoreAgnostic_CORINFO_LOOKUP(C return lookup; } -inline CORINFO_LOOKUP SpmiRecordsHelper::RestoreCORINFO_LOOKUP(Agnostic_CORINFO_LOOKUP& agnosticLookup) +inline CORINFO_LOOKUP SpmiRecordsHelper::RestoreCORINFO_LOOKUP(const Agnostic_CORINFO_LOOKUP& agnosticLookup) { CORINFO_LOOKUP lookup; ZeroMemory(&lookup, sizeof(lookup)); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 6d0cadcd5616ae..9302dc77bc403f 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1404,6 +1404,14 @@ void interceptor_ICJI::getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfo) mc->recGetAsyncInfo(pAsyncInfo); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAwaitReturnCall(CORINFO_METHOD_HANDLE callerHandle, CORINFO_LOOKUP* instArg) +{ + mc->cr->AddCall("getAwaitReturnCall"); + CORINFO_METHOD_HANDLE result = original_ICorJitInfo->getAwaitReturnCall(callerHandle, instArg); + mc->recGetAwaitReturnCall(callerHandle, instArg, result); + return result; +} + /*********************************************************************************/ // // Diagnostic methods diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index 874083bd15b940..70de01f8704eb1 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -986,6 +986,14 @@ void interceptor_ICJI::getAsyncInfo( original_ICorJitInfo->getAsyncInfo(pAsyncInfoOut); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAwaitReturnCall( + CORINFO_METHOD_HANDLE callerHandle, + CORINFO_LOOKUP* instArg) +{ + mcs->AddCall("getAwaitReturnCall"); + return original_ICorJitInfo->getAwaitReturnCall(callerHandle, instArg); +} + mdMethodDef interceptor_ICJI::getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index b870f0f947f2f6..f301b3d057987e 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -864,6 +864,13 @@ void interceptor_ICJI::getAsyncInfo( original_ICorJitInfo->getAsyncInfo(pAsyncInfoOut); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAwaitReturnCall( + CORINFO_METHOD_HANDLE callerHandle, + CORINFO_LOOKUP* instArg) +{ + return original_ICorJitInfo->getAwaitReturnCall(callerHandle, instArg); +} + mdMethodDef interceptor_ICJI::getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 93262fd2279b01..abe084f498f809 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1212,6 +1212,12 @@ void MyICJI::getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfo) jitInstance->mc->repGetAsyncInfo(pAsyncInfo); } +CORINFO_METHOD_HANDLE MyICJI::getAwaitReturnCall(CORINFO_METHOD_HANDLE callerHandle, CORINFO_LOOKUP* instArg) +{ + jitInstance->mc->cr->AddCall("getAwaitReturnCall"); + return jitInstance->mc->repGetAwaitReturnCall(callerHandle, instArg); +} + /*********************************************************************************/ // // Diagnostic methods diff --git a/src/coreclr/vm/asyncthunks.cpp b/src/coreclr/vm/asyncthunks.cpp index 0bc85a906c7c71..a4e804006c9af3 100644 --- a/src/coreclr/vm/asyncthunks.cpp +++ b/src/coreclr/vm/asyncthunks.cpp @@ -31,19 +31,17 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_ // a non-async thunk is implemented in terms of the async variant which has user code pAsyncOtherVariant = this->GetAsyncVariant(); } + else if (IsReturnDroppingThunk()) + { + // this is a special void-returning async variant that calls + // the normal async variant and drops the result + pAsyncOtherVariant = this->GetAsyncVariant(); + } else { - if (!IsReturnDroppingThunk()) - { - // an async thunk is implemented in terms of non-async variant - pAsyncOtherVariant = this->GetOrdinaryVariant(); - } - else - { - // this is a special void-returning async variant that calls - // the normal async variant and drops the result - pAsyncOtherVariant = this->GetAsyncVariant(); - } + // Async variant of something else, like an unsafe accessor. Leave it + // up to the unsafe accessor to create these. + return false; } _ASSERTE(!IsWrapperStub() && !pAsyncOtherVariant->IsWrapperStub()); @@ -64,14 +62,8 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_ } else { - if (IsReturnDroppingThunk()) - { - EmitReturnDroppingThunk(pAsyncOtherVariant, msig, &sl); - } - else - { - EmitAsyncMethodThunk(pAsyncOtherVariant, msig, &sl); - } + _ASSERTE(IsReturnDroppingThunk()); + EmitReturnDroppingThunk(pAsyncOtherVariant, msig, &sl); } NewHolder ilResolver = new ILStubResolver(); @@ -377,44 +369,6 @@ int MethodDesc::GetTokenForGenericMethodCallWithAsyncReturnType(ILCodeStream* pC return pCode->GetToken(md, mdTokenNil, methodSigToken); } -// Given a method Bar.Foo, return a MethodSpec token for Bar.Foo -// instantiated with the result type from the current async method's return -// type. For example, if "this" represents Task> Foo(), and -// "md" is TaskAwaiter.GetResult(), this returns a MethodSpec representing -// TaskAwaiter>.GetResult(). -int MethodDesc::GetTokenForGenericTypeMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md) -{ - if (!md->HasClassOrMethodInstantiation()) - { - return pCode->GetToken(md); - } - - // We never get here with a method instantiation currently. - _ASSERTE(!md->HasMethodInstantiation()); - - SigBuilder typeSigBuilder; - typeSigBuilder.AppendData(ELEMENT_TYPE_GENERICINST); - typeSigBuilder.AppendData(ELEMENT_TYPE_INTERNAL); - // TODO: (async) Encoding potentially shared method tables in - // signatures of tokens seems odd, but this hits assert - // with the typical method table. - typeSigBuilder.AppendPointer(md->GetMethodTable()); - typeSigBuilder.AppendData(1); - - SigPointer retTypeSig = GetAsyncThunkResultTypeSig(); - PCCOR_SIGNATURE retTypeSigRaw; - uint32_t retTypeSigLen; - retTypeSig.GetSignature(&retTypeSigRaw, &retTypeSigLen); - - typeSigBuilder.AppendBlob((const PVOID)retTypeSigRaw, retTypeSigLen); - - DWORD typeSigLen; - PCCOR_SIGNATURE typeSig = (PCCOR_SIGNATURE)typeSigBuilder.GetSignature(&typeSigLen); - int typeSigToken = pCode->GetSigToken(typeSig, typeSigLen); - - return pCode->GetToken(md, typeSigToken); -} - int MethodDesc::GetTokenForThunkTarget(ILCodeStream* pCode, MethodDesc* md) { int token; @@ -475,180 +429,6 @@ int MethodDesc::GetTokenForThunkTarget(ILCodeStream* pCode, MethodDesc* md) return token; } -// Provided a Task-returning method, emits an async wrapper. -// The emitted code matches method EmitAsyncMethodThunk in the Managed Type System. -void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pTaskReturningVariant, MetaSig& msig, ILStubLinker* pSL) -{ - _ASSERTE(!pTaskReturningVariant->IsAsyncThunkMethod()); - _ASSERTE(!pTaskReturningVariant->IsVoid()); - - // Implement IL that is effectively the following: - // { - // Task task = other(arg); - // if (!task.IsCompleted) - // { - // TailAwait(); - // return AsyncHelpers.TransparentAwait(task); - // } - // return AsyncHelpers.CompletedTaskResult(task); - // } - - // For ValueTask: - - // { - // ValueTask vt = other(arg); - // if (!vt.IsCompleted) - // { - // TailAwait(); - // return AsyncHelpers.TransparentAwaitValueTask(vt); - // } - - // return vt.Result/vt.ThrowIfCompletedUnsuccessfully(); - // } - ILCodeStream* pCode = pSL->NewCodeStream(ILStubLinker::kDispatch); - - int token = GetTokenForThunkTarget(pCode, pTaskReturningVariant); - - DWORD localArg = 0; - if (msig.HasThis()) - { - pCode->EmitLDARG(localArg++); - } - for (UINT iArg = 0; iArg < msig.NumFixedArgs(); iArg++) - { - pCode->EmitLDARG(localArg++); - } - - // other(arg) - if (pTaskReturningVariant->IsAbstract()) - { - _ASSERTE(pTaskReturningVariant->IsCLRToCOMCall()); - pCode->EmitCALLVIRT(token, localArg, 1); - } - else - { - pCode->EmitCALL(token, localArg, 1); - } - - TypeHandle thLogicalRetType = msig.GetRetTypeHandleThrowing(); - if (IsAsyncVariantForValueTaskReturningMethod()) - { - MethodTable* pMTValueTask; - int isCompletedToken; - int completionResultToken; - int transparentAwaitValueTaskToken; - if (msig.IsReturnTypeVoid()) - { - pMTValueTask = CoreLibBinder::GetClass(CLASS__VALUETASK); - - MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK__GET_ISCOMPLETED); - MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK__THROW_IF_COMPLETED_UNSUCCESSFULLY); - MethodDesc* pMDTransparentAwaitValueTask = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_VALUE_TASK); - - isCompletedToken = pCode->GetToken(pMDValueTaskIsCompleted); - completionResultToken = pCode->GetToken(pMDCompletionResult); - transparentAwaitValueTaskToken = pCode->GetToken(pMDTransparentAwaitValueTask); - } - else - { - MethodTable* pMTValueTaskOpen = CoreLibBinder::GetClass(CLASS__VALUETASK_1); - pMTValueTask = ClassLoader::LoadGenericInstantiationThrowing(pMTValueTaskOpen->GetModule(), pMTValueTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); - - MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_ISCOMPLETED); - MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_RESULT); - MethodDesc* pMDTransparentAwaitValueTask = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_VALUE_TASK_OF_T); - - pMDValueTaskIsCompleted = FindOrCreateAssociatedMethodDesc(pMDValueTaskIsCompleted, pMTValueTask, FALSE, Instantiation(), FALSE); - pMDCompletionResult = FindOrCreateAssociatedMethodDesc(pMDCompletionResult, pMTValueTask, FALSE, Instantiation(), FALSE); - pMDTransparentAwaitValueTask = FindOrCreateAssociatedMethodDesc(pMDTransparentAwaitValueTask, pMDTransparentAwaitValueTask->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); - - isCompletedToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDValueTaskIsCompleted); - completionResultToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDCompletionResult); - transparentAwaitValueTaskToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, pMDTransparentAwaitValueTask); - } - - LocalDesc valueTaskLocalDesc(pMTValueTask); - DWORD valueTaskLocal = pCode->NewLocal(valueTaskLocalDesc); - ILCodeLabel* valueTaskCompletedLabel = pCode->NewCodeLabel(); - - // Store value task returned by call to actual user func - pCode->EmitSTLOC(valueTaskLocal); - pCode->EmitLDLOCA(valueTaskLocal); - - // Was it already completed? - pCode->EmitCALL(isCompletedToken, 1, 1); - pCode->EmitBRTRUE(valueTaskCompletedLabel); - - // No, tail await to TransparentAwaitValueTask - pCode->EmitLDLOC(valueTaskLocal); - pCode->EmitCALL(METHOD__ASYNC_HELPERS__TAIL_AWAIT, 0, 0); - pCode->EmitCALL(transparentAwaitValueTaskToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); - pCode->EmitRET(); - - // Yes, just get the result - pCode->EmitLabel(valueTaskCompletedLabel); - pCode->EmitLDLOCA(valueTaskLocal); - pCode->EmitCALL(completionResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); - pCode->EmitRET(); - } - else - { - MethodTable* pMTTask; - - int completedTaskResultToken; - int transparentAwaitToken; - - if (msig.IsReturnTypeVoid()) - { - pMTTask = CoreLibBinder::GetClass(CLASS__TASK); - - MethodDesc* pMDCompletedTask = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK); - MethodDesc* pMDTransparentAwait = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT); - - completedTaskResultToken = pCode->GetToken(pMDCompletedTask); - transparentAwaitToken = pCode->GetToken(pMDTransparentAwait); - } - else - { - MethodTable* pMTTaskOpen = CoreLibBinder::GetClass(CLASS__TASK_1); - pMTTask = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskOpen->GetModule(), pMTTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); - - MethodDesc* pMDCompletedTaskResult = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK_RESULT); - MethodDesc* pMDTransparentAwait = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_OF_T); - - pMDCompletedTaskResult = FindOrCreateAssociatedMethodDesc(pMDCompletedTaskResult, pMDCompletedTaskResult->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); - pMDTransparentAwait = FindOrCreateAssociatedMethodDesc(pMDTransparentAwait, pMDTransparentAwait->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); - - completedTaskResultToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, pMDCompletedTaskResult); - transparentAwaitToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, pMDTransparentAwait); - } - - LocalDesc taskLocalDesc(pMTTask); - DWORD taskLocal = pCode->NewLocal(taskLocalDesc); - ILCodeLabel* pGetResultLabel = pCode->NewCodeLabel(); - - // Store task returned by actual user func - pCode->EmitSTLOC(taskLocal); - - // Did it already complete? - pCode->EmitLDLOC(taskLocal); - pCode->EmitCALL(METHOD__TASK__GET_ISCOMPLETED, 1, 1); - pCode->EmitBRTRUE(pGetResultLabel); - - // No, so tail await to TransparentAwait - pCode->EmitLDLOC(taskLocal); - pCode->EmitCALL(METHOD__ASYNC_HELPERS__TAIL_AWAIT, 0, 0); - pCode->EmitCALL(transparentAwaitToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); - pCode->EmitRET(); - - // Yes, so just get the result - pCode->EmitLabel(pGetResultLabel); - pCode->EmitLDLOC(taskLocal); - pCode->EmitCALL(completedTaskResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1); - pCode->EmitRET(); - } -} - // 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. diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index cc515fd5f610e8..a7ef3a15801e76 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -719,6 +719,10 @@ DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT, TransparentAwait DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_OF_T, TransparentAwaitOfT, NoSig) DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_VALUE_TASK, TransparentAwaitValueTask, NoSig) DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_VALUE_TASK_OF_T, TransparentAwaitValueTaskOfT, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_TASK_WITH_RESULT, TransparentAwaitWithResult, SM_Task_RetVoid) +DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_VALUETASK_WITH_RESULT, TransparentAwaitWithResult, SM_ValueTask_RetVoid) +DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_TASK_OF_T_WITH_RESULT, TransparentAwaitWithResult, GM_TaskOfT_RetT) +DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_VALUETASK_OF_T_WITH_RESULT, TransparentAwaitWithResult, GM_ValueTaskOfT_RetT) DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK_RESULT, CompletedTaskResult, NoSig) DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK, CompletedTask, NoSig) DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_EXECUTION_CONTEXT, CaptureExecutionContext, NoSig) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index c0ec22f3e75a8f..91537b36919bad 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1310,7 +1310,7 @@ uint32_t CEEInfo::getThreadLocalFieldInfo (CORINFO_FIELD_HANDLE field, bool isG typeIndex = MethodTableAuxiliaryData::GetThreadStaticsInfo(pMT->GetAuxiliaryData())->NonGCTlsIndex.GetIndexOffset(); } - assert(typeIndex != TypeIDProvider::INVALID_TYPE_ID); + _ASSERTE(typeIndex != TypeIDProvider::INVALID_TYPE_ID); EE_TO_JIT_TRANSITION(); return typeIndex; @@ -3324,10 +3324,22 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr _ASSERTE(false); } + FinishComputeRuntimeLookup(sigBuilder, pCallerMD, pResultLookup); +} + +void CEEInfo::FinishComputeRuntimeLookup( + SigBuilder& sigBuilder, + MethodDesc* pCallerMD, + CORINFO_LOOKUP* pResultLookup) +{ + CORINFO_RUNTIME_LOOKUP* pResult = &pResultLookup->runtimeLookup; DictionaryEntrySignatureSource signatureSource = FromJIT; WORD slot; + MethodDesc* pContextMD = pCallerMD; + MethodTable* pContextMT = pCallerMD->GetMethodTable(); + // It's a method dictionary lookup if (pResultLookup->lookupKind.runtimeLookupKind == CORINFO_LOOKUP_METHODPARAM) { @@ -7588,6 +7600,12 @@ COR_ILMETHOD_DECODER* CEEInfo::getMethodInfoWorker( getMethodInfoILMethodHeaderHelper(cxt.Header, methInfo); localSig = SigPointer{ cxt.Header->LocalVarSig, cxt.Header->cbLocalVarSig }; } + + if (ftn->IsAsyncVariantMethod() && ftn->IsAsyncThunkMethod()) + { + // This is an async version and the IL belongs to the sync version. + methInfo->options = (CorInfoOptions)(methInfo->options | CORINFO_ASYNC_VERSION); + } } else if (ftn->IsDynamicMethod()) { @@ -10378,6 +10396,139 @@ void CEEInfo::getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut) EE_TO_JIT_TRANSITION(); } +CORINFO_METHOD_HANDLE CEEInfo::getAwaitReturnCall(CORINFO_METHOD_HANDLE callerHandle, CORINFO_LOOKUP* instArg) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + MethodDesc* pMD = NULL; + + JIT_TO_EE_TRANSITION(); + + instArg->lookupKind.needsRuntimeLookup = false; + instArg->constLookup.accessType = IAT_VALUE; + instArg->constLookup.addr = NULL; + + MethodDesc* pCallerMD = GetMethod(callerHandle); + + _ASSERTE(pCallerMD->IsAsyncVariantMethod() && pCallerMD->IsAsyncThunkMethod()); + + MetaSig sig(pCallerMD); + TypeHandle retType = sig.GetRetTypeHandleThrowing(); + MethodDesc* pTypicalAwaitMD; + + if (pCallerMD->IsAsyncVariantForValueTaskReturningMethod()) + { + if (sig.IsReturnTypeVoid()) + { + pTypicalAwaitMD = pMD = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_VALUETASK_WITH_RESULT); + } + else + { + pTypicalAwaitMD = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_VALUETASK_OF_T_WITH_RESULT); + pMD = MethodDesc::FindOrCreateAssociatedMethodDesc(pTypicalAwaitMD, pTypicalAwaitMD->GetMethodTable(), FALSE, Instantiation(&retType, 1), TRUE); + } + } + else + { + if (sig.IsReturnTypeVoid()) + { + pTypicalAwaitMD = pMD = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_TASK_WITH_RESULT); + } + else + { + pTypicalAwaitMD = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_TASK_OF_T_WITH_RESULT); + pMD = MethodDesc::FindOrCreateAssociatedMethodDesc(pTypicalAwaitMD, pTypicalAwaitMD->GetMethodTable(), FALSE, Instantiation(&retType, 1), TRUE); + } + } + + if (pMD->RequiresInstArg()) + { + if (retType.IsCanonicalSubtype()) + { + ComputeRuntimeLookupForAwaitCall(pCallerMD, pTypicalAwaitMD, instArg); + } + else + { + MethodDesc* pContext = MethodDesc::FindOrCreateAssociatedMethodDesc(pTypicalAwaitMD, pTypicalAwaitMD->GetMethodTable(), FALSE, Instantiation(&retType, 1), FALSE); + instArg->lookupKind.needsRuntimeLookup = false; + instArg->constLookup.accessType = IAT_VALUE; + instArg->constLookup.addr = pContext; + } + } + + EE_TO_JIT_TRANSITION(); + + return CORINFO_METHOD_HANDLE(pMD); +} + +// Compute the runtime lookup for the instantiation argument for an +// AsyncHelpers.TransparentAwait call, to be used for wrapping a return value +// from pCallerMD for its runtime async version. +// For example, if pCallerMD is ValueTask> Foo(), then we call this +// for the runtime async version of Foo and it gives a runtime lookup that +// computes the method desc for AsyncHelpers.TransparentAwait>. +void CEEInfo::ComputeRuntimeLookupForAwaitCall(MethodDesc* pCallerMD, MethodDesc* pTypicalAwaitMD, CORINFO_LOOKUP* lookup) +{ + lookup->lookupKind.needsRuntimeLookup = true; + + CORINFO_RUNTIME_LOOKUP* rlookup = &lookup->runtimeLookup; + + rlookup->signature = NULL; + rlookup->indirectFirstOffset = 0; + rlookup->indirectSecondOffset = 0; + + rlookup->sizeOffset = CORINFO_NO_SIZE_CHECK; + rlookup->indirections = CORINFO_USEHELPER; + + if (pCallerMD->RequiresInstMethodDescArg()) + { + lookup->lookupKind.runtimeLookupKind = CORINFO_LOOKUP_METHODPARAM; + rlookup->helper = CORINFO_HELP_RUNTIMEHANDLE_METHOD; + } + else if (pCallerMD->RequiresInstMethodTableArg()) + { + lookup->lookupKind.runtimeLookupKind = CORINFO_LOOKUP_CLASSPARAM; + rlookup->helper = CORINFO_HELP_RUNTIMEHANDLE_CLASS; + } + else + { + lookup->lookupKind.runtimeLookupKind = CORINFO_LOOKUP_THISOBJ; + rlookup->helper = CORINFO_HELP_RUNTIMEHANDLE_CLASS; + } + + SigBuilder sigBuilder; + sigBuilder.AppendData(MethodDescSlot); + + if (lookup->lookupKind.runtimeLookupKind != CORINFO_LOOKUP_METHODPARAM) + { + sigBuilder.AppendData(pCallerMD->GetMethodTable()->GetNumDicts() - 1); + } + + // Containing type + sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + sigBuilder.AppendPointer(pTypicalAwaitMD->GetMethodTable()); + + // Method flags + sigBuilder.AppendData(ENCODE_METHOD_SIG_MethodInstantiation | ENCODE_METHOD_SIG_InstantiatingStub); + + // Method + sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + sigBuilder.AppendPointer(pTypicalAwaitMD->GetMethodTable()); + sigBuilder.AppendData(RidFromToken(pTypicalAwaitMD->GetMemberDef())); + + // Finally the instantiation. + SigPointer resultSig = pCallerMD->GetAsyncThunkResultTypeSig(); + // 1 argument + sigBuilder.AppendData(1); + resultSig.ConvertToInternalExactlyOne(pCallerMD->GetModule(), NULL, &sigBuilder); + + FinishComputeRuntimeLookup(sigBuilder, pCallerMD, lookup); +} + static MethodTable* getContinuationType( size_t dataSize, bool* objRefs, diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index c5c8445d957a45..aebb381a0bf37d 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -299,6 +299,7 @@ class CEEInfo : public ICorJitInfo void GetTypeContext(CORINFO_CONTEXT_HANDLE context, SigTypeContext* pTypeContext); void HandleException(struct _EXCEPTION_POINTERS* pExceptionPointers); + public: #include "icorjitinfoimpl_generated.h" uint32_t getClassAttribsInternal (CORINFO_CLASS_HANDLE cls); @@ -408,6 +409,12 @@ class CEEInfo : public ICorJitInfo MethodDesc * pTemplateMD /* for method-based slots */, MethodDesc * pCallerMD, CORINFO_LOOKUP *pResultLookup); + void FinishComputeRuntimeLookup( + SigBuilder& sig, + MethodDesc* pCallerMD, + CORINFO_LOOKUP* pResultLookup); + + void ComputeRuntimeLookupForAwaitCall(MethodDesc* pCallerMD, MethodDesc* pTypicalAwaitMD, CORINFO_LOOKUP* lookup); #if defined(FEATURE_GDBJIT) CalledMethod * GetCalledMethods() { return m_pCalledMethods; } diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 88c986a9c6e4b7..fb3801964ee0cf 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -554,6 +554,11 @@ DEFINE_METASIG_T(SM(RefRuntimeAsyncAwaitState_RetValueTask, r(g(RUNTIME_ASYNC_AW DEFINE_METASIG_T(GM(RefRuntimeAsyncAwaitState_RetTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(g(RUNTIME_ASYNC_AWAIT_STATE)), GI(C(TASK_1), 1, M(0)))) DEFINE_METASIG_T(GM(RefRuntimeAsyncAwaitState_RetValueTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(g(RUNTIME_ASYNC_AWAIT_STATE)), GI(g(VALUETASK_1), 1, M(0)))) +DEFINE_METASIG_T(SM(Task_RetVoid, C(TASK), v)) +DEFINE_METASIG_T(SM(ValueTask_RetVoid, g(VALUETASK), v)) +DEFINE_METASIG_T(GM(TaskOfT_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, GI(C(TASK_1), 1, M(0)), M(0))) +DEFINE_METASIG_T(GM(ValueTaskOfT_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, GI(g(VALUETASK_1), 1, M(0)), M(0))) + // Undefine macros in case we include the file again in the compilation unit #undef DEFINE_METASIG diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index dc0c6b3cef2b08..0ff2b314c2c53d 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1546,7 +1546,7 @@ class MethodDesc LIMITED_METHOD_DAC_CONTRACT; // methods with transient IL bodies do not have IL headers - return IsIL() && !IsUnboxingStub() && !IsAsyncThunkMethod(); + return IsIL() && !IsUnboxingStub() && (!IsAsyncThunkMethod() || SupportsAsyncVersionCodegen()); } ULONG GetRVA(); @@ -2075,6 +2075,12 @@ class MethodDesc return hasAsyncFlags(asyncFlags, AsyncMethodFlags::ReturnDroppingThunk); } + inline bool SupportsAsyncVersionCodegen() const + { + LIMITED_METHOD_DAC_CONTRACT; + return IsAsyncThunkMethod() && IsAsyncVariantMethod() && !IsReturnDroppingThunk(); + } + inline bool MatchesAsyncVariantLookup(AsyncVariantLookup lookup) const { LIMITED_METHOD_DAC_CONTRACT; @@ -2303,13 +2309,11 @@ class MethodDesc bool TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); void EmitTaskReturningThunk(MethodDesc* pAsyncCallVariant, MetaSig& thunkMsig, ILStubLinker* pSL); - void EmitAsyncMethodThunk(MethodDesc* pTaskReturningVariant, MetaSig& msig, ILStubLinker* pSL); void EmitReturnDroppingThunk(MethodDesc* pAsyncOtherVariant, MetaSig& msig, ILStubLinker* pSL); - SigPointer GetAsyncThunkResultTypeSig(); int GetTokenForThunkTarget(ILCodeStream* pCode, MethodDesc* md); int GetTokenForGenericMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md); - int GetTokenForGenericTypeMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md); public: + SigPointer GetAsyncThunkResultTypeSig(); static void CreateDerivedTargetSig(MetaSig& msig, SigBuilder* stubSigBuilder); bool TryGenerateTransientILImplementation(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); void GenerateFunctionPointerCall(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); diff --git a/src/coreclr/vm/method.inl b/src/coreclr/vm/method.inl index 9331543274d7a5..84be3c9617b20f 100644 --- a/src/coreclr/vm/method.inl +++ b/src/coreclr/vm/method.inl @@ -178,7 +178,30 @@ inline bool MethodDesc::IsDiagnosticsHidden() // tolerate if the runtime-implemented frame is missing because they can still see the managed target method. WRAPPER_NO_CONTRACT; - return IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(); + if (IsILStub()) + { + return true; + } + + if (IsAsyncThunkMethod()) + { + if (IsReturnDroppingThunk()) + { + return true; + } + + if (!SupportsAsyncVersionCodegen()) + { + return true; + } + } + + if (IsWrapperStub()) + { + return true; + } + + return false; } inline BOOL MethodDesc::IsQCall() diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index d4220ae0185414..3a1243257892f5 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -689,7 +689,6 @@ namespace // So, if config provides no header and we see an implementation method desc, then just ask the method desc itself. if (ilHeader == NULL && pMD->IsAsyncVariantMethod()) { - _ASSERTE(!pMD->IsAsyncThunkMethod()); ilHeader = pMD->GetILHeader(); } diff --git a/src/tests/async/reflection/reflection.cs b/src/tests/async/reflection/reflection.cs index a5992542037f2e..db08b050071965 100644 --- a/src/tests/async/reflection/reflection.cs +++ b/src/tests/async/reflection/reflection.cs @@ -237,6 +237,9 @@ public class Accessors2 [Fact] public static void UnsafeAccessors() { + PrivateAsync1.s = 0; + PrivateAsync2.s = 0; + Accessors2.accessor(null, 7).GetAwaiter().GetResult(); Assert.Equal(4, PrivateAsync1.s); Assert.Equal(4, PrivateAsync2.s); @@ -246,6 +249,26 @@ public static void UnsafeAccessors() Assert.Equal(8, PrivateAsync2.s); } + [Fact] + public static void UnsafeAccessorsAsync() + { + UnsafeAccessorsAsyncInner().GetAwaiter().GetResult(); + } + + private static async Task UnsafeAccessorsAsyncInner() + { + PrivateAsync1.s = 0; + PrivateAsync2.s = 0; + + await Accessors2.accessor(null, 7); + Assert.Equal(4, PrivateAsync1.s); + Assert.Equal(4, PrivateAsync2.s); + + await Accessors1.accessor(null, 7); + Assert.Equal(8, PrivateAsync1.s); + Assert.Equal(8, PrivateAsync2.s); + } + [Fact] public static void CurrentMethod() {