jz 0.0.0 → 0.1.1

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