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/module/array.js CHANGED
@@ -8,11 +8,10 @@
8
8
  * @module array
9
9
  */
10
10
 
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'
11
+ import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr, multiCount, arrayLoop, elemLoad, elemStore, truthyIR, extractF64Bits, appendStaticSlots, mkPtrIR, slotAddr, isLiteralStr, resolveValType, undefExpr } from '../src/ir.js'
12
12
  import { emit, materializeMulti } from '../src/emit.js'
13
- import { valTypeOf, lookupValType, VAL, extractParams, updateRep } from '../src/analyze.js'
14
- import { ctx, inc, err, PTR } from '../src/ctx.js'
15
- import { staticPropertyKey } from '../src/key.js'
13
+ import { valTypeOf, lookupValType, VAL, extractParams, updateRep, staticPropertyKey } from '../src/analyze.js'
14
+ import { ctx, inc, err, PTR, LAYOUT } from '../src/ctx.js'
16
15
  import { strHashLiteral } from './collection.js'
17
16
 
18
17
 
@@ -43,6 +42,8 @@ function hoistArrayValue(arr) {
43
42
  }
44
43
  }
45
44
 
45
+ const arrayLenFromPtr = ptr => ['i32.load', ['i32.sub', ['local.get', `$${ptr}`], ['i32.const', 8]]]
46
+
46
47
  // Pure-expression check: no statements, binders, control flow, or assignments.
47
48
  // Inlining is only safe for these — anything else needs the full closure machinery.
48
49
  const NOT_PURE_OPS = new Set([
@@ -179,10 +180,42 @@ const arrayGrowDeps = (knownArray = false) => () => [
179
180
  ...(needsArrayDynMove() ? ['__dyn_move'] : []),
180
181
  ]
181
182
 
183
+ // Arrays keep dynamic props in the global table because old/new array storage can
184
+ // be forwarded. Relocate that entry when growth moves the backing store.
182
185
  const maybeDynMoveIR = () => needsArrayDynMove()
183
186
  ? '(call $__dyn_move (local.get $off) (local.get $newOff))'
184
187
  : ''
185
188
 
189
+ // Per-object propsPtr lives in the 16-byte header at $off-16. On grow we copy it
190
+ // from old to new header (still HASH-tagged → unshifted ARRAY case). On shift we
191
+ // migrate it to the global __dyn_props because the forwarding writes overwrite
192
+ // the destination's $newOff-16 slot. The HASH-tag check rejects 0 (no props) and
193
+ // forwarding garbage from a prior shift, so chained shift→grow is safe — the
194
+ // global hash takes over and __dyn_move keeps it in sync.
195
+ const headerPropsCopyIR = () => needsArrayDynMove() ? `
196
+ (local.set $oldProps (f64.load (i32.sub (local.get $off) (i32.const 16))))
197
+ (if (i32.eq
198
+ (i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
199
+ (i32.const ${PTR.HASH}))
200
+ (then (f64.store (i32.sub (local.get $newOff) (i32.const 16)) (local.get $oldProps))))` : ''
201
+
202
+ const headerPropsToGlobalIR = () => needsArrayDynMove() ? `
203
+ (if (i32.ge_u (local.get $off) (i32.const 16))
204
+ (then
205
+ (local.set $oldProps (f64.load (i32.sub (local.get $off) (i32.const 16))))
206
+ (if (i32.eq
207
+ (i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
208
+ (i32.const ${PTR.HASH}))
209
+ (then
210
+ (local.set $root (global.get $__dyn_props))
211
+ (if (f64.eq (local.get $root) (f64.const 0))
212
+ (then (local.set $root (call $__hash_new))))
213
+ (local.set $root (f64.reinterpret_i64 (call $__ihash_set_local
214
+ (i64.reinterpret_f64 (local.get $root))
215
+ (i64.reinterpret_f64 (f64.convert_i32_s (local.get $newOff)))
216
+ (i64.reinterpret_f64 (local.get $oldProps)))))
217
+ (global.set $__dyn_props (local.get $root)))))) ` : ''
218
+
186
219
  export default (ctx) => {
187
220
  Object.assign(ctx.core.stdlibDeps, {
188
221
  __arr_idx: [],
@@ -190,7 +223,7 @@ export default (ctx) => {
190
223
  __arr_grow_known: arrayGrowDeps(true),
191
224
  __arr_shift: () => [
192
225
  '__ptr_offset',
193
- ...(needsArrayDynMove() ? ['__dyn_move'] : []),
226
+ ...(needsArrayDynMove() ? ['__dyn_move', '__hash_new', '__ihash_set_local'] : []),
194
227
  ],
195
228
  __arr_set_idx_ptr: ['__arr_grow', '__ptr_offset', '__set_len'],
196
229
  __typed_idx: () => ctx.features.typedarray || ctx.features.external
@@ -211,21 +244,20 @@ export default (ctx) => {
211
244
  const t = temp('t')
212
245
  return typed(['i32.and',
213
246
  ['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
214
- ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${t}`]], ['i32.const', PTR.ARRAY]]], 'i32')
247
+ ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.ARRAY]]], 'i32')
215
248
  }
216
249
 
217
250
  // ARRAY-only indexed read. Inline forwarding-follow + bounds check + load — avoids
218
251
  // the redundant double pass through __len then __ptr_offset that both follow forwarding.
219
- ctx.core.stdlib['__arr_idx'] = `(func $__arr_idx (param $ptr f64) (param $i i32) (result f64)
220
- (local $bits i64) (local $off i32)
221
- (local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
252
+ ctx.core.stdlib['__arr_idx'] = `(func $__arr_idx (param $ptr i64) (param $i i32) (result f64)
253
+ (local $off i32)
222
254
  (if (result f64)
223
255
  (i32.ne
224
- (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
256
+ (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
225
257
  (i32.const ${PTR.ARRAY}))
226
258
  (then (f64.const nan:${UNDEF_NAN}))
227
259
  (else
228
- (local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
260
+ (local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
229
261
  (block $done
230
262
  (loop $follow
231
263
  (br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
@@ -242,13 +274,32 @@ export default (ctx) => {
242
274
  (then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
243
275
  (else (f64.const nan:${UNDEF_NAN})))))) `
244
276
 
277
+ ctx.core.stdlib['__arr_idx_known'] = `(func $__arr_idx_known (param $ptr i64) (param $i i32) (result f64)
278
+ (local $off i32)
279
+ (local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
280
+ (block $done
281
+ (loop $follow
282
+ (br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
283
+ (br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
284
+ (br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
285
+ (local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
286
+ (br $follow)))
287
+ (if (result f64)
288
+ (i32.and
289
+ (i32.ge_u (local.get $off) (i32.const 8))
290
+ (i32.and
291
+ (i32.ge_s (local.get $i) (i32.const 0))
292
+ (i32.lt_u (local.get $i) (i32.load (i32.sub (local.get $off) (i32.const 8))))))
293
+ (then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
294
+ (else (f64.const nan:${UNDEF_NAN}))))`
295
+
245
296
  // Runtime-dispatch index: element-type aware load with bounds check + view indirection.
246
297
  // Full body handles TYPED element types and view indirection since external host can
247
298
  // pass typed arrays even when typedarray module isn't loaded. When features.typedarray
248
299
  // and features.external are both off, collapses to ARRAY-only f64 indexing.
249
300
  ctx.core.stdlib['__typed_idx'] = () => {
250
301
  if (!ctx.features.typedarray && !ctx.features.external) {
251
- return `(func $__typed_idx (param $ptr f64) (param $i i32) (result f64)
302
+ return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
252
303
  (local $len i32)
253
304
  (local.set $len (call $__len (local.get $ptr)))
254
305
  (if (result f64)
@@ -258,13 +309,12 @@ export default (ctx) => {
258
309
  (then (f64.const nan:${UNDEF_NAN}))
259
310
  (else (f64.load (i32.add (call $__ptr_offset (local.get $ptr)) (i32.shl (local.get $i) (i32.const 3)))))))`
260
311
  }
261
- // Hot (~37M calls in watr self-host). Type/aux/offset extracted once from $bits.
262
- return `(func $__typed_idx (param $ptr f64) (param $i i32) (result f64)
263
- (local $bits i64) (local $t i32) (local $off i32) (local $et i32) (local $len i32) (local $aux i32)
264
- (local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
265
- (local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF))))
266
- (local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 32)) (i64.const 0x7FFF))))
267
- (local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
312
+ // Hot (~37M calls in watr self-host). Type/aux/offset extracted once from $ptr.
313
+ return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
314
+ (local $t i32) (local $off i32) (local $et i32) (local $len i32) (local $aux i32)
315
+ (local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
316
+ (local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
317
+ (local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
268
318
  (if
269
319
  (i32.and
270
320
  (i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
@@ -282,7 +332,9 @@ export default (ctx) => {
282
332
  (local.set $et (i32.and (local.get $aux) (i32.const 7)))
283
333
  (if (result f64) (i32.ge_u (local.get $et) (i32.const 6))
284
334
  (then (if (result f64) (i32.eq (local.get $et) (i32.const 7))
285
- (then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
335
+ (then (if (result f64) (i32.and (local.get $aux) (i32.const 16))
336
+ (then (f64.reinterpret_i64 (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
337
+ (else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))
286
338
  (else (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
287
339
  (else (if (result f64) (i32.ge_u (local.get $et) (i32.const 4))
288
340
  (then (if (result f64) (i32.and (local.get $et) (i32.const 1))
@@ -299,23 +351,56 @@ export default (ctx) => {
299
351
  }
300
352
 
301
353
  // Array.from(src) — shallow copy of array (memory.copy of f64 elements)
302
- ctx.core.stdlib['__arr_from'] = `(func $__arr_from (param $src f64) (result f64)
354
+ ctx.core.stdlib['__arr_from'] = `(func $__arr_from (param $src i64) (result f64)
303
355
  (local $len i32) (local $dst i32)
304
356
  (local.set $len (call $__len (local.get $src)))
305
357
  (local.set $dst (call $__alloc_hdr (local.get $len) (local.get $len) (i32.const 8)))
306
358
  (memory.copy (local.get $dst) (call $__ptr_offset (local.get $src)) (i32.shl (local.get $len) (i32.const 3)))
307
359
  (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $dst)))`
308
360
 
309
- ctx.core.emit['Array.from'] = (src) => {
361
+ function arrayLikeLength(src) {
362
+ if (!Array.isArray(src) || src[0] !== '{}') return null
363
+ for (let i = 1; i < src.length; i++) {
364
+ const prop = src[i]
365
+ if (!Array.isArray(prop) || prop[0] !== ':') continue
366
+ const key = typeof prop[1] === 'string' ? prop[1] : staticPropertyKey(prop[1])
367
+ if (key === 'length') return prop[2]
368
+ }
369
+ return null
370
+ }
371
+
372
+ ctx.core.emit['Array.from'] = (src, mapFn) => {
373
+ const lengthExpr = arrayLikeLength(src)
374
+ if (lengthExpr) {
375
+ const len = tempI32('fl'), i = tempI32('fi')
376
+ const lenIR = ['local.get', `$${len}`]
377
+ const out = allocPtr({ type: PTR.ARRAY, len: lenIR, tag: 'fr' })
378
+ const cb = mapFn && makeCallback(mapFn, [null, { val: VAL.NUMBER }])
379
+ const undef = typed(['f64.reinterpret_i64', ['i64.const', UNDEF_NAN]], 'f64')
380
+ const item = cb ? cb.call([undef, idxArg(cb, i)]) : undef
381
+ const id = ctx.func.uniq++
382
+ return typed(['block', ['result', 'f64'],
383
+ ['local.set', `$${len}`, asI32(emit(lengthExpr))],
384
+ out.init,
385
+ ...(cb ? [cb.setup] : []),
386
+ ['local.set', `$${i}`, ['i32.const', 0]],
387
+ ['block', `$brk${id}`, ['loop', `$loop${id}`,
388
+ ['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], lenIR]],
389
+ elemStore(out.local, i, asF64(item)),
390
+ ['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
391
+ ['br', `$loop${id}`]]],
392
+ out.ptr], 'f64')
393
+ }
310
394
  inc('__arr_from')
311
- return typed(['call', '$__arr_from', asF64(emit(src))], 'f64')
395
+ return typed(['call', '$__arr_from', asI64(emit(src))], 'f64')
312
396
  }
313
397
 
314
398
  // Grow array if capacity insufficient. Returns (possibly new) NaN-boxed pointer.
315
399
  // Old storage is left behind as a forwarding header so existing aliases keep
316
400
  // seeing the current backing store after growth.
317
- ctx.core.stdlib['__arr_grow'] = () => `(func $__arr_grow (param $ptr f64) (param $minCap i32) (result f64)
401
+ ctx.core.stdlib['__arr_grow'] = () => `(func $__arr_grow (param $ptr i64) (param $minCap i32) (result f64)
318
402
  (local $t i32) (local $off i32) (local $oldCap i32) (local $newCap i32) (local $newOff i32) (local $len i32)
403
+ ${needsArrayDynMove() ? '(local $oldProps f64)' : ''}
319
404
  (local.set $t (call $__ptr_type (local.get $ptr)))
320
405
  (local.set $off (call $__ptr_offset (local.get $ptr)))
321
406
  ;; Defensive path: invalid/non-array pointer -> create fresh array buffer.
@@ -329,7 +414,7 @@ export default (ctx) => {
329
414
  (return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))))
330
415
  (local.set $oldCap (i32.load (i32.sub (local.get $off) (i32.const 4))))
331
416
  (if (i32.ge_s (local.get $oldCap) (local.get $minCap))
332
- (then (return (local.get $ptr))))
417
+ (then (return (f64.reinterpret_i64 (local.get $ptr)))))
333
418
  (local.set $newCap (select
334
419
  (local.get $minCap)
335
420
  (i32.shl (local.get $oldCap) (i32.const 1))
@@ -337,17 +422,19 @@ export default (ctx) => {
337
422
  (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
338
423
  (local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap) (i32.const 8)))
339
424
  (memory.copy (local.get $newOff) (local.get $off) (i32.shl (local.get $len) (i32.const 3)))
425
+ ${headerPropsCopyIR()}
340
426
  ${maybeDynMoveIR()}
341
427
  (i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newOff))
342
428
  (i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
343
429
  (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))`
344
430
 
345
- ctx.core.stdlib['__arr_grow_known'] = () => `(func $__arr_grow_known (param $ptr f64) (param $minCap i32) (result f64)
431
+ ctx.core.stdlib['__arr_grow_known'] = () => `(func $__arr_grow_known (param $ptr i64) (param $minCap i32) (result f64)
346
432
  (local $off i32) (local $oldCap i32) (local $newCap i32) (local $newOff i32) (local $len i32)
433
+ ${needsArrayDynMove() ? '(local $oldProps f64)' : ''}
347
434
  (local.set $off (call $__ptr_offset (local.get $ptr)))
348
435
  (local.set $oldCap (i32.load (i32.sub (local.get $off) (i32.const 4))))
349
436
  (if (i32.ge_s (local.get $oldCap) (local.get $minCap))
350
- (then (return (local.get $ptr))))
437
+ (then (return (f64.reinterpret_i64 (local.get $ptr)))))
351
438
  (local.set $newCap (select
352
439
  (local.get $minCap)
353
440
  (i32.shl (local.get $oldCap) (i32.const 1))
@@ -355,6 +442,7 @@ export default (ctx) => {
355
442
  (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
356
443
  (local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap) (i32.const 8)))
357
444
  (memory.copy (local.get $newOff) (local.get $off) (i32.shl (local.get $len) (i32.const 3)))
445
+ ${headerPropsCopyIR()}
358
446
  ${maybeDynMoveIR()}
359
447
  (i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newOff))
360
448
  (i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
@@ -363,21 +451,22 @@ export default (ctx) => {
363
451
  // Hot for arr[i] = val (~18M calls in watr self-host). Compute base via __ptr_offset
364
452
  // once and read len from the inline header (i32.load base-8) — avoids __len's separate
365
453
  // forwarding follow. On the rare grow path the base is recomputed after relocation.
366
- ctx.core.stdlib['__arr_set_idx_ptr'] = `(func $__arr_set_idx_ptr (param $ptr f64) (param $i i32) (param $val f64) (result f64)
367
- (local $base i32)
454
+ ctx.core.stdlib['__arr_set_idx_ptr'] = `(func $__arr_set_idx_ptr (param $ptr i64) (param $i i32) (param $val f64) (result f64)
455
+ (local $base i32) (local $p f64)
456
+ (local.set $p (f64.reinterpret_i64 (local.get $ptr)))
368
457
  (if (i32.lt_s (local.get $i) (i32.const 0))
369
- (then (return (local.get $ptr))))
458
+ (then (return (local.get $p))))
370
459
  (local.set $base (call $__ptr_offset (local.get $ptr)))
371
460
  (if (i32.ge_u (local.get $i)
372
461
  (i32.load (i32.sub (local.get $base) (i32.const 8))))
373
462
  (then
374
- (local.set $ptr (call $__arr_grow (local.get $ptr) (i32.add (local.get $i) (i32.const 1))))
375
- (call $__set_len (local.get $ptr) (i32.add (local.get $i) (i32.const 1)))
376
- (local.set $base (call $__ptr_offset (local.get $ptr)))))
463
+ (local.set $p (call $__arr_grow (local.get $ptr) (i32.add (local.get $i) (i32.const 1))))
464
+ (call $__set_len (i64.reinterpret_f64 (local.get $p)) (i32.add (local.get $i) (i32.const 1)))
465
+ (local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))))
377
466
  (f64.store
378
467
  (i32.add (local.get $base) (i32.shl (local.get $i) (i32.const 3)))
379
468
  (local.get $val))
380
- (local.get $ptr))`
469
+ (local.get $p))`
381
470
 
382
471
  // === Array literal ===
383
472
 
@@ -416,28 +505,31 @@ export default (ctx) => {
416
505
  const src = temp('ss'), slen = tempI32('sl'), si = tempI32('si')
417
506
  const id = ctx.func.uniq++
418
507
  const spreadVal = multiCount(e[1]) ? materializeMulti(e[1]) : asF64(emit(e[1]))
419
- const spreadItem = ctx.module.modules['string']
420
- ? ['if', ['result', 'f64'],
421
- ['i32.or',
422
- ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${src}`]], ['i32.const', PTR.STRING]],
423
- ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${src}`]], ['i32.const', PTR.SSO]]],
424
- ['then', (inc('__str_idx'), ['call', '$__str_idx', ['local.get', `$${src}`], ['local.get', `$${si}`]])],
425
- ['else', (['call', '$__typed_idx', ['local.get', `$${src}`], ['local.get', `$${si}`]])]]
426
- : (['call', '$__typed_idx', ['local.get', `$${src}`], ['local.get', `$${si}`]])
508
+ const srcVT = valTypeOf(e[1])
509
+ const spreadItem = srcVT === VAL.ARRAY
510
+ ? (inc('__arr_idx_known'), ['call', '$__arr_idx_known', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
511
+ : srcVT === VAL.STRING
512
+ ? (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
513
+ : ctx.module.modules['string']
514
+ ? ['if', ['result', 'f64'],
515
+ ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${src}`]]], ['i32.const', PTR.STRING]],
516
+ ['then', (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])],
517
+ ['else', (['call', '$__typed_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])]]
518
+ : (['call', '$__typed_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
427
519
 
428
520
  body.push(
429
521
  ['local.set', `$${src}`, spreadVal],
430
- ['local.set', `$${slen}`, ['call', '$__len', ['local.get', `$${src}`]]],
522
+ ['local.set', `$${slen}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${src}`]]]],
431
523
  ['local.set', `$${si}`, ['i32.const', 0]],
432
524
  ['block', `$brk${id}`, ['loop', `$loop${id}`,
433
525
  ['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${si}`], ['local.get', `$${slen}`]]],
434
- ['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['local.get', `$${out}`], ['local.get', `$${pos}`], spreadItem]],
526
+ ['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['i64.reinterpret_f64', ['local.get', `$${out}`]], ['local.get', `$${pos}`], spreadItem]],
435
527
  ['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]],
436
528
  ['local.set', `$${si}`, ['i32.add', ['local.get', `$${si}`], ['i32.const', 1]]],
437
529
  ['br', `$loop${id}`]]])
438
530
  } else {
439
531
  body.push(
440
- ['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['local.get', `$${out}`], ['local.get', `$${pos}`], asF64(emit(e))]],
532
+ ['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['i64.reinterpret_f64', ['local.get', `$${out}`]], ['local.get', `$${pos}`], asF64(emit(e))]],
441
533
  ['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]])
442
534
  }
443
535
  }
@@ -469,7 +561,7 @@ export default (ctx) => {
469
561
  if (r) return r
470
562
  }
471
563
  // Literal string key on schema-known object → direct payload slot read (skip __dyn_get)
472
- const litKey = Array.isArray(idx) && idx[0] === 'str' && typeof idx[1] === 'string' ? idx[1]
564
+ const litKey = isLiteralStr(idx) ? idx[1]
473
565
  : typeof arr === 'string' && lookupValType(arr) === VAL.OBJECT ? staticPropertyKey(idx)
474
566
  : null
475
567
  if (litKey != null && typeof arr === 'string' && ctx.schema.find) {
@@ -477,16 +569,16 @@ export default (ctx) => {
477
569
  if (slot >= 0) {
478
570
  inc('__ptr_offset')
479
571
  return typed(['f64.load',
480
- ['i32.add', ['call', '$__ptr_offset', asF64(emit(arr))], ['i32.const', slot * 8]]], 'f64')
572
+ ['i32.add', ['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(emit(arr))]], ['i32.const', slot * 8]]], 'f64')
481
573
  }
482
574
  }
483
575
  if (litKey != null && typeof arr === 'string' && lookupValType(arr) === VAL.HASH) {
484
576
  inc('__hash_get_local_h')
485
- return typed(['call', '$__hash_get_local_h', asF64(emit(arr)), asF64(emit(['str', litKey])), ['i32.const', strHashLiteral(litKey)]], 'f64')
577
+ return typed(['f64.reinterpret_i64', ['call', '$__hash_get_local_h', asI64(emit(arr)), asI64(emit(['str', litKey])), ['i32.const', strHashLiteral(litKey)]]], 'f64')
486
578
  }
487
579
  if (litKey != null && typeof arr !== 'string' && valTypeOf(arr) === VAL.HASH) {
488
580
  inc('__hash_get_local_h')
489
- return typed(['call', '$__hash_get_local_h', asF64(emit(arr)), asF64(emit(['str', litKey])), ['i32.const', strHashLiteral(litKey)]], 'f64')
581
+ return typed(['f64.reinterpret_i64', ['call', '$__hash_get_local_h', asI64(emit(arr)), asI64(emit(['str', litKey])), ['i32.const', strHashLiteral(litKey)]]], 'f64')
490
582
  }
491
583
  // Multi-value calls are materialized at call site (see '()' handler), so
492
584
  // func()[i] works naturally — func() returns a heap array pointer, [i] indexes it.
@@ -496,16 +588,16 @@ export default (ctx) => {
496
588
  const dynLoad = (objExpr, keyExpr) => {
497
589
  if (ctx.transform.strict) err(`strict mode: dynamic property access \`${typeof arr === 'string' ? arr : '<expr>'}[<expr>]\` falls back to __dyn_get. Use a literal key or known typed-array receiver, or pass { strict: false }.`)
498
590
  inc('__dyn_get')
499
- return ['call', '$__dyn_get', objExpr, keyExpr]
591
+ return ['f64.reinterpret_i64', ['call', '$__dyn_get', ['i64.reinterpret_f64', objExpr], ['i64.reinterpret_f64', keyExpr]]]
500
592
  }
501
- const stringLoad = () => (inc('__str_idx'), ['call', '$__str_idx', ptrExpr, vi])
502
- const arrayLoad = (['call', '$__typed_idx', ptrExpr, vi])
593
+ const stringLoad = () => (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ptrExpr], vi])
594
+ const arrayLoad = (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], vi])
503
595
  const emitDynamicKeyDispatch = (objExpr, numericLoad) => {
504
596
  const keyTmp = temp()
505
597
  inc('__is_str_key')
506
598
  return typed(['block', ['result', 'f64'],
507
599
  ['local.set', `$${keyTmp}`, asF64(emit(idx))],
508
- ['if', ['result', 'f64'], ['call', '$__is_str_key', ['local.get', `$${keyTmp}`]],
600
+ ['if', ['result', 'f64'], ['call', '$__is_str_key', ['i64.reinterpret_f64', ['local.get', `$${keyTmp}`]]],
509
601
  ['then', dynLoad(objExpr, ['local.get', `$${keyTmp}`])],
510
602
  ['else', numericLoad(['local.get', `$${keyTmp}`])]]], 'f64')
511
603
  }
@@ -515,11 +607,31 @@ export default (ctx) => {
515
607
  if (keyType === VAL.STRING) return typed(dynLoad(asF64(emit(arr)), asF64(emit(idx))), 'f64')
516
608
  if (useRuntimeKeyDispatch)
517
609
  return emitDynamicKeyDispatch(asF64(emit(arr)), keyExpr =>
518
- ['f64.load', ['i32.add', ['call', '$__ptr_offset', inner], ['i32.shl', asI32(typed(keyExpr, 'f64')), ['i32.const', 3]]]])
610
+ ['f64.load', ['i32.add', ['call', '$__ptr_offset', ['i64.reinterpret_f64', inner]], ['i32.shl', asI32(typed(keyExpr, 'f64')), ['i32.const', 3]]]])
519
611
  return typed(
520
- ['f64.load', ['i32.add', ['call', '$__ptr_offset', inner], ['i32.shl', vi, ['i32.const', 3]]]],
612
+ ['f64.load', ['i32.add', ['call', '$__ptr_offset', ['i64.reinterpret_f64', inner]], ['i32.shl', vi, ['i32.const', 3]]]],
521
613
  'f64')
522
614
  }
615
+ // HASH receiver with runtime string key: probe the HASH directly via
616
+ // __hash_get_local. Mirrors the literal-key path above but defers the
617
+ // hash computation to runtime. When the key's val-type is unknown at
618
+ // compile time, gate on __is_str_key — HASH has no numeric-key
619
+ // semantics, so non-string keys return undef.
620
+ if (vt === VAL.HASH) {
621
+ if (keyType === VAL.STRING) {
622
+ inc('__hash_get_local')
623
+ return typed(['f64.reinterpret_i64', ['call', '$__hash_get_local', ['i64.reinterpret_f64', ptrExpr], asI64(emit(idx))]], 'f64')
624
+ }
625
+ if (useRuntimeKeyDispatch) {
626
+ inc('__hash_get_local', '__is_str_key')
627
+ const keyTmp = temp()
628
+ return typed(['block', ['result', 'f64'],
629
+ ['local.set', `$${keyTmp}`, asF64(emit(idx))],
630
+ ['if', ['result', 'f64'], ['call', '$__is_str_key', ['i64.reinterpret_f64', ['local.get', `$${keyTmp}`]]],
631
+ ['then', ['f64.reinterpret_i64', ['call', '$__hash_get_local', ['i64.reinterpret_f64', ptrExpr], ['i64.reinterpret_f64', ['local.get', `$${keyTmp}`]]]]],
632
+ ['else', undefExpr()]]], 'f64')
633
+ }
634
+ }
523
635
  // Known array → direct f64 element load, skip string check
524
636
  if (keyType === VAL.STRING)
525
637
  return typed(dynLoad(ptrExpr, asF64(emit(idx))), 'f64')
@@ -539,7 +651,7 @@ export default (ctx) => {
539
651
  // Fast path fires on schemaId (Array<{x,y,z}> shapes) OR plain elem-val
540
652
  // (Array<NUMBER> from `.map(x => x*k)` etc.).
541
653
  const rep = typeof arr === 'string' ? ctx.func.repByLocal?.get(arr) : null
542
- const hasElemFact = rep?.arrayElemSchema != null || rep?.arrayElemValType != null
654
+ const hasElemFact = rep?.arrayElemSchema != null
543
655
  if (hasElemFact && keyIsNum) {
544
656
  inc('__ptr_offset')
545
657
  // __ptr_offset returns i32 — base local must be i32 (not the default
@@ -550,10 +662,9 @@ export default (ctx) => {
550
662
  return typed(['f64.load',
551
663
  ['i32.add',
552
664
  ['local.tee', `$${baseI32}`,
553
- ['call', '$__ptr_offset', ptrExpr]],
665
+ ['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
554
666
  ['i32.shl', vi, ['i32.const', 3]]]], 'f64')
555
667
  }
556
- inc('__arr_idx')
557
668
  const baseTmp = temp()
558
669
  // Numeric key (literal or known-NUMBER name) → skip __is_str_key dispatch;
559
670
  // arrays don't honor string-key access for numeric keys (keys aren't coerced
@@ -563,11 +674,13 @@ export default (ctx) => {
563
674
  ['local.set', `$${baseTmp}`, ptrExpr],
564
675
  emitDynamicKeyDispatch(typed(['local.get', `$${baseTmp}`], 'f64'), keyExpr => {
565
676
  const keyI32 = asI32(typed(keyExpr, 'f64'))
566
- return (['call', '$__arr_idx', ['local.get', `$${baseTmp}`], keyI32])
677
+ inc('__arr_idx')
678
+ return (['call', '$__arr_idx', ['i64.reinterpret_f64', ['local.get', `$${baseTmp}`]], keyI32])
567
679
  })], 'f64')
680
+ inc('__arr_idx_known')
568
681
  return typed(['block', ['result', 'f64'],
569
682
  ['local.set', `$${baseTmp}`, ptrExpr],
570
- (['call', '$__arr_idx', ['local.get', `$${baseTmp}`], vi])], 'f64')
683
+ (['call', '$__arr_idx_known', ['i64.reinterpret_f64', ['local.get', `$${baseTmp}`]], vi])], 'f64')
571
684
  }
572
685
  // Known string → single-char SSO string
573
686
  if (vt === 'string')
@@ -582,30 +695,26 @@ export default (ctx) => {
582
695
  if (useRuntimeKeyDispatch && !keyIsNum)
583
696
  return emitDynamicKeyDispatch(ptrExpr, keyExpr => {
584
697
  const keyI32 = asI32(typed(keyExpr, 'f64'))
585
- return (['call', '$__typed_idx', ptrExpr, keyI32])
698
+ return (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])
586
699
  })
587
- return typed((['call', '$__typed_idx', ptrExpr, vi]), 'f64')
700
+ return typed((['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], vi]), 'f64')
588
701
  }
589
702
  if (useRuntimeKeyDispatch)
590
703
  return emitDynamicKeyDispatch(ptrExpr, keyExpr => {
591
704
  const keyI32 = asI32(typed(keyExpr, 'f64'))
592
705
  if (ctx.module.modules['string']) {
593
706
  return ['if', ['result', 'f64'],
594
- ['i32.or',
595
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.STRING]],
596
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.SSO]]],
597
- ['then', (inc('__str_idx'), ['call', '$__str_idx', ptrExpr, keyI32])],
598
- ['else', (['call', '$__typed_idx', ptrExpr, keyI32])]]
707
+ ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ptrExpr]], ['i32.const', PTR.STRING]],
708
+ ['then', (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])],
709
+ ['else', (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])]]
599
710
  }
600
- return (['call', '$__typed_idx', ptrExpr, keyI32])
711
+ return (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])
601
712
  })
602
713
  // Unknown → runtime dispatch (string module loaded → check ptr_type)
603
714
  if (ctx.module.modules['string'])
604
715
  return typed(
605
716
  ['if', ['result', 'f64'],
606
- ['i32.or',
607
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.STRING]],
608
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.SSO]]],
717
+ ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ptrExpr]], ['i32.const', PTR.STRING]],
609
718
  ['then', stringLoad()],
610
719
  ['else', arrayLoad]],
611
720
  'f64')
@@ -637,7 +746,7 @@ export default (ctx) => {
637
746
  // dispatch and prologue entirely; on grow we re-extract offset because the
638
747
  // alloc may have relocated the buffer.
639
748
  body.push(
640
- ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]],
749
+ ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
641
750
  ['local.set', `$${len}`,
642
751
  ['i32.load', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 8]]]],
643
752
  ['if',
@@ -645,17 +754,17 @@ export default (ctx) => {
645
754
  ['i32.load', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 4]]],
646
755
  ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]],
647
756
  ['then',
648
- ['local.set', `$${t}`, ['call', `$${grow}`, ['local.get', `$${t}`],
757
+ ['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
649
758
  ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]]],
650
- ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]]]],
759
+ ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]],
651
760
  )
652
761
  } else {
653
762
  body.push(
654
- ['local.set', `$${len}`, ['call', '$__len', ['local.get', `$${t}`]]],
763
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
655
764
  // Grow if needed: ensure cap >= len + vals.length
656
- ['local.set', `$${t}`, ['call', `$${grow}`, ['local.get', `$${t}`],
765
+ ['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
657
766
  ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]]],
658
- ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]],
767
+ ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
659
768
  )
660
769
  }
661
770
 
@@ -671,7 +780,7 @@ export default (ctx) => {
671
780
  }
672
781
 
673
782
  // Update length header, update source variable (pointer may have changed from grow), return new length
674
- body.push(['call', '$__set_len', ['local.get', `$${t}`], ['local.get', `$${len}`]])
783
+ body.push(['call', '$__set_len', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['local.get', `$${len}`]])
675
784
  // Update the source variable if it's a named variable (so arr still points to valid memory)
676
785
  if (typeof arr === 'string') {
677
786
  if (ctx.func.boxed?.has(arr)) {
@@ -694,34 +803,36 @@ export default (ctx) => {
694
803
  // Known ARRAY → inline len (skips __len dispatch tree).
695
804
  const vt = typeof arr === 'string' ? lookupValType(arr) : valTypeOf(arr)
696
805
  const rawLen = vt === VAL.ARRAY
697
- ? ['i32.load', ['i32.sub', ['call', '$__ptr_offset', ['local.get', `$${t}`]], ['i32.const', 8]]]
698
- : ['call', '$__len', ['local.get', `$${t}`]]
806
+ ? ['i32.load', ['i32.sub', ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', 8]]]
807
+ : ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]
699
808
  return typed(['block', ['result', 'f64'],
700
809
  ['local.set', `$${t}`, va],
701
810
  ['local.set', `$${len}`, ['i32.sub', rawLen, ['i32.const', 1]]],
702
- ['call', '$__set_len', ['local.get', `$${t}`], ['local.get', `$${len}`]],
811
+ ['call', '$__set_len', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['local.get', `$${len}`]],
703
812
  ['f64.load',
704
- ['i32.add', ['call', '$__ptr_offset', ['local.get', `$${t}`]], ['i32.shl', ['local.get', `$${len}`], ['i32.const', 3]]]]], 'f64')
813
+ ['i32.add', ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.shl', ['local.get', `$${len}`], ['i32.const', 3]]]]], 'f64')
705
814
  }
706
815
 
707
816
  // .shift() → remove first element, shift remaining left, return removed
708
- ctx.core.emit['.shift'] = arrMethod('__arr_shift')
817
+ ctx.core.emit['.shift'] = (arr) => (inc('__arr_shift'),
818
+ typed(['call', '$__arr_shift', asI64(emit(arr))], 'f64'))
709
819
 
710
- ctx.core.stdlib['__arr_shift'] = () => `(func $__arr_shift (param $arr f64) (result f64)
711
- (local $bits i64) (local $rawOff i32) (local $off i32) (local $newOff i32) (local $len i32) (local $cap i32) (local $val f64)
712
- (local.set $bits (i64.reinterpret_f64 (local.get $arr)))
713
- (local.set $rawOff (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
820
+ ctx.core.stdlib['__arr_shift'] = () => `(func $__arr_shift (param $arr i64) (result f64)
821
+ (local $rawOff i32) (local $off i32) (local $newOff i32) (local $len i32) (local $cap i32) (local $val f64)
822
+ ${needsArrayDynMove() ? '(local $oldProps f64) (local $root f64)' : ''}
823
+ (local.set $rawOff (i32.wrap_i64 (i64.and (local.get $arr) (i64.const ${LAYOUT.OFFSET_MASK}))))
714
824
  (local.set $off (call $__ptr_offset (local.get $arr)))
715
825
  (if (result f64) (i32.lt_u (local.get $off) (i32.const 8))
716
- (then (f64.const 0))
826
+ (then (f64.const nan:${UNDEF_NAN}))
717
827
  (else
718
828
  (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
719
829
  (if (result f64) (i32.le_s (local.get $len) (i32.const 0))
720
- (then (f64.const 0))
830
+ (then (f64.const nan:${UNDEF_NAN}))
721
831
  (else
722
832
  (local.set $val (f64.load (local.get $off)))
723
833
  (local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
724
834
  (local.set $newOff (i32.add (local.get $off) (i32.const 8)))
835
+ ${headerPropsToGlobalIR()}
725
836
  (i32.store (local.get $off) (i32.sub (local.get $len) (i32.const 1)))
726
837
  (i32.store (i32.add (local.get $off) (i32.const 4))
727
838
  (select (i32.sub (local.get $cap) (i32.const 1)) (i32.const 0) (i32.gt_s (local.get $cap) (i32.const 0))))
@@ -746,10 +857,10 @@ export default (ctx) => {
746
857
  const svt = typeof arr === 'string' ? lookupValType(arr) : valTypeOf(arr)
747
858
  const lenInit = svt === VAL.ARRAY
748
859
  ? ['local.set', `$${len}`, ['i32.load', ['i32.sub', ['local.get', `$${off}`], ['i32.const', 8]]]]
749
- : ['local.set', `$${len}`, ['call', '$__len', va]]
860
+ : ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', va]]]
750
861
  const body = [
751
862
  recv.setup,
752
- ['local.set', `$${off}`, ['call', '$__ptr_offset', va]],
863
+ ['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', va]]],
753
864
  lenInit,
754
865
  // clamp start to [0, len]
755
866
  ['local.set', `$${s}`, vs],
@@ -788,26 +899,27 @@ export default (ctx) => {
788
899
  ['i32.sub', ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${s}`]], ['local.get', `$${cnt}`]],
789
900
  ['i32.const', 3]]],
790
901
  // update length
791
- ['call', '$__set_len', va, ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${cnt}`]]],
902
+ ['call', '$__set_len', ['i64.reinterpret_f64', va], ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${cnt}`]]],
792
903
  out.ptr,
793
904
  ]
794
905
  return typed(['block', ['result', 'f64'], ...body], 'f64')
795
906
  }
796
907
 
797
908
  // .unshift(val) → prepend element, shift existing right
798
- ctx.core.emit['.unshift'] = arrMethod('__arr_unshift', 1)
799
-
800
- ctx.core.stdlib['__arr_unshift'] = `(func $__arr_unshift (param $arr f64) (param $val f64) (result f64)
801
- (local $off i32) (local $len i32)
802
- (local.set $arr (call $__arr_grow (local.get $arr) (i32.add (call $__len (local.get $arr)) (i32.const 1))))
803
- (local.set $off (call $__ptr_offset (local.get $arr)))
804
- (local.set $len (call $__len (local.get $arr)))
909
+ ctx.core.emit['.unshift'] = (arr, val) => (inc('__arr_unshift'),
910
+ typed(['call', '$__arr_unshift', asI64(emit(arr)), asF64(emit(val))], 'f64'))
911
+
912
+ ctx.core.stdlib['__arr_unshift'] = `(func $__arr_unshift (param $arr i64) (param $val f64) (result f64)
913
+ (local $off i32) (local $len i32) (local $a f64)
914
+ (local.set $a (call $__arr_grow (local.get $arr) (i32.add (call $__len (local.get $arr)) (i32.const 1))))
915
+ (local.set $off (call $__ptr_offset (i64.reinterpret_f64 (local.get $a))))
916
+ (local.set $len (call $__len (i64.reinterpret_f64 (local.get $a))))
805
917
  (memory.copy
806
918
  (i32.add (local.get $off) (i32.const 8))
807
919
  (local.get $off)
808
920
  (i32.shl (local.get $len) (i32.const 3)))
809
921
  (f64.store (local.get $off) (local.get $val))
810
- (call $__set_len (local.get $arr) (i32.add (local.get $len) (i32.const 1)))
922
+ (call $__set_len (i64.reinterpret_f64 (local.get $a)) (i32.add (local.get $len) (i32.const 1)))
811
923
  (f64.convert_i32_s (i32.add (local.get $len) (i32.const 1))))`
812
924
 
813
925
  // .some(fn) → return 1 if any element passes, else 0 (early exit)
@@ -929,7 +1041,7 @@ export default (ctx) => {
929
1041
  const up = detectUpstream(arr)
930
1042
  if (up && up.method === 'filter' && isPureCallback(fn)) {
931
1043
  const recv = hoistArrayValue(up.source)
932
- const count = tempI32('fc'), maxLen = tempI32('fm')
1044
+ const count = tempI32('fc'), maxLen = tempI32('fm'), base = tempI32('fb')
933
1045
  const upReps = callbackArgReps(up.source)
934
1046
  const filterCb = makeCallback(up.fn, upReps), mapCb = makeCallback(fn, upReps)
935
1047
  const out = allocPtr({ type: PTR.ARRAY, len: 0, cap: ['local.get', `$${maxLen}`], tag: 'fm' })
@@ -938,28 +1050,32 @@ export default (ctx) => {
938
1050
  ['then',
939
1051
  elemStore(out.local, count, asF64(mapCb.call([item, idxArg(mapCb, count)]))),
940
1052
  ['local.set', `$${count}`, ['i32.add', ['local.get', `$${count}`], ['i32.const', 1]]]]]
941
- ], maxLen)
1053
+ ], maxLen, base)
1054
+ inc('__ptr_offset')
942
1055
  return typed(['block', ['result', 'f64'],
943
1056
  recv.setup, filterCb.setup, mapCb.setup,
944
- ['local.set', `$${maxLen}`, ['call', '$__len', recv.value]],
1057
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', recv.value]]],
1058
+ ['local.set', `$${maxLen}`, arrayLenFromPtr(base)],
945
1059
  out.init, ['local.set', `$${count}`, ['i32.const', 0]],
946
1060
  ...loop,
947
1061
  ['i32.store', ['i32.sub', ['local.get', `$${out.local}`], ['i32.const', 8]], ['local.get', `$${count}`]],
948
1062
  out.ptr], 'f64')
949
1063
  }
950
1064
  const recv = hoistArrayValue(arr)
951
- const len = tempI32('ml')
1065
+ const len = tempI32('ml'), base = tempI32('mb')
952
1066
  const cb = makeCallback(fn, callbackArgReps(arr))
953
1067
  const lenIR = ['local.get', `$${len}`]
954
1068
  const out = allocPtr({ type: PTR.ARRAY, len: lenIR, tag: 'mo' })
955
1069
  // Reuse the precomputed len local in arrayLoop (skip its internal load).
956
1070
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
957
1071
  elemStore(out.local, i, asF64(cb.call([item, idxArg(cb, i)])))
958
- ], len)
1072
+ ], len, base)
1073
+ inc('__ptr_offset')
959
1074
  return typed(['block', ['result', 'f64'],
960
1075
  recv.setup,
961
1076
  cb.setup,
962
- ['local.set', `$${len}`, ['call', '$__len', recv.value]],
1077
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', recv.value]]],
1078
+ ['local.set', `$${len}`, arrayLenFromPtr(base)],
963
1079
  out.init,
964
1080
  ...loop,
965
1081
  out.ptr], 'f64')
@@ -970,7 +1086,7 @@ export default (ctx) => {
970
1086
  const up = detectUpstream(arr)
971
1087
  if (up && up.method === 'map' && isPureCallback(fn)) {
972
1088
  const recv = hoistArrayValue(up.source)
973
- const count = tempI32('fc'), maxLen = tempI32('fm'), mapped = temp('mv')
1089
+ const count = tempI32('fc'), maxLen = tempI32('fm'), base = tempI32('fb'), mapped = temp('mv')
974
1090
  const upReps = callbackArgReps(up.source)
975
1091
  const mapCb = makeCallback(up.fn, upReps), filterCb = makeCallback(fn)
976
1092
  const out = allocPtr({ type: PTR.ARRAY, len: 0, cap: ['local.get', `$${maxLen}`], tag: 'mf' })
@@ -980,17 +1096,19 @@ export default (ctx) => {
980
1096
  ['then',
981
1097
  ['f64.store', ['i32.add', ['local.get', `$${out.local}`], ['i32.shl', ['local.get', `$${count}`], ['i32.const', 3]]], ['local.get', `$${mapped}`]],
982
1098
  ['local.set', `$${count}`, ['i32.add', ['local.get', `$${count}`], ['i32.const', 1]]]]]
983
- ], maxLen)
1099
+ ], maxLen, base)
1100
+ inc('__ptr_offset')
984
1101
  return typed(['block', ['result', 'f64'],
985
1102
  recv.setup, mapCb.setup, filterCb.setup,
986
- ['local.set', `$${maxLen}`, ['call', '$__len', recv.value]],
1103
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', recv.value]]],
1104
+ ['local.set', `$${maxLen}`, arrayLenFromPtr(base)],
987
1105
  out.init, ['local.set', `$${count}`, ['i32.const', 0]],
988
1106
  ...loop,
989
1107
  ['i32.store', ['i32.sub', ['local.get', `$${out.local}`], ['i32.const', 8]], ['local.get', `$${count}`]],
990
1108
  out.ptr], 'f64')
991
1109
  }
992
1110
  const recv = hoistArrayValue(arr)
993
- const count = tempI32('fc'), maxLen = tempI32('fm')
1111
+ const count = tempI32('fc'), maxLen = tempI32('fm'), base = tempI32('fb')
994
1112
  const cb = makeCallback(fn, callbackArgReps(arr))
995
1113
  const out = allocPtr({ type: PTR.ARRAY, len: 0, cap: ['local.get', `$${maxLen}`], tag: 'fo' })
996
1114
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
@@ -998,11 +1116,13 @@ export default (ctx) => {
998
1116
  ['then',
999
1117
  ['f64.store', ['i32.add', ['local.get', `$${out.local}`], ['i32.shl', ['local.get', `$${count}`], ['i32.const', 3]]], item],
1000
1118
  ['local.set', `$${count}`, ['i32.add', ['local.get', `$${count}`], ['i32.const', 1]]]]]
1001
- ], maxLen)
1119
+ ], maxLen, base)
1120
+ inc('__ptr_offset')
1002
1121
  return typed(['block', ['result', 'f64'],
1003
1122
  recv.setup,
1004
1123
  cb.setup,
1005
- ['local.set', `$${maxLen}`, ['call', '$__len', recv.value]],
1124
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', recv.value]]],
1125
+ ['local.set', `$${maxLen}`, arrayLenFromPtr(base)],
1006
1126
  out.init,
1007
1127
  ['local.set', `$${count}`, ['i32.const', 0]],
1008
1128
  ...loop,
@@ -1112,13 +1232,139 @@ export default (ctx) => {
1112
1232
  ['local.get', `$${result}`]], 'f64')
1113
1233
  }
1114
1234
 
1235
+ // .reverse() → in-place swap arr[i] ↔ arr[len-1-i], returns the array.
1236
+ ctx.core.emit['.reverse'] = (arr) => {
1237
+ const recv = hoistArrayValue(arr)
1238
+ const arrTmp = temp('rv')
1239
+ const base = tempI32('rb')
1240
+ const len = tempI32('rl')
1241
+ const i = tempI32('ri')
1242
+ const j = tempI32('rj')
1243
+ const tmp = temp('rt')
1244
+ const id = ctx.func.uniq++
1245
+ const exit = `$revexit${id}`, loop = `$revloop${id}`
1246
+
1247
+ inc('__ptr_offset')
1248
+
1249
+ const addr = (idxIR) => ['i32.add', ['local.get', `$${base}`], ['i32.shl', idxIR, ['i32.const', 3]]]
1250
+
1251
+ return typed(['block', ['result', 'f64'],
1252
+ recv.setup,
1253
+ ['local.set', `$${arrTmp}`, recv.value],
1254
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${arrTmp}`]]]],
1255
+ ['local.set', `$${len}`, ['i32.load', ['i32.sub', ['local.get', `$${base}`], ['i32.const', 8]]]],
1256
+ ['local.set', `$${i}`, ['i32.const', 0]],
1257
+ ['local.set', `$${j}`, ['i32.sub', ['local.get', `$${len}`], ['i32.const', 1]]],
1258
+ ['block', exit,
1259
+ ['loop', loop,
1260
+ ['br_if', exit, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${j}`]]],
1261
+ ['local.set', `$${tmp}`, ['f64.load', addr(['local.get', `$${i}`])]],
1262
+ ['f64.store', addr(['local.get', `$${i}`]), ['f64.load', addr(['local.get', `$${j}`])]],
1263
+ ['f64.store', addr(['local.get', `$${j}`]), ['local.get', `$${tmp}`]],
1264
+ ['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
1265
+ ['local.set', `$${j}`, ['i32.sub', ['local.get', `$${j}`], ['i32.const', 1]]],
1266
+ ['br', loop]]],
1267
+ ['local.get', `$${arrTmp}`]
1268
+ ], 'f64')
1269
+ }
1270
+
1271
+ // Insertion sort — stable, in-place, O(n²). The comparator is called per
1272
+ // shift; positive return → swap. NaN returns become "no swap" via f64.gt's
1273
+ // IEEE 754 semantics (NaN compares false), matching the spec's NaN-as-0
1274
+ // behavior. When fn is omitted, elements are compared as strings via
1275
+ // __to_str → __str_cmp (byte-wise; NOT locale-aware).
1276
+ ctx.core.emit['.sort'] = (arr, fn) => {
1277
+ const recv = hoistArrayValue(arr)
1278
+ const arrTmp = temp('sr')
1279
+ const base = tempI32('sb')
1280
+ const len = tempI32('sl')
1281
+ const i = tempI32('si')
1282
+ const j = tempI32('sj')
1283
+ const cur = temp('sc')
1284
+ const neighbor = temp('sn')
1285
+ const id = ctx.func.uniq++
1286
+ const outerExit = `$sortexit${id}`, innerExit = `$sortinnerexit${id}`
1287
+ const outerLoop = `$sortouter${id}`, innerLoop = `$sortinner${id}`
1288
+
1289
+ let cmpExpr, cmpSetup
1290
+ if (fn == null) {
1291
+ inc('__to_str', '__str_cmp')
1292
+ cmpExpr = (aIR, bIR) => typed(['f64.convert_i32_s',
1293
+ ['call', '$__str_cmp',
1294
+ ['call', '$__to_str', ['i64.reinterpret_f64', aIR]],
1295
+ ['call', '$__to_str', ['i64.reinterpret_f64', bIR]]
1296
+ ]
1297
+ ], 'f64')
1298
+ cmpSetup = ['nop']
1299
+ } else {
1300
+ const cb = makeCallback(fn, [])
1301
+ cmpSetup = cb.setup
1302
+ cmpExpr = (aIR, bIR) => asF64(cb.call([
1303
+ typed(aIR, 'f64'),
1304
+ typed(bIR, 'f64')
1305
+ ]))
1306
+ }
1307
+
1308
+ inc('__ptr_offset')
1309
+
1310
+ const addr = (idxIR) => ['i32.add', ['local.get', `$${base}`], ['i32.shl', idxIR, ['i32.const', 3]]]
1311
+ const jPlus1 = ['i32.add', ['local.get', `$${j}`], ['i32.const', 1]]
1312
+
1313
+ return typed(['block', ['result', 'f64'],
1314
+ recv.setup,
1315
+ cmpSetup,
1316
+ ['local.set', `$${arrTmp}`, recv.value],
1317
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${arrTmp}`]]]],
1318
+ ['local.set', `$${len}`, ['i32.load', ['i32.sub', ['local.get', `$${base}`], ['i32.const', 8]]]],
1319
+
1320
+ ['local.set', `$${i}`, ['i32.const', 1]],
1321
+ ['block', outerExit,
1322
+ ['loop', outerLoop,
1323
+ ['br_if', outerExit, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
1324
+ ['local.set', `$${cur}`, ['f64.load', addr(['local.get', `$${i}`])]],
1325
+ ['local.set', `$${j}`, ['i32.sub', ['local.get', `$${i}`], ['i32.const', 1]]],
1326
+
1327
+ ['block', innerExit,
1328
+ ['loop', innerLoop,
1329
+ ['br_if', innerExit, ['i32.lt_s', ['local.get', `$${j}`], ['i32.const', 0]]],
1330
+ ['local.set', `$${neighbor}`, ['f64.load', addr(['local.get', `$${j}`])]],
1331
+ // Break unless cmp(neighbor, cur) > 0. f64.gt is false for NaN.
1332
+ ['br_if', innerExit, ['i32.eqz',
1333
+ ['f64.gt',
1334
+ cmpExpr(['local.get', `$${neighbor}`], ['local.get', `$${cur}`]),
1335
+ ['f64.const', 0]]]],
1336
+ ['f64.store', addr(jPlus1), ['local.get', `$${neighbor}`]],
1337
+ ['local.set', `$${j}`, ['i32.sub', ['local.get', `$${j}`], ['i32.const', 1]]],
1338
+ ['br', innerLoop]]],
1339
+
1340
+ ['f64.store', addr(jPlus1), ['local.get', `$${cur}`]],
1341
+ ['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
1342
+ ['br', outerLoop]]],
1343
+
1344
+ ['local.get', `$${arrTmp}`]
1345
+ ], 'f64')
1346
+ }
1347
+
1348
+ // Boxed pointer values (strings/objects/etc.) carry NaN payloads, and
1349
+ // f64.eq treats NaN as not-equal to anything — even bit-identical NaN —
1350
+ // so a raw f64 compare misses string and reference matches. Route those
1351
+ // through __eq, the same helper `==` uses for STRING/BIGINT/cross-type.
1352
+ // f64.eq stays the fast path when the search value is statically NUMBER.
1353
+ const arrEqIR = (val) => {
1354
+ const vt = resolveValType(val, valTypeOf, lookupValType)
1355
+ if (vt === VAL.NUMBER) return (item, vv) => ['f64.eq', item, vv]
1356
+ inc('__eq')
1357
+ return (item, vv) => ['call', '$__eq', ['i64.reinterpret_f64', item], ['i64.reinterpret_f64', vv]]
1358
+ }
1359
+
1115
1360
  ctx.core.emit['.indexOf'] = (arr, val) => {
1116
1361
  const recv = hoistArrayValue(arr)
1117
1362
  const vv = asF64(emit(val))
1363
+ const eq = arrEqIR(val)
1118
1364
  const result = tempI32('ix')
1119
1365
  const exit = `$exit${ctx.func.uniq++}`
1120
1366
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
1121
- ['if', ['f64.eq', item, vv],
1367
+ ['if', eq(item, vv),
1122
1368
  ['then', ['local.set', `$${result}`, ['local.get', `$${i}`]], ['br', exit]]]
1123
1369
  ])
1124
1370
  return typed(['block', ['result', 'f64'],
@@ -1131,10 +1377,11 @@ export default (ctx) => {
1131
1377
  ctx.core.emit['.includes'] = (arr, val) => {
1132
1378
  const recv = hoistArrayValue(arr)
1133
1379
  const vv = asF64(emit(val))
1380
+ const eq = arrEqIR(val)
1134
1381
  const result = tempI32('ic')
1135
1382
  const exit = `$exit${ctx.func.uniq++}`
1136
1383
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
1137
- ['if', ['f64.eq', item, vv],
1384
+ ['if', eq(item, vv),
1138
1385
  ['then', ['local.set', `$${result}`, ['i32.const', 1]], ['br', exit]]]
1139
1386
  ])
1140
1387
  return typed(['block', ['result', 'f64'],
@@ -1146,6 +1393,19 @@ export default (ctx) => {
1146
1393
 
1147
1394
  // .at(i) → array element with negative index support
1148
1395
  ctx.core.emit['.array:at'] = (arr, idx) => {
1396
+ const vt = typeof arr === 'string' ? lookupValType(arr) : valTypeOf(arr)
1397
+ if (vt === VAL.ARRAY) {
1398
+ inc('__ptr_offset')
1399
+ const t = tempI32('ai'), off = tempI32('ao')
1400
+ return typed(['block', ['result', 'f64'],
1401
+ ['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(emit(arr))]]],
1402
+ ['local.set', `$${t}`, asI32(emit(idx))],
1403
+ ['if', ['i32.lt_s', ['local.get', `$${t}`], ['i32.const', 0]],
1404
+ ['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`],
1405
+ ['i32.load', ['i32.sub', ['local.get', `$${off}`], ['i32.const', 8]]]]]]],
1406
+ ['f64.load', ['i32.add', ['local.get', `$${off}`],
1407
+ ['i32.shl', ['local.get', `$${t}`], ['i32.const', 3]]]]], 'f64')
1408
+ }
1149
1409
  const t = tempI32('ai'), a = temp('aa')
1150
1410
  return typed(['block', ['result', 'f64'],
1151
1411
  ['local.set', `$${a}`, asF64(emit(arr))],
@@ -1153,10 +1413,11 @@ export default (ctx) => {
1153
1413
  // Negative index: t += length
1154
1414
  ['if', ['i32.lt_s', ['local.get', `$${t}`], ['i32.const', 0]],
1155
1415
  ['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`],
1156
- ['call', '$__len', ['local.get', `$${a}`]]]]]],
1157
- ['f64.load', ['i32.add', ['call', '$__ptr_offset', ['local.get', `$${a}`]],
1416
+ ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${a}`]]]]]]],
1417
+ ['f64.load', ['i32.add', ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${a}`]]],
1158
1418
  ['i32.shl', ['local.get', `$${t}`], ['i32.const', 3]]]]], 'f64')
1159
1419
  }
1420
+ ctx.core.emit['.at'] = ctx.core.emit['.array:at']
1160
1421
 
1161
1422
  ctx.core.emit['.slice'] = (arr, start, end) => {
1162
1423
  // BUFFER slice → byte-level copy handled in typedarray module.
@@ -1171,7 +1432,7 @@ export default (ctx) => {
1171
1432
  const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${outLen}`], tag: 'so' })
1172
1433
  return typed(['block', ['result', 'f64'],
1173
1434
  recv.setup,
1174
- ['local.set', `$${ptr}`, ['call', '$__ptr_offset', recv.value]],
1435
+ ['local.set', `$${ptr}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', recv.value]]],
1175
1436
  ['local.set', `$${len}`, ['i32.load', ['i32.sub', ['local.get', `$${ptr}`], ['i32.const', 8]]]],
1176
1437
  ['local.set', `$${s}`, rawStart],
1177
1438
  ['if', ['i32.lt_s', ['local.get', `$${s}`], ['i32.const', 0]],
@@ -1204,14 +1465,14 @@ export default (ctx) => {
1204
1465
  // Calculate total length
1205
1466
  const body = [
1206
1467
  recv.setup,
1207
- ['local.set', `$${len}`, ['call', '$__len', va]],
1468
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', va]]],
1208
1469
  ]
1209
1470
 
1210
1471
  const otherVals = []
1211
1472
  for (const other of others) {
1212
1473
  const vo = asF64(emit(other))
1213
1474
  otherVals.push(vo)
1214
- body.push(['local.set', `$${len}`, ['i32.add', ['local.get', `$${len}`], ['call', '$__len', vo]]])
1475
+ body.push(['local.set', `$${len}`, ['i32.add', ['local.get', `$${len}`], ['call', '$__len', ['i64.reinterpret_f64', vo]]]])
1215
1476
  }
1216
1477
 
1217
1478
  body.push(out.init)
@@ -1220,8 +1481,8 @@ export default (ctx) => {
1220
1481
  const srcOff = tempI32('co')
1221
1482
  body.push(
1222
1483
  ['local.set', `$${pos}`, ['i32.const', 0]],
1223
- ['local.set', `$${len}`, ['call', '$__len', va]],
1224
- ['local.set', `$${srcOff}`, ['call', '$__ptr_offset', va]]
1484
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', va]]],
1485
+ ['local.set', `$${srcOff}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', va]]]
1225
1486
  )
1226
1487
  const id = ctx.func.uniq++
1227
1488
  body.push(
@@ -1236,7 +1497,7 @@ export default (ctx) => {
1236
1497
 
1237
1498
  // Copy each other array
1238
1499
  const offset = tempI32('off')
1239
- body.push(['local.set', `$${offset}`, ['call', '$__len', va]])
1500
+ body.push(['local.set', `$${offset}`, ['call', '$__len', ['i64.reinterpret_f64', va]]])
1240
1501
 
1241
1502
  const otherOff = tempI32('co2')
1242
1503
  for (let i = 0; i < otherVals.length; i++) {
@@ -1244,8 +1505,8 @@ export default (ctx) => {
1244
1505
  const id2 = ctx.func.uniq++
1245
1506
  body.push(
1246
1507
  ['local.set', `$${pos}`, ['i32.const', 0]],
1247
- ['local.set', `$${len}`, ['call', '$__len', vo]],
1248
- ['local.set', `$${otherOff}`, ['call', '$__ptr_offset', vo]],
1508
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', vo]]],
1509
+ ['local.set', `$${otherOff}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', vo]]],
1249
1510
  ['block', `$done${id2}`, ['loop', `$loop${id2}`,
1250
1511
  ['br_if', `$done${id2}`, ['i32.ge_s', ['local.get', `$${pos}`], ['local.get', `$${len}`]]],
1251
1512
  ['f64.store',
@@ -1262,7 +1523,7 @@ export default (ctx) => {
1262
1523
  }
1263
1524
 
1264
1525
  // .flat() → flatten one level of nested arrays
1265
- ctx.core.stdlib['__arr_flat'] = `(func $__arr_flat (param $src f64) (result f64)
1526
+ ctx.core.stdlib['__arr_flat'] = `(func $__arr_flat (param $src i64) (result f64)
1266
1527
  (local $len i32) (local $off i32) (local $i i32) (local $total i32) (local $dst i32) (local $pos i32)
1267
1528
  (local $elem f64) (local $subLen i32) (local $subOff i32) (local $j i32)
1268
1529
  (local.set $off (call $__ptr_offset (local.get $src)))
@@ -1273,8 +1534,8 @@ export default (ctx) => {
1273
1534
  (br_if $c1 (i32.ge_s (local.get $i) (local.get $len)))
1274
1535
  (local.set $elem (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
1275
1536
  (if (i32.and (f64.ne (local.get $elem) (local.get $elem))
1276
- (i32.eq (call $__ptr_type (local.get $elem)) (i32.const ${PTR.ARRAY})))
1277
- (then (local.set $total (i32.add (local.get $total) (call $__len (local.get $elem)))))
1537
+ (i32.eq (call $__ptr_type (i64.reinterpret_f64 (local.get $elem))) (i32.const ${PTR.ARRAY})))
1538
+ (then (local.set $total (i32.add (local.get $total) (call $__len (i64.reinterpret_f64 (local.get $elem))))))
1278
1539
  (else (local.set $total (i32.add (local.get $total) (i32.const 1)))))
1279
1540
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
1280
1541
  (br $cl1)))
@@ -1289,10 +1550,10 @@ export default (ctx) => {
1289
1550
  (br_if $c2 (i32.ge_s (local.get $i) (local.get $len)))
1290
1551
  (local.set $elem (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
1291
1552
  (if (i32.and (f64.ne (local.get $elem) (local.get $elem))
1292
- (i32.eq (call $__ptr_type (local.get $elem)) (i32.const ${PTR.ARRAY})))
1553
+ (i32.eq (call $__ptr_type (i64.reinterpret_f64 (local.get $elem))) (i32.const ${PTR.ARRAY})))
1293
1554
  (then
1294
- (local.set $subOff (call $__ptr_offset (local.get $elem)))
1295
- (local.set $subLen (call $__len (local.get $elem)))
1555
+ (local.set $subOff (call $__ptr_offset (i64.reinterpret_f64 (local.get $elem))))
1556
+ (local.set $subLen (call $__len (i64.reinterpret_f64 (local.get $elem))))
1296
1557
  (local.set $j (i32.const 0))
1297
1558
  (block $s (loop $sl
1298
1559
  (br_if $s (i32.ge_s (local.get $j) (local.get $subLen)))
@@ -1308,15 +1569,17 @@ export default (ctx) => {
1308
1569
  (br $cl2)))
1309
1570
  (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $dst)))`
1310
1571
 
1311
- ctx.core.emit['.flat'] = arrMethod('__arr_flat')
1572
+ ctx.core.emit['.flat'] = (arr) => (inc('__arr_flat'),
1573
+ typed(['call', '$__arr_flat', asI64(emit(arr))], 'f64'))
1312
1574
 
1313
1575
  // .flatMap(fn) → map then flatten
1314
1576
  ctx.core.emit['.flatMap'] = (arr, fn) => {
1315
1577
  const mapped = ctx.core.emit['.map'](arr, fn)
1316
1578
  inc('__arr_flat')
1317
- return typed(['call', '$__arr_flat', asF64(mapped)], 'f64')
1579
+ return typed(['call', '$__arr_flat', asI64(mapped)], 'f64')
1318
1580
  }
1319
1581
 
1320
1582
  // .join(sep) → concatenate array elements with separator string
1321
- ctx.core.emit['.join'] = arrMethod('__str_join', 1)
1583
+ ctx.core.emit['.join'] = (arr, sep) => (inc('__str_join'),
1584
+ typed(['call', '$__str_join', asI64(emit(arr)), asI64(emit(sep))], 'f64'))
1322
1585
  }