jz 0.6.0 → 0.8.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/README.md +99 -72
- package/bench/README.md +253 -100
- package/bench/bench.svg +58 -81
- package/cli.js +85 -12
- package/dist/interop.js +1 -0
- package/dist/jz.js +6196 -0
- package/index.d.ts +126 -0
- package/index.js +222 -35
- package/interop.js +240 -141
- package/layout.js +34 -18
- package/module/array.js +99 -111
- package/module/collection.js +124 -30
- package/module/console.js +1 -1
- package/module/core.js +163 -19
- package/module/json.js +3 -3
- package/module/math.js +162 -3
- package/module/number.js +268 -13
- package/module/object.js +37 -5
- package/module/regex.js +8 -7
- package/module/simd.js +37 -5
- package/module/string.js +203 -168
- package/module/typedarray.js +565 -112
- package/package.json +26 -7
- package/src/abi/string.js +29 -29
- package/src/ast.js +19 -2
- package/src/autoload.js +3 -0
- package/src/compile/analyze-scans.js +174 -11
- package/src/compile/analyze.js +101 -4
- package/src/compile/cse-load.js +200 -0
- package/src/compile/emit-assign.js +82 -20
- package/src/compile/emit.js +592 -51
- package/src/compile/index.js +504 -89
- package/src/compile/infer.js +36 -1
- package/src/compile/loop-divmod.js +109 -0
- package/src/compile/loop-model.js +91 -0
- package/src/compile/loop-square.js +102 -0
- package/src/compile/narrow.js +275 -41
- package/src/compile/peel-stencil.js +215 -0
- package/src/compile/plan/advise.js +55 -1
- package/src/compile/plan/common.js +29 -0
- package/src/compile/plan/index.js +8 -1
- package/src/compile/plan/inline.js +180 -22
- package/src/compile/plan/literals.js +313 -24
- package/src/compile/plan/scope.js +115 -39
- package/src/compile/program-facts.js +21 -2
- package/src/ctx.js +96 -19
- package/src/helper-counters.js +137 -0
- package/src/ir.js +157 -20
- package/src/kind-traits.js +34 -3
- package/src/kind.js +79 -4
- package/src/op-policy.js +5 -2
- package/src/ops.js +119 -0
- package/src/optimize/index.js +1274 -151
- package/src/optimize/recurse.js +182 -0
- package/src/optimize/vectorize.js +4187 -253
- package/src/prepare/index.js +75 -14
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +6 -2
- package/src/static.js +9 -0
- package/src/type.js +63 -51
- package/src/wat/assemble.js +286 -21
- package/src/widen.js +21 -0
- package/src/wat/optimize.js +0 -3760
package/module/typedarray.js
CHANGED
|
@@ -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,
|
|
15
|
+
import { inc, PTR, LAYOUT, registerGetter } from '../src/ctx.js'
|
|
16
16
|
|
|
17
17
|
const _NAN_BITS = nanPrefixHex()
|
|
18
18
|
|
|
@@ -28,6 +28,12 @@ const STORE = [
|
|
|
28
28
|
'i32.store8', 'i32.store8', 'i32.store16', 'i32.store16',
|
|
29
29
|
'i32.store', 'i32.store', 'f32.store', 'f64.store',
|
|
30
30
|
]
|
|
31
|
+
// f64 value → this element's stored representation (paired with STORE). Signed
|
|
32
|
+
// kinds trunc_s, unsigned trunc_u, f32 demotes, f64 stores as-is (null = identity).
|
|
33
|
+
const FROM_F64 = [
|
|
34
|
+
'i32.trunc_f64_s', 'i32.trunc_f64_u', 'i32.trunc_f64_s', 'i32.trunc_f64_u',
|
|
35
|
+
'i32.trunc_f64_s', 'i32.trunc_f64_u', 'f32.demote_f64', null,
|
|
36
|
+
]
|
|
31
37
|
|
|
32
38
|
// SIMD: vector width per element type (elements per v128)
|
|
33
39
|
const VEC_WIDTH = [16, 16, 8, 8, 4, 4, 4, 2] // 128 bits / element bits
|
|
@@ -201,6 +207,18 @@ export default (ctx) => {
|
|
|
201
207
|
__byte_offset: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
202
208
|
__to_buffer: ['__ptr_type', '__ptr_offset', '__ptr_aux', '__mkptr'],
|
|
203
209
|
__typed_set_idx: ['__ptr_aux', '__ptr_offset'],
|
|
210
|
+
__typed_get_idx: ['__ptr_aux', '__ptr_offset'],
|
|
211
|
+
// __clamp_idx is body-called by every range op (fill/copyWithin/subarray/slice). It has NO
|
|
212
|
+
// other manual-dep edge in the whole stdlib, so it's reachable ONLY via resolveIncludes'
|
|
213
|
+
// auto-scan — which diverges under self-host (jz.wasm), dropping it ("Unknown func
|
|
214
|
+
// $__clamp_idx" on typed .fill/.subarray in the kernel). Declare it manually here so the
|
|
215
|
+
// reliable dep path includes it. Pinned by test/selfhost-includes.js.
|
|
216
|
+
__typed_fill: ['__len', '__typed_set_idx', '__clamp_idx'],
|
|
217
|
+
__typed_reverse: ['__len', '__typed_get_idx', '__typed_set_idx'],
|
|
218
|
+
__typed_copyWithin: ['__len', '__typed_get_idx', '__typed_set_idx', '__clamp_idx'],
|
|
219
|
+
__typed_sort: ['__len', '__typed_get_idx', '__typed_set_idx'],
|
|
220
|
+
__subarray: ['__ptr_aux', '__ptr_offset', '__typed_shift', '__typed_data', '__len', '__mkptr', '__alloc', '__clamp_idx'],
|
|
221
|
+
__typed_slice_rt: ['__ptr_aux', '__typed_shift', '__typed_data', '__len', '__mkptr', '__alloc_hdr_n', '__clamp_idx'],
|
|
204
222
|
// __str_join uses __typed_idx when typedarray is loaded (plain arrays promoted to
|
|
205
223
|
// Int32Array by promoteIntArrayLiterals can produce PTR.TYPED results via .map()).
|
|
206
224
|
__str_join: [...(ctx.core.stdlibDeps.__str_join ?? []), '__typed_idx'],
|
|
@@ -261,6 +279,60 @@ export default (ctx) => {
|
|
|
261
279
|
(i32.load (i32.add (local.get $off) (i32.const 8)))))
|
|
262
280
|
(else (call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0) (local.get $off)))))))`
|
|
263
281
|
|
|
282
|
+
// __subarray(ptr, begin, end, useEnd) — runtime-dispatched `.subarray(...)` for
|
|
283
|
+
// when the receiver's elem type / view-ness isn't statically known (a binding
|
|
284
|
+
// reassigned owned→view: `let c = new T(d); c = c.subarray(n)`). Reads elem type
|
|
285
|
+
// and the view bit off the aux byte, so it is correct whether `ptr` is an owned
|
|
286
|
+
// typed array or an existing view. Mirrors the static `.typed:subarray` emitter:
|
|
287
|
+
// builds the 16-byte descriptor [byteLen][dataOff][rootOff] and tags TYPED|view.
|
|
288
|
+
// Cold path (slicing, not per-element) — correctness over speed.
|
|
289
|
+
ctx.core.stdlib['__subarray'] = `(func $__subarray (param $ptr i64) (param $begin i32) (param $end i32) (param $useEnd i32) (result f64)
|
|
290
|
+
(local $aux i32) (local $shift i32) (local $off i32) (local $data i32) (local $root i32)
|
|
291
|
+
(local $len i32) (local $lo i32) (local $hi i32) (local $n i32) (local $desc i32)
|
|
292
|
+
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
293
|
+
(local.set $shift (call $__typed_shift (i32.and (local.get $aux) (i32.const 7))))
|
|
294
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
295
|
+
(local.set $data (call $__typed_data (local.get $ptr)))
|
|
296
|
+
(local.set $root
|
|
297
|
+
(if (result i32) (i32.and (local.get $aux) (i32.const 8))
|
|
298
|
+
(then (i32.load (i32.add (local.get $off) (i32.const 8))))
|
|
299
|
+
(else (local.get $off))))
|
|
300
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
301
|
+
(local.set $lo (call $__clamp_idx (local.get $begin) (local.get $len)))
|
|
302
|
+
(if (local.get $useEnd)
|
|
303
|
+
(then
|
|
304
|
+
(local.set $hi (call $__clamp_idx (local.get $end) (local.get $len))))
|
|
305
|
+
(else (local.set $hi (local.get $len))))
|
|
306
|
+
(local.set $n (select (i32.sub (local.get $hi) (local.get $lo)) (i32.const 0) (i32.gt_s (local.get $hi) (local.get $lo))))
|
|
307
|
+
(local.set $desc (call $__alloc (i32.const 16)))
|
|
308
|
+
(i32.store (local.get $desc) (i32.shl (local.get $n) (local.get $shift)))
|
|
309
|
+
(i32.store (i32.add (local.get $desc) (i32.const 4)) (i32.add (local.get $data) (i32.shl (local.get $lo) (local.get $shift))))
|
|
310
|
+
(i32.store (i32.add (local.get $desc) (i32.const 8)) (local.get $root))
|
|
311
|
+
(call $__mkptr (i32.const ${PTR.TYPED}) (i32.or (local.get $aux) (i32.const 8)) (local.get $desc)))`
|
|
312
|
+
|
|
313
|
+
// __typed_slice_rt(ptr, begin, end, useEnd) — runtime-dispatched `.slice(...)` for
|
|
314
|
+
// a receiver whose elem type / view-ness isn't statically known. Unlike subarray
|
|
315
|
+
// this returns a fresh OWNED copy (bit-exact memory.copy, so bigint-safe too). Reads
|
|
316
|
+
// elem type + data address off the aux byte. Cold path — correctness over speed.
|
|
317
|
+
ctx.core.stdlib['__typed_slice_rt'] = `(func $__typed_slice_rt (param $ptr i64) (param $begin i32) (param $end i32) (param $useEnd i32) (result f64)
|
|
318
|
+
(local $aux i32) (local $et i32) (local $shift i32) (local $src i32)
|
|
319
|
+
(local $len i32) (local $lo i32) (local $hi i32) (local $n i32) (local $byteLen i32) (local $dst i32)
|
|
320
|
+
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
321
|
+
(local.set $et (i32.and (local.get $aux) (i32.const 7)))
|
|
322
|
+
(local.set $shift (call $__typed_shift (local.get $et)))
|
|
323
|
+
(local.set $src (call $__typed_data (local.get $ptr)))
|
|
324
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
325
|
+
(local.set $lo (call $__clamp_idx (local.get $begin) (local.get $len)))
|
|
326
|
+
(if (local.get $useEnd)
|
|
327
|
+
(then
|
|
328
|
+
(local.set $hi (call $__clamp_idx (local.get $end) (local.get $len))))
|
|
329
|
+
(else (local.set $hi (local.get $len))))
|
|
330
|
+
(local.set $n (select (i32.sub (local.get $hi) (local.get $lo)) (i32.const 0) (i32.gt_s (local.get $hi) (local.get $lo))))
|
|
331
|
+
(local.set $byteLen (i32.shl (local.get $n) (local.get $shift)))
|
|
332
|
+
(local.set $dst (call $__alloc_hdr_n (local.get $byteLen) (local.get $byteLen) (i32.const 1)))
|
|
333
|
+
(memory.copy (local.get $dst) (i32.add (local.get $src) (i32.shl (local.get $lo) (local.get $shift))) (local.get $byteLen))
|
|
334
|
+
(call $__mkptr (i32.const ${PTR.TYPED}) (i32.and (local.get $aux) (i32.const 23)) (local.get $dst)))`
|
|
335
|
+
|
|
264
336
|
// Constructor: new Float64Array(len) | new F64Array(arr) | new F64Array(buf) | new F64Array(buf, off, len)
|
|
265
337
|
for (const [name, elemType] of Object.entries(TYPED_ELEM_CODE)) {
|
|
266
338
|
const aux = typedAux(name)
|
|
@@ -273,6 +345,7 @@ export default (ctx) => {
|
|
|
273
345
|
// and tags the TYPED ptr with aux=elemType|8. Reads/writes alias the parent,
|
|
274
346
|
// .buffer reconstructs the root BUFFER, .byteOffset = dataOff - parentOff.
|
|
275
347
|
if (offsetExpr != null && lenExpr2 != null) {
|
|
348
|
+
ctx.features.typedView = true // subview aliases the parent buffer — SLP must not assume disjoint bases
|
|
276
349
|
const src = temp('tvs')
|
|
277
350
|
const parentOff = tempI32('tvp')
|
|
278
351
|
const byteLen = tempI32('tvb')
|
|
@@ -298,9 +371,12 @@ export default (ctx) => {
|
|
|
298
371
|
// TYPED retagged at the same offset — the byteLen header is shared with the parent.
|
|
299
372
|
// __len(view) = byteLen >> shift computes elemCount for this view's elemType.
|
|
300
373
|
if (srcType === VAL.BUFFER || srcType === VAL.TYPED) {
|
|
374
|
+
ctx.features.typedView = true // zero-copy reinterpret aliases the source — SLP must not pack across it
|
|
301
375
|
return mkPtrIR(PTR.TYPED, aux, ['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(emit(lenExpr))]])
|
|
302
376
|
}
|
|
303
377
|
if (srcType == null && ctx.core.emit[`${name}.from`]) {
|
|
378
|
+
ctx.features.typedView = true // unknown arg: runtime may take the buffer/typed zero-copy-view branch
|
|
379
|
+
|
|
304
380
|
// Runtime dispatch: number → allocate; array → copy elements; buffer/typed → zero-copy view.
|
|
305
381
|
const src = temp('ts')
|
|
306
382
|
const len = tempI32('tl')
|
|
@@ -405,7 +481,7 @@ export default (ctx) => {
|
|
|
405
481
|
// .buffer — always aliased (zero-copy). BUFFER: passthrough.
|
|
406
482
|
// Owned TYPED: retag as BUFFER at same offset — the byteLen header is shared.
|
|
407
483
|
// TYPED view (incl. DataView): BUFFER at descriptor[8] (root parent data offset).
|
|
408
|
-
|
|
484
|
+
registerGetter('.buffer', (obj) => {
|
|
409
485
|
if (typeof obj === 'string') {
|
|
410
486
|
const ctor = ctx.types.typedElem?.get(obj)
|
|
411
487
|
if (ctor === 'new.ArrayBuffer') return asF64(emit(obj))
|
|
@@ -425,7 +501,7 @@ export default (ctx) => {
|
|
|
425
501
|
|
|
426
502
|
// .byteLength — BUFFER: raw __len. Owned TYPED: elemCount * stride.
|
|
427
503
|
// View TYPED (incl. DataView): descriptor[0], via the __byte_length fallback.
|
|
428
|
-
|
|
504
|
+
registerGetter('.byteLength', (obj) => {
|
|
429
505
|
if (typeof obj === 'string') {
|
|
430
506
|
const ctor = ctx.types.typedElem?.get(obj)
|
|
431
507
|
if (ctor === 'new.ArrayBuffer') {
|
|
@@ -449,7 +525,7 @@ export default (ctx) => {
|
|
|
449
525
|
})
|
|
450
526
|
|
|
451
527
|
// .byteOffset — owned: 0. View: descriptor[4] - descriptor[8].
|
|
452
|
-
|
|
528
|
+
registerGetter('.byteOffset', (obj) => {
|
|
453
529
|
if (typeof obj === 'string') {
|
|
454
530
|
const ctor = ctx.types.typedElem?.get(obj)
|
|
455
531
|
if (ctor?.endsWith('.view')) {
|
|
@@ -538,6 +614,10 @@ export default (ctx) => {
|
|
|
538
614
|
// We treat `undefined`/`null`/`0`/`''` as falsy (BE) per ToBoolean.
|
|
539
615
|
const staticLE = (node) => {
|
|
540
616
|
if (node === undefined) return false // arg omitted → BE
|
|
617
|
+
// Prepare lowers a literal `true`/`false` endianness flag to `['bool', 0|1]`;
|
|
618
|
+
// fold it so `dv.getInt16(p, true)` is one native load (no per-access LE branch
|
|
619
|
+
// + dead byte-swap arm — the dominant overhead in DataView codec loops).
|
|
620
|
+
if (Array.isArray(node) && node[0] === 'bool') return !!node[1]
|
|
541
621
|
if (Array.isArray(node) && node[0] == null) {
|
|
542
622
|
// [] → undefined, [null, x] → literal x
|
|
543
623
|
if (node.length === 1) return false
|
|
@@ -598,7 +678,18 @@ export default (ctx) => {
|
|
|
598
678
|
(f64.reinterpret_i64 (i64.const ${_NAN_BITS}))
|
|
599
679
|
(local.get $v)
|
|
600
680
|
(f64.ne (local.get $v) (local.get $v))))`
|
|
601
|
-
|
|
681
|
+
// Inline the NaN-canonicalization select rather than a call — a DataView float
|
|
682
|
+
// read runs this per sample in codec loops, and the inlined `(select nan v (v≠v))`
|
|
683
|
+
// is the exact idiom the auto-vectorizer recognizes (vs an opaque call).
|
|
684
|
+
const canonNaN = (vIR) => {
|
|
685
|
+
const t = temp('cn')
|
|
686
|
+
return typed(['block', ['result', 'f64'],
|
|
687
|
+
['local.set', `$${t}`, vIR],
|
|
688
|
+
['select',
|
|
689
|
+
['f64.reinterpret_i64', ['i64.const', _NAN_BITS]],
|
|
690
|
+
['local.get', `$${t}`],
|
|
691
|
+
['f64.ne', ['local.get', `$${t}`], ['local.get', `$${t}`]]]], 'f64')
|
|
692
|
+
}
|
|
602
693
|
|
|
603
694
|
// ToIndex for DataView byte offsets (spec 25.1.2.x getViewValue/setViewValue):
|
|
604
695
|
// ToIntegerOrInfinity then reject negatives and >2^53-1 with a RangeError. The
|
|
@@ -639,8 +730,25 @@ export default (ctx) => {
|
|
|
639
730
|
const dvIndexChecked = (offNode, size, viewSize) => {
|
|
640
731
|
ctx.runtime.throws = true
|
|
641
732
|
const idxT = tempI32('dvb')
|
|
733
|
+
const offIR = emit(offNode)
|
|
734
|
+
// Fast path: a proven-i32 byte offset (a loop-advanced `p`, `i*2`, a `|0` value)
|
|
735
|
+
// carries no fraction/NaN/negative-from-ToNumber surprise, so it skips the f64
|
|
736
|
+
// ToIndex round-trip and the `__dv_index` CALL — the dominant per-sample cost in
|
|
737
|
+
// DataView-based codec loops. An i32 range (`<0`) + bounds (`>viewSize-size`)
|
|
738
|
+
// check is all the spec needs here; this lowers `dv.getInt16(p,true)` to one
|
|
739
|
+
// `i32.load16_s`, matching the explicit-typed-view path.
|
|
740
|
+
if (offIR.type === 'i32') {
|
|
741
|
+
return typed(['block', ['result', 'i32'],
|
|
742
|
+
['local.set', `$${idxT}`, offIR],
|
|
743
|
+
['if', ['i32.or',
|
|
744
|
+
['i32.lt_s', ['local.get', `$${idxT}`], ['i32.const', 0]],
|
|
745
|
+
['i32.gt_s', ['local.get', `$${idxT}`], ['i32.sub', viewSize, ['i32.const', size]]]],
|
|
746
|
+
['then', ['throw', '$__jz_err', ['f64.const', 0]]]],
|
|
747
|
+
['local.get', `$${idxT}`]], 'i32')
|
|
748
|
+
}
|
|
749
|
+
inc('__dv_index')
|
|
642
750
|
return typed(['block', ['result', 'i32'],
|
|
643
|
-
['local.set', `$${idxT}`,
|
|
751
|
+
['local.set', `$${idxT}`, typed(['call', '$__dv_index', toNumF64(offNode, offIR)], 'i32')],
|
|
644
752
|
['if', ['i32.gt_s', ['local.get', `$${idxT}`], ['i32.sub', viewSize, ['i32.const', size]]],
|
|
645
753
|
['then', ['throw', '$__jz_err', ['f64.const', 0]]]],
|
|
646
754
|
['local.get', `$${idxT}`]], 'i32')
|
|
@@ -807,16 +915,11 @@ export default (ctx) => {
|
|
|
807
915
|
len: ['i32.mul', ['local.get', `$${len}`], ['i32.const', stride]], stride: 1, tag: 'tf' })
|
|
808
916
|
const t = out.local
|
|
809
917
|
const id = ctx.func.uniq++
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
['f32.demote_f64', ['f64.load', ['i32.add', ['local.get', `$${off}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]]
|
|
816
|
-
: [store,
|
|
817
|
-
['i32.add', ['local.get', `$${t}`], ['i32.mul', ['local.get', `$${i}`], ['i32.const', stride]]],
|
|
818
|
-
[(elemType & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s',
|
|
819
|
-
['f64.load', ['i32.add', ['local.get', `$${off}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]]
|
|
918
|
+
const conv = FROM_F64[elemType]
|
|
919
|
+
const srcF64 = ['f64.load', ['i32.add', ['local.get', `$${off}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]
|
|
920
|
+
const storeExpr = [store,
|
|
921
|
+
['i32.add', ['local.get', `$${t}`], ['i32.mul', ['local.get', `$${i}`], ['i32.const', stride]]],
|
|
922
|
+
conv ? [conv, srcF64] : srcF64]
|
|
820
923
|
return typed(['block', ['result', 'f64'],
|
|
821
924
|
['local.set', `$${srcL}`, asF64(emit(src))],
|
|
822
925
|
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${srcL}`]]]],
|
|
@@ -842,18 +945,28 @@ export default (ctx) => {
|
|
|
842
945
|
* at the chain output (the result is always an owned typed array). */
|
|
843
946
|
const TYPED_CHAIN_METHODS = new Set(['map', 'filter', 'slice'])
|
|
844
947
|
const resolveElem = (arr) => {
|
|
845
|
-
let receiver = arr, chainOutput = false
|
|
948
|
+
let receiver = arr, chainOutput = false, viewOutput = false
|
|
846
949
|
// Walk method-call chain inward. `arr.method(...)` parses as
|
|
847
|
-
// ['()', ['.', recv, 'method'], ...args] — peel until we hit a name.
|
|
950
|
+
// ['()', ['.', recv, 'method'], ...args] — peel until we hit a name. The OUTERMOST
|
|
951
|
+
// op decides view-ness: `.subarray(...)` yields a zero-copy VIEW (reads must indirect
|
|
952
|
+
// through the descriptor), whereas `.map`/`.slice`/… yield a fresh non-view copy.
|
|
848
953
|
while (Array.isArray(receiver) && receiver[0] === '()' &&
|
|
849
954
|
Array.isArray(receiver[1]) && receiver[1][0] === '.' &&
|
|
850
|
-
TYPED_CHAIN_METHODS.has(receiver[1][2])) {
|
|
955
|
+
(TYPED_CHAIN_METHODS.has(receiver[1][2]) || receiver[1][2] === 'subarray')) {
|
|
956
|
+
if (!chainOutput) viewOutput = receiver[1][2] === 'subarray'
|
|
851
957
|
receiver = receiver[1][1]
|
|
852
958
|
chainOutput = true
|
|
853
959
|
}
|
|
854
|
-
|
|
960
|
+
// Nested receiver `arr[i]` where `arr`'s elements are typed arrays of a known
|
|
961
|
+
// ctor (`Array.from(n, () => new Float32Array())` — codec channelData). The i-th
|
|
962
|
+
// element IS that owned typed array; emit(receiver) already loads its pointer, so
|
|
963
|
+
// the standard typedDataAddr path inlines `arr[i][j]` to a direct load/store.
|
|
964
|
+
const ctor = (Array.isArray(receiver) && receiver[0] === '[]' && receiver.length === 3 && typeof receiver[1] === 'string'
|
|
965
|
+
? ctx.func.localReps?.get(receiver[1])?.arrayElemTypedCtor
|
|
966
|
+
: null)
|
|
967
|
+
|| (typeof receiver === 'string' && ctx.types.typedElem?.get(receiver))
|
|
855
968
|
if (!ctor) return null
|
|
856
|
-
const isView = !chainOutput && ctor.endsWith('.view')
|
|
969
|
+
const isView = viewOutput || (!chainOutput && ctor.endsWith('.view'))
|
|
857
970
|
const name = ctor.endsWith('.view') ? ctor.slice(4, -5) : ctor.slice(4)
|
|
858
971
|
const et = TYPED_ELEM_CODE[name]
|
|
859
972
|
return et == null ? null : { et, isView, isBigInt: name === 'BigInt64Array' || name === 'BigUint64Array' }
|
|
@@ -863,71 +976,22 @@ export default (ctx) => {
|
|
|
863
976
|
* Owned: low 32 bits of the NaN-box (or the unboxed local directly).
|
|
864
977
|
* View: load descriptor[4]. Uses ptrOffsetIR so unboxed-TYPED locals pass through
|
|
865
978
|
* without a rebox-then-unbox round trip, and globals fold to inline bit-extract. */
|
|
979
|
+
// A typed array (Float64Array/Int32Array/…) is a FIXED-SIZE allocation — it has no
|
|
980
|
+
// grow op, so it can never relocate, so its base needs no realloc-forwarding follow.
|
|
981
|
+
// An already-unboxed pointer is the offset itself; a boxed f64 pointer extracts its
|
|
982
|
+
// low-32 offset directly — no __ptr_offset call. (The general ptrOffsetIR keeps the
|
|
983
|
+
// forwarding follow because ARRAY/HASH/SET/MAP relocate and an *inferred* OBJECT can
|
|
984
|
+
// alias a relocated ARRAY — but VAL.TYPED is a narrow type that can only be a real
|
|
985
|
+
// typed array, so the follow is provably dead here.)
|
|
986
|
+
const typedBase = (objIR) => objIR.ptrKind != null && objIR.ptrKind !== VAL.ARRAY
|
|
987
|
+
? objIR
|
|
988
|
+
: ['i32.wrap_i64', ['i64.and', ['i64.reinterpret_f64', asF64(objIR)], ['i64.const', LAYOUT.OFFSET_MASK]]]
|
|
866
989
|
const typedDataAddr = (objIR, isView) => isView
|
|
867
|
-
? ['i32.load', ['i32.add',
|
|
868
|
-
:
|
|
990
|
+
? ['i32.load', ['i32.add', typedBase(objIR), ['i32.const', 4]]]
|
|
991
|
+
: typedBase(objIR)
|
|
869
992
|
|
|
870
993
|
// Runtime-dispatch typed index: checks ptr_type + aux to load with correct stride.
|
|
871
994
|
// For TYPED views (aux bit 3), $off indirects through descriptor[4] to real data.
|
|
872
|
-
// Factory — collapses to ARRAY-only f64 indexing when no TYPED pointer can reach here.
|
|
873
|
-
// Identical factory in array.js; whichever module loads last wins the registration.
|
|
874
|
-
ctx.core.stdlib['__typed_idx'] = () => {
|
|
875
|
-
if (!ctx.features.typedarray && !ctx.features.external) {
|
|
876
|
-
return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
|
|
877
|
-
(local $len i32)
|
|
878
|
-
(local.set $len (call $__len (local.get $ptr)))
|
|
879
|
-
(if (result f64)
|
|
880
|
-
(i32.or
|
|
881
|
-
(i32.lt_s (local.get $i) (i32.const 0))
|
|
882
|
-
(i32.ge_u (local.get $i) (local.get $len)))
|
|
883
|
-
(then (f64.const nan:${UNDEF_NAN}))
|
|
884
|
-
(else (f64.load (i32.add (call $__ptr_offset (local.get $ptr)) (i32.shl (local.get $i) (i32.const 3)))))))`
|
|
885
|
-
}
|
|
886
|
-
return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
|
|
887
|
-
(local $off i32) (local $et i32) (local $len i32) (local $aux i32)
|
|
888
|
-
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
889
|
-
;; ARRAY fast path: __ptr_offset already followed any forwarding — read header len + f64.load, no $__len call.
|
|
890
|
-
(if (i32.and (i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.ARRAY})) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
891
|
-
(then (return (if (result f64)
|
|
892
|
-
(i32.and (i32.ge_s (local.get $i) (i32.const 0)) (i32.lt_u (local.get $i) (i32.load (i32.sub (local.get $off) (i32.const 8)))))
|
|
893
|
-
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
894
|
-
(else (f64.const nan:${UNDEF_NAN}))))))
|
|
895
|
-
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
896
|
-
(if
|
|
897
|
-
(i32.and
|
|
898
|
-
(i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.TYPED}))
|
|
899
|
-
(i32.ne (i32.and (local.get $aux) (i32.const 8)) (i32.const 0)))
|
|
900
|
-
(then (local.set $off (i32.load (i32.add (local.get $off) (i32.const 4))))))
|
|
901
|
-
(local.set $len (call $__len (local.get $ptr)))
|
|
902
|
-
(if (result f64)
|
|
903
|
-
(i32.or
|
|
904
|
-
(i32.lt_s (local.get $i) (i32.const 0))
|
|
905
|
-
(i32.ge_u (local.get $i) (local.get $len)))
|
|
906
|
-
(then (f64.const nan:${UNDEF_NAN}))
|
|
907
|
-
(else
|
|
908
|
-
(if (result f64) (i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.TYPED}))
|
|
909
|
-
(then
|
|
910
|
-
(local.set $et (i32.and (local.get $aux) (i32.const 7)))
|
|
911
|
-
(if (result f64) (i32.ge_u (local.get $et) (i32.const 6))
|
|
912
|
-
(then (if (result f64) (i32.eq (local.get $et) (i32.const 7))
|
|
913
|
-
(then (if (result f64) (i32.and (local.get $aux) (i32.const ${TYPED_ELEM_BIGINT_FLAG}))
|
|
914
|
-
(then (f64.reinterpret_i64 (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
|
|
915
|
-
(else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))
|
|
916
|
-
(else (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
917
|
-
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 4))
|
|
918
|
-
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
919
|
-
(then (f64.convert_i32_u (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
|
|
920
|
-
(else (f64.convert_i32_s (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
921
|
-
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 2))
|
|
922
|
-
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
923
|
-
(then (f64.convert_i32_u (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
|
|
924
|
-
(else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))))
|
|
925
|
-
(else (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
926
|
-
(then (f64.convert_i32_u (i32.load8_u (i32.add (local.get $off) (local.get $i)))))
|
|
927
|
-
(else (f64.convert_i32_s (i32.load8_s (i32.add (local.get $off) (local.get $i)))))))))))))
|
|
928
|
-
(else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))))`
|
|
929
|
-
}
|
|
930
|
-
|
|
931
995
|
ctx.core.stdlib['__typed_set_idx'] = `(func $__typed_set_idx (param $ptr i64) (param $i i32) (param $v f64) (result f64)
|
|
932
996
|
(local $off i32) (local $aux i32) (local $et i32) (local $bits i32)
|
|
933
997
|
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
@@ -956,6 +1020,209 @@ export default (ctx) => {
|
|
|
956
1020
|
(else (i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $bits))))))))))))
|
|
957
1021
|
(local.get $v))`
|
|
958
1022
|
|
|
1023
|
+
// .fill(value, start?, end?) for typed arrays. The plain-array __arr_fill gates
|
|
1024
|
+
// on PTR.ARRAY and silently no-ops a typed receiver (the storage layout and
|
|
1025
|
+
// element width differ); this loops the element-width-aware __typed_set_idx
|
|
1026
|
+
// over the clamped range so every element kind (u8…f64, BigInt) fills correctly.
|
|
1027
|
+
// start/end default 0/length, accept negatives, and clamp to [0, length].
|
|
1028
|
+
ctx.core.stdlib['__typed_fill'] = `(func $__typed_fill (param $ptr i64) (param $val f64) (param $start i32) (param $end i32) (result f64)
|
|
1029
|
+
(local $len i32) (local $i i32)
|
|
1030
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
1031
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
1032
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
1033
|
+
(local.set $i (local.get $start))
|
|
1034
|
+
(block $done (loop $fill
|
|
1035
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $end)))
|
|
1036
|
+
(drop (call $__typed_set_idx (local.get $ptr) (local.get $i) (local.get $val)))
|
|
1037
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1038
|
+
(br $fill)))
|
|
1039
|
+
(f64.reinterpret_i64 (local.get $ptr)))`
|
|
1040
|
+
|
|
1041
|
+
// Element-width/-kind-aware read: arr[i] → f64, the read mirror of __typed_set_idx.
|
|
1042
|
+
// Integers convert by signedness (et&1 ⇒ unsigned); BigInt returns the raw i64 bits
|
|
1043
|
+
// reinterpreted as f64 so a get→set roundtrip is bit-exact (set_idx stores the bits
|
|
1044
|
+
// back unchanged). Used by the in-place algorithms below for random-access reads.
|
|
1045
|
+
ctx.core.stdlib['__typed_get_idx'] = `(func $__typed_get_idx (param $ptr i64) (param $i i32) (result f64)
|
|
1046
|
+
(local $off i32) (local $aux i32) (local $et i32)
|
|
1047
|
+
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
1048
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
1049
|
+
(if (i32.and (local.get $aux) (i32.const 8))
|
|
1050
|
+
(then (local.set $off (i32.load (i32.add (local.get $off) (i32.const 4))))))
|
|
1051
|
+
(local.set $et (i32.and (local.get $aux) (i32.const 7)))
|
|
1052
|
+
(if (result f64) (i32.and (local.get $aux) (i32.const ${TYPED_ELEM_BIGINT_FLAG}))
|
|
1053
|
+
(then (f64.reinterpret_i64 (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
|
|
1054
|
+
(else (if (result f64) (i32.eq (local.get $et) (i32.const 7))
|
|
1055
|
+
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
1056
|
+
(else (if (result f64) (i32.eq (local.get $et) (i32.const 6))
|
|
1057
|
+
(then (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
|
|
1058
|
+
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 4))
|
|
1059
|
+
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
1060
|
+
(then (f64.convert_i32_u (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
|
|
1061
|
+
(else (f64.convert_i32_s (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
1062
|
+
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 2))
|
|
1063
|
+
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
1064
|
+
(then (f64.convert_i32_u (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
|
|
1065
|
+
(else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))))
|
|
1066
|
+
(else (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
1067
|
+
(then (f64.convert_i32_u (i32.load8_u (i32.add (local.get $off) (local.get $i)))))
|
|
1068
|
+
(else (f64.convert_i32_s (i32.load8_s (i32.add (local.get $off) (local.get $i)))))))))))))))))`
|
|
1069
|
+
|
|
1070
|
+
// .reverse() — in-place, element-kind-agnostic via get/set (bit-exact for BigInt).
|
|
1071
|
+
ctx.core.stdlib['__typed_reverse'] = `(func $__typed_reverse (param $ptr i64) (result f64)
|
|
1072
|
+
(local $len i32) (local $i i32) (local $j i32) (local $t f64)
|
|
1073
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
1074
|
+
(local.set $i (i32.const 0))
|
|
1075
|
+
(local.set $j (i32.sub (local.get $len) (i32.const 1)))
|
|
1076
|
+
(block $done (loop $rev
|
|
1077
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $j)))
|
|
1078
|
+
(local.set $t (call $__typed_get_idx (local.get $ptr) (local.get $i)))
|
|
1079
|
+
(drop (call $__typed_set_idx (local.get $ptr) (local.get $i) (call $__typed_get_idx (local.get $ptr) (local.get $j))))
|
|
1080
|
+
(drop (call $__typed_set_idx (local.get $ptr) (local.get $j) (local.get $t)))
|
|
1081
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1082
|
+
(local.set $j (i32.sub (local.get $j) (i32.const 1)))
|
|
1083
|
+
(br $rev)))
|
|
1084
|
+
(f64.reinterpret_i64 (local.get $ptr)))`
|
|
1085
|
+
|
|
1086
|
+
// .copyWithin(target, start, end) — in-place overlap-safe move. Indices accept
|
|
1087
|
+
// negatives (from end) and clamp to [0, len]; count = min(end-start, len-target).
|
|
1088
|
+
// Direction picked so overlapping ranges don't clobber unread source elements.
|
|
1089
|
+
ctx.core.stdlib['__typed_copyWithin'] = `(func $__typed_copyWithin (param $ptr i64) (param $target i32) (param $start i32) (param $end i32) (result f64)
|
|
1090
|
+
(local $len i32) (local $count i32) (local $k i32)
|
|
1091
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
1092
|
+
(local.set $target (call $__clamp_idx (local.get $target) (local.get $len)))
|
|
1093
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
1094
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
1095
|
+
(local.set $count (i32.sub (local.get $end) (local.get $start)))
|
|
1096
|
+
(if (i32.gt_s (local.get $count) (i32.sub (local.get $len) (local.get $target)))
|
|
1097
|
+
(then (local.set $count (i32.sub (local.get $len) (local.get $target)))))
|
|
1098
|
+
(if (i32.lt_s (local.get $target) (local.get $start))
|
|
1099
|
+
(then
|
|
1100
|
+
(local.set $k (i32.const 0))
|
|
1101
|
+
(block $fd (loop $fl
|
|
1102
|
+
(br_if $fd (i32.ge_s (local.get $k) (local.get $count)))
|
|
1103
|
+
(drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $target) (local.get $k))
|
|
1104
|
+
(call $__typed_get_idx (local.get $ptr) (i32.add (local.get $start) (local.get $k)))))
|
|
1105
|
+
(local.set $k (i32.add (local.get $k) (i32.const 1)))
|
|
1106
|
+
(br $fl))))
|
|
1107
|
+
(else
|
|
1108
|
+
(local.set $k (local.get $count))
|
|
1109
|
+
(block $bd (loop $bl
|
|
1110
|
+
(br_if $bd (i32.le_s (local.get $k) (i32.const 0)))
|
|
1111
|
+
(local.set $k (i32.sub (local.get $k) (i32.const 1)))
|
|
1112
|
+
(drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $target) (local.get $k))
|
|
1113
|
+
(call $__typed_get_idx (local.get $ptr) (i32.add (local.get $start) (local.get $k)))))
|
|
1114
|
+
(br $bl)))))
|
|
1115
|
+
(f64.reinterpret_i64 (local.get $ptr)))`
|
|
1116
|
+
|
|
1117
|
+
// .sort() — default numeric order (insertion sort, stable). NaN sorts to the end,
|
|
1118
|
+
// -0 before +0 (the equal-value tiebreak via signed-bit compare). BigInt arrays are
|
|
1119
|
+
// compared as signed i64 on their exact bits. A user comparator is handled inline by
|
|
1120
|
+
// the .typed:sort emitter (this helper is the no-argument numeric path).
|
|
1121
|
+
ctx.core.stdlib['__typed_sort'] = `(func $__typed_sort (param $ptr i64) (result f64)
|
|
1122
|
+
(local $isbig i32) (local $len i32) (local $i i32) (local $j i32) (local $saved f64) (local $nb f64)
|
|
1123
|
+
(local.set $isbig (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const ${TYPED_ELEM_BIGINT_FLAG})))
|
|
1124
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
1125
|
+
(local.set $i (i32.const 1))
|
|
1126
|
+
(block $od (loop $ol
|
|
1127
|
+
(br_if $od (i32.ge_s (local.get $i) (local.get $len)))
|
|
1128
|
+
(local.set $saved (call $__typed_get_idx (local.get $ptr) (local.get $i)))
|
|
1129
|
+
(local.set $j (i32.sub (local.get $i) (i32.const 1)))
|
|
1130
|
+
(block $id (loop $il
|
|
1131
|
+
(br_if $id (i32.lt_s (local.get $j) (i32.const 0)))
|
|
1132
|
+
(local.set $nb (call $__typed_get_idx (local.get $ptr) (local.get $j)))
|
|
1133
|
+
(br_if $id (i32.eqz
|
|
1134
|
+
(if (result i32) (local.get $isbig)
|
|
1135
|
+
(then (i64.gt_s (i64.reinterpret_f64 (local.get $nb)) (i64.reinterpret_f64 (local.get $saved))))
|
|
1136
|
+
(else (if (result i32) (f64.ne (local.get $nb) (local.get $nb))
|
|
1137
|
+
(then (i32.eqz (f64.ne (local.get $saved) (local.get $saved))))
|
|
1138
|
+
(else (if (result i32) (f64.ne (local.get $saved) (local.get $saved))
|
|
1139
|
+
(then (i32.const 0))
|
|
1140
|
+
(else (if (result i32) (f64.gt (local.get $nb) (local.get $saved))
|
|
1141
|
+
(then (i32.const 1))
|
|
1142
|
+
(else (if (result i32) (f64.lt (local.get $nb) (local.get $saved))
|
|
1143
|
+
(then (i32.const 0))
|
|
1144
|
+
(else (i64.gt_s (i64.reinterpret_f64 (local.get $nb)) (i64.reinterpret_f64 (local.get $saved)))))))))))))))
|
|
1145
|
+
(drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $j) (i32.const 1)) (local.get $nb)))
|
|
1146
|
+
(local.set $j (i32.sub (local.get $j) (i32.const 1)))
|
|
1147
|
+
(br $il)))
|
|
1148
|
+
(drop (call $__typed_set_idx (local.get $ptr) (i32.add (local.get $j) (i32.const 1)) (local.get $saved)))
|
|
1149
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1150
|
+
(br $ol)))
|
|
1151
|
+
(f64.reinterpret_i64 (local.get $ptr)))`
|
|
1152
|
+
|
|
1153
|
+
ctx.core.emit['.typed:fill'] = (arr, val, start, end) => {
|
|
1154
|
+
inc('__typed_fill')
|
|
1155
|
+
return typed(['call', '$__typed_fill',
|
|
1156
|
+
asI64(emit(arr)),
|
|
1157
|
+
val == null ? undefExpr() : asF64(emit(val)),
|
|
1158
|
+
start == null ? ['i32.const', 0] : asI32(emit(start)),
|
|
1159
|
+
end == null ? ['i32.const', 0x7FFFFFFF] : asI32(emit(end))], 'f64')
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// .reverse() / .copyWithin(...) for typed arrays. The plain-array helpers gate on
|
|
1163
|
+
// PTR.ARRAY and silently no-op a typed receiver; these go through the element-kind-
|
|
1164
|
+
// aware get/set helpers so every width and signedness reverses/moves correctly.
|
|
1165
|
+
ctx.core.emit['.typed:reverse'] = (arr) => {
|
|
1166
|
+
inc('__typed_reverse')
|
|
1167
|
+
return typed(['call', '$__typed_reverse', asI64(emit(arr))], 'f64')
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
ctx.core.emit['.typed:copyWithin'] = (arr, target, start, end) => {
|
|
1171
|
+
inc('__typed_copyWithin')
|
|
1172
|
+
return typed(['call', '$__typed_copyWithin',
|
|
1173
|
+
asI64(emit(arr)),
|
|
1174
|
+
target == null ? ['i32.const', 0] : asI32(emit(target)),
|
|
1175
|
+
start == null ? ['i32.const', 0] : asI32(emit(start)),
|
|
1176
|
+
end == null ? ['i32.const', 0x7FFFFFFF] : asI32(emit(end))], 'f64')
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// .sort(compareFn?) for typed arrays. No argument → the numeric __typed_sort helper
|
|
1180
|
+
// (typed-array default is NUMERIC, unlike Array.sort's lexicographic default — so it
|
|
1181
|
+
// must NOT route through the plain-array string comparator). With a comparator, an
|
|
1182
|
+
// insertion sort is emitted inline, calling the closure per neighbor compare; a
|
|
1183
|
+
// positive return shifts (same convention as Array.prototype.sort).
|
|
1184
|
+
// Sort the typed array VALUE `arrValIR` in place and return it. Factored out so
|
|
1185
|
+
// .typed:sort sorts the receiver and .typed:toSorted sorts a fresh copy with one body.
|
|
1186
|
+
const emitTypedSort = (arrValIR, fn) => {
|
|
1187
|
+
if (fn == null) {
|
|
1188
|
+
inc('__typed_sort')
|
|
1189
|
+
return typed(['call', '$__typed_sort', asI64(arrValIR)], 'f64')
|
|
1190
|
+
}
|
|
1191
|
+
inc('__len', '__typed_get_idx', '__typed_set_idx')
|
|
1192
|
+
const arrL = temp('tsa'), cbL = temp('tsf')
|
|
1193
|
+
const len = tempI32('tsn'), i = tempI32('tsi'), j = tempI32('tsj')
|
|
1194
|
+
const cur = temp('tsc'), nb = temp('tsb')
|
|
1195
|
+
const id = ctx.func.uniq++
|
|
1196
|
+
const oE = `$tsoe${id}`, oL = `$tsol${id}`, iE = `$tsie${id}`, iL = `$tsil${id}`
|
|
1197
|
+
const ptr = () => ['i64.reinterpret_f64', ['local.get', `$${arrL}`]]
|
|
1198
|
+
const jp1 = ['i32.add', ['local.get', `$${j}`], ['i32.const', 1]]
|
|
1199
|
+
return typed(['block', ['result', 'f64'],
|
|
1200
|
+
['local.set', `$${arrL}`, asF64(arrValIR)],
|
|
1201
|
+
['local.set', `$${cbL}`, asF64(emit(fn))],
|
|
1202
|
+
['local.set', `$${len}`, ['call', '$__len', ptr()]],
|
|
1203
|
+
['local.set', `$${i}`, ['i32.const', 1]],
|
|
1204
|
+
['block', oE, ['loop', oL,
|
|
1205
|
+
['br_if', oE, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
1206
|
+
['local.set', `$${cur}`, ['call', '$__typed_get_idx', ptr(), ['local.get', `$${i}`]]],
|
|
1207
|
+
['local.set', `$${j}`, ['i32.sub', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1208
|
+
['block', iE, ['loop', iL,
|
|
1209
|
+
['br_if', iE, ['i32.lt_s', ['local.get', `$${j}`], ['i32.const', 0]]],
|
|
1210
|
+
['local.set', `$${nb}`, ['call', '$__typed_get_idx', ptr(), ['local.get', `$${j}`]]],
|
|
1211
|
+
// Break unless cmp(neighbor, cur) > 0. f64.gt is false for NaN (spec NaN-as-0).
|
|
1212
|
+
['br_if', iE, ['i32.eqz', ['f64.gt',
|
|
1213
|
+
asF64(ctx.closure.call(typed(['local.get', `$${cbL}`], 'f64'),
|
|
1214
|
+
[typed(['local.get', `$${nb}`], 'f64'), typed(['local.get', `$${cur}`], 'f64')])),
|
|
1215
|
+
['f64.const', 0]]]],
|
|
1216
|
+
['drop', ['call', '$__typed_set_idx', ptr(), jp1, ['local.get', `$${nb}`]]],
|
|
1217
|
+
['local.set', `$${j}`, ['i32.sub', ['local.get', `$${j}`], ['i32.const', 1]]],
|
|
1218
|
+
['br', iL]]],
|
|
1219
|
+
['drop', ['call', '$__typed_set_idx', ptr(), jp1, ['local.get', `$${cur}`]]],
|
|
1220
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1221
|
+
['br', oL]]],
|
|
1222
|
+
['local.get', `$${arrL}`]], 'f64')
|
|
1223
|
+
}
|
|
1224
|
+
ctx.core.emit['.typed:sort'] = (arr, fn) => emitTypedSort(emit(arr), fn)
|
|
1225
|
+
|
|
959
1226
|
// Type-aware TypedArray read: arr[i]
|
|
960
1227
|
ctx.core.emit['.typed:[]'] = (arr, i) => {
|
|
961
1228
|
const r = resolveElem(arr)
|
|
@@ -1007,20 +1274,28 @@ export default (ctx) => {
|
|
|
1007
1274
|
['f32.store', off, ['f32.demote_f64', ['local.get', `$${vt}`]]],
|
|
1008
1275
|
['local.get', `$${vt}`]], void_ ? 'void' : 'f64') // Float32Array
|
|
1009
1276
|
}
|
|
1010
|
-
// Integer store: when the source is already i32-typed (bitwise ops, |0,
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1277
|
+
// Integer store: when the source is already i32-typed (bitwise ops, |0, known-i32 var) —
|
|
1278
|
+
// OR an `f64.convert_i32_*` that peels back to i32 (an Int8/Uint8/Int16/… element READ
|
|
1279
|
+
// materialized as f64 by the universal value model) — store the i32 low bits directly,
|
|
1280
|
+
// skipping the f64 detour that costs a sign branch + i64 trunc + i32 wrap on every write.
|
|
1281
|
+
// This eradicates the f64 round-trip on byte/typed-array TRANSFORMS — `out[i] = table[in[j]]`
|
|
1282
|
+
// and `dst[i] = src[j]` (base64, qoi, wav, blur) — where both sides are integer elements.
|
|
1283
|
+
// `store8/16` mask the low bits, so storing the convert's i32 source is bit-identical; the
|
|
1284
|
+
// non-void result reboxes that i32 to f64 (the assignment's RHS value, in element range here).
|
|
1285
|
+
const i32Backed = valIR.type === 'i32' ||
|
|
1286
|
+
(Array.isArray(valIR) && (valIR[0] === 'f64.convert_i32_s' || valIR[0] === 'f64.convert_i32_u'))
|
|
1287
|
+
if (i32Backed) {
|
|
1288
|
+
const vi = asI32(valIR)
|
|
1289
|
+
const cheap = Array.isArray(vi) &&
|
|
1290
|
+
((vi[0] === 'local.get' && typeof vi[1] === 'string') ||
|
|
1291
|
+
(vi[0] === 'i32.const' && (typeof vi[1] === 'number' || typeof vi[1] === 'string')))
|
|
1292
|
+
if (void_ && cheap) return typed([STORE[et], off, vi], 'void')
|
|
1018
1293
|
const v32 = tempI32('tw')
|
|
1019
1294
|
return typed(void_ ? ['block',
|
|
1020
|
-
['local.set', `$${v32}`,
|
|
1295
|
+
['local.set', `$${v32}`, vi],
|
|
1021
1296
|
[STORE[et], off, ['local.get', `$${v32}`]]]
|
|
1022
1297
|
: ['block', ['result', 'f64'],
|
|
1023
|
-
['local.set', `$${v32}`,
|
|
1298
|
+
['local.set', `$${v32}`, vi],
|
|
1024
1299
|
[STORE[et], off, ['local.get', `$${v32}`]],
|
|
1025
1300
|
[(et & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', ['local.get', `$${v32}`]]], void_ ? 'void' : 'f64')
|
|
1026
1301
|
}
|
|
@@ -1257,18 +1532,54 @@ export default (ctx) => {
|
|
|
1257
1532
|
|
|
1258
1533
|
// .indexOf: scalar value-equality search. Returns -1 on miss. Compare on f64
|
|
1259
1534
|
// — Array.prototype.indexOf uses strict equality (NaN ≠ NaN).
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1535
|
+
// Effective start index for a fromIndex arg: negative counts from the end. The match
|
|
1536
|
+
// guard is `i >= start` and i ≥ 0, so a start below 0 needs no clamp (always passes).
|
|
1537
|
+
const fromStart = (fiL, len) => ['if', ['result', 'i32'],
|
|
1538
|
+
['i32.lt_s', ['local.get', `$${fiL}`], ['i32.const', 0]],
|
|
1539
|
+
['then', ['i32.add', ['local.get', `$${fiL}`], ['local.get', `$${len}`]]],
|
|
1540
|
+
['else', ['local.get', `$${fiL}`]]]
|
|
1541
|
+
|
|
1542
|
+
ctx.core.emit['.typed:indexOf'] = (arr, val, fromIndex) => {
|
|
1543
|
+
const found = tempI32('tif'), needle = temp('tin'), fiL = tempI32('tifx')
|
|
1544
|
+
const loop = typedLoop(arr, (load, i, len, _ptr, exit) => {
|
|
1545
|
+
const matched = ['f64.eq', load(), ['local.get', `$${needle}`]]
|
|
1546
|
+
const cond = fromIndex == null ? matched
|
|
1547
|
+
: ['i32.and', ['i32.ge_s', ['local.get', `$${i}`], fromStart(fiL, len)], matched]
|
|
1548
|
+
return [['if', cond, ['then',
|
|
1549
|
+
['local.set', `$${found}`, ['local.get', `$${i}`]], ['br', exit]]]]
|
|
1550
|
+
})
|
|
1268
1551
|
if (!loop) return null
|
|
1269
1552
|
return typed(['block', ['result', 'f64'],
|
|
1270
1553
|
['local.set', `$${needle}`, asF64(emit(val))],
|
|
1271
1554
|
['local.set', `$${found}`, ['i32.const', -1]],
|
|
1555
|
+
...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
|
|
1556
|
+
...loop.setup,
|
|
1557
|
+
['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// .lastIndexOf(val): the last index whose element strictly-equals val, else -1.
|
|
1561
|
+
// Was unimplemented for typed arrays (threw). A forward scan that keeps the latest
|
|
1562
|
+
// match needs no reverse iteration — equivalent for the no-fromIndex form, which is
|
|
1563
|
+
// the common case. With a fromIndex the matcher additionally bounds i ≤ fromIndex
|
|
1564
|
+
// (negative counts from the end, resolved against the typedLoop len). NaN never
|
|
1565
|
+
// strict-equals (f64.eq), matching JS.
|
|
1566
|
+
ctx.core.emit['.typed:lastIndexOf'] = (arr, val, fromIndex) => {
|
|
1567
|
+
const found = tempI32('tlf'), needle = temp('tln'), fiL = tempI32('tlfi')
|
|
1568
|
+
const loop = typedLoop(arr, (load, i, len) => {
|
|
1569
|
+
const matched = ['f64.eq', load(), ['local.get', `$${needle}`]]
|
|
1570
|
+
if (fromIndex == null)
|
|
1571
|
+
return [['if', matched, ['then', ['local.set', `$${found}`, ['local.get', `$${i}`]]]]]
|
|
1572
|
+
const lim = ['if', ['result', 'i32'], ['i32.lt_s', ['local.get', `$${fiL}`], ['i32.const', 0]],
|
|
1573
|
+
['then', ['i32.add', ['local.get', `$${fiL}`], ['local.get', `$${len}`]]],
|
|
1574
|
+
['else', ['local.get', `$${fiL}`]]]
|
|
1575
|
+
return [['if', ['i32.and', ['i32.le_s', ['local.get', `$${i}`], lim], matched],
|
|
1576
|
+
['then', ['local.set', `$${found}`, ['local.get', `$${i}`]]]]]
|
|
1577
|
+
})
|
|
1578
|
+
if (!loop) return null
|
|
1579
|
+
return typed(['block', ['result', 'f64'],
|
|
1580
|
+
['local.set', `$${needle}`, asF64(emit(val))],
|
|
1581
|
+
['local.set', `$${found}`, ['i32.const', -1]],
|
|
1582
|
+
...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
|
|
1272
1583
|
...loop.setup,
|
|
1273
1584
|
['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
|
|
1274
1585
|
}
|
|
@@ -1276,21 +1587,23 @@ export default (ctx) => {
|
|
|
1276
1587
|
// .includes: like indexOf but NaN-equal-NaN (JS spec). Stash needle bits as
|
|
1277
1588
|
// i64 and compare via i64.eq so two NaNs with matching bit patterns match
|
|
1278
1589
|
// (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,
|
|
1282
|
-
['
|
|
1283
|
-
['
|
|
1284
|
-
|
|
1285
|
-
['i64.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
['
|
|
1289
|
-
|
|
1590
|
+
ctx.core.emit['.typed:includes'] = (arr, val, fromIndex) => {
|
|
1591
|
+
const found = tempI32('thf'), needle = temp('thn'), fiL = tempI32('thx')
|
|
1592
|
+
const loop = typedLoop(arr, (load, i, len, _ptr, exit) => {
|
|
1593
|
+
const matched = ['i32.or',
|
|
1594
|
+
['f64.eq', load(), ['local.get', `$${needle}`]],
|
|
1595
|
+
['i64.eq',
|
|
1596
|
+
['i64.reinterpret_f64', load()],
|
|
1597
|
+
['i64.reinterpret_f64', ['local.get', `$${needle}`]]]]
|
|
1598
|
+
const cond = fromIndex == null ? matched
|
|
1599
|
+
: ['i32.and', ['i32.ge_s', ['local.get', `$${i}`], fromStart(fiL, len)], matched]
|
|
1600
|
+
return [['if', cond, ['then', ['local.set', `$${found}`, ['i32.const', 1]], ['br', exit]]]]
|
|
1601
|
+
})
|
|
1290
1602
|
if (!loop) return null
|
|
1291
1603
|
return typed(['block', ['result', 'f64'],
|
|
1292
1604
|
['local.set', `$${needle}`, asF64(emit(val))],
|
|
1293
1605
|
['local.set', `$${found}`, ['i32.const', 0]],
|
|
1606
|
+
...(fromIndex == null ? [] : [['local.set', `$${fiL}`, asI32(emit(fromIndex))]]),
|
|
1294
1607
|
...loop.setup,
|
|
1295
1608
|
['f64.convert_i32_s', ['local.get', `$${found}`]]], 'f64')
|
|
1296
1609
|
}
|
|
@@ -1328,6 +1641,40 @@ export default (ctx) => {
|
|
|
1328
1641
|
ctx.core.emit['.typed:find'] = (arr, fn) => findCommon(arr, fn, false)
|
|
1329
1642
|
ctx.core.emit['.typed:findIndex'] = (arr, fn) => findCommon(arr, fn, true)
|
|
1330
1643
|
|
|
1644
|
+
// .findLast / .findLastIndex — like find/findIndex but keep the LAST match instead of
|
|
1645
|
+
// the first, so no early break (a forward scan that overwrites on each hit). Without a
|
|
1646
|
+
// typed handler these routed through the plain-array versions, which read elements as
|
|
1647
|
+
// raw f64 and returned garbage for non-f64 typed arrays.
|
|
1648
|
+
const findLastCommon = (arr, fn, returnIndex) => {
|
|
1649
|
+
const cbLoc = temp('tLc'), result = temp('tLr'), foundIdx = tempI32('tLi')
|
|
1650
|
+
const loop = typedLoop(arr, (load, i) => {
|
|
1651
|
+
const itemLoc = temp('tLit')
|
|
1652
|
+
return [
|
|
1653
|
+
['local.set', `$${itemLoc}`, load()],
|
|
1654
|
+
['if', truthyIR(ctx.closure.call(
|
|
1655
|
+
typed(['local.get', `$${cbLoc}`], 'f64'),
|
|
1656
|
+
[typed(['local.get', `$${itemLoc}`], 'f64'),
|
|
1657
|
+
typed(['f64.convert_i32_s', ['local.get', `$${i}`]], 'f64')])),
|
|
1658
|
+
['then',
|
|
1659
|
+
returnIndex
|
|
1660
|
+
? ['local.set', `$${foundIdx}`, ['local.get', `$${i}`]]
|
|
1661
|
+
: ['local.set', `$${result}`, typed(['local.get', `$${itemLoc}`], 'f64')]]]
|
|
1662
|
+
]
|
|
1663
|
+
})
|
|
1664
|
+
if (!loop) return null
|
|
1665
|
+
return typed(['block', ['result', 'f64'],
|
|
1666
|
+
['local.set', `$${cbLoc}`, asF64(emit(fn))],
|
|
1667
|
+
returnIndex
|
|
1668
|
+
? ['local.set', `$${foundIdx}`, ['i32.const', -1]]
|
|
1669
|
+
: ['local.set', `$${result}`, undefExpr()],
|
|
1670
|
+
...loop.setup,
|
|
1671
|
+
returnIndex
|
|
1672
|
+
? typed(['f64.convert_i32_s', ['local.get', `$${foundIdx}`]], 'f64')
|
|
1673
|
+
: typed(['local.get', `$${result}`], 'f64')], 'f64')
|
|
1674
|
+
}
|
|
1675
|
+
ctx.core.emit['.typed:findLast'] = (arr, fn) => findLastCommon(arr, fn, false)
|
|
1676
|
+
ctx.core.emit['.typed:findLastIndex'] = (arr, fn) => findLastCommon(arr, fn, true)
|
|
1677
|
+
|
|
1331
1678
|
// .some / .every: short-circuit boolean reduction. some=∃, every=∀.
|
|
1332
1679
|
const anyAllCommon = (arr, fn, isEvery) => {
|
|
1333
1680
|
const cbLoc = temp('tac'), result = tempI32('tar')
|
|
@@ -1411,7 +1758,17 @@ export default (ctx) => {
|
|
|
1411
1758
|
// Bulk copy via `memory.copy`.
|
|
1412
1759
|
ctx.core.emit['.typed:slice'] = (arr, start, end) => {
|
|
1413
1760
|
const r = resolveElem(arr)
|
|
1414
|
-
if (!r
|
|
1761
|
+
if (!r) {
|
|
1762
|
+
// Elem type / view-ness not statically known (owned→view reassigned binding).
|
|
1763
|
+
// Dispatch off the runtime aux byte instead of crashing on empty IR.
|
|
1764
|
+
inc('__typed_slice_rt')
|
|
1765
|
+
return typed(['call', '$__typed_slice_rt',
|
|
1766
|
+
['i64.reinterpret_f64', asF64(emit(arr))],
|
|
1767
|
+
start == null ? ['i32.const', 0] : asI32(emit(start)),
|
|
1768
|
+
end == null ? ['i32.const', 0] : asI32(emit(end)),
|
|
1769
|
+
['i32.const', end == null ? 0 : 1]], 'f64')
|
|
1770
|
+
}
|
|
1771
|
+
if (r.isBigInt) return null
|
|
1415
1772
|
const { et, isView } = r
|
|
1416
1773
|
const arrLoc = temp('tsa'), srcPtr = tempI32('tssp'), srcLen = tempI32('tssl')
|
|
1417
1774
|
const lo = tempI32('tslo'), hi = tempI32('tshi'), n = tempI32('tsn')
|
|
@@ -1460,4 +1817,100 @@ export default (ctx) => {
|
|
|
1460
1817
|
['i32.shl', ['local.get', `$${n}`], ['i32.const', SHIFT[et]]]],
|
|
1461
1818
|
dst.ptr], 'f64')
|
|
1462
1819
|
}
|
|
1820
|
+
|
|
1821
|
+
// .toReversed() — a reversed COPY (receiver unchanged): full slice-copy, then reverse
|
|
1822
|
+
// the copy in place. (slice bails on BigInt, so BigInt receivers fall back to a throw.)
|
|
1823
|
+
ctx.core.emit['.typed:toReversed'] = (arr) => {
|
|
1824
|
+
const copy = ctx.core.emit['.typed:slice'](arr)
|
|
1825
|
+
if (!copy) return null
|
|
1826
|
+
inc('__typed_reverse')
|
|
1827
|
+
const c = temp('ttv')
|
|
1828
|
+
return typed(['block', ['result', 'f64'],
|
|
1829
|
+
['local.set', `$${c}`, asF64(copy)],
|
|
1830
|
+
['call', '$__typed_reverse', ['i64.reinterpret_f64', ['local.get', `$${c}`]]]], 'f64')
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// .toSorted(fn?) — a sorted COPY (receiver unchanged): slice-copy, then sort in place.
|
|
1834
|
+
ctx.core.emit['.typed:toSorted'] = (arr, fn) => {
|
|
1835
|
+
const copy = ctx.core.emit['.typed:slice'](arr)
|
|
1836
|
+
return copy ? emitTypedSort(copy, fn) : null
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// .with(index, value) — a COPY with one element replaced (receiver unchanged). Negative
|
|
1840
|
+
// index counts from the end; out of range throws RangeError ($__jz_err), per spec.
|
|
1841
|
+
ctx.core.emit['.typed:with'] = (arr, index, value) => {
|
|
1842
|
+
const copy = ctx.core.emit['.typed:slice'](arr)
|
|
1843
|
+
if (!copy) return null
|
|
1844
|
+
ctx.runtime.throws = true
|
|
1845
|
+
inc('__len', '__typed_set_idx')
|
|
1846
|
+
const c = temp('twc'), idx = tempI32('twi'), len = tempI32('twl')
|
|
1847
|
+
return typed(['block', ['result', 'f64'],
|
|
1848
|
+
['local.set', `$${c}`, asF64(copy)],
|
|
1849
|
+
['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${c}`]]]],
|
|
1850
|
+
['local.set', `$${idx}`, asI32(emit(index))],
|
|
1851
|
+
['if', ['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
|
|
1852
|
+
['then', ['local.set', `$${idx}`, ['i32.add', ['local.get', `$${idx}`], ['local.get', `$${len}`]]]]],
|
|
1853
|
+
['if', ['i32.or',
|
|
1854
|
+
['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
|
|
1855
|
+
['i32.ge_s', ['local.get', `$${idx}`], ['local.get', `$${len}`]]],
|
|
1856
|
+
['then', ['throw', '$__jz_err', ['f64.const', 0]]]],
|
|
1857
|
+
['drop', ['call', '$__typed_set_idx', ['i64.reinterpret_f64', ['local.get', `$${c}`]],
|
|
1858
|
+
['local.get', `$${idx}`], asF64(emit(value))]],
|
|
1859
|
+
['local.get', `$${c}`]], 'f64')
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// .subarray(begin, end) — a zero-copy VIEW sharing the receiver's buffer (writes alias,
|
|
1863
|
+
// NOT a copy). Builds the 16-byte descriptor [byteLen][dataOff][parentOff] and tags the
|
|
1864
|
+
// TYPED ptr with aux|view, exactly like new TypedArray(buffer, byteOffset, length).
|
|
1865
|
+
ctx.core.emit['.typed:subarray'] = (arr, begin, end) => {
|
|
1866
|
+
ctx.features.typedView = true // zero-copy view aliases the receiver — covers inline `a.subarray(1)[i]=…` the bound-decl path in analyze.js misses
|
|
1867
|
+
const r = resolveElem(arr)
|
|
1868
|
+
if (!r) {
|
|
1869
|
+
// Elem type / view-ness not statically known (owned→view reassigned binding).
|
|
1870
|
+
// Dispatch off the runtime aux byte instead of crashing on empty IR.
|
|
1871
|
+
inc('__subarray')
|
|
1872
|
+
return typed(['call', '$__subarray',
|
|
1873
|
+
['i64.reinterpret_f64', asF64(emit(arr))],
|
|
1874
|
+
begin == null ? ['i32.const', 0] : asI32(emit(begin)),
|
|
1875
|
+
end == null ? ['i32.const', 0] : asI32(emit(end)),
|
|
1876
|
+
['i32.const', end == null ? 0 : 1]], 'f64')
|
|
1877
|
+
}
|
|
1878
|
+
const { et, isView, isBigInt } = r
|
|
1879
|
+
const shift = SHIFT[et]
|
|
1880
|
+
const viewAux = et | 8 | (isBigInt ? 16 : 0)
|
|
1881
|
+
const arrL = temp('tua'), srcOff = tempI32('tuo'), data = tempI32('tud'), root = tempI32('tur')
|
|
1882
|
+
const len = tempI32('tul'), lo = tempI32('tulo'), hi = tempI32('tuhi'), n = tempI32('tun'), desc = tempI32('tude')
|
|
1883
|
+
inc('__len')
|
|
1884
|
+
const off4 = (o) => ['i32.load', ['i32.add', ['local.get', `$${srcOff}`], ['i32.const', o]]]
|
|
1885
|
+
const clamp = (boundExpr, dflt, name) => {
|
|
1886
|
+
if (boundExpr == null) return dflt
|
|
1887
|
+
const v = tempI32(name)
|
|
1888
|
+
return ['block', ['result', 'i32'],
|
|
1889
|
+
['local.set', `$${v}`, asI32(emit(boundExpr))],
|
|
1890
|
+
['if', ['i32.lt_s', ['local.get', `$${v}`], ['i32.const', 0]],
|
|
1891
|
+
['then', ['local.set', `$${v}`, ['i32.add', ['local.get', `$${v}`], ['local.get', `$${len}`]]]]],
|
|
1892
|
+
['if', ['i32.lt_s', ['local.get', `$${v}`], ['i32.const', 0]],
|
|
1893
|
+
['then', ['local.set', `$${v}`, ['i32.const', 0]]]],
|
|
1894
|
+
['if', ['i32.gt_s', ['local.get', `$${v}`], ['local.get', `$${len}`]],
|
|
1895
|
+
['then', ['local.set', `$${v}`, ['local.get', `$${len}`]]]],
|
|
1896
|
+
['local.get', `$${v}`]]
|
|
1897
|
+
}
|
|
1898
|
+
return typed(['block', ['result', 'f64'],
|
|
1899
|
+
['local.set', `$${arrL}`, asF64(emit(arr))],
|
|
1900
|
+
['local.set', `$${srcOff}`, typedBase(typed(['local.get', `$${arrL}`], 'f64'))],
|
|
1901
|
+
['local.set', `$${data}`, isView ? off4(4) : ['local.get', `$${srcOff}`]],
|
|
1902
|
+
['local.set', `$${root}`, isView ? off4(8) : ['local.get', `$${srcOff}`]],
|
|
1903
|
+
['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${arrL}`]]]],
|
|
1904
|
+
['local.set', `$${lo}`, clamp(begin, ['i32.const', 0], 'tub')],
|
|
1905
|
+
['local.set', `$${hi}`, clamp(end, ['local.get', `$${len}`], 'tue')],
|
|
1906
|
+
['local.set', `$${n}`, ['select',
|
|
1907
|
+
['i32.sub', ['local.get', `$${hi}`], ['local.get', `$${lo}`]], ['i32.const', 0],
|
|
1908
|
+
['i32.gt_s', ['local.get', `$${hi}`], ['local.get', `$${lo}`]]]],
|
|
1909
|
+
['local.set', `$${desc}`, ['call', '$__alloc', ['i32.const', 16]]],
|
|
1910
|
+
['i32.store', ['local.get', `$${desc}`], ['i32.shl', ['local.get', `$${n}`], ['i32.const', shift]]],
|
|
1911
|
+
['i32.store', ['i32.add', ['local.get', `$${desc}`], ['i32.const', 4]],
|
|
1912
|
+
['i32.add', ['local.get', `$${data}`], ['i32.shl', ['local.get', `$${lo}`], ['i32.const', shift]]]],
|
|
1913
|
+
['i32.store', ['i32.add', ['local.get', `$${desc}`], ['i32.const', 8]], ['local.get', `$${root}`]],
|
|
1914
|
+
mkPtrIR(PTR.TYPED, viewAux, ['local.get', `$${desc}`])], 'f64')
|
|
1915
|
+
}
|
|
1463
1916
|
}
|