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.
@@ -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
+ }