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/package.json
CHANGED
|
@@ -1,37 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jz",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Ahead-of-time compiler for the numeric core of JavaScript (DSP, audio, math, parsers) to lean GC-free WASM — valid jz is valid JS, no type annotations, no runtime.",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.js"
|
|
12
|
+
},
|
|
9
13
|
"./wasi": "./wasi.js",
|
|
10
|
-
"./interop": "./interop.js"
|
|
14
|
+
"./interop": "./interop.js",
|
|
15
|
+
"./transform": "./transform.js"
|
|
11
16
|
},
|
|
12
17
|
"files": [
|
|
13
18
|
"index.js",
|
|
19
|
+
"index.d.ts",
|
|
14
20
|
"cli.js",
|
|
15
21
|
"wasi.js",
|
|
16
22
|
"interop.js",
|
|
23
|
+
"layout.js",
|
|
24
|
+
"transform.js",
|
|
25
|
+
"jzify",
|
|
17
26
|
"src",
|
|
18
27
|
"module",
|
|
19
|
-
"
|
|
28
|
+
"dist/jz.js",
|
|
29
|
+
"dist/interop.js",
|
|
30
|
+
"README.md",
|
|
31
|
+
"jz.svg",
|
|
32
|
+
"alternatives.svg",
|
|
33
|
+
"bench/bench.svg"
|
|
20
34
|
],
|
|
21
35
|
"bin": {
|
|
22
36
|
"jz": "cli.js"
|
|
23
37
|
},
|
|
24
38
|
"scripts": {
|
|
25
39
|
"test": "node test/index.js",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
40
|
+
"test:opt0": "JZ_TEST_OPTIMIZE=0 node test/index.js",
|
|
41
|
+
"test:opt1": "JZ_TEST_OPTIMIZE=1 node test/index.js",
|
|
42
|
+
"test:opt3": "JZ_TEST_OPTIMIZE=3 node test/index.js",
|
|
43
|
+
"test:wasi": "JZ_TEST_HOST=wasi node test/index.js",
|
|
44
|
+
"test:wasm": "JZ_TEST_TARGET=jz.wasm node test/index.js",
|
|
45
|
+
"test:matrix": "npm test && npm run test:opt0 && npm run test:opt3 && npm run test:wasi",
|
|
46
|
+
"test:bench": "node test/bench.js",
|
|
47
|
+
"test:fuzz": "node test/fuzz.js --count=5000",
|
|
48
|
+
"test:ratchet": "node test/perf-ratchet.js",
|
|
49
|
+
"test:self": "node test/selfhost.js",
|
|
50
|
+
"test:262": "node test/test262.js",
|
|
51
|
+
"test:262:builtins": "node test/test262-builtins.js",
|
|
52
|
+
"test:all": "npm run test:matrix && npm run test:262 && npm run test:262:builtins && npm run test:bench && npm run test:self",
|
|
28
53
|
"bench": "node bench/bench.mjs",
|
|
54
|
+
"bench:fuzz": "node scripts/fuzz-bench.mjs",
|
|
29
55
|
"bench:size": "node scripts/bench-size.mjs",
|
|
30
56
|
"bench:compile": "node scripts/bench-compile.mjs",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
57
|
+
"bench:startup": "node scripts/bench-startup.mjs",
|
|
58
|
+
"bench:readme": "node scripts/bench-readme.mjs",
|
|
59
|
+
"build": "node scripts/build-dist.mjs",
|
|
60
|
+
"build:examples": "node examples/build.mjs all",
|
|
61
|
+
"prepare": "npm run build"
|
|
35
62
|
},
|
|
36
63
|
"repository": {
|
|
37
64
|
"type": "git",
|
|
@@ -44,8 +71,8 @@
|
|
|
44
71
|
"author": "Dmitry Iv",
|
|
45
72
|
"license": "MIT",
|
|
46
73
|
"dependencies": {
|
|
47
|
-
"subscript": "^10.4.
|
|
48
|
-
"watr": "^4.6.
|
|
74
|
+
"subscript": "^10.4.17",
|
|
75
|
+
"watr": "^4.6.10"
|
|
49
76
|
},
|
|
50
77
|
"keywords": [
|
|
51
78
|
"javascript",
|
|
@@ -53,9 +80,26 @@
|
|
|
53
80
|
"compiler",
|
|
54
81
|
"functional",
|
|
55
82
|
"minimal",
|
|
56
|
-
"wasm"
|
|
83
|
+
"wasm",
|
|
84
|
+
"aot",
|
|
85
|
+
"simd",
|
|
86
|
+
"wasi",
|
|
87
|
+
"wat",
|
|
88
|
+
"dsp",
|
|
89
|
+
"audio",
|
|
90
|
+
"creative-coding",
|
|
91
|
+
"assemblyscript",
|
|
92
|
+
"porffor",
|
|
93
|
+
"numeric",
|
|
94
|
+
"signal-processing",
|
|
95
|
+
"math",
|
|
96
|
+
"audio-worklet",
|
|
97
|
+
"js-to-wasm",
|
|
98
|
+
"performance"
|
|
57
99
|
],
|
|
58
100
|
"devDependencies": {
|
|
101
|
+
"esbuild": "^0.28.0",
|
|
102
|
+
"sprae": "^13.3.8",
|
|
59
103
|
"tst": "^9.4.0"
|
|
60
104
|
}
|
|
61
105
|
}
|
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
|
},
|