Skip to content

Add support for targeting custom WIT worlds - #43

Merged
tschneidereit merged 11 commits into
mainfrom
cm-support
Oct 28, 2026
Merged

tschneidereit merged 11 commits into
mainfrom
cm-support

Conversation

@tschneidereit

Copy link
Copy Markdown
Member

This is the last big remaining piece of core architecture for StarlingMonkey: supporting arbitrary WIT worlds instead of just WASI.

Where the old StarlingMonkey was paired with an external tool, ComponentizeJS, this iteration comes with its own tool, starling-componentize.

What's also different is that there is much tighter integration with the runtime's handling of things like futures, streams, requests, and responses: they're now all mapped to idiomatic JS types like Promise and ReadableStream, and can be used interchangeably with builtin type: an incoming WASI request can be passed to fetch, and a fetch response returned as a WASI response, for example.

This PR also comes with a bunch of changes to existing crates, laying the groundworks for efficiently building on them in the new wit-bridge runtime support for arbitrary WIT worlds.

Rename the pre-initialization terms after Wizer's own: a snapshot's instance is wizened.
`crates/core-runtime/core` is a 31 MB process core dump with no use in the build.
Property names passed as `&'static CStr` are interned through
`get_or_init_property_id`, with `*_by_name` variants for runtime names.
A raw `Value` no longer implements `ToJSVal`, so a function taking
`impl ToJSVal` can GC before it converts its argument, and
`Object::from_raw_value` takes a raw value read from a rooted location
right before the call. Typed array bytes are only borrowed inside
closures, the `ArrayBuffer` and view brand checks accept resizable buffers
and cross-compartment wrappers, and calling a non-callable value from
native code throws "X is not a function". `#[jsclass]` accessors are
non-enumerable, constructors called without `new` throw SpiderMonkey's
class-constructor error, hidden classes register through
`register_hidden_class`, and the generated module `register` is safe.
Unused APIs are removed.

Each global keeps its `setTimeout` and `setInterval` timers in a map of its
own, through the new `js::class::global_state`, so `clearTimeout` and
`clearInterval` clear the global's timers that another event loop created,
and no other global's. Serve timeouts accept fractional seconds,
`--enforce-fetch-restrictions` is renamed `--web-security-restrictions`,
and `file:` URL imports resolve on wasm. The core-runtime tests that run
on wasm share one test binary, and redundant tests are removed. Dependent
crates get the call-site changes the new APIs require.
`btoa` and `TextDecoder.decode` report out-of-memory instead of aborting,
reading a whole body rejects when its buffer can't grow, an outgoing body
whose chunk can't be allocated fails its send, and all of them throw one
`js::error::OutOfMemory`. The C++ `console` prints messages that contain
NUL characters in full. `onabort` follows the `EventHandler` rules, and
`performance.now()` and `Event.timeStamp` are coarsened to 100 µs while
web security restrictions are on.

Duplicated helpers and accessors are folded together, dead code (including
the unused Rust `console`) is removed, and Rust tests that repeat passing
WPT subtests are dropped. The streams tests are one binary.
…ests

The native server prints the address it listens on, so `--serve 0` works,
and `serve_with_shutdown` takes a listener its caller bound. A second
`pre_initialize` call no longer leaves startup stuck in `Driving`, which
made every later request wait forever. Native fetch errors include their
underlying causes, `send` lives in the native and wasm backends, and
wpt-support reports a failed eval on stderr.

`serve-test-support` gains an `eventually` helper, port-0 servers,
per-test work directories and a cached `wasmtime --version`, and the
native serve tests listen on port 0. The wire-response unit tests, which
never ran, move to an integration test. Redundant serve, CLI and
WPT-support tests are merged or removed, and the examples that `showcase`
covers no longer run as tests.
CI pins every action to a commit, runs with read-only permissions, and
installs tools through `install-tool.sh`, which checks their SHA-256
digests with `scripts/check-sha256.sh`. Clippy fails on warnings.

The justfile's recipe docs are full sentences, and the WPT harness reads
its ports from the server config. `docs/builtin-development.md` describes
the rules builtins follow. The README documents
`--web-security-restrictions`, fractional serve timeouts and the safe
`#[jsmodule]` `register`.
`core_runtime::diagnostics::report_later` queues a message without running
JavaScript or allocating GC things, so a finalizer can call it.
`run_microtasks` writes the queued messages to stderr after it reports
unhandled rejections.
…wasm builds

`#[js::allow_unrooted_interior]` exempts an enum's variants as it exempts a
struct's fields, and crown treats `hash_map::Values` as a borrow, like
`hash_map::Iter`. `just check-gc` checks every workspace library and example
natively, then the runtime for wasm32-wasip2, which compiles the code only
wasm builds use. CI runs the recipe. `scripts/check-crown.sh` finds the crown
binary in `CARGO_TARGET_DIR` when that is set.
`RootedHeap` registers its cell in a per-instance slab with a free list
instead of mozjs's rooted-traceables list, so creating and dropping one takes
constant time in any drop order. That list was scanned from its newest entry
on every removal. `RootedHeap<Value>` holds any JS value, and timers keep
their callback and arguments in `RootedHeap`s.
…component

Lift and lower component-model values through wit-dylib's interpreter metadata, export and
import functions, resources, streams and futures from JavaScript, and build the runtime as a
component that a guest's world links against. Report promises lowered to futures that are
collected without settling. The export naming rules live in `component-naming`, which the
componentizer shares.

GC zeal runs over `js`, `core-runtime`, `component-model` and the builtin crates, and CI runs
it as three jobs that each take every third mode of the full set.
Componentize a JavaScript application against a WIT world: snapshot it with Wizer, link it
with the runtime, generate TypeScript declarations for its world, and ship it as an npm
package. The Linux binaries are static musl builds that use mimalloc. The componentization
suites run against a `debugmozjs` build of the runtime, and releases publish a `SHA256SUMS`
file.
@tschneidereit
tschneidereit merged commit b7bffd1 into main Oct 28, 2026
16 checks passed
@tschneidereit
tschneidereit deleted the cm-support branch October 28, 2026 16:01
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.

1 participant