jz 0.1.0 → 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,8 +8,10 @@
8
8
  * @module array
9
9
  */
10
10
 
11
- import { emit, typed, asF64, asI32, valTypeOf, lookupValType, VAL, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr, extractParams, multiCount, materializeMulti, arrayLoop, elemLoad, elemStore, truthyIR, extractF64Bits, appendStaticSlots, mkPtrIR, slotAddr, updateRep } from '../src/compile.js'
12
- import { ctx, inc, err, PTR } from '../src/ctx.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
+ import { emit, materializeMulti } from '../src/emit.js'
13
+ import { valTypeOf, lookupValType, VAL, extractParams, updateRep, staticPropertyKey } from '../src/analyze.js'
14
+ import { ctx, inc, err, PTR, LAYOUT } from '../src/ctx.js'
13
15
  import { strHashLiteral } from './collection.js'
14
16
 
15
17
 
@@ -40,6 +42,8 @@ function hoistArrayValue(arr) {
40
42
  }
41
43
  }
42
44
 
45
+ const arrayLenFromPtr = ptr => ['i32.load', ['i32.sub', ['local.get', `$${ptr}`], ['i32.const', 8]]]
46
+
43
47
  // Pure-expression check: no statements, binders, control flow, or assignments.
44
48
  // Inlining is only safe for these — anything else needs the full closure machinery.
45
49
  const NOT_PURE_OPS = new Set([
@@ -176,10 +180,42 @@ const arrayGrowDeps = (knownArray = false) => () => [
176
180
  ...(needsArrayDynMove() ? ['__dyn_move'] : []),
177
181
  ]
178
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.
179
185
  const maybeDynMoveIR = () => needsArrayDynMove()
180
186
  ? '(call $__dyn_move (local.get $off) (local.get $newOff))'
181
187
  : ''
182
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
+
183
219
  export default (ctx) => {
184
220
  Object.assign(ctx.core.stdlibDeps, {
185
221
  __arr_idx: [],
@@ -187,7 +223,7 @@ export default (ctx) => {
187
223
  __arr_grow_known: arrayGrowDeps(true),
188
224
  __arr_shift: () => [
189
225
  '__ptr_offset',
190
- ...(needsArrayDynMove() ? ['__dyn_move'] : []),
226
+ ...(needsArrayDynMove() ? ['__dyn_move', '__hash_new', '__ihash_set_local'] : []),
191
227
  ],
192
228
  __arr_set_idx_ptr: ['__arr_grow', '__ptr_offset', '__set_len'],
193
229
  __typed_idx: () => ctx.features.typedarray || ctx.features.external
@@ -208,21 +244,20 @@ export default (ctx) => {
208
244
  const t = temp('t')
209
245
  return typed(['i32.and',
210
246
  ['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
211
- ['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')
212
248
  }
213
249
 
214
250
  // ARRAY-only indexed read. Inline forwarding-follow + bounds check + load — avoids
215
251
  // the redundant double pass through __len then __ptr_offset that both follow forwarding.
216
- ctx.core.stdlib['__arr_idx'] = `(func $__arr_idx (param $ptr f64) (param $i i32) (result f64)
217
- (local $bits i64) (local $off i32)
218
- (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)
219
254
  (if (result f64)
220
255
  (i32.ne
221
- (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})))
222
257
  (i32.const ${PTR.ARRAY}))
223
258
  (then (f64.const nan:${UNDEF_NAN}))
224
259
  (else
225
- (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}))))
226
261
  (block $done
227
262
  (loop $follow
228
263
  (br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
@@ -239,13 +274,32 @@ export default (ctx) => {
239
274
  (then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
240
275
  (else (f64.const nan:${UNDEF_NAN})))))) `
241
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
+
242
296
  // Runtime-dispatch index: element-type aware load with bounds check + view indirection.
243
297
  // Full body handles TYPED element types and view indirection since external host can
244
298
  // pass typed arrays even when typedarray module isn't loaded. When features.typedarray
245
299
  // and features.external are both off, collapses to ARRAY-only f64 indexing.
246
300
  ctx.core.stdlib['__typed_idx'] = () => {
247
301
  if (!ctx.features.typedarray && !ctx.features.external) {
248
- 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)
249
303
  (local $len i32)
250
304
  (local.set $len (call $__len (local.get $ptr)))
251
305
  (if (result f64)
@@ -255,13 +309,12 @@ export default (ctx) => {
255
309
  (then (f64.const nan:${UNDEF_NAN}))
256
310
  (else (f64.load (i32.add (call $__ptr_offset (local.get $ptr)) (i32.shl (local.get $i) (i32.const 3)))))))`
257
311
  }
258
- // Hot (~37M calls in watr self-host). Type/aux/offset extracted once from $bits.
259
- return `(func $__typed_idx (param $ptr f64) (param $i i32) (result f64)
260
- (local $bits i64) (local $t i32) (local $off i32) (local $et i32) (local $len i32) (local $aux i32)
261
- (local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
262
- (local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF))))
263
- (local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 32)) (i64.const 0x7FFF))))
264
- (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}))))
265
318
  (if
266
319
  (i32.and
267
320
  (i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
@@ -279,7 +332,9 @@ export default (ctx) => {
279
332
  (local.set $et (i32.and (local.get $aux) (i32.const 7)))
280
333
  (if (result f64) (i32.ge_u (local.get $et) (i32.const 6))
281
334
  (then (if (result f64) (i32.eq (local.get $et) (i32.const 7))
282
- (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)))))))
283
338
  (else (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
284
339
  (else (if (result f64) (i32.ge_u (local.get $et) (i32.const 4))
285
340
  (then (if (result f64) (i32.and (local.get $et) (i32.const 1))
@@ -296,23 +351,56 @@ export default (ctx) => {
296
351
  }
297
352
 
298
353
  // Array.from(src) — shallow copy of array (memory.copy of f64 elements)
299
- 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)
300
355
  (local $len i32) (local $dst i32)
301
356
  (local.set $len (call $__len (local.get $src)))
302
357
  (local.set $dst (call $__alloc_hdr (local.get $len) (local.get $len) (i32.const 8)))
303
358
  (memory.copy (local.get $dst) (call $__ptr_offset (local.get $src)) (i32.shl (local.get $len) (i32.const 3)))
304
359
  (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $dst)))`
305
360
 
306
- 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
+ }
307
394
  inc('__arr_from')
308
- return typed(['call', '$__arr_from', asF64(emit(src))], 'f64')
395
+ return typed(['call', '$__arr_from', asI64(emit(src))], 'f64')
309
396
  }
310
397
 
311
398
  // Grow array if capacity insufficient. Returns (possibly new) NaN-boxed pointer.
312
399
  // Old storage is left behind as a forwarding header so existing aliases keep
313
400
  // seeing the current backing store after growth.
314
- 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)
315
402
  (local $t i32) (local $off i32) (local $oldCap i32) (local $newCap i32) (local $newOff i32) (local $len i32)
403
+ ${needsArrayDynMove() ? '(local $oldProps f64)' : ''}
316
404
  (local.set $t (call $__ptr_type (local.get $ptr)))
317
405
  (local.set $off (call $__ptr_offset (local.get $ptr)))
318
406
  ;; Defensive path: invalid/non-array pointer -> create fresh array buffer.
@@ -326,7 +414,7 @@ export default (ctx) => {
326
414
  (return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))))
327
415
  (local.set $oldCap (i32.load (i32.sub (local.get $off) (i32.const 4))))
328
416
  (if (i32.ge_s (local.get $oldCap) (local.get $minCap))
329
- (then (return (local.get $ptr))))
417
+ (then (return (f64.reinterpret_i64 (local.get $ptr)))))
330
418
  (local.set $newCap (select
331
419
  (local.get $minCap)
332
420
  (i32.shl (local.get $oldCap) (i32.const 1))
@@ -334,17 +422,19 @@ export default (ctx) => {
334
422
  (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
335
423
  (local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap) (i32.const 8)))
336
424
  (memory.copy (local.get $newOff) (local.get $off) (i32.shl (local.get $len) (i32.const 3)))
425
+ ${headerPropsCopyIR()}
337
426
  ${maybeDynMoveIR()}
338
427
  (i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newOff))
339
428
  (i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
340
429
  (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))`
341
430
 
342
- 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)
343
432
  (local $off i32) (local $oldCap i32) (local $newCap i32) (local $newOff i32) (local $len i32)
433
+ ${needsArrayDynMove() ? '(local $oldProps f64)' : ''}
344
434
  (local.set $off (call $__ptr_offset (local.get $ptr)))
345
435
  (local.set $oldCap (i32.load (i32.sub (local.get $off) (i32.const 4))))
346
436
  (if (i32.ge_s (local.get $oldCap) (local.get $minCap))
347
- (then (return (local.get $ptr))))
437
+ (then (return (f64.reinterpret_i64 (local.get $ptr)))))
348
438
  (local.set $newCap (select
349
439
  (local.get $minCap)
350
440
  (i32.shl (local.get $oldCap) (i32.const 1))
@@ -352,6 +442,7 @@ export default (ctx) => {
352
442
  (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
353
443
  (local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap) (i32.const 8)))
354
444
  (memory.copy (local.get $newOff) (local.get $off) (i32.shl (local.get $len) (i32.const 3)))
445
+ ${headerPropsCopyIR()}
355
446
  ${maybeDynMoveIR()}
356
447
  (i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newOff))
357
448
  (i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
@@ -360,21 +451,22 @@ export default (ctx) => {
360
451
  // Hot for arr[i] = val (~18M calls in watr self-host). Compute base via __ptr_offset
361
452
  // once and read len from the inline header (i32.load base-8) — avoids __len's separate
362
453
  // forwarding follow. On the rare grow path the base is recomputed after relocation.
363
- ctx.core.stdlib['__arr_set_idx_ptr'] = `(func $__arr_set_idx_ptr (param $ptr f64) (param $i i32) (param $val f64) (result f64)
364
- (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)))
365
457
  (if (i32.lt_s (local.get $i) (i32.const 0))
366
- (then (return (local.get $ptr))))
458
+ (then (return (local.get $p))))
367
459
  (local.set $base (call $__ptr_offset (local.get $ptr)))
368
460
  (if (i32.ge_u (local.get $i)
369
461
  (i32.load (i32.sub (local.get $base) (i32.const 8))))
370
462
  (then
371
- (local.set $ptr (call $__arr_grow (local.get $ptr) (i32.add (local.get $i) (i32.const 1))))
372
- (call $__set_len (local.get $ptr) (i32.add (local.get $i) (i32.const 1)))
373
- (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))))))
374
466
  (f64.store
375
467
  (i32.add (local.get $base) (i32.shl (local.get $i) (i32.const 3)))
376
468
  (local.get $val))
377
- (local.get $ptr))`
469
+ (local.get $p))`
378
470
 
379
471
  // === Array literal ===
380
472
 
@@ -413,28 +505,31 @@ export default (ctx) => {
413
505
  const src = temp('ss'), slen = tempI32('sl'), si = tempI32('si')
414
506
  const id = ctx.func.uniq++
415
507
  const spreadVal = multiCount(e[1]) ? materializeMulti(e[1]) : asF64(emit(e[1]))
416
- const spreadItem = ctx.module.modules['string']
417
- ? ['if', ['result', 'f64'],
418
- ['i32.or',
419
- ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${src}`]], ['i32.const', PTR.STRING]],
420
- ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${src}`]], ['i32.const', PTR.SSO]]],
421
- ['then', (inc('__str_idx'), ['call', '$__str_idx', ['local.get', `$${src}`], ['local.get', `$${si}`]])],
422
- ['else', (['call', '$__typed_idx', ['local.get', `$${src}`], ['local.get', `$${si}`]])]]
423
- : (['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}`]])
424
519
 
425
520
  body.push(
426
521
  ['local.set', `$${src}`, spreadVal],
427
- ['local.set', `$${slen}`, ['call', '$__len', ['local.get', `$${src}`]]],
522
+ ['local.set', `$${slen}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${src}`]]]],
428
523
  ['local.set', `$${si}`, ['i32.const', 0]],
429
524
  ['block', `$brk${id}`, ['loop', `$loop${id}`,
430
525
  ['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${si}`], ['local.get', `$${slen}`]]],
431
- ['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]],
432
527
  ['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]],
433
528
  ['local.set', `$${si}`, ['i32.add', ['local.get', `$${si}`], ['i32.const', 1]]],
434
529
  ['br', `$loop${id}`]]])
435
530
  } else {
436
531
  body.push(
437
- ['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))]],
438
533
  ['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]])
439
534
  }
440
535
  }
@@ -466,22 +561,24 @@ export default (ctx) => {
466
561
  if (r) return r
467
562
  }
468
563
  // Literal string key on schema-known object → direct payload slot read (skip __dyn_get)
469
- const litKey = Array.isArray(idx) && idx[0] === 'str' && typeof idx[1] === 'string' ? idx[1] : null
564
+ const litKey = isLiteralStr(idx) ? idx[1]
565
+ : typeof arr === 'string' && lookupValType(arr) === VAL.OBJECT ? staticPropertyKey(idx)
566
+ : null
470
567
  if (litKey != null && typeof arr === 'string' && ctx.schema.find) {
471
568
  const slot = ctx.schema.find(arr, litKey)
472
569
  if (slot >= 0) {
473
570
  inc('__ptr_offset')
474
571
  return typed(['f64.load',
475
- ['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')
476
573
  }
477
574
  }
478
575
  if (litKey != null && typeof arr === 'string' && lookupValType(arr) === VAL.HASH) {
479
576
  inc('__hash_get_local_h')
480
- 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')
481
578
  }
482
579
  if (litKey != null && typeof arr !== 'string' && valTypeOf(arr) === VAL.HASH) {
483
580
  inc('__hash_get_local_h')
484
- 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')
485
582
  }
486
583
  // Multi-value calls are materialized at call site (see '()' handler), so
487
584
  // func()[i] works naturally — func() returns a heap array pointer, [i] indexes it.
@@ -491,16 +588,16 @@ export default (ctx) => {
491
588
  const dynLoad = (objExpr, keyExpr) => {
492
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 }.`)
493
590
  inc('__dyn_get')
494
- return ['call', '$__dyn_get', objExpr, keyExpr]
591
+ return ['f64.reinterpret_i64', ['call', '$__dyn_get', ['i64.reinterpret_f64', objExpr], ['i64.reinterpret_f64', keyExpr]]]
495
592
  }
496
- const stringLoad = () => (inc('__str_idx'), ['call', '$__str_idx', ptrExpr, vi])
497
- 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])
498
595
  const emitDynamicKeyDispatch = (objExpr, numericLoad) => {
499
596
  const keyTmp = temp()
500
597
  inc('__is_str_key')
501
598
  return typed(['block', ['result', 'f64'],
502
599
  ['local.set', `$${keyTmp}`, asF64(emit(idx))],
503
- ['if', ['result', 'f64'], ['call', '$__is_str_key', ['local.get', `$${keyTmp}`]],
600
+ ['if', ['result', 'f64'], ['call', '$__is_str_key', ['i64.reinterpret_f64', ['local.get', `$${keyTmp}`]]],
504
601
  ['then', dynLoad(objExpr, ['local.get', `$${keyTmp}`])],
505
602
  ['else', numericLoad(['local.get', `$${keyTmp}`])]]], 'f64')
506
603
  }
@@ -510,11 +607,31 @@ export default (ctx) => {
510
607
  if (keyType === VAL.STRING) return typed(dynLoad(asF64(emit(arr)), asF64(emit(idx))), 'f64')
511
608
  if (useRuntimeKeyDispatch)
512
609
  return emitDynamicKeyDispatch(asF64(emit(arr)), keyExpr =>
513
- ['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]]]])
514
611
  return typed(
515
- ['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]]]],
516
613
  'f64')
517
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
+ }
518
635
  // Known array → direct f64 element load, skip string check
519
636
  if (keyType === VAL.STRING)
520
637
  return typed(dynLoad(ptrExpr, asF64(emit(idx))), 'f64')
@@ -534,7 +651,7 @@ export default (ctx) => {
534
651
  // Fast path fires on schemaId (Array<{x,y,z}> shapes) OR plain elem-val
535
652
  // (Array<NUMBER> from `.map(x => x*k)` etc.).
536
653
  const rep = typeof arr === 'string' ? ctx.func.repByLocal?.get(arr) : null
537
- const hasElemFact = rep?.arrayElemSchema != null || rep?.arrayElemValType != null
654
+ const hasElemFact = rep?.arrayElemSchema != null
538
655
  if (hasElemFact && keyIsNum) {
539
656
  inc('__ptr_offset')
540
657
  // __ptr_offset returns i32 — base local must be i32 (not the default
@@ -545,10 +662,9 @@ export default (ctx) => {
545
662
  return typed(['f64.load',
546
663
  ['i32.add',
547
664
  ['local.tee', `$${baseI32}`,
548
- ['call', '$__ptr_offset', ptrExpr]],
665
+ ['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
549
666
  ['i32.shl', vi, ['i32.const', 3]]]], 'f64')
550
667
  }
551
- inc('__arr_idx')
552
668
  const baseTmp = temp()
553
669
  // Numeric key (literal or known-NUMBER name) → skip __is_str_key dispatch;
554
670
  // arrays don't honor string-key access for numeric keys (keys aren't coerced
@@ -558,11 +674,13 @@ export default (ctx) => {
558
674
  ['local.set', `$${baseTmp}`, ptrExpr],
559
675
  emitDynamicKeyDispatch(typed(['local.get', `$${baseTmp}`], 'f64'), keyExpr => {
560
676
  const keyI32 = asI32(typed(keyExpr, 'f64'))
561
- return (['call', '$__arr_idx', ['local.get', `$${baseTmp}`], keyI32])
677
+ inc('__arr_idx')
678
+ return (['call', '$__arr_idx', ['i64.reinterpret_f64', ['local.get', `$${baseTmp}`]], keyI32])
562
679
  })], 'f64')
680
+ inc('__arr_idx_known')
563
681
  return typed(['block', ['result', 'f64'],
564
682
  ['local.set', `$${baseTmp}`, ptrExpr],
565
- (['call', '$__arr_idx', ['local.get', `$${baseTmp}`], vi])], 'f64')
683
+ (['call', '$__arr_idx_known', ['i64.reinterpret_f64', ['local.get', `$${baseTmp}`]], vi])], 'f64')
566
684
  }
567
685
  // Known string → single-char SSO string
568
686
  if (vt === 'string')
@@ -577,30 +695,26 @@ export default (ctx) => {
577
695
  if (useRuntimeKeyDispatch && !keyIsNum)
578
696
  return emitDynamicKeyDispatch(ptrExpr, keyExpr => {
579
697
  const keyI32 = asI32(typed(keyExpr, 'f64'))
580
- return (['call', '$__typed_idx', ptrExpr, keyI32])
698
+ return (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])
581
699
  })
582
- return typed((['call', '$__typed_idx', ptrExpr, vi]), 'f64')
700
+ return typed((['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], vi]), 'f64')
583
701
  }
584
702
  if (useRuntimeKeyDispatch)
585
703
  return emitDynamicKeyDispatch(ptrExpr, keyExpr => {
586
704
  const keyI32 = asI32(typed(keyExpr, 'f64'))
587
705
  if (ctx.module.modules['string']) {
588
706
  return ['if', ['result', 'f64'],
589
- ['i32.or',
590
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.STRING]],
591
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.SSO]]],
592
- ['then', (inc('__str_idx'), ['call', '$__str_idx', ptrExpr, keyI32])],
593
- ['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])]]
594
710
  }
595
- return (['call', '$__typed_idx', ptrExpr, keyI32])
711
+ return (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])
596
712
  })
597
713
  // Unknown → runtime dispatch (string module loaded → check ptr_type)
598
714
  if (ctx.module.modules['string'])
599
715
  return typed(
600
716
  ['if', ['result', 'f64'],
601
- ['i32.or',
602
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.STRING]],
603
- ['i32.eq', ['call', '$__ptr_type', ptrExpr], ['i32.const', PTR.SSO]]],
717
+ ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ptrExpr]], ['i32.const', PTR.STRING]],
604
718
  ['then', stringLoad()],
605
719
  ['else', arrayLoad]],
606
720
  'f64')
@@ -632,7 +746,7 @@ export default (ctx) => {
632
746
  // dispatch and prologue entirely; on grow we re-extract offset because the
633
747
  // alloc may have relocated the buffer.
634
748
  body.push(
635
- ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]],
749
+ ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
636
750
  ['local.set', `$${len}`,
637
751
  ['i32.load', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 8]]]],
638
752
  ['if',
@@ -640,17 +754,17 @@ export default (ctx) => {
640
754
  ['i32.load', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 4]]],
641
755
  ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]],
642
756
  ['then',
643
- ['local.set', `$${t}`, ['call', `$${grow}`, ['local.get', `$${t}`],
757
+ ['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
644
758
  ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]]],
645
- ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]]]],
759
+ ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]],
646
760
  )
647
761
  } else {
648
762
  body.push(
649
- ['local.set', `$${len}`, ['call', '$__len', ['local.get', `$${t}`]]],
763
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
650
764
  // Grow if needed: ensure cap >= len + vals.length
651
- ['local.set', `$${t}`, ['call', `$${grow}`, ['local.get', `$${t}`],
765
+ ['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
652
766
  ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]]],
653
- ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]],
767
+ ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
654
768
  )
655
769
  }
656
770
 
@@ -666,7 +780,7 @@ export default (ctx) => {
666
780
  }
667
781
 
668
782
  // Update length header, update source variable (pointer may have changed from grow), return new length
669
- 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}`]])
670
784
  // Update the source variable if it's a named variable (so arr still points to valid memory)
671
785
  if (typeof arr === 'string') {
672
786
  if (ctx.func.boxed?.has(arr)) {
@@ -689,34 +803,36 @@ export default (ctx) => {
689
803
  // Known ARRAY → inline len (skips __len dispatch tree).
690
804
  const vt = typeof arr === 'string' ? lookupValType(arr) : valTypeOf(arr)
691
805
  const rawLen = vt === VAL.ARRAY
692
- ? ['i32.load', ['i32.sub', ['call', '$__ptr_offset', ['local.get', `$${t}`]], ['i32.const', 8]]]
693
- : ['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}`]]]
694
808
  return typed(['block', ['result', 'f64'],
695
809
  ['local.set', `$${t}`, va],
696
810
  ['local.set', `$${len}`, ['i32.sub', rawLen, ['i32.const', 1]]],
697
- ['call', '$__set_len', ['local.get', `$${t}`], ['local.get', `$${len}`]],
811
+ ['call', '$__set_len', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['local.get', `$${len}`]],
698
812
  ['f64.load',
699
- ['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')
700
814
  }
701
815
 
702
816
  // .shift() → remove first element, shift remaining left, return removed
703
- 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'))
704
819
 
705
- ctx.core.stdlib['__arr_shift'] = () => `(func $__arr_shift (param $arr f64) (result f64)
706
- (local $bits i64) (local $rawOff i32) (local $off i32) (local $newOff i32) (local $len i32) (local $cap i32) (local $val f64)
707
- (local.set $bits (i64.reinterpret_f64 (local.get $arr)))
708
- (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}))))
709
824
  (local.set $off (call $__ptr_offset (local.get $arr)))
710
825
  (if (result f64) (i32.lt_u (local.get $off) (i32.const 8))
711
- (then (f64.const 0))
826
+ (then (f64.const nan:${UNDEF_NAN}))
712
827
  (else
713
828
  (local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
714
829
  (if (result f64) (i32.le_s (local.get $len) (i32.const 0))
715
- (then (f64.const 0))
830
+ (then (f64.const nan:${UNDEF_NAN}))
716
831
  (else
717
832
  (local.set $val (f64.load (local.get $off)))
718
833
  (local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
719
834
  (local.set $newOff (i32.add (local.get $off) (i32.const 8)))
835
+ ${headerPropsToGlobalIR()}
720
836
  (i32.store (local.get $off) (i32.sub (local.get $len) (i32.const 1)))
721
837
  (i32.store (i32.add (local.get $off) (i32.const 4))
722
838
  (select (i32.sub (local.get $cap) (i32.const 1)) (i32.const 0) (i32.gt_s (local.get $cap) (i32.const 0))))
@@ -741,10 +857,10 @@ export default (ctx) => {
741
857
  const svt = typeof arr === 'string' ? lookupValType(arr) : valTypeOf(arr)
742
858
  const lenInit = svt === VAL.ARRAY
743
859
  ? ['local.set', `$${len}`, ['i32.load', ['i32.sub', ['local.get', `$${off}`], ['i32.const', 8]]]]
744
- : ['local.set', `$${len}`, ['call', '$__len', va]]
860
+ : ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', va]]]
745
861
  const body = [
746
862
  recv.setup,
747
- ['local.set', `$${off}`, ['call', '$__ptr_offset', va]],
863
+ ['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', va]]],
748
864
  lenInit,
749
865
  // clamp start to [0, len]
750
866
  ['local.set', `$${s}`, vs],
@@ -783,26 +899,27 @@ export default (ctx) => {
783
899
  ['i32.sub', ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${s}`]], ['local.get', `$${cnt}`]],
784
900
  ['i32.const', 3]]],
785
901
  // update length
786
- ['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}`]]],
787
903
  out.ptr,
788
904
  ]
789
905
  return typed(['block', ['result', 'f64'], ...body], 'f64')
790
906
  }
791
907
 
792
908
  // .unshift(val) → prepend element, shift existing right
793
- ctx.core.emit['.unshift'] = arrMethod('__arr_unshift', 1)
794
-
795
- ctx.core.stdlib['__arr_unshift'] = `(func $__arr_unshift (param $arr f64) (param $val f64) (result f64)
796
- (local $off i32) (local $len i32)
797
- (local.set $arr (call $__arr_grow (local.get $arr) (i32.add (call $__len (local.get $arr)) (i32.const 1))))
798
- (local.set $off (call $__ptr_offset (local.get $arr)))
799
- (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))))
800
917
  (memory.copy
801
918
  (i32.add (local.get $off) (i32.const 8))
802
919
  (local.get $off)
803
920
  (i32.shl (local.get $len) (i32.const 3)))
804
921
  (f64.store (local.get $off) (local.get $val))
805
- (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)))
806
923
  (f64.convert_i32_s (i32.add (local.get $len) (i32.const 1))))`
807
924
 
808
925
  // .some(fn) → return 1 if any element passes, else 0 (early exit)
@@ -924,7 +1041,7 @@ export default (ctx) => {
924
1041
  const up = detectUpstream(arr)
925
1042
  if (up && up.method === 'filter' && isPureCallback(fn)) {
926
1043
  const recv = hoistArrayValue(up.source)
927
- const count = tempI32('fc'), maxLen = tempI32('fm')
1044
+ const count = tempI32('fc'), maxLen = tempI32('fm'), base = tempI32('fb')
928
1045
  const upReps = callbackArgReps(up.source)
929
1046
  const filterCb = makeCallback(up.fn, upReps), mapCb = makeCallback(fn, upReps)
930
1047
  const out = allocPtr({ type: PTR.ARRAY, len: 0, cap: ['local.get', `$${maxLen}`], tag: 'fm' })
@@ -933,28 +1050,32 @@ export default (ctx) => {
933
1050
  ['then',
934
1051
  elemStore(out.local, count, asF64(mapCb.call([item, idxArg(mapCb, count)]))),
935
1052
  ['local.set', `$${count}`, ['i32.add', ['local.get', `$${count}`], ['i32.const', 1]]]]]
936
- ], maxLen)
1053
+ ], maxLen, base)
1054
+ inc('__ptr_offset')
937
1055
  return typed(['block', ['result', 'f64'],
938
1056
  recv.setup, filterCb.setup, mapCb.setup,
939
- ['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)],
940
1059
  out.init, ['local.set', `$${count}`, ['i32.const', 0]],
941
1060
  ...loop,
942
1061
  ['i32.store', ['i32.sub', ['local.get', `$${out.local}`], ['i32.const', 8]], ['local.get', `$${count}`]],
943
1062
  out.ptr], 'f64')
944
1063
  }
945
1064
  const recv = hoistArrayValue(arr)
946
- const len = tempI32('ml')
1065
+ const len = tempI32('ml'), base = tempI32('mb')
947
1066
  const cb = makeCallback(fn, callbackArgReps(arr))
948
1067
  const lenIR = ['local.get', `$${len}`]
949
1068
  const out = allocPtr({ type: PTR.ARRAY, len: lenIR, tag: 'mo' })
950
1069
  // Reuse the precomputed len local in arrayLoop (skip its internal load).
951
1070
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
952
1071
  elemStore(out.local, i, asF64(cb.call([item, idxArg(cb, i)])))
953
- ], len)
1072
+ ], len, base)
1073
+ inc('__ptr_offset')
954
1074
  return typed(['block', ['result', 'f64'],
955
1075
  recv.setup,
956
1076
  cb.setup,
957
- ['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)],
958
1079
  out.init,
959
1080
  ...loop,
960
1081
  out.ptr], 'f64')
@@ -965,7 +1086,7 @@ export default (ctx) => {
965
1086
  const up = detectUpstream(arr)
966
1087
  if (up && up.method === 'map' && isPureCallback(fn)) {
967
1088
  const recv = hoistArrayValue(up.source)
968
- const count = tempI32('fc'), maxLen = tempI32('fm'), mapped = temp('mv')
1089
+ const count = tempI32('fc'), maxLen = tempI32('fm'), base = tempI32('fb'), mapped = temp('mv')
969
1090
  const upReps = callbackArgReps(up.source)
970
1091
  const mapCb = makeCallback(up.fn, upReps), filterCb = makeCallback(fn)
971
1092
  const out = allocPtr({ type: PTR.ARRAY, len: 0, cap: ['local.get', `$${maxLen}`], tag: 'mf' })
@@ -975,17 +1096,19 @@ export default (ctx) => {
975
1096
  ['then',
976
1097
  ['f64.store', ['i32.add', ['local.get', `$${out.local}`], ['i32.shl', ['local.get', `$${count}`], ['i32.const', 3]]], ['local.get', `$${mapped}`]],
977
1098
  ['local.set', `$${count}`, ['i32.add', ['local.get', `$${count}`], ['i32.const', 1]]]]]
978
- ], maxLen)
1099
+ ], maxLen, base)
1100
+ inc('__ptr_offset')
979
1101
  return typed(['block', ['result', 'f64'],
980
1102
  recv.setup, mapCb.setup, filterCb.setup,
981
- ['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)],
982
1105
  out.init, ['local.set', `$${count}`, ['i32.const', 0]],
983
1106
  ...loop,
984
1107
  ['i32.store', ['i32.sub', ['local.get', `$${out.local}`], ['i32.const', 8]], ['local.get', `$${count}`]],
985
1108
  out.ptr], 'f64')
986
1109
  }
987
1110
  const recv = hoistArrayValue(arr)
988
- const count = tempI32('fc'), maxLen = tempI32('fm')
1111
+ const count = tempI32('fc'), maxLen = tempI32('fm'), base = tempI32('fb')
989
1112
  const cb = makeCallback(fn, callbackArgReps(arr))
990
1113
  const out = allocPtr({ type: PTR.ARRAY, len: 0, cap: ['local.get', `$${maxLen}`], tag: 'fo' })
991
1114
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
@@ -993,11 +1116,13 @@ export default (ctx) => {
993
1116
  ['then',
994
1117
  ['f64.store', ['i32.add', ['local.get', `$${out.local}`], ['i32.shl', ['local.get', `$${count}`], ['i32.const', 3]]], item],
995
1118
  ['local.set', `$${count}`, ['i32.add', ['local.get', `$${count}`], ['i32.const', 1]]]]]
996
- ], maxLen)
1119
+ ], maxLen, base)
1120
+ inc('__ptr_offset')
997
1121
  return typed(['block', ['result', 'f64'],
998
1122
  recv.setup,
999
1123
  cb.setup,
1000
- ['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)],
1001
1126
  out.init,
1002
1127
  ['local.set', `$${count}`, ['i32.const', 0]],
1003
1128
  ...loop,
@@ -1107,13 +1232,139 @@ export default (ctx) => {
1107
1232
  ['local.get', `$${result}`]], 'f64')
1108
1233
  }
1109
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
+
1110
1360
  ctx.core.emit['.indexOf'] = (arr, val) => {
1111
1361
  const recv = hoistArrayValue(arr)
1112
1362
  const vv = asF64(emit(val))
1363
+ const eq = arrEqIR(val)
1113
1364
  const result = tempI32('ix')
1114
1365
  const exit = `$exit${ctx.func.uniq++}`
1115
1366
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
1116
- ['if', ['f64.eq', item, vv],
1367
+ ['if', eq(item, vv),
1117
1368
  ['then', ['local.set', `$${result}`, ['local.get', `$${i}`]], ['br', exit]]]
1118
1369
  ])
1119
1370
  return typed(['block', ['result', 'f64'],
@@ -1126,10 +1377,11 @@ export default (ctx) => {
1126
1377
  ctx.core.emit['.includes'] = (arr, val) => {
1127
1378
  const recv = hoistArrayValue(arr)
1128
1379
  const vv = asF64(emit(val))
1380
+ const eq = arrEqIR(val)
1129
1381
  const result = tempI32('ic')
1130
1382
  const exit = `$exit${ctx.func.uniq++}`
1131
1383
  const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
1132
- ['if', ['f64.eq', item, vv],
1384
+ ['if', eq(item, vv),
1133
1385
  ['then', ['local.set', `$${result}`, ['i32.const', 1]], ['br', exit]]]
1134
1386
  ])
1135
1387
  return typed(['block', ['result', 'f64'],
@@ -1141,6 +1393,19 @@ export default (ctx) => {
1141
1393
 
1142
1394
  // .at(i) → array element with negative index support
1143
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
+ }
1144
1409
  const t = tempI32('ai'), a = temp('aa')
1145
1410
  return typed(['block', ['result', 'f64'],
1146
1411
  ['local.set', `$${a}`, asF64(emit(arr))],
@@ -1148,10 +1413,11 @@ export default (ctx) => {
1148
1413
  // Negative index: t += length
1149
1414
  ['if', ['i32.lt_s', ['local.get', `$${t}`], ['i32.const', 0]],
1150
1415
  ['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`],
1151
- ['call', '$__len', ['local.get', `$${a}`]]]]]],
1152
- ['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}`]]],
1153
1418
  ['i32.shl', ['local.get', `$${t}`], ['i32.const', 3]]]]], 'f64')
1154
1419
  }
1420
+ ctx.core.emit['.at'] = ctx.core.emit['.array:at']
1155
1421
 
1156
1422
  ctx.core.emit['.slice'] = (arr, start, end) => {
1157
1423
  // BUFFER slice → byte-level copy handled in typedarray module.
@@ -1166,7 +1432,7 @@ export default (ctx) => {
1166
1432
  const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${outLen}`], tag: 'so' })
1167
1433
  return typed(['block', ['result', 'f64'],
1168
1434
  recv.setup,
1169
- ['local.set', `$${ptr}`, ['call', '$__ptr_offset', recv.value]],
1435
+ ['local.set', `$${ptr}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', recv.value]]],
1170
1436
  ['local.set', `$${len}`, ['i32.load', ['i32.sub', ['local.get', `$${ptr}`], ['i32.const', 8]]]],
1171
1437
  ['local.set', `$${s}`, rawStart],
1172
1438
  ['if', ['i32.lt_s', ['local.get', `$${s}`], ['i32.const', 0]],
@@ -1199,14 +1465,14 @@ export default (ctx) => {
1199
1465
  // Calculate total length
1200
1466
  const body = [
1201
1467
  recv.setup,
1202
- ['local.set', `$${len}`, ['call', '$__len', va]],
1468
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', va]]],
1203
1469
  ]
1204
1470
 
1205
1471
  const otherVals = []
1206
1472
  for (const other of others) {
1207
1473
  const vo = asF64(emit(other))
1208
1474
  otherVals.push(vo)
1209
- 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]]]])
1210
1476
  }
1211
1477
 
1212
1478
  body.push(out.init)
@@ -1215,8 +1481,8 @@ export default (ctx) => {
1215
1481
  const srcOff = tempI32('co')
1216
1482
  body.push(
1217
1483
  ['local.set', `$${pos}`, ['i32.const', 0]],
1218
- ['local.set', `$${len}`, ['call', '$__len', va]],
1219
- ['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]]]
1220
1486
  )
1221
1487
  const id = ctx.func.uniq++
1222
1488
  body.push(
@@ -1231,7 +1497,7 @@ export default (ctx) => {
1231
1497
 
1232
1498
  // Copy each other array
1233
1499
  const offset = tempI32('off')
1234
- body.push(['local.set', `$${offset}`, ['call', '$__len', va]])
1500
+ body.push(['local.set', `$${offset}`, ['call', '$__len', ['i64.reinterpret_f64', va]]])
1235
1501
 
1236
1502
  const otherOff = tempI32('co2')
1237
1503
  for (let i = 0; i < otherVals.length; i++) {
@@ -1239,8 +1505,8 @@ export default (ctx) => {
1239
1505
  const id2 = ctx.func.uniq++
1240
1506
  body.push(
1241
1507
  ['local.set', `$${pos}`, ['i32.const', 0]],
1242
- ['local.set', `$${len}`, ['call', '$__len', vo]],
1243
- ['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]]],
1244
1510
  ['block', `$done${id2}`, ['loop', `$loop${id2}`,
1245
1511
  ['br_if', `$done${id2}`, ['i32.ge_s', ['local.get', `$${pos}`], ['local.get', `$${len}`]]],
1246
1512
  ['f64.store',
@@ -1257,7 +1523,7 @@ export default (ctx) => {
1257
1523
  }
1258
1524
 
1259
1525
  // .flat() → flatten one level of nested arrays
1260
- 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)
1261
1527
  (local $len i32) (local $off i32) (local $i i32) (local $total i32) (local $dst i32) (local $pos i32)
1262
1528
  (local $elem f64) (local $subLen i32) (local $subOff i32) (local $j i32)
1263
1529
  (local.set $off (call $__ptr_offset (local.get $src)))
@@ -1268,8 +1534,8 @@ export default (ctx) => {
1268
1534
  (br_if $c1 (i32.ge_s (local.get $i) (local.get $len)))
1269
1535
  (local.set $elem (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
1270
1536
  (if (i32.and (f64.ne (local.get $elem) (local.get $elem))
1271
- (i32.eq (call $__ptr_type (local.get $elem)) (i32.const ${PTR.ARRAY})))
1272
- (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))))))
1273
1539
  (else (local.set $total (i32.add (local.get $total) (i32.const 1)))))
1274
1540
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
1275
1541
  (br $cl1)))
@@ -1284,10 +1550,10 @@ export default (ctx) => {
1284
1550
  (br_if $c2 (i32.ge_s (local.get $i) (local.get $len)))
1285
1551
  (local.set $elem (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
1286
1552
  (if (i32.and (f64.ne (local.get $elem) (local.get $elem))
1287
- (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})))
1288
1554
  (then
1289
- (local.set $subOff (call $__ptr_offset (local.get $elem)))
1290
- (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))))
1291
1557
  (local.set $j (i32.const 0))
1292
1558
  (block $s (loop $sl
1293
1559
  (br_if $s (i32.ge_s (local.get $j) (local.get $subLen)))
@@ -1303,15 +1569,17 @@ export default (ctx) => {
1303
1569
  (br $cl2)))
1304
1570
  (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $dst)))`
1305
1571
 
1306
- 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'))
1307
1574
 
1308
1575
  // .flatMap(fn) → map then flatten
1309
1576
  ctx.core.emit['.flatMap'] = (arr, fn) => {
1310
1577
  const mapped = ctx.core.emit['.map'](arr, fn)
1311
1578
  inc('__arr_flat')
1312
- return typed(['call', '$__arr_flat', asF64(mapped)], 'f64')
1579
+ return typed(['call', '$__arr_flat', asI64(mapped)], 'f64')
1313
1580
  }
1314
1581
 
1315
1582
  // .join(sep) → concatenate array elements with separator string
1316
- 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'))
1317
1585
  }