From e582a11772e87d91e4357be22fdc7523c713e6d0 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 26 Jun 2024 15:55:53 -0700 Subject: [PATCH 01/10] Initial commit --- docs/design/specs/runtime-async.md | 71 ++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/design/specs/runtime-async.md diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md new file mode 100644 index 0000000000000..1045f2058ae18 --- /dev/null +++ b/docs/design/specs/runtime-async.md @@ -0,0 +1,71 @@ + +This document is a draft of changes to ECMA-335 for the "runtime async" feature. When the feature is officially supported, it can be merged into the final ECMA-335 augments document. + +# Runtime-async + +Async is currently a feature implemented by various .NET languages as a compiler rewrite to support methods that can "yield" control back to their caller at specific "suspension" points. While effective, it's believed that implementation directly in the .NET runtime can provide improvements, especially in performance. + +## Spec modifications + +These are proposed modifications to the ECMA-335 specification for runtime-async. + +### I.8.4.5 Sync and Async Methods + +Methods may be either 'sync' or 'async'. Async methods have a special signature encoding, described in [### I.8.6.1.5 Method signatures]. + +Sync methods are all other methods. + +Unlike sync methods, async methods support suspension. Suspension allows async methods to yield control flow back to their caller at certain well-defined suspension points, and resume execution of the remaining method at a later time or location, potentially on another thread. + +Async methods support the following suspension points: + +* Calling another async method. No special instructions need to be provided. If the callee suspends, the caller will suspend as well. +* Using a library in the .NET corelib to "await" an "INotifyCompletion" type. The signatures of these methods shall be: + ```C# + // public static async2 Task AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion + public static void modopt([System.Runtime]System.Threading.Tasks.Task) AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion + // public static async2 Task UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion + public static void modopt([System.Runtime]System.Threading.Tasks.Task) UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion + ``` + +Each of the above methods will have semantics analogous to the current AsyncTaskMethodBuilder.AwaitOnCompleted/AwaitUnsafeOnCompleted methods. After calling this method, in can be presumed that the task has completed. + +Async methods have the following restrictions: +* Usage of the `localloc` and instruction is forbidden +* The `lodloca` and `ldarga` instructions are redefined to return managed pointers instead of pointers. +* Pinning locals may not be created +* The `tail` prefix is forbidden + +Suspension points may not appear in exception handling blocks. + +All async methods effectively have two entry points, or signatures. The first signature is the one present in the above code: a modopt before the return type. The second signature is a "Task-equivalent signature", described in further detail in [I.8.6.1.5 Method signatures]. + +Async methods have a special calling convention and may not be called directly outside of other async methods. To call an async method from a sync method, callers must use the second "Task-equivalent signature". + +Callers may retrieve a Task-equivalent return type from an async method via calling the "Task-equivalent signature". This functionality is available in both sync and async methods. + +### I.8.6.1.5 Method signatures + +The list of relevant components is augmented to include sync vs. async method types. Async methods have some additions to normal signature compatibility. + +Async MethodDef entries implicitly create two member definitions: one explicit, primary definiton, and a second implicit, runtime-generated definition. + +The primary, mandatory definition must be present in metadata as a MethodDef. This signature must have a required custom modifier as the last custom modifier before the return type. The custom modifier must fit the following requirements: +* If the return type is void, the custom modifier must be either `System.Threading.Task` or `System.Threading.ValueTask`. +* If the return type is not `void`, the modifier must be to either `System.Threading.Task` or `System.Threading.ValueTask`. The return type must be a valid substitution for the type parameter of the custom modifier type. + +_[Note: async methods have the same return type conventions as sync methods. If the async method produces a System.Int32, the return type must be System.Int32.]_ + +The second async signature is implicit and runtime-generated and is hereafter referred to as the "Task-equivalent" signature. It is generated based on the primary signature. The transformation is as follows: + +If the async return type is void, the return type of the Task-equivalent signature is the type of the async custom modifier. + +Otherwise, the Task-equivalent return type is the custom modifier type (either Task`1 or ValueTask`1), substituted with the async return type. + +### I.8.10.2 Method inheritance + +For the purposes of inheritance and hiding, both async signatures ([### I.8.6.1.5 Method signatures]) are used for hiding and overriding, but cannot be configured separately. + +### II.10.3.2 The .override directive + +Async methods participate in overrides through both definitions (both signatures). An async method with a .override overrides the target method signature, as well as the secondary "Task-equivalent" signature if applicable. An async method may also override only the "Task-equivalent" signature, if an async signature is not present on the base class. A sync method may not override an async method, even if it matches the async method's "Task-equivalent" definition. \ No newline at end of file From d830f9b42cfc0e22c6206d7cb59bfcfca8c83e1a Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 27 Jun 2024 10:02:36 -0700 Subject: [PATCH 02/10] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Kotas Co-authored-by: Hamish Arblaster Co-authored-by: Aleksey Kliger (λgeek) Co-authored-by: Fred Silberberg --- docs/design/specs/runtime-async.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index 1045f2058ae18..0ed4f0ab99b92 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -20,7 +20,7 @@ Unlike sync methods, async methods support suspension. Suspension allows async m Async methods support the following suspension points: * Calling another async method. No special instructions need to be provided. If the callee suspends, the caller will suspend as well. -* Using a library in the .NET corelib to "await" an "INotifyCompletion" type. The signatures of these methods shall be: +* Using new .NET runtime APIs to "await" an "INotifyCompletion" type. The signatures of these methods shall be: ```C# // public static async2 Task AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion public static void modopt([System.Runtime]System.Threading.Tasks.Task) AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion @@ -31,8 +31,8 @@ Async methods support the following suspension points: Each of the above methods will have semantics analogous to the current AsyncTaskMethodBuilder.AwaitOnCompleted/AwaitUnsafeOnCompleted methods. After calling this method, in can be presumed that the task has completed. Async methods have the following restrictions: -* Usage of the `localloc` and instruction is forbidden -* The `lodloca` and `ldarga` instructions are redefined to return managed pointers instead of pointers. +* Usage of the `localloc` instruction is forbidden +* The `ldloca` and `ldarga` instructions are redefined to return managed pointers instead of pointers. * Pinning locals may not be created * The `tail` prefix is forbidden @@ -50,7 +50,7 @@ The list of relevant components is augmented to include sync vs. async method ty Async MethodDef entries implicitly create two member definitions: one explicit, primary definiton, and a second implicit, runtime-generated definition. -The primary, mandatory definition must be present in metadata as a MethodDef. This signature must have a required custom modifier as the last custom modifier before the return type. The custom modifier must fit the following requirements: +The primary, mandatory definition must be present in metadata as a MethodDef. This signature is required to have a `modopt` (optional modifier) as the last custom modifier before the return type. The custom modifier must fit the following requirements: * If the return type is void, the custom modifier must be either `System.Threading.Task` or `System.Threading.ValueTask`. * If the return type is not `void`, the modifier must be to either `System.Threading.Task` or `System.Threading.ValueTask`. The return type must be a valid substitution for the type parameter of the custom modifier type. @@ -60,7 +60,7 @@ The second async signature is implicit and runtime-generated and is hereafter re If the async return type is void, the return type of the Task-equivalent signature is the type of the async custom modifier. -Otherwise, the Task-equivalent return type is the custom modifier type (either Task`1 or ValueTask`1), substituted with the async return type. +Otherwise, the Task-equivalent return type is the custom modifier type (either ``Task`1`` or ``ValueTask`1``), substituted with the async return type. ### I.8.10.2 Method inheritance From 3463e21287d10ca37be599342d928c9da62a38e5 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 27 Jun 2024 15:53:57 -0700 Subject: [PATCH 03/10] Respond to PR comments --- docs/design/specs/runtime-async.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index 0ed4f0ab99b92..670fb05d7f8b0 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -23,9 +23,9 @@ Async methods support the following suspension points: * Using new .NET runtime APIs to "await" an "INotifyCompletion" type. The signatures of these methods shall be: ```C# // public static async2 Task AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion - public static void modopt([System.Runtime]System.Threading.Tasks.Task) AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion + public static void modreq([System.Runtime]System.Threading.Tasks.Task) AwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : INotifyCompletion // public static async2 Task UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion - public static void modopt([System.Runtime]System.Threading.Tasks.Task) UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion + public static void modreq([System.Runtime]System.Threading.Tasks.Task) UnsafeAwaitAwaiterFromRuntimeAsync(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion ``` Each of the above methods will have semantics analogous to the current AsyncTaskMethodBuilder.AwaitOnCompleted/AwaitUnsafeOnCompleted methods. After calling this method, in can be presumed that the task has completed. @@ -38,7 +38,7 @@ Async methods have the following restrictions: Suspension points may not appear in exception handling blocks. -All async methods effectively have two entry points, or signatures. The first signature is the one present in the above code: a modopt before the return type. The second signature is a "Task-equivalent signature", described in further detail in [I.8.6.1.5 Method signatures]. +All async methods effectively have two entry points, or signatures. The first signature is the one present in the above code: a modreq before the return type. The second signature is a "Task-equivalent signature", described in further detail in [I.8.6.1.5 Method signatures]. Async methods have a special calling convention and may not be called directly outside of other async methods. To call an async method from a sync method, callers must use the second "Task-equivalent signature". @@ -57,10 +57,10 @@ The primary, mandatory definition must be present in metadata as a MethodDef. Th _[Note: async methods have the same return type conventions as sync methods. If the async method produces a System.Int32, the return type must be System.Int32.]_ The second async signature is implicit and runtime-generated and is hereafter referred to as the "Task-equivalent" signature. It is generated based on the primary signature. The transformation is as follows: +* If the async return type is void, the return type of the Task-equivalent signature is the type of the async custom modifier. +* Otherwise, the Task-equivalent return type is the custom modifier type (either ``Task`1`` or ``ValueTask`1``), substituted with the async return type. -If the async return type is void, the return type of the Task-equivalent signature is the type of the async custom modifier. - -Otherwise, the Task-equivalent return type is the custom modifier type (either ``Task`1`` or ``ValueTask`1``), substituted with the async return type. +It is an error to explicitly declare a method with the same signature as an async method's "Task-equivalent" signature. ### I.8.10.2 Method inheritance @@ -68,4 +68,4 @@ For the purposes of inheritance and hiding, both async signatures ([### I.8.6.1. ### II.10.3.2 The .override directive -Async methods participate in overrides through both definitions (both signatures). An async method with a .override overrides the target method signature, as well as the secondary "Task-equivalent" signature if applicable. An async method may also override only the "Task-equivalent" signature, if an async signature is not present on the base class. A sync method may not override an async method, even if it matches the async method's "Task-equivalent" definition. \ No newline at end of file +Async methods participate in overrides through both definitions (both signatures). An async method with a .override overrides the target method signature, as well as the secondary "Task-equivalent" signature if applicable. An async method may also override a sync method matching the "Task-equivalent" signature, if an async signature is not present on the base class. A sync method may also override an async method's "Task-equivalent" signature. This will behave as though both the async and "Task-equivalent" methods have been overridden. \ No newline at end of file From e3f25cd5eefd91eb2fb03205d0598d8268381782 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 28 Jun 2024 14:44:30 -0700 Subject: [PATCH 04/10] Respond to PR comments --- docs/design/specs/runtime-async.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index 670fb05d7f8b0..c09d5c815d1e5 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -30,14 +30,18 @@ Async methods support the following suspension points: Each of the above methods will have semantics analogous to the current AsyncTaskMethodBuilder.AwaitOnCompleted/AwaitUnsafeOnCompleted methods. After calling this method, in can be presumed that the task has completed. -Async methods have the following restrictions: +Only local variables which are "hoisted" may be used across suspension points. That is, only "hoisted" local variables will have their state preserved after returning from a suspension. On methods with the `localsinit` flag set, non-"hoisted" local variables will be initialized to their default value when resuming from suspension. Otherwise, these variables will have an undefined value. To identify "hoisted" local variables, they must have an optional custom modifier to the `System.Runtime.CompilerServices.HoistedLocal` class, which will be a new .NET runtime API. This custom modifier must be the last custom modifier on the variable. It is invalid for by-ref variables, or variables with a by-ref-like type, to be marked hoisted. Hoisted local variables are stored in managed memory and cannot be converted to unmanaged pointers without explicit pinning. + +Async methods have some temporary restrictions with may be lifted later: +* The `tail` prefix is forbidden * Usage of the `localloc` instruction is forbidden -* The `ldloca` and `ldarga` instructions are redefined to return managed pointers instead of pointers. * Pinning locals may not be created -* The `tail` prefix is forbidden -Suspension points may not appear in exception handling blocks. +Other restrictions are likely to be permanent, including: +* Suspension points may not appear in exception handling blocks. +* By-ref variables and variables with by-ref-like type may not be used across suspension points +To facilitate All async methods effectively have two entry points, or signatures. The first signature is the one present in the above code: a modreq before the return type. The second signature is a "Task-equivalent signature", described in further detail in [I.8.6.1.5 Method signatures]. Async methods have a special calling convention and may not be called directly outside of other async methods. To call an async method from a sync method, callers must use the second "Task-equivalent signature". From db9697ce3ad807bad354032b899630d6b5ec8919 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 28 Jun 2024 14:46:34 -0700 Subject: [PATCH 05/10] Typo --- docs/design/specs/runtime-async.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index c09d5c815d1e5..b1b2d2c365ac5 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -37,11 +37,10 @@ Async methods have some temporary restrictions with may be lifted later: * Usage of the `localloc` instruction is forbidden * Pinning locals may not be created -Other restrictions are likely to be permanent, including: -* Suspension points may not appear in exception handling blocks. -* By-ref variables and variables with by-ref-like type may not be used across suspension points +Other restrictions are likely to be permanent. + +Suspension points may not appear in exception handling blocks. -To facilitate All async methods effectively have two entry points, or signatures. The first signature is the one present in the above code: a modreq before the return type. The second signature is a "Task-equivalent signature", described in further detail in [I.8.6.1.5 Method signatures]. Async methods have a special calling convention and may not be called directly outside of other async methods. To call an async method from a sync method, callers must use the second "Task-equivalent signature". From 52769b8c5cdb90c77cc326ae1fe03061f409b309 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 8 Jul 2024 21:27:43 -0700 Subject: [PATCH 06/10] Update runtime-async.md --- docs/design/specs/runtime-async.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index b1b2d2c365ac5..d8995a3f9bcc6 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -63,7 +63,11 @@ The second async signature is implicit and runtime-generated and is hereafter re * If the async return type is void, the return type of the Task-equivalent signature is the type of the async custom modifier. * Otherwise, the Task-equivalent return type is the custom modifier type (either ``Task`1`` or ``ValueTask`1``), substituted with the async return type. -It is an error to explicitly declare a method with the same signature as an async method's "Task-equivalent" signature. +It is an error to explicitly declare a method with the same signature as an async method's synthesized "Task-equivalent" signature. + +Unlike async methods, sync MethodDefs do not always generate two member definitions. However, if the sync MethodDef signature would match the "Task-equivalent" signature of an async method, an async definition is also synthesized. More precisely, sync methods that have a return type of `System.Threading.Task`, `System.Threading.ValueTask`, `System.Threading.Task`, or `System.Threading.ValueTask` and have parameters meeting any further requirements of an async method definition, will generate an equivalent async definition corresponding to the inversion of the rules for generating the async method "Task-equivalent" signature. + +_[Note: these rules operate before generic substitution, meaning that a method which only meets requirements after substitution would not be considered as valid.]_ ### I.8.10.2 Method inheritance @@ -71,4 +75,4 @@ For the purposes of inheritance and hiding, both async signatures ([### I.8.6.1. ### II.10.3.2 The .override directive -Async methods participate in overrides through both definitions (both signatures). An async method with a .override overrides the target method signature, as well as the secondary "Task-equivalent" signature if applicable. An async method may also override a sync method matching the "Task-equivalent" signature, if an async signature is not present on the base class. A sync method may also override an async method's "Task-equivalent" signature. This will behave as though both the async and "Task-equivalent" methods have been overridden. \ No newline at end of file +Async methods participate in overrides through both definitions (both signatures). An async method with a .override overrides the target method signature, as well as the secondary "Task-equivalent" signature if applicable. An async method may also override a sync method matching the "Task-equivalent" signature, if an async signature is not present on the base class. A sync method may also override an async method's "Task-equivalent" signature. This will behave as though both the async and "Task-equivalent" methods have been overridden. From 21a66e87202cdda2e121b27a1017bc3ca4c301a0 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 9 Jul 2024 09:37:01 -0700 Subject: [PATCH 07/10] Update runtime-async.md --- docs/design/specs/runtime-async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index d8995a3f9bcc6..4f5e0a6b985bb 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -35,7 +35,7 @@ Only local variables which are "hoisted" may be used across suspension points. T Async methods have some temporary restrictions with may be lifted later: * The `tail` prefix is forbidden * Usage of the `localloc` instruction is forbidden -* Pinning locals may not be created +* Pinning locals may not be marked `HoistedLocal` Other restrictions are likely to be permanent. From 745ebe912e2bcb922959b76be0ceae086cb55b69 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 9 Jul 2024 09:52:08 -0700 Subject: [PATCH 08/10] Add note about using MethodRef for implicit methods --- docs/design/specs/runtime-async.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index 4f5e0a6b985bb..60548f28d6871 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -76,3 +76,7 @@ For the purposes of inheritance and hiding, both async signatures ([### I.8.6.1. ### II.10.3.2 The .override directive Async methods participate in overrides through both definitions (both signatures). An async method with a .override overrides the target method signature, as well as the secondary "Task-equivalent" signature if applicable. An async method may also override a sync method matching the "Task-equivalent" signature, if an async signature is not present on the base class. A sync method may also override an async method's "Task-equivalent" signature. This will behave as though both the async and "Task-equivalent" methods have been overridden. + +### II.15.4.6 async methods + +In certain cases described in [I.8.6.1.5 Method signatures] MethodDef definitions for sync and async methods may result in the definition of additional "implicit" definitions. All call sites to an implicit definition must use a MethodRef, even if the implicit method is definined inside the same module or assembly. The "explicit" definition which exists in metadata can be called using a regular `MethodDef` token. From 43fea46f4530e57354dc14f75bbafa47bf87fc64 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 17 Sep 2024 15:33:28 -0700 Subject: [PATCH 09/10] Update docs/design/specs/runtime-async.md Co-authored-by: Jakob Botsch Nielsen --- docs/design/specs/runtime-async.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index 60548f28d6871..ce56de97a6a15 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -31,6 +31,7 @@ Async methods support the following suspension points: Each of the above methods will have semantics analogous to the current AsyncTaskMethodBuilder.AwaitOnCompleted/AwaitUnsafeOnCompleted methods. After calling this method, in can be presumed that the task has completed. Only local variables which are "hoisted" may be used across suspension points. That is, only "hoisted" local variables will have their state preserved after returning from a suspension. On methods with the `localsinit` flag set, non-"hoisted" local variables will be initialized to their default value when resuming from suspension. Otherwise, these variables will have an undefined value. To identify "hoisted" local variables, they must have an optional custom modifier to the `System.Runtime.CompilerServices.HoistedLocal` class, which will be a new .NET runtime API. This custom modifier must be the last custom modifier on the variable. It is invalid for by-ref variables, or variables with a by-ref-like type, to be marked hoisted. Hoisted local variables are stored in managed memory and cannot be converted to unmanaged pointers without explicit pinning. +The code generator is free to ignore the `HoistedLocal` modifier if it can prove that this makes no observable difference in the execution of the generated program. This can be observable in diagnostics since it may mean the value of a local with the `HoistedLocal` modifier will not be available after certain suspension points. Async methods have some temporary restrictions with may be lifted later: * The `tail` prefix is forbidden From b161b78930ade28a77a6edc1cbd4291f10a6c6dc Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Sun, 29 Sep 2024 11:46:07 -0700 Subject: [PATCH 10/10] Update runtime-async.md --- docs/design/specs/runtime-async.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/design/specs/runtime-async.md b/docs/design/specs/runtime-async.md index ce56de97a6a15..c7e8ba9f06071 100644 --- a/docs/design/specs/runtime-async.md +++ b/docs/design/specs/runtime-async.md @@ -38,9 +38,10 @@ Async methods have some temporary restrictions with may be lifted later: * Usage of the `localloc` instruction is forbidden * Pinning locals may not be marked `HoistedLocal` -Other restrictions are likely to be permanent. - -Suspension points may not appear in exception handling blocks. +Other restrictions are likely to be permanent, including +* By-ref locals cannot be hoisted across suspension points +* Suspension points may not appear in exception handling blocks. +* Only four types will be supported as the return type for "runtime-async" methods: `System.Threading.Task`, `System.Threading.ValueTask`, `System.Threading.Task`, and `System.Threading.ValueTask` All async methods effectively have two entry points, or signatures. The first signature is the one present in the above code: a modreq before the return type. The second signature is a "Task-equivalent signature", described in further detail in [I.8.6.1.5 Method signatures].