Open agocke opened 5 days ago
Tagging subscribers to this area: @mangod9 See info in area-owners.md if you want to be subscribed.
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.
@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?
@julealgon see https://github.com/dotnet/runtime/issues/94620
What do we do for async delegates in general and Func
in particular?
Related is are also methods like the following:
T M<T>()
, when called as await M<Task>()
Task<T> M<T>()
when called as await M<int>()
@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>()
@julealgon Also see https://github.com/dotnet/runtime/pull/104063
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?
Design issues to be resolved:
Func<T>
in particular?