Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

17 Commits

Folders and files

Repository files navigation

openworkers-jsc

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:

  • Context and ContextGroup, with the execution time limit of the group
  • Value and Object: constructors, checks, conversions, properties, calls. They borrow the Context they 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 stack
  • sys, the raw C declarations, including the private time limit functions

Features

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

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-0c06faadf65bf8e8c8ad3a5a8aca83e1e9ed653f of oven-sh/WebKit, asset bun-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 with git rev-parse. JSC calls into it, so the build script compiles it with the cc crate and the flags of Bun. The build needs git and curl.

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 the timer::TimerHost that the embedder installs on its thread. See the timer module for what JSC uses them for. Without a host, the timers never fire.
  • Bun__thisThreadHasVM: true on a thread that has a TimerHost.
  • 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.

Execution time limit

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.

Tests and bench

cargo test
cargo test --no-default-features --features static
cargo run --release --example bench
cargo run --release --example bench --no-default-features --features static

The 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/bench

License

The 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.md and the licenses/ 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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages