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/LICENSE +21 -0
- package/README.md +373 -0
- package/cli.js +163 -0
- package/index.js +247 -0
- package/module/array.js +1322 -0
- package/module/collection.js +793 -0
- package/module/console.js +192 -0
- package/module/core.js +644 -0
- package/module/function.js +181 -0
- package/module/index.js +15 -0
- package/module/json.js +506 -0
- package/module/math.js +390 -0
- package/module/number.js +601 -0
- package/module/object.js +359 -0
- package/module/regex.js +914 -0
- package/module/schema.js +106 -0
- package/module/string.js +985 -0
- package/module/symbol.js +55 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +713 -0
- package/package.json +56 -5
- package/src/analyze.js +1927 -0
- package/src/autoload.js +175 -0
- package/src/compile.js +1211 -0
- package/src/ctx.js +244 -0
- package/src/emit.js +2105 -0
- package/src/host.js +536 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +418 -0
- package/src/key.js +73 -0
- package/src/narrow.js +928 -0
- package/src/optimize.js +1352 -0
- package/src/plan.js +105 -0
- package/src/prepare.js +1534 -0
- package/src/source.js +76 -0
- package/wasi.js +80 -0
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
|
+
}
|