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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared AST utilities for plan/ subsystems.
|
|
3
|
+
*
|
|
4
|
+
* Helpers used by multiple subfiles (scalarize/loops/inline). Single-use helpers
|
|
5
|
+
* stay with their consumer.
|
|
6
|
+
*
|
|
7
|
+
* @module compile/plan/common
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ctx } from '../../ctx.js'
|
|
11
|
+
import { ASSIGN_OPS, some, callArgs } from '../../ast.js'
|
|
12
|
+
import { constIntExpr } from '../../static.js'
|
|
13
|
+
import { typedElemCtor } from '../../type.js'
|
|
14
|
+
import { PASS_NAMES } from '../../optimize/index.js'
|
|
15
|
+
|
|
16
|
+
/** True iff the optimizer is active (any non-falsy pass under `ctx.transform.optimize`). */
|
|
17
|
+
export const optimizing = () => { const c = ctx.transform.optimize; return !!c && PASS_NAMES.some(n => c[n]) }
|
|
18
|
+
|
|
19
|
+
/** Ops whose body opens a new loop scope. (`for-in`/`for-of` excluded — they
|
|
20
|
+
* bind a fresh per-iter local on each entry, so jz lowers them differently.) */
|
|
21
|
+
export const LOOP_OPS = new Set(['for', 'while', 'do', 'do-while'])
|
|
22
|
+
|
|
23
|
+
/** Inline-substitution argument check — pure, side-effect-free, captures nothing. */
|
|
24
|
+
export const isSimpleArg = node => {
|
|
25
|
+
if (typeof node === 'string' || typeof node === 'number') return true
|
|
26
|
+
if (!Array.isArray(node)) return false
|
|
27
|
+
if (node[0] == null) return typeof node[1] === 'number'
|
|
28
|
+
if (node[0] === 'str') return typeof node[1] === 'string'
|
|
29
|
+
if (node[0] === 'u-' || (node[0] === '-' && node.length === 2)) return isSimpleArg(node[1])
|
|
30
|
+
if (['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '>>>'].includes(node[0]))
|
|
31
|
+
return isSimpleArg(node[1]) && isSimpleArg(node[2])
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Maximum loop nesting under `node`. Closures (`=>`) opaque — their loops don't count. */
|
|
36
|
+
export const loopDepth = (node, depth) => {
|
|
37
|
+
if (!Array.isArray(node)) return depth
|
|
38
|
+
if (node[0] === '=>') return depth
|
|
39
|
+
const here = LOOP_OPS.has(node[0]) ? depth + 1 : depth
|
|
40
|
+
let max = here
|
|
41
|
+
for (let i = 1; i < node.length; i++) {
|
|
42
|
+
const d = loopDepth(node[i], here)
|
|
43
|
+
if (d > max) max = d
|
|
44
|
+
}
|
|
45
|
+
return max
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Node-count weight — drives inline cost heuristics. */
|
|
49
|
+
export const nodeSize = (node) => {
|
|
50
|
+
if (!Array.isArray(node)) return 1
|
|
51
|
+
let n = 1
|
|
52
|
+
for (let i = 1; i < node.length; i++) n += nodeSize(node[i])
|
|
53
|
+
return n
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Collect every binder name in `node` into `out` (lexical-scope set; doesn't
|
|
57
|
+
* descend into nested arrow bodies — those open a new scope). */
|
|
58
|
+
export const collectBindings = (node, out) => {
|
|
59
|
+
if (!Array.isArray(node)) return
|
|
60
|
+
const op = node[0]
|
|
61
|
+
if (op === '=>') return
|
|
62
|
+
if (op === 'let' || op === 'const') {
|
|
63
|
+
for (let i = 1; i < node.length; i++) collectBindingTarget(node[i], out)
|
|
64
|
+
}
|
|
65
|
+
for (let i = 1; i < node.length; i++) collectBindings(node[i], out)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const collectBindingTarget = (node, out) => {
|
|
69
|
+
if (typeof node === 'string') { out.add(node); return }
|
|
70
|
+
if (!Array.isArray(node)) return
|
|
71
|
+
if (node[0] === '=') collectBindingTarget(node[1], out)
|
|
72
|
+
else if (node[0] === '...' && typeof node[1] === 'string') out.add(node[1])
|
|
73
|
+
else if (node[0] === ',' || node[0] === '[]' || node[0] === '{}')
|
|
74
|
+
for (let i = 1; i < node.length; i++) collectBindingTarget(node[i], out)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** True iff `node` writes to any name in `names` (incl. `++`/`--` and compound assigns). */
|
|
78
|
+
export const mutatesAny = (node, names) => some(node, n => {
|
|
79
|
+
const op = n[0]
|
|
80
|
+
if ((op === '++' || op === '--') && typeof n[1] === 'string') return names.has(n[1])
|
|
81
|
+
return ASSIGN_OPS.has(op) && typeof n[1] === 'string' && names.has(n[1])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
/** Deep-clone array-tree AST. Plain values pass through by identity. */
|
|
85
|
+
export const clonePlain = node => Array.isArray(node) ? node.map(clonePlain) : node
|
|
86
|
+
|
|
87
|
+
/** AST index of a `for` loop's body, or null if `stmt` isn't a `for`.
|
|
88
|
+
* Handles both `['for', cond, body]` (3-arg while-like) and the full
|
|
89
|
+
* `['for', init, cond, step, body]` C-style form. */
|
|
90
|
+
export const forLoopBodyIndex = (stmt) =>
|
|
91
|
+
Array.isArray(stmt) && stmt[0] === 'for' ? (stmt.length === 3 ? 2 : 4) : null
|
|
92
|
+
|
|
93
|
+
/** Reconstruct a `for` node with its body replaced. Preserves arity. */
|
|
94
|
+
export const withForLoopBody = (stmt, body) =>
|
|
95
|
+
stmt.length === 3 ? ['for', stmt[1], body] : ['for', stmt[1], stmt[2], stmt[3], body]
|
|
96
|
+
|
|
97
|
+
// === Fixed-size typed-array recognition ===
|
|
98
|
+
// Shared by scalarize (replaces the literal with N locals) and inline (filters
|
|
99
|
+
// inlinings that would trample a typed-array param). Tables/thresholds live here
|
|
100
|
+
// so both subsystems agree on what "scalarizable" means.
|
|
101
|
+
|
|
102
|
+
/** Fixed-size typed-array ctors eligible for scalar replacement, mapped to the
|
|
103
|
+
* element store-coercion kind ('' = none, i.e. Float64Array's f64-identity).
|
|
104
|
+
* Excluded: Float32Array (Math.fround pulls module), Uint32Array (range > i32),
|
|
105
|
+
* Uint8ClampedArray (round-half-even clamp). Coerced (truthy) types are only
|
|
106
|
+
* scalarized when fully local. */
|
|
107
|
+
export const SCALAR_TYPED_COERCE = {
|
|
108
|
+
'new.Float64Array': '',
|
|
109
|
+
'new.Int32Array': 'i32',
|
|
110
|
+
'new.Int16Array': 'i16', 'new.Uint16Array': 'u16',
|
|
111
|
+
'new.Int8Array': 'i8', 'new.Uint8Array': 'u8',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Default 64 covers the 8×8 block kernels (DCT/JPEG-shaped). Measured at 64
|
|
115
|
+
// elements: scalarized form is ~2.2× SMALLER (stores fold away; local refs
|
|
116
|
+
// out-LEB the memory ops they replace) and 2.5× faster than the memory form —
|
|
117
|
+
// there is no LEB128 cliff in practice.
|
|
118
|
+
export const maxScalarTypedArrayLen = () => ctx.transform.optimize?.scalarTypedArrayLen ?? 64
|
|
119
|
+
|
|
120
|
+
/** Recognize `new TypedArrayCtor(N)` with `N` a static small integer.
|
|
121
|
+
* Returns `{len, coerce}` or null. */
|
|
122
|
+
export const fixedScalarTypedArray = (expr) => {
|
|
123
|
+
const ctor = typedElemCtor(expr)
|
|
124
|
+
if (ctor == null || !(ctor in SCALAR_TYPED_COERCE)) return null
|
|
125
|
+
const args = callArgs(expr)
|
|
126
|
+
if (!args || args.length !== 1) return null
|
|
127
|
+
const len = constIntExpr(args[0])
|
|
128
|
+
return len != null && len >= 0 && len <= maxScalarTypedArrayLen()
|
|
129
|
+
? { len, coerce: SCALAR_TYPED_COERCE[ctor] } : null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Map of name → {len, coerce} for every fixed-size typed-array binding declared
|
|
133
|
+
* via `let`/`const` directly in `body` (no descent into nested arrows). */
|
|
134
|
+
export const fixedTypedArraysInBody = (body) => {
|
|
135
|
+
const out = new Map()
|
|
136
|
+
const walk = node => {
|
|
137
|
+
if (!Array.isArray(node) || node[0] === '=>') return
|
|
138
|
+
if (node[0] === 'let' || node[0] === 'const') {
|
|
139
|
+
for (let i = 1; i < node.length; i++) {
|
|
140
|
+
const d = node[i]
|
|
141
|
+
if (!Array.isArray(d) || d[0] !== '=' || typeof d[1] !== 'string') continue
|
|
142
|
+
const fixed = fixedScalarTypedArray(d[2])
|
|
143
|
+
if (fixed != null) out.set(d[1], fixed)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (let i = 1; i < node.length; i++) walk(node[i])
|
|
147
|
+
}
|
|
148
|
+
walk(body)
|
|
149
|
+
return out
|
|
150
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-emit compile planning: bridges prepare (AST shape) and emit (wasm bytes).
|
|
3
|
+
*
|
|
4
|
+
* # Stage contract
|
|
5
|
+
* IN: populated `ctx` from prepare.js (functions, schemas, scopes, modules)
|
|
6
|
+
* plus the prepared AST.
|
|
7
|
+
* OUT: returns a `programFacts` object; mutates `ctx` so each function has
|
|
8
|
+
* narrowed signatures, finalized global reps, and per-call decisions.
|
|
9
|
+
*
|
|
10
|
+
* # Pipeline (top-level `plan(ast)`)
|
|
11
|
+
* 1. unboxConstTypedGlobals — finalize global storage. (Global value facts
|
|
12
|
+
* themselves are seeded by prepare via `infer.recordGlobalRep`.)
|
|
13
|
+
* 2. collectProgramFacts — sweep arrow bodies for typed-elem usage, key sets,
|
|
14
|
+
* loop depth, control-transfer shapes; rerun if hot inlining changes the AST.
|
|
15
|
+
* 3. materializeAutoBoxSchemas / resolveClosureWidth — settle layout decisions.
|
|
16
|
+
* 4. Whole-program narrowing (skipped on simple programs):
|
|
17
|
+
* - narrowSignatures — pick a specialization per function from call sites
|
|
18
|
+
* - specializeBimorphicTyped — split typed-elem hot paths into two variants
|
|
19
|
+
* when callers diverge between two ctors
|
|
20
|
+
* - refineDynKeys — tighten dynamic property-key sets
|
|
21
|
+
*
|
|
22
|
+
* No bytes are emitted here; emit.js consumes the planned ctx + programFacts.
|
|
23
|
+
*
|
|
24
|
+
* @module plan
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { ctx } from '../../ctx.js'
|
|
28
|
+
import { collectProgramFacts, refreshProgramFacts } from '../program-facts.js'
|
|
29
|
+
import narrowSignatures, {
|
|
30
|
+
specializeBimorphicTyped, refineDynKeys,
|
|
31
|
+
applyJsstringBoundaryCarrierStandalone, narrowBoolResults,
|
|
32
|
+
strictBoundaryTypeCheck,
|
|
33
|
+
} from '../narrow.js'
|
|
34
|
+
|
|
35
|
+
import { optimizing } from './common.js'
|
|
36
|
+
import { adviseProgram } from './advise.js'
|
|
37
|
+
import {
|
|
38
|
+
inferModuleLetTypes, unboxConstTypedGlobals, inferModuleIntGlobals,
|
|
39
|
+
flattenFuncNamespaces, devirtGlobalCalls,
|
|
40
|
+
materializeAutoBoxSchemas, resolveClosureWidth, canSkipWholeProgramNarrowing,
|
|
41
|
+
} from './scope.js'
|
|
42
|
+
import { inlineHotInternalCalls, inlineLocalLambdas, specializeFixedRestCalls } from './inline.js'
|
|
43
|
+
import { bindNestedRowLengths, unrollRowLenPadLoops, splitCharScanLoops } from './loops.js'
|
|
44
|
+
import {
|
|
45
|
+
scalarizeFunctionTypedArrays, scalarizeFunctionArrayLiterals,
|
|
46
|
+
promoteIntArrayLiterals, scalarizeFunctionObjectLiterals,
|
|
47
|
+
} from './literals.js'
|
|
48
|
+
|
|
49
|
+
export default function plan(ast, profiler) {
|
|
50
|
+
// Per-pass timing under `plan:` — the plan stage is the compile pipeline's
|
|
51
|
+
// multi-pass hot spot (each mutating pass triggers a whole-program fact
|
|
52
|
+
// refresh), so the profile must show WHICH pass and refresh dominate.
|
|
53
|
+
const t = profiler?.time ? (name, fn) => profiler.time(`plan:${name}`, fn) : (_, fn) => fn()
|
|
54
|
+
// AST-mutating pass: run timed; on change, re-sweep program facts (timed
|
|
55
|
+
// separately — the refreshes are usually the cost, not the passes).
|
|
56
|
+
let programFacts
|
|
57
|
+
const sweep = (name, pass) => {
|
|
58
|
+
if (t(name, pass)) programFacts = t('refreshFacts', () => refreshProgramFacts(ast, programFacts))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
t('inferModuleLetTypes', () => inferModuleLetTypes(ast))
|
|
62
|
+
t('unboxConstTypedGlobals', unboxConstTypedGlobals)
|
|
63
|
+
t('inferModuleIntGlobals', () => inferModuleIntGlobals(ast))
|
|
64
|
+
|
|
65
|
+
programFacts = t('collectFacts', () => collectProgramFacts(ast))
|
|
66
|
+
// Function-namespace SROA — dissolve reassigned `f.prop` slots into module
|
|
67
|
+
// globals before inlining/narrowing, so all downstream passes see plain
|
|
68
|
+
// globals instead of the dynamic property machinery.
|
|
69
|
+
sweep('flattenFuncNamespaces', () => flattenFuncNamespaces(ast))
|
|
70
|
+
// Devirtualize calls through init-constant function globals (closure
|
|
71
|
+
// devirtualization) — must follow the SROA above, which creates the globals.
|
|
72
|
+
t('devirtGlobalCalls', () => devirtGlobalCalls(ast))
|
|
73
|
+
sweep('bindNestedRowLengths', bindNestedRowLengths)
|
|
74
|
+
sweep('unrollRowLenPadLoops', unrollRowLenPadLoops)
|
|
75
|
+
// The call-inlining family (`inlineHotInternalCalls` self-gates on `sourceInline`)
|
|
76
|
+
// is a pure speed optimization — the un-inlined calls emit correctly. Scalar
|
|
77
|
+
// replacement (`scalarize*`) and array promotion gate on `optimizing()`: off only
|
|
78
|
+
// under a fully-disabled optimizer, on for every enabled preset (incl. the
|
|
79
|
+
// `optimize:{sourceInline:false}` heap-elision-test form, which is level-2 based).
|
|
80
|
+
sweep('inlineHotInternalCalls', () => inlineHotInternalCalls(programFacts, ast))
|
|
81
|
+
sweep('bindNestedRowLengths', bindNestedRowLengths)
|
|
82
|
+
sweep('unrollRowLenPadLoops', unrollRowLenPadLoops)
|
|
83
|
+
sweep('inlineLocalLambdas', inlineLocalLambdas)
|
|
84
|
+
sweep('specializeFixedRestCalls', () => specializeFixedRestCalls(programFacts))
|
|
85
|
+
if (optimizing()) {
|
|
86
|
+
sweep('splitCharScan', splitCharScanLoops)
|
|
87
|
+
sweep('scalarizeArrayLiterals', scalarizeFunctionArrayLiterals)
|
|
88
|
+
sweep('scalarizeObjectLiterals', scalarizeFunctionObjectLiterals)
|
|
89
|
+
// Promotion runs AFTER literal scalarization (those that fully reduce to scalars
|
|
90
|
+
// are gone) and BEFORE typed-array scalarization (so a freshly-promoted array's
|
|
91
|
+
// fixed-length-typed-of-known-size variant could still participate in loop
|
|
92
|
+
// unrolling — currently it can't, since promotion produces the `[...]`-arg
|
|
93
|
+
// form rather than `new Int32Array(N)`, but the ordering keeps the door open).
|
|
94
|
+
sweep('promoteIntArrayLiterals', promoteIntArrayLiterals)
|
|
95
|
+
sweep('scalarizeTypedArrays', () => scalarizeFunctionTypedArrays(programFacts))
|
|
96
|
+
}
|
|
97
|
+
ctx.types.dynKeyVars = programFacts.dynVars
|
|
98
|
+
ctx.types.dynWriteVars = programFacts.dynWriteVars
|
|
99
|
+
ctx.types.anyDynKey = programFacts.anyDyn
|
|
100
|
+
|
|
101
|
+
t('materializeAutoBoxSchemas', () => materializeAutoBoxSchemas(programFacts))
|
|
102
|
+
t('resolveClosureWidth', () => resolveClosureWidth(programFacts))
|
|
103
|
+
if (canSkipWholeProgramNarrowing(programFacts)) {
|
|
104
|
+
// Phase J (jsstring boundary opt-in) is body-local and call-site-independent;
|
|
105
|
+
// run it even when the rest of narrowing is skipped so simple `export let
|
|
106
|
+
// f = (s) => s.length` still flips to externref. Likewise the boolean-result
|
|
107
|
+
// fact, so `export let f = (a) => a > 2` boxes its boundary atom.
|
|
108
|
+
applyJsstringBoundaryCarrierStandalone(programFacts)
|
|
109
|
+
narrowBoolResults()
|
|
110
|
+
strictBoundaryTypeCheck(programFacts)
|
|
111
|
+
adviseProgram(programFacts)
|
|
112
|
+
return programFacts
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
t('narrowSignatures', () => narrowSignatures(programFacts, ast))
|
|
116
|
+
t('specializeBimorphicTyped', () => specializeBimorphicTyped(programFacts))
|
|
117
|
+
t('refineDynKeys', () => refineDynKeys(programFacts))
|
|
118
|
+
strictBoundaryTypeCheck(programFacts)
|
|
119
|
+
|
|
120
|
+
adviseProgram(programFacts)
|
|
121
|
+
return programFacts
|
|
122
|
+
}
|