Use Bun's JavaScriptCore builds on Android and Linux - #206
matthargett wants to merge 10 commits into
Conversation
|
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. |
There was a problem hiding this comment.
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.
…the linux-amd64 asset is now plain -lto (baseline-lto is no longer published)
8b3eb46 to
b42a8b5
Compare
…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.
|
Status update — the branch moved to the current Bun WebKit build and the description above is refreshed to match. Since the July review:
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 |
…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.
Summary
jsc-androidnpm/AAR dependency and the Linux WebKitGTK dependency with the JavaScriptCore archives Bun publishes from its WebKit fork (oven-sh/WebKitreleaseautobuild-1bd03678132783e3bb98a48205f4e8439253cf90, 2026-09-13);arm64-v8aandx86_64on Android, and only glibcarm64andx86_64on Linux;-D JSR_BUN_JSC_LTO=ON) for builds that want one cross-module LTO link and can supply the matching LLVM 21 toolchain;oven-sh/mimalloc), which the current archives allocate through (mi_theap_*), as aFetchContentdependency withMI_OVERRIDE=OFFso it never replaces the process allocator;This removes the need for npm, Maven, an AAR, WebKitGTK, or a separate
jsc-android-buildscriptspublication pipeline.Platform model
Android
arm64-v8a:bun-webkit-linux-arm64-android.tar.gzx86_64:bun-webkit-linux-amd64-android.tar.gzlibjsc.soboundary with a narrow export map and 16 KiB ELF alignment. Bun now also publishes-android-ltoarchives; they are not used.Linux
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 installslibstdc++-12-devfor that header surface. The archives are built against libstdc++, so the build must use Clang.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=thinand the configure step insists on clang 21, lld 21 andllvm-ar/llvm-ranlib21 asCMAKE_AR/CMAKE_RANLIB— binutilsarand older LLVMgold plugins cannot index LLVM 21 bitcode, and-flto=fullfails 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-ltobitcode from rustc 1.94 (LLVM 21.1.8) and its own C++ in one whole-program link.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
--no-install-recommends;llvm-21(archiver, symbolizer) only for the LTO and ThreadSanitizer jobs.Ubuntu_JSC_LTO(bun-jsc-lto: true, the ThinLTO archives),Ubuntu_Sanitizers_clangandUbuntu_ThreadSanitizer_clang(the default plain archives under ASan/UBSan and TSan),Android_JSC.-fsanitize=threadand built withMI_TSAN=1, which routes its deliberately racy lock-free reads through atomics; without that the job reported 153 races inside mimalloc.TSAN_OPTIONSnamesllvm-symbolizer-21so any report is symbolized.include/, JavaScriptCore, WTF, bmalloc, and ICU, then deletes the compressed archive.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:libjsc.soload segments are 16 KiB-aligned and the APK passeszipalign -c -P 16 -v 4.Layered onto the pending
napi-v7patch stack: LinuxNodeApiTests16 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/NodeApiis 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 v9node_api_basic_envsurface.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