Introduce NightMonkey: an ahead-of-time JS-to-Wasm compiler. - #2
Open
cfallin wants to merge 5 commits into
Open
Conversation
Member
Author
|
|
rustc 1.98 adds the OpenEmbedded targets (x86_64-oe-linux-gnu and friends) to its target list, so an x86_64-pc-linux-gnu host now has two candidate rust targets and no narrowing step chose between them: the host vendor "pc" matches neither "unknown" nor "oe", and configure died with "Don't know how to translate x86_64-pc-linux-gnu for rustc". Prefer the generic "unknown" vendor when the vendor match is inconclusive.
**NightMonkey** is an ahead-of-time JS-to-Wasm compilation tier built inside SpiderMonkey. NIGHT expands to *Nonlocal Inference with Guiding Heuristics for Types*: an optimistic whole-program type analysis guides code generation, with dynamic guards for correctness. (The night monkey, genus *Aotus*, is the only truly nocturnal monkey: it does its work in the night, before the program runs during the day.) Each JS function's bytecode is compiled to a WebAssembly function that runs alongside the runtime compiled to Wasm. There are two modes of use: - **Snapshot** (the shipping flow): the `nightmonkey` host binary drives Wizer in-process to snapshot the runtime plus loaded user program (or processes an existing Wizer snapshot), reads out JS bytecode and heap objects (such as prototype objects), and rewrites that snapshot with compiled bodies. - **In-process** (the testing flow): the JS shell compiled to Wasm runs under `wasm-jit-runner`, walks its own live heap, compiles the script tree, and injects the bodies into its running instance via runner hostcalls (`--night-inprocess`). A drop-in shell for jit-tests. NightMonkey has a two-part structure: an *optimistic static type analysis* and a *guard-based codegen backend*. The idea is that we: 1. "Predict" types statically, using a model of JavaScript semantics that is intentionally optimistic (elides corner-cases). We call this the "likelier-types analysis" (in a nod to the initial version of the analysis, the "likely-types analysis"; this one is a little better). 2. Generate an optimistic Wasm body for a given JS function bytecode body, using those predicted types. 3. Insert dynamic guards checking those assumptions, with fallbacks to a fully generic (but still compiled!) Wasm body. The *key constraint* that NightMonkey adheres to, and attempts to solve: we cannot derive type information, or any other profiling information, by observing a running program. In other words, unlike the standard JIT approach based on the "JIT hypothesis" (that a warmed-up program will reach a steady state with stable types, which we can then specialize for), we must decide any specialization we will do ahead-of-time, based on whatever analysis or heuristics we can come up with. The thing we permit ourselves in return is much more analysis time: unlike a JIT engine, we do not need to compile in milliseconds. NightMonkey performs its analysis using a whole-program, call-sensitive, points-to (heap abstraction) + callgraph analysis, over a lattice that is a hybrid of a Steensgaard (union-find-based) and capped Andersen (points-to-set/membership-based) design. The codegen using the types that come out of this analysis is then a "two-track" approach: there is one optimistic track that adheres to "type contexts" that are maximally optimal, and one fully generic track. (Earlier experiments tried to do more multiversioning, a la Static Basic Block Versioning, but that did not converge well.) As of 2026-09-04, comparing to native IonMonkey and baseline tiers, and against Wasm-hosted interpreter and weval+PBL execution: ```plain bench native-ion nat-baseline wasm-interp weval aot aot/wasm-int aot/weval weval/wasm-int ion/weval ion/aot baseline/aot richards 29205 6489 377 936 11893 31.55 12.71 2.48 31.20 2.46 0.55 deltablue 28179 6870 395 978 6678 16.91 6.83 2.48 28.81 4.22 1.03 crypto 42755 5654 714 949 15696 21.98 16.54 1.33 45.05 2.72 0.36 raytrace 58549 11458 1045 1815 11964 11.45 6.59 1.74 32.26 4.89 0.96 earley-boyer 83262 21153 1510 3982 15088 9.99 3.79 2.64 20.91 5.52 1.40 navier-stokes 43926 8269 1223 2090 24980 20.43 11.95 1.71 21.02 1.76 0.33 splay 29291 23303 5248 6853 10693 2.04 1.56 1.31 4.27 2.74 2.18 regexp 18601 7223 596 766 2484 4.17 3.24 1.29 24.28 7.49 2.91 pdfjs 95804 40738 4116 6185 24743 6.01 4.00 1.50 15.49 3.87 1.65 mandreel 73940 11619 865 1269 19545 22.60 15.40 1.47 58.27 3.78 0.59 code-load 70224 69259 37108 37005 37271 1.00 1.01 1.00 1.90 1.88 1.86 box2d 99321 22370 1896 4135 25999 13.71 6.29 2.18 24.02 3.82 0.86 react-bench 0.631 1.415 15.026 10.421 2.862 5.25 3.64 1.44 16.52 4.54 2.02 geomean 49233 14111 1527 2569 14247 8.93 5.37 1.66 18.94 3.53 1.05 (octane = Score higher-better; react-bench = ms/render lower-better; best-of-3, taskset -c 1) (ratio cols = speedup of A over B, direction-corrected for react-bench; geomean row: lane cols over octane scores only, ratio cols over all benches) ``` We can conclude that NightMonkey is ~9x faster than the Wasm interpreter on average, or ~5x faster than weval+PBL. It is nearly on par with the native baseline compiler, and within ~3.5x of the IonMonkey optimized native-code ceiling (while running within a Wasm engine). On benchmarks where type-based specialization works especially well, NightMonkey comes within ~2.5x (e.g. Richards) of native Ion. This work has been done over the past ~6 months with the use of LLM-assisted code generation (mainly Claude Opus and Fable) but with careful line-by-line review and obsessive review of analysis results and generated code. I've read through the whole implementation, checked it to the best of my ability and will continue to do so. It is "a lot" but it does pass jit-tests cleanly. This commit message and the README are fully human-authored.
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.
NightMonkey is an ahead-of-time JS-to-Wasm compilation tier built inside SpiderMonkey. NIGHT expands to Nonlocal Inference with Guiding Heuristics for Types: an optimistic whole-program type analysis guides code generation, with dynamic guards for correctness. (The night monkey, genus Aotus (really!), is the only truly nocturnal monkey: it does its work in the night, before the program runs during the day.)
Each JS function's bytecode is compiled to a WebAssembly function that runs alongside the runtime compiled to Wasm. There are two modes of use:
nightmonkeyhost binary drives Wizer in-process to snapshot the runtime plus loaded user program (or processes an existing Wizer snapshot), reads out JS bytecode and heap objects (such as prototype objects), and rewrites that snapshot with compiled bodies.wasm-jit-runner, walks its own live heap, compiles the script tree, and injects the bodies into its running instance via runner hostcalls (--night-inprocess). A drop-in shell for jit-tests.NightMonkey has a two-part structure: an optimistic static type analysis and a guard-based codegen backend. The idea is that we:
"Predict" types statically, using a model of JavaScript semantics that is intentionally optimistic (elides corner-cases). We call this the "likelier-types analysis" (in a nod to the initial version of the analysis, the "likely-types analysis"; this one is a little better).
Generate an optimistic Wasm body for a given JS function bytecode body, using those predicted types.
Insert dynamic guards checking those assumptions, with fallbacks to a fully generic (but still compiled!) Wasm body.
The key constraint that NightMonkey adheres to, and attempts to solve: we cannot derive type information, or any other profiling information, by observing a running program. In other words, unlike the standard JIT approach based on the "JIT hypothesis" (that a warmed-up program will reach a steady state with stable types, which we can then specialize for), we must decide any specialization we will do ahead-of-time, based on whatever analysis or heuristics we can come up with. The thing we permit ourselves in return is much more analysis time: unlike a JIT engine, we do not need to compile in milliseconds.
NightMonkey performs its analysis using a whole-program, call-sensitive, points-to (heap abstraction) + callgraph analysis, over a lattice that is a hybrid of a Steensgaard (union-find-based) and capped Andersen (points-to-set/membership-based) design.
The codegen using the types that come out of this analysis is then a "two-track" approach: there is one optimistic track that adheres to "type contexts" that are maximally optimal, and one fully generic track. (Earlier experiments tried to do more multiversioning, a la Static Basic Block Versioning, but that did not converge well.)
As of 2026-09-04, comparing to native IonMonkey and baseline tiers, and against Wasm-hosted interpreter and weval+PBL execution:
We can conclude that NightMonkey is ~9x faster than the Wasm interpreter on average, or ~5x faster than weval+PBL. It is nearly on par with the native baseline compiler, and within ~3.5x of the IonMonkey optimized native-code ceiling (while running within a Wasm engine). On benchmarks where type-based specialization works especially well, NightMonkey comes within ~2.5x (e.g. Richards) of native Ion.
This work has been done over the past ~6 months with the use of LLM-assisted code generation (mainly Claude Opus and Fable) but with careful line-by-line review and obsessive review of analysis results and generated code. I've read through the whole implementation, checked it to the best of my ability and will continue to do so. It is "a lot" but it does pass jit-tests cleanly. This commit message and the README are fully human-authored.