jz 0.5.1 → 0.7.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.
- package/README.md +315 -318
- package/bench/README.md +369 -0
- package/bench/bench.svg +102 -0
- package/cli.js +104 -30
- package/dist/interop.js +1 -0
- package/dist/jz.js +6024 -0
- package/index.d.ts +126 -0
- package/index.js +362 -84
- package/interop.js +159 -186
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +184 -0
- package/module/array.js +377 -176
- package/module/collection.js +639 -145
- package/module/console.js +55 -43
- package/module/core.js +264 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +551 -187
- package/module/number.js +327 -60
- package/module/object.js +474 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +117 -0
- package/module/string.js +621 -226
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +459 -73
- package/package.json +58 -14
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +29 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +824 -0
- package/src/compile/analyze.js +1600 -0
- package/src/compile/emit-assign.js +411 -0
- package/src/compile/emit.js +3512 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +778 -128
- package/src/{infer.js → compile/infer.js} +62 -98
- package/src/compile/loop-divmod.js +155 -0
- package/src/{narrow.js → compile/narrow.js} +409 -106
- package/src/compile/peel-stencil.js +261 -0
- package/src/compile/plan/advise.js +370 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +122 -0
- package/src/compile/plan/inline.js +682 -0
- package/src/compile/plan/literals.js +1199 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +649 -0
- package/src/compile/program-facts.js +423 -0
- package/src/ctx.js +220 -64
- package/src/ir.js +589 -172
- package/src/kind-traits.js +132 -0
- package/src/kind.js +524 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1432 -473
- package/src/optimize/vectorize.js +4660 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +649 -205
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +116 -0
- package/src/resolve.js +12 -3
- package/src/static.js +208 -0
- package/src/type.js +651 -0
- package/src/{assemble.js → wat/assemble.js} +275 -55
- package/src/wat/optimize.js +3938 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/bench/README.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# jz benchmark suite
|
|
2
|
+
|
|
3
|
+
Cross-target workload suite for jz codegen quality. Each benchmark is a case
|
|
4
|
+
folder under `bench/`:
|
|
5
|
+
|
|
6
|
+
```txt
|
|
7
|
+
bench/<case>/<case>.js JavaScript source used by V8, jz, etc.
|
|
8
|
+
bench/<case>/<case>.c optional native C baseline
|
|
9
|
+
bench/<case>/<case>.rs optional Rust baseline
|
|
10
|
+
bench/<case>/<case>.go optional Go baseline
|
|
11
|
+
bench/<case>/<case>.zig optional Zig baseline
|
|
12
|
+
bench/<case>/<case>.as.ts optional AssemblyScript baseline
|
|
13
|
+
bench/<case>/<case>.npy.py optional NumPy baseline
|
|
14
|
+
bench/<case>/<case>.wat optional hand-written WAT baseline
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Every case prints the same line:
|
|
18
|
+
|
|
19
|
+
```txt
|
|
20
|
+
median_us=<int> checksum=<u32> samples=<int> stages=<int> runs=<int>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The orchestrator runs selected cases against selected targets and flags checksum
|
|
24
|
+
drift as `DIFF`.
|
|
25
|
+
|
|
26
|
+
## Run
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm run bench
|
|
30
|
+
node bench/bench.mjs --targets=nat,rust,go,numpy,v8,jz
|
|
31
|
+
node bench/bench.mjs --targets=jz --cases=biquad,mat4,poly,bitwise
|
|
32
|
+
node bench/bench.mjs --targets=v8,deno,bun,spidermonkey,graaljs
|
|
33
|
+
node bench/bench.mjs --cases=biquad,fft,synth,bytebeat,blur
|
|
34
|
+
node bench/bench.mjs biquad
|
|
35
|
+
node bench/bench.mjs mat4 --targets=nat,v8,jz
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Cases
|
|
39
|
+
|
|
40
|
+
| id | purpose |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| [`biquad`](biquad/biquad.js) | DSP filter cascade; dense f64 typed-array loop and offset-fusion baseline |
|
|
43
|
+
| [`mat4`](mat4/mat4.js) | fixed-size typed-array loops; exposes loop unrolling and offset fusion gaps |
|
|
44
|
+
| [`poly`](poly/poly.js) | same `sum` called with `Float64Array` and `Int32Array`; exposes bimorphic typed-array dispatch |
|
|
45
|
+
| [`bitwise`](bitwise/bitwise.js) | long `i32` narrowing chains with `Math.imul`, shifts, and unsigned conversion |
|
|
46
|
+
| [`tokenizer`](tokenizer/tokenizer.js) | string-heavy scan with `charCodeAt`, branches, and integer token accumulation |
|
|
47
|
+
| [`callback`](callback/callback.js) | `Array.map` callback path; exposes closure/call-indirect and array allocation cost |
|
|
48
|
+
| [`aos`](aos/aos.js) | array-of-object rows copied into typed arrays; exposes schema-slot read cost |
|
|
49
|
+
| [`mandelbrot`](mandelbrot/mandelbrot.js) | 256×256 escape-time iteration; dense f64 hot loop with conditional break and i32 counter |
|
|
50
|
+
| [`json`](json/json.js) | runtime `JSON.parse` of one module-local source plus heterogeneous object/array walk with a stable inferred JSON shape |
|
|
51
|
+
| [`sort`](sort/sort.js) | in-place heapsort over a typed array; exposes call-heavy nested loops and typed-array index propagation |
|
|
52
|
+
| [`crc32`](crc32/crc32.js) | table-driven CRC-32 over a mutable byte buffer; exposes integer narrowing and typed-array parameter propagation |
|
|
53
|
+
| [`dotprod`](dotprod/dotprod.js) | multiply-accumulate reduction; jz reassociates to 4 SIMD accumulators, beating strict-fp serial native |
|
|
54
|
+
| [`bytebeat`](bytebeat/bytebeat.js) | classic integer "bytebeat" one-line-song synthesis to 8-bit PCM; pure i32, bit-exact everywhere |
|
|
55
|
+
| [`fft`](fft/fft.js) | radix-2 Cooley–Tukey FFT over a transcendental-free twiddle table; the canonical numeric/audio kernel |
|
|
56
|
+
| [`synth`](synth/synth.js) | minisynth audio pipeline — polynomial oscillator + ADSR + biquad per sample; loop-carried f64 |
|
|
57
|
+
| [`blur`](blur/blur.js) | separable box blur on an RGBA8 image; integer stencil with edge clamp, the canonical image pipeline |
|
|
58
|
+
| [`watr`](watr/watr.js) | watr's WAT-to-wasm compiler on a small WAT corpus; compares jz-compiled compiler code with raw V8 |
|
|
59
|
+
| [`jessie`](jessie/jessie.js) | the subscript/jessie JS parser over a realistic source corpus; branch-, allocation- and recursion-heavy front-end work |
|
|
60
|
+
| [`jz`](jz/jz.js) | the jz compiler itself (scripts/self.js pipeline) compiling three small programs at L2 — the self-host row runs jz.wasm compiling JavaScript; output bytes are checksummed so the parity gate doubles as a determinism proof |
|
|
61
|
+
|
|
62
|
+
The `watr`, `jessie`, and `jz` rows are self-referential — jz (or its deps)
|
|
63
|
+
compiling code. They stay runnable (`--cases=watr,jessie,jz`) and gated in
|
|
64
|
+
`test/bench.js`, but are hidden from the bench page and the headline geomean SVG:
|
|
65
|
+
they answer a different question (compiler throughput on itself) from the
|
|
66
|
+
cross-language kernel comparison the page is about.
|
|
67
|
+
|
|
68
|
+
Native rows for `json` are fixed-source references, not semantic equivalents
|
|
69
|
+
of JavaScript `JSON.parse`: C/Rust/Zig hand-parse the known schema from a
|
|
70
|
+
compile-time string, and Zig may constant-fold the whole parse+walk under
|
|
71
|
+
ReleaseFast; Go uses `encoding/json` but still unmarshals the same compile-time
|
|
72
|
+
string. The jz row parses a `let` source at runtime so `JSON.parse` is not
|
|
73
|
+
compile-time folded, while the compiler can still specialize the stable literal
|
|
74
|
+
shape. External unknown-shape JSON still uses the generic runtime parser.
|
|
75
|
+
|
|
76
|
+
Native-language rows are intentionally per case. NumPy rows are used only
|
|
77
|
+
where a vectorized array implementation is a meaningful Python convention.
|
|
78
|
+
|
|
79
|
+
Pure interpreters (CPython, QuickJS, Hermes, Javy's embedded-QuickJS) are not
|
|
80
|
+
benchmarked: this suite measures compiled-code quality, and an interpreter row
|
|
81
|
+
inflates jz's lead for free without answering a question a user actually has.
|
|
82
|
+
The headline field is wasm-vs-wasm, apples-to-apples: jz against the other
|
|
83
|
+
languages compiled to the same target — WebAssembly run in V8 — i.e. Rust, Go,
|
|
84
|
+
and C (`wasm32-wasi`), AssemblyScript, and Porffor, plus the JS JITs it replaces
|
|
85
|
+
(V8/Bun/Deno/SpiderMonkey/GraalJS). Native C/Rust/Zig/Go and NumPy's vectorized
|
|
86
|
+
C are kept only as a speed-of-light reference, never presented as the wasm peer.
|
|
87
|
+
|
|
88
|
+
### Parity classes
|
|
89
|
+
|
|
90
|
+
The `parity` column is `ok` when the run's checksum matches the most common
|
|
91
|
+
checksum across all targets, `DIFF` when it diverges in a way that suggests
|
|
92
|
+
a bug, and `fma` when the divergence is the documented FMA-fusion class.
|
|
93
|
+
The Go arm64 backend auto-fuses `a*b + c` to `FMADDD` (mandatory in ARMv8,
|
|
94
|
+
no compiler flag to disable it), which alters bit-level rounding on
|
|
95
|
+
recurrence-style loops like `biquad`. Result is still IEEE-754
|
|
96
|
+
correctly-rounded; cascade is the same algorithm.
|
|
97
|
+
|
|
98
|
+
## Targets
|
|
99
|
+
|
|
100
|
+
| id | what it measures |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `nat` | clang `-O3` native C baseline, when a matching C workload exists |
|
|
103
|
+
| `natgcc` | gcc `-O3`, when real gcc is installed |
|
|
104
|
+
| `rust` | Rust `rustc -C opt-level=3 -C target-cpu=native` — native baseline; the headline rival is `rust-wasm` |
|
|
105
|
+
| `go` | Go native compiler — native baseline; the headline rival is `go-wasm` |
|
|
106
|
+
| `zig` | Zig `build-exe -O ReleaseFast` — native baseline |
|
|
107
|
+
| `numpy` | vectorized NumPy, when a matching `.npy.py` exists |
|
|
108
|
+
| `v8` | raw JavaScript on Node/V8 |
|
|
109
|
+
| `deno` | raw JavaScript on Deno/V8 |
|
|
110
|
+
| `bun` | raw JavaScript on Bun/JavaScriptCore |
|
|
111
|
+
| `spidermonkey` | raw JavaScript on SpiderMonkey shell (`spidermonkey`, `sm`, `js128`, `js115`, `js102`, or `js`) |
|
|
112
|
+
| `shermes` | Static Hermes — JS AOT-compiled to a native binary (`shermes -O`), when installed |
|
|
113
|
+
| `graaljs` | raw JavaScript on GraalJS |
|
|
114
|
+
| `jz` | jz output with host imports for timing/logging (measures wasm size without WASI console/perf bloat) |
|
|
115
|
+
| `as` | AssemblyScript `asc -O3 --runtime stub`, when a matching `.as.ts` exists |
|
|
116
|
+
| `rust-wasm` | Rust → `wasm32-wasip1` (`rustc --target wasm32-wasip1 -C opt-level=3`), run in node's V8 — the apples-to-apples Rust rival |
|
|
117
|
+
| `go-wasm` | Go → `wasm32-wasip1` (`GOOS=wasip1 GOARCH=wasm go build`), run in node's V8 |
|
|
118
|
+
| `c-wasm` | C → `wasm32-wasi` (`zig cc -target wasm32-wasi -O3`, no emcc/wasi-sdk needed), run in node's V8 |
|
|
119
|
+
| `jz-wasmtime` | jz output on wasmtime |
|
|
120
|
+
| `jz-w2c` | jz wasm translated by wabt `wasm2c`, then clang `-O3` |
|
|
121
|
+
| `wat` | hand-written WAT baseline when a case provides `run-wat.mjs` |
|
|
122
|
+
| `porf` | Porffor (`porf run`) when installed |
|
|
123
|
+
| `jawsm` | jawsm when installed |
|
|
124
|
+
|
|
125
|
+
The `size` column reports the artifact size each target measures: the
|
|
126
|
+
compiled native binary for `nat`/`rust`/`go`/`zig`, the produced
|
|
127
|
+
`.wasm` for `jz`/`as`/`rust-wasm`/`go-wasm`/`c-wasm`/hand-WAT/jawsm/`jz-w2c` (the C-translated
|
|
128
|
+
executable), or the source file for raw-JS interpreters where there is no
|
|
129
|
+
compile step. For source files with imports, raw-JS size is only the entry file;
|
|
130
|
+
jz size is the bundled wasm artifact.
|
|
131
|
+
|
|
132
|
+
Note the preset trade: these speed tables build jz at the default
|
|
133
|
+
(`optimize: 2`, speed-leaning — loop unroll + SIMD lift), while the dedicated
|
|
134
|
+
size comparison (`npm run bench:size`) builds with `optimize: 'size'` —
|
|
135
|
+
typically ~2× smaller on the same kernel (biquad: 3.5 kB default vs 1.6 kB
|
|
136
|
+
size-tuned). Pick the preset for what you ship; the two tables are not the
|
|
137
|
+
same artifact.
|
|
138
|
+
|
|
139
|
+
Runtime command overrides:
|
|
140
|
+
|
|
141
|
+
`watr` is intentionally compiled by jz with a size-oriented pass config
|
|
142
|
+
(`watr:false`, `smallConstForUnroll:false`): on a large compiler bundle, the
|
|
143
|
+
default WAT-level optimizer and small-loop unroll grow code more than they help.
|
|
144
|
+
This keeps the target measuring the best current jz artifact for that workload.
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
BUN_BIN=/path/to/bun \
|
|
148
|
+
DENO_BIN=/path/to/deno \
|
|
149
|
+
SPIDERMONKEY_BIN=/path/to/js \
|
|
150
|
+
SHERMES_BIN=/path/to/shermes \
|
|
151
|
+
GRAALJS_BIN=/path/to/graaljs \
|
|
152
|
+
PORF_BIN=/path/to/porf \
|
|
153
|
+
node bench/bench.mjs --targets=bun,deno,spidermonkey,shermes,graaljs,porf
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Reading the numbers (darwin/arm64, M-class)
|
|
157
|
+
|
|
158
|
+
Snapshots from `node bench/bench.mjs --targets=v8,jz,as`.
|
|
159
|
+
Where jz lands relative to **V8 raw JS** (`v8/node`) and **AssemblyScript**
|
|
160
|
+
(`as`) is the headline comparison. Native and hand-WAT rows are shown where
|
|
161
|
+
available from earlier runs.
|
|
162
|
+
|
|
163
|
+
### biquad — f64 typed-array DSP cascade
|
|
164
|
+
|
|
165
|
+
| target | median | ×v8 | size | parity |
|
|
166
|
+
| --- | ---: | ---: | ---: | --- |
|
|
167
|
+
| **jz → V8 wasm** | **4.64 ms** | **1.94×** | **3.4 kB** | **ok** |
|
|
168
|
+
| AssemblyScript (asc -O3 --runtime stub) | 6.51 ms | 1.38× | 1.9 kB | ok |
|
|
169
|
+
| V8 (node) raw JS | 8.98 ms | 1.00× | 3.2 kB | ok |
|
|
170
|
+
| native C (clang -O3) | 3.86 ms | 2.32× | 32.7 kB | ok |
|
|
171
|
+
| hand-WAT → V8 wasm | 6.49 ms | 1.90× | 767 B | ok |
|
|
172
|
+
|
|
173
|
+
jz beats V8 raw JS by 2.1× and AS by 1.4×. The typed-array scalarization,
|
|
174
|
+
offset-fusion, and base-hoisting pipeline delivers dense-f64 loop codegen
|
|
175
|
+
that matches the hand-WAT floor.
|
|
176
|
+
|
|
177
|
+
### mat4 — fixed-size Float64Array multiply
|
|
178
|
+
|
|
179
|
+
| target | median | ×v8 | size | parity |
|
|
180
|
+
| --- | ---: | ---: | ---: | --- |
|
|
181
|
+
| **jz → V8 wasm** | **1.49 ms** | **5.75×** | **3.1 kB** | **ok** |
|
|
182
|
+
| AssemblyScript (asc -O3 --runtime stub) | 6.71 ms | 1.28× | 1.6 kB | ok |
|
|
183
|
+
| V8 (node) raw JS | 8.57 ms | 1.00× | 1.2 kB | ok |
|
|
184
|
+
| native C (clang -O3) | 1.97 ms | 4.35× | 32.8 kB | ok |
|
|
185
|
+
| hand-WAT → V8 wasm | 8.12 ms | 1.47× | 414 B | ok |
|
|
186
|
+
|
|
187
|
+
jz is 5.9× faster than V8 raw JS and 4.6× faster than AS. The scalarized
|
|
188
|
+
SIMD hot path (unrolled 4×4 multiply) is the win; V8's JIT doesn't vectorize
|
|
189
|
+
this from JS source.
|
|
190
|
+
|
|
191
|
+
### poly — bimorphic typed-array reduce
|
|
192
|
+
|
|
193
|
+
| target | median | ×v8 | size | parity |
|
|
194
|
+
| --- | ---: | ---: | ---: | --- |
|
|
195
|
+
| **jz → V8 wasm** | **0.13 ms** | **12.67×** | **1.4 kB** | **ok** |
|
|
196
|
+
| AssemblyScript (asc -O3 --runtime stub) | 0.81 ms | 2.07× | 1.3 kB | ok |
|
|
197
|
+
| V8 (node) raw JS | 1.67 ms | 1.00× | 1014 B | ok |
|
|
198
|
+
|
|
199
|
+
jz is 12.9× faster than V8 raw JS and 5.9× faster than AS. The bimorphic
|
|
200
|
+
`sum` (called with both `Float64Array` and `Int32Array`) stays on typed
|
|
201
|
+
paths without falling back to generic dispatch.
|
|
202
|
+
|
|
203
|
+
### bitwise — i32 narrowing chains (`Math.imul`, shifts, FNV-1a)
|
|
204
|
+
|
|
205
|
+
| target | median | ×v8 | size | parity |
|
|
206
|
+
| --- | ---: | ---: | ---: | --- |
|
|
207
|
+
| **jz → V8 wasm** | **1.01 ms** | **3.84×** | **1.2 kB** | **ok** |
|
|
208
|
+
| V8 (node) raw JS | 3.87 ms | 1.00× | 1005 B | ok |
|
|
209
|
+
| AssemblyScript (asc -O3 --runtime stub) | 9.15 ms | 0.42× | 1.5 kB | ok |
|
|
210
|
+
| native C (clang -O3) | 0.95 ms | 4.08× | 32.9 kB | ok |
|
|
211
|
+
| hand-WAT → V8 wasm | 3.59 ms | 1.11× | 355 B | ok |
|
|
212
|
+
|
|
213
|
+
jz is 4.0× faster than V8 raw JS and 8.8× faster than AS. The i32 hot path
|
|
214
|
+
(`Math.imul`, `|0`, `>>>0`) now lowers to raw `i32` ops without NaN-box
|
|
215
|
+
overhead on every operation.
|
|
216
|
+
|
|
217
|
+
### tokenizer — string scan with `charCodeAt` and integer accumulation
|
|
218
|
+
|
|
219
|
+
| target | median | ×v8 | size | parity |
|
|
220
|
+
| --- | ---: | ---: | ---: | --- |
|
|
221
|
+
| **jz → V8 wasm** | **0.06 ms** | **2.26×** | **2.2 kB** | **ok** |
|
|
222
|
+
| AssemblyScript (asc -O3 --runtime stub) | 0.06 ms | 2.15× | 1.6 kB | ok |
|
|
223
|
+
| V8 (node) raw JS | 0.13 ms | 1.00× | 2.0 kB | ok |
|
|
224
|
+
|
|
225
|
+
jz is 2.4× faster than V8 raw JS and now edges out AS by ~1.2× on this
|
|
226
|
+
`charCodeAt`-heavy scan. Both are well ahead of V8.
|
|
227
|
+
|
|
228
|
+
### callback — `Array.map` closure + i32 fold
|
|
229
|
+
|
|
230
|
+
| target | median | ×v8 | size | parity |
|
|
231
|
+
| --- | ---: | ---: | ---: | --- |
|
|
232
|
+
| **jz → V8 wasm** | **0.26 ms** | **2.37×** | **1.4 kB** | **ok** |
|
|
233
|
+
| V8 (node) raw JS | 0.62 ms | 1.00× | 1.3 kB | ok |
|
|
234
|
+
| AssemblyScript (asc -O3 --runtime stub) | 0.80 ms | 0.78× | 1.9 kB | ok |
|
|
235
|
+
|
|
236
|
+
jz is 2.3× faster than V8 raw JS and 2.9× faster than AS. Closure +
|
|
237
|
+
`Array.map` lowers to a preallocated typed loop with no per-iteration alloc.
|
|
238
|
+
V8's JIT does not inline the closure across the `map` boundary.
|
|
239
|
+
|
|
240
|
+
### aos — array-of-object rows to typed arrays
|
|
241
|
+
|
|
242
|
+
| target | median | ×v8 | size | parity |
|
|
243
|
+
| --- | ---: | ---: | ---: | --- |
|
|
244
|
+
| **jz → V8 wasm** | **0.69 ms** | **1.93×** | **1.8 kB** | **ok** |
|
|
245
|
+
| V8 (node) raw JS | 1.33 ms | 1.00× | 1.1 kB | ok |
|
|
246
|
+
| AssemblyScript (asc -O3 --runtime stub) | 1.40 ms | 0.95× | 2.1 kB | ok |
|
|
247
|
+
|
|
248
|
+
jz is 1.9× faster than V8 raw JS and 2.0× faster than AS. Schema-slot
|
|
249
|
+
reads are direct field offsets; the gap is small because the workload is
|
|
250
|
+
memory-bound.
|
|
251
|
+
|
|
252
|
+
### mandelbrot — 256×256 escape-time iteration
|
|
253
|
+
|
|
254
|
+
| target | median | ×v8 | size | parity |
|
|
255
|
+
| --- | ---: | ---: | ---: | --- |
|
|
256
|
+
| AssemblyScript (asc -O3 --runtime stub) | 8.90 ms | 1.11× | 1.3 kB | ok |
|
|
257
|
+
| **jz → V8 wasm** | **8.45 ms** | **1.17×** | **1.4 kB** | **ok** |
|
|
258
|
+
| V8 (node) raw JS | 9.90 ms | 1.00× | 1.8 kB | ok |
|
|
259
|
+
|
|
260
|
+
jz is 1.1× faster than V8 raw JS and ties AS. The dense f64 hot loop with
|
|
261
|
+
conditional break compacts to 1.0 kB — the smallest wasm in the suite.
|
|
262
|
+
|
|
263
|
+
### json — runtime `JSON.parse` plus stable-shape walk
|
|
264
|
+
|
|
265
|
+
| target | median | ×v8 | size | parity |
|
|
266
|
+
| --- | ---: | ---: | ---: | --- |
|
|
267
|
+
| **jz → V8 wasm** | **0.21 ms** | **1.28×** | **9.9 kB** | **ok** |
|
|
268
|
+
| V8 (node) raw JS | 0.27 ms | 1.00× | 1.2 kB | ok |
|
|
269
|
+
|
|
270
|
+
jz is 1.3× faster than V8 raw JS. The runtime parser is specialized to the
|
|
271
|
+
inferred JSON shape; AS is skipped because it cannot parse JSON at runtime.
|
|
272
|
+
|
|
273
|
+
### sort — in-place heapsort over typed array
|
|
274
|
+
|
|
275
|
+
| target | median | ×v8 | size | parity |
|
|
276
|
+
| --- | ---: | ---: | ---: | --- |
|
|
277
|
+
| **jz → V8 wasm** | **4.65 ms** | **1.67×** | **1.8 kB** | **ok** |
|
|
278
|
+
| AssemblyScript (asc -O3 --runtime stub) | 7.96 ms | 0.98× | 1.9 kB | ok |
|
|
279
|
+
| V8 (node) raw JS | 7.79 ms | 1.00× | 1.6 kB | ok |
|
|
280
|
+
|
|
281
|
+
jz is 1.6× faster than V8 raw JS and 1.4× faster than AS. Call-heavy
|
|
282
|
+
nested loops with typed-array index propagation stay on the i32 path.
|
|
283
|
+
|
|
284
|
+
### crc32 — table-driven CRC-32 over byte buffer
|
|
285
|
+
|
|
286
|
+
| target | median | ×v8 | size | parity |
|
|
287
|
+
| --- | ---: | ---: | ---: | --- |
|
|
288
|
+
| **jz → V8 wasm** | **8.74 ms** | **1.11×** | **1.5 kB** | **ok** |
|
|
289
|
+
| AssemblyScript (asc -O3 --runtime stub) | 8.76 ms | 1.11× | 1.4 kB | ok |
|
|
290
|
+
| V8 (node) raw JS | 9.71 ms | 1.00× | 1.8 kB | ok |
|
|
291
|
+
|
|
292
|
+
jz is 1.2× faster than V8 raw JS and ties AS. Integer narrowing and
|
|
293
|
+
typed-array parameter propagation keep the LUT lookup on raw i32.
|
|
294
|
+
|
|
295
|
+
### Audio + image showcase (cross-language, bit-exact)
|
|
296
|
+
|
|
297
|
+
Four standard kernels added to make the audio story concrete. All are written so
|
|
298
|
+
the output is **bit-identical** across every engine and native target — integer
|
|
299
|
+
math (bytebeat, blur) or transcendental-free f64 (fft, synth: in-source Taylor
|
|
300
|
+
polynomials, not `Math.sin`, which differs per libm). Go's arm64 auto-FMA gives
|
|
301
|
+
the documented `fma` parity class on the f64 cases.
|
|
302
|
+
|
|
303
|
+
| case | jz | vs V8 | vs AS | vs fastest native | jz wasm |
|
|
304
|
+
| --- | ---: | ---: | ---: | --- | ---: |
|
|
305
|
+
| **synth** — osc + ADSR + biquad | **2.32 ms** | **1.33×** | **1.07×** | **beats all** (Rust 0.89×) | 2.0 kB |
|
|
306
|
+
| **fft** — radix-2 Cooley–Tukey | **1.14 ms** | **1.27×** | **1.13×** | **ties** (Rust 1.07×) | 2.3 kB |
|
|
307
|
+
| **blur** — RGBA box blur | **0.90 ms** | **9.4×** | **5.8×** | trails (native SIMDs the stencil, 1.5×) | 3.4 kB |
|
|
308
|
+
| **bytebeat** — integer one-liner | **0.67 ms** | **3.7×** | **5.4×** | trails (native vectorizes, 1.3×) | 1.5 kB |
|
|
309
|
+
|
|
310
|
+
The headline: jz beats the JS field (V8, AssemblyScript) on **every** audio/image
|
|
311
|
+
case, **ties native on FFT**, and is the **fastest of all targets on the synth
|
|
312
|
+
pipeline** — its per-sample loop is loop-carried (oscillator phase + biquad
|
|
313
|
+
feedback), so native can't auto-vectorize it either, and jz's tight scalar f64
|
|
314
|
+
codegen with no NaN-box overhead wins outright. The two stateless integer kernels
|
|
315
|
+
(bytebeat, blur) are where `clang`/`zig`/`rustc` auto-vectorize an
|
|
316
|
+
embarrassingly-parallel loop jz emits as scalar — native is the floor there, but
|
|
317
|
+
jz still beats the JS field by 3.7–9.4× there. (Numbers: darwin/arm64, Apple M4 Max; the live snapshot
|
|
318
|
+
is [results.json](results.json).)
|
|
319
|
+
|
|
320
|
+
### watr — WAT-to-wasm compiler on small corpus
|
|
321
|
+
|
|
322
|
+
| target | median | ×v8 | size | parity |
|
|
323
|
+
| --- | ---: | ---: | ---: | --- |
|
|
324
|
+
| V8 (node) raw JS | 1.38 ms | 1.00× | 2.6 kB | ok |
|
|
325
|
+
| **jz → V8 wasm** | **1.17 ms** | **1.17×** | **238.4 kB** | **ok** |
|
|
326
|
+
|
|
327
|
+
jz is 1.07× slower than V8 raw JS on this large compiler bundle. The size
|
|
328
|
+
(144 kB) is the full jz-compiled watr parser + encoder + optimizer; V8's JIT
|
|
329
|
+
has the advantage of profile-guided tiering on a long-running compiler.
|
|
330
|
+
|
|
331
|
+
### Where the gaps live
|
|
332
|
+
|
|
333
|
+
Aggregate geomean (jz / target):
|
|
334
|
+
|
|
335
|
+
| target | speed | size |
|
|
336
|
+
| --- | ---: | ---: |
|
|
337
|
+
| V8 (node) | **0.40×** | — |
|
|
338
|
+
| AssemblyScript | **0.36×** | **1.16×** |
|
|
339
|
+
|
|
340
|
+
jz wins or ties V8 on every kernel case; the only V8 losses are the
|
|
341
|
+
self-hosting rows `watr` (1.07×) and `jessie` (1.28×). AS is beaten on
|
|
342
|
+
speed across the shared cases. On size jz is ~1.1× AS (geomean 1.16×,
|
|
343
|
+
median 1.10×) — jz wins on speed, AS on bytes.
|
|
344
|
+
|
|
345
|
+
Against the systems languages compiled to the same target — **WebAssembly, run in
|
|
346
|
+
V8** — jz is **2.4× faster than C, 2.6× than Rust, and 4.9× than Go** (geomean),
|
|
347
|
+
and **competitive with native C** itself (1.07×, the lone non-wasm reference).
|
|
348
|
+
That apples-to-apples wasm field is the headline chart above.
|
|
349
|
+
|
|
350
|
+
Case-by-case summary:
|
|
351
|
+
|
|
352
|
+
* **biquad, mat4, poly, bitwise, callback: large wins.** jz beats V8 by
|
|
353
|
+
2.0–12.9× and AS by 1.4–9.1×. Typed-array scalarization, i32 narrowing,
|
|
354
|
+
and closure lowering are the drivers.
|
|
355
|
+
* **tokenizer, aos, mandelbrot, sort, crc32: modest wins.** jz beats V8 by
|
|
356
|
+
1.1–2.4× and ties or beats AS. These are memory-bound or branch-heavy
|
|
357
|
+
workloads where codegen quality matters less than data layout.
|
|
358
|
+
* **json: solid win.** jz beats V8 by 1.3× on runtime JSON parsing; AS
|
|
359
|
+
cannot run this case.
|
|
360
|
+
* **alpha: the native floor.** jz beats V8 2.0× and every wasm rival (Rust/C/Go
|
|
361
|
+
→ wasm), but trails the native-C reference ~14× (374 µs vs 26 µs) — alpha's hot
|
|
362
|
+
path is an unsigned i32 multiply. jz already lifts it to 128-bit SIMD (an i16x8
|
|
363
|
+
widening byte-map); native C pulls ahead on width alone — 256-bit AVX2 with a
|
|
364
|
+
fused byte multiply-add, run as native code with no per-load bounds checks. The
|
|
365
|
+
residual is the wasm-v128-vs-AVX2 ceiling, not a missing pass. A known gap we
|
|
366
|
+
publish rather than hide.
|
|
367
|
+
* **watr: near parity.** jz is 1.07× slower than V8 on a 144 kB compiler
|
|
368
|
+
bundle. It is one of two self-host rows (with jessie) where V8's
|
|
369
|
+
profile-guided JIT tiers beat jz's AOT wasm.
|
package/bench/bench.svg
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 474" width="720" height="474" role="img" aria-label="jz benchmark — every rival compiled to WebAssembly, run in V8 · native C = reference; geometric mean across 22 benchmark cases · lower is faster, jz = 1.00× baseline; each ball's speed is proportional to that engine's geometric-mean runtime across the corpus">
|
|
2
|
+
<rect width="720" height="474" rx="12" fill="#ffffff"/>
|
|
3
|
+
|
|
4
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
5
|
+
<rect x="174" y="43.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
6
|
+
<line x1="174" y1="38" x2="174" y2="52" stroke="#ced4da" stroke-width="2"/>
|
|
7
|
+
<line x1="650" y1="38" x2="650" y2="52" stroke="#ced4da" stroke-width="2"/>
|
|
8
|
+
<text x="156" y="41" text-anchor="end" font-size="14" font-weight="700" fill="#000000">jz</text>
|
|
9
|
+
<text x="156" y="56" text-anchor="end" font-size="10" fill="#aeb4ba">-O3</text>
|
|
10
|
+
<text x="662" y="49" font-size="13" font-weight="700" fill="#000000">1.00×</text>
|
|
11
|
+
<circle cx="182.0" cy="45" r="8" fill="#000000">
|
|
12
|
+
<animate attributeName="cx" dur="1.6s" repeatCount="indefinite" calcMode="linear"
|
|
13
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-0.00s"/>
|
|
14
|
+
</circle>
|
|
15
|
+
</g>
|
|
16
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
17
|
+
<rect x="174" y="93.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
18
|
+
<line x1="174" y1="88" x2="174" y2="102" stroke="#ced4da" stroke-width="2"/>
|
|
19
|
+
<line x1="650" y1="88" x2="650" y2="102" stroke="#ced4da" stroke-width="2"/>
|
|
20
|
+
<text x="156" y="91" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">native C</text>
|
|
21
|
+
<text x="156" y="106" text-anchor="end" font-size="10" fill="#aeb4ba">clang -O3 · ref</text>
|
|
22
|
+
<text x="662" y="99" font-size="13" font-weight="500" fill="#868e96">1.07×</text>
|
|
23
|
+
<circle cx="182.0" cy="95" r="8" fill="#adb5bd">
|
|
24
|
+
<animate attributeName="cx" dur="1.7120000000000002s" repeatCount="indefinite" calcMode="linear"
|
|
25
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-0.41s"/>
|
|
26
|
+
</circle>
|
|
27
|
+
</g>
|
|
28
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
29
|
+
<rect x="174" y="143.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
30
|
+
<line x1="174" y1="138" x2="174" y2="152" stroke="#ced4da" stroke-width="2"/>
|
|
31
|
+
<line x1="650" y1="138" x2="650" y2="152" stroke="#ced4da" stroke-width="2"/>
|
|
32
|
+
<text x="156" y="141" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">C</text>
|
|
33
|
+
<text x="156" y="156" text-anchor="end" font-size="10" fill="#aeb4ba">zig cc → wasm</text>
|
|
34
|
+
<text x="662" y="149" font-size="13" font-weight="500" fill="#868e96">2.43×</text>
|
|
35
|
+
<circle cx="182.0" cy="145" r="8" fill="#adb5bd">
|
|
36
|
+
<animate attributeName="cx" dur="3.8880000000000003s" repeatCount="indefinite" calcMode="linear"
|
|
37
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-0.82s"/>
|
|
38
|
+
</circle>
|
|
39
|
+
</g>
|
|
40
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
41
|
+
<rect x="174" y="193.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
42
|
+
<line x1="174" y1="188" x2="174" y2="202" stroke="#ced4da" stroke-width="2"/>
|
|
43
|
+
<line x1="650" y1="188" x2="650" y2="202" stroke="#ced4da" stroke-width="2"/>
|
|
44
|
+
<text x="156" y="191" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">Rust</text>
|
|
45
|
+
<text x="156" y="206" text-anchor="end" font-size="10" fill="#aeb4ba">rustc → wasm</text>
|
|
46
|
+
<text x="662" y="199" font-size="13" font-weight="500" fill="#868e96">2.63×</text>
|
|
47
|
+
<circle cx="182.0" cy="195" r="8" fill="#adb5bd">
|
|
48
|
+
<animate attributeName="cx" dur="4.208s" repeatCount="indefinite" calcMode="linear"
|
|
49
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-1.23s"/>
|
|
50
|
+
</circle>
|
|
51
|
+
</g>
|
|
52
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
53
|
+
<rect x="174" y="243.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
54
|
+
<line x1="174" y1="238" x2="174" y2="252" stroke="#ced4da" stroke-width="2"/>
|
|
55
|
+
<line x1="650" y1="238" x2="650" y2="252" stroke="#ced4da" stroke-width="2"/>
|
|
56
|
+
<text x="156" y="241" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">V8</text>
|
|
57
|
+
<text x="156" y="256" text-anchor="end" font-size="10" fill="#aeb4ba">Node (JS)</text>
|
|
58
|
+
<text x="662" y="249" font-size="13" font-weight="500" fill="#868e96">2.69×</text>
|
|
59
|
+
<circle cx="182.0" cy="245" r="8" fill="#adb5bd">
|
|
60
|
+
<animate attributeName="cx" dur="4.304s" repeatCount="indefinite" calcMode="linear"
|
|
61
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-1.64s"/>
|
|
62
|
+
</circle>
|
|
63
|
+
</g>
|
|
64
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
65
|
+
<rect x="174" y="293.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
66
|
+
<line x1="174" y1="288" x2="174" y2="302" stroke="#ced4da" stroke-width="2"/>
|
|
67
|
+
<line x1="650" y1="288" x2="650" y2="302" stroke="#ced4da" stroke-width="2"/>
|
|
68
|
+
<text x="156" y="291" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">AssemblyScript</text>
|
|
69
|
+
<text x="156" y="306" text-anchor="end" font-size="10" fill="#aeb4ba">asc -O3</text>
|
|
70
|
+
<text x="662" y="299" font-size="13" font-weight="500" fill="#868e96">2.80×</text>
|
|
71
|
+
<circle cx="182.0" cy="295" r="8" fill="#adb5bd">
|
|
72
|
+
<animate attributeName="cx" dur="4.4799999999999995s" repeatCount="indefinite" calcMode="linear"
|
|
73
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-2.05s"/>
|
|
74
|
+
</circle>
|
|
75
|
+
</g>
|
|
76
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
77
|
+
<rect x="174" y="343.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
78
|
+
<line x1="174" y1="338" x2="174" y2="352" stroke="#ced4da" stroke-width="2"/>
|
|
79
|
+
<line x1="650" y1="338" x2="650" y2="352" stroke="#ced4da" stroke-width="2"/>
|
|
80
|
+
<text x="156" y="341" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">Porffor</text>
|
|
81
|
+
<text x="156" y="356" text-anchor="end" font-size="10" fill="#aeb4ba">runs 3 / 22</text>
|
|
82
|
+
<text x="662" y="349" font-size="13" font-weight="500" fill="#868e96">3.49×</text>
|
|
83
|
+
<circle cx="182.0" cy="345" r="8" fill="#adb5bd">
|
|
84
|
+
<animate attributeName="cx" dur="5.5840000000000005s" repeatCount="indefinite" calcMode="linear"
|
|
85
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-2.46s"/>
|
|
86
|
+
</circle>
|
|
87
|
+
</g>
|
|
88
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
89
|
+
<rect x="174" y="393.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
90
|
+
<line x1="174" y1="388" x2="174" y2="402" stroke="#ced4da" stroke-width="2"/>
|
|
91
|
+
<line x1="650" y1="388" x2="650" y2="402" stroke="#ced4da" stroke-width="2"/>
|
|
92
|
+
<text x="156" y="391" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">Go</text>
|
|
93
|
+
<text x="156" y="406" text-anchor="end" font-size="10" fill="#aeb4ba">gc → wasm</text>
|
|
94
|
+
<text x="662" y="399" font-size="13" font-weight="500" fill="#868e96">4.91×</text>
|
|
95
|
+
<circle cx="182.0" cy="395" r="8" fill="#adb5bd">
|
|
96
|
+
<animate attributeName="cx" dur="7.856000000000001s" repeatCount="indefinite" calcMode="linear"
|
|
97
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-2.87s"/>
|
|
98
|
+
</circle>
|
|
99
|
+
</g>
|
|
100
|
+
<text x="360" y="440" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="11" font-weight="600" fill="#495057">every rival compiled to WebAssembly, run in V8 · native C = reference</text>
|
|
101
|
+
<text x="360" y="458" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="11" fill="#868e96">geometric mean across 22 benchmark cases · lower is faster, jz = 1.00× baseline</text>
|
|
102
|
+
</svg>
|