Bun's WebKit PR: Shared-Memory Threads Land in JavaScriptCore
A massive open pull request from Oven-sh (the company behind Bun) introduces shared-heap threading to JavaScriptCore (JSC). The PR, now at revision 45, adds multi-mutator heap support with per-thread allocators, JIT tiers under N mutators, and a full Thread/Lock/Condition/ThreadLocal API. This is a foundational change: JavaScriptCore can finally run multiple JavaScript threads with a shared heap, a capability long present in V8 but absent in JSC.
The Core Mechanism: Per-Thread Allocators and Shared VM State
The design splits the heap into a heap server and per-thread allocators. Each thread gets its own allocation context, while the VM state is shared. The key data structure is the "segmented butterfly" — a tagged array representation that supports concurrent writes with TID (thread ID) tagging. JIT tiers (DFG, FTL) now emit code that checks thread ownership and falls back to slow paths when needed.
Behind the scenes, the PR introduces --useJSThreads to enable the new threading model, with --useThreadGIL as a fallback that retains the global interpreter lock. The GIL-off mode removes the JSLock entirely, serialized only by the VM's JSLock as a "semantic oracle" for the upcoming shared-heap implementation.
Benchmarks: 76% Allocation Tax Reduction
The PR includes detailed performance analysis using the intcs benchmark suite. At revision 43, the cumulative reduction in allocation tax reached 76.3% of the original 1912ms gap. Key optimizations:
- H-VMLITE-TLCPTR: Bakes a process-constant TLC slot index at JIT-compile time, eliminating the 3-hop allocator lookup (
allocationClientForCurrentThread -> allocatorForSizeStep -> allocateForClient).CompleteSubspace::allocateForClientcalls dropped from 27.8M to 0. - H-ISO-TLCSLOT: Per-type IsoSubspace TLC slot stamped at heap creation, removing
tlcSlotForConcurrentlylookups for all iso-subspace types except JSArray. - Thin-thunk: Replaces the full
operationCompileFTLLazySlowPathC call with a JIT-side tail jump, reducing calls from 36.4M to 56 (a 99.9998% reduction).
After these changes, intcs W=1 improved from 7788ms to 6381ms, and RSS dropped by 2.3% (W=1) and 10.6% (W=16). The corpus now passes 95/96 tests, with all checksums stable.
The String.fromCharCode Race and Fix
A subtle bug was uncovered: String.fromCharCode is a lazy static property on StringConstructor. If a worker thread accesses it first, the foreign-TID structure transition segments the butterfly, causing DFG GetButterfly to emit a speculation check that OSR-exits forever. This led to a 4600ms slow-mode loop with 15 recompilations.
The fix (revision 44-45) adds a ConcurrentButterfly::trySegmentedTransition gate that reuses the existing flat allocation for property-only transitions, and a handleGetById fallback that consults hasExitSite(BadIndexingType). After the fix, worker-reify tests went from 12/12 slow to 12/12 fast.
What This Means for Bun Developers
This PR is still open, but it represents a massive leap for JavaScriptCore threading. Once merged, Bun will be able to run true shared-memory multi-threaded JavaScript without the GIL bottleneck. The immediate takeaway: if you're building CPU-bound workloads (e.g., data processing, image manipulation), watch this PR. The performance gains are real — but expect ecosystem adjustments as the threading model stabilizes.
Code Example: Enabling Threads
To test the new threading model, build WebKit with --useJSThreads:
# Build JSC with threading support
cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_JIT=ON -DUSE_JS_THREADS=ON ..
make -j$(nproc)
# Run a test with threads enabled
./jsc --useJSThreads=true test.js
The PR also includes a 39-test corpus and a TSAN build target for race detection.
Remaining Work
Two major items remain:
- JSArray iso-TLC slot: Currently excluded because inline butterfly tagging is not yet thread-safe. Estimated gain: 400-500ms.
- Task-8: TID-tag every JIT inline butterfly install. Required before JSArray can be included.
The PR notes that the residual ~75% allocation tax is now reduced to ~25%, with the remaining bottleneck being MakeRope thunk traversals for iso-subspace strings.
Conclusion
This PR is the most significant threading change to JavaScriptCore in years. It's not merged yet, but the design and benchmarks are compelling. If you're a Bun or WebKit contributor, review the THREAD.md design doc and the evidence pack. For everyone else: keep an eye on this — it will change how you think about JavaScript parallelism.

