Skip to content

Fix recursive inline member ordering in optimizer - #20111

Open
majocha wants to merge 27 commits into
dotnet:mainfrom
majocha:fix-20085
Open

Fix recursive inline member ordering in optimizer#20111
majocha wants to merge 27 commits into
dotnet:mainfrom
majocha:fix-20085

Conversation

@majocha

@majocha majocha commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Recursive inline members in a recursive binding group can depend on siblings that appear later in source order. The optimizer now discovers these dependencies, optimizes bindings in dependency order, and restores source order before emitting the result. Trait-witness dependencies and module-level recursive groups are handled as well, while non-inline recursive groups retain the existing behavior.

Regression coverage exercises recursive inline member access, trait-witness resolution, and the existing Issue 1565 cases. Emitted-IL and AOT baselines are updated for the resulting stable output.

Fixes #1565

majocha and others added 8 commits August 1, 2026 13:12
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
majocha and others added 12 commits August 2, 2026 09:41
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@majocha

majocha commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

It works. I'll mark as ready for review but I'm not sure the approach here is the best.

@majocha
majocha marked this pull request as ready for review August 6, 2026 07:58
@majocha
majocha requested a review from a team as a code owner August 6, 2026 07:58
@github-actions github-actions Bot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Aug 6, 2026

@T-Gro T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 This review was generated by AI (@expert-reviewer agent). Findings may contain inaccuracies — please verify independently.

List.mapFold (OptimizeBinding cenv isRec) env xs
if isRec then
let xsArray = xs |> List.toArray
let order = GetBindingOptimizationOrder cenv false true xs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike the module-level path (OptimizeModuleBindings, which gates on binds |> List.exists (fun b -> b.Var.ShouldInline)), this local recursive path runs the full dependency analysis unconditionally for every let rec group — including groups with no inline bindings. That adds a double expression traversal (freeInExpr + FoldExpr, plus trait-witness codegen) on a hot compile path, and it changes emitted IL for non-inline recursive functions (see the updated Verify13043 and Regression_TLR_MutualInnerRec_* baselines), which contradicts the PR statement that non-inline groups retain existing behavior. Consider gating this branch on the same inline-presence check and falling back to the original List.mapFold (OptimizeBinding cenv isRec) otherwise.

| Expr.Op(TOp.TraitCall traitInfo, _, args, m) ->
let depIdxs = addTraitSolutionDependencies depIdxs traitInfo

match ConstraintSolver.CodegenWitnessExprForTraitConstraint cenv.TcVal cenv.g cenv.amap m traitInfo args with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Witness-codegen failures here are silently swallowed (| _ -> depIdxs). Any sibling dependency that is reachable only through the synthesized witness (the resolved member) is then dropped from the schedule. If witness resolution is itself order-sensitive and fails while a sibling is still unoptimized, the ordering problem this PR fixes could reappear for trait-witness cases. Worth confirming the error branch can never hide a real sibling dependency (and note this re-runs witness codegen purely for dependency discovery, which is not free).

let visiting = HashSet<int>()
let visited = HashSet<int>()

let rec visit idx =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visit and the addBindingDependencies/FoldExpr walk are not stack-guarded, unlike other expression traversals in this file (CollectLocalsWithStackGuard, StackGuard). Deeply nested expressions or very large recursive groups could overflow the stack. Consider routing these through the existing stack-guard mechanism.

let fvs = freeInExpr CollectLocalsNoCaching expr

let depIdxs =
let depIdxs = addVals depIdxs (fvs.FreeLocals |> Zset.elements)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

freeInExpr already collects FreeLocals, and the FoldExpr below re-adds the same locals via its Expr.Val intercept — the two passes largely duplicate work for every binding. The fold is only strictly needed for the TraitCall witness handling; you could take the plain value dependencies from freeInExpr alone and reserve the fold for trait calls, avoiding a second full traversal per binding.

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Aug 10, 2026
@T-Gro
T-Gro self-requested a review August 10, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-reviewed PR reviewed by AI review council AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Improve diagnostics for inlining in class methods - FS1114, FS1113, FS1116, FS1118

2 participants