jz 0.9.1 → 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 +20 -20
- package/dist/interop.js +1 -1
- package/dist/jz.js +1642 -1045
- 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/module/typedarray.js
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* @module typed
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { typed, asF64, asI32, asI64, toNumF64, UNDEF_NAN, NULL_NAN, TRUE_NAN, FALSE_NAN, allocPtr, mkPtrIR, ptrOffsetIR, ptrTypeEq, temp, tempI32, tempI64, undefExpr, truthyIR } from '../src/ir.js'
|
|
10
|
+
import { typed, asF64, asI32, asI64, toNumF64, coerceNullishToNum, UNDEF_NAN, NULL_NAN, TRUE_NAN, FALSE_NAN, allocPtr, mkPtrIR, ptrOffsetIR, ptrTypeEq, temp, tempI32, tempI64, undefExpr, truthyIR } from '../src/ir.js'
|
|
11
|
+
import { isReassigned, T } from '../src/ast.js'
|
|
11
12
|
import { emit, idx, deps, call } from '../src/bridge.js'
|
|
12
13
|
import { strHashLiteral } from './collection.js'
|
|
13
14
|
import { valTypeOf } from '../src/kind.js'
|
|
14
|
-
import { typedIdxProven } from '../src/type.js'
|
|
15
|
+
import { typedIdxProven, typedElemCtor } from '../src/type.js'
|
|
15
16
|
import { VAL, lookupValType } from '../src/reps.js'
|
|
16
17
|
import { nanPrefixHex, TYPED_ELEM_NAMES, TYPED_ELEM_CODE, TYPED_ELEM_BIGINT_FLAG, encodeTypedElemAux } from '../layout.js'
|
|
17
18
|
import { inc, PTR, LAYOUT, registerGetter } from '../src/ctx.js'
|
|
@@ -208,8 +209,9 @@ export default (ctx) => {
|
|
|
208
209
|
__byte_length: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
209
210
|
__byte_offset: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
210
211
|
__to_buffer: ['__ptr_type', '__ptr_offset', '__ptr_aux', '__mkptr'],
|
|
211
|
-
__typed_set_idx: ['__ptr_aux', '__ptr_offset'
|
|
212
|
-
|
|
212
|
+
__typed_set_idx: () => ['__ptr_aux', '__ptr_offset',
|
|
213
|
+
...(ctx.features.f16 ? ['__f64_to_f16'] : []), ...(ctx.features.clamped ? ['__u8_clamp'] : [])],
|
|
214
|
+
__typed_get_idx: () => ['__ptr_aux', '__ptr_offset', ...(ctx.features.f16 ? ['__f16_to_f64'] : [])],
|
|
213
215
|
// __clamp_idx is body-called by every range op (fill/copyWithin/subarray/slice). It has NO
|
|
214
216
|
// other manual-dep edge in the whole stdlib, so it's reachable ONLY via resolveIncludes'
|
|
215
217
|
// auto-scan — which diverges under self-host (jz.wasm), dropping it ("Unknown func
|
|
@@ -367,7 +369,7 @@ export default (ctx) => {
|
|
|
367
369
|
(local.set $byteLen (i32.shl (local.get $n) (local.get $shift)))
|
|
368
370
|
(local.set $dst (call $__alloc_hdr_n (local.get $byteLen) (local.get $byteLen) (i32.const 1)))
|
|
369
371
|
(memory.copy (local.get $dst) (i32.add (local.get $src) (i32.shl (local.get $lo) (local.get $shift))) (local.get $byteLen))
|
|
370
|
-
(call $__mkptr (i32.const ${PTR.TYPED}) (i32.and (local.get $aux) (i32.const
|
|
372
|
+
(call $__mkptr (i32.const ${PTR.TYPED}) (i32.and (local.get $aux) (i32.const 119)) (local.get $dst)))`
|
|
371
373
|
|
|
372
374
|
// Constructor: new Float64Array(len) | new F64Array(arr) | new F64Array(buf) | new F64Array(buf, off, len)
|
|
373
375
|
for (const [name, elemType] of Object.entries(TYPED_ELEM_CODE)) {
|
|
@@ -375,6 +377,8 @@ export default (ctx) => {
|
|
|
375
377
|
const stride = STRIDE[elemType]
|
|
376
378
|
ctx.core.emit[`new.${name}`] = (lenExpr, offsetExpr, lenExpr2) => {
|
|
377
379
|
ctx.features.typedarray = true
|
|
380
|
+
if (name === 'Float16Array') ctx.features.f16 = true
|
|
381
|
+
if (name === 'Uint8ClampedArray') ctx.features.clamped = true
|
|
378
382
|
const srcType = typeof lenExpr === 'string' ? lookupValType(lenExpr) : valTypeOf(lenExpr)
|
|
379
383
|
// Subview: new TypedArray(buffer, byteOffset, length) — true JS-parity view.
|
|
380
384
|
// Allocates a 16-byte descriptor [byteLen:i32][dataOff:i32][parentOff:i32][pad]
|
|
@@ -412,15 +416,17 @@ export default (ctx) => {
|
|
|
412
416
|
const conv = FROM_F64[elemType]
|
|
413
417
|
const srcElem = ['call', '$__typed_idx', ['i64.reinterpret_f64', ['local.get', `$${srcTemp}`]], ['local.get', `$${ci}`]]
|
|
414
418
|
const cid = ctx.func.uniq++
|
|
419
|
+
const dstOff = ['i32.add', ['local.get', `$${out.local}`], ['i32.mul', ['local.get', `$${ci}`], ['i32.const', stride]]]
|
|
420
|
+
const storeIR = (name === 'Float16Array' || name === 'Uint8ClampedArray')
|
|
421
|
+
? elemStoreIR({ et: elemType, isF16: name === 'Float16Array', isClamped: name === 'Uint8ClampedArray' }, dstOff, srcElem)
|
|
422
|
+
: [STORE[elemType], dstOff, conv ? [conv, srcElem] : srcElem]
|
|
415
423
|
return ['block', ['result', 'f64'],
|
|
416
424
|
['local.set', `$${cl}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${srcTemp}`]]]],
|
|
417
425
|
out.init,
|
|
418
426
|
['local.set', `$${ci}`, ['i32.const', 0]],
|
|
419
427
|
['block', `$tcb${cid}`, ['loop', `$tclp${cid}`,
|
|
420
428
|
['br_if', `$tcb${cid}`, ['i32.ge_s', ['local.get', `$${ci}`], ['local.get', `$${cl}`]]],
|
|
421
|
-
|
|
422
|
-
['i32.add', ['local.get', `$${out.local}`], ['i32.mul', ['local.get', `$${ci}`], ['i32.const', stride]]],
|
|
423
|
-
conv ? [conv, srcElem] : srcElem],
|
|
429
|
+
storeIR,
|
|
424
430
|
['local.set', `$${ci}`, ['i32.add', ['local.get', `$${ci}`], ['i32.const', 1]]],
|
|
425
431
|
['br', `$tclp${cid}`]]],
|
|
426
432
|
out.ptr]
|
|
@@ -975,12 +981,66 @@ export default (ctx) => {
|
|
|
975
981
|
}
|
|
976
982
|
}
|
|
977
983
|
|
|
984
|
+
// DataView.getFloat16 / setFloat16 (ES2025) — the 2-byte binary16 payload
|
|
985
|
+
// around the same descriptor/bounds/LE machinery as get/setUint16, with the
|
|
986
|
+
// core conversion kernels at the value edge.
|
|
987
|
+
ctx.core.emit['.getFloat16'] = (dv, off, leNode) => {
|
|
988
|
+
inc('__f16_to_f64')
|
|
989
|
+
const desc = tempI32('dvD')
|
|
990
|
+
const addr = ['i32.add', dvDataOff(desc), dvIndexChecked(off, 2, dvViewSize(desc))]
|
|
991
|
+
const cvt = (raw16) => typed(['call', '$__f16_to_f64', raw16], 'f64')
|
|
992
|
+
const le = staticLE(leNode)
|
|
993
|
+
let body
|
|
994
|
+
if (le === true) body = cvt(['i32.load16_u', addr])
|
|
995
|
+
else if (le === false) body = cvt(bswap16I32(typed(['i32.load16_u', addr], 'i32')))
|
|
996
|
+
else {
|
|
997
|
+
const aT = tempI32('dvga'), leT = tempI32('dvgle')
|
|
998
|
+
body = typed(['block', ['result', 'f64'],
|
|
999
|
+
['local.set', `$${aT}`, addr],
|
|
1000
|
+
['local.set', `$${leT}`, truthyIR(emit(leNode))],
|
|
1001
|
+
['if', ['result', 'f64'], ['local.get', `$${leT}`],
|
|
1002
|
+
['then', cvt(['i32.load16_u', ['local.get', `$${aT}`]])],
|
|
1003
|
+
['else', cvt(bswap16I32(typed(['i32.load16_u', ['local.get', `$${aT}`]], 'i32')))]]], 'f64')
|
|
1004
|
+
}
|
|
1005
|
+
return typed(['block', ['result', 'f64'],
|
|
1006
|
+
['local.set', `$${desc}`, dvDescriptor(dv)],
|
|
1007
|
+
asF64(body)], 'f64')
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
ctx.core.emit['.setFloat16'] = (dv, off, val, leNode) => {
|
|
1011
|
+
inc('__f64_to_f16')
|
|
1012
|
+
const desc = tempI32('dvD'), aT = tempI32('dvsa'), vT = tempI32('dvsv16')
|
|
1013
|
+
const addr = ['i32.add', dvDataOff(desc), dvIndexChecked(off, 2, dvViewSize(desc))]
|
|
1014
|
+
const le = staticLE(leNode)
|
|
1015
|
+
const store = (bits) => ['i32.store16', ['local.get', `$${aT}`], bits]
|
|
1016
|
+
const bitsLE = ['local.get', `$${vT}`]
|
|
1017
|
+
const bitsBE = () => bswap16I32(typed(['local.get', `$${vT}`], 'i32'))
|
|
1018
|
+
let storeIR
|
|
1019
|
+
if (le === true) storeIR = store(bitsLE)
|
|
1020
|
+
else if (le === false) storeIR = store(bitsBE())
|
|
1021
|
+
else {
|
|
1022
|
+
const leT = tempI32('dvsle')
|
|
1023
|
+
storeIR = ['block',
|
|
1024
|
+
['local.set', `$${leT}`, truthyIR(emit(leNode))],
|
|
1025
|
+
['if', ['local.get', `$${leT}`], ['then', store(bitsLE)], ['else', store(bitsBE())]]]
|
|
1026
|
+
}
|
|
1027
|
+
return typed(['block', ['result', 'f64'],
|
|
1028
|
+
['local.set', `$${desc}`, dvDescriptor(dv)],
|
|
1029
|
+
['local.set', `$${aT}`, addr],
|
|
1030
|
+
['local.set', `$${vT}`, ['call', '$__f64_to_f16', asF64(emit(val))]],
|
|
1031
|
+
storeIR,
|
|
1032
|
+
undefExpr()], 'f64')
|
|
1033
|
+
}
|
|
1034
|
+
|
|
978
1035
|
// TypedArray.from(arr) — convert regular array to typed array
|
|
979
1036
|
for (const [name, elemType] of Object.entries(TYPED_ELEM_CODE)) {
|
|
980
1037
|
const aux = typedAux(name)
|
|
981
1038
|
const stride = STRIDE[elemType], store = STORE[elemType]
|
|
982
1039
|
ctx.core.emit[`${name}.from`] = (src) => {
|
|
983
1040
|
ctx.features.typedarray = true
|
|
1041
|
+
if (name === 'Float16Array') ctx.features.f16 = true
|
|
1042
|
+
if (name === 'Uint8ClampedArray') ctx.features.clamped = true
|
|
1043
|
+
const fl = { et: elemType, isF16: name === 'Float16Array', isClamped: name === 'Uint8ClampedArray' }
|
|
984
1044
|
// Bare array-literal source (`Int32Array.from([…])`, `new Int32Array([…])`): build the
|
|
985
1045
|
// typed array directly — alloc + one native-typed store per element — instead of
|
|
986
1046
|
// materializing an intermediate f64 ARRAY (every element a 9-byte f64.const) plus a
|
|
@@ -996,6 +1056,7 @@ export default (ctx) => {
|
|
|
996
1056
|
for (let k = 0; k < elems.length; k++) {
|
|
997
1057
|
const addr = k === 0 ? ['local.get', `$${out.local}`]
|
|
998
1058
|
: ['i32.add', ['local.get', `$${out.local}`], ['i32.const', k * stride]]
|
|
1059
|
+
if (fl.isF16 || fl.isClamped) { body.push(elemStoreIR(fl, addr, asF64(emit(elems[k])))); continue }
|
|
999
1060
|
const v = elemType <= 5 ? asI32(emit(elems[k]))
|
|
1000
1061
|
: elemType === 6 ? ['f32.demote_f64', asF64(emit(elems[k]))]
|
|
1001
1062
|
: asF64(emit(elems[k]))
|
|
@@ -1012,9 +1073,10 @@ export default (ctx) => {
|
|
|
1012
1073
|
const id = ctx.func.uniq++
|
|
1013
1074
|
const conv = FROM_F64[elemType]
|
|
1014
1075
|
const srcF64 = ['f64.load', ['i32.add', ['local.get', `$${off}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1076
|
+
const dstAddr = ['i32.add', ['local.get', `$${t}`], ['i32.mul', ['local.get', `$${i}`], ['i32.const', stride]]]
|
|
1077
|
+
const storeExpr = (fl.isF16 || fl.isClamped)
|
|
1078
|
+
? elemStoreIR(fl, dstAddr, srcF64)
|
|
1079
|
+
: [store, dstAddr, conv ? [conv, srcF64] : srcF64]
|
|
1018
1080
|
return typed(['block', ['result', 'f64'],
|
|
1019
1081
|
['local.set', `$${srcL}`, asF64(emit(src))],
|
|
1020
1082
|
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${srcL}`]]]],
|
|
@@ -1060,11 +1122,40 @@ export default (ctx) => {
|
|
|
1060
1122
|
? ctx.func.localReps?.get(receiver[1])?.arrayElemTypedCtor
|
|
1061
1123
|
: null)
|
|
1062
1124
|
|| (typeof receiver === 'string' && ctx.types.typedElem?.get(receiver))
|
|
1125
|
+
// Direct fresh-ctor receiver: `new Int32Array([…]).map(f)` — the ctor call
|
|
1126
|
+
// node IS the receiver, no binding to look up. Without this the chain fell
|
|
1127
|
+
// to the plain-array emitters, which read f64 slots — silently wrong for
|
|
1128
|
+
// every element kind except Float64Array.
|
|
1129
|
+
|| typedElemCtor(receiver)
|
|
1063
1130
|
if (!ctor) return null
|
|
1064
1131
|
const isView = viewOutput || (!chainOutput && ctor.endsWith('.view'))
|
|
1065
1132
|
const name = ctor.endsWith('.view') ? ctor.slice(4, -5) : ctor.slice(4)
|
|
1066
1133
|
const et = TYPED_ELEM_CODE[name]
|
|
1067
|
-
|
|
1134
|
+
if (name === 'Float16Array') ctx.features.f16 = true
|
|
1135
|
+
if (name === 'Uint8ClampedArray') ctx.features.clamped = true
|
|
1136
|
+
return et == null ? null : { et, isView, name,
|
|
1137
|
+
isBigInt: name === 'BigInt64Array' || name === 'BigUint64Array',
|
|
1138
|
+
isF16: name === 'Float16Array', isClamped: name === 'Uint8ClampedArray' }
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Canonical element accessors — the ONE home for kind-aware load/store IR.
|
|
1142
|
+
// r is a resolveElem result (or {et,...} with optional flags). f16 rides the
|
|
1143
|
+
// u16 slot with a conversion kernel each way; clamped is a u8 with the
|
|
1144
|
+
// ToUint8Clamp store. Everything else keeps the exact historical opcodes.
|
|
1145
|
+
const elemLoadIR = (r, off) => {
|
|
1146
|
+
if (r.isBigInt) return ['f64.reinterpret_i64', ['i64.load', off]]
|
|
1147
|
+
if (r.isF16) { inc('__f16_to_f64'); return ['call', '$__f16_to_f64', ['i32.load16_u', off]] }
|
|
1148
|
+
if (r.et === 7) return ['f64.load', off]
|
|
1149
|
+
if (r.et === 6) return ['f64.promote_f32', ['f32.load', off]]
|
|
1150
|
+
return [(r.et & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', [LOAD[r.et], off]]
|
|
1151
|
+
}
|
|
1152
|
+
const elemStoreIR = (r, off, valF64) => {
|
|
1153
|
+
if (r.isBigInt) return ['i64.store', off, ['i64.reinterpret_f64', valF64]]
|
|
1154
|
+
if (r.isF16) { inc('__f64_to_f16'); return ['i32.store16', off, ['call', '$__f64_to_f16', valF64]] }
|
|
1155
|
+
if (r.isClamped) { inc('__u8_clamp'); return ['i32.store8', off, ['call', '$__u8_clamp', valF64]] }
|
|
1156
|
+
if (r.et === 7) return ['f64.store', off, valF64]
|
|
1157
|
+
if (r.et === 6) return ['f32.store', off, ['f32.demote_f64', valF64]]
|
|
1158
|
+
return [STORE[r.et], off, [(r.et & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s', valF64]]
|
|
1068
1159
|
}
|
|
1069
1160
|
|
|
1070
1161
|
/** Emit the real data byte-address for a typed array IR node.
|
|
@@ -1087,7 +1178,7 @@ export default (ctx) => {
|
|
|
1087
1178
|
|
|
1088
1179
|
// Runtime-dispatch typed index: checks ptr_type + aux to load with correct stride.
|
|
1089
1180
|
// For TYPED views (aux bit 3), $off indirects through descriptor[4] to real data.
|
|
1090
|
-
ctx.core.stdlib['__typed_set_idx'] = `(func $__typed_set_idx (param $ptr i64) (param $i i32) (param $v f64) (result f64)
|
|
1181
|
+
ctx.core.stdlib['__typed_set_idx'] = () => `(func $__typed_set_idx (param $ptr i64) (param $i i32) (param $v f64) (result f64)
|
|
1091
1182
|
(local $off i32) (local $aux i32) (local $et i32) (local $bits i32) (local $vb i64)
|
|
1092
1183
|
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
1093
1184
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
@@ -1110,6 +1201,14 @@ export default (ctx) => {
|
|
|
1110
1201
|
(if (i32.or (i64.eq (local.get $vb) (i64.const ${FALSE_NAN}))
|
|
1111
1202
|
(i64.eq (local.get $vb) (i64.const ${NULL_NAN})))
|
|
1112
1203
|
(then (local.set $v (f64.const 0))))))
|
|
1204
|
+
${ctx.features.f16 ? `(if (i32.and (local.get $aux) (i32.const 32)) (then
|
|
1205
|
+
(i32.store16 (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1)))
|
|
1206
|
+
(call $__f64_to_f16 (local.get $v)))
|
|
1207
|
+
(return (local.get $v))))` : ''}
|
|
1208
|
+
${ctx.features.clamped ? `(if (i32.and (local.get $aux) (i32.const 64)) (then
|
|
1209
|
+
(i32.store8 (i32.add (local.get $off) (local.get $i))
|
|
1210
|
+
(call $__u8_clamp (local.get $v)))
|
|
1211
|
+
(return (local.get $v))))` : ''}
|
|
1113
1212
|
(if (i32.eq (local.get $et) (i32.const 7))
|
|
1114
1213
|
(then (f64.store (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))) (local.get $v)))
|
|
1115
1214
|
(else
|
|
@@ -1150,7 +1249,7 @@ export default (ctx) => {
|
|
|
1150
1249
|
// Integers convert by signedness (et&1 ⇒ unsigned); BigInt returns the raw i64 bits
|
|
1151
1250
|
// reinterpreted as f64 so a get→set roundtrip is bit-exact (set_idx stores the bits
|
|
1152
1251
|
// back unchanged). Used by the in-place algorithms below for random-access reads.
|
|
1153
|
-
ctx.core.stdlib['__typed_get_idx'] = `(func $__typed_get_idx (param $ptr i64) (param $i i32) (result f64)
|
|
1252
|
+
ctx.core.stdlib['__typed_get_idx'] = () => `(func $__typed_get_idx (param $ptr i64) (param $i i32) (result f64)
|
|
1154
1253
|
(local $off i32) (local $aux i32) (local $et i32)
|
|
1155
1254
|
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
1156
1255
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
@@ -1168,9 +1267,11 @@ export default (ctx) => {
|
|
|
1168
1267
|
(then (f64.convert_i32_u (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
|
|
1169
1268
|
(else (f64.convert_i32_s (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
1170
1269
|
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 2))
|
|
1171
|
-
(then (if (result f64) (i32.and (local.get $
|
|
1270
|
+
(then ${ctx.features.f16 ? `(if (result f64) (i32.and (local.get $aux) (i32.const 32))
|
|
1271
|
+
(then (call $__f16_to_f64 (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
|
|
1272
|
+
(else ` : ''}(if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
1172
1273
|
(then (f64.convert_i32_u (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
|
|
1173
|
-
(else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))))
|
|
1274
|
+
(else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1)))))))${ctx.features.f16 ? '))' : ''})
|
|
1174
1275
|
(else (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
1175
1276
|
(then (f64.convert_i32_u (i32.load8_u (i32.add (local.get $off) (local.get $i)))))
|
|
1176
1277
|
(else (f64.convert_i32_s (i32.load8_s (i32.add (local.get $off) (local.get $i)))))))))))))))))`
|
|
@@ -1339,16 +1440,57 @@ export default (ctx) => {
|
|
|
1339
1440
|
// ADJACENT HEAP or trapped past memory (the Root F silent-corruption class).
|
|
1340
1441
|
// `i32.lt_u` folds the negative case in (a negative i32 is a huge u32). The
|
|
1341
1442
|
// checked result is number|undefined, so it carries NO valKind tag.
|
|
1443
|
+
// Elem-count for a checked site. A TypedArray's length is immutable, so for
|
|
1444
|
+
// a stable PARAM receiver (readable at entry, never reassigned in the body)
|
|
1445
|
+
// the shifted count materializes ONCE as a function-entry local shared by
|
|
1446
|
+
// every checked read/write guard of that receiver — the size-tier
|
|
1447
|
+
// complement of the speed tier's loop hoists (drained by collectParamInits,
|
|
1448
|
+
// the probeHoist mechanism). Non-params (mid-body inits) and reassigned
|
|
1449
|
+
// names keep the inline header load.
|
|
1450
|
+
const leanLen = (arr, et, isView) => {
|
|
1451
|
+
const lenIR = () => ['i32.shr_u',
|
|
1452
|
+
['i32.load', isView ? typedBase(emit(arr)) : ['i32.sub', typedBase(emit(arr)), ['i32.const', 8]]],
|
|
1453
|
+
['i32.const', SHIFT[et]]]
|
|
1454
|
+
if (!ctx.transform.optimize?.leanCheckedIdx ||
|
|
1455
|
+
typeof arr !== 'string' ||
|
|
1456
|
+
!ctx.func.current?.params?.some(p => p.name === arr) ||
|
|
1457
|
+
!ctx.func.body || isReassigned(ctx.func.body, arr)) return lenIR()
|
|
1458
|
+
const memo = (ctx.func.lenHoist ??= new Map())
|
|
1459
|
+
const key = `${arr} ${SHIFT[et]}${isView ? 'v' : ''}`
|
|
1460
|
+
let h = memo.get(key)
|
|
1461
|
+
if (!h) {
|
|
1462
|
+
h = { t: tempI32('tlen'), init: lenIR() }
|
|
1463
|
+
memo.set(key, h)
|
|
1464
|
+
}
|
|
1465
|
+
return ['local.get', `$${h.t}`]
|
|
1466
|
+
}
|
|
1342
1467
|
ctx.core.emit['.typed:[]'] = (arr, i) => {
|
|
1343
1468
|
const r = resolveElem(arr)
|
|
1344
1469
|
if (r == null) return null // unknown type, fallback to generic
|
|
1345
1470
|
const { et, isView, isBigInt } = r
|
|
1346
1471
|
const proven = typedIdxProven(arr, i)
|
|
1347
|
-
const loadOf = (off) =>
|
|
1348
|
-
: et === 7 ? ['f64.load', off]
|
|
1349
|
-
: et === 6 ? ['f64.promote_f32', ['f32.load', off]]
|
|
1350
|
-
: [(et & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', [LOAD[et], off]]
|
|
1472
|
+
const loadOf = (off) => elemLoadIR(r, off)
|
|
1351
1473
|
if (!proven) {
|
|
1474
|
+
// (A $__typed_idx call per site was tried for the size tier and REVERTED:
|
|
1475
|
+
// the helper + its __len/__ptr_offset chain cost ~+900 B while these
|
|
1476
|
+
// kernels have 1-3 unproven sites — inline wins until many sites share it.)
|
|
1477
|
+
// Size tier: IF-form checked read — guard hits load DIRECTLY (no address
|
|
1478
|
+
// clamp, no select pair), guard misses yield undefined. ~6 ops/site
|
|
1479
|
+
// leaner than the branchless form below, whose only reason is keeping
|
|
1480
|
+
// SPEED-tier kernel bodies branch-free for the SIMD lift (off at -Os).
|
|
1481
|
+
if (ctx.transform.optimize?.leanCheckedIdx) {
|
|
1482
|
+
const ti = tempI32('tbi')
|
|
1483
|
+
const off = ['i32.add', typedDataAddr(emit(arr), isView),
|
|
1484
|
+
['i32.shl', ['local.get', `$${ti}`], ['i32.const', SHIFT[et]]]]
|
|
1485
|
+
const rd = typed(['if', ['result', 'f64'],
|
|
1486
|
+
['i32.lt_u', ['local.tee', `$${ti}`, idx(i)], leanLen(arr, et, isView)],
|
|
1487
|
+
['then', loadOf(off)],
|
|
1488
|
+
['else', undefExpr()]], 'f64')
|
|
1489
|
+
// number|undefined with the undefined confined to a CONST arm — a numeric
|
|
1490
|
+
// consumer (toNumF64) folds ToNumber into that arm statically
|
|
1491
|
+
if (!isBigInt) rd.checkedNumRead = true
|
|
1492
|
+
return rd
|
|
1493
|
+
}
|
|
1352
1494
|
// BRANCHLESS checked read: `select(load(in ? idx : 0), undefined, in)`. The
|
|
1353
1495
|
// address clamp makes the load unconditionally safe (index 0 of the data
|
|
1354
1496
|
// region is mapped arena even for a 0-length array — the loaded value is
|
|
@@ -1364,10 +1506,12 @@ export default (ctx) => {
|
|
|
1364
1506
|
const off = ['i32.add', typedDataAddr(emit(arr), isView),
|
|
1365
1507
|
['i32.shl', ['select', ['local.get', `$${ti}`], ['i32.const', 0], ['local.get', `$${tin}`]],
|
|
1366
1508
|
['i32.const', SHIFT[et]]]]
|
|
1367
|
-
|
|
1509
|
+
const rd = typed(['block', ['result', 'f64'],
|
|
1368
1510
|
['local.set', `$${ti}`, idx(i)],
|
|
1369
1511
|
['local.set', `$${tin}`, ['i32.lt_u', ['local.get', `$${ti}`], lenIR]],
|
|
1370
1512
|
['select', loadOf(off), undefExpr(), ['local.get', `$${tin}`]]], 'f64')
|
|
1513
|
+
if (!isBigInt) rd.checkedNumRead = true
|
|
1514
|
+
return rd
|
|
1371
1515
|
}
|
|
1372
1516
|
const objIR = emit(arr), vi = idx(i)
|
|
1373
1517
|
const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
|
|
@@ -1377,6 +1521,25 @@ export default (ctx) => {
|
|
|
1377
1521
|
const t = typed(loadOf(off), 'f64'); t.valKind = VAL.NUMBER; return t
|
|
1378
1522
|
}
|
|
1379
1523
|
|
|
1524
|
+
// A store value that can evaluate INSIDE the bounds guard when the assignment
|
|
1525
|
+
// is a statement (void): pure reads/arithmetic — including a checked-read
|
|
1526
|
+
// if-form and compiler-owned tees (the ${'$'}+T temp namespace; their writes are
|
|
1527
|
+
// site-private) — but never calls or writes to user-visible names. Spec-wise
|
|
1528
|
+
// the RHS evaluates regardless of bounds; for a PURE value that is
|
|
1529
|
+
// unobservable, and dropping the temp+set per site is the -Os win on codec
|
|
1530
|
+
// kernels (out[op+k] = pure-int-expr).
|
|
1531
|
+
const PURE_STORE_OP = /^(i32|i64|f32|f64)\.(load(8_[su]|16_[su]|32_[su])?|const|add|sub|mul|div(_[su])?|and|or|xor|shl|shr_[su]|rotl|rotr|eqz|eq|ne|[lg][te](_[su])?|clz|ctz|popcnt|convert_i(32|64)_[su]|promote_f32|demote_f64|reinterpret_(i32|i64|f32|f64)|trunc_sat_f(32|64)_[su]|wrap_i64|extend(8|16|32)?_(s|i32_[su])|neg|abs|min|max|sqrt|ceil|floor|trunc|nearest|copysign)$/
|
|
1532
|
+
const pureStorable = (n) => {
|
|
1533
|
+
if (!Array.isArray(n)) return true
|
|
1534
|
+
const op = n[0]
|
|
1535
|
+
if (op === 'local.get') return typeof n[1] === 'string'
|
|
1536
|
+
if (op === 'local.tee') return typeof n[1] === 'string' && n[1].startsWith('$' + T) && pureStorable(n[2])
|
|
1537
|
+
if (op === 'select' || op === 'if' || op === 'block' || op === 'then' || op === 'else' || op === 'result')
|
|
1538
|
+
return n.slice(1).every(pureStorable)
|
|
1539
|
+
if (PURE_STORE_OP.test(op)) return n.slice(1).every(pureStorable)
|
|
1540
|
+
return false
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1380
1543
|
// Type-aware TypedArray write: arr[i] = val. Same proof gate as the read: proven
|
|
1381
1544
|
// indexes keep the direct unchecked store byte-identical; unproven ones evaluate
|
|
1382
1545
|
// the RHS (its effects and the assignment's value are unconditional per spec),
|
|
@@ -1399,32 +1562,65 @@ export default (ctx) => {
|
|
|
1399
1562
|
// Wrap a store statement in the bounds guard on the unproven path. The value
|
|
1400
1563
|
// temp is set OUTSIDE the guard (spec: RHS evaluates regardless).
|
|
1401
1564
|
const guard = (store) => proven ? store
|
|
1402
|
-
: ['if', ['i32.lt_u', vi, ['
|
|
1403
|
-
|
|
1404
|
-
['i32.const', SHIFT[et]]]], ['then', store]]
|
|
1405
|
-
const objIR = emit(arr), valIR = emit(val)
|
|
1565
|
+
: ['if', ['i32.lt_u', vi, leanLen(arr, et, isView)], ['then', store]]
|
|
1566
|
+
const objIR = emit(arr); let valIR = emit(val)
|
|
1406
1567
|
const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
|
|
1407
|
-
if (
|
|
1568
|
+
if (r.isF16 || r.isClamped) {
|
|
1569
|
+
// conversion is not a truncation — always through the kernel (RTNE /
|
|
1570
|
+
// ToUint8Clamp); the i32Backed shortcut below would store raw low bits
|
|
1408
1571
|
const vt = temp('tw')
|
|
1572
|
+
const conv = elemStoreIR(r, off, ['local.get', `$${vt}`])
|
|
1409
1573
|
return typed(void_ ? ['block', ...pre,
|
|
1410
1574
|
['local.set', `$${vt}`, asF64(valIR)],
|
|
1411
|
-
guard(
|
|
1575
|
+
guard(conv)]
|
|
1412
1576
|
: ['block', ['result', 'f64'], ...pre,
|
|
1413
1577
|
['local.set', `$${vt}`, asF64(valIR)],
|
|
1414
|
-
guard(
|
|
1578
|
+
guard(conv),
|
|
1415
1579
|
['local.get', `$${vt}`]], void_ ? 'void' : 'f64')
|
|
1416
1580
|
}
|
|
1417
|
-
if (
|
|
1581
|
+
if (isBigInt) {
|
|
1582
|
+
if (void_ && ctx.transform.optimize?.leanCheckedIdx && pureStorable(valIR)) return typed(['block', ...pre,
|
|
1583
|
+
guard(['i64.store', off, ['i64.reinterpret_f64', asF64(valIR)]])], 'void')
|
|
1418
1584
|
const vt = temp('tw')
|
|
1419
1585
|
return typed(void_ ? ['block', ...pre,
|
|
1420
1586
|
['local.set', `$${vt}`, asF64(valIR)],
|
|
1421
|
-
guard(['
|
|
1587
|
+
guard(['i64.store', off, ['i64.reinterpret_f64', ['local.get', `$${vt}`]]])]
|
|
1422
1588
|
: ['block', ['result', 'f64'], ...pre,
|
|
1423
1589
|
['local.set', `$${vt}`, asF64(valIR)],
|
|
1424
|
-
guard(['
|
|
1425
|
-
['local.get', `$${vt}`]], void_ ? 'void' : 'f64')
|
|
1590
|
+
guard(['i64.store', off, ['i64.reinterpret_f64', ['local.get', `$${vt}`]]]),
|
|
1591
|
+
['local.get', `$${vt}`]], void_ ? 'void' : 'f64')
|
|
1592
|
+
}
|
|
1593
|
+
if (et === 7) { // Float64Array
|
|
1594
|
+
// ToNumber on the STORED value (spec: TypedArray stores coerce). Via
|
|
1595
|
+
// toNumF64, so a provably-numeric RHS keeps the raw store byte-identical
|
|
1596
|
+
// and a checked-read RHS folds its sentinel arm statically — raw sentinel
|
|
1597
|
+
// BITS in an f64 slot read back as `undefined` at the boundary where JS
|
|
1598
|
+
// stores (and reads back) NaN. The assignment's own VALUE (non-void
|
|
1599
|
+
// result) is the RHS pre-coercion per spec, so that path coerces a COPY
|
|
1600
|
+
// at the store only (nullish-canon on the temp — no strings can hide in
|
|
1601
|
+
// a no-__to_num program, so the sentinel canon IS full ToNumber there).
|
|
1602
|
+
const stored = toNumF64(val, valIR)
|
|
1603
|
+
if (void_) {
|
|
1604
|
+
if (ctx.transform.optimize?.leanCheckedIdx && pureStorable(stored)) return typed(['block', ...pre,
|
|
1605
|
+
guard(['f64.store', off, asF64(stored)])], 'void')
|
|
1606
|
+
const vt = temp('tw')
|
|
1607
|
+
return typed(['block', ...pre,
|
|
1608
|
+
['local.set', `$${vt}`, asF64(stored)],
|
|
1609
|
+
guard(['f64.store', off, ['local.get', `$${vt}`]])], 'void')
|
|
1610
|
+
}
|
|
1611
|
+
const vt = temp('tw')
|
|
1612
|
+
const reread = typed(['local.get', `$${vt}`], 'f64')
|
|
1613
|
+
const storeV = stored === valIR ? reread
|
|
1614
|
+
: ctx.core.stdlib['__to_num'] ? toNumF64(val, reread)
|
|
1615
|
+
: coerceNullishToNum(reread)
|
|
1616
|
+
return typed(['block', ['result', 'f64'], ...pre,
|
|
1617
|
+
['local.set', `$${vt}`, asF64(valIR)],
|
|
1618
|
+
guard(['f64.store', off, asF64(storeV)]),
|
|
1619
|
+
['local.get', `$${vt}`]], 'f64')
|
|
1426
1620
|
}
|
|
1427
1621
|
if (et === 6) {
|
|
1622
|
+
if (void_ && ctx.transform.optimize?.leanCheckedIdx && pureStorable(valIR)) return typed(['block', ...pre,
|
|
1623
|
+
guard(['f32.store', off, ['f32.demote_f64', asF64(valIR)]])], 'void')
|
|
1428
1624
|
const vt = temp('tw')
|
|
1429
1625
|
return typed(void_ ? ['block', ...pre,
|
|
1430
1626
|
['local.set', `$${vt}`, asF64(valIR)],
|
|
@@ -1442,13 +1638,32 @@ export default (ctx) => {
|
|
|
1442
1638
|
// and `dst[i] = src[j]` (base64, qoi, wav, blur) — where both sides are integer elements.
|
|
1443
1639
|
// `store8/16` mask the low bits, so storing the convert's i32 source is bit-identical; the
|
|
1444
1640
|
// non-void result reboxes that i32 to f64 (the assignment's RHS value, in element range here).
|
|
1641
|
+
// A lean checked READ as the store value (out[op] = src[i] — the codec
|
|
1642
|
+
// byte-transform class): ToInt32 composes through it — the hit arm's
|
|
1643
|
+
// convert peels to the raw i32 load, the undefined miss arm is NaN → 0.
|
|
1644
|
+
// Rebuilding as an i32 if-form keeps the store on the integer path
|
|
1645
|
+
// (no f64 round-trip, no temp) — the -Os pairing of the two emitters.
|
|
1646
|
+
if (Array.isArray(valIR) && valIR[0] === 'if' &&
|
|
1647
|
+
Array.isArray(valIR[1]) && valIR[1][1] === 'f64' &&
|
|
1648
|
+
Array.isArray(valIR[3]) && valIR[3][0] === 'then' &&
|
|
1649
|
+
Array.isArray(valIR[3][1]) && (valIR[3][1][0] === 'f64.convert_i32_s' || valIR[3][1][0] === 'f64.convert_i32_u') &&
|
|
1650
|
+
Array.isArray(valIR[4]) && valIR[4][0] === 'else' &&
|
|
1651
|
+
Array.isArray(valIR[4][1]) && valIR[4][1][0] === 'f64.const' && String(valIR[4][1][1]).startsWith('nan:')) {
|
|
1652
|
+
valIR = typed(['if', ['result', 'i32'], valIR[2],
|
|
1653
|
+
['then', valIR[3][1][1]],
|
|
1654
|
+
['else', ['i32.const', 0]]], 'i32')
|
|
1655
|
+
}
|
|
1445
1656
|
const i32Backed = valIR.type === 'i32' ||
|
|
1446
1657
|
(Array.isArray(valIR) && (valIR[0] === 'f64.convert_i32_s' || valIR[0] === 'f64.convert_i32_u'))
|
|
1447
1658
|
if (i32Backed) {
|
|
1448
1659
|
const vi32 = asI32(valIR)
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1660
|
+
// lean (-Os) widens "cheap" to any pure value (inline into the guard, no
|
|
1661
|
+
// temp); SPEED tiers keep the narrow form — the temp'd store is the shape
|
|
1662
|
+
// the SIMD widening/in-place recognizers pattern-match (battery-caught).
|
|
1663
|
+
const cheap = ctx.transform.optimize?.leanCheckedIdx ? pureStorable(vi32)
|
|
1664
|
+
: Array.isArray(vi32) &&
|
|
1665
|
+
((vi32[0] === 'local.get' && typeof vi32[1] === 'string') ||
|
|
1666
|
+
(vi32[0] === 'i32.const' && (typeof vi32[1] === 'number' || typeof vi32[1] === 'string')))
|
|
1452
1667
|
if (void_ && cheap && proven) return typed([STORE[et], off, vi32], 'void')
|
|
1453
1668
|
if (void_ && cheap) return typed(['block', ...pre, guard([STORE[et], off, vi32])], 'void')
|
|
1454
1669
|
const v32 = tempI32('tw')
|
|
@@ -1493,9 +1708,7 @@ export default (ctx) => {
|
|
|
1493
1708
|
if (r) {
|
|
1494
1709
|
const { et } = r
|
|
1495
1710
|
const addr = ['i32.add', ['local.get', `$${dst}`], ['i32.shl', idx, ['i32.const', SHIFT[et]]]]
|
|
1496
|
-
store =
|
|
1497
|
-
: et === 6 ? ['f32.store', addr, ['f32.demote_f64', val]]
|
|
1498
|
-
: [STORE[et], addr, [(et & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s', val]]
|
|
1711
|
+
store = elemStoreIR(r, addr, val)
|
|
1499
1712
|
} else {
|
|
1500
1713
|
store = ['drop', ['call', '$__typed_set_idx', ['i64.reinterpret_f64', ['local.get', `$${dst}`]], idx, val]]
|
|
1501
1714
|
}
|
|
@@ -1522,9 +1735,7 @@ export default (ctx) => {
|
|
|
1522
1735
|
const r = resolveElem(arr)
|
|
1523
1736
|
const elemType = r?.et
|
|
1524
1737
|
const isView = r?.isView
|
|
1525
|
-
const elemName = r
|
|
1526
|
-
? (elemType === 7 ? 'BigInt64Array' : 'BigUint64Array') // unreachable: SIMD path gated below
|
|
1527
|
-
: ['Int8Array','Uint8Array','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array'][elemType])
|
|
1738
|
+
const elemName = r?.name
|
|
1528
1739
|
|
|
1529
1740
|
// BigInt typed arrays: SIMD path doesn't support them; defer to scalar map.
|
|
1530
1741
|
if (r?.isBigInt) {
|
|
@@ -1533,8 +1744,9 @@ export default (ctx) => {
|
|
|
1533
1744
|
return null
|
|
1534
1745
|
}
|
|
1535
1746
|
|
|
1536
|
-
// Try SIMD: inline arrow with recognizable pattern
|
|
1537
|
-
|
|
1747
|
+
// Try SIMD: inline arrow with recognizable pattern (f16/clamped decline —
|
|
1748
|
+
// their element conversion is a call, not a lane op)
|
|
1749
|
+
if (elemType != null && !r.isF16 && !r.isClamped && Array.isArray(fn) && fn[0] === '=>') {
|
|
1538
1750
|
const [, rawParam, body] = fn
|
|
1539
1751
|
const param = Array.isArray(rawParam) && rawParam[0] === '()' ? rawParam[1] : rawParam
|
|
1540
1752
|
const pattern = analyzeSimd(body, param)
|
|
@@ -1562,15 +1774,11 @@ export default (ctx) => {
|
|
|
1562
1774
|
|
|
1563
1775
|
const loadElem = () => {
|
|
1564
1776
|
const off = ['i32.add', ['local.get', `$${ptr}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', shift]]]
|
|
1565
|
-
|
|
1566
|
-
if (elemType === 6) return typed(['f64.promote_f32', ['f32.load', off]], 'f64')
|
|
1567
|
-
return typed([(elemType & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', [LOAD[elemType], off]], 'f64')
|
|
1777
|
+
return typed(elemLoadIR(r, off), 'f64')
|
|
1568
1778
|
}
|
|
1569
1779
|
const storeElem = (val) => {
|
|
1570
1780
|
const off = ['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', shift]]]
|
|
1571
|
-
|
|
1572
|
-
if (elemType === 6) return ['f32.store', off, ['f32.demote_f64', val]]
|
|
1573
|
-
return [STORE[elemType], off, [(elemType & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s', val]]
|
|
1781
|
+
return elemStoreIR(r, off, val)
|
|
1574
1782
|
}
|
|
1575
1783
|
|
|
1576
1784
|
const id = ctx.func.uniq++
|
|
@@ -1618,9 +1826,7 @@ export default (ctx) => {
|
|
|
1618
1826
|
inc('__len')
|
|
1619
1827
|
const loadElem = () => {
|
|
1620
1828
|
const off = ['i32.add', ['local.get', `$${ptr}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', SHIFT[et]]]]
|
|
1621
|
-
|
|
1622
|
-
if (et === 6) return typed(['f64.promote_f32', ['f32.load', off]], 'f64')
|
|
1623
|
-
return typed([(et & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', [LOAD[et], off]], 'f64')
|
|
1829
|
+
return typed(elemLoadIR(r, off), 'f64')
|
|
1624
1830
|
}
|
|
1625
1831
|
const setup = [
|
|
1626
1832
|
['local.set', `$${ptr}`, typedDataAddr(asF64(va), isView)],
|
|
@@ -1904,17 +2110,13 @@ export default (ctx) => {
|
|
|
1904
2110
|
inc('__len')
|
|
1905
2111
|
const loadAt = (ptrLoc, iLoc) => {
|
|
1906
2112
|
const off = ['i32.add', ['local.get', `$${ptrLoc}`], ['i32.shl', ['local.get', `$${iLoc}`], ['i32.const', SHIFT[et]]]]
|
|
1907
|
-
|
|
1908
|
-
if (et === 6) return typed(['f64.promote_f32', ['f32.load', off]], 'f64')
|
|
1909
|
-
return typed([(et & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', [LOAD[et], off]], 'f64')
|
|
2113
|
+
return typed(elemLoadIR(r, off), 'f64')
|
|
1910
2114
|
}
|
|
1911
2115
|
const storeAt = (ptrLoc, iLoc, valF64) => {
|
|
1912
2116
|
const off = ['i32.add', ['local.get', `$${ptrLoc}`], ['i32.shl', ['local.get', `$${iLoc}`], ['i32.const', SHIFT[et]]]]
|
|
1913
|
-
|
|
1914
|
-
if (et === 6) return ['f32.store', off, ['f32.demote_f64', valF64]]
|
|
1915
|
-
return [STORE[et], off, [(et & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s', valF64]]
|
|
2117
|
+
return elemStoreIR(r, off, valF64)
|
|
1916
2118
|
}
|
|
1917
|
-
const dst = allocPtr({ type: PTR.TYPED, aux: typedAux(ET_NAME[et]),
|
|
2119
|
+
const dst = allocPtr({ type: PTR.TYPED, aux: typedAux(r.name || ET_NAME[et]),
|
|
1918
2120
|
len: ['i32.shl', ['local.get', `$${maxLen}`], ['i32.const', SHIFT[et]]],
|
|
1919
2121
|
stride: 1, tag: 'tfd' })
|
|
1920
2122
|
const id = ctx.func.uniq++
|
|
@@ -1987,7 +2189,7 @@ export default (ctx) => {
|
|
|
1987
2189
|
['i32.gt_s', ['local.get', `$${idx}`], ['local.get', `$${srcLen}`]]],
|
|
1988
2190
|
['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]]]]
|
|
1989
2191
|
}
|
|
1990
|
-
const dst = allocPtr({ type: PTR.TYPED, aux: typedAux(ET_NAME[et]),
|
|
2192
|
+
const dst = allocPtr({ type: PTR.TYPED, aux: typedAux(r.name || ET_NAME[et]),
|
|
1991
2193
|
len: ['i32.shl', ['local.get', `$${n}`], ['i32.const', SHIFT[et]]],
|
|
1992
2194
|
stride: 1, tag: 'tsd' })
|
|
1993
2195
|
return typed(['block', ['result', 'f64'],
|
|
@@ -2105,4 +2307,112 @@ export default (ctx) => {
|
|
|
2105
2307
|
['i32.store', ['i32.add', ['local.get', `$${desc}`], ['i32.const', 8]], ['local.get', `$${root}`]],
|
|
2106
2308
|
mkPtrIR(PTR.TYPED, viewAux, ['local.get', `$${desc}`])], 'f64')
|
|
2107
2309
|
}
|
|
2310
|
+
|
|
2311
|
+
// === ES2026 Uint8Array base64/hex codecs ===
|
|
2312
|
+
// Kernels live in module/string.js (byte↔text is string domain); these
|
|
2313
|
+
// emitters wire the typed-array surface: u8.toBase64()/toHex()/setFrom*()
|
|
2314
|
+
// and the Uint8Array.fromBase64/fromHex statics. Options are compile-time
|
|
2315
|
+
// literals (jz doctrine: no per-call option-bag parsing) — alphabet
|
|
2316
|
+
// 'base64'|'base64url', omitPadding, and lastChunkHandling:'loose' (the
|
|
2317
|
+
// default; other modes are rejected with a clean error).
|
|
2318
|
+
const codecOpts = (node, method, allowPad) => {
|
|
2319
|
+
let url = 0, pad = 1
|
|
2320
|
+
if (node === undefined) return { url, pad }
|
|
2321
|
+
if (!Array.isArray(node) || (node[0] !== '{' && node[0] !== '{}'))
|
|
2322
|
+
err(`${method} options must be a literal object — jz resolves codec options at compile time`)
|
|
2323
|
+
// prepared literals arrive as ['{}'|'{', ...entries] (entries may also ride
|
|
2324
|
+
// a single ','/';' wrapper node)
|
|
2325
|
+
const entries = node.length === 2 && Array.isArray(node[1]) && (node[1][0] === ',' || node[1][0] === ';')
|
|
2326
|
+
? node[1].slice(1) : node.slice(1).filter(e => e != null)
|
|
2327
|
+
const lit = (v) => Array.isArray(v) && v[0] === 'str' ? v[1]
|
|
2328
|
+
: Array.isArray(v) && v[0] === 'bool' ? !!v[1]
|
|
2329
|
+
: Array.isArray(v) && v[0] == null ? v[1] : undefined
|
|
2330
|
+
for (const it of entries) {
|
|
2331
|
+
if (!Array.isArray(it) || it[0] !== ':') err(`${method} options must use literal keys and values`)
|
|
2332
|
+
const k = typeof it[1] === 'string' ? it[1] : Array.isArray(it[1]) && it[1][0] === 'str' ? it[1][1] : null
|
|
2333
|
+
const v = lit(it[2])
|
|
2334
|
+
if (k === 'alphabet') {
|
|
2335
|
+
if (v === 'base64url') url = 1
|
|
2336
|
+
else if (v !== 'base64') err(`${method}: unknown alphabet ${JSON.stringify(v)} — 'base64' or 'base64url'`)
|
|
2337
|
+
} else if (k === 'omitPadding' && allowPad) pad = v ? 0 : 1
|
|
2338
|
+
else if (k === 'lastChunkHandling') {
|
|
2339
|
+
if (v !== 'loose') err(`${method}: only lastChunkHandling:'loose' (the default) is supported`)
|
|
2340
|
+
} else err(`${method}: unsupported option '${k}'`)
|
|
2341
|
+
}
|
|
2342
|
+
return { url, pad }
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// {read, written} result record for setFromBase64/setFromHex — a one-shot
|
|
2346
|
+
// dictionary (HASH) object, same shape TextEncoder.encodeInto returns.
|
|
2347
|
+
const readWrittenHash = (rwIR) => {
|
|
2348
|
+
inc('__hash_new', '__hash_set')
|
|
2349
|
+
const rw = tempI64('sfrw'), h = temp('sfh')
|
|
2350
|
+
const hI64 = ['i64.reinterpret_f64', ['local.get', `$${h}`]]
|
|
2351
|
+
const field = (hi) => ['i64.reinterpret_f64', ['f64.convert_i32_s',
|
|
2352
|
+
['i32.wrap_i64', hi ? ['i64.shr_u', ['local.get', `$${rw}`], ['i64.const', 32]] : ['local.get', `$${rw}`]]]]
|
|
2353
|
+
return typed(['block', ['result', 'f64'],
|
|
2354
|
+
['local.set', `$${rw}`, rwIR],
|
|
2355
|
+
['local.set', `$${h}`, ['call', '$__hash_new']],
|
|
2356
|
+
['local.set', `$${h}`, ['f64.reinterpret_i64',
|
|
2357
|
+
['call', '$__hash_set', hI64, asI64(emit(['str', 'read'])), field(true)]]],
|
|
2358
|
+
['local.set', `$${h}`, ['f64.reinterpret_i64',
|
|
2359
|
+
['call', '$__hash_set', hI64, asI64(emit(['str', 'written'])), field(false)]]],
|
|
2360
|
+
['local.get', `$${h}`]], 'f64')
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
const emitToBase64 = (arr, opts) => {
|
|
2364
|
+
const { url, pad } = codecOpts(opts, 'toBase64', true)
|
|
2365
|
+
ctx.runtime.throws = true
|
|
2366
|
+
inc('__u8_data', '__b64_enc', '__len')
|
|
2367
|
+
const t = temp('tb64')
|
|
2368
|
+
return typed(['block', ['result', 'f64'],
|
|
2369
|
+
['local.set', `$${t}`, asF64(emit(arr))],
|
|
2370
|
+
['call', '$__b64_enc',
|
|
2371
|
+
['call', '$__u8_data', ['i64.reinterpret_f64', ['local.get', `$${t}`]]],
|
|
2372
|
+
['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]],
|
|
2373
|
+
['i32.const', url], ['i32.const', pad]]], 'f64')
|
|
2374
|
+
}
|
|
2375
|
+
ctx.core.emit['.typed:toBase64'] = ctx.core.emit['.toBase64'] = emitToBase64
|
|
2376
|
+
|
|
2377
|
+
const emitToHex = (arr) => {
|
|
2378
|
+
ctx.runtime.throws = true
|
|
2379
|
+
inc('__u8_data', '__hex_enc', '__len')
|
|
2380
|
+
const t = temp('thex')
|
|
2381
|
+
return typed(['block', ['result', 'f64'],
|
|
2382
|
+
['local.set', `$${t}`, asF64(emit(arr))],
|
|
2383
|
+
['call', '$__hex_enc',
|
|
2384
|
+
['call', '$__u8_data', ['i64.reinterpret_f64', ['local.get', `$${t}`]]],
|
|
2385
|
+
['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
2386
|
+
}
|
|
2387
|
+
ctx.core.emit['.typed:toHex'] = ctx.core.emit['.toHex'] = emitToHex
|
|
2388
|
+
|
|
2389
|
+
const emitSetFromBase64 = (arr, str, opts) => {
|
|
2390
|
+
const { url } = codecOpts(opts, 'setFromBase64', false)
|
|
2391
|
+
ctx.runtime.throws = true
|
|
2392
|
+
inc('__b64_set')
|
|
2393
|
+
return readWrittenHash(['call', '$__b64_set', asI64(emit(arr)), asI64(emit(str)), ['i32.const', url]])
|
|
2394
|
+
}
|
|
2395
|
+
ctx.core.emit['.typed:setFromBase64'] = ctx.core.emit['.setFromBase64'] = emitSetFromBase64
|
|
2396
|
+
|
|
2397
|
+
const emitSetFromHex = (arr, str) => {
|
|
2398
|
+
ctx.runtime.throws = true
|
|
2399
|
+
inc('__hex_set')
|
|
2400
|
+
return readWrittenHash(['call', '$__hex_set', asI64(emit(arr)), asI64(emit(str))])
|
|
2401
|
+
}
|
|
2402
|
+
ctx.core.emit['.typed:setFromHex'] = ctx.core.emit['.setFromHex'] = emitSetFromHex
|
|
2403
|
+
|
|
2404
|
+
ctx.core.emit['Uint8Array.fromBase64'] = (str, opts) => {
|
|
2405
|
+
const { url } = codecOpts(opts, 'fromBase64', false)
|
|
2406
|
+
ctx.runtime.throws = true
|
|
2407
|
+
ctx.features.typedarray = true
|
|
2408
|
+
inc('__b64_from')
|
|
2409
|
+
return typed(['call', '$__b64_from', asI64(emit(str)), ['i32.const', url]], 'f64')
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
ctx.core.emit['Uint8Array.fromHex'] = (str) => {
|
|
2413
|
+
ctx.runtime.throws = true
|
|
2414
|
+
ctx.features.typedarray = true
|
|
2415
|
+
inc('__hex_from')
|
|
2416
|
+
return typed(['call', '$__hex_from', asI64(emit(str))], 'f64')
|
|
2417
|
+
}
|
|
2108
2418
|
}
|