Repository navigation
Optimizations and bugfixes throughout the codebase - #40
Merged
Merged
Conversation
`Runtime::init` returns initializer-script failures as `Err` instead of exiting the process, and `starling` exits with status 1 on any error and with clap's statuses on argument errors. An imported module's compile error now propagates with its own message and location. `--async-stacks` records async parent frames for `Error.stack` in every realm, not only under a debugger. A failed `wasi:http` startup is final, so later requests fail with the same error instead of repeating the bootstrap. Cancelling one event loop's async-promise futures keeps the unowned ones any loop may wait on, and `Runtime`'s `Drop` drops the rest while the context is alive. Invocations drive their event loops through shared references, and the wasm drivers sleep with `platform::clock::sleep`. Tests share one "evaluate and read `__out`" helper, the deadline-order test registers its longest timer last, and a new end-to-end test covers a fetch with a request header the host refuses.
The mozjs update builds the WASI archives at `-O3` with SIMD128 and brings the ICU4X data provider's locale fallback. The Rust code for wasm32-wasip2 and wasm32-wasip3 is compiled with the `simd128` target feature too, and the C++ builtins with `-msimd128` whenever the Rust code has it. The wasm constructor wrapper's run-once guard is an `AtomicBool` instead of a `static mut`.
Scopes root values on one stack shared by all scopes instead of each allocating its own pages, and `js::gc::add_root_tracer` registers tracers that run along with it. Call arguments are rooted before they are converted, so a conversion that allocates can no longer invalidate the values converted before it. A class instance's private data is read from its fixed slot inline, `Stack<T>::data()` returns its guard directly, `[LegacyUnforgeable]` accessors are copied from a per-global template, and hot property keys are interned once per global. Strings convert between Rust and JS with `encoding_rs`, and `TextDecoder` and `TextEncoder` work on JS strings without a Rust `String` in between. `ArrayBuffer::transfer` moves the contents instead of copying them. The event loop indexes its tasks by readiness and deadline, events are timestamped with a coarse monotonic clock, header lookups lowercase the name once, and streamed bodies are written in batches of up to 64 KiB.
Builtin handles such as `Array`, `Map` and `Promise` convert from JS values with a class check, and the raw-pointer `ToJSVal` and `FromJSVal` implementations are removed. `EnforceRange<T>` converts `[EnforceRange]` integers, and `js::Callable` holds WebIDL callback functions, which the streams' callback dictionary members and queuing strategy `size` now use. `cast_payload` recovers a native callback's payload, `CallbackArgs::from_values` builds the arguments for calling one directly, `exception::get_pending` and `take_pending` return `Option`, and `AssociatedMemory::set` takes a rooted object. `Headers` and `URLSearchParams` share the WebIDL pair iterator steps, and `Result<Promise, E>` operations throw their error before rejecting. The class macros no longer support `#[post_init]`, `#[jsglobals]` rejects options, and macro-generated modules no longer allow unused imports. `Array::define_element`, `Object::set_immutable_prototype` and `RuntimeConfig::content_script` are removed.
Stream algorithms are passed as `AlgorithmArg`s, and native ones are called directly instead of through a JS function created per algorithm. A readable stream's controller and reader are held as WebIDL unions, controllers get their stream at construction, tee states keep their branches' controllers, byte-stream buffers and views sit in typed slots, a pipe's shutdown state is one `PipeShutdown`, and the close sentinel is a `ValueWithSize` variant. An absent size algorithm is stored as `undefined`, and internal promises and reactions are created once per global. Failures with no caller to propagate to are reported and cleared instead of being left pending or unwrapped, promise-returning operations reject by returning an error, and the async iterator clears its ongoing promise once it settles, following the annotated WebIDL steps. The rest of the code uses existing helpers instead of hand-written equivalents.
`Request` and `Response` keep their body state in one `BodyStorage`, with `HostBackedBodyOwner` and `BodyMixin` providing the body methods over it, and hold their headers and signal in bare heaps. Getters of `URL`, `Request`, `Response`, `DOMException` and `Event` return a `Ref<'_, str>` into the instance's data, and methods that never used their `scope` or never failed drop both. web-url shares its URL-encoded parsing and serialization. Across the runtime, `unsafe` blocks get SAFETY comments, stale docs are corrected, hand-written checks use existing helpers, and values are passed to `impl ToJSVal` parameters without being rooted first.
The timer globals are `#[jsglobals]` functions with a `TimerHandler` union. Event listener arguments convert as `EventListener?` and `(EventListenerOptions or boolean)`, `AbortSignal.any` takes a WebIDL sequence, and `structuredClone`'s options and `RequestInit`'s `referrerPolicy` and `signal` convert as their WebIDL types. `DOMException` gets a Rust constructor with required arguments. Error-like classes such as `DOMException` capture their stack as a `SavedFrame`, which a `stack` accessor like `Error.prototype.stack` formats on first read. `btoa` and `atob` are annotated with their spec steps. A `once` listener is removed from its target's listener list when it runs, where it used to stay in the list for good. `URLSearchParams Iterator` is no longer installed as a global, and `respondWith` locks a materialized body stream when it sends it. web-fetch and web-url no longer hold class data borrowed while running code that can reenter. The CORS-safelisted header value limits count bytes instead of UTF-8 code units, the promise from cancelling an aborted request's body is marked handled, and `new Request` checks the input request's current body for unusability. `navigator.userAgent` loses a stray parenthesis.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In the final (he says) run-up to bringing in support for custom WIT interfaces, and driven by the results of a fairly comprehensive benchmark suite, I went through the codebase with a fine comb to address performance concerns.
In doing so, I also found and addressed a number of spec adherence issues and other bugs. The result is a set of 7 commits touching most parts of the codebase. Each of them follows a reasonably coherent theme and compiles independently. I didn't run the entire test suite on each of them, so I can't say with 100% certainty that they pass individually. If the full CI run shows anything, I'll fold the fixes into the commit(s) introducing the issue(s), though.
With these applied, we're almost universally at parity or faster than the old StarlingMonkey. Here's an overview of the performance on a number of different benchmark cases, focused on the performance of runtime builtins:
ng-wasm-p3 vs cpp-wasm
All tests run in the following environment:
First, an apples-to-apples comparison, with all requests running in a fresh instance:
Mostly wins, but some losses, and some ties. The best results is probably
alloc/response-64k-x100, which applies an optimization the C++ version could also gain: it batches up small body chunks instead of sending them to the host one by one.Next up, the same C++ numbers, but this time in comparison to the WASIp3 version running with instance reuse enabled, while still only using a single instance at a time:
As expected, compute-heavy workloads don't benefit too much, but everything else is between 2.6x and 7x as fast.
Anyway, all this seems pretty nice!
(The benchmark suite itself will also land eventually. I want to whittle it down to something with a more reasonable runtime first, though.)