dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.29k stars 4.74k forks source link

Runtime-async open issues #109632

Open agocke opened 5 days ago

agocke commented 5 days ago

Design issues to be resolved:

dotnet-policy-service[bot] commented 5 days ago

Tagging subscribers to this area: @mangod9 See info in area-owners.md if you want to be subscribed.

jakobbotsch commented 4 days ago

What, if any, are the stack requirements on calling a method with the async calling convention inside an async method?

From RyuJIT's perspective restrictions will not make things simpler -- i.e. having the stack non-empty is fine. Of course these values are subject to same restrictions as locals live across suspension points.

What do we do for async delegates in general and Func in particular? What do we do for function pointers to async methods?

I think without introducing an explicit async calling convention in the signature the only option is to let them point to the runtime synthesized Task<T> returning version of the method. It means taking the address of a runtime-async function is illegal in IL (when doing this in C# the compiler would instead generate IL taking the address of the Task/Task<T> returning version).

From RyuJIT's perspective I think this can still be optimizable via PGO and delegate GDV in the "runtime-async calling delegate that ends up in runtime-async" case. Roslyn will generate the "runtime-async calling compiler-async" IL, but I think we can recognize this and optimize it to "runtime-async calling runtime-async" when GDV kicks in.

Having an explicit async calling convention would be a way for the user to ensure that this optimization kicks in. The UnmanagedCallersOnly and delegate* unmanaged work laid a pretty extensible framework for doing this sort of thing, so maybe it wouldn't be too expensive to build that support for function pointers. For delegates I'm not so sure, seems like we would end up with AsyncAction and AsyncFunc there to encode the async calling convention.

julealgon commented 4 days ago

@agocke can you provide a bit more context to this? I assume there is a broader discussion and that this issue here is just kind of a "summary" of sorts.

Is the idea to move away from "compiler-generated" async state machine and have the runtime recognize the actual pattern as a first-class thing?

KeterSCP commented 4 days ago

@julealgon see https://github.com/dotnet/runtime/issues/94620

333fred commented 4 days ago

What do we do for async delegates in general and Func in particular?

Related is are also methods like the following:

agocke commented 2 days ago

@333fred I think those two questions are answered in the spec. There's a note at the bottom:

[Note: these rules operate before generic substitution, meaning that a method which only meets requirements after substitution would not be considered as valid.]

So your example (1) is invalid -- that method is not async. Your example (2) is fine -- that method is async. The unwrapping signature is modreq(Task<T>) T M<T>()

agocke commented 2 days ago

@julealgon Also see https://github.com/dotnet/runtime/pull/104063

333fred commented 1 day ago

So your example (1) is invalid -- that method is not async.

Andy and I talked about this offline: it is related to the Func<T> question, and is complicated by the fact that there's literally a new calling convention here. We need to do some experiments to measure whether the overhead of introducing an extra thunk for runtime-async methods is advantageous instead of just using the standard Task-runtime async thunk.

The unwrapping signature is modreq(Task<T>) T M<T>()

More precision please 🙂. What's the IL you'd like to see emitted here? Is it referencing the type parameter as !0, or is it filling in the concrete type?