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