jz 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -72
- package/bench/README.md +253 -100
- package/bench/bench.svg +58 -81
- package/cli.js +85 -12
- package/dist/interop.js +1 -0
- package/dist/jz.js +6196 -0
- package/index.d.ts +126 -0
- package/index.js +222 -35
- package/interop.js +240 -141
- package/layout.js +34 -18
- package/module/array.js +99 -111
- package/module/collection.js +124 -30
- package/module/console.js +1 -1
- package/module/core.js +163 -19
- package/module/json.js +3 -3
- package/module/math.js +162 -3
- package/module/number.js +268 -13
- package/module/object.js +37 -5
- package/module/regex.js +8 -7
- package/module/simd.js +37 -5
- package/module/string.js +203 -168
- package/module/typedarray.js +565 -112
- package/package.json +26 -7
- package/src/abi/string.js +29 -29
- package/src/ast.js +19 -2
- package/src/autoload.js +3 -0
- package/src/compile/analyze-scans.js +174 -11
- package/src/compile/analyze.js +101 -4
- package/src/compile/cse-load.js +200 -0
- package/src/compile/emit-assign.js +82 -20
- package/src/compile/emit.js +592 -51
- package/src/compile/index.js +504 -89
- package/src/compile/infer.js +36 -1
- package/src/compile/loop-divmod.js +109 -0
- package/src/compile/loop-model.js +91 -0
- package/src/compile/loop-square.js +102 -0
- package/src/compile/narrow.js +275 -41
- package/src/compile/peel-stencil.js +215 -0
- package/src/compile/plan/advise.js +55 -1
- package/src/compile/plan/common.js +29 -0
- package/src/compile/plan/index.js +8 -1
- package/src/compile/plan/inline.js +180 -22
- package/src/compile/plan/literals.js +313 -24
- package/src/compile/plan/scope.js +115 -39
- package/src/compile/program-facts.js +21 -2
- package/src/ctx.js +96 -19
- package/src/helper-counters.js +137 -0
- package/src/ir.js +157 -20
- package/src/kind-traits.js +34 -3
- package/src/kind.js +79 -4
- package/src/op-policy.js +5 -2
- package/src/ops.js +119 -0
- package/src/optimize/index.js +1274 -151
- package/src/optimize/recurse.js +182 -0
- package/src/optimize/vectorize.js +4187 -253
- package/src/prepare/index.js +75 -14
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +6 -2
- package/src/static.js +9 -0
- package/src/type.js +63 -51
- package/src/wat/assemble.js +286 -21
- package/src/widen.js +21 -0
- package/src/wat/optimize.js +0 -3760
package/src/compile/analyze.js
CHANGED
|
@@ -76,7 +76,7 @@ import { TYPED_ELEM_CODE, TYPED_ELEM_VIEW_FLAG, TYPED_ELEM_BIGINT_FLAG, encodeTy
|
|
|
76
76
|
import {
|
|
77
77
|
findFreeVars, findMutations, boxedCaptures,
|
|
78
78
|
collectI32SafeIndexVars, collectF64StridedIndexVars, narrowUint32, scanBindingUses,
|
|
79
|
-
scanFlatObjects, scanSliceViews, USE,
|
|
79
|
+
scanFlatObjects, scanSliceViews, scanNeverGrown, scanNumericFill, USE,
|
|
80
80
|
} from './analyze-scans.js'
|
|
81
81
|
|
|
82
82
|
export { findFreeVars, findMutations, boxedCaptures } from './analyze-scans.js'
|
|
@@ -118,12 +118,26 @@ const makeTypedTracker = (get, set, del) => {
|
|
|
118
118
|
if (poison.has(name)) return
|
|
119
119
|
const setOrInvalidate = (c) => {
|
|
120
120
|
if (c === MIXED_CTORS) return invalidate(name)
|
|
121
|
+
// Module-level alias fact: a `.view` ctor (subarray / buffer-backed) is the ONLY
|
|
122
|
+
// way two typed-array bindings can overlap. Recording that the program creates
|
|
123
|
+
// ANY view lets memory-reordering passes (SLP) stay sound by bailing when set —
|
|
124
|
+
// with no view, distinct typed bases own disjoint allocations.
|
|
125
|
+
if (typeof c === 'string' && c.endsWith('.view')) ctx.features.typedView = true
|
|
121
126
|
const prev = get(name)
|
|
122
127
|
if (prev && prev !== c) invalidate(name)
|
|
123
128
|
else set(name, c)
|
|
124
129
|
}
|
|
125
130
|
const ctor = typedElemCtor(rhs)
|
|
126
131
|
if (ctor) return setOrInvalidate(ctor)
|
|
132
|
+
// `recv.subarray(...)` is a zero-copy VIEW aliasing the receiver's buffer — its elem
|
|
133
|
+
// ctor is the receiver's type with the `.view` flag, so the binding unboxes to a typed
|
|
134
|
+
// pointer and element writes take the descriptor-indirected path (not desc-as-data).
|
|
135
|
+
if (Array.isArray(rhs) && rhs[0] === '()' && Array.isArray(rhs[1]) && rhs[1][0] === '.'
|
|
136
|
+
&& rhs[1][2] === 'subarray' && typeof rhs[1][1] === 'string') {
|
|
137
|
+
const recvCtor = resolveName(rhs[1][1])
|
|
138
|
+
if (recvCtor) return setOrInvalidate((recvCtor.endsWith('.view') ? recvCtor.slice(0, -5) : recvCtor) + '.view')
|
|
139
|
+
return
|
|
140
|
+
}
|
|
127
141
|
// TYPED-narrowed call result carries its elem aux on f.sig.ptrAux — reverse-map
|
|
128
142
|
// to a canonical ctor so the unboxed local's rep restores the same aux.
|
|
129
143
|
if (Array.isArray(rhs) && rhs[0] === '()' && typeof rhs[1] === 'string') {
|
|
@@ -159,7 +173,7 @@ export function analyzeBody(body) {
|
|
|
159
173
|
// for any slice and can't be WeakMap-keyed. Return empty maps without caching.
|
|
160
174
|
if (body === null || typeof body !== 'object') return {
|
|
161
175
|
locals: new Map(), valTypes: new Map(), arrElemSchemas: new Map(),
|
|
162
|
-
arrElemValTypes: new Map(), typedElems: new Map(), escapes: new Map(),
|
|
176
|
+
arrElemValTypes: new Map(), arrElemTypedCtors: new Map(), typedElems: new Map(), escapes: new Map(),
|
|
163
177
|
flatObjects: new Map(),
|
|
164
178
|
}
|
|
165
179
|
const hit = _bodyFactsCache.get(body)
|
|
@@ -175,6 +189,10 @@ export function analyzeBody(body) {
|
|
|
175
189
|
// so `chord[j]` is a Number and skips __to_num. Single-level only — enough for the
|
|
176
190
|
// 2-D table pattern without a general nested-type lattice.
|
|
177
191
|
const arrElemElemValTypes = new Map()
|
|
192
|
+
// `name`'s elements are all typed arrays of one ctor ('new.Float32Array'), e.g.
|
|
193
|
+
// `Array.from(nCh, () => new Float32Array(n))` (codec channelData). Lets `arr[i]`
|
|
194
|
+
// resolve as that typed array so `arr[i][j]` / `let o = arr[i]; o[j]` inline.
|
|
195
|
+
const arrElemTypedCtors = new Map()
|
|
178
196
|
const typedElems = new Map()
|
|
179
197
|
const escapes = new Map() // name → bool: local holds allocation, true if it escapes
|
|
180
198
|
|
|
@@ -215,6 +233,22 @@ export function analyzeBody(body) {
|
|
|
215
233
|
return arrElemValTypes.get(name) || null
|
|
216
234
|
}
|
|
217
235
|
|
|
236
|
+
// Disagreement → null poison, like observeArrValType. Records the common
|
|
237
|
+
// TypedArray ctor of an array's elements.
|
|
238
|
+
const observeArrTypedCtor = (arr, ctor) => {
|
|
239
|
+
if (typeof arr !== 'string') return
|
|
240
|
+
if (arrElemTypedCtors.get(arr) === null) return
|
|
241
|
+
if (!ctor) { arrElemTypedCtors.set(arr, null); return }
|
|
242
|
+
if (!arrElemTypedCtors.has(arr)) arrElemTypedCtors.set(arr, ctor)
|
|
243
|
+
else if (arrElemTypedCtors.get(arr) !== ctor) arrElemTypedCtors.set(arr, null)
|
|
244
|
+
}
|
|
245
|
+
// The ctor a `new XxxArray(...)` element expr produces ('new.Float32Array'), if any.
|
|
246
|
+
const elemTypedCtorOf = (expr) => {
|
|
247
|
+
const c = typedElemCtor(expr)
|
|
248
|
+
// typed-array views/buffers only — exclude ArrayBuffer/DataView (no element index).
|
|
249
|
+
return c && !c.includes('ArrayBuffer') && !c.includes('DataView') ? c : null
|
|
250
|
+
}
|
|
251
|
+
|
|
218
252
|
const exprElemSourceVal = (expr) => {
|
|
219
253
|
if (typeof expr === 'string') {
|
|
220
254
|
const repVt = ctx.func.localReps?.get(expr)?.val
|
|
@@ -306,6 +340,14 @@ export function analyzeBody(body) {
|
|
|
306
340
|
if (exprElemSourceVal(elems[k]) !== common) common = null
|
|
307
341
|
}
|
|
308
342
|
if (common != null) observeArrValType(name, common)
|
|
343
|
+
// Array-of-typed-arrays literal (`[new Float32Array(n), …]`): record the
|
|
344
|
+
// common element ctor so `name[i]` is a known typed array.
|
|
345
|
+
if (common === VAL.TYPED) {
|
|
346
|
+
let ctor = elemTypedCtorOf(elems[0])
|
|
347
|
+
for (let k = 1; k < elems.length && ctor != null; k++)
|
|
348
|
+
if (elemTypedCtorOf(elems[k]) !== ctor) ctor = null
|
|
349
|
+
observeArrTypedCtor(name, ctor)
|
|
350
|
+
}
|
|
309
351
|
// Array-of-arrays literal: record the common element-of-element kind so a
|
|
310
352
|
// later `x = name[i]` binds `x`'s element type one level down.
|
|
311
353
|
if (common === VAL.ARRAY) {
|
|
@@ -319,8 +361,12 @@ export function analyzeBody(body) {
|
|
|
319
361
|
}
|
|
320
362
|
// `x = arr[i]` where `arr` is a known array-of-arrays → `x`'s elements take
|
|
321
363
|
// `arr`'s nested element kind (the missing index-step in observeArrValType).
|
|
364
|
+
// `arr` may be a function-local (arrElemElemValTypes) or a module-level const
|
|
365
|
+
// table (global rep, recorded by recordGlobalRep) — the latter dynWrite-guarded.
|
|
322
366
|
if (Array.isArray(rhs) && rhs[0] === '[]' && rhs.length === 3 && typeof rhs[1] === 'string') {
|
|
323
367
|
const nested = arrElemElemValTypes.get(rhs[1])
|
|
368
|
+
?? (!ctx.func.localReps?.has(rhs[1]) && !ctx.types?.dynWriteVars?.has(rhs[1])
|
|
369
|
+
? ctx.scope.globalReps?.get(rhs[1])?.arrayElemElemValType : null)
|
|
324
370
|
if (nested) observeArrValType(name, nested)
|
|
325
371
|
}
|
|
326
372
|
}
|
|
@@ -328,6 +374,18 @@ export function analyzeBody(body) {
|
|
|
328
374
|
const f = ctx.func.map?.get(rhs[1])
|
|
329
375
|
if (f?.arrayElemValType) observeArrValType(name, f.arrayElemValType)
|
|
330
376
|
}
|
|
377
|
+
// `Array.from(arg, () => new XxxArray(...))` — codec channelData and per-row
|
|
378
|
+
// typed-array tables. The map-callback's returned ctor is every element's type.
|
|
379
|
+
// Post-prepare AST: `['()', 'Array.from', [',', arg, callback]]` (args in a comma node).
|
|
380
|
+
if (Array.isArray(rhs) && rhs[0] === '()' && rhs[1] === 'Array.from' && Array.isArray(rhs[2])) {
|
|
381
|
+
const args = rhs[2][0] === ',' ? rhs[2].slice(1) : [rhs[2]]
|
|
382
|
+
const fn = args[1]
|
|
383
|
+
const body = Array.isArray(fn) && fn[0] === '=>' ? fn[2] : null
|
|
384
|
+
const ret = Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return'
|
|
385
|
+
? body[1][1] : body
|
|
386
|
+
const ctor = ret && elemTypedCtorOf(ret)
|
|
387
|
+
if (ctor) { observeArrValType(name, VAL.TYPED); observeArrTypedCtor(name, ctor) }
|
|
388
|
+
}
|
|
331
389
|
if (typeof rhs === 'string') {
|
|
332
390
|
const v = elemValOf(rhs)
|
|
333
391
|
if (v) observeArrValType(name, v)
|
|
@@ -456,9 +514,20 @@ export function analyzeBody(body) {
|
|
|
456
514
|
}
|
|
457
515
|
observeArrSchema(arr, exprSchemaId(a, localSchemaMap))
|
|
458
516
|
observeArrValType(arr, exprElemSourceVal(a))
|
|
517
|
+
// `ch.push(new Float32Array(m))` — track the element ctor so `ch[c][i]`
|
|
518
|
+
// inlines, same as the Array.from / array-literal forms.
|
|
519
|
+
if (exprElemSourceVal(a) === VAL.TYPED) observeArrTypedCtor(arr, elemTypedCtorOf(a))
|
|
459
520
|
}
|
|
460
521
|
}
|
|
461
522
|
|
|
523
|
+
// `ch[c] = new Float32Array(m)` — index-fill construction of a typed-array-of-
|
|
524
|
+
// arrays (`let ch = new Array(n); for(c) ch[c] = new T(m)`). Mirror push.
|
|
525
|
+
if (op === '=' && Array.isArray(node[1]) && node[1][0] === '[]' && node[1].length === 3
|
|
526
|
+
&& typeof node[1][1] === 'string' && valTypeOf(node[2]) === VAL.TYPED) {
|
|
527
|
+
observeArrValType(node[1][1], VAL.TYPED)
|
|
528
|
+
observeArrTypedCtor(node[1][1], elemTypedCtorOf(node[2]))
|
|
529
|
+
}
|
|
530
|
+
|
|
462
531
|
// `=` reassignment — locals widen, valTypes/typedElems track,
|
|
463
532
|
// arrElemSchemas/ValTypes invalidate when rhs isn't array-producing.
|
|
464
533
|
if (op === '=' && typeof node[1] === 'string') {
|
|
@@ -472,6 +541,7 @@ export function analyzeBody(body) {
|
|
|
472
541
|
trackTyped(name, rhs)
|
|
473
542
|
if (arrElemSchemas.has(name) && !isArrayProducingRhs(rhs)) observeArrSchema(name, null)
|
|
474
543
|
if (arrElemValTypes.has(name) && !isArrayProducingRhs(rhs)) observeArrValType(name, null)
|
|
544
|
+
if (arrElemTypedCtors.has(name) && !isArrayProducingRhs(rhs)) observeArrTypedCtor(name, null)
|
|
475
545
|
return
|
|
476
546
|
}
|
|
477
547
|
|
|
@@ -517,7 +587,7 @@ export function analyzeBody(body) {
|
|
|
517
587
|
const prevTypedOverlay = ctx.func.localTypedElemsOverlay
|
|
518
588
|
ctx.func.localValTypesOverlay = valTypes
|
|
519
589
|
ctx.func.localTypedElemsOverlay = typedElems
|
|
520
|
-
let unsignedLocals
|
|
590
|
+
let unsignedLocals, numericFill
|
|
521
591
|
try {
|
|
522
592
|
walk(body)
|
|
523
593
|
widenLocalTypes(body, locals)
|
|
@@ -526,6 +596,18 @@ export function analyzeBody(body) {
|
|
|
526
596
|
// reconsidered with final types — and stays f64, since a relational compare
|
|
527
597
|
// is a non-transparent read that disqualifies narrowing anyway.
|
|
528
598
|
unsignedLocals = narrowUint32(body, locals)
|
|
599
|
+
// Numeric-fill arrays — fresh `Array(n)`/`[]` whose every element write stores a
|
|
600
|
+
// Number, so `a[i]` reads can skip __to_num (the win `[1,2,3]` already gets, for the
|
|
601
|
+
// construct-then-fill kernel shape). Runs HERE, inside the val-type overlay, so a
|
|
602
|
+
// write of a bare numeric local (`a[i] = out`) resolves via the just-built `valTypes`.
|
|
603
|
+
// A bare read of the array's OWN elements (`a[i] = a[j]`, heapsort) is Numeric by
|
|
604
|
+
// induction; any genuinely non-numeric write still fails the test and disqualifies.
|
|
605
|
+
const numericFillRhs = (rhs, selfName) => {
|
|
606
|
+
if (Array.isArray(rhs) && rhs[0] === '[]' && rhs[1] === selfName) return true
|
|
607
|
+
if (typeof rhs === 'string') return valTypes.get(rhs) === VAL.NUMBER || exprElemSourceVal(rhs) === VAL.NUMBER
|
|
608
|
+
return valTypeOf(rhs) === VAL.NUMBER
|
|
609
|
+
}
|
|
610
|
+
numericFill = scanNumericFill(body, numericFillRhs)
|
|
529
611
|
} finally {
|
|
530
612
|
ctx.func.localValTypesOverlay = prevOverlay
|
|
531
613
|
ctx.func.localTypedElemsOverlay = prevTypedOverlay
|
|
@@ -545,7 +627,10 @@ export function analyzeBody(body) {
|
|
|
545
627
|
// Consumed by emitDecl, which lowers the initializer to a SLICE_BIT view.
|
|
546
628
|
const sliceViews = doSchemas ? scanSliceViews(body) : new Set()
|
|
547
629
|
|
|
548
|
-
|
|
630
|
+
// Never-relocated array bindings — reads may skip the realloc-forwarding follow.
|
|
631
|
+
const neverGrown = doSchemas ? scanNeverGrown(body) : new Set()
|
|
632
|
+
|
|
633
|
+
const result = { locals, valTypes, arrElemSchemas, arrElemValTypes, arrElemTypedCtors, typedElems, escapes, flatObjects, sliceViews, unsignedLocals, neverGrown, numericFill }
|
|
549
634
|
_bodyFactsCache.set(body, result)
|
|
550
635
|
return result
|
|
551
636
|
}
|
|
@@ -677,6 +762,18 @@ export function analyzeValTypes(body) {
|
|
|
677
762
|
for (const [name, vt] of facts.arrElemValTypes) {
|
|
678
763
|
if (vt != null) updateRep(name, { arrayElemValType: vt })
|
|
679
764
|
}
|
|
765
|
+
// Array-of-typed-arrays element ctor → rep, so `arr[i]` resolves as a typed array
|
|
766
|
+
// and `arr[i][j]` / `let o = arr[i]; o[j]` inline (codec channelData scatter).
|
|
767
|
+
for (const [name, ctor] of facts.arrElemTypedCtors) {
|
|
768
|
+
if (ctor != null) updateRep(name, { arrayElemTypedCtor: ctor })
|
|
769
|
+
}
|
|
770
|
+
// Construct-then-fill numeric arrays (`let a = Array(n); a[i] = expr`) carry no
|
|
771
|
+
// element evidence at their decl, so the walk above leaves them untyped. scanNumericFill
|
|
772
|
+
// proved every write Numeric and every other use a pure read — record NUMBER so `arr[i]`
|
|
773
|
+
// reads skip __to_num, unless an observation already poisoned the slot to a conflict.
|
|
774
|
+
for (const name of facts.numericFill || []) {
|
|
775
|
+
if (facts.arrElemValTypes.get(name) !== null) updateRep(name, { arrayElemValType: VAL.NUMBER })
|
|
776
|
+
}
|
|
680
777
|
// Propagate body-observed array-elem schemas to localReps so unboxablePtrs's
|
|
681
778
|
// `let p = arr[i]` rule (which only consults rep) sees the schema and can unbox `p`
|
|
682
779
|
// to an i32 offset. Without this, `arr.push({x,y,z})` followed by `arr[i].x` reads
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sound CSE of repeated pure typed-array element loads within a straight-line region.
|
|
3
|
+
*
|
|
4
|
+
* `re[b] = re[a] - tr; …; re[a] = re[a] + tr` — the fft butterfly loads `re[a]` twice.
|
|
5
|
+
* Cache the first load in a temp and reuse it (eliminating the redundant load) when every
|
|
6
|
+
* intervening store writes a provably-DIFFERENT element, so it cannot clobber the cached value.
|
|
7
|
+
*
|
|
8
|
+
* Soundness (no array-distinctness / non-aliasing assumption):
|
|
9
|
+
* A cached `arr[idx]` survives a store `any[idx2]` iff `idx2 ≠ idx` is PROVABLE. If the indices
|
|
10
|
+
* differ, the store hits a different element even if `any` aliases `arr`. If `idx2` might equal
|
|
11
|
+
* `idx` (incl. a different array at the same index — could alias), invalidate. Array-name-
|
|
12
|
+
* agnostic; never assumes non-aliasing. Reassigning `arr` / any var in `idx`, an impure call, or
|
|
13
|
+
* a control-flow edge also flushes. So `re[a]` (intervening stores all at index `b = a+half ≠ a`)
|
|
14
|
+
* is CSE'd, while `im[a]` (the `re[a]` store is at the same index `a`) correctly is NOT.
|
|
15
|
+
*
|
|
16
|
+
* provablyDiffer: distinct int constants, or `idx2 = idx ± P` with P provably > 0 — the canonical
|
|
17
|
+
* P is a loop bound: inside `for (j = C≥0; j < B; …)` the body runs only when `0 ≤ j < B`, so
|
|
18
|
+
* `B ≥ 1`, hence `a + B ≠ a`. A name index resolves through its single `let X = a + b` def.
|
|
19
|
+
*
|
|
20
|
+
* Runs post-analyze (purity known) and pre-emit, mutating the body. Purely conservative.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { ASSIGN_OPS } from '../ast.js'
|
|
24
|
+
|
|
25
|
+
const isArr = (x) => Array.isArray(x) // arrow, not a bare builtin alias — jz can't self-host a builtin as a first-class value
|
|
26
|
+
const isName = (x) => typeof x === 'string'
|
|
27
|
+
|
|
28
|
+
const stableIdx = (e) => {
|
|
29
|
+
if (isName(e) || typeof e === 'number') return true
|
|
30
|
+
if (!isArr(e)) return false
|
|
31
|
+
const op = e[0]
|
|
32
|
+
if (op == null) return typeof e[1] === 'number'
|
|
33
|
+
if ((op === '+' || op === '-' || op === '*') && e.length === 3) return stableIdx(e[1]) && stableIdx(e[2])
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const idxKey = (e) => {
|
|
38
|
+
if (isName(e)) return e
|
|
39
|
+
if (typeof e === 'number') return `#${e}`
|
|
40
|
+
if (isArr(e) && e[0] == null) return `#${e[1]}`
|
|
41
|
+
if (isArr(e)) return `(${e[0]} ${idxKey(e[1])} ${e[2] !== undefined ? idxKey(e[2]) : ''})`
|
|
42
|
+
return '?'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const idxVars = (e, out) => {
|
|
46
|
+
if (isName(e)) out.add(e)
|
|
47
|
+
else if (isArr(e)) for (let i = 1; i < e.length; i++) idxVars(e[i], out)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const CONTROL = new Set(['for', 'while', 'do', 'if', 'loop', 'block', 'switch', 'try', '=>',
|
|
51
|
+
'br', 'br_if', 'br_table', 'return', 'continue', 'break', 'throw', 'unreachable'])
|
|
52
|
+
const ASSIGN = new Set([...ASSIGN_OPS, '++', '--'])
|
|
53
|
+
|
|
54
|
+
function buildFacts(body) {
|
|
55
|
+
const def = new Map(), declCount = new Map(), positive = new Set()
|
|
56
|
+
const scan = (n) => {
|
|
57
|
+
if (!isArr(n)) return
|
|
58
|
+
const op = n[0]
|
|
59
|
+
if (op === 'let' || op === 'const') {
|
|
60
|
+
for (let i = 1; i < n.length; i++) {
|
|
61
|
+
const d = n[i]
|
|
62
|
+
if (isName(d)) declCount.set(d, (declCount.get(d) || 0) + 1)
|
|
63
|
+
else if (isArr(d) && d[0] === '=' && isName(d[1])) {
|
|
64
|
+
declCount.set(d[1], (declCount.get(d[1]) || 0) + 1)
|
|
65
|
+
def.set(d[1], d[2])
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (op === 'for') {
|
|
70
|
+
// parse shape ['for', [';', init, cond, step], body]; post-prepare ['for', init, cond, step, body].
|
|
71
|
+
const hdr2 = isArr(n[1]) && n[1][0] === ';'
|
|
72
|
+
const init = hdr2 ? n[1][1] : n[1]
|
|
73
|
+
const cond = hdr2 ? n[1][2] : n[2]
|
|
74
|
+
const lo = isArr(init) && (init[0] === 'let' || init[0] === 'const') && isArr(init[1]) && init[1][0] === '=' ? init[1][2]
|
|
75
|
+
: (isArr(init) && init[0] === '=' ? init[2] : null)
|
|
76
|
+
const loZero = (typeof lo === 'number' && lo >= 0) || (isArr(lo) && lo[0] == null && lo[1] >= 0)
|
|
77
|
+
// `for (j = 0; j < BOUND; …)` ⇒ inside the body BOUND ≥ 1 (j ≥ 0 ∧ j < BOUND). Mark BOUND positive.
|
|
78
|
+
if (loZero && isArr(cond) && (cond[0] === '<' || cond[0] === '<=') && isName(cond[2])) positive.add(cond[2])
|
|
79
|
+
}
|
|
80
|
+
for (let i = 1; i < n.length; i++) scan(n[i])
|
|
81
|
+
}
|
|
82
|
+
scan(body)
|
|
83
|
+
for (const [n, c] of declCount) if (c > 1) def.delete(n) // reassigned ⇒ not a stable def
|
|
84
|
+
return { def, positive }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const cval = (e) => typeof e === 'number' ? e : (isArr(e) && e[0] == null ? e[1] : null)
|
|
88
|
+
|
|
89
|
+
const isPositive = (e, F) => {
|
|
90
|
+
const c = cval(e); if (c != null) return c > 0
|
|
91
|
+
if (isName(e)) return F.positive.has(e)
|
|
92
|
+
if (isArr(e) && (e[0] === '+' || e[0] === '*') && e.length === 3) return isPositive(e[1], F) && isPositive(e[2], F)
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const asBasePlus = (e, F) => {
|
|
97
|
+
if (isName(e) && F.def.has(e)) e = F.def.get(e)
|
|
98
|
+
if (isArr(e) && e[0] === '+' && e.length === 3) return { base: e[1], off: e[2] }
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function provablyDiffer(idx, idx2, F) {
|
|
103
|
+
const ka = idxKey(idx), kb = idxKey(idx2)
|
|
104
|
+
if (ka === kb) return false
|
|
105
|
+
const va = cval(idx), vb = cval(idx2)
|
|
106
|
+
if (va != null && vb != null) return va !== vb
|
|
107
|
+
const bp2 = asBasePlus(idx2, F); if (bp2 && idxKey(bp2.base) === ka && isPositive(bp2.off, F)) return true
|
|
108
|
+
const bp1 = asBasePlus(idx, F); if (bp1 && idxKey(bp1.base) === kb && isPositive(bp1.off, F)) return true
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param body function-body AST (mutated in place)
|
|
114
|
+
* @param isTypedArray (name) => boolean — receiver is a pure typed-array load
|
|
115
|
+
* @param freshName () => string — unique temp local name
|
|
116
|
+
* @returns number of loads eliminated
|
|
117
|
+
*/
|
|
118
|
+
export function cseLoads(body, isTypedArray, freshName) {
|
|
119
|
+
if (!isArr(body)) return 0
|
|
120
|
+
const F = buildFacts(body)
|
|
121
|
+
let eliminated = 0
|
|
122
|
+
|
|
123
|
+
// Process the statement list of a `[';', …]` node (children [1..]).
|
|
124
|
+
const runSeq = (seq) => {
|
|
125
|
+
// key → { arr, idxNode, idxVars, temp, firstParent, firstIdx, firstStmt }
|
|
126
|
+
const avail = new Map()
|
|
127
|
+
const inserts = [] // { at: stmtIdx, binding }
|
|
128
|
+
|
|
129
|
+
const flush = () => avail.clear()
|
|
130
|
+
const invalidateVar = (name) => { for (const [k, e] of avail) if (e.arr === name || e.idxVars.has(name)) avail.delete(k) }
|
|
131
|
+
|
|
132
|
+
const reads = (node, parent, pi, si) => {
|
|
133
|
+
if (!isArr(node) || node[0] === 'str') return
|
|
134
|
+
// Element/member assignment targets must stay targets. Plain `=` does not
|
|
135
|
+
// read the slot; compound/update ops do, but rewriting the LHS node itself
|
|
136
|
+
// turns the eventual store into a temp assignment. Only inspect receiver /
|
|
137
|
+
// index subexpressions here, then let the RHS participate in load CSE.
|
|
138
|
+
if (ASSIGN.has(node[0]) && isArr(node[1]) && (node[1][0] === '[]' || node[1][0] === '.' || node[1][0] === '?.')) {
|
|
139
|
+
const lhs = node[1]
|
|
140
|
+
reads(lhs[0] === '[]' ? lhs[2] : lhs[1], lhs, lhs[0] === '[]' ? 2 : 1, si) // index / receiver
|
|
141
|
+
for (let i = 2; i < node.length; i++) reads(node[i], node, i, si) // rhs value
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if (node[0] === '[]' && isName(node[1]) && isTypedArray(node[1]) && stableIdx(node[2])) {
|
|
145
|
+
const arr = node[1], key = `${arr}|${idxKey(node[2])}`
|
|
146
|
+
const e = avail.get(key)
|
|
147
|
+
if (e) {
|
|
148
|
+
if (e.temp === null) {
|
|
149
|
+
e.temp = freshName()
|
|
150
|
+
inserts.push({ at: e.firstStmt, binding: ['let', ['=', e.temp, ['[]', arr, e.idxNode]]] })
|
|
151
|
+
e.firstParent[e.firstIdx] = e.temp // rewrite the 1st occurrence to read the temp
|
|
152
|
+
}
|
|
153
|
+
parent[pi] = e.temp // rewrite this (2nd+) occurrence
|
|
154
|
+
eliminated++
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
const vars = new Set(); idxVars(node[2], vars)
|
|
158
|
+
avail.set(key, { arr, idxNode: node[2], idxVars: vars, temp: null, firstParent: parent, firstIdx: pi, firstStmt: si })
|
|
159
|
+
return // don't descend into a stable index
|
|
160
|
+
}
|
|
161
|
+
if (node[0] === '()' || node[0] === 'call') { flush(); for (let i = 1; i < node.length; i++) reads(node[i], node, i, si); return }
|
|
162
|
+
for (let i = 1; i < node.length; i++) reads(node[i], node, i, si)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const writes = (node) => {
|
|
166
|
+
if (!isArr(node)) return
|
|
167
|
+
const op = node[0]
|
|
168
|
+
if (ASSIGN.has(op)) {
|
|
169
|
+
const lhs = node[1]
|
|
170
|
+
if (isName(lhs)) invalidateVar(lhs)
|
|
171
|
+
else if (isArr(lhs) && lhs[0] === '[]') {
|
|
172
|
+
const idx2 = lhs[2]
|
|
173
|
+
for (const [k, e] of [...avail]) if (!provablyDiffer(e.idxNode, idx2, F)) avail.delete(k)
|
|
174
|
+
}
|
|
175
|
+
for (let i = 2; i < node.length; i++) writes(node[i])
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
for (let i = 1; i < node.length; i++) writes(node[i])
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (let si = 1; si < seq.length; si++) {
|
|
182
|
+
const s = seq[si]
|
|
183
|
+
if (!isArr(s)) continue
|
|
184
|
+
if (CONTROL.has(s[0])) { flush(); continue } // nesting handled by the outer `descend`
|
|
185
|
+
reads(s, seq, si, si)
|
|
186
|
+
writes(s)
|
|
187
|
+
}
|
|
188
|
+
inserts.sort((a, b) => b.at - a.at)
|
|
189
|
+
for (const ins of inserts) seq.splice(ins.at, 0, ins.binding)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Walk to every `[';', …]` sequence; run CSE on each, then recurse into its (now-rewritten) stmts.
|
|
193
|
+
const descend = (node) => {
|
|
194
|
+
if (!isArr(node)) return
|
|
195
|
+
if (node[0] === ';') runSeq(node)
|
|
196
|
+
for (let i = 1; i < node.length; i++) descend(node[i])
|
|
197
|
+
}
|
|
198
|
+
descend(body)
|
|
199
|
+
return eliminated
|
|
200
|
+
}
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* @module compile/emit-assign
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { ctx, err, inc, PTR } from '../ctx.js'
|
|
13
|
+
import { ctx, err, inc, warnDeopt, PTR } from '../ctx.js'
|
|
14
14
|
import { T } from '../ast.js'
|
|
15
|
-
import { staticPropertyKey } from '../static.js'
|
|
15
|
+
import { staticPropertyKey, staticIndexKey } from '../static.js'
|
|
16
16
|
import { valTypeOf, shapeOf } from '../kind.js'
|
|
17
17
|
import { VAL, lookupValType, repOf } from '../reps.js'
|
|
18
18
|
import {
|
|
@@ -74,8 +74,9 @@ function storeArrayPayload(arrExpr, idxNode, valueExpr, persist) {
|
|
|
74
74
|
/** Strict-mode guard for dynamic property writes — emitted in branches that
|
|
75
75
|
* fall through to `__dyn_set` or its key-kind dispatch. */
|
|
76
76
|
function ensureDynSetAllowed(arr) {
|
|
77
|
-
if (!ctx.transform.strict) return
|
|
78
77
|
const arrLabel = typeof arr === 'string' ? arr : '<expr>'
|
|
78
|
+
warnDeopt('deopt-dyn-write', `dynamic property write \`${arrLabel}[…] = …\` couldn't resolve a static type — it falls back to a runtime hash store (~2× slower than a typed/slot write, far worse in a hot loop). Use a literal key, a numeric typed-array index, or a Map for genuinely dynamic keys.`)
|
|
79
|
+
if (!ctx.transform.strict) return
|
|
79
80
|
err(`strict mode: dynamic property assignment \`${arrLabel}[<expr>] = ...\` falls back to __dyn_set. Use a literal key or known array/typed-array numeric index, or pass { strict: false }.`)
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -98,26 +99,54 @@ function dispatchByKeyKind(arr, keyExpr, valueExpr, numericIR) {
|
|
|
98
99
|
['else', numericIR(['local.get', `$${keyTmp}`])]])
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
/** Raw indexed f64.store at `ptrOffset(o)+idx*8` — the lean fallback for a receiver
|
|
103
|
+
* proven to be ARRAY/TYPED at runtime (the ARRAY/TYPED forks are taken first).
|
|
104
|
+
* Operands are pre-set locals: `$obj` f64, `$idx` i32, `$val` f64. */
|
|
105
|
+
const rawIndexedStore = (obj, idx, val, arrVT) => ['block', ['result', 'f64'],
|
|
106
|
+
['f64.store', ['i32.add', ptrOffsetIR(['local.get', `$${obj}`], arrVT), ['i32.shl', ['local.get', `$${idx}`], ['i32.const', 3]]], ['local.get', `$${val}`]],
|
|
107
|
+
['local.get', `$${val}`]]
|
|
108
|
+
|
|
109
|
+
/** Numeric element store for a receiver that may be OBJECT/HASH at runtime: those
|
|
110
|
+
* keep dynamic indexed props in the propsPtr HASH sidecar at off-16 (paired with
|
|
111
|
+
* __dyn_get); a raw store at ptrOffset(o)+i*8 lands in the schema-slot region —
|
|
112
|
+
* silent corruption at small i, an OOB trap at large i (the self-host `blur`
|
|
113
|
+
* crash). The propsPtr hash is STRING-keyed (object keys are strings: `o[3]` ≡
|
|
114
|
+
* `o["3"]`), so the index is rendered to its string form — the same string the
|
|
115
|
+
* __dyn_get read produces, content-compared by the hash. ARRAY/TYPED fall to the
|
|
116
|
+
* raw store. Caller must `inc('__dyn_set','__i32_to_str')`. Gated by `mayBeObject`
|
|
117
|
+
* so pure typed-array programs (an f64 param indexed in a hot loop) keep the lean
|
|
118
|
+
* raw store and never drag in __dyn_set / __i32_to_str. */
|
|
119
|
+
const objHashOrRawStore = (obj, idx, val, arrVT) => ['if', ['result', 'f64'],
|
|
120
|
+
['i32.or', ptrTypeEq(['local.get', `$${obj}`], PTR.OBJECT), ptrTypeEq(['local.get', `$${obj}`], PTR.HASH)],
|
|
121
|
+
['then', ['block', ['result', 'f64'],
|
|
122
|
+
['drop', ['call', '$__dyn_set',
|
|
123
|
+
['i64.reinterpret_f64', ['local.get', `$${obj}`]],
|
|
124
|
+
['i64.reinterpret_f64', ['call', '$__i32_to_str', ['local.get', `$${idx}`]]],
|
|
125
|
+
['i64.reinterpret_f64', ['local.get', `$${val}`]]]],
|
|
126
|
+
['local.get', `$${val}`]]],
|
|
127
|
+
['else', rawIndexedStore(obj, idx, val, arrVT)]]
|
|
128
|
+
|
|
101
129
|
/** Build a `__ptr_type`-fork IR for `arr[idx] = val` when receiver is opaque
|
|
102
130
|
* (non-string expr, or string-named binding of unknown VAL). Forks on
|
|
103
131
|
* ARRAY → `__arr_set_idx_ptr` (+ optional persist), TYPED → `__typed_set_idx`,
|
|
104
|
-
*
|
|
105
|
-
function emitPolymorphicElementStore(arrExpr, idxI32, valueExpr, arrVT, persist) {
|
|
132
|
+
* OBJECT/HASH → `__dyn_set` (only when `mayBeObject`), else → raw f64.store. */
|
|
133
|
+
function emitPolymorphicElementStore(arrExpr, idxI32, valueExpr, arrVT, persist, mayBeObject) {
|
|
106
134
|
const objTmp = temp('asu')
|
|
107
135
|
const idxTmp = tempI32('asi')
|
|
108
136
|
const ptrTmp = temp('asp')
|
|
109
137
|
const valTmp = temp()
|
|
110
138
|
const hasTypedSet = !!ctx.core.stdlib['__typed_set_idx']
|
|
111
139
|
inc('__ptr_type', '__arr_set_idx_ptr')
|
|
140
|
+
if (mayBeObject) inc('__dyn_set', '__i32_to_str')
|
|
112
141
|
if (hasTypedSet) inc('__typed_set_idx')
|
|
113
142
|
const arrSetCall = ['call', '$__arr_set_idx_ptr', ['i64.reinterpret_f64', ['local.get', `$${objTmp}`]], ['local.get', `$${idxTmp}`], ['local.get', `$${valTmp}`]]
|
|
114
143
|
const arrayBranch = ['block', ['result', 'f64'],
|
|
115
144
|
['local.set', `$${ptrTmp}`, arrSetCall],
|
|
116
145
|
...(persist ? [persist(['local.get', `$${ptrTmp}`])] : []),
|
|
117
146
|
['local.get', `$${valTmp}`]]
|
|
118
|
-
const fallbackStore =
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
const fallbackStore = mayBeObject
|
|
148
|
+
? objHashOrRawStore(objTmp, idxTmp, valTmp, arrVT)
|
|
149
|
+
: rawIndexedStore(objTmp, idxTmp, valTmp, arrVT)
|
|
121
150
|
const elseBranch = hasTypedSet
|
|
122
151
|
? ['if', ['result', 'f64'],
|
|
123
152
|
ptrTypeEq(['local.get', `$${objTmp}`], PTR.TYPED),
|
|
@@ -159,10 +188,12 @@ export function emitElementAssign(arr, idx, val) {
|
|
|
159
188
|
: typeof arr === 'string' && lookupValType(arr) === VAL.OBJECT ? staticPropertyKey(idx)
|
|
160
189
|
: null
|
|
161
190
|
|
|
162
|
-
// 1. SRoA flat object: `o['k'] = x` → `local.set $o#i` (no heap
|
|
163
|
-
|
|
191
|
+
// 1. SRoA flat object/array: `o['k'] = x` / `a[2] = x` → `local.set $o#i` (no heap
|
|
192
|
+
// store). A bare integer index resolves its slot key here (not via `litKey`).
|
|
193
|
+
if (typeof arr === 'string' && ctx.func.flatObjects?.has(arr)) {
|
|
164
194
|
const fo = ctx.func.flatObjects.get(arr)
|
|
165
|
-
const
|
|
195
|
+
const flatKey = litKey != null ? litKey : staticIndexKey(idx)
|
|
196
|
+
const fi = flatKey != null ? fo.names.indexOf(flatKey) : -1
|
|
166
197
|
if (fi >= 0) return withTemp(valueExpr, t => [
|
|
167
198
|
['local.set', `$${arr}#${fi}`, ['local.get', `$${t}`]],
|
|
168
199
|
['local.get', `$${t}`]])
|
|
@@ -183,8 +214,13 @@ export function emitElementAssign(arr, idx, val) {
|
|
|
183
214
|
if (keyType === VAL.STRING) return dynSetCall(arr, keyExpr, valueExpr)
|
|
184
215
|
|
|
185
216
|
// 5. Typed-array receiver → __typed_set_idx (or per-ctor element write).
|
|
186
|
-
|
|
187
|
-
|
|
217
|
+
// Also fires for a nested `arr[c]` receiver whose array's elements are typed
|
|
218
|
+
// arrays of a known ctor (codec `ch[c][i] = …` channelData scatter) — the
|
|
219
|
+
// `.typed:[]=` emitter resolves the element ctor and inlines the store.
|
|
220
|
+
const nestedElemTypedCtor = Array.isArray(arr) && arr[0] === '[]' && arr.length === 3 &&
|
|
221
|
+
typeof arr[1] === 'string' ? repOf(arr[1])?.arrayElemTypedCtor : null
|
|
222
|
+
if (ctx.core.emit['.typed:[]='] &&
|
|
223
|
+
((typeof arr === 'string' && lookupValType(arr) === 'typed') || nestedElemTypedCtor)) {
|
|
188
224
|
const r = ctx.core.emit['.typed:[]=']?.(arr, idx, val, void_)
|
|
189
225
|
if (r) return r
|
|
190
226
|
// Element ctor unknown — runtime aux-byte dispatch. __typed_set_idx
|
|
@@ -221,6 +257,28 @@ export function emitElementAssign(arr, idx, val) {
|
|
|
221
257
|
const knownArrVT = typeof arr === 'string' ? lookupValType(arr) : null
|
|
222
258
|
const arrVT = knownArrVT || VAL.OBJECT
|
|
223
259
|
|
|
260
|
+
// 7b. Known-OBJECT receiver with a non-static key. The schema-slot (step 2) and
|
|
261
|
+
// string-key (step 4) fast paths already returned, so the key here is a numeric
|
|
262
|
+
// or runtime-unknown expression. A raw indexed f64.store (steps 8–9 / the default
|
|
263
|
+
// below) would compute `ptrOffset(o) + i*8` into an allocation sized for schema
|
|
264
|
+
// slots only — silent slot corruption at small i, an out-of-bounds memory trap at
|
|
265
|
+
// large i. Route to __dyn_set (the per-OBJECT propsPtr hash sidecar), mirroring
|
|
266
|
+
// emitPropertyAssign's OBJECT dot-write path; __dyn_get reads it back. This closes
|
|
267
|
+
// the `o.prop=v` vs `o[expr]=v` asymmetry that faulted the self-host.
|
|
268
|
+
if (knownArrVT === VAL.OBJECT) return dynSetCall(arr, keyExpr, valueExpr)
|
|
269
|
+
|
|
270
|
+
// A receiver "may be an OBJECT/HASH at runtime" unless the analyzer has proven it
|
|
271
|
+
// is an indexable array/typed candidate (`rep.notString`, set by infer.js for
|
|
272
|
+
// bindings used as `x[i]` array/typed receivers — e.g. a Float64Array param `buf`).
|
|
273
|
+
// Those keep the lean raw-store path and never drag in __dyn_set / __i32_to_str
|
|
274
|
+
// (which carry their own rehash/itoa loops — a real size + hot-loop-ratchet cost).
|
|
275
|
+
// Everything else (an object-literal local `let o = {}` — the Root A′ case — or a
|
|
276
|
+
// fully-opaque expression) gets the OBJECT-safe fork that routes to the propsPtr
|
|
277
|
+
// sidecar instead of an out-of-bounds raw f64.store.
|
|
278
|
+
const mayBeObject = typeof arr !== 'string' || repOf(arr)?.notString !== true
|
|
279
|
+
// OBJECT-safe numeric store when the receiver may be an object, else the lean raw store.
|
|
280
|
+
const numStore = (o, i, v) => mayBeObject ? objHashOrRawStore(o, i, v, arrVT) : rawIndexedStore(o, i, v, arrVT)
|
|
281
|
+
|
|
224
282
|
// 8. Polymorphic + runtime key dispatch — key kind unknown AND receiver shape
|
|
225
283
|
// possibly TypedArray (or fully opaque). Numeric branch forks on __ptr_type.
|
|
226
284
|
// Deliberately a 2-fork (TYPED vs else) rather than reusing
|
|
@@ -235,6 +293,7 @@ export function emitElementAssign(arr, idx, val) {
|
|
|
235
293
|
const idxTmp = tempI32('asi')
|
|
236
294
|
const valTmp = temp()
|
|
237
295
|
inc('__ptr_type', '__typed_set_idx')
|
|
296
|
+
if (mayBeObject) inc('__i32_to_str')
|
|
238
297
|
// When arr type is unknown (could be TypedArray) and __typed_set_idx is
|
|
239
298
|
// available, dispatch the numeric branch through __ptr_type so TypedArray
|
|
240
299
|
// writes go by element type. Without this, ternary-typed arrays (e.g.
|
|
@@ -247,23 +306,26 @@ export function emitElementAssign(arr, idx, val) {
|
|
|
247
306
|
['if', ['result', 'f64'],
|
|
248
307
|
ptrTypeEq(['local.get', `$${objTmp}`], PTR.TYPED),
|
|
249
308
|
['then', ['call', '$__typed_set_idx', ['i64.reinterpret_f64', ['local.get', `$${objTmp}`]], ['local.get', `$${idxTmp}`], ['local.get', `$${valTmp}`]]],
|
|
250
|
-
['else',
|
|
251
|
-
['f64.store', ['i32.add', ptrOffsetIR(['local.get', `$${objTmp}`], arrVT), ['i32.shl', ['local.get', `$${idxTmp}`], ['i32.const', 3]]], ['local.get', `$${valTmp}`]],
|
|
252
|
-
['local.get', `$${valTmp}`]]]]])
|
|
309
|
+
['else', numStore(objTmp, idxTmp, valTmp)]]])
|
|
253
310
|
}
|
|
311
|
+
const objTmpB = temp('asu')
|
|
312
|
+
const idxTmpB = tempI32('asi')
|
|
254
313
|
const valTmp = temp()
|
|
314
|
+
inc('__ptr_type')
|
|
315
|
+
if (mayBeObject) inc('__i32_to_str')
|
|
255
316
|
return dispatchByKeyKind(arr, keyExpr, valueExpr, keyNode => ['block', ['result', 'f64'],
|
|
317
|
+
['local.set', `$${objTmpB}`, asF64(emit(arr))],
|
|
318
|
+
['local.set', `$${idxTmpB}`, asI32(typed(keyNode, 'f64'))],
|
|
256
319
|
['local.set', `$${valTmp}`, valueExpr],
|
|
257
|
-
|
|
258
|
-
['local.get', `$${valTmp}`]])
|
|
320
|
+
numStore(objTmpB, idxTmpB, valTmp)])
|
|
259
321
|
}
|
|
260
322
|
|
|
261
323
|
// 9. Opaque receiver (non-string expr) or string-named with unknown VT — pure
|
|
262
324
|
// __ptr_type dispatch (no key-kind fork: key is provably numeric here).
|
|
263
325
|
if (typeof arr !== 'string')
|
|
264
|
-
return emitPolymorphicElementStore(emit(arr), asI32(emit(idx)), valueExpr, arrVT, null)
|
|
326
|
+
return emitPolymorphicElementStore(emit(arr), asI32(emit(idx)), valueExpr, arrVT, null, mayBeObject)
|
|
265
327
|
if (knownArrVT == null)
|
|
266
|
-
return emitPolymorphicElementStore(emit(arr), asI32(emit(idx)), valueExpr, arrVT, persistBinding(arr))
|
|
328
|
+
return emitPolymorphicElementStore(emit(arr), asI32(emit(idx)), valueExpr, arrVT, persistBinding(arr), mayBeObject)
|
|
267
329
|
|
|
268
330
|
// Default: known-VT receiver that isn't ARRAY/TYPED/OBJECT special — raw f64.store.
|
|
269
331
|
return withTemp(valueExpr, t => [
|