Skip to content

WIP: Reduce IL size of LexFilter - #20920

Draft
T-Gro wants to merge 41 commits into
dotnet:mainfrom
T-Gro:t-gro-guarded-match-delivery
Draft

T-Gro wants to merge 41 commits into
dotnet:mainfrom
T-Gro:t-gro-guarded-match-delivery

Conversation

@T-Gro

@T-Gro T-Gro commented Nov 3, 2026

Copy link
Copy Markdown
Member

Description

Fixes #20632

LexFilter.hwTokenFetch runs for every token the parser reads. At 110 KB of IL it was too large for the JIT to optimize. Three changes:

  • Shared guards. A when guard reached from several decision paths is compiled once as a helper. A repeated rest-of-match is compiled once and reached by a branch in the same method. Guard order and count, active-pattern calls, mutable state, tail calls, quotations and debug points do not change.
  • Compact union switches. A union test emitted one jump-table entry per union case. The table now ends at the highest tested case, and up to four sparse cases use compare-and-branch. Dense switches do not change; no IL baselines change.
  • LexFilter cleanup. Remove the internal --tokenize-debug switch and its 102 trace statements. End the balancing-rule match so the offside rules compile once, not once per token kind.
IL bytes main this PR
hwTokenFetch 110,353 (MinOpts) 20,985 (FullOpts)
All of LexFilter 196,561 73,488 (−63%)
FSharp.Compiler.Service 5,429,307 5,166,264 (−4.8%)
FSharp.Core 616,182 575,365 (−6.6%)
Time ratio, this PR / … main .NET 10.0.403 SDK compiler
Parse 224 compiler files, .NET 11 0.60 [0.59, 0.61] 0.60 [0.59, 0.60]
Parse 224 compiler files, .NET 10 0.61 [0.60, 0.61] 0.60 [0.59, 0.61]
Typecheck a 547-line script, .NET 11 0.94 [0.91, 1.00] 0.88 [0.83, 0.93]

20 randomized fresh-process pairs per row, ReadyToRun off, ratio of medians, 95% bootstrap interval. Parse allocations are unchanged.

Checklist

  • Test cases added
  • Performance benchmarks added in case of performance changes
  • Release notes entry updated

T-Gro and others added 30 commits October 16, 2026 23:04
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>
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>
Share repeated guards and exact residual states without a source-pattern whitelist or union-layout prediction. Preserve action ownership, source lambda boundaries, recursive initialization and original method continuations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the actual MatchBuilder target index with the incomplete clause instead of assuming it follows the source clauses. Reuse that clause for its bindings and guard.

Add baseline guarded-match regressions using the existing templated fixtures. Leave generic guard and action sharing, and its dependent coverage, in the upper layer.

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>
Keep separate typed identity tables and their growth schedules. Reuse the existing test execution helper without changing the harness's real-signature mode.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the four typed memo tables in optional struct state. Simple matches and diagnostic passes now use the existing disabled path without constructing unused sharing caches.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use the existing BCL struct tuple for the four distinct dictionary roles. This retains disabled-path allocation savings without emitting a public anonymous-record type.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Call the existing frontier investigator directly on disabled paths. Keep the enabled memoization path and its defensive fallback unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use an ordinary option around the existing struct tuple. Disabled matches still allocate no memo state; recursive calls pass one reference instead of copying the wide value option. Enabled matches add one Some cell. This is a measured exception to the usual value-option preference.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reuse the narrow correction from 84140c45554fc2a9b2ec38750af0a74508c601cc. All 31 current determinism cases pass without changing compiler behavior or test assertions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Avoid structural equality in per-clause producer setup. Keep the existing allocation and join mechanisms unchanged.

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>
T-Gro and others added 10 commits November 3, 2026 11:42
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reuse ExistsExpr, route promotion through the existing memo dispatcher, keep main's clause-group shape, and drop redundant annotations, comments and opens.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Share the context fixture for the growth case, inline single-use fixtures, and table the option matrices.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drop the --tokenize-debug option, its TokenizeOption case and the 102 offside-rule trace statements.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The token-specific balancing clauses now end their own match, so the stack-only offside rules are no longer copied into every token branch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Stop the jump table at the highest tested tag, and use compare-and-branch for up to four sparse tags.

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 Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

✅ No release notes required

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

LexFilter's hwTokenFetch is too large for the JIT to optimize and zeroes a large stack frame for every token

1 participant