jz 0.8.0 → 0.9.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 +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6178
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +318 -43
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +1032 -145
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +428 -93
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +82 -11
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +1055 -70
- package/src/compile/index.js +277 -29
- package/src/compile/infer.js +42 -4
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/narrow.js +555 -18
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1179 -704
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +982 -67
- package/src/prepare/index.js +809 -67
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- package/wasi.js +3 -0
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ JZ distills **"the good parts"** ([Crockford](https://www.youtube.com/watch?v=_D
|
|
|
21
21
|
| Good for | Not for |
|
|
22
22
|
|------------------------------|---------------------------|
|
|
23
23
|
| DSP, audio, synthesis | UI, DOM, the frontend |
|
|
24
|
-
| Image, video, pixels |
|
|
25
|
-
| Simulation, physics, games |
|
|
24
|
+
| Image, video, pixels | Serving HTTP, hot I/O |
|
|
25
|
+
| Simulation, physics, games | I/O-bound orchestration |
|
|
26
26
|
| Parsers, codecs, compression | Dynamic, polymorphic, OOP |
|
|
27
27
|
| Scientific, numeric, ML | Security crypto, big-ints |
|
|
28
28
|
| Hashing, checksums, RNG | Glue, plumbing, orchestration |
|
|
@@ -35,7 +35,7 @@ Output `.wasm` is portable — run it in any host (browser, Node, Deno, edge, pl
|
|
|
35
35
|
`npm install jz`
|
|
36
36
|
|
|
37
37
|
```js
|
|
38
|
-
import jz, { compile, compileModule, instantiate } from 'jz'
|
|
38
|
+
import jz, { compile, compileModule, instantiate, transform } from 'jz'
|
|
39
39
|
|
|
40
40
|
// Compile + instantiate
|
|
41
41
|
const { exports: { add } } = jz('export let add = (a, b) => a + b')
|
|
@@ -52,6 +52,12 @@ instantiate(mod).exports.f(21) // 42 — repeat cheaply, no recompile
|
|
|
52
52
|
const asyncMod = await WebAssembly.compile(wasm)
|
|
53
53
|
const asyncInst = await WebAssembly.instantiate(asyncMod)
|
|
54
54
|
asyncInst.exports.f(21) // 42
|
|
55
|
+
|
|
56
|
+
// jzify as a standalone source→source transform — the same lowering the
|
|
57
|
+
// compiler runs, printed back as canonical jz (also `import transform from 'jz/transform'`)
|
|
58
|
+
transform('var x = 1; function f() { return x }')
|
|
59
|
+
// → 'const f = () => {\n return x;\n};\nlet x;\nx = 1;' (decls hoist, then init order)
|
|
60
|
+
transform(alreadyCanonicalSource, { onlyLowered: true }) // → null (nothing to lower)
|
|
55
61
|
```
|
|
56
62
|
|
|
57
63
|
<details>
|
|
@@ -151,7 +157,7 @@ Options:
|
|
|
151
157
|
|
|
152
158
|
## Language
|
|
153
159
|
|
|
154
|
-
JZ is a **strict modern JS subset**. Built-in jzify transform extends support to legacy patterns.
|
|
160
|
+
JZ is a **strict modern JS subset**. Built-in jzify transform extends support to legacy/async patterns.
|
|
155
161
|
|
|
156
162
|
```
|
|
157
163
|
┌────────────────────────────────────────────────────────────────────────┐
|
|
@@ -162,18 +168,23 @@ JZ is a **strict modern JS subset**. Built-in jzify transform extends support to
|
|
|
162
168
|
│ │ try/catch/finally throw │ │
|
|
163
169
|
│ │ operators strings booleans numbers arrays objects `${}` │ │
|
|
164
170
|
│ │ Math Number String Array Object JSON RegExp Symbol null │ │
|
|
165
|
-
│ │ ArrayBuffer DataView TypedArray Map Set
|
|
171
|
+
│ │ ArrayBuffer DataView TypedArray Map Set Atomics │ │
|
|
166
172
|
│ │ parseInt parseFloat encodeURIComponent Error BigInt │ │
|
|
167
173
|
│ │ console setTimeout/setInterval Date performance │ │
|
|
174
|
+
│ │ structuredClone groupBy Set algebra iterator helpers │ │
|
|
175
|
+
│ │ fs.read/write (WASI hosts) fetch via async host imports │ │
|
|
168
176
|
│ └────────────────────────────────────────────────────────────────────┘ │
|
|
169
177
|
│ jz default (jzify) │
|
|
170
178
|
│ var function arguments switch new Foo() │
|
|
171
179
|
│ class new this extends super static #private │
|
|
180
|
+
│ function* yield yield* Foo.prototype.m = … │
|
|
181
|
+
│ async/await async function* for await Promise using │
|
|
182
|
+
│ Symbol.iterator Symbol.asyncIterator Symbol.dispose │
|
|
183
|
+
│ SharedArrayBuffer (→ ArrayBuffer) │
|
|
172
184
|
│ == != instanceof undefined WeakMap WeakSet │
|
|
173
185
|
│ │
|
|
174
186
|
└────────────────────────────────────────────────────────────────────────┘
|
|
175
187
|
Not supported
|
|
176
|
-
async/await Promise function* yield
|
|
177
188
|
delete getters/setters eval Function with
|
|
178
189
|
Proxy Reflect
|
|
179
190
|
import() DOM fetch Intl Node APIs
|
|
@@ -192,17 +203,51 @@ Each follows one rule: **JZ takes WASM/native conventions over JS edge-cases whe
|
|
|
192
203
|
- **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.
|
|
193
204
|
- **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).
|
|
194
205
|
- **No GC** — call `memory.reset()` between batches; `WeakMap`/`WeakSet` wired to `Map`/`Set`.
|
|
195
|
-
-
|
|
206
|
+
- **Pseudo-classical constructors** — `function P(x) { this.x = x }` + `P.prototype.m = function () {…}` fold into the class lowering automatically (the pre-`class` npm idiom); arrow-valued members keep lexical `this` and stay out.
|
|
207
|
+
- **Generators (sync) + iterator helpers** — `function*`/`yield` compile to regenerator-style state machines (no stack suspension): `next(v)`/`return(v)` are ordinary closure calls, `for-of` over a generator call desugars to a plain loop, and ES2025 helper chains (`g().map(f).filter(p).take(n)`, terminals `toArray`/`reduce`/`some`/`every`/`find`/`forEach`) fuse into ONE loop — no intermediate iterator objects. Helper results are also first-class VALUES (`let evens = nat().filter(even)` then `evens.take(5).toArray()`, `x instanceof Iterator`, `Array.from(iter)`) — helper-using programs mint decorated iterator objects, spec-shaped (value+counter callbacks, lazy, early close). `yield*` delegates to any iterable, `for-of` drives any iterator value (stored machines, hand-rolled `{ next }`, `[Symbol.iterator]()` providers) lazily, and `[...g()]` spreads via the fused path. v1 scope: no `try` across yield — rejects with a precise message.
|
|
208
|
+
- **async/await + Promise, no engine event loop** — an `async` function lowers to the same state machine (`await` ≡ `yield`) driven by a plain-jz promise runtime compiled into the module (pay-per-use: sync programs link none of it). Promises are ordinary fixed-shape objects with `then`/`catch`/`finally`; `Promise.resolve/reject/all/race/allSettled/any/try/withResolvers`, `new Promise(executor)`, and plain-object thenable adoption all work. Async GENERATORS ride the same machine with tagged yields (`async function*`, `yield*` delegation, serialized `next()`), and `for await (x of src)` drives async iterators, sync iterators, and arrays of promises alike. The job queue drains at host boundaries (export return, timer tick) — an async export returns a real host `Promise` that settles when the machine completes (including across `setTimeout` awaits). Divergences: job ordering is per-drain-cycle rather than per-continuation, no unhandled-rejection reporting, no `try` across `await` in v1 (precise reject), and `memory.reset()` while a promise is pending is out of contract.
|
|
209
|
+
- **`fetch` just works** — a bare `fetch(url)` binds from the JS host automatically (no import statement); the `Response` crosses as a live external handle, so `r.text()`/`r.json()` dispatch host-side and are awaitable in turn. Any custom host function returning a `Promise` is likewise awaitable via `{ imports }`. I/O stays host-side (the doctrine), asynchrony crosses the boundary:
|
|
210
|
+
```js
|
|
211
|
+
const { exports } = jz(`
|
|
212
|
+
export let title = async (url) => {
|
|
213
|
+
let r = await fetch(url)
|
|
214
|
+
let body = await r.text()
|
|
215
|
+
return body.slice(body.indexOf('<title>') + 7, body.indexOf('</title>'))
|
|
216
|
+
}`)
|
|
217
|
+
await exports.title('https://example.com')
|
|
218
|
+
```
|
|
219
|
+
Identical in browser and Node — no WASI involved (WASI preview1 has no networking; wasi-http needs the component model, which stays future). Under `--host wasi` a warning tells you to wire `env.fetch` yourself.
|
|
220
|
+
- **Workers v1 (shared-memory SPMD)** — `sharedMemory: true` compiles against a shared `WebAssembly.Memory` (atomic heap bump, wasm `shared` memtype); `Atomics.*` on Int32Array lowers to wasm thread ops (`wait`/`notify` included); `jz.pool(src, {threads})` runs the same kernel across node worker_threads over one memory — annotate shared-array params as `(arr = new Int32Array(0))`. v1 contract: shared typed arrays + scalars; strings/objects stay thread-local.
|
|
221
|
+
- **`String(number)` is ES-spec exact** — shortest round-trip digits via a built-in Ryū formatter (`String(0.1 + 0.2)` → `"0.30000000000000004"`, `String(Math.PI)` → `"3.141592653589793"`), including exponential notation and subnormals; its ~9.7 KB power-of-5 table is lazily included only in modules that stringify floats.
|
|
196
222
|
- **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.
|
|
197
223
|
- **`Date` getters return UTC** (`getHours` ≡ `getUTCHours`) – the IANA timezone database is hundreds of KB.
|
|
198
224
|
- **`Math.random` is seedable** — default draws host entropy; pass `randomSeed: n` for a reproducible stream.
|
|
199
225
|
</details>
|
|
200
226
|
|
|
227
|
+
<details>
|
|
228
|
+
<summary><strong>What will JZ never support — and why?</strong></summary>
|
|
229
|
+
|
|
230
|
+
Everything else is compiled, lowered by the built-in jzify pass, or rejected with a clean error — silent divergence is treated as a bug. These stay out **by design**; each is traded for a zero-cost guarantee:
|
|
231
|
+
|
|
232
|
+
- **Proxy, Reflect** — traps have no meaning over fixed-shape structs with compile-time-resolved offsets.
|
|
233
|
+
- **Property descriptors & accessors** — `defineProperty` descriptors, getters/setters, `writable`/`enumerable`: objects carry values only, no per-property metadata — that's what makes a property read one load.
|
|
234
|
+
- **Live prototype chains** — `__proto__`, delegation, monkey-patching: `Object.create(proto)` is a documented shallow copy; method dispatch is static.
|
|
235
|
+
- **`delete` on literal-key properties** — an object's shape is fixed at construction (computed-key/dictionary-mode `delete o[k]` works).
|
|
236
|
+
- **eval, `Function` constructor, `with`** — would require the compiler (or an interpreter) at runtime; JZ ships neither.
|
|
237
|
+
- **Intl, Temporal** — ICU/CLDR and timezone tables are hundreds of KB to MB, against single-digit-kB output. `Date` keeps deterministic UTC slices.
|
|
238
|
+
- **UTF-16 string semantics & Unicode tables** — strings are UTF-8 bytes; `\p{…}` classes, `normalize` forms and locale case tables are the same multi-KB cost Intl was refused for.
|
|
239
|
+
- **Arbitrary-precision BigInt** — BigInt is a raw 64-bit integer (wraps past ±2⁶³); bignum chains allocate unboundedly, and security crypto is explicitly out of scope.
|
|
240
|
+
- **WeakRef, FinalizationRegistry** — no GC to observe; `WeakMap`/`WeakSet` fold to `Map`/`Set`.
|
|
241
|
+
- **Annex B legacies, DOM, fetch, Node APIs** — the parts of JS the subset exists to shed; I/O stays host-side.
|
|
242
|
+
|
|
243
|
+
The litmus for this list: the feature either needs a runtime JZ refuses to ship, or per-access metadata that would tax every program — including the ones not using it.
|
|
244
|
+
</details>
|
|
245
|
+
|
|
201
246
|
|
|
202
247
|
<details>
|
|
203
248
|
<summary><strong>Can I use existing npm packages or JS libraries?</strong></summary>
|
|
204
249
|
|
|
205
|
-
Only the ones that fit the JZ subset. There's no runtime, so packages touching the DOM
|
|
250
|
+
Only the ones that fit the JZ subset. There's no runtime, so packages touching the DOM or Node APIs won't compile — but pure numeric/algorithmic source does, `async`/`Promise` code included (network calls cross as awaitable host imports, not bundled clients).
|
|
206
251
|
|
|
207
252
|
- **Relative imports** (`./dep.js`) bundle at compile time.
|
|
208
253
|
- **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.
|
|
@@ -315,6 +360,8 @@ There's two possible `host` targets:
|
|
|
315
360
|
- **`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.
|
|
316
361
|
- **`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.
|
|
317
362
|
|
|
363
|
+
Under `--host wasi` module init ships as the standard WASI reactor export `_initialize` (never a wasm start section — the p1 ABI forbids WASI calls there). ABI-following hosts (wasmtime, node:wasi `initialize()`, `jz/wasi`, `jz/interop`) call it after wiring memory; instantiating by hand, call `instance.exports._initialize?.()` once before anything else. Command entries (`run`, `_start`) self-init, so invoking them directly also works.
|
|
364
|
+
|
|
318
365
|
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:
|
|
319
366
|
|
|
320
367
|
| What your code does | `js` (default) | `wasi` |
|
|
@@ -446,7 +493,7 @@ Literals (`0` vs `0.5`), operators (`|` `<<` `&` ⇒ i32), and how a value is us
|
|
|
446
493
|
<details>
|
|
447
494
|
<summary><strong>Is JZ production-ready?</strong></summary>
|
|
448
495
|
|
|
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
|
|
496
|
+
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 slice (≈3,900 language + built-ins files at **0 fail** — every excluded test is skip-classified by name, so a new failure can't hide), the benchmark gate, and the self-host build in CI, so regressions surface immediately.
|
|
450
497
|
|
|
451
498
|
</details>
|
|
452
499
|
|
package/bench/README.md
CHANGED
|
@@ -53,6 +53,8 @@ node bench/bench.mjs mat4 --targets=nat,v8,jz
|
|
|
53
53
|
| [`dotprod`](dotprod/dotprod.js) | multiply-accumulate reduction; JZ reassociates to 4 SIMD accumulators, beating strict-fp serial native |
|
|
54
54
|
| [`bytebeat`](bytebeat/bytebeat.js) | classic integer "bytebeat" one-line-song synthesis to 8-bit PCM; pure i32, bit-exact everywhere |
|
|
55
55
|
| [`fft`](fft/fft.js) | radix-2 Cooley–Tukey FFT over a transcendental-free twiddle table; the canonical numeric/audio kernel |
|
|
56
|
+
| [`fftplan`](fftplan/fftplan.js) | the same butterflies through a Map-cached plan object — how every JS DSP lib ships FFT; exposes typed-array kind loss through return values / object fields / params (dynamic element access in the hot loop) |
|
|
57
|
+
| [`provenance`](provenance/provenance.js) | one butterfly kernel over bit-identical tables that differ only in provenance — direct return, returned-object field, Map-cached field, module-global memo; per-edge coverage for the `fftplan` kind-loss class |
|
|
56
58
|
| [`synth`](synth/synth.js) | minisynth audio pipeline — polynomial oscillator + ADSR + biquad per sample; loop-carried f64 |
|
|
57
59
|
| [`blur`](blur/blur.js) | separable box blur on an RGBA8 image; integer stencil with edge clamp, the canonical image pipeline |
|
|
58
60
|
| [`conv2d`](conv2d/conv2d.js) | int8 quantized conv layer (Cin→Cout, 3×3, i32 MAC + ReLU requant); the spatial-convolution counterpart to dense `matmul`, the edge-ML inference kernel |
|
|
@@ -70,16 +72,28 @@ node bench/bench.mjs mat4 --targets=nat,v8,jz
|
|
|
70
72
|
| [`sieve`](sieve/sieve.js) | Sieve of Eratosthenes over a byte array; pure strided scatter (j = i², i²+i, …) guarded by an outer branch — a non-contiguous write pattern |
|
|
71
73
|
| [`vm`](vm/vm.js) | tiny bytecode interpreter — a fetch-decode-dispatch loop (if/else opcode chain + indirect code-array loads) running an integer recurrence; the canonical VM/regex-engine inner loop |
|
|
72
74
|
| [`spmv`](spmv/spmv.js) | sparse matrix×vector in CSR form; the MAC inner loop gathers `x[col[k]]` through a column-index array — the data-dependent gather dense codegen handles worst (exact-integer f64, bit-identical everywhere) |
|
|
73
|
-
| [`hashjoin`](hashjoin/hashjoin.js) | probe-dominated relational hash join (build small, stream a large probe side, sum matched payloads) — the database/dataframe kernel and JZ's boss case: `probe()` returns a typed-array element through a param JZ can't yet prove is i32, so `sum + probe()` lowers to a polymorphic number-or-string add per probe — the only case JZ
|
|
75
|
+
| [`hashjoin`](hashjoin/hashjoin.js) | probe-dominated relational hash join (build small, stream a large probe side, sum matched payloads) — the database/dataframe kernel and JZ's boss case: `probe()` returns a typed-array element through a param JZ can't yet prove is i32, so `sum + probe()` lowers to a polymorphic number-or-string add per probe — then the only case JZ trailed V8; proving the return i32 measured 8.7 → 5.2 ms, jz now beats V8 1.3× |
|
|
76
|
+
| [`dispatch`](dispatch/dispatch.js) | data-driven dispatch through a table of 8 first-class functions — one call site fans out per an unpredictable opcode stream; the megamorphic call-site / virtual-dispatch kernel (strategy tables, event pipelines, effect chains), the classic call-IC deopt shape |
|
|
77
|
+
| [`shapes`](shapes/shapes.js) | per-variant measure summed over records of 8 heterogeneous object shapes — every access site sees 8 hidden classes; the megamorphic property-load / hidden-class kernel (JSON rows, AST nodes, ECS entities), the classic load-IC deopt shape — and JZ's widest gap (the schema union falls to dynamic-property probes) |
|
|
78
|
+
| [`strbuild`](strbuild/strbuild.js) | per-record string formatting (`id,name,value\n` per row: int→string + concat + char fold) — the serializer/logger inner loop, the string-allocation deopt shape (a giant `+=` accumulator is deliberately avoided: eager no-GC strings make it quadratic in allocation — jz exhausts memory on it) |
|
|
79
|
+
| [`wordcount`](wordcount/wordcount.js) | token-frequency counting into a plain object with 512 computed string keys over a skewed stream — the group-by/histogram kernel; a JIT drops the object to dictionary mode (the keyed-store deopt), while jz's string-keyed hash store + interning carry it |
|
|
80
|
+
| [`immutable`](immutable/immutable.js) | integer particle step in the immutable-update idiom — every step replaces each record with a fresh `{x,y,vx,vy}` (React/Redux-style state); value-semantics natives get it free, a JIT leans on young-gen GC + escape analysis, jz pays a bump allocation per object — the escape-analysis/SROA probe |
|
|
81
|
+
| [`colorconv`](colorconv/colorconv.js) | sRGB → Oklab over an image buffer — EOTF pow → 3×3 → cbrt → 3×3; lab probe for pow/cbrt intrinsic quality |
|
|
82
|
+
| [`colorlch`](colorlch/colorlch.js) | sRGB → OkLCh fused in one loop (pow → 3×3 → cbrt → 3×3 → sqrt + atan2); lab probe — the fused body must never lose to the split loops |
|
|
83
|
+
| [`colorlog`](colorlog/colorlog.js) | ARRI LogC4 → XYZ — runtime-base exp2 decode + 3×3; lab probe for runtime-exponent pow/exp2 |
|
|
84
|
+
| [`colorpq`](colorpq/colorpq.js) | sRGB → JzAzBz — 3×3 → ST 2084 PQ (≈12 signed-pow) → 3×3; lab probe for signed pow with non-constant exponent |
|
|
74
85
|
| [`watr`](watr/watr.js) | watr's WAT-to-wasm compiler on a small WAT corpus; compares jz-compiled compiler code with raw V8 |
|
|
75
86
|
| [`jessie`](jessie/jessie.js) | the subscript/jessie JS parser over a realistic source corpus; branch-, allocation- and recursion-heavy front-end work |
|
|
76
87
|
| [`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 |
|
|
77
88
|
|
|
78
|
-
The `
|
|
79
|
-
compiling code
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
cross-language kernel comparison
|
|
89
|
+
The **`lab` rows** — the self-referential `watr`/`jessie`/`jz` (JZ or its deps
|
|
90
|
+
compiling code) and the JS-only intrinsic probes `colorconv`/`colorlch`/
|
|
91
|
+
`colorlog`/`colorpq` (pow/cbrt/exp2/atan2 gap trackers with no cross-language
|
|
92
|
+
ports; their transcendental checksums legitimately diverge per libm) — answer
|
|
93
|
+
jz-internal questions, not the cross-language kernel comparison. They sit out
|
|
94
|
+
every aggregate (the geomean SVG, the page strip and geomeans, the aggregate
|
|
95
|
+
table below) but stay visible on the bench page under the `lab` chip and
|
|
96
|
+
runnable via `--cases=…`; the self-host rows stay gated in `test/bench.js`.
|
|
83
97
|
|
|
84
98
|
Native rows for `json` are fixed-source references, not semantic equivalents
|
|
85
99
|
of JavaScript `JSON.parse`: C/Rust/Zig hand-parse the known schema from a
|
|
@@ -210,9 +224,9 @@ wasm-in-V8 would be a wrong-class single-case compare.
|
|
|
210
224
|
|
|
211
225
|
| target | median | ×v8 | size | parity |
|
|
212
226
|
| --- | ---: | ---: | ---: | --- |
|
|
213
|
-
| **JZ → V8 wasm** | **4.
|
|
214
|
-
| AssemblyScript (asc -O3 --runtime stub) | 6.
|
|
215
|
-
| V8 (node) raw JS |
|
|
227
|
+
| **JZ → V8 wasm** | **4.68 ms** | **1.94×** | **1.7 kB** | **ok** |
|
|
228
|
+
| AssemblyScript (asc -O3 --runtime stub) | 6.59 ms | 1.38× | 1.8 kB | ok |
|
|
229
|
+
| V8 (node) raw JS | 9.09 ms | 1.00× | 3.2 kB | ok |
|
|
216
230
|
| hand-WAT → V8 wasm | 6.49 ms | 1.90× | 767 B | ok |
|
|
217
231
|
|
|
218
232
|
JZ beats V8 raw JS by 2.1× and AS by 1.4×. The typed-array scalarization,
|
|
@@ -223,9 +237,9 @@ that matches the hand-WAT floor.
|
|
|
223
237
|
|
|
224
238
|
| target | median | ×v8 | size | parity |
|
|
225
239
|
| --- | ---: | ---: | ---: | --- |
|
|
226
|
-
| **JZ → V8 wasm** | **
|
|
227
|
-
| AssemblyScript (asc -O3 --runtime stub) | 6.
|
|
228
|
-
| V8 (node) raw JS | 8.
|
|
240
|
+
| **JZ → V8 wasm** | **0.77 ms** | **11.15×** | **1.5 kB** | **ok** |
|
|
241
|
+
| AssemblyScript (asc -O3 --runtime stub) | 6.72 ms | 1.28× | 1.4 kB | ok |
|
|
242
|
+
| V8 (node) raw JS | 8.60 ms | 1.00× | 1.2 kB | ok |
|
|
229
243
|
| hand-WAT → V8 wasm | 8.12 ms | 1.47× | 414 B | ok |
|
|
230
244
|
|
|
231
245
|
JZ is 5.9× faster than V8 raw JS and 4.6× faster than AS. The scalarized
|
|
@@ -236,9 +250,9 @@ this from JS source.
|
|
|
236
250
|
|
|
237
251
|
| target | median | ×v8 | size | parity |
|
|
238
252
|
| --- | ---: | ---: | ---: | --- |
|
|
239
|
-
| **JZ → V8 wasm** | **0.13 ms** | **12.
|
|
240
|
-
| AssemblyScript (asc -O3 --runtime stub) | 0.
|
|
241
|
-
| V8 (node) raw JS | 1.
|
|
253
|
+
| **JZ → V8 wasm** | **0.13 ms** | **12.30×** | **1.0 kB** | **ok** |
|
|
254
|
+
| AssemblyScript (asc -O3 --runtime stub) | 0.79 ms | 2.06× | 1.3 kB | ok |
|
|
255
|
+
| V8 (node) raw JS | 1.64 ms | 1.00× | 1014 B | ok |
|
|
242
256
|
|
|
243
257
|
JZ is 12.9× faster than V8 raw JS and 5.9× faster than AS. The bimorphic
|
|
244
258
|
`sum` (called with both `Float64Array` and `Int32Array`) stays on typed
|
|
@@ -248,10 +262,10 @@ paths without falling back to generic dispatch.
|
|
|
248
262
|
|
|
249
263
|
| target | median | ×v8 | size | parity |
|
|
250
264
|
| --- | ---: | ---: | ---: | --- |
|
|
251
|
-
| **JZ → V8 wasm** | **
|
|
252
|
-
| V8 (node) raw JS | 3.
|
|
253
|
-
| AssemblyScript (asc -O3 --runtime stub) | 9.
|
|
254
|
-
| hand-WAT → V8 wasm | 3.
|
|
265
|
+
| **JZ → V8 wasm** | **0.99 ms** | **3.81×** | **1.0 kB** | **ok** |
|
|
266
|
+
| V8 (node) raw JS | 3.77 ms | 1.00× | 1005 B | ok |
|
|
267
|
+
| AssemblyScript (asc -O3 --runtime stub) | 9.11 ms | 0.41× | 1.3 kB | ok |
|
|
268
|
+
| hand-WAT → V8 wasm | 3.49 ms | 1.08× | 355 B | ok |
|
|
255
269
|
|
|
256
270
|
JZ is 4.0× faster than V8 raw JS and 8.8× faster than AS. The i32 hot path
|
|
257
271
|
(`Math.imul`, `|0`, `>>>0`) now lowers to raw `i32` ops without NaN-box
|
|
@@ -261,9 +275,9 @@ overhead on every operation.
|
|
|
261
275
|
|
|
262
276
|
| target | median | ×v8 | size | parity |
|
|
263
277
|
| --- | ---: | ---: | ---: | --- |
|
|
264
|
-
| **JZ → V8 wasm** | **0.06 ms** | **2.
|
|
265
|
-
| AssemblyScript (asc -O3 --runtime stub) | 0.06 ms | 2.
|
|
266
|
-
| V8 (node) raw JS | 0.
|
|
278
|
+
| **JZ → V8 wasm** | **0.06 ms** | **2.16×** | **2.0 kB** | **ok** |
|
|
279
|
+
| AssemblyScript (asc -O3 --runtime stub) | 0.06 ms | 2.13× | 1.5 kB | ok |
|
|
280
|
+
| V8 (node) raw JS | 0.14 ms | 1.00× | 2.0 kB | ok |
|
|
267
281
|
|
|
268
282
|
JZ is 2.4× faster than V8 raw JS and now edges out AS by ~1.2× on this
|
|
269
283
|
`charCodeAt`-heavy scan. Both are well ahead of V8.
|
|
@@ -272,9 +286,9 @@ JZ is 2.4× faster than V8 raw JS and now edges out AS by ~1.2× on this
|
|
|
272
286
|
|
|
273
287
|
| target | median | ×v8 | size | parity |
|
|
274
288
|
| --- | ---: | ---: | ---: | --- |
|
|
275
|
-
| **JZ → V8 wasm** | **0.
|
|
276
|
-
| V8 (node) raw JS | 0.
|
|
277
|
-
| AssemblyScript (asc -O3 --runtime stub) | 0.80 ms | 0.
|
|
289
|
+
| **JZ → V8 wasm** | **0.27 ms** | **2.25×** | **1.4 kB** | **ok** |
|
|
290
|
+
| V8 (node) raw JS | 0.60 ms | 1.00× | 1.3 kB | ok |
|
|
291
|
+
| AssemblyScript (asc -O3 --runtime stub) | 0.80 ms | 0.76× | 1.8 kB | ok |
|
|
278
292
|
|
|
279
293
|
JZ is 2.3× faster than V8 raw JS and 2.9× faster than AS. Closure +
|
|
280
294
|
`Array.map` lowers to a preallocated typed loop with no per-iteration alloc.
|
|
@@ -284,9 +298,9 @@ V8's JIT does not inline the closure across the `map` boundary.
|
|
|
284
298
|
|
|
285
299
|
| target | median | ×v8 | size | parity |
|
|
286
300
|
| --- | ---: | ---: | ---: | --- |
|
|
287
|
-
| **JZ → V8 wasm** | **0.
|
|
288
|
-
| V8 (node) raw JS | 1.
|
|
289
|
-
| AssemblyScript (asc -O3 --runtime stub) | 1.40 ms | 0.
|
|
301
|
+
| **JZ → V8 wasm** | **0.67 ms** | **2.00×** | **1.7 kB** | **ok** |
|
|
302
|
+
| V8 (node) raw JS | 1.34 ms | 1.00× | 1.1 kB | ok |
|
|
303
|
+
| AssemblyScript (asc -O3 --runtime stub) | 1.40 ms | 0.96× | 1.9 kB | ok |
|
|
290
304
|
|
|
291
305
|
JZ is 1.9× faster than V8 raw JS and 2.0× faster than AS. Schema-slot
|
|
292
306
|
reads are direct field offsets; the gap is small because the workload is
|
|
@@ -296,9 +310,9 @@ memory-bound.
|
|
|
296
310
|
|
|
297
311
|
| target | median | ×v8 | size | parity |
|
|
298
312
|
| --- | ---: | ---: | ---: | --- |
|
|
299
|
-
| AssemblyScript (asc -O3 --runtime stub) | 8.
|
|
300
|
-
| **JZ → V8 wasm** | **8.45 ms** | **1.
|
|
301
|
-
| V8 (node) raw JS | 9.
|
|
313
|
+
| AssemblyScript (asc -O3 --runtime stub) | 8.85 ms | 1.11× | 1.3 kB | ok |
|
|
314
|
+
| **JZ → V8 wasm** | **8.45 ms** | **1.16×** | **1.1 kB** | **ok** |
|
|
315
|
+
| V8 (node) raw JS | 9.83 ms | 1.00× | 1.8 kB | ok |
|
|
302
316
|
|
|
303
317
|
JZ is 1.1× faster than V8 raw JS and ties AS. The dense f64 hot loop with
|
|
304
318
|
conditional break compacts to 1.0 kB — the smallest wasm in the suite.
|
|
@@ -307,7 +321,7 @@ conditional break compacts to 1.0 kB — the smallest wasm in the suite.
|
|
|
307
321
|
|
|
308
322
|
| target | median | ×v8 | size | parity |
|
|
309
323
|
| --- | ---: | ---: | ---: | --- |
|
|
310
|
-
| **JZ → V8 wasm** | **0.
|
|
324
|
+
| **JZ → V8 wasm** | **0.22 ms** | **1.22×** | **9.7 kB** | **ok** |
|
|
311
325
|
| V8 (node) raw JS | 0.27 ms | 1.00× | 1.2 kB | ok |
|
|
312
326
|
|
|
313
327
|
JZ is 1.3× faster than V8 raw JS. The runtime parser is specialized to the
|
|
@@ -317,9 +331,9 @@ inferred JSON shape; AS is skipped because it cannot parse JSON at runtime.
|
|
|
317
331
|
|
|
318
332
|
| target | median | ×v8 | size | parity |
|
|
319
333
|
| --- | ---: | ---: | ---: | --- |
|
|
320
|
-
| **JZ → V8 wasm** | **
|
|
321
|
-
| AssemblyScript (asc -O3 --runtime stub) | 7.
|
|
322
|
-
| V8 (node) raw JS |
|
|
334
|
+
| **JZ → V8 wasm** | **5.41 ms** | **1.64×** | **1.6 kB** | **ok** |
|
|
335
|
+
| AssemblyScript (asc -O3 --runtime stub) | 7.93 ms | 1.12× | 1.8 kB | ok |
|
|
336
|
+
| V8 (node) raw JS | 8.87 ms | 1.00× | 1.6 kB | ok |
|
|
323
337
|
|
|
324
338
|
JZ is 1.6× faster than V8 raw JS and 1.4× faster than AS. Call-heavy
|
|
325
339
|
nested loops with typed-array index propagation stay on the i32 path.
|
|
@@ -328,9 +342,9 @@ nested loops with typed-array index propagation stay on the i32 path.
|
|
|
328
342
|
|
|
329
343
|
| target | median | ×v8 | size | parity |
|
|
330
344
|
| --- | ---: | ---: | ---: | --- |
|
|
331
|
-
| **JZ → V8 wasm** | **8.
|
|
332
|
-
| AssemblyScript (asc -O3 --runtime stub) | 8.
|
|
333
|
-
| V8 (node) raw JS | 9.
|
|
345
|
+
| **JZ → V8 wasm** | **8.62 ms** | **1.12×** | **1.0 kB** | **ok** |
|
|
346
|
+
| AssemblyScript (asc -O3 --runtime stub) | 8.74 ms | 1.10× | 1.3 kB | ok |
|
|
347
|
+
| V8 (node) raw JS | 9.62 ms | 1.00× | 1.8 kB | ok |
|
|
334
348
|
|
|
335
349
|
JZ is 1.2× faster than V8 raw JS and ties AS. Integer narrowing and
|
|
336
350
|
typed-array parameter propagation keep the LUT lookup on raw i32.
|
|
@@ -419,12 +433,64 @@ dependent-gather / branchy-DP** kernels, not in dense scalar or dispatch loops.
|
|
|
419
433
|
Like the batch above, these stay bench-only (out of `test/bench.js`) until the gap
|
|
420
434
|
closes.
|
|
421
435
|
|
|
436
|
+
### Deopt kernels — dispatch, shapes, strings, maps, allocation
|
|
437
|
+
|
|
438
|
+
Five cases aimed square at the *deoptimization* shapes — the dynamic patterns
|
|
439
|
+
that make a JIT bail out of optimized code, and that an AOT compiler must
|
|
440
|
+
instead resolve statically. All are bit-identical across every target and
|
|
441
|
+
data-shuffled, so no branch predictor, inline cache, or devirtualizer can
|
|
442
|
+
settle on a fast path. Together they cover the dynamic-JS surface jz commits to
|
|
443
|
+
compile well: **calls** (`dispatch` — one call site fans out to a table of 8
|
|
444
|
+
first-class functions, the megamorphic call-IC shape), **hidden classes**
|
|
445
|
+
(`shapes` — records of 8 heterogeneous object shapes at one access site, the
|
|
446
|
+
megamorphic load-IC shape), **string churn** (`strbuild` — per-record
|
|
447
|
+
int→string + concat, the serializer inner loop), **keyed maps** (`wordcount` —
|
|
448
|
+
counting into a plain object with 512 computed string keys, the
|
|
449
|
+
dictionary-mode keyed-store deopt), and **allocation** (`immutable` — a fresh
|
|
450
|
+
`{x,y,vx,vy}` per particle per step, the React/Redux-style immutable update).
|
|
451
|
+
The static rivals write what a static language writes: fn-pointer tables,
|
|
452
|
+
tagged unions, stack-buffer formatting, string hash maps, value-type structs.
|
|
453
|
+
|
|
454
|
+
| case | JZ | vs V8 | vs AS | vs native C | what it probes |
|
|
455
|
+
| --- | ---: | ---: | ---: | ---: | --- |
|
|
456
|
+
| **dispatch** — fn-table dispatch | **10.4 ms** | 0.84× | **1.23×** | 0.46× | `call_indirect` vs megamorphic call IC |
|
|
457
|
+
| **strbuild** — per-record formatting | 1.83 ms | 0.92× | 0.83× | 0.83× | int→string + concat temporaries |
|
|
458
|
+
| **wordcount** — string-keyed counting | 8.80 ms | 0.30× | 0.93× | 0.28× | dynamic keys vs dictionary-mode IC |
|
|
459
|
+
| **immutable** — fresh-object step | 1.32 ms | 0.33× | 0.52× | 0.06× | escape analysis / allocation churn |
|
|
460
|
+
| **shapes** — 8-shape record scan | 18.1 ms | 0.19× | 0.06× | 0.09× | schema-union access vs megamorphic load IC |
|
|
461
|
+
|
|
462
|
+
The probes split jz's dynamic story cleanly. On `dispatch` **jz leads the
|
|
463
|
+
systems-language wasm field** — AS 1.23×, Rust/C/Zig/Go→wasm 1.05–1.2× — its
|
|
464
|
+
data-selected `call_indirect` is already tighter than what the systems
|
|
465
|
+
languages ship through the same table (only MoonBit's moonrun row edges it);
|
|
466
|
+
the remaining field is JIT call machinery — V8 0.84×, and JavaScriptCore's
|
|
467
|
+
call ICs win the case outright at ~2.3 ms — with bounded-table devirt (guarded
|
|
468
|
+
direct calls over the 8 known closures) the lever to close it. `strbuild` is
|
|
469
|
+
near-parity with V8 but rust-wasm leads 1.3× (and zig-wasm's stack-buffer
|
|
470
|
+
formatting 4.5×) — per-temporary `__str_concat` allocation and generic
|
|
471
|
+
`__itoa` are the levers. (The giant `out +=` accumulator variant is
|
|
472
|
+
deliberately excluded: eager no-GC strings make it quadratic in allocation —
|
|
473
|
+
jz exhausts memory on it. A real landmine for jz users, documented in the
|
|
474
|
+
case header.) `wordcount` ties AS's Map while V8's dictionary mode runs 3.3×
|
|
475
|
+
ahead and a plain C strcmp table 10× — per-op string hashing is the gap,
|
|
476
|
+
interning the lever. `immutable` trails every rival — jz bump-allocates each
|
|
477
|
+
escaping object with no scalar replacement and no reclamation, where V8's
|
|
478
|
+
young-gen GC recycles and value-semantics natives never allocate at all.
|
|
479
|
+
`shapes` stays **the widest gap in the suite, by design**: the 8-schema union
|
|
480
|
+
defeats schema-slot inference, so every field read lowers through the
|
|
481
|
+
`__dyn_get` dynamic-property probe — ~5× behind V8's megamorphic IC and ~16×
|
|
482
|
+
behind AS's kind-tagged flat struct (which ties native C); the general fix is
|
|
483
|
+
shape-set devirt — a bounded schema union lowering to a tag-switch over direct
|
|
484
|
+
slot loads, the static mirror of a polymorphic IC. `dispatch` passes the
|
|
485
|
+
fastest-wasm gate; `shapes`, `wordcount`, `immutable`, and `strbuild` are
|
|
486
|
+
pinned red in `WASM_TODO` — the deopt work list, loudest first.
|
|
487
|
+
|
|
422
488
|
### watr — WAT-to-wasm compiler on small corpus
|
|
423
489
|
|
|
424
490
|
| target | median | ×v8 | size | parity |
|
|
425
491
|
| --- | ---: | ---: | ---: | --- |
|
|
426
|
-
| V8 (node) raw JS |
|
|
427
|
-
| **JZ → V8 wasm** | **
|
|
492
|
+
| V8 (node) raw JS | 0.97 ms | 1.00× | 2.6 kB | ok |
|
|
493
|
+
| **JZ → V8 wasm** | **0.96 ms** | **1.00×** | **244.3 kB** | **DIFF** |
|
|
428
494
|
|
|
429
495
|
JZ is 1.07× slower than V8 raw JS on this large compiler bundle. The size
|
|
430
496
|
(144 kB) is the full jz-compiled watr parser + encoder + optimizer; V8's JIT
|
|
@@ -436,19 +502,24 @@ Aggregate geomean (JZ / target):
|
|
|
436
502
|
|
|
437
503
|
| target | speed | size |
|
|
438
504
|
| --- | ---: | ---: |
|
|
439
|
-
| V8 (node) | **0.
|
|
440
|
-
| AssemblyScript | **0.
|
|
505
|
+
| V8 (node) | **0.53×** | — |
|
|
506
|
+
| AssemblyScript | **0.58×** | **1.06×** |
|
|
441
507
|
|
|
442
|
-
JZ wins or ties V8 on every kernel case; the
|
|
443
|
-
self-
|
|
444
|
-
|
|
445
|
-
|
|
508
|
+
JZ wins or ties V8 on every dense kernel case; the open V8 losses are the
|
|
509
|
+
self-host lab rows (`watr`, `jessie`) and the deliberate deopt probes above
|
|
510
|
+
(`dispatch`, `shapes`, `wordcount`, `immutable`, `strbuild` — the dynamic-JS
|
|
511
|
+
work list). AS is beaten on speed across the shared cases except the tracked
|
|
512
|
+
gather/probe gaps (`dict`, `noise`, `levenshtein`) and the deopt probes
|
|
513
|
+
(`shapes`, `immutable`, `strbuild`; `wordcount` is a tie). On size JZ matches
|
|
514
|
+
AS (geomean ~1.0×) — JZ wins on speed and holds size parity.
|
|
446
515
|
|
|
447
516
|
Against the systems languages compiled to the same target — **WebAssembly, run in
|
|
448
|
-
V8** — JZ is **
|
|
449
|
-
That apples-to-apples wasm field is the headline chart
|
|
450
|
-
|
|
451
|
-
|
|
517
|
+
V8** — JZ is **1.6× faster than C, 1.7× than Rust, and 3.4× than Go** (geomean,
|
|
518
|
+
deopt probes included). That apples-to-apples wasm field is the headline chart
|
|
519
|
+
above. Native `clang -O3` — the lone non-wasm reference row, the speed-of-light
|
|
520
|
+
ceiling, never a per-case beat-claim — now leads the corpus geomean 1.2×: the
|
|
521
|
+
deopt probes are exactly where native value semantics and mature runtimes pull
|
|
522
|
+
ahead, and closing them is the current work list.
|
|
452
523
|
|
|
453
524
|
Case-by-case summary:
|
|
454
525
|
|
package/bench/bench.svg
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 424" width="720" height="424" style="color-scheme:light dark" role="img" aria-label="JZ benchmark — every rival compiled to WebAssembly, run in V8 · native C = reference; geometric mean across
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 424" width="720" height="424" style="color-scheme:light dark" role="img" aria-label="JZ benchmark — every rival compiled to WebAssembly, run in V8 · native C = reference; geometric mean across 45 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
2
|
|
|
3
3
|
<g font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
|
4
4
|
<rect x="174" y="43.5" width="476" height="3" rx="1.5" fill="currentColor" fill-opacity="0.1"/>
|
|
5
5
|
<line x1="174" y1="38" x2="174" y2="52" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
6
6
|
<line x1="650" y1="38" x2="650" y2="52" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
7
|
-
<text x="156" y="41" text-anchor="end" font-size="14" font-weight="
|
|
8
|
-
<text x="156" y="56" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46"
|
|
9
|
-
<text x="662" y="49" font-size="13" font-weight="
|
|
10
|
-
<circle cx="182.0" cy="45" r="8" fill="currentColor" fill-opacity="
|
|
11
|
-
<animate attributeName="cx" dur="1.
|
|
7
|
+
<text x="156" y="41" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">native C</text>
|
|
8
|
+
<text x="156" y="56" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">clang -O3 · ref</text>
|
|
9
|
+
<text x="662" y="49" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">0.82×</text>
|
|
10
|
+
<circle cx="182.0" cy="45" r="8" fill="currentColor" fill-opacity="0.4">
|
|
11
|
+
<animate attributeName="cx" dur="1.3125489894306868s" repeatCount="indefinite" calcMode="linear"
|
|
12
12
|
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-0.00s"/>
|
|
13
13
|
</circle>
|
|
14
14
|
</g>
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
<rect x="174" y="93.5" width="476" height="3" rx="1.5" fill="currentColor" fill-opacity="0.1"/>
|
|
17
17
|
<line x1="174" y1="88" x2="174" y2="102" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
18
18
|
<line x1="650" y1="88" x2="650" y2="102" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
19
|
-
<text x="156" y="91" text-anchor="end" font-size="14" font-weight="
|
|
20
|
-
<text x="156" y="106" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46"
|
|
21
|
-
<text x="662" y="99" font-size="13" font-weight="
|
|
22
|
-
<circle cx="182.0" cy="95" r="8" fill="currentColor" fill-opacity="
|
|
23
|
-
<animate attributeName="cx" dur="1.
|
|
19
|
+
<text x="156" y="91" text-anchor="end" font-size="14" font-weight="700" fill="currentColor" fill-opacity="1">JZ</text>
|
|
20
|
+
<text x="156" y="106" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">-O3</text>
|
|
21
|
+
<text x="662" y="99" font-size="13" font-weight="700" fill="currentColor" fill-opacity="1">1.00×</text>
|
|
22
|
+
<circle cx="182.0" cy="95" r="8" fill="currentColor" fill-opacity="1">
|
|
23
|
+
<animate attributeName="cx" dur="1.6s" repeatCount="indefinite" calcMode="linear"
|
|
24
24
|
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-0.41s"/>
|
|
25
25
|
</circle>
|
|
26
26
|
</g>
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
<line x1="650" y1="138" x2="650" y2="152" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
31
31
|
<text x="156" y="141" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">C</text>
|
|
32
32
|
<text x="156" y="156" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">clang → wasm</text>
|
|
33
|
-
<text x="662" y="149" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">
|
|
33
|
+
<text x="662" y="149" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">1.64×</text>
|
|
34
34
|
<circle cx="182.0" cy="145" r="8" fill="currentColor" fill-opacity="0.4">
|
|
35
|
-
<animate attributeName="cx" dur="
|
|
35
|
+
<animate attributeName="cx" dur="2.624757929306874s" repeatCount="indefinite" calcMode="linear"
|
|
36
36
|
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-0.82s"/>
|
|
37
37
|
</circle>
|
|
38
38
|
</g>
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
<rect x="174" y="193.5" width="476" height="3" rx="1.5" fill="currentColor" fill-opacity="0.1"/>
|
|
41
41
|
<line x1="174" y1="188" x2="174" y2="202" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
42
42
|
<line x1="650" y1="188" x2="650" y2="202" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
43
|
-
<text x="156" y="191" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">
|
|
44
|
-
<text x="156" y="206" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">
|
|
45
|
-
<text x="662" y="199" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">
|
|
43
|
+
<text x="156" y="191" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">AssemblyScript</text>
|
|
44
|
+
<text x="156" y="206" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">asc -O3</text>
|
|
45
|
+
<text x="662" y="199" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">1.73×</text>
|
|
46
46
|
<circle cx="182.0" cy="195" r="8" fill="currentColor" fill-opacity="0.4">
|
|
47
|
-
<animate attributeName="cx" dur="
|
|
47
|
+
<animate attributeName="cx" dur="2.7633688737454363s" repeatCount="indefinite" calcMode="linear"
|
|
48
48
|
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-1.23s"/>
|
|
49
49
|
</circle>
|
|
50
50
|
</g>
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
<rect x="174" y="243.5" width="476" height="3" rx="1.5" fill="currentColor" fill-opacity="0.1"/>
|
|
53
53
|
<line x1="174" y1="238" x2="174" y2="252" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
54
54
|
<line x1="650" y1="238" x2="650" y2="252" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
55
|
-
<text x="156" y="241" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">
|
|
56
|
-
<text x="156" y="256" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">
|
|
57
|
-
<text x="662" y="249" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">
|
|
55
|
+
<text x="156" y="241" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">Rust</text>
|
|
56
|
+
<text x="156" y="256" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">rustc → wasm</text>
|
|
57
|
+
<text x="662" y="249" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">1.73×</text>
|
|
58
58
|
<circle cx="182.0" cy="245" r="8" fill="currentColor" fill-opacity="0.4">
|
|
59
|
-
<animate attributeName="cx" dur="
|
|
59
|
+
<animate attributeName="cx" dur="2.77333545752851s" repeatCount="indefinite" calcMode="linear"
|
|
60
60
|
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-1.64s"/>
|
|
61
61
|
</circle>
|
|
62
62
|
</g>
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
<line x1="650" y1="288" x2="650" y2="302" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
67
67
|
<text x="156" y="291" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">V8</text>
|
|
68
68
|
<text x="156" y="306" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">Node (JS)</text>
|
|
69
|
-
<text x="662" y="299" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">
|
|
69
|
+
<text x="662" y="299" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">1.90×</text>
|
|
70
70
|
<circle cx="182.0" cy="295" r="8" fill="currentColor" fill-opacity="0.4">
|
|
71
|
-
<animate attributeName="cx" dur="3.
|
|
71
|
+
<animate attributeName="cx" dur="3.0407840012463723s" repeatCount="indefinite" calcMode="linear"
|
|
72
72
|
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-2.05s"/>
|
|
73
73
|
</circle>
|
|
74
74
|
</g>
|
|
@@ -77,13 +77,13 @@
|
|
|
77
77
|
<line x1="174" y1="338" x2="174" y2="352" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
78
78
|
<line x1="650" y1="338" x2="650" y2="352" stroke="currentColor" stroke-opacity="0.3" stroke-width="2"/>
|
|
79
79
|
<text x="156" y="341" text-anchor="end" font-size="14" font-weight="500" fill="currentColor" fill-opacity="0.82">Porffor</text>
|
|
80
|
-
<text x="156" y="356" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">runs 4 /
|
|
81
|
-
<text x="662" y="349" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">3.
|
|
80
|
+
<text x="156" y="356" text-anchor="end" font-size="10" fill="currentColor" fill-opacity="0.46">runs 4 / 45</text>
|
|
81
|
+
<text x="662" y="349" font-size="13" font-weight="500" fill="currentColor" fill-opacity="0.6">3.27×</text>
|
|
82
82
|
<circle cx="182.0" cy="345" r="8" fill="currentColor" fill-opacity="0.4">
|
|
83
|
-
<animate attributeName="cx" dur="
|
|
83
|
+
<animate attributeName="cx" dur="5.225740832087161s" repeatCount="indefinite" calcMode="linear"
|
|
84
84
|
keyTimes="0;0.5;1" values="182.0;642.0;182.0" begin="-2.46s"/>
|
|
85
85
|
</circle>
|
|
86
86
|
</g>
|
|
87
87
|
<text x="360" y="390" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="11" font-weight="600" fill="currentColor" fill-opacity="0.72">every rival compiled to WebAssembly, run in V8 · native C = reference</text>
|
|
88
|
-
<text x="360" y="408" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="11" fill="currentColor" fill-opacity="0.5">geometric mean across
|
|
88
|
+
<text x="360" y="408" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="11" fill="currentColor" fill-opacity="0.5">geometric mean across 45 benchmark cases · lower is faster, JZ = 1.00× baseline</text>
|
|
89
89
|
</svg>
|
package/cli.js
CHANGED
|
@@ -132,7 +132,9 @@ async function handleJzify(args) {
|
|
|
132
132
|
if (!inputFile) throw new Error('No input file specified')
|
|
133
133
|
if (!outputFile) outputFile = inputFile.replace(/\.js$/, '.jz')
|
|
134
134
|
const code = readFileSync(inputFile, 'utf8')
|
|
135
|
-
const
|
|
135
|
+
const warnings = { entries: [] }
|
|
136
|
+
const out = transform(code, { warnings }) + '\n'
|
|
137
|
+
for (const w of warnings.entries) console.warn(formatWarning(w))
|
|
136
138
|
if (outputFile === '-') {
|
|
137
139
|
process.stdout.write(out)
|
|
138
140
|
} else {
|