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/module/symbol.js
CHANGED
package/module/timer.js
CHANGED
|
@@ -27,17 +27,12 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { typed, asF64, asI64, UNDEF_NAN, MAX_CLOSURE_ARITY, temp, tempI64 } from '../src/ir.js'
|
|
30
|
-
import { emit } from '../src/
|
|
31
|
-
import { inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
30
|
+
import { emit, deps, hostImport } from '../src/bridge.js'
|
|
31
|
+
import { inc, PTR, LAYOUT, declGlobal } from '../src/ctx.js'
|
|
32
32
|
|
|
33
33
|
const MAX_TIMERS = 64
|
|
34
34
|
const ENTRY_SIZE = 40
|
|
35
35
|
|
|
36
|
-
const addImportOnce = (ctx, mod, name, fn) => {
|
|
37
|
-
if (ctx.module.imports.some(i => i[1] === `"${mod}"` && i[2] === `"${name}"`)) return
|
|
38
|
-
ctx.module.imports.push(['import', `"${mod}"`, `"${name}"`, fn])
|
|
39
|
-
}
|
|
40
|
-
|
|
41
36
|
// Shared "fire a NaN-boxed closure with 0 args" trampoline. Funcref index lives
|
|
42
37
|
// in upper 16 bits of the pointer payload; remaining $ftN slots get UNDEF_NAN.
|
|
43
38
|
// Closure is also passed as $__env so captures resolve via env-load.
|
|
@@ -55,7 +50,7 @@ const setupWasi = (ctx) => {
|
|
|
55
50
|
// Always include init + tick + loop when timer module loads (structural, not per-emitter)
|
|
56
51
|
inc('__timer_init', '__timer_tick', '__timer_loop')
|
|
57
52
|
|
|
58
|
-
|
|
53
|
+
deps({
|
|
59
54
|
__timer_init: ['__alloc'],
|
|
60
55
|
__timer_add: ['__time_ns'],
|
|
61
56
|
__timer_cancel: [],
|
|
@@ -68,7 +63,7 @@ const setupWasi = (ctx) => {
|
|
|
68
63
|
// call_indirect always matches the $ftN type (env, argc, a0..a7)
|
|
69
64
|
ctx.closure.floor = MAX_CLOSURE_ARITY
|
|
70
65
|
|
|
71
|
-
|
|
66
|
+
hostImport('wasi_snapshot_preview1', 'clock_time_get',
|
|
72
67
|
['func', '$__clock_time_get', ['param', 'i32'], ['param', 'i64'], ['param', 'i32'], ['result', 'i32']])
|
|
73
68
|
|
|
74
69
|
// __time_ns() → i64 — current monotonic nanoseconds
|
|
@@ -235,9 +230,9 @@ const setupWasi = (ctx) => {
|
|
|
235
230
|
// $__timer_queue: i32 — base address of timer array
|
|
236
231
|
// $__timer_next_id: i32 — next timer ID
|
|
237
232
|
// $__timer_count: i32 — number of active timers
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
declGlobal('__timer_queue', 'i32')
|
|
234
|
+
declGlobal('__timer_next_id', 'i32')
|
|
235
|
+
declGlobal('__timer_count', 'i32')
|
|
241
236
|
|
|
242
237
|
// Emitter: setTimeout(closure, delay) → timer_id
|
|
243
238
|
ctx.core.emit['setTimeout'] = (closureExpr, delayExpr) => {
|
|
@@ -279,9 +274,9 @@ const setupJsHost = (ctx) => {
|
|
|
279
274
|
// canonicalization at the wasm→JS boundary (same reason as env.print —
|
|
280
275
|
// see module/console.js header). delay is a real numeric f64 (no NaN-box
|
|
281
276
|
// hazard), repeat is i32, return is the timer id (numeric int).
|
|
282
|
-
const needSetTimeout = () =>
|
|
277
|
+
const needSetTimeout = () => hostImport('env', 'setTimeout',
|
|
283
278
|
['func', '$__set_timeout', ['param', 'i64'], ['param', 'f64'], ['param', 'i32'], ['result', 'f64']])
|
|
284
|
-
const needClearTimeout = () =>
|
|
279
|
+
const needClearTimeout = () => hostImport('env', 'clearTimeout',
|
|
285
280
|
['func', '$__clear_timeout', ['param', 'f64'], ['result', 'f64']])
|
|
286
281
|
|
|
287
282
|
ctx.core.stdlib['__invoke_closure'] = invokeClosureFn(true)
|
package/module/typedarray.js
CHANGED
|
@@ -7,23 +7,17 @@
|
|
|
7
7
|
* @module typed
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { typed, asF64, asI32, asI64, toNumF64, UNDEF_NAN, allocPtr, mkPtrIR, ptrOffsetIR, temp, tempI32, tempI64, undefExpr, truthyIR } from '../src/ir.js'
|
|
11
|
-
import { emit } from '../src/
|
|
12
|
-
import { valTypeOf
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Float32Array: 6, Float64Array: 7,
|
|
22
|
-
BigInt64Array: 7, BigUint64Array: 7,
|
|
23
|
-
}
|
|
24
|
-
const BIGINT_ELEM_FLAG = 16
|
|
25
|
-
const typedAux = (name, isView = false) => ELEM[name] | (isView ? 8 : 0) |
|
|
26
|
-
(name === 'BigInt64Array' || name === 'BigUint64Array' ? BIGINT_ELEM_FLAG : 0)
|
|
10
|
+
import { typed, asF64, asI32, asI64, toNumF64, UNDEF_NAN, allocPtr, mkPtrIR, ptrOffsetIR, ptrTypeEq, temp, tempI32, tempI64, undefExpr, truthyIR } from '../src/ir.js'
|
|
11
|
+
import { emit, idx, deps, call } from '../src/bridge.js'
|
|
12
|
+
import { valTypeOf } from '../src/kind.js'
|
|
13
|
+
import { VAL, lookupValType } from '../src/reps.js'
|
|
14
|
+
import { nanPrefixHex, TYPED_ELEM_NAMES, TYPED_ELEM_CODE, TYPED_ELEM_BIGINT_FLAG, encodeTypedElemAux } from '../layout.js'
|
|
15
|
+
import { inc, PTR, getter } from '../src/ctx.js'
|
|
16
|
+
|
|
17
|
+
const _NAN_BITS = nanPrefixHex()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const typedAux = (name, isView = false) => encodeTypedElemAux(name, isView)
|
|
27
21
|
const STRIDE = [1, 1, 2, 2, 4, 4, 4, 8]
|
|
28
22
|
const SHIFT = [0, 0, 1, 1, 2, 2, 2, 3]
|
|
29
23
|
const LOAD = [
|
|
@@ -202,11 +196,14 @@ function genSimdMap(name, elemType, pattern) {
|
|
|
202
196
|
|
|
203
197
|
|
|
204
198
|
export default (ctx) => {
|
|
205
|
-
|
|
199
|
+
deps({
|
|
206
200
|
__byte_length: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
207
201
|
__byte_offset: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
208
202
|
__to_buffer: ['__ptr_type', '__ptr_offset', '__ptr_aux', '__mkptr'],
|
|
209
203
|
__typed_set_idx: ['__ptr_aux', '__ptr_offset'],
|
|
204
|
+
// __str_join uses __typed_idx when typedarray is loaded (plain arrays promoted to
|
|
205
|
+
// Int32Array by promoteIntArrayLiterals can produce PTR.TYPED results via .map()).
|
|
206
|
+
__str_join: [...(ctx.core.stdlibDeps.__str_join ?? []), '__typed_idx'],
|
|
210
207
|
})
|
|
211
208
|
|
|
212
209
|
// .map invokes with arity 1; .forEach/.find/.some/.every/.filter/.findIndex
|
|
@@ -217,6 +214,10 @@ export default (ctx) => {
|
|
|
217
214
|
|
|
218
215
|
inc('__mkptr', '__alloc', '__len')
|
|
219
216
|
|
|
217
|
+
const buf = call('__to_buffer', 'I')
|
|
218
|
+
const blen = call('__byte_length', 'I', 'i32')
|
|
219
|
+
const boff = call('__byte_offset', 'I', 'i32')
|
|
220
|
+
|
|
220
221
|
// === Runtime helpers: byte length, buffer coerce ===
|
|
221
222
|
// __typed_shift lives in core (needed by __len/__cap).
|
|
222
223
|
|
|
@@ -261,7 +262,7 @@ export default (ctx) => {
|
|
|
261
262
|
(else (call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0) (local.get $off)))))))`
|
|
262
263
|
|
|
263
264
|
// Constructor: new Float64Array(len) | new F64Array(arr) | new F64Array(buf) | new F64Array(buf, off, len)
|
|
264
|
-
for (const [name, elemType] of Object.entries(
|
|
265
|
+
for (const [name, elemType] of Object.entries(TYPED_ELEM_CODE)) {
|
|
265
266
|
const aux = typedAux(name)
|
|
266
267
|
const stride = STRIDE[elemType]
|
|
267
268
|
ctx.core.emit[`new.${name}`] = (lenExpr, offsetExpr, lenExpr2) => {
|
|
@@ -317,7 +318,7 @@ export default (ctx) => {
|
|
|
317
318
|
numAlloc.ptr]],
|
|
318
319
|
// Pointer: array → copy elements; buffer/typed → zero-copy view on same offset
|
|
319
320
|
['else', ['if', ['result', 'f64'],
|
|
320
|
-
['
|
|
321
|
+
ptrTypeEq(['local.get', `$${src}`], PTR.ARRAY),
|
|
321
322
|
['then', ctx.core.emit[`${name}.from`](src)],
|
|
322
323
|
['else', mkPtrIR(PTR.TYPED, aux,
|
|
323
324
|
['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${src}`]]])]]]]], 'f64')
|
|
@@ -404,14 +405,14 @@ export default (ctx) => {
|
|
|
404
405
|
// .buffer — always aliased (zero-copy). BUFFER: passthrough.
|
|
405
406
|
// Owned TYPED: retag as BUFFER at same offset — the byteLen header is shared.
|
|
406
407
|
// TYPED view (incl. DataView): BUFFER at descriptor[8] (root parent data offset).
|
|
407
|
-
ctx.core.emit['.buffer'] = (obj) => {
|
|
408
|
+
ctx.core.emit['.buffer'] = getter((obj) => {
|
|
408
409
|
if (typeof obj === 'string') {
|
|
409
410
|
const ctor = ctx.types.typedElem?.get(obj)
|
|
410
411
|
if (ctor === 'new.ArrayBuffer') return asF64(emit(obj))
|
|
411
412
|
if (ctor?.startsWith('new.')) {
|
|
412
413
|
const isView = ctor.endsWith('.view')
|
|
413
414
|
const name = isView ? ctor.slice(4, -5) : ctor.slice(4)
|
|
414
|
-
if (
|
|
415
|
+
if (TYPED_ELEM_CODE[name] != null) {
|
|
415
416
|
const parentOff = isView
|
|
416
417
|
? ['i32.load', ['i32.add', ptrOffsetIR(emit(obj), VAL.TYPED), ['i32.const', 8]]]
|
|
417
418
|
: ptrOffsetIR(emit(obj), VAL.TYPED)
|
|
@@ -419,13 +420,12 @@ export default (ctx) => {
|
|
|
419
420
|
}
|
|
420
421
|
}
|
|
421
422
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
423
|
+
return buf(obj)
|
|
424
|
+
})
|
|
425
425
|
|
|
426
426
|
// .byteLength — BUFFER: raw __len. Owned TYPED: elemCount * stride.
|
|
427
427
|
// View TYPED (incl. DataView): descriptor[0], via the __byte_length fallback.
|
|
428
|
-
ctx.core.emit['.byteLength'] = (obj) => {
|
|
428
|
+
ctx.core.emit['.byteLength'] = getter((obj) => {
|
|
429
429
|
if (typeof obj === 'string') {
|
|
430
430
|
const ctor = ctx.types.typedElem?.get(obj)
|
|
431
431
|
if (ctor === 'new.ArrayBuffer') {
|
|
@@ -434,7 +434,7 @@ export default (ctx) => {
|
|
|
434
434
|
if (ctor && ctor.startsWith('new.')) {
|
|
435
435
|
const isView = ctor.endsWith('.view')
|
|
436
436
|
const name = isView ? ctor.slice(4, -5) : ctor.slice(4)
|
|
437
|
-
const et =
|
|
437
|
+
const et = TYPED_ELEM_CODE[name]
|
|
438
438
|
if (et != null) {
|
|
439
439
|
if (isView) {
|
|
440
440
|
return typed(['f64.convert_i32_s',
|
|
@@ -445,12 +445,11 @@ export default (ctx) => {
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
448
|
+
return blen(obj)
|
|
449
|
+
})
|
|
451
450
|
|
|
452
451
|
// .byteOffset — owned: 0. View: descriptor[4] - descriptor[8].
|
|
453
|
-
ctx.core.emit['.byteOffset'] = (obj) => {
|
|
452
|
+
ctx.core.emit['.byteOffset'] = getter((obj) => {
|
|
454
453
|
if (typeof obj === 'string') {
|
|
455
454
|
const ctor = ctx.types.typedElem?.get(obj)
|
|
456
455
|
if (ctor?.endsWith('.view')) {
|
|
@@ -462,11 +461,10 @@ export default (ctx) => {
|
|
|
462
461
|
['i32.load', ['i32.add', ['local.get', `$${t}`], ['i32.const', 4]]],
|
|
463
462
|
['i32.load', ['i32.add', ['local.get', `$${t}`], ['i32.const', 8]]]]]], 'f64')
|
|
464
463
|
}
|
|
465
|
-
if (ctor?.startsWith('new.') &&
|
|
464
|
+
if (ctor?.startsWith('new.') && TYPED_ELEM_CODE[ctor.slice(4)] != null) return typed(['f64.const', 0], 'f64')
|
|
466
465
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
466
|
+
return boff(obj)
|
|
467
|
+
})
|
|
470
468
|
|
|
471
469
|
// Runtime fallback for .byteOffset when variable view-ness is unknown.
|
|
472
470
|
ctx.core.stdlib['__byte_offset'] = `(func $__byte_offset (param $ptr i64) (result i32)
|
|
@@ -488,8 +486,7 @@ export default (ctx) => {
|
|
|
488
486
|
ctx.core.emit['ArrayBuffer.isView'] = (v) => {
|
|
489
487
|
if (v === undefined) return typed(['f64.const', 0], 'f64')
|
|
490
488
|
const va = asF64(emit(v))
|
|
491
|
-
return typed(['f64.convert_i32_s',
|
|
492
|
-
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', va]], ['i32.const', PTR.TYPED]]], 'f64')
|
|
489
|
+
return typed(['f64.convert_i32_s', ptrTypeEq(va, PTR.TYPED)], 'f64')
|
|
493
490
|
}
|
|
494
491
|
|
|
495
492
|
// x instanceof Float64Array | Int32Array | … — typed-pointer predicate emitted
|
|
@@ -502,7 +499,7 @@ export default (ctx) => {
|
|
|
502
499
|
const t = temp('ityp')
|
|
503
500
|
return typed(['i32.and',
|
|
504
501
|
['f64.ne', ['local.tee', `$${t}`, v], ['local.get', `$${t}`]],
|
|
505
|
-
['
|
|
502
|
+
ptrTypeEq(['local.get', `$${t}`], PTR.TYPED)], 'i32')
|
|
506
503
|
}
|
|
507
504
|
|
|
508
505
|
// buf.slice(begin?, end?) on a BUFFER → fresh BUFFER with the byte range copied.
|
|
@@ -598,7 +595,7 @@ export default (ctx) => {
|
|
|
598
595
|
// (cheap: 4 wasm ops) so `getFloat32`/`getFloat64` return a real-spec NaN value.
|
|
599
596
|
ctx.core.stdlib['__canon_nan'] = `(func $__canon_nan (param $v f64) (result f64)
|
|
600
597
|
(select
|
|
601
|
-
(f64.reinterpret_i64 (i64.const
|
|
598
|
+
(f64.reinterpret_i64 (i64.const ${_NAN_BITS}))
|
|
602
599
|
(local.get $v)
|
|
603
600
|
(f64.ne (local.get $v) (local.get $v))))`
|
|
604
601
|
const canonNaN = (vIR) => { inc('__canon_nan'); return typed(['call', '$__canon_nan', vIR], 'f64') }
|
|
@@ -799,7 +796,7 @@ export default (ctx) => {
|
|
|
799
796
|
}
|
|
800
797
|
|
|
801
798
|
// TypedArray.from(arr) — convert regular array to typed array
|
|
802
|
-
for (const [name, elemType] of Object.entries(
|
|
799
|
+
for (const [name, elemType] of Object.entries(TYPED_ELEM_CODE)) {
|
|
803
800
|
const aux = typedAux(name)
|
|
804
801
|
const stride = STRIDE[elemType], store = STORE[elemType]
|
|
805
802
|
ctx.core.emit[`${name}.from`] = (src) => {
|
|
@@ -858,7 +855,7 @@ export default (ctx) => {
|
|
|
858
855
|
if (!ctor) return null
|
|
859
856
|
const isView = !chainOutput && ctor.endsWith('.view')
|
|
860
857
|
const name = ctor.endsWith('.view') ? ctor.slice(4, -5) : ctor.slice(4)
|
|
861
|
-
const et =
|
|
858
|
+
const et = TYPED_ELEM_CODE[name]
|
|
862
859
|
return et == null ? null : { et, isView, isBigInt: name === 'BigInt64Array' || name === 'BigUint64Array' }
|
|
863
860
|
}
|
|
864
861
|
|
|
@@ -913,7 +910,7 @@ export default (ctx) => {
|
|
|
913
910
|
(local.set $et (i32.and (local.get $aux) (i32.const 7)))
|
|
914
911
|
(if (result f64) (i32.ge_u (local.get $et) (i32.const 6))
|
|
915
912
|
(then (if (result f64) (i32.eq (local.get $et) (i32.const 7))
|
|
916
|
-
(then (if (result f64) (i32.and (local.get $aux) (i32.const ${
|
|
913
|
+
(then (if (result f64) (i32.and (local.get $aux) (i32.const ${TYPED_ELEM_BIGINT_FLAG}))
|
|
917
914
|
(then (f64.reinterpret_i64 (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
|
|
918
915
|
(else (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))))
|
|
919
916
|
(else (f64.promote_f32 (f32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 2))))))))
|
|
@@ -938,7 +935,7 @@ export default (ctx) => {
|
|
|
938
935
|
(if (i32.ne (i32.and (local.get $aux) (i32.const 8)) (i32.const 0))
|
|
939
936
|
(then (local.set $off (i32.load (i32.add (local.get $off) (i32.const 4))))))
|
|
940
937
|
(local.set $et (i32.and (local.get $aux) (i32.const 7)))
|
|
941
|
-
(if (i32.and (local.get $aux) (i32.const ${
|
|
938
|
+
(if (i32.and (local.get $aux) (i32.const ${TYPED_ELEM_BIGINT_FLAG}))
|
|
942
939
|
(then (i64.store (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))) (i64.reinterpret_f64 (local.get $v))))
|
|
943
940
|
(else
|
|
944
941
|
(if (i32.eq (local.get $et) (i32.const 7))
|
|
@@ -960,11 +957,11 @@ export default (ctx) => {
|
|
|
960
957
|
(local.get $v))`
|
|
961
958
|
|
|
962
959
|
// Type-aware TypedArray read: arr[i]
|
|
963
|
-
ctx.core.emit['.typed:[]'] = (arr,
|
|
960
|
+
ctx.core.emit['.typed:[]'] = (arr, i) => {
|
|
964
961
|
const r = resolveElem(arr)
|
|
965
962
|
if (r == null) return null // unknown type, fallback to generic
|
|
966
963
|
const { et, isView, isBigInt } = r
|
|
967
|
-
const objIR = emit(arr), vi =
|
|
964
|
+
const objIR = emit(arr), vi = idx(i)
|
|
968
965
|
const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
|
|
969
966
|
if (isBigInt) return typed(['f64.reinterpret_i64', ['i64.load', off]], 'f64')
|
|
970
967
|
if (et === 7) return typed(['f64.load', off], 'f64') // Float64Array
|
|
@@ -974,11 +971,11 @@ export default (ctx) => {
|
|
|
974
971
|
}
|
|
975
972
|
|
|
976
973
|
// Type-aware TypedArray write: arr[i] = val
|
|
977
|
-
ctx.core.emit['.typed:[]='] = (arr,
|
|
974
|
+
ctx.core.emit['.typed:[]='] = (arr, i, val, void_ = false) => {
|
|
978
975
|
const r = resolveElem(arr)
|
|
979
976
|
if (r == null) return null
|
|
980
977
|
const { et, isView, isBigInt } = r
|
|
981
|
-
const objIR = emit(arr), vi =
|
|
978
|
+
const objIR = emit(arr), vi = idx(i), valIR = emit(val)
|
|
982
979
|
const off = ['i32.add', typedDataAddr(objIR, isView), ['i32.shl', vi, ['i32.const', SHIFT[et]]]]
|
|
983
980
|
if (isBigInt) {
|
|
984
981
|
const vt = temp('tw')
|
|
@@ -1213,7 +1210,7 @@ export default (ctx) => {
|
|
|
1213
1210
|
// ABI width at 2 to spare a slot across the whole program). Reduce passes
|
|
1214
1211
|
// (acc, item). Closure invocation goes through `ctx.closure.call` directly.
|
|
1215
1212
|
// The element-type-name list is needed by allocPtr for typedAux:
|
|
1216
|
-
const ET_NAME =
|
|
1213
|
+
const ET_NAME = TYPED_ELEM_NAMES
|
|
1217
1214
|
|
|
1218
1215
|
// .forEach: callback (item, idx). Result is 0 to match array.js's
|
|
1219
1216
|
// convention (spec says undefined; both modules pick 0 since f() exposes the
|
package/package.json
CHANGED
|
@@ -1,37 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Functional JS subset compiling to WASM",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.js",
|
|
9
9
|
"./wasi": "./wasi.js",
|
|
10
|
-
"./interop": "./interop.js"
|
|
10
|
+
"./interop": "./interop.js",
|
|
11
|
+
"./transform": "./transform.js"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"index.js",
|
|
14
15
|
"cli.js",
|
|
15
16
|
"wasi.js",
|
|
16
17
|
"interop.js",
|
|
18
|
+
"layout.js",
|
|
19
|
+
"transform.js",
|
|
20
|
+
"jzify",
|
|
17
21
|
"src",
|
|
18
22
|
"module",
|
|
19
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"jz.svg",
|
|
25
|
+
"alternatives.svg",
|
|
26
|
+
"bench/bench.svg"
|
|
20
27
|
],
|
|
21
28
|
"bin": {
|
|
22
29
|
"jz": "cli.js"
|
|
23
30
|
},
|
|
24
31
|
"scripts": {
|
|
25
32
|
"test": "node test/index.js",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
33
|
+
"test:opt0": "JZ_TEST_OPTIMIZE=0 node test/index.js",
|
|
34
|
+
"test:opt1": "JZ_TEST_OPTIMIZE=1 node test/index.js",
|
|
35
|
+
"test:opt3": "JZ_TEST_OPTIMIZE=3 node test/index.js",
|
|
36
|
+
"test:wasi": "JZ_TEST_HOST=wasi node test/index.js",
|
|
37
|
+
"test:wasm": "JZ_TEST_TARGET=jz.wasm node test/index.js",
|
|
38
|
+
"test:matrix": "npm test && npm run test:opt0 && npm run test:opt3 && npm run test:wasi",
|
|
39
|
+
"test:bench": "node test/bench.js",
|
|
40
|
+
"test:fuzz": "node test/fuzz.js --count=5000",
|
|
41
|
+
"test:ratchet": "node test/perf-ratchet.js",
|
|
42
|
+
"test:self": "node test/selfhost.js",
|
|
43
|
+
"test:262": "node test/test262.js",
|
|
44
|
+
"test:262:builtins": "node test/test262-builtins.js",
|
|
45
|
+
"test:all": "npm run test:matrix && npm run test:262 && npm run test:262:builtins && npm run test:bench && npm run test:self",
|
|
28
46
|
"bench": "node bench/bench.mjs",
|
|
47
|
+
"bench:fuzz": "node scripts/fuzz-bench.mjs",
|
|
29
48
|
"bench:size": "node scripts/bench-size.mjs",
|
|
30
49
|
"bench:compile": "node scripts/bench-compile.mjs",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"prepublishOnly": "npm run test:
|
|
50
|
+
"bench:startup": "node scripts/bench-startup.mjs",
|
|
51
|
+
"build": "node scripts/build-dist.mjs",
|
|
52
|
+
"build:examples": "for dir in examples/*/; do [ -f \"$dir/build.mjs\" ] && node \"$dir/build.mjs\"; done",
|
|
53
|
+
"prepublishOnly": "npm test && npm run test:self"
|
|
35
54
|
},
|
|
36
55
|
"repository": {
|
|
37
56
|
"type": "git",
|
|
@@ -44,8 +63,8 @@
|
|
|
44
63
|
"author": "Dmitry Iv",
|
|
45
64
|
"license": "MIT",
|
|
46
65
|
"dependencies": {
|
|
47
|
-
"subscript": "^10.4.
|
|
48
|
-
"watr": "^4.6.
|
|
66
|
+
"subscript": "^10.4.17",
|
|
67
|
+
"watr": "^4.6.10"
|
|
49
68
|
},
|
|
50
69
|
"keywords": [
|
|
51
70
|
"javascript",
|
|
@@ -53,9 +72,19 @@
|
|
|
53
72
|
"compiler",
|
|
54
73
|
"functional",
|
|
55
74
|
"minimal",
|
|
56
|
-
"wasm"
|
|
75
|
+
"wasm",
|
|
76
|
+
"aot",
|
|
77
|
+
"simd",
|
|
78
|
+
"wasi",
|
|
79
|
+
"wat",
|
|
80
|
+
"dsp",
|
|
81
|
+
"audio",
|
|
82
|
+
"creative-coding",
|
|
83
|
+
"assemblyscript",
|
|
84
|
+
"porffor"
|
|
57
85
|
],
|
|
58
86
|
"devDependencies": {
|
|
87
|
+
"esbuild": "^0.28.0",
|
|
59
88
|
"tst": "^9.4.0"
|
|
60
89
|
}
|
|
61
90
|
}
|
package/src/abi/index.js
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/abi — internal codegen carriers.
|
|
3
|
-
*
|
|
4
|
-
* The `abi/` directory hosts compiler-internal codegen modules — one file per
|
|
5
|
-
* value type, each exporting every carrier (slot strategy) the compiler may
|
|
6
|
-
* pick for that type. **No user surface.** `opts.host` is the only knob users
|
|
7
|
-
* see; internal representation is analysis-driven and per-site.
|
|
8
|
-
*
|
|
9
|
-
* Today the narrower has not yet been wired to pick carriers per site, so
|
|
10
|
-
* each type module's `default` export is used as the carrier for every site
|
|
11
|
-
* of that type. `ctx.abi.<type>` resolves to that default; codegen reads
|
|
12
|
-
* `ctx.abi.string.ops.byteLen(...)` etc. Per-site dispatch arrives by
|
|
13
|
-
* exposing all carriers (`ctx.abi.string.sso`, `.jsstring`) and letting
|
|
14
|
-
* `narrow.js` tag each binding with a carrier choice.
|
|
15
|
-
*
|
|
16
|
-
* No presets, no preset-name discriminant, no public ABI knob — carrier
|
|
17
|
-
* choice is analysis-driven, not user-pickable. See `.work/todo.md`
|
|
18
|
-
* "Boundary protocol and internal representation" for the policy.
|
|
2
|
+
* src/abi — internal codegen carriers + per-site dispatch.
|
|
19
3
|
*
|
|
20
4
|
* @module src/abi
|
|
21
5
|
*/
|
|
@@ -25,11 +9,43 @@ import sso, { jsstring } from './string.js'
|
|
|
25
9
|
import tagged from './object.js'
|
|
26
10
|
import taggedLinear, { structInline } from './array.js'
|
|
27
11
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
/** All carriers per value type — keyed by stable id for rep.carrier lookup. */
|
|
13
|
+
export const CARRIERS = Object.freeze({
|
|
14
|
+
number: Object.freeze({ nanboxF64 }),
|
|
15
|
+
string: Object.freeze({ sso, jsstring }),
|
|
16
|
+
object: Object.freeze({ tagged }),
|
|
17
|
+
array: Object.freeze({ taggedLinear, structInline }),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const DEFAULT_ID = Object.freeze({
|
|
21
|
+
number: 'nanboxF64',
|
|
22
|
+
string: 'sso',
|
|
23
|
+
object: 'tagged',
|
|
24
|
+
array: 'taggedLinear',
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/** Pick carrier bundle for `type` given optional binding rep hints. */
|
|
28
|
+
export function resolveCarrier(type, rep) {
|
|
29
|
+
const table = CARRIERS[type]
|
|
30
|
+
if (!table) return null
|
|
31
|
+
const id = rep?.carrier ?? DEFAULT_ID[type]
|
|
32
|
+
return table[id] ?? table[DEFAULT_ID[type]]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Default carrier ops bundle — backward-compatible flat shape on ctx.abi. */
|
|
36
|
+
export const DEFAULTS = Object.freeze({
|
|
37
|
+
number: nanboxF64,
|
|
38
|
+
string: sso,
|
|
39
|
+
object: tagged,
|
|
40
|
+
array: taggedLinear,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/** ctx.abi bundle: default carriers + resolve() + registry. All access is by
|
|
44
|
+
* fixed key (`ctx.abi.object.ops`, `.string`, `.resolve`, …), so a plain
|
|
45
|
+
* literal — the shape jz compiles directly — is equivalent to the former
|
|
46
|
+
* null-proto `Object.assign` merge. */
|
|
47
|
+
export function makeAbi() {
|
|
48
|
+
return { ...DEFAULTS, carriers: CARRIERS, resolve: resolveCarrier }
|
|
49
|
+
}
|
|
31
50
|
|
|
32
|
-
// Carrier re-exports — for tests and tools that want to reach a specific
|
|
33
|
-
// carrier directly. Per-site narrowing will use these via `ctx.abi.<type>`
|
|
34
|
-
// once the narrower exposes the full carrier dictionary.
|
|
35
51
|
export { nanboxF64, sso, jsstring, tagged, taggedLinear, structInline }
|
package/src/abi/string.js
CHANGED
|
@@ -56,42 +56,12 @@
|
|
|
56
56
|
// take. Kept as a one-liner so each op reads as a single `call`.
|
|
57
57
|
const ssoI64 = (sF64) => ['i64.reinterpret_f64', sF64]
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// resolves but the value is `undefined` until ctx.js finishes. All charCodeAt /
|
|
62
|
-
// inline paths read these constants at *call* time (compile-time, after ctx is
|
|
63
|
-
// fully initialized) — never at module top level — so this is safe.
|
|
64
|
-
import { LAYOUT } from '../ctx.js'
|
|
59
|
+
import { isReassigned } from '../ast.js'
|
|
60
|
+
import { LAYOUT, oobNanIR, ssoBitI64Hex } from '../../layout.js'
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
// order cycle (abi/string ← ctx ← analyze ← ir ← ctx). Kept minimal: detects
|
|
68
|
-
// any `=`/compound-assign/`++`/`--` of `name` anywhere in `body`. `let`/`const`
|
|
69
|
-
// declarations are NOT reassignments (the param can't be redeclared anyway,
|
|
70
|
-
// but a shadowing inner `let s = ...` shouldn't poison the outer param's
|
|
71
|
-
// fast path). False positives are conservative — we just disable the fast
|
|
72
|
-
// path and fall through to shape 2.
|
|
73
|
-
const _CC_ASSIGN_OPS = new Set(['=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??='])
|
|
74
|
-
const _paramReassigned = (body, name) => {
|
|
75
|
-
if (!Array.isArray(body)) return false
|
|
76
|
-
const op = body[0]
|
|
77
|
-
if (_CC_ASSIGN_OPS.has(op) && body[1] === name) return true
|
|
78
|
-
if ((op === '++' || op === '--') && body[1] === name) return true
|
|
79
|
-
if (op === 'let' || op === 'const') {
|
|
80
|
-
for (let i = 1; i < body.length; i++) {
|
|
81
|
-
const d = body[i]
|
|
82
|
-
if (Array.isArray(d) && d[0] === '=' && d[2] != null && _paramReassigned(d[2], name)) return true
|
|
83
|
-
}
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
86
|
-
for (let i = 1; i < body.length; i++) if (_paramReassigned(body[i], name)) return true
|
|
87
|
-
return false
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Pre-shifted SSO discriminator (bit 46 of the i64 ptr). Computed lazily on
|
|
91
|
-
* first read since LAYOUT is undefined when this module's top level runs.
|
|
92
|
-
* Memoized in the closure so all later call sites pay zero overhead. */
|
|
62
|
+
/** Pre-shifted SSO discriminator — layout.js is cycle-free; memoized at first use. */
|
|
93
63
|
let _ssoBitI64 = null
|
|
94
|
-
const ssoBitI64 = () => _ssoBitI64 ??=
|
|
64
|
+
const ssoBitI64 = () => _ssoBitI64 ??= ssoBitI64Hex()
|
|
95
65
|
|
|
96
66
|
/** Allocate a fresh i64 local in the current function. Replicated here (not
|
|
97
67
|
* imported from `src/ir.js`) to keep this module loadable during ctx.js
|
|
@@ -134,7 +104,7 @@ function emitDecompCharRead(dec, iI32, ctx, oobNan) {
|
|
|
134
104
|
['else', heapByteExpr]]
|
|
135
105
|
const use = ['if', ['result', rt],
|
|
136
106
|
['i32.ge_u', idx, ['local.get', `$${dec.len}`]],
|
|
137
|
-
['then', oobNan ?
|
|
107
|
+
['then', oobNan ? oobNanIR() : ['i32.const', 0]],
|
|
138
108
|
['else', oobNan ? ['f64.convert_i32_u', ccByte] : ccByte]]
|
|
139
109
|
return spill
|
|
140
110
|
? ['block', ['result', rt], ['local.set', `$${spill}`, iI32], use]
|
|
@@ -252,7 +222,7 @@ export const sso = {
|
|
|
252
222
|
// being false to terminate; an i32 `0` would loop forever.
|
|
253
223
|
// A fresh OOB node per use — IR nodes must not be structurally shared
|
|
254
224
|
// (later passes mutate in place).
|
|
255
|
-
const mkOob = () => oobNan ?
|
|
225
|
+
const mkOob = () => oobNan ? oobNanIR() : ['i32.const', 0]
|
|
256
226
|
const rt = oobNan ? 'f64' : 'i32'
|
|
257
227
|
const widen = b => oobNan ? ['f64.convert_i32_u', b] : b
|
|
258
228
|
// Shape 1: receiver is a `local.get` of a non-boxed function parameter.
|
|
@@ -273,7 +243,7 @@ export const sso = {
|
|
|
273
243
|
// to actually be `f64` (i64.reinterpret_f64 below would fail
|
|
274
244
|
// validation otherwise — narrowed-to-int params have type 'i32').
|
|
275
245
|
if (param && param.type === 'f64' && param.ptrKind == null
|
|
276
|
-
&& !isBoxed && ctx.func.body && !
|
|
246
|
+
&& !isBoxed && ctx.func.body && !isReassigned(ctx.func.body, name)) {
|
|
277
247
|
if (!ctx.func.charDecomp) ctx.func.charDecomp = new Map()
|
|
278
248
|
let dec = ctx.func.charDecomp.get(name)
|
|
279
249
|
if (!dec) {
|
|
@@ -362,10 +332,31 @@ export const sso = {
|
|
|
362
332
|
return preface
|
|
363
333
|
},
|
|
364
334
|
|
|
365
|
-
/** Content equality. Both args: f64 slot carriers. Returns i32 boolean.
|
|
335
|
+
/** Content equality. Both args: f64 slot carriers. Returns i32 boolean.
|
|
336
|
+
* The bit-eq fast path is inlined at the site: static-literal dedup, SSO
|
|
337
|
+
* packing and slice interning make identical bits the DOMINANT equal case
|
|
338
|
+
* (a compiler comparing tree tags against literals hits it ~always), so
|
|
339
|
+
* most comparisons skip the __str_eq call entirely. Content compare only
|
|
340
|
+
* on bit-mismatch. */
|
|
366
341
|
eq: (aF64, bF64, ctx) => {
|
|
367
342
|
ctx.core.includes.add('__str_eq')
|
|
368
|
-
|
|
343
|
+
// i64 temps allocated through the passed ctx — importing ir.js's tempI64
|
|
344
|
+
// here would close the abi→ir→ctx→abi module cycle the kernel bundler
|
|
345
|
+
// rejects (mirrors freshLocal's registration).
|
|
346
|
+
const fresh = () => {
|
|
347
|
+
let n
|
|
348
|
+
do { n = `seq${ctx.func.uniq++}` } while (ctx.func.locals.has(n))
|
|
349
|
+
ctx.func.locals.set(n, 'i64')
|
|
350
|
+
return n
|
|
351
|
+
}
|
|
352
|
+
const ta = fresh(), tb = fresh()
|
|
353
|
+
return ['block', ['result', 'i32'],
|
|
354
|
+
['local.set', `$${ta}`, ssoI64(aF64)],
|
|
355
|
+
['local.set', `$${tb}`, ssoI64(bF64)],
|
|
356
|
+
['if', ['result', 'i32'],
|
|
357
|
+
['i64.eq', ['local.get', `$${ta}`], ['local.get', `$${tb}`]],
|
|
358
|
+
['then', ['i32.const', 1]],
|
|
359
|
+
['else', ['call', '$__str_eq', ['local.get', `$${ta}`], ['local.get', `$${tb}`]]]]]
|
|
369
360
|
},
|
|
370
361
|
|
|
371
362
|
/** Three-way byte compare. Both args: f64 slot carriers. Returns i32 ∈ {-1, 0, 1}. */
|
|
@@ -375,8 +366,14 @@ export const sso = {
|
|
|
375
366
|
},
|
|
376
367
|
|
|
377
368
|
/** Concat with ToString coercion on both sides. Both args: f64 slot
|
|
378
|
-
* carriers. Returns f64 (the new STRING ptr's slot carrier).
|
|
379
|
-
|
|
369
|
+
* carriers. Returns f64 (the new STRING ptr's slot carrier).
|
|
370
|
+
* Named `cat`, not `concat`: under self-host this op is invoked as a
|
|
371
|
+
* method on the statically-untyped `ctx.abi.string.ops` receiver, and the
|
|
372
|
+
* name `concat` collides with `Array.prototype.concat` — the method-call
|
|
373
|
+
* dispatcher's string/array runtime guess (emit.js) would hijack it into a
|
|
374
|
+
* bogus array concat. A non-builtin name routes through dynamic property
|
|
375
|
+
* dispatch (load the closure slot, call it) correctly. */
|
|
376
|
+
cat: (aF64, bF64, ctx) => {
|
|
380
377
|
ctx.core.includes.add('__str_concat')
|
|
381
378
|
return ['call', '$__str_concat', ssoI64(aF64), ssoI64(bF64)]
|
|
382
379
|
},
|