jz 0.5.0 → 0.6.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 +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- 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 +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +281 -142
- 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 +461 -185
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +591 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +600 -205
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -2997
- package/src/jzify.js +0 -1553
- 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,15 +121,13 @@ 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
|
|
@@ -147,20 +137,20 @@ const decode = v => {
|
|
|
147
137
|
if (v === v) return v // fast path: non-NaN
|
|
148
138
|
_f64[0] = v
|
|
149
139
|
if (_u32[0] !== 0) return v
|
|
150
|
-
if (_u32[1] ===
|
|
151
|
-
if (_u32[1] ===
|
|
152
|
-
if (_u32[1] ===
|
|
153
|
-
if (_u32[1] ===
|
|
140
|
+
if (_u32[1] === ATOM_HI[ATOM.NULL]) return null
|
|
141
|
+
if (_u32[1] === ATOM_HI[ATOM.UNDEF]) return undefined
|
|
142
|
+
if (_u32[1] === ATOM_HI[ATOM.FALSE]) return false
|
|
143
|
+
if (_u32[1] === ATOM_HI[ATOM.TRUE]) return true
|
|
154
144
|
return v
|
|
155
145
|
}
|
|
156
146
|
|
|
157
147
|
export const ptr = (type, aux, offset) => {
|
|
158
|
-
_u32[1] = (
|
|
148
|
+
_u32[1] = encodePtrHi(type, aux)
|
|
159
149
|
_u32[0] = offset >>> 0; return _f64[0]
|
|
160
150
|
}
|
|
161
151
|
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]
|
|
152
|
+
export const type = (p) => { _f64[0] = p; return decodePtrType(_u32[1]) }
|
|
153
|
+
export const aux = (p) => { _f64[0] = p; return decodePtrAux(_u32[1]) }
|
|
164
154
|
|
|
165
155
|
// Typed element metadata: [elemId, byteStride, DataView getter, DataView setter]
|
|
166
156
|
const ELEMS = {
|
|
@@ -285,9 +275,9 @@ export const memory = (src) => {
|
|
|
285
275
|
if (str.length <= 4 && /^[\x00-\x7f]*$/.test(str)) {
|
|
286
276
|
let packed = 0
|
|
287
277
|
for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
|
|
288
|
-
return ptr(4,
|
|
278
|
+
return ptr(4, LAYOUT.SSO_BIT | str.length, packed) // STRING + SSO_BIT
|
|
289
279
|
}
|
|
290
|
-
const enc =
|
|
280
|
+
const enc = TEXT_ENC.encode(str)
|
|
291
281
|
const n = enc.length, raw = alloc(4 + n), m = dv()
|
|
292
282
|
m.setInt32(raw, n, true)
|
|
293
283
|
const off = raw + 4
|
|
@@ -307,23 +297,22 @@ export const memory = (src) => {
|
|
|
307
297
|
mem.wrapVal = function(v) {
|
|
308
298
|
if (v === null || v === undefined) return coerce(v)
|
|
309
299
|
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)
|
|
300
|
+
if (typeof v === 'string') return mem.String(v)
|
|
301
|
+
// BigInt as a data value crosses the boundary as a decimal-string; wasm-side
|
|
302
|
+
// numeric parsers accept string form.
|
|
303
|
+
if (typeof v === 'bigint') return mem.String(v.toString())
|
|
304
|
+
if (Array.isArray(v)) return mem.Array(v)
|
|
305
|
+
if (v instanceof ArrayBuffer) return mem.Buffer(v)
|
|
306
|
+
if (v instanceof DataView) return mem.Buffer(v.buffer)
|
|
318
307
|
const typedName = v?.constructor?.name
|
|
319
|
-
if (typedName && ELEMS[typedName]) return
|
|
320
|
-
if (typeof v === 'object' || typeof v === 'function') return
|
|
308
|
+
if (typedName && ELEMS[typedName]) return mem[typedName](v)
|
|
309
|
+
if (typeof v === 'object' || typeof v === 'function') return mem.External(v)
|
|
321
310
|
return UNDEF_NAN
|
|
322
311
|
}
|
|
323
312
|
|
|
324
313
|
mem.External = function(obj) {
|
|
325
314
|
if (obj === null || obj === undefined) return coerce(obj)
|
|
326
|
-
const map =
|
|
315
|
+
const map = mem._extMap
|
|
327
316
|
if (!map) return UNDEF_NAN
|
|
328
317
|
let id = map.indexOf(obj)
|
|
329
318
|
if (id === -1) { id = map.length; map.push(obj) }
|
|
@@ -333,14 +322,14 @@ export const memory = (src) => {
|
|
|
333
322
|
mem.Object = function(obj) {
|
|
334
323
|
const objKeys = Object.keys(obj)
|
|
335
324
|
const key = objKeys.join(',')
|
|
336
|
-
const schemas =
|
|
325
|
+
const schemas = mem.schemas
|
|
337
326
|
let sid = schemas.findIndex(s => s.join(',') === key)
|
|
338
327
|
if (sid === -1) {
|
|
339
328
|
const matches = schemas.reduce((a, s, i) =>
|
|
340
329
|
(s.length === objKeys.length && objKeys.every(k => s.includes(k)) ? a.concat(i) : a), [])
|
|
341
330
|
if (matches.length === 1) sid = matches[0]
|
|
342
331
|
else if (matches.length > 1) throw Error(`Ambiguous schema for {${key}} — pass keys in schema order`)
|
|
343
|
-
else if (
|
|
332
|
+
else if (mem._extMap) return mem.External(obj)
|
|
344
333
|
else throw Error(`No schema for {${key}}`)
|
|
345
334
|
}
|
|
346
335
|
const schema = schemas[sid], n = schema.length, raw = alloc(n * 8)
|
|
@@ -350,8 +339,8 @@ export const memory = (src) => {
|
|
|
350
339
|
for (let i = 0; i < n; i++) {
|
|
351
340
|
let v = obj[schema[i]]
|
|
352
341
|
if (v === null || v === undefined) v = coerce(v)
|
|
353
|
-
else if (typeof v === 'string') v =
|
|
354
|
-
else if (Array.isArray(v)) v =
|
|
342
|
+
else if (typeof v === 'string') v = mem.String(v)
|
|
343
|
+
else if (Array.isArray(v)) v = mem.Array(v)
|
|
355
344
|
wrapped[i] = f64ToI64(v)
|
|
356
345
|
}
|
|
357
346
|
const dst = new BigInt64Array(mem.buffer, raw, n)
|
|
@@ -360,7 +349,7 @@ export const memory = (src) => {
|
|
|
360
349
|
}
|
|
361
350
|
|
|
362
351
|
mem.read = function(p) {
|
|
363
|
-
if (Array.isArray(p)) return p.map(v =>
|
|
352
|
+
if (Array.isArray(p)) return p.map(v => mem.read(v)) // multi-value tuple
|
|
364
353
|
if (p === p) return p // regular number passthrough (NaN fails ===)
|
|
365
354
|
const t = type(p), a = aux(p), off = offset(p)
|
|
366
355
|
if (t === 0 && off === 0) {
|
|
@@ -369,13 +358,13 @@ export const memory = (src) => {
|
|
|
369
358
|
if (a === 4) return false
|
|
370
359
|
if (a === 5) return true
|
|
371
360
|
}
|
|
372
|
-
if (t === 11 &&
|
|
361
|
+
if (t === 11 && mem._extMap) return mem._extMap[off]
|
|
373
362
|
if (t === 1) { // ARRAY
|
|
374
363
|
let m = dv(), aOff = off
|
|
375
364
|
// Follow forwarding pointers (cap === -1 means array was reallocated)
|
|
376
365
|
while (m.getInt32(aOff - 4, true) === -1) aOff = m.getInt32(aOff - 8, true)
|
|
377
366
|
const len = m.getInt32(aOff - 8, true), out = new Array(len)
|
|
378
|
-
for (let i = 0; i < len; i++) out[i] =
|
|
367
|
+
for (let i = 0; i < len; i++) out[i] = mem.read(m.getFloat64(aOff + i * 8, true))
|
|
379
368
|
return out
|
|
380
369
|
}
|
|
381
370
|
if (t === 3) { // TYPED
|
|
@@ -396,21 +385,21 @@ export const memory = (src) => {
|
|
|
396
385
|
new Uint8Array(out).set(new Uint8Array(mem.buffer, off, byteLen))
|
|
397
386
|
return out
|
|
398
387
|
}
|
|
399
|
-
if (t === 4) { // STRING (aux
|
|
388
|
+
if (t === 4) { // STRING (aux SSO_BIT = inline, else heap)
|
|
400
389
|
const a2 = aux(p)
|
|
401
|
-
if (a2 &
|
|
390
|
+
if (a2 & LAYOUT.SSO_BIT) {
|
|
402
391
|
const len = a2 & 0x7; let s = ''
|
|
403
392
|
for (let i = 0; i < len; i++) s += String.fromCharCode((off >>> (i * 8)) & 0xFF)
|
|
404
393
|
return s
|
|
405
394
|
}
|
|
406
395
|
const len = dv().getInt32(off - 4, true)
|
|
407
|
-
return
|
|
396
|
+
return TEXT_DEC.decode(new Uint8Array(mem.buffer, off, len))
|
|
408
397
|
}
|
|
409
398
|
if (t === 6) { // OBJECT
|
|
410
|
-
const m = dv(), sid = aux(p), keys =
|
|
399
|
+
const m = dv(), sid = aux(p), keys = mem.schemas[sid]
|
|
411
400
|
if (!keys) return p
|
|
412
401
|
const obj = {}
|
|
413
|
-
for (let i = 0; i < keys.length; i++) obj[keys[i]] =
|
|
402
|
+
for (let i = 0; i < keys.length; i++) obj[keys[i]] = mem.read(m.getFloat64(off + i * 8, true))
|
|
414
403
|
return obj
|
|
415
404
|
}
|
|
416
405
|
if (t === 7) { // HASH
|
|
@@ -419,8 +408,8 @@ export const memory = (src) => {
|
|
|
419
408
|
for (let i = 0, found = 0; i < cap && found < size; i++) {
|
|
420
409
|
const hash = m.getFloat64(off + i * 24, true)
|
|
421
410
|
if (hash !== 0) {
|
|
422
|
-
const key =
|
|
423
|
-
obj[key] =
|
|
411
|
+
const key = mem.read(m.getFloat64(off + i * 24 + 8, true))
|
|
412
|
+
obj[key] = mem.read(m.getFloat64(off + i * 24 + 16, true))
|
|
424
413
|
found++
|
|
425
414
|
}
|
|
426
415
|
}
|
|
@@ -431,7 +420,7 @@ export const memory = (src) => {
|
|
|
431
420
|
const set = new Set()
|
|
432
421
|
for (let i = 0; i < cap && set.size < size; i++) {
|
|
433
422
|
const hash = m.getFloat64(off + i * 16, true)
|
|
434
|
-
if (hash !== 0) set.add(
|
|
423
|
+
if (hash !== 0) set.add(mem.read(m.getFloat64(off + i * 16 + 8, true)))
|
|
435
424
|
}
|
|
436
425
|
return set
|
|
437
426
|
}
|
|
@@ -440,7 +429,7 @@ export const memory = (src) => {
|
|
|
440
429
|
const map = new Map()
|
|
441
430
|
for (let i = 0; i < cap && map.size < size; i++) {
|
|
442
431
|
const hash = m.getFloat64(off + i * 24, true)
|
|
443
|
-
if (hash !== 0) map.set(
|
|
432
|
+
if (hash !== 0) map.set(mem.read(m.getFloat64(off + i * 24 + 8, true)), mem.read(m.getFloat64(off + i * 24 + 16, true)))
|
|
444
433
|
}
|
|
445
434
|
return map
|
|
446
435
|
}
|
|
@@ -470,7 +459,7 @@ export const memory = (src) => {
|
|
|
470
459
|
for (let i = 0; i < data.length; i++) m[setter](off + i * stride, data[i], true)
|
|
471
460
|
}
|
|
472
461
|
} else if (t === 6) {
|
|
473
|
-
const schema =
|
|
462
|
+
const schema = mem.schemas[aux(p)]
|
|
474
463
|
if (!schema) throw Error(`write: unknown schema`)
|
|
475
464
|
for (const k of Object.keys(data)) {
|
|
476
465
|
const i = schema.indexOf(k)
|
|
@@ -485,10 +474,19 @@ export const memory = (src) => {
|
|
|
485
474
|
mem.reset = wasmExports?._clear || jsReset
|
|
486
475
|
|
|
487
476
|
// TypedArray constructors: memory.Float64Array(data), etc.
|
|
477
|
+
// Bulk-copy path: when input is a TypedArray whose element type matches
|
|
478
|
+
// the target (same stride), use .set() for a fast memcpy instead of
|
|
479
|
+
// per-element DataView writes. Falls back to DataView for mismatched types.
|
|
480
|
+
const TA = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array]
|
|
488
481
|
for (const [name, [elemId, stride, , setter]] of Object.entries(ELEMS)) {
|
|
489
482
|
mem[name] = (data) => {
|
|
490
|
-
const n = data.length, bytes = n * stride, off = hdr(bytes, bytes, bytes)
|
|
491
|
-
|
|
483
|
+
const n = data.length, bytes = n * stride, off = hdr(bytes, bytes, bytes)
|
|
484
|
+
if (stride >= 2 && TA[elemId] && data instanceof TA[elemId]) {
|
|
485
|
+
new TA[elemId](mem.buffer, off, n).set(data)
|
|
486
|
+
} else {
|
|
487
|
+
const m = dv()
|
|
488
|
+
for (let i = 0; i < n; i++) m[setter](off + i * stride, data[i], true)
|
|
489
|
+
}
|
|
492
490
|
return ptr(3, elemId, off)
|
|
493
491
|
}
|
|
494
492
|
}
|
|
@@ -513,19 +511,8 @@ export const wrap = (memSrc, inst) => {
|
|
|
513
511
|
restFuncs.set(typeof entry === 'string' ? entry : entry.name, typeof entry === 'string' ? 0 : entry.fixed)
|
|
514
512
|
} catch (e) { /* ignore */ }
|
|
515
513
|
}
|
|
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
514
|
// externref-param exports: positions where the wasm side takes an externref
|
|
528
|
-
// (
|
|
515
|
+
// (jsstring carrier — js-host only). JS values at these positions pass through
|
|
529
516
|
// unchanged — no `mem.wrapVal` (would NaN-box into f64, defeating the point).
|
|
530
517
|
// `def` (optional) maps idx → default-string for jsstring params whose
|
|
531
518
|
// default substitution happens JS-side (the wasm side never sees null).
|
|
@@ -542,7 +529,6 @@ export const wrap = (memSrc, inst) => {
|
|
|
542
529
|
}
|
|
543
530
|
} catch { /* ignore */ }
|
|
544
531
|
}
|
|
545
|
-
|
|
546
532
|
const mem = memory(memSrc)
|
|
547
533
|
const lastErrBits = realInst.exports.__jz_last_err_bits
|
|
548
534
|
const decodeThrown = error => {
|
|
@@ -558,118 +544,52 @@ export const wrap = (memSrc, inst) => {
|
|
|
558
544
|
throw wrapped
|
|
559
545
|
}
|
|
560
546
|
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
|
-
}
|
|
547
|
+
// Wrap one positional arg. Externref slots (jsstring carrier) pass the JS
|
|
548
|
+
// value straight through — `mem.wrapVal` would NaN-box it — substituting a
|
|
549
|
+
// jsstring literal default for a missing arg. Every other slot marshals via
|
|
550
|
+
// `box`: `coerce` for pure-scalar modules, `mem.wrapVal` for heap modules.
|
|
551
|
+
// Quiet-NaN ABI: every export value is f64, so there is no per-position i64
|
|
552
|
+
// carrier — args flow straight to the wasm call, results straight to decode/read.
|
|
553
|
+
const wrapArgAt = (ext, i, x, box) =>
|
|
554
|
+
ext?.has(i) ? (x === undefined && ext.def?.has(i) ? ext.def.get(i) : x) : box(x)
|
|
607
555
|
|
|
608
556
|
// Pure scalar module (no memory): pass f64 values directly, no marshaling
|
|
609
557
|
if (!mem) {
|
|
610
558
|
for (const [name, fn] of Object.entries(realInst.exports)) {
|
|
611
559
|
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
560
|
const ext = extExp.get(name)
|
|
615
561
|
const len = fn.length
|
|
616
|
-
try {
|
|
617
|
-
exports[name] = makeFastWrapper(fn, len, p, r, decode, null, ext)
|
|
618
|
-
continue
|
|
619
|
-
} catch { /* CSP fallback */ }
|
|
620
562
|
exports[name] = (...args) => {
|
|
621
563
|
while (args.length < len) args.push(undefined)
|
|
622
564
|
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))
|
|
565
|
+
return decode(fn(...args.map((x, i) => wrapArgAt(ext, i, x, coerce))))
|
|
628
566
|
} catch (e) { decodeThrown(e) }
|
|
629
567
|
}
|
|
630
568
|
}
|
|
631
569
|
return exports
|
|
632
570
|
}
|
|
633
571
|
const memWrapVal = mem.wrapVal.bind(mem)
|
|
634
|
-
const memRead = mem.read.bind(mem)
|
|
635
572
|
for (const [name, fn] of Object.entries(realInst.exports)) {
|
|
636
573
|
if (restFuncs.has(name) && typeof fn === 'function') {
|
|
637
574
|
const fixed = restFuncs.get(name)
|
|
638
|
-
const sig = i64Exp.get(name)
|
|
639
|
-
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
640
575
|
const ext = extExp.get(name)
|
|
641
576
|
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
|
-
})
|
|
577
|
+
const a = args.slice(0, fixed).map((x, i) => wrapArgAt(ext, i, x, memWrapVal))
|
|
646
578
|
while (a.length < fixed) a.push(UNDEF_NAN)
|
|
647
579
|
a.push(mem.Array(args.slice(fixed)))
|
|
648
580
|
try {
|
|
649
|
-
|
|
650
|
-
return mem.read(adaptRet(ret, r))
|
|
581
|
+
return mem.read(fn.apply(null, a))
|
|
651
582
|
} catch (error) {
|
|
652
583
|
decodeThrown(error)
|
|
653
584
|
}
|
|
654
585
|
}
|
|
655
586
|
} else if (typeof fn === 'function') {
|
|
656
|
-
const sig = i64Exp.get(name)
|
|
657
|
-
const p = sig?.p || EMPTY_SET, r = sig?.r || 0
|
|
658
587
|
const ext = extExp.get(name)
|
|
659
588
|
const len = fn.length
|
|
660
|
-
try {
|
|
661
|
-
exports[name] = makeFastWrapper(fn, len, p, r, memRead, memWrapVal, ext)
|
|
662
|
-
continue
|
|
663
|
-
} catch { /* CSP fallback */ }
|
|
664
589
|
exports[name] = (...args) => {
|
|
665
590
|
while (args.length < len) args.push(undefined)
|
|
666
591
|
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))
|
|
592
|
+
return mem.read(fn.apply(null, args.map((x, i) => wrapArgAt(ext, i, x, memWrapVal))))
|
|
673
593
|
} catch (error) {
|
|
674
594
|
decodeThrown(error)
|
|
675
595
|
}
|
|
@@ -747,6 +667,16 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
747
667
|
imports.env.now = (clock) =>
|
|
748
668
|
clock === 1 ? (typeof performance !== 'undefined' ? performance.now() : Date.now()) : Date.now()
|
|
749
669
|
}
|
|
670
|
+
// One i32 of entropy to seed Math.random — only present when compiled with
|
|
671
|
+
// { randomSeed: true }. Prefers crypto; falls back to Math.random.
|
|
672
|
+
if (envFns.has('rngSeed') && !imports.env.rngSeed) {
|
|
673
|
+
imports.env.rngSeed = () => {
|
|
674
|
+
const a = new Uint32Array(1)
|
|
675
|
+
if (globalThis.crypto?.getRandomValues) globalThis.crypto.getRandomValues(a)
|
|
676
|
+
else a[0] = (Math.random() * 0x100000000) >>> 0
|
|
677
|
+
return a[0] | 0
|
|
678
|
+
}
|
|
679
|
+
}
|
|
750
680
|
if (envFns.has('parseFloat') && !imports.env.parseFloat) {
|
|
751
681
|
imports.env.parseFloat = (valBig) => {
|
|
752
682
|
const s = state.mem.read(i64ToF64(valBig))
|
|
@@ -811,8 +741,8 @@ const jssProbeNative = () => {
|
|
|
811
741
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // header
|
|
812
742
|
0x01, 0x06, 0x01, 0x60, 0x01, 0x6f, 0x01, 0x7f, // type: (externref)→i32
|
|
813
743
|
0x02, 0x18, 0x01, // import section
|
|
814
|
-
0x0f, ...
|
|
815
|
-
0x06, ...
|
|
744
|
+
0x0f, ...TEXT_ENC.encode('wasm:js-string'), // mod name
|
|
745
|
+
0x06, ...TEXT_ENC.encode('length'), // name
|
|
816
746
|
0x00, 0x00, // kind=func, type=0
|
|
817
747
|
])
|
|
818
748
|
const mod = new WebAssembly.Module(bytes, { builtins: ['js-string'] })
|
|
@@ -921,7 +851,6 @@ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
|
921
851
|
*/
|
|
922
852
|
export const instantiate = (wasm, opts = {}) => {
|
|
923
853
|
const state = prepareInterop(opts)
|
|
924
|
-
opts.extMap = state.extMap
|
|
925
854
|
// Prefer native `wasm:js-string` builtins when the engine honors the option.
|
|
926
855
|
// The option is silently accepted by V8 17+/Safari 18.4+; older engines that
|
|
927
856
|
// don't recognize it either throw or ignore it — try-fallback handles both.
|
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>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `arguments` object + destructuring-param lowering for function→arrow conversion.
|
|
3
|
+
* @module jzify/arguments
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { paramList } from '../src/ast.js'
|
|
7
|
+
import { isDestructurePat, prependDecls } from './hoist-vars.js'
|
|
8
|
+
|
|
9
|
+
function usesArguments(node) {
|
|
10
|
+
if (node === 'arguments') return true
|
|
11
|
+
if (!Array.isArray(node)) return false
|
|
12
|
+
if (node[0] === 'function') return false
|
|
13
|
+
// Literal node `[, value]` (op === null) — node[1] is a string/number VALUE, not an
|
|
14
|
+
// identifier. A string literal `'arguments'` must not read as the arguments object.
|
|
15
|
+
if (node[0] == null) return false
|
|
16
|
+
if (node[0] === '.' || node[0] === '?.') return usesArguments(node[1])
|
|
17
|
+
if (node[0] === ':') return usesArguments(node[2])
|
|
18
|
+
for (let i = 1; i < node.length; i++) if (usesArguments(node[i])) return true
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function bindsArguments(body) {
|
|
23
|
+
const isArgDecl = s => Array.isArray(s) && (s[0] === 'var' || s[0] === 'let' || s[0] === 'const') &&
|
|
24
|
+
s.slice(1).some(d => d === 'arguments' || (Array.isArray(d) && d[0] === '=' && d[1] === 'arguments'))
|
|
25
|
+
let n = body
|
|
26
|
+
if (Array.isArray(n) && n[0] === '{}') n = n[1]
|
|
27
|
+
if (Array.isArray(n) && n[0] === ';') return n.slice(1).some(isArgDecl)
|
|
28
|
+
return isArgDecl(n)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function renameArguments(node, to) {
|
|
32
|
+
if (node === 'arguments') return to
|
|
33
|
+
if (!Array.isArray(node)) return node
|
|
34
|
+
if (node[0] === 'function') return node
|
|
35
|
+
// Literal node `[, value]` — node[1] is a value, not an identifier; leave untouched
|
|
36
|
+
// so a string literal `'arguments'` survives the rename.
|
|
37
|
+
if (node[0] == null) return node
|
|
38
|
+
if (node[0] === '.' || node[0] === '?.')
|
|
39
|
+
return [node[0], renameArguments(node[1], to), node[2]]
|
|
40
|
+
if (node[0] === ':')
|
|
41
|
+
return [node[0], node[1], renameArguments(node[2], to)]
|
|
42
|
+
return node.map(n => renameArguments(n, to))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function prependParamDecls(decl, body) {
|
|
46
|
+
if (Array.isArray(body) && body[0] === '{}') {
|
|
47
|
+
const inner = body[1]
|
|
48
|
+
if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
|
|
49
|
+
if (inner == null) return ['{}', decl]
|
|
50
|
+
return ['{}', [';', decl, inner]]
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(body) && (body[0] === ';' || body[0] === 'return')) return [';', decl, body]
|
|
53
|
+
return ['{}', [';', decl, ['return', body]]]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @param {ReturnType<import('./names.js').createNames>} names */
|
|
57
|
+
export function createArgumentsLowering(names) {
|
|
58
|
+
function lowerArguments(params, body) {
|
|
59
|
+
if (bindsArguments(body)) body = renameArguments(body, names.arg())
|
|
60
|
+
const paramsNeedLowering = paramList(params).some(isDestructurePat)
|
|
61
|
+
const usesArgsObj = usesArguments(params) || usesArguments(body)
|
|
62
|
+
if (!paramsNeedLowering && !usesArgsObj) return [params, body]
|
|
63
|
+
const name = names.arg()
|
|
64
|
+
const decls = []
|
|
65
|
+
for (const [idx, param] of paramList(params).entries()) {
|
|
66
|
+
if (Array.isArray(param) && param[0] === '...') {
|
|
67
|
+
decls.push(['=', param[1], ['()', ['.', name, 'slice'], [null, idx]]])
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(param) && param[0] === '=') {
|
|
71
|
+
decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
decls.push(['=', param, ['[]', name, [null, idx]]])
|
|
75
|
+
}
|
|
76
|
+
const renamed = usesArgsObj ? renameArguments(body, name) : body
|
|
77
|
+
return [['()', ['...', name]], decls.length ? prependParamDecls(['let', ...decls], renamed) : renamed]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let transformRef = null
|
|
81
|
+
const transformPattern = (node) => {
|
|
82
|
+
const transform = transformRef
|
|
83
|
+
if (node == null || !Array.isArray(node)) return node
|
|
84
|
+
const op = node[0]
|
|
85
|
+
if (op === '=') return ['=', transformPattern(node[1]), transform(node[2])]
|
|
86
|
+
if (op === ':') return [':', transform(node[1]), transformPattern(node[2])]
|
|
87
|
+
if (op === '...') return ['...', transformPattern(node[1])]
|
|
88
|
+
if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(transformPattern)]
|
|
89
|
+
return transform(node)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
lowerArguments,
|
|
94
|
+
transformPattern,
|
|
95
|
+
bindTransform(fn) { transformRef = fn },
|
|
96
|
+
}
|
|
97
|
+
}
|