jz 0.5.1 → 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.
Files changed (80) hide show
  1. package/README.md +288 -314
  2. package/bench/README.md +319 -0
  3. package/bench/bench.svg +112 -0
  4. package/cli.js +32 -24
  5. package/index.js +177 -55
  6. package/interop.js +88 -159
  7. package/jz.svg +5 -0
  8. package/jzify/arguments.js +97 -0
  9. package/jzify/bundler.js +382 -0
  10. package/jzify/classes.js +328 -0
  11. package/jzify/hoist-vars.js +177 -0
  12. package/jzify/index.js +51 -0
  13. package/jzify/names.js +37 -0
  14. package/jzify/switch.js +106 -0
  15. package/jzify/transform.js +349 -0
  16. package/layout.js +179 -0
  17. package/module/array.js +322 -153
  18. package/module/collection.js +603 -145
  19. package/module/console.js +55 -43
  20. package/module/core.js +266 -153
  21. package/module/date.js +15 -3
  22. package/module/function.js +73 -6
  23. package/module/index.js +2 -1
  24. package/module/json.js +226 -61
  25. package/module/math.js +414 -186
  26. package/module/number.js +306 -60
  27. package/module/object.js +448 -184
  28. package/module/regex.js +255 -25
  29. package/module/schema.js +24 -6
  30. package/module/simd.js +85 -0
  31. package/module/string.js +586 -220
  32. package/module/symbol.js +1 -1
  33. package/module/timer.js +9 -14
  34. package/module/typedarray.js +45 -48
  35. package/package.json +41 -12
  36. package/src/abi/index.js +39 -23
  37. package/src/abi/string.js +38 -41
  38. package/src/ast.js +460 -0
  39. package/src/autoload.js +26 -24
  40. package/src/bridge.js +111 -0
  41. package/src/compile/analyze-scans.js +661 -0
  42. package/src/compile/analyze.js +1565 -0
  43. package/src/compile/emit-assign.js +408 -0
  44. package/src/compile/emit.js +3201 -0
  45. package/src/compile/flow-types.js +103 -0
  46. package/src/{compile.js → compile/index.js} +497 -125
  47. package/src/{infer.js → compile/infer.js} +27 -98
  48. package/src/{narrow.js → compile/narrow.js} +302 -96
  49. package/src/compile/plan/advise.js +316 -0
  50. package/src/compile/plan/common.js +150 -0
  51. package/src/compile/plan/index.js +118 -0
  52. package/src/compile/plan/inline.js +679 -0
  53. package/src/compile/plan/literals.js +984 -0
  54. package/src/compile/plan/loops.js +472 -0
  55. package/src/compile/plan/scope.js +573 -0
  56. package/src/compile/program-facts.js +404 -0
  57. package/src/ctx.js +176 -58
  58. package/src/ir.js +540 -171
  59. package/src/kind-traits.js +105 -0
  60. package/src/kind.js +462 -0
  61. package/src/op-policy.js +57 -0
  62. package/src/{optimize.js → optimize/index.js} +1106 -446
  63. package/src/optimize/vectorize.js +1874 -0
  64. package/src/param-reps.js +65 -0
  65. package/src/parse.js +44 -0
  66. package/src/{prepare.js → prepare/index.js} +589 -202
  67. package/src/reps.js +115 -0
  68. package/src/resolve.js +12 -3
  69. package/src/static.js +199 -0
  70. package/src/type.js +647 -0
  71. package/src/{assemble.js → wat/assemble.js} +86 -48
  72. package/src/wat/optimize.js +3760 -0
  73. package/transform.js +21 -0
  74. package/wasi.js +47 -5
  75. package/src/analyze.js +0 -3818
  76. package/src/emit.js +0 -3040
  77. package/src/jzify.js +0 -1580
  78. package/src/plan.js +0 -2132
  79. package/src/vectorize.js +0 -1088
  80. /package/src/{codegen.js → wat/codegen.js} +0 -0
package/src/reps.js ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * ValueRep storage + VAL lattice lookups (ctx-backed, cycle-free).
3
+ *
4
+ * Thin accessors shared by ir, emit, stdlib, and analyze. Keeps heavy AST
5
+ * walkers in analyze.js without pulling them into ir.js.
6
+ *
7
+ * ## Lookup priority (lookupValType / lookupNotString)
8
+ *
9
+ * A single binding can carry several pieces of type knowledge, set at different
10
+ * lifecycle phases. Accessors resolve them in this fixed order — first hit wins:
11
+ *
12
+ * 1. `ctx.func.refinements` flow-sensitive (typeof/instanceof guard)
13
+ * 2. `ctx.func.localValTypesOverlay` call-site / loop-iter overlay (transient)
14
+ * 3. `ctx.func.localReps` per-function plan/analyze fact (durable)
15
+ * 4. `ctx.scope.globalValTypes` module-level binding (durable)
16
+ *
17
+ * Writes go through `updateRep` (#3 mutator) / `updateGlobalRep` (#4 mutator).
18
+ * Refinements (#1) are managed by `withRefinements` in emit; overlay (#2) is
19
+ * scoped by call/loop-emit code and torn down when the scope exits.
20
+ *
21
+ * Mutation sites by phase:
22
+ * plan.js — initial reps from prepare-pass typing
23
+ * analyze.js — boxing decisions, schema bindings, sched facts
24
+ * compile/index.js — closure-arg upgrades, propagation across calls
25
+ * emit.js — withRefinements / overlay, transient narrowing only
26
+ *
27
+ * @module reps
28
+ */
29
+
30
+ import { ctx } from './ctx.js'
31
+
32
+ /** Value kinds — method dispatch, schema, carrier selection. */
33
+ export const VAL = {
34
+ NUMBER: 'number', ARRAY: 'array', STRING: 'string',
35
+ OBJECT: 'object', HASH: 'hash', SET: 'set', MAP: 'map',
36
+ CLOSURE: 'closure', TYPED: 'typed', REGEX: 'regex',
37
+ BIGINT: 'bigint', BUFFER: 'buffer', DATE: 'date',
38
+ BOOL: 'boolean',
39
+ }
40
+
41
+ /**
42
+ * A binding's inferred representation. Every field optional; absence = "unknown".
43
+ * Written only through updateRep / updateGlobalRep, plus a few direct r.wasm /
44
+ * r.typedCtor mutations in narrow.js's signature fixpoint. This is the *closed*
45
+ * shape: REP_FIELDS is the single source of truth — it gates updateRep in debug
46
+ * mode and drives repView, so a typo'd key surfaces loudly instead of silently
47
+ * vanishing into the open `{...prev, ...fields}` spread.
48
+ *
49
+ * @typedef {Object} ValueRep
50
+ * @property {string} [val] VAL.* kind (number/array/string/…).
51
+ * @property {number} [ptrKind] PTR.* pointer class for NaN-box rebox.
52
+ * @property {number} [ptrAux] aux bits in the NaN-box (schema id / elem type).
53
+ * @property {number} [schemaId] object-shape id (OBJECT kind).
54
+ * @property {number} [intConst] proven constant integer value.
55
+ * @property {boolean} [intCertain] integer-valued on every path.
56
+ * @property {boolean} [notString] proven not a string (skips string-path guards).
57
+ * @property {number} [arrayElemSchema] element object-schema id for arrays.
58
+ * @property {string} [arrayElemValType] element VAL.* kind for arrays.
59
+ * @property {string} [carrier] abi carrier id override (e.g. 'jsstring').
60
+ * @property {boolean} [unsigned] i32 carries an unsigned value (`>>>` result).
61
+ * @property {*} [jsonShape] inferred shape for the JSON.stringify fast path.
62
+ * @property {string} [typedCtor] TypedArray ctor name (TYPED kind); null = bimorphic.
63
+ * @property {string} [wasm] wasm storage type 'i32'|'f64' (narrow.js fixpoint).
64
+ * @property {boolean} [nullable] binding can hold null/undefined on some path
65
+ * (init or an assignment was a nullish literal) — suppresses the `=== null` /
66
+ * `=== undefined` constant-fold even when `val` is a definite non-null kind.
67
+ */
68
+ export const REP_FIELDS = new Set([
69
+ 'val', 'ptrKind', 'ptrAux', 'schemaId', 'intConst', 'intCertain', 'notString',
70
+ 'arrayElemSchema', 'arrayElemValType', 'carrier', 'unsigned', 'jsonShape',
71
+ 'typedCtor', 'wasm', 'nullable',
72
+ ])
73
+
74
+ const DBG_REPS = typeof process !== 'undefined' && process.env?.JZ_DEBUG_INVARIANTS === '1'
75
+ const assertRepFields = (name, fields) => {
76
+ for (const k in fields)
77
+ if (!REP_FIELDS.has(k))
78
+ throw new Error(`updateRep('${name}', {${k}}): unknown ValueRep field — typo, or add it to REP_FIELDS in reps.js`)
79
+ }
80
+
81
+ /** @returns {ValueRep|undefined} */
82
+ export const repOf = name => ctx.func.localReps?.get(name)
83
+
84
+ export const updateRep = (name, fields) => {
85
+ if (DBG_REPS) assertRepFields(name, fields)
86
+ const m = ctx.func.localReps ||= new Map()
87
+ const prev = m.get(name) || {}
88
+ const next = { ...prev, ...fields }
89
+ for (const k of Object.keys(next)) if (next[k] === undefined) delete next[k]
90
+ if (Object.keys(next).length === 0) m.delete(name)
91
+ else m.set(name, next)
92
+ }
93
+
94
+ export const repOfGlobal = name => ctx.scope.globalReps?.get(name)
95
+
96
+ export const updateGlobalRep = (name, fields) => {
97
+ if (DBG_REPS) assertRepFields(name, fields)
98
+ const m = ctx.scope.globalReps ||= new Map()
99
+ const prev = m.get(name)
100
+ m.set(name, prev ? { ...prev, ...fields } : { ...fields })
101
+ }
102
+
103
+ export const lookupValType = name => {
104
+ const r = ctx.func.refinements
105
+ if (r?.size) { const v = r.get(name)?.val; if (v) return v }
106
+ const ov = ctx.func.localValTypesOverlay
107
+ if (ov) { const v = ov.get(name); if (v) return v }
108
+ return ctx.func.localReps?.get(name)?.val || ctx.scope.globalValTypes?.get(name) || null
109
+ }
110
+
111
+ export const lookupNotString = name => {
112
+ const r = ctx.func.refinements
113
+ if (r?.size && r.get(name)?.notString) return true
114
+ return ctx.func.localReps?.get(name)?.notString === true
115
+ }
package/src/resolve.js CHANGED
@@ -14,9 +14,18 @@ import { readFileSync, existsSync } from 'fs'
14
14
  import { dirname, resolve, join } from 'path'
15
15
  import { execFileSync } from 'child_process'
16
16
 
17
- // Matches both `import` and `export ... from` statements (statement position).
18
- // Lazy `[^'"]*?` consumes whatever sits between the keyword and the first quote.
19
- const importRe = /^\s*(?:import|export)\s+[^'"]*?['"]([^'"]+)['"]/gm
17
+ // Matches real module imports/exports at statement position and ONLY those:
18
+ // import 'x' (bare side-effect import)
19
+ // import from 'x' (default/named/namespace)
20
+ // export … from 'x' / export * from (re-export)
21
+ // The specifier MUST follow `from` (or be a bare `import`'s string). The old
22
+ // `(?:import|export)\s+[^'"]*?['"]…` matched any post-keyword string because
23
+ // `[^'"]` spans newlines, so `export const X = [⏎ 'lit'` was read as `export …
24
+ // 'lit'` — bundling then rewrote the bare string literal `'lit'` to a module path.
25
+ // (Self-host fallout: `PASS_NAMES = ['watr', …]` had `'watr'` rewritten to
26
+ // `…/node_modules/watr/watr.js`, corrupting every `'watr'` constant — and so the
27
+ // kernel's whole optimize config, since `cfg.watr` then read that path.)
28
+ const importRe = /^\s*(?:import\b[^'"]*?\bfrom\s*|import\s+|export\b[^'"]*?\bfrom\s*)['"]([^'"]+)['"]/gm
20
29
 
21
30
  /**
22
31
  * @param {string} entryFile - absolute or cwd-relative path to the entry module.
package/src/static.js ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Compile-time static evaluation — literals, property keys, schema ids.
3
+ * @module static
4
+ */
5
+ import { I32_MIN, I32_MAX } from './ast.js'
6
+ import { ctx } from './ctx.js'
7
+ import { repOf, VAL } from './reps.js'
8
+
9
+ /** Extract integer value from AST literal node. Returns null if not a 32-bit integer. */
10
+ export function intLiteralValue(expr) {
11
+ let v = null
12
+ if (typeof expr === 'number') v = expr
13
+ else if (Array.isArray(expr) && expr[0] == null && typeof expr[1] === 'number') v = expr[1]
14
+ else if (Array.isArray(expr) && expr[0] === 'u-' && typeof expr[1] === 'number') v = -expr[1]
15
+ else if (typeof expr === 'string') v = repOf(expr)?.intConst ?? ctx.scope.constInts?.get(expr) ?? null
16
+ return v != null && Number.isInteger(v) && v >= I32_MIN && v <= I32_MAX ? v : null
17
+ }
18
+
19
+ /** Non-negative integer literal — used for string/typed-array index bounds. */
20
+ export const nonNegIntLiteral = (node) => { const n = intLiteralValue(node); return n != null && n >= 0 ? n : null }
21
+
22
+ /** Fold compile-time integer expressions (literals, const bindings, + - * <<). */
23
+ export function constIntExpr(node) {
24
+ let lit = intLiteralValue(node)
25
+ if (lit == null && typeof node === 'number' && Number.isInteger(node)) lit = node
26
+ if (lit == null && Array.isArray(node) && node[0] == null && Number.isInteger(node[1])) lit = node[1]
27
+ if (lit != null) return lit
28
+ if (typeof node === 'string') return repOf(node)?.intConst ?? ctx.scope.constInts?.get(node) ?? null
29
+ if (!Array.isArray(node)) return null
30
+ const op = node[0]
31
+ if (op === 'u-') {
32
+ const v = constIntExpr(node[1])
33
+ return v == null ? null : -v
34
+ }
35
+ if (node.length !== 3) return null
36
+ const a = constIntExpr(node[1]), b = constIntExpr(node[2])
37
+ if (a == null || b == null) return null
38
+ if (op === '+') return a + b
39
+ if (op === '-') return a - b
40
+ if (op === '*') return a * b
41
+ if (op === '<<') return a << b
42
+ return null
43
+ }
44
+
45
+
46
+ export const NO_VALUE = Symbol('no-static-property-key')
47
+
48
+ export function staticPropertyKey(node) {
49
+ const value = staticValue(node)
50
+ return value === NO_VALUE ? null : String(value)
51
+ }
52
+
53
+ export function staticValue(node) {
54
+ if (node === undefined) return undefined
55
+ if (node === null || typeof node === 'number' || typeof node === 'boolean') return node
56
+ if (typeof node === 'string') return ctx.scope.constStrs?.get(node) ?? NO_VALUE
57
+ if (!Array.isArray(node)) return NO_VALUE
58
+
59
+ const [op, ...args] = node
60
+ if (op == null) return args.length ? args[0] : undefined
61
+ if (op === 'str') return args[0]
62
+ // Self-host kernel boundary marks a literal bool as `['bool', 1|0]` (interop
63
+ // normalizeBigints), where native keeps `[, true]` (caught by op==null above).
64
+ // Recover the boolean from its 0/1 carrier so const-folded keys/conditions
65
+ // resolve on the kernel leg (e.g. `{ [true ? 3 : 4]: 5 }`).
66
+ if (op === 'bool') { const c = staticValue(args[0]); return c === NO_VALUE ? NO_VALUE : !!c }
67
+ if (op === '[]' && args.length === 1) return staticValue(args[0])
68
+ if (op === '()' && args[0] === 'String' && args.length === 2) {
69
+ const value = staticValue(args[1])
70
+ return value === NO_VALUE ? NO_VALUE : String(value)
71
+ }
72
+ if (op === '()' && args[0] === 'Number' && args.length === 2) {
73
+ const value = staticValue(args[1])
74
+ return value === NO_VALUE ? NO_VALUE : Number(value)
75
+ }
76
+ if (op === '?:' || op === '?') {
77
+ const cond = staticValue(args[0])
78
+ return cond === NO_VALUE ? NO_VALUE : staticValue(cond ? args[1] : args[2])
79
+ }
80
+ if (op === '&&' || op === '||') {
81
+ const left = staticValue(args[0])
82
+ if (left === NO_VALUE) return NO_VALUE
83
+ return op === '&&' ? (left ? staticValue(args[1]) : left) : (left ? left : staticValue(args[1]))
84
+ }
85
+ if (op === '??') {
86
+ const left = staticValue(args[0])
87
+ return left === NO_VALUE ? NO_VALUE : left == null ? staticValue(args[1]) : left
88
+ }
89
+
90
+ if (args.length === 1) {
91
+ const value = staticValue(args[0])
92
+ if (value === NO_VALUE) return NO_VALUE
93
+ // Parser emits raw `-`/`+` for both unary and binary; prep later normalizes
94
+ // unary to `u-`/`u+`, but staticPropertyKey runs on raw parser AST.
95
+ if (op === 'u+' || op === '+') return +value
96
+ if (op === 'u-' || op === '-') return -value
97
+ if (op === '!') return !value
98
+ if (op === '~') return ~value
99
+ return NO_VALUE
100
+ }
101
+
102
+ if (args.length === 2) {
103
+ const left = staticValue(args[0])
104
+ const right = staticValue(args[1])
105
+ if (left === NO_VALUE || right === NO_VALUE) return NO_VALUE
106
+ switch (op) {
107
+ case '+': return typeof left === 'string' || typeof right === 'string' ? String(left) + String(right) : Number(left) + Number(right)
108
+ case '-': return Number(left) - Number(right)
109
+ case '*': return Number(left) * Number(right)
110
+ case '/': return Number(left) / Number(right)
111
+ case '%': return Number(left) % Number(right)
112
+ case '**': return Number(left) ** Number(right)
113
+ case '&': return Number(left) & Number(right)
114
+ case '|': return Number(left) | Number(right)
115
+ case '^': return Number(left) ^ Number(right)
116
+ case '<<': return Number(left) << Number(right)
117
+ case '>>': return Number(left) >> Number(right)
118
+ case '>>>': return Number(left) >>> Number(right)
119
+ default: return NO_VALUE
120
+ }
121
+ }
122
+
123
+ return NO_VALUE
124
+ }
125
+
126
+ /** Decode a `['{}', ...]` AST's children into `{names, values}`, or null if any
127
+ * property is non-static-key (computed key, spread, shorthand). Matches the
128
+ * emitter's flatten rule for comma-grouped props. Used by collectProgramFacts,
129
+ * narrowSignatures, and objLiteralSchemaId; the emitter (module/object.js)
130
+ * does its own decoding because it must handle the spread/computed-key paths. */
131
+ export function staticObjectProps(args) {
132
+ const raw = args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
133
+ const names = [], values = []
134
+ for (const p of raw) {
135
+ if (!Array.isArray(p) || p[0] !== ':' || typeof p[1] !== 'string') return null
136
+ names.push(p[1]); values.push(p[2])
137
+ }
138
+ return names.length ? { names, values } : null
139
+ }
140
+
141
+ export function staticArrayElems(expr) {
142
+ if (!Array.isArray(expr)) return null
143
+ if (expr[0] === '[') return expr.slice(1)
144
+ if (expr[0] !== '[]' || expr.length >= 3) return null
145
+ const arg = expr[1]
146
+ if (arg == null) return []
147
+ return Array.isArray(arg) && arg[0] === ',' ? arg.slice(1) : [arg]
148
+ }
149
+
150
+ /** Schema-id for an object literal expression. Returns null on dynamic keys, spread, shorthand. */
151
+ export function objLiteralSchemaId(expr) {
152
+ if (!Array.isArray(expr) || expr[0] !== '{}' || !ctx.schema?.register) return null
153
+ const parsed = staticObjectProps(expr.slice(1))
154
+ return parsed ? ctx.schema.register(parsed.names) : null
155
+ }
156
+
157
+ /** Resolve schemaId of an expression, given a per-function schemaId map for locals.
158
+ * Used for both intra-function arr elem-schema observation and func.arrayElemSchema
159
+ * return inference. Recognizes: object literals, var names with bound schemaId,
160
+ * user fn calls with narrowed result schema, ?: / && / || when both branches agree. */
161
+ export function exprSchemaId(expr, localSchemaMap) {
162
+ if (typeof expr === 'string') {
163
+ if (localSchemaMap?.has(expr)) return localSchemaMap.get(expr)
164
+ return ctx.schema?.idOf?.(expr) ?? null
165
+ }
166
+ if (!Array.isArray(expr)) return null
167
+ const op = expr[0]
168
+ if (op === '{}') return objLiteralSchemaId(expr)
169
+ if (op === '()' && typeof expr[1] === 'string') {
170
+ const f = ctx.func.map?.get(expr[1])
171
+ if (f?.valResult === VAL.OBJECT && f.sig?.ptrAux != null) return f.sig.ptrAux
172
+ return null
173
+ }
174
+ if (op === '?:') {
175
+ const a = exprSchemaId(expr[2], localSchemaMap)
176
+ const b = exprSchemaId(expr[3], localSchemaMap)
177
+ return a != null && a === b ? a : null
178
+ }
179
+ if (op === '&&' || op === '||') {
180
+ const a = exprSchemaId(expr[1], localSchemaMap)
181
+ const b = exprSchemaId(expr[2], localSchemaMap)
182
+ return a != null && a === b ? a : null
183
+ }
184
+ return null
185
+ }
186
+
187
+ export function inlineArraySid(name) {
188
+ if (typeof name !== 'string') return null
189
+ // structInline is keyed on the per-function `localReps` rep, so it is only
190
+ // consistent for a *function-local* array — a write site and a read site in the
191
+ // same frame agree. A module-global array is read across functions whose frames
192
+ // carry no rep for it, so the carrier would diverge: `G.push({a,b})` in one
193
+ // function flattens the struct into K cells, while `G.length` / `G[i].a` in
194
+ // another sees a plain array (K=1) and reads garbage. Never inline a global's
195
+ // element struct — the plain Array<ptr> representation is consistent everywhere.
196
+ if (ctx.scope.globals?.has(name)) return null
197
+ const sid = ctx.func.localReps?.get(name)?.arrayElemSchema
198
+ return sid != null && ctx.schema.inlineArray?.has(sid) ? sid : null
199
+ }