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/interop.js
CHANGED
|
@@ -49,14 +49,25 @@ const envFuncNames = (mod) =>
|
|
|
49
49
|
// threads must share a pointer cell in linear memory). 8-byte aligned bump on
|
|
50
50
|
// the JS side; wasm `_alloc` takes over if exported.
|
|
51
51
|
|
|
52
|
+
// Every i32 heap address that crosses the wasm boundary — a `$__heap` Global's `.value`,
|
|
53
|
+
// or a DataView 32-bit read — comes back through JS as a SIGNED int32 (WebAssembly JS API
|
|
54
|
+
// spec: i32 is observable as ToInt32, range -2^31..2^31-1), regardless of what the address
|
|
55
|
+
// actually represents (offsets are conceptually unsigned, 0..4GiB). `>>> 0` reinterprets
|
|
56
|
+
// the bit pattern back to unsigned. Skipping this is harmless below 2 GiB (same value
|
|
57
|
+
// either way) and silently wrong past it — a "negative" address then poisons every
|
|
58
|
+
// downstream `+`/comparison, and a DataView write at a negative offset throws RangeError.
|
|
52
59
|
const makeJsAllocator = (mem, heapGlobal) => {
|
|
53
60
|
const dv = () => new DataView(mem.buffer)
|
|
54
|
-
const getPtr = heapGlobal ? () => heapGlobal.value : () => dv().
|
|
61
|
+
const getPtr = heapGlobal ? () => heapGlobal.value >>> 0 : () => dv().getUint32(HEAP.PTR_ADDR, true)
|
|
55
62
|
const setPtr = heapGlobal ? v => { heapGlobal.value = v } : v => dv().setInt32(HEAP.PTR_ADDR, v, true)
|
|
56
63
|
// Rewind target: the global's post-static-init value, else the fixed start.
|
|
57
|
-
const base = heapGlobal ? heapGlobal.value : HEAP.START
|
|
64
|
+
const base = heapGlobal ? (heapGlobal.value >>> 0) : HEAP.START
|
|
58
65
|
const alloc = (bytes) => {
|
|
59
|
-
|
|
66
|
+
// Align up to 8 without `& ~7` — a JS bitwise op ToInt32-truncates its RESULT too,
|
|
67
|
+
// so `(x + 7) & ~7` would re-introduce the same sign flip past 2 GiB even with a
|
|
68
|
+
// correctly-unsigned `getPtr()`. Plain arithmetic has no such ceiling.
|
|
69
|
+
const ptr = getPtr()
|
|
70
|
+
const aligned = ptr - (ptr % 8)
|
|
60
71
|
const next = aligned + bytes
|
|
61
72
|
if (next > mem.buffer.byteLength)
|
|
62
73
|
mem.grow(Math.ceil((next - mem.buffer.byteLength) / 65536))
|
|
@@ -69,7 +80,7 @@ const makeJsAllocator = (mem, heapGlobal) => {
|
|
|
69
80
|
const initHeapPtr = () => {
|
|
70
81
|
if (heapGlobal) return
|
|
71
82
|
const d = dv()
|
|
72
|
-
if (d.
|
|
83
|
+
if (d.getUint32(HEAP.PTR_ADDR, true) < HEAP.START) d.setInt32(HEAP.PTR_ADDR, HEAP.START, true)
|
|
73
84
|
}
|
|
74
85
|
return { alloc, reset, initHeapPtr }
|
|
75
86
|
}
|
|
@@ -241,7 +252,11 @@ export const memory = (src) => {
|
|
|
241
252
|
// Allocator scaffold: bumps the exported `$__heap` global (or memory[1020] for
|
|
242
253
|
// shared memory). Wasm `_alloc` takes over when exported; `_clear`/jsReset rewinds.
|
|
243
254
|
const { alloc: jsAlloc, reset: jsReset, initHeapPtr } = makeJsAllocator(mem, wasmExports?.__heap)
|
|
244
|
-
|
|
255
|
+
// `_alloc`'s i32 result crosses the wasm→JS boundary SIGNED (same ToInt32 rule as any
|
|
256
|
+
// other i32 — see makeJsAllocator's comment); `>>> 0` restores the true unsigned address
|
|
257
|
+
// once the heap grows past 2 GiB, matching jsAlloc's own already-unsigned return.
|
|
258
|
+
const wasmAlloc = wasmExports?._alloc && (bytes => wasmExports._alloc(bytes) >>> 0)
|
|
259
|
+
let alloc = wasmAlloc || jsAlloc
|
|
245
260
|
initHeapPtr()
|
|
246
261
|
|
|
247
262
|
// Write 16-byte header matching WASM `__alloc_hdr`:
|
|
@@ -282,7 +297,7 @@ export const memory = (src) => {
|
|
|
282
297
|
// If already enhanced, just update bindings (new module compiled into same memory)
|
|
283
298
|
if (_enhanced.has(mem)) {
|
|
284
299
|
mem.schemas = schemas
|
|
285
|
-
if (
|
|
300
|
+
if (wasmAlloc) { alloc = wasmAlloc; mem.alloc = alloc }
|
|
286
301
|
mem.reset = jsReset // post-init rewind — see the note at the first-enhance path
|
|
287
302
|
if (extMap) mem._extMap = extMap
|
|
288
303
|
return mem
|
|
@@ -355,6 +370,53 @@ export const memory = (src) => {
|
|
|
355
370
|
return ptr(11, 0, id)
|
|
356
371
|
}
|
|
357
372
|
|
|
373
|
+
// First-class jz HASH from a plain JS object — the schema-less marshal. Builds the
|
|
374
|
+
// kernel's exact open-addressed table ([seq<<32|hash:i64][key:f64][val:f64] × cap,
|
|
375
|
+
// home slot = hash & (cap-1), linear probe, len/cap header at -8/-4) so every
|
|
376
|
+
// wasm-side dyn op — reads, writes, NEW props, growth, delete, iteration — runs
|
|
377
|
+
// natively with stable identity. The External reflection path decodes/re-marshals
|
|
378
|
+
// per access, so nested container mutation (`params.P[i][j] = …`) lands on
|
|
379
|
+
// marshaling copies and silently vanishes — a params-bag must be a real hash.
|
|
380
|
+
// Hash twins of module/collection.js (clampHash / ssoMix / byteFnv) — MUST agree
|
|
381
|
+
// with __str_hash or wasm probes start at the wrong home slot and miss.
|
|
382
|
+
const clampHash = (h) => (h <= 1 ? (h + 2) | 0 : h)
|
|
383
|
+
const jzStrHash = (box) => {
|
|
384
|
+
const b = bits(box)
|
|
385
|
+
if ((b >> 32n) & BigInt(LAYOUT.SSO_BIT)) { // SSO: fixed-cost mix over payload
|
|
386
|
+
const lo = Number(b & 0xFFFFFFFFn) | 0
|
|
387
|
+
const hi = Number((b >> 32n) & 0x1FFFn) | 0
|
|
388
|
+
let h = Math.imul(hi ^ 0x9E3779B9, 0x85EBCA6B)
|
|
389
|
+
h = Math.imul(lo ^ h, 0xC2B2AE35)
|
|
390
|
+
h = (h ^ (h >>> 15)) | 0
|
|
391
|
+
return clampHash(h) >>> 0
|
|
392
|
+
}
|
|
393
|
+
const off = Number(b & 0xFFFFFFFFn), m = dv()
|
|
394
|
+
const len = m.getInt32(off - 4, true)
|
|
395
|
+
let h = 0x811c9dc5 | 0
|
|
396
|
+
for (let i = 0; i < len; i++) h = Math.imul(h ^ m.getUint8(off + i), 0x01000193) | 0
|
|
397
|
+
return clampHash(h) >>> 0
|
|
398
|
+
}
|
|
399
|
+
mem.Hash = function(obj) {
|
|
400
|
+
const entries = Object.entries(obj)
|
|
401
|
+
let cap = 8
|
|
402
|
+
while (entries.length * 4 >= cap * 3) cap <<= 1 // stay under the 75% grow trigger
|
|
403
|
+
const off = hdr(entries.length, cap, cap * 24)
|
|
404
|
+
// Stage every slot as i64 bits (empty = 0) — same NaN-canonicalization dodge as mem.Array.
|
|
405
|
+
const staged = new BigInt64Array(cap * 3)
|
|
406
|
+
entries.forEach(([k, v], seq) => {
|
|
407
|
+
const keyBox = mem.String(k)
|
|
408
|
+
const h = jzStrHash(keyBox)
|
|
409
|
+
let idx = h & (cap - 1)
|
|
410
|
+
while (staged[idx * 3] !== 0n) idx = (idx + 1) & (cap - 1)
|
|
411
|
+
staged[idx * 3] = (BigInt(seq) << 32n) | BigInt(h >>> 0)
|
|
412
|
+
staged[idx * 3 + 1] = bits(keyBox)
|
|
413
|
+
staged[idx * 3 + 2] = bits(mem.wrapVal(v))
|
|
414
|
+
})
|
|
415
|
+
const dst = new BigInt64Array(mem.buffer, off, cap * 3)
|
|
416
|
+
dst.set(staged)
|
|
417
|
+
return ptr(7, 0, off)
|
|
418
|
+
}
|
|
419
|
+
|
|
358
420
|
mem.Object = function(obj) {
|
|
359
421
|
const objKeys = Object.keys(obj)
|
|
360
422
|
const key = objKeys.join(',')
|
|
@@ -365,8 +427,7 @@ export const memory = (src) => {
|
|
|
365
427
|
(s.length === objKeys.length && objKeys.every(k => s.includes(k)) ? a.concat(i) : a), [])
|
|
366
428
|
if (matches.length === 1) sid = matches[0]
|
|
367
429
|
else if (matches.length > 1) throw Error(`Ambiguous schema for {${key}} — pass keys in schema order`)
|
|
368
|
-
else
|
|
369
|
-
else throw Error(`No schema for {${key}}`)
|
|
430
|
+
else return mem.Hash(obj) // no compiled schema: first-class hash (External loses nested-mutation identity)
|
|
370
431
|
}
|
|
371
432
|
const schema = schemas[sid], n = schema.length, raw = alloc(n * 8)
|
|
372
433
|
// Stage as i64 bits so V8 can't canonicalize NaN-payload pointers across
|
|
@@ -554,7 +615,7 @@ export const memory = (src) => {
|
|
|
554
615
|
* Wrap raw WASM exports with JS calling convention adaptation.
|
|
555
616
|
* Handles: undefined → sentinel NaN for defaults, rest-param array packing.
|
|
556
617
|
*/
|
|
557
|
-
export const wrap = (memSrc, inst) => {
|
|
618
|
+
export const wrap = (memSrc, inst, state) => {
|
|
558
619
|
const restFuncs = new Map()
|
|
559
620
|
const mod = inst ? memSrc : memSrc.module || memSrc
|
|
560
621
|
const realInst = inst || memSrc.instance || memSrc
|
|
@@ -595,6 +656,66 @@ export const wrap = (memSrc, inst) => {
|
|
|
595
656
|
catch { /* ignore */ }
|
|
596
657
|
}
|
|
597
658
|
const mem = memory(memSrc)
|
|
659
|
+
// Async boundary: a module compiled from async source exports __mt_drain /
|
|
660
|
+
// __p_state / __p_value (the jzify-injected runtime). Every export call ends
|
|
661
|
+
// the "turn" — the microtask queue drains — and a promise-shaped return
|
|
662
|
+
// adopts into a HOST Promise: settled ones immediately, pending ones (parked
|
|
663
|
+
// on a timer) settle from the after-tick sweep. Sync modules: finishRet is
|
|
664
|
+
// pass-through, zero overhead beyond one truthiness check.
|
|
665
|
+
const mtDrain = realInst.exports.__mt_drain
|
|
666
|
+
const pState = realInst.exports.__p_state
|
|
667
|
+
const pValue = realInst.exports.__p_value
|
|
668
|
+
const asyncMod = !!(mtDrain && pState && pValue)
|
|
669
|
+
const pending = []
|
|
670
|
+
// Match the raw ret's carrier to the reader's param lane (i64Exp filled
|
|
671
|
+
// below); a reader's own result may ride the i64 lane too — __p_state's
|
|
672
|
+
// NUMBER comes back as f64 bits (reinterpret), __p_value's BOX stays raw
|
|
673
|
+
// bits for mem.read.
|
|
674
|
+
const pcall = (fn, name, raw) => {
|
|
675
|
+
const lane = i64Exp.get(name)
|
|
676
|
+
return fn(typeof raw === 'bigint' ? (lane?.p?.has(0) ? raw : i64ToF64(raw)) : (lane?.p?.has(0) ? bits(raw) : raw))
|
|
677
|
+
}
|
|
678
|
+
const pStateOf = (raw) => {
|
|
679
|
+
const r = pcall(pState, '__p_state', raw)
|
|
680
|
+
return typeof r === 'bigint' ? i64ToF64(r) : r
|
|
681
|
+
}
|
|
682
|
+
const readSettled = (raw) => {
|
|
683
|
+
const v = pcall(pValue, '__p_value', raw)
|
|
684
|
+
return mem ? mem.read(v) : decode(v)
|
|
685
|
+
}
|
|
686
|
+
const sweep = () => {
|
|
687
|
+
mtDrain()
|
|
688
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
689
|
+
const e = pending[i]
|
|
690
|
+
const st = pStateOf(e.raw)
|
|
691
|
+
if (st < 1) continue
|
|
692
|
+
pending.splice(i, 1)
|
|
693
|
+
st === 1 ? e.resolve(readSettled(e.raw)) : e.reject(readSettled(e.raw))
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const adopt = (raw, read) => {
|
|
697
|
+
mtDrain()
|
|
698
|
+
const st = pStateOf(raw)
|
|
699
|
+
if (st < 0) return read(raw) // not a promise — plain value
|
|
700
|
+
if (st === 1) return Promise.resolve(readSettled(raw))
|
|
701
|
+
if (st === 2) return Promise.reject(readSettled(raw))
|
|
702
|
+
return new Promise((resolve, reject) => pending.push({ raw, resolve, reject }))
|
|
703
|
+
}
|
|
704
|
+
if (asyncMod && state) {
|
|
705
|
+
state.afterTick = sweep
|
|
706
|
+
// Async host imports: a thenable returned by a host import becomes a jz
|
|
707
|
+
// promise (made + settled through the runtime's exports, lane-matched).
|
|
708
|
+
const mk = realInst.exports.__p_make, fin = realInst.exports.__p_finish
|
|
709
|
+
if (mk && fin) {
|
|
710
|
+
const lanes = i64Exp.get('__p_finish')
|
|
711
|
+
state.pmake = () => mk()
|
|
712
|
+
state.pfinish = (praw, st, vbits) => fin(
|
|
713
|
+
lanes?.p?.has(0) ? (typeof praw === 'bigint' ? praw : bits(praw)) : (typeof praw === 'bigint' ? i64ToF64(praw) : praw),
|
|
714
|
+
lanes?.p?.has(1) ? f64ToI64(st) : st,
|
|
715
|
+
lanes?.p?.has(2) ? vbits : i64ToF64(vbits))
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const finishRet = (raw, read) => asyncMod ? adopt(raw, read) : read(raw)
|
|
598
719
|
const lastErrBits = realInst.exports.__jz_last_err_bits
|
|
599
720
|
const decodeThrown = error => {
|
|
600
721
|
if (!(error instanceof WebAssembly.Exception) || !lastErrBits) throw error
|
|
@@ -669,7 +790,8 @@ export const wrap = (memSrc, inst) => {
|
|
|
669
790
|
a.push(ie && ie.p.has(fixed) ? restArr : i64ToF64(restArr))
|
|
670
791
|
try {
|
|
671
792
|
const ret = fn.apply(null, a)
|
|
672
|
-
|
|
793
|
+
if (typeof ret === 'bigint' && !(ie && ie.r)) return ret
|
|
794
|
+
return finishRet(ret, r => mem.read(r))
|
|
673
795
|
} catch (error) {
|
|
674
796
|
decodeThrown(error)
|
|
675
797
|
}
|
|
@@ -682,7 +804,8 @@ export const wrap = (memSrc, inst) => {
|
|
|
682
804
|
while (args.length < len) args.push(undefined)
|
|
683
805
|
try {
|
|
684
806
|
const ret = fn.apply(null, args.map(i64Arg(ie, ext, memWrapVal)))
|
|
685
|
-
|
|
807
|
+
if (typeof ret === 'bigint' && !(ie && ie.r)) return ret
|
|
808
|
+
return finishRet(ret, r => mem.read(r))
|
|
686
809
|
} catch (error) {
|
|
687
810
|
decodeThrown(error)
|
|
688
811
|
}
|
|
@@ -694,6 +817,26 @@ export const wrap = (memSrc, inst) => {
|
|
|
694
817
|
return exports
|
|
695
818
|
}
|
|
696
819
|
|
|
820
|
+
// Host-call return marshalling shared by opts.imports wrappers, __ext_call and
|
|
821
|
+
// the auto-wired web globals: a thenable becomes a jz promise (awaitable in
|
|
822
|
+
// the module — settled via the async runtime's exports, then drain + sweep);
|
|
823
|
+
// everything else boxes through wrapVal.
|
|
824
|
+
const hostRet = (state, ret) => {
|
|
825
|
+
if (ret != null && typeof ret.then === 'function' && state.pmake) {
|
|
826
|
+
const praw = state.pmake()
|
|
827
|
+
const box = (v) => bits(state.mem ? state.mem.wrapVal(v) : coerce(v))
|
|
828
|
+
ret.then(
|
|
829
|
+
(v) => { state.pfinish(praw, 1, box(v)); state.afterTick?.() },
|
|
830
|
+
(e) => { state.pfinish(praw, 2, box(e instanceof Error ? e.message : e)); state.afterTick?.() })
|
|
831
|
+
return typeof praw === 'bigint' ? praw : bits(praw)
|
|
832
|
+
}
|
|
833
|
+
return bits(state.mem ? state.mem.wrapVal(ret) : coerce(ret))
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Callable web globals auto-wired from globalThis when the module imports them
|
|
837
|
+
// (module/web.js lowers bare `fetch(...)` etc. to env imports under host:'js').
|
|
838
|
+
const WEB_GLOBALS = new Set(['fetch'])
|
|
839
|
+
|
|
697
840
|
const prepareInterop = (opts) => {
|
|
698
841
|
const state = { extMap: [null], mem: null }
|
|
699
842
|
opts._interp = opts._interp || {}
|
|
@@ -710,14 +853,28 @@ const prepareInterop = (opts) => {
|
|
|
710
853
|
return (state.mem.read(propBig) in state.extMap[offset(objBig)]) ? 1 : 0
|
|
711
854
|
}
|
|
712
855
|
opts._interp.__ext_set = (objBig, propBig, valBig) => {
|
|
713
|
-
|
|
856
|
+
let v = state.mem.read(valBig)
|
|
857
|
+
// A TYPED value decodes to a LIVE VIEW into wasm's own linear memory
|
|
858
|
+
// (mem.read's t===3 branch: `new Ctor(mem.buffer, off, len)`) — sound for
|
|
859
|
+
// a value read-and-immediately-consumed inside one host call, but a host
|
|
860
|
+
// OBJECT PROPERTY is real, persistent JS state: any later Memory.grow()
|
|
861
|
+
// (the bump allocator never frees, so any sufficiently long-running
|
|
862
|
+
// program eventually grows) detaches/reallocates `mem.buffer`, silently
|
|
863
|
+
// invalidating every such view still held on the host side — the next
|
|
864
|
+
// access throws "detached ArrayBuffer" (or, for a stale non-typed read,
|
|
865
|
+
// would silently read zeros). A host object is host-owned persistent
|
|
866
|
+
// state: store an independent copy. `.slice()` is TypedArray's native
|
|
867
|
+
// same-ctor copy — exactly `new Ctor(view)` with no manual size/offset
|
|
868
|
+
// bookkeeping — and a no-op for every other decoded value shape.
|
|
869
|
+
if (ArrayBuffer.isView(v)) v = v.slice()
|
|
870
|
+
state.extMap[offset(objBig)][state.mem.read(propBig)] = v
|
|
714
871
|
return 1
|
|
715
872
|
}
|
|
716
873
|
opts._interp.__ext_call = (objBig, propBig, argsBig) => {
|
|
717
874
|
const obj = state.extMap[offset(objBig)]
|
|
718
875
|
const prop = state.mem.read(propBig)
|
|
719
876
|
const args = state.mem.read(argsBig)
|
|
720
|
-
return
|
|
877
|
+
return hostRet(state, obj[prop].apply(obj, args))
|
|
721
878
|
}
|
|
722
879
|
return state
|
|
723
880
|
}
|
|
@@ -729,6 +886,15 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
729
886
|
const envFns = envFuncNames(mod)
|
|
730
887
|
if (!envFns.size) return
|
|
731
888
|
if (!imports.env) imports.env = {}
|
|
889
|
+
// Web globals (fetch, …): the module imported them because bare calls were
|
|
890
|
+
// lowered by module/web.js — bind from globalThis with full marshalling;
|
|
891
|
+
// thenables adopt into jz promises. opts.imports.env overrides win.
|
|
892
|
+
for (const name of envFns) {
|
|
893
|
+
if (imports.env[name] || !WEB_GLOBALS.has(name)) continue
|
|
894
|
+
const host = globalThis[name]
|
|
895
|
+
if (typeof host !== 'function') continue
|
|
896
|
+
imports.env[name] = (...args) => hostRet(state, host(...args.map(a => state.mem ? state.mem.read(a) : decode(a))))
|
|
897
|
+
}
|
|
732
898
|
if (envFns.has('print') && !imports.env.print) {
|
|
733
899
|
const buf = ['', '', ''] // fd 0/1/2 line buffers
|
|
734
900
|
const pending = []
|
|
@@ -791,7 +957,9 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
791
957
|
let nextId = 1
|
|
792
958
|
if (envFns.has('setTimeout') && !imports.env.setTimeout) imports.env.setTimeout = (cbBig, delayMs, repeat) => {
|
|
793
959
|
const id = nextId++
|
|
794
|
-
|
|
960
|
+
// after each timer callback: drain microtasks + settle host promises
|
|
961
|
+
// parked on async exports (state.afterTick set by wrap for async modules)
|
|
962
|
+
const fire = () => { state.invoke?.(cbBig); state.afterTick?.() }
|
|
795
963
|
if (repeat) {
|
|
796
964
|
const h = setInterval(fire, delayMs)
|
|
797
965
|
cancel.set(id, () => clearInterval(h))
|
|
@@ -877,8 +1045,7 @@ const buildImports = (mod, opts, state) => {
|
|
|
877
1045
|
// i64 carrier: args arrive as BigInt bits (box) or number; decode with integer
|
|
878
1046
|
// ops — never materialize a box as f64. Return the i64 bits of the wrapped result.
|
|
879
1047
|
const decoded = args.map(a => state.mem ? state.mem.read(a) : decode(a))
|
|
880
|
-
|
|
881
|
-
return bits(state.mem ? state.mem.wrapVal(ret) : coerce(ret))
|
|
1048
|
+
return hostRet(state, fn.call(fns, ...decoded))
|
|
882
1049
|
}
|
|
883
1050
|
}
|
|
884
1051
|
}
|
|
@@ -910,6 +1077,11 @@ const buildImports = (mod, opts, state) => {
|
|
|
910
1077
|
|
|
911
1078
|
const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
912
1079
|
if (needsWasi) imports._setMemory(inst.exports.memory)
|
|
1080
|
+
// WASI reactor convention: a `host: 'wasi'` module ships its init as the standard
|
|
1081
|
+
// `_initialize` export (never a wasm start section — WASI calls there would fire
|
|
1082
|
+
// before _setMemory above). Called for ANY module exporting it, imports or not:
|
|
1083
|
+
// a hostless wasi module (no console/Date use) still needs its init run.
|
|
1084
|
+
inst.exports._initialize?.()
|
|
913
1085
|
|
|
914
1086
|
// Trampoline used by env.setTimeout/clearTimeout to fire scheduled closures.
|
|
915
1087
|
state.invoke = inst.exports.__invoke_closure || null
|
|
@@ -927,7 +1099,7 @@ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
|
927
1099
|
// its SSO/atom boundary values), but the result's `.memory` stays null — the
|
|
928
1100
|
// module genuinely exposes no linear memory. `jz.memory(result)` still hands back
|
|
929
1101
|
// a usable reader on demand.
|
|
930
|
-
return { exports: wrap(memSrc), memory: enhanced?.scalar ? null : enhanced, instance: inst, module: mod }
|
|
1102
|
+
return { exports: wrap(memSrc, undefined, state), memory: enhanced?.scalar ? null : enhanced, instance: inst, module: mod }
|
|
931
1103
|
}
|
|
932
1104
|
|
|
933
1105
|
/**
|
package/jzify/arguments.js
CHANGED
|
@@ -19,6 +19,24 @@ function usesArguments(node) {
|
|
|
19
19
|
return false
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// A parameter literally named `arguments` (or a destructure / default / rest that
|
|
23
|
+
// binds it) SHADOWS the arguments object: every `arguments` in the body resolves to
|
|
24
|
+
// that parameter, not the args array. So the args-object lowering must not fire —
|
|
25
|
+
// `function f(arguments){ return arguments }` is just a one-param function, and `f()`
|
|
26
|
+
// returns undefined (not the `[]` args object). Mirrors the spec's param-scope
|
|
27
|
+
// shadowing of the implicit `arguments` binding (test262 13_A15_T3).
|
|
28
|
+
function paramsBindArguments(params) {
|
|
29
|
+
const bound = (p) => {
|
|
30
|
+
if (p === 'arguments') return true
|
|
31
|
+
if (!Array.isArray(p)) return false
|
|
32
|
+
if (p[0] === '=' || p[0] === '...') return bound(p[1])
|
|
33
|
+
if (p[0] === '[]' || p[0] === '{}' || p[0] === ',') return p.slice(1).some(bound)
|
|
34
|
+
if (p[0] === ':') return bound(p[2])
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
return paramList(params).some(bound)
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
function bindsArguments(body) {
|
|
23
41
|
const isArgDecl = s => Array.isArray(s) && (s[0] === 'var' || s[0] === 'let' || s[0] === 'const') &&
|
|
24
42
|
s.slice(1).some(d => d === 'arguments' || (Array.isArray(d) && d[0] === '=' && d[1] === 'arguments'))
|
|
@@ -56,6 +74,15 @@ function prependParamDecls(decl, body) {
|
|
|
56
74
|
/** @param {ReturnType<import('./names.js').createNames>} names */
|
|
57
75
|
export function createArgumentsLowering(names) {
|
|
58
76
|
function lowerArguments(params, body) {
|
|
77
|
+
// A param named `arguments` shadows the implicit args object: rename it (and its
|
|
78
|
+
// in-scope body refs — renameArguments stops at nested `function`s, and arrows
|
|
79
|
+
// legitimately inherit the param) to a fresh binding, so it becomes an ordinary
|
|
80
|
+
// parameter. No args-object lowering fires, and the strict-subset
|
|
81
|
+
// `arguments`-unsupported guard never sees the name. (test262 13_A15_T3.)
|
|
82
|
+
if (paramsBindArguments(params)) {
|
|
83
|
+
const fresh = names.arg()
|
|
84
|
+
return lowerArguments(renameArguments(params, fresh), renameArguments(body, fresh))
|
|
85
|
+
}
|
|
59
86
|
if (bindsArguments(body)) body = renameArguments(body, names.arg())
|
|
60
87
|
const paramsNeedLowering = paramList(params).some(isDestructurePat)
|
|
61
88
|
const usesArgsObj = usesArguments(params) || usesArguments(body)
|
|
@@ -68,7 +95,11 @@ export function createArgumentsLowering(names) {
|
|
|
68
95
|
continue
|
|
69
96
|
}
|
|
70
97
|
if (Array.isArray(param) && param[0] === '=') {
|
|
71
|
-
|
|
98
|
+
// Param default fires ONLY on undefined, not null — `??` would take the
|
|
99
|
+
// default for a passed null. The rest-array index read is idempotent
|
|
100
|
+
// and pure, so the ternary re-reads instead of spilling a temp.
|
|
101
|
+
const read = ['[]', name, [null, idx]]
|
|
102
|
+
decls.push(['=', param[1], ['?:', ['===', read, 'undefined'], renameArguments(param[2], name), read]])
|
|
72
103
|
continue
|
|
73
104
|
}
|
|
74
105
|
decls.push(['=', param, ['[]', name, [null, idx]]])
|