jz 0.6.0 → 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 (49) hide show
  1. package/README.md +66 -43
  2. package/bench/README.md +128 -78
  3. package/bench/bench.svg +32 -42
  4. package/cli.js +73 -7
  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 +190 -34
  9. package/interop.js +71 -27
  10. package/layout.js +5 -0
  11. package/module/array.js +71 -39
  12. package/module/collection.js +41 -5
  13. package/module/core.js +2 -4
  14. package/module/math.js +138 -2
  15. package/module/number.js +21 -0
  16. package/module/object.js +26 -0
  17. package/module/simd.js +37 -5
  18. package/module/string.js +41 -12
  19. package/module/typedarray.js +415 -26
  20. package/package.json +21 -6
  21. package/src/autoload.js +3 -0
  22. package/src/compile/analyze-scans.js +174 -11
  23. package/src/compile/analyze.js +38 -3
  24. package/src/compile/emit-assign.js +9 -6
  25. package/src/compile/emit.js +347 -36
  26. package/src/compile/index.js +307 -29
  27. package/src/compile/infer.js +36 -1
  28. package/src/compile/loop-divmod.js +155 -0
  29. package/src/compile/narrow.js +108 -11
  30. package/src/compile/peel-stencil.js +261 -0
  31. package/src/compile/plan/advise.js +55 -1
  32. package/src/compile/plan/index.js +4 -0
  33. package/src/compile/plan/inline.js +5 -2
  34. package/src/compile/plan/literals.js +220 -5
  35. package/src/compile/plan/scope.js +115 -39
  36. package/src/compile/program-facts.js +21 -2
  37. package/src/ctx.js +45 -7
  38. package/src/ir.js +55 -7
  39. package/src/kind-traits.js +27 -0
  40. package/src/kind.js +65 -3
  41. package/src/optimize/index.js +344 -45
  42. package/src/optimize/vectorize.js +2968 -182
  43. package/src/prepare/index.js +64 -7
  44. package/src/prepare/lift-iife.js +149 -0
  45. package/src/reps.js +3 -2
  46. package/src/static.js +9 -0
  47. package/src/type.js +10 -6
  48. package/src/wat/assemble.js +195 -13
  49. package/src/wat/optimize.js +363 -185
package/module/string.js CHANGED
@@ -114,8 +114,8 @@ export default (ctx) => {
114
114
  __str_copy: [],
115
115
  __str_slice: ['__str_byteLen', '__alloc'],
116
116
  __str_slice_view: ['__str_byteLen', '__mkptr', '__str_slice'],
117
- __str_indexof: ['__str_byteLen', '__to_str'],
118
- __str_lastindexof: ['__str_byteLen', '__to_str'],
117
+ __str_indexof: ['__str_byteLen'],
118
+ __str_lastindexof: ['__str_byteLen'],
119
119
  __wrap1: ['__alloc', '__mkptr'],
120
120
  __str_substring: ['__str_slice'],
121
121
  __str_startswith: ['__str_byteLen'],
@@ -145,9 +145,21 @@ export default (ctx) => {
145
145
  __str_byteLen: ['__ptr_type', '__ptr_aux', '__str_len'],
146
146
  })
147
147
 
148
+ // String *runtime* construction (concat/slice/split/`String(n)`/shared-memory
149
+ // pools) allocates and boxes pointers, so the module eagerly declares its core
150
+ // heap deps — same as object.js / typedarray.js / array.js. Several such helpers
151
+ // (`__str_slice`, `__str_split`, `__str_case`, …) call `$__mkptr`/`$__alloc` only
152
+ // through nested template thunks (sliceSsoPackWat/internProbeWat); resolveIncludes'
153
+ // auto-derivation realizes those thunks to recover the dep, but that re-realization
154
+ // is not reliable under self-host (the jz.wasm kernel re-runs the thunk late, with
155
+ // intern/heap state the native run never reaches), so a runtime string op would
156
+ // emit `call $__mkptr` with no definition. Declaring the deps explicitly here makes
157
+ // inclusion independent of thunk re-realization. Reachability pruning still drops
158
+ // both for a literals-only module (no `__mkptr`/`__alloc` call site → unreachable),
159
+ // so a heap-free program stays heap-free — no allocator, no memory, no exports.
148
160
  inc('__mkptr', '__alloc')
149
161
 
150
- // === String literal: "abc" → SSO if ≤4 ASCII, else heap ===
162
+ // === String literal: "abc" → SSO if ≤4 ASCII, else static data ===
151
163
 
152
164
  bind('str', (str) => {
153
165
  const MAX_SSO = 4
@@ -684,8 +696,8 @@ export default (ctx) => {
684
696
  (local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
685
697
  (local $hoff i32) (local $noff i32)
686
698
  (local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
687
- ;; ToString the search value (21.1.3.9 step 4) coerces undefined/null/number/etc.
688
- (local.set $ndl (call $__to_str (local.get $ndl)))
699
+ ;; The search value is ToString'd at the call site (searchArg) per 21.1.3.9 step 4 —
700
+ ;; a known-string needle passes raw, so this helper carries no float-pulling __to_str.
689
701
  (local.set $hlen (call $__str_byteLen (local.get $hay)))
690
702
  (local.set $nlen (call $__str_byteLen (local.get $ndl)))
691
703
  (if (i32.eqz (local.get $nlen)) (then (return (local.get $from))))
@@ -729,8 +741,7 @@ export default (ctx) => {
729
741
  (local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
730
742
  (local $hoff i32) (local $noff i32)
731
743
  (local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
732
- ;; ToString the search value (21.1.3.10 step 4)
733
- (local.set $ndl (call $__to_str (local.get $ndl)))
744
+ ;; The search value is ToString'd at the call site (searchArg) per 21.1.3.10 step 4.
734
745
  (local.set $hlen (call $__str_byteLen (local.get $hay)))
735
746
  (local.set $nlen (call $__str_byteLen (local.get $ndl)))
736
747
  ;; Empty needle always matches at the clamp(from,0,hlen) position
@@ -1430,9 +1441,22 @@ export default (ctx) => {
1430
1441
  // covers string/number/null/undefined needles, but two cases need help here:
1431
1442
  // a BOOL rides the 0/1 carrier (→ "0"/"1" not "true"/"false"), and an OBJECT
1432
1443
  // needs compile-time ToPrimitive(string) (__to_str can't invoke user toString).
1433
- const searchArg = (search) =>
1434
- valTypeOf(search) === VAL.BOOL ? asI64(bool(search)) :
1435
- valTypeOf(search) === VAL.OBJECT ? toStrI64(search, emit(search)) : asI64(emit(search))
1444
+ // Coerce the search operand to a string AT THE CALL SITE (21.1.3.x ToString step),
1445
+ // so the `__str_indexof` family carries no embedded `__to_str`. A known-STRING arg
1446
+ // (the overwhelmingly common `s.indexOf("x")` / `s.indexOf(t)` shape) passes raw —
1447
+ // dropping the whole ToString → float-formatter dep tree (~4 KB) that an internal,
1448
+ // unconditional coercion forced into every search-method program. Mirrors
1449
+ // `stringSearchMethod` (startsWith/endsWith), which has always coerced here.
1450
+ const searchArg = (search) => {
1451
+ const vt = valTypeOf(search)
1452
+ if (vt === VAL.STRING) return asI64(emit(search))
1453
+ if (vt === VAL.BOOL) return asI64(bool(search))
1454
+ if (vt === VAL.OBJECT) return toStrI64(search, emit(search))
1455
+ inc('__to_str')
1456
+ return ['call', '$__to_str', asI64(emit(search))]
1457
+ }
1458
+ // Replacement operand of .replace/.replaceAll — same call-site ToString as searchArg.
1459
+ const strReplArg = searchArg
1436
1460
 
1437
1461
  bind('.string:indexOf', (str, search, from) => {
1438
1462
  inc('__str_indexof')
@@ -1573,9 +1597,14 @@ export default (ctx) => {
1573
1597
  asI64(tail)], 'f64')]]], 'f64')
1574
1598
  }
1575
1599
  inc('__str_replace')
1576
- return typed(['call', '$__str_replace', asI64(emit(str)), asI64(emit(search)), asI64(emit(repl))], 'f64')
1600
+ // search/repl ToString'd at the call site (searchArg) __str_replace's __str_indexof
1601
+ // no longer coerces internally, so a non-string search must be stringified here.
1602
+ return typed(['call', '$__str_replace', asI64(emit(str)), searchArg(search), strReplArg(repl)], 'f64')
1603
+ })
1604
+ bind('.replaceAll', (str, search, repl) => {
1605
+ inc('__str_replaceall')
1606
+ return typed(['call', '$__str_replaceall', asI64(emit(str)), searchArg(search), strReplArg(repl)], 'f64')
1577
1607
  })
1578
- bind('.replaceAll', method('__str_replaceall', 'III'))
1579
1608
 
1580
1609
  const caseMethod = (lo, hi, delta) => (str) => {
1581
1610
  inc('__str_case')
@@ -12,7 +12,7 @@ import { emit, idx, deps, call } from '../src/bridge.js'
12
12
  import { valTypeOf } from '../src/kind.js'
13
13
  import { VAL, lookupValType } from '../src/reps.js'
14
14
  import { nanPrefixHex, TYPED_ELEM_NAMES, TYPED_ELEM_CODE, TYPED_ELEM_BIGINT_FLAG, encodeTypedElemAux } from '../layout.js'
15
- import { inc, PTR, getter } from '../src/ctx.js'
15
+ import { inc, PTR, LAYOUT, getter } from '../src/ctx.js'
16
16
 
17
17
  const _NAN_BITS = nanPrefixHex()
18
18
 
@@ -201,6 +201,11 @@ export default (ctx) => {
201
201
  __byte_offset: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
202
202
  __to_buffer: ['__ptr_type', '__ptr_offset', '__ptr_aux', '__mkptr'],
203
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'],
204
209
  // __str_join uses __typed_idx when typedarray is loaded (plain arrays promoted to
205
210
  // Int32Array by promoteIntArrayLiterals can produce PTR.TYPED results via .map()).
206
211
  __str_join: [...(ctx.core.stdlibDeps.__str_join ?? []), '__typed_idx'],
@@ -842,18 +847,21 @@ export default (ctx) => {
842
847
  * at the chain output (the result is always an owned typed array). */
843
848
  const TYPED_CHAIN_METHODS = new Set(['map', 'filter', 'slice'])
844
849
  const resolveElem = (arr) => {
845
- let receiver = arr, chainOutput = false
850
+ let receiver = arr, chainOutput = false, viewOutput = false
846
851
  // Walk method-call chain inward. `arr.method(...)` parses as
847
- // ['()', ['.', 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.
848
855
  while (Array.isArray(receiver) && receiver[0] === '()' &&
849
856
  Array.isArray(receiver[1]) && receiver[1][0] === '.' &&
850
- 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'
851
859
  receiver = receiver[1][1]
852
860
  chainOutput = true
853
861
  }
854
862
  const ctor = typeof receiver === 'string' && ctx.types.typedElem?.get(receiver)
855
863
  if (!ctor) return null
856
- const isView = !chainOutput && ctor.endsWith('.view')
864
+ const isView = viewOutput || (!chainOutput && ctor.endsWith('.view'))
857
865
  const name = ctor.endsWith('.view') ? ctor.slice(4, -5) : ctor.slice(4)
858
866
  const et = TYPED_ELEM_CODE[name]
859
867
  return et == null ? null : { et, isView, isBigInt: name === 'BigInt64Array' || name === 'BigUint64Array' }
@@ -863,9 +871,19 @@ export default (ctx) => {
863
871
  * Owned: low 32 bits of the NaN-box (or the unboxed local directly).
864
872
  * View: load descriptor[4]. Uses ptrOffsetIR so unboxed-TYPED locals pass through
865
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]]]
866
884
  const typedDataAddr = (objIR, isView) => isView
867
- ? ['i32.load', ['i32.add', ptrOffsetIR(objIR, VAL.TYPED), ['i32.const', 4]]]
868
- : ptrOffsetIR(objIR, VAL.TYPED)
885
+ ? ['i32.load', ['i32.add', typedBase(objIR), ['i32.const', 4]]]
886
+ : typedBase(objIR)
869
887
 
870
888
  // Runtime-dispatch typed index: checks ptr_type + aux to load with correct stride.
871
889
  // For TYPED views (aux bit 3), $off indirects through descriptor[4] to real data.
@@ -956,6 +974,219 @@ export default (ctx) => {
956
974
  (else (i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $bits))))))))))))
957
975
  (local.get $v))`
958
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
+
959
1190
  // Type-aware TypedArray read: arr[i]
960
1191
  ctx.core.emit['.typed:[]'] = (arr, i) => {
961
1192
  const r = resolveElem(arr)
@@ -1257,18 +1488,54 @@ export default (ctx) => {
1257
1488
 
1258
1489
  // .indexOf: scalar value-equality search. Returns -1 on miss. Compare on f64
1259
1490
  // — Array.prototype.indexOf uses strict equality (NaN ≠ NaN).
1260
- ctx.core.emit['.typed:indexOf'] = (arr, val) => {
1261
- const found = tempI32('tif'), needle = temp('tin')
1262
- const loop = typedLoop(arr, (load, i, _len, _ptr, exit) => [
1263
- ['if', ['f64.eq', load(), ['local.get', `$${needle}`]],
1264
- ['then',
1265
- ['local.set', `$${found}`, ['local.get', `$${i}`]],
1266
- ['br', exit]]]
1267
- ])
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
+ })
1268
1507
  if (!loop) return null
1269
1508
  return typed(['block', ['result', 'f64'],
1270
1509
  ['local.set', `$${needle}`, asF64(emit(val))],
1271
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
+ })
1534
+ if (!loop) return null
1535
+ return typed(['block', ['result', 'f64'],
1536
+ ['local.set', `$${needle}`, asF64(emit(val))],
1537
+ ['local.set', `$${found}`, ['i32.const', -1]],
1538
+ ...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
1272
1539
  ...loop.setup,
1273
1540
  ['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
1274
1541
  }
@@ -1276,21 +1543,23 @@ export default (ctx) => {
1276
1543
  // .includes: like indexOf but NaN-equal-NaN (JS spec). Stash needle bits as
1277
1544
  // i64 and compare via i64.eq so two NaNs with matching bit patterns match
1278
1545
  // (f64.eq would say false).
1279
- ctx.core.emit['.typed:includes'] = (arr, val) => {
1280
- const found = tempI32('thf'), needle = temp('thn')
1281
- const loop = typedLoop(arr, (load, _i, _len, _ptr, exit) => [
1282
- ['if',
1283
- ['i32.or',
1284
- ['f64.eq', load(), ['local.get', `$${needle}`]],
1285
- ['i64.eq',
1286
- ['i64.reinterpret_f64', load()],
1287
- ['i64.reinterpret_f64', ['local.get', `$${needle}`]]]],
1288
- ['then', ['local.set', `$${found}`, ['i32.const', 1]], ['br', exit]]]
1289
- ])
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
+ })
1290
1558
  if (!loop) return null
1291
1559
  return typed(['block', ['result', 'f64'],
1292
1560
  ['local.set', `$${needle}`, asF64(emit(val))],
1293
1561
  ['local.set', `$${found}`, ['i32.const', 0]],
1562
+ ...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
1294
1563
  ...loop.setup,
1295
1564
  ['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
1296
1565
  }
@@ -1328,6 +1597,40 @@ export default (ctx) => {
1328
1597
  ctx.core.emit['.typed:find'] = (arr, fn) => findCommon(arr, fn, false)
1329
1598
  ctx.core.emit['.typed:findIndex'] = (arr, fn) => findCommon(arr, fn, true)
1330
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
+
1331
1634
  // .some / .every: short-circuit boolean reduction. some=∃, every=∀.
1332
1635
  const anyAllCommon = (arr, fn, isEvery) => {
1333
1636
  const cbLoc = temp('tac'), result = tempI32('tar')
@@ -1460,4 +1763,90 @@ export default (ctx) => {
1460
1763
  ['i32.shl', ['local.get', `$${n}`], ['i32.const', SHIFT[et]]]],
1461
1764
  dst.ptr], 'f64')
1462
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
+ }
1463
1852
  }