jz 0.4.0 → 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 +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +251 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +551 -128
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +540 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- 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 +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +662 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +786 -94
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +822 -150
- 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/src/fuse.js +0 -159
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'],
|
|
@@ -399,7 +422,54 @@ export default (ctx) => {
|
|
|
399
422
|
return null
|
|
400
423
|
}
|
|
401
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
|
+
|
|
402
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
|
+
}
|
|
403
473
|
const lengthExpr = arrayLikeLength(src)
|
|
404
474
|
if (lengthExpr) {
|
|
405
475
|
const len = tempI32('fl'), i = tempI32('fi')
|
|
@@ -421,8 +491,26 @@ export default (ctx) => {
|
|
|
421
491
|
['br', `$loop${id}`]]],
|
|
422
492
|
out.ptr], 'f64')
|
|
423
493
|
}
|
|
424
|
-
|
|
425
|
-
|
|
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')
|
|
426
514
|
}
|
|
427
515
|
|
|
428
516
|
// Grow array if capacity insufficient. Returns (possibly new) NaN-boxed pointer.
|
|
@@ -482,17 +570,24 @@ export default (ctx) => {
|
|
|
482
570
|
// once and read len from the inline header (i32.load base-8) — avoids __len's separate
|
|
483
571
|
// forwarding follow. On the rare grow path the base is recomputed after relocation.
|
|
484
572
|
ctx.core.stdlib['__arr_set_idx_ptr'] = `(func $__arr_set_idx_ptr (param $ptr i64) (param $i i32) (param $val f64) (result f64)
|
|
485
|
-
(local $base i32) (local $p f64)
|
|
573
|
+
(local $base i32) (local $p f64) (local $oldLen i32) (local $k i32)
|
|
486
574
|
(local.set $p (f64.reinterpret_i64 (local.get $ptr)))
|
|
487
575
|
(if (i32.lt_s (local.get $i) (i32.const 0))
|
|
488
576
|
(then (return (local.get $p))))
|
|
489
577
|
(local.set $base (call $__ptr_offset (local.get $ptr)))
|
|
490
|
-
(
|
|
491
|
-
|
|
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))
|
|
492
580
|
(then
|
|
493
581
|
(local.set $p (call $__arr_grow (local.get $ptr) (i32.add (local.get $i) (i32.const 1))))
|
|
494
582
|
(local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))
|
|
495
|
-
(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)))))
|
|
496
591
|
(f64.store
|
|
497
592
|
(i32.add (local.get $base) (i32.shl (local.get $i) (i32.const 3)))
|
|
498
593
|
(local.get $val))
|
|
@@ -538,53 +633,11 @@ export default (ctx) => {
|
|
|
538
633
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
539
634
|
}
|
|
540
635
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const body = [
|
|
547
|
-
...a.setup,
|
|
548
|
-
['local.set', `$${out}`, a.ptr],
|
|
549
|
-
['local.set', `$${pos}`, ['i32.const', 0]],
|
|
550
|
-
]
|
|
551
|
-
|
|
552
|
-
for (const e of elems) {
|
|
553
|
-
if (Array.isArray(e) && e[0] === '...') {
|
|
554
|
-
const src = temp('ss'), slen = tempI32('sl'), si = tempI32('si')
|
|
555
|
-
const id = ctx.func.uniq++
|
|
556
|
-
const spreadVal = multiCount(e[1]) ? materializeMulti(e[1]) : asF64(emit(e[1]))
|
|
557
|
-
const srcVT = valTypeOf(e[1])
|
|
558
|
-
const spreadItem = srcVT === VAL.ARRAY
|
|
559
|
-
? (inc('__arr_idx_known'), ['call', '$__arr_idx_known', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
|
|
560
|
-
: srcVT === VAL.STRING
|
|
561
|
-
? (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
|
|
562
|
-
: ctx.module.modules['string']
|
|
563
|
-
? ['if', ['result', 'f64'],
|
|
564
|
-
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${src}`]]], ['i32.const', PTR.STRING]],
|
|
565
|
-
['then', (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])],
|
|
566
|
-
['else', (['call', '$__typed_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])]]
|
|
567
|
-
: (['call', '$__typed_idx', ['i64.reinterpret_f64', ['local.get', `$${src}`]], ['local.get', `$${si}`]])
|
|
568
|
-
|
|
569
|
-
body.push(
|
|
570
|
-
['local.set', `$${src}`, spreadVal],
|
|
571
|
-
['local.set', `$${slen}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${src}`]]]],
|
|
572
|
-
['local.set', `$${si}`, ['i32.const', 0]],
|
|
573
|
-
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
574
|
-
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${si}`], ['local.get', `$${slen}`]]],
|
|
575
|
-
['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['i64.reinterpret_f64', ['local.get', `$${out}`]], ['local.get', `$${pos}`], spreadItem]],
|
|
576
|
-
['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]],
|
|
577
|
-
['local.set', `$${si}`, ['i32.add', ['local.get', `$${si}`], ['i32.const', 1]]],
|
|
578
|
-
['br', `$loop${id}`]]])
|
|
579
|
-
} else {
|
|
580
|
-
body.push(
|
|
581
|
-
['local.set', `$${out}`, ['call', '$__arr_set_idx_ptr', ['i64.reinterpret_f64', ['local.get', `$${out}`]], ['local.get', `$${pos}`], asF64(emit(e))]],
|
|
582
|
-
['local.set', `$${pos}`, ['i32.add', ['local.get', `$${pos}`], ['i32.const', 1]]])
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
body.push(['local.get', `$${out}`])
|
|
587
|
-
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))
|
|
588
641
|
}
|
|
589
642
|
|
|
590
643
|
// === Index read ===
|
|
@@ -596,6 +649,8 @@ export default (ctx) => {
|
|
|
596
649
|
if (typeof arr !== 'string' && !(Array.isArray(arr) && arr[0] === 'local.get')) {
|
|
597
650
|
const vtArr = valTypeOf(arr)
|
|
598
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.
|
|
599
654
|
if (vtArr) updateRep(h, { val: vtArr })
|
|
600
655
|
const setup = ['local.set', `$${h}`, asF64(emit(arr))]
|
|
601
656
|
const result = ctx.core.emit['[]'](h, idx)
|
|
@@ -613,12 +668,17 @@ export default (ctx) => {
|
|
|
613
668
|
const litKey = isLiteralStr(idx) ? idx[1]
|
|
614
669
|
: typeof arr === 'string' && lookupValType(arr) === VAL.OBJECT ? staticPropertyKey(idx)
|
|
615
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
|
+
}
|
|
616
677
|
if (litKey != null && typeof arr === 'string' && ctx.schema.find) {
|
|
617
678
|
const slot = ctx.schema.find(arr, litKey)
|
|
618
679
|
if (slot >= 0) {
|
|
619
680
|
inc('__ptr_offset')
|
|
620
|
-
return typed(['
|
|
621
|
-
['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')
|
|
622
682
|
}
|
|
623
683
|
}
|
|
624
684
|
if (litKey != null && typeof arr === 'string' && lookupValType(arr) === VAL.HASH) {
|
|
@@ -656,9 +716,9 @@ export default (ctx) => {
|
|
|
656
716
|
if (keyType === VAL.STRING) return typed(dynLoad(asF64(emit(arr)), asF64(emit(idx))), 'f64')
|
|
657
717
|
if (useRuntimeKeyDispatch)
|
|
658
718
|
return emitDynamicKeyDispatch(asF64(emit(arr)), keyExpr =>
|
|
659
|
-
|
|
719
|
+
ctx.abi.array.ops.load(['call', '$__ptr_offset', ['i64.reinterpret_f64', inner]], asI32(typed(keyExpr, 'f64'))))
|
|
660
720
|
return typed(
|
|
661
|
-
|
|
721
|
+
ctx.abi.array.ops.load(['call', '$__ptr_offset', ['i64.reinterpret_f64', inner]], vi),
|
|
662
722
|
'f64')
|
|
663
723
|
}
|
|
664
724
|
// HASH receiver with runtime string key: probe the HASH directly via
|
|
@@ -685,6 +745,24 @@ export default (ctx) => {
|
|
|
685
745
|
if (keyType === VAL.STRING)
|
|
686
746
|
return typed(dynLoad(ptrExpr, asF64(emit(idx))), 'f64')
|
|
687
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
|
+
}
|
|
688
766
|
// Known-ARRAY → __arr_idx (single forwarding follow + inline bounds check),
|
|
689
767
|
// not __typed_idx (which does __len + __ptr_offset = two forwarding follows
|
|
690
768
|
// plus type-dispatch overhead irrelevant for plain arrays).
|
|
@@ -699,7 +777,7 @@ export default (ctx) => {
|
|
|
699
777
|
// NaN-boxed pointer for OBJECT/STRING; downstream typed() handles both).
|
|
700
778
|
// Fast path fires on schemaId (Array<{x,y,z}> shapes) OR plain elem-val
|
|
701
779
|
// (Array<NUMBER> from `.map(x => x*k)` etc.).
|
|
702
|
-
const rep = typeof arr === 'string' ? ctx.func.
|
|
780
|
+
const rep = typeof arr === 'string' ? ctx.func.localReps?.get(arr) : null
|
|
703
781
|
const hasElemFact = rep?.arrayElemSchema != null
|
|
704
782
|
if (hasElemFact && keyIsNum) {
|
|
705
783
|
inc('__ptr_offset')
|
|
@@ -708,11 +786,9 @@ export default (ctx) => {
|
|
|
708
786
|
// `i32.wrap_i64 (i64.reinterpret_f64 (f64.load …))` → `i32.load …`
|
|
709
787
|
// when this load feeds a ptrUnboxed OBJECT field.
|
|
710
788
|
const baseI32 = tempI32('ab')
|
|
711
|
-
return typed(
|
|
712
|
-
['
|
|
713
|
-
|
|
714
|
-
['call', '$__ptr_offset', ['i64.reinterpret_f64', ptrExpr]]],
|
|
715
|
-
['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')
|
|
716
792
|
}
|
|
717
793
|
const baseTmp = temp()
|
|
718
794
|
// Numeric key (literal or known-NUMBER name) → skip __is_str_key dispatch;
|
|
@@ -748,10 +824,23 @@ export default (ctx) => {
|
|
|
748
824
|
})
|
|
749
825
|
return typed((['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], vi]), 'f64')
|
|
750
826
|
}
|
|
751
|
-
|
|
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)
|
|
752
841
|
return emitDynamicKeyDispatch(ptrExpr, keyExpr => {
|
|
753
842
|
const keyI32 = asI32(typed(keyExpr, 'f64'))
|
|
754
|
-
if (ctx.module.modules['string']) {
|
|
843
|
+
if (ctx.module.modules['string'] && !notString) {
|
|
755
844
|
return ['if', ['result', 'f64'],
|
|
756
845
|
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ptrExpr]], ['i32.const', PTR.STRING]],
|
|
757
846
|
['then', (inc('__str_idx'), ['call', '$__str_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])],
|
|
@@ -760,7 +849,7 @@ export default (ctx) => {
|
|
|
760
849
|
return (['call', '$__typed_idx', ['i64.reinterpret_f64', ptrExpr], keyI32])
|
|
761
850
|
})
|
|
762
851
|
// Unknown → runtime dispatch (string module loaded → check ptr_type)
|
|
763
|
-
if (ctx.module.modules['string'])
|
|
852
|
+
if (ctx.module.modules['string'] && !notString)
|
|
764
853
|
return typed(
|
|
765
854
|
['if', ['result', 'f64'],
|
|
766
855
|
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ptrExpr]], ['i32.const', PTR.STRING]],
|
|
@@ -774,6 +863,23 @@ export default (ctx) => {
|
|
|
774
863
|
|
|
775
864
|
// .push(val) → append, increment len, return array (possibly reallocated pointer)
|
|
776
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
|
+
}
|
|
777
883
|
// Out-of-line fast path: single value, named known-ARRAY receiver. One call +
|
|
778
884
|
// var update instead of ~30 inlined instructions — the dominant size cost of
|
|
779
885
|
// push-heavy code (e.g. watr's WASM emitter).
|
|
@@ -857,7 +963,11 @@ export default (ctx) => {
|
|
|
857
963
|
else
|
|
858
964
|
body.push(['local.set', `$${arr}`, ['local.get', `$${t}`]])
|
|
859
965
|
}
|
|
860
|
-
|
|
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}`]])
|
|
861
971
|
|
|
862
972
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
863
973
|
}
|
|
@@ -1647,5 +1757,5 @@ export default (ctx) => {
|
|
|
1647
1757
|
|
|
1648
1758
|
// .join(sep) → concatenate array elements with separator string
|
|
1649
1759
|
ctx.core.emit['.join'] = (arr, sep) => (inc('__str_join'),
|
|
1650
|
-
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'))
|
|
1651
1761
|
}
|