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.
Files changed (73) hide show
  1. package/README.md +56 -9
  2. package/bench/README.md +121 -50
  3. package/bench/bench.svg +27 -27
  4. package/cli.js +3 -1
  5. package/dist/interop.js +1 -1
  6. package/dist/jz.js +7541 -6178
  7. package/index.js +165 -74
  8. package/interop.js +189 -17
  9. package/jzify/arguments.js +32 -1
  10. package/jzify/async.js +409 -0
  11. package/jzify/classes.js +136 -18
  12. package/jzify/generators.js +639 -0
  13. package/jzify/hoist-vars.js +73 -1
  14. package/jzify/index.js +123 -4
  15. package/jzify/names.js +1 -0
  16. package/jzify/transform.js +239 -1
  17. package/layout.js +48 -3
  18. package/module/array.js +318 -43
  19. package/module/atomics.js +144 -0
  20. package/module/collection.js +1429 -155
  21. package/module/core.js +431 -40
  22. package/module/date.js +142 -120
  23. package/module/fs.js +144 -0
  24. package/module/function.js +6 -3
  25. package/module/index.js +4 -1
  26. package/module/json.js +270 -49
  27. package/module/math.js +1032 -145
  28. package/module/number.js +532 -163
  29. package/module/object.js +353 -95
  30. package/module/regex.js +157 -7
  31. package/module/schema.js +100 -2
  32. package/module/string.js +428 -93
  33. package/module/typedarray.js +264 -72
  34. package/module/web.js +36 -0
  35. package/package.json +5 -5
  36. package/src/abi/string.js +82 -11
  37. package/src/ast.js +11 -5
  38. package/src/autoload.js +28 -1
  39. package/src/bridge.js +7 -2
  40. package/src/compile/analyze-scans.js +22 -7
  41. package/src/compile/analyze.js +136 -30
  42. package/src/compile/dyn-closure-tables.js +277 -0
  43. package/src/compile/emit-assign.js +281 -15
  44. package/src/compile/emit.js +1055 -70
  45. package/src/compile/index.js +277 -29
  46. package/src/compile/infer.js +42 -4
  47. package/src/compile/inplace-store.js +329 -0
  48. package/src/compile/loop-recurrence.js +167 -0
  49. package/src/compile/narrow.js +555 -18
  50. package/src/compile/peel-stencil.js +5 -0
  51. package/src/compile/plan/index.js +45 -3
  52. package/src/compile/plan/inline.js +8 -1
  53. package/src/compile/plan/literals.js +39 -0
  54. package/src/compile/plan/scope.js +430 -23
  55. package/src/compile/program-facts.js +732 -38
  56. package/src/ctx.js +64 -9
  57. package/src/helper-counters.js +7 -1
  58. package/src/ir.js +113 -5
  59. package/src/kind-traits.js +66 -3
  60. package/src/kind.js +84 -12
  61. package/src/op-policy.js +7 -4
  62. package/src/optimize/index.js +1179 -704
  63. package/src/optimize/recurse.js +2 -2
  64. package/src/optimize/vectorize.js +982 -67
  65. package/src/prepare/index.js +809 -67
  66. package/src/prepare/math-kernel.js +331 -0
  67. package/src/prepare/pre-eval.js +714 -0
  68. package/src/snapshot.js +194 -0
  69. package/src/type.js +1170 -56
  70. package/src/wat/assemble.js +403 -65
  71. package/src/wat/codegen.js +74 -14
  72. package/transform.js +113 -4
  73. 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().getInt32(HEAP.PTR_ADDR, true)
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
- const aligned = (getPtr() + 7) & ~7
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.getInt32(HEAP.PTR_ADDR, true) < HEAP.START) d.setInt32(HEAP.PTR_ADDR, HEAP.START, true)
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
- let alloc = wasmExports?._alloc || jsAlloc
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 (wasmExports?._alloc) { alloc = wasmExports._alloc; mem.alloc = alloc }
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 if (mem._extMap) return mem.External(obj)
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
- return typeof ret === 'bigint' && !(ie && ie.r) ? ret : mem.read(ret)
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
- return typeof ret === 'bigint' && !(ie && ie.r) ? ret : mem.read(ret)
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
- state.extMap[offset(objBig)][state.mem.read(propBig)] = state.mem.read(valBig)
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 bits(state.mem.wrapVal(obj[prop].apply(obj, args)))
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
- const fire = () => state.invoke?.(cbBig)
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
- const ret = fn.call(fns, ...decoded)
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
  /**
@@ -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
- decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
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]]])