You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two places in the call-graph phase hold more of the AST than the work requires, and both are
measurable on vscode/src (6,758 files, one program).
1. indexCallExpressions materialises every AST node. It traverses with ts-morph's forEachDescendant, which wraps each visited node in a JS object and caches it on the SourceFile for the lifetime of the program. Indexing call sites therefore keeps the entire AST
of every file resident, when only the ~471k call-like nodes are ever used. Measured: +5.09 GB
for the index alone.
2. Every program stays materialised through the whole solve.#137 released programs but ran
the entire solve first, so all 92 of vscode's programs were live before disposal began; #127
interleaved but never released, so they stayed live too. Neither bounded anything on its own —
both measured ~30.6 GB on vscode and were killed. vscode/src is ONE program and completes;
the whole repo is 92 and does not.
Scope boundary
This is #112 Step 2 ("bound the resident program set") plus the indexing half of ceiling 1.
Not in scope: #112 Step 3 (incremental reuse) or Step 4 (sharding). Explicitly not a fix for
whole-vscode -a 4 — see Caveats.
Goals
Walk the raw compiler AST in indexCallExpressions, wrapping only the matches
Interleave solve → extract → release per program — dropped, measured at +0.90 GB (see below)
Differential test pinning the raw walk against the wrapper walk it replaces
Byte-identical analysis.json on the fixture apps at -a 4 with every graph selector
Caveats and known risks
This does not make whole-vscode -a 4 fit, and the issue should not be read as claiming it
does. Measured on vscode/src at -a 4, RSS is already 21.33 GB when the interprocedural
phase begins, and the entire retained state of that phase is 0.48 GB:
The remaining ~21 GB is tsc's own parse/bind/check state, which the analyzer does not own. That
rules out restructuring L4 — including per-program or chunked L4, which would additionally lose
every cross-program summary edge and so silently degrade the level rather than chunk it.
Current status of the ceiling, for the record:
run
result
whole vscode -a 3
completes — 19m41s, 28.4 GB, 1.41 GB output
whole vscode -a 4
killed at 28.0-31.7 GB across six configurations
vscode/src-a 4
completes — 11m48s, 27.0 GB
Reaching whole-vscode -a 4 needs #112 Step 3 or 4, not further trimming of this phase.
Risk on the raw walk: it must key call sites identically to the wrapper walk, or edge provenance
shifts. Pinned by a differential test that was mutation-checked (dropping tagged templates, and
dropping the recursion, both fail it).
Risk on the interleave: extraction must be the last reader of a program before release. The root
program is deliberately spared, because finalizeAnalysis still needs it for the config-use
dataflow tier.
Definition of done
bun test green, including the new differential test
analysis.json byte-identical at -a 4 (all graph selectors) on dataflow-app, multi-tsconfig-app and sample-app
vscode/src-a 4 still completes, at a peak no worse than an origin/main run measured
under identical conditions in the same session (do not carry a number over from an
earlier run: the 27.0 GB target originally written here came from a run whose invocation
was not recorded and does not reproduce — origin/main itself measures 29.69 GB here)
The phase-level reductions do not move peak RSS. They are real where measured (call-site
index +3.23 GB to +2.52 GB) but the peak is set later in the run, which the 21.33 GB
pre-wavefront floor above already implied. #142 is in draft pending per-commit attribution.
Per-commit attribution
build
peak RSS
origin/main
29.69 GB
raw walk only
28.75 GB
raw walk + interleave
29.65 GB
The raw walk is worth 0.94 GB. The interleave gives 0.90 GB of it back — releasing source
files does not free the tsc state that dominates, and extracting per program gives up the
batched path's sharing. #142 carries the raw walk only; the interleave stays on perf/interleave-dispose unmerged.
Problem
Two places in the call-graph phase hold more of the AST than the work requires, and both are
measurable on
vscode/src(6,758 files, one program).1.
indexCallExpressionsmaterialises every AST node. It traverses with ts-morph'sforEachDescendant, which wraps each visited node in a JS object and caches it on theSourceFilefor the lifetime of the program. Indexing call sites therefore keeps the entire ASTof every file resident, when only the ~471k call-like nodes are ever used. Measured: +5.09 GB
for the index alone.
2. Every program stays materialised through the whole solve. #137 released programs but ran
the entire solve first, so all 92 of vscode's programs were live before disposal began; #127
interleaved but never released, so they stayed live too. Neither bounded anything on its own —
both measured ~30.6 GB on vscode and were killed.
vscode/srcis ONE program and completes;the whole repo is 92 and does not.
Scope boundary
This is #112 Step 2 ("bound the resident program set") plus the indexing half of ceiling 1.
Not in scope: #112 Step 3 (incremental reuse) or Step 4 (sharding). Explicitly not a fix for
whole-vscode
-a 4— see Caveats.Goals
indexCallExpressions, wrapping only the matchesInterleave solve → extract → release per program— dropped, measured at +0.90 GB (see below)analysis.jsonon the fixture apps at-a 4with every graph selectorCaveats and known risks
This does not make whole-vscode
-a 4fit, and the issue should not be read as claiming itdoes. Measured on
vscode/srcat-a 4, RSS is already 21.33 GB when the interproceduralphase begins, and the entire retained state of that phase is 0.48 GB:
datas(126,236 callables, 991k CFG nodes, 1.3M CFG edges)ddg(1,691,626 edges)summaries(126,236 entries)The remaining ~21 GB is tsc's own parse/bind/check state, which the analyzer does not own. That
rules out restructuring L4 — including per-program or chunked L4, which would additionally lose
every cross-program summary edge and so silently degrade the level rather than chunk it.
Current status of the ceiling, for the record:
-a 3-a 4vscode/src-a 4Reaching whole-vscode
-a 4needs #112 Step 3 or 4, not further trimming of this phase.Risk on the raw walk: it must key call sites identically to the wrapper walk, or edge provenance
shifts. Pinned by a differential test that was mutation-checked (dropping tagged templates, and
dropping the recursion, both fail it).
Risk on the interleave: extraction must be the last reader of a program before release. The root
program is deliberately spared, because
finalizeAnalysisstill needs it for the config-usedataflow tier.
Definition of done
bun testgreen, including the new differential testanalysis.jsonbyte-identical at-a 4(all graph selectors) ondataflow-app,multi-tsconfig-appandsample-appvscode/src-a 4still completes, at a peak no worse than anorigin/mainrun measuredunder identical conditions in the same session (do not carry a number over from an
earlier run: the 27.0 GB target originally written here came from a run whose invocation
was not recorded and does not reproduce —
origin/mainitself measures 29.69 GB here)Measured outcome (correction)
End-to-end A/B on
vscode/src -a 4, same machine and flags:origin/mainThe phase-level reductions do not move peak RSS. They are real where measured (call-site
index +3.23 GB to +2.52 GB) but the peak is set later in the run, which the 21.33 GB
pre-wavefront floor above already implied. #142 is in draft pending per-commit attribution.
Per-commit attribution
origin/mainThe raw walk is worth 0.94 GB. The interleave gives 0.90 GB of it back — releasing source
files does not free the tsc state that dominates, and extracting per program gives up the
batched path's sharing. #142 carries the raw walk only; the interleave stays on
perf/interleave-disposeunmerged.