Recognize Task<->ValueTask adaptions in async versions #130081
Recognize Task<->ValueTask adaptions in async versions #130081jakobbotsch wants to merge 6 commits into
Conversation
Support looking through `return new ValueTask(TaskReturningFunction())` and `return ValueTaskReturningFunction().AsTask()` in async versions. These can be transformed into async calls.
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
| // 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; | ||
| } |
There was a problem hiding this comment.
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.
| ResultIndexFirstBit = 11, | ||
| ResultIndexNumBits = 21, |
There was a problem hiding this comment.
This would require another R2R version bump. I think I will include this flag change in #129894 instead.
There was a problem hiding this comment.
Pull request overview
This PR extends CoreCLR “runtime-async / async-version” recognition so the JIT can see through common Task↔ValueTask 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 andAsTask()methods as[Intrinsic]so the JIT can pattern-match them. - Teach the JIT importer to recognize async-version tail patterns involving
ValueTaskadapters 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. |
| 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; | ||
| } | ||
| } |
Support looking through
return new ValueTask(TaskReturningFunction())andreturn ValueTaskReturningFunction().AsTask()in async versions. These can be transformed into async calls.This is a common pattern in the BCL, e.g.:
runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs
Line 268 in 06362e8
runtime/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Unix.cs
Line 176 in 5ee89e4
Example:
Diff: