Repository navigation
Conversation
✅ Release notes checked
|
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Continue outlining past bindings that cannot be outlined, copy arguments duplicated into match targets, restrict sequential handling to NormalSeq, guard the outline walk against deep let chains, and share the Await builder and match/quotation helpers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Outline suspending InlineIfLambda callbacks and prepare runtime-async bodies in a dedicated pass after the first optimization loop, so every body is prepared once in its final shape. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Outlined callbacks ran as separate runtime-async methods, so AsyncLocal writes they made were discarded on return. A callback selected by if/match is now constructed once, recording its branch and captures in locals, and each invocation dispatches to an inlined copy of the selected body. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Every invocation copies every branch body. Skip the rewrite when the copies would exceed 2000 expression nodes, leaving the callback as a closure so a remaining suspension is reported as FS3918. Add regression tests for callbacks constructed in a loop and nested branch-selected callbacks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
|
🔍 Tooling Safety Check — Affects-Build-Infra, Affects-Compiler-Output
|
T-Gro
left a comment
There was a problem hiding this comment.
🤖 🕵️ AI review — verify independently.
| | _ -> | ||
| let hoisted = | ||
| if v.IsMutable then | ||
| Construct.NewModifiedVal id v |
There was a problem hiding this comment.
🤖🕵️ [P2] Outer count becomes count (shadowed) at the first printfn, before the inner declaration, with --debug:portable --optimize-.
open System.Threading.Tasks
open System.Runtime.CompilerServices
open Microsoft.FSharp.Core.CompilerServices
let inline invoke ([<InlineIfLambda>] callback: unit -> int) : Task<int> =
StateMachineHelpers.__runtimeAsyncReturn (callback ())
let run flag (ready: Task<int>) =
let mutable count = 99
invoke (
if flag then
printfn "outer = %d" count
let mutable count = 10
fun () ->
let value = AsyncHelpers.Await ready
count <- count + value
count
else
fun () -> 0)|
The size and complexity of this is getting out of hand. I'm thinking about returning to the outlining concept, with saving and restoring execution context at suspend boundaries. Ideally the runtime should provide a public way to transparently await a thunk, which is what we need here. |
|
Can we do outlining safely also w.r.t to order of effects and mutation of locals? |
I guess this all should be covered, because we no longer inline. The main issue is ExecutionContext: we need to continue on the outlined child context. Capturing the final state inside the outlined function and restoring it on resume does it. This will not prevent side effects like AsyncLocal change notifications. This means the execution will still be subtly different for outlined vs inlined callbacks. |
|
I'll make a separate branch with a prototype, retaining all the regression tests from here. |
|
Including this PR, there is now a sizeable catalogue of custom optimizations used by the runtime-async feature:
There is also runtime-async-specific reoptimization of debug pipe chains once synthetic pipe-input bindings are in scope ( |
5f8b7ad to
994d184
Compare
…s.fs Co-authored-by: Tomas Grosup <tomasgrosup@microsoft.com>
Fixes #20577
When an
InlineIfLambdafunction or delegate still contains anAwaitafter inlining, it used to be expanded at every call, which repeated its setup code and split up the state it captures. Now its setup runs once, recording which branch was chosen and the values that branch uses. Each call runs an inlined copy of the chosen body. Everything stays in the enclosing runtime-async method, soAsyncLocalchanges made by the callback remain visible to the caller.LowerLocalMutables, so they see the final inlined shape. Each marker body is prepared once, after the callbacks are inlined. As a result, anAwaitthat ends up inside awithhandler is now rewritten; before, it was missed.InlineIfLambdalambda out of its binding, so the normal inliner handles callbacks that don't branch.try/withor called from inside a closure are reported as FS3918.Composes with #20655.
Checklist