From 0d9e09818038ee9d45100c0f9115f18e476f9dbb Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 1 Sep 2026 16:39:01 +0200 Subject: [PATCH] Avoid duplicate ValueTask checks in transparent awaits Classify ValueTask backing objects once in TransparentAwait and pass typed Task or IValueTaskSource instances to the suspend helpers. This mirrors the existing AsyncHelpers.Await implementation and removes repeated type checks and temporary ValueTask materialization from runtime-async tail awaits. This is about 2% RPS improvement on the TechEmpower Arm64 JSON benchmarks. That benchmark was spending a not insignificant time in `CastHelpers.IsInstanceOfType` invoked by the duplicated `is Task` check that was inside AsyncHelpers.TransparentSuspend. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1d56f651-7596-4378-81ee-21221f3c746e --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 148 ++++++++---------- 1 file changed, 66 insertions(+), 82 deletions(-) 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 90f74e4b867457..228458d68ebfc4 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 @@ -435,64 +435,30 @@ private static unsafe T Suspend(Task task, ConfigureAwaitOptions options) return default!; } - /// - /// Used by internal thunks that implement awaiting on ValueTask. - /// A ValueTask may wrap: - /// - Completed result (we never await this) - /// - Task - /// - ValueTaskSource - /// Therefore, when we are awaiting a ValueTask completion we are really - /// awaiting a completion of an underlying Task or ValueTaskSource. - /// - /// ValueTask whose completion we are awaiting. [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] - private static unsafe void TransparentSuspend(ValueTask valueTask) + private static unsafe void TransparentSuspend(IValueTaskSource source, short token) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); - Continuation nextCont; - object? obj = valueTask._obj; - if (obj is Task t) + ValueTaskSourceContinuation? vtsCont = state.CachedValueTaskSourceContinuation; + if (vtsCont != null) { - RuntimeAsyncTaskContinuation? taskCont = state.CachedTaskContinuation; - if (taskCont != null) - { - state.CachedTaskContinuation = null; - } - else - { - taskCont = new RuntimeAsyncTaskContinuation(); - } - - taskCont.Initialize(t); - state.StackState->TaskContinuation = taskCont; - nextCont = taskCont; + state.CachedValueTaskSourceContinuation = null; } else { - ValueTaskSourceContinuation? vtsCont = state.CachedValueTaskSourceContinuation; - if (vtsCont != null) - { - state.CachedValueTaskSourceContinuation = null; - } - else - { - vtsCont = new ValueTaskSourceContinuation(); - } - - Debug.Assert(obj is IValueTaskSource); - vtsCont.Initialize(Unsafe.As(ref obj), valueTask._token); - state.StackState->ValueTaskSourceContinuation = vtsCont; - nextCont = vtsCont; + vtsCont = new ValueTaskSourceContinuation(); } - sentinelContinuation.Next = nextCont; + vtsCont.Initialize(source, token); + sentinelContinuation.Next = vtsCont; + state.StackState->ValueTaskSourceContinuation = vtsCont; state.CaptureContexts(); - AsyncSuspend(nextCont); + AsyncSuspend(vtsCont); } [Intrinsic] @@ -536,50 +502,27 @@ private static unsafe void Suspend(IValueTaskSource source, short token, bool co [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] - private static unsafe T TransparentSuspend(ValueTask valueTask) + private static unsafe T TransparentSuspend(IValueTaskSource source, short token) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); - Continuation nextCont; - object? obj = valueTask._obj; - if (obj is Task t) + ValueTaskSourceContinuation? vtsCont = state.CachedValueTaskSourceContinuation; + if (vtsCont != null) { - RuntimeAsyncTaskContinuation? taskCont = state.CachedTaskContinuation; - if (taskCont != null) - { - state.CachedTaskContinuation = null; - } - else - { - taskCont = new RuntimeAsyncTaskContinuation(); - } - - taskCont.Initialize(t); - state.StackState->TaskContinuation = taskCont; - nextCont = taskCont; + state.CachedValueTaskSourceContinuation = null; } else { - ValueTaskSourceContinuation? vtsCont = state.CachedValueTaskSourceContinuation; - if (vtsCont != null) - { - state.CachedValueTaskSourceContinuation = null; - } - else - { - vtsCont = new ValueTaskSourceContinuation(); - } - - Debug.Assert(obj is IValueTaskSource); - vtsCont.Initialize(Unsafe.As>(ref obj), valueTask._token); - state.StackState->ValueTaskSourceContinuation = vtsCont; - nextCont = vtsCont; + vtsCont = new ValueTaskSourceContinuation(); } - sentinelContinuation.Next = nextCont; + vtsCont.Initialize(source, token); + + sentinelContinuation.Next = vtsCont; + state.StackState->ValueTaskSourceContinuation = vtsCont; state.CaptureContexts(); - AsyncSuspend(nextCont); + AsyncSuspend(vtsCont); return default!; } @@ -706,14 +649,35 @@ private static void TransparentAwait(Task task) [MethodImpl(MethodImplOptions.Async)] private static void TransparentAwait(ValueTask task) { - if (!task.IsCompleted) + object? obj = task._obj; + if (obj == null) + { + return; + } + + if (obj is Task t) + { + if (!t.IsCompleted) + { + TailAwait(); + TransparentSuspend(t); + return; + } + + TaskAwaiter.ValidateEnd(t); + return; + } + + Debug.Assert(obj is IValueTaskSource); + IValueTaskSource vts = Unsafe.As(ref obj); + if (vts.GetStatus(task._token) == ValueTaskSourceStatus.Pending) { TailAwait(); - TransparentSuspend(task); + TransparentSuspend(vts, task._token); return; } - task.ThrowIfCompletedUnsuccessfully(); + vts.GetResult(task._token); } [BypassReadyToRun] @@ -734,13 +698,33 @@ private static T TransparentAwait(Task task) [MethodImpl(MethodImplOptions.Async)] private static T TransparentAwait(ValueTask task) { - if (!task.IsCompleted) + object? obj = task._obj; + if (obj == null) + { + return task._result!; + } + + if (obj is Task t) + { + if (!t.IsCompleted) + { + TailAwait(); + return TransparentSuspend(t); + } + + TaskAwaiter.ValidateEnd(t); + return t.ResultOnSuccess; + } + + Debug.Assert(obj is IValueTaskSource); + IValueTaskSource vts = Unsafe.As>(ref obj); + if (vts.GetStatus(task._token) == ValueTaskSourceStatus.Pending) { TailAwait(); - return TransparentSuspend(task); + return TransparentSuspend(vts, task._token); } - return task.Result; + return vts.GetResult(task._token); } // Represents execution of a chain of suspended and resuming runtime