Skip to content

Replace ForEachReturnStatement closure with direct kind-switched walk#4239

Open
mds-ant wants to merge 2 commits into
microsoft:mainfrom
mds-ant:perf/foreach-return-walk
Open

Replace ForEachReturnStatement closure with direct kind-switched walk#4239
mds-ant wants to merge 2 commits into
microsoft:mainfrom
mds-ant:perf/foreach-return-walk

Conversation

@mds-ant

@mds-ant mds-ant commented Jun 8, 2026

Copy link
Copy Markdown

Context

ForEachReturnStatement allocated a self-referential closure on every call and recursed via ForEachChild, which dispatches through an interface and visits expression-typed children (conditions, initializers) that the closure immediately rejects.

This PR

This PR rewrites ForEachReturnStatement as a self-recursive package-level function that switches on Kind and recurses only into the statement-typed children of each construct. This removes the per-call closure allocation, the ForEachChild interface dispatch, and the wasted visits to non-statement children. The set of traversed kinds is unchanged.

This PR was assisted by Claude Code.

Performance

hyperfine benchmark:

Benchmark 1: ./built/local/tsgo_main --noEmit -p ~/Code/vscode/src/tsconfig.json
  Time (mean ± σ):      5.012 s ±  0.108 s    [User: 25.429 s, System: 1.427 s]
  Range (min … max):    4.825 s …  5.273 s    25 runs

Benchmark 2: ./built/local/tsgo_optimized --noEmit -p ~/Code/vscode/src/tsconfig.json
  Time (mean ± σ):      4.950 s ±  0.099 s    [User: 24.952 s, System: 1.421 s]
  Range (min … max):    4.771 s …  5.158 s    25 runs

Memory allocations (measured using --noEmit --singleThreaded --diagnostics):

Before After Delta
20,918,299 20,403,098 –2.46%
goos: darwin
goarch: arm64
pkg: github.com/microsoft/typescript-go/internal/ast
cpu: Apple M1 Pro
                                                                       │ ./out_before.txt │           ./out_after.txt           │
                                                                       │      sec/op      │   sec/op     vs base                │
ForEachReturnStatement/checker.ts-10                                          402.8µ ± 1%   259.6µ ± 1%  -35.56% (p=0.000 n=10)
ForEachReturnStatement/Herebyfile.mjs-10                                     2328.0n ± 0%   804.1n ± 0%  -65.46% (p=0.000 n=10)
ForEachReturnStatement/jsxComplexSignatureHasApplicabilityError.tsx-10       118.50n ± 0%   24.45n ± 0%  -79.37% (p=0.000 n=10)
geomean                                                                       4.807µ        1.722µ       -64.19%

                                                                       │ ./out_before.txt │              ./out_after.txt              │
                                                                       │       B/op       │     B/op      vs base                     │
ForEachReturnStatement/checker.ts-10                                         83.81Ki ± 0%    0.00Ki ± 0%  -100.00% (p=0.000 n=10)
ForEachReturnStatement/Herebyfile.mjs-10                                     1.281Ki ± 0%   0.000Ki ± 0%  -100.00% (p=0.000 n=10)
ForEachReturnStatement/jsxComplexSignatureHasApplicabilityError.tsx-10         96.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)

                                                                       │ ./out_before.txt │             ./out_after.txt              │
                                                                       │    allocs/op     │  allocs/op   vs base                     │
ForEachReturnStatement/checker.ts-10                                          5.364k ± 0%   0.000k ± 0%  -100.00% (p=0.000 n=10)
ForEachReturnStatement/Herebyfile.mjs-10                                       82.00 ± 0%     0.00 ± 0%  -100.00% (p=0.000 n=10)
ForEachReturnStatement/jsxComplexSignatureHasApplicabilityError.tsx-10         6.000 ± 0%    0.000 ± 0%  -100.00% (p=0.000 n=10)

ForEachReturnStatement allocated a self-referential closure on every call
and recursed via ForEachChild, which dispatches through an interface and
visits expression-typed children (conditions, initializers) that the
closure immediately rejects.

Rewrite as a self-recursive package-level function that switches on Kind
and recurses only into the statement-typed children of each construct.
This removes the per-call closure allocation, the ForEachChild interface
dispatch, and the wasted visits to non-statement children. The set of
traversed kinds is unchanged.
Copilot AI review requested due to automatic review settings June 8, 2026 09:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Refactors ForEachReturnStatement to use explicit, kind-specific traversal (instead of generic ForEachChild) and adds a small helper to traverse NodeLists, with an added nil guard for safety.

Changes:

  • Rewrote ForEachReturnStatement to explicitly recurse through statement-bearing node kinds.
  • Added forEachReturnStatementInNodeList to traverse statement lists (*NodeList).
  • Added a nil guard to avoid panics when called with a nil node.

@jakebailey

Copy link
Copy Markdown
Member

hyperfine benchmark

You omitted the comparison, no? I suspect this might be within noise when end to end

@jakebailey

Copy link
Copy Markdown
Member

This is probably fine, but an alternative approach would have been to copy the parent pointer code where we just reuse the closure, I think.

@DanielRosenwasser

Copy link
Copy Markdown
Member

@typescript-bot perf test this

@typescript-automation

typescript-automation Bot commented Jun 8, 2026

Copy link
Copy Markdown

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
perf test this ✅ Started 👀 Results

@typescript-automation

Copy link
Copy Markdown

@DanielRosenwasser
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - native
Errors 4 4 ~ ~ ~ p=1.000 n=6
Symbols 81,785 (± 0.03%) 81,775 (± 0.05%) ~ 81,731 81,823 p=0.810 n=6
Types 98,821 98,821 ~ ~ ~ p=1.000 n=6
Memory Used 179,780k (± 0.45%) 179,587k (± 0.38%) ~ 178,655k 180,291k p=0.575 n=6
Memory Allocs 1,768,045 (± 0.01%) 1,762,829 (± 0.02%) -5,215 (- 0.29%) 1,762,280 1,763,246 p=0.005 n=6
Config Time 0.000s (±244.70%) 0.000s (±154.76%) ~ 0.000s 0.001s p=0.595 n=6
Parse Time 0.066s (± 7.10%) 0.065s (± 5.50%) ~ 0.061s 0.070s p=0.747 n=6
Bind Time 0.021s (±11.33%) 0.019s (±19.39%) ~ 0.015s 0.026s p=0.223 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.987s (± 1.19%) 1.008s (± 1.30%) +0.021s (+ 2.09%) 0.996s 1.030s p=0.019 n=6
Total Time 1.076s (± 1.02%) 1.093s (± 1.35%) ~ 1.076s 1.113s p=0.054 n=6
angular-1 - native
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 884,411 (± 0.09%) 884,627 (± 0.11%) ~ 883,405 886,476 p=0.689 n=6
Types 263,954 (± 0.00%) 263,954 (± 0.00%) ~ 263,952 263,957 p=0.930 n=6
Memory Used 831,381k (± 0.04%) 831,252k (± 0.10%) ~ 830,821k 832,877k p=0.173 n=6
Memory Allocs 6,703,061 (± 0.23%) 6,621,076 (± 0.12%) -81,986 (- 1.22%) 6,613,809 6,636,284 p=0.005 n=6
Config Time 0.026s (±14.91%) 0.024s (±12.82%) ~ 0.021s 0.029s p=0.277 n=6
Parse Time 0.268s (± 4.78%) 0.264s (± 2.85%) ~ 0.257s 0.274s p=0.335 n=6
Bind Time 0.048s (±29.02%) 0.053s (±27.95%) ~ 0.041s 0.079s p=0.294 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 2.040s (± 1.54%) 2.056s (± 2.35%) ~ 1.986s 2.105s p=0.575 n=6
Total Time 2.396s (± 0.91%) 2.409s (± 1.43%) ~ 2.368s 2.460s p=0.469 n=6
mui-docs - native
Errors 11,246 (± 0.02%) 11,245 (± 0.03%) ~ 11,240 11,247 p=0.599 n=6
Symbols 4,179,255 4,179,255 ~ ~ ~ p=1.000 n=6
Types 1,526,525 1,526,525 ~ ~ ~ p=1.000 n=6
Memory Used 4,864,996k (± 0.03%) 4,865,039k (± 0.02%) ~ 4,864,054k 4,866,769k p=0.810 n=6
Memory Allocs 108,956,580 (±16.71%) 98,058,780 (±22.01%) ~ 77,631,680 127,368,477 p=0.378 n=6
Config Time 0.024s (±10.55%) 0.027s (±14.19%) ~ 0.021s 0.033s p=0.124 n=6
Parse Time 1.365s (±21.53%) 1.196s (±24.29%) ~ 0.890s 1.593s p=0.378 n=6
Bind Time 0.002s 0.003s (±19.35%) 🔻+0.001s (+33.33%) 0.002s 0.003s p=0.025 n=6
Check Time 19.170s (± 0.49%) 19.181s (± 0.59%) ~ 19.081s 19.401s p=1.000 n=6
Emit Time 0.539s (± 4.69%) 0.602s (±19.01%) ~ 0.524s 0.834s p=0.109 n=6
Total Time 21.900s (± 1.30%) 21.813s (± 1.67%) ~ 21.424s 22.347s p=0.575 n=6
self-build-src - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,396,711 1,396,711 ~ ~ ~ p=1.000 n=6
Types 442,164 442,164 ~ ~ ~ p=1.000 n=6
Memory Used 1,649,669k (± 0.26%) 1,653,474k (± 0.47%) ~ 1,642,501k 1,663,009k p=0.471 n=6
Memory Allocs 57,512,422 (± 0.04%) 57,038,889 (± 0.11%) -473,533 (- 0.82%) 56,983,839 57,125,517 p=0.005 n=6
Config Time 0.019s (±27.66%) 0.019s (±14.53%) ~ 0.016s 0.023s p=1.000 n=6
Parse Time 0.265s (± 2.23%) 0.274s (± 2.22%) +0.010s (+ 3.59%) 0.265s 0.282s p=0.045 n=6
Bind Time 0.000s (±154.76%) 0.000s (±244.70%) ~ 0.000s 0.001s p=0.595 n=6
Check Time 2.693s (± 0.50%) 2.726s (± 0.62%) +0.033s (+ 1.21%) 2.711s 2.758s p=0.013 n=6
Emit Time 0.264s (± 4.16%) 0.274s (± 3.45%) ~ 0.260s 0.289s p=0.091 n=6
Total Time 33.742s (± 0.70%) 34.083s (± 0.56%) +0.341s (+ 1.01%) 33.811s 34.383s p=0.045 n=6
self-compiler - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 338,176 338,176 ~ ~ ~ p=1.000 n=6
Types 199,522 199,522 ~ ~ ~ p=1.000 n=6
Memory Used 332,151k (± 0.03%) 332,182k (± 0.06%) ~ 331,874k 332,467k p=0.810 n=6
Memory Allocs 2,557,485 (± 0.02%) 2,532,299 (± 0.05%) -25,186 (- 0.98%) 2,531,054 2,533,938 p=0.005 n=6
Config Time 0.001s 0.001s ~ ~ ~ p=1.000 n=6
Parse Time 0.134s (± 2.98%) 0.139s (± 6.01%) ~ 0.129s 0.151s p=0.423 n=6
Bind Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Check Time 1.395s (± 1.22%) 1.401s (± 0.92%) ~ 1.389s 1.420s p=0.575 n=6
Emit Time 0.082s (± 7.82%) 0.091s (±10.79%) ~ 0.075s 0.100s p=0.092 n=6
Total Time 1.661s (± 0.94%) 1.687s (± 1.05%) +0.026s (+ 1.57%) 1.661s 1.706s p=0.045 n=6
ts-pre-modules - native
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 97,488 97,488 ~ ~ ~ p=1.000 n=6
Types 356 356 ~ ~ ~ p=1.000 n=6
Memory Used 133,422k (± 0.03%) 133,415k (± 0.03%) ~ 133,357k 133,445k p=0.689 n=6
Memory Allocs 193,758 (± 0.25%) 193,489 (± 0.22%) ~ 192,989 193,978 p=0.471 n=6
Config Time 0.001s 0.001s (±48.94%) ~ 0.000s 0.001s p=0.405 n=6
Parse Time 0.120s (± 3.51%) 0.116s (± 3.63%) ~ 0.112s 0.123s p=0.145 n=6
Bind Time 0.038s (±12.32%) 0.041s (± 8.04%) ~ 0.037s 0.046s p=0.421 n=6
Check Time 0s 0s ~ ~ ~ p=1.000 n=6
Emit Time 0.000s 0.000s ~ ~ ~ p=1.000 n=6
Total Time 0.162s (± 2.01%) 0.161s (± 2.29%) ~ 0.157s 0.166s p=0.468 n=6
vscode - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 6,561,217 6,561,217 ~ ~ ~ p=1.000 n=6
Types 2,306,245 2,306,245 ~ ~ ~ p=1.000 n=6
Memory Used 4,436,756k (± 0.04%) 4,437,792k (± 0.03%) ~ 4,435,606k 4,440,305k p=0.173 n=6
Memory Allocs 31,819,103 (± 0.12%) 31,271,431 (± 0.09%) -547,672 (- 1.72%) 31,247,942 31,324,012 p=0.005 n=6
Config Time 0.069s (± 4.25%) 0.062s (± 8.52%) ~ 0.056s 0.070s p=0.065 n=6
Parse Time 0.855s (± 2.78%) 0.836s (± 4.16%) ~ 0.780s 0.879s p=0.336 n=6
Bind Time 0.151s (±30.11%) 0.167s (±34.18%) ~ 0.128s 0.252s p=1.000 n=6
Check Time 8.453s (± 0.59%) 8.536s (± 1.57%) ~ 8.388s 8.782s p=0.173 n=6
Emit Time 2.245s (± 6.30%) 2.072s (±10.69%) ~ 1.828s 2.340s p=0.230 n=6
Total Time 11.792s (± 1.13%) 11.694s (± 1.04%) ~ 11.539s 11.873s p=0.230 n=6
webpack - native
Errors 583 583 ~ ~ ~ p=1.000 n=6
Symbols 717,611 717,611 ~ ~ ~ p=1.000 n=6
Types 339,294 339,294 ~ ~ ~ p=1.000 n=6
Memory Used 564,922k (± 0.02%) 564,766k (± 0.03%) ~ 564,573k 565,058k p=0.109 n=6
Memory Allocs 4,097,019 (± 0.92%) 4,050,216 (± 0.32%) -46,803 (- 1.14%) 4,033,174 4,068,066 p=0.013 n=6
Config Time 0.009s (±25.20%) 0.010s (±22.32%) ~ 0.007s 0.013s p=0.463 n=6
Parse Time 0.152s (± 3.33%) 0.150s (± 5.14%) ~ 0.140s 0.160s p=0.809 n=6
Bind Time 0.028s (± 8.91%) 0.027s (±32.91%) ~ 0.021s 0.045s p=0.294 n=6
Check Time 1.090s (± 1.34%) 1.078s (± 0.47%) ~ 1.072s 1.085s p=0.149 n=6
Emit Time 0.001s 0.002s (±36.48%) ~ 0.001s 0.002s p=0.071 n=6
Total Time 1.293s (± 1.16%) 1.278s (± 0.73%) ~ 1.266s 1.286s p=0.127 n=6
xstate-main - native
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,060,815 1,060,815 ~ ~ ~ p=1.000 n=6
Types 384,533 384,533 ~ ~ ~ p=1.000 n=6
Memory Used 638,301k (± 0.01%) 638,335k (± 0.01%) ~ 638,289k 638,451k p=0.230 n=6
Memory Allocs 5,161,389 (± 0.07%) 5,106,983 (± 0.07%) -54,406 (- 1.05%) 5,103,715 5,112,259 p=0.005 n=6
Config Time 0.002s 0.002s ~ ~ ~ p=1.000 n=6
Parse Time 0.122s (± 4.80%) 0.116s (± 8.35%) ~ 0.104s 0.126s p=0.228 n=6
Bind Time 0.025s (±19.51%) 0.029s (±25.58%) ~ 0.021s 0.036s p=0.332 n=6
Check Time 1.100s (± 0.51%) 1.102s (± 1.10%) ~ 1.081s 1.117s p=0.422 n=6
Emit Time 0.001s (±109.43%) 0.001s (±48.94%) ~ 0.000s 0.001s p=0.282 n=6
Total Time 1.254s (± 0.58%) 1.252s (± 1.21%) ~ 1.240s 1.272s p=0.688 n=6
System info unknown
Hosts
  • native
Scenarios
  • Compiler-Unions - native
  • angular-1 - native
  • mui-docs - native
  • self-build-src - native
  • self-compiler - native
  • ts-pre-modules - native
  • vscode - native
  • webpack - native
  • xstate-main - native
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

lsp

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-UnionsLSP - native
Req 1 - updateOpen 9ms (± 5.53%) 9ms (± 5.53%) ~ 9ms 10ms p=1.000 n=6
Req 2 - geterr 945ms (± 0.72%) 959ms (± 1.01%) +14ms (+ 1.43%) 944ms 973ms p=0.024 n=6
Req 3 - references 46ms (± 7.55%) 46ms (± 8.86%) ~ 42ms 51ms p=0.746 n=6
Req 4 - navto 16ms (± 2.58%) 20ms (±21.18%) 🔻+4ms (+23.16%) 16ms 26ms p=0.026 n=6
Req 5 - completionInfo count 1,357 1,357 ~ ~ ~ p=1.000 n=6
Req 5 - completionInfo 14ms (±10.63%) 17ms (±25.62%) ~ 13ms 24ms p=0.253 n=6
CompilerLSP - native
Req 1 - updateOpen 10ms 11ms (± 5.21%) ~ 10ms 11ms p=0.071 n=6
Req 2 - geterr 437ms (± 2.27%) 437ms (± 2.80%) ~ 422ms 456ms p=0.936 n=6
Req 3 - references 47ms (± 3.29%) 46ms (± 4.66%) ~ 44ms 49ms p=0.325 n=6
Req 4 - navto 17ms (± 2.42%) 17ms (± 4.47%) ~ 16ms 18ms p=1.000 n=6
Req 5 - completionInfo count 1,519 1,519 ~ ~ ~ p=1.000 n=6
Req 5 - completionInfo 18ms (±10.53%) 18ms (±10.53%) ~ 16ms 21ms p=0.739 n=6
System info unknown
Hosts
  • native
Scenarios
  • CompilerLSP - native
  • Compiler-UnionsLSP - native
  • xstate-main-1-LSP - native
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

startup

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
lsp-startup - native
Execution time 4.88ms (±516.55%) 4.88ms (±516.54%) -0.00ms (- 0.07%) 0.00ms 491.42ms p=0.000 n=600
tsgo-startup - native
Execution time 6.00ms (±516.54%) 5.96ms (±516.55%) -0.04ms (- 0.71%) 0.00ms 603.82ms p=0.000 n=600
System info unknown
Hosts
  • native
Scenarios
  • lsp-startup - native
  • tsgo-startup - native
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@RyanCavanaugh RyanCavanaugh added the No linked issue This PR doesn't say what bug it fixes label Jun 8, 2026
@RyanCavanaugh RyanCavanaugh added this to the Possible Improvement milestone Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

No linked issue This PR doesn't say what bug it fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants