Skip to content

Commit

Permalink
Update runtime async spec
Browse files Browse the repository at this point in the history
Updates from the runtime side: dotnet/runtime#110420.
  • Loading branch information
333fred committed Jan 22, 2025
1 parent 9cab90a commit 6111367
Showing 1 changed file with 99 additions and 55 deletions.
154 changes: 99 additions & 55 deletions docs/compilers/CSharp/Runtime Async Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>(TAwaiter awaiter) where TAwaiter : INotifyCompletion;
[RuntimeAsyncMethod]
[MethodImpl(MethodImplOptions.Async)]
public static Task UnsafeAwaitAwaiterFromRuntimeAsync<TAwaiter>(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<T>(Task<T> task);
[MethodImpl(MethodImplOptions.Async)]
public static void Await(ValueTask task);
[MethodImpl(MethodImplOptions.Async)]
public static T Await<T>(ValueTask<T> 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;
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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)
```

---------------------------
Expand All @@ -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<T>`-returning method
Expand All @@ -164,8 +193,15 @@ class C
}
```

Translated C#:

```cs
int i = System.Runtime.CompilerServices.RuntimeHelpers.Await<int>(C.M());
```

```il
call modreq(class [System.Runtime]System.Threading.Tasks.Task`1<int32>) int32 C::M()
call class [System.Runtime]System.Threading.Tasks.Task`1<int32> C::M()
call int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await<int32>(class [System.Runtime]System.Threading.Tasks.Task`1<int32>)
stloc.0
```

Expand All @@ -181,9 +217,17 @@ class C
}
```

Translated C#:

```cs
var c = new C();
int i = System.Runtime.CompilerServices.RuntimeHelpers.Await<int>(c.M());
```

```il
newobj instance void C::.ctor()
callvirt instance modreq(class [System.Runtime]System.Threading.Tasks.Task`1<int32>) int32 C::M()
callvirt instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> C::M()
call int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::Await<int32>(class [System.Runtime]System.Threading.Tasks.Task`1<int32>)
stloc.0
```

Expand Down Expand Up @@ -390,7 +434,7 @@ catch (Exception e)

if (pendingCatch == 1)
{
/* Runtime-Async Call */ C.M();
System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M());
throw pendingException;
}
```
Expand All @@ -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
}
```

Expand Down Expand Up @@ -459,7 +505,7 @@ catch (Exception e)
pendingException = e;
}

/* Runtime-Async Call */ C.M();
System.Runtime.CompilerServices.RuntimeHelpers.Await(C.M());

if (pendingException != null)
{
Expand All @@ -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
}
```

Expand All @@ -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>) 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<int32> C::M1()
IL_0015: call !!0 System.Runtime.CompilerServices.RuntimeHelpers::Await<int32>(class [System.Runtime]System.Threading.Tasks.Task`1<!!0>)
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
}
```

0 comments on commit 6111367

Please sign in to comment.