jz 0.5.0 → 0.6.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 +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- 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 +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +281 -142
- 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 +461 -185
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +591 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +600 -205
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -2997
- package/src/jzify.js +0 -1553
- 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,319 @@
|
|
|
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>.py optional scalar CPython baseline
|
|
14
|
+
bench/<case>/<case>.npy.py optional NumPy baseline
|
|
15
|
+
bench/<case>/<case>.wat optional hand-written WAT baseline
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Every case prints the same line:
|
|
19
|
+
|
|
20
|
+
```txt
|
|
21
|
+
median_us=<int> checksum=<u32> samples=<int> stages=<int> runs=<int>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The orchestrator runs selected cases against selected targets and flags checksum
|
|
25
|
+
drift as `DIFF`.
|
|
26
|
+
|
|
27
|
+
## Run
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm run bench
|
|
31
|
+
node bench/bench.mjs --targets=nat,rust,go,numpy,v8,jz
|
|
32
|
+
node bench/bench.mjs --targets=jz --cases=biquad,mat4,poly,bitwise
|
|
33
|
+
node bench/bench.mjs --targets=v8,deno,bun,spidermonkey,hermes,graaljs,qjs
|
|
34
|
+
node bench/bench.mjs --cases=biquad,mat4,tokenizer,json,sort,crc32
|
|
35
|
+
node bench/bench.mjs biquad
|
|
36
|
+
node bench/bench.mjs mat4 --targets=nat,v8,jz
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Cases
|
|
40
|
+
|
|
41
|
+
| id | purpose |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| [`biquad`](biquad/biquad.js) | DSP filter cascade; dense f64 typed-array loop and offset-fusion baseline |
|
|
44
|
+
| [`mat4`](mat4/mat4.js) | fixed-size typed-array loops; exposes loop unrolling and offset fusion gaps |
|
|
45
|
+
| [`poly`](poly/poly.js) | same `sum` called with `Float64Array` and `Int32Array`; exposes bimorphic typed-array dispatch |
|
|
46
|
+
| [`bitwise`](bitwise/bitwise.js) | long `i32` narrowing chains with `Math.imul`, shifts, and unsigned conversion |
|
|
47
|
+
| [`tokenizer`](tokenizer/tokenizer.js) | string-heavy scan with `charCodeAt`, branches, and integer token accumulation |
|
|
48
|
+
| [`callback`](callback/callback.js) | `Array.map` callback path; exposes closure/call-indirect and array allocation cost |
|
|
49
|
+
| [`aos`](aos/aos.js) | array-of-object rows copied into typed arrays; exposes schema-slot read cost |
|
|
50
|
+
| [`mandelbrot`](mandelbrot/mandelbrot.js) | 256×256 escape-time iteration; dense f64 hot loop with conditional break and i32 counter |
|
|
51
|
+
| [`json`](json/json.js) | runtime `JSON.parse` of one module-local source plus heterogeneous object/array walk with a stable inferred JSON shape |
|
|
52
|
+
| [`sort`](sort/sort.js) | in-place heapsort over a typed array; exposes call-heavy nested loops and typed-array index propagation |
|
|
53
|
+
| [`crc32`](crc32/crc32.js) | table-driven CRC-32 over a mutable byte buffer; exposes integer narrowing and typed-array parameter propagation |
|
|
54
|
+
| [`watr`](watr/watr.js) | watr's WAT-to-wasm compiler on a small WAT corpus; compares jz-compiled compiler code with raw V8 |
|
|
55
|
+
| [`jessie`](jessie/jessie.js) | the subscript/jessie JS parser over a realistic source corpus; branch-, allocation- and recursion-heavy front-end work |
|
|
56
|
+
| [`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 |
|
|
57
|
+
|
|
58
|
+
Native rows for `json` are fixed-source references, not semantic equivalents
|
|
59
|
+
of JavaScript `JSON.parse`: C/Rust/Zig hand-parse the known schema from a
|
|
60
|
+
compile-time string, and Zig may constant-fold the whole parse+walk under
|
|
61
|
+
ReleaseFast; Go uses `encoding/json` but still unmarshals the same compile-time
|
|
62
|
+
string. The jz row parses a `let` source at runtime so `JSON.parse` is not
|
|
63
|
+
compile-time folded, while the compiler can still specialize the stable literal
|
|
64
|
+
shape. External unknown-shape JSON still uses the generic runtime parser.
|
|
65
|
+
|
|
66
|
+
Native-language rows are intentionally per case. NumPy rows are used only
|
|
67
|
+
where a vectorized array implementation is a meaningful Python convention;
|
|
68
|
+
scalar CPython is kept to the tokenizer row to avoid turning the suite into
|
|
69
|
+
a Python loop-overhead benchmark.
|
|
70
|
+
|
|
71
|
+
### Parity classes
|
|
72
|
+
|
|
73
|
+
The `parity` column is `ok` when the run's checksum matches the most common
|
|
74
|
+
checksum across all targets, `DIFF` when it diverges in a way that suggests
|
|
75
|
+
a bug, and `fma` when the divergence is the documented FMA-fusion class.
|
|
76
|
+
The Go arm64 backend auto-fuses `a*b + c` to `FMADDD` (mandatory in ARMv8,
|
|
77
|
+
no compiler flag to disable it), which alters bit-level rounding on
|
|
78
|
+
recurrence-style loops like `biquad`. Result is still IEEE-754
|
|
79
|
+
correctly-rounded; cascade is the same algorithm.
|
|
80
|
+
|
|
81
|
+
## Targets
|
|
82
|
+
|
|
83
|
+
| id | what it measures |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `nat` | clang `-O3` native C baseline, when a matching C workload exists |
|
|
86
|
+
| `natgcc` | gcc `-O3`, when real gcc is installed |
|
|
87
|
+
| `rust` | Rust `rustc -C opt-level=3 -C target-cpu=native`, when a matching `.rs` exists |
|
|
88
|
+
| `go` | Go native compiler, when a matching `.go` exists |
|
|
89
|
+
| `zig` | Zig `build-exe -O ReleaseFast`, when a matching `.zig` exists |
|
|
90
|
+
| `python` | scalar CPython, when a matching `.py` exists |
|
|
91
|
+
| `numpy` | vectorized NumPy, when a matching `.npy.py` exists |
|
|
92
|
+
| `v8` | raw JavaScript on Node/V8 |
|
|
93
|
+
| `deno` | raw JavaScript on Deno/V8 |
|
|
94
|
+
| `bun` | raw JavaScript on Bun/JavaScriptCore |
|
|
95
|
+
| `spidermonkey` | raw JavaScript on SpiderMonkey shell (`spidermonkey`, `sm`, `js128`, `js115`, `js102`, or `js`) |
|
|
96
|
+
| `hermes` | raw JavaScript on Hermes |
|
|
97
|
+
| `shermes` | Static Hermes — JS AOT-compiled to a native binary (`shermes -O`), when installed |
|
|
98
|
+
| `graaljs` | raw JavaScript on GraalJS |
|
|
99
|
+
| `jz` | jz output with host imports for timing/logging (measures wasm size without WASI console/perf bloat) |
|
|
100
|
+
| `as` | AssemblyScript `asc -O3 --runtime stub`, when a matching `.as.ts` exists |
|
|
101
|
+
| `jz-wasmtime` | jz output on wasmtime |
|
|
102
|
+
| `jz-w2c` | jz wasm translated by wabt `wasm2c`, then clang `-O3` |
|
|
103
|
+
| `wat` | hand-written WAT baseline when a case provides `run-wat.mjs` |
|
|
104
|
+
| `qjs` | QuickJS when installed |
|
|
105
|
+
| `porf` | Porffor (`porf run`) when installed |
|
|
106
|
+
| `jawsm` | jawsm when installed |
|
|
107
|
+
|
|
108
|
+
The `size` column reports the artifact size each target measures: the
|
|
109
|
+
compiled native binary for `nat`/`rust`/`go`/`zig`, the produced
|
|
110
|
+
`.wasm` for `jz`/`as`/hand-WAT/jawsm/`jz-w2c` (the C-translated
|
|
111
|
+
executable), or the source file for raw-JS interpreters where there is no
|
|
112
|
+
compile step. For source files with imports, raw-JS size is only the entry file;
|
|
113
|
+
jz size is the bundled wasm artifact.
|
|
114
|
+
|
|
115
|
+
Note the preset trade: these speed tables build jz at the default
|
|
116
|
+
(`optimize: 2`, speed-leaning — loop unroll + SIMD lift), while the dedicated
|
|
117
|
+
size comparison (`npm run bench:size`) builds with `optimize: 'size'` —
|
|
118
|
+
typically ~2× smaller on the same kernel (biquad: 3.5 kB default vs 1.6 kB
|
|
119
|
+
size-tuned). Pick the preset for what you ship; the two tables are not the
|
|
120
|
+
same artifact.
|
|
121
|
+
|
|
122
|
+
Runtime command overrides:
|
|
123
|
+
|
|
124
|
+
`watr` is intentionally compiled by jz with a size-oriented pass config
|
|
125
|
+
(`watr:false`, `smallConstForUnroll:false`): on a large compiler bundle, the
|
|
126
|
+
default WAT-level optimizer and small-loop unroll grow code more than they help.
|
|
127
|
+
This keeps the target measuring the best current jz artifact for that workload.
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
BUN_BIN=/path/to/bun \
|
|
131
|
+
DENO_BIN=/path/to/deno \
|
|
132
|
+
SPIDERMONKEY_BIN=/path/to/js \
|
|
133
|
+
HERMES_BIN=/path/to/hermes \
|
|
134
|
+
SHERMES_BIN=/path/to/shermes \
|
|
135
|
+
GRAALJS_BIN=/path/to/graaljs \
|
|
136
|
+
PORF_BIN=/path/to/porf \
|
|
137
|
+
node bench/bench.mjs --targets=bun,deno,spidermonkey,hermes,shermes,graaljs,porf
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Reading the numbers (darwin/arm64, M-class)
|
|
141
|
+
|
|
142
|
+
Snapshots from `node bench/bench.mjs --targets=v8,jz,as`.
|
|
143
|
+
Where jz lands relative to **V8 raw JS** (`v8/node`) and **AssemblyScript**
|
|
144
|
+
(`as`) is the headline comparison. Native and hand-WAT rows are shown where
|
|
145
|
+
available from earlier runs.
|
|
146
|
+
|
|
147
|
+
### biquad — f64 typed-array DSP cascade
|
|
148
|
+
|
|
149
|
+
| target | median | ×v8 | size | parity |
|
|
150
|
+
| --- | ---: | ---: | ---: | --- |
|
|
151
|
+
| **jz → V8 wasm** | **6.50 ms** | **1.90×** | **3.4 kB** | **ok** |
|
|
152
|
+
| AssemblyScript (asc -O3 --runtime stub) | 9.03 ms | 1.38× | 1.9 kB | ok |
|
|
153
|
+
| V8 (node) raw JS | 12.35 ms | 1.00× | 3.2 kB | ok |
|
|
154
|
+
| native C (clang -O3) | 5.32 ms | 2.32× | 32.7 kB | ok |
|
|
155
|
+
| hand-WAT → V8 wasm | 6.49 ms | 1.90× | 767 B | ok |
|
|
156
|
+
|
|
157
|
+
jz beats V8 raw JS by 1.9× and AS by 1.4×. The typed-array scalarization,
|
|
158
|
+
offset-fusion, and base-hoisting pipeline delivers dense-f64 loop codegen
|
|
159
|
+
that matches the hand-WAT floor.
|
|
160
|
+
|
|
161
|
+
### mat4 — fixed-size Float64Array multiply
|
|
162
|
+
|
|
163
|
+
| target | median | ×v8 | size | parity |
|
|
164
|
+
| --- | ---: | ---: | ---: | --- |
|
|
165
|
+
| **jz → V8 wasm** | **2.74 ms** | **4.37×** | **3.3 kB** | **ok** |
|
|
166
|
+
| AssemblyScript (asc -O3 --runtime stub) | 9.32 ms | 1.28× | 1.6 kB | ok |
|
|
167
|
+
| V8 (node) raw JS | 11.96 ms | 1.00× | 1.2 kB | ok |
|
|
168
|
+
| native C (clang -O3) | 2.76 ms | 4.33× | 32.8 kB | ok |
|
|
169
|
+
| hand-WAT → V8 wasm | 8.12 ms | 1.47× | 414 B | ok |
|
|
170
|
+
|
|
171
|
+
jz is 4.4× faster than V8 raw JS and 3.4× faster than AS. The scalarized
|
|
172
|
+
SIMD hot path (unrolled 4×4 multiply) is the win; V8's JIT doesn't vectorize
|
|
173
|
+
this from JS source.
|
|
174
|
+
|
|
175
|
+
### poly — bimorphic typed-array reduce
|
|
176
|
+
|
|
177
|
+
| target | median | ×v8 | size | parity |
|
|
178
|
+
| --- | ---: | ---: | ---: | --- |
|
|
179
|
+
| **jz → V8 wasm** | **0.37 ms** | **6.22×** | **1.2 kB** | **ok** |
|
|
180
|
+
| AssemblyScript (asc -O3 --runtime stub) | 1.15 ms | 2.02× | 1.3 kB | ok |
|
|
181
|
+
| V8 (node) raw JS | 2.32 ms | 1.00× | 1014 B | ok |
|
|
182
|
+
|
|
183
|
+
jz is 6.2× faster than V8 raw JS and 3.1× faster than AS. The bimorphic
|
|
184
|
+
`sum` (called with both `Float64Array` and `Int32Array`) stays on typed
|
|
185
|
+
paths without falling back to generic dispatch.
|
|
186
|
+
|
|
187
|
+
### bitwise — i32 narrowing chains (`Math.imul`, shifts, FNV-1a)
|
|
188
|
+
|
|
189
|
+
| target | median | ×v8 | size | parity |
|
|
190
|
+
| --- | ---: | ---: | ---: | --- |
|
|
191
|
+
| **jz → V8 wasm** | **1.40 ms** | **3.81×** | **1.2 kB** | **ok** |
|
|
192
|
+
| V8 (node) raw JS | 5.32 ms | 1.00× | 1005 B | ok |
|
|
193
|
+
| AssemblyScript (asc -O3 --runtime stub) | 12.13 ms | 0.44× | 1.5 kB | ok |
|
|
194
|
+
| native C (clang -O3) | 1.31 ms | 4.06× | 32.9 kB | ok |
|
|
195
|
+
| hand-WAT → V8 wasm | 4.88 ms | 1.09× | 355 B | ok |
|
|
196
|
+
|
|
197
|
+
jz is 3.8× faster than V8 raw JS and 8.7× faster than AS. The i32 hot path
|
|
198
|
+
(`Math.imul`, `|0`, `>>>0`) now lowers to raw `i32` ops without NaN-box
|
|
199
|
+
overhead on every operation.
|
|
200
|
+
|
|
201
|
+
### tokenizer — string scan with `charCodeAt` and integer accumulation
|
|
202
|
+
|
|
203
|
+
| target | median | ×v8 | size | parity |
|
|
204
|
+
| --- | ---: | ---: | ---: | --- |
|
|
205
|
+
| AssemblyScript (asc -O3 --runtime stub) | 0.08 ms | 2.63× | 1.6 kB | ok |
|
|
206
|
+
| **jz → V8 wasm** | **0.10 ms** | **2.03×** | **1.7 kB** | **ok** |
|
|
207
|
+
| V8 (node) raw JS | 0.21 ms | 1.00× | 2.0 kB | ok |
|
|
208
|
+
|
|
209
|
+
jz is 2.0× faster than V8 raw JS. AS wins here by 1.3× because its
|
|
210
|
+
`String#charCodeAt` is a direct memory load without NaN-box decode, while
|
|
211
|
+
jz still boxes the string pointer. The gap is narrow (0.02 ms) and both
|
|
212
|
+
are well ahead of V8.
|
|
213
|
+
|
|
214
|
+
### callback — `Array.map` closure + i32 fold
|
|
215
|
+
|
|
216
|
+
| target | median | ×v8 | size | parity |
|
|
217
|
+
| --- | ---: | ---: | ---: | --- |
|
|
218
|
+
| **jz → V8 wasm** | **0.03 ms** | **27.56×** | **1.4 kB** | **ok** |
|
|
219
|
+
| AssemblyScript (asc -O3 --runtime stub) | 1.49 ms | 0.59× | 1.9 kB | ok |
|
|
220
|
+
| V8 (node) raw JS | 0.88 ms | 1.00× | 828 B | ok |
|
|
221
|
+
|
|
222
|
+
jz is 27.6× faster than V8 raw JS and 49.7× faster than AS. Closure +
|
|
223
|
+
`Array.map` lowers to a preallocated typed loop with no per-iteration alloc.
|
|
224
|
+
V8's JIT does not inline the closure across the `map` boundary.
|
|
225
|
+
|
|
226
|
+
### aos — array-of-object rows to typed arrays
|
|
227
|
+
|
|
228
|
+
| target | median | ×v8 | size | parity |
|
|
229
|
+
| --- | ---: | ---: | ---: | --- |
|
|
230
|
+
| **jz → V8 wasm** | **1.62 ms** | **1.12×** | **1.8 kB** | **ok** |
|
|
231
|
+
| V8 (node) raw JS | 1.82 ms | 1.00× | 1.1 kB | ok |
|
|
232
|
+
| AssemblyScript (asc -O3 --runtime stub) | 1.91 ms | 0.95× | 2.2 kB | ok |
|
|
233
|
+
|
|
234
|
+
jz is 1.1× faster than V8 raw JS and 1.2× faster than AS. Schema-slot
|
|
235
|
+
reads are direct field offsets; the gap is small because the workload is
|
|
236
|
+
memory-bound.
|
|
237
|
+
|
|
238
|
+
### mandelbrot — 256×256 escape-time iteration
|
|
239
|
+
|
|
240
|
+
| target | median | ×v8 | size | parity |
|
|
241
|
+
| --- | ---: | ---: | ---: | --- |
|
|
242
|
+
| AssemblyScript (asc -O3 --runtime stub) | 12.42 ms | 1.11× | 1.3 kB | ok |
|
|
243
|
+
| **jz → V8 wasm** | **12.55 ms** | **1.10×** | **1.0 kB** | **ok** |
|
|
244
|
+
| V8 (node) raw JS | 13.80 ms | 1.00× | 1.8 kB | ok |
|
|
245
|
+
|
|
246
|
+
jz is 1.1× faster than V8 raw JS and ties AS. The dense f64 hot loop with
|
|
247
|
+
conditional break compacts to 1.0 kB — the smallest wasm in the suite.
|
|
248
|
+
|
|
249
|
+
### json — runtime `JSON.parse` plus stable-shape walk
|
|
250
|
+
|
|
251
|
+
| target | median | ×v8 | size | parity |
|
|
252
|
+
| --- | ---: | ---: | ---: | --- |
|
|
253
|
+
| **jz → V8 wasm** | **0.23 ms** | **1.65×** | **7.7 kB** | **ok** |
|
|
254
|
+
| V8 (node) raw JS | 0.38 ms | 1.00× | 1.2 kB | ok |
|
|
255
|
+
|
|
256
|
+
jz is 1.7× faster than V8 raw JS. The runtime parser is specialized to the
|
|
257
|
+
inferred JSON shape; AS is skipped because it cannot parse JSON at runtime.
|
|
258
|
+
|
|
259
|
+
### sort — in-place heapsort over typed array
|
|
260
|
+
|
|
261
|
+
| target | median | ×v8 | size | parity |
|
|
262
|
+
| --- | ---: | ---: | ---: | --- |
|
|
263
|
+
| **jz → V8 wasm** | **5.96 ms** | **1.87×** | **1.6 kB** | **ok** |
|
|
264
|
+
| AssemblyScript (asc -O3 --runtime stub) | 10.22 ms | 1.09× | 1.9 kB | ok |
|
|
265
|
+
| V8 (node) raw JS | 11.13 ms | 1.00× | 1.6 kB | ok |
|
|
266
|
+
|
|
267
|
+
jz is 1.9× faster than V8 raw JS and 1.7× faster than AS. Call-heavy
|
|
268
|
+
nested loops with typed-array index propagation stay on the i32 path.
|
|
269
|
+
|
|
270
|
+
### crc32 — table-driven CRC-32 over byte buffer
|
|
271
|
+
|
|
272
|
+
| target | median | ×v8 | size | parity |
|
|
273
|
+
| --- | ---: | ---: | ---: | --- |
|
|
274
|
+
| **jz → V8 wasm** | **12.12 ms** | **1.11×** | **1.2 kB** | **ok** |
|
|
275
|
+
| AssemblyScript (asc -O3 --runtime stub) | 12.19 ms | 1.10× | 1.4 kB | ok |
|
|
276
|
+
| V8 (node) raw JS | 13.43 ms | 1.00× | 1.8 kB | ok |
|
|
277
|
+
|
|
278
|
+
jz is 1.1× faster than V8 raw JS and ties AS. Integer narrowing and
|
|
279
|
+
typed-array parameter propagation keep the LUT lookup on raw i32.
|
|
280
|
+
|
|
281
|
+
### watr — WAT-to-wasm compiler on small corpus
|
|
282
|
+
|
|
283
|
+
| target | median | ×v8 | size | parity |
|
|
284
|
+
| --- | ---: | ---: | ---: | --- |
|
|
285
|
+
| V8 (node) raw JS | 1.45 ms | 1.00× | 2.6 kB | ok |
|
|
286
|
+
| **jz → V8 wasm** | **1.56 ms** | **1.07×** | **144.4 kB** | **ok** |
|
|
287
|
+
|
|
288
|
+
jz is 1.07× slower than V8 raw JS on this large compiler bundle. The size
|
|
289
|
+
(144 kB) is the full jz-compiled watr parser + encoder + optimizer; V8's JIT
|
|
290
|
+
has the advantage of profile-guided tiering on a long-running compiler.
|
|
291
|
+
|
|
292
|
+
### Where the gaps live
|
|
293
|
+
|
|
294
|
+
Aggregate geomean (jz / target):
|
|
295
|
+
|
|
296
|
+
| target | speed | size |
|
|
297
|
+
| --- | ---: | ---: |
|
|
298
|
+
| V8 (node) | **0.41×** | — |
|
|
299
|
+
| AssemblyScript | **0.40×** | **0.85×** |
|
|
300
|
+
| Porffor | **0.32×** | **0.04×** |
|
|
301
|
+
| wasm-opt slack | — | **0.91×** |
|
|
302
|
+
|
|
303
|
+
jz wins or ties V8 on every case except `watr` (1.07×). AS is beaten on
|
|
304
|
+
all cases except `tokenizer` (AS 0.08 ms vs jz 0.10 ms). The size geomean
|
|
305
|
+
is 0.85× AS — jz output is smaller on average despite beating AS on speed.
|
|
306
|
+
|
|
307
|
+
Case-by-case summary:
|
|
308
|
+
|
|
309
|
+
* **biquad, mat4, poly, bitwise, callback: large wins.** jz beats V8 by
|
|
310
|
+
1.9–27.6× and AS by 1.4–49.7×. Typed-array scalarization, i32 narrowing,
|
|
311
|
+
and closure lowering are the drivers.
|
|
312
|
+
* **tokenizer, aos, mandelbrot, sort, crc32: modest wins.** jz beats V8 by
|
|
313
|
+
1.1–2.0× and ties or beats AS. These are memory-bound or branch-heavy
|
|
314
|
+
workloads where codegen quality matters less than data layout.
|
|
315
|
+
* **json: solid win.** jz beats V8 by 1.7× on runtime JSON parsing; AS
|
|
316
|
+
cannot run this case.
|
|
317
|
+
* **watr: near parity.** jz is 1.07× slower than V8 on a 144 kB compiler
|
|
318
|
+
bundle. This is the only case where V8's profile-guided JIT tiers beat
|
|
319
|
+
jz's AOT wasm.
|
package/bench/bench.svg
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 500" width="720" height="500" role="img" aria-label="jz benchmark — each ball runs back and forth at a rate proportional to that engine's speed; jz is the 1.00× baseline, lower is faster">
|
|
2
|
+
<rect width="720" height="500" 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">→ wasm</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">Rust</text>
|
|
21
|
+
<text x="156" y="106" text-anchor="end" font-size="10" fill="#aeb4ba">rustc -O3</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.7111717105322697s" 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">Zig</text>
|
|
33
|
+
<text x="156" y="156" text-anchor="end" font-size="10" fill="#aeb4ba">ReleaseFast</text>
|
|
34
|
+
<text x="662" y="149" font-size="13" font-weight="500" fill="#868e96">1.31×</text>
|
|
35
|
+
<circle cx="182.0" cy="145" r="8" fill="#adb5bd">
|
|
36
|
+
<animate attributeName="cx" dur="2.097403438402887s" 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">Bun</text>
|
|
45
|
+
<text x="156" y="206" text-anchor="end" font-size="10" fill="#aeb4ba">JSC</text>
|
|
46
|
+
<text x="662" y="199" font-size="13" font-weight="500" fill="#868e96">1.51×</text>
|
|
47
|
+
<circle cx="182.0" cy="195" r="8" fill="#adb5bd">
|
|
48
|
+
<animate attributeName="cx" dur="2.4126551201460966s" 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">Go</text>
|
|
57
|
+
<text x="156" y="256" text-anchor="end" font-size="10" fill="#aeb4ba">gc</text>
|
|
58
|
+
<text x="662" y="249" font-size="13" font-weight="500" fill="#868e96">1.62×</text>
|
|
59
|
+
<circle cx="182.0" cy="245" r="8" fill="#adb5bd">
|
|
60
|
+
<animate attributeName="cx" dur="2.599256056991375s" 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">V8</text>
|
|
69
|
+
<text x="156" y="306" text-anchor="end" font-size="10" fill="#aeb4ba">Node</text>
|
|
70
|
+
<text x="662" y="299" font-size="13" font-weight="500" fill="#868e96">1.98×</text>
|
|
71
|
+
<circle cx="182.0" cy="295" r="8" fill="#adb5bd">
|
|
72
|
+
<animate attributeName="cx" dur="3.161748945741268s" 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">AssemblyScript</text>
|
|
81
|
+
<text x="156" y="356" text-anchor="end" font-size="10" fill="#aeb4ba">asc -O3</text>
|
|
82
|
+
<text x="662" y="349" font-size="13" font-weight="500" fill="#868e96">2.02×</text>
|
|
83
|
+
<circle cx="182.0" cy="345" r="8" fill="#adb5bd">
|
|
84
|
+
<animate attributeName="cx" dur="3.234040602137033s" 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">Porffor</text>
|
|
93
|
+
<text x="156" y="406" text-anchor="end" font-size="10" fill="#aeb4ba">runs 3 / 13</text>
|
|
94
|
+
<text x="662" y="399" font-size="13" font-weight="500" fill="#868e96">3.08×</text>
|
|
95
|
+
<circle cx="182.0" cy="395" r="8" fill="#adb5bd">
|
|
96
|
+
<animate attributeName="cx" dur="4.926330541139543s" 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
|
+
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
101
|
+
<rect x="174" y="443.5" width="476" height="3" rx="1.5" fill="#edf0f2"/>
|
|
102
|
+
<line x1="174" y1="438" x2="174" y2="452" stroke="#ced4da" stroke-width="2"/>
|
|
103
|
+
<line x1="650" y1="438" x2="650" y2="452" stroke="#ced4da" stroke-width="2"/>
|
|
104
|
+
<text x="156" y="441" text-anchor="end" font-size="14" font-weight="500" fill="#343a40">NumPy</text>
|
|
105
|
+
<text x="156" y="456" text-anchor="end" font-size="10" fill="#aeb4ba">Python</text>
|
|
106
|
+
<text x="662" y="449" font-size="13" font-weight="500" fill="#868e96">8.31×</text>
|
|
107
|
+
<circle cx="182.0" cy="445" r="8" fill="#adb5bd">
|
|
108
|
+
<animate attributeName="cx" dur="10s" repeatCount="indefinite" calcMode="linear"
|
|
109
|
+
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-3.28s"/>
|
|
110
|
+
</circle>
|
|
111
|
+
</g>
|
|
112
|
+
</svg>
|
package/cli.js
CHANGED
|
@@ -7,24 +7,27 @@
|
|
|
7
7
|
import { readFileSync, writeFileSync } from 'fs'
|
|
8
8
|
import { resolve } from 'path'
|
|
9
9
|
import { pathToFileURL } from 'url'
|
|
10
|
-
import { parse } from 'subscript/feature/jessie'
|
|
11
10
|
import jz, { compile } from './index.js'
|
|
12
|
-
import
|
|
13
|
-
import { codegen } from './src/codegen.js'
|
|
11
|
+
import transform from './transform.js'
|
|
14
12
|
import { resolveModuleGraph } from './src/resolve.js'
|
|
15
13
|
import { createRequire } from 'module'
|
|
16
14
|
|
|
17
15
|
const jzRequire = createRequire(import.meta.url)
|
|
18
16
|
const PKG = jzRequire('./package.json')
|
|
19
17
|
|
|
18
|
+
function formatWarning(w) {
|
|
19
|
+
const where = w.line != null ? ` (${w.line}:${w.column})` : ''
|
|
20
|
+
return `warning[${w.code}]${where}: ${w.message}`
|
|
21
|
+
}
|
|
22
|
+
|
|
20
23
|
function showHelp() {
|
|
21
24
|
console.log(`
|
|
22
25
|
jz v${PKG.version} - min JS → WASM compiler
|
|
23
26
|
|
|
24
27
|
Usage:
|
|
25
|
-
jz <file.js> Compile JS to WASM (
|
|
26
|
-
jz --strict <file.js> Strict mode
|
|
27
|
-
jz --jzify <file.js> Transform JS → jz (auto-derives output file)
|
|
28
|
+
jz <file.js> Compile JS to WASM (full JS subset; .jz = strict)
|
|
29
|
+
jz --strict <file.js> Strict mode — pure canonical subset, no lowering
|
|
30
|
+
jz --jzify <file.js> Transform JS → jz source (auto-derives output file)
|
|
28
31
|
jz -e <expression> Evaluate expression
|
|
29
32
|
jz --help Show this help
|
|
30
33
|
|
|
@@ -47,8 +50,8 @@ Options:
|
|
|
47
50
|
--host <js|wasi> Runtime-service lowering (default js)
|
|
48
51
|
--no-alloc Omit _alloc/_clear allocator exports (standalone wasm)
|
|
49
52
|
--names Emit wasm name section for profilers/debuggers
|
|
50
|
-
--strict
|
|
51
|
-
--jzify Transform JS to jz (no compilation)
|
|
53
|
+
--strict Pure canonical subset: reject full-JS syntax + dynamic fallbacks
|
|
54
|
+
--jzify Transform JS to jz source (no compilation)
|
|
52
55
|
--eval, -e Evaluate expression or file
|
|
53
56
|
--wat Output WAT text instead of binary
|
|
54
57
|
--resolve Resolve bare specifiers via Node.js module resolution
|
|
@@ -84,18 +87,20 @@ async function main() {
|
|
|
84
87
|
|
|
85
88
|
async function handleEvaluate(args) {
|
|
86
89
|
const input = args.join(' ')
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const isFile = args.length === 1 && (args[0].endsWith('.js') || args[0].endsWith('.jz'))
|
|
91
|
+
// A bare expression ("1 + 2") is wrapped in an arrow so its value can be printed. A file, or
|
|
92
|
+
// `-e` text that is already module-level (top-level export/import), compiles as-is — wrapping it
|
|
93
|
+
// would splice `export let _ = () => export …` and crash with a garbled SyntaxError.
|
|
94
|
+
const isModule = isFile || /^\s*(export|import)\b/m.test(input)
|
|
95
|
+
const code = isFile ? readFileSync(args[0], 'utf8')
|
|
96
|
+
: isModule ? input
|
|
97
|
+
: `export let _ = () => ${input}`
|
|
93
98
|
|
|
94
99
|
const { exports } = jz(code)
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
if (exports.
|
|
98
|
-
else console.log(exports)
|
|
101
|
+
if (exports._ !== undefined) console.log(exports._()) // expression eval → print value
|
|
102
|
+
else if (typeof exports.main === 'function') console.log(exports.main()) // module with a main() entry
|
|
103
|
+
else console.log(`compiled; exports: ${Object.keys(exports).join(', ') || '(none)'}`)
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
async function handleJzify(args) {
|
|
@@ -107,9 +112,7 @@ async function handleJzify(args) {
|
|
|
107
112
|
if (!inputFile) throw new Error('No input file specified')
|
|
108
113
|
if (!outputFile) outputFile = inputFile.replace(/\.js$/, '.jz')
|
|
109
114
|
const code = readFileSync(inputFile, 'utf8')
|
|
110
|
-
const
|
|
111
|
-
const transformed = jzifyFn(ast)
|
|
112
|
-
const out = codegen(transformed) + '\n'
|
|
115
|
+
const out = transform(code) + '\n'
|
|
113
116
|
if (outputFile === '-') {
|
|
114
117
|
process.stdout.write(out)
|
|
115
118
|
} else {
|
|
@@ -154,17 +157,19 @@ async function handleCompile(args) {
|
|
|
154
157
|
const { code: codeRewritten, modules } = resolveModuleGraph(inputFile, { resolveNode })
|
|
155
158
|
if (process.env.JZ_DEBUG_MODULES === '1') console.error('modules:', Object.keys(modules))
|
|
156
159
|
|
|
157
|
-
//
|
|
158
|
-
//
|
|
160
|
+
// jzify is default-on; strict (pure canonical subset) is opt-in.
|
|
161
|
+
// `.jz` files are treated as strict; `--strict` forces it for any extension.
|
|
162
|
+
if (inputFile.endsWith('.jz')) strict = true
|
|
163
|
+
const warnings = { entries: [] }
|
|
159
164
|
const opts = {
|
|
160
165
|
wat,
|
|
161
|
-
|
|
166
|
+
warnings,
|
|
162
167
|
strict,
|
|
163
168
|
importMetaUrl: pathToFileURL(resolve(inputFile)).href,
|
|
164
169
|
...(optimize !== undefined && { optimize }),
|
|
165
170
|
...(host && { host }),
|
|
166
171
|
...(alloc === false && { alloc: false }),
|
|
167
|
-
...(names && {
|
|
172
|
+
...(names && { profile: { names: true } }),
|
|
168
173
|
...(Object.keys(modules).length && { modules }),
|
|
169
174
|
}
|
|
170
175
|
|
|
@@ -175,6 +180,9 @@ async function handleCompile(args) {
|
|
|
175
180
|
|
|
176
181
|
const result = compile(codeRewritten, opts)
|
|
177
182
|
|
|
183
|
+
for (const w of warnings.entries)
|
|
184
|
+
console.warn(formatWarning(w))
|
|
185
|
+
|
|
178
186
|
if (outputFile === '-') {
|
|
179
187
|
process.stdout.write(result)
|
|
180
188
|
} else if (wat) {
|