jz 0.7.0 → 0.8.1
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.
- package/README.md +37 -33
- package/bench/README.md +176 -73
- package/bench/bench.svg +58 -71
- package/cli.js +12 -5
- package/dist/interop.js +1 -1
- package/dist/jz.js +1366 -1101
- package/index.js +40 -9
- package/interop.js +193 -138
- package/layout.js +29 -18
- package/module/array.js +49 -73
- package/module/collection.js +83 -25
- package/module/console.js +1 -1
- package/module/core.js +161 -15
- package/module/json.js +3 -3
- package/module/math.js +167 -117
- package/module/number.js +247 -13
- package/module/object.js +11 -5
- package/module/regex.js +8 -7
- package/module/string.js +295 -171
- package/module/typedarray.js +169 -105
- package/package.json +7 -3
- package/src/abi/string.js +40 -35
- package/src/ast.js +19 -2
- package/src/compile/analyze.js +64 -2
- package/src/compile/cse-load.js +200 -0
- package/src/compile/emit-assign.js +73 -14
- package/src/compile/emit.js +324 -34
- package/src/compile/index.js +204 -61
- package/src/compile/infer.js +8 -1
- package/src/compile/loop-divmod.js +12 -58
- package/src/compile/loop-model.js +91 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/loop-square.js +102 -0
- package/src/compile/narrow.js +180 -34
- package/src/compile/peel-stencil.js +18 -64
- package/src/compile/plan/common.js +29 -0
- package/src/compile/plan/index.js +4 -1
- package/src/compile/plan/inline.js +176 -21
- package/src/compile/plan/literals.js +93 -19
- package/src/ctx.js +51 -12
- package/src/helper-counters.js +137 -0
- package/src/ir.js +102 -13
- package/src/kind-traits.js +7 -3
- package/src/kind.js +14 -1
- package/src/op-policy.js +5 -2
- package/src/ops.js +119 -0
- package/src/optimize/index.js +1125 -136
- package/src/optimize/recurse.js +182 -0
- package/src/optimize/vectorize.js +1302 -144
- package/src/prepare/index.js +29 -12
- package/src/reps.js +4 -1
- package/src/type.js +53 -45
- package/src/wat/assemble.js +92 -9
- package/src/widen.js +21 -0
- package/src/wat/optimize.js +0 -3938
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<img src="jz.svg" alt="
|
|
1
|
+
<a href="https://dy.github.io/jz/"><img src="jz.svg" alt="JZ logo" width="120"/></a>
|
|
2
2
|
|
|
3
3
|
 [](http://npmjs.org/package/jz) [](https://github.com/dy/jz/actions/workflows/test.yml) [](https://github.com/dy/jz/actions/workflows/bench.yml)
|
|
4
4
|
|
|
5
|
-
**
|
|
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/)
|
|
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
|
-
|
|
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
|
-
- **
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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.
|
|
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).
|
|
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
|
-
|
|
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
|
|
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 —
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
465
|
+
<summary><strong>Can JZ compile itself?</strong></summary>
|
|
462
466
|
|
|
463
|
-
Yes — fully.
|
|
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
|
|
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 (
|
|
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;
|
|
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=
|
|
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
|
|
538
|
-
* [**watr**](https://www.npmjs.com/package/watr) — WAT to WASM compiler. Binary encoding, validation, and peephole optimization.
|
|
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](
|
|
544
|
-
the architecture & design rationale (NaN-boxing, type inference, native pipeline) in [
|
|
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>
|
|
551
|
+
<p align=center><a href="https://github.com/dy">dy</a> • <a href="https://github.com/krishnized/license/">ॐ</a></p>
|