jz 0.5.1 → 0.6.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 +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- 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 +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +266 -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 +414 -186
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +586 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +589 -202
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -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/array.js
CHANGED
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
* @module array
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr, multiCount, arrayLoop, elemLoad, elemStore, truthyIR, extractF64Bits, appendStaticSlots, mkPtrIR, slotAddr, isLiteralStr, resolveValType, undefExpr } from '../src/ir.js'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { typed, asF64, asI64, asI32, NULL_NAN, UNDEF_NAN, temp, tempI32, allocPtr, multiCount, arrayLoop, elemLoad, elemStore, truthyIR, extractF64Bits, appendStaticSlots, mkPtrIR, slotAddr, isLiteralStr, resolveValType, undefExpr, ptrTypeEq } from '../src/ir.js'
|
|
12
|
+
import { inBoundsArrIdx } from '../src/type.js'
|
|
13
|
+
import { emit, spread, deps } from '../src/bridge.js'
|
|
14
|
+
import { valTypeOf } from '../src/kind.js'
|
|
15
|
+
import { extractParams, classifyParam, ASSIGN_OPS, refsName, REFS_IN_EXPR } from '../src/ast.js'
|
|
16
|
+
import { staticPropertyKey, staticObjectProps, inlineArraySid } from '../src/static.js'
|
|
17
|
+
import { VAL, lookupValType, lookupNotString, updateRep } from '../src/reps.js'
|
|
14
18
|
import { structInline } from '../src/abi/index.js'
|
|
15
|
-
import { ctx, inc, err, PTR, LAYOUT } from '../src/ctx.js'
|
|
19
|
+
import { ctx, inc, err, PTR, LAYOUT, followForwardingWat } from '../src/ctx.js'
|
|
16
20
|
import { strHashLiteral } from './collection.js'
|
|
17
21
|
|
|
18
22
|
|
|
@@ -22,17 +26,23 @@ function allocArray(len, cap) {
|
|
|
22
26
|
return { local: a.local, setup: [a.init], ptr: a.ptr }
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
/** Pack literal i64 slots as a static ARRAY
|
|
26
|
-
*
|
|
29
|
+
/** Pack literal i64 slots as a static ARRAY into the data segment, returning a
|
|
30
|
+
* folded ARRAY pointer to the first slot. The 16-byte header MUST match
|
|
31
|
+
* __alloc_hdr (core.js): a zeroed dyn-props word at off-16, then len/cap at
|
|
32
|
+
* off-8/-4. Heap arrays get that props word zeroed for free; a static array with
|
|
33
|
+
* only an 8-byte header left off-16 pointing at adjacent data-segment bytes, so
|
|
34
|
+
* for-in / named-prop lookup (which read off-16 as the props-sidecar pointer)
|
|
35
|
+
* walked garbage → OOB (test262 built-ins/Object/keys sparse-array). */
|
|
27
36
|
function staticArrayPtr(slots) {
|
|
28
37
|
if (!ctx.runtime.data) ctx.runtime.data = ''
|
|
29
38
|
while (ctx.runtime.data.length % 8 !== 0) ctx.runtime.data += '\0'
|
|
30
39
|
const headerOff = ctx.runtime.data.length
|
|
31
40
|
const len = slots.length
|
|
32
|
-
const hdr = new Uint8Array(
|
|
33
|
-
|
|
41
|
+
const hdr = new Uint8Array(16); const dv = new DataView(hdr.buffer)
|
|
42
|
+
dv.setInt32(8, len, true); dv.setInt32(12, len, true) // off-8: len, off-4: cap (props word at 0..7 stays 0)
|
|
43
|
+
for (let i = 0; i < 16; i++) ctx.runtime.data += String.fromCharCode(hdr[i])
|
|
34
44
|
appendStaticSlots(slots)
|
|
35
|
-
return mkPtrIR(PTR.ARRAY, 0, headerOff +
|
|
45
|
+
return mkPtrIR(PTR.ARRAY, 0, headerOff + 16)
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
function hoistArrayValue(arr) {
|
|
@@ -94,16 +104,8 @@ function substExpr(node, mapping) {
|
|
|
94
104
|
return out
|
|
95
105
|
}
|
|
96
106
|
|
|
97
|
-
// Check whether a name is referenced inside a pure expression body.
|
|
98
|
-
// Mirrors substExpr's traversal — skips property names on '.'/'?.' and object keys on ':'.
|
|
99
107
|
function exprUses(node, name) {
|
|
100
|
-
|
|
101
|
-
if (!Array.isArray(node)) return false
|
|
102
|
-
const op = node[0]
|
|
103
|
-
if (op === '.' || op === '?.') return exprUses(node[1], name)
|
|
104
|
-
if (op === ':') return exprUses(node[2], name)
|
|
105
|
-
for (let i = 1; i < node.length; i++) if (exprUses(node[i], name)) return true
|
|
106
|
-
return false
|
|
108
|
+
return refsName(node, name, REFS_IN_EXPR)
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
// Callback factory: returns { setup, call, usedParams } where call(argExprs) emits the invocation.
|
|
@@ -239,26 +241,32 @@ const headerPropsToGlobalIR = () => needsArrayDynMove() ? `
|
|
|
239
241
|
(global.set $__dyn_props (local.get $root)))))) ` : ''
|
|
240
242
|
|
|
241
243
|
export default (ctx) => {
|
|
242
|
-
|
|
243
|
-
__arr_idx: [],
|
|
244
|
+
deps({
|
|
245
|
+
__arr_idx: ['__ptr_offset_fwd'],
|
|
244
246
|
__arr_grow: arrayGrowDeps(false),
|
|
245
247
|
__arr_grow_known: arrayGrowDeps(true),
|
|
246
248
|
__arr_shift: () => [
|
|
247
249
|
'__ptr_offset',
|
|
248
250
|
...(needsArrayDynMove() ? ['__dyn_move', '__hash_new', '__ihash_set_local'] : []),
|
|
249
251
|
],
|
|
252
|
+
__arr_fill: ['__ptr_offset'],
|
|
250
253
|
__arr_set_idx_ptr: ['__arr_grow', '__ptr_offset'],
|
|
251
254
|
__arr_push1: ['__arr_grow_known', '__ptr_offset'],
|
|
255
|
+
__arr_set_length: ['__arr_grow_known', '__ptr_offset', '__ptr_type'],
|
|
252
256
|
__arr_unshift: ['__arr_grow', '__len', '__ptr_offset'],
|
|
257
|
+
__arr_splice: ['__arr_grow', '__len', '__ptr_offset', '__alloc_hdr', '__mkptr'],
|
|
253
258
|
__typed_idx: () => ctx.features.typedarray || ctx.features.external
|
|
254
|
-
? ['__len']
|
|
255
|
-
: ['__len', '__ptr_offset'],
|
|
259
|
+
? ['__len', '__ptr_offset_fwd']
|
|
260
|
+
: ['__len', '__ptr_offset', '__ptr_offset_fwd'],
|
|
261
|
+
__arr_idx_known: ['__ptr_offset_fwd'],
|
|
256
262
|
})
|
|
257
263
|
|
|
258
|
-
// Iteration methods
|
|
259
|
-
// (item, idx)
|
|
260
|
-
//
|
|
261
|
-
|
|
264
|
+
// Iteration methods invoke callbacks with an implicit trailing index: .map/
|
|
265
|
+
// .filter/.forEach pass (item, idx); .reduce/.reduceRight pass (acc, item, idx).
|
|
266
|
+
// The uniform closure width must accommodate the widest of these (arity 3) even
|
|
267
|
+
// when no source-level closure declares that arity — otherwise a reduce callback
|
|
268
|
+
// routed through the closure path overflows the call_indirect signature.
|
|
269
|
+
ctx.closure.floor = Math.max(ctx.closure.floor ?? 0, 3)
|
|
262
270
|
|
|
263
271
|
inc('__ptr_offset', '__ptr_type', '__len', '__set_len', '__typed_idx', '__is_truthy')
|
|
264
272
|
|
|
@@ -268,7 +276,7 @@ export default (ctx) => {
|
|
|
268
276
|
const t = temp('t')
|
|
269
277
|
return typed(['i32.and',
|
|
270
278
|
['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
271
|
-
['
|
|
279
|
+
ptrTypeEq(['local.get', `$${t}`], PTR.ARRAY)], 'i32')
|
|
272
280
|
}
|
|
273
281
|
|
|
274
282
|
ctx.core.emit['new.Array'] = (len) => {
|
|
@@ -298,13 +306,7 @@ export default (ctx) => {
|
|
|
298
306
|
(then (f64.const nan:${UNDEF_NAN}))
|
|
299
307
|
(else
|
|
300
308
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
301
|
-
(
|
|
302
|
-
(loop $follow
|
|
303
|
-
(br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
|
|
304
|
-
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
305
|
-
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
306
|
-
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
307
|
-
(br $follow)))
|
|
309
|
+
${followForwardingWat('$off', { lowGuard: true })}
|
|
308
310
|
(if (result f64)
|
|
309
311
|
(i32.and
|
|
310
312
|
(i32.ge_u (local.get $off) (i32.const 8))
|
|
@@ -317,13 +319,7 @@ export default (ctx) => {
|
|
|
317
319
|
ctx.core.stdlib['__arr_idx_known'] = `(func $__arr_idx_known (param $ptr i64) (param $i i32) (result f64)
|
|
318
320
|
(local $off i32)
|
|
319
321
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
320
|
-
(
|
|
321
|
-
(loop $follow
|
|
322
|
-
(br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
|
|
323
|
-
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
324
|
-
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
325
|
-
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
326
|
-
(br $follow)))
|
|
322
|
+
${followForwardingWat('$off', { lowGuard: true })}
|
|
327
323
|
(if (result f64)
|
|
328
324
|
(i32.and
|
|
329
325
|
(i32.ge_u (local.get $off) (i32.const 8))
|
|
@@ -357,12 +353,7 @@ export default (ctx) => {
|
|
|
357
353
|
;; ARRAY fast path: follow forwarding inline, bounds-check against header len, f64.load — no $__len call.
|
|
358
354
|
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.ARRAY})) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
359
355
|
(then
|
|
360
|
-
(
|
|
361
|
-
(loop $follow
|
|
362
|
-
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
363
|
-
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
364
|
-
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
365
|
-
(br $follow)))
|
|
356
|
+
${followForwardingWat('$off', { lowGuard: false })}
|
|
366
357
|
(return (if (result f64)
|
|
367
358
|
(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)))))
|
|
368
359
|
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
@@ -433,7 +424,7 @@ export default (ctx) => {
|
|
|
433
424
|
const op = n[0]
|
|
434
425
|
if (op == null) return true // [null,x] literal — null/number/bigint
|
|
435
426
|
if (op === '=>') return false // arrow function — callable
|
|
436
|
-
if (op === '{}' || op === 'str' || op === 'strcat' || op === '//') return true
|
|
427
|
+
if (op === '{}' || op === 'str' || op === 'strcat' || op === '//' || op === 'bool') return true // object/string/regexp/boolean literal
|
|
437
428
|
if (op === '[]' && n.length < 3) return true // array literal
|
|
438
429
|
if (op === '()' && n[1] === 'Symbol') return true // Symbol(...) result
|
|
439
430
|
return false // calls / member access — unknown
|
|
@@ -515,11 +506,14 @@ export default (ctx) => {
|
|
|
515
506
|
|
|
516
507
|
// Grow array if capacity insufficient. Returns (possibly new) NaN-boxed pointer.
|
|
517
508
|
// Old storage is left behind as a forwarding header so existing aliases keep
|
|
518
|
-
// seeing the current backing store after growth.
|
|
519
|
-
|
|
520
|
-
|
|
509
|
+
// seeing the current backing store after growth. `defensive` adds a type/bounds
|
|
510
|
+
// guard (non-array ptr → fresh 4-cap buffer) for untyped call sites; the `_known`
|
|
511
|
+
// variant skips it for hot paths that already proved the receiver is an array.
|
|
512
|
+
// Single source of truth: both stdlib entries share the grow/relocate tail.
|
|
513
|
+
const arrGrow = (name, defensive) => `(func $${name} (param $ptr i64) (param $minCap i32) (result f64)
|
|
514
|
+
${defensive ? '(local $t i32) ' : ''}(local $off i32) (local $oldCap i32) (local $newCap i32) (local $newOff i32) (local $len i32)
|
|
521
515
|
${needsArrayDynMove() ? '(local $oldProps f64)' : ''}
|
|
522
|
-
(local.set $t (call $__ptr_type (local.get $ptr)))
|
|
516
|
+
${defensive ? `(local.set $t (call $__ptr_type (local.get $ptr)))
|
|
523
517
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
524
518
|
;; Defensive path: invalid/non-array pointer -> create fresh array buffer.
|
|
525
519
|
(if
|
|
@@ -529,7 +523,7 @@ export default (ctx) => {
|
|
|
529
523
|
(then
|
|
530
524
|
(local.set $newCap (select (local.get $minCap) (i32.const 4) (i32.gt_s (local.get $minCap) (i32.const 4))))
|
|
531
525
|
(local.set $newOff (call $__alloc_hdr (i32.const 0) (local.get $newCap)))
|
|
532
|
-
(return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))))
|
|
526
|
+
(return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))))` : `(local.set $off (call $__ptr_offset (local.get $ptr)))`}
|
|
533
527
|
(local.set $oldCap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
534
528
|
(if (i32.ge_s (local.get $oldCap) (local.get $minCap))
|
|
535
529
|
(then (return (f64.reinterpret_i64 (local.get $ptr)))))
|
|
@@ -546,25 +540,8 @@ export default (ctx) => {
|
|
|
546
540
|
(i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
|
|
547
541
|
(call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))`
|
|
548
542
|
|
|
549
|
-
ctx.core.stdlib['
|
|
550
|
-
|
|
551
|
-
${needsArrayDynMove() ? '(local $oldProps f64)' : ''}
|
|
552
|
-
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
553
|
-
(local.set $oldCap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
554
|
-
(if (i32.ge_s (local.get $oldCap) (local.get $minCap))
|
|
555
|
-
(then (return (f64.reinterpret_i64 (local.get $ptr)))))
|
|
556
|
-
(local.set $newCap (select
|
|
557
|
-
(local.get $minCap)
|
|
558
|
-
(i32.shl (local.get $oldCap) (i32.const 1))
|
|
559
|
-
(i32.gt_s (local.get $minCap) (i32.shl (local.get $oldCap) (i32.const 1)))))
|
|
560
|
-
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
561
|
-
(local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap)))
|
|
562
|
-
(memory.copy (local.get $newOff) (local.get $off) (i32.shl (local.get $len) (i32.const 3)))
|
|
563
|
-
${headerPropsCopyIR()}
|
|
564
|
-
${maybeDynMoveIR()}
|
|
565
|
-
(i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newOff))
|
|
566
|
-
(i32.store (i32.sub (local.get $off) (i32.const 4)) (i32.const -1))
|
|
567
|
-
(call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))`
|
|
543
|
+
ctx.core.stdlib['__arr_grow'] = () => arrGrow('__arr_grow', true)
|
|
544
|
+
ctx.core.stdlib['__arr_grow_known'] = () => arrGrow('__arr_grow_known', false)
|
|
568
545
|
|
|
569
546
|
// Hot for arr[i] = val (~18M calls in watr self-host). Compute base via __ptr_offset
|
|
570
547
|
// once and read len from the inline header (i32.load base-8) — avoids __len's separate
|
|
@@ -609,6 +586,37 @@ export default (ctx) => {
|
|
|
609
586
|
(i32.store (i32.sub (local.get $base) (i32.const 8)) (i32.add (local.get $len) (i32.const 1)))
|
|
610
587
|
(local.get $p))`
|
|
611
588
|
|
|
589
|
+
// arr.length = N. Truncation (N ≤ len) just rewrites the len word in place — no
|
|
590
|
+
// relocation, so aliases keep their pointer. Growth past capacity relocates via
|
|
591
|
+
// __arr_grow_known (old header forward-marked) and undefined-fills the new slots,
|
|
592
|
+
// matching JS sparse-array semantics. Non-ARRAY receivers are left unchanged so a
|
|
593
|
+
// mistyped `.length =` cannot corrupt object/collection headers. Returns the
|
|
594
|
+
// (possibly relocated) pointer; the assignment's value is N (computed at the call site).
|
|
595
|
+
ctx.core.stdlib['__arr_set_length'] = `(func $__arr_set_length (param $ptr i64) (param $n i32) (result f64)
|
|
596
|
+
(local $base i32) (local $p f64) (local $oldLen i32) (local $cap i32) (local $k i32)
|
|
597
|
+
(local.set $p (f64.reinterpret_i64 (local.get $ptr)))
|
|
598
|
+
(if (i32.ne (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.ARRAY}))
|
|
599
|
+
(then (return (local.get $p))))
|
|
600
|
+
(if (i32.lt_s (local.get $n) (i32.const 0)) (then (local.set $n (i32.const 0))))
|
|
601
|
+
(local.set $base (call $__ptr_offset (local.get $ptr)))
|
|
602
|
+
(if (i32.lt_u (local.get $base) (i32.const 8)) (then (return (local.get $p))))
|
|
603
|
+
(local.set $oldLen (i32.load (i32.sub (local.get $base) (i32.const 8))))
|
|
604
|
+
(local.set $cap (i32.load (i32.sub (local.get $base) (i32.const 4))))
|
|
605
|
+
(if (i32.gt_s (local.get $n) (local.get $cap))
|
|
606
|
+
(then
|
|
607
|
+
(local.set $p (call $__arr_grow_known (local.get $ptr) (local.get $n)))
|
|
608
|
+
(local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))))
|
|
609
|
+
(if (i32.gt_u (local.get $n) (local.get $oldLen))
|
|
610
|
+
(then
|
|
611
|
+
(local.set $k (local.get $oldLen))
|
|
612
|
+
(block $fdone (loop $fill
|
|
613
|
+
(br_if $fdone (i32.ge_u (local.get $k) (local.get $n)))
|
|
614
|
+
(i64.store (i32.add (local.get $base) (i32.shl (local.get $k) (i32.const 3))) (i64.const ${UNDEF_NAN}))
|
|
615
|
+
(local.set $k (i32.add (local.get $k) (i32.const 1)))
|
|
616
|
+
(br $fill)))))
|
|
617
|
+
(i32.store (i32.sub (local.get $base) (i32.const 8)) (local.get $n))
|
|
618
|
+
(local.get $p))`
|
|
619
|
+
|
|
612
620
|
// === Array literal ===
|
|
613
621
|
|
|
614
622
|
ctx.core.emit['['] = (...elems) => {
|
|
@@ -633,10 +641,10 @@ export default (ctx) => {
|
|
|
633
641
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
634
642
|
}
|
|
635
643
|
|
|
636
|
-
// Spread literal:
|
|
644
|
+
// Spread literal: spread pre-sums the total length, allocates
|
|
637
645
|
// exact, and bulk-copies ARRAY sources with a single memory.copy — vs the
|
|
638
646
|
// per-element __arr_set_idx_ptr grow loop. Normalise the parser's `['...', x]`.
|
|
639
|
-
return
|
|
647
|
+
return spread(elems.map(e =>
|
|
640
648
|
Array.isArray(e) && e[0] === '...' ? ['__spread', e[1]] : e))
|
|
641
649
|
}
|
|
642
650
|
|
|
@@ -674,8 +682,8 @@ export default (ctx) => {
|
|
|
674
682
|
const fi = fo.names.indexOf(litKey)
|
|
675
683
|
if (fi >= 0) return typed(['local.get', `$${arr}#${fi}`], 'f64')
|
|
676
684
|
}
|
|
677
|
-
if (litKey != null && typeof arr === 'string' && ctx.schema.
|
|
678
|
-
const slot = ctx.schema.
|
|
685
|
+
if (litKey != null && typeof arr === 'string' && ctx.schema.slotOf) {
|
|
686
|
+
const slot = ctx.schema.slotOf(arr, litKey)
|
|
679
687
|
if (slot >= 0) {
|
|
680
688
|
inc('__ptr_offset')
|
|
681
689
|
return typed(ctx.abi.object.ops.load(['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(emit(arr))]], slot), 'f64')
|
|
@@ -776,10 +784,19 @@ export default (ctx) => {
|
|
|
776
784
|
// f64.load is correct regardless of elem kind (returns raw f64 for NUMBER,
|
|
777
785
|
// NaN-boxed pointer for OBJECT/STRING; downstream typed() handles both).
|
|
778
786
|
// Fast path fires on schemaId (Array<{x,y,z}> shapes) OR plain elem-val
|
|
779
|
-
// (Array<NUMBER> from `.map(x => x*k)` etc.)
|
|
787
|
+
// (Array<NUMBER> from `.map(x => x*k)` etc.) — both are proven facts from
|
|
788
|
+
// the narrowing fixpoint (carried onto params via paramReps).
|
|
780
789
|
const rep = typeof arr === 'string' ? ctx.func.localReps?.get(arr) : null
|
|
781
|
-
const hasElemFact = rep?.arrayElemSchema != null
|
|
782
|
-
|
|
790
|
+
const hasElemFact = rep?.arrayElemSchema != null || rep?.arrayElemValType != null
|
|
791
|
+
// Take the unchecked inline load ONLY when the index is proven in-bounds by an
|
|
792
|
+
// enclosing canonical loop `for (let i=C; i<arr.length; i++)`. Skipping the bounds
|
|
793
|
+
// check on an arbitrary numeric index is unsound: `a[1]` on a length-1 array would
|
|
794
|
+
// read the raw (uninitialized) cell instead of undefined. Constant / unproven
|
|
795
|
+
// indices fall through to __arr_idx(_known) below, which bounds-checks → UNDEF_NAN.
|
|
796
|
+
const idxProvenInBounds = hasElemFact && keyIsNum
|
|
797
|
+
&& typeof arr === 'string' && typeof idx === 'string'
|
|
798
|
+
&& inBoundsArrIdx(ctx).has(arr + '\x00' + idx)
|
|
799
|
+
if (idxProvenInBounds) {
|
|
783
800
|
inc('__ptr_offset')
|
|
784
801
|
// __ptr_offset returns i32 — base local must be i32 (not the default
|
|
785
802
|
// f64 NaN-box temp). Flat tee form so downstream peepholes can fold
|
|
@@ -790,6 +807,23 @@ export default (ctx) => {
|
|
|
790
807
|
['local.tee', `$${baseI32}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
|
|
791
808
|
vi), 'f64')
|
|
792
809
|
}
|
|
810
|
+
// Known-elem array, numeric key, NOT proven in-bounds → inline bounds-checked
|
|
811
|
+
// load: `idx < len ? load : undefined`. Same semantics as __arr_idx_known but
|
|
812
|
+
// inline, so watr hoists the loop-invariant len load and CSEs the base — the
|
|
813
|
+
// residual cost is a single (predictable) compare per access, not a call. Skipping
|
|
814
|
+
// the check would read raw memory for OOB indices (e.g. `a[1]` on a length-1 array).
|
|
815
|
+
if (hasElemFact && keyIsNum) {
|
|
816
|
+
inc('__ptr_offset')
|
|
817
|
+
const baseI32 = tempI32('ab'), idxI32 = tempI32('ai')
|
|
818
|
+
return typed(['if', ['result', 'f64'],
|
|
819
|
+
['i32.lt_u',
|
|
820
|
+
['local.tee', `$${idxI32}`, vi],
|
|
821
|
+
['i32.load', ['i32.sub',
|
|
822
|
+
['local.tee', `$${baseI32}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
|
|
823
|
+
['i32.const', 8]]]],
|
|
824
|
+
['then', ctx.abi.array.ops.load(['local.get', `$${baseI32}`], ['local.get', `$${idxI32}`])],
|
|
825
|
+
['else', undefExpr()]], 'f64')
|
|
826
|
+
}
|
|
793
827
|
const baseTmp = temp()
|
|
794
828
|
// Numeric key (literal or known-NUMBER name) → skip __is_str_key dispatch;
|
|
795
829
|
// arrays don't honor string-key access for numeric keys (keys aren't coerced
|
|
@@ -842,7 +876,7 @@ export default (ctx) => {
|
|
|
842
876
|
const keyI32 = asI32(typed(keyExpr, 'f64'))
|
|
843
877
|
if (ctx.module.modules['string'] && !notString) {
|
|
844
878
|
return ['if', ['result', 'f64'],
|
|
845
|
-
|
|
879
|
+
ptrTypeEq(ptrExpr, PTR.STRING),
|
|
846
880
|
['then', (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])],
|
|
847
881
|
['else', (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])]]
|
|
848
882
|
}
|
|
@@ -852,7 +886,7 @@ export default (ctx) => {
|
|
|
852
886
|
if (ctx.module.modules['string'] && !notString)
|
|
853
887
|
return typed(
|
|
854
888
|
['if', ['result', 'f64'],
|
|
855
|
-
|
|
889
|
+
ptrTypeEq(ptrExpr, PTR.STRING),
|
|
856
890
|
['then', stringLoad()],
|
|
857
891
|
['else', arrayLoad]],
|
|
858
892
|
'f64')
|
|
@@ -1021,6 +1055,42 @@ export default (ctx) => {
|
|
|
1021
1055
|
(i32.store (i32.sub (local.get $rawOff) (i32.const 4)) (i32.const -1))))
|
|
1022
1056
|
(local.get $val))))))`
|
|
1023
1057
|
|
|
1058
|
+
// .fill(value, start?, end?) — overwrite [start, end) with value; mutate + return the
|
|
1059
|
+
// array. start/end default to 0 / length and accept negatives (offset from end). The
|
|
1060
|
+
// INT_MAX end sentinel clamps to length in the helper, which also normalizes negatives.
|
|
1061
|
+
ctx.core.emit['.fill'] = (arr, val, start, end) => {
|
|
1062
|
+
inc('__arr_fill')
|
|
1063
|
+
return typed(['call', '$__arr_fill',
|
|
1064
|
+
asI64(emit(arr)),
|
|
1065
|
+
val == null ? undefExpr() : asF64(emit(val)),
|
|
1066
|
+
start == null ? ['i32.const', 0] : asI32(emit(start)),
|
|
1067
|
+
end == null ? ['i32.const', 0x7FFFFFFF] : asI32(emit(end))], 'f64')
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
ctx.core.stdlib['__arr_fill'] = `(func $__arr_fill (param $arr i64) (param $val f64) (param $start i32) (param $end i32) (result f64)
|
|
1071
|
+
(local $off i32) (local $len i32) (local $i i32)
|
|
1072
|
+
(if (i32.eq
|
|
1073
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $arr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
1074
|
+
(i32.const ${PTR.ARRAY}))
|
|
1075
|
+
(then
|
|
1076
|
+
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1077
|
+
(if (i32.ge_u (local.get $off) (i32.const 8))
|
|
1078
|
+
(then
|
|
1079
|
+
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
1080
|
+
(if (i32.lt_s (local.get $start) (i32.const 0)) (then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
1081
|
+
(if (i32.lt_s (local.get $start) (i32.const 0)) (then (local.set $start (i32.const 0))))
|
|
1082
|
+
(if (i32.gt_s (local.get $start) (local.get $len)) (then (local.set $start (local.get $len))))
|
|
1083
|
+
(if (i32.lt_s (local.get $end) (i32.const 0)) (then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
1084
|
+
(if (i32.lt_s (local.get $end) (i32.const 0)) (then (local.set $end (i32.const 0))))
|
|
1085
|
+
(if (i32.gt_s (local.get $end) (local.get $len)) (then (local.set $end (local.get $len))))
|
|
1086
|
+
(local.set $i (local.get $start))
|
|
1087
|
+
(block $done (loop $fill
|
|
1088
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $end)))
|
|
1089
|
+
(f64.store (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))) (local.get $val))
|
|
1090
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1091
|
+
(br $fill)))))))
|
|
1092
|
+
(f64.reinterpret_i64 (local.get $arr)))`
|
|
1093
|
+
|
|
1024
1094
|
// .splice(start) | .splice(start, deleteCount) → remove range, return removed as new array
|
|
1025
1095
|
ctx.core.emit['.splice'] = (arr, start, deleteCount) => {
|
|
1026
1096
|
const recv = hoistArrayValue(arr)
|
|
@@ -1098,59 +1168,111 @@ export default (ctx) => {
|
|
|
1098
1168
|
(i32.store (i32.sub (local.get $off) (i32.const 8)) (i32.add (local.get $len) (i32.const 1)))
|
|
1099
1169
|
(f64.convert_i32_s (i32.add (local.get $len) (i32.const 1))))`
|
|
1100
1170
|
|
|
1101
|
-
// .
|
|
1102
|
-
|
|
1171
|
+
// .splice(start, deleteCount, ...items) with inserts → __arr_splice. Deletes
|
|
1172
|
+
// `del` elements at `s`, inserts the elements of `ins`, returns the removed
|
|
1173
|
+
// elements as a new array. Mutation is in place: a grow relocates the buffer
|
|
1174
|
+
// but leaves a forwarding pointer, so the caller's NaN-box still resolves —
|
|
1175
|
+
// no write-back needed (mirrors __arr_unshift). memory.copy is memmove, so the
|
|
1176
|
+
// tail shift is correct whether the array grew (shift right) or shrank (left).
|
|
1177
|
+
ctx.core.stdlib['__arr_splice'] = `(func $__arr_splice (param $arr i64) (param $start i32) (param $del i32) (param $ins i64) (result f64)
|
|
1178
|
+
(local $off i32) (local $len i32) (local $s i32) (local $cnt i32)
|
|
1179
|
+
(local $m i32) (local $newLen i32) (local $tail i32) (local $out f64) (local $a f64)
|
|
1180
|
+
(local.set $len (call $__len (local.get $arr)))
|
|
1181
|
+
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1182
|
+
(local.set $s (local.get $start))
|
|
1183
|
+
(if (i32.lt_s (local.get $s) (i32.const 0))
|
|
1184
|
+
(then
|
|
1185
|
+
(local.set $s (i32.add (local.get $s) (local.get $len)))
|
|
1186
|
+
(if (i32.lt_s (local.get $s) (i32.const 0)) (then (local.set $s (i32.const 0))))))
|
|
1187
|
+
(if (i32.gt_s (local.get $s) (local.get $len)) (then (local.set $s (local.get $len))))
|
|
1188
|
+
(local.set $cnt (local.get $del))
|
|
1189
|
+
(if (i32.lt_s (local.get $cnt) (i32.const 0)) (then (local.set $cnt (i32.const 0))))
|
|
1190
|
+
(if (i32.gt_s (i32.add (local.get $s) (local.get $cnt)) (local.get $len))
|
|
1191
|
+
(then (local.set $cnt (i32.sub (local.get $len) (local.get $s)))))
|
|
1192
|
+
(local.set $m (call $__len (local.get $ins)))
|
|
1193
|
+
(local.set $newLen (i32.add (i32.sub (local.get $len) (local.get $cnt)) (local.get $m)))
|
|
1194
|
+
(local.set $tail (i32.sub (i32.sub (local.get $len) (local.get $s)) (local.get $cnt)))
|
|
1195
|
+
(local.set $out (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (call $__alloc_hdr (local.get $cnt) (local.get $cnt))))
|
|
1196
|
+
(memory.copy
|
|
1197
|
+
(call $__ptr_offset (i64.reinterpret_f64 (local.get $out)))
|
|
1198
|
+
(i32.add (local.get $off) (i32.shl (local.get $s) (i32.const 3)))
|
|
1199
|
+
(i32.shl (local.get $cnt) (i32.const 3)))
|
|
1200
|
+
(local.set $a (f64.reinterpret_i64 (local.get $arr)))
|
|
1201
|
+
(if (i32.gt_s (local.get $newLen) (local.get $len))
|
|
1202
|
+
(then (local.set $a (call $__arr_grow (local.get $arr) (local.get $newLen)))))
|
|
1203
|
+
(local.set $off (call $__ptr_offset (i64.reinterpret_f64 (local.get $a))))
|
|
1204
|
+
(memory.copy
|
|
1205
|
+
(i32.add (local.get $off) (i32.shl (i32.add (local.get $s) (local.get $m)) (i32.const 3)))
|
|
1206
|
+
(i32.add (local.get $off) (i32.shl (i32.add (local.get $s) (local.get $cnt)) (i32.const 3)))
|
|
1207
|
+
(i32.shl (local.get $tail) (i32.const 3)))
|
|
1208
|
+
(memory.copy
|
|
1209
|
+
(i32.add (local.get $off) (i32.shl (local.get $s) (i32.const 3)))
|
|
1210
|
+
(call $__ptr_offset (local.get $ins))
|
|
1211
|
+
(i32.shl (local.get $m) (i32.const 3)))
|
|
1212
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $newLen))
|
|
1213
|
+
(local.get $out))`
|
|
1214
|
+
|
|
1215
|
+
// Early-exit callback iterator: init value, exit test, value on match.
|
|
1216
|
+
const earlyExitMethod = ({ tag, init, test, onMatch, reverse }) => (arr, fn) => {
|
|
1103
1217
|
const recv = hoistArrayValue(arr)
|
|
1104
|
-
const r = temp(
|
|
1218
|
+
const r = temp(tag)
|
|
1105
1219
|
const exit = `$exit${ctx.func.uniq++}`
|
|
1106
1220
|
const cb = makeCallback(fn, callbackArgReps(arr))
|
|
1107
1221
|
const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
|
|
1108
|
-
['if',
|
|
1109
|
-
['then', ['local.set', `$${r}`,
|
|
1110
|
-
])
|
|
1222
|
+
['if', test(cb, i, item),
|
|
1223
|
+
['then', ['local.set', `$${r}`, onMatch(cb, i, item)], ['br', exit]]]
|
|
1224
|
+
], undefined, undefined, reverse)
|
|
1111
1225
|
return typed(['block', ['result', 'f64'],
|
|
1112
1226
|
recv.setup,
|
|
1113
1227
|
cb.setup,
|
|
1114
|
-
['local.set', `$${r}`,
|
|
1228
|
+
['local.set', `$${r}`, init],
|
|
1115
1229
|
['block', exit, ...loop],
|
|
1116
1230
|
['local.get', `$${r}`]], 'f64')
|
|
1117
1231
|
}
|
|
1118
1232
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
|
|
1126
|
-
['if', ['i32.eqz', truthyIR(cb.call([item, idxArg(cb, i)]))],
|
|
1127
|
-
['then', ['local.set', `$${r}`, ['f64.const', 0]], ['br', exit]]]
|
|
1128
|
-
])
|
|
1129
|
-
return typed(['block', ['result', 'f64'],
|
|
1130
|
-
recv.setup,
|
|
1131
|
-
cb.setup,
|
|
1132
|
-
['local.set', `$${r}`, ['f64.const', 1]],
|
|
1133
|
-
['block', exit, ...loop],
|
|
1134
|
-
['local.get', `$${r}`]], 'f64')
|
|
1135
|
-
}
|
|
1233
|
+
ctx.core.emit['.some'] = earlyExitMethod({
|
|
1234
|
+
tag: 'sr',
|
|
1235
|
+
init: ['f64.const', 0],
|
|
1236
|
+
test: (cb, i, item) => truthyIR(cb.call([item, idxArg(cb, i)])),
|
|
1237
|
+
onMatch: () => ['f64.const', 1],
|
|
1238
|
+
})
|
|
1136
1239
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
]
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1240
|
+
ctx.core.emit['.every'] = earlyExitMethod({
|
|
1241
|
+
tag: 'ev',
|
|
1242
|
+
init: ['f64.const', 1],
|
|
1243
|
+
test: (cb, i, item) => ['i32.eqz', truthyIR(cb.call([item, idxArg(cb, i)]))],
|
|
1244
|
+
onMatch: () => ['f64.const', 0],
|
|
1245
|
+
})
|
|
1246
|
+
|
|
1247
|
+
ctx.core.emit['.findIndex'] = earlyExitMethod({
|
|
1248
|
+
tag: 'fi',
|
|
1249
|
+
init: ['f64.const', -1],
|
|
1250
|
+
test: (cb, i, item) => truthyIR(cb.call([item, idxArg(cb, i)])),
|
|
1251
|
+
onMatch: (_cb, i) => ['f64.convert_i32_s', ['local.get', `$${i}`]],
|
|
1252
|
+
})
|
|
1253
|
+
|
|
1254
|
+
ctx.core.emit['.find'] = earlyExitMethod({
|
|
1255
|
+
tag: 'ff',
|
|
1256
|
+
init: ['f64.reinterpret_i64', ['i64.const', NULL_NAN]],
|
|
1257
|
+
test: (cb, i, item) => truthyIR(cb.call([item, idxArg(cb, i)])),
|
|
1258
|
+
onMatch: (_cb, _i, item) => item,
|
|
1259
|
+
})
|
|
1260
|
+
|
|
1261
|
+
ctx.core.emit['.findLastIndex'] = earlyExitMethod({
|
|
1262
|
+
tag: 'fli',
|
|
1263
|
+
init: ['f64.const', -1],
|
|
1264
|
+
test: (cb, i, item) => truthyIR(cb.call([item, idxArg(cb, i)])),
|
|
1265
|
+
onMatch: (_cb, i) => ['f64.convert_i32_s', ['local.get', `$${i}`]],
|
|
1266
|
+
reverse: true,
|
|
1267
|
+
})
|
|
1268
|
+
|
|
1269
|
+
ctx.core.emit['.findLast'] = earlyExitMethod({
|
|
1270
|
+
tag: 'fl',
|
|
1271
|
+
init: ['f64.reinterpret_i64', ['i64.const', NULL_NAN]],
|
|
1272
|
+
test: (cb, i, item) => truthyIR(cb.call([item, idxArg(cb, i)])),
|
|
1273
|
+
onMatch: (_cb, _i, item) => item,
|
|
1274
|
+
reverse: true,
|
|
1275
|
+
})
|
|
1154
1276
|
|
|
1155
1277
|
// === Array methods ===
|
|
1156
1278
|
|
|
@@ -1170,13 +1292,13 @@ export default (ctx) => {
|
|
|
1170
1292
|
}
|
|
1171
1293
|
function isPureCallback(fn) {
|
|
1172
1294
|
if (!Array.isArray(fn) || fn[0] !== '=>') return false
|
|
1173
|
-
const body = fn[2]
|
|
1174
1295
|
const params = new Set()
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
|
|
1296
|
+
for (const r of extractParams(fn[1])) {
|
|
1297
|
+
const p = classifyParam(r)
|
|
1298
|
+
if (p.name) params.add(p.name)
|
|
1299
|
+
}
|
|
1178
1300
|
const locals = new Set(params)
|
|
1179
|
-
collectLocals(
|
|
1301
|
+
collectLocals(fn[2], locals)
|
|
1180
1302
|
let pure = true
|
|
1181
1303
|
;(function walk(node) {
|
|
1182
1304
|
if (!pure || !Array.isArray(node)) return
|
|
@@ -1184,13 +1306,13 @@ export default (ctx) => {
|
|
|
1184
1306
|
if (op === '=>') return
|
|
1185
1307
|
if (op === '()' || op === '?.()' || op === 'new') { pure = false; return }
|
|
1186
1308
|
if (op === '++' || op === '--') { pure = false; return }
|
|
1187
|
-
if (op
|
|
1309
|
+
if (ASSIGN_OPS.has(op)) {
|
|
1188
1310
|
const t = args[0]
|
|
1189
1311
|
if (typeof t === 'string') { if (!locals.has(t)) { pure = false; return } }
|
|
1190
1312
|
else { pure = false; return }
|
|
1191
1313
|
}
|
|
1192
1314
|
for (const a of args) walk(a)
|
|
1193
|
-
})(
|
|
1315
|
+
})(fn[2])
|
|
1194
1316
|
return pure
|
|
1195
1317
|
}
|
|
1196
1318
|
|
|
@@ -1315,30 +1437,56 @@ export default (ctx) => {
|
|
|
1315
1437
|
const acc = temp('ra'), mapped = temp('mv')
|
|
1316
1438
|
const upReps = callbackArgReps(up.source)
|
|
1317
1439
|
const mapCb = makeCallback(up.fn, upReps), redCb = makeCallback(fn)
|
|
1440
|
+
const mget = typed(['local.get', `$${mapped}`], 'f64')
|
|
1441
|
+
// map preserves indices → the reduce callback's index is the loop counter.
|
|
1442
|
+
const fold = i => ['local.set', `$${acc}`, asF64(redCb.call([typed(['local.get', `$${acc}`], 'f64'), mget, idxArg(redCb, i, 2)]))]
|
|
1318
1443
|
const loop = arrayLoop(recv.value, (_p, _l, i, item) => [
|
|
1319
1444
|
['local.set', `$${mapped}`, asF64(mapCb.call([item, idxArg(mapCb, i)]))],
|
|
1320
|
-
|
|
1445
|
+
// No-init: seed accumulator with the first mapped element (see base path).
|
|
1446
|
+
init !== undefined ? fold(i)
|
|
1447
|
+
: ['if', ['i32.eqz', ['local.get', `$${i}`]],
|
|
1448
|
+
['then', ['local.set', `$${acc}`, mget]],
|
|
1449
|
+
['else', fold(i)]]
|
|
1321
1450
|
])
|
|
1322
1451
|
return typed(['block', ['result', 'f64'],
|
|
1323
1452
|
recv.setup, mapCb.setup, redCb.setup,
|
|
1324
|
-
['local.set', `$${acc}`, init ? asF64(emit(init)) : ['f64.const', 0]],
|
|
1453
|
+
['local.set', `$${acc}`, init !== undefined ? asF64(emit(init)) : ['f64.const', 0]],
|
|
1325
1454
|
...loop, ['local.get', `$${acc}`]], 'f64')
|
|
1326
1455
|
}
|
|
1327
1456
|
// .filter(f).reduce(g, init) → single loop: test f, accumulate with g if passes
|
|
1328
1457
|
if (up && up.method === 'filter') {
|
|
1329
1458
|
const recv = hoistArrayValue(up.source)
|
|
1330
1459
|
const acc = temp('ra')
|
|
1460
|
+
// No-init: seed is the first *passing* element, whose index isn't known
|
|
1461
|
+
// statically (filter), so track a seeded flag rather than i==0.
|
|
1462
|
+
const seeded = init !== undefined ? null : tempI32('rs')
|
|
1331
1463
|
const upReps = callbackArgReps(up.source)
|
|
1332
1464
|
const filterCb = makeCallback(up.fn, upReps)
|
|
1333
1465
|
// reduce cb signature: (acc, item, idx). Item rep mirrors upstream's item rep.
|
|
1334
1466
|
const redCb = makeCallback(fn, [null, upReps[0], { val: VAL.NUMBER }])
|
|
1467
|
+
// filter renumbers: the reduce index counts *passing* elements, not the
|
|
1468
|
+
// source position, so track a dedicated filtered-position counter (only when
|
|
1469
|
+
// the callback actually reads its index — else idxArg drops the arg anyway).
|
|
1470
|
+
const usesIdx = redCb.usedParams ? !!redCb.usedParams[2] : true
|
|
1471
|
+
const fpos = usesIdx ? tempI32('rp') : null
|
|
1472
|
+
const fold = item => ['local.set', `$${acc}`, asF64(redCb.call([typed(['local.get', `$${acc}`], 'f64'), item, fpos ? idxArg(redCb, fpos, 2) : null]))]
|
|
1473
|
+
const bump = fpos ? [['local.set', `$${fpos}`, ['i32.add', ['local.get', `$${fpos}`], ['i32.const', 1]]]] : []
|
|
1474
|
+
const accumulate = item => ['block',
|
|
1475
|
+
seeded
|
|
1476
|
+
? ['if', ['local.get', `$${seeded}`],
|
|
1477
|
+
['then', fold(item)],
|
|
1478
|
+
['else', ['block', ['local.set', `$${acc}`, asF64(item)], ['local.set', `$${seeded}`, ['i32.const', 1]]]]]
|
|
1479
|
+
: fold(item),
|
|
1480
|
+
...bump]
|
|
1335
1481
|
const loop = arrayLoop(recv.value, (_p, _l, i, item) => [
|
|
1336
1482
|
['if', truthyIR(filterCb.call([item, idxArg(filterCb, i)])),
|
|
1337
|
-
['then',
|
|
1483
|
+
['then', accumulate(item)]]
|
|
1338
1484
|
])
|
|
1339
1485
|
return typed(['block', ['result', 'f64'],
|
|
1340
1486
|
recv.setup, filterCb.setup, redCb.setup,
|
|
1341
|
-
['local.set', `$${
|
|
1487
|
+
...(fpos ? [['local.set', `$${fpos}`, ['i32.const', 0]]] : []),
|
|
1488
|
+
...(seeded ? [['local.set', `$${seeded}`, ['i32.const', 0]]] : []),
|
|
1489
|
+
['local.set', `$${acc}`, init !== undefined ? asF64(emit(init)) : ['f64.const', 0]],
|
|
1342
1490
|
...loop, ['local.get', `$${acc}`]], 'f64')
|
|
1343
1491
|
}
|
|
1344
1492
|
const recv = hoistArrayValue(arr)
|
|
@@ -1346,13 +1494,47 @@ export default (ctx) => {
|
|
|
1346
1494
|
// reduce cb signature: (acc, item, idx). Item rep mirrors recv's elem val type.
|
|
1347
1495
|
const reps = callbackArgReps(arr)
|
|
1348
1496
|
const cb = makeCallback(fn, [null, reps[0], { val: VAL.NUMBER }])
|
|
1349
|
-
|
|
1350
|
-
|
|
1497
|
+
// No initial value: JS seeds the accumulator with element 0 and folds from
|
|
1498
|
+
// index 1 — NOT a 0 seed folded over every element. A 0 seed is invisible
|
|
1499
|
+
// for `+` (additive identity) but wrong for `*` (→0) and corrupts non-numeric
|
|
1500
|
+
// folds (string reduce emits a bare `0` in the joined result). Seed at i==0.
|
|
1501
|
+
const fold = (item, i) => ['local.set', `$${acc}`, asF64(cb.call([typed(['local.get', `$${acc}`], 'f64'), item, idxArg(cb, i, 2)]))]
|
|
1502
|
+
const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
|
|
1503
|
+
init !== undefined ? fold(item, i)
|
|
1504
|
+
: ['if', ['i32.eqz', ['local.get', `$${i}`]],
|
|
1505
|
+
['then', ['local.set', `$${acc}`, asF64(item)]],
|
|
1506
|
+
['else', fold(item, i)]]
|
|
1351
1507
|
])
|
|
1352
1508
|
return typed(['block', ['result', 'f64'],
|
|
1353
1509
|
recv.setup,
|
|
1354
1510
|
cb.setup,
|
|
1355
|
-
['local.set', `$${acc}`, init ? asF64(emit(init)) : ['f64.const', 0]],
|
|
1511
|
+
['local.set', `$${acc}`, init !== undefined ? asF64(emit(init)) : ['f64.const', 0]],
|
|
1512
|
+
...loop,
|
|
1513
|
+
['local.get', `$${acc}`]], 'f64')
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// .reduceRight(fn, init) — same accumulator fold as .reduce but the last
|
|
1517
|
+
// arrayLoop arg (reverse) walks elements len-1→0. No map/filter fusion: the
|
|
1518
|
+
// reverse-fused shapes don't occur in practice and the base case is the only
|
|
1519
|
+
// form jz emits. (Previously absent despite being autoload-declared + test262-
|
|
1520
|
+
// tracked — a phantom builtin that silently returned undefined.)
|
|
1521
|
+
ctx.core.emit['.reduceRight'] = (arr, fn, init) => {
|
|
1522
|
+
const recv = hoistArrayValue(arr)
|
|
1523
|
+
const acc = temp('ra')
|
|
1524
|
+
const reps = callbackArgReps(arr)
|
|
1525
|
+
const cb = makeCallback(fn, [null, reps[0], { val: VAL.NUMBER }])
|
|
1526
|
+
// No-init: reverse walk seeds with the last element (i == len-1), folds down.
|
|
1527
|
+
const fold = (item, i) => ['local.set', `$${acc}`, asF64(cb.call([typed(['local.get', `$${acc}`], 'f64'), item, idxArg(cb, i, 2)]))]
|
|
1528
|
+
const loop = arrayLoop(recv.value, (_ptr, len, i, item) => [
|
|
1529
|
+
init !== undefined ? fold(item, i)
|
|
1530
|
+
: ['if', ['i32.eq', ['local.get', `$${i}`], ['i32.sub', ['local.get', `$${len}`], ['i32.const', 1]]],
|
|
1531
|
+
['then', ['local.set', `$${acc}`, asF64(item)]],
|
|
1532
|
+
['else', fold(item, i)]]
|
|
1533
|
+
], null, null, true)
|
|
1534
|
+
return typed(['block', ['result', 'f64'],
|
|
1535
|
+
recv.setup,
|
|
1536
|
+
cb.setup,
|
|
1537
|
+
['local.set', `$${acc}`, init !== undefined ? asF64(emit(init)) : ['f64.const', 0]],
|
|
1356
1538
|
...loop,
|
|
1357
1539
|
['local.get', `$${acc}`]], 'f64')
|
|
1358
1540
|
}
|
|
@@ -1391,23 +1573,6 @@ export default (ctx) => {
|
|
|
1391
1573
|
return typed(['block', ['result', 'f64'], recv.setup, cb.setup, ...loop, ['f64.const', 0]], 'f64')
|
|
1392
1574
|
}
|
|
1393
1575
|
|
|
1394
|
-
ctx.core.emit['.find'] = (arr, fn) => {
|
|
1395
|
-
const recv = hoistArrayValue(arr)
|
|
1396
|
-
const result = temp('ff')
|
|
1397
|
-
const exit = `$exit${ctx.func.uniq++}`
|
|
1398
|
-
const cb = makeCallback(fn, callbackArgReps(arr))
|
|
1399
|
-
const loop = arrayLoop(recv.value, (_ptr, _len, i, item) => [
|
|
1400
|
-
['if', truthyIR(cb.call([item, idxArg(cb, i)])),
|
|
1401
|
-
['then', ['local.set', `$${result}`, item], ['br', exit]]]
|
|
1402
|
-
])
|
|
1403
|
-
return typed(['block', ['result', 'f64'],
|
|
1404
|
-
recv.setup,
|
|
1405
|
-
cb.setup,
|
|
1406
|
-
['local.set', `$${result}`, ['f64.reinterpret_i64', ['i64.const', NULL_NAN]]],
|
|
1407
|
-
['block', exit, ...loop],
|
|
1408
|
-
['local.get', `$${result}`]], 'f64')
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
1576
|
// .reverse() → in-place swap arr[i] ↔ arr[len-1-i], returns the array.
|
|
1412
1577
|
ctx.core.emit['.reverse'] = (arr) => {
|
|
1413
1578
|
const recv = hoistArrayValue(arr)
|
|
@@ -1697,6 +1862,10 @@ export default (ctx) => {
|
|
|
1697
1862
|
body.push(out.ptr)
|
|
1698
1863
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
1699
1864
|
}
|
|
1865
|
+
// Unqualified alias so an untyped-receiver `.concat` gets emit's runtime
|
|
1866
|
+
// string-vs-array ptr-type branch (string → `.string:concat`, array → this),
|
|
1867
|
+
// mirroring `.at`. A known-ARRAY receiver still dispatches here directly.
|
|
1868
|
+
ctx.core.emit['.concat'] = ctx.core.emit['.array:concat']
|
|
1700
1869
|
|
|
1701
1870
|
// .flat() → flatten one level of nested arrays
|
|
1702
1871
|
ctx.core.stdlib['__arr_flat'] = `(func $__arr_flat (param $src i64) (result f64)
|