Skip to content

Remove dead code from the FFI shim layer, link_interface tables, jssink/jest codegen, and REPL shims - #37788

Open
robobun wants to merge 3 commits into
mainfrom
claude/farm/78cfe692/dead-code-ffi-shims-dispatch
Open

Remove dead code from the FFI shim layer, link_interface tables, jssink/jest codegen, and REPL shims#37788
robobun wants to merge 3 commits into
mainfrom
claude/farm/78cfe692/dead-code-ffi-shims-dispatch

Conversation

@robobun

@robobun robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • Nothing user-visible is broken; this is a cleanup. About 1,200 lines of functions that nothing references had piled up in the Rust/C++ FFI shim layer, the link_interface! tables, a few #[no_mangle] Rust exports, two codegen surfaces, and the REPL shims.
  • They survive because each side of the FFI boundary only sees a declaration of the other, so a shim still compiles cleanly after its last caller on the other side is gone. The linker was already discarding all of them, so the shipped binary never contained this code.
  • Some entries in headers.h and the C++ sources declare functions that no longer exist anywhere, such as the 13 Reader__*__fastpath / FFI__ptr__fastpath DOMJIT entry points.

Fix

  • Deletes each function together with its declaration on the other side of the boundary and its generated wrappers; where the only caller was itself unreferenced, the chain is deleted from the root. generate-jssink.ts stops emitting a per-sink getter that was installed for no sink, and the 7 noConstructor jest matcher classes drop call: true.
  • Property to check: every removed function was both on the linker's discard list (--gc-sections --print-gc-sections) and textually unreferenced across src/, packages/, scripts/ and regenerated codegen. Anything referenced only from another platform, a debugger entry point, or an open PR was left alone (listed under "Left alone on purpose" in the original).
  • Two knock-on edits: Bun.deepEquals(a, b) was getting its Bun__deepEquals<false, false, false, false> instantiation from a deleted shim body, so it gets an explicit one (the first build caught this); the Windows SystemErrno::MAX becomes #[cfg(test)] because its last non-test reader went away (rust:check-all on the Windows targets caught this).
  • Verification: a new source-lint test pins every removed symbol; its 7 tests fail on main and pass here. The full debug build, rust:check-all on all 10 targets, and the runtime tests over the touched areas pass. Two tests timed out locally on a heavily loaded box; both are in paths this diff only removes unreferenced exports from, so CI is expected to pass them.

Background

  • FFI shim layer: bun's runtime is Rust plus C++ around JavaScriptCore. Rust calls C++ through extern "C" shims in bindings.cpp, declared for Rust in headers.h (the [[ZIG_EXPORT]] ones also get generated cppbind wrappers); C++ calls Rust through #[no_mangle] exports. Neither compiler can tell when the other side stopped calling something; only the linker can.
  • bun_dispatch::link_interface!: declares a table of methods a lower-level crate can call on a higher-level crate it cannot depend on directly; a link_impl_*! block in the higher crate supplies the arms. Removing a method means removing the table entry and its arm together.
  • --gc-sections: with each function in its own section, the linker drops every section nothing references, and --print-gc-sections lists what it dropped. That list is what "not in the shipped binary" means above.
  • Codegen surfaces: generate-jssink.ts emits the C++ wrapper class for each of the 8 sink types (ArrayBufferSink and friends); jest.classes.ts is the input to the class-binding generator, where call: true emits a <Class>Class__call thunk for the class's constructor function.
  • Explicit template instantiation: a C++ template body is only compiled for the parameter sets used in that file. Deleting the last in-file use removes the instantiation, so a use from another file then needs an explicit template ...; line or the link fails.

[review] gate passed · iteration 0 · 44 files touched

fails on main (without fix)
ASAN without fix: 7 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-ffi-shims-dispatch.test.ts
bun test v1.4.0 (24c6dd585)

test/internal/source-lints/dead-symbols-ffi-shims-dispatch.test.ts:
127 |   // Only these shims used it.
128 |   checks.push(["src/jsc/bindings/helpers.h", /\btoJSString\(/]);
129 |   // Rust callers of the two shims above that were themselves unreferenced.
130 |   checks.push(["src/jsc/bun_string_jsc.rs", /JSC__createRangeError|fn to_range_error_instance/]);
131 |   checks.push(["src/jsc/FetchHeaders.rs", /WebCore__FetchHeaders__createValue\b/]);
132 |   expect(resurrected(checks)).toEqual([]);
                                    ^
error: expect(received).toEqual(expected)

- []
+ [
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isCell\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isNull\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isUndefined\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isUndefinedOrNull\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isNumber\s*\(",
+   "src/jsc/bin
... (truncated)

release without fix: 7 FAILED
bun test v1.4.0-canary.1 (da3851e57)

test/internal/source-lints/dead-symbols-ffi-shims-dispatch.test.ts:
127 |   // Only these shims used it.
128 |   checks.push(["src/jsc/bindings/helpers.h", /\btoJSString\(/]);
129 |   // Rust callers of the two shims above that were themselves unreferenced.
130 |   checks.push(["src/jsc/bun_string_jsc.rs", /JSC__createRangeError|fn to_range_error_instance/]);
131 |   checks.push(["src/jsc/FetchHeaders.rs", /WebCore__FetchHeaders__createValue\b/]);
132 |   expect(resurrected(checks)).toEqual([]);
                                    ^
error: expect(received).toEqual(expected)

- []
+ [
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isCell\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isNull\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isUndefined\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isUndefinedOrNull\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isNumber\s*\(",
+   "src/jsc/bindings/headers.h: \bJSC__JSValue__isNumber\s*\(",
+   "src/jsc/bindings/bindings.cpp: \bJSC__JSValue__isObject\s*\(",
+   "src/jsc/bindings/headers.h: \bJSC__JSValue__isObject\s*\(",
+   "src/
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-ffi-shims-dispatch.test.ts
bun test v1.4.0 (24c6dd585)

test/internal/source-lints/dead-symbols-ffi-shims-dispatch.test.ts:
(pass) C-ABI shims in bindings.cpp that no Rust code calls stay deleted [159.56ms]
(pass) declarations in headers.h for functions that no longer exist stay deleted [11.66ms]
(pass) Rust exports nothing in C++ calls stay deleted [34.89ms]
(pass) link_interface! methods nothing dispatched through stay deleted [13.37ms]
(pass) dead C++ helpers on the global object stay deleted [17.27ms]
(pass) REPL shim exports nothing destructures stay deleted [5.96ms]
(pass) codegen no longer emits functions nothing references [9.71ms]

 7 pass
 0 fail
 8 expect() calls
Ran 7 tests across 1 file. [2.65s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 956ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/661] cc obj/vendor/tinycc/tccgen.c.o
[2/661] cc obj/vendor/tinycc/tccasm.c.o
[3/661] cc obj/vendor/tinycc/tccdbg.c.o
[4/661] cc obj/vendor/tinycc/tccelf.c.o
[5/661] fetch mimalloc
[mimalloc] up to date
[6/660] fetch lshpack
[lshpack] up to date
[7/658] cc obj/vendor/tinycc/x86_64-link.c.o
[8/658] cc obj/vendor/tinycc/x86_64-gen.c.o
[9/658] fetch lsqpack
[lsqpack] up to date
[10/658] cc obj/vendor/tinycc/i386-asm.c.o
[11/658] fetch boringssl
[boringssl] up to date
[12/236] cc obj/vendor/tinycc/tccrun.c.o
[13/236] gen bake.{client,server,error}.js
-> bake.client.js, bake.server.js, bake.error.js
[14/236] fetch lsquic
[lsquic] up to date
[15/167] gen NodeModuleModule.lut.h
Generating /workspace/bun/build/release/codegen/NodeModuleModule.lut.h from /workspace/bun/src/jsc/modules/NodeModuleModule.cpp
[16/167] fetch lolhtml
[lolhtml] up to date
[17/167] gen generated_host_exports.rs
generated_host_exports.rs: 91 exports (host=3, lazy=10, generic=78, rust=0); 239 extern-C blocks audited
[18/167] gen cpp.rs (cp
... (truncated)
diff hotspot
src/ast/transpiler_cache.rs                        |   8 +-
 src/bun_alloc/lib.rs                               |  11 +-
 src/bun_core/lib.rs                                |   4 -
 src/bun_core/string/mod.rs                         |   2 -
 src/bun_core/util.rs                               |   2 -
 src/codegen/generate-jssink.ts                     |   9 -
 src/errno/lib.rs                                   |  61 +--
 src/errno/windows_errno.rs                         |   2 +
 src/event_loop/lib.rs                              |   5 -
 src/http_jsc/websocket_client.rs                   |  46 +-
 src/js/internal/repl/node-inspect.js               |  10 +-
 src/js/internal/repl/node-shims.js                 |  11 -
 src/jsc/ConsoleObject.rs                           |  51 +-
 src/jsc/FetchHeaders.rs                            |  19 -
 src/jsc/JSBigInt.rs                                |  17 -
 src/jsc/JSCScheduler.rs                            |  14 -
 src/jsc/JSValue.rs                                 |   2 +-
 src/jsc/RuntimeTranspilerCache.rs                  |   1 -
 src/jsc/ZigException.rs                            |   5 -
 src/jsc/array_buffer.rs                            |   5 -
 src/jsc/bindings/BunDebugger.cpp                   |   3 -
 src/jsc/bindings/BunString.cpp                     |   8 -
 src/jsc/bindings/JSBigIntBinding.cpp               |  33 --
 src/jsc/bindings/JSEnvironmentVariableMap.cpp      |   1 -
 src/jsc/bindings/ScriptExecutionContext.h          |   9 -
 src/jsc/bindings/ZigGlobalObject.cpp               |  49 --
 src/jsc/bindings/ZigGlobalObject.h                 |  13 -
 src/jsc/bindings/bindings.cpp                      | 609 +--------------------
 src/jsc/bindings/headers-handwritten.h             |   2 -
 src/jsc/bindings/headers.h                         |  88 ---
 src/jsc/bindings/helpers.h                         |   5 -
 src/jsc/bindings/linux_perf_tracing.cpp            |   9 -
 src/jsc/bindings/napi.h         
... (truncated)

gate history · 2 passed · 0 rejected · iteration 0

evidence per changed file
file                                  reads  edits  tests
src/ast/transpiler_cache.rs               1      2      0
src/bun_alloc/lib.rs                      0      0      0
src/bun_core/lib.rs                       1      2      0
src/bun_core/string/mod.rs                0      0      0
src/bun_core/util.rs                      0      0      0
src/codegen/generate-jssink.ts            2      2      0
src/errno/lib.rs                          3      5      0
src/errno/windows_errno.rs                0      0      0
src/event_loop/lib.rs                     1      1      0
src/http_jsc/websocket_client.rs          0      0      0
src/js/internal/repl/node-inspect.js      1      1      0
src/js/internal/repl/node-shims.js        0      0      0
src/jsc/ConsoleObject.rs                  1      2      0
src/jsc/FetchHeaders.rs                   0      0      0
src/jsc/JSBigInt.rs                       0      0      0
src/jsc/JSCScheduler.rs                   1      1      0
(+ 28 more files)
Original description

Removes 1,208 lines that nothing references (55 lines added in src, all comment/attribute adjustments plus one explicit template instantiation; the rest of the additions are the source lint), across the Rust/C++ FFI shim layer, the bun_dispatch::link_interface! tables, a handful of #[no_mangle] Rust exports, two codegen surfaces, and the REPL shims. No behavior change: every removed function was discarded by the linker, i.e. the shipped binary never contained it.

How the candidates were found

The debug binary was relinked with -Wl,--gc-sections -Wl,--print-gc-sections; the discarded .text.* sections from bun's own objects (C++ and libbun_rust.a) gave the set of functions nothing references at link time. That set was then filtered by

  • textual references across src/, packages/, scripts/ and the regenerated build/debug/codegen/ output, so anything still referenced from code that only compiles on another platform (Bun__Process__hasTitle, archive_entry_set_pathname_utf8, bun_sysconf__SC_CLK_TCK, the kqueue no-orphans hooks, ...) or reached only from a debugger entry point (dumpBtjsTrace and everything under it) was left alone;
  • for things with a Rust-side caller, walking up to the root that has no references and deleting the chain from there (StringJsc::to_range_error_instance -> JSC__createRangeError, FetchHeaders::from -> WebCore__FetchHeaders__createValue, Bun__tickWhilePaused -> EventLoop::tick_while_paused);
  • the diffs of every open dead-code PR plus every open PR touching the files edited here, at the symbol level, both for removals (to not duplicate them) and additions (to not delete something an in-flight PR starts using, e.g. JSC__VM__notifyNeedDebuggerBreak, which Start the inspector at runtime on SIGUSR1 / process._debugProcess #37336 adds a caller for).

Additionally every declaration in headers.h and every extern "C" declaration in the C++ sources was checked against the symbols the binary actually defines, which found the declarations for functions that no longer exist anywhere.

Removed

bindings.cpp / headers.h (about 680 lines)

68 C-ABI shims that no Rust code declares or calls any more (the Rust JSValue/JSPromise/VM types implement these inline or never needed them), together with their headers.h declarations and the cppbind wrappers generated for the [[ZIG_EXPORT]] ones:

  • JSC__JSValue__{isCell, isNull, isUndefined, isUndefinedOrNull, isNumber, isObject, isInt32, isInt32AsAnyInt, isError, isGetterSetter, isCustomGetterSetter, eqlCell, deepEquals, jsNumberFromChar, jsNumberFromU16, jsNumberFromInt32, jsNumberFromInt64, jsNumberFromUint64, jsTDZValue, jsType, createInternalPromise, createRangeError, createTypeError, fastGetDirect_, getPropertyValue, putRecord, symbolKeyFor}
  • JSC__JSInternalPromise__{create, isHandled, reject, rejectAsHandled, rejectAsHandledException, rejectedPromise, resolve, result, setHandled, status} (the type is aliased to JSPromise; only resolvedPromise is used), JSC__JSPromise__{asValue, isHandled, resolveOnNextTick, rejectOnNextTickWithHandled}
  • JSC__JSObject__{getArrayLength, getDirect, putDirect}, JSC__JSCell__{getObject, toObject}, JSC__JSString__toObject, JSC__JSFunction__optimizeSoon, JSC__JSGlobalObject__{getCachedObject, putCachedObject}, JSC__JSMap__has, JSC__JSModuleLoader__evaluate, JSC__createRangeError
  • JSC__VM__{clearExecutionTimeLimit, setExecutionTimeLimit, deleteAllCode, isEntered, isJITEnabled, notifyNeedShellTimeoutCheck, notifyNeedWatchdogCheck, performOpportunisticallyScheduledTasks}
  • ZigString__{to16BitValue, toAtomicValue, toExternalValueWithCallback}, WebCore__DOMURL__{href_, pathname_}, WebCore__FetchHeaders__createValue (+ FetchHeaders::from in Rust), Bun__CallFrame__isFromBunMain, and Zig::toJSString in helpers.h, which only the two create*Error shims used
  • declarations for functions that do not exist anywhere: the 13 Reader__*__fastpath / FFI__ptr__fastpath DOMJIT entry points (only the slow paths exist), JSC__JSGlobalObject__createSyntheticModule_, JSC__JSValue__{then, createStringArray, hasOwnProperty, toString}, JSC__VM__create, ZigException__fromException, Zig__GlobalObject__{fetch, promiseRejectionTracker}, a stray _fromJS

Bun.deepEquals(a, b) was getting its Bun__deepEquals<false, false, false, false> instantiation from the deleted JSC__JSValue__deepEquals body, so it now has an explicit instantiation next to the existing one for <true, false, false, true> (this is what the first build of this branch caught).

link_interface! methods nothing dispatches through

  • JsEventLoop::{file_polls, put_file_poll, pipe_read_buffer, stdout, stderr} (bun_event_loop) and their Jsc arms in bun_jsc::event_loop, plus EventLoop::pipe_read_buffer, which only the arm called. The live pipe_read_buffer is bun_io's EventLoopCtx, a different interface.
  • ErrnoNames::{max_dense, win32_name} (bun_core) and their Sys arms, win32_errno_name and system_errno_max_dense in bun_errno; the Windows SystemErrno::MAX that only max_dense read becomes #[cfg(test)] (the exhaustive round-trip test still iterates it, and the Win32 mapping test now calls init_win32_error directly).
  • TranspilerCacheImpl::is_disabled (bun_ast) and its Jsc arm; RuntimeTranspilerStore calls the inherent is_disabled() directly.

Rust exports with no C++ caller

  • Bun__ConsoleObject__{profile, profileEnd, record, recordEnd, screenshot} and the console_noop_hooks! macro; ConsoleObject.cpp only forwards timeStamp, which is now a plain function
  • Bun__tickWhilePaused + EventLoop::tick_while_paused (+ two declarations in BunDebugger.cpp; runWhilePaused blocks on a condition variable now), Bun__internal_drainTimers, Bun__getVerboseFetchValue (declared in JSEnvironmentVariableMap.cpp, never called), Bun__Timer__getNextID
  • Bun__WTFStringImpl__{ref, deref} on both sides (the Rust comment already said "Rust no longer calls these"; ref/deref are inlined in bun_alloc, only destroy crosses FFI), BlobArrayBuffer_deallocator in blob/Store.rs (Blob.rs has the live blob_store_array_buffer_deallocator), WebSocket::write_blob and its two __writeBlob exports (WebSocket.cpp sends blobs through writeBinaryData), Bun__linux_trace_close
  • JSC__JSBigInt__{orderDouble, orderUint64} and the BigIntOrderable impls for f64/u64; only i64 comparisons are performed
  • the unused export = "Bun__NodeUtil__jsParseArgs" / "PostgresSQLConnection__createInstance" symbol names (both functions are reached through Rust tables; the C++ BUN_DECLARE_HOST_FUNCTION for the former is gone too)

C++ helpers

  • ZigGlobalObject: JSDOMFileConstructor_getter/_setter, functionLazyNavigatorGetter + navigatorObject(), GlobalObject_getPerformanceObject + performanceObject() (navigator/performance are installed from the LUT directly), hasNapiFinalizers() + NapiEnv::hasFinalizers(), NapiEnv::isVMTerminating(), ScriptExecutionContext::postCrossThreadTask, the NodeVM*ModulePrototype() accessors
  • the seven <Sink>() constructor accessors whose only reader was the generated getter below (ArrayBufferSink() stays; BunObject.cpp uses it)

Codegen

  • generate-jssink.ts no longer emits function<Sink>__getter, which was declared and defined for all 8 sinks and installed for none of them
  • jest.classes.ts: call: true on the 7 noConstructor asymmetric matcher classes only produced a <Class>Class__call thunk that the (non-existent) constructor would have used; expect.any() etc. reach ExpectAny::call through expect.rs directly

Built-in JS

  • internal/repl/node-inspect.js: the format / formatWithOptions getters; internal/repl/node-shims.js: BuiltinModule.{exists, canBeRequiredByUsers, canBeRequiredWithoutScheme} and two constants entries (completion.js is the only consumer and destructures neither)

test/internal/source-lints/dead-symbols-ffi-shims-dispatch.test.ts pins every removed symbol; all 7 tests fail on main and pass here (each regex was also checked individually to match on main).

Verification

  • bun bd (full debug build) passes; bun run rust:check-all passes on all 10 target triples (the first run failed on the two Windows targets because SystemErrno::MAX became unused there, which is the #[cfg(test)] change above); cargo test -p bun_errno; rustfmt, clang-format, prettier and oxlint are clean.
  • bun bd test passes on test/js/bun/test/expect.test.js, test/js/bun/bun-object/deep-equals.test.ts, test/js/bun/globals.test.js, test/js/web/web-globals.test.js, test/cli/run/transpiler-cache.test.ts, test/js/bun/repl/repl.test.ts, test/js/web/websocket/{websocket-blob,websocket-client}.test.ts, test/js/bun/util/{arraybuffersink,filesink}.test.ts, test/js/node/readline/readline.node.test.ts, test/js/node/fs/fs-stats-constructor.test.ts, test/js/web/timers/setImmediate.test.js, test/js/bun/console/console-write.test.ts, and test/internal/source-lints/. Two tests timed out locally (setInterval doesn't leak memory at 30s and the parseArgs 100 mixed several times stress test at 5s); the container was running at a load average of ~55 on 16 cores and the release binary runs both in well under a second, and neither path is touched functionally here (the timer change removes two unreferenced exports, the parseArgs change removes an unused symbol name), so I expect CI to disagree with my machine rather than with the diff.
  • No open PR removes any of the symbols above (checked at the symbol level against the 17 open dead-code PRs and at the line level against every open PR touching these files; the only shared removed lines are boilerplate like return 0;), and none adds a reference to them.

Left alone on purpose

…codegen surfaces

Delete the JSC__*/ZigString__*/WebCore__* C-ABI shims in bindings.cpp that no
Rust code calls (and their headers.h declarations), declarations in headers.h
for functions that no longer exist, link_interface! methods nothing dispatches
through (JsEventLoop, ErrnoNames, TranspilerCacheImpl), Rust exports with no
C++ caller (console no-op hooks, Bun__tickWhilePaused, Bun__internal_drainTimers,
Bun__getVerboseFetchValue, Bun__Timer__getNextID, WTFStringImpl ref/deref,
BlobArrayBuffer_deallocator, WebSocket writeBlob, BigInt f64/u64 ordering), a
few unreferenced helpers on the global object and NapiEnv, the per-sink
__getter that generate-jssink.ts emitted for every sink, the unused call: true
on the asymmetric matcher classes, and two REPL shim exports nothing reads.

Bun.deepEquals relied on the deleted JSC__JSValue__deepEquals shim as the
implicit instantiation point of Bun__deepEquals<false, false, false, false>;
it is now instantiated explicitly next to the existing one.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 764874aa-f997-41ce-bf69-45ea176e23c0

📥 Commits

Reviewing files that changed from the base of the PR and between 626034f and 24c6dd5.

📒 Files selected for processing (44)
  • src/ast/transpiler_cache.rs
  • src/bun_alloc/lib.rs
  • src/bun_core/lib.rs
  • src/bun_core/string/mod.rs
  • src/bun_core/util.rs
  • src/codegen/generate-jssink.ts
  • src/errno/lib.rs
  • src/errno/windows_errno.rs
  • src/event_loop/lib.rs
  • src/http_jsc/websocket_client.rs
  • src/js/internal/repl/node-inspect.js
  • src/js/internal/repl/node-shims.js
  • src/jsc/ConsoleObject.rs
  • src/jsc/FetchHeaders.rs
  • src/jsc/JSBigInt.rs
  • src/jsc/JSCScheduler.rs
  • src/jsc/JSValue.rs
  • src/jsc/RuntimeTranspilerCache.rs
  • src/jsc/ZigException.rs
  • src/jsc/array_buffer.rs
  • src/jsc/bindings/BunDebugger.cpp
  • src/jsc/bindings/BunString.cpp
  • src/jsc/bindings/JSBigIntBinding.cpp
  • src/jsc/bindings/JSEnvironmentVariableMap.cpp
  • src/jsc/bindings/ScriptExecutionContext.h
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/jsc/bindings/ZigGlobalObject.h
  • src/jsc/bindings/bindings.cpp
  • src/jsc/bindings/headers-handwritten.h
  • src/jsc/bindings/headers.h
  • src/jsc/bindings/helpers.h
  • src/jsc/bindings/linux_perf_tracing.cpp
  • src/jsc/bindings/napi.h
  • src/jsc/bun_string_jsc.rs
  • src/jsc/event_loop.rs
  • src/jsc/lib.rs
  • src/jsc/virtual_machine_exports.rs
  • src/runtime/jsc_hooks.rs
  • src/runtime/node/util/parse_args.rs
  • src/runtime/test_runner/jest.classes.ts
  • src/runtime/timer/Timer.rs
  • src/runtime/webcore/blob/Store.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • test/internal/source-lints/dead-symbols-ffi-shims-dispatch.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:05 AM PT - Aug 12th, 2026

@robobun, your commit 24c6dd5 has some failures in Build #93013 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 37788

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

bun-37788 --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.

Beyond the two inline nits, I spot-checked that the removed symbols are actually unreferenced across src/: the WebSocket writeBlob exports, the Bun__ConsoleObject__{profile,record,screenshot} hooks, the seven <Sink>() constructor accessors, performanceObject()/navigatorObject()/hasNapiFinalizers(), the JsEventLoop::{pipe_read_buffer,stdout,stderr} dispatch arms, and the REPL shim exports (completion.js only destructures ALL_PROPERTIES/SKIP_SYMBOLS and only calls BuiltinModule.getSchemeOnlyModuleNames()). Also confirmed the removed StringJsc::to_range_error_instance is distinct from the still-live ZigStringJsc method of the same name, and the explicit Bun__deepEquals<false,false,false,false> instantiation is present.

Extended reasoning...

This is a large dead-code removal across 44 files in the FFI shim layer, so I'm not approving on my own, but the spot-checks above all came back clean and the two inline findings are cosmetic.

Comment thread src/js/internal/repl/node-inspect.js
Comment thread src/jsc/bindings/ScriptExecutionContext.h
Comment thread src/bun_alloc/lib.rs Outdated
Comment thread src/bun_core/string/mod.rs Outdated
Comment thread src/errno/lib.rs Outdated
Comment thread src/errno/windows_errno.rs Outdated
Comment thread src/js/internal/repl/node-inspect.js
Comment thread src/jsc/ConsoleObject.rs Outdated
Comment thread src/jsc/ZigException.rs Outdated
Comment thread src/jsc/array_buffer.rs Outdated
Comment thread src/jsc/bindings/bindings.cpp
Comment thread src/jsc/lib.rs Outdated
Comment thread src/bun_alloc/lib.rs
Comment thread src/jsc/lib.rs
@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-ups, both pushed:

  • 522b44c: dropped the <wtf/CrossThreadTask.h> include that only postCrossThreadTask needed (the two TUs that use crossThreadCopy get CrossThreadCopier.h through ExceptionOr.h; full bun bd passes without it) and fixed the node-inspect.js header comment.
  • 24c6dd5: the comment hunks flagged by comment-cop were comments that existed to name shims this PR deletes. Six of them are removed outright (string/mod.rs, errno test doc, ConsoleObject.rs, ZigException.rs, array_buffer.rs) or cut to one line (windows_errno.rs MAX, bun_alloc ref()); the remaining three are pre-existing docs that only lost a stale example (jsc/lib.rs, node-inspect.js) or the note on the explicit Bun__deepEquals instantiations, which is kept on purpose since removing the shim that used to instantiate that combination is exactly what broke the first build of this branch. Replies on each thread.

Net diff is now +315 / -1211; the source lint still fails on main and passes here.

@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 again after 522b44c / 24c6dd5 and didn't find any bugs — both earlier nits (the stale node-inspect.js header comment and the leftover <wtf/CrossThreadTask.h> include) are addressed, and the comment-cop items are resolved. Given the scope (~1,200 lines removed across 44 files in the core FFI shim layer — bindings.cpp, headers.h, ZigGlobalObject.{cpp,h}, plus link_interface! tables and two codegen surfaces), a human pass is still worthwhile.

What was reviewed:

  • The one non-deletion change: the explicit Bun__deepEquals<false,false,false,false> instantiation replaces the one the removed JSC__JSValue__deepEquals body used to provide — matches the callers in BunObject.cpp.
  • jest.classes.ts call: true removals: confirmed generate-classes.ts only wires call into a <Class>Class__call thunk on the constructor path, which noConstructor: true suppresses; expect.any() etc. reach ExpectAny::call via expect.rs.
  • node-shims.js ONLY_ENUMERABLE/SKIP_STRINGS — checked completion.js only destructures ALL_PROPERTIES/SKIP_SYMBOLS.
  • timer_all import removal in Timer.rstimer_all_mut is still imported and used.
Extended reasoning...

Overview

This PR removes ~1,200 lines of dead code from the Rust↔C++ FFI shim layer (bindings.cpp, headers.h, headers-handwritten.h, helpers.h, BunString.cpp, JSBigIntBinding.cpp, ZigGlobalObject.{cpp,h}, napi.h, ScriptExecutionContext.h), the bun_dispatch::link_interface! tables in bun_core/bun_event_loop/bun_ast and their impl arms, several #[no_mangle] Rust exports (ConsoleObject noop hooks, Bun__tickWhilePaused, Bun__Timer__getNextID, BlobArrayBuffer_deallocator, WebSocket write_blob, etc.), two codegen surfaces (generate-jssink.ts per-sink __getter, jest.classes.ts call: true on 7 noConstructor matchers), and REPL shim exports. The additions are: one explicit template instantiation in bindings.cpp, comment/attribute adjustments, a #[cfg(test)] gate on SystemErrno::MAX for Windows, and a new source-lint test that pins every removed symbol.

The candidates were found by relinking with --gc-sections --print-gc-sections, then filtered against textual references across all source trees (so platform-gated and debugger-only code was left alone) and cross-checked at the symbol level against open PRs. The PR description lists what was intentionally left (URL::from_js, remaining simdutf__*, JSC__VM__notifyNeedDebuggerBreak, etc.) with the specific in-flight PR each conflicts with.

Security risks

None. This is purely subtractive against code the linker already discards; no new input handling, no auth/crypto/permission surfaces touched.

Level of scrutiny

High — not because the individual deletions are risky (each removed function was already dead in the shipped binary), but because of the breadth: 44 files across the core Rust/C++ binding boundary, including bindings.cpp (−609 lines), headers.h (−88 lines), and ZigGlobalObject.{cpp,h}. The one subtle correctness dependency is the explicit Bun__deepEquals<false,false,false,false> instantiation, which the author added after the first build broke — that's now in place next to the existing <true,false,false,true> instantiation. The link_interface! removals require knowing that no dispatch site names those methods, which is easy to get wrong for macro-generated tables; the source-lint test guards against re-addition but not against a missed live caller. A maintainer spot-checking the --print-gc-sections methodology and the open-PR coordination list would be valuable.

Other factors

  • My two earlier inline nits (stale node-inspect.js comment, leftover CrossThreadTask.h include) were addressed in 522b44c; the comment-cop flags were addressed in 24c6dd5 with per-thread replies.
  • Verification listed in the PR description covers full bun bd, rust:check-all on all 10 targets, cargo test -p bun_errno, and the relevant test files for each touched subsystem.
  • The new source-lint test is well-scoped (reads source files, per-symbol regex checks, all 7 tests fail on main per the evidence block).
  • The jest.classes.ts change touches the test-runner codegen input; I verified in generate-classes.ts that call: true under noConstructor: true only feeds the constructor's call slot, which is never emitted.

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for 24c6dd5 (build 93013): 193 of 194 jobs passed; the only red job is one of the two darwin 26 aarch64 test-bun shards, which expired without ever being assigned an agent (its sibling shard on the same lane waited 5.5 hours, then ran and passed). The three tests Buildkite lists as flaky (test-fs-read-stream-pos.js, test-watch-mode-kill-signal-default.mjs on alpine aarch64, bun-install-registry.test.ts hoisting on Windows aarch64) each passed on retry and are outside the code this PR touches.

The expiry is a capacity problem on the release-tier=latest macOS lane, not this diff: that tier is currently served by two hosts and has about 210 shards queued (oldest from 09:28 UTC), so the pipeline's scheduling timeout is expiring shards across builds. Re-running the build now would put two fresh shards at the back of the same queue, so I am not retriggering; retrying just the expired job once that queue drains (or merging on the strength of the passing sibling shard) is the cheaper option. The diff itself is done and ready for review.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants