Skip to content

Use Bun's JavaScriptCore builds on Android and Linux - #206

Open
matthargett wants to merge 10 commits into
BabylonJS:mainfrom
rebeckerspecialties:agent/use-bun-jsc-android
Open

matthargett wants to merge 10 commits into
BabylonJS:mainfrom
rebeckerspecialties:agent/use-bun-jsc-android

Conversation

@matthargett

@matthargett matthargett commented Jul 12, 2026

Copy link
Copy Markdown

Summary

  • replace the abandoned jsc-android npm/AAR dependency and the Linux WebKitGTK dependency with the JavaScriptCore archives Bun publishes from its WebKit fork (oven-sh/WebKit release autobuild-1bd03678132783e3bb98a48205f4e8439253cf90, 2026-09-13);
  • support only arm64-v8a and x86_64 on Android, and only glibc arm64 and x86_64 on Linux;
  • link the plain (native-code) archives by default on both platforms; Bun's ThinLTO archives are an opt-in (-D JSR_BUN_JSC_LTO=ON) for builds that want one cross-module LTO link and can supply the matching LLVM 21 toolchain;
  • link Bun's mimalloc fork (oven-sh/mimalloc), which the current archives allocate through (mi_theap_*), as a FetchContent dependency with MI_OVERRIDE=OFF so it never replaces the process allocator;
  • download only the selected asset, extract only the headers and the required archives, validate a revision/SHA-keyed completion stamp, and delete the tarball immediately; and
  • adapt JsRuntimeHost to the Bun embedder-held JSC API-lock model, including teardown ordering that keeps the N-API environment alive through VM last-chance finalizers.

This removes the need for npm, Maven, an AAR, WebKitGTK, or a separate jsc-android-buildscripts publication pipeline.

Platform model

Android

  • arm64-v8a: bun-webkit-linux-arm64-android.tar.gz
  • x86_64: bun-webkit-linux-amd64-android.tar.gz
  • API 28 minimum for the JSC path; V8/Hermes retain their existing minimums.
  • NDK r26b or newer is required by the current C++23 WebKit headers; the test app selects CI NDK r28c for JSC.
  • The archives are relinked into the existing libjsc.so boundary with a narrow export map and 16 KiB ELF alignment. Bun now also publishes -android-lto archives; they are not used.

Linux

  • Default: bun-webkit-linux-amd64.tar.gz / bun-webkit-linux-arm64.tar.gz (native ELF objects). Any Clang with a C++23 standard library that has <expected> works; Ubuntu 22.04 installs libstdc++-12-dev for that header surface. The archives are built against libstdc++, so the build must use Clang.
  • Opt-in JSR_BUN_JSC_LTO=ON: bun-webkit-linux-*-lto.tar.gz. These are ThinLTO bitcode archives produced by LLVM 21.1.x (EnableSplitLTOUnit=0), so the shim is compiled and linked with -flto=thin and the configure step insists on clang 21, lld 21 and llvm-ar/llvm-ranlib 21 as CMAKE_AR/CMAKE_RANLIB — binutils ar and older LLVMgold plugins cannot index LLVM 21 bitcode, and -flto=full fails with "inconsistent LTO Unit splitting" because clang 21 forces split units for full LTO. This is what lets a downstream (BabylonNative) link Bun's JSC, Rust -C linker-plugin-lto bitcode from rustc 1.94 (LLVM 21.1.8) and its own C++ in one whole-program link.
  • The practical compatibility floor is pinned to Ubuntu 22.04: GLIBC 2.35 and GLIBCXX 3.4.30.

The Bun Linux archives contain bmalloc local-exec TLS relocations, so they cannot be repackaged as a shared JSC library. They must flow through static napi/JsRuntimeHost targets into the final executable; a shared downstream final consumer is unsupported.

CI and disk use

  • Linux CI is pinned to Ubuntu 22.04 and installs Clang/LLD 21 plus the GCC 12 C++ headers with --no-install-recommends; llvm-21 (archiver, symbolizer) only for the LTO and ThreadSanitizer jobs.
  • Jobs: Ubuntu_JSC_LTO (bun-jsc-lto: true, the ThinLTO archives), Ubuntu_Sanitizers_clang and Ubuntu_ThreadSanitizer_clang (the default plain archives under ASan/UBSan and TSan), Android_JSC.
  • ThreadSanitizer: mimalloc is instrumented through the global -fsanitize=thread and built with MI_TSAN=1, which routes its deliberately racy lock-free reads through atomics; without that the job reported 153 races inside mimalloc. TSAN_OPTIONS names llvm-symbolizer-21 so any report is symbolized.
  • CMake downloads one ABI/architecture asset, checks its pinned SHA-256, extracts only include/, JavaScriptCore, WTF, bmalloc, and ICU, then deletes the compressed archive.
  • CI audits PIE flags, the JSC wrapper symbol, unresolved symbols, absence of a dynamic JavaScriptCore dependency, and maximum GLIBC/GLIBCXX symbol versions.

Validation

Upstream fork-PR runs sit at GitHub's approval gate, so the full 24-job matrix runs on the fork twin rebeckerspecialties#20 for every push of this branch. At the current head the Linux JSC jobs (ThinLTO; plain archives under ASan/UBSan and under TSan with zero reports) and Android JSC are green, alongside the rest of the matrix.

Earlier, on the main-based change before the archive refresh:

  • Android arm64 API 31 emulator: 206 JavaScript tests and all 6 native tests passed; Android x86_64 build completed with NDK r28c; libjsc.so load segments are 16 KiB-aligned and the APK passes zipalign -c -P 16 -v 4.
  • Linux x64 PIE runtime smoke passed before layering the N-API stack: 204 JavaScript tests and all 6 native tests.

Layered onto the pending napi-v7 patch stack: Linux NodeApiTests 16 registered, 15 passed, one pre-existing quarantined case skipped; Android arm64 API 31 emulator 149 JavaScript tests passed and 11 of 12 native Node-API tests passed with the same quarantine skipped; every packaged arm64 native library is 16 KiB-aligned.

Conformance boundary

Tests/NodeApi is the repository patch stack adapted from Node.js js-native-api tests; it is not the complete upstream Node-API CTS. This establishes the selected v1-v6 surface plus the current v7 stack integration, but it does not claim complete N-API v7 coverage. The imported detach/get-all-property-names cases remain gated because their harness path also depends on the v9 node_api_basic_env surface.

Follow-up

The WebKit archives contain private cached-bytecode machinery but not the stable Bun bytecode generation/loading facade. A revision-locked JsRuntimeHost API for precompiled Babylon bundles remains separate work.

This is the JSC replacement referenced by the Android XR preparation in #182, and it unblocks the modern JSC portion of #187.

Closes #186

@matthargett matthargett changed the title Use Bun's JavaScriptCore build on Android Use Bun JavaScriptCore builds on Android and Linux Jul 12, 2026
@matthargett matthargett changed the title Use Bun JavaScriptCore builds on Android and Linux Use Bun's JavaScriptCore builds on Android and Linux Jul 12, 2026
@matthargett
matthargett marked this pull request as ready for review July 12, 2026 06:53
Copilot AI review requested due to automatic review settings July 12, 2026 06:53
@matthargett

Copy link
Copy Markdown
Author

Ready for review. The hosted CI run is currently at GitHub's fork-workflow approval gate (run 29183225207); my token cannot approve workflows for the upstream repository. GitGuardian is green. The PR description records the completed Ubuntu 22.04 baseline-LTO, arm64 Android, N-API patch-stack, conformance, ABI-floor, and 16 KiB alignment validation.

Copilot AI 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.

Pull request overview

This PR migrates the JavaScriptCore integration on Android and Linux to consume Bun’s pinned WebKit/JavaScriptCore archive releases (instead of jsc-android/AAR and Linux WebKitGTK), and adapts JsRuntimeHost’s teardown/locking behavior to Bun’s embedder-held JSC API-lock model.

Changes:

  • Replace Android jsc-android + Linux WebKitGTK linkage with selective download/extraction of Bun WebKit JSC headers + static archives, plus a small wrapper (bun_jsc) to align with required locking/teardown semantics.
  • Tighten platform support expectations (Android arm64/x86_64 with API 28+ for JSC; Linux glibc arm64/x86_64 with Clang/LLD 21 and C++23 headers).
  • Update CI to build on Ubuntu 22.04 and audit the resulting PIE/static-JSC linkage properties.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Tests/UnitTests/Android/app/build.gradle Raises minSdk for JSC builds and wires JSR_BUN_JSC_ROOT override through Gradle/CMake.
Core/Node-API/Source/jsc_bun.exports Adds an Android linker version-script to constrain exported symbols for libjsc.so.
Core/Node-API/Source/jsc_bun.cpp Introduces Bun-JSC init + VM/API-lock helpers used by the embedder/teardown path.
Core/Node-API/Source/js_native_api_javascriptcore.h Adds Bun-specific context/API locking RAII, plus shutdown-aware teardown ordering.
Core/Node-API/Source/js_native_api_javascriptcore.cc Avoids ref-tracking mutations during shutdown; ensures napi_coerce_to_bool validates env under Bun locking model.
Core/Node-API/package-jsc.json Removes the old jsc-android dependency metadata.
Core/Node-API/Include/Engine/JavaScriptCore/napi/env.h Adds Napi::ContextLock wrapper to keep the VM alive across ordering-sensitive operations.
Core/Node-API/CMakeLists.txt Implements Bun WebKit archive download/extract/stamp + Android shared wrapper / Linux static-LTO propagation.
Core/AppRuntime/Source/AppRuntime_JavaScriptCore.cpp Calls JSCBunInitialize() and adjusts release/detach ordering using Napi::ContextLock.
.github/workflows/ci.yml Updates Linux CI job matrix naming/selection to reflect the new JSC-LTO flow.
.github/workflows/build-linux.yml Pins Ubuntu 22.04, installs LLVM/LLD 21 as needed, and adds PIE/static-JSC audits.
.github/tsan_suppressions.txt Updates suppressions commentary/targets for the Bun statically-linked JSC scenario.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Core/Node-API/CMakeLists.txt Outdated
Comment thread .github/tsan_suppressions.txt Outdated
RaananW

This comment was marked as off-topic.

@matthargett
matthargett force-pushed the agent/use-bun-jsc-android branch from 8b3eb46 to b42a8b5 Compare September 13, 2026 18:52
…archive's split-lto-unit state

The Sept autobuild's libJavaScriptCore.a is LTO bitcode built with split-lto-unit
OFF (LLVM 21, summary version 12). Our shim compiled with -fwhole-program-vtables,
which forces split-lto-unit ON, so lld-21 rejected the mixed link: 'inconsistent LTO
Unit splitting'. The prebuilt archive can't be recompiled, so the shim matches it
with plain -flto and no whole-program devirtualization (a no-op on a one-TU shim).
…'s ThinLTO with an LLVM 21 toolchain

Bun publishes two Linux flavours of its JavaScriptCore: plain native archives, and
bitcode archives that are ThinLTO (module summaries, EnableSplitLTOUnit=0) produced by
LLVM 21. The bitcode flavour lets the final link optimize across JSC and this runtime,
but it couples the whole build to one LLVM major: the same one must compile the
bitcode we contribute, index the archives, and run the LTO link.

Two things broke that coupling. clang emits -flto-unit (LTO-unit splitting ON) for
every -flto=full translation unit regardless of -fno-split-lto-unit, so our shim could
never agree with Bun's split=0 modules ("inconsistent LTO Unit splitting"); the shim
now uses -flto=thin, matching Bun's mode. And binutils ar (with a stale LLVMgold
plugin) cannot read LLVM 21 bitcode, leaving our bitcode archive without a symbol
index ("LLVM gold plugin has failed to create LTO module ... Reader: LLVM 13.0.1");
the LTO configuration now requires llvm-ar/llvm-ranlib from LLVM 21 and the Linux
workflow passes them for the Ubuntu_JSC_LTO job.

Default is the plain archives with no LTO flags or toolchain-version requirements
beyond Clang, so a stock toolchain (and the sanitizer jobs) just work; the LTO
build is opt-in with -D JSR_BUN_JSC_LTO=ON. Android keeps Bun's native archives:
Bun now publishes Android ThinLTO archives too, but the NDK is not LLVM 21.
…rchives allocate through

The September autobuild's libJavaScriptCore/libWTF call mi_malloc, mi_free,
mi_malloc_aligned and Bun's own mi_theap_get_default/mi_theap_collect (thread-heap
entry points that exist only in oven-sh/mimalloc), and the archives no longer bundle
an allocator, so every Linux link failed with those symbols undefined. Build the fork
as a static library with MI_OVERRIDE=OFF -- JavaScriptCore calls the mi_* API
explicitly and the process allocator is left alone -- and put it in the link group
with WTF/bmalloc/ICU. Android's archives are unaffected (the shared shim links with
--no-undefined and did).
The Android archives from the same autobuild reference the same mi_* entry points
(mi_malloc, mi_free, mi_theap_get_default, mi_collect, mi_manage_os_memory_ex, ...),
so the libjsc.so link failed just like Linux. Declare the mimalloc fork once, before
the platform branch, and link mimalloc-static into both shims.
The TSan job's reports came back as bare addresses (llvm-21 only ships
llvm-symbolizer-21, which TSan does not find on its own), so none of the
153 warnings on the current Bun archives could be attributed. Point
TSAN_OPTIONS at the versioned symbolizer and install llvm-21 for that
job too.

mimalloc is already instrumented through the global -fsanitize=thread,
but its lock-free paths read a few fields racily by design; MI_TSAN
routes those through atomics so only real races remain.
@matthargett

Copy link
Copy Markdown
Author

Status update — the branch moved to the current Bun WebKit build and the description above is refreshed to match. Since the July review:

  • Archives: oven-sh/WebKit autobuild-1bd03678 (2026-09-13). Bun stopped publishing the baseline-lto asset, and its -lto archives are ThinLTO bitcode from LLVM 21 (EnableSplitLTOUnit=0), so -flto=full fails with "inconsistent LTO Unit splitting" under clang 21. The plain native-code archives are now the default on both platforms; ThinLTO is opt-in (-D JSR_BUN_JSC_LTO=ON) and the configure step insists on clang/lld/llvm-ar/llvm-ranlib 21 so the bitcode is indexed and linked by the LLVM that produced it. That opt-in is what a downstream needs for one cross-module link with Rust -C linker-plugin-lto bitcode (rustc 1.94 = LLVM 21.1.8); upstream CI exercises both shapes.
  • mimalloc: the current archives allocate through Bun's mimalloc fork (mi_theap_*), so it is fetched as a static dependency with MI_OVERRIDE=OFF and linked into the Linux and Android shims.
  • ThreadSanitizer: the first TSan run against these archives reported 153 races, all inside mimalloc's intentionally racy lock-free reads. Building it with MI_TSAN=1 under ENABLE_THREAD_SANITIZER (and pointing TSAN_OPTIONS at llvm-symbolizer-21 so reports are readable) brings the job to zero reports — nothing needed changing in JSC or Bun.

On the "missing CI coverage" note: upstream fork-PR runs stop at GitHub's approval gate, so the full 24-job matrix runs on the fork twin rebeckerspecialties#20 for every push. Latest head af7e60e: 24/24 green — https://github.com/rebeckerspecialties/JsRuntimeHost/actions/runs/34832759625 (Ubuntu ThinLTO, plain archives under ASan/UBSan and TSan, Android JSC, plus the rest of the matrix).

matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 14, 2026
…e integration branch

Resolutions:
- Core/Node-API/CMakeLists.txt: Bun's pinned WebKit archives replace the
  jsc-android package block outright (the r294992 bump is moot).
- js_native_api_javascriptcore.h: keep the instance-data fields and the
  mutex-guarded registry insert/erase, wrapped in Bun's context-lock
  scopes exactly as BabylonJS#206 places them (registry erase after the scope).
- AppRuntime_JavaScriptCore.cpp: the re-armed watchdog stays; Bun's
  JSCBunInitialize() and the ContextLock-scoped context release are added
  around it.
- build-linux.yml: symbolized TSAN_OPTIONS from BabylonJS#206 plus LSAN_OPTIONS.
- build.gradle: compile/target SDK 35 and NDK 28.2 from this branch,
  minSdk 28 for the JavaScriptCore engine from BabylonJS#206.
- package-jsc.json is gone with the npm package.
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.

RFC: replace abandoned jsc-android with a maintained JSC (Bun's WebKit fork) on Android

3 participants