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.
Files changed (86) hide show
  1. package/README.md +315 -318
  2. package/bench/README.md +369 -0
  3. package/bench/bench.svg +102 -0
  4. package/cli.js +104 -30
  5. package/dist/interop.js +1 -0
  6. package/dist/jz.js +6024 -0
  7. package/index.d.ts +126 -0
  8. package/index.js +362 -84
  9. package/interop.js +159 -186
  10. package/jz.svg +5 -0
  11. package/jzify/arguments.js +97 -0
  12. package/jzify/bundler.js +382 -0
  13. package/jzify/classes.js +328 -0
  14. package/jzify/hoist-vars.js +177 -0
  15. package/jzify/index.js +51 -0
  16. package/jzify/names.js +37 -0
  17. package/jzify/switch.js +106 -0
  18. package/jzify/transform.js +349 -0
  19. package/layout.js +184 -0
  20. package/module/array.js +377 -176
  21. package/module/collection.js +639 -145
  22. package/module/console.js +55 -43
  23. package/module/core.js +264 -153
  24. package/module/date.js +15 -3
  25. package/module/function.js +73 -6
  26. package/module/index.js +2 -1
  27. package/module/json.js +226 -61
  28. package/module/math.js +551 -187
  29. package/module/number.js +327 -60
  30. package/module/object.js +474 -184
  31. package/module/regex.js +255 -25
  32. package/module/schema.js +24 -6
  33. package/module/simd.js +117 -0
  34. package/module/string.js +621 -226
  35. package/module/symbol.js +1 -1
  36. package/module/timer.js +9 -14
  37. package/module/typedarray.js +459 -73
  38. package/package.json +58 -14
  39. package/src/abi/index.js +39 -23
  40. package/src/abi/string.js +38 -41
  41. package/src/ast.js +460 -0
  42. package/src/autoload.js +29 -24
  43. package/src/bridge.js +111 -0
  44. package/src/compile/analyze-scans.js +824 -0
  45. package/src/compile/analyze.js +1600 -0
  46. package/src/compile/emit-assign.js +411 -0
  47. package/src/compile/emit.js +3512 -0
  48. package/src/compile/flow-types.js +103 -0
  49. package/src/{compile.js → compile/index.js} +778 -128
  50. package/src/{infer.js → compile/infer.js} +62 -98
  51. package/src/compile/loop-divmod.js +155 -0
  52. package/src/{narrow.js → compile/narrow.js} +409 -106
  53. package/src/compile/peel-stencil.js +261 -0
  54. package/src/compile/plan/advise.js +370 -0
  55. package/src/compile/plan/common.js +150 -0
  56. package/src/compile/plan/index.js +122 -0
  57. package/src/compile/plan/inline.js +682 -0
  58. package/src/compile/plan/literals.js +1199 -0
  59. package/src/compile/plan/loops.js +472 -0
  60. package/src/compile/plan/scope.js +649 -0
  61. package/src/compile/program-facts.js +423 -0
  62. package/src/ctx.js +220 -64
  63. package/src/ir.js +589 -172
  64. package/src/kind-traits.js +132 -0
  65. package/src/kind.js +524 -0
  66. package/src/op-policy.js +57 -0
  67. package/src/{optimize.js → optimize/index.js} +1432 -473
  68. package/src/optimize/vectorize.js +4660 -0
  69. package/src/param-reps.js +65 -0
  70. package/src/parse.js +44 -0
  71. package/src/{prepare.js → prepare/index.js} +649 -205
  72. package/src/prepare/lift-iife.js +149 -0
  73. package/src/reps.js +116 -0
  74. package/src/resolve.js +12 -3
  75. package/src/static.js +208 -0
  76. package/src/type.js +651 -0
  77. package/src/{assemble.js → wat/assemble.js} +275 -55
  78. package/src/wat/optimize.js +3938 -0
  79. package/transform.js +21 -0
  80. package/wasi.js +47 -5
  81. package/src/analyze.js +0 -3818
  82. package/src/emit.js +0 -3040
  83. package/src/jzify.js +0 -1580
  84. package/src/plan.js +0 -2132
  85. package/src/vectorize.js +0 -1088
  86. /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.5.1",
4
- "description": "Functional JS subset compiling to WASM",
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
- ".": "./index.js",
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
- "README.md"
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
- "test262": "node test/test262.js",
27
- "test262:builtins": "node test/test262-builtins.js",
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
- "build:examples": "for dir in examples/*; do node \"$dir/build.mjs\"; done",
32
- "test:bench": "node test/bench.js",
33
- "test:all": "npm test && npm run test:bench",
34
- "prepublishOnly": "npm run test:all"
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.15",
48
- "watr": "^4.6.9"
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
- /** The default carrier bundlewhat `ctx.abi` resolves to. Identity-stable
29
- * reference so codegen can compare without string keys. */
30
- export const DEFAULTS = Object.freeze({ number: nanboxF64, string: sso, object: tagged, array: taggedLinear })
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
- // LAYOUT lives in `src/ctx.js`, which loads this module mid-bootstrap (line 10
60
- // of ctx.js, before LAYOUT itself is bound). ESM live bindings mean the import
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
- // Local copy of analyze.js#isReassigned pulling the real one creates a load-
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 ??= '0x' + (BigInt(LAYOUT.SSO_BIT) << BigInt(LAYOUT.AUX_SHIFT)).toString(16).toUpperCase().padStart(16, '0')
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 ? ['f64.const', 'nan:0x7FF8000000000000'] : ['i32.const', 0]],
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 ? ['f64.const', 'nan:0x7FF8000000000000'] : ['i32.const', 0]
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 && !_paramReassigned(ctx.func.body, name)) {
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
- return ['call', '$__str_eq', ssoI64(aF64), ssoI64(bF64)]
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
- concat: (aF64, bF64, ctx) => {
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
  },