From 4fd0709532bdb3f12738293ab366a62d4bd6c560 Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Fri, 5 Sep 2025 18:14:34 -0700
Subject: [PATCH 1/4] Enable async feature/tests
---
src/coreclr/inc/clrconfigvalues.h | 2 +-
src/tests/async/Directory.Build.props | 2 +-
src/tests/async/Directory.Build.targets | 5 -----
src/tests/async/struct/struct.cs | 4 ++++
4 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h
index 542510f79b4a74..3c2a9c3dab3dd8 100644
--- a/src/coreclr/inc/clrconfigvalues.h
+++ b/src/coreclr/inc/clrconfigvalues.h
@@ -720,7 +720,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64
#endif
// Runtime-async
-RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 0, "Enables runtime async method support")
+RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 1, "Enables runtime async method support")
///
/// Uncategorized
diff --git a/src/tests/async/Directory.Build.props b/src/tests/async/Directory.Build.props
index a1f7d48cf60a7d..f505092384b1b1 100644
--- a/src/tests/async/Directory.Build.props
+++ b/src/tests/async/Directory.Build.props
@@ -5,7 +5,7 @@
true
- $(NoWarn);xUnit1013;CS1998
+ $(NoWarn);xUnit1013;CS1998;SYSLIB5007
false
$(Features);runtime-async=on
diff --git a/src/tests/async/Directory.Build.targets b/src/tests/async/Directory.Build.targets
index 5a4d413a9e1f62..61a1b2822ed114 100644
--- a/src/tests/async/Directory.Build.targets
+++ b/src/tests/async/Directory.Build.targets
@@ -4,10 +4,5 @@
true
-
-
- true
-
-
diff --git a/src/tests/async/struct/struct.cs b/src/tests/async/struct/struct.cs
index 7c5062d499cb42..32116fe3cec777 100644
--- a/src/tests/async/struct/struct.cs
+++ b/src/tests/async/struct/struct.cs
@@ -45,6 +45,8 @@ private struct S
public S(int value) => Value = value;
+ // Roslyn NYI - async in structs. Remove opt-out once supported.
+ [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)]
public async Task Test()
{
// TODO: C# compiler is expected to do this, but not in the prototype.
@@ -59,6 +61,8 @@ public async Task Test()
AssertEqual(102, @this.Value);
}
+ // Roslyn NYI - async in structs. Remove opt-out once supported.
+ [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)]
private async Task InstanceCall()
{
// TODO: C# compiler is expected to do this, but not in the prototype.
From a356d289c0d702b48e362370da8d884c52450a3d Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Mon, 13 Oct 2025 14:45:20 -0700
Subject: [PATCH 2/4] Fix for configured ValueTask sources
---
.../CompilerServices/AsyncHelpers.CoreCLR.cs | 44 +++++++++
src/coreclr/vm/asyncthunks.cpp | 4 +-
src/coreclr/vm/corelib.h | 4 +-
.../src/System/Threading/Tasks/ValueTask.cs | 96 ++++++++++++++++---
4 files changed, 131 insertions(+), 17 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 a7ab5415d25d97..29c2c01c892eda 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
@@ -10,6 +10,7 @@
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
+using System.Threading.Tasks.Sources;
namespace System.Runtime.CompilerServices
{
@@ -491,6 +492,49 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet
}
else if (calledTask != null)
{
+ if (calledTask is IValueTaskAsTask vtTask)
+ {
+ if (!vtTask.IsConfigured)
+ {
+ // ValueTaskSource can be configured to use scheduling context or to ignore it.
+ // The awaiter must inform the source whether it wants to continue on a context,
+ // but the source may decide to ignore the suggestion. Since the behavior of
+ // the source takes precedence, we clear the context flags from the awaiting
+ // continuation (so it will run transparently on what source decides) and tell
+ // the source that awaiting frame prefers to continue on a context.
+ // The reason why we do it here is because the continuation chain builds from the
+ // innermost frame out and when the leaf thunk links the head continuation,
+ // it does not know if the caller wants to continue in a context. Thus the thunk
+ // creates an "unconfigured" IValueTaskAsTask and we configure it here.
+ ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None;
+ CorInfoContinuationFlags continuationFlags = headContinuation.Next!.Flags;
+
+ const CorInfoContinuationFlags continueOnContextFlags =
+ CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_SYNCHRONIZATION_CONTEXT |
+ CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_TASK_SCHEDULER;
+
+ if ((continuationFlags & continueOnContextFlags) != 0)
+ {
+ // effectively move context flags from headContinuation to the source config.
+ configFlags |= ValueTaskSourceOnCompletedFlags.UseSchedulingContext;
+ headContinuation.Next!.Flags &= ~continueOnContextFlags;
+ }
+
+ vtTask.Configure(configFlags);
+
+ if (!calledTask.TryAddCompletionAction(task))
+ {
+ // calledTask has already completed and we need to schedule
+ // our code for execution ourselves.
+ // Restore the continuation flags before doing that.
+ headContinuation.Next!.Flags = continuationFlags;
+ ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal: true);
+ }
+
+ return;
+ }
+ }
+
// Runtime async callable wrapper for task returning
// method. This implements the context transparent
// forwarding and makes these wrappers minimal cost.
diff --git a/src/coreclr/vm/asyncthunks.cpp b/src/coreclr/vm/asyncthunks.cpp
index cf4e290bc6e7c4..ccc0c615d23d8f 100644
--- a/src/coreclr/vm/asyncthunks.cpp
+++ b/src/coreclr/vm/asyncthunks.cpp
@@ -591,7 +591,7 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m
MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK__GET_ISCOMPLETED);
MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK__THROW_IF_COMPLETED_UNSUCCESSFULLY);
- MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_TASK);
+ MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_UNCONFIGURED_TASK);
isCompletedToken = pCode->GetToken(pMDValueTaskIsCompleted);
completionResultToken = pCode->GetToken(pMDCompletionResult);
@@ -604,7 +604,7 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m
MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_ISCOMPLETED);
MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_RESULT);
- MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_TASK);
+ MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_UNCONFIGURED_TASK);
pMDValueTaskIsCompleted = FindOrCreateAssociatedMethodDesc(pMDValueTaskIsCompleted, pMTValueTask, FALSE, Instantiation(), FALSE);
pMDCompletionResult = FindOrCreateAssociatedMethodDesc(pMDCompletionResult, pMTValueTask, FALSE, Instantiation(), FALSE);
diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h
index bfc6cc586ca38c..b9429f2591a31c 100644
--- a/src/coreclr/vm/corelib.h
+++ b/src/coreclr/vm/corelib.h
@@ -346,7 +346,7 @@ DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor,
DEFINE_CLASS(VALUETASK_1, Tasks, ValueTask`1)
DEFINE_METHOD(VALUETASK_1, GET_ISCOMPLETED, get_IsCompleted, NoSig)
DEFINE_METHOD(VALUETASK_1, GET_RESULT, get_Result, NoSig)
-DEFINE_METHOD(VALUETASK_1, AS_TASK, AsTask, IM_RetTaskOfT)
+DEFINE_METHOD(VALUETASK_1, AS_UNCONFIGURED_TASK, AsUnconfiguredTask, IM_RetTaskOfT)
DEFINE_CLASS(VALUETASK, Tasks, ValueTask)
DEFINE_METHOD(VALUETASK, FROM_EXCEPTION, FromException, SM_Exception_RetValueTask)
@@ -355,7 +355,7 @@ DEFINE_METHOD(VALUETASK, FROM_RESULT_T, FromResult, GM_T_RetValueTaskOfT)
DEFINE_METHOD(VALUETASK, GET_COMPLETED_TASK, get_CompletedTask, SM_RetValueTask)
DEFINE_METHOD(VALUETASK, GET_ISCOMPLETED, get_IsCompleted, NoSig)
DEFINE_METHOD(VALUETASK, THROW_IF_COMPLETED_UNSUCCESSFULLY, ThrowIfCompletedUnsuccessfully, NoSig)
-DEFINE_METHOD(VALUETASK, AS_TASK, AsTask, IM_RetTask)
+DEFINE_METHOD(VALUETASK, AS_UNCONFIGURED_TASK, AsUnconfiguredTask, IM_RetTask)
DEFINE_CLASS(TASK_1, Tasks, Task`1)
DEFINE_METHOD(TASK_1, GET_RESULTONSUCCESS, get_ResultOnSuccess, NoSig)
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs
index 90f86c68159c28..87185e18eed710 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs
@@ -176,7 +176,17 @@ public Task AsTask()
return
obj == null ? Task.CompletedTask :
obj as Task ??
- GetTaskForValueTaskSource(Unsafe.As(obj));
+ GetTaskForValueTaskSource(Unsafe.As(obj), ValueTaskSourceOnCompletedFlags.None);
+ }
+
+ internal Task AsUnconfiguredTask()
+ {
+ object? obj = _obj;
+ Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
+ return
+ obj == null ? Task.CompletedTask :
+ obj as Task ??
+ GetTaskForValueTaskSource(Unsafe.As(obj), IValueTaskAsTask._unconfigured);
}
/// Gets a that may be used at any point in the future.
@@ -187,7 +197,7 @@ obj as Task ??
/// The is passed in rather than reading and casting
/// so that the caller can pass in an object it's already validated.
///
- private Task GetTaskForValueTaskSource(IValueTaskSource t)
+ private Task GetTaskForValueTaskSource(IValueTaskSource t, ValueTaskSourceOnCompletedFlags flags)
{
ValueTaskSourceStatus status = t.GetStatus(_token);
if (status != ValueTaskSourceStatus.Pending)
@@ -225,11 +235,11 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t)
}
}
- return new ValueTaskSourceAsTask(t, _token);
+ return new ValueTaskSourceAsTask(t, _token, flags);
}
/// Type used to create a to represent a .
- private sealed class ValueTaskSourceAsTask : Task
+ private sealed class ValueTaskSourceAsTask : Task, IValueTaskAsTask
{
private static readonly Action