jz 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/interop.js CHANGED
@@ -203,6 +203,9 @@ const ELEMS = {
203
203
  Uint32Array: [5, 4, 'getUint32', 'setUint32'],
204
204
  Float32Array: [6, 4, 'getFloat32', 'setFloat32'],
205
205
  Float64Array: [7, 8, 'getFloat64', 'setFloat64'],
206
+ // flag-carrying kinds: elemId = base code | flag (32 = f16, 64 = clamped)
207
+ Float16Array: [35, 2, 'getFloat16', 'setFloat16'],
208
+ Uint8ClampedArray: [65, 1, 'getUint8', 'setUint8'],
206
209
  }
207
210
  // Pre-built lookup by element ID (avoids Object.values on each access)
208
211
  const ELEM_BY_ID = Object.values(ELEMS)
@@ -400,9 +403,12 @@ export const memory = (src) => {
400
403
  const entries = Object.entries(obj)
401
404
  let cap = 8
402
405
  while (entries.length * 4 >= cap * 3) cap <<= 1 // stay under the 75% grow trigger
403
- const off = hdr(entries.length, cap, cap * 24)
406
+ // cap × (24-B entry + 4-B probe hash lane) — collection.js's exact layout;
407
+ // the lane (after the entries) is what wasm probes walk
408
+ const off = hdr(entries.length, cap, cap * 28)
404
409
  // Stage every slot as i64 bits (empty = 0) — same NaN-canonicalization dodge as mem.Array.
405
410
  const staged = new BigInt64Array(cap * 3)
411
+ const lane = new Int32Array(cap)
406
412
  entries.forEach(([k, v], seq) => {
407
413
  const keyBox = mem.String(k)
408
414
  const h = jzStrHash(keyBox)
@@ -411,9 +417,11 @@ export const memory = (src) => {
411
417
  staged[idx * 3] = (BigInt(seq) << 32n) | BigInt(h >>> 0)
412
418
  staged[idx * 3 + 1] = bits(keyBox)
413
419
  staged[idx * 3 + 2] = bits(mem.wrapVal(v))
420
+ lane[idx] = h | 0
414
421
  })
415
422
  const dst = new BigInt64Array(mem.buffer, off, cap * 3)
416
423
  dst.set(staged)
424
+ new Int32Array(mem.buffer, off + cap * 24, cap).set(lane)
417
425
  return ptr(7, 0, off)
418
426
  }
419
427
 
@@ -474,7 +482,10 @@ export const memory = (src) => {
474
482
  if (t === 3) { // TYPED
475
483
  const elem = a & 7
476
484
  const [, stride] = ELEM_BY_ID[elem]
477
- const Ctor = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array][elem]
485
+ const Ctor = (a & 32)
486
+ ? (globalThis.Float16Array ?? (() => { throw new Error('decoding a Float16Array result needs a host with Float16Array (Node ≥ 24 / modern browsers)') })())
487
+ : (a & 64) ? Uint8ClampedArray
488
+ : [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array][elem]
478
489
  if (a & 8) {
479
490
  const byteLen = m.getInt32(off, true), dataOff = m.getInt32(off + 4, true)
480
491
  return new Ctor(mem.buffer, dataOff, byteLen / stride)
@@ -574,6 +585,8 @@ export const memory = (src) => {
574
585
  // the target (same stride), use .set() for a fast memcpy instead of
575
586
  // per-element DataView writes. Falls back to DataView for mismatched types.
576
587
  const TA = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array]
588
+ TA[65] = Uint8ClampedArray
589
+ if (globalThis.Float16Array) TA[35] = globalThis.Float16Array
577
590
  for (const [name, [elemId, stride, , setter]] of Object.entries(ELEMS)) {
578
591
  mem[name] = (data) => {
579
592
  const n = data.length, bytes = n * stride, off = hdr(bytes, bytes, bytes)
@@ -844,13 +857,24 @@ const prepareInterop = (opts) => {
844
857
  // in JS) — see module/collection.js header for rationale. f64 returns are
845
858
  // wrapped back to BigInt so the wasm side reinterprets a non-canonicalized
846
859
  // bit pattern.
847
- opts._interp.__ext_prop = (objBig, propBig) => {
860
+ // A dynamic member op can reach the host with a receiver that is NOT an
861
+ // external handle (extMap[0] is null — e.g. a builtin's placeholder value, or
862
+ // a number that inference couldn't type whose method jz doesn't implement).
863
+ // Without the guard that surfaces as a bare host TypeError ("Cannot read
864
+ // properties of null") — a mystery. Name the actual failure instead.
865
+ const extRecv = (objBig, prop, what) => {
848
866
  const obj = state.extMap[offset(objBig)]
867
+ if (obj == null) throw new Error(`'${prop}' — jz dispatched this ${what} to the host, but the receiver is not a host object (an unsupported builtin method, or a receiver type jz couldn't resolve)`)
868
+ return obj
869
+ }
870
+ opts._interp.__ext_prop = (objBig, propBig) => {
849
871
  const prop = state.mem.read(propBig)
872
+ const obj = extRecv(objBig, prop, 'property read')
850
873
  return bits(state.mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop]))
851
874
  }
852
875
  opts._interp.__ext_has = (objBig, propBig) => {
853
- return (state.mem.read(propBig) in state.extMap[offset(objBig)]) ? 1 : 0
876
+ const prop = state.mem.read(propBig)
877
+ return (prop in extRecv(objBig, prop, 'membership test')) ? 1 : 0
854
878
  }
855
879
  opts._interp.__ext_set = (objBig, propBig, valBig) => {
856
880
  let v = state.mem.read(valBig)
@@ -867,13 +891,16 @@ const prepareInterop = (opts) => {
867
891
  // same-ctor copy — exactly `new Ctor(view)` with no manual size/offset
868
892
  // bookkeeping — and a no-op for every other decoded value shape.
869
893
  if (ArrayBuffer.isView(v)) v = v.slice()
870
- state.extMap[offset(objBig)][state.mem.read(propBig)] = v
894
+ const prop = state.mem.read(propBig)
895
+ extRecv(objBig, prop, 'property write')[prop] = v
871
896
  return 1
872
897
  }
873
898
  opts._interp.__ext_call = (objBig, propBig, argsBig) => {
874
- const obj = state.extMap[offset(objBig)]
875
899
  const prop = state.mem.read(propBig)
900
+ const obj = extRecv(objBig, prop, 'method call')
876
901
  const args = state.mem.read(argsBig)
902
+ if (typeof obj[prop] !== 'function')
903
+ throw new Error(`'${prop}' is not a function on this host ${obj?.constructor?.name ?? 'object'}`)
877
904
  return hostRet(state, obj[prop].apply(obj, args))
878
905
  }
879
906
  return state
@@ -935,6 +962,19 @@ const installDefaultEnvImports = (mod, imports, state) => {
935
962
  return a[0] | 0
936
963
  }
937
964
  }
965
+ // Byte-fill entropy for crypto.getRandomValues/randomUUID (module/crypto.js).
966
+ // Fills wasm linear memory directly; the view is created per call — never
967
+ // cached — so a Memory.grow between calls can't leave a detached view.
968
+ if (envFns.has('random') && !imports.env.random) {
969
+ imports.env.random = (off, len) => {
970
+ const view = new Uint8Array(state.mem.buffer, off, len)
971
+ if (globalThis.crypto?.getRandomValues) globalThis.crypto.getRandomValues(view)
972
+ else for (let i = 0; i < len; i++) view[i] = (Math.random() * 256) >>> 0
973
+ }
974
+ }
975
+ if (envFns.has('hardwareConcurrency') && !imports.env.hardwareConcurrency) {
976
+ imports.env.hardwareConcurrency = () => globalThis.navigator?.hardwareConcurrency ?? 1
977
+ }
938
978
  if (envFns.has('parseFloat') && !imports.env.parseFloat) {
939
979
  imports.env.parseFloat = (valBig) => {
940
980
  const s = readArgBits(state, valBig)
@@ -975,6 +1015,31 @@ const installDefaultEnvImports = (mod, imports, state) => {
975
1015
  return 0
976
1016
  }
977
1017
  }
1018
+ // requestAnimationFrame wiring: real rAF where the host has one; a 16 ms
1019
+ // timer elsewhere (Node) so frame-driven modules still run — the callback
1020
+ // receives a real timestamp either way via __invoke_closure1.
1021
+ if (envFns.has('requestAnimationFrame') || envFns.has('cancelAnimationFrame')) {
1022
+ const cancel = new Map()
1023
+ let nextId = 1
1024
+ if (envFns.has('requestAnimationFrame') && !imports.env.requestAnimationFrame) imports.env.requestAnimationFrame = (cbBig) => {
1025
+ const id = nextId++
1026
+ const fire = (t) => { cancel.delete(id); state.invoke1?.(cbBig, t); state.afterTick?.() }
1027
+ const raf = globalThis.requestAnimationFrame
1028
+ if (typeof raf === 'function') {
1029
+ const h = raf(fire)
1030
+ cancel.set(id, () => globalThis.cancelAnimationFrame?.(h))
1031
+ } else {
1032
+ const h = setTimeout(() => fire(typeof performance !== 'undefined' ? performance.now() : Date.now()), 16)
1033
+ cancel.set(id, () => clearTimeout(h))
1034
+ }
1035
+ return id
1036
+ }
1037
+ if (envFns.has('cancelAnimationFrame') && !imports.env.cancelAnimationFrame) imports.env.cancelAnimationFrame = (id) => {
1038
+ const c = cancel.get(id)
1039
+ if (c) { c(); cancel.delete(id) }
1040
+ return 0
1041
+ }
1042
+ }
978
1043
  }
979
1044
 
980
1045
  // JS-side polyfills for `wasm:js-string` builtins. Used when the engine does
@@ -1085,6 +1150,8 @@ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
1085
1150
 
1086
1151
  // Trampoline used by env.setTimeout/clearTimeout to fire scheduled closures.
1087
1152
  state.invoke = inst.exports.__invoke_closure || null
1153
+ // One-arg variant — env.requestAnimationFrame passes the frame timestamp.
1154
+ state.invoke1 = inst.exports.__invoke_closure1 || null
1088
1155
 
1089
1156
  // Drive WASM timer queue via JS scheduling (non-blocking, no-op if absent).
1090
1157
  attachTimers(inst)
package/jzify/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  import { JZIFY_CLASS_ERRORS as JC } from '../src/op-policy.js'
11
11
  import { parse } from '../src/parse.js'
12
12
  import { createAsyncLowering, ASYNC_RUNTIME, ASYNC_GEN_RUNTIME } from './async.js'
13
+ import { USP_RUNTIME } from './webrt.js'
13
14
  import { createNames } from './names.js'
14
15
  import { foldStaticExportHelpers, foldStaticBundlerHelpers, canonicalizeObjectIdioms } from './bundler.js'
15
16
  import { createSwitchLowering, normalizeCaseBody } from './switch.js'
@@ -47,7 +48,9 @@ const iterProto = { on: false }
47
48
  const genErr = (msg) => { throw new Error('jzify: ' + msg) }
48
49
  const { lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, unwindChain, fuseTerminal, fusedLoop, isTerminal } = createGeneratorLowering({ transform, err: genErr, generatorNames, genTemp: (t) => names.genTemp(t), iterProto })
49
50
  const { lowerAsync, lowerAsyncGen, noteAsync, asyncUsed, agenUsed, resetAsync } = createAsyncLowering({ genTemp: (t) => names.genTemp(t), err: genErr })
50
- bindGenerators({ lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, lowerAsync, lowerAsyncGen, noteAsync, generatorNames, iterProto, unwindChain, fuseTerminal, fusedLoop, isTerminal })
51
+ // Web-runtime splice flags (URLSearchParams, …) reset per transform run.
52
+ const webrt = { usp: false }
53
+ bindGenerators({ lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, lowerAsync, lowerAsyncGen, noteAsync, generatorNames, iterProto, unwindChain, fuseTerminal, fusedLoop, isTerminal, webrt })
51
54
  transformSwitch = createSwitchLowering(transform, names)
52
55
 
53
56
  // Spread normalization for iterator values — injected only when a spread site
@@ -140,6 +143,7 @@ export default function jzify(ast) {
140
143
  if (Array.isArray(ast) && ast[0] === ';') ast = [';', ...foldPseudoClassical(ast.slice(1))]
141
144
  resetAsync()
142
145
  iterProto.drain = false
146
+ webrt.usp = false
143
147
  let out = transformScope(ast)
144
148
  const prepend = (src) => {
145
149
  // runtimes ride the same well-known-symbol canonicalization as user code
@@ -159,6 +163,8 @@ export default function jzify(ast) {
159
163
  // [Symbol.iterator]() mints (__it_from) → splice the decorated-iterator
160
164
  // factory. Programs without value-position helpers never take this shape.
161
165
  if (iterProto.helpersUsed || iterProto.fromUsed) prepend(ITER_HELPERS_RUNTIME)
166
+ // URLSearchParams sites → splice the jz-source implementation (webrt.js).
167
+ if (webrt.usp) prepend(USP_RUNTIME)
162
168
  // async generators → splice the tagged-yield driver (before the promise
163
169
  // runtime prepend so it lands AFTER it in module order — it calls __p_*).
164
170
  if (agenUsed()) prepend(ASYNC_GEN_RUNTIME)
@@ -9,8 +9,8 @@ import { isDestructurePat } from './hoist-vars.js'
9
9
 
10
10
  const ERROR_INSTANCEOF = new Set(['Error', 'TypeError', 'SyntaxError', 'RangeError', 'ReferenceError', 'URIError', 'EvalError'])
11
11
 
12
- const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32Array',
13
- 'Int16Array','Uint16Array','Int8Array','Uint8Array',
12
+ const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Float16Array','Int32Array','Uint32Array',
13
+ 'Int16Array','Uint16Array','Int8Array','Uint8Array','Uint8ClampedArray',
14
14
  'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
15
15
 
16
16
  const isProto = n => Array.isArray(n) && n[0] === '.' && Array.isArray(n[1]) && n[1][0] === '.' && n[1][2] === 'prototype'
@@ -263,6 +263,19 @@ export function createTransform(opts) {
263
263
  _gen.noteAsync()
264
264
  return ['()', P_STATIC[callee[2]], ...rest.map(a => a == null ? a : transform(a))]
265
265
  }
266
+ // queueMicrotask(fn) IS the runtime's job queue: push onto __mt, drained
267
+ // at the same host boundaries promise jobs are. Evaluates to undefined
268
+ // per spec (push's return length is discarded by the comma).
269
+ if (callee === 'queueMicrotask' && rest.length) {
270
+ _gen.noteAsync()
271
+ return [',', ['()', ['.', '__mt', 'push'], ...rest.map(a => a == null ? a : transform(a))], 'undefined']
272
+ }
273
+ }
274
+ // URLSearchParams rides a spliced jz-source runtime (jzify/webrt.js);
275
+ // `new URLSearchParams(x)` unwraps to this same call via the `new` handler.
276
+ if (callee === 'URLSearchParams' && _gen?.webrt) {
277
+ _gen.webrt.usp = true
278
+ return ['()', '__usp_new', ...rest.map(a => a == null ? a : transform(a))]
266
279
  }
267
280
  // Terminal iterator helper (toArray/reduce/forEach/some/every/find) on a
268
281
  // chain rooted at a known generator call → fused IIFE loop.
@@ -448,6 +461,12 @@ export function createTransform(opts) {
448
461
  // form, and `new URL(rel, import.meta.url)` lowers to a static href string there.
449
462
  // User classes (lowered to factory arrows by jzify) become plain calls below.
450
463
  if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array' || name === 'RegExp' || name === 'URL')) return ['new', transform(ctor), ...cargs.map(transform)]
464
+ // paren-less `new URLSearchParams` (ctor arrives as a bare string —
465
+ // the call-node form unwraps through the '()' handler below)
466
+ if (name === 'URLSearchParams' && typeof ctor === 'string' && _gen?.webrt) {
467
+ _gen.webrt.usp = true
468
+ return ['()', '__usp_new']
469
+ }
451
470
  if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
452
471
  return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
453
472
  },
package/jzify/webrt.js ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Web-platform runtime pieces, as readable jz source — spliced ahead of user
3
+ * code by jzify when used (the ASYNC_RUNTIME pattern; pay-per-use, programs
4
+ * without them compile byte-identically).
5
+ *
6
+ * URLSearchParams: `new URLSearchParams(init)` canonicalizes to __usp_new —
7
+ * a fixed-shape object with closure methods over parallel key/value arrays
8
+ * (the promise-runtime object shape). WHATWG semantics: forgiving percent
9
+ * decode ('+' is space, malformed escapes pass through as literals — never a
10
+ * throw), application/x-www-form-urlencoded escaping on toString (space→'+',
11
+ * only [A-Za-z0-9*-._] bare, UTF-8 bytes %XX'd — jz's byte-strings make that
12
+ * exact). Divergences (documented): keys()/values()/entries() return arrays
13
+ * (iterable with for-of, not live iterators), sort() compares UTF-8 bytes
14
+ * (spec: UTF-16 units — differs only past ASCII), and the object is not
15
+ * itself iterable — iterate `.entries()`.
16
+ *
17
+ * @module jzify/webrt
18
+ */
19
+
20
+ export const USP_RUNTIME = `
21
+ let __usp_hex = (c) => c >= 48 && c <= 57 ? c - 48 : c >= 65 && c <= 70 ? c - 55 : c >= 97 && c <= 102 ? c - 87 : -1
22
+ let __usp_lt = (a, b) => {
23
+ let n = a.length < b.length ? a.length : b.length
24
+ for (let i = 0; i < n; i++) {
25
+ let x = a.charCodeAt(i)
26
+ let y = b.charCodeAt(i)
27
+ if (x !== y) return x < y
28
+ }
29
+ return a.length < b.length
30
+ }
31
+ let __usp_dec = (s) => {
32
+ let n = s.length
33
+ let buf = new Uint8Array(n)
34
+ let j = 0
35
+ for (let i = 0; i < n; i++) {
36
+ let c = s.charCodeAt(i)
37
+ if (c === 43) { buf[j] = 32; j = j + 1 }
38
+ else if (c === 37 && i + 2 < n) {
39
+ let h = __usp_hex(s.charCodeAt(i + 1))
40
+ let l = __usp_hex(s.charCodeAt(i + 2))
41
+ if (h >= 0 && l >= 0) { buf[j] = h * 16 + l; j = j + 1; i = i + 2 }
42
+ else { buf[j] = c; j = j + 1 }
43
+ } else { buf[j] = c; j = j + 1 }
44
+ }
45
+ return new TextDecoder().decode(buf.slice(0, j))
46
+ }
47
+ let __usp_esc = (s) => {
48
+ let out = ''
49
+ let n = s.length
50
+ for (let i = 0; i < n; i++) {
51
+ let c = s.charCodeAt(i)
52
+ if (c === 32) out = out + '+'
53
+ else if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 42 || c === 45 || c === 46 || c === 95) out = out + s[i]
54
+ else {
55
+ let h = c >> 4
56
+ let l = c & 15
57
+ out = out + '%' + String.fromCharCode(h < 10 ? 48 + h : 55 + h) + String.fromCharCode(l < 10 ? 48 + l : 55 + l)
58
+ }
59
+ }
60
+ return out
61
+ }
62
+ let __usp_new = (init) => {
63
+ let ks = []
64
+ let vs = []
65
+ let self = { __usp: 1, size: 0, get: undefined, getAll: undefined, has: undefined, append: undefined, set: undefined, delete: undefined, forEach: undefined, keys: undefined, values: undefined, entries: undefined, sort: undefined, toString: undefined }
66
+ let find = (k) => { for (let i = 0; i < ks.length; i++) { if (ks[i] === k) return i } return -1 }
67
+ self.get = (k) => { let i = find(String(k)); return i < 0 ? null : vs[i] }
68
+ self.getAll = (k) => { let key = String(k); let out = []; for (let i = 0; i < ks.length; i++) { if (ks[i] === key) out.push(vs[i]) } return out }
69
+ self.has = (k, v) => { let key = String(k); for (let i = 0; i < ks.length; i++) { if (ks[i] === key && (v === undefined || vs[i] === String(v))) return true } return false }
70
+ self.append = (k, v) => { ks.push(String(k)); vs.push(String(v)); self.size = ks.length; return undefined }
71
+ self.set = (k, v) => {
72
+ let key = String(k)
73
+ let i = find(key)
74
+ if (i < 0) { self.append(key, v); return undefined }
75
+ vs[i] = String(v)
76
+ let w = i + 1
77
+ for (let r = i + 1; r < ks.length; r++) {
78
+ if (ks[r] !== key) { ks[w] = ks[r]; vs[w] = vs[r]; w = w + 1 }
79
+ }
80
+ ks.splice(w)
81
+ vs.splice(w)
82
+ self.size = ks.length
83
+ return undefined
84
+ }
85
+ self.delete = (k, v) => {
86
+ let key = String(k)
87
+ let w = 0
88
+ for (let r = 0; r < ks.length; r++) {
89
+ if (!(ks[r] === key && (v === undefined || vs[r] === String(v)))) { ks[w] = ks[r]; vs[w] = vs[r]; w = w + 1 }
90
+ }
91
+ ks.splice(w)
92
+ vs.splice(w)
93
+ self.size = ks.length
94
+ return undefined
95
+ }
96
+ self.forEach = (fn) => { for (let i = 0; i < ks.length; i++) fn(vs[i], ks[i], self); return undefined }
97
+ self.keys = () => ks.slice()
98
+ self.values = () => vs.slice()
99
+ self.entries = () => { let out = []; for (let i = 0; i < ks.length; i++) out.push([ks[i], vs[i]]); return out }
100
+ self.sort = () => {
101
+ for (let i = 1; i < ks.length; i++) {
102
+ let k = ks[i]
103
+ let v = vs[i]
104
+ let j = i - 1
105
+ while (j >= 0 && __usp_lt(k, ks[j])) { ks[j + 1] = ks[j]; vs[j + 1] = vs[j]; j = j - 1 }
106
+ ks[j + 1] = k
107
+ vs[j + 1] = v
108
+ }
109
+ return undefined
110
+ }
111
+ self.toString = () => {
112
+ let out = ''
113
+ for (let i = 0; i < ks.length; i++) {
114
+ if (i > 0) out = out + '&'
115
+ out = out + __usp_esc(ks[i]) + '=' + __usp_esc(vs[i])
116
+ }
117
+ return out
118
+ }
119
+ if (init != null) {
120
+ if (typeof init === 'string') {
121
+ let s = init
122
+ if (s[0] === '?') s = s.slice(1)
123
+ if (s.length > 0) {
124
+ let parts = s.split('&')
125
+ for (let pi = 0; pi < parts.length; pi++) {
126
+ let part = parts[pi]
127
+ if (part.length > 0) {
128
+ let eq = part.indexOf('=')
129
+ if (eq < 0) self.append(__usp_dec(part), '')
130
+ else self.append(__usp_dec(part.slice(0, eq)), __usp_dec(part.slice(eq + 1)))
131
+ }
132
+ }
133
+ }
134
+ } else if (Array.isArray(init)) {
135
+ for (let pi = 0; pi < init.length; pi++) { let pair = init[pi]; self.append(pair[0], pair[1]) }
136
+ } else if (init.__usp === 1) {
137
+ init.forEach((v, k) => self.append(k, v))
138
+ } else {
139
+ let es = Object.entries(init)
140
+ for (let ei = 0; ei < es.length; ei++) { let e = es[ei]; self.append(e[0], e[1]) }
141
+ }
142
+ }
143
+ return self
144
+ }
145
+ `
package/layout.js CHANGED
@@ -80,14 +80,20 @@ export const ptrBits = (type, aux = 0, offset = 0) =>
80
80
  // `module/typedarray` stdlib.
81
81
  // =============================================================================
82
82
 
83
- /** Base element-type codes for PTR.TYPED aux (0–7). BigInt ctors share 7 + TYPED_ELEM_BIGINT_FLAG. */
83
+ /** Base element-type codes for PTR.TYPED aux (0–7). Flag-sharing kinds ride a
84
+ * base code (which fixes stride/shift) plus a discriminator bit: BigInt ctors
85
+ * share 7 + BIGINT_FLAG, Float16Array shares 3 (u16 storage) + F16_FLAG,
86
+ * Uint8ClampedArray shares 1 (u8 storage) + CLAMPED_FLAG. */
84
87
  export const TYPED_ELEM_CODE = {
85
88
  Int8Array: 0, Uint8Array: 1, Int16Array: 2, Uint16Array: 3,
86
89
  Int32Array: 4, Uint32Array: 5, Float32Array: 6, Float64Array: 7,
87
90
  BigInt64Array: 7, BigUint64Array: 7,
91
+ Float16Array: 3, Uint8ClampedArray: 1,
88
92
  }
89
93
  export const TYPED_ELEM_VIEW_FLAG = 8
90
94
  export const TYPED_ELEM_BIGINT_FLAG = 16
95
+ export const TYPED_ELEM_F16_FLAG = 32
96
+ export const TYPED_ELEM_CLAMPED_FLAG = 64
91
97
 
92
98
  export const TYPED_ELEM_NAMES = ['Int8Array', 'Uint8Array', 'Int16Array', 'Uint16Array',
93
99
  'Int32Array', 'Uint32Array', 'Float32Array', 'Float64Array']
@@ -97,7 +103,9 @@ export function encodeTypedElemAux(name, isView = false) {
97
103
  const et = TYPED_ELEM_CODE[name]
98
104
  if (et == null) return null
99
105
  return et | (isView ? TYPED_ELEM_VIEW_FLAG : 0) |
100
- (name === 'BigInt64Array' || name === 'BigUint64Array' ? TYPED_ELEM_BIGINT_FLAG : 0)
106
+ (name === 'BigInt64Array' || name === 'BigUint64Array' ? TYPED_ELEM_BIGINT_FLAG : 0) |
107
+ (name === 'Float16Array' ? TYPED_ELEM_F16_FLAG : 0) |
108
+ (name === 'Uint8ClampedArray' ? TYPED_ELEM_CLAMPED_FLAG : 0)
101
109
  }
102
110
 
103
111
  /** Encode a `typedElemCtor` string ('new.Int32Array' | 'new.Int32Array.view') to the 4-bit
@@ -116,7 +124,9 @@ export function typedElemAux(ctor) {
116
124
  export function ctorFromElemAux(aux) {
117
125
  if (aux == null) return null
118
126
  const isView = (aux & 8) !== 0
119
- const name = (aux & 16) !== 0 ? 'BigInt64Array' : TYPED_ELEM_NAMES[aux & 7]
127
+ const name = (aux & TYPED_ELEM_F16_FLAG) !== 0 ? 'Float16Array'
128
+ : (aux & TYPED_ELEM_CLAMPED_FLAG) !== 0 ? 'Uint8ClampedArray'
129
+ : (aux & 16) !== 0 ? 'BigInt64Array' : TYPED_ELEM_NAMES[aux & 7]
120
130
  if (!name) return null
121
131
  return isView ? `new.${name}.view` : `new.${name}`
122
132
  }
package/module/array.js CHANGED
@@ -13,7 +13,7 @@ import { inBoundsArrIdx } from '../src/type.js'
13
13
  import { emit, spread, deps, idx as emitIndex } from '../src/bridge.js'
14
14
  import { valTypeOf } from '../src/kind.js'
15
15
  import { extractParams, classifyParam, ASSIGN_OPS, refsName, REFS_IN_EXPR } from '../src/ast.js'
16
- import { staticPropertyKey, staticObjectProps, inlineArraySid, staticIndexKey, intLiteralValue } from '../src/static.js'
16
+ import { staticPropertyKey, staticObjectProps, inlineArraySid, staticIndexKey, intLiteralValue, structLiteralFields } from '../src/static.js'
17
17
  import { VAL, lookupValType, lookupNotString, updateRep } from '../src/reps.js'
18
18
  import { structInline } from '../src/abi/index.js'
19
19
  import { ctx, inc, err, warnDeopt, PTR, LAYOUT, followForwardingWat } from '../src/ctx.js'
@@ -62,25 +62,6 @@ function hoistArrayValue(arr) {
62
62
 
63
63
  const arrayLenFromPtr = ptr => ['i32.load', ['i32.sub', ['local.get', `$${ptr}`], ['i32.const', 8]]]
64
64
 
65
- /** K schema-ordered field-value AST nodes of an object literal `{S}` for a
66
- * structInline `.push({S})`, or null if `lit` is not a plain static-key `{}`
67
- * literal carrying exactly schema `sid`'s fields. Mapped by name into schema
68
- * order so push sites with differing key order flatten to the same cell run. */
69
- function structLiteralFields(lit, sid) {
70
- if (!Array.isArray(lit) || lit[0] !== '{}') return null
71
- const parsed = staticObjectProps(lit.slice(1))
72
- const schema = ctx.schema.list[sid]
73
- if (!parsed || parsed.names.length !== schema.length) return null
74
- const byName = new Map()
75
- for (let i = 0; i < parsed.names.length; i++) byName.set(parsed.names[i], parsed.values[i])
76
- const out = []
77
- for (const name of schema) {
78
- if (!byName.has(name)) return null
79
- out.push(byName.get(name))
80
- }
81
- return out
82
- }
83
-
84
65
  // Pure-expression check: no statements, binders, control flow, or assignments.
85
66
  // Inlining is only safe for these — anything else needs the full closure machinery.
86
67
  const NOT_PURE_OPS = new Set([
@@ -854,11 +835,16 @@ export default (ctx) => {
854
835
  if (inlSid != null) {
855
836
  const baseI32 = tempI32('ab')
856
837
  const K = ctx.schema.list[inlSid].length
857
- const cell = typed(structInline(K).ops.elemAddr(
838
+ const packed = ctx.schema.inlineCellI32?.has(inlSid)
839
+ const cell = typed(structInline(K, packed).ops.elemAddr(
858
840
  ['local.tee', `$${baseI32}`, arrBase()],
859
841
  vi), 'i32')
860
842
  cell.ptrKind = VAL.OBJECT
861
843
  cell.ptrAux = inlSid
844
+ // Packed i32 cells: slot access through this node (and through cursor
845
+ // locals bound to it — readVar re-derives via inlineCellCursors) must
846
+ // pick the packedI32 ops.
847
+ if (packed) cell.cellI32 = true
862
848
  return cell
863
849
  }
864
850
  // Known-ARRAY → __arr_idx (single forwarding follow + inline bounds check),
@@ -1010,14 +996,18 @@ export default (ctx) => {
1010
996
  // statement-position hint now so a dropped `xs.push(v)` can skip computing
1011
997
  // the JS return length while still performing the mutation/writeback.
1012
998
  const void_ = ctx.func._expect === 'void'
1013
- // structInline Array<S>: `.push({S})` writes the K schema fields as K
1014
- // consecutive f64 cells. Flatten the struct literal into K schema-ordered
999
+ // structInline Array<S>: `.push({S})` writes the K schema fields as
1000
+ // consecutive cells. Flatten the struct literal into K schema-ordered
1015
1001
  // field-value nodes and fall through to the general multi-value store path
1016
- // — `len`/`cap` count physical cells, so `__arr_grow_known` and the cell
1017
- // loop are reused untouched; `.push` then returns the logical element count
1018
- // (`len / K`). K=1 stays a single value → the `__arr_push1` fast path.
1002
+ // — `len`/`cap` count physical 8-byte cells, so `__arr_grow_known` and the
1003
+ // cell loop are reused untouched; `.push` then returns the logical element
1004
+ // count (`len / cellsPerElem`). K=1 stays a single value → the
1005
+ // `__arr_push1` fast path. Packed schemas (inlineCellI32) store K raw i32
1006
+ // fields into ⌈K/2⌉ cells — the store loop below branches per layout.
1019
1007
  const inlSid = inlineArraySid(arr)
1020
1008
  const inlK = inlSid != null ? ctx.schema.list[inlSid].length : 0
1009
+ const inlPacked = inlSid != null && ctx.schema.inlineCellI32?.has(inlSid)
1010
+ const inlCpe = inlSid != null ? structInline(inlK, inlPacked).cpe : 1
1021
1011
  if (inlSid != null) {
1022
1012
  const flat = []
1023
1013
  for (const v of vals) {
@@ -1027,6 +1017,8 @@ export default (ctx) => {
1027
1017
  }
1028
1018
  vals = flat
1029
1019
  }
1020
+ // Physical cells this push appends (grow target + len increment basis).
1021
+ const pushCells = inlPacked ? inlCpe * (vals.length / inlK) : vals.length
1030
1022
  // Out-of-line fast path: single value, named known-ARRAY receiver. One call +
1031
1023
  // var update instead of ~30 inlined instructions — the dominant size cost of
1032
1024
  // push-heavy code (e.g. watr's WASM emitter).
@@ -1071,31 +1063,45 @@ export default (ctx) => {
1071
1063
  ['if',
1072
1064
  ['i32.lt_s',
1073
1065
  ['i32.load', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 4]]],
1074
- ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]],
1066
+ ['i32.add', ['local.get', `$${len}`], ['i32.const', pushCells]]],
1075
1067
  ['then',
1076
1068
  ['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
1077
- ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]]],
1069
+ ['i32.add', ['local.get', `$${len}`], ['i32.const', pushCells]]]],
1078
1070
  ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]],
1079
1071
  )
1080
1072
  } else {
1081
1073
  body.push(
1082
1074
  ['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
1083
- // Grow if needed: ensure cap >= len + vals.length
1075
+ // Grow if needed: ensure cap >= len + pushCells
1084
1076
  ['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
1085
- ['i32.add', ['local.get', `$${len}`], ['i32.const', vals.length]]]],
1077
+ ['i32.add', ['local.get', `$${len}`], ['i32.const', pushCells]]]],
1086
1078
  ['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
1087
1079
  )
1088
1080
  }
1089
1081
 
1090
- // Store each value and increment len
1091
- for (const val of vals) {
1092
- const vv = carrierF64(val, emit(val))
1093
- body.push(
1094
- ['f64.store',
1095
- ['i32.add', ['local.get', `$${pushBase}`], ['i32.shl', ['local.get', `$${len}`], ['i32.const', 3]]],
1096
- vv],
1097
- ['local.set', `$${len}`, ['i32.add', ['local.get', `$${len}`], ['i32.const', 1]]]
1098
- )
1082
+ if (inlPacked) {
1083
+ // Packed i32 cells: per element, K raw i32 stores at `base + len*8 + j*4`
1084
+ // (schema order — structLiteralFields already ordered the flatten), then
1085
+ // len advances by ⌈K/2⌉ physical cells. Values are exact by the
1086
+ // slotI32Certain census (packing precondition).
1087
+ for (let e = 0; e < vals.length; e += inlK) {
1088
+ for (let j = 0; j < inlK; j++) {
1089
+ const off = ['i32.add', ['local.get', `$${pushBase}`], ['i32.shl', ['local.get', `$${len}`], ['i32.const', 3]]]
1090
+ body.push(['i32.store', j === 0 ? off : ['i32.add', off, ['i32.const', j * 4]], asI32(emit(vals[e + j]))])
1091
+ }
1092
+ body.push(['local.set', `$${len}`, ['i32.add', ['local.get', `$${len}`], ['i32.const', inlCpe]]])
1093
+ }
1094
+ } else {
1095
+ // Store each value and increment len
1096
+ for (const val of vals) {
1097
+ const vv = carrierF64(val, emit(val))
1098
+ body.push(
1099
+ ['f64.store',
1100
+ ['i32.add', ['local.get', `$${pushBase}`], ['i32.shl', ['local.get', `$${len}`], ['i32.const', 3]]],
1101
+ vv],
1102
+ ['local.set', `$${len}`, ['i32.add', ['local.get', `$${len}`], ['i32.const', 1]]]
1103
+ )
1104
+ }
1099
1105
  }
1100
1106
 
1101
1107
  // Update length header (write directly via the offset we already hold —
@@ -1113,9 +1119,10 @@ export default (ctx) => {
1113
1119
  body.push(['local.set', `$${arr}`, ['local.get', `$${t}`]])
1114
1120
  }
1115
1121
  // structInline: `len` counts physical cells — `.push` returns the JS array
1116
- // length, i.e. the logical element count `len / K`.
1117
- body.push(['f64.convert_i32_s', inlK > 1
1118
- ? ['i32.div_s', ['local.get', `$${len}`], ['i32.const', inlK]]
1122
+ // length, i.e. the logical element count `len / cellsPerElem`.
1123
+ const lenDiv = inlPacked ? inlCpe : inlK
1124
+ body.push(['f64.convert_i32_s', lenDiv > 1
1125
+ ? ['i32.div_s', ['local.get', `$${len}`], ['i32.const', lenDiv]]
1119
1126
  : ['local.get', `$${len}`]])
1120
1127
 
1121
1128
  return typed(['block', ['result', 'f64'], ...body], 'f64')