jz 0.3.1 → 0.5.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 +277 -258
- package/cli.js +10 -52
- package/index.js +181 -29
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +195 -76
- package/module/collection.js +249 -20
- package/module/console.js +1 -1
- package/module/core.js +267 -119
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +367 -61
- package/module/math.js +568 -128
- package/module/number.js +390 -227
- package/module/object.js +352 -39
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +555 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +12 -6
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1872 -487
- package/src/assemble.js +58 -20
- package/src/autoload.js +13 -1
- package/src/codegen.js +177 -0
- package/src/compile.js +462 -186
- package/src/ctx.js +85 -18
- package/src/emit.js +668 -197
- package/src/infer.js +548 -0
- package/src/ir.js +302 -27
- package/src/jzify.js +898 -259
- package/src/narrow.js +455 -246
- package/src/optimize.js +263 -99
- package/src/plan.js +713 -35
- package/src/prepare.js +818 -293
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
package/module/array.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
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 { emit,
|
|
13
|
-
import { valTypeOf, lookupValType, VAL, extractParams, updateRep, staticPropertyKey } from '../src/analyze.js'
|
|
12
|
+
import { emit, buildArrayWithSpreads } from '../src/emit.js'
|
|
13
|
+
import { valTypeOf, lookupValType, lookupNotString, VAL, extractParams, updateRep, staticPropertyKey, staticObjectProps, inlineArraySid } from '../src/analyze.js'
|
|
14
|
+
import { structInline } from '../src/abi/index.js'
|
|
14
15
|
import { ctx, inc, err, PTR, LAYOUT } from '../src/ctx.js'
|
|
15
16
|
import { strHashLiteral } from './collection.js'
|
|
16
17
|
|
|
@@ -44,6 +45,25 @@ function hoistArrayValue(arr) {
|
|
|
44
45
|
|
|
45
46
|
const arrayLenFromPtr = ptr => ['i32.load', ['i32.sub', ['local.get', `$${ptr}`], ['i32.const', 8]]]
|
|
46
47
|
|
|
48
|
+
/** K schema-ordered field-value AST nodes of an object literal `{S}` for a
|
|
49
|
+
* structInline `.push({S})`, or null if `lit` is not a plain static-key `{}`
|
|
50
|
+
* literal carrying exactly schema `sid`'s fields. Mapped by name into schema
|
|
51
|
+
* order so push sites with differing key order flatten to the same cell run. */
|
|
52
|
+
function structLiteralFields(lit, sid) {
|
|
53
|
+
if (!Array.isArray(lit) || lit[0] !== '{}') return null
|
|
54
|
+
const parsed = staticObjectProps(lit.slice(1))
|
|
55
|
+
const schema = ctx.schema.list[sid]
|
|
56
|
+
if (!parsed || parsed.names.length !== schema.length) return null
|
|
57
|
+
const byName = new Map()
|
|
58
|
+
for (let i = 0; i < parsed.names.length; i++) byName.set(parsed.names[i], parsed.values[i])
|
|
59
|
+
const out = []
|
|
60
|
+
for (const name of schema) {
|
|
61
|
+
if (!byName.has(name)) return null
|
|
62
|
+
out.push(byName.get(name))
|
|
63
|
+
}
|
|
64
|
+
return out
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
// Pure-expression check: no statements, binders, control flow, or assignments.
|
|
48
68
|
// Inlining is only safe for these — anything else needs the full closure machinery.
|
|
49
69
|
const NOT_PURE_OPS = new Set([
|
|
@@ -115,6 +135,8 @@ function makeCallback(fn, argReps) {
|
|
|
115
135
|
: typed(['f64.reinterpret_i64', ['i64.const', UNDEF_NAN]], 'f64')
|
|
116
136
|
stmts.push(['local.set', `$${fresh}`, ae])
|
|
117
137
|
}
|
|
138
|
+
// Emit-time rep seeding for inliner-fresh param locals (lifecycle:
|
|
139
|
+
// analysis already finished; these `inl_i` names didn't exist then).
|
|
118
140
|
// Apply argReps hints (caller knows recv elem val type) to inlined-param
|
|
119
141
|
// reps so emit(subst) sees `inl_i.val=NUMBER` and elides __to_num/__is_str_key.
|
|
120
142
|
if (argReps) {
|
|
@@ -156,7 +178,7 @@ function callbackArgReps(arr) {
|
|
|
156
178
|
const vt = lookupValType(arr)
|
|
157
179
|
if (vt === VAL.TYPED) itemRep = { val: VAL.NUMBER }
|
|
158
180
|
else if (vt === VAL.ARRAY) {
|
|
159
|
-
const elemVt = ctx.func.
|
|
181
|
+
const elemVt = ctx.func.localReps?.get(arr)?.arrayElemValType
|
|
160
182
|
if (elemVt) itemRep = { val: elemVt }
|
|
161
183
|
}
|
|
162
184
|
} else {
|
|
@@ -227,6 +249,7 @@ export default (ctx) => {
|
|
|
227
249
|
],
|
|
228
250
|
__arr_set_idx_ptr: ['__arr_grow', '__ptr_offset'],
|
|
229
251
|
__arr_push1: ['__arr_grow_known', '__ptr_offset'],
|
|
252
|
+
__arr_unshift: ['__arr_grow', '__len', '__ptr_offset'],
|
|
230
253
|
__typed_idx: () => ctx.features.typedarray || ctx.features.external
|
|
231
254
|
? ['__len']
|
|
232
255
|
: ['__len', '__ptr_offset'],
|
|
@@ -251,7 +274,13 @@ export default (ctx) => {
|
|
|
251
274
|
ctx.core.emit['new.Array'] = (len) => {
|
|
252
275
|
const n = tempI32('alen')
|
|
253
276
|
const nIR = ['local.get', `$${n}`]
|
|
254
|
-
|
|
277
|
+
// L3/'speed' bumps the cap floor to skip the first growth cycles (default 0
|
|
278
|
+
// → grow on first push). Length stays exactly what the user requested.
|
|
279
|
+
const minCap = ctx.transform.optimize?.arrayMinCap | 0
|
|
280
|
+
const capIR = minCap > 0
|
|
281
|
+
? ['select', ['i32.const', minCap], nIR, ['i32.gt_s', ['i32.const', minCap], nIR]]
|
|
282
|
+
: nIR
|
|
283
|
+
const out = allocPtr({ type: PTR.ARRAY, len: nIR, cap: capIR, tag: 'newarr' })
|
|
255
284
|
return typed(['block', ['result', 'f64'],
|
|
256
285
|
['local.set', `$${n}`, len == null ? ['i32.const', 0] : asI32(emit(len))],
|
|
257
286
|
out.init,
|
|
@@ -378,7 +407,7 @@ export default (ctx) => {
|
|
|
378
407
|
ctx.core.stdlib['__arr_from'] = `(func $__arr_from (param $src i64) (result f64)
|
|
379
408
|
(local $len i32) (local $dst i32)
|
|
380
409
|
(local.set $len (call $__len (local.get $src)))
|
|
381
|
-
(local.set $dst (call $__alloc_hdr (local.get $len) (local.get $len)
|
|
410
|
+
(local.set $dst (call $__alloc_hdr (local.get $len) (local.get $len)))
|
|
382
411
|
(memory.copy (local.get $dst) (call $__ptr_offset (local.get $src)) (i32.shl (local.get $len) (i32.const 3)))
|
|
383
412
|
(call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $dst)))`
|
|
384
413
|
|
|
@@ -393,7 +422,54 @@ export default (ctx) => {
|
|
|
393
422
|
return null
|
|
394
423
|
}
|
|
395
424
|
|
|
425
|
+
// Array.from(items, mapfn): spec step 2 — if mapfn is not undefined and
|
|
426
|
+
// IsCallable(mapfn) is false, throw a TypeError before iterating items.
|
|
427
|
+
// An explicit `undefined` arrives as the literal node [null, undefined];
|
|
428
|
+
// treat it as absent. Statically flag literal forms that can't be callable.
|
|
429
|
+
const isUndefinedNode = (n) => n === undefined
|
|
430
|
+
|| (Array.isArray(n) && n[0] == null && n.length === 2 && n[1] === undefined)
|
|
431
|
+
const isNonCallableMapFn = (n) => {
|
|
432
|
+
if (!Array.isArray(n)) return false // undefined / identifier — unknown
|
|
433
|
+
const op = n[0]
|
|
434
|
+
if (op == null) return true // [null,x] literal — null/number/bigint
|
|
435
|
+
if (op === '=>') return false // arrow function — callable
|
|
436
|
+
if (op === '{}' || op === 'str' || op === 'strcat' || op === '//') return true
|
|
437
|
+
if (op === '[]' && n.length < 3) return true // array literal
|
|
438
|
+
if (op === '()' && n[1] === 'Symbol') return true // Symbol(...) result
|
|
439
|
+
return false // calls / member access — unknown
|
|
440
|
+
}
|
|
441
|
+
|
|
396
442
|
ctx.core.emit['Array.from'] = (src, mapFn) => {
|
|
443
|
+
if (isUndefinedNode(mapFn)) mapFn = undefined
|
|
444
|
+
else if (isNonCallableMapFn(mapFn)) {
|
|
445
|
+
ctx.runtime.throws = true
|
|
446
|
+
return typed(['block', ['result', 'f64'], ['throw', '$__jz_err', ['f64.const', 0]]], 'f64')
|
|
447
|
+
}
|
|
448
|
+
// Array.from(string) → array of single-char strings. The generic __arr_from
|
|
449
|
+
// path memory.copies len*8 bytes from the string's byte storage (1 byte/char),
|
|
450
|
+
// reading far past its end → OOB trap. Iterate __str_idx per char instead.
|
|
451
|
+
if (resolveValType(src, valTypeOf, lookupValType) === VAL.STRING) {
|
|
452
|
+
inc('__str_idx', '__str_len')
|
|
453
|
+
const len = tempI32('sfl'), i = tempI32('sfi'), s = temp('sfs')
|
|
454
|
+
const lenIR = ['local.get', `$${len}`]
|
|
455
|
+
const out = allocPtr({ type: PTR.ARRAY, len: lenIR, tag: 'sfr' })
|
|
456
|
+
const cb = mapFn && makeCallback(mapFn, [null, { val: VAL.NUMBER }])
|
|
457
|
+
const ch = typed(['call', '$__str_idx', ['i64.reinterpret_f64', ['local.get', `$${s}`]], ['local.get', `$${i}`]], 'f64')
|
|
458
|
+
const item = cb ? cb.call([ch, idxArg(cb, i)]) : ch
|
|
459
|
+
const id = ctx.func.uniq++
|
|
460
|
+
return typed(['block', ['result', 'f64'],
|
|
461
|
+
['local.set', `$${s}`, asF64(emit(src))],
|
|
462
|
+
['local.set', `$${len}`, ['call', '$__str_len', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]],
|
|
463
|
+
out.init,
|
|
464
|
+
...(cb ? [cb.setup] : []),
|
|
465
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
466
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
467
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], lenIR]],
|
|
468
|
+
elemStore(out.local, i, asF64(item)),
|
|
469
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
470
|
+
['br', `$loop${id}`]]],
|
|
471
|
+
out.ptr], 'f64')
|
|
472
|
+
}
|
|
397
473
|
const lengthExpr = arrayLikeLength(src)
|
|
398
474
|
if (lengthExpr) {
|
|
399
475
|
const len = tempI32('fl'), i = tempI32('fi')
|
|
@@ -415,8 +491,26 @@ export default (ctx) => {
|
|
|
415
491
|
['br', `$loop${id}`]]],
|
|
416
492
|
out.ptr], 'f64')
|
|
417
493
|
}
|
|
418
|
-
|
|
419
|
-
|
|
494
|
+
if (!mapFn) {
|
|
495
|
+
inc('__arr_from')
|
|
496
|
+
return typed(['call', '$__arr_from', asI64(emit(src))], 'f64')
|
|
497
|
+
}
|
|
498
|
+
// mapfn present: iterate the source array element by element, reading each
|
|
499
|
+
// slot fresh inside the loop so a callback that mutates a not-yet-visited
|
|
500
|
+
// source element sees its update (spec reads source[k] per step).
|
|
501
|
+
inc('__len')
|
|
502
|
+
const cb = makeCallback(mapFn, [null, { val: VAL.NUMBER }])
|
|
503
|
+
const s = temp('afs'), len = tempI32('afl')
|
|
504
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${len}`], tag: 'aff' })
|
|
505
|
+
const loop = arrayLoop(typed(['local.get', `$${s}`], 'f64'),
|
|
506
|
+
(_p, _l, i, item) => [elemStore(out.local, i, asF64(cb.call([item, idxArg(cb, i)])))], len)
|
|
507
|
+
return typed(['block', ['result', 'f64'],
|
|
508
|
+
['local.set', `$${s}`, asF64(emit(src))],
|
|
509
|
+
['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]],
|
|
510
|
+
out.init,
|
|
511
|
+
cb.setup,
|
|
512
|
+
...loop,
|
|
513
|
+
out.ptr], 'f64')
|
|
420
514
|
}
|
|
421
515
|
|
|
422
516
|
// Grow array if capacity insufficient. Returns (possibly new) NaN-boxed pointer.
|
|
@@ -434,7 +528,7 @@ export default (ctx) => {
|
|
|
434
528
|
(i32.lt_u (local.get $off) (i32.const 8)))
|
|
435
529
|
(then
|
|
436
530
|
(local.set $newCap (select (local.get $minCap) (i32.const 4) (i32.gt_s (local.get $minCap) (i32.const 4))))
|
|
437
|
-
(local.set $newOff (call $__alloc_hdr (i32.const 0) (local.get $newCap)
|
|
531
|
+
(local.set $newOff (call $__alloc_hdr (i32.const 0) (local.get $newCap)))
|
|
438
532
|
(return (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $newOff)))))
|
|
439
533
|
(local.set $oldCap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
440
534
|
(if (i32.ge_s (local.get $oldCap) (local.get $minCap))
|
|
@@ -444,7 +538,7 @@ export default (ctx) => {
|
|
|
444
538
|
(i32.shl (local.get $oldCap) (i32.const 1))
|
|
445
539
|
(i32.gt_s (local.get $minCap) (i32.shl (local.get $oldCap) (i32.const 1)))))
|
|
446
540
|
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
447
|
-
(local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap)
|
|
541
|
+
(local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap)))
|
|
448
542
|
(memory.copy (local.get $newOff) (local.get $off) (i32.shl (local.get $len) (i32.const 3)))
|
|
449
543
|
${headerPropsCopyIR()}
|
|
450
544
|
${maybeDynMoveIR()}
|
|
@@ -464,7 +558,7 @@ export default (ctx) => {
|
|
|
464
558
|
(i32.shl (local.get $oldCap) (i32.const 1))
|
|
465
559
|
(i32.gt_s (local.get $minCap) (i32.shl (local.get $oldCap) (i32.const 1)))))
|
|
466
560
|
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
467
|
-
(local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap)
|
|
561
|
+
(local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $newCap)))
|
|
468
562
|
(memory.copy (local.get $newOff) (local.get $off) (i32.shl (local.get $len) (i32.const 3)))
|
|
469
563
|
${headerPropsCopyIR()}
|
|
470
564
|
${maybeDynMoveIR()}
|
|
@@ -476,17 +570,24 @@ export default (ctx) => {
|
|
|
476
570
|
// once and read len from the inline header (i32.load base-8) — avoids __len's separate
|
|
477
571
|
// forwarding follow. On the rare grow path the base is recomputed after relocation.
|
|
478
572
|
ctx.core.stdlib['__arr_set_idx_ptr'] = `(func $__arr_set_idx_ptr (param $ptr i64) (param $i i32) (param $val f64) (result f64)
|
|
479
|
-
(local $base i32) (local $p f64)
|
|
573
|
+
(local $base i32) (local $p f64) (local $oldLen i32) (local $k i32)
|
|
480
574
|
(local.set $p (f64.reinterpret_i64 (local.get $ptr)))
|
|
481
575
|
(if (i32.lt_s (local.get $i) (i32.const 0))
|
|
482
576
|
(then (return (local.get $p))))
|
|
483
577
|
(local.set $base (call $__ptr_offset (local.get $ptr)))
|
|
484
|
-
(
|
|
485
|
-
|
|
578
|
+
(local.set $oldLen (i32.load (i32.sub (local.get $base) (i32.const 8))))
|
|
579
|
+
(if (i32.ge_u (local.get $i) (local.get $oldLen))
|
|
486
580
|
(then
|
|
487
581
|
(local.set $p (call $__arr_grow (local.get $ptr) (i32.add (local.get $i) (i32.const 1))))
|
|
488
582
|
(local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))
|
|
489
|
-
(i32.store (i32.sub (local.get $base) (i32.const 8)) (i32.add (local.get $i) (i32.const 1)))
|
|
583
|
+
(i32.store (i32.sub (local.get $base) (i32.const 8)) (i32.add (local.get $i) (i32.const 1)))
|
|
584
|
+
;; gap slots [oldLen, i) are holes — fill with undefined, not zero
|
|
585
|
+
(local.set $k (local.get $oldLen))
|
|
586
|
+
(block $fdone (loop $fill
|
|
587
|
+
(br_if $fdone (i32.ge_u (local.get $k) (local.get $i)))
|
|
588
|
+
(i64.store (i32.add (local.get $base) (i32.shl (local.get $k) (i32.const 3))) (i64.const ${UNDEF_NAN}))
|
|
589
|
+
(local.set $k (i32.add (local.get $k) (i32.const 1)))
|
|
590
|
+
(br $fill)))))
|
|
490
591
|
(f64.store
|
|
491
592
|
(i32.add (local.get $base) (i32.shl (local.get $i) (i32.const 3)))
|
|
492
593
|
(local.get $val))
|
|
@@ -522,7 +623,9 @@ export default (ctx) => {
|
|
|
522
623
|
const slots = elems.map(e => extractF64Bits(asF64(emit(e))))
|
|
523
624
|
if (slots.every(b => b !== null)) return staticArrayPtr(slots)
|
|
524
625
|
}
|
|
525
|
-
|
|
626
|
+
// L3/'speed' bumps the cap floor (default 4) to skip more growth cycles.
|
|
627
|
+
const minCap = Math.max(ctx.transform.optimize?.arrayMinCap | 0, 4)
|
|
628
|
+
const a = allocArray(len, Math.max(len, minCap))
|
|
526
629
|
const body = [...a.setup]
|
|
527
630
|
for (let i = 0; i < len; i++)
|
|
528
631
|
body.push(['f64.store', slotAddr(a.local, i), asF64(emit(elems[i]))])
|
|
@@ -530,52 +633,11 @@ export default (ctx) => {
|
|
|
530
633
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
531
634
|
}
|
|
532
635
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
...a.setup,
|
|
539
|
-
['local.set', `$${out}`, a.ptr],
|
|
540
|
-
['local.set', `$${pos}`, ['i32.const', 0]],
|
|
541
|
-
]
|
|
542
|
-
|
|
543
|
-
for (const e of elems) {
|
|
544
|
-
if (Array.isArray(e) && e[0] === '...') {
|
|
545
|
-
const src = temp('ss'), slen = tempI32('sl'), si = tempI32('si')
|
|
546
|
-
const id = ctx.func.uniq++
|
|
547
|
-
const spreadVal = multiCount(e[1]) ? materializeMulti(e[1]) : asF64(emit(e[1]))
|
|
548
|
-
const srcVT = valTypeOf(e[1])
|
|
549
|
-
const spreadItem = srcVT === VAL.ARRAY
|
|
550
|
-
? (inc('__arr_idx_known'), ['call', '$__arr_idx_known', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
|
|
551
|
-
: srcVT === VAL.STRING
|
|
552
|
-
? (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
|
|
553
|
-
: ctx.module.modules['string']
|
|
554
|
-
? ['if', ['result', 'f64'],
|
|
555
|
-
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${src}`]]], ['i32.const', PTR.STRING]],
|
|
556
|
-
['then', (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])],
|
|
557
|
-
['else', (['call', '$__typed_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])]]
|
|
558
|
-
: (['call', '$__typed_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
|
|
559
|
-
|
|
560
|
-
body.push(
|
|
561
|
-
['local.set', `$${src}`, spreadVal],
|
|
562
|
-
['local.set', `$${slen}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${src}`]]]],
|
|
563
|
-
['local.set', `$${si}`, ['i32.const', 0]],
|
|
564
|
-
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
565
|
-
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${si}`], ['local.get', `$${slen}`]]],
|
|
566
|
-
['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['i64.reinterpret_f64', ['local.get', `$${out}`]], ['local.get', `$${pos}`], spreadItem]],
|
|
567
|
-
['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]],
|
|
568
|
-
['local.set', `$${si}`, ['i32.add', ['local.get', `$${si}`], ['i32.const', 1]]],
|
|
569
|
-
['br', `$loop${id}`]]])
|
|
570
|
-
} else {
|
|
571
|
-
body.push(
|
|
572
|
-
['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['i64.reinterpret_f64', ['local.get', `$${out}`]], ['local.get', `$${pos}`], asF64(emit(e))]],
|
|
573
|
-
['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]])
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
body.push(['local.get', `$${out}`])
|
|
578
|
-
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
636
|
+
// Spread literal: buildArrayWithSpreads pre-sums the total length, allocates
|
|
637
|
+
// exact, and bulk-copies ARRAY sources with a single memory.copy — vs the
|
|
638
|
+
// per-element __arr_set_idx_ptr grow loop. Normalise the parser's `['...', x]`.
|
|
639
|
+
return buildArrayWithSpreads(elems.map(e =>
|
|
640
|
+
Array.isArray(e) && e[0] === '...' ? ['__spread', e[1]] : e))
|
|
579
641
|
}
|
|
580
642
|
|
|
581
643
|
// === Index read ===
|
|
@@ -587,6 +649,8 @@ export default (ctx) => {
|
|
|
587
649
|
if (typeof arr !== 'string' && !(Array.isArray(arr) && arr[0] === 'local.get')) {
|
|
588
650
|
const vtArr = valTypeOf(arr)
|
|
589
651
|
const h = temp('ai')
|
|
652
|
+
// Emit-time rep seed on fresh hoist-temp `h` so the recursive emit
|
|
653
|
+
// below (`ctx.core.emit['[]'](h, idx)`) takes the typed-arr fast path.
|
|
590
654
|
if (vtArr) updateRep(h, { val: vtArr })
|
|
591
655
|
const setup = ['local.set', `$${h}`, asF64(emit(arr))]
|
|
592
656
|
const result = ctx.core.emit['[]'](h, idx)
|
|
@@ -604,12 +668,17 @@ export default (ctx) => {
|
|
|
604
668
|
const litKey = isLiteralStr(idx) ? idx[1]
|
|
605
669
|
: typeof arr === 'string' && lookupValType(arr) === VAL.OBJECT ? staticPropertyKey(idx)
|
|
606
670
|
: null
|
|
671
|
+
// SRoA flat object: `o['k']` → `local.get $o#i` (analyze.js scanFlatObjects).
|
|
672
|
+
if (litKey != null && typeof arr === 'string' && ctx.func.flatObjects?.has(arr)) {
|
|
673
|
+
const fo = ctx.func.flatObjects.get(arr)
|
|
674
|
+
const fi = fo.names.indexOf(litKey)
|
|
675
|
+
if (fi >= 0) return typed(['local.get', `$${arr}#${fi}`], 'f64')
|
|
676
|
+
}
|
|
607
677
|
if (litKey != null && typeof arr === 'string' && ctx.schema.find) {
|
|
608
678
|
const slot = ctx.schema.find(arr, litKey)
|
|
609
679
|
if (slot >= 0) {
|
|
610
680
|
inc('__ptr_offset')
|
|
611
|
-
return typed(['
|
|
612
|
-
['i32.add', ['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(emit(arr))]], ['i32.const', slot * 8]]], 'f64')
|
|
681
|
+
return typed(ctx.abi.object.ops.load(['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(emit(arr))]], slot), 'f64')
|
|
613
682
|
}
|
|
614
683
|
}
|
|
615
684
|
if (litKey != null && typeof arr === 'string' && lookupValType(arr) === VAL.HASH) {
|
|
@@ -647,9 +716,9 @@ export default (ctx) => {
|
|
|
647
716
|
if (keyType === VAL.STRING) return typed(dynLoad(asF64(emit(arr)), asF64(emit(idx))), 'f64')
|
|
648
717
|
if (useRuntimeKeyDispatch)
|
|
649
718
|
return emitDynamicKeyDispatch(asF64(emit(arr)), keyExpr =>
|
|
650
|
-
|
|
719
|
+
ctx.abi.array.ops.load(['call', '$__ptr_offset', ['i64.reinterpret_f64', inner]], asI32(typed(keyExpr, 'f64'))))
|
|
651
720
|
return typed(
|
|
652
|
-
|
|
721
|
+
ctx.abi.array.ops.load(['call', '$__ptr_offset', ['i64.reinterpret_f64', inner]], vi),
|
|
653
722
|
'f64')
|
|
654
723
|
}
|
|
655
724
|
// HASH receiver with runtime string key: probe the HASH directly via
|
|
@@ -676,6 +745,24 @@ export default (ctx) => {
|
|
|
676
745
|
if (keyType === VAL.STRING)
|
|
677
746
|
return typed(dynLoad(ptrExpr, asF64(emit(idx))), 'f64')
|
|
678
747
|
if (vt === 'array') {
|
|
748
|
+
// structInline Array<S>: element i is K consecutive inline f64 schema
|
|
749
|
+
// cells — no per-row heap object, no stored element pointer. `arr[i]` is
|
|
750
|
+
// the byte address of the element's first cell, returned as a first-class
|
|
751
|
+
// unboxed OBJECT pointer (schema S); `arr[i].field` then composes a plain
|
|
752
|
+
// `+field*8` off it. The narrower proved every use of this binding is one
|
|
753
|
+
// structInline handles (src/analyze.js analyzeStructInline).
|
|
754
|
+
const inlSid = inlineArraySid(arr)
|
|
755
|
+
if (inlSid != null) {
|
|
756
|
+
inc('__ptr_offset')
|
|
757
|
+
const baseI32 = tempI32('ab')
|
|
758
|
+
const K = ctx.schema.list[inlSid].length
|
|
759
|
+
const cell = typed(structInline(K).ops.elemAddr(
|
|
760
|
+
['local.tee', `$${baseI32}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
|
|
761
|
+
vi), 'i32')
|
|
762
|
+
cell.ptrKind = VAL.OBJECT
|
|
763
|
+
cell.ptrAux = inlSid
|
|
764
|
+
return cell
|
|
765
|
+
}
|
|
679
766
|
// Known-ARRAY → __arr_idx (single forwarding follow + inline bounds check),
|
|
680
767
|
// not __typed_idx (which does __len + __ptr_offset = two forwarding follows
|
|
681
768
|
// plus type-dispatch overhead irrelevant for plain arrays).
|
|
@@ -690,7 +777,7 @@ export default (ctx) => {
|
|
|
690
777
|
// NaN-boxed pointer for OBJECT/STRING; downstream typed() handles both).
|
|
691
778
|
// Fast path fires on schemaId (Array<{x,y,z}> shapes) OR plain elem-val
|
|
692
779
|
// (Array<NUMBER> from `.map(x => x*k)` etc.).
|
|
693
|
-
const rep = typeof arr === 'string' ? ctx.func.
|
|
780
|
+
const rep = typeof arr === 'string' ? ctx.func.localReps?.get(arr) : null
|
|
694
781
|
const hasElemFact = rep?.arrayElemSchema != null
|
|
695
782
|
if (hasElemFact && keyIsNum) {
|
|
696
783
|
inc('__ptr_offset')
|
|
@@ -699,11 +786,9 @@ export default (ctx) => {
|
|
|
699
786
|
// `i32.wrap_i64 (i64.reinterpret_f64 (f64.load …))` → `i32.load …`
|
|
700
787
|
// when this load feeds a ptrUnboxed OBJECT field.
|
|
701
788
|
const baseI32 = tempI32('ab')
|
|
702
|
-
return typed(
|
|
703
|
-
['
|
|
704
|
-
|
|
705
|
-
['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
|
|
706
|
-
['i32.shl', vi, ['i32.const', 3]]]], 'f64')
|
|
789
|
+
return typed(ctx.abi.array.ops.load(
|
|
790
|
+
['local.tee', `$${baseI32}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
|
|
791
|
+
vi), 'f64')
|
|
707
792
|
}
|
|
708
793
|
const baseTmp = temp()
|
|
709
794
|
// Numeric key (literal or known-NUMBER name) → skip __is_str_key dispatch;
|
|
@@ -739,10 +824,23 @@ export default (ctx) => {
|
|
|
739
824
|
})
|
|
740
825
|
return typed((['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], vi]), 'f64')
|
|
741
826
|
}
|
|
742
|
-
|
|
827
|
+
// Pure-write narrowing: an `xs[i] = v` / `xs.length = n` site in this
|
|
828
|
+
// body, with no offsetting string-shape evidence (typeof string check,
|
|
829
|
+
// STRING_ONLY method call, string-literal assignment), proves `arr` isn't
|
|
830
|
+
// a primitive string. Skip the runtime `__ptr_type==STRING` gate —
|
|
831
|
+
// `__typed_idx` already handles both ARRAY and TYPED tags internally.
|
|
832
|
+
// Discharge analysis lives in src/infer.js (notStringEvidence source); flow-sensitive
|
|
833
|
+
// notString refinements (from `if (typeof x === 'string') return ...`) overlay via lookup.
|
|
834
|
+
const notString = typeof arr === 'string' && lookupNotString(arr)
|
|
835
|
+
// A provably-NUMBER key can never be a string key, so the `__is_str_key`
|
|
836
|
+
// dispatch is statically dead — its numeric arm is what runs. Fall through
|
|
837
|
+
// to the direct ptr_type==STRING ? __str_idx : __typed_idx form below, which
|
|
838
|
+
// indexes with the i32 `vi` and skips the per-element f64 round-trip + call.
|
|
839
|
+
// Mirrors the `&& !keyIsNum` guard in the known-array/typed branches above.
|
|
840
|
+
if (useRuntimeKeyDispatch && keyType !== VAL.NUMBER)
|
|
743
841
|
return emitDynamicKeyDispatch(ptrExpr, keyExpr => {
|
|
744
842
|
const keyI32 = asI32(typed(keyExpr, 'f64'))
|
|
745
|
-
if (ctx.module.modules['string']) {
|
|
843
|
+
if (ctx.module.modules['string'] && !notString) {
|
|
746
844
|
return ['if', ['result', 'f64'],
|
|
747
845
|
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ptrExpr]], ['i32.const', PTR.STRING]],
|
|
748
846
|
['then', (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])],
|
|
@@ -751,7 +849,7 @@ export default (ctx) => {
|
|
|
751
849
|
return (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])
|
|
752
850
|
})
|
|
753
851
|
// Unknown → runtime dispatch (string module loaded → check ptr_type)
|
|
754
|
-
if (ctx.module.modules['string'])
|
|
852
|
+
if (ctx.module.modules['string'] && !notString)
|
|
755
853
|
return typed(
|
|
756
854
|
['if', ['result', 'f64'],
|
|
757
855
|
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ptrExpr]], ['i32.const', PTR.STRING]],
|
|
@@ -765,6 +863,23 @@ export default (ctx) => {
|
|
|
765
863
|
|
|
766
864
|
// .push(val) → append, increment len, return array (possibly reallocated pointer)
|
|
767
865
|
ctx.core.emit['.push'] = (arr, ...vals) => {
|
|
866
|
+
// structInline Array<S>: `.push({S})` writes the K schema fields as K
|
|
867
|
+
// consecutive f64 cells. Flatten the struct literal into K schema-ordered
|
|
868
|
+
// field-value nodes and fall through to the general multi-value store path
|
|
869
|
+
// — `len`/`cap` count physical cells, so `__arr_grow_known` and the cell
|
|
870
|
+
// loop are reused untouched; `.push` then returns the logical element count
|
|
871
|
+
// (`len / K`). K=1 stays a single value → the `__arr_push1` fast path.
|
|
872
|
+
const inlSid = inlineArraySid(arr)
|
|
873
|
+
const inlK = inlSid != null ? ctx.schema.list[inlSid].length : 0
|
|
874
|
+
if (inlSid != null) {
|
|
875
|
+
const flat = []
|
|
876
|
+
for (const v of vals) {
|
|
877
|
+
const fields = structLiteralFields(v, inlSid)
|
|
878
|
+
if (!fields) err(`structInline Array.push expects { ${ctx.schema.list[inlSid].join(', ')} } literal arguments`)
|
|
879
|
+
flat.push(...fields)
|
|
880
|
+
}
|
|
881
|
+
vals = flat
|
|
882
|
+
}
|
|
768
883
|
// Out-of-line fast path: single value, named known-ARRAY receiver. One call +
|
|
769
884
|
// var update instead of ~30 inlined instructions — the dominant size cost of
|
|
770
885
|
// push-heavy code (e.g. watr's WASM emitter).
|
|
@@ -848,7 +963,11 @@ export default (ctx) => {
|
|
|
848
963
|
else
|
|
849
964
|
body.push(['local.set', `$${arr}`, ['local.get', `$${t}`]])
|
|
850
965
|
}
|
|
851
|
-
|
|
966
|
+
// structInline: `len` counts physical cells — `.push` returns the JS array
|
|
967
|
+
// length, i.e. the logical element count `len / K`.
|
|
968
|
+
body.push(['f64.convert_i32_s', inlK > 1
|
|
969
|
+
? ['i32.div_s', ['local.get', `$${len}`], ['i32.const', inlK]]
|
|
970
|
+
: ['local.get', `$${len}`]])
|
|
852
971
|
|
|
853
972
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
854
973
|
}
|
|
@@ -1638,5 +1757,5 @@ export default (ctx) => {
|
|
|
1638
1757
|
|
|
1639
1758
|
// .join(sep) → concatenate array elements with separator string
|
|
1640
1759
|
ctx.core.emit['.join'] = (arr, sep) => (inc('__str_join'),
|
|
1641
|
-
typed(['call', '$__str_join', asI64(emit(arr)), asI64(emit(sep))], 'f64'))
|
|
1760
|
+
typed(['call', '$__str_join', asI64(emit(arr)), asI64(emit(sep == null ? ['str', ','] : sep))], 'f64'))
|
|
1642
1761
|
}
|