Skip to content

Reduce binary size: let Visit/VisitMut implementors skip TS type-space (VISIT_TS_TYPES) #24317

Description

@Boshen

Summary

Many Visit / VisitMut implementors only inspect runtime code, yet every impl monomorphizes the TypeScript type-grammar walk_* functions it can reach. Measurement shows this is ~23% of all visitor machinery bytes in oxlint. Proposal: an opt-out associated const on the traits (VISIT_TS_TYPES) that gates the default visit_* methods of pure type-space nodes. A working prototype cut 203 KiB off the oxlint binary with zero behavior change (all 1168 linter tests pass).

Measured baseline

Methodology: release profile exactly as shipped (fat LTO, codegen-units=1, panic=abort) with strip=none and -Csymbol-mangling-version=v0 so each monomorphization is attributable to its instantiating type; cargo-bloat --message-format json for bins, nm address-deltas + rustfilt for the napi cdylibs. arm64 macOS, rustc 1.96.1.

Visitor/traverse machinery as share of .text:

artifact file / .text Visit VisitMut Traverse total
oxlint 17.5 / 11.8 MiB 1.05 MiB (52 impls) 130 KiB — (not linked) ~10%
oxc_transform_napi 4.3 / 3.0 MiB 269 KiB (14 impls) 248 KiB (12 impls) 393 KiB ~30%
oxc_minify_napi 3.0 / 2.0 MiB 135 KiB (5 impls) 0 164 KiB ~15%
oxfmt 8.3 / 5.6 MiB 0 0 0 0% (dep present, no live impls — fully dead-stripped)

Key facts:

  • Marginal cost of one Visit/VisitMut impl is ~10–27 KiB (LTO prunes unreachable walks; only walks reachable from the entry visit_* calls are instantiated). A full-surface visitor (SemanticBuilder) is ~68–80 KiB.
  • oxc_linter has 44 helper visitors ≈ 819 KiB — the largest visitor cost in oxlint.
  • TS-grammar walks (walk_ts_*) are 282 KiB = 23% of oxlint's visitor machinery, remarkably uniform: ~20–24% of every small helper visitor, 18% of SemanticBuilder.
  • A Traverse walk-tree instantiation is ~31–38 KiB; only 5 exist workspace-wide (TransformerImpl, ModuleRunnerTransform, InjectGlobalVariables, and the minifier fork's PeepholeOptimizations, Normalize) — the plugin-dispatch architecture is already good for binary size.
  • oxc_traverse's generated ancestor.rs (18.6k lines) is ~free in the binary (<1 KiB survives dead-stripping) — its cost is compile time, not size.
  • In oxc-transform, oxc_traverse and oxc_minifier's private traverse fork coexist; the duplicated ctx layer costs ~70–100 KiB.

Metadata

Metadata

Assignees

Labels

A-ast-toolsArea - AST toolsA-linterArea - LinterC-performanceCategory - Solution not expected to change functional behavior, only performance

Type

No type

Fields

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions