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.
package/src/host.js ADDED
@@ -0,0 +1,536 @@
1
+ /**
2
+ * Interop runtime — JS ↔ WASM value marshaling.
3
+ *
4
+ * NaN-boxing encoder/decoder, bump allocation, schema transport, host-object
5
+ * handling. This is the *package* runtime that runs after compilation — it is
6
+ * kept separate from the *compiler core* (index.js jz.compile) so the minimal
7
+ * "pure scalar" story remains clean.
8
+ *
9
+ * Exports:
10
+ * UNDEF_NAN, NULL_NAN, coerce — null/undefined sentinels
11
+ * ptr / offset / type / aux — NaN-boxed pointer codec
12
+ * memory(src) — enhance a WebAssembly.Memory
13
+ * wrap(memSrc, inst?) — adapt WASM exports to JS calling convention
14
+ * instantiate(compile, code, opts?) — compile + instantiate + wrap
15
+ *
16
+ * @module runtime
17
+ */
18
+
19
+ import { wasi } from '../wasi.js'
20
+
21
+ // NaN-boxing encode/decode — shared 8-byte scratch buffer
22
+ const _buf = new ArrayBuffer(8), _u32 = new Uint32Array(_buf), _f64 = new Float64Array(_buf)
23
+ // Sentinel NaN for "undefined/missing arg" (payload=1, distinct from JS NaN payload=0)
24
+ _u32[1] = 0x7FF80000; _u32[0] = 1; export const UNDEF_NAN = _f64[0]
25
+ // Null NaN: type=0 (ATOM), aux=1, offset=0 — distinct from 0, NaN, and UNDEF_NAN
26
+ _u32[1] = 0x7FF80001; _u32[0] = 0; export const NULL_NAN = _f64[0]
27
+
28
+ // Coerce JS null/undefined → NaN-boxed sentinels for WASM boundary
29
+ export const coerce = v => v === null ? NULL_NAN : v === undefined ? UNDEF_NAN : v
30
+
31
+ // Decode f64 return value: null/undefined sentinels → JS values, numbers pass through
32
+ const decode = v => {
33
+ if (v === v) return v // fast path: non-NaN
34
+ _f64[0] = v
35
+ if (_u32[1] === 0x7FF80001 && _u32[0] === 0) return null
36
+ if (_u32[1] === 0x7FF80000 && _u32[0] === 1) return undefined
37
+ return v
38
+ }
39
+
40
+ export const ptr = (type, aux, offset) => {
41
+ _u32[1] = (0x7FF80000 | ((type & 0xF) << 15) | (aux & 0x7FFF)) >>> 0
42
+ _u32[0] = offset >>> 0; return _f64[0]
43
+ }
44
+ export const offset = (p) => { _f64[0] = p; return _u32[0] }
45
+ export const type = (p) => { _f64[0] = p; return (_u32[1] >>> 15) & 0xF }
46
+ export const aux = (p) => { _f64[0] = p; return _u32[1] & 0x7FFF }
47
+
48
+ // Typed element metadata: [elemId, byteStride, DataView getter, DataView setter]
49
+ const ELEMS = {
50
+ Int8Array: [0, 1, 'getInt8', 'setInt8'],
51
+ Uint8Array: [1, 1, 'getUint8', 'setUint8'],
52
+ Int16Array: [2, 2, 'getInt16', 'setInt16'],
53
+ Uint16Array: [3, 2, 'getUint16', 'setUint16'],
54
+ Int32Array: [4, 4, 'getInt32', 'setInt32'],
55
+ Uint32Array: [5, 4, 'getUint32', 'setUint32'],
56
+ Float32Array: [6, 4, 'getFloat32', 'setFloat32'],
57
+ Float64Array: [7, 8, 'getFloat64', 'setFloat64'],
58
+ }
59
+ // Pre-built lookup by element ID (avoids Object.values on each access)
60
+ const ELEM_BY_ID = Object.values(ELEMS)
61
+
62
+ const _enhanced = new WeakSet()
63
+
64
+ /**
65
+ * Enhance WebAssembly.Memory with jz read/write methods (monkey-patch).
66
+ * - memory() → create new Memory, patch, return
67
+ * - memory({ initial: N }) → create with options, patch, return
68
+ * - memory(wasmMemory) → patch existing, return same object
69
+ * - memory(instanceResult) → bind to instance (patch its memory, bind alloc/schemas/extMap)
70
+ */
71
+ export const memory = (src) => {
72
+ // Already enhanced — return as-is (idempotent)
73
+ if (src instanceof WebAssembly.Memory && _enhanced.has(src)) return src
74
+
75
+ // Create new Memory from nothing or options
76
+ if (!src || (typeof src === 'object' && !(src instanceof WebAssembly.Memory) && !src.instance && !src.exports && !src.memory)) {
77
+ const mem = new WebAssembly.Memory({ initial: src?.initial || 1, ...(src?.maximum ? { maximum: src.maximum } : {}), ...(src?.shared ? { shared: src.shared } : {}) })
78
+ return memory(mem)
79
+ }
80
+
81
+ // Resolve the WebAssembly.Memory object
82
+ let mem, wasmExports, extMap, mod
83
+ if (src instanceof WebAssembly.Memory) {
84
+ mem = src
85
+ wasmExports = null
86
+ extMap = null
87
+ mod = null
88
+ } else {
89
+ // Instance result: { module, instance, exports, extMap }
90
+ const raw = src?.instance?.exports || src?.exports || src
91
+ mem = src?.exports?.memory || raw.memory
92
+ if (!mem) return null // pure scalar module — no memory
93
+ wasmExports = { ...raw, memory: mem }
94
+ extMap = src.extMap || null
95
+ mod = src.module || null
96
+ }
97
+
98
+ const dv = () => new DataView(mem.buffer)
99
+
100
+ // JS-side bump allocator (heap ptr at byte 1020, same convention as WASM)
101
+ const jsAlloc = (bytes) => {
102
+ const d = dv(), p = d.getInt32(1020, true)
103
+ const aligned = (p + 7) & ~7 // 8-byte align
104
+ const next = aligned + bytes
105
+ if (next > mem.buffer.byteLength) mem.grow(Math.ceil((next - mem.buffer.byteLength) / 65536))
106
+ d.setInt32(1020, next, true)
107
+ return aligned
108
+ }
109
+
110
+ // Use WASM allocator if available, else JS-side bump
111
+ let alloc = wasmExports?._alloc || jsAlloc
112
+
113
+ // Initialize heap pointer if not yet set
114
+ const initDv = dv()
115
+ if (initDv.getInt32(1020, true) < 1024) initDv.setInt32(1020, 1024, true)
116
+
117
+ // Write header (len + cap), return data offset
118
+ const hdr = (len, cap, bytes) => {
119
+ const raw = alloc(8 + bytes)
120
+ const m = dv()
121
+ m.setInt32(raw, len, true)
122
+ m.setInt32(raw + 4, cap, true)
123
+ return raw + 8
124
+ }
125
+
126
+ // Read schemas from module custom section, merge into memory.schemas
127
+ let schemas = mem.schemas || []
128
+ if (mod) {
129
+ const secs = WebAssembly.Module.customSections(mod, 'jz:schema')
130
+ if (secs.length) {
131
+ const b = new Uint8Array(secs[0]), td = new TextDecoder()
132
+ let i = 0
133
+ const varint = () => { let r = 0, s = 0; while (1) { const x = b[i++]; r |= (x & 0x7F) << s; if (!(x & 0x80)) return r; s += 7 } }
134
+ const dec = () => {
135
+ const t = b[i++]
136
+ if (t === 0) return null
137
+ if (t === 1) return [null, dec()]
138
+ const n = varint(), s = td.decode(b.subarray(i, i + n)); i += n; return s
139
+ }
140
+ const nS = varint(), newSchemas = []
141
+ for (let j = 0; j < nS; j++) { const k = varint(), props = []; for (let p = 0; p < k; p++) props.push(dec()); newSchemas.push(props) }
142
+ for (const s of newSchemas) {
143
+ const key = s.join(',')
144
+ if (!schemas.some(existing => existing.join(',') === key)) schemas.push(s)
145
+ }
146
+ }
147
+ }
148
+
149
+ // If already enhanced, just update bindings (new module compiled into same memory)
150
+ if (_enhanced.has(mem)) {
151
+ mem.schemas = schemas
152
+ if (wasmExports?._alloc) { alloc = wasmExports._alloc; mem.alloc = alloc }
153
+ if (wasmExports?._reset) mem.reset = wasmExports._reset
154
+ if (extMap) mem._extMap = extMap
155
+ return mem
156
+ }
157
+
158
+ // Patch methods onto the Memory instance
159
+ mem.schemas = schemas
160
+ mem._extMap = extMap
161
+
162
+ mem.Array = (data) => {
163
+ const n = data.length, off = hdr(n, n, n * 8), m = dv()
164
+ for (let i = 0; i < n; i++) m.setFloat64(off + i * 8, mem.wrapVal(data[i]), true)
165
+ return ptr(1, 0, off)
166
+ }
167
+
168
+ mem.String = (str) => {
169
+ if (str.length <= 4 && /^[\x00-\x7f]*$/.test(str)) {
170
+ let packed = 0
171
+ for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
172
+ return ptr(5, str.length, packed) // SSO
173
+ }
174
+ const enc = new TextEncoder().encode(str)
175
+ const n = enc.length, raw = alloc(4 + n), m = dv()
176
+ m.setInt32(raw, n, true)
177
+ const off = raw + 4
178
+ enc.forEach((b, i) => m.setUint8(off + i, b))
179
+ return ptr(4, 0, off)
180
+ }
181
+
182
+ mem.Buffer = (data) => {
183
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data)
184
+ : ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
185
+ : new Uint8Array(data)
186
+ const n = bytes.length, off = hdr(n, n, n), m = new Uint8Array(mem.buffer)
187
+ m.set(bytes, off)
188
+ return ptr(2, 0, off)
189
+ }
190
+
191
+ mem.wrapVal = function(v) {
192
+ if (v === null || v === undefined) return coerce(v)
193
+ if (typeof v === 'number' || typeof v === 'boolean') return Number(v)
194
+ if (typeof v === 'string') return this.String(v)
195
+ if (Array.isArray(v)) return this.Array(v)
196
+ if (v instanceof ArrayBuffer) return this.Buffer(v)
197
+ if (v instanceof DataView) return this.Buffer(v.buffer)
198
+ const typedName = v?.constructor?.name
199
+ if (typedName && ELEMS[typedName]) return this[typedName](v)
200
+ if (typeof v === 'object' || typeof v === 'function') return this.External(v)
201
+ return UNDEF_NAN
202
+ }
203
+
204
+ mem.External = function(obj) {
205
+ if (obj === null || obj === undefined) return coerce(obj)
206
+ const map = this._extMap
207
+ if (!map) return UNDEF_NAN
208
+ let id = map.indexOf(obj)
209
+ if (id === -1) { id = map.length; map.push(obj) }
210
+ return ptr(11, 0, id)
211
+ }
212
+
213
+ mem.Object = function(obj) {
214
+ const objKeys = Object.keys(obj)
215
+ const key = objKeys.join(',')
216
+ const schemas = this.schemas
217
+ let sid = schemas.findIndex(s => s.join(',') === key)
218
+ if (sid === -1) {
219
+ const matches = schemas.reduce((a, s, i) =>
220
+ (s.length === objKeys.length && objKeys.every(k => s.includes(k)) ? a.concat(i) : a), [])
221
+ if (matches.length === 1) sid = matches[0]
222
+ else if (matches.length > 1) throw Error(`Ambiguous schema for {${key}} — pass keys in schema order`)
223
+ else if (this._extMap) return this.External(obj)
224
+ else throw Error(`No schema for {${key}}`)
225
+ }
226
+ const schema = schemas[sid], n = schema.length, raw = alloc(n * 8), m = dv()
227
+ for (let i = 0; i < n; i++) {
228
+ let v = obj[schema[i]]
229
+ if (v === null || v === undefined) v = coerce(v)
230
+ else if (typeof v === 'string') v = this.String(v)
231
+ else if (Array.isArray(v)) v = this.Array(v)
232
+ m.setFloat64(raw + i * 8, v, true)
233
+ }
234
+ return ptr(6, sid, raw)
235
+ }
236
+
237
+ mem.read = function(p) {
238
+ if (Array.isArray(p)) return p.map(v => this.read(v)) // multi-value tuple
239
+ if (p === p) return p // regular number passthrough (NaN fails ===)
240
+ const t = type(p), a = aux(p), off = offset(p)
241
+ if (t === 0 && a === 1 && off === 0) return null
242
+ if (t === 0 && a === 0 && off === 1) return undefined
243
+ if (t === 11 && this._extMap) return this._extMap[off]
244
+ if (t === 1) { // ARRAY
245
+ let m = dv(), aOff = off
246
+ // Follow forwarding pointers (cap === -1 means array was reallocated)
247
+ while (m.getInt32(aOff - 4, true) === -1) aOff = m.getInt32(aOff - 8, true)
248
+ const len = m.getInt32(aOff - 8, true), out = new Array(len)
249
+ for (let i = 0; i < len; i++) out[i] = this.read(m.getFloat64(aOff + i * 8, true))
250
+ return out
251
+ }
252
+ if (t === 3) { // TYPED
253
+ const a2 = aux(p), elem = a2 & 7
254
+ const [, stride] = ELEM_BY_ID[elem]
255
+ const Ctor = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array][elem]
256
+ const m = dv()
257
+ if (a2 & 8) {
258
+ const byteLen = m.getInt32(off, true), dataOff = m.getInt32(off + 4, true)
259
+ return new Ctor(mem.buffer, dataOff, byteLen / stride)
260
+ }
261
+ const byteLen = m.getInt32(off - 8, true)
262
+ return new Ctor(mem.buffer, off, byteLen / stride)
263
+ }
264
+ if (t === 2) { // BUFFER
265
+ const byteLen = dv().getInt32(off - 8, true)
266
+ const out = new ArrayBuffer(byteLen)
267
+ new Uint8Array(out).set(new Uint8Array(mem.buffer, off, byteLen))
268
+ return out
269
+ }
270
+ if (t === 4) { // STRING (heap)
271
+ const len = dv().getInt32(off - 4, true)
272
+ return new TextDecoder().decode(new Uint8Array(mem.buffer, off, len))
273
+ }
274
+ if (t === 5) { // STRING_SSO
275
+ const len = aux(p); let s = ''
276
+ for (let i = 0; i < len; i++) s += String.fromCharCode((off >>> (i * 8)) & 0xFF)
277
+ return s
278
+ }
279
+ if (t === 6) { // OBJECT
280
+ const m = dv(), sid = aux(p), keys = this.schemas[sid]
281
+ if (!keys) return p
282
+ const obj = {}
283
+ for (let i = 0; i < keys.length; i++) obj[keys[i]] = this.read(m.getFloat64(off + i * 8, true))
284
+ return obj
285
+ }
286
+ if (t === 7) { // HASH
287
+ const m = dv(), size = m.getInt32(off - 8, true), cap = m.getInt32(off - 4, true)
288
+ const obj = {}
289
+ for (let i = 0, found = 0; i < cap && found < size; i++) {
290
+ const hash = m.getFloat64(off + i * 24, true)
291
+ if (hash !== 0) {
292
+ const key = this.read(m.getFloat64(off + i * 24 + 8, true))
293
+ obj[key] = this.read(m.getFloat64(off + i * 24 + 16, true))
294
+ found++
295
+ }
296
+ }
297
+ return obj
298
+ }
299
+ if (t === 8) { // SET
300
+ const m = dv(), size = m.getInt32(off - 8, true), cap = m.getInt32(off - 4, true)
301
+ const set = new Set()
302
+ for (let i = 0; i < cap && set.size < size; i++) {
303
+ const hash = m.getFloat64(off + i * 16, true)
304
+ if (hash !== 0) set.add(this.read(m.getFloat64(off + i * 16 + 8, true)))
305
+ }
306
+ return set
307
+ }
308
+ if (t === 9) { // MAP
309
+ const m = dv(), size = m.getInt32(off - 8, true), cap = m.getInt32(off - 4, true)
310
+ const map = new Map()
311
+ for (let i = 0; i < cap && map.size < size; i++) {
312
+ const hash = m.getFloat64(off + i * 24, true)
313
+ if (hash !== 0) map.set(this.read(m.getFloat64(off + i * 24 + 8, true)), this.read(m.getFloat64(off + i * 24 + 16, true)))
314
+ }
315
+ return map
316
+ }
317
+ if (t === 10) return p // CLOSURE
318
+ return p
319
+ }
320
+
321
+ mem.write = function(p, data) {
322
+ const t = type(p), off = offset(p), m = dv()
323
+ if (t === 1) {
324
+ const cap = m.getInt32(off - 4, true)
325
+ if (data.length > cap) throw Error(`write: ${data.length} exceeds capacity ${cap}`)
326
+ m.setInt32(off - 8, data.length, true)
327
+ for (let i = 0; i < data.length; i++) m.setFloat64(off + i * 8, coerce(data[i]), true)
328
+ } else if (t === 3) {
329
+ const a2 = aux(p), elem = a2 & 7
330
+ const [, stride, , setter] = ELEM_BY_ID[elem]
331
+ const byteLen = data.length * stride
332
+ if (a2 & 8) {
333
+ const viewByteLen = m.getInt32(off, true), dataOff = m.getInt32(off + 4, true)
334
+ if (byteLen > viewByteLen) throw Error(`write: ${byteLen} bytes exceeds view size ${viewByteLen}`)
335
+ for (let i = 0; i < data.length; i++) m[setter](dataOff + i * stride, data[i], true)
336
+ } else {
337
+ const byteCap = m.getInt32(off - 4, true)
338
+ if (byteLen > byteCap) throw Error(`write: ${byteLen} bytes exceeds capacity ${byteCap}`)
339
+ m.setInt32(off - 8, byteLen, true)
340
+ for (let i = 0; i < data.length; i++) m[setter](off + i * stride, data[i], true)
341
+ }
342
+ } else if (t === 6) {
343
+ const schema = this.schemas[aux(p)]
344
+ if (!schema) throw Error(`write: unknown schema`)
345
+ for (const k of Object.keys(data)) {
346
+ const i = schema.indexOf(k)
347
+ if (i >= 0) m.setFloat64(off + i * 8, coerce(data[k]), true)
348
+ }
349
+ } else {
350
+ throw Error(`write: unsupported type ${t}`)
351
+ }
352
+ }
353
+
354
+ mem.alloc = alloc
355
+ mem.reset = wasmExports?._reset || null
356
+
357
+ // TypedArray constructors: memory.Float64Array(data), etc.
358
+ for (const [name, [elemId, stride, , setter]] of Object.entries(ELEMS)) {
359
+ mem[name] = (data) => {
360
+ const n = data.length, bytes = n * stride, off = hdr(bytes, bytes, bytes), m = dv()
361
+ for (let i = 0; i < n; i++) m[setter](off + i * stride, data[i], true)
362
+ return ptr(3, elemId, off)
363
+ }
364
+ }
365
+
366
+ _enhanced.add(mem)
367
+ return mem
368
+ }
369
+
370
+ /**
371
+ * Wrap raw WASM exports with JS calling convention adaptation.
372
+ * Handles: undefined → sentinel NaN for defaults, rest-param array packing.
373
+ */
374
+ export const wrap = (memSrc, inst) => {
375
+ const restFuncs = new Map()
376
+ const mod = inst ? memSrc : memSrc.module || memSrc
377
+ const realInst = inst || memSrc.instance || memSrc
378
+ const restSecs = WebAssembly.Module.customSections(mod, 'jz:rest')
379
+ if (restSecs.length) {
380
+ try {
381
+ for (const entry of JSON.parse(new TextDecoder().decode(restSecs[0])))
382
+ restFuncs.set(typeof entry === 'string' ? entry : entry.name, typeof entry === 'string' ? 0 : entry.fixed)
383
+ } catch (e) { /* ignore */ }
384
+ }
385
+
386
+ const mem = memory(memSrc)
387
+ const lastErrBits = realInst.exports.__jz_last_err_bits
388
+ const decodeThrown = error => {
389
+ if (!(error instanceof WebAssembly.Exception) || !lastErrBits) throw error
390
+ const bits = lastErrBits.value
391
+ _u32[0] = Number(bits & 0xffffffffn)
392
+ _u32[1] = Number((bits >> 32n) & 0xffffffffn)
393
+ const value = mem ? mem.read(_f64[0]) : _f64[0]
394
+ if (value instanceof Error) throw value
395
+ const wrapped = new Error(typeof value === 'string' ? value : String(value))
396
+ wrapped.cause = error
397
+ wrapped.thrown = value
398
+ throw wrapped
399
+ }
400
+ const exports = {}
401
+ // Pure scalar module (no memory): pass f64 values directly, no marshaling
402
+ if (!mem) {
403
+ for (const [name, fn] of Object.entries(realInst.exports))
404
+ exports[name] = typeof fn === 'function'
405
+ ? (...args) => { while (args.length < fn.length) args.push(undefined); try { return decode(fn(...args.map(coerce))) } catch (e) { decodeThrown(e) } }
406
+ : fn
407
+ return exports
408
+ }
409
+ for (const [name, fn] of Object.entries(realInst.exports)) {
410
+ if (restFuncs.has(name) && typeof fn === 'function') {
411
+ const fixed = restFuncs.get(name)
412
+ exports[name] = (...args) => {
413
+ const a = args.slice(0, fixed).map(x => mem.wrapVal(x))
414
+ while (a.length < fixed) a.push(UNDEF_NAN)
415
+ a.push(mem.Array(args.slice(fixed)))
416
+ try {
417
+ return mem.read(fn.apply(null, a))
418
+ } catch (error) {
419
+ decodeThrown(error)
420
+ }
421
+ }
422
+ } else if (typeof fn === 'function') {
423
+ exports[name] = (...args) => {
424
+ while (args.length < fn.length) args.push(undefined)
425
+ try {
426
+ return mem.read(fn.apply(null, args.map(x => mem.wrapVal(x))))
427
+ } catch (error) {
428
+ decodeThrown(error)
429
+ }
430
+ }
431
+ } else {
432
+ exports[name] = fn
433
+ }
434
+ }
435
+ return exports
436
+ }
437
+
438
+ const prepareInterop = (opts) => {
439
+ const state = { extMap: [null], mem: null }
440
+ opts._interp = opts._interp || {}
441
+ opts._interp.__ext_prop = (objPtr, propPtr) => {
442
+ const obj = state.extMap[offset(objPtr)]
443
+ const prop = state.mem.read(propPtr)
444
+ return state.mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop])
445
+ }
446
+ opts._interp.__ext_has = (objPtr, propPtr) => {
447
+ return (state.mem.read(propPtr) in state.extMap[offset(objPtr)]) ? 1 : 0
448
+ }
449
+ opts._interp.__ext_set = (objPtr, propPtr, valPtr) => {
450
+ state.extMap[offset(objPtr)][state.mem.read(propPtr)] = state.mem.read(valPtr)
451
+ return 1
452
+ }
453
+ opts._interp.__ext_call = (objPtr, propPtr, argsPtr) => {
454
+ const obj = state.extMap[offset(objPtr)]
455
+ const prop = state.mem.read(propPtr)
456
+ const args = state.mem.read(argsPtr)
457
+ return state.mem.wrapVal(obj[prop].apply(obj, args))
458
+ }
459
+ return state
460
+ }
461
+
462
+ const buildImports = (mod, opts, state) => {
463
+ const needsWasi = WebAssembly.Module.imports(mod).some(i => i.module === 'wasi_snapshot_preview1')
464
+ const imports = needsWasi ? wasi(opts) : {}
465
+ if (opts._interp) imports.env = { ...imports.env, ...opts._interp }
466
+
467
+ // Host imports: decode NaN-boxed args for JS and wrap JS returns back into jz values.
468
+ if (opts.imports) for (const [modName, fns] of Object.entries(opts.imports)) {
469
+ if (!imports[modName]) imports[modName] = {}
470
+ for (const name of Object.getOwnPropertyNames(fns)) {
471
+ const spec = fns[name]
472
+ const fn = typeof spec === 'function' ? spec : (spec && typeof spec === 'object' ? spec.fn : null)
473
+ if (typeof fn === 'function')
474
+ imports[modName][name] = (...args) => {
475
+ const ret = fn.call(fns, ...args.map(a => state.mem ? state.mem.read(a) : a))
476
+ return state.mem ? state.mem.wrapVal(ret) : coerce(ret)
477
+ }
478
+ }
479
+ }
480
+ // Shared memory: normalize (auto-wrap raw Memory), pass as import
481
+ if (opts.memory) {
482
+ // Auto-wrap raw WebAssembly.Memory → enhanced jz.memory
483
+ if (opts.memory instanceof WebAssembly.Memory && !_enhanced.has(opts.memory)) opts.memory = memory(opts.memory)
484
+ if (!imports.env) imports.env = {}
485
+ imports.env.memory = opts.memory instanceof WebAssembly.Memory ? opts.memory : opts.memory
486
+ }
487
+ // Auto-imported host globals: provide as WebAssembly.Global wrapping NaN-boxed external refs
488
+ for (const imp of WebAssembly.Module.imports(mod)) {
489
+ if (imp.kind === 'global' && imp.module === 'env') {
490
+ const host = globalThis[imp.name]
491
+ if (host !== undefined) {
492
+ if (!imports.env) imports.env = {}
493
+ let id = state.extMap.indexOf(host); if (id === -1) { id = state.extMap.length; state.extMap.push(host) }
494
+ imports.env[imp.name] = new WebAssembly.Global({ value: 'f64', mutable: true }, ptr(11, 0, id))
495
+ }
496
+ }
497
+ }
498
+ return { imports, needsWasi }
499
+ }
500
+
501
+ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
502
+ if (needsWasi) imports._setMemory(inst.exports.memory)
503
+
504
+ // Drive WASM timer queue via JS scheduling (non-blocking)
505
+ if (inst.exports.__timer_tick) {
506
+ const tick = inst.exports.__timer_tick
507
+ let hadTimers = false
508
+ const id = setInterval(() => {
509
+ const remaining = tick()
510
+ if (remaining > 0) hadTimers = true
511
+ if (hadTimers && remaining <= 0) clearInterval(id)
512
+ }, 1)
513
+ }
514
+
515
+ // For shared memory, resolve memory from import; for own memory, from export
516
+ const rawMemory = opts.memory || inst.exports.memory
517
+ const memSrc = { module: mod, instance: inst, exports: { ...inst.exports, memory: rawMemory }, extMap: state.extMap }
518
+ const enhanced = memory(memSrc)
519
+ state.mem = enhanced
520
+ return { exports: wrap(memSrc), memory: enhanced, instance: inst, module: mod }
521
+ }
522
+
523
+ /**
524
+ * Compile, instantiate, and wrap exports (with WASI + rest-param support).
525
+ * `compile` is the jz.compile function (injected to avoid importing the compiler core).
526
+ */
527
+ export const instantiate = (compile, code, opts = {}) => {
528
+ const state = prepareInterop(opts)
529
+ const wasm = compile(code, opts)
530
+ opts.extMap = state.extMap
531
+ const mod = new WebAssembly.Module(wasm)
532
+ const { imports, needsWasi } = buildImports(mod, opts, state)
533
+ const hasImports = Object.keys(imports).some(k => k !== '_setMemory')
534
+ const inst = new WebAssembly.Instance(mod, hasImports ? imports : undefined)
535
+ return finishInstantiation(mod, inst, imports, needsWasi, opts, state)
536
+ }