Skip to content

Commit 014c11c

Browse files
committed
Support for ValueTaskSource
1 parent db5c040 commit 014c11c

File tree

7 files changed

+204
-83
lines changed

7 files changed

+204
-83
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Runtime.Versioning;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14+
using System.Threading.Tasks.Sources;
1415

1516
namespace System.Runtime.CompilerServices
1617
{
@@ -134,9 +135,14 @@ public static partial class AsyncHelpers
134135
private struct RuntimeAsyncAwaitState
135136
{
136137
public Continuation? SentinelContinuation;
138+
139+
// The following are the possible introducers of asynchrony into a chain of awaits.
140+
// In other words - when we build a chain of continuations it would be logicaly attached
141+
// to one of these notifiers.
137142
public ICriticalNotifyCompletion? CriticalNotifier;
138143
public INotifyCompletion? Notifier;
139-
public Task? CalledTask;
144+
public IValueTaskSourceNotifier? ValueTaskSourceNotifier;
145+
public Task? TaskNotifier;
140146
}
141147

142148
[ThreadStatic]
@@ -171,17 +177,35 @@ private static unsafe Continuation AllocContinuationClass(Continuation prevConti
171177
return newContinuation;
172178
}
173179

180+
/// <summary>
181+
/// Used by internal thunks that implement awaiting on Task or a ValueTask.
182+
/// A ValueTask may wrap:
183+
/// - Completed result (we never await this)
184+
/// - Task
185+
/// - ValueTaskSource
186+
/// Therefore, when we are awaiting a ValueTask completion we are really
187+
/// awaiting a completion of an underlying Task or ValueTaskSource.
188+
/// </summary>
189+
/// <param name="o"> Task or a ValueTaskNotifier whose completion we are awaiting.</param>
174190
[BypassReadyToRun]
175191
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)]
176192
[RequiresPreviewFeatures]
177-
private static void TransparentAwaitTask(Task t)
193+
private static void TransparentAwait(object o)
178194
{
179195
ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState;
180196
Continuation? sentinelContinuation = state.SentinelContinuation;
181197
if (sentinelContinuation == null)
182198
state.SentinelContinuation = sentinelContinuation = new Continuation();
183199

184-
state.CalledTask = t;
200+
if (o is Task t)
201+
{
202+
state.TaskNotifier = t;
203+
}
204+
else
205+
{
206+
state.ValueTaskSourceNotifier = (IValueTaskSourceNotifier)o;
207+
}
208+
185209
AsyncSuspend(sentinelContinuation);
186210
}
187211

@@ -419,13 +443,16 @@ public static unsafe void DispatchContinuations<T, TOps>(T task) where T : Task,
419443
public static void HandleSuspended<T, TOps>(T task) where T : Task, ITaskCompletionAction where TOps : IRuntimeAsyncTaskOps<T>
420444
{
421445
ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState;
446+
422447
ICriticalNotifyCompletion? critNotifier = state.CriticalNotifier;
423448
INotifyCompletion? notifier = state.Notifier;
424-
Task? calledTask = state.CalledTask;
449+
IValueTaskSourceNotifier? vtsNotifier = state.ValueTaskSourceNotifier;
450+
Task? taskNotifier = state.TaskNotifier;
425451

426452
state.CriticalNotifier = null;
427453
state.Notifier = null;
428-
state.CalledTask = null;
454+
state.ValueTaskSourceNotifier = null;
455+
state.TaskNotifier = null;
429456

430457
Continuation sentinelContinuation = state.SentinelContinuation!;
431458
Continuation headContinuation = sentinelContinuation.Next!;
@@ -447,16 +474,44 @@ public static void HandleSuspended<T, TOps>(T task) where T : Task, ITaskComplet
447474
{
448475
critNotifier.UnsafeOnCompleted(TOps.GetContinuationAction(task));
449476
}
450-
else if (calledTask != null)
477+
else if (taskNotifier != null)
451478
{
452479
// Runtime async callable wrapper for task returning
453480
// method. This implements the context transparent
454481
// forwarding and makes these wrappers minimal cost.
455-
if (!calledTask.TryAddCompletionAction(task))
482+
if (!taskNotifier.TryAddCompletionAction(task))
456483
{
457484
ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal: true);
458485
}
459486
}
487+
else if (vtsNotifier != null)
488+
{
489+
// The awaiter must inform the ValueTaskSource source on whether the continuation
490+
// wants to run on a context, although the source may decide to ignore the suggestion.
491+
// Since the behavior of the source takes precedence, we clear the context flags of
492+
// the awaiting continuation (so it will run transparently on what the source decides)
493+
// and then tell the source if the awaiting frame prefers to continue on a context.
494+
// The reason why we do it here and not when the notifier is created is because
495+
// the continuation chain builds from the innermost frame out and at the time when the
496+
// notifier is created we do not know yet if the caller wants to continue on a context.
497+
ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None;
498+
ContinuationFlags continuationFlags = headContinuation.Next!.Flags;
499+
500+
const ContinuationFlags continueOnContextFlags =
501+
ContinuationFlags.ContinueOnCapturedSynchronizationContext |
502+
ContinuationFlags.ContinueOnCapturedTaskScheduler;
503+
504+
if ((continuationFlags & continueOnContextFlags) != 0)
505+
{
506+
// if await has captured some context, inform the source
507+
configFlags |= ValueTaskSourceOnCompletedFlags.UseSchedulingContext;
508+
}
509+
510+
// Clear continuation flags, so that continuation runs transparently
511+
headContinuation.Next!.Flags &= ~continueFlags;
512+
513+
vtsNotifier.OnCompleted(o => TOps.GetContinuationAction((T)o!).Invoke(), task, configFlags);
514+
}
460515
else
461516
{
462517
Debug.Assert(notifier != null);

src/coreclr/jit/importer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4432,6 +4432,14 @@ bool Compiler::impIsImplicitTailCallCandidate(
44324432
return false;
44334433
}
44344434

4435+
// We keep finding cases where tailcalling awaits is problematic.
4436+
// Perhaps at the end there will be a set of conditions when tailcalling is ok.
4437+
// For now we will just forbid.
4438+
if (prefixFlags & PREFIX_IS_TASK_AWAIT)
4439+
{
4440+
return false;
4441+
}
4442+
44354443
#if !FEATURE_TAILCALL_OPT_SHARED_RETURN
44364444
// the block containing call is marked as BBJ_RETURN
44374445
// We allow shared ret tail call optimization on recursive calls even under

src/coreclr/vm/asyncthunks.cpp

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -475,27 +475,30 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m
475475
_ASSERTE(!pAsyncOtherVariant->IsVoid());
476476

477477
// Implement IL that is effectively the following:
478-
/*
479-
{
480-
Task task = other(arg);
481-
if (!task.IsCompleted)
482-
{
483-
// Magic function which will suspend the current run of async methods
484-
AsyncHelpers.TransparentAwaitTask(task);
485-
}
486-
return AsyncHelpers.CompletedTaskResult(task);
487-
}
478+
// {
479+
// Task task = other(arg);
480+
// if (!task.IsCompleted)
481+
// {
482+
// // Magic function which will suspend the current run of async methods
483+
// AsyncHelpers.TransparentAwait(task);
484+
// }
485+
// return AsyncHelpers.CompletedTaskResult(task);
486+
// }
488487

489-
For ValueTask:
490-
{
491-
ValueTask vt = other(arg);
492-
if (vt.IsCompleted)
493-
return vt.Result/vt.ThrowIfCompletedUnsuccessfully();
488+
// For ValueTask:
494489

495-
Task task = vt.AsTask();
496-
<same code as above>
497-
}
498-
*/
490+
// {
491+
// ValueTask vt = other(arg);
492+
// if (!vt.IsCompleted)
493+
// {
494+
// taskOrNotifier = vt.AsTaskOrNotifier()
495+
496+
// // Magic function which will suspend the current run of async methods
497+
// AsyncHelpers.TransparentAwait(taskOrNotifier);
498+
// }
499+
500+
// return vt.Result/vt.ThrowIfCompletedUnsuccessfully();
501+
// }
499502
ILCodeStream* pCode = pSL->NewCodeStream(ILStubLinker::kDispatch);
500503

501504
int userFuncToken;
@@ -563,32 +566,27 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m
563566
pCode->EmitLDARG(localArg++);
564567
}
565568

569+
// other(arg)
566570
pCode->EmitCALL(userFuncToken, localArg, 1);
567571

568572
TypeHandle thLogicalRetType = msig.GetRetTypeHandleThrowing();
569573
if (IsValueTaskAsyncThunk())
570574
{
571-
// Emit
572-
// if (vtask.IsCompleted)
573-
// return vtask.Result/vtask.ThrowIfCompletedUnsuccessfully()
574-
// task = vtask.AsTask()
575-
//
576-
577575
MethodTable* pMTValueTask;
578576
int isCompletedToken;
579577
int completionResultToken;
580-
int asTaskToken;
578+
int asTaskOrNotifierToken;
581579
if (msig.IsReturnTypeVoid())
582580
{
583581
pMTValueTask = CoreLibBinder::GetClass(CLASS__VALUETASK);
584582

585583
MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK__GET_ISCOMPLETED);
586584
MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK__THROW_IF_COMPLETED_UNSUCCESSFULLY);
587-
MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_TASK);
585+
MethodDesc* pMDAsTaskOrNotifier = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_TASK_OR_NOTIFIER);
588586

589587
isCompletedToken = pCode->GetToken(pMDValueTaskIsCompleted);
590588
completionResultToken = pCode->GetToken(pMDCompletionResult);
591-
asTaskToken = pCode->GetToken(pMDAsTask);
589+
asTaskOrNotifierToken = pCode->GetToken(pMDAsTaskOrNotifier);
592590
}
593591
else
594592
{
@@ -597,73 +595,75 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m
597595

598596
MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_ISCOMPLETED);
599597
MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_RESULT);
600-
MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_TASK);
598+
MethodDesc* pMDAsTaskOrNotifier = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_TASK_OR_NOTIFIER);
601599

602600
pMDValueTaskIsCompleted = FindOrCreateAssociatedMethodDesc(pMDValueTaskIsCompleted, pMTValueTask, FALSE, Instantiation(), FALSE);
603601
pMDCompletionResult = FindOrCreateAssociatedMethodDesc(pMDCompletionResult, pMTValueTask, FALSE, Instantiation(), FALSE);
604-
pMDAsTask = FindOrCreateAssociatedMethodDesc(pMDAsTask, pMTValueTask, FALSE, Instantiation(), FALSE);
602+
pMDAsTaskOrNotifier = FindOrCreateAssociatedMethodDesc(pMDAsTaskOrNotifier, pMTValueTask, FALSE, Instantiation(), FALSE);
605603

606604
isCompletedToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDValueTaskIsCompleted);
607605
completionResultToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDCompletionResult);
608-
asTaskToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDAsTask);
606+
asTaskOrNotifierToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, pMDAsTaskOrNotifier);
609607
}
610608

611609
LocalDesc valueTaskLocalDesc(pMTValueTask);
612610
DWORD valueTaskLocal = pCode->NewLocal(valueTaskLocalDesc);
613-
ILCodeLabel* valueTaskNotCompletedLabel = pCode->NewCodeLabel();
611+
ILCodeLabel* valueTaskCompletedLabel = pCode->NewCodeLabel();
614612

615613
// Store value task returned by call to actual user func
616614
pCode->EmitSTLOC(valueTaskLocal);
617-
618615
pCode->EmitLDLOCA(valueTaskLocal);
619616
pCode->EmitCALL(isCompletedToken, 1, 1);
620-
pCode->EmitBRFALSE(valueTaskNotCompletedLabel);
617+
pCode->EmitBRTRUE(valueTaskCompletedLabel);
621618

622619
pCode->EmitLDLOCA(valueTaskLocal);
623-
pCode->EmitCALL(completionResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1);
624-
pCode->EmitRET();
620+
pCode->EmitCALL(asTaskOrNotifierToken, 1, 1);
621+
pCode->EmitCALL(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT, 1, 0);
625622

626-
pCode->EmitLabel(valueTaskNotCompletedLabel);
623+
pCode->EmitLabel(valueTaskCompletedLabel);
627624
pCode->EmitLDLOCA(valueTaskLocal);
628-
pCode->EmitCALL(asTaskToken, 1, 1);
629-
}
630-
631-
MethodTable* pMTTask;
632-
633-
int completedTaskResultToken;
634-
if (msig.IsReturnTypeVoid())
635-
{
636-
pMTTask = CoreLibBinder::GetClass(CLASS__TASK);
637-
638-
MethodDesc* pMDCompletedTask = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK);
639-
completedTaskResultToken = pCode->GetToken(pMDCompletedTask);
625+
pCode->EmitCALL(completionResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1);
626+
pCode->EmitRET();
640627
}
641628
else
642629
{
643-
MethodTable* pMTTaskOpen = CoreLibBinder::GetClass(CLASS__TASK_1);
644-
pMTTask = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskOpen->GetModule(), pMTTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable();
630+
MethodTable* pMTTask;
645631

646-
MethodDesc* pMDCompletedTaskResult = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK_RESULT);
647-
pMDCompletedTaskResult = FindOrCreateAssociatedMethodDesc(pMDCompletedTaskResult, pMDCompletedTaskResult->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE);
648-
completedTaskResultToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, pMDCompletedTaskResult);
649-
}
632+
int completedTaskResultToken;
633+
if (msig.IsReturnTypeVoid())
634+
{
635+
pMTTask = CoreLibBinder::GetClass(CLASS__TASK);
650636

651-
LocalDesc taskLocalDesc(pMTTask);
652-
DWORD taskLocal = pCode->NewLocal(taskLocalDesc);
653-
ILCodeLabel* pGetResultLabel = pCode->NewCodeLabel();
637+
MethodDesc* pMDCompletedTask = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK);
638+
completedTaskResultToken = pCode->GetToken(pMDCompletedTask);
639+
}
640+
else
641+
{
642+
MethodTable* pMTTaskOpen = CoreLibBinder::GetClass(CLASS__TASK_1);
643+
pMTTask = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskOpen->GetModule(), pMTTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable();
654644

655-
// Store task returned by actual user func or by ValueTask.AsTask
656-
pCode->EmitSTLOC(taskLocal);
645+
MethodDesc* pMDCompletedTaskResult = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__COMPLETED_TASK_RESULT);
646+
pMDCompletedTaskResult = FindOrCreateAssociatedMethodDesc(pMDCompletedTaskResult, pMDCompletedTaskResult->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE);
647+
completedTaskResultToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, pMDCompletedTaskResult);
648+
}
657649

658-
pCode->EmitLDLOC(taskLocal);
659-
pCode->EmitCALL(METHOD__TASK__GET_ISCOMPLETED, 1, 1);
660-
pCode->EmitBRTRUE(pGetResultLabel);
650+
LocalDesc taskLocalDesc(pMTTask);
651+
DWORD taskLocal = pCode->NewLocal(taskLocalDesc);
652+
ILCodeLabel* pGetResultLabel = pCode->NewCodeLabel();
661653

662-
pCode->EmitLDLOC(taskLocal);
663-
pCode->EmitCALL(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT_TASK, 1, 0);
654+
// Store task returned by actual user func or by ValueTask.AsTask
655+
pCode->EmitSTLOC(taskLocal);
664656

665-
pCode->EmitLabel(pGetResultLabel);
666-
pCode->EmitLDLOC(taskLocal);
667-
pCode->EmitCALL(completedTaskResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1);
668-
pCode->EmitRET();
657+
pCode->EmitLDLOC(taskLocal);
658+
pCode->EmitCALL(METHOD__TASK__GET_ISCOMPLETED, 1, 1);
659+
pCode->EmitBRTRUE(pGetResultLabel);
660+
661+
pCode->EmitLDLOC(taskLocal);
662+
pCode->EmitCALL(METHOD__ASYNC_HELPERS__TRANSPARENT_AWAIT, 1, 0);
663+
664+
pCode->EmitLabel(pGetResultLabel);
665+
pCode->EmitLDLOC(taskLocal);
666+
pCode->EmitCALL(completedTaskResultToken, 1, msig.IsReturnTypeVoid() ? 0 : 1);
667+
pCode->EmitRET();
668+
}
669669
}

src/coreclr/vm/corelib.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor,
346346
DEFINE_CLASS(VALUETASK_1, Tasks, ValueTask`1)
347347
DEFINE_METHOD(VALUETASK_1, GET_ISCOMPLETED, get_IsCompleted, NoSig)
348348
DEFINE_METHOD(VALUETASK_1, GET_RESULT, get_Result, NoSig)
349-
DEFINE_METHOD(VALUETASK_1, AS_TASK, AsTask, IM_RetTaskOfT)
349+
DEFINE_METHOD(VALUETASK_1, AS_TASK_OR_NOTIFIER, AsTaskOrNotifier, IM_RetObj)
350350

351351
DEFINE_CLASS(VALUETASK, Tasks, ValueTask)
352352
DEFINE_METHOD(VALUETASK, FROM_EXCEPTION, FromException, SM_Exception_RetValueTask)
@@ -355,7 +355,7 @@ DEFINE_METHOD(VALUETASK, FROM_RESULT_T, FromResult, GM_T_RetValueTaskOfT)
355355
DEFINE_METHOD(VALUETASK, GET_COMPLETED_TASK, get_CompletedTask, SM_RetValueTask)
356356
DEFINE_METHOD(VALUETASK, GET_ISCOMPLETED, get_IsCompleted, NoSig)
357357
DEFINE_METHOD(VALUETASK, THROW_IF_COMPLETED_UNSUCCESSFULLY, ThrowIfCompletedUnsuccessfully, NoSig)
358-
DEFINE_METHOD(VALUETASK, AS_TASK, AsTask, IM_RetTask)
358+
DEFINE_METHOD(VALUETASK, AS_TASK_OR_NOTIFIER, AsTaskOrNotifier, IM_RetObj)
359359

360360
DEFINE_CLASS(TASK_1, Tasks, Task`1)
361361
DEFINE_METHOD(TASK_1, GET_RESULTONSUCCESS, get_ResultOnSuccess, NoSig)
@@ -741,9 +741,9 @@ DEFINE_METHOD(ASYNC_HELPERS, VALUETASK_FROM_EXCEPTION, ValueTaskFromExcepti
741741
DEFINE_METHOD(ASYNC_HELPERS, VALUETASK_FROM_EXCEPTION_1, ValueTaskFromException, GM_Exception_RetValueTaskOfT)
742742

743743
DEFINE_METHOD(ASYNC_HELPERS, UNSAFE_AWAIT_AWAITER_1, UnsafeAwaitAwaiter, GM_T_RetVoid)
744-
DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT_TASK, TransparentAwaitTask, NoSig)
745-
DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK_RESULT, CompletedTaskResult, NoSig)
746-
DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK, CompletedTask, NoSig)
744+
DEFINE_METHOD(ASYNC_HELPERS, TRANSPARENT_AWAIT, TransparentAwait, NoSig)
745+
DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK_RESULT, CompletedTaskResult, NoSig)
746+
DEFINE_METHOD(ASYNC_HELPERS, COMPLETED_TASK, CompletedTask, NoSig)
747747
DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_EXECUTION_CONTEXT, CaptureExecutionContext, NoSig)
748748
DEFINE_METHOD(ASYNC_HELPERS, RESTORE_EXECUTION_CONTEXT, RestoreExecutionContext, NoSig)
749749
DEFINE_METHOD(ASYNC_HELPERS, CAPTURE_CONTINUATION_CONTEXT, CaptureContinuationContext, NoSig)

0 commit comments

Comments
 (0)