diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp
index 1bc7423a44b..7725b32dac5 100644
--- a/src/coreclr/jit/importercalls.cpp
+++ b/src/coreclr/jit/importercalls.cpp
@@ -528,7 +528,8 @@ var_types Compiler::impImportCall(OPCODE opcode,
// calls in JIT generated state machines only.
if (compIsAsync2() &&
((ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_AwaitAwaiterFromRuntimeAsync) ||
- (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_UnsafeAwaitAwaiterFromRuntimeAsync)))
+ (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_UnsafeAwaitAwaiterFromRuntimeAsync) ||
+ (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_Await)))
{
assert((call != nullptr) && call->OperIs(GT_CALL));
call->AsCall()->gtIsAsyncCall = true;
@@ -913,7 +914,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
impPopCallArgs(sig, call->AsCall());
// Extra args
- if ((instParam != nullptr) || sig->isAsyncCall() || (varArgsCookie != nullptr))
+ if ((instParam != nullptr) || call->AsCall()->IsAsync2() || (varArgsCookie != nullptr))
{
if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L)
{
@@ -923,7 +924,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
.WellKnown(WellKnownArg::VarArgsCookie));
}
- if (sig->isAsyncCall())
+ if (call->AsCall()->IsAsync2())
{
call->AsCall()->gtArgs.PushFront(this, NewCallArg::Primitive(gtNewNull(), TYP_REF)
.WellKnown(WellKnownArg::AsyncContinuation));
@@ -943,7 +944,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
NewCallArg::Primitive(instParam).WellKnown(WellKnownArg::InstParam));
}
- if (sig->isAsyncCall())
+ if (call->AsCall()->IsAsync2())
{
call->AsCall()->gtArgs.PushBack(this, NewCallArg::Primitive(gtNewNull(), TYP_REF)
.WellKnown(WellKnownArg::AsyncContinuation));
@@ -3376,7 +3377,8 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
}
if ((ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_AwaitAwaiterFromRuntimeAsync) ||
- (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_UnsafeAwaitAwaiterFromRuntimeAsync))
+ (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_UnsafeAwaitAwaiterFromRuntimeAsync) ||
+ (ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_Await))
{
// These are marked intrinsics simply to mark the call node as async,
// which the caller will do. Make sure we keep pIntrinsicName assigned
@@ -10840,6 +10842,11 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
result =
NI_System_Runtime_CompilerServices_RuntimeHelpers_UnsafeAwaitAwaiterFromRuntimeAsync;
}
+ else if (strcmp(methodName, "Await") == 0)
+ {
+ result =
+ NI_System_Runtime_CompilerServices_RuntimeHelpers_Await;
+ }
else if (strcmp(methodName, "SuspendAsync2") == 0)
{
result = NI_System_Runtime_CompilerServices_RuntimeHelpers_SuspendAsync2;
diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h
index 3b64215ea79..54b8225d6f4 100644
--- a/src/coreclr/jit/namedintrinsiclist.h
+++ b/src/coreclr/jit/namedintrinsiclist.h
@@ -120,6 +120,7 @@ enum NamedIntrinsic : unsigned short
NI_System_Runtime_CompilerServices_RuntimeHelpers_GetMethodTable,
NI_System_Runtime_CompilerServices_RuntimeHelpers_AwaitAwaiterFromRuntimeAsync,
NI_System_Runtime_CompilerServices_RuntimeHelpers_UnsafeAwaitAwaiterFromRuntimeAsync,
+ NI_System_Runtime_CompilerServices_RuntimeHelpers_Await,
NI_System_Runtime_CompilerServices_RuntimeHelpers_SuspendAsync2,
NI_System_Runtime_CompilerServices_RuntimeHelpers_get_RuntimeAsyncViaJitGeneratedStateMachines,
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml
index 4897d4b5ffe..99abece3c82 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml
@@ -857,6 +857,12 @@
CP0002
M:System.Threading.Lock.#ctor(System.Boolean)
+
+ CP0002
+ M:System.Runtime.CompilerServices.RuntimeHelpers.Await``1(System.Threading.Tasks.Task{``0})
+ ref/net10.0/System.Private.CoreLib.dll
+ lib/net10.0/System.Private.CoreLib.dll
+
CP0002
M:System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync``1(``0)
diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h
index abea233a074..e9537ff001e 100644
--- a/src/coreclr/vm/corelib.h
+++ b/src/coreclr/vm/corelib.h
@@ -685,6 +685,7 @@ DEFINE_METHOD(RUNTIME_HELPERS, FINALIZE_VALUETASK_RETURNING_THUNK, Finalize
DEFINE_METHOD(RUNTIME_HELPERS, FINALIZE_VALUETASK_RETURNING_THUNK_1, FinalizeValueTaskReturningThunk, GM_Continuation_RetValueTaskOfT)
DEFINE_METHOD(RUNTIME_HELPERS, UNSAFE_AWAIT_AWAITER_FROM_RUNTIME_ASYNC_1, UnsafeAwaitAwaiterFromRuntimeAsync, GM_T_RetVoid)
DEFINE_METHOD(RUNTIME_HELPERS, AWAIT_AWAITER_FROM_RUNTIME_ASYNC_1, AwaitAwaiterFromRuntimeAsync, GM_T_RetVoid)
+DEFINE_METHOD(RUNTIME_HELPERS, AWAIT_1, Await, GM_TaskOfT_RetT)
DEFINE_CLASS(SPAN_HELPERS, System, SpanHelpers)
DEFINE_METHOD(SPAN_HELPERS, MEMSET, Fill, SM_RefByte_Byte_UIntPtr_RetVoid)
diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h
index 7b6ac5cf69e..a4f0a923186 100644
--- a/src/coreclr/vm/metasig.h
+++ b/src/coreclr/vm/metasig.h
@@ -571,6 +571,7 @@ DEFINE_METASIG_T(IM(RetValueTask, _, g(VALUETASK)))
DEFINE_METASIG_T(GM(Exception_RetTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, C(EXCEPTION), GI(C(TASK_1), 1, M(0))))
DEFINE_METASIG_T(GM(T_RetTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, M(0), GI(C(TASK_1), 1, M(0))))
+DEFINE_METASIG_T(GM(TaskOfT_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, GI(C(TASK_1), 1, M(0)), M(0)))
DEFINE_METASIG_T(GM(Exception_RetValueTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, C(EXCEPTION), GI(g(VALUETASK_1), 1, M(0))))
DEFINE_METASIG_T(GM(T_RetValueTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, M(0), GI(g(VALUETASK_1), 1, M(0))))
diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp
index 49168a9b734..2ebee8a097d 100644
--- a/src/coreclr/vm/method.hpp
+++ b/src/coreclr/vm/method.hpp
@@ -70,7 +70,12 @@ enum class AsyncMethodKind
AsyncImplHelper,
// Synthetic Async2 method that forwards to the NotAsync Task-returning method
- AsyncThunkHelper
+ AsyncThunkHelper,
+
+ // Actual IL method that is explicitly declared as Async2 and thus compiled into a state machine.
+ // Such methods do not get Async thunks and can only be called from another Async2 method using Async2 call convention.
+ // This is used in a few infrastructure methods like `Await`
+ AsyncImplExplicit,
};
struct AsyncMethodData
@@ -1829,10 +1834,13 @@ class MethodDesc
// CONSIDER: We probably need a better name for the concept, but it is hard to beat shortness of "async2"
inline bool IsAsync2Method() const
{
- // Right now the only Async2 methods that exist are synthetic helpers.
- // It is possible to declare an Async2 method directly in IL/Metadata,
- // but we do not have a scenario for that.
- return IsAsyncHelperMethod();
+ LIMITED_METHOD_DAC_CONTRACT;
+ if (!HasAsyncMethodData())
+ return false;
+ auto asyncKind = GetAddrOfAsyncMethodData()->kind;
+ return asyncKind == AsyncMethodKind::AsyncThunkHelper ||
+ asyncKind == AsyncMethodKind::AsyncImplHelper ||
+ asyncKind == AsyncMethodKind::AsyncImplExplicit;
}
inline bool IsStructMethodOperatingOnCopy()
diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp
index f864e625aa8..c1765dfd3d2 100644
--- a/src/coreclr/vm/methodtablebuilder.cpp
+++ b/src/coreclr/vm/methodtablebuilder.cpp
@@ -3605,7 +3605,18 @@ MethodTableBuilder::EnumerateClassMethods()
else
{
_ASSERTE(IsAsyncTaskMethodNormal(asyncMethodType));
- pNewMethod->SetAsyncMethodKind(AsyncMethodKind::NotAsync);
+
+ if (IsMiAsync(dwImplFlags))
+ {
+ // TODO: VS must validate that only a few special methods can do this.
+ // the possibility is useful, but should not become a general
+ // feature by accident.
+ pNewMethod->SetAsyncMethodKind(AsyncMethodKind::AsyncImplExplicit);
+ }
+ else
+ {
+ pNewMethod->SetAsyncMethodKind(AsyncMethodKind::NotAsync);
+ }
}
pDeclaredMethod = pNewMethod;
diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp
index 6aec5a3ef49..1bc08bf81b8 100644
--- a/src/coreclr/vm/threadsuspend.cpp
+++ b/src/coreclr/vm/threadsuspend.cpp
@@ -4915,7 +4915,9 @@ bool IsSpecialCaseAsyncRet(MethodDesc* pMD)
// causing loading to happen? Also, can we just mark them as async2 in SPC,
// or force them to be fully interruptible?
LPCUTF8 name = pMD->GetName();
- return strcmp(name, "UnsafeAwaitAwaiterFromRuntimeAsync") == 0 || strcmp(name, "AwaitAwaiterFromRuntimeAsync") == 0;
+ return strcmp(name, "UnsafeAwaitAwaiterFromRuntimeAsync") == 0 ||
+ strcmp(name, "AwaitAwaiterFromRuntimeAsync") == 0 ||
+ strcmp(name, "Await") == 0;
}
static bool GetReturnAddressHijackInfo(EECodeInfo *pCodeInfo, ReturnKind *pReturnKind, bool* hasAsyncRet)
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs
index f5dfe8afc57..0c946d16aca 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs
@@ -178,8 +178,8 @@ public static unsafe ReadOnlySpan CreateSpan(RuntimeFieldHandle fldHandle)
#if !NATIVEAOT
[Intrinsic]
- [MethodImpl(MethodImplOptions.NoInlining)]
[BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining | (MethodImplOptions)0x0400)] // NoInlining | Async
public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion
{
ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState;
@@ -196,7 +196,7 @@ public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) wher
// recognizes as an async2 call.
[Intrinsic]
[BypassReadyToRun]
- [MethodImpl(MethodImplOptions.NoInlining)]
+ [MethodImpl(MethodImplOptions.NoInlining | (MethodImplOptions)0x0400)] // NoInlining | Async
public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion
{
ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState;
@@ -208,6 +208,25 @@ public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter
SuspendAsync2(sentinelContinuation);
return;
}
+
+ // TODO: should this be called "AwaitFromRuntimeAsync" ? (i.e. same as above, but no "Awaiter")
+ //
+ // Marked intrinsic since this needs to be
+ // recognizes as an async2 call.
+ [Intrinsic]
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining | (MethodImplOptions)0x0400)] // NoInlining | Async
+ public static T Await(Task task)
+ {
+ TaskAwaiter awaiter = task.GetAwaiter();
+ if (!awaiter.IsCompleted)
+ {
+ UnsafeAwaitAwaiterFromRuntimeAsync(awaiter);
+ }
+
+ return awaiter.GetResult();
+ }
+
#endif
}
}
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 0cd7d5b7fce..d58f86cd27a 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -13708,6 +13708,7 @@ public static void RunModuleConstructor(System.ModuleHandle module) { }
public delegate void TryCode(object? userData);
public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { }
public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion { }
+ public static T Await(System.Threading.Tasks.Task task) { throw null; }
}
public sealed partial class RuntimeWrappedException : System.Exception
{
diff --git a/src/tests/async/varying-yields-await.csproj b/src/tests/async/varying-yields-await.csproj
new file mode 100644
index 00000000000..a36e587b076
--- /dev/null
+++ b/src/tests/async/varying-yields-await.csproj
@@ -0,0 +1,9 @@
+
+
+ True
+ AWAIT;$(DefineConstants)
+
+
+
+
+
diff --git a/src/tests/async/varying-yields.cs b/src/tests/async/varying-yields.cs
index 6edc30e8118..50de5a4920f 100644
--- a/src/tests/async/varying-yields.cs
+++ b/src/tests/async/varying-yields.cs
@@ -4,6 +4,8 @@
//#define ASYNC1_TASK
//#define ASYNC1_VALUETASK
+#pragma warning disable 4014, 1998
+
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -91,9 +93,19 @@ async2 Task
double liveState3 = _yieldProbability;
if (depth == 0)
+#if AWAIT
+ return RuntimeHelpers.Await(Loop());
+#else
return await Loop();
+#endif
+
+ long result =
+#if AWAIT
+ RuntimeHelpers.Await(Run(depth - 1));
+#else
+ await Run(depth - 1);
+#endif
- long result = await Run(depth - 1);
Sink = (int)liveState1 + (int)liveState2 + (int)(1 / liveState3) + depth;
return result;
}
@@ -117,7 +129,12 @@ async2 Task
{
for (int i = 0; i < 20; i++)
{
- numIters += await DoYields();
+ numIters +=
+#if AWAIT
+ RuntimeHelpers.Await(DoYields());
+#else
+ await DoYields();
+#endif
}
}