Skip to content

Trim ~2 MB from the release binary without touching hot paths - #39770

Merged
Jarred-Sumner merged 12 commits into
mainfrom
claude/binary-size-trim
Aug 21, 2026
Merged

Jarred-Sumner merged 12 commits into
mainfrom
claude/binary-size-trim

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Source-only changes (no compiler / linker / profile flags) that make the release bun binary ~2 MB smaller with no measurable change to throughput, startup, or memory.

build before after delta
Linux x64 release (bun run build:release, stripped bun) 76,126,192 74,110,960 −2,015,232
Linux x64 LTO (--profile=btg, i.e. the codegen CI ships) 80,762,648 78,649,112 −2,113,536

How

Nothing here changes an algorithm on a hot path; it is almost entirely "compile this once instead of N times" and "make this a table instead of code":

C++ bindings

  • subspaceForImpl<T>: the create-on-first-use slow path is one out-of-line function keyed by the two subspace member offsets (BUN_SUBSPACE_SLOTS), instead of a full instantiation per wrapper class (~160 classes). The inline fast path is unchanged.
  • Zig::GlobalObject::finishCreation: the 121 LazyProperty/LazyClassStructure initLater lambdas become four offset tables with one shared initializer each (one callFunc instantiation per table instead of per member). Generated lazy class structures likewise share one initializer via an array. The shared initializer tolerates being reached through a different realm's global (defaultGlobalObject).
  • Generated classes (generate-classes.ts): shared structureForNewTarget for the new.target subclass path, shared throwInvalidThisCallError, analyzeHeap driven by an offset/name table (visitChildren stays inline — it is GC-hot), X__getConstructor via one helper, prototypes allocated through one allocatePlainObjectCell.
  • Out-of-line helpers for once-per-class / cold setup code that was inlined at hundreds of sites: Bun::createClassStructure (Structure::create), Bun::reifyStaticPropertyTable, Bun::putToStringTagWithoutTransition, JSDOMConstructorBase::initializeBaseProperties, WebCore::setSubclassStructure, Bun::putDirectNamed (only used in cold object builders: process.report/config/versions, X509 legacy object, perf timing toJSON, …).
  • Bun__deepEquals: the per-type special-object comparisons (typed arrays, Date, RegExp, Error, DOM wrappers, boxed primitives, …) are compiled once and take the mode at runtime; Map/Set and the plain-object tail stay in the specialised template so expect().toEqual on collections is unchanged.
  • Data tables instead of code: process.binding('constants') / node:constants (~850 numeric constants), process.versions, uv errname map, HTTP parser method lists, BunBuiltinNames (array + loop instead of a member per identifier), the 38 fixed-template $ERR_* messages, createInternalModuleById, ExposeNodeModuleGlobals, addBuiltinGlobals private functions.
  • IsoSubspace tables hold owned pointers with one looping destructor instead of ~300 inlined unique_ptr destructors ×2.
  • JSStreamsRuntime internal structures as an array with one initializer.
  • A handful of clearly cold, large functions are compiled with __attribute__((minsize)): CPU/heap profile report generation, process.report.getReport(), process.config, process.dlopen/execve argument handling, $ERR_* construction, X509 legacy object, WebSocket::connect, MIMEParams parsing, jwk key import, RSA keygen option parsing, new Worker() option parsing, Bun.WebView construction, mock.module, uncaught-error formatting.

Rust

  • comptime_string_map!: maps over 64 keys (file extensions, HTML entities, CSS property names, encoding labels, named colours, …) store sorted key data and binary-search per length bucket instead of emitting a compare tree; #[inline] on a map opts back into the tree (used for Method::which).
  • CSS: SizeHandler::flush helper instead of an 18-way macro expansion (40 KB → 1 KB); margin/padding/inset/scroll-margin handlers share one body over a static spec table; PropertyId::from_name_and_prefix was a 248-way eql_case_insensitive chain and is now a length-bucketed table (smaller and faster); Property::parse arms go through one parse_value::<T> per value type; parse_nested_block prologue/epilogue out of line; the parse/parse_bundler at-rule parsers are one type; is_compatible is a [[u32; 9]; 215] table generated by build-prefixes.js; Token is Copy.
  • bun install run_tasks is compiled once behind a small callbacks table instead of once per installer type; PackageInstaller const-generic bools that only gate cold branches are runtime bools. Same for Bun.spawn/spawnSync glue and bun test --coverage reporters.
  • JestPrettyFormat (snapshot serializer) is instantiated for one writer type.
  • lol_html is instantiated for one output-sink type shared by HTMLRewriter and the bundler's HTML scanner (the HTMLRewriter arm is statically dispatched).
  • XML parser: repr(C) field order so the sink-independent DTD methods are byte-identical across sink types and fold under ICF.
  • Markdown HTML-entity table packed into a name blob + offsets + codepoints (was 2125 Entity structs); MIME by_name uses a sorted name blob (generated by mime_type_list.generate.ts from mime_type_list.txt) instead of building a 2300-entry hash map at runtime.
  • Panic-site trimming on very common inlined helpers (CallFrame::arguments_count, opaque_deref, sys::Error::from_code_int), cold error arms out of line in host_fn, Log::add_error*, output::err.
  • Assets that Bun never executes itself — shell completions and the browser-side dev-server/error-overlay bundles (bake.client.js, bake.error.js, bun-error, client.tsx, react-refresh.js) — are embedded zstd-compressed and inflated once on first use (bun_zstd::embed_compressed!). Everything Bun runs or parses at startup (builtin modules, bake.server.js, runtime.js, …) is untouched.

How did you verify your code works?

  • Behaviour: targeted suites pass on the release build (css incl. css.test.ts 1181 tests, workers, streams, expect/jest-extended, node assert, url/urlpattern, x509, vm, serve, node-http, sqlite, napi, plugin, process, shell, install lifecycle scripts, html rewriter, bake, bundler css/minify); outputs compared byte-for-byte against main for bun build of typescript.js and several large stylesheets, bun completions, $ERR_* messages, MIME/extension lookups, markdown entities, process.report, constructor name/length/toStringTag of the Web globals. New tests: test/js/node/errors/error-code-messages.test.ts, Blob type-interning cases in test/js/web/fetch/blob.test.ts.
  • Perf: user-space instruction counts (perf stat -e instructions:u, concurrent GC/JIT off for determinism) vs main on the same machine are within ±0.5% for: startup (-e 1, --version, a small script), new Blob/Event/Request/Headers, Bun.deepEquals (plain/strict/Map), Bun.color, URLPattern.exec, require of 40 builtins, fs stat/read, bun:sqlite, JSON, Buffer, TextDecoder, $ shell, Transpiler, resolve, URL, Bun.inspect, HTMLRewriter (get/set/removeAttribute handlers), full-GC with 100k live wrappers, Bun.serve+fetch loop, spawnSync, test runner (toEqual + snapshots), bun build typescript.js --minify; CSS minification of bootstrap/tailwind is 1–2% fewer instructions.
  • Two things I tried and reverted because they were measurable: table-driven visitChildren for generated classes (+~50 instructions per wrapper per GC) and diverging on OOM inside GlobalAlloc::alloc (pushed RawVec growth out of line at every allocation site).

Notes for reviewers

  • mime_type_list.txt drops 9 entries that were never in the interned table (by_name parity with main is exact; the new blob test pins a few in/out-of-set cases).
  • JsCell moved from bun_jsc to bun_ptr (re-exported, no call-site changes) so the bundler-level HTML sink can name it; the two JsCell<JsRef> forwarding accessors became the JsCellRefExt trait.
  • The dev server now inflates bake.client.js (~100 KB) once on first use instead of pointing at .rodata; RSS-neutral (one heap copy replaces the old NUL-terminated copy).

Jarred-Sumner and others added 11 commits August 20, 2026 09:42
- GlobalAlloc: diverge on failed small allocations so liballoc's
  per-site handle_alloc_error branch folds away
- semver::String: branchless inline length instead of an unrolled scan
- CallFrame/sys::Error/css ParserState: drop checked casts JSC/libc guarantee
- opaque_deref: one shared cold null panic instead of per-site expect
- host_fn: outline the Err arm of every host-call trampoline
- comptime_string_map: only force-inline small maps; case-insensitive
  lookup reuses the exact-match tree
- node_fallbacks: build the table from a const spec array
- css: split parse_nested_block into non-generic enter/exit; avoid
  re-wrapping the result in parse_entirely
- DefineEnumProperty: length-dispatched lowercase lookup instead of an
  eq_ignore_ascii_case chain per variant
- run_tasks: type-erase the callbacks so the 2k-line body compiles once
- Output::err / Log::add_error: keep the generic surface thin
- C++: table-driven BunBuiltinNames, process.binding constants,
  node:constants, process.versions; generated classes visit/analyze
  cached fields from an offset table
…wer monomorphs

- comptime_string_map: maps over 64 keys look up via binary search over
  sorted key data instead of an inlined compare tree
- css compat: Feature::is_compatible reads a per-feature min-version table
  (generator updated to emit it)
- css colors: convert_via! conversions are no longer force-inlined
- MimeType: replace the lazily built 2310-entry runtime hash map with a
  static set of common types; drop the ALL table
- spawn/spawnSync, coverage reporters, install_package_with_name_and_resolution
  take runtime bools instead of const generics
- JestPrettyFormat routes every writer through one adapter type
- C++: createInternalModuleById reads a module table; shared invalid-this
  throw for generated host functions; table-driven HTTP method lists and
  process.binding('uv')
…y table

- Generated lazy class structures live in one array with a shared
  initLater callback that dispatches through a creator table
- Zig::GlobalObject visits its LazyProperty/LazyClassStructure/WriteBarrier
  members from an offset table
- bun_md: pack the HTML entity table (names blob + offsets + codepoints)
  instead of 2125 pointer-carrying structs; bitset boundary checks
- css Token is Copy; console typed-array printing shares one loop
- clippy/fmt cleanups for earlier changes
… table, js2native, runtime source dedup

- Shell completions and browser-only bundles (dev-server client runtime,
  error overlay/page, react-refresh, framework client.tsx) are embedded
  zstd-compressed in release and inflated on first use; nothing Bun itself
  executes or parses is compressed
- jsFunctionMakeErrorWithCode: fixed-text-around-arguments messages come
  from a table instead of a switch case each
- js2native wrappers share one out-of-line JSFunction::create
- bundler runtime source: assemble the per-target variant once instead of
  embedding four concatenated copies of runtime.js
…er HTML passes

lol_html's rewriter/tokenizer was instantiated once per sink closure type
(three copies). Every Bun HtmlRewriter is now built with the same boxed
sink type.
…handler de-duplication

- subspaceForImpl: one out-of-line slow path keyed by subspace member offsets
- Zig::GlobalObject lazy properties/class structures initialised from offset tables
- specialObjectsDequal: per-type comparisons compiled once with a runtime mode
- out-of-line putDirectNamed / createClassStructure / reifyStaticPropertyTable /
  allocatePlainObjectCell / setSubclassStructure / structureForNewTarget helpers
- IsoSubspace tables hold owned pointers with a single looping destructor
- JSStreamsRuntime internal structures as an array
- CSS: SizeHandler::flush helper, margin/padding handler over a static spec,
  PropertyId::from_name_and_prefix via comptime_string_map, Property::parse
  value helper, single at-rule parser type for parse/parse_bundler
- XML parser repr(C) so sink-independent methods fold
…ng parity, cold-path helpers

- MimeType::by_name_static interns exactly the previous MIME set from a
  generated sorted table (mime_type_list.generate.ts + sync test)
- comptime_string_map: #[inline] opt-in for the compare tree (Method::which),
  compare-tree maps stay inlined; only >64-key maps use the sorted table
- keep visitChildren of generated classes inline (GC-hot); analyzeHeap table only
- keep Map/Set deep-equals in the specialised template
- revert the GlobalAlloc OOM fold (it pushed RawVec growth out of line)
- lazy GlobalObject member tables tolerate a foreign owner global
- HTMLRewriter/HTMLScanner share one lol_html sink enum with a statically
  dispatched buffer arm; JsCell moves to bun_ptr (re-exported from bun_jsc)
- one inflated copy of bake.client.js (bundler accessor), no extra NUL copy
- process.binding constants tables end with a sentinel (Windows dlopen table)
- minsize on a few cold, large diagnostic/setup functions; table-driven
  ExposeNodeModuleGlobals / addBuiltinGlobals private functions / getConstructor
- drop dead installer downcast trait methods, EmbedKind variants, stray file
- tests: table-driven ERR_* messages, Blob type interning
No-Verification-Needed: lint inventory only
… baseline the SizeSpec table finding

No-Verification-Needed: lint-only changes (clippy not_unsafe_ptr_arg_deref shape, mordant baseline, named locals at one call site)
@robobun

robobun commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator
Updated 4:50 PM PT - Aug 20th, 2026

✅ @Jarred-Sumner, your commit ddb8d72fab23534c8fdbc9f8a6cee18689a15d7c passed in Build #101885! 🎉


🧪   To try this PR locally:

bunx bun-pr 39770

That installs a local version of the PR into your bun-39770 executable, so you can run:

bun-39770 --bun

@claude claude Bot 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.

I reviewed this PR and the automated bug-hunting pass found no issues. Given the scope — 300 files spanning GC iso-subspace ownership, offset-driven subspaceForImpl/lazy-class-structure initialization in ZigGlobalObject, generated-class codegen, and type-erased run_tasks with raw-pointer context casts — a human review is still warranted.

Checked: the new subspaceForImplSlow offset-based slot access preserves the destructible/custom heap-cell-type selection and output-constraint registration; the DOMIsoSubspaces raw-pointer table's looping destructor covers every slot; BundlerAtRuleParser with track_layers_and_imports=false matches the removed DefaultAtRuleParser no-op behaviour; ErasedCallbacks pointer casts are gated on the same IS_*_INSTALLER flags that previously selected the identity-cast trait methods.

Extended reasoning...

Overview

This PR reduces the release binary by ~2 MB via source-only de-monomorphization and table-ification across ~300 files: C++ JSC bindings (subspaceForImpl, ZigGlobalObject::finishCreation lazy initializers, generated-class codegen, Bun__deepEquals, BunBuiltinNames, ProcessBindingConstants, IsoSubspace ownership), Rust (comptime_string_map! sorted-blob lookup for large maps, CSS SizeHandler/is_compatible/PropertyId table-ification, run_tasks type erasure, PackageInstaller const-generic → runtime bools, single lol_html sink type, MIME/entity blob tables), and zstd-compressed embedding of browser-only assets.

Security risks

No new attack surface. Changes are internal codegen/compilation-shape refactors. The offset-based member access in BUN_SUBSPACE_SLOTS and GeneratedCachedField is bounded by compile-time OBJECT_OFFSETOF on known struct members. The ErasedCallbacks raw-pointer casts in run_tasks recover the same &mut C::Ctx that was erased at the single call site and are gated on the same const flags as before.

Level of scrutiny

High. This touches GC-critical paths: IsoSubspace allocation/ownership (now raw pointers freed by a loop over the struct reinterpreted as a pointer array — relies on DOMIsoSubspaces containing nothing but pointer members), the shared initLater callback that recovers its class index by pointer arithmetic against m_generatedLazyClasses, and subspaceForImpl's fast-path load. Each of these is a place where a subtle mismatch (a non-pointer field added to the subspace tables, a lazy-class accessor called with an owner from the wrong realm) would be a memory-safety bug. The PR description is thorough and the perf/behaviour verification is extensive, but the design choices (offset tables vs. per-class lambdas, raw-pointer subspace slots, __attribute__((minsize)) placement) deserve maintainer sign-off.

Other factors

No prior reviews on the timeline. New tests cover * messages and Blob MIME interning; the large-map comptime_string_map path has a unit test. The author documents two approaches that were tried and reverted for measurable regressions, which suggests careful validation. Nonetheless, 300 files across bindings/codegen/GC is well beyond the auto-approval threshold.

No-Verification-Needed: storage type only; cfg(bun_codegen_embed) arm checked

@claude claude Bot 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.

I reviewed this PR and the automated bug-hunting pass didn't surface any issues. Given the scope — 300 files spanning JSC GC subspace ownership, generated-class initialization, ZigGlobalObject::finishCreation, type-erased run_tasks with new *mut () casts, IsoSubspace lifetime changes, and the JsCell crate move — a maintainer should still look this over before it lands.

Checked: the shared LazyClassStructure initializer's index recovery via pointer arithmetic and its defaultGlobalObject fallback; subspaceForImplSlow preserves the destructible/cell/custom heap-cell-type selection and output-constraint registration; the DOMIsoSubspaces looping destructor covers every slot; ErasedCallbacks cast targets match the two IS_*_INSTALLER flags; BundlerAtRuleParser with track_layers_and_imports=false never dereferences the null import_records pointer.

Extended reasoning...

Overview

This PR is a broad binary-size reduction touching ~300 files across nearly every subsystem: JSC C++ bindings (subspace allocation, ZigGlobalObject::finishCreation, generated classes, IsoSubspace ownership), Rust code generation (comptime_string_map!, CSS property handlers, run_tasks type erasure), embedded assets (zstd-compressed browser bundles), and dozens of mechanical putDirect → putDirectNamed / Structure::create → createClassStructure rewrites. The stated goal is to compile shared logic once instead of N times and replace code with data tables, without changing hot-path behaviour.

Security risks

No new attack surface is introduced — this is internal refactoring. The main risk category is memory safety rather than security per se: the IsoSubspace tables now hold raw owned pointers freed by a size-based loop in a hand-written destructor; run_tasks erases &mut C::Ctx to *mut () and casts back via is_package_installer/is_store_installer flags; the shared LazyClassStructure initializer recovers its array index by subtracting a member pointer from the global's array base. None of these looked wrong on inspection, but they are exactly the kind of change where a subtle mismatch (a non-pointer field added to a subspace table, a callbacks flag set on the wrong impl) would be a UAF or wild write.

Level of scrutiny

High. This is neither small nor mechanical: it changes GC subspace lifetime management, rewrites how ~160 wrapper classes get their structures/prototypes/constructors initialized, moves JsCell between crates, introduces new unsafe blocks in the installer, and adds __attribute__((minsize)) to functions across crypto, WebSocket, worker construction, and error formatting. The PR description is thorough and the perf/behaviour verification is extensive, but the blast radius covers essentially every JS-visible class in the runtime.

Other factors

The bug-hunting system found nothing, and the diff is internally consistent where I spot-checked (the BundlerAtRuleParser unification guards every layer/import hook on track_layers_and_imports so the null import_records pointer in the plain parse path is never dereferenced; subspaceForImplSlow preserves the outputConstraintSpaces append; the deleteSubspaceTable static_assert enforces the all-pointers layout it depends on). But per the approval guidelines this is squarely in "large, touches critical code paths, contains design decisions a maintainer should weigh in on" territory — the choice to hold IsoSubspaces as raw pointers with a reinterpret-cast destructor loop, and the pointer-arithmetic index recovery in the shared lazy-class initializer, are patterns a human reviewer should explicitly sign off on.

Jarred-Sumner pushed a commit that referenced this pull request Aug 21, 2026
…e construction (#39833)

Follow-up to #39770, which moved the IsoSubspace creation path out of
`WebCore::subspaceForImpl` into one out-of-line `subspaceForImplSlow`.
#39503 did the same thing and is closed as superseded. These are the two
pieces of it that still apply.

### Problem
- `.claude/skills/implementing-jsc-classes-cpp/SKILL.md:30-35` still
shows the four-lambda `subspaceForImpl` call. That signature no longer
exists, so the snippet does not compile when copied.
- Nothing stops a new class, or a codegen template, from constructing
its own `IsoSubspace` again. The compiler does not enforce that
invariant, and a template that does it pays for it once per generated
class.

### Fix
- The skill snippet now shows the `BUN_SUBSPACE_SLOTS` form that every
caller on main uses (for example
`src/jsc/bindings/AsyncContextFrame.h:39`), and says where the subspaces
are created.
- `test/internal/source-lints/iso-subspace-creation.test.ts` scans
`src/**/*.{h,cpp}` and `src/codegen/**/*.ts` for `ISO_SUBSPACE_INIT`,
`makeUnique<IsoSubspace>` or `new IsoSubspace` outside
`src/jsc/bindings/BunClientData.cpp`. It also asserts that file still
constructs one, so the lint cannot pass on an empty scan.
- `source-lints.yml` now also triggers on `src/**/*.h`, `src/**/*.cpp`
and `src/codegen/**`, the files this lint reads (the README in that
directory asks for this). `src/codegen/**` replaces the narrower
`src/codegen/class-definitions.ts` entry.
- Verified: the lint passes on main (`bun bd test` and the released
bun). It fails on the tree before #39770, listing the four constructions
in `BunClientData.h`, and it fails when a construction is added to a
header or to `generate-jssink.ts`, naming the file and line.

### Background
- Every JS class with C++ fields has a `subspaceFor<T>` hook that JSC
calls on each allocation of `T`. The creation of the subspace behind it
(lock, construct, register, fill the two slots) is about 640 bytes of
code and runs once per type. Inlined per class it cost about 160 KB of
the linux-x64 binary, which is what #39770 removed.
- `test/internal/source-lints/` holds grep-style lints over `src/`. They
run on GitHub Actions against a released bun and are excluded from the
Buildkite shards, so they only run on PRs that touch the paths listed in
the workflow.

<!-- robobun:evidence:begin -->

---

**[stamp-90s]** gate passed · iteration 1 · 3 files touched

<details><summary>passes on PR (with fix)</summary>

```console
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/internal/source-lints/iso-subspace-creation.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/internal/source-lints/iso-subspace-creation.test.ts
bun test v1.4.0 (6e906e4)

test/internal/source-lints/iso-subspace-creation.test.ts:
(pass) IsoSubspaces are only constructed in BunClientData.cpp [1035.72ms]

 1 pass
 0 fail
 3 expect() calls
Ran 1 test across 1 file. [4.46s]
Exit: 0
```

</details>

<details><summary>diff hotspot</summary>

```
.../skills/implementing-jsc-classes-cpp/SKILL.md   |  8 +--
 .github/workflows/source-lints.yml                 |  8 ++-
 .../source-lints/iso-subspace-creation.test.ts     | 67 ++++++++++++++++++++++
 3 files changed, 76 insertions(+), 7 deletions(-)
```

</details>

**gate history** · 1 passed · 0 rejected · iteration 1

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
.claude/skills/implementing-jsc-classes-cpp/SKILL.md          2      2      0
.github/workflows/source-lints.yml                            1      1      0
test/internal/source-lints/iso-subspace-creation.test.ts      4      8      0
```

</details>

<!-- robobun:evidence:end -->
robobun added a commit that referenced this pull request Aug 21, 2026
#39795 removed four Default impls this branch had converted (Query,
ClassStaticBlock, NamedImport and the MetadataResolve one), so they are gone.
#39770 split add_error, add_range_error_with_notes and range_data into an
inline generic wrapper and one shared body; here the wrapper also does the
Option<Loc> / Option<Range> conversion and the body takes the Option. Four
diagnostics main added without a location (no bundlable entry point, external
entry point, and the two builtin / browser-field entry point errors) pass None.

main itself does not build at this point (PackageInstaller.rs:1359, #39816
against #39770); that is tracked separately and not touched here.
Jarred-Sumner pushed a commit that referenced this pull request Aug 21, 2026
…uilt-in JS, bindgen, uSockets, and 66 Cargo manifests (#39732)

### Problem
- The tree carries code that nothing references: FFI shims with no
caller on either side, enum variants that are never constructed,
write-only fields, exports that no module imports, and Cargo dependency
edges that no source file uses.
- 16 dead-code PRs that removed much of this were closed as stale after
merge conflicts. Their deletions were never re-applied (list in the
notes).

### Fix
- Re-apply the deletions that still apply to current main and add new
ones in the same areas: `simdutf_sys`, `ncrypto` and the WebCore
bindings, `js_parser`, `bundler`, `js_printer`, `bun_install`, built-in
JS, bindgen, uSockets, and 66 Cargo manifests. 176 files, +95 / -3129
(`Cargo.lock` is -436 of that).
- Every deletion was checked again with `rg` over `src/`, `packages/`,
`src/codegen/` and the generated code. Items that became live again
since the old PRs stay (examples in the notes).
- No deletion duplicates an open dead-code PR. Deletions that need
#39618 or #39697 to land first are listed as follow-ups in the notes.
- Verified: `bun bd`, `bun run rust:check-all` (12 targets pass),
byte-identical bindgen output, `test/internal/source-lints`, and the
test files of the touched areas (list in the notes). New tests in
`transpiler.test.js` and `bundler_edgecase.test.ts` pin the two
diagnostics that `mark_strict_mode_feature` still emits and the class
body printing that `visit_class` still does.

### Background
- A Rust `extern "C"` declaration with no call site creates no link
reference, so a dead FFI chain is removed on both sides at once. Three
C++ definitions whose Rust side #39618 removes stay until that PR lands.
- `cargo check` covers the host target only. `rust:check-all` repeats it
for every shipped target, which proves the `#[cfg]`-gated deletions and
the Windows-only dependency removal (`bun_sys` -> `bun_output`).
- The dependency removals are manifest-only. `Cargo.lock` loses the
matching entries and nothing else: no version changes.

<details><summary>Notes</summary>

**Closed PRs re-applied**: #35437, #35559, #35775, #35880, #36115,
#36237, #37012, #37062, #37089, #37208, #37272, #37454, #37788, #38005,
#38439, #38703.

**Kept because they are live again on main**:
`GenericIndexOptional::{get, is_some, is_none}`, the WebSocket deflate
`OutOfMemory` variants, `V8Local::reinterpret`, `SystemErrno::MAX`,
`MarkPopErrorOnReturn::peekError`,
`ResourceTiming::populateServerTiming`, `UvHandle` in
`test/parallel/Channel.rs`, the `h2::FrameType` entries that `hawk.toml`
marks as a code table.

**Tests run**: node-http, node-http2, fetch headers, streams,
readable-stream-blob-consumed, filesink, transpiler, buffer, url,
FormData, TextEncoder, MessageChannel, serve-direct-readable-stream,
serve-body-leak, crypto key objects, crypto-rsa, scrypt, pbkdf2,
sqlite-sql, local-sql, postgres-simple-query-pipeline,
sql-helpers-validation, bun-outdated, websocket-server, test/internal
(bindgen, codegen outputs, source lints). The `localhost` proxy test in
node-http and the concurrent WebSocket send tests fail the same way on
unmodified main in this environment (the first is a `localhost`
resolution issue, the second is the 300k-message benchmark starving its
concurrent neighbours in a debug build).

**By kind**: C/C++ -1327, Rust -702 (+75, mostly signature updates at
call sites), built-in JS/TS -130, codegen TS -75 (+13), Cargo manifests
-459, `Cargo.lock` -436.

**simdutf_sys** (`simdutf.rs`, `bun-simdutf.cpp`,
`parsers/benches/support/simdutf_shim.cpp`): 16 shim chains with no
caller, each removed as Rust wrapper + `extern` declaration + C++
definition: `simdutf__convert_utf8_to_utf16le`, `_utf16be`,
`_utf16be_with_errors`, `convert_utf8_to_utf32_with_errors`,
`convert_valid_utf8_to_utf32`, `convert_utf16be_to_utf8_with_errors`,
`convert_valid_utf16be_to_utf8`, `convert_utf32_to_utf8_with_errors`,
`convert_valid_utf32_to_utf8`, `convert_utf32_to_utf16be_with_errors`,
`convert_valid_utf32_to_utf16be`,
`convert_utf16be_to_utf32_with_errors`,
`convert_valid_utf16be_to_utf32`, `utf8_length_from_utf16be`,
`utf32_length_from_utf16be`, `utf32_length_from_utf8`, plus the now
empty `utf32` modules and the `be` wrappers.

**ncrypto** (`ncrypto.h/.cpp`): `BignumPointer::isOne`,
`X509View::ifRsa`, `X509View::ifEc`, `BIOPointer::NewFp`,
`checkScryptParams`, `scrypt`, `pbkdf2`,
`EVPKeyCtxPointer::setRsaMgf1Md`, `Rsa::encrypt`, `Rsa::decrypt` and the
`RSA_Cipher` template, `Cipher::ForEach` with
`CipherCallbackContext`/`array_push_back`, `NCRYPTO_REQUIRE`,
`NCRYPTO_VERSION` and the version enum.

**JSC / WebCore bindings**: `ZigGlobalObject`:
`functionFulfillModuleSync` (and the `fulfillModuleSync` builtin name
plus `$fulfillModuleSync` stub), `JSDOMFileConstructor_getter/_setter`,
`navigatorObject`, `functionLazyNavigatorGetter`,
`GlobalObject_getPerformanceObject`, `hasNapiFinalizers`,
`jsFunctionNotImplemented`,
`jsFunctionCreateFunctionThatMasqueradesAsUndefined`,
`Zig__GlobalObject__getModuleRegistryMap`/`resetModuleRegistryMap`,
`NodeVM*ModulePrototype()` accessors, `ZIG_GLOBAL_OBJECT_DEFINED`.
`BunString.cpp`: `Bun__WTFStringImpl__ref`/`deref` definitions (Rust
inlines these; #39618 removes the declarations). `JSBuffer.cpp`: the
`JSValue`-name `validateOffset` overload and the three unused
`jsBufferConstructorAlloc*WithoutTypeChecks` JIT operations.
`NodeValidator`: `validateString(JSValue name)` and
`validateOneOf(span<ASCIILiteral>)`. `ScriptExecutionContext`:
`ensureOnMainThread`, `executionContext`. `napi.h`: `hasFinalizers`,
`currentFinalizer`, `isVMTerminating`. `IDLTypes.h`: `IDLDate`, the
`NullableTypeWithLessPadding` helpers and two includes.
`BunProcess.cpp`: three unused `*CodeGenerator` aliases.
`c-bindings.cpp`: `HNS_PER_SEC`, `NS_PER_HNS`, `HNS_PER_US`.
`BunCommonStrings.h`: `ConnectionWasClosed`, `ec`, `ed25519`, `rsa`,
`rsaPss`, `jwkDsa`, `jwkG`, `systemError`, `x25519`.
`BakeAdditionsToGlobalObject.h`: the never-read
`m_bakeGetAsyncLocalStorage` lazy property (the function is still
installed directly) and the `LazyPropertyOfGlobalObject` alias.
`JSBundlerPlugin.cpp`: the `JSBundlerPlugin__onVirtualModulePlugin`
declaration, which has no definition. WebCore:
`DeferredPromise::whenSettled` and the
`PromiseFunction`/`BindingPromiseFunction` adapters,
`JSEventListener::sourceURL/sourcePosition`, `Event::receivedTarget`,
`toJS(PerformanceObserverCallback)` and `callbackData()`,
`jsFetchHeaders_getRawKeys` (its only caller in `internal/http.ts` is
removed too), stale forward declarations in
`Performance.h`/`ResourceTiming.h`, and the commented-out
`BINDING_INTEGRITY` blocks in 8 generated-style files. `node/crypto`:
`JSPrivateKeyObjectConstructor` and `JSPublicKeyObjectConstructor` (4
files, superseded by `JSKeyObjectConstructor`). Bake:
`BakeRegisterProductionChunk`, `BakeProdSourceMap`, `BakeProduction.h`,
the `IncrementalGraph` log scope.

**uSockets**: `us_poll_ext`, `us_loop_iteration_number`,
`us_socket_is_tls`, `us_connecting_socket_get_loop`,
`us_udp_packet_buffer_local_ip` / `bsd_udp_packet_buffer_local_ip`.

**Rust**: `js_parser`: the six `StrictModeFeature` variants that are
never constructed (and the `can_be_transformed` branch),
`FnOnlyDataVisit::{class_name_ref,
should_replace_this_with_class_name_ref, is_inside_async_arrow_fn}` with
the `this` substitution path that was gated on the always-false flag
(the `shadow_ref` arena cell becomes a plain `Ref`). `bundler`:
`Linker::{resolver, hashed_filenames}`, `IS_CACHE_ENABLED`,
`InputFileFlags::IS_PLUGIN_FILE`, `parse_task::Step::ReadFile`.
`js_printer`: the write-only `Options::transform_only`. `bun_install`:
`CacheBehavior`/`ManifestLoad` (every caller passed
`LoadFromMemoryFallbackToDisk`, so the parameter and the memory-only
branch are gone from `by_name`, `by_name_hash` and
`by_name_hash_allow_expired`), `pub use patch_install as patch`.
`webcore`: `ReadableStream::detach_if_possible` (empty) and the `global`
parameter of `done()`, `BlobExt::{on_structured_clone_transfer,
get_mime_type}`, six `StartTag` variants that no sink uses. `server`:
`AnyRoute::ref_`, the write-only `OPENED_BIT`. `bun_core`: `concat`,
`ExternalShared::as_ptr`, `QuoteEscapeFormatFlags::ascii_only`.
`bun_io`: stale `Waker`/`Closer` re-exports. `bun_sys`: `UTIME_OMIT`.
`cli`: the never-read `IS_MAIN_THREAD` thread local. `css`:
`DeclarationContext::Keyframes`, the empty `generated_color_conversions`
module. `html_rewriter`: the `EndTag.replace` host function that
`html_rewriter.classes.ts` does not expose.

**Built-in JS/TS**: `internal/http.ts`: 29 unused symbol constants,
`filterEnvForProxies`, `getRawKeys`, `emitCloseNTAndComplete`,
`ClientRequestEmitState`. `node/http2.ts`: `kSettingNames`, three unused
primordials. `internal/sql/query.ts` and `internal/repl/node-shims.js`:
export entries nothing imports, and the `BuiltinModule` shim methods
nothing calls. `builtins.d.ts`: 8 stubs for builtin names that no longer
exist.

**bindgen** (`src/codegen/bindgen*.ts`): `allFunctions`,
`ArgStrategyChildItem`, `Variant.argStruct`,
`Struct.namespace`/`toString`, `FuncMetadata`/`exposedOn`/`ExposedOn`,
`FuncWithoutOverloads`, the dead `debug` binding, two shadowed duplicate
`case` labels and an unreachable `return`. Generated output is
byte-identical.

**Cargo**: 459 dependency lines across 66 manifests (mostly the
`strum`/`bstr`/`scopeguard`/`const_format`/`enum-map`/`enumset`/`libc`/`bitflags`
boilerplate block, plus 94 `bun_*` edges such as `bun_jsc ->
bun_simdutf_sys` and `bun_bundler_jsc -> 8 crates`). One dev-dependency
(`bun_router -> bun_js_parser`, checked with `cargo check -p bun_router
--tests`). The manifests that #39618 and #39697 already edit
(`collections`, `io`, `paths`, `css`, `shell_parser`, `sql`, `sql_jsc`)
were left alone.

**Rebase note**: main restructured the private builtin function
registration in `ZigGlobalObject::addBuiltinGlobals` into a table
(#39770). The conflict was resolved by dropping the
`k_fulfillModuleSync` row from the new table, which is the same
registration the first version of this PR removed. #39770 also touched
the two `JS*KeyObjectConstructor.h` files before this PR deletes them;
they are still unreferenced on main, so the deletion stands. Re-verified
after the rebase: `bun bd`, `rust:check-all` 12/12, and the test files
listed above.

**Follow-ups once open PRs land** (not done here to avoid duplicating
them): after #39618: the C++ definitions of `URL__fromJS`
(BunString.cpp) and `Bun__allocUint8ArrayForCopy` (ZigGlobalObject.cpp),
the seven `<Sink>()` constructor accessors in `ZigGlobalObject.h` that
only the generated `__getter` functions use, the Rust `extern`
declarations of `Bun__WTFStringImpl__ref/deref`, and
`BufferWriter::append_null_byte` (no writer ever sets it to true). After
#39697: the root `[workspace.dependencies] typed-arena` entry.
Independently of those: the `DeferredPromise::{promise, resolve(),
reject(...)}` overloads and `DOMPromise::whenPromiseIsSettled` have no
callers but sit next to code #39618 edits.
</details>

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 1 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/bundler/bundler_edgecase.test.ts

<!-- robobun:evidence:end -->
robobun added a commit that referenced this pull request Sep 18, 2026
…-physical

Conflict in src/css/properties/margin_padding.rs: #39770 moved the
SizeHandler body into the non-generic SizeHandlerImpl that reads a SizeSpec
table. The changes from this branch are ported onto it (S::X becomes spec.x,
spec is passed to compiles_logical, flush_before_unparsed and
flush_compiled_inline). No logic change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants