Uh oh!
There was an error while loading. Please reload this page.
Runtime async via intrinsic - #20235
Conversation
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
|
…c; add Language preview release notes
The features dictionary lost ImplicitDIMCoverage, MethodOverloadsCache,
ErrorOnMissingSignatureAttribute, DirectDelegateConstruction,
AccessProtectedBaseFieldFromClosure and RecordSpreads entries, causing
54 CI test failures ('Unable to find feature' internal errors and
preview features not enabled).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>Uh oh!
There was an error while loading. Please reload this page.
| "__stateMachine should always be guarded by __useResumableCode and only used in valid state machine implementations" | ||
| [<MethodImpl(MethodImplOptions.NoInlining)>] | ||
| let __runtimeAsync<'T> (value: 'T) : Task<'T> = |
There was a problem hiding this comment.
Why a fail and not a retype?
I also wonder about the "older compiler, newer Fsharp.Core" consequences here.
There was a problem hiding this comment.
Retype as in different nominal type than actual resulting type? The idea here is to have it typecheck correctly with no extra compiler work. By the spec we need the resulting method to actually return 't, but have a Task<'t> nominal return type, so this covers it. During typecheck we got Task<'t> just from the signature here. Optimizer erases __runtimeAsync and we emit async method which returns 't just like the runtime expects.
As for FSharp.Core / compiler mismatch, this is, kind of like __stateMachine, an "expert" feature for authoring CE builders or low level runtime async methods. The fail with unsupported compiler is same behavior as __stateMachine.
There was a problem hiding this comment.
Got it, this is just for the final return/Run.
Maybe the name could indicate it?
Like __return_from_runtimeAsync ?
There was a problem hiding this comment.
Yes, that's the intent . more ideas: __asyncReturn, __runtimeAsyncReturn
T-Gro
left a comment
There was a problem hiding this comment.
🕵️🤖 A couple more notes without inline anchors:
- Non-generic
Task/ValueTaskreturns are silently unsupported — worth a diagnostic so it errors instead of miscompiling (docs L31). - ILVerify can't verify
async-returning methods yet, so this codegen stays unverified in CI — known gap.
Backed by a small runtime-async edge-case suite (runtime behavior + emitted IL) driven through a test-only runtimeTask CE.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
majocha
commented
Aug 13, 2026
Another thing to think through is inlining. Currently there are no checks at all for use of suspending Currently it is up to the "expert" user to not misuse This is still a sketch, but it successfully compiles |
T-Gro
commented
Aug 14, 2026
We could have a notion of PostIlxGen checks. |
T-Gro
commented
Aug 18, 2026
🕵️🤖 Parked a runtime-async edge-case suite on |
Roslyn-async2-inspired edge cases for the runtime-async intrinsic, driven through the test-only runtimeTask CE (treated as a hypothetical library): * execution fixture (RuntimeAsync/RuntimeAsyncEdgeCases.fs): locals/loops across suspension, non-ref struct across suspension, ValueTask operand, exception propagation, IAsyncDisposable with genuinely-async DisposeAsync. * facts (RuntimeAsyncEdgeCaseTests.fs): Await overload selection per operand type, no compiler state machine (direct + CE), the C1 forbidden `tail.` prefix, and a parametrized set of currently-undiagnosed contract-forbidden patterns (await-in-finally/catch, ref-struct- and byref-across-suspension). Every asserted IL substring and runtime symptom was captured empirically on the pinned net11 preview; the forbidden patterns match docs/runtime-async.md. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 55dd72c8-46d3-4959-9677-c52d41779596
The sequence-points baseline (and ildasm) cannot render MethodImplOptions.Async (0x2000), so the lifted __runtimeAsync body shows up as a plain outer@<line> closure. Factor the metadata flag check into assertAsyncFlagOnLiftedClosureOnly and chain it onto the sequence-points fact so the exact program that emits the .bsl also proves the async marker lands only on the lifted closure, never on the user's outer/helper methods. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 55dd72c8-46d3-4959-9677-c52d41779596
majocha
commented
Aug 20, 2026
Looks like the runtime feature is quickly evolving, see #19056 (comment) |
majocha
commented
Aug 20, 2026
I wonder how to support the other allowed return types. __runtimeAsyncReturn<'T>: 'T -> Task<'T>__runtimeAsyncReturnValueTask<'T>: 'T -> ValueTask<'T>__runtimeAsyncReturnUnit : unit -> Task
__runtimeAsyncReturnValueTaskUnit : unit -> ValueTaskand it quickly becomes a whole zoo. Do we need the non-generic versions at all? Only for potential C# interop, I guess. The upside is that the current type check is all we need to keep it correct, without any extra handling. The other alternative it to have a unconstrained |
majocha
commented
Aug 21, 2026
I pulled them in and adjusted to |
majocha
commented
Aug 21, 2026
|
majocha
commented
Aug 21, 2026
The downside is the errors surface only during build, not in the IDE, just like with resumable state machines. |
majocha
commented
Aug 21, 2026
Regarding suspension in exception handling blocks, it's not hard to do a rewrite during optimization, just like C# compiler does. |
04e7fb3 to
27944e7Comparemajocha
commented
Aug 22, 2026
This needs to work in debug mode, too. Now that's going to be fun. |
eeeec60 to
25e87baCompare585df73 to
34060f9Compare
Yet another proof of concept (see also #19449)
The experiment here is to explore viability of working CE builders with just a minimal compiler support.
consider
intrinsic, which when used as a member or function body, promotes it to
managed async.We can have a fully inlining builder which wraps the code in it's Run method:
Because resumption is handled by the runtime, the builder is effectively just a sync builder with
There are more concerns than suspension / resumption that the runtime apparently does not handle and they would have to be covered either by the compiler or the builder implementations:
awaits in EH blocks (IAsyncDisposable) - this has a PoC in the tests here.
byref locals and execution context / sync context, see C# docs below.
runtime spec :
Runtime-async specification
interesting docs on C# implementation