Skip to content

Early-read var analysis repeatedly rescans large bundles for named function references #1797

Open

Description

@MathurAditya724

We are experimenting with compiling the complete Sentry CLI with scriptc without removing functionality, then benchmarking it against the existing Node SEA build. Its large dependency bundle exposed repeated whole-file work in early-read var analysis.

Observed behavior

earlyReadVarSymbols already builds a symbol-keyed reference index for a source file. However, ScopeAnalysis.referencesOf calls referencesByName again for each named function it analyzes. A bundle with many function declarations can therefore trigger repeated traversal of the same AST.

A reduced call-chain workload has this shape, scaled to 1,000 links:

var value = 7;
function f0() { return value; }
function f1() { return f0(); }
function f2() { return f1(); }
// Continue the chain through f1000.
f1000();

In three local fresh-program runs on Darwin arm64 with Node 24.22.0, the early-read analysis alone took approximately 961–1,010 ms before the change and 15–16 ms after it. An unused-functions control remained around 15–17 ms in both versions. The baseline was 18400d0, compared with PR head 17fa1a4. These are analysis microbenchmarks, not full compilation or CLI runtime measurements.

Expected behavior and reproduction

Whole-file reference scans should not grow with the number of named functions. Reuse must preserve binding identity, shadowing, duplicate declarations, shorthand references, and separate immutable source-file snapshots.

The regression tests in #1577 compare whole-file scan counts for 12- and 48-function chains and cover those binding boundaries. From a prepared checkout containing the tests:

pnpm exec vitest run packages/compiler/src/frontend/lowering/var-hoisting.test.ts

Proposed fix and scope

#1577 includes function names when building the existing per-file index and passes that index into ScopeAnalysis. Function references are retrieved by symbol rather than by another file traversal. It preserves the existing settlement rules.

Local baseline/candidate checks returned identical early-read results for 30 explicit and 150 generated cases. The 32-workload artifact comparison and compiler self-host LLVM comparison were also byte-identical. The complete CLI remains blocked elsewhere; whole-compiler timing and full CLI benchmarks are still outstanding.

Current status

Main now contains an alternative fix in 70aed0f13: it collects identifiers for function declarations in one lazy per-file name index and resolves the relevant references by symbol. PR #1577 preserves that implementation exactly and now contributes the four regression tests described above. The original timing comparison documents the investigation; it is not a new performance gain attributable to the current test-only PR.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions