diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index ad5108609191be..2db316bb13bc64 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5684,6 +5684,7 @@ class Compiler void impLoadLoc(unsigned ilLclNum, IL_OFFSET offset); bool impReturnInstruction(int prefixFlags, OPCODE& opcode); bool impWrapTopOfStackInAwait(); + bool impFoldAwaitedTopOfStack(); void impPoisonImplicitByrefsBeforeReturn(); // A free list of linked list nodes used to represent to-do stacks of basic blocks. diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 36d68da03a55ea..b0e7854b90de84 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -13172,6 +13172,10 @@ void Compiler::gtGetLclVarNameInfo(unsigned lclNum, const char** ilKindOut, cons { ilName = "TypeCtx"; } + else if (ilNum == (unsigned)ICorDebugInfo::ASYNC_CONTINUATION_ILNUM) + { + ilName = "AsyncCont"; + } else if (ilNum == (unsigned)ICorDebugInfo::UNKNOWN_ILNUM) { if (lclNumIsTrueCSE(lclNum)) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 4fe2fd1f150ace..6af09efed79635 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9091,7 +9091,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (isAwait) { _impResolveToken(CORINFO_TOKENKIND_Await); - if (resolvedToken.hMethod == nullptr) + if (resolvedToken.hMethod == NO_METHOD_HANDLE) { // This can happen in cases when the Task-returning method is not a runtime Async // function. For example "T M1(T arg) => arg" when called with a Task argument. @@ -11316,11 +11316,6 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) { return false; } - - if (info.compRetType == TYP_VOID) - { - impAppendTree(impPopStack().val, CHECK_SPILL_ALL, impCurStmtDI); - } } if (info.compRetType != TYP_VOID) @@ -11689,6 +11684,11 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) // bool Compiler::impWrapTopOfStackInAwait() { + if (impFoldAwaitedTopOfStack()) + { + return true; + } + CORINFO_LOOKUP instArgLookup; CORINFO_METHOD_HANDLE awaitMethod = info.compCompHnd->getAwaitReturnCall(info.compMethodHnd, &instArgLookup); @@ -11778,10 +11778,142 @@ bool Compiler::impWrapTopOfStackInAwait() awaitCall->SetIsAsync(asyncInfo); - impPushOnStack(toPush, makeTypeInfo(awaitSig.retType, awaitSig.retTypeClass)); + if (info.compRetType == TYP_VOID) + { + impAppendTree(toPush, CHECK_SPILL_ALL, impCurStmtDI); + } + else + { + impPushOnStack(toPush, makeTypeInfo(awaitSig.retType, awaitSig.retTypeClass)); + } return true; } +//------------------------------------------------------------------------ +// impFoldAwaitedTopOfStack: +// Fold a few patterns where introducing a call to +// AsyncHelpers.TransparentAwaitWithResult is unnecessary. +// +// Returns: +// True if the top of stack was folded and the importer stack was updated +// appropriately. +// +// Remarks: +// In async versions this folds these cases: +// +// - return new ValueTask() (like ValueTask.CompletedTask) +// - return default (ValueTask.CompletedTask) +// - return new ValueTask(value) (like ValueTask.FromResult) +// +// We cannot represent these values as async variants, so we cannot fold them +// in the simple intrinsic path where other cases like Task.FromResult and +// ValueTask.FromResult are folded. +// +bool Compiler::impFoldAwaitedTopOfStack() +{ + if (!opts.Tier0OptimizationEnabled()) + { + return false; + } + + GenTree* value = impStackTop().val; + if (!value->OperIsScalarLocal()) + { + return false; + } + + GenTreeLclVarCommon* valueLcl = value->AsLclVarCommon(); + + Statement* lastStmt = impLastStmt; + if (lastStmt == nullptr) + { + return false; + } + + GenTree* lastTree = lastStmt->GetRootNode(); + // Look for a valueTask = 0 init, for "new ValueTask()" and "default" cases. + if (lastTree->OperIs(GT_STORE_LCL_VAR)) + { + GenTreeLclVarCommon* storeLcl = lastTree->AsLclVarCommon(); + if (storeLcl->GetLclNum() != valueLcl->GetLclNum()) + { + return false; + } + + if (!storeLcl->Data()->IsIntegralConst(0)) + { + return false; + } + + impPopStack(); + if (info.compRetType == TYP_VOID) + { + lastTree->gtBashToNOP(); + } + else if (info.compRetType != TYP_STRUCT) + { + impPushOnStack(gtNewZeroConNode(info.compRetType), typeInfo(info.compRetType)); + lastTree->gtBashToNOP(); + } + else + { + unsigned returnLcl = lvaGrabTemp(true DEBUGARG("Return temp")); + lvaSetStruct(returnLcl, info.compMethodInfo->args.retTypeClass, false); + // Reuse the ValueTask zeroing + lastTree->AsLclVar()->SetLclNum(returnLcl); + impPushOnStack(gtNewLclVarNode(returnLcl), + makeTypeInfo(info.compMethodInfo->args.retType, info.compMethodInfo->args.retTypeClass)); + } + + JITDUMP("Optimized \"return new ValueTask()\" to return default\n"); + return true; + } + else if (lastTree->IsCall() && + lastTree->AsCall()->IsSpecialIntrinsic(this, NI_System_Threading_Tasks_ValueTask_1__ctor)) + { + CallArg* thisArg = lastTree->AsCall()->gtArgs.GetThisArg(); + if (!thisArg->GetNode()->OperIs(GT_LCL_ADDR) || + (thisArg->GetNode()->AsLclVarCommon()->GetLclNum() != valueLcl->GetLclNum())) + { + return false; + } + + CORINFO_SIG_INFO sig; + info.compCompHnd->getMethodSig(lastTree->AsCall()->gtCallMethHnd, &sig); + + if (sig.numArgs != 1) + { + return false; + } + + assert((sig.sigInst.classInstCount == 1) && (sig.sigInst.methInstCount == 0)); + CORINFO_CLASS_HANDLE paramClass = info.compCompHnd->getArgClass(&sig, sig.args); + if (paramClass != sig.sigInst.classInst[0]) + { + return false; + } + + CallArg* valueArg = lastTree->AsCall()->gtArgs.GetUserArgByIndex(1); + GenTree* value = valueArg->GetNode(); + + if (varTypeIsSmall(valueArg->GetSignatureType()) && fgCastNeeded(value, valueArg->GetSignatureType())) + { + value = gtNewCastNode(TYP_INT, value, false, valueArg->GetSignatureType()); + } + + StackEntry se = impPopStack(); + impPushOnStack(value, se.seTypeInfo); + JITDUMP("Optimized \"return new ValueTask(value)\" to return value directly:\n"); + DISPTREE(value); + + // Finally remove the old constructor call, which is unneeded. + lastTree->gtBashToNOP(); + return true; + } + + return false; +} + #ifdef DEBUG //------------------------------------------------------------------------ // impPoisonImplicitByrefsBeforeReturn: diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index a21557cff6accd..10351ae6db4908 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -3518,6 +3518,14 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, betterToExpand = opts.IsInstrumented(); break; + // Lightweight runtime async intrinsics + case NI_System_Threading_Tasks_Task_FromResult: + case NI_System_Threading_Tasks_Task_get_CompletedTask: + case NI_System_Threading_Tasks_ValueTask_FromResult: + case NI_System_Threading_Tasks_ValueTask_get_CompletedTask: + betterToExpand = (sig->callConv & CORINFO_CALLCONV_ASYNCCALL) != 0; + break; + default: // Various intrinsics are all small enough to prefer expansions. betterToExpand |= ni >= NI_SYSTEM_MATH_START && ni <= NI_SYSTEM_MATH_END; @@ -5088,6 +5096,56 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, break; } + case NI_System_Threading_Tasks_Task_FromResult: + case NI_System_Threading_Tasks_ValueTask_FromResult: + { + assert(sig->sigInst.methInstCount == 1); + + if ((sig->callConv & CORINFO_CALLCONV_ASYNCCALL) == 0) + { + break; + } + + CORINFO_CLASS_HANDLE typeHnd = sig->sigInst.methInst[0]; + ClassLayout* layout = nullptr; + var_types type = TypeHandleToVarType(typeHnd, &layout); + + GenTree* value = impPopStack().val; + if (varTypeIsStruct(value)) + { + value = impNormStructVal(value, CHECK_SPILL_ALL); + } + else + { + value = impImplicitR4orR8Cast(value, type); + value = impImplicitIorI4Cast(value, type); + } + + if (varTypeIsSmall(type) && fgCastNeeded(value, type)) + { + value = gtNewCastNode(TYP_INT, value, false, type); + } + + retNode = value; + break; + } + case NI_System_Threading_Tasks_Task_get_CompletedTask: + case NI_System_Threading_Tasks_ValueTask_get_CompletedTask: + { + if ((sig->callConv & CORINFO_CALLCONV_ASYNCCALL) == 0) + { + break; + } + + retNode = gtNewNothingNode(); + break; + } + case NI_System_Threading_Tasks_ValueTask_1__ctor: + { + // We fold this when we try to wrap it in await in async versions + isSpecial = compIsAsyncVersion(); + break; + } default: break; } @@ -11599,6 +11657,35 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) result = NI_System_Threading_Tasks_Task_ConfigureAwait; } } + else if (strcmp(className, "Task") == 0) + { + if (strcmp(methodName, "FromResult") == 0) + { + result = NI_System_Threading_Tasks_Task_FromResult; + } + else if (strcmp(methodName, "get_CompletedTask") == 0) + { + result = NI_System_Threading_Tasks_Task_get_CompletedTask; + } + } + else if (strcmp(className, "ValueTask") == 0) + { + if (strcmp(methodName, "FromResult") == 0) + { + result = NI_System_Threading_Tasks_ValueTask_FromResult; + } + else if (strcmp(methodName, "get_CompletedTask") == 0) + { + result = NI_System_Threading_Tasks_ValueTask_get_CompletedTask; + } + } + else if (strcmp(className, "ValueTask`1") == 0) + { + if (strcmp(methodName, ".ctor") == 0) + { + result = NI_System_Threading_Tasks_ValueTask_1__ctor; + } + } } } } diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 80c1fea64ad444..5f96a30903d1d2 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -167,6 +167,13 @@ enum NamedIntrinsic : unsigned short NI_System_Threading_Interlocked_MemoryBarrier, NI_System_Threading_Tasks_Task_ConfigureAwait, + NI_System_Threading_Tasks_Task_FromResult, + NI_System_Threading_Tasks_Task_get_CompletedTask, + + NI_System_Threading_Tasks_ValueTask_FromResult, + NI_System_Threading_Tasks_ValueTask_get_CompletedTask, + + NI_System_Threading_Tasks_ValueTask_1__ctor, // These two are special marker IDs so that we still get the inlining profitability boost NI_System_Numerics_Intrinsic, diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 5381d2d4418d0c..46e2c1da4e0146 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -1611,7 +1611,11 @@ WaitHandle IAsyncResult.AsyncWaitHandle internal static readonly Task s_cachedCompleted = new Task(false, default, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default); /// Gets a task that's already been completed successfully. - public static Task CompletedTask => s_cachedCompleted; + public static Task CompletedTask + { + [Intrinsic] + get => s_cachedCompleted; + } /// /// Provides an event that can be used to wait for completion. @@ -5541,6 +5545,7 @@ private static int WaitAnyCore(Task[] tasks, int millisecondsTimeout, Cancellati /// The result to store into the completed task. /// The successfully completed task. [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm + [Intrinsic] public static unsafe Task FromResult(TResult result) { // The goal of this function is to be give back a cached task if possible, or to otherwise give back a new task. diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 90f86c68159c28..dbb08c49559c07 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -110,12 +110,17 @@ private ValueTask(object? obj, short token, bool continueOnCapturedContext) } /// Gets a task that has already completed successfully. - public static ValueTask CompletedTask => default; + public static ValueTask CompletedTask + { + [Intrinsic] + get => default; + } /// Creates a that's completed successfully with the specified result. /// The type of the result returned by the task. /// The result to store into the completed task. /// The successfully completed task. + [Intrinsic] public static ValueTask FromResult(TResult result) => new ValueTask(result); @@ -479,6 +484,7 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContex /// Initialize the with a result value. /// The result. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Intrinsic] public ValueTask(TResult result) { _result = result;