Enable runtime async via compiler intrinsics - #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>
|
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 |
We could have a notion of PostIlxGen checks. |
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
|
Looks like the runtime feature is quickly evolving, see #19056 (comment) |
|
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 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
I ordered the AI to take the sample runtime async asyncSeq and make a full FSharp.Control.TaskSeq reimplementation: https://github.com/majocha/FSharp.Control.TaskSeq/tree/runtime-async Remarkably it passes the whole test suite. (in Release). It also reveals some more debug configuration bugs in this PR. |
This comment has been minimized.
This comment has been minimized.
The coreclr_release job was canceled after the 120-minute limit due to a flaky infrastructure timeout (memory pressure hanging an unrelated test assembly). The same job timed out on unrelated PRs #20235 and #20393 in the last 10 days. ComponentTests (all DIM/interface tests affected by this PR) passed fully, so the merge resolution is correct. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
T-Gro
left a comment
There was a problem hiding this comment.
🤖🕵️ AI-assisted review. Every finding reproduced on a local build of dac78834: captured output, attribution controls, --optimize+ and --optimize-.
| // application of local type functions with type parameters = measure types and body = local value - inline the body | ||
| GenExpr cenv cgbuf eenv v sequel | ||
|
|
||
| | Expr.App _ when TryGetRuntimeAsyncReturn g expr |> Option.isSome -> GenRuntimeAsyncReturnAsStartedTask cenv cgbuf eenv expr sequel |
There was a problem hiding this comment.
🤖🕵️ byref captured by the closure synthesized after PostInferenceChecks, so FS0406 has already run. The flag lands on f@8::Invoke, not f.
[<NoCompilerInlining>]
let f (x: byref<int>) : Task<int> =
let y = x
__runtimeAsyncReturn (x + y)- now: compiles clean; ILVerify
StackByRef@0x06; run → NRE; both opt modes - expected: rejected, or lowered without a capturing closure
- no leading
let, oryunused → flag onM::f, verifies clean, returns 2 - fix: reject byref/byref-like/pinning free values before the lambda is built
| let (|RuntimeAsyncApplication|_|) = | ||
| function | ||
| | ApplicableExpr(expr = (RuntimeAsyncReturnFunction g (vref, flags, m))) -> | ||
| checkLanguageFeatureAndRecover g.langVersion LanguageFeature.RuntimeAsync m |
There was a problem hiding this comment.
🤖🕵️ Pipe bypasses the preview gate — enforced only in syntactic application position, while downstream recognition is structural.
// --langversion:9
let f x = x |> __runtimeAsyncReturn // compiles, implAttrs=0x2008
let g x = __runtimeAsyncReturn x // error FS3350- expected: FS3350 for both
- with a suspension in the body: opt+ emits the flag, opt- gives FS3916 — a different symptom, not absence
- fix: gate where recognition happens, plus a re-check at the IlxGen placements —
LowerStateMachineExpralready re-gates there (IlxGen.fs:3248) - imported cross-assembly
inlinealso emits the flag under F# 9 — deliberate?
|
🔍 Tooling Safety Check — Affects-Bootstrap, Affects-Build-Infra, Affects-Compiler-Output, Affects-Test-Tooling
|
|
Unfortunatelly to make async iterators like See: |
|
AI thoughts on runtime async state machines implementation: |
Add preview F# compiler support for .NET runtime-async methods. Compiler-recognized
__runtimeAsyncReturnintrinsics markTask/ValueTaskmethods and lambdas withMethodImplOptions.Async, whileAsyncHelpers.Await*calls become runtime suspension points.The optimizer preserves and specializes inline suspension fragments, rewrites suspending exception handlers and finally compensations, and reports unsupported byref or suspension patterns. The feature is gated by
langversion:previewand target-runtime metadata support; FSharp.Core exposes the intrinsics only fornet10.0, with builders remaining application-defined.Consider an inline "sync" CE builder. applying
__runtimeAsyncReturnto the inlined code in its Run method compiles the computation expression into a single runtime async method:Resumption is handled by the runtime, calling
AsyncHelpers.AwaitinBindis all that is needed to make the resulting CE async:runtime spec :
Runtime-async specification
interesting docs and links:
To do:
--optimize-(debug configuration)IAsyncEnumerableMoveNextfor async iterators