From 6111367532faf47a969aac3c84c9979ec2f1adec Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Wed, 22 Jan 2025 15:35:33 -0800 Subject: [PATCH 1/4] Update runtime async spec Updates from the runtime side: https://github.com/dotnet/runtime/pull/110420. --- docs/compilers/CSharp/Runtime Async Design.md | 154 +++++++++++------- 1 file changed, 99 insertions(+), 55 deletions(-) diff --git a/docs/compilers/CSharp/Runtime Async Design.md b/docs/compilers/CSharp/Runtime Async Design.md index e577604505e9f..91238b94eba2d 100644 --- a/docs/compilers/CSharp/Runtime Async Design.md +++ b/docs/compilers/CSharp/Runtime Async Design.md @@ -27,20 +27,32 @@ We use the following helper APIs to indicate suspension points to the runtime, i ```cs namespace System.Runtime.CompilerServices; -// These methods are used to await things that cannot use runtime async signature form // TODO: Clarify which of these should be preferred? Should we always emit the `Unsafe` version when awaiting something that implements `ICriticalNotifyCompletion`? namespace System.Runtime.CompilerServices; public static class RuntimeHelpers { - [RuntimeAsyncMethod] + // These methods are used to await things that cannot use the Await helpers below + [MethodImpl(MethodImplOptions.Async)] public static Task AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion; - [RuntimeAsyncMethod] + [MethodImpl(MethodImplOptions.Async)] public static Task UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion; + + // These methods are used to directly await method calls + // TODO: Do we only use these when directly awaiting a method call, or do we always use these when a `Task` is awaited, even if that task is a local? + [MethodImpl(MethodImplOptions.Async)] + public static void Await(Task task); + [MethodImpl(MethodImplOptions.Async)] + public static T Await(Task task); + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ValueTask task); + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ValueTask task); } ``` -We presume the following `MethodImplOptions` bit is present. This is used to indicate to the JIT that it should generate an async state machine for the method. +We presume the following `MethodImplOptions` bit is present. This is used to indicate to the JIT that it should generate an async state machine for the method. This bit is not allowed to be used manually on any method; it is added by the compiler +to an `async` method. ```cs namespace System.Runtime.CompilerServices; @@ -77,6 +89,8 @@ public class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribut As mentioned previously, we try to expose as little of this to initial binding as possible. The one major exception to this is our handling of the `MethodImplOption.Async`; we do not let this be applied to user code, and will issue an error if a user tries to do this by hand. +TODO: We may need special handling for the implementation of the `RuntimeHelpers.Await` methods in corelib to permit usage of `MethodImplOptions.Async` directly, as they will not be `async` as we think of it in C#. + Compiler generated async state machines and runtime generated async share some of the same building blocks. Both need to have `await`s with in `catch` and `finally` blocks rewritten to pend the exceptions, perform the `await` outside of the `catch`/`finally` region, and then have the exceptions restored as necessary. @@ -132,8 +146,15 @@ class C await C.M(); ``` +Translated C#: + +```cs +System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -call modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() +call [System.Runtime]System.Threading.Tasks.Task C::M() +call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) ``` --------------------------- @@ -148,9 +169,17 @@ class C } ``` +Translated C#: + +```cs +var c = new C(); +System.Runtime.CompilerServices.RuntimeHelpers.Await(c.M()); +``` + ```il newobj instance void C::.ctor() -callvirt instance modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() +callvirt instance class [System.Runtime]System.Threading.Tasks.Task C::M() +call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) ``` #### Await a concrete `T` `Task`-returning method @@ -164,8 +193,15 @@ class C } ``` +Translated C#: + +```cs +int i = System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -call modreq(class [System.Runtime]System.Threading.Tasks.Task`1) int32 C::M() +call class [System.Runtime]System.Threading.Tasks.Task`1 C::M() +call int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) stloc.0 ``` @@ -181,9 +217,17 @@ class C } ``` +Translated C#: + +```cs +var c = new C(); +int i = System.Runtime.CompilerServices.RuntimeHelpers.Await(c.M()); +``` + ```il newobj instance void C::.ctor() -callvirt instance modreq(class [System.Runtime]System.Threading.Tasks.Task`1) int32 C::M() +callvirt instance class [System.Runtime]System.Threading.Tasks.Task`1 C::M() +call int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) stloc.0 ``` @@ -390,7 +434,7 @@ catch (Exception e) if (pendingCatch == 1) { - /* Runtime-Async Call */ C.M(); + System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); throw pendingException; } ``` @@ -402,29 +446,31 @@ if (pendingCatch == 1) [1] class [System.Runtime]System.Exception pendingException ) + IL_0000: ldc.i4.0 + IL_0001: stloc.0 .try { - IL_0000: newobj instance void [System.Runtime]System.Exception::.ctor() - IL_0005: throw - } + IL_0002: newobj instance void [System.Runtime]System.Exception::.ctor() + IL_0007: throw + } // end .try catch [System.Runtime]System.Exception { - IL_0006: stloc.1 - IL_0007: ldc.i4.1 - IL_0008: stloc.0 - IL_0009: leave.s IL_000b - } + IL_0008: ldc.i4.1 + IL_0009: stloc.0 + IL_000a: stloc.1 + IL_000b: leave.s IL_000d + } // end handler - IL_000b: ldloc.0 - IL_000c: ldc.i4.1 - IL_000d: bne.un.s IL_0017 + IL_000d: ldloc.0 + IL_000e: ldc.i4.1 + IL_000f: bne.un.s IL_001d - IL_000f: ldloc.1 - IL_0010: call modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() - IL_0015: pop - IL_0016: throw + IL_0011: call class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_0016: call void System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_001b: ldloc.1 + IL_001c: throw - IL_0017: ret + IL_001d: ret } ``` @@ -459,7 +505,7 @@ catch (Exception e) pendingException = e; } -/* Runtime-Async Call */ C.M(); +System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); if (pendingException != null) { @@ -477,22 +523,22 @@ if (pendingException != null) { IL_0000: newobj instance void [System.Runtime]System.Exception::.ctor() IL_0005: throw - } + } // end .try catch [System.Runtime]System.Exception { IL_0006: stloc.0 IL_0007: leave.s IL_0009 - } + } // end handler - IL_0009: call modreq(class [System.Runtime]System.Threading.Tasks.Task) void C::M() - IL_000e: pop - IL_000f: ldloc.0 - IL_0010: brfalse.s IL_0014 + IL_0009: call class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_000e: call void System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0013: ldloc.0 + IL_0014: brfalse.s IL_0018 - IL_0012: ldloc.0 - IL_0013: throw + IL_0016: ldloc.0 + IL_0017: throw - IL_0014: ret + IL_0018: ret } ``` @@ -515,36 +561,34 @@ Translated C#: int[] a = new int[] { }; int _tmp1 = C.M2(); int _tmp2 = a[_tmp1]; -int _tmp3 = /* Runtime-Async Call */ C.M1(); +int _tmp3 = System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M1()); a[_tmp1] = _tmp2 + _tmp3; ``` ```il { .locals init ( - [0] int32[] a, - [1] int32 _tmp1, - [2] int32 _tmp2, - [3] int32 _tmp3 + [0] int32 _tmp1, + [1] int32 _tmp2, + [2] int32 _tmp3 ) IL_0000: ldc.i4.0 IL_0001: newarr [System.Runtime]System.Int32 - IL_0006: stloc.0 - IL_0007: call int32 C::M2() - IL_000c: stloc.1 + IL_0006: call int32 C::M2() + IL_000b: stloc.0 + IL_000c: dup IL_000d: ldloc.0 - IL_000e: ldloc.1 - IL_000f: ldelem.i4 - IL_0010: stloc.2 - IL_0011: call modreq(class [System.Runtime]System.Threading.Tasks.Task) int32 C::M1() - IL_0016: stloc.3 - IL_0017: ldloc.0 - IL_0018: ldloc.1 - IL_0019: ldloc.2 - IL_001a: ldloc.3 - IL_001b: add - IL_001c: stelem.i4 - IL_001d: ret + IL_000e: ldelem.i4 + IL_000f: stloc.1 + IL_0010: call class [System.Runtime]System.Threading.Tasks.Task`1 C::M1() + IL_0015: call !!0 System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) + IL_001a: stloc.2 + IL_001b: ldloc.0 + IL_001c: ldloc.1 + IL_001d: ldloc.2 + IL_001e: add + IL_001f: stelem.i4 + IL_0020: ret } ``` From bf14259dd3dad620a5083a0d91b636c2f2785910 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Thu, 23 Jan 2025 11:18:06 -0800 Subject: [PATCH 2/4] Update method signature --- docs/compilers/CSharp/Runtime Async Design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/compilers/CSharp/Runtime Async Design.md b/docs/compilers/CSharp/Runtime Async Design.md index 91238b94eba2d..a22c8ead06f3b 100644 --- a/docs/compilers/CSharp/Runtime Async Design.md +++ b/docs/compilers/CSharp/Runtime Async Design.md @@ -34,9 +34,9 @@ public static class RuntimeHelpers { // These methods are used to await things that cannot use the Await helpers below [MethodImpl(MethodImplOptions.Async)] - public static Task AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion; + public static void AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion; [MethodImpl(MethodImplOptions.Async)] - public static Task UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion; + public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion; // These methods are used to directly await method calls // TODO: Do we only use these when directly awaiting a method call, or do we always use these when a `Task` is awaited, even if that task is a local? From cbd105adb0187b29e6f595dd47f0da291a78106f Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Thu, 23 Jan 2025 11:20:13 -0800 Subject: [PATCH 3/4] Add note about blocking --- docs/compilers/CSharp/Runtime Async Design.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/compilers/CSharp/Runtime Async Design.md b/docs/compilers/CSharp/Runtime Async Design.md index a22c8ead06f3b..b2bcc93f073c9 100644 --- a/docs/compilers/CSharp/Runtime Async Design.md +++ b/docs/compilers/CSharp/Runtime Async Design.md @@ -54,6 +54,8 @@ public static class RuntimeHelpers We presume the following `MethodImplOptions` bit is present. This is used to indicate to the JIT that it should generate an async state machine for the method. This bit is not allowed to be used manually on any method; it is added by the compiler to an `async` method. +TODO: We may want to block directly calling `MethodImplOptions.Async` methods with non-`Task`/`ValueTask` return types. + ```cs namespace System.Runtime.CompilerServices; From 1de4e6a2a8615615c2a3d6b4cd80e7692f1782c6 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Thu, 23 Jan 2025 14:00:50 -0800 Subject: [PATCH 4/4] Fill in more examples --- docs/compilers/CSharp/Runtime Async Design.md | 251 ++++++++++++++---- 1 file changed, 197 insertions(+), 54 deletions(-) diff --git a/docs/compilers/CSharp/Runtime Async Design.md b/docs/compilers/CSharp/Runtime Async Design.md index b2bcc93f073c9..031e31fc0ba2e 100644 --- a/docs/compilers/CSharp/Runtime Async Design.md +++ b/docs/compilers/CSharp/Runtime Async Design.md @@ -39,7 +39,6 @@ public static class RuntimeHelpers public static void UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion; // These methods are used to directly await method calls - // TODO: Do we only use these when directly awaiting a method call, or do we always use these when a `Task` is awaited, even if that task is a local? [MethodImpl(MethodImplOptions.Async)] public static void Await(Task task); [MethodImpl(MethodImplOptions.Async)] @@ -249,14 +248,7 @@ Translated C#: ```cs var local = C.M(); -{ - var awaiter = local.GetAwaiter(); - if (!awaiter.IsComplete) - { - /* Runtime-Async Call */ System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync(awaiter); - } - awaiter.GetResult() -} +System.Runtime.CompilerServices.RuntimeHelpers.Await(local); ``` ```il @@ -266,19 +258,10 @@ var local = C.M(); ) IL_0000: call class [System.Runtime]System.Threading.Tasks.Task C::M() - IL_0005: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter [System.Runtime]System.Threading.Tasks.Task::GetAwaiter() - IL_000a: stloc.0 - IL_000b: ldloca.s 0 - IL_000d: call instance bool [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted() - IL_0012: brtrue.s IL_001b - - IL_0014: ldloc.0 - IL_0015: call class [System.Runtime]System.Threading.Tasks.Task System.Runtime.CompilerServices.RuntimeHelpers::AwaitAwaiterFromRuntimeAsync(!!0) - IL_001a: pop - - IL_001b: ldloca.s 0 - IL_001d: call instance void [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter::GetResult() - IL_0022: ret + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_000c: ret } ``` @@ -301,38 +284,22 @@ Translated C#: ```cs var local = C.M(); -var i = -{ - var awaiter = local.GetAwaiter(); - if (!awaiter.IsComplete) - { - /* Runtime-Async Call */ System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync(awaiter); - } - awaiter.GetResult() -}; +var i = System.Runtime.CompilerServices.RuntimeHelpers.Await(local); ``` ```il { .locals init ( - [0] valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 awaiter + [0] class [System.Runtime]System.Threading.Tasks.Task`1 local, + [1] int32 i ) IL_0000: call class [System.Runtime]System.Threading.Tasks.Task`1 C::M() - IL_0005: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1 class [System.Runtime]System.Threading.Tasks.Task`1::GetAwaiter() - IL_000a: stloc.0 - IL_000b: ldloca.s 0 - IL_000d: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::get_IsCompleted() - IL_0012: brtrue.s IL_001b - - IL_0014: ldloc.0 - IL_0015: call class [System.Runtime]System.Threading.Tasks.Task System.Runtime.CompilerServices.RuntimeHelpers::AwaitAwaiterFromRuntimeAsync>(!!0) - IL_001a: pop - - IL_001b: ldloca.s 0 - IL_001d: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() - IL_0022: pop - IL_0023: ret + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: call !!0 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) + IL_000c: stloc.1 + IL_000d: ret } ``` @@ -347,14 +314,24 @@ class C } ``` +Translated C#: + +```cs +System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: call !!0 C::M() + IL_0005: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_000a: ret +} ``` #### Await a generic `T` `Task`-returning method ```cs -await C.M(); +int i = await C.M(); class C { @@ -362,8 +339,19 @@ class C } ``` +Translated C#: + +```cs +int i = System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: call class [System.Runtime]System.Threading.Tasks.Task`1 C::M() + IL_0005: call !!0 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) + IL_000a: stloc.0 + IL_000b: ret +} ``` #### Await a `Task`-returning delegate @@ -380,8 +368,30 @@ class C } ``` +Translated C# + +```cs +AsyncDelegate d = C.M; +System.Runtime.CompilerServices.RuntimeHelpers.Await(d()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: ldsfld class AsyncDelegate Program/'<>O'::'<0>__M' + IL_0005: dup + IL_0006: brtrue.s IL_001b + + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_0010: newobj instance void AsyncDelegate::.ctor(object, native int) + IL_0015: dup + IL_0016: stsfld class AsyncDelegate Program/'<>O'::'<0>__M' + + IL_001b: callvirt instance class [System.Runtime]System.Threading.Tasks.Task AsyncDelegate::Invoke() + IL_0020: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0025: ret +} ``` #### Await a `T`-returning delegate @@ -396,8 +406,30 @@ class C } ``` +Translated C#: + +```cs +Func d = C.M; +System.Runtime.CompilerServices.RuntimeHelpers.Await(d()); +``` + ```il -TODO: https://github.com/dotnet/runtime/issues/109632 +{ + IL_0000: ldsfld class [System.Runtime]System.Func`1 Program/'<>O'::'<0>__M' + IL_0005: dup + IL_0006: brtrue.s IL_001b + + IL_0008: pop + IL_0009: ldnull + IL_000a: ldftn class [System.Runtime]System.Threading.Tasks.Task C::M() + IL_0010: newobj instance void class [System.Runtime]System.Func`1::.ctor(object, native int) + IL_0015: dup + IL_0016: stsfld class [System.Runtime]System.Func`1 Program/'<>O'::'<0>__M' + + IL_001b: callvirt instance !0 class [System.Runtime]System.Func`1::Invoke() + IL_0020: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0025: ret +} ``` #### Awaiting in a `catch` block @@ -468,7 +500,7 @@ if (pendingCatch == 1) IL_000f: bne.un.s IL_001d IL_0011: call class [System.Runtime]System.Threading.Tasks.Task C::M() - IL_0016: call void System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0016: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) IL_001b: ldloc.1 IL_001c: throw @@ -533,7 +565,7 @@ if (pendingException != null) } // end handler IL_0009: call class [System.Runtime]System.Threading.Tasks.Task C::M() - IL_000e: call void System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_000e: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) IL_0013: ldloc.0 IL_0014: brfalse.s IL_0018 @@ -584,7 +616,7 @@ a[_tmp1] = _tmp2 + _tmp3; IL_000e: ldelem.i4 IL_000f: stloc.1 IL_0010: call class [System.Runtime]System.Threading.Tasks.Task`1 C::M1() - IL_0015: call !!0 System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) + IL_0015: call !!0 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task`1) IL_001a: stloc.2 IL_001b: ldloc.0 IL_001c: ldloc.1 @@ -594,3 +626,114 @@ a[_tmp1] = _tmp2 + _tmp3; IL_0020: ret } ``` + +#### Await a non-Task/ValueTask implementor of ICriticalNotifyCompletion + +```cs +var c = new C(); +await c; + +class C +{ + public class Awaiter : ICriticalNotifyCompletion + { + public void OnCompleted(Action continuation) { } + public void UnsafeOnCompleted(Action continuation) { } + public bool IsCompleted => true; + public void GetResult() { } + } + + public Awaiter GetAwaiter() => new Awaiter(); +} +``` + +Translated C#: + +```cs +var c = new C(); +_ = { + var awaiter = c.GetAwaiter(); + if (!awaiter.IsCompleted) + { + System.Runtime.CompilerServices.RuntimeHelpers.UnsafeAwaitAwaiterFromRuntimeAsync(awaiter); + } + awaiter.GetResult() +}; +``` + +```il +{ + .locals init ( + [0] class C/Awaiter awaiter + ) + + IL_0000: newobj instance void C::.ctor() + IL_0005: callvirt instance class C/Awaiter C::GetAwaiter() + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: callvirt instance bool C/Awaiter::get_IsCompleted() + IL_0011: brtrue.s IL_0019 + + IL_0013: ldloc.0 + IL_0014: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::UnsafeAwaitAwaiterFromRuntimeAsync(!!0) + + IL_0019: ldloc.0 + IL_001a: callvirt instance void C/Awaiter::GetResult() + IL_001f: ret +} +``` + +#### Await a non-Task/ValueTask implementor of INotifyCompletion + +```cs +var c = new C(); +await c; + +class C +{ + public class Awaiter : INotifyCompletion + { + public void OnCompleted(Action continuation) { } + public bool IsCompleted => true; + public void GetResult() { } + } + + public Awaiter GetAwaiter() => new Awaiter(); +} +``` + +Translated C#: + +```cs +var c = new C(); +_ = { + var awaiter = c.GetAwaiter(); + if (!awaiter.IsCompleted) + { + System.Runtime.CompilerServices.RuntimeHelpers.AwaitAwaiterFromRuntimeAsync(awaiter); + } + awaiter.GetResult() +}; +``` + +```il +{ + .locals init ( + [0] class C/Awaiter awaiter + ) + + IL_0000: newobj instance void C::.ctor() + IL_0005: callvirt instance class C/Awaiter C::GetAwaiter() + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: callvirt instance bool C/Awaiter::get_IsCompleted() + IL_0011: brtrue.s IL_0019 + + IL_0013: ldloc.0 + IL_0014: call void [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::AwaitAwaiterFromRuntimeAsync(!!0) + + IL_0019: ldloc.0 + IL_001a: callvirt instance void C/Awaiter::GetResult() + IL_001f: ret +} +```