Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
146 changes: 139 additions & 7 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>(T arg) => arg" when called with a Task argument.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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<T>(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:
Expand Down
87 changes: 87 additions & 0 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
}
Comment thread
jakobbotsch marked this conversation as resolved.
else if (strcmp(className, "ValueTask`1") == 0)
{
if (strcmp(methodName, ".ctor") == 0)
{
result = NI_System_Threading_Tasks_ValueTask_1__ctor;
}
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1611,7 +1611,11 @@ WaitHandle IAsyncResult.AsyncWaitHandle
internal static readonly Task<VoidTaskResult> s_cachedCompleted = new Task<VoidTaskResult>(false, default, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);

/// <summary>Gets a task that's already been completed successfully.</summary>
public static Task CompletedTask => s_cachedCompleted;
public static Task CompletedTask
{
[Intrinsic]
get => s_cachedCompleted;
}

/// <summary>
/// Provides an event that can be used to wait for completion.
Expand Down Expand Up @@ -5541,6 +5545,7 @@ private static int WaitAnyCore(Task[] tasks, int millisecondsTimeout, Cancellati
/// <param name="result">The result to store into the completed task.</param>
/// <returns>The successfully completed task.</returns>
[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<TResult> FromResult<TResult>(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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,17 @@ private ValueTask(object? obj, short token, bool continueOnCapturedContext)
}

/// <summary>Gets a task that has already completed successfully.</summary>
public static ValueTask CompletedTask => default;
public static ValueTask CompletedTask
{
[Intrinsic]
get => default;
}

/// <summary>Creates a <see cref="ValueTask{TResult}"/> that's completed successfully with the specified result.</summary>
/// <typeparam name="TResult">The type of the result returned by the task.</typeparam>
/// <param name="result">The result to store into the completed task.</param>
/// <returns>The successfully completed task.</returns>
[Intrinsic]
public static ValueTask<TResult> FromResult<TResult>(TResult result) =>
new ValueTask<TResult>(result);

Expand Down Expand Up @@ -479,6 +484,7 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContex
/// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <typeparamref name="TResult"/> result value.</summary>
/// <param name="result">The result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Intrinsic]
public ValueTask(TResult result)
{
_result = result;
Expand Down
Loading