jz 0.5.1 → 0.7.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.
- package/README.md +315 -318
- package/bench/README.md +369 -0
- package/bench/bench.svg +102 -0
- package/cli.js +104 -30
- package/dist/interop.js +1 -0
- package/dist/jz.js +6024 -0
- package/index.d.ts +126 -0
- package/index.js +362 -84
- package/interop.js +159 -186
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +184 -0
- package/module/array.js +377 -176
- package/module/collection.js +639 -145
- package/module/console.js +55 -43
- package/module/core.js +264 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +551 -187
- package/module/number.js +327 -60
- package/module/object.js +474 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +117 -0
- package/module/string.js +621 -226
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +459 -73
- package/package.json +58 -14
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +29 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +824 -0
- package/src/compile/analyze.js +1600 -0
- package/src/compile/emit-assign.js +411 -0
- package/src/compile/emit.js +3512 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +778 -128
- package/src/{infer.js → compile/infer.js} +62 -98
- package/src/compile/loop-divmod.js +155 -0
- package/src/{narrow.js → compile/narrow.js} +409 -106
- package/src/compile/peel-stencil.js +261 -0
- package/src/compile/plan/advise.js +370 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +122 -0
- package/src/compile/plan/inline.js +682 -0
- package/src/compile/plan/literals.js +1199 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +649 -0
- package/src/compile/program-facts.js +423 -0
- package/src/ctx.js +220 -64
- package/src/ir.js +589 -172
- package/src/kind-traits.js +132 -0
- package/src/kind.js +524 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1432 -473
- package/src/optimize/vectorize.js +4660 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +649 -205
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +116 -0
- package/src/resolve.js +12 -3
- package/src/static.js +208 -0
- package/src/type.js +651 -0
- package/src/{assemble.js → wat/assemble.js} +275 -55
- package/src/wat/optimize.js +3938 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/interop.js
CHANGED
|
@@ -24,7 +24,13 @@
|
|
|
24
24
|
* @module jz/interop
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import { wasi } from './wasi.js'
|
|
27
|
+
import { wasi, attachTimers } from './wasi.js'
|
|
28
|
+
import { HEAP, encodePtrHi, decodePtrType, decodePtrAux, ATOM, ATOM_HI, LAYOUT } from './layout.js'
|
|
29
|
+
|
|
30
|
+
// Stateless + reusable — one instance avoids a per-call allocation on the hot
|
|
31
|
+
// string read/write paths (mem.String / mem.read STRING).
|
|
32
|
+
const TEXT_ENC = new TextEncoder()
|
|
33
|
+
const TEXT_DEC = new TextDecoder()
|
|
28
34
|
|
|
29
35
|
// ── WASI linking ────────────────────────────────────────────────────────────
|
|
30
36
|
|
|
@@ -43,15 +49,12 @@ const envFuncNames = (mod) =>
|
|
|
43
49
|
// threads must share a pointer cell in linear memory). 8-byte aligned bump on
|
|
44
50
|
// the JS side; wasm `_alloc` takes over if exported.
|
|
45
51
|
|
|
46
|
-
const HEAP_PTR_ADDR = 1020
|
|
47
|
-
const HEAP_START = 1024
|
|
48
|
-
|
|
49
52
|
const makeJsAllocator = (mem, heapGlobal) => {
|
|
50
53
|
const dv = () => new DataView(mem.buffer)
|
|
51
|
-
const getPtr = heapGlobal ? () => heapGlobal.value : () => dv().getInt32(
|
|
52
|
-
const setPtr = heapGlobal ? v => { heapGlobal.value = v } : v => dv().setInt32(
|
|
54
|
+
const getPtr = heapGlobal ? () => heapGlobal.value : () => dv().getInt32(HEAP.PTR_ADDR, true)
|
|
55
|
+
const setPtr = heapGlobal ? v => { heapGlobal.value = v } : v => dv().setInt32(HEAP.PTR_ADDR, v, true)
|
|
53
56
|
// Rewind target: the global's post-static-init value, else the fixed start.
|
|
54
|
-
const base = heapGlobal ? heapGlobal.value :
|
|
57
|
+
const base = heapGlobal ? heapGlobal.value : HEAP.START
|
|
55
58
|
const alloc = (bytes) => {
|
|
56
59
|
const aligned = (getPtr() + 7) & ~7
|
|
57
60
|
const next = aligned + bytes
|
|
@@ -66,7 +69,7 @@ const makeJsAllocator = (mem, heapGlobal) => {
|
|
|
66
69
|
const initHeapPtr = () => {
|
|
67
70
|
if (heapGlobal) return
|
|
68
71
|
const d = dv()
|
|
69
|
-
if (d.getInt32(
|
|
72
|
+
if (d.getInt32(HEAP.PTR_ADDR, true) < HEAP.START) d.setInt32(HEAP.PTR_ADDR, HEAP.START, true)
|
|
70
73
|
}
|
|
71
74
|
return { alloc, reset, initHeapPtr }
|
|
72
75
|
}
|
|
@@ -101,17 +104,6 @@ const sectionReader = (bytes) => {
|
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
const attachTimers = (inst) => {
|
|
105
|
-
if (!inst.exports.__timer_tick) return
|
|
106
|
-
const tick = inst.exports.__timer_tick
|
|
107
|
-
let hadTimers = false
|
|
108
|
-
const id = setInterval(() => {
|
|
109
|
-
const remaining = tick()
|
|
110
|
-
if (remaining > 0) hadTimers = true
|
|
111
|
-
if (hadTimers && remaining <= 0) clearInterval(id)
|
|
112
|
-
}, 1)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
107
|
// ── NaN-box codec ───────────────────────────────────────────────────────────
|
|
116
108
|
|
|
117
109
|
// NaN-boxing encode/decode — shared 8-byte scratch buffer
|
|
@@ -129,38 +121,58 @@ const _bi64 = (() => {
|
|
|
129
121
|
})()
|
|
130
122
|
export const i64ToF64 = _bi64.i64ToF64
|
|
131
123
|
export const f64ToI64 = _bi64.f64ToI64
|
|
124
|
+
|
|
132
125
|
// Reserved atoms (type=0, offset=0): aux=1 → null, aux=2 → undefined.
|
|
133
126
|
// Distinct from 0, JS NaN (payload=0), and all pointers.
|
|
134
|
-
_u32[1] =
|
|
135
|
-
_u32[1] =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// inner 0/1 number carrier never escapes the wasm boundary.
|
|
139
|
-
_u32[1] = 0x7FF80004; _u32[0] = 0; export const FALSE_NAN = _f64[0]
|
|
140
|
-
_u32[1] = 0x7FF80005; _u32[0] = 0; export const TRUE_NAN = _f64[0]
|
|
127
|
+
_u32[1] = ATOM_HI[ATOM.NULL]; _u32[0] = 0; export const NULL_NAN = _f64[0]
|
|
128
|
+
_u32[1] = ATOM_HI[ATOM.UNDEF]; _u32[0] = 0; export const UNDEF_NAN = _f64[0]
|
|
129
|
+
_u32[1] = ATOM_HI[ATOM.FALSE]; _u32[0] = 0; export const FALSE_NAN = _f64[0]
|
|
130
|
+
_u32[1] = ATOM_HI[ATOM.TRUE]; _u32[0] = 0; export const TRUE_NAN = _f64[0]
|
|
141
131
|
|
|
142
132
|
// Coerce JS null/undefined → NaN-boxed sentinels for WASM boundary
|
|
143
133
|
export const coerce = v => v === null ? NULL_NAN : v === undefined ? UNDEF_NAN : v
|
|
144
134
|
|
|
145
|
-
//
|
|
135
|
+
// Memory-free decode of an f64 boundary value: numbers pass through, the reserved
|
|
136
|
+
// atoms become null/undefined/false/true, and an SSO string unpacks its ≤4 inline
|
|
137
|
+
// ASCII bytes straight from the NaN payload. These are exactly the value forms a
|
|
138
|
+
// *memoryless* module can carry across the boundary — a heap string/array/object
|
|
139
|
+
// would need linear memory to exist, so it can't reach here. Heap-carrying modules
|
|
140
|
+
// route through `mem.read` instead (which also handles these, via memory).
|
|
146
141
|
const decode = v => {
|
|
147
|
-
if (v === v) return v // fast path: non-NaN
|
|
142
|
+
if (v === v) return v // fast path: non-NaN number
|
|
148
143
|
_f64[0] = v
|
|
144
|
+
// STRING (type 4) + SSO bit: content is the low 32 bits, length the low aux bits.
|
|
145
|
+
if (decodePtrType(_u32[1]) === 4) {
|
|
146
|
+
const a = decodePtrAux(_u32[1])
|
|
147
|
+
if (a & LAYOUT.SSO_BIT) {
|
|
148
|
+
const len = a & 0x7, off = _u32[0]; let s = ''
|
|
149
|
+
for (let i = 0; i < len; i++) s += String.fromCharCode((off >>> (i * 8)) & 0xFF)
|
|
150
|
+
return s
|
|
151
|
+
}
|
|
152
|
+
}
|
|
149
153
|
if (_u32[0] !== 0) return v
|
|
150
|
-
if (_u32[1] ===
|
|
151
|
-
if (_u32[1] ===
|
|
152
|
-
if (_u32[1] ===
|
|
153
|
-
if (_u32[1] ===
|
|
154
|
+
if (_u32[1] === ATOM_HI[ATOM.NULL]) return null
|
|
155
|
+
if (_u32[1] === ATOM_HI[ATOM.UNDEF]) return undefined
|
|
156
|
+
if (_u32[1] === ATOM_HI[ATOM.FALSE]) return false
|
|
157
|
+
if (_u32[1] === ATOM_HI[ATOM.TRUE]) return true
|
|
154
158
|
return v
|
|
155
159
|
}
|
|
156
160
|
|
|
161
|
+
// Decode a boundary value that arrives as i64 bits (BigInt carrier) into a JS
|
|
162
|
+
// value. Heap-carrying modules go through `mem.read`; a memoryless module's value
|
|
163
|
+
// is a number/atom/SSO string, decodable from the bits alone.
|
|
164
|
+
const readArgBits = (state, big) => {
|
|
165
|
+
const f = i64ToF64(big)
|
|
166
|
+
return state.mem ? state.mem.read(f) : decode(f)
|
|
167
|
+
}
|
|
168
|
+
|
|
157
169
|
export const ptr = (type, aux, offset) => {
|
|
158
|
-
_u32[1] = (
|
|
170
|
+
_u32[1] = encodePtrHi(type, aux)
|
|
159
171
|
_u32[0] = offset >>> 0; return _f64[0]
|
|
160
172
|
}
|
|
161
173
|
export const offset = (p) => { _f64[0] = p; return _u32[0] }
|
|
162
|
-
export const type = (p) => { _f64[0] = p; return (_u32[1]
|
|
163
|
-
export const aux = (p) => { _f64[0] = p; return _u32[1]
|
|
174
|
+
export const type = (p) => { _f64[0] = p; return decodePtrType(_u32[1]) }
|
|
175
|
+
export const aux = (p) => { _f64[0] = p; return decodePtrAux(_u32[1]) }
|
|
164
176
|
|
|
165
177
|
// Typed element metadata: [elemId, byteStride, DataView getter, DataView setter]
|
|
166
178
|
const ELEMS = {
|
|
@@ -206,7 +218,11 @@ export const memory = (src) => {
|
|
|
206
218
|
// Instance result: { module, instance, exports, extMap }
|
|
207
219
|
const raw = src?.instance?.exports || src?.exports || src
|
|
208
220
|
mem = src?.exports?.memory || raw.memory
|
|
209
|
-
|
|
221
|
+
// Memoryless module (SSO strings / atoms / numbers only — no linear memory):
|
|
222
|
+
// hand back a minimal reader instead of null so callers can still decode its
|
|
223
|
+
// boundary values from bits. `read`/`wrapVal` cover the value forms that exist
|
|
224
|
+
// without memory; `scalar` flags the fast path that skips heap marshaling.
|
|
225
|
+
if (!mem) return { read: decode, wrapVal: coerce, scalar: true }
|
|
210
226
|
wasmExports = { ...raw, memory: mem }
|
|
211
227
|
extMap = src.extMap || null
|
|
212
228
|
mod = src.module || null
|
|
@@ -259,8 +275,7 @@ export const memory = (src) => {
|
|
|
259
275
|
if (_enhanced.has(mem)) {
|
|
260
276
|
mem.schemas = schemas
|
|
261
277
|
if (wasmExports?._alloc) { alloc = wasmExports._alloc; mem.alloc = alloc }
|
|
262
|
-
|
|
263
|
-
else if (!mem.reset) mem.reset = jsReset
|
|
278
|
+
mem.reset = jsReset // post-init rewind — see the note at the first-enhance path
|
|
264
279
|
if (extMap) mem._extMap = extMap
|
|
265
280
|
return mem
|
|
266
281
|
}
|
|
@@ -285,9 +300,9 @@ export const memory = (src) => {
|
|
|
285
300
|
if (str.length <= 4 && /^[\x00-\x7f]*$/.test(str)) {
|
|
286
301
|
let packed = 0
|
|
287
302
|
for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
|
|
288
|
-
return ptr(4,
|
|
303
|
+
return ptr(4, LAYOUT.SSO_BIT | str.length, packed) // STRING + SSO_BIT
|
|
289
304
|
}
|
|
290
|
-
const enc =
|
|
305
|
+
const enc = TEXT_ENC.encode(str)
|
|
291
306
|
const n = enc.length, raw = alloc(4 + n), m = dv()
|
|
292
307
|
m.setInt32(raw, n, true)
|
|
293
308
|
const off = raw + 4
|
|
@@ -307,23 +322,22 @@ export const memory = (src) => {
|
|
|
307
322
|
mem.wrapVal = function(v) {
|
|
308
323
|
if (v === null || v === undefined) return coerce(v)
|
|
309
324
|
if (typeof v === 'number' || typeof v === 'boolean') return Number(v)
|
|
310
|
-
if (typeof v === 'string') return
|
|
311
|
-
// BigInt as a data value crosses the boundary as a decimal-string
|
|
312
|
-
// numeric parsers accept string form.
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
if (
|
|
316
|
-
if (v instanceof
|
|
317
|
-
if (v instanceof DataView) return this.Buffer(v.buffer)
|
|
325
|
+
if (typeof v === 'string') return mem.String(v)
|
|
326
|
+
// BigInt as a data value crosses the boundary as a decimal-string; wasm-side
|
|
327
|
+
// numeric parsers accept string form.
|
|
328
|
+
if (typeof v === 'bigint') return mem.String(v.toString())
|
|
329
|
+
if (Array.isArray(v)) return mem.Array(v)
|
|
330
|
+
if (v instanceof ArrayBuffer) return mem.Buffer(v)
|
|
331
|
+
if (v instanceof DataView) return mem.Buffer(v.buffer)
|
|
318
332
|
const typedName = v?.constructor?.name
|
|
319
|
-
if (typedName && ELEMS[typedName]) return
|
|
320
|
-
if (typeof v === 'object' || typeof v === 'function') return
|
|
333
|
+
if (typedName && ELEMS[typedName]) return mem[typedName](v)
|
|
334
|
+
if (typeof v === 'object' || typeof v === 'function') return mem.External(v)
|
|
321
335
|
return UNDEF_NAN
|
|
322
336
|
}
|
|
323
337
|
|
|
324
338
|
mem.External = function(obj) {
|
|
325
339
|
if (obj === null || obj === undefined) return coerce(obj)
|
|
326
|
-
const map =
|
|
340
|
+
const map = mem._extMap
|
|
327
341
|
if (!map) return UNDEF_NAN
|
|
328
342
|
let id = map.indexOf(obj)
|
|
329
343
|
if (id === -1) { id = map.length; map.push(obj) }
|
|
@@ -333,14 +347,14 @@ export const memory = (src) => {
|
|
|
333
347
|
mem.Object = function(obj) {
|
|
334
348
|
const objKeys = Object.keys(obj)
|
|
335
349
|
const key = objKeys.join(',')
|
|
336
|
-
const schemas =
|
|
350
|
+
const schemas = mem.schemas
|
|
337
351
|
let sid = schemas.findIndex(s => s.join(',') === key)
|
|
338
352
|
if (sid === -1) {
|
|
339
353
|
const matches = schemas.reduce((a, s, i) =>
|
|
340
354
|
(s.length === objKeys.length && objKeys.every(k => s.includes(k)) ? a.concat(i) : a), [])
|
|
341
355
|
if (matches.length === 1) sid = matches[0]
|
|
342
356
|
else if (matches.length > 1) throw Error(`Ambiguous schema for {${key}} — pass keys in schema order`)
|
|
343
|
-
else if (
|
|
357
|
+
else if (mem._extMap) return mem.External(obj)
|
|
344
358
|
else throw Error(`No schema for {${key}}`)
|
|
345
359
|
}
|
|
346
360
|
const schema = schemas[sid], n = schema.length, raw = alloc(n * 8)
|
|
@@ -350,8 +364,8 @@ export const memory = (src) => {
|
|
|
350
364
|
for (let i = 0; i < n; i++) {
|
|
351
365
|
let v = obj[schema[i]]
|
|
352
366
|
if (v === null || v === undefined) v = coerce(v)
|
|
353
|
-
else if (typeof v === 'string') v =
|
|
354
|
-
else if (Array.isArray(v)) v =
|
|
367
|
+
else if (typeof v === 'string') v = mem.String(v)
|
|
368
|
+
else if (Array.isArray(v)) v = mem.Array(v)
|
|
355
369
|
wrapped[i] = f64ToI64(v)
|
|
356
370
|
}
|
|
357
371
|
const dst = new BigInt64Array(mem.buffer, raw, n)
|
|
@@ -360,7 +374,7 @@ export const memory = (src) => {
|
|
|
360
374
|
}
|
|
361
375
|
|
|
362
376
|
mem.read = function(p) {
|
|
363
|
-
if (Array.isArray(p)) return p.map(v =>
|
|
377
|
+
if (Array.isArray(p)) return p.map(v => mem.read(v)) // multi-value tuple
|
|
364
378
|
if (p === p) return p // regular number passthrough (NaN fails ===)
|
|
365
379
|
const t = type(p), a = aux(p), off = offset(p)
|
|
366
380
|
if (t === 0 && off === 0) {
|
|
@@ -369,13 +383,13 @@ export const memory = (src) => {
|
|
|
369
383
|
if (a === 4) return false
|
|
370
384
|
if (a === 5) return true
|
|
371
385
|
}
|
|
372
|
-
if (t === 11 &&
|
|
386
|
+
if (t === 11 && mem._extMap) return mem._extMap[off]
|
|
373
387
|
if (t === 1) { // ARRAY
|
|
374
388
|
let m = dv(), aOff = off
|
|
375
389
|
// Follow forwarding pointers (cap === -1 means array was reallocated)
|
|
376
390
|
while (m.getInt32(aOff - 4, true) === -1) aOff = m.getInt32(aOff - 8, true)
|
|
377
391
|
const len = m.getInt32(aOff - 8, true), out = new Array(len)
|
|
378
|
-
for (let i = 0; i < len; i++) out[i] =
|
|
392
|
+
for (let i = 0; i < len; i++) out[i] = mem.read(m.getFloat64(aOff + i * 8, true))
|
|
379
393
|
return out
|
|
380
394
|
}
|
|
381
395
|
if (t === 3) { // TYPED
|
|
@@ -396,21 +410,21 @@ export const memory = (src) => {
|
|
|
396
410
|
new Uint8Array(out).set(new Uint8Array(mem.buffer, off, byteLen))
|
|
397
411
|
return out
|
|
398
412
|
}
|
|
399
|
-
if (t === 4) { // STRING (aux
|
|
413
|
+
if (t === 4) { // STRING (aux SSO_BIT = inline, else heap)
|
|
400
414
|
const a2 = aux(p)
|
|
401
|
-
if (a2 &
|
|
415
|
+
if (a2 & LAYOUT.SSO_BIT) {
|
|
402
416
|
const len = a2 & 0x7; let s = ''
|
|
403
417
|
for (let i = 0; i < len; i++) s += String.fromCharCode((off >>> (i * 8)) & 0xFF)
|
|
404
418
|
return s
|
|
405
419
|
}
|
|
406
420
|
const len = dv().getInt32(off - 4, true)
|
|
407
|
-
return
|
|
421
|
+
return TEXT_DEC.decode(new Uint8Array(mem.buffer, off, len))
|
|
408
422
|
}
|
|
409
423
|
if (t === 6) { // OBJECT
|
|
410
|
-
const m = dv(), sid = aux(p), keys =
|
|
424
|
+
const m = dv(), sid = aux(p), keys = mem.schemas[sid]
|
|
411
425
|
if (!keys) return p
|
|
412
426
|
const obj = {}
|
|
413
|
-
for (let i = 0; i < keys.length; i++) obj[keys[i]] =
|
|
427
|
+
for (let i = 0; i < keys.length; i++) obj[keys[i]] = mem.read(m.getFloat64(off + i * 8, true))
|
|
414
428
|
return obj
|
|
415
429
|
}
|
|
416
430
|
if (t === 7) { // HASH
|
|
@@ -419,8 +433,8 @@ export const memory = (src) => {
|
|
|
419
433
|
for (let i = 0, found = 0; i < cap && found < size; i++) {
|
|
420
434
|
const hash = m.getFloat64(off + i * 24, true)
|
|
421
435
|
if (hash !== 0) {
|
|
422
|
-
const key =
|
|
423
|
-
obj[key] =
|
|
436
|
+
const key = mem.read(m.getFloat64(off + i * 24 + 8, true))
|
|
437
|
+
obj[key] = mem.read(m.getFloat64(off + i * 24 + 16, true))
|
|
424
438
|
found++
|
|
425
439
|
}
|
|
426
440
|
}
|
|
@@ -431,7 +445,7 @@ export const memory = (src) => {
|
|
|
431
445
|
const set = new Set()
|
|
432
446
|
for (let i = 0; i < cap && set.size < size; i++) {
|
|
433
447
|
const hash = m.getFloat64(off + i * 16, true)
|
|
434
|
-
if (hash !== 0) set.add(
|
|
448
|
+
if (hash !== 0) set.add(mem.read(m.getFloat64(off + i * 16 + 8, true)))
|
|
435
449
|
}
|
|
436
450
|
return set
|
|
437
451
|
}
|
|
@@ -440,7 +454,7 @@ export const memory = (src) => {
|
|
|
440
454
|
const map = new Map()
|
|
441
455
|
for (let i = 0; i < cap && map.size < size; i++) {
|
|
442
456
|
const hash = m.getFloat64(off + i * 24, true)
|
|
443
|
-
if (hash !== 0) map.set(
|
|
457
|
+
if (hash !== 0) map.set(mem.read(m.getFloat64(off + i * 24 + 8, true)), mem.read(m.getFloat64(off + i * 24 + 16, true)))
|
|
444
458
|
}
|
|
445
459
|
return map
|
|
446
460
|
}
|
|
@@ -470,7 +484,7 @@ export const memory = (src) => {
|
|
|
470
484
|
for (let i = 0; i < data.length; i++) m[setter](off + i * stride, data[i], true)
|
|
471
485
|
}
|
|
472
486
|
} else if (t === 6) {
|
|
473
|
-
const schema =
|
|
487
|
+
const schema = mem.schemas[aux(p)]
|
|
474
488
|
if (!schema) throw Error(`write: unknown schema`)
|
|
475
489
|
for (const k of Object.keys(data)) {
|
|
476
490
|
const i = schema.indexOf(k)
|
|
@@ -482,13 +496,29 @@ export const memory = (src) => {
|
|
|
482
496
|
}
|
|
483
497
|
|
|
484
498
|
mem.alloc = alloc
|
|
485
|
-
|
|
499
|
+
// Rewind to the JS-captured post-init heap mark, NOT the wasm `_clear` (which
|
|
500
|
+
// rewinds to the static-data end and would clobber module-global heap values —
|
|
501
|
+
// a top-level `let o = {…}` — on the first alloc after reset). `jsReset`'s base
|
|
502
|
+
// is `$__heap` read after instantiation (start ran), i.e. exactly the high-water
|
|
503
|
+
// mark above all module-init allocations. Both share `$__heap`, so a wasm `_alloc`
|
|
504
|
+
// and this reset stay consistent. (Shared memory has no `$__heap` global → base
|
|
505
|
+
// is the fixed start, preserving prior behavior.)
|
|
506
|
+
mem.reset = jsReset
|
|
486
507
|
|
|
487
508
|
// TypedArray constructors: memory.Float64Array(data), etc.
|
|
509
|
+
// Bulk-copy path: when input is a TypedArray whose element type matches
|
|
510
|
+
// the target (same stride), use .set() for a fast memcpy instead of
|
|
511
|
+
// per-element DataView writes. Falls back to DataView for mismatched types.
|
|
512
|
+
const TA = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array]
|
|
488
513
|
for (const [name, [elemId, stride, , setter]] of Object.entries(ELEMS)) {
|
|
489
514
|
mem[name] = (data) => {
|
|
490
|
-
const n = data.length, bytes = n * stride, off = hdr(bytes, bytes, bytes)
|
|
491
|
-
|
|
515
|
+
const n = data.length, bytes = n * stride, off = hdr(bytes, bytes, bytes)
|
|
516
|
+
if (stride >= 2 && TA[elemId] && data instanceof TA[elemId]) {
|
|
517
|
+
new TA[elemId](mem.buffer, off, n).set(data)
|
|
518
|
+
} else {
|
|
519
|
+
const m = dv()
|
|
520
|
+
for (let i = 0; i < n; i++) m[setter](off + i * stride, data[i], true)
|
|
521
|
+
}
|
|
492
522
|
return ptr(3, elemId, off)
|
|
493
523
|
}
|
|
494
524
|
}
|
|
@@ -513,19 +543,8 @@ export const wrap = (memSrc, inst) => {
|
|
|
513
543
|
restFuncs.set(typeof entry === 'string' ? entry : entry.name, typeof entry === 'string' ? 0 : entry.fixed)
|
|
514
544
|
} catch (e) { /* ignore */ }
|
|
515
545
|
}
|
|
516
|
-
// i64-ABI exports: boundary-wrapped funcs whose NaN-boxed pointer params/
|
|
517
|
-
// result ride i64 to dodge V8's NaN canonicalization. Map: name → { p, r }
|
|
518
|
-
// with p = bit mask of i64 params, r = 1 iff result is i64. JS side
|
|
519
|
-
// reinterprets f64↔BigInt only at those positions (see
|
|
520
|
-
// synthesizeBoundaryWrappers).
|
|
521
|
-
const i64Exp = new Map(), EMPTY_SET = new Set()
|
|
522
|
-
const i64Bytes = customSection(mod, 'jz:i64exp')
|
|
523
|
-
if (i64Bytes) {
|
|
524
|
-
try { for (const e of JSON.parse(td.decode(i64Bytes))) i64Exp.set(e.name, { p: new Set(e.p), r: e.r }) }
|
|
525
|
-
catch { /* ignore */ }
|
|
526
|
-
}
|
|
527
546
|
// externref-param exports: positions where the wasm side takes an externref
|
|
528
|
-
// (
|
|
547
|
+
// (jsstring carrier — js-host only). JS values at these positions pass through
|
|
529
548
|
// unchanged — no `mem.wrapVal` (would NaN-box into f64, defeating the point).
|
|
530
549
|
// `def` (optional) maps idx → default-string for jsstring params whose
|
|
531
550
|
// default substitution happens JS-side (the wasm side never sees null).
|
|
@@ -542,7 +561,6 @@ export const wrap = (memSrc, inst) => {
|
|
|
542
561
|
}
|
|
543
562
|
} catch { /* ignore */ }
|
|
544
563
|
}
|
|
545
|
-
|
|
546
564
|
const mem = memory(memSrc)
|
|
547
565
|
const lastErrBits = realInst.exports.__jz_last_err_bits
|
|
548
566
|
const decodeThrown = error => {
|
|
@@ -550,7 +568,9 @@ export const wrap = (memSrc, inst) => {
|
|
|
550
568
|
const bits = lastErrBits.value
|
|
551
569
|
_u32[0] = Number(bits & 0xffffffffn)
|
|
552
570
|
_u32[1] = Number((bits >> 32n) & 0xffffffffn)
|
|
553
|
-
|
|
571
|
+
// Memoryless module: the thrown value is a number/atom/SSO string — decode it
|
|
572
|
+
// from bits. (A heap Error/string can only exist when the module has memory.)
|
|
573
|
+
const value = mem ? mem.read(_f64[0]) : decode(_f64[0])
|
|
554
574
|
if (value instanceof Error) throw value
|
|
555
575
|
const wrapped = new Error(typeof value === 'string' ? value : String(value))
|
|
556
576
|
wrapped.cause = error
|
|
@@ -558,118 +578,52 @@ export const wrap = (memSrc, inst) => {
|
|
|
558
578
|
throw wrapped
|
|
559
579
|
}
|
|
560
580
|
const exports = {}
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
// Arity-specialized wrapper: rest-spread + .map() + .apply() costs ~85ns/call
|
|
571
|
-
// on hot loops (mandelbrot benchmark: 51ms wrapped vs 35ms direct over 200K
|
|
572
|
-
// calls). Generating positional `function(a0, a1, ...)` via Function lets V8
|
|
573
|
-
// fully inline the WASM call. Falls back to the spread-form wrapper if the
|
|
574
|
-
// Function constructor is unavailable (CSP) or arity is unusually large.
|
|
575
|
-
// `ext` is a Set of externref param positions — those slots skip wrap/coerce
|
|
576
|
-
// and pass the JS value directly (jsstring carrier: JS string → externref).
|
|
577
|
-
// `ext.def` (optional Map idx→string) carries jsstring-literal defaults that
|
|
578
|
-
// are substituted JS-side when the caller passes `undefined`.
|
|
579
|
-
const makeFastWrapper = (fn, len, p, r, decode_, wrap_, ext) => {
|
|
580
|
-
const params = [], wrapped = []
|
|
581
|
-
const defs = ext?.def
|
|
582
|
-
const defArgs = [], defNames = []
|
|
583
|
-
for (let i = 0; i < len; i++) {
|
|
584
|
-
const a = `a${i}`
|
|
585
|
-
params.push(a)
|
|
586
|
-
if (ext?.has(i)) {
|
|
587
|
-
if (defs?.has(i)) {
|
|
588
|
-
const dn = `def${i}`
|
|
589
|
-
defNames.push(dn); defArgs.push(defs.get(i))
|
|
590
|
-
wrapped.push(`(${a} === undefined ? ${dn} : ${a})`)
|
|
591
|
-
} else {
|
|
592
|
-
wrapped.push(a)
|
|
593
|
-
}
|
|
594
|
-
continue
|
|
595
|
-
}
|
|
596
|
-
const w = wrap_ ? `wrap_(${a})` : `coerce(${a})`
|
|
597
|
-
wrapped.push(p.has(i) ? `f64ToI64(${w})` : w)
|
|
598
|
-
}
|
|
599
|
-
const callExpr = `fn(${wrapped.join(',')})`
|
|
600
|
-
const retExpr = r ? `i64ToF64(${callExpr})` : callExpr
|
|
601
|
-
const body = `return function(${params.join(',')}) {\n` +
|
|
602
|
-
` try { return decode_(${retExpr}) } catch (e) { decodeThrown(e) }\n` +
|
|
603
|
-
`}`
|
|
604
|
-
return new Function('fn', 'wrap_', 'coerce', 'decode_', 'f64ToI64', 'i64ToF64', 'decodeThrown', ...defNames, body)(
|
|
605
|
-
fn, wrap_, coerce, decode_, f64ToI64, i64ToF64, decodeThrown, ...defArgs)
|
|
606
|
-
}
|
|
581
|
+
// Wrap one positional arg. Externref slots (jsstring carrier) pass the JS
|
|
582
|
+
// value straight through — `mem.wrapVal` would NaN-box it — substituting a
|
|
583
|
+
// jsstring literal default for a missing arg. Every other slot marshals via
|
|
584
|
+
// `box`: `coerce` for pure-scalar modules, `mem.wrapVal` for heap modules.
|
|
585
|
+
// Quiet-NaN ABI: every export value is f64, so there is no per-position i64
|
|
586
|
+
// carrier — args flow straight to the wasm call, results straight to decode/read.
|
|
587
|
+
const wrapArgAt = (ext, i, x, box) =>
|
|
588
|
+
ext?.has(i) ? (x === undefined && ext.def?.has(i) ? ext.def.get(i) : x) : box(x)
|
|
607
589
|
|
|
608
590
|
// Pure scalar module (no memory): pass f64 values directly, no marshaling
|
|
609
|
-
if (!mem) {
|
|
591
|
+
if (!mem || mem.scalar) {
|
|
610
592
|
for (const [name, fn] of Object.entries(realInst.exports)) {
|
|
611
593
|
if (typeof fn !== 'function') { exports[name] = fn; continue }
|
|
612
|
-
const sig = i64Exp.get(name)
|
|
613
|
-
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
614
594
|
const ext = extExp.get(name)
|
|
615
595
|
const len = fn.length
|
|
616
|
-
try {
|
|
617
|
-
exports[name] = makeFastWrapper(fn, len, p, r, decode, null, ext)
|
|
618
|
-
continue
|
|
619
|
-
} catch { /* CSP fallback */ }
|
|
620
596
|
exports[name] = (...args) => {
|
|
621
597
|
while (args.length < len) args.push(undefined)
|
|
622
598
|
try {
|
|
623
|
-
|
|
624
|
-
if (!ext?.has(i)) return coerce(x)
|
|
625
|
-
return x === undefined && ext.def?.has(i) ? ext.def.get(i) : x
|
|
626
|
-
}), p)
|
|
627
|
-
return decode(adaptRet(fn(...wasmArgs), r))
|
|
599
|
+
return decode(fn(...args.map((x, i) => wrapArgAt(ext, i, x, coerce))))
|
|
628
600
|
} catch (e) { decodeThrown(e) }
|
|
629
601
|
}
|
|
630
602
|
}
|
|
631
603
|
return exports
|
|
632
604
|
}
|
|
633
605
|
const memWrapVal = mem.wrapVal.bind(mem)
|
|
634
|
-
const memRead = mem.read.bind(mem)
|
|
635
606
|
for (const [name, fn] of Object.entries(realInst.exports)) {
|
|
636
607
|
if (restFuncs.has(name) && typeof fn === 'function') {
|
|
637
608
|
const fixed = restFuncs.get(name)
|
|
638
|
-
const sig = i64Exp.get(name)
|
|
639
|
-
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
640
609
|
const ext = extExp.get(name)
|
|
641
610
|
exports[name] = (...args) => {
|
|
642
|
-
const a = args.slice(0, fixed).map((x, i) =>
|
|
643
|
-
if (!ext?.has(i)) return mem.wrapVal(x)
|
|
644
|
-
return x === undefined && ext.def?.has(i) ? ext.def.get(i) : x
|
|
645
|
-
})
|
|
611
|
+
const a = args.slice(0, fixed).map((x, i) => wrapArgAt(ext, i, x, memWrapVal))
|
|
646
612
|
while (a.length < fixed) a.push(UNDEF_NAN)
|
|
647
613
|
a.push(mem.Array(args.slice(fixed)))
|
|
648
614
|
try {
|
|
649
|
-
|
|
650
|
-
return mem.read(adaptRet(ret, r))
|
|
615
|
+
return mem.read(fn.apply(null, a))
|
|
651
616
|
} catch (error) {
|
|
652
617
|
decodeThrown(error)
|
|
653
618
|
}
|
|
654
619
|
}
|
|
655
620
|
} else if (typeof fn === 'function') {
|
|
656
|
-
const sig = i64Exp.get(name)
|
|
657
|
-
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
658
621
|
const ext = extExp.get(name)
|
|
659
622
|
const len = fn.length
|
|
660
|
-
try {
|
|
661
|
-
exports[name] = makeFastWrapper(fn, len, p, r, memRead, memWrapVal, ext)
|
|
662
|
-
continue
|
|
663
|
-
} catch { /* CSP fallback */ }
|
|
664
623
|
exports[name] = (...args) => {
|
|
665
624
|
while (args.length < len) args.push(undefined)
|
|
666
625
|
try {
|
|
667
|
-
|
|
668
|
-
if (!ext?.has(i)) return mem.wrapVal(x)
|
|
669
|
-
return x === undefined && ext.def?.has(i) ? ext.def.get(i) : x
|
|
670
|
-
})
|
|
671
|
-
const ret = fn.apply(null, adaptArgs(boxed, p))
|
|
672
|
-
return mem.read(adaptRet(ret, r))
|
|
626
|
+
return mem.read(fn.apply(null, args.map((x, i) => wrapArgAt(ext, i, x, memWrapVal))))
|
|
673
627
|
} catch (error) {
|
|
674
628
|
decodeThrown(error)
|
|
675
629
|
}
|
|
@@ -729,7 +683,7 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
729
683
|
// across the wasm→JS boundary (see module/console.js header). Reinterpret
|
|
730
684
|
// the BigInt's bits as f64 here so mem.read sees the original NaN-box.
|
|
731
685
|
const write = (valBig, fd, sep) => {
|
|
732
|
-
const v = state
|
|
686
|
+
const v = readArgBits(state, valBig)
|
|
733
687
|
buf[fd] += String(v)
|
|
734
688
|
if (sep === 32) buf[fd] += ' '
|
|
735
689
|
else if (sep === 10) flush(fd)
|
|
@@ -747,15 +701,25 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
747
701
|
imports.env.now = (clock) =>
|
|
748
702
|
clock === 1 ? (typeof performance !== 'undefined' ? performance.now() : Date.now()) : Date.now()
|
|
749
703
|
}
|
|
704
|
+
// One i32 of entropy to seed Math.random — only present when compiled with
|
|
705
|
+
// { randomSeed: true }. Prefers crypto; falls back to Math.random.
|
|
706
|
+
if (envFns.has('rngSeed') && !imports.env.rngSeed) {
|
|
707
|
+
imports.env.rngSeed = () => {
|
|
708
|
+
const a = new Uint32Array(1)
|
|
709
|
+
if (globalThis.crypto?.getRandomValues) globalThis.crypto.getRandomValues(a)
|
|
710
|
+
else a[0] = (Math.random() * 0x100000000) >>> 0
|
|
711
|
+
return a[0] | 0
|
|
712
|
+
}
|
|
713
|
+
}
|
|
750
714
|
if (envFns.has('parseFloat') && !imports.env.parseFloat) {
|
|
751
715
|
imports.env.parseFloat = (valBig) => {
|
|
752
|
-
const s = state
|
|
716
|
+
const s = readArgBits(state, valBig)
|
|
753
717
|
return parseFloat(s)
|
|
754
718
|
}
|
|
755
719
|
}
|
|
756
720
|
if (envFns.has('parseInt') && !imports.env.parseInt) {
|
|
757
721
|
imports.env.parseInt = (valBig, radix) => {
|
|
758
|
-
const s = state
|
|
722
|
+
const s = readArgBits(state, valBig)
|
|
759
723
|
return parseInt(s, radix || undefined)
|
|
760
724
|
}
|
|
761
725
|
}
|
|
@@ -811,8 +775,8 @@ const jssProbeNative = () => {
|
|
|
811
775
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // header
|
|
812
776
|
0x01, 0x06, 0x01, 0x60, 0x01, 0x6f, 0x01, 0x7f, // type: (externref)→i32
|
|
813
777
|
0x02, 0x18, 0x01, // import section
|
|
814
|
-
0x0f, ...
|
|
815
|
-
0x06, ...
|
|
778
|
+
0x0f, ...TEXT_ENC.encode('wasm:js-string'), // mod name
|
|
779
|
+
0x06, ...TEXT_ENC.encode('length'), // name
|
|
816
780
|
0x00, 0x00, // kind=func, type=0
|
|
817
781
|
])
|
|
818
782
|
const mod = new WebAssembly.Module(bytes, { builtins: ['js-string'] })
|
|
@@ -904,7 +868,11 @@ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
|
904
868
|
const enhanced = memory(memSrc)
|
|
905
869
|
state.mem = enhanced
|
|
906
870
|
state.flushPrint?.()
|
|
907
|
-
|
|
871
|
+
// A memoryless module keeps a minimal reader internally (state.mem, for decoding
|
|
872
|
+
// its SSO/atom boundary values), but the result's `.memory` stays null — the
|
|
873
|
+
// module genuinely exposes no linear memory. `jz.memory(result)` still hands back
|
|
874
|
+
// a usable reader on demand.
|
|
875
|
+
return { exports: wrap(memSrc), memory: enhanced?.scalar ? null : enhanced, instance: inst, module: mod }
|
|
908
876
|
}
|
|
909
877
|
|
|
910
878
|
/**
|
|
@@ -919,24 +887,29 @@ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
|
919
887
|
* @param {object} [opts] host options: imports, memory, _interp, host-shape flags
|
|
920
888
|
* @returns {{ exports, memory, instance, module }}
|
|
921
889
|
*/
|
|
890
|
+
/**
|
|
891
|
+
* Compile wasm bytes to a `WebAssembly.Module`, preferring native
|
|
892
|
+
* `wasm:js-string` builtins when the engine honors the option (V8 17+/Safari
|
|
893
|
+
* 18.4+; older engines throw or ignore it — try-fallback handles both). A
|
|
894
|
+
* `WebAssembly.Module` passed in is returned as-is. Factor it out so callers
|
|
895
|
+
* can compile once and instantiate many times without re-validating the bytes
|
|
896
|
+
* (`instantiate(toModule(wasm))` skips the per-call compile on hot loops).
|
|
897
|
+
*
|
|
898
|
+
* @param {Uint8Array|ArrayBuffer|WebAssembly.Module} wasm
|
|
899
|
+
* @returns {WebAssembly.Module}
|
|
900
|
+
*/
|
|
901
|
+
export const toModule = (wasm) => {
|
|
902
|
+
if (wasm instanceof WebAssembly.Module) return wasm
|
|
903
|
+
if (jssProbeNative()) {
|
|
904
|
+
try { return new WebAssembly.Module(wasm, { builtins: ['js-string'] }) }
|
|
905
|
+
catch { return new WebAssembly.Module(wasm) }
|
|
906
|
+
}
|
|
907
|
+
return new WebAssembly.Module(wasm)
|
|
908
|
+
}
|
|
909
|
+
|
|
922
910
|
export const instantiate = (wasm, opts = {}) => {
|
|
923
911
|
const state = prepareInterop(opts)
|
|
924
|
-
|
|
925
|
-
// Prefer native `wasm:js-string` builtins when the engine honors the option.
|
|
926
|
-
// The option is silently accepted by V8 17+/Safari 18.4+; older engines that
|
|
927
|
-
// don't recognize it either throw or ignore it — try-fallback handles both.
|
|
928
|
-
let mod
|
|
929
|
-
if (wasm instanceof WebAssembly.Module) {
|
|
930
|
-
mod = wasm
|
|
931
|
-
} else if (jssProbeNative()) {
|
|
932
|
-
try {
|
|
933
|
-
mod = new WebAssembly.Module(wasm, { builtins: ['js-string'] })
|
|
934
|
-
} catch {
|
|
935
|
-
mod = new WebAssembly.Module(wasm)
|
|
936
|
-
}
|
|
937
|
-
} else {
|
|
938
|
-
mod = new WebAssembly.Module(wasm)
|
|
939
|
-
}
|
|
912
|
+
const mod = toModule(wasm)
|
|
940
913
|
const { imports, needsWasi } = buildImports(mod, opts, state)
|
|
941
914
|
const hasImports = Object.keys(imports).some(k => k !== '_setMemory')
|
|
942
915
|
const inst = new WebAssembly.Instance(mod, hasImports ? imports : undefined)
|
package/jz.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="630" height="630" viewBox="0 0 630 630" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M630 0H0V630H630V0Z" fill="black"/>
|
|
3
|
+
<path d="M165.65 526.474L213.863 497.296C223.164 513.788 231.625 527.74 251.92 527.74C271.374 527.74 283.639 520.13 283.639 490.53V289.23H342.843V491.367C342.843 552.688 306.899 580.599 254.458 580.599C207.096 580.599 179.605 556.07 165.65 526.469" fill="white"/>
|
|
4
|
+
<path d="M412.075 316.04H534.138L409.808 550.902H546.138" stroke="white" stroke-width="54" stroke-linecap="square"/>
|
|
5
|
+
</svg>
|