Skip to content

Recognize Task<->ValueTask adaptions in async versions #130081

Draft
jakobbotsch wants to merge 6 commits into
dotnet:mainfrom
jakobbotsch:async-versions-adapt-tasks
Draft

Recognize Task<->ValueTask adaptions in async versions #130081
jakobbotsch wants to merge 6 commits into
dotnet:mainfrom
jakobbotsch:async-versions-adapt-tasks

Conversation

@jakobbotsch

@jakobbotsch jakobbotsch commented Jul 1, 2026

Copy link
Copy Markdown
Member

Support looking through return new ValueTask(TaskReturningFunction()) and return ValueTaskReturningFunction().AsTask() in async versions. These can be transformed into async calls.

This is a common pattern in the BCL, e.g.:

return ReceiveAsync(buffer, socketFlags, fromNetworkStream, default).AsTask();

return new ValueTask(WriteAsyncCore(buffer, cancellationToken));

Example:

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static ValueTask TestTaskToValueTask()
    {
        return new ValueTask(TestTask());
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static async Task TestTask()
    {
        await Task.Yield();
    }

Diff:

 G_M37697_IG01:
-       push     rbp
-       sub      rsp, 80
-       lea      rbp, [rsp+0x50]
-       vxorps   xmm4, xmm4, xmm4
-       vmovdqu  ymmword ptr [rbp-0x30], ymm4
-       vmovdqa  xmmword ptr [rbp-0x10], xmm4
-       mov      gword ptr [rbp+0x10], rcx
-						;; size=28 bbWeight=1 PerfScore 5.08
+       sub      rsp, 40
+						;; size=4 bbWeight=1 PerfScore 0.25
 G_M37697_IG02:
-       vxorps   xmm0, xmm0, xmm0
-       vmovdqu  xmmword ptr [rbp-0x10], xmm0
-       call     [AsyncMicro.Program:TestTask():System.Threading.Tasks.Task]
-       mov      gword ptr [rbp-0x18], rax
-       mov      rdx, gword ptr [rbp-0x18]
-       lea      rcx, [rbp-0x10]
-       call     [System.Threading.Tasks.ValueTask:.ctor(System.Threading.Tasks.Task):this]
-						;; size=33 bbWeight=1 PerfScore 9.83
-G_M37697_IG03:
-       vmovdqu  xmm0, xmmword ptr [rbp-0x10]
-       vmovdqu  xmmword ptr [rbp-0x28], xmm0
-						;; size=10 bbWeight=1 PerfScore 2.00
-G_M37697_IG04:
-       lea      rdx, [rbp-0x28]
        xor      rcx, rcx
-       call     [System.Runtime.CompilerServices.AsyncHelpers:TransparentAwaitWithResult(System.Threading.Tasks.ValueTask)]
-       mov      gword ptr [rbp-0x30], rcx
-       cmp      gword ptr [rbp-0x30], 0
-       jne      SHORT G_M37697_IG06
+       call     [AsyncMicro.Program:TestTask()]
+       test     rcx, rcx
+       jne      SHORT G_M37697_IG04
        xor      ecx, ecx
-						;; size=25 bbWeight=1 PerfScore 7.00
-G_M37697_IG05:
-       add      rsp, 80
-       pop      rbp
+						;; size=15 bbWeight=1 PerfScore 4.75
+G_M37697_IG03:
+       add      rsp, 40
        ret      
-						;; size=6 bbWeight=1 PerfScore 1.75
-G_M37697_IG06:
-       mov      rax, gword ptr [rbp-0x30]
-       mov      rcx, rax
-						;; size=7 bbWeight=0 PerfScore 0.00
-G_M37697_IG07:
-       add      rsp, 80
-       pop      rbp
+						;; size=5 bbWeight=1 PerfScore 1.25
+G_M37697_IG04:
+       add      rsp, 40
        ret      
-						;; size=6 bbWeight=0 PerfScore 0.00
+						;; size=5 bbWeight=0 PerfScore 0.00
 
-; Total bytes of code 115, prolog size 24, PerfScore 25.67, instruction count 31, allocated bytes for code 115 (MethodHash=6db76cbe) for method AsyncMicro.Program:TestTaskToValueTask() (Tier0)
+; Total bytes of code 29, prolog size 4, PerfScore 6.25, instruction count 10, allocated bytes for code 29 (MethodHash=6db76cbe) for method AsyncMicro.Program:TestTaskToValueTask() (FullOpts)
 ; ============================================================
 

Support looking through `return new ValueTask(TaskReturningFunction())`
and `return ValueTaskReturningFunction().AsTask()` in async versions.
These can be transformed into async calls.
Copilot AI review requested due to automatic review settings July 1, 2026 14:05
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 1, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Comment on lines +7271 to +7278
// We cannot inline if the callee returns valueTask.AsTask() in an
// async version. We need to preserve the continuation in this case to
// be able to mark it with CORINFO_CONTINUATION_VALUETASK_ADAPTED_TO_TASK.
if ((prefixFlags & PREFIX_IS_ADAPTED_FROM_VALUETASK) != 0)
{
compInlineResult->NoteFatal(InlineObservation::CALLEE_AWAIT);
return;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very satisfied with this. The main problem is that return valueTask.AsTask() comes with additional ceremony around handling IValueTaskSource because the returned Task does not have the exact same semantics as ValueTask for that case. In particular the Task adapter always passes ValueTaskSourceOnCompletedFlags.None to the IValueTaskSource. We need to teach our IValueTaskSource handling about this special case, and that requires introducing the new flag, but then we end up not able to inline once we see this case.

One solution may be to have the JIT insert an extra Continuation into the chain when this flag is present if we inlined.

In any case I think this optimization even if it means we disallow inlining will still be beneficial.

Comment on lines +48 to +49
ResultIndexFirstBit = 11,
ResultIndexNumBits = 21,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require another R2R version bump. I think I will include this flag change in #129894 instead.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends CoreCLR “runtime-async / async-version” recognition so the JIT can see through common TaskValueTask adapters (new ValueTask(task) and valueTask.AsTask()) and treat them as direct async calls, reducing wrapper overhead. It also adds tests intended to cover these adapter patterns.

Changes:

  • Mark ValueTask / ValueTask<T> Task-based constructors and AsTask() methods as [Intrinsic] so the JIT can pattern-match them.
  • Teach the JIT importer to recognize async-version tail patterns involving ValueTask adapters and propagate a new “adapted” flag into async-call lowering and continuation flags.
  • Add/adjust async tests/projects for adapter scenarios and align the IL project’s assembly naming.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/tests/async/async-versions/async-versions.ilproj Adds an IL SDK project for the IL-authored async-version tests; removes injected C# helper attribute source.
src/tests/async/async-versions/async-versions.il Renames the IL assembly to match the project name (async-versions).
src/tests/async/async-versions-task-adapters/async-versions-task-adapters.csproj New test project to validate Task↔ValueTask adapter behavior.
src/tests/async/async-versions-task-adapters/async-versions-task-adapters.cs New xUnit tests covering new ValueTask(Task), ValueTask.AsTask(), and ValueTask<Task<int>> overload binding behavior.
src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs Adds [Intrinsic] to relevant constructors/AsTask() overloads to enable JIT recognition.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs Adds a new continuation flag bit and shifts bitfield layout constants.
src/coreclr/jit/namedintrinsiclist.h Adds new NamedIntrinsic IDs for ValueTask/ValueTask<T> ctor and AsTask.
src/coreclr/jit/importercalls.cpp Blocks certain tail-await optimizations/inlining for adapted patterns; wires new intrinsic lookups.
src/coreclr/jit/importer.cpp Factors stloc/ldloca matching; adds async-version tail-call pattern matching for AsTask() and newobj ValueTask(..).
src/coreclr/jit/gentree.h Adds AsyncCallInfo.IsValueTaskAsTask marker.
src/coreclr/jit/compiler.h Adds PREFIX_IS_ADAPTED_FROM_VALUETASK prefix flag.
src/coreclr/jit/async.cpp Sets the new continuation flag based on IsValueTaskAsTask.
src/coreclr/inc/corinfo.h Adds CORINFO_CONTINUATION_VALUETASK_ADAPTED_TO_TASK and updates bitfield layout constants.

Comment thread src/coreclr/jit/importer.cpp
Copilot AI review requested due to automatic review settings July 1, 2026 14:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.

Comment on lines +6116 to +6127
if (info.compRetType != TYP_VOID)
{
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])
{
// This is "class ValueTask<T> { ValueTask(T value) }" overload
// which is not what we are looking for. That one gets folded
// by impFoldAwaitedTopOfStack.
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants