jz 0.1.1 → 0.2.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 +257 -90
- package/cli.js +34 -8
- package/index.js +102 -13
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +691 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +434 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +626 -132
- package/src/autoload.js +14 -6
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +634 -145
- package/src/host.js +244 -51
- package/src/ir.js +81 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1132 -4
- package/src/prepare.js +321 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/module/symbol.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Type=0 (ATOM): aux=atomId, offset=0.
|
|
5
5
|
*
|
|
6
6
|
* Reserved atom IDs (0-15):
|
|
7
|
-
* 0
|
|
8
|
-
* 1
|
|
9
|
-
* 2
|
|
7
|
+
* 0 = reserved
|
|
8
|
+
* 1 = null (NULL_NAN sentinel)
|
|
9
|
+
* 2 = undefined (UNDEF_NAN sentinel)
|
|
10
|
+
* 3-15 = reserved
|
|
10
11
|
*
|
|
11
12
|
* User symbols start at ID 16.
|
|
12
13
|
* Symbol('name') → unique atom per call site (compile-time)
|
package/module/timer.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Timer module — setTimeout/setInterval/clearTimeout/clearInterval.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Runs inline after __start — no JS host, no Rust runner needed.
|
|
4
|
+
* Two host-mode lowerings:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
6
|
+
* `host: 'js'` (default): emit `env.setTimeout(cb: f64, delay: f64, repeat: i32) -> f64`
|
|
7
|
+
* and `env.clearTimeout(id: f64) -> f64`. The JS host (src/host.js) drives both
|
|
8
|
+
* via global setTimeout/setInterval and calls back into wasm through the
|
|
9
|
+
* exported `__invoke_closure(clos: i64) -> f64` trampoline. No queue, no
|
|
10
|
+
* polling — the host's event loop does the scheduling.
|
|
11
|
+
*
|
|
12
|
+
* `host: 'wasi'`: pure-WASM timer queue using WASI clock_time_get for
|
|
13
|
+
* deadlines. Runs inline after __start (or via __timer_loop on wasmtime/
|
|
14
|
+
* wasmer). No JS host needed.
|
|
15
|
+
*
|
|
16
|
+
* WASI queue layout: heap-allocated array of entries in linear memory.
|
|
8
17
|
* Each entry (40 bytes):
|
|
9
18
|
* [0] id (i32) — unique timer ID
|
|
10
19
|
* [4] pad (i32)
|
|
@@ -17,14 +26,32 @@
|
|
|
17
26
|
* @module timer
|
|
18
27
|
*/
|
|
19
28
|
|
|
20
|
-
import { typed, asF64, UNDEF_NAN, MAX_CLOSURE_ARITY, temp } from '../src/ir.js'
|
|
29
|
+
import { typed, asF64, asI64, UNDEF_NAN, MAX_CLOSURE_ARITY, temp, tempI64 } from '../src/ir.js'
|
|
21
30
|
import { emit } from '../src/emit.js'
|
|
22
|
-
import { inc, PTR } from '../src/ctx.js'
|
|
31
|
+
import { inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
23
32
|
|
|
24
33
|
const MAX_TIMERS = 64
|
|
25
34
|
const ENTRY_SIZE = 40
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
const addImportOnce = (ctx, mod, name, fn) => {
|
|
37
|
+
if (ctx.module.imports.some(i => i[1] === `"${mod}"` && i[2] === `"${name}"`)) return
|
|
38
|
+
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, fn])
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Shared "fire a NaN-boxed closure with 0 args" trampoline. Funcref index lives
|
|
42
|
+
// in upper 16 bits of the pointer payload; remaining $ftN slots get UNDEF_NAN.
|
|
43
|
+
// Closure is also passed as $__env so captures resolve via env-load.
|
|
44
|
+
// `exported` adds (export "__invoke_closure") so the JS host can call it.
|
|
45
|
+
const invokeClosureFn = (exported) => `(func $__invoke_closure${exported ? ' (export "__invoke_closure")' : ''} (param $clos i64) (result f64)
|
|
46
|
+
(call_indirect (type \$ftN)
|
|
47
|
+
(f64.reinterpret_i64 (local.get $clos))
|
|
48
|
+
(i32.const 0)
|
|
49
|
+
${Array.from({length: MAX_CLOSURE_ARITY}, () => `(f64.const nan:${UNDEF_NAN})`).join('\n ')}
|
|
50
|
+
(i32.wrap_i64 (i64.and
|
|
51
|
+
(i64.shr_u (local.get $clos) (i64.const ${LAYOUT.AUX_SHIFT}))
|
|
52
|
+
(i64.const ${LAYOUT.AUX_MASK})))))`
|
|
53
|
+
|
|
54
|
+
const setupWasi = (ctx) => {
|
|
28
55
|
// Always include init + tick + loop when timer module loads (structural, not per-emitter)
|
|
29
56
|
inc('__timer_init', '__timer_tick', '__timer_loop')
|
|
30
57
|
|
|
@@ -32,7 +59,7 @@ export default (ctx) => {
|
|
|
32
59
|
__timer_init: ['__alloc'],
|
|
33
60
|
__timer_add: ['__time_ns'],
|
|
34
61
|
__timer_cancel: [],
|
|
35
|
-
__timer_dispatch: [],
|
|
62
|
+
__timer_dispatch: ['__invoke_closure'],
|
|
36
63
|
__timer_tick: ['__time_ns', '__timer_dispatch'],
|
|
37
64
|
__timer_loop: ['__time_ns', '__timer_dispatch'],
|
|
38
65
|
})
|
|
@@ -41,12 +68,8 @@ export default (ctx) => {
|
|
|
41
68
|
// call_indirect always matches the $ftN type (env, argc, a0..a7)
|
|
42
69
|
ctx.closure.floor = MAX_CLOSURE_ARITY
|
|
43
70
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ctx.module.imports.push(
|
|
47
|
-
['import', '"wasi_snapshot_preview1"', '"clock_time_get"',
|
|
48
|
-
['func', '$__clock_time_get', ['param', 'i32'], ['param', 'i64'], ['param', 'i32'], ['result', 'i32']]])
|
|
49
|
-
}
|
|
71
|
+
addImportOnce(ctx, 'wasi_snapshot_preview1', 'clock_time_get',
|
|
72
|
+
['func', '$__clock_time_get', ['param', 'i32'], ['param', 'i64'], ['param', 'i32'], ['result', 'i32']])
|
|
50
73
|
|
|
51
74
|
// __time_ns() → i64 — current monotonic nanoseconds
|
|
52
75
|
// Reuses address 0-7 for the i64 output (same as __time_ms in console.js)
|
|
@@ -63,9 +86,9 @@ export default (ctx) => {
|
|
|
63
86
|
(global.set $__timer_queue (call $__alloc (i32.const ${MAX_TIMERS * ENTRY_SIZE})))
|
|
64
87
|
(memory.fill (global.get $__timer_queue) (i32.const 0) (i32.const ${MAX_TIMERS * ENTRY_SIZE})))`
|
|
65
88
|
|
|
66
|
-
// __timer_add(closure_ptr:
|
|
89
|
+
// __timer_add(closure_ptr: i64, delay_ms: f64, interval: i32) → f64 (timer ID)
|
|
67
90
|
// interval=0 → setTimeout, interval=1 → setInterval
|
|
68
|
-
ctx.core.stdlib['__timer_add'] = `(func $__timer_add (param $clos
|
|
91
|
+
ctx.core.stdlib['__timer_add'] = `(func $__timer_add (param $clos i64) (param $delay f64) (param $is_interval i32) (result f64)
|
|
69
92
|
(local $id i32)
|
|
70
93
|
(local $slot i32)
|
|
71
94
|
(local $base i32)
|
|
@@ -97,7 +120,7 @@ export default (ctx) => {
|
|
|
97
120
|
(i32.store (i32.add (local.get $base) (i32.mul (local.get $slot) (i32.const ${ENTRY_SIZE})))
|
|
98
121
|
(local.get $id))
|
|
99
122
|
;; closure_ptr @ offset+8
|
|
100
|
-
(
|
|
123
|
+
(i64.store (i32.add (local.get $base) (i32.add (i32.mul (local.get $slot) (i32.const ${ENTRY_SIZE})) (i32.const 8)))
|
|
101
124
|
(local.get $clos))
|
|
102
125
|
;; deadline_ns @ offset+16
|
|
103
126
|
(i64.store (i32.add (local.get $base) (i32.add (i32.mul (local.get $slot) (i32.const ${ENTRY_SIZE})) (i32.const 16)))
|
|
@@ -143,7 +166,7 @@ export default (ctx) => {
|
|
|
143
166
|
(local $slot i32)
|
|
144
167
|
(local $base i32)
|
|
145
168
|
(local $dispatched i32)
|
|
146
|
-
(local $clos
|
|
169
|
+
(local $clos i64)
|
|
147
170
|
(local $interval f64)
|
|
148
171
|
(local $id i32)
|
|
149
172
|
(local.set $dispatched (i32.const 0))
|
|
@@ -160,7 +183,7 @@ export default (ctx) => {
|
|
|
160
183
|
(local.get $now))
|
|
161
184
|
(then
|
|
162
185
|
;; Read closure and interval before potentially clearing
|
|
163
|
-
(local.set $clos (
|
|
186
|
+
(local.set $clos (i64.load (i32.add (local.get $base) (i32.add (i32.mul (local.get $slot) (i32.const ${ENTRY_SIZE})) (i32.const 8)))))
|
|
164
187
|
(local.set $interval (f64.load (i32.add (local.get $base) (i32.add (i32.mul (local.get $slot) (i32.const ${ENTRY_SIZE})) (i32.const 24)))))
|
|
165
188
|
(local.set $id (i32.load (i32.add (local.get $base) (i32.mul (local.get $slot) (i32.const ${ENTRY_SIZE})))))
|
|
166
189
|
;; Interval? Reschedule
|
|
@@ -173,15 +196,8 @@ export default (ctx) => {
|
|
|
173
196
|
;; Timeout: mark dead
|
|
174
197
|
(i32.store (i32.add (local.get $base) (i32.add (i32.mul (local.get $slot) (i32.const ${ENTRY_SIZE})) (i32.const 32))) (i32.const 0))
|
|
175
198
|
(global.set $__timer_count (i32.sub (global.get $__timer_count) (i32.const 1)))))
|
|
176
|
-
;;
|
|
177
|
-
|
|
178
|
-
(drop (call_indirect (type \$ftN)
|
|
179
|
-
(local.get $clos) ;; env = closure pointer itself
|
|
180
|
-
(i32.const 0) ;; argc = 0
|
|
181
|
-
${Array.from({length: MAX_CLOSURE_ARITY}, () => `(f64.const nan:${UNDEF_NAN})`).join('\n ')}
|
|
182
|
-
(i32.wrap_i64 (i64.and
|
|
183
|
-
(i64.shr_u (i64.reinterpret_f64 (local.get $clos)) (i64.const 32))
|
|
184
|
-
(i64.const 0x7FFF)))))
|
|
199
|
+
;; Fire closure with 0 args (shared trampoline)
|
|
200
|
+
(drop (call $__invoke_closure (local.get $clos)))
|
|
185
201
|
(local.set $dispatched (i32.add (local.get $dispatched) (i32.const 1)))))))
|
|
186
202
|
(local.set $slot (i32.add (local.get $slot) (i32.const 1)))
|
|
187
203
|
(br $scan)))
|
|
@@ -213,6 +229,8 @@ export default (ctx) => {
|
|
|
213
229
|
;; Loop
|
|
214
230
|
(br $poll))))`
|
|
215
231
|
|
|
232
|
+
ctx.core.stdlib['__invoke_closure'] = invokeClosureFn(false)
|
|
233
|
+
|
|
216
234
|
// Register globals for timer state
|
|
217
235
|
// $__timer_queue: i32 — base address of timer array
|
|
218
236
|
// $__timer_next_id: i32 — next timer ID
|
|
@@ -224,18 +242,18 @@ export default (ctx) => {
|
|
|
224
242
|
// Emitter: setTimeout(closure, delay) → timer_id
|
|
225
243
|
ctx.core.emit['setTimeout'] = (closureExpr, delayExpr) => {
|
|
226
244
|
inc('__timer_add')
|
|
227
|
-
const t =
|
|
245
|
+
const t = tempI64('tc')
|
|
228
246
|
return typed(['block', ['result', 'f64'],
|
|
229
|
-
['local.set', `$${t}`,
|
|
247
|
+
['local.set', `$${t}`, asI64(emit(closureExpr))],
|
|
230
248
|
['call', '$__timer_add', ['local.get', `$${t}`], asF64(emit(delayExpr)), ['i32.const', 0]]], 'f64')
|
|
231
249
|
}
|
|
232
250
|
|
|
233
251
|
// Emitter: setInterval(closure, delay) → timer_id
|
|
234
252
|
ctx.core.emit['setInterval'] = (closureExpr, delayExpr) => {
|
|
235
253
|
inc('__timer_add')
|
|
236
|
-
const t =
|
|
254
|
+
const t = tempI64('tc')
|
|
237
255
|
return typed(['block', ['result', 'f64'],
|
|
238
|
-
['local.set', `$${t}`,
|
|
256
|
+
['local.set', `$${t}`, asI64(emit(closureExpr))],
|
|
239
257
|
['call', '$__timer_add', ['local.get', `$${t}`], asF64(emit(delayExpr)), ['i32.const', 1]]], 'f64')
|
|
240
258
|
}
|
|
241
259
|
|
|
@@ -251,3 +269,43 @@ export default (ctx) => {
|
|
|
251
269
|
return typed(['call', '$__timer_cancel', asF64(emit(idExpr))], 'f64')
|
|
252
270
|
}
|
|
253
271
|
}
|
|
272
|
+
|
|
273
|
+
const setupJsHost = (ctx) => {
|
|
274
|
+
// Timer callbacks are invoked through __invoke_closure, which always pads to
|
|
275
|
+
// MAX_CLOSURE_ARITY. Set the ABI floor before plan() resolves $ftN width.
|
|
276
|
+
ctx.closure.floor = MAX_CLOSURE_ARITY
|
|
277
|
+
|
|
278
|
+
// env.setTimeout's cb param is i64 (NaN-box bits) to dodge V8's f64 NaN
|
|
279
|
+
// canonicalization at the wasm→JS boundary (same reason as env.print —
|
|
280
|
+
// see module/console.js header). delay is a real numeric f64 (no NaN-box
|
|
281
|
+
// hazard), repeat is i32, return is the timer id (numeric int).
|
|
282
|
+
const needSetTimeout = () => addImportOnce(ctx, 'env', 'setTimeout',
|
|
283
|
+
['func', '$__set_timeout', ['param', 'i64'], ['param', 'f64'], ['param', 'i32'], ['result', 'f64']])
|
|
284
|
+
const needClearTimeout = () => addImportOnce(ctx, 'env', 'clearTimeout',
|
|
285
|
+
['func', '$__clear_timeout', ['param', 'f64'], ['result', 'f64']])
|
|
286
|
+
|
|
287
|
+
ctx.core.stdlib['__invoke_closure'] = invokeClosureFn(true)
|
|
288
|
+
|
|
289
|
+
const emitSet = (closureExpr, delayExpr, repeat) => {
|
|
290
|
+
needSetTimeout()
|
|
291
|
+
inc('__invoke_closure')
|
|
292
|
+
return typed(['call', '$__set_timeout',
|
|
293
|
+
['i64.reinterpret_f64', asF64(emit(closureExpr))],
|
|
294
|
+
asF64(emit(delayExpr)),
|
|
295
|
+
['i32.const', repeat]], 'f64')
|
|
296
|
+
}
|
|
297
|
+
ctx.core.emit['setTimeout'] = (c, d) => emitSet(c, d, 0)
|
|
298
|
+
ctx.core.emit['setInterval'] = (c, d) => emitSet(c, d, 1)
|
|
299
|
+
|
|
300
|
+
const emitClear = (idExpr) => {
|
|
301
|
+
needClearTimeout()
|
|
302
|
+
return typed(['call', '$__clear_timeout', asF64(emit(idExpr))], 'f64')
|
|
303
|
+
}
|
|
304
|
+
ctx.core.emit['clearTimeout'] = emitClear
|
|
305
|
+
ctx.core.emit['clearInterval'] = emitClear
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export default (ctx) => {
|
|
309
|
+
if (ctx.transform.host === 'wasi') setupWasi(ctx)
|
|
310
|
+
else setupJsHost(ctx)
|
|
311
|
+
}
|