jz 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -72
- package/index.js +38 -8
- package/module/array.js +7 -2
- package/module/collection.js +3 -1
- package/module/console.js +3 -1
- package/module/core.js +3 -1
- package/module/function.js +4 -3
- package/module/json.js +3 -1
- package/module/math.js +2 -1
- package/module/number.js +3 -7
- package/module/object.js +3 -1
- package/module/regex.js +2 -1
- package/module/schema.js +3 -1
- package/module/string.js +63 -6
- package/module/symbol.js +2 -1
- package/module/timer.js +2 -2
- package/module/typedarray.js +3 -1
- package/package.json +4 -2
- package/src/analyze.js +28 -7
- package/src/autoload.js +175 -0
- package/src/compile.js +58 -1022
- package/src/ctx.js +1 -0
- package/src/emit.js +16 -6
- package/src/host.js +38 -26
- package/src/jzify.js +45 -18
- package/src/key.js +73 -0
- package/src/narrow.js +928 -0
- package/src/plan.js +105 -0
- package/src/prepare.js +151 -215
- package/src/source.js +76 -0
- package/wasi.js +10 -4
package/README.md
CHANGED
|
@@ -17,15 +17,17 @@ dist(3, 4) // 5
|
|
|
17
17
|
|
|
18
18
|
## Why?
|
|
19
19
|
|
|
20
|
-
**Write plain JS, compile to WASM** – fast, portable and long-lasting. JZ distills the modern functional core – the "good parts" [Crockford](https://www.youtube.com/watch?v=_DKkVvOt6dk) – from legacy semantics,
|
|
20
|
+
**Write plain JS, compile to WASM** – fast, portable and long-lasting. JZ distills the modern functional core – the "good parts" [Crockford](https://www.youtube.com/watch?v=_DKkVvOt6dk) – from legacy semantics, specs evolution and perf quirks.
|
|
21
21
|
|
|
22
22
|
* **Static** – no runtime, no GC, no dynamic constructs.
|
|
23
23
|
* **Valid jz = valid js** — test in browser, compile to wasm.
|
|
24
24
|
* **Minimal** — output is close to hand-written WAT.
|
|
25
25
|
<!-- * **Realtime** — compiles faster than `eval`, useful for live-coding and REPL. -->
|
|
26
26
|
|
|
27
|
-
Inspired by [porffor](https://github.com/CanadaHonk/porffor) and [piezo](https://github.com/dy/piezo).
|
|
28
|
-
<!--
|
|
27
|
+
**Used by**: [web-audio-api](https://github.com/audiojs/web-audio-api), [color-space](https://github.com/colorjs/color-space), [audiojs](https://github.com/colorjs/audiojs). Inspired by [porffor](https://github.com/CanadaHonk/porffor) and [piezo](https://github.com/dy/piezo).
|
|
28
|
+
<!-- * [audio-filter](https://github.com/audiojs/audio-filter)
|
|
29
|
+
* [digital-filter](https://github.com/audiojs/digital-filter)
|
|
30
|
+
* [time-stretch](https://github.com/audiojs/time-stretch) -->
|
|
29
31
|
|
|
30
32
|
| Good for | Not for |
|
|
31
33
|
|-----------------------------|----------------------------|
|
|
@@ -78,7 +80,7 @@ flowchart TB
|
|
|
78
80
|
subgraph JS[JS — not supported]
|
|
79
81
|
subgraph JZify[JZ + jzify]
|
|
80
82
|
subgraph JZ[JZ strict]
|
|
81
|
-
j1["let/const, arrows, default/rest params, flow, break/continue, try/catch, a[]/a()/a.b, operators, strings, booleans, numbers, std, memory, host"]:::plain
|
|
83
|
+
j1["let/const, arrows, default/rest params, flow, break/continue, try/catch/finally, a[]/a()/a.b, operators, strings, booleans, numbers, std, memory, host"]:::plain
|
|
82
84
|
end
|
|
83
85
|
z1["var, function, arguments, switch, new Foo(), ==, !=, instanceof"]:::plain
|
|
84
86
|
end
|
|
@@ -94,17 +96,17 @@ flowchart TB
|
|
|
94
96
|
|
|
95
97
|
```
|
|
96
98
|
┌────────────────────────────────────────────────────────────────────────┐
|
|
97
|
-
│ JZify
|
|
99
|
+
│ JZify │
|
|
98
100
|
│ var function arguments switch new Foo() │
|
|
99
101
|
│ == != instanceof undefined │
|
|
100
102
|
│ │
|
|
101
103
|
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
102
|
-
│ │ JZ
|
|
103
|
-
│ │ let/const => ...xs destructuring import/export
|
|
104
|
+
│ │ JZ │ │
|
|
105
|
+
│ │ let/const => ...xs destructuring import/export `${}` │ │
|
|
104
106
|
│ │ if/else for/while/do-while/of/in break/continue │ │
|
|
105
107
|
│ │ try/catch/finally throw │ │
|
|
106
|
-
│ │ operators strings booleans numbers arrays objects
|
|
107
|
-
│ │ Math Number String Array Object JSON RegExp Symbol
|
|
108
|
+
│ │ operators strings booleans numbers arrays objects null │ │
|
|
109
|
+
│ │ Math Number String Array Object JSON RegExp Symbol │ │
|
|
108
110
|
│ │ ArrayBuffer DataView TypedArray Map Set │ │
|
|
109
111
|
│ │ console setTimeout/setInterval Date performance │ │
|
|
110
112
|
│ └────────────────────────────────────────────────────────────────────┘ │
|
|
@@ -118,6 +120,24 @@ Not supported
|
|
|
118
120
|
```
|
|
119
121
|
|
|
120
122
|
|
|
123
|
+
## Benchmark
|
|
124
|
+
|
|
125
|
+
| | **jz** | [Node](https://nodejs.org/) | [AS](https://github.com/AssemblyScript/assemblyscript) | WAT | C | [Go](https://go.dev/) | [Rust](https://www.rust-lang.org/) |
|
|
126
|
+
|---|---|---|---|---|---|---|---|
|
|
127
|
+
| [**biquad**](bench/biquad/biquad.js) | **6.44 ms**<br>**3.4 kB** | 12.30 ms<br>3.2 kB | 9.04 ms<br>1.9 kB | 6.48 ms<br>767 B | 5.43 ms<br>32.7 kB | 9.03 ms<br>1.60 MB<br>fma | 5.33 ms<br>380.7 kB |
|
|
128
|
+
| [**tokenizer**](bench/tokenizer/tokenizer.js) | **0.10 ms**<br>**1.6 kB** | 0.18 ms<br>1.4 kB | 0.08 ms<br>1.5 kB | — | 0.13 ms<br>32.9 kB | 0.07 ms<br>1.60 MB | 0.12 ms<br>380.7 kB |
|
|
129
|
+
| [**mat4**](bench/mat4/mat4.js) | **4.00 ms**<br>**1.7 kB** | 11.64 ms<br>1.1 kB | 9.18 ms<br>1.5 kB | 7.99 ms<br>353 B | 2.62 ms<br>32.8 kB | 11.93 ms<br>1.60 MB | 0.80 ms<br>380.7 kB |
|
|
130
|
+
| [**aos**](bench/aos/aos.js) | **1.50 ms**<br>**2.3 kB** | 1.81 ms<br>1.1 kB | 1.91 ms<br>2.2 kB | — | 1.22 ms<br>32.9 kB | 0.90 ms<br>1.60 MB | 1.20 ms<br>380.7 kB |
|
|
131
|
+
| [**bitwise**](bench/bitwise/bitwise.js) | **4.93 ms**<br>**1.2 kB** | 5.31 ms<br>1005 B | 12.36 ms<br>1.5 kB | 4.96 ms<br>355 B | 1.31 ms<br>32.9 kB | 5.24 ms<br>1.60 MB | 1.30 ms<br>380.7 kB |
|
|
132
|
+
| [**poly**](bench/poly/poly.js) | **1.13 ms**<br>**1.3 kB** | 2.31 ms<br>1014 B | 1.14 ms<br>1.3 kB | — | 0.52 ms<br>32.9 kB | 0.80 ms<br>1.60 MB | 0.52 ms<br>380.7 kB |
|
|
133
|
+
| [**callback**](bench/callback/callback.js) | **0.01 ms**<br>**1.5 kB** | 1.03 ms<br>828 B | 1.48 ms<br>1.9 kB | — | 0.09 ms<br>32.9 kB | 0.20 ms<br>1.60 MB | 0.08 ms<br>380.7 kB |
|
|
134
|
+
| [**json**](bench/json/json.js) | **0.20 ms**<br>**2.8 kB** | 0.38 ms<br>923 B | — | — | 0.02 ms<br>32.8 kB | 1.06 ms<br>1.97 MB | 0.03 ms<br>380.7 kB |
|
|
135
|
+
| [**watr**](bench/watr/watr.js) | **1.82 ms**<br>**137.1 kB** | 1.50 ms<br>2.6 kB | — | — | — | — | — |
|
|
136
|
+
|
|
137
|
+
_Numbers from `node bench/bench.mjs` on Apple Silicon, May 2026. `jz` uses host imports for benchmark timing/logging (measures wasm without WASI console/perf bloat). Raw-JS size is the entry file; jz size is the bundled wasm artifact. `fma` is the documented Go arm64 fused-multiply-add checksum class. See [benchmark](./bench/)._
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
121
141
|
|
|
122
142
|
## FAQ
|
|
123
143
|
|
|
@@ -139,9 +159,8 @@ memory.Array([1, 2, 3]) // → array pointer
|
|
|
139
159
|
memory.Float64Array([1.0, 2.0]) // → typed array pointer
|
|
140
160
|
memory.Int32Array([10, 20, 30]) // all typed array constructors available
|
|
141
161
|
|
|
142
|
-
//
|
|
162
|
+
// Objects: keys and order must match the jz source declaration.
|
|
143
163
|
// jz objects are fixed-layout schemas (like C structs), not dynamic key bags.
|
|
144
|
-
// If the jz source declares `{ x, y }`, you must pass `{ x, y }` in that order.
|
|
145
164
|
memory.Object({ x: 3, y: 4 }) // → object pointer
|
|
146
165
|
|
|
147
166
|
// Strings/arrays inside objects are auto-wrapped to pointers:
|
|
@@ -205,47 +224,26 @@ jz`export let f = () => ${{label: 'origin', x: 0, y: 0}}.label.length` // 6
|
|
|
205
224
|
|
|
206
225
|
Functions are imported as host calls. Non-serializable values (host objects, class instances) fall back to post-instantiation getters automatically.
|
|
207
226
|
|
|
208
|
-
### Does it support
|
|
227
|
+
### Does it support imports?
|
|
209
228
|
|
|
210
|
-
Yes — standard ES `import` syntax
|
|
229
|
+
Yes — standard ES `import` syntax, bundled at compile-time into one WASM.
|
|
211
230
|
|
|
212
231
|
```js
|
|
232
|
+
// modules: jz source bundled at compile time
|
|
213
233
|
const { exports } = jz(
|
|
214
234
|
'import { add } from "./math.jz"; export let f = (a, b) => add(a, b)',
|
|
215
235
|
{ modules: { './math.jz': 'export let add = (a, b) => a + b' } }
|
|
216
236
|
)
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Transitive imports work (main → math → utils → …). Circular imports error at compile time. Output is always one WASM binary — no runtime resolution.
|
|
220
|
-
|
|
221
|
-
**CLI** resolves filesystem imports automatically.
|
|
222
|
-
|
|
223
|
-
```sh
|
|
224
|
-
jz main.jz -o main.wasm # reads ./math.jz, ./utils.jz automatically
|
|
225
|
-
```
|
|
226
237
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
```js
|
|
230
|
-
// Transitive bundling — all merged into one WASM
|
|
231
|
-
const { exports } = jz(mainSrc, { modules: {
|
|
232
|
-
'./math.jz': 'import { sq } from "./utils.jz"; export let dist = (x, y) => (sq(x) + sq(y)) ** 0.5',
|
|
233
|
-
// Fetch sources yourself, pass them in
|
|
234
|
-
'./utils.jz': await fetch('./util.jz').then(r => r.text())
|
|
235
|
-
} })
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Can I call JS/host functions from jz?
|
|
239
|
-
|
|
240
|
-
Yes — JS functions are wired at instantiation via the `imports` option:
|
|
241
|
-
|
|
242
|
-
```js
|
|
238
|
+
// imports: JS functions wired at instantiation
|
|
243
239
|
const { exports } = jz(
|
|
244
240
|
'import { log } from "host"; export let f = (x) => { log(x); return x }',
|
|
245
241
|
{ imports: { host: { log: console.log } } }
|
|
246
242
|
)
|
|
247
243
|
```
|
|
248
244
|
|
|
245
|
+
Transitive imports work (main → math → utils → …). Circular imports error at compile time. Output is always one WASM binary — no runtime resolution.
|
|
246
|
+
|
|
249
247
|
You can also pass whole host environment objects — `Math`, `Date`, `window`, `console`, or any custom namespace object. jz extracts the functions it needs via `Object.getOwnPropertyNames`, so non-enumerable built-ins (like `Math.sin`) work automatically:
|
|
250
248
|
|
|
251
249
|
```js
|
|
@@ -268,6 +266,23 @@ const { exports } = jz(
|
|
|
268
266
|
)
|
|
269
267
|
```
|
|
270
268
|
|
|
269
|
+
**CLI** resolves filesystem imports automatically.
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
jz main.jz -o main.wasm # reads ./math.jz, ./utils.jz automatically
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Browser**: fetch sources yourself, pass via `{ modules }`. The compiler stays synchronous and pure — no I/O.
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
// Transitive bundling — all merged into one WASM
|
|
279
|
+
const { exports } = jz(mainSrc, { modules: {
|
|
280
|
+
'./math.jz': 'import { sq } from "./utils.jz"; export let dist = (x, y) => (sq(x) + sq(y)) ** 0.5',
|
|
281
|
+
// Fetch sources yourself, pass them in
|
|
282
|
+
'./utils.jz': await fetch('./util.jz').then(r => r.text())
|
|
283
|
+
} })
|
|
284
|
+
```
|
|
285
|
+
|
|
271
286
|
### Can two modules share data?
|
|
272
287
|
|
|
273
288
|
Yes — `jz.memory()` creates a shared memory that modules compile into. Schemas accumulate automatically, so objects created in one module are readable by another:
|
|
@@ -305,14 +320,23 @@ wasmer run program.wasm
|
|
|
305
320
|
deno run program.wasm
|
|
306
321
|
```
|
|
307
322
|
|
|
308
|
-
|
|
323
|
+
Pure numeric modules have no imports and instantiate with standard
|
|
324
|
+
`WebAssembly.Module` / `WebAssembly.Instance`, which is the right shape for JS
|
|
325
|
+
hosts such as EdgeJS. Compile once at startup or build time, then reuse the
|
|
326
|
+
module; do not compile JZ source per request.
|
|
327
|
+
|
|
328
|
+
`console.log` compiles to WASI `fd_write` by default — works natively on
|
|
329
|
+
wasmtime/wasmer/deno without polyfills. In JS hosts, `jz()` auto-applies the
|
|
330
|
+
small `jz/wasi` polyfill; pass `{ write(fd, text) { ... } }` to capture or route
|
|
331
|
+
stdout/stderr without depending on `process.stdout`.
|
|
309
332
|
|
|
310
333
|
|
|
311
334
|
### What host features are supported?
|
|
312
335
|
|
|
313
336
|
The compiled `.wasm` uses one import namespace:
|
|
314
337
|
|
|
315
|
-
-
|
|
338
|
+
- none — pure scalar/compute modules. Instantiate directly with standard WebAssembly APIs.
|
|
339
|
+
- `wasi_snapshot_preview1` — standard WASI Preview 1 calls. Run natively on wasmtime, wasmer, deno; for browsers/Node/EdgeJS-like hosts, jz ships a tiny polyfill (`jz/wasi`) auto-applied by the `jz()` runtime.
|
|
316
340
|
|
|
317
341
|
| JS API | Maps to | Notes |
|
|
318
342
|
|--------|---------|-------|
|
|
@@ -323,21 +347,6 @@ The compiled `.wasm` uses one import namespace:
|
|
|
323
347
|
| `setTimeout`, `clearTimeout` | WASM timer queue + `__timer_tick` | JS runtime drives tick via `setInterval`; wasmtime uses blocking `__timer_loop` |
|
|
324
348
|
| `setInterval`, `clearInterval` | WASM timer queue + `__timer_tick` | Same — native WASM implementation, no host imports |
|
|
325
349
|
|
|
326
|
-
### How do I add custom operators / extend the stdlib?
|
|
327
|
-
|
|
328
|
-
jz's emitter table (`ctx.core.emit`) maps AST operators → WASM IR generators. Module files in `module/` register handlers on it. To add your own:
|
|
329
|
-
|
|
330
|
-
```js
|
|
331
|
-
import { emitter } from 'jz/src/compile.js'
|
|
332
|
-
|
|
333
|
-
// Register a custom operator: my.double(x) → x * 2
|
|
334
|
-
emitter['my.double'] = (x) => {
|
|
335
|
-
return ['f64.mul', ['f64.const', 2], x]
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
The naming convention follows the AST path: `Math.sin` → `math.sin`, `arr.push` → `.push`, typed variants like `.f64:push`. See any file in `module/` for the full pattern — each exports a function that registers emitters and stdlib on `ctx`.
|
|
340
|
-
|
|
341
350
|
### Can I compile jz to C?
|
|
342
351
|
|
|
343
352
|
Yes, via [wasm2c](https://github.com/WebAssembly/wabt/blob/main/wasm2c) or [w2c2](https://github.com/turbolent/w2c2):
|
|
@@ -349,23 +358,6 @@ cc program.c -o program
|
|
|
349
358
|
```
|
|
350
359
|
|
|
351
360
|
|
|
352
|
-
## Benchmark
|
|
353
|
-
|
|
354
|
-
| | **jz** | [Node](https://nodejs.org/) | [AS](https://github.com/AssemblyScript/assemblyscript) | WAT | C | [Go](https://go.dev/) | [Zig](https://ziglang.org/) | [Rust](https://www.rust-lang.org/) | [NumPy](https://numpy.org/) | [Porffor](https://github.com/CanadaHonk/porffor) |
|
|
355
|
-
|---|---|---|---|---|---|---|---|---|---|---|
|
|
356
|
-
| [**biquad**](bench/biquad/biquad.js) | **6.44ms**<br>**3.4kB** | 12.30ms<br>3.2kB | 9.04ms<br>1.9kB | 6.48ms<br>767 B | 5.43ms | 9.03ms<br>fma | 5.09ms | 5.33ms | 3.15s | — |
|
|
357
|
-
| [**tokenizer**](bench/tokenizer/tokenizer.js) | **0.10ms**<br>**1.6kB** | 0.18ms<br>1.4kB | 0.08ms<br>1.5kB | — | 0.13ms | 0.07ms | 0.12ms | 0.12ms | 5.21ms | 0.46ms<br>2.6kB |
|
|
358
|
-
| [**mat4**](bench/mat4/mat4.js) | **4.00ms**<br>**1.7kB** | 11.64ms<br>1.1kB | 9.18ms<br>1.5kB | 7.99ms<br>353 B | 2.62ms | 11.93ms | 2.60ms | 0.80ms | 323.69ms | 87.65ms<br>2.3kB |
|
|
359
|
-
| [**aos**](bench/aos/aos.js) | **1.50ms**<br>**2.3kB** | 1.81ms<br>1.1kB | 1.91ms<br>2.2kB | — | 1.22ms | 0.90ms | 0.99ms | 1.20ms | 2.23ms | — |
|
|
360
|
-
| [**bitwise**](bench/bitwise/bitwise.js) | **4.93ms**<br>**1.2kB** | 5.31ms<br>1005 B | 12.36ms<br>1.5kB | 4.96ms<br>355 B | 1.31ms | 5.24ms | 4.26ms | 1.30ms | 14.89ms | — |
|
|
361
|
-
| [**poly**](bench/poly/poly.js) | **1.13ms**<br>**1.3kB** | 2.31ms<br>1014 B | 1.14ms<br>1.3kB | — | 0.52ms | 0.80ms | — | 0.52ms | 0.60ms | — |
|
|
362
|
-
| [**callback**](bench/callback/callback.js) | **0.01ms**<br>**1.5kB** | 1.03ms<br>828 B | 1.48ms<br>1.9kB | — | 0.09ms | 0.20ms | 0.01ms | 0.08ms | 1.84ms | — |
|
|
363
|
-
| [**json**](bench/json/json.js) | **0.20ms**<br>**2.8kB** | 0.38ms<br>923 B | — | — | 0.02ms | 1.06ms | — | 0.03ms | 1.19ms | — |
|
|
364
|
-
| [**watr**](bench/watr/watr.js) | **1.82ms**<br>**137.1kB** | 1.50ms<br>85.3kB | — | — | — | — | — | — | — | — |
|
|
365
|
-
|
|
366
|
-
_Numbers from `node bench/bench.mjs` on Apple Silicon._
|
|
367
|
-
|
|
368
|
-
|
|
369
361
|
## Alternatives
|
|
370
362
|
|
|
371
363
|
* [porffor](https://github.com/CanadaHonk/porffor) — ahead-of-time JS→WASM compiler targeting full TC39 semantics. Implements the spec progressively (test262). Where jz restricts the language for performance, porffor aims for completeness.
|
package/index.js
CHANGED
|
@@ -42,9 +42,11 @@ import { parse } from 'subscript/jessie'
|
|
|
42
42
|
import { compile as watrCompile, print as watrPrint, optimize as watrOptimize } from "watr";
|
|
43
43
|
import { ctx, reset } from './src/ctx.js'
|
|
44
44
|
import prepare, { GLOBALS } from './src/prepare.js'
|
|
45
|
-
import compile
|
|
45
|
+
import compile from './src/compile.js'
|
|
46
|
+
import { emitter } from './src/emit.js'
|
|
46
47
|
import { optimizeFunc, resolveOptimize } from './src/optimize.js'
|
|
47
48
|
import jzify from './src/jzify.js'
|
|
49
|
+
import { normalizeSource } from './src/source.js'
|
|
48
50
|
import {
|
|
49
51
|
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
50
52
|
} from './src/host.js'
|
|
@@ -62,6 +64,25 @@ const importsMayReturnExternal = (imports) => {
|
|
|
62
64
|
return false
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
const nowMs = () => globalThis.performance?.now ? globalThis.performance.now() : Date.now()
|
|
68
|
+
|
|
69
|
+
const compileProfiler = (profile) => {
|
|
70
|
+
if (!profile) return null
|
|
71
|
+
profile.entries ||= []
|
|
72
|
+
profile.totals ||= {}
|
|
73
|
+
return {
|
|
74
|
+
time(name, fn) {
|
|
75
|
+
const start = nowMs()
|
|
76
|
+
try { return fn() }
|
|
77
|
+
finally {
|
|
78
|
+
const ms = nowMs() - start
|
|
79
|
+
profile.entries.push({ name, ms })
|
|
80
|
+
profile.totals[name] = (profile.totals[name] || 0) + ms
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
65
86
|
/**
|
|
66
87
|
* jz — JS subset → WASM compiler.
|
|
67
88
|
*
|
|
@@ -94,9 +115,14 @@ jz.memory = enhanceMemory
|
|
|
94
115
|
* - `3`: reserved for future aggressive passes (currently == 2).
|
|
95
116
|
* - `{ level?: 0|1|2|3, watr?: bool, hoistAddrBase?: bool, ... }`: per-pass
|
|
96
117
|
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
118
|
+
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
119
|
+
* `entries` and `totals` for parse / jzify / prepare / compile / plan / watr phases.
|
|
97
120
|
* @returns {Uint8Array|string}
|
|
98
121
|
*/
|
|
99
122
|
jz.compile = (code, opts = {}) => {
|
|
123
|
+
const profiler = compileProfiler(opts.profile)
|
|
124
|
+
const time = (name, fn) => profiler ? profiler.time(name, fn) : fn()
|
|
125
|
+
|
|
100
126
|
reset(emitter, GLOBALS)
|
|
101
127
|
ctx.error.src = code
|
|
102
128
|
|
|
@@ -125,21 +151,25 @@ jz.compile = (code, opts = {}) => {
|
|
|
125
151
|
}
|
|
126
152
|
}
|
|
127
153
|
|
|
128
|
-
let parsed = parse(code)
|
|
129
|
-
if (opts.jzify) parsed = jzify(parsed)
|
|
130
|
-
const ast = prepare(parsed)
|
|
131
|
-
const module = compile(ast)
|
|
154
|
+
let parsed = time('parse', () => parse(normalizeSource(code)))
|
|
155
|
+
if (opts.jzify) parsed = time('jzify', () => jzify(parsed))
|
|
156
|
+
const ast = time('prepare', () => prepare(parsed))
|
|
157
|
+
const module = time('compile', () => compile(ast, profiler))
|
|
132
158
|
|
|
133
159
|
const cfg = ctx.transform.optimize
|
|
134
|
-
const optimized = cfg.watr ? watrOptimize(module) : module
|
|
160
|
+
const optimized = cfg.watr ? time('watrOptimize', () => watrOptimize(module)) : module
|
|
135
161
|
// Final peephole pass: watrOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
136
162
|
// (e.g. inlined closure body's `i32.wrap_i64 (i64.reinterpret_f64 __env)` next to caller's
|
|
137
163
|
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
138
164
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
139
165
|
if (cfg.watr) {
|
|
140
|
-
|
|
166
|
+
time('watrReopt', () => {
|
|
167
|
+
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, cfg)
|
|
168
|
+
})
|
|
141
169
|
}
|
|
142
|
-
return opts.wat
|
|
170
|
+
return opts.wat
|
|
171
|
+
? time('watrPrint', () => watrPrint(optimized))
|
|
172
|
+
: time('watrCompile', () => watrCompile(optimized))
|
|
143
173
|
}
|
|
144
174
|
|
|
145
175
|
/**
|
package/module/array.js
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
* @module array
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { typed, asF64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr, multiCount, arrayLoop, elemLoad, elemStore, truthyIR, extractF64Bits, appendStaticSlots, mkPtrIR, slotAddr } from '../src/ir.js'
|
|
12
|
+
import { emit, materializeMulti } from '../src/emit.js'
|
|
13
|
+
import { valTypeOf, lookupValType, VAL, extractParams, updateRep } from '../src/analyze.js'
|
|
12
14
|
import { ctx, inc, err, PTR } from '../src/ctx.js'
|
|
15
|
+
import { staticPropertyKey } from '../src/key.js'
|
|
13
16
|
import { strHashLiteral } from './collection.js'
|
|
14
17
|
|
|
15
18
|
|
|
@@ -466,7 +469,9 @@ export default (ctx) => {
|
|
|
466
469
|
if (r) return r
|
|
467
470
|
}
|
|
468
471
|
// Literal string key on schema-known object → direct payload slot read (skip __dyn_get)
|
|
469
|
-
const litKey = Array.isArray(idx) && idx[0] === 'str' && typeof idx[1] === 'string' ? idx[1]
|
|
472
|
+
const litKey = Array.isArray(idx) && idx[0] === 'str' && typeof idx[1] === 'string' ? idx[1]
|
|
473
|
+
: typeof arr === 'string' && lookupValType(arr) === VAL.OBJECT ? staticPropertyKey(idx)
|
|
474
|
+
: null
|
|
470
475
|
if (litKey != null && typeof arr === 'string' && ctx.schema.find) {
|
|
471
476
|
const slot = ctx.schema.find(arr, litKey)
|
|
472
477
|
if (slot >= 0) {
|
package/module/collection.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* @module collection
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { typed, asF64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr } from '../src/ir.js'
|
|
12
|
+
import { emit, emitFlat } from '../src/emit.js'
|
|
13
|
+
import { valTypeOf, lookupValType, VAL } from '../src/analyze.js'
|
|
12
14
|
import { inc, PTR } from '../src/ctx.js'
|
|
13
15
|
|
|
14
16
|
const SET_ENTRY = 16 // hash + key
|
package/module/console.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* @module wasi
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { typed, asF64 } from '../src/ir.js'
|
|
16
|
+
import { emit } from '../src/emit.js'
|
|
17
|
+
import { valTypeOf, VAL } from '../src/analyze.js'
|
|
16
18
|
import { exprType } from '../src/analyze.js'
|
|
17
19
|
import { inc, PTR } from '../src/ctx.js'
|
|
18
20
|
|
package/module/core.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* @module core
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { typed, asF64, asI32, NULL_NAN, UNDEF_NAN, temp, usesDynProps, ptrOffsetIR, isNullish } from '../src/ir.js'
|
|
13
|
+
import { emit } from '../src/emit.js'
|
|
14
|
+
import { valTypeOf, lookupValType, VAL, T, repOf, updateRep } from '../src/analyze.js'
|
|
13
15
|
import { err, inc, PTR } from '../src/ctx.js'
|
|
14
16
|
import { initSchema } from './schema.js'
|
|
15
17
|
import { strHashLiteral } from './collection.js'
|
package/module/function.js
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
* @module fn
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { isReassigned } from '../src/emit.js'
|
|
13
|
+
import { typed, asF64, asI32, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN } from '../src/ir.js'
|
|
14
|
+
import { emit, isReassigned } from '../src/emit.js'
|
|
15
|
+
import { T, lookupValType, repOf } from '../src/analyze.js'
|
|
15
16
|
import { PTR, inc, err } from '../src/ctx.js'
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
export default (ctx) => {
|
|
19
|
-
inc('__mkptr', '__alloc', '__len', '__ptr_offset')
|
|
20
|
+
inc('__mkptr', '__alloc', '__len', '__ptr_offset', '__ptr_type')
|
|
20
21
|
|
|
21
22
|
// Uniform closure convention: (env f64, argc i32, a0..a{MAX-1} f64) → f64
|
|
22
23
|
if (!ctx.closure.types) ctx.closure.types = new Set()
|
package/module/json.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* @module json
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { typed, asF64, temp, nullExpr, allocPtr, slotAddr } from '../src/ir.js'
|
|
12
|
+
import { emit } from '../src/emit.js'
|
|
13
|
+
import { T } from '../src/analyze.js'
|
|
12
14
|
import { err, inc, PTR } from '../src/ctx.js'
|
|
13
15
|
import { strHashLiteral } from './collection.js'
|
|
14
16
|
|
package/module/math.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* @module math
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { typed, asF64, asI32, temp, arrayLoop } from '../src/ir.js'
|
|
17
|
+
import { emit } from '../src/emit.js'
|
|
17
18
|
import { inc } from '../src/ctx.js'
|
|
18
19
|
import { repOf } from '../src/analyze.js'
|
|
19
20
|
|
package/module/number.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* @module number
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64 } from '../src/ir.js'
|
|
13
|
+
import { emit } from '../src/emit.js'
|
|
14
|
+
import { valTypeOf, VAL } from '../src/analyze.js'
|
|
13
15
|
import { inc, PTR } from '../src/ctx.js'
|
|
14
16
|
|
|
15
17
|
export default (ctx) => {
|
|
@@ -557,12 +559,6 @@ export default (ctx) => {
|
|
|
557
559
|
['i32.const', 1]]]]], 'f64')
|
|
558
560
|
}
|
|
559
561
|
|
|
560
|
-
ctx.core.emit['String'] = (x) => {
|
|
561
|
-
inc('__ftoa')
|
|
562
|
-
if (Array.isArray(x) && x[0] === 'str') return emit(x)
|
|
563
|
-
return typed(['call', '$__ftoa', asF64(emit(x)), ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
564
|
-
}
|
|
565
|
-
|
|
566
562
|
// Number(x) — identity for numbers, i64→f64 conversion for BigInt
|
|
567
563
|
ctx.core.emit['Number'] = (x) => {
|
|
568
564
|
if (valTypeOf(x) === VAL.BIGINT)
|
package/module/object.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* @module object
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { typed, asF64, temp, tempI32, allocPtr, needsDynShadow, mkPtrIR, extractF64Bits, appendStaticSlots, slotAddr } from '../src/ir.js'
|
|
11
|
+
import { emit } from '../src/emit.js'
|
|
12
|
+
import { valTypeOf, lookupValType, VAL, repOf, updateRep } from '../src/analyze.js'
|
|
11
13
|
import { ctx, err, inc, PTR } from '../src/ctx.js'
|
|
12
14
|
|
|
13
15
|
|
package/module/regex.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* @module regex
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { typed, asF64, UNDEF_NAN, mkPtrIR, temp, tempI32 } from '../src/ir.js'
|
|
11
|
+
import { emit } from '../src/emit.js'
|
|
11
12
|
import { err, inc, PTR } from '../src/ctx.js'
|
|
12
13
|
|
|
13
14
|
// Build IR that constructs a match array: [full, cap1, cap2, ...]
|
package/module/schema.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* @module schema
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { typed, asF64 } from '../src/ir.js'
|
|
11
|
+
import { emit } from '../src/emit.js'
|
|
12
|
+
import { VAL, lookupValType, repOf } from '../src/analyze.js'
|
|
11
13
|
import { err, inc } from '../src/ctx.js'
|
|
12
14
|
|
|
13
15
|
/** Initialize schema helpers on ctx. Called once per compilation from core module. */
|
package/module/string.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* @module string
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { typed, asF64, asI32, NULL_NAN, UNDEF_NAN, mkPtrIR, temp, tempI32 } from '../src/ir.js'
|
|
14
|
+
import { emit } from '../src/emit.js'
|
|
15
|
+
import { valTypeOf, VAL } from '../src/analyze.js'
|
|
14
16
|
import { inc, PTR } from '../src/ctx.js'
|
|
15
17
|
|
|
16
18
|
|
|
@@ -52,7 +54,8 @@ export default (ctx) => {
|
|
|
52
54
|
for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
|
|
53
55
|
return mkPtrIR(PTR.SSO, str.length, packed)
|
|
54
56
|
}
|
|
55
|
-
const
|
|
57
|
+
const bytes = new TextEncoder().encode(str)
|
|
58
|
+
const len = bytes.length
|
|
56
59
|
if (!ctx.memory.shared) {
|
|
57
60
|
// Own memory: place in static data segment (no runtime allocation)
|
|
58
61
|
if (!ctx.runtime.data) ctx.runtime.data = ''
|
|
@@ -61,7 +64,7 @@ export default (ctx) => {
|
|
|
61
64
|
while (ctx.runtime.data.length % 4 !== 0) ctx.runtime.data += '\0'
|
|
62
65
|
const offset = ctx.runtime.data.length
|
|
63
66
|
ctx.runtime.data += String.fromCharCode(len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF, (len >> 24) & 0xFF)
|
|
64
|
-
for (let i = 0; i < len; i++) ctx.runtime.data += String.fromCharCode(
|
|
67
|
+
for (let i = 0; i < len; i++) ctx.runtime.data += String.fromCharCode(bytes[i])
|
|
65
68
|
ctx.runtime.dataDedup.set(str, offset)
|
|
66
69
|
return mkPtrIR(PTR.STRING, 0, offset + 4)
|
|
67
70
|
}
|
|
@@ -75,10 +78,10 @@ export default (ctx) => {
|
|
|
75
78
|
}
|
|
76
79
|
let off = ctx.runtime.strPoolDedup.get(str)
|
|
77
80
|
if (off === undefined) {
|
|
78
|
-
// Pack length header then bytes; offset points PAST the length (at the data).
|
|
81
|
+
// Pack length header then UTF-8 bytes; offset points PAST the length (at the data).
|
|
79
82
|
ctx.runtime.strPool += String.fromCharCode(len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF, (len >> 24) & 0xFF)
|
|
80
83
|
off = ctx.runtime.strPool.length
|
|
81
|
-
ctx.runtime.strPool +=
|
|
84
|
+
for (let i = 0; i < len; i++) ctx.runtime.strPool += String.fromCharCode(bytes[i])
|
|
82
85
|
ctx.runtime.strPoolDedup.set(str, off)
|
|
83
86
|
}
|
|
84
87
|
return mkPtrIR(PTR.STRING, 0, ['i32.add', ['global.get', '$__strBase'], ['i32.const', off]])
|
|
@@ -486,12 +489,19 @@ export default (ctx) => {
|
|
|
486
489
|
(br $loop)))
|
|
487
490
|
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
488
491
|
|
|
489
|
-
// Coerce value to string: numbers → __ftoa,
|
|
492
|
+
// Coerce value to string: numbers → __ftoa, nullish → static strings,
|
|
493
|
+
// plain NaN → "NaN", arrays → join(","), other string-like pointers pass through.
|
|
490
494
|
ctx.core.stdlib['__to_str'] = `(func $__to_str (param $val f64) (result f64)
|
|
491
495
|
(local $type i32)
|
|
496
|
+
(local $bits i64)
|
|
492
497
|
;; Not NaN → number, convert
|
|
493
498
|
(if (f64.eq (local.get $val) (local.get $val))
|
|
494
499
|
(then (return (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0)))))
|
|
500
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $val)))
|
|
501
|
+
(if (i64.eq (local.get $bits) (i64.const ${NULL_NAN}))
|
|
502
|
+
(then (return (call $__static_str (i32.const 5)))))
|
|
503
|
+
(if (i64.eq (local.get $bits) (i64.const ${UNDEF_NAN}))
|
|
504
|
+
(then (return (call $__static_str (i32.const 6)))))
|
|
495
505
|
(local.set $type (call $__ptr_type (local.get $val)))
|
|
496
506
|
;; Plain NaN (type=0) → "NaN" string
|
|
497
507
|
(if (i32.eqz (local.get $type))
|
|
@@ -774,6 +784,42 @@ export default (ctx) => {
|
|
|
774
784
|
return result
|
|
775
785
|
}
|
|
776
786
|
|
|
787
|
+
ctx.core.emit['strcat'] = (...parts) => {
|
|
788
|
+
inc('__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy')
|
|
789
|
+
if (!parts.length) return mkPtrIR(PTR.SSO, 0, 0)
|
|
790
|
+
if (parts.length === 1) return typed(['call', '$__to_str', asF64(emit(parts[0]))], 'f64')
|
|
791
|
+
|
|
792
|
+
const vals = parts.map(() => temp('s'))
|
|
793
|
+
const lens = parts.map(() => tempI32('sl'))
|
|
794
|
+
const total = tempI32('st')
|
|
795
|
+
const off = tempI32('so')
|
|
796
|
+
const dst = tempI32('sd')
|
|
797
|
+
const ir = []
|
|
798
|
+
|
|
799
|
+
for (let i = 0; i < parts.length; i++) {
|
|
800
|
+
ir.push(['local.set', `$${vals[i]}`, ['call', '$__to_str', asF64(emit(parts[i]))]])
|
|
801
|
+
ir.push(['local.set', `$${lens[i]}`, ['call', '$__str_byteLen', ['local.get', `$${vals[i]}`]]])
|
|
802
|
+
}
|
|
803
|
+
ir.push(['local.set', `$${total}`, ['i32.const', 0]])
|
|
804
|
+
for (const len of lens)
|
|
805
|
+
ir.push(['local.set', `$${total}`, ['i32.add', ['local.get', `$${total}`], ['local.get', `$${len}`]]])
|
|
806
|
+
const alloc = [
|
|
807
|
+
['local.set', `$${off}`, ['call', '$__alloc', ['i32.add', ['i32.const', 4], ['local.get', `$${total}`]]]],
|
|
808
|
+
['i32.store', ['local.get', `$${off}`], ['local.get', `$${total}`]],
|
|
809
|
+
['local.set', `$${off}`, ['i32.add', ['local.get', `$${off}`], ['i32.const', 4]]],
|
|
810
|
+
['local.set', `$${dst}`, ['local.get', `$${off}`]],
|
|
811
|
+
]
|
|
812
|
+
for (let i = 0; i < parts.length; i++) {
|
|
813
|
+
alloc.push(['call', '$__str_copy', ['local.get', `$${vals[i]}`], ['local.get', `$${dst}`], ['local.get', `$${lens[i]}`]])
|
|
814
|
+
alloc.push(['local.set', `$${dst}`, ['i32.add', ['local.get', `$${dst}`], ['local.get', `$${lens[i]}`]]])
|
|
815
|
+
}
|
|
816
|
+
alloc.push(['call', '$__mkptr', ['i32.const', PTR.STRING], ['i32.const', 0], ['local.get', `$${off}`]])
|
|
817
|
+
ir.push(['if', ['result', 'f64'], ['i32.eqz', ['local.get', `$${total}`]],
|
|
818
|
+
['then', mkPtrIR(PTR.SSO, 0, 0)],
|
|
819
|
+
['else', ['block', ['result', 'f64'], ...alloc]]])
|
|
820
|
+
return typed(['block', ['result', 'f64'], ...ir], 'f64')
|
|
821
|
+
}
|
|
822
|
+
|
|
777
823
|
ctx.core.emit['.padStart'] = (str, len, pad) => {
|
|
778
824
|
inc('__str_pad')
|
|
779
825
|
const vpad = pad != null ? asF64(emit(pad)) : mkPtrIR(PTR.SSO, 1, 32)
|
|
@@ -806,6 +852,17 @@ export default (ctx) => {
|
|
|
806
852
|
}
|
|
807
853
|
|
|
808
854
|
// String.fromCharCode(code) → 1-char SSO string
|
|
855
|
+
ctx.core.emit['String'] = (value) => {
|
|
856
|
+
if (value === undefined) return emit(['str', ''])
|
|
857
|
+
if (valTypeOf(value) === VAL.STRING) return emit(value)
|
|
858
|
+
if (valTypeOf(value) === VAL.NUMBER) {
|
|
859
|
+
inc('__ftoa')
|
|
860
|
+
return typed(['call', '$__ftoa', asF64(emit(value)), ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
861
|
+
}
|
|
862
|
+
inc('__to_str')
|
|
863
|
+
return typed(['call', '$__to_str', asF64(emit(value))], 'f64')
|
|
864
|
+
}
|
|
865
|
+
|
|
809
866
|
ctx.core.emit['String.fromCharCode'] = (code) => mkPtrIR(PTR.SSO, 1, asI32(emit(code)))
|
|
810
867
|
|
|
811
868
|
// String.fromCodePoint(cp) → UTF-8 encoded string
|
package/module/symbol.js
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* @module symbol
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { typed, asF64, mkPtrIR } from '../src/ir.js'
|
|
21
|
+
import { emit } from '../src/emit.js'
|
|
21
22
|
import { err, inc, PTR } from '../src/ctx.js'
|
|
22
23
|
|
|
23
24
|
const RESERVED = 16 // first user atom ID
|
package/module/timer.js
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* @module timer
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { typed, asF64, UNDEF_NAN, MAX_CLOSURE_ARITY, temp } from '../src/ir.js'
|
|
21
|
+
import { emit } from '../src/emit.js'
|
|
21
22
|
import { inc, PTR } from '../src/ctx.js'
|
|
22
|
-
import { temp } from '../src/ir.js'
|
|
23
23
|
|
|
24
24
|
const MAX_TIMERS = 64
|
|
25
25
|
const ENTRY_SIZE = 40
|