jz 0.9.0 → 0.9.2
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 +19 -7
- package/bench/bench.svg +18 -18
- package/dist/interop.js +1 -1
- package/dist/jz.js +2026 -1429
- package/interop.js +73 -6
- package/jzify/index.js +7 -1
- package/jzify/transform.js +21 -2
- package/jzify/webrt.js +145 -0
- package/layout.js +13 -3
- package/module/array.js +49 -42
- package/module/collection.js +222 -111
- package/module/console.js +4 -0
- package/module/core.js +86 -7
- package/module/crypto.js +124 -0
- package/module/index.js +3 -1
- package/module/math.js +5 -0
- package/module/navigator.js +26 -0
- package/module/schema.js +11 -3
- package/module/string.js +433 -28
- package/module/timer.js +42 -1
- package/module/typedarray.js +370 -60
- package/package.json +3 -3
- package/src/abi/array.js +24 -16
- package/src/abi/index.js +2 -2
- package/src/abi/object.js +17 -0
- package/src/ast.js +53 -0
- package/src/autoload.js +19 -3
- package/src/compile/analyze.js +190 -21
- package/src/compile/emit-assign.js +111 -7
- package/src/compile/emit.js +84 -20
- package/src/compile/index.js +37 -4
- package/src/compile/inplace-store.js +10 -9
- package/src/compile/narrow.js +71 -1
- package/src/compile/plan/index.js +5 -0
- package/src/compile/plan/literals.js +11 -2
- package/src/ctx.js +14 -0
- package/src/ir.js +26 -1
- package/src/optimize/index.js +1 -0
- package/src/optimize/vectorize.js +80 -6
- package/src/prepare/index.js +6 -0
- package/src/static.js +31 -0
- package/src/type.js +356 -47
package/src/type.js
CHANGED
|
@@ -26,8 +26,8 @@ import { typedElemAux } from '../layout.js'
|
|
|
26
26
|
* NOT be mistaken for a typed-array construction (else its global misdispatches as
|
|
27
27
|
* a TypedArray, e.g. `map.set(k,v)` lowering to `arr.set(src,offset)`). */
|
|
28
28
|
const TYPED_FAMILY_CTORS = new Set([
|
|
29
|
-
'Int8Array', 'Uint8Array', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array',
|
|
30
|
-
'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array', 'ArrayBuffer', 'DataView',
|
|
29
|
+
'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array',
|
|
30
|
+
'Float16Array', 'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array', 'ArrayBuffer', 'DataView',
|
|
31
31
|
])
|
|
32
32
|
|
|
33
33
|
/** Extract typed-array ctor name ('new.Float32Array', 'new.Int8Array.view', etc) from RHS,
|
|
@@ -208,7 +208,10 @@ export function affineIdxOfIV(idx, iv, body, env) {
|
|
|
208
208
|
return invariantIdxExpr(e, iv, body, env) ? { a: 0, slots: [{ k: 1, e }], bConst: 0 } : null
|
|
209
209
|
}
|
|
210
210
|
const r = aff(idx)
|
|
211
|
-
|
|
211
|
+
// Negative iv-coefficients are admitted (the mirror index `N−k` of symmetric
|
|
212
|
+
// fills): the guard emitter picks extremes by the SIGN of a — a·iv is maximal
|
|
213
|
+
// at maxIv for a ≥ 0 but at ENTRY for a < 0, and minimal at the other end.
|
|
214
|
+
return r && Number.isInteger(r.a) && Number.isInteger(r.bConst)
|
|
212
215
|
&& r.slots.every(t => Number.isInteger(t.k)) ? r : null
|
|
213
216
|
}
|
|
214
217
|
|
|
@@ -268,9 +271,24 @@ export function bodyAffineEnv(body, iv) {
|
|
|
268
271
|
* DROPPED (its first iterations are genuinely OOB — the checked form is the
|
|
269
272
|
* semantics, a guard would just always fail). */
|
|
270
273
|
export function versionableTypedFor(init, cond, step, body, locals, entryHint = null) {
|
|
271
|
-
|
|
274
|
+
// `&&`-cond whiles (`while (len < max && src[j+len] === src[ip+len]) len++`
|
|
275
|
+
// — the LZ match scan): the countable bound must be the LEFTMOST conjunct.
|
|
276
|
+
// Every later conjunct short-circuits AFTER it, so its accesses run only at
|
|
277
|
+
// iv < bound (exactly the pre-increment extent), and a false conjunct only
|
|
278
|
+
// exits the loop EARLY — the iv range never grows. The rest conjuncts ride
|
|
279
|
+
// into both arms verbatim; their typed accesses are candidates (scanned
|
|
280
|
+
// after the body so a same-key BODY access — possibly post-increment, wider
|
|
281
|
+
// — registers its extent first).
|
|
282
|
+
let condRest = null
|
|
283
|
+
let c = cond
|
|
284
|
+
while (Array.isArray(c) && c[0] === '&&' && Array.isArray(c[1])) {
|
|
285
|
+
condRest = condRest == null ? c[2] : ['&&', c[2], condRest] // scan-only bag
|
|
286
|
+
c = c[1]
|
|
287
|
+
}
|
|
288
|
+
if (!Array.isArray(c) || (c[0] !== '<' && c[0] !== '<=') || typeof c[1] !== 'string') return null
|
|
272
289
|
if (containsNestedClosure(body)) return null
|
|
273
|
-
|
|
290
|
+
if (condRest != null && containsNestedClosure(condRest)) return null
|
|
291
|
+
const iv = c[1], incl = c[0] === '<='
|
|
274
292
|
if (redeclaresName(body, iv)) return null
|
|
275
293
|
// iv start: a static init decl (`for (let i = 0; …)`) folds the lo conjunct;
|
|
276
294
|
// otherwise (while-shapes: `let i = 0; while (i < n) …`) the guard reads the
|
|
@@ -348,7 +366,7 @@ export function versionableTypedFor(init, cond, step, body, locals, entryHint =
|
|
|
348
366
|
if (L == null || L < 1 || !Number.isInteger(L)) return null
|
|
349
367
|
bump = L
|
|
350
368
|
} else return null
|
|
351
|
-
const bound =
|
|
369
|
+
const bound = c[2]
|
|
352
370
|
// bKind drives the guard's conversion to a max-iv i64:
|
|
353
371
|
// 'i32' — literal, i32-machine name, or a typed receiver's .length: exact extend;
|
|
354
372
|
// 'f64' — any other stable name (an untyped param, a NaN-boxed unknown): the
|
|
@@ -370,6 +388,24 @@ export function versionableTypedFor(init, cond, step, body, locals, entryHint =
|
|
|
370
388
|
if (inds) for (const nm of inds.keys()) env.set(nm, null)
|
|
371
389
|
const cands = []
|
|
372
390
|
const seen = new Set()
|
|
391
|
+
// A body-advanced iv (bump > 0) exceeds bound−1 only AFTER its increment
|
|
392
|
+
// runs — accesses in top-level statements strictly BEFORE the write see
|
|
393
|
+
// iv ≤ bound−1 and need no widening. The canonical tail-increment while
|
|
394
|
+
// (`…reads…; k++`) then guards exactly; only genuinely post-increment
|
|
395
|
+
// accesses widen. Nested/mid-expression writes keep everything `post`.
|
|
396
|
+
let ivWriteAt = -1
|
|
397
|
+
const seqBody = Array.isArray(body) && (body[0] === '{}' || body[0] === ';')
|
|
398
|
+
if (bump > 0 && seqBody) {
|
|
399
|
+
for (let s = 1; s < body.length; s++) {
|
|
400
|
+
const st = body[s]
|
|
401
|
+
if (Array.isArray(st) && ((st[0] === '=' || st[0] === '+=') && st[1] === iv || (st[0] === '++' || st[0] === '--') && st[1] === iv)) { ivWriteAt = s; break }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
let scanTop = -1 // current top-level statement index during scan
|
|
405
|
+
// cond-rest accesses are exactly pre-increment: short-circuit order proves
|
|
406
|
+
// they evaluate only when `iv < bound` already held this iteration
|
|
407
|
+
let forcePre = false
|
|
408
|
+
const isPost = () => !forcePre && bump > 0 && (ivWriteAt === -1 || scanTop === -1 || scanTop >= ivWriteAt)
|
|
373
409
|
const scan = (n) => {
|
|
374
410
|
if (!Array.isArray(n)) return
|
|
375
411
|
if (n[0] === '[]' && n.length === 3 && typeof n[1] === 'string' && n[1] !== iv
|
|
@@ -390,7 +426,7 @@ export function versionableTypedFor(init, cond, step, body, locals, entryHint =
|
|
|
390
426
|
&& !(aff.slots.length === 0 && startC != null && aff.a * startC + aff.bConst < 0)) {
|
|
391
427
|
seen.add(key)
|
|
392
428
|
const slots = aff.slots.map(t => ({ ...t, kind: exprType(t.e, locals) === 'i32' ? 'i32' : 'f64' }))
|
|
393
|
-
cands.push({ recv: n[1], idx: n[2], a: aff.a, slots, bConst: aff.bConst })
|
|
429
|
+
cands.push({ recv: n[1], idx: n[2], a: aff.a, slots, bConst: aff.bConst, post: isPost() })
|
|
394
430
|
} else {
|
|
395
431
|
// LAST resort — beyond the affine model (masked ring cursors, wrap
|
|
396
432
|
// idioms): an interval HULL the static walk bounded but couldn't
|
|
@@ -407,8 +443,16 @@ export function versionableTypedFor(init, cond, step, body, locals, entryHint =
|
|
|
407
443
|
}
|
|
408
444
|
for (let k = 1; k < n.length; k++) scan(n[k])
|
|
409
445
|
}
|
|
410
|
-
|
|
411
|
-
|
|
446
|
+
if (seqBody) {
|
|
447
|
+
for (let s = 1; s < body.length; s++) { scanTop = s; scan(body[s]) }
|
|
448
|
+
scanTop = -1
|
|
449
|
+
} else scan(body)
|
|
450
|
+
// `&&`-cond rest conjuncts — scanned AFTER the body so a shared-key body
|
|
451
|
+
// access (potentially post-increment, wider extent) wins the seen-set
|
|
452
|
+
if (condRest != null) { forcePre = true; scan(condRest); forcePre = false }
|
|
453
|
+
// typeof-process guard, not globalThis.process — a bare `globalThis` read
|
|
454
|
+
// compiles to an env.globalThis import in the self-host build; typeof folds dead
|
|
455
|
+
if (typeof process !== 'undefined' && process.env.JZ_DBG_VS) console.error('VS', iv, 'cands', cands.length, 'bump', bump, 'ivWriteAt', ivWriteAt, 'body0', Array.isArray(body) ? body[0] : typeof body, cands.slice(0,4).map(c => c.recv + (c.range ? ':hull' : c.ind ? ':ind' : ':aff') + (c.post ? ':POST' : '')).join(' '))
|
|
412
456
|
return cands.length
|
|
413
457
|
? { iv, ivKind: exprType(iv, locals) === 'i32' ? 'i32' : 'f64', startC, bump, bound, bKind, incl, stepBy, cands }
|
|
414
458
|
: null
|
|
@@ -426,6 +470,14 @@ export function versionableTypedFor(init, cond, step, body, locals, entryHint =
|
|
|
426
470
|
* top body (redeclaresName catches inner decls — a per-row offset slot must
|
|
427
471
|
* not be read before its row exists). Unliftable levels simply keep their own
|
|
428
472
|
* inner versioning during arm emission — graceful degradation, not a bail. */
|
|
473
|
+
/** The countable-iv name of a (possibly `&&`-chained) loop cond — the leftmost
|
|
474
|
+
* conjunct's lhs. Feeds the sibling-decl entryHint lookup for while-shapes. */
|
|
475
|
+
const condIvName = (cnd) => {
|
|
476
|
+
let c = cnd
|
|
477
|
+
while (Array.isArray(c) && c[0] === '&&' && Array.isArray(c[1])) c = c[1]
|
|
478
|
+
return Array.isArray(c) && (c[0] === '<' || c[0] === '<=') && typeof c[1] === 'string' ? c[1] : null
|
|
479
|
+
}
|
|
480
|
+
|
|
429
481
|
export function versionableTypedNest(init, cond, step, body, locals) {
|
|
430
482
|
if (containsNestedClosure(body)) return null
|
|
431
483
|
const levels = []
|
|
@@ -478,8 +530,8 @@ export function versionableTypedNest(init, cond, step, body, locals) {
|
|
|
478
530
|
continue
|
|
479
531
|
}
|
|
480
532
|
if (Array.isArray(st) && st[0] === 'while' && st.length === 3
|
|
481
|
-
&& Array.isArray(st[1]) &&
|
|
482
|
-
walkLoop(null, st[1], null, st[2], lastDecls.get(st[1]
|
|
533
|
+
&& Array.isArray(st[1]) && condIvName(st[1]) != null) {
|
|
534
|
+
walkLoop(null, st[1], null, st[2], lastDecls.get(condIvName(st[1])) ?? null, false)
|
|
483
535
|
} else if (Array.isArray(st) && st[0] === 'for' && st.length === 5) {
|
|
484
536
|
walkLoop(st[1], st[2], st[3], st[4], null, false)
|
|
485
537
|
} else scanStmts(st)
|
|
@@ -652,6 +704,15 @@ export function isUnitIncrement(step, name) {
|
|
|
652
704
|
return false
|
|
653
705
|
}
|
|
654
706
|
|
|
707
|
+
export function isUnitDecrement(step, name) {
|
|
708
|
+
if (!Array.isArray(step)) return false
|
|
709
|
+
if (step[0] === '--' && step[1] === name) return true
|
|
710
|
+
// postfix `i--` in value position lowers to `(--i) + 1`
|
|
711
|
+
if (step[0] === '+' && Array.isArray(step[1]) && step[1][0] === '--'
|
|
712
|
+
&& step[1][1] === name && intLiteralValue(step[2]) === 1) return true
|
|
713
|
+
return false
|
|
714
|
+
}
|
|
715
|
+
|
|
655
716
|
/** `let`/`const` re-declaration of `name` within `node` — does not cross `=>`
|
|
656
717
|
* (a closure has its own scope; collection already stops at closure boundaries). */
|
|
657
718
|
function redeclaresName(node, name) {
|
|
@@ -873,6 +934,9 @@ const ipOk = (v) => v != null && v[0] >= -IP_LIM && v[1] <= IP_LIM
|
|
|
873
934
|
* assignments embedded in expressions). */
|
|
874
935
|
function scanIntervalIdx(body, out, lens, ranges) {
|
|
875
936
|
const env = new Map() // name → [lo, hi] | null (unknown)
|
|
937
|
+
// While-body fixpoint passes walk EXPLORATORILY — env may be transiently too
|
|
938
|
+
// narrow, so proof/hull recording is suppressed until the stable final pass.
|
|
939
|
+
let recording = true
|
|
876
940
|
const symEnv = new Map() // name → { h: symbolic hull, incNode } — wrap cursors vs mutable bounds
|
|
877
941
|
// names written inside ANY closure in this body: a later call can change them at
|
|
878
942
|
// any point — they never hold a trusted interval
|
|
@@ -924,6 +988,14 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
924
988
|
const r = NARROW_ELEM_RANGE[ctx.types.typedElem?.get(x)]
|
|
925
989
|
return r ?? null
|
|
926
990
|
}
|
|
991
|
+
// `X.length` of a typed receiver with a known static length — the length-
|
|
992
|
+
// identity atom: `const n = a.length` binds a singleton, `(a.length-1)>>1`
|
|
993
|
+
// style index math evaluates exactly. Typed lengths are fixed for the
|
|
994
|
+
// binding's lifetime (the tracker drops the entry on any rebinding).
|
|
995
|
+
if ((op === '.' || op === '?.') && e.length === 3 && typeof x === 'string' && e[2] === 'length') {
|
|
996
|
+
const L = lens(x)
|
|
997
|
+
if (L != null) return [L, L]
|
|
998
|
+
}
|
|
927
999
|
if (e.length === 2 && op === '()') return ev(x) // grouping, not a call
|
|
928
1000
|
if (e.length === 2 && (op === '-' || op === 'u-')) { const v = ev(x); return ipOk(v) && v ? [-v[1], -v[0]] : null }
|
|
929
1001
|
if (op === '?:' && e.length === 4) { // join of both arms, each under its refinement
|
|
@@ -967,29 +1039,55 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
967
1039
|
else if (op === '%' && B[0] === B[1] && B[0] > 0 && A[0] >= 0) r = [0, Math.min(A[1], B[0] - 1)]
|
|
968
1040
|
return ipOk(r) ? r : null
|
|
969
1041
|
}
|
|
970
|
-
// condition refinement for if-arms: `name < K` / `name >= K` … over a known name
|
|
1042
|
+
// condition refinement for if-arms: `name < K` / `name >= K` … over a known name.
|
|
1043
|
+
// The lhs also admits the AFFINE form `name ± c` (`inl_i + 3 <= N` — the strided
|
|
1044
|
+
// codec cursors): the comparison re-biases to `name OP K∓c`. The rhs admits any
|
|
1045
|
+
// ACCESS-FREE expression the evaluator folds to a singleton (`src.length | 0`).
|
|
1046
|
+
const pureExpr = (e) => {
|
|
1047
|
+
if (!Array.isArray(e)) return true
|
|
1048
|
+
if (e[0] === '[]' || e[0] === '()' || e[0] === 'new' || e[0] === '?:' || e[0] === '=' || ASSIGN_OPS.has(e[0])) return false
|
|
1049
|
+
for (let k = 1; k < e.length; k++) if (!pureExpr(e[k])) return false
|
|
1050
|
+
return true
|
|
1051
|
+
}
|
|
971
1052
|
const refine = (c, negate) => {
|
|
972
1053
|
if (!Array.isArray(c) || c.length !== 3) return null
|
|
973
1054
|
let [op, l, r] = c
|
|
974
|
-
// rhs: an int literal/module const,
|
|
975
|
-
//
|
|
1055
|
+
// rhs: an int literal/module const, a body-known interval (`xi >= ww`, or a
|
|
1056
|
+
// RANGE-valued name — `child < end` inside the extract loop, where end is
|
|
1057
|
+
// the enclosing downward iv: the sound bound is the range's op-side
|
|
1058
|
+
// endpoint, hi for </<=, lo for >/>=), or a folded access-free expression
|
|
976
1059
|
const rE = typeof r === 'string' ? env.get(r) : null
|
|
977
|
-
|
|
978
|
-
if (
|
|
979
|
-
|
|
1060
|
+
let rLo = constInt(r), rHi = rLo
|
|
1061
|
+
if (rLo == null && rE) { rLo = rE[0]; rHi = rE[1] }
|
|
1062
|
+
if (rLo == null && Array.isArray(r) && pureExpr(r)) {
|
|
1063
|
+
const rr = ev(r)
|
|
1064
|
+
if (rr) { rLo = rr[0]; rHi = rr[1] }
|
|
1065
|
+
}
|
|
1066
|
+
if (rLo == null) return null
|
|
1067
|
+
if (Array.isArray(l) && l.length === 3 && (l[0] === '+' || l[0] === '-')) {
|
|
1068
|
+
const cR = intLiteralValue(l[2]), cL = intLiteralValue(l[1])
|
|
1069
|
+
if (typeof l[1] === 'string' && cR != null) { rLo = l[0] === '+' ? rLo - cR : rLo + cR; rHi = l[0] === '+' ? rHi - cR : rHi + cR; l = l[1] }
|
|
1070
|
+
else if (l[0] === '+' && typeof l[2] === 'string' && cL != null) { rLo = rLo - cL; rHi = rHi - cL; l = l[2] }
|
|
1071
|
+
}
|
|
1072
|
+
if (typeof l !== 'string') return null
|
|
1073
|
+
const v = env.get(l)
|
|
980
1074
|
if (!v) return null
|
|
981
1075
|
if (negate) op = op === '<' ? '>=' : op === '<=' ? '>' : op === '>' ? '<=' : op === '>=' ? '<'
|
|
982
1076
|
: op === '===' ? '!==' : op === '!==' ? '===' : null
|
|
983
|
-
if (op === '<') return [l, [v[0], Math.min(v[1],
|
|
984
|
-
if (op === '<=') return [l, [v[0], Math.min(v[1],
|
|
985
|
-
if (op === '>') return [l, [Math.max(v[0],
|
|
986
|
-
if (op === '>=') return [l, [Math.max(v[0],
|
|
987
|
-
if (op === '===') return [l, [Math.max(v[0],
|
|
1077
|
+
if (op === '<') return [l, [v[0], Math.min(v[1], rHi - 1)]]
|
|
1078
|
+
if (op === '<=') return [l, [v[0], Math.min(v[1], rHi)]]
|
|
1079
|
+
if (op === '>') return [l, [Math.max(v[0], rLo + 1), v[1]]]
|
|
1080
|
+
if (op === '>=') return [l, [Math.max(v[0], rLo), v[1]]]
|
|
1081
|
+
if (op === '===') return [l, [Math.max(v[0], rLo), Math.min(v[1], rHi)]]
|
|
988
1082
|
// ≠K tightens only at an ENDPOINT (interior point removal keeps the hull) —
|
|
989
|
-
// exactly the toroidal-wrap ternary (`y === 0 ? h-1 : y-1`)
|
|
990
|
-
if (op === '!==') return [l, [v[0] ===
|
|
1083
|
+
// exactly the toroidal-wrap ternary (`y === 0 ? h-1 : y-1`); singleton rhs only
|
|
1084
|
+
if (op === '!==' && rLo === rHi) return [l, [v[0] === rLo ? rLo + 1 : v[0], v[1] === rLo ? rLo - 1 : v[1]]]
|
|
991
1085
|
return null
|
|
992
1086
|
}
|
|
1087
|
+
// every conjunct of an `&&` chain holds where the whole condition held
|
|
1088
|
+
const refineAll = (c2) => Array.isArray(c2) && c2[0] === '&&'
|
|
1089
|
+
? [...refineAll(c2[1]), ...refineAll(c2[2])]
|
|
1090
|
+
: (() => { const r = refine(c2, false); return r ? [r] : [] })()
|
|
993
1091
|
const killAssigned = (n) => {
|
|
994
1092
|
if (!Array.isArray(n)) return // descend into closures too — capture-writes stay dead
|
|
995
1093
|
if (ASSIGN_OPS.has(n[0]) || n[0] === '++' || n[0] === '--') {
|
|
@@ -1000,14 +1098,134 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
1000
1098
|
}
|
|
1001
1099
|
for (let k = 1; k < n.length; k++) killAssigned(n[k])
|
|
1002
1100
|
}
|
|
1101
|
+
// A canonical-iv range `iv ∈ [entry, B−1]` is a body-independent THEOREM only
|
|
1102
|
+
// while B is invariant: a body-written bound (`while (i < n) { …; n = 12 }`)
|
|
1103
|
+
// admits iv past the entry bound — the seed then "proved" raw OOB reads
|
|
1104
|
+
// (dist-reproduced on every canonical loop form). Every name the bound reads
|
|
1105
|
+
// must be unwritten AND undeclared in the body.
|
|
1106
|
+
const boundInvariant = (bexpr, body) => {
|
|
1107
|
+
if (bexpr == null) return false
|
|
1108
|
+
const s = new Set(); collectNames(bexpr, s)
|
|
1109
|
+
for (const bn of s) if (isReassigned(body, bn) || redeclaresName(body, bn)) return false
|
|
1110
|
+
return true
|
|
1111
|
+
}
|
|
1112
|
+
// ABRUPT EDGES. A `break` reaches the loop's exit — and a `continue` its back
|
|
1113
|
+
// edge — carrying the flow state AT the statement, which the fall-through walk
|
|
1114
|
+
// never sees (`if (c) { x = BIG; break } x = 0` exits with x = BIG). Loop walks
|
|
1115
|
+
// push a frame; break/continue snapshot env into it; exits/joins hull the
|
|
1116
|
+
// snapshots in. Bare break binds to the innermost frame (a `switch` frame
|
|
1117
|
+
// swallows it); bare continue to the innermost LOOP frame; labeled forms can
|
|
1118
|
+
// cross any number of frames, so they conservatively feed every open one.
|
|
1119
|
+
const loopStack = [] // { kind: 'loop' | 'switch', breaks: [], continues: [] }
|
|
1120
|
+
const hullInto = (snap) => {
|
|
1121
|
+
for (const k2 of new Set([...env.keys(), ...snap.keys()])) {
|
|
1122
|
+
const a = env.get(k2), b = snap.get(k2)
|
|
1123
|
+
env.set(k2, a && b ? [Math.min(a[0], b[0]), Math.max(a[1], b[1])] : null)
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// LOOP BODY FIXPOINT (2-round widening). Pass A walks from the ENTRY state
|
|
1127
|
+
// (∩ cond) and yields the back-edge state; the JOIN hulls entry with it (a
|
|
1128
|
+
// name known on only one edge → null); pass B re-walks from join∩cond and
|
|
1129
|
+
// any name whose back-edge escapes its join widens to unknown; the FINAL
|
|
1130
|
+
// pass walks the stable env with proof recording ON, leaving env at the
|
|
1131
|
+
// loop invariant. `seedFn` re-applies body-independent theorems (canonical
|
|
1132
|
+
// iv ranges, wrap cursors) each pass; `condNode` refines at body top,
|
|
1133
|
+
// descending `&&` (both conjuncts hold when the loop is entered).
|
|
1134
|
+
const loopFixpoint = (seedFn, walkFn, condNode, exitBodyEnd = false) => {
|
|
1135
|
+
const applyCond = () => { if (condNode != null) for (const r of refineAll(condNode)) if (!closureWrites.has(r[0])) env.set(r[0], r[1]) }
|
|
1136
|
+
const restore = (m) => { env.clear(); for (const [k2, v2] of m) env.set(k2, v2) }
|
|
1137
|
+
// every pass walks under a loop frame: continue edges are back-edges too,
|
|
1138
|
+
// so their snapshots hull into the pass-end state before any join/verify
|
|
1139
|
+
const walkPass = () => {
|
|
1140
|
+
const lc = { kind: 'loop', breaks: [], continues: [] }
|
|
1141
|
+
loopStack.push(lc); walkFn(); loopStack.pop()
|
|
1142
|
+
for (const s of lc.continues) hullInto(s)
|
|
1143
|
+
return lc
|
|
1144
|
+
}
|
|
1145
|
+
const entryEnv = new Map(env)
|
|
1146
|
+
const prevRec = recording
|
|
1147
|
+
recording = false
|
|
1148
|
+
seedFn(); applyCond(); walkPass() // pass A: discovery
|
|
1149
|
+
// WIDENING JOIN: an escaping bound widens to the i32 extreme instead of the
|
|
1150
|
+
// one-step hull — the pass-B seed's cond refinement then clamps it to the
|
|
1151
|
+
// loop bound. This is what turns `for (; x + 3 <= N; x += 3)` into the
|
|
1152
|
+
// invariant x ∈ [0, N−3] (the strided-accumulator class) rather than
|
|
1153
|
+
// null: hull(entry, one step) can never contain step №2, so without the
|
|
1154
|
+
// widen every advancing cursor escapes to unknown.
|
|
1155
|
+
const joined = new Map()
|
|
1156
|
+
for (const k2 of new Set([...entryEnv.keys(), ...env.keys()])) {
|
|
1157
|
+
const a = entryEnv.get(k2), b = env.get(k2)
|
|
1158
|
+
joined.set(k2, a && b
|
|
1159
|
+
? [b[0] < a[0] ? -IP_LIM : Math.min(a[0], b[0]), b[1] > a[1] ? IP_LIM : Math.max(a[1], b[1])]
|
|
1160
|
+
: null)
|
|
1161
|
+
}
|
|
1162
|
+
restore(joined); seedFn(); applyCond(); walkPass() // pass B: verify
|
|
1163
|
+
// the back edge re-evaluates the condition before re-entering the body, so
|
|
1164
|
+
// the state to verify against the invariant is walk-end ∩ cond
|
|
1165
|
+
applyCond()
|
|
1166
|
+
for (const [k2, v2] of env) {
|
|
1167
|
+
const j = joined.get(k2)
|
|
1168
|
+
if (!(v2 && j && v2[0] >= j[0] && v2[1] <= j[1])) joined.set(k2, null)
|
|
1169
|
+
}
|
|
1170
|
+
// NARROWING (≤2 decreasing passes): the widened invariant is sound but
|
|
1171
|
+
// loose — a name with no cond conjunct to re-clamp it sits at ±IP_LIM even
|
|
1172
|
+
// when the loop's true range is finite (`i = child` copy chains: i only
|
|
1173
|
+
// ever receives root- or cond-clamped child-values, so hull(entry,
|
|
1174
|
+
// end-state) is the real invariant). Each pass recomputes the hull from
|
|
1175
|
+
// the stable state — every reachable back-edge state ⊆ F(joined ∩ cond),
|
|
1176
|
+
// so hull(entry, F(joined ∩ cond)) contains them all and only TIGHTENS
|
|
1177
|
+
// (meet with the previous invariant keeps the sequence decreasing).
|
|
1178
|
+
//
|
|
1179
|
+
// GATE (exact, not heuristic — a raw compile-time win jz.wasm pays for):
|
|
1180
|
+
// ONLY a name at an ±IP_LIM endpoint can narrow. The join gave every
|
|
1181
|
+
// non-escaping name its exact one-step hull (min/max of entry ∪ back-edge),
|
|
1182
|
+
// which is already the tightest interval containing both edges — a fresh
|
|
1183
|
+
// walk reproduces the same stable end-state, so the meet is a no-op there.
|
|
1184
|
+
// The escaped names (widened to the sentinel) are the sole candidates. If
|
|
1185
|
+
// NONE widened, skip both extra body walks (the common case: cond-clamped
|
|
1186
|
+
// ivs never escape). Turns the heapsort-class cost into zero on every
|
|
1187
|
+
// ordinary loop.
|
|
1188
|
+
const widened = () => {
|
|
1189
|
+
for (const [, j] of joined) if (j && (j[0] === -IP_LIM || j[1] === IP_LIM)) return true
|
|
1190
|
+
return false
|
|
1191
|
+
}
|
|
1192
|
+
for (let np = 0; np < 2 && widened(); np++) {
|
|
1193
|
+
restore(joined); seedFn(); applyCond(); walkPass(); applyCond()
|
|
1194
|
+
let changed = false
|
|
1195
|
+
for (const [k2, j] of joined) {
|
|
1196
|
+
const a = entryEnv.get(k2), b = env.get(k2)
|
|
1197
|
+
if (!j || !a || !b) continue
|
|
1198
|
+
const nl = Math.max(j[0], Math.min(a[0], b[0])), nh = Math.min(j[1], Math.max(a[1], b[1]))
|
|
1199
|
+
if (nl > j[0] || nh < j[1]) { joined.set(k2, [nl, nh]); changed = true }
|
|
1200
|
+
}
|
|
1201
|
+
if (!changed) break
|
|
1202
|
+
}
|
|
1203
|
+
recording = prevRec
|
|
1204
|
+
restore(joined); seedFn(); applyCond()
|
|
1205
|
+
const lcF = walkPass() // FINAL: record on the stable env
|
|
1206
|
+
// exit state:
|
|
1207
|
+
// - default: the invariant (joined) — sound for any trip count.
|
|
1208
|
+
// - exitBodyEnd (caller proved ≥1 trip): the final walk's BODY-END state —
|
|
1209
|
+
// tighter for defined-every-iteration names (an inlined preamble's
|
|
1210
|
+
// `inl_i = 0` keeps [0,0] where the join would null it), and sound
|
|
1211
|
+
// because the walk ran from the verified invariant, so its end state
|
|
1212
|
+
// covers every real last-iteration state. Zero-trip loops must NOT use
|
|
1213
|
+
// it: their real exit is the ENTRY state, which body-end doesn't cover.
|
|
1214
|
+
if (!exitBodyEnd) restore(joined)
|
|
1215
|
+
// ∪ break-edge states (a break bypasses the loop condition and reaches the
|
|
1216
|
+
// exit mid-body; the caller's tighter iv/wrap exit forms stay sound — each
|
|
1217
|
+
// is an every-point invariant that covers break states)
|
|
1218
|
+
for (const s of lcF.breaks) hullInto(s)
|
|
1219
|
+
}
|
|
1003
1220
|
const visit = (n) => {
|
|
1004
1221
|
if (!Array.isArray(n) || n[0] === '=>') return
|
|
1005
1222
|
if (n._rangeFacts) return visitWithFacts(n)
|
|
1006
1223
|
const op = n[0]
|
|
1007
1224
|
if (op === '[]' && n.length === 3 && typeof n[1] === 'string') {
|
|
1008
1225
|
const idxV = ev(n[2])
|
|
1226
|
+
if (!recording) return // exploratory fixpoint pass: env effects only
|
|
1009
1227
|
const L = lens(n[1])
|
|
1010
|
-
if (
|
|
1228
|
+
if (typeof process !== 'undefined' && process.env.JZ_DBG_IP) console.error('IPW', n[1], JSON.stringify(n[2]).slice(0,50), JSON.stringify(idxV), 'len', L)
|
|
1011
1229
|
if (L != null && idxV && idxV[0] >= 0 && idxV[1] < L) out.add(idxKey(n[1], n[2]))
|
|
1012
1230
|
// a bounded idx against an UNKNOWN length is half a proof — export the hull
|
|
1013
1231
|
// (joined over every sighting of this key) for the versioning guard to close
|
|
@@ -1043,7 +1261,22 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
1043
1261
|
if (ASSIGN_OPS.has(op) || op === '++' || op === '--') {
|
|
1044
1262
|
if (typeof n[1] === 'string' && symEnv.get(n[1])?.incNode === n) symEnv.delete(n[1])
|
|
1045
1263
|
for (let k = 2; k < n.length; k++) visit(n[k])
|
|
1046
|
-
if (typeof n[1] === 'string')
|
|
1264
|
+
if (typeof n[1] === 'string') {
|
|
1265
|
+
// `x += K` / `x -= K` / `x++` / `x--` transfer exactly — a strided
|
|
1266
|
+
// accumulator keeps a computable back-edge for the loop fixpoint
|
|
1267
|
+
// (cond-clamped by the widening join); anything else is unknown
|
|
1268
|
+
const cur = env.get(n[1])
|
|
1269
|
+
let nv = null
|
|
1270
|
+
if (cur) {
|
|
1271
|
+
if (op === '++') nv = [cur[0] + 1, cur[1] + 1]
|
|
1272
|
+
else if (op === '--') nv = [cur[0] - 1, cur[1] - 1]
|
|
1273
|
+
else if (op === '+=' || op === '-=') {
|
|
1274
|
+
const d = ev(n[2])
|
|
1275
|
+
if (d) nv = op === '+=' ? [cur[0] + d[0], cur[1] + d[1]] : [cur[0] - d[1], cur[1] - d[0]]
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
setEnv(n[1], nv)
|
|
1279
|
+
}
|
|
1047
1280
|
else {
|
|
1048
1281
|
visit(n[1]) // records the member-write access proof (`out[idx] = …`)
|
|
1049
1282
|
if (Array.isArray(n[1]) && n[1][0] !== '[]' && n[1][0] !== '.' && n[1][0] !== '?.') {
|
|
@@ -1052,12 +1285,29 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
1052
1285
|
}
|
|
1053
1286
|
return
|
|
1054
1287
|
}
|
|
1288
|
+
if (op === 'break' || op === 'continue') {
|
|
1289
|
+
if (typeof n[1] === 'string') { // labeled: may cross frames — feed every open one
|
|
1290
|
+
for (const fr of loopStack) if (fr.kind === 'loop') { fr.breaks.push(new Map(env)); fr.continues.push(new Map(env)) }
|
|
1291
|
+
}
|
|
1292
|
+
else if (op === 'break') {
|
|
1293
|
+
const fr = loopStack[loopStack.length - 1]
|
|
1294
|
+
if (fr && fr.kind === 'loop') fr.breaks.push(new Map(env))
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
const fr = loopStack.findLast(f => f.kind === 'loop')
|
|
1298
|
+
if (fr) fr.continues.push(new Map(env))
|
|
1299
|
+
}
|
|
1300
|
+
return
|
|
1301
|
+
}
|
|
1055
1302
|
if (op === 'for' && n.length === 5) {
|
|
1056
1303
|
const [, init, cond, step, lbody] = n
|
|
1057
1304
|
visit(init)
|
|
1058
|
-
// canonical literal-interval iv: `for (iv = A; iv </<= B; iv++)
|
|
1305
|
+
// canonical literal-interval iv: `for (iv = A; iv </<= B; iv++)` — or the
|
|
1306
|
+
// DOWNWARD twin `for (iv = A; iv >/>= B; iv--)` (heapify roots, reverse
|
|
1307
|
+
// scans) — A/B singleton through the full evaluator
|
|
1059
1308
|
let iv = null, range = null
|
|
1060
|
-
|
|
1309
|
+
const down = Array.isArray(cond) && (cond[0] === '>' || cond[0] === '>=')
|
|
1310
|
+
if (Array.isArray(cond) && (cond[0] === '<' || cond[0] === '<=' || down) && typeof cond[1] === 'string') {
|
|
1061
1311
|
const decls = new Map(); collectDecls(init, decls)
|
|
1062
1312
|
// start/bound through the full evaluator: function-local consts (`x < ww`)
|
|
1063
1313
|
// and computed starts (`k = -rr`) resolve as singleton intervals
|
|
@@ -1066,15 +1316,29 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
1066
1316
|
const Bs = cond[2] != null ? ev(cond[2]) : null
|
|
1067
1317
|
const A = As && As[0] === As[1] ? As[0] : null
|
|
1068
1318
|
const B = Bs && Bs[0] === Bs[1] ? Bs[0] : null
|
|
1069
|
-
if (A != null && B != null &&
|
|
1070
|
-
&&
|
|
1071
|
-
|
|
1319
|
+
if (A != null && B != null && !isReassigned(lbody, cond[1]) && !redeclaresName(lbody, cond[1])
|
|
1320
|
+
&& (cond[2] == null || boundInvariant(cond[2], lbody))
|
|
1321
|
+
&& (down ? isUnitDecrement(step, cond[1]) : isUnitIncrement(step, cond[1]))) {
|
|
1322
|
+
iv = cond[1]
|
|
1323
|
+
range = down ? [cond[0] === '>' ? B + 1 : B, A] : [A, cond[0] === '<' ? B - 1 : B]
|
|
1072
1324
|
}
|
|
1073
1325
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1326
|
+
// Body fixpoint (same engine as `while` below): the canonical-iv range is
|
|
1327
|
+
// a body-independent theorem re-seeded each pass; everything else
|
|
1328
|
+
// discovers its invariant. This is what proves heapsort's `child` chains
|
|
1329
|
+
// (`child = 2*i+1; while-ish descend`) and medianUs's `samples[mid]`.
|
|
1330
|
+
// Exit state: a canonical iv with a non-empty LITERAL range proves ≥1
|
|
1331
|
+
// trip, so the tighter body-end exit is sound (post-loop peel tails read
|
|
1332
|
+
// `src[inl_i+…]` off exactly that state); anything else takes the joined
|
|
1333
|
+
// invariant (zero-trip exit = entry state ⊆ joined).
|
|
1334
|
+
const seeded = iv && range[0] <= range[1] && !closureWrites.has(iv)
|
|
1335
|
+
const seeds = () => {
|
|
1336
|
+
if (seeded) env.set(iv, range)
|
|
1337
|
+
else if (iv) env.set(iv, null)
|
|
1338
|
+
}
|
|
1339
|
+
loopFixpoint(seeds,
|
|
1340
|
+
() => { if (cond != null) visit(cond); visit(lbody); if (step != null) visit(step) },
|
|
1341
|
+
cond, seeded)
|
|
1078
1342
|
if (iv) env.set(iv, null) // iv holds the exit value after the loop
|
|
1079
1343
|
return
|
|
1080
1344
|
}
|
|
@@ -1087,7 +1351,8 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
1087
1351
|
let iv = null, entry = null, brange = null
|
|
1088
1352
|
if (Array.isArray(c) && c[0] === '<' && typeof c[1] === 'string' && wbody != null) {
|
|
1089
1353
|
entry = env.get(c[1]); brange = c[2] != null ? ev(c[2]) : null
|
|
1090
|
-
if (entry && brange && ivMonotoneInc(wbody, c[1]) && !redeclaresName(wbody, c[1])
|
|
1354
|
+
if (entry && brange && ivMonotoneInc(wbody, c[1]) && !redeclaresName(wbody, c[1])
|
|
1355
|
+
&& boundInvariant(c[2], wbody)) iv = c[1]
|
|
1091
1356
|
}
|
|
1092
1357
|
// WRAPPING-CURSOR invariant (`si = si + K; if (si >= C) si = 0` — the ring
|
|
1093
1358
|
// index of table-driven maps): the pair is self-closing on [0, C-1], so an
|
|
@@ -1148,29 +1413,53 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
1148
1413
|
else symWraps.push([nm, { lo: 0, hiName: Cname, hiBias: -1, entryHi: e0[1] }, a2])
|
|
1149
1414
|
}
|
|
1150
1415
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1416
|
+
// Body fixpoint (loopFixpoint below): the monotone-iv/wrap/symWrap seeds
|
|
1417
|
+
// are theorems independent of the body, re-applied each pass; everything
|
|
1418
|
+
// else discovers its invariant. Bounds heapsort's `while (child < n)`
|
|
1419
|
+
// chains, medianUs's downward insertion scan, and interpreter
|
|
1420
|
+
// `while (pc < N)` dispatch — shapes the single-kill walk lost entirely.
|
|
1421
|
+
const seeds = () => {
|
|
1422
|
+
if (iv) env.set(iv, [entry[0], brange[1] - 1])
|
|
1423
|
+
for (const [nm, r] of wraps) if (!closureWrites.has(nm)) env.set(nm, r)
|
|
1424
|
+
for (const [nm, h, incNode] of symWraps) if (!closureWrites.has(nm)) symEnv.set(nm, { h, incNode })
|
|
1425
|
+
}
|
|
1426
|
+
loopFixpoint(seeds, () => { visit(c); for (let k = 2; k < n.length; k++) visit(n[k]) }, c)
|
|
1427
|
+
// exit state: the invariant (already in env) hulls entry ∪ back-edges;
|
|
1428
|
+
// iv/wraps publish their tighter exit forms
|
|
1157
1429
|
if (iv) env.set(iv, [Math.min(entry[0], brange[0]), Math.max(entry[1], brange[1])])
|
|
1158
1430
|
for (const [nm, r] of wraps) if (!closureWrites.has(nm)) env.set(nm, r) // holds at exit too
|
|
1159
1431
|
for (const [nm] of symWraps) symEnv.delete(nm)
|
|
1160
1432
|
return
|
|
1161
1433
|
}
|
|
1162
1434
|
if (op === 'do' || op === 'for-of' || op === 'for-in' || op === 'label'
|
|
1163
|
-
|| op === 'switch' || op === 'try') {
|
|
1435
|
+
|| op === 'switch' || op === 'try' || op === 'catch' || op === 'finally') {
|
|
1436
|
+
// ('try' is the parser shape; prepare lowers it to 'catch'/'finally' nodes,
|
|
1437
|
+
// which is what this walk actually receives)
|
|
1164
1438
|
killAssigned(n) // unknown trip count / branch selection: no interval survives entry
|
|
1165
|
-
|
|
1439
|
+
// Each child walks from the killed entry state and the construct EXITS at
|
|
1440
|
+
// it: case selection enters any child directly, an exception can leave a
|
|
1441
|
+
// `try` child mid-statement, a `do` body can break out — so neither a
|
|
1442
|
+
// sibling's nor the last child's flow state is the construct's. In-child
|
|
1443
|
+
// straight-line proofs (defined-before-use chains) still record.
|
|
1444
|
+
const killed = new Map(env)
|
|
1445
|
+
const fr = op === 'switch' ? { kind: 'switch', breaks: [], continues: [] }
|
|
1446
|
+
: op === 'do' || op === 'for-of' || op === 'for-in' ? { kind: 'loop', breaks: [], continues: [] }
|
|
1447
|
+
: null // label/try: transparent — abrupt edges bind to enclosing frames
|
|
1448
|
+
if (fr) loopStack.push(fr)
|
|
1449
|
+
for (let k = 1; k < n.length; k++) {
|
|
1450
|
+
visit(n[k])
|
|
1451
|
+
env.clear(); for (const [k2, v2] of killed) env.set(k2, v2)
|
|
1452
|
+
}
|
|
1453
|
+
if (fr) loopStack.pop()
|
|
1166
1454
|
return
|
|
1167
1455
|
}
|
|
1168
1456
|
if (op === 'if') {
|
|
1169
1457
|
const [, c, thenB, elseB] = n
|
|
1170
1458
|
visit(c)
|
|
1171
1459
|
const save = new Map(env)
|
|
1172
|
-
|
|
1173
|
-
|
|
1460
|
+
// every `&&` conjunct holds on the then path (`if (child+1 < n && a[child] <
|
|
1461
|
+
// a[child+1]) child++` — the ++ under BOTH bounds)
|
|
1462
|
+
for (const rT of refineAll(c)) if (!closureWrites.has(rT[0])) env.set(rT[0], rT[1])
|
|
1174
1463
|
visit(thenB)
|
|
1175
1464
|
const afterThen = new Map(env)
|
|
1176
1465
|
env.clear(); for (const [k2, v2] of save) env.set(k2, v2)
|
|
@@ -1187,6 +1476,25 @@ function scanIntervalIdx(body, out, lens, ranges) {
|
|
|
1187
1476
|
}
|
|
1188
1477
|
return
|
|
1189
1478
|
}
|
|
1479
|
+
// Short-circuit operands evaluate under the left side's verdict: `&&`'s rhs
|
|
1480
|
+
// runs only where lhs HELD (`child + 1 < n && a[child] < a[child + 1]` — the
|
|
1481
|
+
// lookahead read is bounds-guarded by its sibling conjunct), `||`'s rhs only
|
|
1482
|
+
// where lhs FAILED. Reads inside the rhs prove under that refinement; writes
|
|
1483
|
+
// there ran conditionally, so the exit state joins both possibilities.
|
|
1484
|
+
if ((op === '&&' || op === '||') && n.length === 3) {
|
|
1485
|
+
visit(n[1])
|
|
1486
|
+
const save = new Map(env)
|
|
1487
|
+
if (op === '&&') { for (const r of refineAll(n[1])) if (!closureWrites.has(r[0])) env.set(r[0], r[1]) }
|
|
1488
|
+
else { const r = refine(n[1], true); if (r && !closureWrites.has(r[0])) env.set(r[0], r[1]) }
|
|
1489
|
+
visit(n[2])
|
|
1490
|
+
const after = new Map(env)
|
|
1491
|
+
env.clear(); for (const [k2, v2] of save) env.set(k2, v2)
|
|
1492
|
+
for (const k2 of new Set([...after.keys(), ...env.keys()])) {
|
|
1493
|
+
const a = after.get(k2), b = env.get(k2)
|
|
1494
|
+
env.set(k2, a && b ? [Math.min(a[0], b[0]), Math.max(a[1], b[1])] : null)
|
|
1495
|
+
}
|
|
1496
|
+
return
|
|
1497
|
+
}
|
|
1190
1498
|
if (op === '()' && n.length === 2) { visit(n[1]); return } // grouping, not a call
|
|
1191
1499
|
if (op === '()' || op === 'new') { // a call may reassign module globals
|
|
1192
1500
|
for (let k = 1; k < n.length; k++) visit(n[k])
|
|
@@ -1472,7 +1780,8 @@ export function exprType(expr, locals) {
|
|
|
1472
1780
|
const ctor = typedElemCtorOf(args[0], locals)
|
|
1473
1781
|
if (ctor) {
|
|
1474
1782
|
const aux = typedElemAux(ctor)
|
|
1475
|
-
|
|
1783
|
+
// int family only — Float16Array shares code 3 with a flag; its elements are floats
|
|
1784
|
+
if (aux != null && (aux & 7) <= 5 && !(aux & 32)) return 'i32'
|
|
1476
1785
|
}
|
|
1477
1786
|
}
|
|
1478
1787
|
return 'f64'
|