Rust bindings to JavaScriptCore (JSC) for OpenWorkers. This crate is the JSC
counterpart of openworkers-v8. It knows nothing about workers: it only
exposes JSC to Rust.
The crate covers what openworkers-runtime-jsc needs, not the whole C API:
ContextandContextGroup, with the execution time limit of the groupValueandObject: constructors, checks, conversions, properties, calls. They borrow theContextthey come from, so they cannot outlive it- native functions from Rust closures (
Object::new_function) - typed arrays and ArrayBuffers
- promises that Rust settles (
Object::new_promise) Protected, to keep a value alive outside the stacksys, the raw C declarations, including the private time limit functions
Enable at least one. With both, static wins: Cargo features add up, so a
crate between this one and the binary cannot turn off the default system.
| Feature | JavaScriptCore |
|---|---|
system (default) |
JavaScriptCore.framework of macOS |
static |
the prebuilt static JSC of oven-sh/WebKit, linked into the binary |
[dependencies]
openworkers-jsc = { path = "../openworkers-jsc", features = ["static"] }static supports macOS on arm64 only. Other targets give a compile error.
The build script fetches two pinned sources. A mismatch fails the build.
- WebKit: release
autobuild-0c06faadf65bf8e8c8ad3a5a8aca83e1e9ed653fof oven-sh/WebKit, assetbun-webkit-macos-arm64.tar.gz, checked by sha256. Bun 1.4.3 pins this build. - mimalloc: oven-sh/mimalloc at
92ef6587c57b262f6e6abf513f7776a2f8d12aeb(version 3.5.3), the commit that Bun 1.4.3 builds, fetched with git and checked withgit rev-parse. JSC calls into it, so the build script compiles it with the cc crate and the flags of Bun. The build needsgitandcurl.
The fetched sources go to $OPENWORKERS_JSC_DIR, or else to
$CARGO_HOME/openworkers-jsc, so a clean build does not download them again.
Delete that directory to fetch them again.
The binary also links libc++, libobjc, libicucore (the ICU of macOS) and
libSystem. The archives target macOS 13: set MACOSX_DEPLOYMENT_TARGET=13.0,
or else the linker warns for each object. .cargo/config.toml does it for
this repository.
Each link of a static binary prints about 250 warnings of the form
ld: alignment (1) of atom 'anon-N' (libJavaScriptCore.a[...]) is too small and may result in unaligned pointers. They come from the objects of the
archive, not from this crate, and the tests pass with them. No flag of the
Apple linker turns off this warning alone: -w turns off all of them. To
hide them, the binary crate can add #![allow(linker_messages)], which also
hides its other linker messages.
The oven-sh build drops the VM lock from some C API functions, because Bun
calls them with the lock held. The crate takes the lock around them (see
src/lock.rs), through two C++ symbols of JSC.
The archive expects the embedder (Bun) to define some hooks. JSC declares them weak, but the macOS linker still needs a definition. This crate defines:
WTFTimer__create,__update,__cancel,__deinit,__isActive,__secondsUntilTimer: the run loop timers of WTF. They go to thetimer::TimerHostthat the embedder installs on its thread. See thetimermodule for what JSC uses them for. Without a host, the timers never fire.Bun__thisThreadHasVM: true on a thread that has aTimerHost.Bun__reportUnhandledError: does nothing. JSC calls it only for a kind of microtask that only Bun queues.
Without a timer host, FinalizationRegistry callbacks never run. The system framework behaves the same on a thread that does not run its CFRunLoop.
ContextGroup::set_execution_time_limit arms the watchdog of JSC. With the
JIT, turn on polling traps, or else a loop that calls native code, such as
Date.now(), is cut late or never:
// Before the first context and before other threads start
unsafe { openworkers_jsc::init(openworkers_jsc::Options { polling_traps: true }) }?;init sets the option through the environment (JSC_usePollingTraps),
which JSC reads when the first context group starts. A write to the
environment races with a read on another thread, so init is unsafe. Child
processes inherit the variable. After the start, init with the options
that JSC read succeeds and changes nothing; with other options it fails, so
a caller can check that JSC runs with polling traps.
The time to cut a 2 s spin under a 50 ms limit:
| Build | JIT | Without polling traps | With polling traps |
|---|---|---|---|
system |
off | 50 to 59 ms | 52 to 56 ms |
system, allow-jit entitlement |
on | not cut (ran 2 s) | 59 ms |
static |
on | 110 ms to 1.7 s | 51 to 60 ms |
A loop without calls is cut at 51 to 62 ms in each case. The static build has the JIT on without an entitlement, since the binary does not use the hardened runtime.
cargo test
cargo test --no-default-features --features static
cargo run --release --example bench
cargo run --release --example bench --no-default-features --features staticThe bench calls one JS function 10,000 times per run, for 3 runs: a JSON round trip of 50 items and a template render. On an Apple M2 with macOS 27.0.1:
| Build | JIT | Min | Median | Max |
|---|---|---|---|---|
system |
off | 123.0 ms | 123.6 ms | 124.1 ms |
system, allow-jit entitlement |
on | 67.0 ms | 68.1 ms | 69.4 ms |
static |
on | 61.1 ms | 61.9 ms | 62.7 ms |
static, JSC_useJIT=false |
off | 106.5 ms | 108.3 ms | 109.4 ms |
The system framework runs without the JIT unless the binary has the
com.apple.security.cs.allow-jit entitlement. An ad-hoc signature is
enough for a local test, with a jit.plist that sets this entitlement to
true:
codesign -f -s - --entitlements jit.plist -o runtime target/release/examples/benchThe code of this crate is under the MIT license.
A binary built with system links the JavaScriptCore of macOS as a system
framework. It contains no third-party code from this crate, and needs no
notice for it.
A binary built with static contains JavaScriptCore, WTF, bmalloc and
mimalloc. Parts of JavaScriptCore and WTF are under the LGPL 2.0 or 2.1.
THIRD_PARTY_NOTICES.md lists the components, their
licenses and their sources. As we read these licenses, a party that
distributes such a binary must:
- ship
THIRD_PARTY_NOTICES.mdand thelicenses/directory with it; - give the source of the LGPL library, or a link to the exact commit
(oven-sh/WebKit
0c06faadf65bf8e8c8ad3a5a8aca83e1e9ed653f); - let users relink the binary with a modified library: the source or object files of the rest of the binary must be available. Open source code meets this.
This is not legal advice. Whether the project uses static for the binaries
it distributes is a decision for the owner of the project; this crate does
not make it.