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/src/host.js
CHANGED
|
@@ -20,10 +20,23 @@ import { wasi } from '../wasi.js'
|
|
|
20
20
|
|
|
21
21
|
// NaN-boxing encode/decode — shared 8-byte scratch buffer
|
|
22
22
|
const _buf = new ArrayBuffer(8), _u32 = new Uint32Array(_buf), _f64 = new Float64Array(_buf)
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
//
|
|
23
|
+
// Cross-typed-array view for i64↔f64 reinterpretation. Used at every wasm↔JS
|
|
24
|
+
// boundary that carries a NaN-boxed pointer as i64 bits — V8 may canonicalize
|
|
25
|
+
// f64 NaN payloads at the boundary, so the carrier is BigInt and reinterpret
|
|
26
|
+
// runs once on each side. Separate buffer so it never aliases _u32/_f64.
|
|
27
|
+
const _bi64 = (() => {
|
|
28
|
+
const ab = new ArrayBuffer(8), bi = new BigInt64Array(ab), fv = new Float64Array(ab)
|
|
29
|
+
return {
|
|
30
|
+
i64ToF64: (big) => { bi[0] = big; return fv[0] },
|
|
31
|
+
f64ToI64: (f) => { fv[0] = f; return bi[0] },
|
|
32
|
+
}
|
|
33
|
+
})()
|
|
34
|
+
export const i64ToF64 = _bi64.i64ToF64
|
|
35
|
+
export const f64ToI64 = _bi64.f64ToI64
|
|
36
|
+
// Reserved atoms (type=0, offset=0): aux=1 → null, aux=2 → undefined.
|
|
37
|
+
// Distinct from 0, JS NaN (payload=0), and all pointers.
|
|
26
38
|
_u32[1] = 0x7FF80001; _u32[0] = 0; export const NULL_NAN = _f64[0]
|
|
39
|
+
_u32[1] = 0x7FF80002; _u32[0] = 0; export const UNDEF_NAN = _f64[0]
|
|
27
40
|
|
|
28
41
|
// Coerce JS null/undefined → NaN-boxed sentinels for WASM boundary
|
|
29
42
|
export const coerce = v => v === null ? NULL_NAN : v === undefined ? UNDEF_NAN : v
|
|
@@ -32,8 +45,9 @@ export const coerce = v => v === null ? NULL_NAN : v === undefined ? UNDEF_NAN :
|
|
|
32
45
|
const decode = v => {
|
|
33
46
|
if (v === v) return v // fast path: non-NaN
|
|
34
47
|
_f64[0] = v
|
|
35
|
-
if (_u32[
|
|
36
|
-
if (_u32[1] ===
|
|
48
|
+
if (_u32[0] !== 0) return v
|
|
49
|
+
if (_u32[1] === 0x7FF80001) return null
|
|
50
|
+
if (_u32[1] === 0x7FF80002) return undefined
|
|
37
51
|
return v
|
|
38
52
|
}
|
|
39
53
|
|
|
@@ -99,16 +113,22 @@ export const memory = (src) => {
|
|
|
99
113
|
|
|
100
114
|
// JS-side bump allocator (heap ptr at byte 1020, same convention as WASM)
|
|
101
115
|
const jsAlloc = (bytes) => {
|
|
102
|
-
|
|
116
|
+
let d = dv(), p = d.getInt32(1020, true)
|
|
103
117
|
const aligned = (p + 7) & ~7 // 8-byte align
|
|
104
118
|
const next = aligned + bytes
|
|
105
|
-
if (next > mem.buffer.byteLength)
|
|
119
|
+
if (next > mem.buffer.byteLength) {
|
|
120
|
+
mem.grow(Math.ceil((next - mem.buffer.byteLength) / 65536))
|
|
121
|
+
d = dv() // buffer was detached by grow
|
|
122
|
+
}
|
|
106
123
|
d.setInt32(1020, next, true)
|
|
107
124
|
return aligned
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
// Use WASM allocator if available, else JS-side bump
|
|
111
128
|
let alloc = wasmExports?._alloc || jsAlloc
|
|
129
|
+
// JS-side reset: rewind the bump pointer at byte 1020. Only used when no WASM
|
|
130
|
+
// _clear is present (otherwise the WASM global / shared slot is authoritative).
|
|
131
|
+
const jsReset = () => dv().setInt32(1020, 1024, true)
|
|
112
132
|
|
|
113
133
|
// Initialize heap pointer if not yet set
|
|
114
134
|
const initDv = dv()
|
|
@@ -150,7 +170,8 @@ export const memory = (src) => {
|
|
|
150
170
|
if (_enhanced.has(mem)) {
|
|
151
171
|
mem.schemas = schemas
|
|
152
172
|
if (wasmExports?._alloc) { alloc = wasmExports._alloc; mem.alloc = alloc }
|
|
153
|
-
if (wasmExports?.
|
|
173
|
+
if (wasmExports?._clear) mem.reset = wasmExports._clear
|
|
174
|
+
else if (!mem.reset) mem.reset = jsReset
|
|
154
175
|
if (extMap) mem._extMap = extMap
|
|
155
176
|
return mem
|
|
156
177
|
}
|
|
@@ -160,8 +181,14 @@ export const memory = (src) => {
|
|
|
160
181
|
mem._extMap = extMap
|
|
161
182
|
|
|
162
183
|
mem.Array = (data) => {
|
|
163
|
-
const n = data.length, off = hdr(n, n, n * 8)
|
|
164
|
-
|
|
184
|
+
const n = data.length, off = hdr(n, n, n * 8)
|
|
185
|
+
// Stage as i64 bits, not as JS Numbers: V8 may transition a JS Array holding
|
|
186
|
+
// NaN-payload doubles to HOLEY_DOUBLE_ELEMENTS, which canonicalizes the NaN
|
|
187
|
+
// payload to 0x7FF8000000000000 — destroying the type/offset bits.
|
|
188
|
+
const wrapped = new BigInt64Array(n)
|
|
189
|
+
for (let i = 0; i < n; i++) wrapped[i] = f64ToI64(mem.wrapVal(data[i]))
|
|
190
|
+
const dst = new BigInt64Array(mem.buffer, off, n)
|
|
191
|
+
for (let i = 0; i < n; i++) dst[i] = wrapped[i]
|
|
165
192
|
return ptr(1, 0, off)
|
|
166
193
|
}
|
|
167
194
|
|
|
@@ -169,7 +196,7 @@ export const memory = (src) => {
|
|
|
169
196
|
if (str.length <= 4 && /^[\x00-\x7f]*$/.test(str)) {
|
|
170
197
|
let packed = 0
|
|
171
198
|
for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
|
|
172
|
-
return ptr(
|
|
199
|
+
return ptr(4, 0x4000 | str.length, packed) // STRING + SSO_BIT
|
|
173
200
|
}
|
|
174
201
|
const enc = new TextEncoder().encode(str)
|
|
175
202
|
const n = enc.length, raw = alloc(4 + n), m = dv()
|
|
@@ -192,6 +219,10 @@ export const memory = (src) => {
|
|
|
192
219
|
if (v === null || v === undefined) return coerce(v)
|
|
193
220
|
if (typeof v === 'number' || typeof v === 'boolean') return Number(v)
|
|
194
221
|
if (typeof v === 'string') return this.String(v)
|
|
222
|
+
// BigInt as a data value crosses the boundary as a decimal-string. wasm-side
|
|
223
|
+
// numeric parsers accept string form. Direct i64 function args go through this
|
|
224
|
+
// path too; the call wrappers below detect BigInt before adaptArgs reinterprets.
|
|
225
|
+
if (typeof v === 'bigint') return this.String(v.toString())
|
|
195
226
|
if (Array.isArray(v)) return this.Array(v)
|
|
196
227
|
if (v instanceof ArrayBuffer) return this.Buffer(v)
|
|
197
228
|
if (v instanceof DataView) return this.Buffer(v.buffer)
|
|
@@ -223,14 +254,19 @@ export const memory = (src) => {
|
|
|
223
254
|
else if (this._extMap) return this.External(obj)
|
|
224
255
|
else throw Error(`No schema for {${key}}`)
|
|
225
256
|
}
|
|
226
|
-
const schema = schemas[sid], n = schema.length, raw = alloc(n * 8)
|
|
257
|
+
const schema = schemas[sid], n = schema.length, raw = alloc(n * 8)
|
|
258
|
+
// Stage as i64 bits so V8 can't canonicalize NaN-payload pointers across
|
|
259
|
+
// recursive allocations. See mem.Array for the same pattern.
|
|
260
|
+
const wrapped = new BigInt64Array(n)
|
|
227
261
|
for (let i = 0; i < n; i++) {
|
|
228
262
|
let v = obj[schema[i]]
|
|
229
263
|
if (v === null || v === undefined) v = coerce(v)
|
|
230
264
|
else if (typeof v === 'string') v = this.String(v)
|
|
231
265
|
else if (Array.isArray(v)) v = this.Array(v)
|
|
232
|
-
|
|
266
|
+
wrapped[i] = f64ToI64(v)
|
|
233
267
|
}
|
|
268
|
+
const dst = new BigInt64Array(mem.buffer, raw, n)
|
|
269
|
+
for (let i = 0; i < n; i++) dst[i] = wrapped[i]
|
|
234
270
|
return ptr(6, sid, raw)
|
|
235
271
|
}
|
|
236
272
|
|
|
@@ -238,8 +274,10 @@ export const memory = (src) => {
|
|
|
238
274
|
if (Array.isArray(p)) return p.map(v => this.read(v)) // multi-value tuple
|
|
239
275
|
if (p === p) return p // regular number passthrough (NaN fails ===)
|
|
240
276
|
const t = type(p), a = aux(p), off = offset(p)
|
|
241
|
-
if (t === 0 &&
|
|
242
|
-
|
|
277
|
+
if (t === 0 && off === 0) {
|
|
278
|
+
if (a === 1) return null
|
|
279
|
+
if (a === 2) return undefined
|
|
280
|
+
}
|
|
243
281
|
if (t === 11 && this._extMap) return this._extMap[off]
|
|
244
282
|
if (t === 1) { // ARRAY
|
|
245
283
|
let m = dv(), aOff = off
|
|
@@ -267,15 +305,16 @@ export const memory = (src) => {
|
|
|
267
305
|
new Uint8Array(out).set(new Uint8Array(mem.buffer, off, byteLen))
|
|
268
306
|
return out
|
|
269
307
|
}
|
|
270
|
-
if (t === 4) { // STRING (heap)
|
|
308
|
+
if (t === 4) { // STRING (aux bit 0x4000 = SSO inline, else heap)
|
|
309
|
+
const a2 = aux(p)
|
|
310
|
+
if (a2 & 0x4000) {
|
|
311
|
+
const len = a2 & 0x7; let s = ''
|
|
312
|
+
for (let i = 0; i < len; i++) s += String.fromCharCode((off >>> (i * 8)) & 0xFF)
|
|
313
|
+
return s
|
|
314
|
+
}
|
|
271
315
|
const len = dv().getInt32(off - 4, true)
|
|
272
316
|
return new TextDecoder().decode(new Uint8Array(mem.buffer, off, len))
|
|
273
317
|
}
|
|
274
|
-
if (t === 5) { // STRING_SSO
|
|
275
|
-
const len = aux(p); let s = ''
|
|
276
|
-
for (let i = 0; i < len; i++) s += String.fromCharCode((off >>> (i * 8)) & 0xFF)
|
|
277
|
-
return s
|
|
278
|
-
}
|
|
279
318
|
if (t === 6) { // OBJECT
|
|
280
319
|
const m = dv(), sid = aux(p), keys = this.schemas[sid]
|
|
281
320
|
if (!keys) return p
|
|
@@ -352,7 +391,7 @@ export const memory = (src) => {
|
|
|
352
391
|
}
|
|
353
392
|
|
|
354
393
|
mem.alloc = alloc
|
|
355
|
-
mem.reset = wasmExports?.
|
|
394
|
+
mem.reset = wasmExports?._clear || jsReset
|
|
356
395
|
|
|
357
396
|
// TypedArray constructors: memory.Float64Array(data), etc.
|
|
358
397
|
for (const [name, [elemId, stride, , setter]] of Object.entries(ELEMS)) {
|
|
@@ -382,6 +421,17 @@ export const wrap = (memSrc, inst) => {
|
|
|
382
421
|
restFuncs.set(typeof entry === 'string' ? entry : entry.name, typeof entry === 'string' ? 0 : entry.fixed)
|
|
383
422
|
} catch (e) { /* ignore */ }
|
|
384
423
|
}
|
|
424
|
+
// i64-ABI exports: boundary-wrapped funcs whose NaN-boxed pointer params/
|
|
425
|
+
// result ride i64 to dodge V8's NaN canonicalization. Map: name → { p, r }
|
|
426
|
+
// with p = bit mask of i64 params, r = 1 iff result is i64. JS side
|
|
427
|
+
// reinterprets f64↔BigInt only at those positions (see
|
|
428
|
+
// synthesizeBoundaryWrappers).
|
|
429
|
+
const i64Exp = new Map(), EMPTY_SET = new Set()
|
|
430
|
+
const i64Secs = WebAssembly.Module.customSections(mod, 'jz:i64exp')
|
|
431
|
+
if (i64Secs.length) {
|
|
432
|
+
try { for (const e of JSON.parse(new TextDecoder().decode(i64Secs[0]))) i64Exp.set(e.name, { p: new Set(e.p), r: e.r }) }
|
|
433
|
+
catch { /* ignore */ }
|
|
434
|
+
}
|
|
385
435
|
|
|
386
436
|
const mem = memory(memSrc)
|
|
387
437
|
const lastErrBits = realInst.exports.__jz_last_err_bits
|
|
@@ -398,32 +448,87 @@ export const wrap = (memSrc, inst) => {
|
|
|
398
448
|
throw wrapped
|
|
399
449
|
}
|
|
400
450
|
const exports = {}
|
|
451
|
+
// Per-position carrier swap: f64 stays Number, i64 positions reinterpret to
|
|
452
|
+
// BigInt before the call and back to Number after. p is a Set of i64 param
|
|
453
|
+
// indices; r = result is i64. Numeric (f64) positions pass through unchanged.
|
|
454
|
+
const adaptArgs = (a, p) => p.size === 0 ? a : a.map((x, i) => p.has(i) ? f64ToI64(x) : x)
|
|
455
|
+
const adaptRet = (ret, r) => r ? i64ToF64(ret) : ret
|
|
456
|
+
|
|
457
|
+
// Arity-specialized wrapper: rest-spread + .map() + .apply() costs ~85ns/call
|
|
458
|
+
// on hot loops (mandelbrot benchmark: 51ms wrapped vs 35ms direct over 200K
|
|
459
|
+
// calls). Generating positional `function(a0, a1, ...)` via Function lets V8
|
|
460
|
+
// fully inline the WASM call. Falls back to the spread-form wrapper if the
|
|
461
|
+
// Function constructor is unavailable (CSP) or arity is unusually large.
|
|
462
|
+
const makeFastWrapper = (fn, len, p, r, decode_, wrap_) => {
|
|
463
|
+
const params = [], wrapped = []
|
|
464
|
+
for (let i = 0; i < len; i++) {
|
|
465
|
+
const a = `a${i}`
|
|
466
|
+
params.push(a)
|
|
467
|
+
const w = wrap_ ? `wrap_(${a})` : `coerce(${a})`
|
|
468
|
+
wrapped.push(p.has(i) ? `f64ToI64(${w})` : w)
|
|
469
|
+
}
|
|
470
|
+
const callExpr = `fn(${wrapped.join(',')})`
|
|
471
|
+
const retExpr = r ? `i64ToF64(${callExpr})` : callExpr
|
|
472
|
+
const body = `return function(${params.join(',')}) {\n` +
|
|
473
|
+
` try { return decode_(${retExpr}) } catch (e) { decodeThrown(e) }\n` +
|
|
474
|
+
`}`
|
|
475
|
+
return new Function('fn', 'wrap_', 'coerce', 'decode_', 'f64ToI64', 'i64ToF64', 'decodeThrown', body)(
|
|
476
|
+
fn, wrap_, coerce, decode_, f64ToI64, i64ToF64, decodeThrown)
|
|
477
|
+
}
|
|
478
|
+
|
|
401
479
|
// Pure scalar module (no memory): pass f64 values directly, no marshaling
|
|
402
480
|
if (!mem) {
|
|
403
|
-
for (const [name, fn] of Object.entries(realInst.exports))
|
|
404
|
-
exports[name] =
|
|
405
|
-
|
|
406
|
-
|
|
481
|
+
for (const [name, fn] of Object.entries(realInst.exports)) {
|
|
482
|
+
if (typeof fn !== 'function') { exports[name] = fn; continue }
|
|
483
|
+
const sig = i64Exp.get(name)
|
|
484
|
+
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
485
|
+
const len = fn.length
|
|
486
|
+
try {
|
|
487
|
+
exports[name] = makeFastWrapper(fn, len, p, r, decode, null)
|
|
488
|
+
continue
|
|
489
|
+
} catch { /* CSP fallback */ }
|
|
490
|
+
exports[name] = (...args) => {
|
|
491
|
+
while (args.length < len) args.push(undefined)
|
|
492
|
+
try {
|
|
493
|
+
const wasmArgs = adaptArgs(args.map(coerce), p)
|
|
494
|
+
return decode(adaptRet(fn(...wasmArgs), r))
|
|
495
|
+
} catch (e) { decodeThrown(e) }
|
|
496
|
+
}
|
|
497
|
+
}
|
|
407
498
|
return exports
|
|
408
499
|
}
|
|
500
|
+
const memWrapVal = mem.wrapVal.bind(mem)
|
|
501
|
+
const memRead = mem.read.bind(mem)
|
|
409
502
|
for (const [name, fn] of Object.entries(realInst.exports)) {
|
|
410
503
|
if (restFuncs.has(name) && typeof fn === 'function') {
|
|
411
504
|
const fixed = restFuncs.get(name)
|
|
505
|
+
const sig = i64Exp.get(name)
|
|
506
|
+
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
412
507
|
exports[name] = (...args) => {
|
|
413
508
|
const a = args.slice(0, fixed).map(x => mem.wrapVal(x))
|
|
414
509
|
while (a.length < fixed) a.push(UNDEF_NAN)
|
|
415
510
|
a.push(mem.Array(args.slice(fixed)))
|
|
416
511
|
try {
|
|
417
|
-
|
|
512
|
+
const ret = fn.apply(null, adaptArgs(a, p))
|
|
513
|
+
return mem.read(adaptRet(ret, r))
|
|
418
514
|
} catch (error) {
|
|
419
515
|
decodeThrown(error)
|
|
420
516
|
}
|
|
421
517
|
}
|
|
422
518
|
} else if (typeof fn === 'function') {
|
|
519
|
+
const sig = i64Exp.get(name)
|
|
520
|
+
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
521
|
+
const len = fn.length
|
|
522
|
+
try {
|
|
523
|
+
exports[name] = makeFastWrapper(fn, len, p, r, memRead, memWrapVal)
|
|
524
|
+
continue
|
|
525
|
+
} catch { /* CSP fallback */ }
|
|
423
526
|
exports[name] = (...args) => {
|
|
424
|
-
while (args.length <
|
|
527
|
+
while (args.length < len) args.push(undefined)
|
|
425
528
|
try {
|
|
426
|
-
|
|
529
|
+
const boxed = args.map(x => mem.wrapVal(x))
|
|
530
|
+
const ret = fn.apply(null, adaptArgs(boxed, p))
|
|
531
|
+
return mem.read(adaptRet(ret, r))
|
|
427
532
|
} catch (error) {
|
|
428
533
|
decodeThrown(error)
|
|
429
534
|
}
|
|
@@ -438,33 +543,106 @@ export const wrap = (memSrc, inst) => {
|
|
|
438
543
|
const prepareInterop = (opts) => {
|
|
439
544
|
const state = { extMap: [null], mem: null }
|
|
440
545
|
opts._interp = opts._interp || {}
|
|
441
|
-
|
|
546
|
+
// __ext_* receive NaN-boxed pointers across the env boundary as i64 (BigInt
|
|
547
|
+
// in JS) — see module/collection.js header for rationale. f64 returns are
|
|
548
|
+
// wrapped back to BigInt so the wasm side reinterprets a non-canonicalized
|
|
549
|
+
// bit pattern.
|
|
550
|
+
opts._interp.__ext_prop = (objBig, propBig) => {
|
|
551
|
+
const objPtr = i64ToF64(objBig), propPtr = i64ToF64(propBig)
|
|
442
552
|
const obj = state.extMap[offset(objPtr)]
|
|
443
553
|
const prop = state.mem.read(propPtr)
|
|
444
|
-
return state.mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop])
|
|
554
|
+
return f64ToI64(state.mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop]))
|
|
445
555
|
}
|
|
446
|
-
opts._interp.__ext_has = (
|
|
447
|
-
return (state.mem.read(
|
|
556
|
+
opts._interp.__ext_has = (objBig, propBig) => {
|
|
557
|
+
return (state.mem.read(i64ToF64(propBig)) in state.extMap[offset(i64ToF64(objBig))]) ? 1 : 0
|
|
448
558
|
}
|
|
449
|
-
opts._interp.__ext_set = (
|
|
450
|
-
state.extMap[offset(
|
|
559
|
+
opts._interp.__ext_set = (objBig, propBig, valBig) => {
|
|
560
|
+
state.extMap[offset(i64ToF64(objBig))][state.mem.read(i64ToF64(propBig))] = state.mem.read(i64ToF64(valBig))
|
|
451
561
|
return 1
|
|
452
562
|
}
|
|
453
|
-
opts._interp.__ext_call = (
|
|
454
|
-
const obj = state.extMap[offset(
|
|
455
|
-
const prop = state.mem.read(
|
|
456
|
-
const args = state.mem.read(
|
|
457
|
-
return state.mem.wrapVal(obj[prop].apply(obj, args))
|
|
563
|
+
opts._interp.__ext_call = (objBig, propBig, argsBig) => {
|
|
564
|
+
const obj = state.extMap[offset(i64ToF64(objBig))]
|
|
565
|
+
const prop = state.mem.read(i64ToF64(propBig))
|
|
566
|
+
const args = state.mem.read(i64ToF64(argsBig))
|
|
567
|
+
return f64ToI64(state.mem.wrapVal(obj[prop].apply(obj, args)))
|
|
458
568
|
}
|
|
459
569
|
return state
|
|
460
570
|
}
|
|
461
571
|
|
|
572
|
+
// Default JS-host wiring for env.print + env.now — auto-installed when the wasm
|
|
573
|
+
// imports them (host: 'js' mode lowering in module/console.js). Caller-provided
|
|
574
|
+
// opts.imports.env entries take precedence.
|
|
575
|
+
const installDefaultEnvImports = (mod, imports, state) => {
|
|
576
|
+
const envFns = new Set(WebAssembly.Module.imports(mod)
|
|
577
|
+
.filter(i => i.module === 'env' && i.kind === 'function').map(i => i.name))
|
|
578
|
+
if (!envFns.size) return
|
|
579
|
+
if (!imports.env) imports.env = {}
|
|
580
|
+
if (envFns.has('print') && !imports.env.print) {
|
|
581
|
+
const buf = ['', '', ''] // fd 0/1/2 line buffers
|
|
582
|
+
const pending = []
|
|
583
|
+
const flush = (fd) => {
|
|
584
|
+
const out = fd === 2 ? console.error : console.log
|
|
585
|
+
out(buf[fd])
|
|
586
|
+
buf[fd] = ''
|
|
587
|
+
}
|
|
588
|
+
// env.print's val param is i64 to dodge V8's f64 NaN canonicalization
|
|
589
|
+
// across the wasm→JS boundary (see module/console.js header). Reinterpret
|
|
590
|
+
// the BigInt's bits as f64 here so mem.read sees the original NaN-box.
|
|
591
|
+
const write = (valBig, fd, sep) => {
|
|
592
|
+
const v = state.mem.read(i64ToF64(valBig))
|
|
593
|
+
buf[fd] += String(v)
|
|
594
|
+
if (sep === 32) buf[fd] += ' '
|
|
595
|
+
else if (sep === 10) flush(fd)
|
|
596
|
+
}
|
|
597
|
+
imports.env.print = (val, fd, sep) => {
|
|
598
|
+
if (!state.mem) pending.push([val, fd, sep])
|
|
599
|
+
else write(val, fd, sep)
|
|
600
|
+
}
|
|
601
|
+
state.flushPrint = () => {
|
|
602
|
+
for (const args of pending) write(...args)
|
|
603
|
+
pending.length = 0
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (envFns.has('now') && !imports.env.now) {
|
|
607
|
+
imports.env.now = (clock) =>
|
|
608
|
+
clock === 1 ? (typeof performance !== 'undefined' ? performance.now() : Date.now()) : Date.now()
|
|
609
|
+
}
|
|
610
|
+
// host: 'js' timer wiring. Wasm calls env.setTimeout/clearTimeout; we drive
|
|
611
|
+
// callbacks back via the exported __invoke_closure trampoline (state.invoke).
|
|
612
|
+
// Each id maps to a cancel thunk so set/clear share state without tagging.
|
|
613
|
+
// env.setTimeout receives cbPtr as i64 bits (BigInt) — see module/timer.js;
|
|
614
|
+
// __invoke_closure also takes i64 now, so the BigInt feeds it directly.
|
|
615
|
+
if (envFns.has('setTimeout') || envFns.has('clearTimeout')) {
|
|
616
|
+
const cancel = new Map()
|
|
617
|
+
let nextId = 1
|
|
618
|
+
if (envFns.has('setTimeout') && !imports.env.setTimeout) imports.env.setTimeout = (cbBig, delayMs, repeat) => {
|
|
619
|
+
const id = nextId++
|
|
620
|
+
const fire = () => state.invoke?.(cbBig)
|
|
621
|
+
if (repeat) {
|
|
622
|
+
const h = setInterval(fire, delayMs)
|
|
623
|
+
cancel.set(id, () => clearInterval(h))
|
|
624
|
+
} else {
|
|
625
|
+
const h = setTimeout(() => { cancel.delete(id); fire() }, delayMs)
|
|
626
|
+
cancel.set(id, () => clearTimeout(h))
|
|
627
|
+
}
|
|
628
|
+
return id
|
|
629
|
+
}
|
|
630
|
+
if (envFns.has('clearTimeout') && !imports.env.clearTimeout) imports.env.clearTimeout = (id) => {
|
|
631
|
+
const c = cancel.get(id)
|
|
632
|
+
if (c) { c(); cancel.delete(id) }
|
|
633
|
+
return 0
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
462
638
|
const buildImports = (mod, opts, state) => {
|
|
463
639
|
const needsWasi = WebAssembly.Module.imports(mod).some(i => i.module === 'wasi_snapshot_preview1')
|
|
464
640
|
const imports = needsWasi ? wasi(opts) : {}
|
|
465
641
|
if (opts._interp) imports.env = { ...imports.env, ...opts._interp }
|
|
466
642
|
|
|
467
|
-
// Host imports: decode NaN-boxed args for JS and wrap JS returns back into jz
|
|
643
|
+
// Host imports: decode NaN-boxed args for JS and wrap JS returns back into jz
|
|
644
|
+
// values. Args/return ride i64 across the boundary (Step 2c) so V8 cannot
|
|
645
|
+
// canonicalize the NaN payload — convert BigInt↔f64 via reinterpret bits.
|
|
468
646
|
if (opts.imports) for (const [modName, fns] of Object.entries(opts.imports)) {
|
|
469
647
|
if (!imports[modName]) imports[modName] = {}
|
|
470
648
|
for (const name of Object.getOwnPropertyNames(fns)) {
|
|
@@ -472,26 +650,37 @@ const buildImports = (mod, opts, state) => {
|
|
|
472
650
|
const fn = typeof spec === 'function' ? spec : (spec && typeof spec === 'object' ? spec.fn : null)
|
|
473
651
|
if (typeof fn === 'function')
|
|
474
652
|
imports[modName][name] = (...args) => {
|
|
475
|
-
|
|
476
|
-
|
|
653
|
+
// i64 carrier: reinterpret BigInt bits → f64 NaN-box. Pure-scalar modules
|
|
654
|
+
// have no memory so skip mem.read; the f64 IS the JS number for numerics.
|
|
655
|
+
const decoded = args.map(a => {
|
|
656
|
+
const f = typeof a === 'bigint' ? i64ToF64(a) : a
|
|
657
|
+
return state.mem ? state.mem.read(f) : decode(f)
|
|
658
|
+
})
|
|
659
|
+
const ret = fn.call(fns, ...decoded)
|
|
660
|
+
return f64ToI64(state.mem ? state.mem.wrapVal(ret) : coerce(ret))
|
|
477
661
|
}
|
|
478
662
|
}
|
|
479
663
|
}
|
|
480
|
-
|
|
481
|
-
|
|
664
|
+
|
|
665
|
+
installDefaultEnvImports(mod, imports, state)
|
|
666
|
+
// Shared memory: normalize (auto-wrap raw Memory), pass as import.
|
|
667
|
+
// Numeric opts.memory is a compile-time page count shorthand, not an import.
|
|
668
|
+
if (opts.memory instanceof WebAssembly.Memory) {
|
|
482
669
|
// Auto-wrap raw WebAssembly.Memory → enhanced jz.memory
|
|
483
|
-
if (
|
|
670
|
+
if (!_enhanced.has(opts.memory)) opts.memory = memory(opts.memory)
|
|
484
671
|
if (!imports.env) imports.env = {}
|
|
485
|
-
imports.env.memory = opts.memory
|
|
672
|
+
imports.env.memory = opts.memory
|
|
486
673
|
}
|
|
487
|
-
// Auto-imported host globals: provide as WebAssembly.Global wrapping NaN-boxed
|
|
674
|
+
// Auto-imported host globals: provide as WebAssembly.Global wrapping NaN-boxed
|
|
675
|
+
// external refs. Carrier is i64 so the NaN payload survives V8's boundary
|
|
676
|
+
// canonicalization — wasm side reinterprets to f64 (see asF64 in src/ir.js).
|
|
488
677
|
for (const imp of WebAssembly.Module.imports(mod)) {
|
|
489
678
|
if (imp.kind === 'global' && imp.module === 'env') {
|
|
490
679
|
const host = globalThis[imp.name]
|
|
491
680
|
if (host !== undefined) {
|
|
492
681
|
if (!imports.env) imports.env = {}
|
|
493
682
|
let id = state.extMap.indexOf(host); if (id === -1) { id = state.extMap.length; state.extMap.push(host) }
|
|
494
|
-
imports.env[imp.name] = new WebAssembly.Global({ value: '
|
|
683
|
+
imports.env[imp.name] = new WebAssembly.Global({ value: 'i64', mutable: false }, f64ToI64(ptr(11, 0, id)))
|
|
495
684
|
}
|
|
496
685
|
}
|
|
497
686
|
}
|
|
@@ -501,6 +690,9 @@ const buildImports = (mod, opts, state) => {
|
|
|
501
690
|
const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
502
691
|
if (needsWasi) imports._setMemory(inst.exports.memory)
|
|
503
692
|
|
|
693
|
+
// Trampoline used by env.setTimeout/clearTimeout to fire scheduled closures.
|
|
694
|
+
state.invoke = inst.exports.__invoke_closure || null
|
|
695
|
+
|
|
504
696
|
// Drive WASM timer queue via JS scheduling (non-blocking)
|
|
505
697
|
if (inst.exports.__timer_tick) {
|
|
506
698
|
const tick = inst.exports.__timer_tick
|
|
@@ -512,11 +704,12 @@ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
|
512
704
|
}, 1)
|
|
513
705
|
}
|
|
514
706
|
|
|
515
|
-
// For shared memory, resolve memory from import; for own memory, from export
|
|
516
|
-
const rawMemory = opts.memory
|
|
707
|
+
// For shared memory, resolve memory from import; for own memory, from export.
|
|
708
|
+
const rawMemory = opts.memory instanceof WebAssembly.Memory ? opts.memory : inst.exports.memory
|
|
517
709
|
const memSrc = { module: mod, instance: inst, exports: { ...inst.exports, memory: rawMemory }, extMap: state.extMap }
|
|
518
710
|
const enhanced = memory(memSrc)
|
|
519
711
|
state.mem = enhanced
|
|
712
|
+
state.flushPrint?.()
|
|
520
713
|
return { exports: wrap(memSrc), memory: enhanced, instance: inst, module: mod }
|
|
521
714
|
}
|
|
522
715
|
|