jz 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +37 -33
  2. package/bench/README.md +176 -73
  3. package/bench/bench.svg +58 -71
  4. package/cli.js +12 -5
  5. package/dist/interop.js +1 -1
  6. package/dist/jz.js +1156 -984
  7. package/index.js +40 -9
  8. package/interop.js +193 -138
  9. package/layout.js +29 -18
  10. package/module/array.js +29 -73
  11. package/module/collection.js +83 -25
  12. package/module/console.js +1 -1
  13. package/module/core.js +161 -15
  14. package/module/json.js +3 -3
  15. package/module/math.js +26 -3
  16. package/module/number.js +247 -13
  17. package/module/object.js +11 -5
  18. package/module/regex.js +8 -7
  19. package/module/string.js +162 -156
  20. package/module/typedarray.js +169 -105
  21. package/package.json +7 -3
  22. package/src/abi/string.js +29 -29
  23. package/src/ast.js +19 -2
  24. package/src/compile/analyze.js +64 -2
  25. package/src/compile/cse-load.js +200 -0
  26. package/src/compile/emit-assign.js +73 -14
  27. package/src/compile/emit.js +253 -23
  28. package/src/compile/index.js +198 -61
  29. package/src/compile/loop-divmod.js +12 -58
  30. package/src/compile/loop-model.js +91 -0
  31. package/src/compile/loop-square.js +102 -0
  32. package/src/compile/narrow.js +169 -32
  33. package/src/compile/peel-stencil.js +18 -64
  34. package/src/compile/plan/common.js +29 -0
  35. package/src/compile/plan/index.js +4 -1
  36. package/src/compile/plan/inline.js +176 -21
  37. package/src/compile/plan/literals.js +93 -19
  38. package/src/ctx.js +51 -12
  39. package/src/helper-counters.js +137 -0
  40. package/src/ir.js +102 -13
  41. package/src/kind-traits.js +7 -3
  42. package/src/kind.js +14 -1
  43. package/src/op-policy.js +5 -2
  44. package/src/ops.js +119 -0
  45. package/src/optimize/index.js +960 -136
  46. package/src/optimize/recurse.js +182 -0
  47. package/src/optimize/vectorize.js +1292 -144
  48. package/src/prepare/index.js +11 -7
  49. package/src/reps.js +4 -1
  50. package/src/type.js +53 -45
  51. package/src/wat/assemble.js +92 -9
  52. package/src/widen.js +21 -0
  53. package/src/wat/optimize.js +0 -3938
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- <img src="jz.svg" alt="jz logo" width="120"/>
1
+ <a href="https://dy.github.io/jz/"><img src="jz.svg" alt="JZ logo" width="120"/></a>
2
2
 
3
3
  ![stability](https://img.shields.io/badge/stability-experimental-black) [![npm](https://img.shields.io/npm/v/jz?color=black)](http://npmjs.org/package/jz) [![test](https://github.com/dy/jz/actions/workflows/test.yml/badge.svg)](https://github.com/dy/jz/actions/workflows/test.yml) [![bench](https://github.com/dy/jz/actions/workflows/bench.yml/badge.svg)](https://github.com/dy/jz/actions/workflows/bench.yml)
4
4
 
5
- **jz** (_javascript zero_) is **minimal functional JS** that compiles to performant WASM.
5
+ **JZ** (_javascript zero_) is **minimal functional JS** that compiles to performant WASM.
6
6
 
7
7
  ```js
8
8
  import jz from 'jz'
@@ -11,7 +11,7 @@ const { exports: { dist } } = jz`export let dist = (x, y) => (x*x + y*y) ** 0.5`
11
11
  dist(3, 4) // 5
12
12
  ```
13
13
 
14
- **[repl](https://dy.github.io/jz/repl/)** · **[floatbeat](https://dy.github.io/jz/floatbeat/)** · **[examples](https://dy.github.io/jz/examples/)** · **[bench](https://dy.github.io/jz/bench/)**
14
+ **[site](https://dy.github.io/jz/)** · **[repl](https://dy.github.io/jz/repl/)**<!-- · **[floatbeat](https://dy.github.io/jz/floatbeat/)**--> · **[examples](https://dy.github.io/jz/examples/)** · **[bench](https://dy.github.io/jz/bench/)**
15
15
 
16
16
 
17
17
  ## Why?
@@ -151,7 +151,7 @@ Options:
151
151
 
152
152
  ## Language
153
153
 
154
- jz is a **strict modern JS subset**. Built-in jzify transform extends support to legacy patterns.
154
+ JZ is a **strict modern JS subset**. Built-in jzify transform extends support to legacy patterns.
155
155
 
156
156
  ```
157
157
  ┌────────────────────────────────────────────────────────────────────────┐
@@ -185,10 +185,12 @@ Not supported
185
185
  <details>
186
186
  <summary><strong>What are the differences with JS?</strong></summary>
187
187
 
188
+ Each follows one rule: **JZ takes WASM/native conventions over JS edge-cases when it's free and the f64 value-precision of real computation is preserved** ([rationale](CONTRIBUTING.md#principles)).
189
+
188
190
  - **Numbers are f64**; integer-proven values (loop counters, array idx, `| 0`) are `i32` and **wrap at ±2³¹**.
189
191
  - **Strings are UTF-8 bytes** — `.length`, `charCodeAt`, indexing, `slice`, `indexOf`, regex count bytes (`"中".length` is `3`); `toUpperCase`/`toLowerCase`/`trim` are ASCII-only. UTF-8 skips UTF-16's 2× and a multi-KB Unicode case table.
190
192
  - **Objects are fixed-shape structs** — literal keys sit in fixed slots; computed writes (`o[k] = v`) fall back to a per-object hash and enumerate normally, but a dot-key added after the literal (`o.b = 2`) stays readable without enumerating (`Object.keys`/`for…in`). Prefer `Map` for heavy dynamic keys.
191
- - **Typed arrays are fixed-size** — `arr.length = n` won't compile, and out-of-bounds reads give `0`.
193
+ - **Array indices are integers, typed-array access is unchecked** — an index coerces to `i32` (asm.js-style), so a fractional or `NaN` index *truncates* (`a[1.5]`→`a[1]`, `a[NaN]`→`a[0]`) rather than yielding JS's `undefined`. A `Float64Array`/etc. is fixed-size (`arr.length = n` won't compile) and read **raw**: an out-of-bounds or negative index reads arbitrary linear memory (a large one traps), not `undefined` — pass valid in-bounds integers. Plain `[]` arrays *are* bounds-checked (`undefined` past the end / for a negative index).
192
194
  - **No GC** — call `memory.reset()` between batches; `WeakMap`/`WeakSet` wired to `Map`/`Set`.
193
195
  - **`String(number)` keeps ~9 significant digits** (`String(Math.PI)` → `"3.14159265"`), so it may not round-trip; `NaN`/`Infinity`/integers are exact. Exact shortest-form needs a multi-KB Ryū/Grisu formatter.
194
196
  - **Errors are just their message** — a caught error is the value you threw (no `.message`, not `instanceof Error`), and `null.x` yields `undefined` instead of throwing. It keeps `throw` and member reads free of object machinery and per-access checks.
@@ -200,12 +202,12 @@ Not supported
200
202
  <details>
201
203
  <summary><strong>Can I use existing npm packages or JS libraries?</strong></summary>
202
204
 
203
- Only the ones that fit the jz subset. There's no runtime, so packages touching the DOM, `async`/`Promise`, the network, or Node APIs won't compile — but pure numeric/algorithmic source does.
205
+ Only the ones that fit the JZ subset. There's no runtime, so packages touching the DOM, `async`/`Promise`, the network, or Node APIs won't compile — but pure numeric/algorithmic source does.
204
206
 
205
207
  - **Relative imports** (`./dep.js`) bundle at compile time.
206
- - **Bare specifiers** (`import { x } from "pkg"`) resolve through Node module resolution only with the `--resolve` CLI flag, or by passing the source yourself via `{ modules }`. The package's source still has to be valid jz.
208
+ - **Bare specifiers** (`import { x } from "pkg"`) resolve through Node module resolution only with the `--resolve` CLI flag, or by passing the source yourself via `{ modules }`. The package's source still has to be valid JZ.
207
209
 
208
- jz is for compiling *your* numeric/DSP/parser code, not for running the npm ecosystem.
210
+ JZ is for compiling *your* numeric/DSP/parser code, not for running the npm ecosystem.
209
211
 
210
212
  </details>
211
213
 
@@ -249,7 +251,7 @@ jz('import { parseInt } from "window"; export let f = () => parseInt("42")',
249
251
  <details>
250
252
  <summary><strong>Can I interpolate values (template literals)?</strong></summary>
251
253
 
252
- `jz` is a tagged template — interpolated values are baked into the source at compile time. Numbers and booleans inline directly; strings, arrays, and objects compile as jz literals:
254
+ `jz` is a tagged template — interpolated values are baked into the source at compile time. Numbers and booleans inline directly; strings, arrays, and objects compile as JZ literals:
253
255
 
254
256
  ```js
255
257
  jz`export let f = () => ${'hello'}.length` // 5
@@ -267,7 +269,7 @@ Interpolated functions become host calls. Non-serializable values (host objects,
267
269
  <details>
268
270
  <summary><strong>How to pass numbers, strings, arrays, objects JS ↔ WASM?</strong></summary>
269
271
 
270
- **Numbers cross natively** as `f64`/`i32`. **Heap values** — strings, arrays, objects, typed arrays — cross as NaN-boxed `f64` pointers into linear memory, allocated through the module's `_alloc`/`_clear` exports. That pointer-plus-allocator convention *is* the whole ABI (a few hundred bytes, documented in [`layout.js`](layout.js) with a worked example in [`test/abi.js`](test/abi.js)). The one shortcut: arrays of ≤ 8 elements come back as plain JS arrays via WASM multi-value.
272
+ **Numbers cross natively** as `f64`/`i32`. **Heap values** — strings, arrays, objects, typed arrays — plus the `null`/`undefined`/boolean atoms cross as the **i64 NaN-box carrier** (a `BigInt` on the JS side) holding a tagged pointer into linear memory, allocated through the module's `_alloc`/`_clear` exports. i64 rather than f64 so the NaN payload survives JSC/Safari, which canonicalizes f64 NaN bits at the boundary; numbers stay f64 (free). That carrier-plus-allocator convention *is* the whole ABI (a few hundred bytes, documented in [`layout.js`](layout.js) with a worked example in [`test/abi.js`](test/abi.js)); a per-export [`jz:i64exp`](interop.js) custom section maps which params/results ride i64, and the signature itself is self-describing for non-JS hosts. The one shortcut: arrays of ≤ 8 numeric elements come back as plain JS arrays via WASM multi-value.
271
273
 
272
274
  The `memory` codec — returned by `jz()` and by `jz/interop`'s `instantiate()` — handles both directions: it marshals arguments in, decodes pointer returns out, and turns a wasm `throw` into a real `Error`:
273
275
 
@@ -293,15 +295,15 @@ memory.read(exports.process(memory.Float64Array([1, 2, 3]))) // Float64Array [2
293
295
  </details>
294
296
 
295
297
  <details>
296
- <summary><strong>Do I need jz at runtime?</strong></summary>
298
+ <summary><strong>Do I need JZ at runtime?</strong></summary>
297
299
 
298
300
  The compiler runs at build time. At runtime you ship the `.wasm` and, at most, a small bridge — never the compiler, the parser, or a language runtime.
299
301
 
300
- - **Pure-number modules — nothing but the `.wasm`.** Instantiate with raw `WebAssembly`, zero jz dependency: `(await WebAssembly.instantiate(wasmBytes)).instance.exports.dist(3, 4)`. Compile with `{ alloc: false }` to drop the `_alloc`/`_clear` exports too.
302
+ - **Pure-number modules — nothing but the `.wasm`.** Instantiate with raw `WebAssembly`, zero JZ dependency: `(await WebAssembly.instantiate(wasmBytes)).instance.exports.dist(3, 4)`. Compile with `{ alloc: false }` to drop the `_alloc`/`_clear` exports too.
301
303
  - **Heap values (strings, arrays, objects) — the `.wasm` plus `jz/interop`.** `import { instantiate } from 'jz/interop'` adds a ~6 KB-gzipped bridge (no compiler, no parser) that builds the same `Module`+`Instance` you'd build by hand and wires the allocator and the `memory` codec from the previous question (plus WASI / `wasm:js-string` imports if the module uses them).
302
304
  - **No JavaScript host at all** — compile with `host: 'wasi'`; see the next question.
303
305
 
304
- For contrast, Rust (`wasm-bindgen`), Go (TinyGo), and C/Zig (Emscripten/WASI-libc) emit per-build generated glue and usually bundle a language runtime. jz keeps the ABI fixed and the optional bridge ~6 KB gzipped.
306
+ For contrast, Rust (`wasm-bindgen`), Go (TinyGo), and C/Zig (Emscripten/WASI-libc) emit per-build generated glue and usually bundle a language runtime. JZ keeps the ABI fixed and the optional bridge ~6 KB gzipped.
305
307
 
306
308
  </details>
307
309
 
@@ -311,7 +313,7 @@ For contrast, Rust (`wasm-bindgen`), Go (TinyGo), and C/Zig (Emscripten/WASI-lib
311
313
  There's two possible `host` targets:
312
314
 
313
315
  - **`js`** (default) — runs inside a JavaScript host (browser, Node, Deno, Bun). `jz()` and `jz/interop` wire the needed `env.*` services automatically (overridable via `opts.imports.env`), and you get full value marshaling across the boundary.
314
- - **`wasi`** — runs on a standalone WASM engine with no JavaScript (wasmtime, wasmer, deno run). jz emits WASI Preview 1, so the module needs no host shims — but there's no host-side marshaler, so heap values must be passed by hand.
316
+ - **`wasi`** — runs on a standalone WASM engine with no JavaScript (wasmtime, wasmer, deno run). JZ emits WASI Preview 1, so the module needs no host shims — but there's no host-side marshaler, so heap values must be passed by hand.
315
317
 
316
318
  Either way the `.wasm` carries at most one import namespace (none, `env`, or `wasi_snapshot_preview1`). The difference is only in how a few runtime services are serviced:
317
319
 
@@ -327,7 +329,7 @@ Either way the `.wasm` carries at most one import namespace (none, `env`, or `wa
327
329
  <details>
328
330
  <summary><strong>How does memory work?</strong></summary>
329
331
 
330
- jz uses a **bump allocator**: every heap value (string, array, object, typed array) bumps a single pointer forward — no free list, no GC. The heap starts at byte 1024 — the first 1 KB holds static data (string/array literals laid out from offset 0, plus the bump pointer itself at byte 1020 when memory is shared across threads). It grows the WASM memory automatically when full, and if the literals overflow that 1 KB the heap simply starts past them.
332
+ JZ uses a **bump allocator**: every heap value (string, array, object, typed array) bumps a single pointer forward — no free list, no GC. The heap starts at byte 1024 — the first 1 KB holds static data (string/array literals laid out from offset 0, plus the bump pointer itself at byte 1020 when memory is shared across threads). It grows the WASM memory automatically when full, and if the literals overflow that 1 KB the heap simply starts past them.
331
333
  Memory is never reclaimed implicitly — a long-running program that allocates per call grows without bound. Reset between independent batches:
332
334
 
333
335
  ```js
@@ -360,7 +362,7 @@ Pass an existing `WebAssembly.Memory` to wrap it: `jz.memory(new WebAssembly.Mem
360
362
  Each compiled module exposes two call surfaces:
361
363
 
362
364
  - **`.exports`** — the JS-wrapped surface: it marshals JS arguments into the heap and decodes pointer return values back to JS values (and turns a wasm `throw` into an `Error`). Use it by default — it's also how you hand a value from one module to another, as in the example above (the value is re-marshaled through the shared memory).
363
- - **`.instance.exports`** — the raw `WebAssembly.Instance` exports: numbers pass through untouched, and a pointer return comes back as a raw NaN-boxed handle. Decode it on the host with `memory.read(ptr)`. Don't pass a raw pointer back *in* as an argument, though — the JS↔wasm `f64` boundary canonicalizes its NaN payload and the pointer is lost; let `.exports` marshal across instead.
365
+ - **`.instance.exports`** — the raw `WebAssembly.Instance` exports: numbers pass through untouched, and a boxed return (string/array/object/atom) comes back as its raw i64 carrier (a `BigInt`). Decode it on the host with `memory.read(ptr)`, and pass it straight back *in* as an argument — the i64 carrier preserves the NaN payload across the boundary on every engine (including JSC/Safari), so no value is lost.
364
366
 
365
367
  </details>
366
368
 
@@ -373,7 +375,7 @@ No runtime, no GC — a module is your code plus a small bump allocator. The geo
373
375
  - **`alloc: false`** — omit the allocator for pure-numeric modules that never marshal heap values.
374
376
  - **`host: 'wasi'`** — no JS-host import shims (the debug `name` section is already off unless you set `names: true`).
375
377
 
376
- Hand-written WAT is still ~3–8× smaller on tight kernels — jz carries generic allocator and stdlib helpers a specialist omits; closing that gap is ongoing. Size budgets are gated in CI alongside speed ([full table](bench/README.md)).
378
+ Hand-written WAT is still ~3–8× smaller on tight kernels — JZ carries generic allocator and stdlib helpers a specialist omits; closing that gap is ongoing. Size budgets are gated in CI alongside speed ([full table](bench/README.md)).
377
379
 
378
380
  </details>
379
381
 
@@ -381,7 +383,7 @@ Hand-written WAT is still ~3–8× smaller on tight kernels — jz carries gener
381
383
  <details>
382
384
  <summary><strong>Which optimizations are applied?</strong></summary>
383
385
 
384
- Ordinary JS is already fast — jz infers the right machine type for your numbers, so you write plain JS. What it does, all on at the default `optimize: 2` (each line is also the habit that triggers it):
386
+ Ordinary JS is already fast — JZ infers the right machine type for your numbers, so you write plain JS. What it does, all on at the default `optimize: 2` (each line is also the habit that triggers it):
385
387
 
386
388
  - **Type narrowing** — parameters/results pinned to `i32`/`f64`/bool/typed-array elements from their call sites, off the boxed path. A `Float64Array`/`Int32Array` is direct memory access; a plain `[]` works too, with a little more overhead.
387
389
  - **Escape analysis & arena rewind** — fixed-shape arrays/objects/typed-arrays become WASM locals; scratch a function doesn't return is freed on exit (no manual cleanup).
@@ -396,7 +398,7 @@ Codegen also adapts to the target: `host: 'js'` lowers `console`/timers to tiny
396
398
  <details>
397
399
  <summary><strong>How do I inspect or debug the output?</strong></summary>
398
400
 
399
- - **Semantics** — valid jz is valid JS: run the same source under Node and diff results (mind the [documented divergences](#faq)); `console.log` works inside compiled modules too.
401
+ - **Semantics** — valid JZ is valid JS: run the same source under Node and diff results (mind the [documented divergences](#faq)); `console.log` works inside compiled modules too.
400
402
  - **Codegen** — `jz program.js --wat` (API: `compile(src, { wat: true })`) shows the emitted WAT: grep `v128` to confirm a loop vectorized, `__dyn_get`/`__ext_call` to spot dynamic fallbacks inference couldn't narrow. `--why-not-simd` (API: `whyNotSimd: true`) goes further — for each loop the auto-vectorizer declined it reports the first blocking op (e.g. `i32.rem_s: no lane-pure SIMD mapping`), so you don't have to grep the WAT to find what's one op away.
401
403
  - **Dynamic fallbacks** — compile with `strict: true` to turn every fallback (`obj[k]`, `for-in`, unknown receiver method) into a compile error pointing at the site.
402
404
  - **Profiling** — `--names` (API: `names: true`) emits a wasm `name` section so DevTools profilers and disassemblers show real function names; `--stats` (API: the `profile` sink) collects per-stage compile timings.
@@ -405,7 +407,7 @@ Codegen also adapts to the target: `host: 'js'` lowers `console`/timers to tiny
405
407
  </details>
406
408
 
407
409
  <details>
408
- <summary><strong>How does jz work?</strong></summary>
410
+ <summary><strong>How does JZ work?</strong></summary>
409
411
 
410
412
  A source string flows through six stages into wasm bytes — no IR leaves the process, the whole thing is one pass per `compile()`:
411
413
 
@@ -429,7 +431,7 @@ Each stage lives in its own place: parsing in [`subscript`](https://github.com/d
429
431
  <details>
430
432
  <summary><strong>Why no type annotations?</strong></summary>
431
433
 
432
- Because `let x: i32` isn't valid JS — annotations would break the promise that valid jz runs and tests as plain JS. So jz reads the types from signals you already write:
434
+ Because `let x: i32` isn't valid JS — annotations would break the promise that valid JZ runs and tests as plain JS. So JZ reads the types from signals you already write:
433
435
 
434
436
  ```js
435
437
  export let bits = (a, b) => a | b // i32 — a bitwise op pins both operands
@@ -442,7 +444,7 @@ Literals (`0` vs `0.5`), operators (`|` `<<` `&` ⇒ i32), and how a value is us
442
444
 
443
445
 
444
446
  <details>
445
- <summary><strong>Is jz production-ready?</strong></summary>
447
+ <summary><strong>Is JZ production-ready?</strong></summary>
446
448
 
447
449
  It's **experimental** (pre-1.0) — the supported subset and the wasm ABI may still change, so pin a version and re-test on upgrade. What's solid: every push runs the full test suite, the test262 conformance subset, the benchmark gate, and the self-host build in CI, so regressions surface immediately.
448
450
 
@@ -454,13 +456,15 @@ It's **experimental** (pre-1.0) — the supported subset and the wasm ABI may st
454
456
 
455
457
  Yes. The compiler is pure and synchronous (no I/O — you hand it the sources), so it runs anywhere JavaScript does — main thread, a Web Worker, or a build step — and compiling a kernel takes single-digit-to-tens of milliseconds, fast enough to do on the fly. The `.wasm` it produces is just a module: instantiate it in any WebAssembly host — browser main thread, Web/Service Worker, Node/Deno/Bun, or a standalone engine.
456
458
 
459
+ Because compiling is that cheap, WASM becomes a *live medium*, not just a build artifact: hot-swap a compute kernel without a reload, recompile user-supplied source on the fly, or treat compiling as part of scripting — not a deploy step.
460
+
457
461
  </details>
458
462
 
459
463
 
460
464
  <details>
461
- <summary><strong>Can jz compile itself?</strong></summary>
465
+ <summary><strong>Can JZ compile itself?</strong></summary>
462
466
 
463
- Yes — fully. jz compiles its own **entire** source to `dist/jz.wasm`: the whole pipeline (parse → jzify → prepare → compile → encode) runs inside WASM, taking a source string and returning wasm bytes with no host help. In other words, `dist/jz.wasm` is jz compiled by jz.
467
+ Yes — fully. JZ compiles its own **entire** source to `dist/jz.wasm`: the whole pipeline (parse → jzify → prepare → compile → encode) runs inside WASM, taking a source string and returning wasm bytes with no host help. In other words, `dist/jz.wasm` is JZ compiled by JZ.
464
468
 
465
469
  `npm run test:self` is the CI gate — it builds `dist/jz.wasm`, then round-trips real programs through the in-wasm compiler and runs their output, proving the wasm-hosted compiler produces working modules.
466
470
 
@@ -468,7 +472,7 @@ Yes — fully. jz compiles its own **entire** source to `dist/jz.wasm`: the whol
468
472
 
469
473
 
470
474
  <details>
471
- <summary><strong>Can I compile jz to C?</strong></summary>
475
+ <summary><strong>Can I compile JZ to C?</strong></summary>
472
476
 
473
477
  Yes, via [wasm2c](https://github.com/WebAssembly/wabt/blob/main/wasm2c) or [w2c2](https://github.com/turbolent/w2c2):
474
478
 
@@ -479,9 +483,9 @@ wasm2c program.opt.wasm -o program.c
479
483
  cc -O3 program.c -o program
480
484
  ```
481
485
 
482
- The full native pipeline (jz → `wasm-opt -O3` → `wasm2c` → `clang -O3 -flto` + PGO) lowers to standalone native code that beats V8 on the watr example corpus (19/21 wins, 2 ties, M4 Max). Details and the regression gate live in [`scripts/native/README.md`](scripts/native/README.md).
486
+ The full native pipeline (JZ → `wasm-opt -O3` → `wasm2c` → `clang -O3 -flto` + PGO) lowers to standalone native code that beats V8 on the watr example corpus (19/21 wins, 2 ties, M4 Max). Details and the regression gate live in [`scripts/native/README.md`](scripts/native/README.md).
483
487
 
484
- [Static Hermes](https://github.com/facebook/hermes) reaches native the same way from the other end — full JS through C/LLVM, with sound type annotations for speed; jz keeps the source plain JS and gets its types by inference.
488
+ [Static Hermes](https://github.com/facebook/hermes) reaches native the same way from the other end — full JS through C/LLVM, with sound type annotations for speed; JZ keeps the source plain JS and gets its types by inference.
485
489
 
486
490
  </details>
487
491
 
@@ -490,7 +494,7 @@ The full native pipeline (jz → `wasm-opt -O3` → `wasm2c` → `clang -O3 -flt
490
494
 
491
495
  ## Performance
492
496
 
493
- <img src="bench/bench.svg?v=5" alt="jz vs alternatives — geometric-mean speed across the bench corpus, every rival compiled to WebAssembly and run in V8 (apples-to-apples); native C is the lone reference, jz = 1.00× baseline" width="720">
497
+ <img src="bench/bench.svg?v=8" alt="JZ vs alternatives — geometric-mean speed across the bench corpus, every rival compiled to WebAssembly and run in V8 (apples-to-apples); native C is the lone reference, JZ = 1.00× baseline" width="720">
494
498
 
495
499
 
496
500
  See [bench →](https://dy.github.io/jz/bench/)
@@ -534,14 +538,14 @@ Small & fast JS subset → full JS spec & bundled engine:
534
538
 
535
539
  ## Built with
536
540
 
537
- * [**subscript**](https://github.com/dy/subscript) — JS parser. Minimal, extensible, builds the exact AST jz needs. Jessie subset keeps the grammar small and deterministic.
538
- * [**watr**](https://www.npmjs.com/package/watr) — WAT to WASM compiler. Binary encoding, validation, and peephole optimization. jz emits WAT text, watr turns it into valid `.wasm`.
541
+ * [**subscript**](https://github.com/dy/subscript) — JS parser. Minimal, extensible, builds the exact AST JZ needs. Jessie subset keeps the grammar small and deterministic.
542
+ * [**watr**](https://www.npmjs.com/package/watr) — WAT to WASM compiler. Binary encoding, validation, and peephole optimization. JZ emits WAT text, watr turns it into valid `.wasm`.
539
543
 
540
544
 
541
545
  ## Contributing
542
546
 
543
- Setup, code layout, and the bench/perf invariants are in [CONTRIBUTING.md](docs/CONTRIBUTING.md);
544
- the architecture & design rationale (NaN-boxing, type inference, native pipeline) in [DESIGN.md](docs/DESIGN.md).
547
+ Setup, code layout, and the bench/perf invariants are in [CONTRIBUTING.md](CONTRIBUTING.md);
548
+ the architecture & design rationale (NaN-boxing, type inference, native pipeline) in [research.md](.work/research.md).
545
549
 
546
550
 
547
- <p align=center>MIT • <a href="https://github.com/krishnized/license/">ॐ</a></p>
551
+ <p align=center><a href="https://github.com/dy">dy</a> • <a href="https://github.com/krishnized/license/">ॐ</a></p>