jz 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +315 -318
  2. package/bench/README.md +369 -0
  3. package/bench/bench.svg +102 -0
  4. package/cli.js +104 -30
  5. package/dist/interop.js +1 -0
  6. package/dist/jz.js +6024 -0
  7. package/index.d.ts +126 -0
  8. package/index.js +362 -84
  9. package/interop.js +159 -186
  10. package/jz.svg +5 -0
  11. package/jzify/arguments.js +97 -0
  12. package/jzify/bundler.js +382 -0
  13. package/jzify/classes.js +328 -0
  14. package/jzify/hoist-vars.js +177 -0
  15. package/jzify/index.js +51 -0
  16. package/jzify/names.js +37 -0
  17. package/jzify/switch.js +106 -0
  18. package/jzify/transform.js +349 -0
  19. package/layout.js +184 -0
  20. package/module/array.js +377 -176
  21. package/module/collection.js +639 -145
  22. package/module/console.js +55 -43
  23. package/module/core.js +264 -153
  24. package/module/date.js +15 -3
  25. package/module/function.js +73 -6
  26. package/module/index.js +2 -1
  27. package/module/json.js +226 -61
  28. package/module/math.js +551 -187
  29. package/module/number.js +327 -60
  30. package/module/object.js +474 -184
  31. package/module/regex.js +255 -25
  32. package/module/schema.js +24 -6
  33. package/module/simd.js +117 -0
  34. package/module/string.js +621 -226
  35. package/module/symbol.js +1 -1
  36. package/module/timer.js +9 -14
  37. package/module/typedarray.js +459 -73
  38. package/package.json +58 -14
  39. package/src/abi/index.js +39 -23
  40. package/src/abi/string.js +38 -41
  41. package/src/ast.js +460 -0
  42. package/src/autoload.js +29 -24
  43. package/src/bridge.js +111 -0
  44. package/src/compile/analyze-scans.js +824 -0
  45. package/src/compile/analyze.js +1600 -0
  46. package/src/compile/emit-assign.js +411 -0
  47. package/src/compile/emit.js +3512 -0
  48. package/src/compile/flow-types.js +103 -0
  49. package/src/{compile.js → compile/index.js} +778 -128
  50. package/src/{infer.js → compile/infer.js} +62 -98
  51. package/src/compile/loop-divmod.js +155 -0
  52. package/src/{narrow.js → compile/narrow.js} +409 -106
  53. package/src/compile/peel-stencil.js +261 -0
  54. package/src/compile/plan/advise.js +370 -0
  55. package/src/compile/plan/common.js +150 -0
  56. package/src/compile/plan/index.js +122 -0
  57. package/src/compile/plan/inline.js +682 -0
  58. package/src/compile/plan/literals.js +1199 -0
  59. package/src/compile/plan/loops.js +472 -0
  60. package/src/compile/plan/scope.js +649 -0
  61. package/src/compile/program-facts.js +423 -0
  62. package/src/ctx.js +220 -64
  63. package/src/ir.js +589 -172
  64. package/src/kind-traits.js +132 -0
  65. package/src/kind.js +524 -0
  66. package/src/op-policy.js +57 -0
  67. package/src/{optimize.js → optimize/index.js} +1432 -473
  68. package/src/optimize/vectorize.js +4660 -0
  69. package/src/param-reps.js +65 -0
  70. package/src/parse.js +44 -0
  71. package/src/{prepare.js → prepare/index.js} +649 -205
  72. package/src/prepare/lift-iife.js +149 -0
  73. package/src/reps.js +116 -0
  74. package/src/resolve.js +12 -3
  75. package/src/static.js +208 -0
  76. package/src/type.js +651 -0
  77. package/src/{assemble.js → wat/assemble.js} +275 -55
  78. package/src/wat/optimize.js +3938 -0
  79. package/transform.js +21 -0
  80. package/wasi.js +47 -5
  81. package/src/analyze.js +0 -3818
  82. package/src/emit.js +0 -3040
  83. package/src/jzify.js +0 -1580
  84. package/src/plan.js +0 -2132
  85. package/src/vectorize.js +0 -1088
  86. /package/src/{codegen.js → wat/codegen.js} +0 -0
@@ -7,23 +7,17 @@
7
7
  * @module typed
8
8
  */
9
9
 
10
- import { typed, asF64, asI32, asI64, toNumF64, UNDEF_NAN, allocPtr, mkPtrIR, ptrOffsetIR, temp, tempI32, tempI64, undefExpr, truthyIR } from '../src/ir.js'
11
- import { emit } from '../src/emit.js'
12
- import { valTypeOf, lookupValType, VAL } from '../src/analyze.js'
13
- import { inc, PTR } from '../src/ctx.js'
14
-
15
-
16
- // Element types and their byte sizes
17
- const ELEM = {
18
- Int8Array: 0, Uint8Array: 1,
19
- Int16Array: 2, Uint16Array: 3,
20
- Int32Array: 4, Uint32Array: 5,
21
- Float32Array: 6, Float64Array: 7,
22
- BigInt64Array: 7, BigUint64Array: 7,
23
- }
24
- const BIGINT_ELEM_FLAG = 16
25
- const typedAux = (name, isView = false) => ELEM[name] | (isView ? 8 : 0) |
26
- (name === 'BigInt64Array' || name === 'BigUint64Array' ? BIGINT_ELEM_FLAG : 0)
10
+ import { typed, asF64, asI32, asI64, toNumF64, UNDEF_NAN, allocPtr, mkPtrIR, ptrOffsetIR, ptrTypeEq, temp, tempI32, tempI64, undefExpr, truthyIR } from '../src/ir.js'
11
+ import { emit, idx, deps, call } from '../src/bridge.js'
12
+ import { valTypeOf } from '../src/kind.js'
13
+ import { VAL, lookupValType } from '../src/reps.js'
14
+ import { nanPrefixHex, TYPED_ELEM_NAMES, TYPED_ELEM_CODE, TYPED_ELEM_BIGINT_FLAG, encodeTypedElemAux } from '../layout.js'
15
+ import { inc, PTR, LAYOUT, getter } from '../src/ctx.js'
16
+
17
+ const _NAN_BITS = nanPrefixHex()
18
+
19
+
20
+ const typedAux = (name, isView = false) => encodeTypedElemAux(name, isView)
27
21
  const STRIDE = [1, 1, 2, 2, 4, 4, 4, 8]
28
22
  const SHIFT = [0, 0, 1, 1, 2, 2, 2, 3]
29
23
  const LOAD = [
@@ -202,11 +196,19 @@ function genSimdMap(name, elemType, pattern) {
202
196
 
203
197
 
204
198
  export default (ctx) => {
205
- Object.assign(ctx.core.stdlibDeps, {
199
+ deps({
206
200
  __byte_length: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
207
201
  __byte_offset: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
208
202
  __to_buffer: ['__ptr_type', '__ptr_offset', '__ptr_aux', '__mkptr'],
209
203
  __typed_set_idx: ['__ptr_aux', '__ptr_offset'],
204
+ __typed_get_idx: ['__ptr_aux', '__ptr_offset'],
205
+ __typed_fill: ['__len', '__typed_set_idx'],
206
+ __typed_reverse: ['__len', '__typed_get_idx', '__typed_set_idx'],
207
+ __typed_copyWithin: ['__len', '__typed_get_idx', '__typed_set_idx'],
208
+ __typed_sort: ['__len', '__typed_get_idx', '__typed_set_idx'],
209
+ // __str_join uses __typed_idx when typedarray is loaded (plain arrays promoted to
210
+ // Int32Array by promoteIntArrayLiterals can produce PTR.TYPED results via .map()).
211
+ __str_join: [...(ctx.core.stdlibDeps.__str_join ?? []), '__typed_idx'],
210
212
  })
211
213
 
212
214
  // .map invokes with arity 1; .forEach/.find/.some/.every/.filter/.findIndex
@@ -217,6 +219,10 @@ export default (ctx) => {
217
219
 
218
220
  inc('__mkptr', '__alloc', '__len')
219
221
 
222
+ const buf = call('__to_buffer', 'I')
223
+ const blen = call('__byte_length', 'I', 'i32')
224
+ const boff = call('__byte_offset', 'I', 'i32')
225
+
220
226
  // === Runtime helpers: byte length, buffer coerce ===
221
227
  // __typed_shift lives in core (needed by __len/__cap).
222
228
 
@@ -261,7 +267,7 @@ export default (ctx) => {
261
267
  (else (call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0) (local.get $off)))))))`
262
268
 
263
269
  // Constructor: new Float64Array(len) | new F64Array(arr) | new F64Array(buf) | new F64Array(buf, off, len)
264
- for (const [name, elemType] of Object.entries(ELEM)) {
270
+ for (const [name, elemType] of Object.entries(TYPED_ELEM_CODE)) {
265
271
  const aux = typedAux(name)
266
272
  const stride = STRIDE[elemType]
267
273
  ctx.core.emit[`new.${name}`] = (lenExpr, offsetExpr, lenExpr2) => {
@@ -317,7 +323,7 @@ export default (ctx) => {
317
323
  numAlloc.ptr]],
318
324
  // Pointer: array → copy elements; buffer/typed → zero-copy view on same offset
319
325
  ['else', ['if', ['result', 'f64'],
320
- ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${src}`]]], ['i32.const', PTR.ARRAY]],
326
+ ptrTypeEq(['local.get', `$${src}`], PTR.ARRAY),
321
327
  ['then', ctx.core.emit[`${name}.from`](src)],
322
328
  ['else', mkPtrIR(PTR.TYPED, aux,
323
329
  ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${src}`]]])]]]]], 'f64')
@@ -404,14 +410,14 @@ export default (ctx) => {
404
410
  // .buffer — always aliased (zero-copy). BUFFER: passthrough.
405
411
  // Owned TYPED: retag as BUFFER at same offset — the byteLen header is shared.
406
412
  // TYPED view (incl. DataView): BUFFER at descriptor[8] (root parent data offset).
407
- ctx.core.emit['.buffer'] = (obj) => {
413
+ ctx.core.emit['.buffer'] = getter((obj) => {
408
414
  if (typeof obj === 'string') {
409
415
  const ctor = ctx.types.typedElem?.get(obj)
410
416
  if (ctor === 'new.ArrayBuffer') return asF64(emit(obj))
411
417
  if (ctor?.startsWith('new.')) {
412
418
  const isView = ctor.endsWith('.view')
413
419
  const name = isView ? ctor.slice(4, -5) : ctor.slice(4)
414
- if (ELEM[name] != null) {
420
+ if (TYPED_ELEM_CODE[name] != null) {
415
421
  const parentOff = isView
416
422
  ? ['i32.load', ['i32.add', ptrOffsetIR(emit(obj), VAL.TYPED), ['i32.const', 8]]]
417
423
  : ptrOffsetIR(emit(obj), VAL.TYPED)
@@ -419,13 +425,12 @@ export default (ctx) => {
419
425
  }
420
426
  }
421
427
  }
422
- inc('__to_buffer')
423
- return typed(['call', '$__to_buffer', asI64(emit(obj))], 'f64')
424
- }
428
+ return buf(obj)
429
+ })
425
430
 
426
431
  // .byteLength — BUFFER: raw __len. Owned TYPED: elemCount * stride.
427
432
  // View TYPED (incl. DataView): descriptor[0], via the __byte_length fallback.
428
- ctx.core.emit['.byteLength'] = (obj) => {
433
+ ctx.core.emit['.byteLength'] = getter((obj) => {
429
434
  if (typeof obj === 'string') {
430
435
  const ctor = ctx.types.typedElem?.get(obj)
431
436
  if (ctor === 'new.ArrayBuffer') {
@@ -434,7 +439,7 @@ export default (ctx) => {
434
439
  if (ctor && ctor.startsWith('new.')) {
435
440
  const isView = ctor.endsWith('.view')
436
441
  const name = isView ? ctor.slice(4, -5) : ctor.slice(4)
437
- const et = ELEM[name]
442
+ const et = TYPED_ELEM_CODE[name]
438
443
  if (et != null) {
439
444
  if (isView) {
440
445
  return typed(['f64.convert_i32_s',
@@ -445,12 +450,11 @@ export default (ctx) => {
445
450
  }
446
451
  }
447
452
  }
448
- inc('__byte_length')
449
- return typed(['f64.convert_i32_s', ['call', '$__byte_length', asI64(emit(obj))]], 'f64')
450
- }
453
+ return blen(obj)
454
+ })
451
455
 
452
456
  // .byteOffset — owned: 0. View: descriptor[4] - descriptor[8].
453
- ctx.core.emit['.byteOffset'] = (obj) => {
457
+ ctx.core.emit['.byteOffset'] = getter((obj) => {
454
458
  if (typeof obj === 'string') {
455
459
  const ctor = ctx.types.typedElem?.get(obj)
456
460
  if (ctor?.endsWith('.view')) {
@@ -462,11 +466,10 @@ export default (ctx) => {
462
466
  ['i32.load', ['i32.add', ['local.get', `$${t}`], ['i32.const', 4]]],
463
467
  ['i32.load', ['i32.add', ['local.get', `$${t}`], ['i32.const', 8]]]]]], 'f64')
464
468
  }
465
- if (ctor?.startsWith('new.') && ELEM[ctor.slice(4)] != null) return typed(['f64.const', 0], 'f64')
469
+ if (ctor?.startsWith('new.') && TYPED_ELEM_CODE[ctor.slice(4)] != null) return typed(['f64.const', 0], 'f64')
466
470
  }
467
- inc('__byte_offset')
468
- return typed(['f64.convert_i32_s', ['call', '$__byte_offset', asI64(emit(obj))]], 'f64')
469
- }
471
+ return boff(obj)
472
+ })
470
473
 
471
474
  // Runtime fallback for .byteOffset when variable view-ness is unknown.
472
475
  ctx.core.stdlib['__byte_offset'] = `(func $__byte_offset (param $ptr i64) (result i32)
@@ -488,8 +491,7 @@ export default (ctx) => {
488
491
  ctx.core.emit['ArrayBuffer.isView'] = (v) => {
489
492
  if (v === undefined) return typed(['f64.const', 0], 'f64')
490
493
  const va = asF64(emit(v))
491
- return typed(['f64.convert_i32_s',
492
- ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', va]], ['i32.const', PTR.TYPED]]], 'f64')
494
+ return typed(['f64.convert_i32_s', ptrTypeEq(va, PTR.TYPED)], 'f64')
493
495
  }
494
496
 
495
497
  // x instanceof Float64Array | Int32Array | … — typed-pointer predicate emitted
@@ -502,7 +504,7 @@ export default (ctx) => {
502
504
  const t = temp('ityp')
503
505
  return typed(['i32.and',
504
506
  ['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
505
- ['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.TYPED]]], 'i32')
507
+ ptrTypeEq(['local.get', `$${t}`], PTR.TYPED)], 'i32')
506
508
  }
507
509
 
508
510
  // buf.slice(begin?, end?) on a BUFFER → fresh BUFFER with the byte range copied.
@@ -598,7 +600,7 @@ export default (ctx) => {
598
600
  // (cheap: 4 wasm ops) so `getFloat32`/`getFloat64` return a real-spec NaN value.
599
601
  ctx.core.stdlib['__canon_nan'] = `(func $__canon_nan (param $v f64) (result f64)
600
602
  (select
601
- (f64.reinterpret_i64 (i64.const 0x7FF8000000000000))
603
+ (f64.reinterpret_i64 (i64.const ${_NAN_BITS}))
602
604
  (local.get $v)
603
605
  (f64.ne (local.get $v) (local.get $v))))`
604
606
  const canonNaN = (vIR) => { inc('__canon_nan'); return typed(['call', '$__canon_nan', vIR], 'f64') }
@@ -799,7 +801,7 @@ export default (ctx) => {
799
801
  }
800
802
 
801
803
  // TypedArray.from(arr) — convert regular array to typed array
802
- for (const [name, elemType] of Object.entries(ELEM)) {
804
+ for (const [name, elemType] of Object.entries(TYPED_ELEM_CODE)) {
803
805
  const aux = typedAux(name)
804
806
  const stride = STRIDE[elemType], store = STORE[elemType]
805
807
  ctx.core.emit[`${name}.from`] = (src) => {
@@ -845,20 +847,23 @@ export default (ctx) => {
845
847
  * at the chain output (the result is always an owned typed array). */
846
848
  const TYPED_CHAIN_METHODS = new Set(['map', 'filter', 'slice'])
847
849
  const resolveElem = (arr) => {
848
- let receiver = arr, chainOutput = false
850
+ let receiver = arr, chainOutput = false, viewOutput = false
849
851
  // Walk method-call chain inward. `arr.method(...)` parses as
850
- // ['()', ['.', recv, 'method'], ...args] — peel until we hit a name.
852
+ // ['()', ['.', recv, 'method'], ...args] — peel until we hit a name. The OUTERMOST
853
+ // op decides view-ness: `.subarray(...)` yields a zero-copy VIEW (reads must indirect
854
+ // through the descriptor), whereas `.map`/`.slice`/… yield a fresh non-view copy.
851
855
  while (Array.isArray(receiver) && receiver[0] === '()' &&
852
856
  Array.isArray(receiver[1]) && receiver[1][0] === '.' &&
853
- TYPED_CHAIN_METHODS.has(receiver[1][2])) {
857
+ (TYPED_CHAIN_METHODS.has(receiver[1][2]) || receiver[1][2] === 'subarray')) {
858
+ if (!chainOutput) viewOutput = receiver[1][2] === 'subarray'
854
859
  receiver = receiver[1][1]
855
860
  chainOutput = true
856
861
  }
857
862
  const ctor = typeof receiver === 'string' && ctx.types.typedElem?.get(receiver)
858
863
  if (!ctor) return null
859
- const isView = !chainOutput && ctor.endsWith('.view')
864
+ const isView = viewOutput || (!chainOutput && ctor.endsWith('.view'))
860
865
  const name = ctor.endsWith('.view') ? ctor.slice(4, -5) : ctor.slice(4)
861
- const et = ELEM[name]
866
+ const et = TYPED_ELEM_CODE[name]
862
867
  return et == null ? null : { et, isView, isBigInt: name === 'BigInt64Array' || name === 'BigUint64Array' }
863
868
  }
864
869
 
@@ -866,9 +871,19 @@ export default (ctx) => {
866
871
  * Owned: low 32 bits of the NaN-box (or the unboxed local directly).
867
872
  * View: load descriptor[4]. Uses ptrOffsetIR so unboxed-TYPED locals pass through
868
873
  * without a rebox-then-unbox round trip, and globals fold to inline bit-extract. */
874
+ // A typed array (Float64Array/Int32Array/…) is a FIXED-SIZE allocation — it has no
875
+ // grow op, so it can never relocate, so its base needs no realloc-forwarding follow.
876
+ // An already-unboxed pointer is the offset itself; a boxed f64 pointer extracts its
877
+ // low-32 offset directly — no __ptr_offset call. (The general ptrOffsetIR keeps the
878
+ // forwarding follow because ARRAY/HASH/SET/MAP relocate and an *inferred* OBJECT can
879
+ // alias a relocated ARRAY — but VAL.TYPED is a narrow type that can only be a real
880
+ // typed array, so the follow is provably dead here.)
881
+ const typedBase = (objIR) => objIR.ptrKind != null && objIR.ptrKind !== VAL.ARRAY
882
+ ? objIR
883
+ : ['i32.wrap_i64', ['i64.and', ['i64.reinterpret_f64', asF64(objIR)], ['i64.const', LAYOUT.OFFSET_MASK]]]
869
884
  const typedDataAddr = (objIR, isView) => isView
870
- ? ['i32.load', ['i32.add', ptrOffsetIR(objIR, VAL.TYPED), ['i32.const', 4]]]
871
- : ptrOffsetIR(objIR, VAL.TYPED)
885
+ ? ['i32.load', ['i32.add', typedBase(objIR), ['i32.const', 4]]]
886
+ : typedBase(objIR)
872
887
 
873
888
  // Runtime-dispatch typed index: checks ptr_type + aux to load with correct stride.
874
889
  // For TYPED views (aux bit 3), $off indirects through descriptor[4] to real data.
@@ -913,7 +928,7 @@ export default (ctx) => {
913
928
  (local.set $et (i32.and (local.get $aux) (i32.const 7)))
914
929
  (if (result f64) (i32.ge_u (local.get $et) (i32.const 6))
915
930
  (then (if (result f64) (i32.eq (local.get $et) (i32.const 7))
916
- (then (if (result f64) (i32.and (local.get $aux) (i32.const ${BIGINT_ELEM_FLAG}))
931
+ (then (if (result f64) (i32.and (local.get $aux) (i32.const ${TYPED_ELEM_BIGINT_FLAG}))
917
932
  (then (f64.reinterpret_i64 (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
918
933
  (else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))
919
934
  (else (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
@@ -938,7 +953,7 @@ export default (ctx) => {
938
953
  (if (i32.ne (i32.and (local.get $aux) (i32.const 8)) (i32.const 0))
939
954
  (then (local.set $off (i32.load (i32.add (local.get $off) (i32.const 4))))))
940
955
  (local.set $et (i32.and (local.get $aux) (i32.const 7)))
941
- (if (i32.and (local.get $aux) (i32.const ${BIGINT_ELEM_FLAG}))
956
+ (if (i32.and (local.get $aux) (i32.const ${TYPED_ELEM_BIGINT_FLAG}))
942
957
  (then (i64.store (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))) (i64.reinterpret_f64 (local.get $v))))
943
958
  (else
944
959
  (if (i32.eq (local.get $et) (i32.const 7))
@@ -959,12 +974,225 @@ export default (ctx) => {
959
974
  (else (i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $bits))))))))))))
960
975
  (local.get $v))`
961
976
 
977
+ // .fill(value, start?, end?) for typed arrays. The plain-array __arr_fill gates
978
+ // on PTR.ARRAY and silently no-ops a typed receiver (the storage layout and
979
+ // element width differ); this loops the element-width-aware __typed_set_idx
980
+ // over the clamped range so every element kind (u8…f64, BigInt) fills correctly.
981
+ // start/end default 0/length, accept negatives, and clamp to [0, length].
982
+ ctx.core.stdlib['__typed_fill'] = `(func $__typed_fill (param $ptr i64) (param $val f64) (param $start i32) (param $end i32) (result f64)
983
+ (local $len i32) (local $i i32)
984
+ (local.set $len (call $__len (local.get $ptr)))
985
+ (if (i32.lt_s (local.get $start) (i32.const 0)) (then (local.set $start (i32.add (local.get $len) (local.get $start)))))
986
+ (if (i32.lt_s (local.get $start) (i32.const 0)) (then (local.set $start (i32.const 0))))
987
+ (if (i32.gt_s (local.get $start) (local.get $len)) (then (local.set $start (local.get $len))))
988
+ (if (i32.lt_s (local.get $end) (i32.const 0)) (then (local.set $end (i32.add (local.get $len) (local.get $end)))))
989
+ (if (i32.lt_s (local.get $end) (i32.const 0)) (then (local.set $end (i32.const 0))))
990
+ (if (i32.gt_s (local.get $end) (local.get $len)) (then (local.set $end (local.get $len))))
991
+ (local.set $i (local.get $start))
992
+ (block $done (loop $fill
993
+ (br_if $done (i32.ge_s (local.get $i) (local.get $end)))
994
+ (drop (call $__typed_set_idx (local.get $ptr) (local.get $i) (local.get $val)))
995
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
996
+ (br $fill)))
997
+ (f64.reinterpret_i64 (local.get $ptr)))`
998
+
999
+ // Element-width/-kind-aware read: arr[i] → f64, the read mirror of __typed_set_idx.
1000
+ // Integers convert by signedness (et&1 ⇒ unsigned); BigInt returns the raw i64 bits
1001
+ // reinterpreted as f64 so a get→set roundtrip is bit-exact (set_idx stores the bits
1002
+ // back unchanged). Used by the in-place algorithms below for random-access reads.
1003
+ ctx.core.stdlib['__typed_get_idx'] = `(func $__typed_get_idx (param $ptr i64) (param $i i32) (result f64)
1004
+ (local $off i32) (local $aux i32) (local $et i32)
1005
+ (local.set $aux (call $__ptr_aux (local.get $ptr)))
1006
+ (local.set $off (call $__ptr_offset (local.get $ptr)))
1007
+ (if (i32.and (local.get $aux) (i32.const 8))
1008
+ (then (local.set $off (i32.load (i32.add (local.get $off) (i32.const 4))))))
1009
+ (local.set $et (i32.and (local.get $aux) (i32.const 7)))
1010
+ (if (result f64) (i32.and (local.get $aux) (i32.const ${TYPED_ELEM_BIGINT_FLAG}))
1011
+ (then (f64.reinterpret_i64 (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
1012
+ (else (if (result f64) (i32.eq (local.get $et) (i32.const 7))
1013
+ (then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
1014
+ (else (if (result f64) (i32.eq (local.get $et) (i32.const 6))
1015
+ (then (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
1016
+ (else (if (result f64) (i32.ge_u (local.get $et) (i32.const 4))
1017
+ (then (if (result f64) (i32.and (local.get $et) (i32.const 1))
1018
+ (then (f64.convert_i32_u (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
1019
+ (else (f64.convert_i32_s (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
1020
+ (else (if (result f64) (i32.ge_u (local.get $et) (i32.const 2))
1021
+ (then (if (result f64) (i32.and (local.get $et) (i32.const 1))
1022
+ (then (f64.convert_i32_u (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
1023
+ (else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))))
1024
+ (else (if (result f64) (i32.and (local.get $et) (i32.const 1))
1025
+ (then (f64.convert_i32_u (i32.load8_u (i32.add (local.get $off) (local.get $i)))))
1026
+ (else (f64.convert_i32_s (i32.load8_s (i32.add (local.get $off) (local.get $i)))))))))))))))))`
1027
+
1028
+ // .reverse() — in-place, element-kind-agnostic via get/set (bit-exact for BigInt).
1029
+ ctx.core.stdlib['__typed_reverse'] = `(func $__typed_reverse (param $ptr i64) (result f64)
1030
+ (local $len i32) (local $i i32) (local $j i32) (local $t f64)
1031
+ (local.set $len (call $__len (local.get $ptr)))
1032
+ (local.set $i (i32.const 0))
1033
+ (local.set $j (i32.sub (local.get $len) (i32.const 1)))
1034
+ (block $done (loop $rev
1035
+ (br_if $done (i32.ge_s (local.get $i) (local.get $j)))
1036
+ (local.set $t (call $__typed_get_idx (local.get $ptr) (local.get $i)))
1037
+ (drop (call $__typed_set_idx (local.get $ptr) (local.get $i) (call $__typed_get_idx (local.get $ptr) (local.get $j))))
1038
+ (drop (call $__typed_set_idx (local.get $ptr) (local.get $j) (local.get $t)))
1039
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1040
+ (local.set $j (i32.sub (local.get $j) (i32.const 1)))
1041
+ (br $rev)))
1042
+ (f64.reinterpret_i64 (local.get $ptr)))`
1043
+
1044
+ // .copyWithin(target, start, end) — in-place overlap-safe move. Indices accept
1045
+ // negatives (from end) and clamp to [0, len]; count = min(end-start, len-target).
1046
+ // Direction picked so overlapping ranges don't clobber unread source elements.
1047
+ ctx.core.stdlib['__typed_copyWithin'] = `(func $__typed_copyWithin (param $ptr i64) (param $target i32) (param $start i32) (param $end i32) (result f64)
1048
+ (local $len i32) (local $count i32) (local $k i32)
1049
+ (local.set $len (call $__len (local.get $ptr)))
1050
+ (if (i32.lt_s (local.get $target) (i32.const 0)) (then (local.set $target (i32.add (local.get $len) (local.get $target)))))
1051
+ (if (i32.lt_s (local.get $target) (i32.const 0)) (then (local.set $target (i32.const 0))))
1052
+ (if (i32.gt_s (local.get $target) (local.get $len)) (then (local.set $target (local.get $len))))
1053
+ (if (i32.lt_s (local.get $start) (i32.const 0)) (then (local.set $start (i32.add (local.get $len) (local.get $start)))))
1054
+ (if (i32.lt_s (local.get $start) (i32.const 0)) (then (local.set $start (i32.const 0))))
1055
+ (if (i32.gt_s (local.get $start) (local.get $len)) (then (local.set $start (local.get $len))))
1056
+ (if (i32.lt_s (local.get $end) (i32.const 0)) (then (local.set $end (i32.add (local.get $len) (local.get $end)))))
1057
+ (if (i32.lt_s (local.get $end) (i32.const 0)) (then (local.set $end (i32.const 0))))
1058
+ (if (i32.gt_s (local.get $end) (local.get $len)) (then (local.set $end (local.get $len))))
1059
+ (local.set $count (i32.sub (local.get $end) (local.get $start)))
1060
+ (if (i32.gt_s (local.get $count) (i32.sub (local.get $len) (local.get $target)))
1061
+ (then (local.set $count (i32.sub (local.get $len) (local.get $target)))))
1062
+ (if (i32.lt_s (local.get $target) (local.get $start))
1063
+ (then
1064
+ (local.set $k (i32.const 0))
1065
+ (block $fd (loop $fl
1066
+ (br_if $fd (i32.ge_s (local.get $k) (local.get $count)))
1067
+ (drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $target) (local.get $k))
1068
+ (call $__typed_get_idx (local.get $ptr) (i32.add (local.get $start) (local.get $k)))))
1069
+ (local.set $k (i32.add (local.get $k) (i32.const 1)))
1070
+ (br $fl))))
1071
+ (else
1072
+ (local.set $k (local.get $count))
1073
+ (block $bd (loop $bl
1074
+ (br_if $bd (i32.le_s (local.get $k) (i32.const 0)))
1075
+ (local.set $k (i32.sub (local.get $k) (i32.const 1)))
1076
+ (drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $target) (local.get $k))
1077
+ (call $__typed_get_idx (local.get $ptr) (i32.add (local.get $start) (local.get $k)))))
1078
+ (br $bl)))))
1079
+ (f64.reinterpret_i64 (local.get $ptr)))`
1080
+
1081
+ // .sort() — default numeric order (insertion sort, stable). NaN sorts to the end,
1082
+ // -0 before +0 (the equal-value tiebreak via signed-bit compare). BigInt arrays are
1083
+ // compared as signed i64 on their exact bits. A user comparator is handled inline by
1084
+ // the .typed:sort emitter (this helper is the no-argument numeric path).
1085
+ ctx.core.stdlib['__typed_sort'] = `(func $__typed_sort (param $ptr i64) (result f64)
1086
+ (local $isbig i32) (local $len i32) (local $i i32) (local $j i32) (local $saved f64) (local $nb f64)
1087
+ (local.set $isbig (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const ${TYPED_ELEM_BIGINT_FLAG})))
1088
+ (local.set $len (call $__len (local.get $ptr)))
1089
+ (local.set $i (i32.const 1))
1090
+ (block $od (loop $ol
1091
+ (br_if $od (i32.ge_s (local.get $i) (local.get $len)))
1092
+ (local.set $saved (call $__typed_get_idx (local.get $ptr) (local.get $i)))
1093
+ (local.set $j (i32.sub (local.get $i) (i32.const 1)))
1094
+ (block $id (loop $il
1095
+ (br_if $id (i32.lt_s (local.get $j) (i32.const 0)))
1096
+ (local.set $nb (call $__typed_get_idx (local.get $ptr) (local.get $j)))
1097
+ (br_if $id (i32.eqz
1098
+ (if (result i32) (local.get $isbig)
1099
+ (then (i64.gt_s (i64.reinterpret_f64 (local.get $nb)) (i64.reinterpret_f64 (local.get $saved))))
1100
+ (else (if (result i32) (f64.ne (local.get $nb) (local.get $nb))
1101
+ (then (i32.eqz (f64.ne (local.get $saved) (local.get $saved))))
1102
+ (else (if (result i32) (f64.ne (local.get $saved) (local.get $saved))
1103
+ (then (i32.const 0))
1104
+ (else (if (result i32) (f64.gt (local.get $nb) (local.get $saved))
1105
+ (then (i32.const 1))
1106
+ (else (if (result i32) (f64.lt (local.get $nb) (local.get $saved))
1107
+ (then (i32.const 0))
1108
+ (else (i64.gt_s (i64.reinterpret_f64 (local.get $nb)) (i64.reinterpret_f64 (local.get $saved)))))))))))))))
1109
+ (drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $j) (i32.const 1)) (local.get $nb)))
1110
+ (local.set $j (i32.sub (local.get $j) (i32.const 1)))
1111
+ (br $il)))
1112
+ (drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $j) (i32.const 1)) (local.get $saved)))
1113
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1114
+ (br $ol)))
1115
+ (f64.reinterpret_i64 (local.get $ptr)))`
1116
+
1117
+ ctx.core.emit['.typed:fill'] = (arr, val, start, end) => {
1118
+ inc('__typed_fill')
1119
+ return typed(['call', '$__typed_fill',
1120
+ asI64(emit(arr)),
1121
+ val == null ? undefExpr() : asF64(emit(val)),
1122
+ start == null ? ['i32.const', 0] : asI32(emit(start)),
1123
+ end == null ? ['i32.const', 0x7FFFFFFF] : asI32(emit(end))], 'f64')
1124
+ }
1125
+
1126
+ // .reverse() / .copyWithin(...) for typed arrays. The plain-array helpers gate on
1127
+ // PTR.ARRAY and silently no-op a typed receiver; these go through the element-kind-
1128
+ // aware get/set helpers so every width and signedness reverses/moves correctly.
1129
+ ctx.core.emit['.typed:reverse'] = (arr) => {
1130
+ inc('__typed_reverse')
1131
+ return typed(['call', '$__typed_reverse', asI64(emit(arr))], 'f64')
1132
+ }
1133
+
1134
+ ctx.core.emit['.typed:copyWithin'] = (arr, target, start, end) => {
1135
+ inc('__typed_copyWithin')
1136
+ return typed(['call', '$__typed_copyWithin',
1137
+ asI64(emit(arr)),
1138
+ target == null ? ['i32.const', 0] : asI32(emit(target)),
1139
+ start == null ? ['i32.const', 0] : asI32(emit(start)),
1140
+ end == null ? ['i32.const', 0x7FFFFFFF] : asI32(emit(end))], 'f64')
1141
+ }
1142
+
1143
+ // .sort(compareFn?) for typed arrays. No argument → the numeric __typed_sort helper
1144
+ // (typed-array default is NUMERIC, unlike Array.sort's lexicographic default — so it
1145
+ // must NOT route through the plain-array string comparator). With a comparator, an
1146
+ // insertion sort is emitted inline, calling the closure per neighbor compare; a
1147
+ // positive return shifts (same convention as Array.prototype.sort).
1148
+ // Sort the typed array VALUE `arrValIR` in place and return it. Factored out so
1149
+ // .typed:sort sorts the receiver and .typed:toSorted sorts a fresh copy with one body.
1150
+ const emitTypedSort = (arrValIR, fn) => {
1151
+ if (fn == null) {
1152
+ inc('__typed_sort')
1153
+ return typed(['call', '$__typed_sort', asI64(arrValIR)], 'f64')
1154
+ }
1155
+ inc('__len', '__typed_get_idx', '__typed_set_idx')
1156
+ const arrL = temp('tsa'), cbL = temp('tsf')
1157
+ const len = tempI32('tsn'), i = tempI32('tsi'), j = tempI32('tsj')
1158
+ const cur = temp('tsc'), nb = temp('tsb')
1159
+ const id = ctx.func.uniq++
1160
+ const oE = `$tsoe${id}`, oL = `$tsol${id}`, iE = `$tsie${id}`, iL = `$tsil${id}`
1161
+ const ptr = () => ['i64.reinterpret_f64', ['local.get', `$${arrL}`]]
1162
+ const jp1 = ['i32.add', ['local.get', `$${j}`], ['i32.const', 1]]
1163
+ return typed(['block', ['result', 'f64'],
1164
+ ['local.set', `$${arrL}`, asF64(arrValIR)],
1165
+ ['local.set', `$${cbL}`, asF64(emit(fn))],
1166
+ ['local.set', `$${len}`, ['call', '$__len', ptr()]],
1167
+ ['local.set', `$${i}`, ['i32.const', 1]],
1168
+ ['block', oE, ['loop', oL,
1169
+ ['br_if', oE, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
1170
+ ['local.set', `$${cur}`, ['call', '$__typed_get_idx', ptr(), ['local.get', `$${i}`]]],
1171
+ ['local.set', `$${j}`, ['i32.sub', ['local.get', `$${i}`], ['i32.const', 1]]],
1172
+ ['block', iE, ['loop', iL,
1173
+ ['br_if', iE, ['i32.lt_s', ['local.get', `$${j}`], ['i32.const', 0]]],
1174
+ ['local.set', `$${nb}`, ['call', '$__typed_get_idx', ptr(), ['local.get', `$${j}`]]],
1175
+ // Break unless cmp(neighbor, cur) > 0. f64.gt is false for NaN (spec NaN-as-0).
1176
+ ['br_if', iE, ['i32.eqz', ['f64.gt',
1177
+ asF64(ctx.closure.call(typed(['local.get', `$${cbL}`], 'f64'),
1178
+ [typed(['local.get', `$${nb}`], 'f64'), typed(['local.get', `$${cur}`], 'f64')])),
1179
+ ['f64.const', 0]]]],
1180
+ ['drop', ['call', '$__typed_set_idx', ptr(), jp1, ['local.get', `$${nb}`]]],
1181
+ ['local.set', `$${j}`, ['i32.sub', ['local.get', `$${j}`], ['i32.const', 1]]],
1182
+ ['br', iL]]],
1183
+ ['drop', ['call', '$__typed_set_idx', ptr(), jp1, ['local.get', `$${cur}`]]],
1184
+ ['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
1185
+ ['br', oL]]],
1186
+ ['local.get', `$${arrL}`]], 'f64')
1187
+ }
1188
+ ctx.core.emit['.typed:sort'] = (arr, fn) => emitTypedSort(emit(arr), fn)
1189
+
962
1190
  // Type-aware TypedArray read: arr[i]
963
- ctx.core.emit['.typed:[]'] = (arr, idx) => {
1191
+ ctx.core.emit['.typed:[]'] = (arr, i) => {
964
1192
  const r = resolveElem(arr)
965
1193
  if (r == null) return null // unknown type, fallback to generic
966
1194
  const { et, isView, isBigInt } = r
967
- const objIR = emit(arr), vi = asI32(emit(idx))
1195
+ const objIR = emit(arr), vi = idx(i)
968
1196
  const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
969
1197
  if (isBigInt) return typed(['f64.reinterpret_i64', ['i64.load', off]], 'f64')
970
1198
  if (et === 7) return typed(['f64.load', off], 'f64') // Float64Array
@@ -974,11 +1202,11 @@ export default (ctx) => {
974
1202
  }
975
1203
 
976
1204
  // Type-aware TypedArray write: arr[i] = val
977
- ctx.core.emit['.typed:[]='] = (arr, idx, val, void_ = false) => {
1205
+ ctx.core.emit['.typed:[]='] = (arr, i, val, void_ = false) => {
978
1206
  const r = resolveElem(arr)
979
1207
  if (r == null) return null
980
1208
  const { et, isView, isBigInt } = r
981
- const objIR = emit(arr), vi = asI32(emit(idx)), valIR = emit(val)
1209
+ const objIR = emit(arr), vi = idx(i), valIR = emit(val)
982
1210
  const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
983
1211
  if (isBigInt) {
984
1212
  const vt = temp('tw')
@@ -1213,7 +1441,7 @@ export default (ctx) => {
1213
1441
  // ABI width at 2 to spare a slot across the whole program). Reduce passes
1214
1442
  // (acc, item). Closure invocation goes through `ctx.closure.call` directly.
1215
1443
  // The element-type-name list is needed by allocPtr for typedAux:
1216
- const ET_NAME = ['Int8Array','Uint8Array','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array']
1444
+ const ET_NAME = TYPED_ELEM_NAMES
1217
1445
 
1218
1446
  // .forEach: callback (item, idx). Result is 0 to match array.js's
1219
1447
  // convention (spec says undefined; both modules pick 0 since f() exposes the
@@ -1260,18 +1488,54 @@ export default (ctx) => {
1260
1488
 
1261
1489
  // .indexOf: scalar value-equality search. Returns -1 on miss. Compare on f64
1262
1490
  // — Array.prototype.indexOf uses strict equality (NaN ≠ NaN).
1263
- ctx.core.emit['.typed:indexOf'] = (arr, val) => {
1264
- const found = tempI32('tif'), needle = temp('tin')
1265
- const loop = typedLoop(arr, (load, i, _len, _ptr, exit) => [
1266
- ['if', ['f64.eq', load(), ['local.get', `$${needle}`]],
1267
- ['then',
1268
- ['local.set', `$${found}`, ['local.get', `$${i}`]],
1269
- ['br', exit]]]
1270
- ])
1491
+ // Effective start index for a fromIndex arg: negative counts from the end. The match
1492
+ // guard is `i >= start` and i ≥ 0, so a start below 0 needs no clamp (always passes).
1493
+ const fromStart = (fiL, len) => ['if', ['result', 'i32'],
1494
+ ['i32.lt_s', ['local.get', `$${fiL}`], ['i32.const', 0]],
1495
+ ['then', ['i32.add', ['local.get', `$${fiL}`], ['local.get', `$${len}`]]],
1496
+ ['else', ['local.get', `$${fiL}`]]]
1497
+
1498
+ ctx.core.emit['.typed:indexOf'] = (arr, val, fromIndex) => {
1499
+ const found = tempI32('tif'), needle = temp('tin'), fiL = tempI32('tifx')
1500
+ const loop = typedLoop(arr, (load, i, len, _ptr, exit) => {
1501
+ const matched = ['f64.eq', load(), ['local.get', `$${needle}`]]
1502
+ const cond = fromIndex == null ? matched
1503
+ : ['i32.and', ['i32.ge_s', ['local.get', `$${i}`], fromStart(fiL, len)], matched]
1504
+ return [['if', cond, ['then',
1505
+ ['local.set', `$${found}`, ['local.get', `$${i}`]], ['br', exit]]]]
1506
+ })
1507
+ if (!loop) return null
1508
+ return typed(['block', ['result', 'f64'],
1509
+ ['local.set', `$${needle}`, asF64(emit(val))],
1510
+ ['local.set', `$${found}`, ['i32.const', -1]],
1511
+ ...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
1512
+ ...loop.setup,
1513
+ ['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
1514
+ }
1515
+
1516
+ // .lastIndexOf(val): the last index whose element strictly-equals val, else -1.
1517
+ // Was unimplemented for typed arrays (threw). A forward scan that keeps the latest
1518
+ // match needs no reverse iteration — equivalent for the no-fromIndex form, which is
1519
+ // the common case. With a fromIndex the matcher additionally bounds i ≤ fromIndex
1520
+ // (negative counts from the end, resolved against the typedLoop len). NaN never
1521
+ // strict-equals (f64.eq), matching JS.
1522
+ ctx.core.emit['.typed:lastIndexOf'] = (arr, val, fromIndex) => {
1523
+ const found = tempI32('tlf'), needle = temp('tln'), fiL = tempI32('tlfi')
1524
+ const loop = typedLoop(arr, (load, i, len) => {
1525
+ const matched = ['f64.eq', load(), ['local.get', `$${needle}`]]
1526
+ if (fromIndex == null)
1527
+ return [['if', matched, ['then', ['local.set', `$${found}`, ['local.get', `$${i}`]]]]]
1528
+ const lim = ['if', ['result', 'i32'], ['i32.lt_s', ['local.get', `$${fiL}`], ['i32.const', 0]],
1529
+ ['then', ['i32.add', ['local.get', `$${fiL}`], ['local.get', `$${len}`]]],
1530
+ ['else', ['local.get', `$${fiL}`]]]
1531
+ return [['if', ['i32.and', ['i32.le_s', ['local.get', `$${i}`], lim], matched],
1532
+ ['then', ['local.set', `$${found}`, ['local.get', `$${i}`]]]]]
1533
+ })
1271
1534
  if (!loop) return null
1272
1535
  return typed(['block', ['result', 'f64'],
1273
1536
  ['local.set', `$${needle}`, asF64(emit(val))],
1274
1537
  ['local.set', `$${found}`, ['i32.const', -1]],
1538
+ ...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
1275
1539
  ...loop.setup,
1276
1540
  ['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
1277
1541
  }
@@ -1279,21 +1543,23 @@ export default (ctx) => {
1279
1543
  // .includes: like indexOf but NaN-equal-NaN (JS spec). Stash needle bits as
1280
1544
  // i64 and compare via i64.eq so two NaNs with matching bit patterns match
1281
1545
  // (f64.eq would say false).
1282
- ctx.core.emit['.typed:includes'] = (arr, val) => {
1283
- const found = tempI32('thf'), needle = temp('thn')
1284
- const loop = typedLoop(arr, (load, _i, _len, _ptr, exit) => [
1285
- ['if',
1286
- ['i32.or',
1287
- ['f64.eq', load(), ['local.get', `$${needle}`]],
1288
- ['i64.eq',
1289
- ['i64.reinterpret_f64', load()],
1290
- ['i64.reinterpret_f64', ['local.get', `$${needle}`]]]],
1291
- ['then', ['local.set', `$${found}`, ['i32.const', 1]], ['br', exit]]]
1292
- ])
1546
+ ctx.core.emit['.typed:includes'] = (arr, val, fromIndex) => {
1547
+ const found = tempI32('thf'), needle = temp('thn'), fiL = tempI32('thx')
1548
+ const loop = typedLoop(arr, (load, i, len, _ptr, exit) => {
1549
+ const matched = ['i32.or',
1550
+ ['f64.eq', load(), ['local.get', `$${needle}`]],
1551
+ ['i64.eq',
1552
+ ['i64.reinterpret_f64', load()],
1553
+ ['i64.reinterpret_f64', ['local.get', `$${needle}`]]]]
1554
+ const cond = fromIndex == null ? matched
1555
+ : ['i32.and', ['i32.ge_s', ['local.get', `$${i}`], fromStart(fiL, len)], matched]
1556
+ return [['if', cond, ['then', ['local.set', `$${found}`, ['i32.const', 1]], ['br', exit]]]]
1557
+ })
1293
1558
  if (!loop) return null
1294
1559
  return typed(['block', ['result', 'f64'],
1295
1560
  ['local.set', `$${needle}`, asF64(emit(val))],
1296
1561
  ['local.set', `$${found}`, ['i32.const', 0]],
1562
+ ...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
1297
1563
  ...loop.setup,
1298
1564
  ['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
1299
1565
  }
@@ -1331,6 +1597,40 @@ export default (ctx) => {
1331
1597
  ctx.core.emit['.typed:find'] = (arr, fn) => findCommon(arr, fn, false)
1332
1598
  ctx.core.emit['.typed:findIndex'] = (arr, fn) => findCommon(arr, fn, true)
1333
1599
 
1600
+ // .findLast / .findLastIndex — like find/findIndex but keep the LAST match instead of
1601
+ // the first, so no early break (a forward scan that overwrites on each hit). Without a
1602
+ // typed handler these routed through the plain-array versions, which read elements as
1603
+ // raw f64 and returned garbage for non-f64 typed arrays.
1604
+ const findLastCommon = (arr, fn, returnIndex) => {
1605
+ const cbLoc = temp('tLc'), result = temp('tLr'), foundIdx = tempI32('tLi')
1606
+ const loop = typedLoop(arr, (load, i) => {
1607
+ const itemLoc = temp('tLit')
1608
+ return [
1609
+ ['local.set', `$${itemLoc}`, load()],
1610
+ ['if', truthyIR(ctx.closure.call(
1611
+ typed(['local.get', `$${cbLoc}`], 'f64'),
1612
+ [typed(['local.get', `$${itemLoc}`], 'f64'),
1613
+ typed(['f64.convert_i32_s', ['local.get', `$${i}`]], 'f64')])),
1614
+ ['then',
1615
+ returnIndex
1616
+ ? ['local.set', `$${foundIdx}`, ['local.get', `$${i}`]]
1617
+ : ['local.set', `$${result}`, typed(['local.get', `$${itemLoc}`], 'f64')]]]
1618
+ ]
1619
+ })
1620
+ if (!loop) return null
1621
+ return typed(['block', ['result', 'f64'],
1622
+ ['local.set', `$${cbLoc}`, asF64(emit(fn))],
1623
+ returnIndex
1624
+ ? ['local.set', `$${foundIdx}`, ['i32.const', -1]]
1625
+ : ['local.set', `$${result}`, undefExpr()],
1626
+ ...loop.setup,
1627
+ returnIndex
1628
+ ? typed(['f64.convert_i32_s', ['local.get', `$${foundIdx}`]], 'f64')
1629
+ : typed(['local.get', `$${result}`], 'f64')], 'f64')
1630
+ }
1631
+ ctx.core.emit['.typed:findLast'] = (arr, fn) => findLastCommon(arr, fn, false)
1632
+ ctx.core.emit['.typed:findLastIndex'] = (arr, fn) => findLastCommon(arr, fn, true)
1633
+
1334
1634
  // .some / .every: short-circuit boolean reduction. some=∃, every=∀.
1335
1635
  const anyAllCommon = (arr, fn, isEvery) => {
1336
1636
  const cbLoc = temp('tac'), result = tempI32('tar')
@@ -1463,4 +1763,90 @@ export default (ctx) => {
1463
1763
  ['i32.shl', ['local.get', `$${n}`], ['i32.const', SHIFT[et]]]],
1464
1764
  dst.ptr], 'f64')
1465
1765
  }
1766
+
1767
+ // .toReversed() — a reversed COPY (receiver unchanged): full slice-copy, then reverse
1768
+ // the copy in place. (slice bails on BigInt, so BigInt receivers fall back to a throw.)
1769
+ ctx.core.emit['.typed:toReversed'] = (arr) => {
1770
+ const copy = ctx.core.emit['.typed:slice'](arr)
1771
+ if (!copy) return null
1772
+ inc('__typed_reverse')
1773
+ const c = temp('ttv')
1774
+ return typed(['block', ['result', 'f64'],
1775
+ ['local.set', `$${c}`, asF64(copy)],
1776
+ ['call', '$__typed_reverse', ['i64.reinterpret_f64', ['local.get', `$${c}`]]]], 'f64')
1777
+ }
1778
+
1779
+ // .toSorted(fn?) — a sorted COPY (receiver unchanged): slice-copy, then sort in place.
1780
+ ctx.core.emit['.typed:toSorted'] = (arr, fn) => {
1781
+ const copy = ctx.core.emit['.typed:slice'](arr)
1782
+ return copy ? emitTypedSort(copy, fn) : null
1783
+ }
1784
+
1785
+ // .with(index, value) — a COPY with one element replaced (receiver unchanged). Negative
1786
+ // index counts from the end; out of range throws RangeError ($__jz_err), per spec.
1787
+ ctx.core.emit['.typed:with'] = (arr, index, value) => {
1788
+ const copy = ctx.core.emit['.typed:slice'](arr)
1789
+ if (!copy) return null
1790
+ ctx.runtime.throws = true
1791
+ inc('__len', '__typed_set_idx')
1792
+ const c = temp('twc'), idx = tempI32('twi'), len = tempI32('twl')
1793
+ return typed(['block', ['result', 'f64'],
1794
+ ['local.set', `$${c}`, asF64(copy)],
1795
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${c}`]]]],
1796
+ ['local.set', `$${idx}`, asI32(emit(index))],
1797
+ ['if', ['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
1798
+ ['then', ['local.set', `$${idx}`, ['i32.add', ['local.get', `$${idx}`], ['local.get', `$${len}`]]]]],
1799
+ ['if', ['i32.or',
1800
+ ['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
1801
+ ['i32.ge_s', ['local.get', `$${idx}`], ['local.get', `$${len}`]]],
1802
+ ['then', ['throw', '$__jz_err', ['f64.const', 0]]]],
1803
+ ['drop', ['call', '$__typed_set_idx', ['i64.reinterpret_f64', ['local.get', `$${c}`]],
1804
+ ['local.get', `$${idx}`], asF64(emit(value))]],
1805
+ ['local.get', `$${c}`]], 'f64')
1806
+ }
1807
+
1808
+ // .subarray(begin, end) — a zero-copy VIEW sharing the receiver's buffer (writes alias,
1809
+ // NOT a copy). Builds the 16-byte descriptor [byteLen][dataOff][parentOff] and tags the
1810
+ // TYPED ptr with aux|view, exactly like new TypedArray(buffer, byteOffset, length).
1811
+ ctx.core.emit['.typed:subarray'] = (arr, begin, end) => {
1812
+ const r = resolveElem(arr)
1813
+ if (!r) return null
1814
+ const { et, isView, isBigInt } = r
1815
+ const shift = SHIFT[et]
1816
+ const viewAux = et | 8 | (isBigInt ? 16 : 0)
1817
+ const arrL = temp('tua'), srcOff = tempI32('tuo'), data = tempI32('tud'), root = tempI32('tur')
1818
+ const len = tempI32('tul'), lo = tempI32('tulo'), hi = tempI32('tuhi'), n = tempI32('tun'), desc = tempI32('tude')
1819
+ inc('__len')
1820
+ const off4 = (o) => ['i32.load', ['i32.add', ['local.get', `$${srcOff}`], ['i32.const', o]]]
1821
+ const clamp = (boundExpr, dflt, name) => {
1822
+ if (boundExpr == null) return dflt
1823
+ const v = tempI32(name)
1824
+ return ['block', ['result', 'i32'],
1825
+ ['local.set', `$${v}`, asI32(emit(boundExpr))],
1826
+ ['if', ['i32.lt_s', ['local.get', `$${v}`], ['i32.const', 0]],
1827
+ ['then', ['local.set', `$${v}`, ['i32.add', ['local.get', `$${v}`], ['local.get', `$${len}`]]]]],
1828
+ ['if', ['i32.lt_s', ['local.get', `$${v}`], ['i32.const', 0]],
1829
+ ['then', ['local.set', `$${v}`, ['i32.const', 0]]]],
1830
+ ['if', ['i32.gt_s', ['local.get', `$${v}`], ['local.get', `$${len}`]],
1831
+ ['then', ['local.set', `$${v}`, ['local.get', `$${len}`]]]],
1832
+ ['local.get', `$${v}`]]
1833
+ }
1834
+ return typed(['block', ['result', 'f64'],
1835
+ ['local.set', `$${arrL}`, asF64(emit(arr))],
1836
+ ['local.set', `$${srcOff}`, typedBase(typed(['local.get', `$${arrL}`], 'f64'))],
1837
+ ['local.set', `$${data}`, isView ? off4(4) : ['local.get', `$${srcOff}`]],
1838
+ ['local.set', `$${root}`, isView ? off4(8) : ['local.get', `$${srcOff}`]],
1839
+ ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${arrL}`]]]],
1840
+ ['local.set', `$${lo}`, clamp(begin, ['i32.const', 0], 'tub')],
1841
+ ['local.set', `$${hi}`, clamp(end, ['local.get', `$${len}`], 'tue')],
1842
+ ['local.set', `$${n}`, ['select',
1843
+ ['i32.sub', ['local.get', `$${hi}`], ['local.get', `$${lo}`]], ['i32.const', 0],
1844
+ ['i32.gt_s', ['local.get', `$${hi}`], ['local.get', `$${lo}`]]]],
1845
+ ['local.set', `$${desc}`, ['call', '$__alloc', ['i32.const', 16]]],
1846
+ ['i32.store', ['local.get', `$${desc}`], ['i32.shl', ['local.get', `$${n}`], ['i32.const', shift]]],
1847
+ ['i32.store', ['i32.add', ['local.get', `$${desc}`], ['i32.const', 4]],
1848
+ ['i32.add', ['local.get', `$${data}`], ['i32.shl', ['local.get', `$${lo}`], ['i32.const', shift]]]],
1849
+ ['i32.store', ['i32.add', ['local.get', `$${desc}`], ['i32.const', 8]], ['local.get', `$${root}`]],
1850
+ mkPtrIR(PTR.TYPED, viewAux, ['local.get', `$${desc}`])], 'f64')
1851
+ }
1466
1852
  }