jz 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +381 -0
- package/cli.js +163 -0
- package/index.js +217 -0
- package/module/array.js +1317 -0
- package/module/collection.js +791 -0
- package/module/console.js +190 -0
- package/module/core.js +642 -0
- package/module/function.js +180 -0
- package/module/index.js +15 -0
- package/module/json.js +504 -0
- package/module/math.js +389 -0
- package/module/number.js +605 -0
- package/module/object.js +357 -0
- package/module/regex.js +913 -0
- package/module/schema.js +104 -0
- package/module/string.js +928 -0
- package/module/symbol.js +54 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +711 -0
- package/package.json +54 -5
- package/src/analyze.js +1906 -0
- package/src/compile.js +2175 -0
- package/src/ctx.js +243 -0
- package/src/emit.js +2095 -0
- package/src/host.js +524 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +391 -0
- package/src/optimize.js +1352 -0
- package/src/prepare.js +1598 -0
- package/wasi.js +74 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypedArray module — Float64Array, Float32Array, Int32Array, etc.
|
|
3
|
+
* SIMD auto-vectorization for .map() on recognized patterns.
|
|
4
|
+
*
|
|
5
|
+
* Type=3 (TYPED): aux=elemType (3 bits), length in memory header [-8:len][-4:cap].
|
|
6
|
+
*
|
|
7
|
+
* @module typed
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { emit, typed, asF64, asI32, valTypeOf, lookupValType, VAL, UNDEF_NAN, allocPtr, mkPtrIR, ptrOffsetIR, temp, tempI32 } from '../src/compile.js'
|
|
11
|
+
import { inc, PTR } from '../src/ctx.js'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// Element types and their byte sizes
|
|
15
|
+
const ELEM = {
|
|
16
|
+
Int8Array: 0, Uint8Array: 1,
|
|
17
|
+
Int16Array: 2, Uint16Array: 3,
|
|
18
|
+
Int32Array: 4, Uint32Array: 5,
|
|
19
|
+
Float32Array: 6, Float64Array: 7,
|
|
20
|
+
BigInt64Array: 7, BigUint64Array: 7,
|
|
21
|
+
}
|
|
22
|
+
const STRIDE = [1, 1, 2, 2, 4, 4, 4, 8]
|
|
23
|
+
const SHIFT = [0, 0, 1, 1, 2, 2, 2, 3]
|
|
24
|
+
const LOAD = [
|
|
25
|
+
'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u',
|
|
26
|
+
'i32.load', 'i32.load', 'f32.load', 'f64.load',
|
|
27
|
+
]
|
|
28
|
+
const STORE = [
|
|
29
|
+
'i32.store8', 'i32.store8', 'i32.store16', 'i32.store16',
|
|
30
|
+
'i32.store', 'i32.store', 'f32.store', 'f64.store',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
// SIMD: vector width per element type (elements per v128)
|
|
34
|
+
const VEC_WIDTH = [16, 16, 8, 8, 4, 4, 4, 2] // 128 bits / element bits
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// === SIMD pattern detection ===
|
|
38
|
+
|
|
39
|
+
/** Check if AST node is a constant number */
|
|
40
|
+
const isConst = node => {
|
|
41
|
+
if (typeof node === 'number') return node
|
|
42
|
+
if (Array.isArray(node) && node[0] == null && typeof node[1] === 'number') return node[1]
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Analyze callback body for SIMD-vectorizable patterns.
|
|
48
|
+
* Returns { op, val } or null.
|
|
49
|
+
*/
|
|
50
|
+
function analyzeSimd(body, param) {
|
|
51
|
+
if (!Array.isArray(body)) return null
|
|
52
|
+
const [op, ...args] = body
|
|
53
|
+
|
|
54
|
+
// Binary: x*c, x+c, x-c, x/c (and commutative)
|
|
55
|
+
if (['+', '-', '*', '/'].includes(op) && args.length === 2) {
|
|
56
|
+
const [a, b] = args
|
|
57
|
+
const isA = a === param, isB = b === param
|
|
58
|
+
const cA = !isA && isConst(a), cB = !isB && isConst(b)
|
|
59
|
+
if (op === '*' && ((isA && cB !== false) || (isB && cA !== false)))
|
|
60
|
+
return { op: 'mul', val: isA ? cB : cA }
|
|
61
|
+
if (op === '+' && ((isA && cB !== false) || (isB && cA !== false)))
|
|
62
|
+
return { op: 'add', val: isA ? cB : cA }
|
|
63
|
+
if (op === '-' && isA && cB !== false) return { op: 'sub', val: cB }
|
|
64
|
+
if (op === '/' && isA && cB !== false) return { op: 'div', val: cB }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Bitwise: x&c, x|c, x^c, x<<c, x>>c, x>>>c
|
|
68
|
+
if (['&', '|', '^', '<<', '>>', '>>>'].includes(op) && args.length === 2) {
|
|
69
|
+
const [a, b] = args
|
|
70
|
+
if (a === param && isConst(b) !== false) {
|
|
71
|
+
const ops = { '&': 'and', '|': 'or', '^': 'xor', '<<': 'shl', '>>': 'shr', '>>>': 'shru' }
|
|
72
|
+
return { op: ops[op], val: isConst(b) }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Unary minus: ['u-', param]
|
|
77
|
+
if (op === 'u-' && args[0] === param) return { op: 'neg' }
|
|
78
|
+
|
|
79
|
+
// Math.abs/sqrt/ceil/floor
|
|
80
|
+
if (op === '()' && typeof args[0] === 'string' && args[0].startsWith('math.')) {
|
|
81
|
+
const method = args[0].slice(5)
|
|
82
|
+
const fnArg = args[1]
|
|
83
|
+
if (fnArg === param && ['abs', 'sqrt', 'ceil', 'floor'].includes(method))
|
|
84
|
+
return { op: method }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
// === SIMD + scalar WAT codegen (parameterized by type prefix) ===
|
|
92
|
+
|
|
93
|
+
/** Generate SIMD v128 op. p=prefix (f64x2/f32x4/i32x4), t=const type (f64/f32/i32). */
|
|
94
|
+
const simdOp = (p, t) => (op, c) => {
|
|
95
|
+
const s = `(${p}.splat (${t}.const ${c}))`
|
|
96
|
+
const ops = {
|
|
97
|
+
mul: `${p}.mul (local.get $v) ${s}`, add: `${p}.add (local.get $v) ${s}`,
|
|
98
|
+
sub: `${p}.sub (local.get $v) ${s}`, div: `${p}.div (local.get $v) ${s}`,
|
|
99
|
+
neg: `${p}.neg (local.get $v)`, abs: `${p}.abs (local.get $v)`,
|
|
100
|
+
sqrt: `${p}.sqrt (local.get $v)`, ceil: `${p}.ceil (local.get $v)`, floor: `${p}.floor (local.get $v)`,
|
|
101
|
+
// i32-only bitwise (no-op for float prefixes since analyzeSimd won't produce these for float)
|
|
102
|
+
and: `v128.and (local.get $v) (i32x4.splat (i32.const ${c}))`,
|
|
103
|
+
or: `v128.or (local.get $v) (i32x4.splat (i32.const ${c}))`,
|
|
104
|
+
xor: `v128.xor (local.get $v) (i32x4.splat (i32.const ${c}))`,
|
|
105
|
+
shl: `i32x4.shl (local.get $v) (i32.const ${c})`, shr: `i32x4.shr_s (local.get $v) (i32.const ${c})`,
|
|
106
|
+
shru: `i32x4.shr_u (local.get $v) (i32.const ${c})`,
|
|
107
|
+
}
|
|
108
|
+
return ops[op] ? `(local.set $v (${ops[op]}))` : null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Generate scalar remainder op. t=type prefix (f64/f32/i32), v=local name. */
|
|
112
|
+
const scalarOp = (t, v) => (op, c) => {
|
|
113
|
+
const g = `(local.get $${v})`
|
|
114
|
+
const ops = {
|
|
115
|
+
mul: `(${t}.mul ${g} (${t}.const ${c}))`, add: `(${t}.add ${g} (${t}.const ${c}))`,
|
|
116
|
+
sub: `(${t}.sub ${g} (${t}.const ${c}))`, div: `(${t}.div ${g} (${t}.const ${c}))`,
|
|
117
|
+
neg: t === 'i32' ? `(i32.sub (i32.const 0) ${g})` : `(${t}.neg ${g})`,
|
|
118
|
+
abs: t === 'i32' ? `(select (i32.sub (i32.const 0) ${g}) ${g} (i32.lt_s ${g} (i32.const 0)))` : `(${t}.abs ${g})`,
|
|
119
|
+
sqrt: `(${t}.sqrt ${g})`, ceil: `(${t}.ceil ${g})`, floor: `(${t}.floor ${g})`,
|
|
120
|
+
and: `(i32.and ${g} (i32.const ${c}))`, or: `(i32.or ${g} (i32.const ${c}))`,
|
|
121
|
+
xor: `(i32.xor ${g} (i32.const ${c}))`, shl: `(i32.shl ${g} (i32.const ${c}))`,
|
|
122
|
+
shr: `(i32.shr_s ${g} (i32.const ${c}))`, shru: `(i32.shr_u ${g} (i32.const ${c}))`,
|
|
123
|
+
}
|
|
124
|
+
return ops[op]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const simdF64 = simdOp('f64x2', 'f64'), simdF32 = simdOp('f32x4', 'f32'), simdI32 = simdOp('i32x4', 'i32')
|
|
128
|
+
const scalarF64 = scalarOp('f64', 'e'), scalarF32 = scalarOp('f32', 'ef'), scalarI32 = scalarOp('i32', 'ei')
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate a SIMD map function as WAT string.
|
|
133
|
+
* Takes (src: f64) → f64, returns new typed array with transform applied.
|
|
134
|
+
*/
|
|
135
|
+
function genSimdMap(name, elemType, pattern) {
|
|
136
|
+
const { op, val: c } = pattern
|
|
137
|
+
const stride = STRIDE[elemType]
|
|
138
|
+
const shift = SHIFT[elemType]
|
|
139
|
+
const load = LOAD[elemType], store = STORE[elemType]
|
|
140
|
+
const vw = VEC_WIDTH[elemType]
|
|
141
|
+
const vBytes = vw * stride // always 16 (128 bits)
|
|
142
|
+
|
|
143
|
+
// Choose SIMD + scalar codegen by element family
|
|
144
|
+
let simdOp, scalarOp, scalarLocal, scalarLoad, scalarStore
|
|
145
|
+
if (elemType === 7) { // Float64Array
|
|
146
|
+
simdOp = simdF64(op, c); scalarOp = scalarF64(op, c)
|
|
147
|
+
scalarLocal = '(local $e f64)'; scalarLoad = 'f64.load'; scalarStore = 'f64.store'
|
|
148
|
+
} else if (elemType === 6) { // Float32Array
|
|
149
|
+
simdOp = simdF32(op, c); scalarOp = scalarF32(op, c)
|
|
150
|
+
scalarLocal = '(local $ef f32)'; scalarLoad = 'f32.load'; scalarStore = 'f32.store'
|
|
151
|
+
} else if (elemType >= 4) { // Int32Array/Uint32Array
|
|
152
|
+
simdOp = simdI32(op, c); scalarOp = scalarI32(op, c)
|
|
153
|
+
scalarLocal = '(local $ei i32)'; scalarLoad = 'i32.load'; scalarStore = 'i32.store'
|
|
154
|
+
} else return null // i8/i16/u8/u16 — no SIMD path (would need i8x16/i16x8)
|
|
155
|
+
|
|
156
|
+
if (!simdOp || !scalarOp) return null
|
|
157
|
+
|
|
158
|
+
// Scalar remainder: load element into local, then store transform result
|
|
159
|
+
const byteOff = `(i32.add (local.get $srcOff) (i32.shl (local.get $i) (i32.const ${shift})))`
|
|
160
|
+
const dstByteOff = `(i32.add (local.get $dstOff) (i32.shl (local.get $i) (i32.const ${shift})))`
|
|
161
|
+
const scalarLoadSet = elemType === 7 ? `(local.set $e (${scalarLoad} ${byteOff}))`
|
|
162
|
+
: elemType === 6 ? `(local.set $ef (${scalarLoad} ${byteOff}))`
|
|
163
|
+
: `(local.set $ei (${scalarLoad} ${byteOff}))`
|
|
164
|
+
const scalarStoreExpr = `${scalarLoadSet}\n (${store} ${dstByteOff} ${scalarOp})`
|
|
165
|
+
|
|
166
|
+
return `(func $${name} (param $src f64) (result f64)
|
|
167
|
+
(local $len i32) (local $srcOff i32) (local $dstOff i32) (local $dst i32)
|
|
168
|
+
(local $i i32) (local $simdLen i32) (local $byteOff i32)
|
|
169
|
+
(local $v v128)
|
|
170
|
+
${scalarLocal}
|
|
171
|
+
(local.set $len (call $__len (local.get $src)))
|
|
172
|
+
(local.set $srcOff (call $__typed_data (local.get $src)))
|
|
173
|
+
;; Alloc result typed array: header(8) + data. Header stores byteLen = len << ${shift}.
|
|
174
|
+
(local.set $dst (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $len) (i32.const ${shift})))))
|
|
175
|
+
(i32.store (local.get $dst) (i32.shl (local.get $len) (i32.const ${shift})))
|
|
176
|
+
(i32.store (i32.add (local.get $dst) (i32.const 4)) (i32.shl (local.get $len) (i32.const ${shift})))
|
|
177
|
+
(local.set $dstOff (i32.add (local.get $dst) (i32.const 8)))
|
|
178
|
+
;; SIMD loop: process ${vw} elements at a time
|
|
179
|
+
(local.set $simdLen (i32.and (local.get $len) (i32.const ${~(vw - 1)})))
|
|
180
|
+
(local.set $i (i32.const 0))
|
|
181
|
+
(block $sdone (loop $sloop
|
|
182
|
+
(br_if $sdone (i32.ge_u (local.get $i) (local.get $simdLen)))
|
|
183
|
+
(local.set $byteOff (i32.shl (local.get $i) (i32.const ${shift})))
|
|
184
|
+
(local.set $v (v128.load (i32.add (local.get $srcOff) (local.get $byteOff))))
|
|
185
|
+
${simdOp}
|
|
186
|
+
(v128.store (i32.add (local.get $dstOff) (local.get $byteOff)) (local.get $v))
|
|
187
|
+
(local.set $i (i32.add (local.get $i) (i32.const ${vw})))
|
|
188
|
+
(br $sloop)))
|
|
189
|
+
;; Scalar remainder
|
|
190
|
+
(block $rdone (loop $rloop
|
|
191
|
+
(br_if $rdone (i32.ge_u (local.get $i) (local.get $len)))
|
|
192
|
+
${scalarStoreExpr}
|
|
193
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
194
|
+
(br $rloop)))
|
|
195
|
+
(call $__mkptr (i32.const ${PTR.TYPED}) (i32.const ${elemType}) (local.get $dstOff)))`
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
export default (ctx) => {
|
|
200
|
+
Object.assign(ctx.core.stdlibDeps, {
|
|
201
|
+
__byte_length: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
202
|
+
__byte_offset: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
203
|
+
__to_buffer: ['__ptr_type', '__ptr_offset', '__ptr_aux', '__mkptr'],
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// .map/.filter invoke callbacks with arity 1 internally.
|
|
207
|
+
ctx.closure.floor = Math.max(ctx.closure.floor ?? 0, 1)
|
|
208
|
+
|
|
209
|
+
inc('__mkptr', '__alloc', '__len')
|
|
210
|
+
|
|
211
|
+
// === Runtime helpers: byte length, buffer coerce ===
|
|
212
|
+
// __typed_shift lives in core (needed by __len/__cap).
|
|
213
|
+
|
|
214
|
+
// __byte_length(ptr) — byte size for BUFFER/TYPED; 0 otherwise.
|
|
215
|
+
// BUFFER and owned TYPED store byteLen at [-8]. TYPED view (aux bit 3) stores byteLen
|
|
216
|
+
// at descriptor[0].
|
|
217
|
+
ctx.core.stdlib['__byte_length'] = `(func $__byte_length (param $ptr f64) (result i32)
|
|
218
|
+
(local $t i32) (local $off i32)
|
|
219
|
+
(local.set $t (call $__ptr_type (local.get $ptr)))
|
|
220
|
+
(if (result i32)
|
|
221
|
+
(i32.or
|
|
222
|
+
(i32.eq (local.get $t) (i32.const ${PTR.BUFFER}))
|
|
223
|
+
(i32.eq (local.get $t) (i32.const ${PTR.TYPED})))
|
|
224
|
+
(then
|
|
225
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
226
|
+
(if (result i32)
|
|
227
|
+
(i32.and
|
|
228
|
+
(i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
|
|
229
|
+
(i32.ne (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const 8)) (i32.const 0)))
|
|
230
|
+
(then (i32.load (local.get $off)))
|
|
231
|
+
(else (i32.load (i32.sub (local.get $off) (i32.const 8))))))
|
|
232
|
+
(else (i32.const 0))))`
|
|
233
|
+
|
|
234
|
+
// __to_buffer(ptr) — return a BUFFER aliasing the same bytes (zero-copy view).
|
|
235
|
+
// BUFFER: passthrough.
|
|
236
|
+
// Owned TYPED: retag as BUFFER at same offset — the byteLen header is shared.
|
|
237
|
+
// TYPED view: retag as BUFFER at the parent data offset (descriptor[8]) — reconstructs
|
|
238
|
+
// the root ArrayBuffer so its own header supplies byteLength.
|
|
239
|
+
ctx.core.stdlib['__to_buffer'] = `(func $__to_buffer (param $ptr f64) (result f64)
|
|
240
|
+
(local $t i32) (local $off i32)
|
|
241
|
+
(local.set $t (call $__ptr_type (local.get $ptr)))
|
|
242
|
+
(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.BUFFER}))
|
|
243
|
+
(then (local.get $ptr))
|
|
244
|
+
(else
|
|
245
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
246
|
+
(if (result f64)
|
|
247
|
+
(i32.and
|
|
248
|
+
(i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
|
|
249
|
+
(i32.ne (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const 8)) (i32.const 0)))
|
|
250
|
+
(then (call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0)
|
|
251
|
+
(i32.load (i32.add (local.get $off) (i32.const 8)))))
|
|
252
|
+
(else (call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0) (local.get $off)))))))`
|
|
253
|
+
|
|
254
|
+
// Constructor: new Float64Array(len) | new F64Array(arr) | new F64Array(buf) | new F64Array(buf, off, len)
|
|
255
|
+
for (const [name, elemType] of Object.entries(ELEM)) {
|
|
256
|
+
const stride = STRIDE[elemType]
|
|
257
|
+
ctx.core.emit[`new.${name}`] = (lenExpr, offsetExpr, lenExpr2) => {
|
|
258
|
+
ctx.features.typedarray = true
|
|
259
|
+
const srcType = typeof lenExpr === 'string' ? lookupValType(lenExpr) : valTypeOf(lenExpr)
|
|
260
|
+
// Subview: new TypedArray(buffer, byteOffset, length) — true JS-parity view.
|
|
261
|
+
// Allocates a 16-byte descriptor [byteLen:i32][dataOff:i32][parentOff:i32][pad]
|
|
262
|
+
// and tags the TYPED ptr with aux=elemType|8. Reads/writes alias the parent,
|
|
263
|
+
// .buffer reconstructs the root BUFFER, .byteOffset = dataOff - parentOff.
|
|
264
|
+
if (offsetExpr != null && lenExpr2 != null) {
|
|
265
|
+
const src = temp('tvs')
|
|
266
|
+
const parentOff = tempI32('tvp')
|
|
267
|
+
const byteLen = tempI32('tvb')
|
|
268
|
+
const dst = tempI32('tvd')
|
|
269
|
+
return typed(['block', ['result', 'f64'],
|
|
270
|
+
['local.set', `$${src}`, asF64(emit(lenExpr))],
|
|
271
|
+
['local.set', `$${parentOff}`, ptrOffsetIR(['local.get', `$${src}`], srcType)],
|
|
272
|
+
['local.set', `$${byteLen}`, ['i32.mul', asI32(emit(lenExpr2)), ['i32.const', stride]]],
|
|
273
|
+
['local.set', `$${dst}`, ['call', '$__alloc', ['i32.const', 16]]],
|
|
274
|
+
['i32.store', ['local.get', `$${dst}`], ['local.get', `$${byteLen}`]],
|
|
275
|
+
['i32.store',
|
|
276
|
+
['i32.add', ['local.get', `$${dst}`], ['i32.const', 4]],
|
|
277
|
+
['i32.add', ['local.get', `$${parentOff}`], asI32(emit(offsetExpr))]],
|
|
278
|
+
['i32.store',
|
|
279
|
+
['i32.add', ['local.get', `$${dst}`], ['i32.const', 8]],
|
|
280
|
+
['local.get', `$${parentOff}`]],
|
|
281
|
+
mkPtrIR(PTR.TYPED, elemType | 8, ['local.get', `$${dst}`])], 'f64')
|
|
282
|
+
}
|
|
283
|
+
// Single arg array-like source: copy elements instead of treating the pointer as a length.
|
|
284
|
+
if (srcType === VAL.ARRAY && ctx.core.emit[`${name}.from`])
|
|
285
|
+
return ctx.core.emit[`${name}.from`](lenExpr)
|
|
286
|
+
// Reinterpret on a buffer or another typed array: zero-copy view.
|
|
287
|
+
// TYPED retagged at the same offset — the byteLen header is shared with the parent.
|
|
288
|
+
// __len(view) = byteLen >> shift computes elemCount for this view's elemType.
|
|
289
|
+
if (srcType === VAL.BUFFER || srcType === VAL.TYPED) {
|
|
290
|
+
return mkPtrIR(PTR.TYPED, elemType, ['call', '$__ptr_offset', asF64(emit(lenExpr))])
|
|
291
|
+
}
|
|
292
|
+
if (srcType == null && ctx.core.emit[`${name}.from`]) {
|
|
293
|
+
// Runtime dispatch: number → allocate; array → copy elements; buffer/typed → zero-copy view.
|
|
294
|
+
const src = temp('ts')
|
|
295
|
+
const len = tempI32('tl')
|
|
296
|
+
const shift = SHIFT[elemType]
|
|
297
|
+
const numBytes = ['i32.shl', ['local.get', `$${len}`], ['i32.const', shift]]
|
|
298
|
+
const numAlloc = allocPtr({ type: PTR.TYPED, aux: elemType, len: numBytes, stride: 1, tag: 'ta' })
|
|
299
|
+
return typed(['block', ['result', 'f64'],
|
|
300
|
+
['local.set', `$${src}`, asF64(emit(lenExpr))],
|
|
301
|
+
['if', ['result', 'f64'],
|
|
302
|
+
['f64.eq', ['local.get', `$${src}`], ['local.get', `$${src}`]],
|
|
303
|
+
// Regular number: treat as length, allocate fresh typed array with byteLen header
|
|
304
|
+
['then', ['block', ['result', 'f64'],
|
|
305
|
+
['local.set', `$${len}`, ['i32.trunc_sat_f64_s', ['local.get', `$${src}`]]],
|
|
306
|
+
numAlloc.init,
|
|
307
|
+
numAlloc.ptr]],
|
|
308
|
+
// Pointer: array → copy elements; buffer/typed → zero-copy view on same offset
|
|
309
|
+
['else', ['if', ['result', 'f64'],
|
|
310
|
+
['i32.eq', ['call', '$__ptr_type', ['local.get', `$${src}`]], ['i32.const', PTR.ARRAY]],
|
|
311
|
+
['then', ctx.core.emit[`${name}.from`](src)],
|
|
312
|
+
['else', mkPtrIR(PTR.TYPED, elemType,
|
|
313
|
+
['call', '$__ptr_offset', ['local.get', `$${src}`]])]]]]], 'f64')
|
|
314
|
+
}
|
|
315
|
+
// Normal: allocate fresh typed array (lenExpr is numeric size). Header stores byteLen.
|
|
316
|
+
const shift = SHIFT[elemType]
|
|
317
|
+
const lenL = tempI32('tan')
|
|
318
|
+
const out = allocPtr({ type: PTR.TYPED, aux: elemType,
|
|
319
|
+
len: ['i32.shl', ['local.get', `$${lenL}`], ['i32.const', shift]], stride: 1, tag: 'ta' })
|
|
320
|
+
return typed(['block', ['result', 'f64'],
|
|
321
|
+
['local.set', `$${lenL}`, asI32(emit(lenExpr))],
|
|
322
|
+
out.init,
|
|
323
|
+
out.ptr], 'f64')
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// === ArrayBuffer (PTR.BUFFER) and DataView ===
|
|
328
|
+
// ArrayBuffer: first-class byte storage with [-8:byteLen][-4:byteCap][bytes].
|
|
329
|
+
// DataView: passthrough ptr to the same BUFFER — DataView methods operate on raw bytes via offset.
|
|
330
|
+
|
|
331
|
+
// new ArrayBuffer(n) → allocate n bytes, return as BUFFER pointer
|
|
332
|
+
const arrayBufferCtor = (sizeExpr) => {
|
|
333
|
+
const n = asI32(emit(sizeExpr))
|
|
334
|
+
const out = allocPtr({ type: PTR.BUFFER, len: n, stride: 1, tag: 'ab' })
|
|
335
|
+
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
336
|
+
}
|
|
337
|
+
ctx.core.emit['new.ArrayBuffer'] = arrayBufferCtor
|
|
338
|
+
|
|
339
|
+
// new DataView(buffer) → same ptr (BUFFER). Its type tag may differ from BUFFER but methods ignore type.
|
|
340
|
+
ctx.core.emit['new.DataView'] = (bufExpr) => asF64(emit(bufExpr))
|
|
341
|
+
|
|
342
|
+
// BigInt64Array(buffer) (bare form, legacy): coerce to same data, Float64Array-compatible storage.
|
|
343
|
+
ctx.core.emit['BigInt64Array'] = (bufExpr) => {
|
|
344
|
+
ctx.features.typedarray = true
|
|
345
|
+
const va = asF64(emit(bufExpr))
|
|
346
|
+
return mkPtrIR(PTR.TYPED, 7, ['call', '$__ptr_offset', va])
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// .buffer — always aliased (zero-copy). BUFFER/DataView: passthrough.
|
|
350
|
+
// Owned TYPED: retag as BUFFER at same offset — the byteLen header is shared.
|
|
351
|
+
// TYPED view: BUFFER at descriptor[8] (root parent data offset).
|
|
352
|
+
ctx.core.emit['.buffer'] = (obj) => {
|
|
353
|
+
if (typeof obj === 'string') {
|
|
354
|
+
const ctor = ctx.types.typedElem?.get(obj)
|
|
355
|
+
if (ctor === 'new.ArrayBuffer' || ctor === 'new.DataView') return asF64(emit(obj))
|
|
356
|
+
if (ctor?.startsWith('new.')) {
|
|
357
|
+
const isView = ctor.endsWith('.view')
|
|
358
|
+
const name = isView ? ctor.slice(4, -5) : ctor.slice(4)
|
|
359
|
+
if (ELEM[name] != null) {
|
|
360
|
+
const parentOff = isView
|
|
361
|
+
? ['i32.load', ['i32.add', ptrOffsetIR(emit(obj), VAL.TYPED), ['i32.const', 8]]]
|
|
362
|
+
: ptrOffsetIR(emit(obj), VAL.TYPED)
|
|
363
|
+
return mkPtrIR(PTR.BUFFER, 0, parentOff)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
inc('__to_buffer')
|
|
368
|
+
return typed(['call', '$__to_buffer', asF64(emit(obj))], 'f64')
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// .byteLength — BUFFER: raw __len. Owned TYPED: elemCount * stride. View TYPED: descriptor[0].
|
|
372
|
+
ctx.core.emit['.byteLength'] = (obj) => {
|
|
373
|
+
if (typeof obj === 'string') {
|
|
374
|
+
const ctor = ctx.types.typedElem?.get(obj)
|
|
375
|
+
if (ctor === 'new.ArrayBuffer' || ctor === 'new.DataView') {
|
|
376
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', asF64(emit(obj))]], 'f64')
|
|
377
|
+
}
|
|
378
|
+
if (ctor && ctor.startsWith('new.')) {
|
|
379
|
+
const isView = ctor.endsWith('.view')
|
|
380
|
+
const name = isView ? ctor.slice(4, -5) : ctor.slice(4)
|
|
381
|
+
const et = ELEM[name]
|
|
382
|
+
if (et != null) {
|
|
383
|
+
if (isView) {
|
|
384
|
+
return typed(['f64.convert_i32_s',
|
|
385
|
+
['i32.load', ptrOffsetIR(emit(obj), VAL.TYPED)]], 'f64')
|
|
386
|
+
}
|
|
387
|
+
return typed(['f64.convert_i32_s',
|
|
388
|
+
['i32.shl', ['call', '$__len', asF64(emit(obj))], ['i32.const', SHIFT[et]]]], 'f64')
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
inc('__byte_length')
|
|
393
|
+
return typed(['f64.convert_i32_s', ['call', '$__byte_length', asF64(emit(obj))]], 'f64')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// .byteOffset — owned: 0. View: descriptor[4] - descriptor[8].
|
|
397
|
+
ctx.core.emit['.byteOffset'] = (obj) => {
|
|
398
|
+
if (typeof obj === 'string') {
|
|
399
|
+
const ctor = ctx.types.typedElem?.get(obj)
|
|
400
|
+
if (ctor?.endsWith('.view')) {
|
|
401
|
+
const t = tempI32('bo')
|
|
402
|
+
return typed(['block', ['result', 'f64'],
|
|
403
|
+
['local.set', `$${t}`, ptrOffsetIR(emit(obj), VAL.TYPED)],
|
|
404
|
+
['f64.convert_i32_s',
|
|
405
|
+
['i32.sub',
|
|
406
|
+
['i32.load', ['i32.add', ['local.get', `$${t}`], ['i32.const', 4]]],
|
|
407
|
+
['i32.load', ['i32.add', ['local.get', `$${t}`], ['i32.const', 8]]]]]], 'f64')
|
|
408
|
+
}
|
|
409
|
+
if (ctor?.startsWith('new.') && ELEM[ctor.slice(4)] != null) return typed(['f64.const', 0], 'f64')
|
|
410
|
+
}
|
|
411
|
+
inc('__byte_offset')
|
|
412
|
+
return typed(['f64.convert_i32_s', ['call', '$__byte_offset', asF64(emit(obj))]], 'f64')
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Runtime fallback for .byteOffset when variable view-ness is unknown.
|
|
416
|
+
ctx.core.stdlib['__byte_offset'] = `(func $__byte_offset (param $ptr f64) (result i32)
|
|
417
|
+
(local $off i32)
|
|
418
|
+
(if (result i32)
|
|
419
|
+
(i32.and
|
|
420
|
+
(i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.TYPED}))
|
|
421
|
+
(i32.ne (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const 8)) (i32.const 0)))
|
|
422
|
+
(then
|
|
423
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
424
|
+
(i32.sub
|
|
425
|
+
(i32.load (i32.add (local.get $off) (i32.const 4)))
|
|
426
|
+
(i32.load (i32.add (local.get $off) (i32.const 8)))))
|
|
427
|
+
(else (i32.const 0))))`
|
|
428
|
+
|
|
429
|
+
// ArrayBuffer.isView(x) — true iff x is a TYPED pointer. (DataView passthrough cannot be
|
|
430
|
+
// distinguished from ArrayBuffer since both are BUFFER pointers; both report false.)
|
|
431
|
+
ctx.core.emit['ArrayBuffer.isView'] = (v) => {
|
|
432
|
+
const va = asF64(emit(v))
|
|
433
|
+
return typed(['f64.convert_i32_s',
|
|
434
|
+
['i32.eq', ['call', '$__ptr_type', va], ['i32.const', PTR.TYPED]]], 'f64')
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// buf.slice(begin?, end?) on a BUFFER → fresh BUFFER with the byte range copied.
|
|
438
|
+
// Only dispatches statically when obj is a tracked ArrayBuffer/DataView variable.
|
|
439
|
+
ctx.core.emit['.buf:slice'] = (obj, beginExpr, endExpr) => {
|
|
440
|
+
const src = temp('bss')
|
|
441
|
+
const beg = tempI32('bsb')
|
|
442
|
+
const end = tempI32('bse')
|
|
443
|
+
const bytes = tempI32('bsn')
|
|
444
|
+
const out = allocPtr({ type: PTR.BUFFER, len: ['local.get', `$${bytes}`], stride: 1, tag: 'bsd' })
|
|
445
|
+
const beginWat = beginExpr == null ? ['i32.const', 0] : asI32(emit(beginExpr))
|
|
446
|
+
const endWat = endExpr == null
|
|
447
|
+
? ['call', '$__len', ['local.get', `$${src}`]]
|
|
448
|
+
: asI32(emit(endExpr))
|
|
449
|
+
return typed(['block', ['result', 'f64'],
|
|
450
|
+
['local.set', `$${src}`, asF64(emit(obj))],
|
|
451
|
+
['local.set', `$${beg}`, beginWat],
|
|
452
|
+
['local.set', `$${end}`, endWat],
|
|
453
|
+
['local.set', `$${bytes}`, ['i32.sub', ['local.get', `$${end}`], ['local.get', `$${beg}`]]],
|
|
454
|
+
['if',
|
|
455
|
+
['i32.lt_s', ['local.get', `$${bytes}`], ['i32.const', 0]],
|
|
456
|
+
['then', ['local.set', `$${bytes}`, ['i32.const', 0]]]],
|
|
457
|
+
out.init,
|
|
458
|
+
['memory.copy',
|
|
459
|
+
['local.get', `$${out.local}`],
|
|
460
|
+
['i32.add', ['call', '$__ptr_offset', ['local.get', `$${src}`]], ['local.get', `$${beg}`]],
|
|
461
|
+
['local.get', `$${bytes}`]],
|
|
462
|
+
out.ptr], 'f64')
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// DataView set methods: extract i32 offset from f64 ptr, store value
|
|
466
|
+
const DV_SET = {
|
|
467
|
+
setInt8: 'i32.store8', setUint8: 'i32.store8',
|
|
468
|
+
setInt16: 'i32.store16', setUint16: 'i32.store16',
|
|
469
|
+
setInt32: 'i32.store', setUint32: 'i32.store',
|
|
470
|
+
setFloat32: 'f32.store', setFloat64: 'f64.store',
|
|
471
|
+
setBigInt64: 'i64.store', setBigUint64: 'i64.store',
|
|
472
|
+
}
|
|
473
|
+
for (const [method, storeOp] of Object.entries(DV_SET)) {
|
|
474
|
+
ctx.core.emit[`.${method}`] = (dv, off, val, _le) => {
|
|
475
|
+
const dvOff = ptrOffsetIR(emit(dv), VAL.BUFFER)
|
|
476
|
+
const addr = ['i32.add', dvOff, asI32(emit(off))]
|
|
477
|
+
let v = emit(val)
|
|
478
|
+
if (method.includes('BigInt') || method.includes('BigUint'))
|
|
479
|
+
v = typed(['i64.reinterpret_f64', asF64(v)], 'i64')
|
|
480
|
+
else if (method.includes('Float64')) v = asF64(v)
|
|
481
|
+
else if (method.includes('Float32')) v = typed(['f32.demote_f64', asF64(v)], 'f32')
|
|
482
|
+
else v = asI32(v)
|
|
483
|
+
return [storeOp, addr, v]
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// DataView get methods: extract i32 offset, load value, return as f64
|
|
488
|
+
const DV_GET = {
|
|
489
|
+
getInt8: ['i32.load8_s', 'i32'], getUint8: ['i32.load8_u', 'i32'],
|
|
490
|
+
getInt16: ['i32.load16_s', 'i32'], getUint16: ['i32.load16_u', 'i32'],
|
|
491
|
+
getInt32: ['i32.load', 'i32'], getUint32: ['i32.load', 'i32'],
|
|
492
|
+
getFloat32: ['f32.load', 'f32'], getFloat64: ['f64.load', 'f64'],
|
|
493
|
+
getBigInt64: ['i64.load', 'i64'], getBigUint64: ['i64.load', 'i64'],
|
|
494
|
+
}
|
|
495
|
+
for (const [method, [loadOp, resultType]] of Object.entries(DV_GET)) {
|
|
496
|
+
ctx.core.emit[`.${method}`] = (dv, off, _le) => {
|
|
497
|
+
const addr = ['i32.add', ptrOffsetIR(emit(dv), VAL.BUFFER), asI32(emit(off))]
|
|
498
|
+
const raw = typed([loadOp, addr], resultType)
|
|
499
|
+
if (resultType === 'f64') return raw
|
|
500
|
+
if (resultType === 'f32') return typed(['f64.promote_f32', raw], 'f64')
|
|
501
|
+
if (resultType === 'i64') return typed(['f64.reinterpret_i64', raw], 'f64')
|
|
502
|
+
return typed(['f64.convert_i32_s', raw], 'f64')
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// TypedArray.from(arr) — convert regular array to typed array
|
|
507
|
+
for (const [name, elemType] of Object.entries(ELEM)) {
|
|
508
|
+
const stride = STRIDE[elemType], store = STORE[elemType]
|
|
509
|
+
ctx.core.emit[`${name}.from`] = (src) => {
|
|
510
|
+
ctx.features.typedarray = true
|
|
511
|
+
const srcL = temp('tfs')
|
|
512
|
+
const len = tempI32('tfl'), i = tempI32('tfi'), off = tempI32('tfo')
|
|
513
|
+
const out = allocPtr({ type: PTR.TYPED, aux: elemType,
|
|
514
|
+
len: ['i32.mul', ['local.get', `$${len}`], ['i32.const', stride]], stride: 1, tag: 'tf' })
|
|
515
|
+
const t = out.local
|
|
516
|
+
const id = ctx.func.uniq++
|
|
517
|
+
const storeExpr = elemType === 7 ? ['f64.store',
|
|
518
|
+
['i32.add', ['local.get', `$${t}`], ['i32.mul', ['local.get', `$${i}`], ['i32.const', stride]]],
|
|
519
|
+
['f64.load', ['i32.add', ['local.get', `$${off}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]
|
|
520
|
+
: elemType === 6 ? ['f32.store',
|
|
521
|
+
['i32.add', ['local.get', `$${t}`], ['i32.mul', ['local.get', `$${i}`], ['i32.const', stride]]],
|
|
522
|
+
['f32.demote_f64', ['f64.load', ['i32.add', ['local.get', `$${off}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]]
|
|
523
|
+
: [store,
|
|
524
|
+
['i32.add', ['local.get', `$${t}`], ['i32.mul', ['local.get', `$${i}`], ['i32.const', stride]]],
|
|
525
|
+
[(elemType & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s',
|
|
526
|
+
['f64.load', ['i32.add', ['local.get', `$${off}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]]
|
|
527
|
+
return typed(['block', ['result', 'f64'],
|
|
528
|
+
['local.set', `$${srcL}`, asF64(emit(src))],
|
|
529
|
+
['local.set', `$${off}`, ['call', '$__ptr_offset', ['local.get', `$${srcL}`]]],
|
|
530
|
+
['local.set', `$${len}`, ['call', '$__len', ['local.get', `$${srcL}`]]],
|
|
531
|
+
out.init,
|
|
532
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
533
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
534
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
535
|
+
storeExpr,
|
|
536
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
537
|
+
['br', `$loop${id}`]]],
|
|
538
|
+
out.ptr], 'f64')
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// .length handled by ptr.js's __len (reads from memory header [-8:len])
|
|
543
|
+
|
|
544
|
+
/** Resolve element type + view-ness for a known TypedArray variable.
|
|
545
|
+
* Returns { et, isView } or null. */
|
|
546
|
+
const resolveElem = (arr) => {
|
|
547
|
+
const ctor = typeof arr === 'string' && ctx.types.typedElem?.get(arr)
|
|
548
|
+
if (!ctor) return null
|
|
549
|
+
const isView = ctor.endsWith('.view')
|
|
550
|
+
const name = isView ? ctor.slice(4, -5) : ctor.slice(4)
|
|
551
|
+
const et = ELEM[name]
|
|
552
|
+
return et == null ? null : { et, isView }
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Emit the real data byte-address for a typed array IR node.
|
|
556
|
+
* Owned: low 32 bits of the NaN-box (or the unboxed local directly).
|
|
557
|
+
* View: load descriptor[4]. Uses ptrOffsetIR so unboxed-TYPED locals pass through
|
|
558
|
+
* without a rebox-then-unbox round trip, and globals fold to inline bit-extract. */
|
|
559
|
+
const typedDataAddr = (objIR, isView) => isView
|
|
560
|
+
? ['i32.load', ['i32.add', ptrOffsetIR(objIR, VAL.TYPED), ['i32.const', 4]]]
|
|
561
|
+
: ptrOffsetIR(objIR, VAL.TYPED)
|
|
562
|
+
|
|
563
|
+
// Runtime-dispatch typed index: checks ptr_type + aux to load with correct stride.
|
|
564
|
+
// For TYPED views (aux bit 3), $off indirects through descriptor[4] to real data.
|
|
565
|
+
// Factory — collapses to ARRAY-only f64 indexing when no TYPED pointer can reach here.
|
|
566
|
+
// Identical factory in array.js; whichever module loads last wins the registration.
|
|
567
|
+
ctx.core.stdlib['__typed_idx'] = () => {
|
|
568
|
+
if (!ctx.features.typedarray && !ctx.features.external) {
|
|
569
|
+
return `(func $__typed_idx (param $ptr f64) (param $i i32) (result f64)
|
|
570
|
+
(local $len i32)
|
|
571
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
572
|
+
(if (result f64)
|
|
573
|
+
(i32.or
|
|
574
|
+
(i32.lt_s (local.get $i) (i32.const 0))
|
|
575
|
+
(i32.ge_u (local.get $i) (local.get $len)))
|
|
576
|
+
(then (f64.const nan:${UNDEF_NAN}))
|
|
577
|
+
(else (f64.load (i32.add (call $__ptr_offset (local.get $ptr)) (i32.shl (local.get $i) (i32.const 3)))))))`
|
|
578
|
+
}
|
|
579
|
+
return `(func $__typed_idx (param $ptr f64) (param $i i32) (result f64)
|
|
580
|
+
(local $off i32) (local $et i32) (local $len i32) (local $aux i32)
|
|
581
|
+
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
582
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
583
|
+
(if
|
|
584
|
+
(i32.and
|
|
585
|
+
(i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.TYPED}))
|
|
586
|
+
(i32.ne (i32.and (local.get $aux) (i32.const 8)) (i32.const 0)))
|
|
587
|
+
(then (local.set $off (i32.load (i32.add (local.get $off) (i32.const 4))))))
|
|
588
|
+
(local.set $len (call $__len (local.get $ptr)))
|
|
589
|
+
(if (result f64)
|
|
590
|
+
(i32.or
|
|
591
|
+
(i32.lt_s (local.get $i) (i32.const 0))
|
|
592
|
+
(i32.ge_u (local.get $i) (local.get $len)))
|
|
593
|
+
(then (f64.const nan:${UNDEF_NAN}))
|
|
594
|
+
(else
|
|
595
|
+
(if (result f64) (i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.TYPED}))
|
|
596
|
+
(then
|
|
597
|
+
(local.set $et (i32.and (local.get $aux) (i32.const 7)))
|
|
598
|
+
(if (result f64) (i32.ge_u (local.get $et) (i32.const 6))
|
|
599
|
+
(then (if (result f64) (i32.eq (local.get $et) (i32.const 7))
|
|
600
|
+
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
601
|
+
(else (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
602
|
+
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 4))
|
|
603
|
+
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
604
|
+
(then (f64.convert_i32_u (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))
|
|
605
|
+
(else (f64.convert_i32_s (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
606
|
+
(else (if (result f64) (i32.ge_u (local.get $et) (i32.const 2))
|
|
607
|
+
(then (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
608
|
+
(then (f64.convert_i32_u (i32.load16_u (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))
|
|
609
|
+
(else (f64.convert_i32_s (i32.load16_s (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 1))))))))
|
|
610
|
+
(else (if (result f64) (i32.and (local.get $et) (i32.const 1))
|
|
611
|
+
(then (f64.convert_i32_u (i32.load8_u (i32.add (local.get $off) (local.get $i)))))
|
|
612
|
+
(else (f64.convert_i32_s (i32.load8_s (i32.add (local.get $off) (local.get $i)))))))))))))
|
|
613
|
+
(else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))))`
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Type-aware TypedArray read: arr[i]
|
|
617
|
+
ctx.core.emit['.typed:[]'] = (arr, idx) => {
|
|
618
|
+
const r = resolveElem(arr)
|
|
619
|
+
if (r == null) return null // unknown type, fallback to generic
|
|
620
|
+
const { et, isView } = r
|
|
621
|
+
const objIR = emit(arr), vi = asI32(emit(idx))
|
|
622
|
+
const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
|
|
623
|
+
if (et === 7) return typed(['f64.load', off], 'f64') // Float64Array
|
|
624
|
+
if (et === 6) return typed(['f64.promote_f32', ['f32.load', off]], 'f64') // Float32Array
|
|
625
|
+
// Integer types: load and convert to f64 (unsigned types use unsigned conversion)
|
|
626
|
+
return typed([(et & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', [LOAD[et], off]], 'f64')
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Type-aware TypedArray write: arr[i] = val
|
|
630
|
+
ctx.core.emit['.typed:[]='] = (arr, idx, val) => {
|
|
631
|
+
const r = resolveElem(arr)
|
|
632
|
+
if (r == null) return null
|
|
633
|
+
const { et, isView } = r
|
|
634
|
+
const objIR = emit(arr), vi = asI32(emit(idx)), vv = asF64(emit(val))
|
|
635
|
+
const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
|
|
636
|
+
if (et === 7) return ['f64.store', off, vv] // Float64Array
|
|
637
|
+
if (et === 6) return ['f32.store', off, ['f32.demote_f64', vv]] // Float32Array
|
|
638
|
+
// Integer types: truncate f64 to i32, then store. Peel f64.convert_i32_*(x) → x:
|
|
639
|
+
// store of bitwise-result already-i32 needs no round-trip.
|
|
640
|
+
const isConv = Array.isArray(vv) && (vv[0] === 'f64.convert_i32_s' || vv[0] === 'f64.convert_i32_u')
|
|
641
|
+
const i32val = isConv ? vv[1] : [(et & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s', vv]
|
|
642
|
+
return [STORE[et], off, i32val]
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// .map() on TypedArrays — SIMD auto-vectorization when pattern detected
|
|
646
|
+
ctx.core.emit['.typed:map'] = (arr, fn) => {
|
|
647
|
+
// Resolve element type + view-ness from variable tracking
|
|
648
|
+
const ctor = typeof arr === 'string' && ctx.types.typedElem?.get(arr)
|
|
649
|
+
const isView = ctor?.endsWith('.view')
|
|
650
|
+
const elemName = isView ? ctor.slice(4, -5) : ctor?.slice(4)
|
|
651
|
+
const elemType = elemName && ELEM[elemName]
|
|
652
|
+
|
|
653
|
+
// Try SIMD: inline arrow with recognizable pattern
|
|
654
|
+
if (elemType != null && Array.isArray(fn) && fn[0] === '=>') {
|
|
655
|
+
const [, rawParam, body] = fn
|
|
656
|
+
const param = Array.isArray(rawParam) && rawParam[0] === '()' ? rawParam[1] : rawParam
|
|
657
|
+
const pattern = analyzeSimd(body, param)
|
|
658
|
+
|
|
659
|
+
if (pattern) {
|
|
660
|
+
const id = ctx.func.uniq++
|
|
661
|
+
const funcName = `__simd_map_${id}`
|
|
662
|
+
const wat = genSimdMap(funcName, elemType, pattern)
|
|
663
|
+
if (wat) {
|
|
664
|
+
ctx.core.stdlib[funcName] = wat
|
|
665
|
+
inc(funcName, '__typed_data', '__len')
|
|
666
|
+
return typed(['call', `$${funcName}`, asF64(emit(arr))], 'f64')
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Scalar fallback: proper typed-array map (preserves element type)
|
|
672
|
+
if (elemType != null) {
|
|
673
|
+
const va = emit(arr), vf = emit(fn)
|
|
674
|
+
const len = tempI32('tml'), ptr = tempI32('tmp'), i = tempI32('tmi')
|
|
675
|
+
const stride = STRIDE[elemType], shift = SHIFT[elemType]
|
|
676
|
+
const dst = allocPtr({ type: PTR.TYPED, aux: elemType,
|
|
677
|
+
len: ['i32.shl', ['local.get', `$${len}`], ['i32.const', shift]], stride: 1, tag: 'tmo' })
|
|
678
|
+
const out = dst.local
|
|
679
|
+
|
|
680
|
+
const loadElem = () => {
|
|
681
|
+
const off = ['i32.add', ['local.get', `$${ptr}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', shift]]]
|
|
682
|
+
if (elemType === 7) return typed(['f64.load', off], 'f64')
|
|
683
|
+
if (elemType === 6) return typed(['f64.promote_f32', ['f32.load', off]], 'f64')
|
|
684
|
+
return typed([(elemType & 1) ? 'f64.convert_i32_u' : 'f64.convert_i32_s', [LOAD[elemType], off]], 'f64')
|
|
685
|
+
}
|
|
686
|
+
const storeElem = (val) => {
|
|
687
|
+
const off = ['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', shift]]]
|
|
688
|
+
if (elemType === 7) return ['f64.store', off, val]
|
|
689
|
+
if (elemType === 6) return ['f32.store', off, ['f32.demote_f64', val]]
|
|
690
|
+
return [STORE[elemType], off, [(elemType & 1) ? 'i32.trunc_f64_u' : 'i32.trunc_f64_s', val]]
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const id = ctx.func.uniq++
|
|
694
|
+
return typed(['block', ['result', 'f64'],
|
|
695
|
+
['local.set', `$${ptr}`, typedDataAddr(va, isView)],
|
|
696
|
+
['local.set', `$${len}`, ['call', '$__len', asF64(va)]],
|
|
697
|
+
dst.init,
|
|
698
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
699
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
700
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
701
|
+
storeElem(asF64(ctx.closure.call(vf, [loadElem()]))),
|
|
702
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
703
|
+
['br', `$loop${id}`]]],
|
|
704
|
+
dst.ptr], 'f64')
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Unknown typed array type: fall back to generic array .map
|
|
708
|
+
if (ctx.core.emit['.map']) return ctx.core.emit['.map'](arr, fn)
|
|
709
|
+
return null
|
|
710
|
+
}
|
|
711
|
+
}
|