-
Notifications
You must be signed in to change notification settings - Fork 5k
[API Proposal]: Public API for the Runtime Async #114310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Public API consumable by guest languages would be really nice to have in .NET as a platform. |
Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices |
It is quite a few new methods and I expect that there will be more. Would it make sense to put all runtime async helpers into its own type?
I assume that the APIs are going to be exposed with
I think so. |
I think we have an Await for everything that can be awaited now, but can't rule out introducing some special cases.
Under something like The only concern, I think, is that there are few things already named |
Also we are almost expecting that We do not want to make them overloads of |
Nit -- this is part of the implementation details of these methods, not part of the signature (i.e. not something that a caller should ever pay attention to). So I am not sure they belong in the API proposal. |
I left the But you are right. These methods are special because of their use and purpose, and methodimpl is just how we mark them. It is an implementation detail that is not a lot different from |
The enum value should also be added to |
Also, the value 0x400 conflicts with the |
Should the implementation be changed to just use |
The implementation of these methods need to be emitted as an async state machine, so it's just a convenient way to propagate this information through the VM/JIT. Before we had |
Conceptually they are non-Task returning async methods. Thus you can’t make ordinary calls to them from ordinary, non-async, methods as how’d you return an incomplete result. We will not allow non-Task returning async methods outside of the runtime. We could just match by name. We did that when we had only two of these helpers. I think MethodImpl scales a bit better if more helpers will be added. |
It does not look like I think we could reuse the bit, but there are a few more bits available. So to be safe, let's use something else. There is a gap between |
We'll also need a new runtime feature flag --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.cs
@@ -47,6 +47,12 @@ public static partial class RuntimeFeature
/// </summary>
public const string NumericIntPtr = nameof(NumericIntPtr);
+ /// <summary>
+ /// Indicates that this version of the runtime supports async methods.
+ /// </summary>
+ [RequiresPreviewFeatures]
+ public const string Async = nameof(Async);
+
/// <summary>
/// Checks whether a certain feature is supported by the Runtime.
/// </summary>
@@ -54,7 +60,18 @@ public static bool IsSupported(string feature)
{
return feature switch
{
- PortablePdb or CovariantReturnsOfClasses or ByRefFields or ByRefLikeGenerics or UnmanagedSignatureCallingConvention or DefaultImplementationsOfInterfaces or VirtualStaticsInInterfaces or NumericIntPtr => true,
+ PortablePdb or
+ CovariantReturnsOfClasses or
+ ByRefFields or
+ ByRefLikeGenerics or
+ UnmanagedSignatureCallingConvention or
+ DefaultImplementationsOfInterfaces or
+ VirtualStaticsInInterfaces or
+ NumericIntPtr or
+#pragma warning disable CA2252 // Using preview features
+ Async=> true,
+#pragma warning restore CA2252 // Using preview features
+
nameof(IsDynamicCodeSupported) => IsDynamicCodeSupported,
nameof(IsDynamicCodeCompiled) => IsDynamicCodeCompiled,
_ => false, |
The support is gated by availability of the new APIs. It makes the new runtime feature flag unnecessary. We typically add a feature flag only when there is no obvious API associated with the feature. Moving the runtime async into a dedicated type will make the gating based on API availability cleaner. |
Re: That is at least the plan for previews. We may know better how often the attribute is really used later. |
I think I have added all that I had for this API and what was suggested in the discussion. |
ExperimentalAttribute allows targeting types. You can mark the whole type as experimental, no need to mark each method individually.
I assume that you mean enum MethodImplAttributes. You can include it in the API proposal for clarity. Or is there more to it? |
The APIs are not called directly by user code. Does |
Nothing prevents these APIs from being called by user code. ExperimentalAttribute is insurance in case somebody ends up calling these APIs in their code for some reason. Runtime async is a transparent implementation detail for user code. RequiresPreviewFeatures is meant to be used on new APIs that can be called by user code only when the compiler preview features are turned on. Runtime async has no APIs like so far. |
OK, I understand--since runtime async methods return regular |
I did not realize that. It would be useful here.
Yes. I did a search and saw a few hits - But really the only other public API to update is just |
Right, this is internal implementation of AOT compilers. We will need it once we get to implementing runtime async for R2R and NAOT. |
Mentioned |
From the Roslyn perspective, I think every change that's corresponds to a differing runtime capability is supposed to be in RuntimeFeature. Even if there are other API changes that would be sufficient, I think the RuntimeFeature API is supposed to be the single check point. It's always present in corelib, so it can't be faked, and the type itself is already considered a SpecialType by Roslyn, so it has special behavior. It looks like the current Roslyn compiler also has this requirement. So if we want to redesign this, we will need to push the requirement back up to Roslyn. |
I don't think it would be particularly difficult to simply gate on the presence of |
It is not how RuntimeFeature has evolved so far. When the runtime feature has one canonical API associated with it, we use the API as the gate. One recent example from many: InlineArrayAttribute. It is runtime capability, Roslyn will light up when it sees InlineArrayAttribute in CoreLib, but it does not have RuntimeFeature. If we want to change the rules for what's gets included in RuntimeFeature, it should be a separate discussion. |
If it's not a problem for Roslyn, not a problem for me either. Only question is whether we gate on one API or many. |
@bartonjs I think this is ready for review. Anything more we need to do? |
With no assigned milestone it was at the bottom of the review queue. Assigning the milestone bumped it, and with blocking it's at the top... but since those were done after the meeting started, I didn't see them. (Really, since they were done after 4pm Monday it didn't make it onto the agenda, but blocking gets extra eyes before the meeting starts). Since it's now marked as blocking, this'll be first up in the next meeting. (Unless it somehow gets trumped by something even-more-blocking) |
Whenever something gets marked as ready for review, its place in the queue can be seen at https://apireview.net/. OrderBy(Blocking).ThenBy(Milestone).ThenBy(HowLongInTheQueue) |
namespace System.Runtime.CompilerServices
{
[Flags]
public partial enum MethodImplOptions
{
Async = 0x2000,
}
}
namespace System.Reflection
{
public partial enum MethodImplAttributes
{
Async = 0x2000,
}
}
namespace System.Runtime.CompilerServices
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5007", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public static partial class AsyncHelpers
{
public static void UnsafeAwaitAwaiter<TAwaiter>(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { }
public static void AwaitAwaiter<TAwaiter>(TAwaiter awaiter) where TAwaiter : INotifyCompletion { }
public static void Await(System.Threading.Tasks.Task task) { }
public static T Await<T>(System.Threading.Tasks.Task<T> task) { }
public static void Await(System.Threading.Tasks.ValueTask task) { }
public static T Await<T>(System.Threading.Tasks.ValueTask<T> task) { }
public static void Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { }
public static T Await<T>(System.Runtime.CompilerServices.ConfiguredTaskAwaitable<T> configuredAwaitable) { }
public static void Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { }
public static T Await<T>(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable<T> configuredAwaitable) { }
}
} |
We now do method construction and validation for runtime async helpers up front in initial binding, rather than doing it in `RuntimeAsyncRewriter`. I've also renamed the APIs as per dotnet/runtime#114310 (comment) (though I haven't added ConfigureAwait support yet, that will be the next PR). We now validate: * The helpers come from `System.Runtime.CompilerServices.AsyncHelpers`, defined in corelib. This means that I now need a fairly extensive corelib mock to be able to compile. When we have a testing runtime that defines these helpers, we can remove the giant mock and use the real one. * We properly error when expected helpers aren't present. * We properly check to make sure that constraints are satisfied when doing generic substitution in one of the runtime helpers. * Runtime async is not turned on if the async method does not return `Task`, `Task<T>`, `ValueTask`, or `ValueTask<T>`. Relates to test plan #75960
It was changed in dotnet/runtime#114310 as 0x400 is a thing in framework.
It was changed in dotnet/runtime#114310 as 0x400 is a thing in framework.
Uh oh!
There was an error while loading. Please reload this page.
Background and motivation
The purpose of this proposal is to introduce the changes in the public API as required by the Runtime Async feature.
For the detailed documentation of IL level interface of Runtime Async feature see proposed changes to ECMA-335 document.
For reference, the location of corresponding C# spec - https://github.com/dotnet/roslyn/blob/main/docs/compilers/CSharp/Runtime%20Async%20Design.md
NOTE: referenced documents are work-in-progress, so could be slightly behind on details.
API Proposal
The API adds a new
Async
member in theMethodImplOptions
enum, and a corresponding change toSystem.Reflection.MethodImplAttributes
.The purpose of this flag is to indicate that a particular method is an
Async
method. There are certain restrictions on when this flag is applicable. For example the method must beTask[<T>]
orValueTask[<T>]
-returning, can't beSynchronized
, etc... The method also needs to satisfy certain correctness invariants in it's body. For example, it must return a value with a type compatible to the unwrapped type of the formal return type in the method signature.The key effect of a method being
Async
is that such method can use a group ofAwait
functions to delegate the "awaiting" to the runtime.The
Await
methods are special methods that provide functionality not otherwise expressible in IL - namely asynchronous waiting for potentially incomplete tasks or awaiters.These
Await
helper methods are only callable fromAsync
methods. Calling one of theseAwait
methods from a method not decorated asMethodImplOptions.Async
is an invalid IL sequence and will result in an appropriate error at IL-compiling or execution time.NOTE: It is permitted for
Await
helpers to call otherAwait
helpers and some of them do that. That is legal because these helpers are themselvesAsync
methods. This part makes no difference to the end user, just something worth mentioning.Experimental
until fully supported and available on all runtime flavors.SYSLIB5007
is just the next experimental warning ID available right now.The warning will say something like:
Runtime Async is experimental
API Usage
The API is not designed or optimized for using directly in the code. The intended purpose is to be used by IL generators such as C# compiler and similar.
From the runtime perspective, there is nothing wrong with invoking the API directly, although compilers may put restrictions on direct use of this API.
Here is an example of a typical use.
When Runtime Async code generation is enabled, C# compiler will emit the following code:
as an IL equivalent of this:
Alternative Designs
This API has evolved from an alternative design where the "await" in Async methods would be encoded by calling Task-returning methods via an alternative signature.
In addition to that, the
Async
methods were defined by using an alternative signature as well.Compared to the scheme that encodes
await
throughAwait
helpers, that scheme had a few inconveniences:Overall, this API requires slightly more complexity on VM and JIT implementation side, while being much friendlier to IL generators.
Risks
Current implementation is CoreCLR-only and employs JIT compiler to perform transformation of async methods into state machines and to provide thunks for interop with ordinary Task-based asynchronous operations.
There is some risk that implementing semantics of this API could be harder on non-JIT runtimes. However, we do not see fundamental reasons why the same semantic could not be implemented in AOT or in Interpreter mode.
The text was updated successfully, but these errors were encountered: