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/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* source (string)
|
|
7
7
|
* ↓ parse (subscript/jessie) — lexing + expression-oriented AST
|
|
8
8
|
* raw AST: nested arrays `[op, ...args]`, no ctx mutation
|
|
9
|
-
* ↓ jzify (
|
|
9
|
+
* ↓ jzify (default-on; skipped under opts.strict) — lower full-JS subset (var/function/class/switch) to jz-native
|
|
10
10
|
* desugared AST: arrow functions + let/const/if only
|
|
11
11
|
* ↓ prepare — validate (reject disallowed ops), normalize (++/--→+=/-=, scope rename),
|
|
12
12
|
* extract (functions→ctx.func.list with sig), resolve (imports→ctx.module.imports),
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
* prepared AST: normalized, with `ctx.func.list` / `ctx.module.imports` / `ctx.schema.list`
|
|
15
15
|
* populated. Arrow bodies carry no type info yet.
|
|
16
16
|
* ↓ compile — drives per-function emit, interleaves analysis (locals/valTypes/captures/
|
|
17
|
-
* narrowing fixpoint) with IR generation via the emitter table (src/emit.js).
|
|
17
|
+
* narrowing fixpoint) with IR generation via the emitter table (src/compile/emit.js).
|
|
18
18
|
* Writes: `ctx.func.valTypes`/`.locals`, `ctx.types.*`, `ctx.runtime.*`, `ctx.core.includes`.
|
|
19
|
-
* Also calls optimizeFunc (src/optimize.js): `hoistPtrType` + fused peephole/inline/memarg walk.
|
|
19
|
+
* Also calls optimizeFunc (src/optimize/index.js): `hoistPtrType` + fused peephole/inline/memarg walk.
|
|
20
20
|
* WAT IR: watr S-expression `['module', ...sections]`, every instruction node carries `.type`.
|
|
21
|
-
* ↓
|
|
22
|
-
* ↓ optimizeFunc 2nd pass — re-folds rebox/unbox roundtrips that
|
|
21
|
+
* ↓ watOptimize (opt-out via opts.optimize=false) — CSE, DCE, const folding at WAT level
|
|
22
|
+
* ↓ optimizeFunc 2nd pass — re-folds rebox/unbox roundtrips that watOptimize's inliner
|
|
23
23
|
* re-introduces at inline boundaries (caller's boxPtrIR meets callee's
|
|
24
24
|
* i32.wrap_i64(i64.reinterpret_f64 __env)). watr's peephole doesn't cover this.
|
|
25
25
|
* ↓ watrPrint (opts.wat=true) → WAT text, or watrCompile → Uint8Array binary
|
|
@@ -40,14 +40,19 @@
|
|
|
40
40
|
* @module jz
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import { parse } from '
|
|
44
|
-
import
|
|
45
|
-
import
|
|
46
|
-
import
|
|
47
|
-
import
|
|
48
|
-
import {
|
|
49
|
-
import
|
|
50
|
-
import
|
|
43
|
+
import { parse } from './src/parse.js'
|
|
44
|
+
import watrCompile from "watr/compile";
|
|
45
|
+
import watrPrint from "watr/print";
|
|
46
|
+
import watOptimize from "./src/wat/optimize.js";
|
|
47
|
+
import { ctx, reset, err, initWarnings, assertCtxInvariants } from './src/ctx.js'
|
|
48
|
+
import prepare, { GLOBALS } from './src/prepare/index.js'
|
|
49
|
+
import compile from './src/compile/index.js'
|
|
50
|
+
import { resetProgramFactsCache } from './src/compile/program-facts.js'
|
|
51
|
+
import { emit, emitter, emitVoid as flat, emitBlockBody as body, emitBoolStr as bool, emitIndex as idx, buildArrayWithSpreads as spread } from './src/compile/emit.js'
|
|
52
|
+
import { optimizeFunc, collectVolatileGlobals, collectReachableGlobalWrites, hoistGlobalPtrOffset, stablePtrGlobalNames, resolveOptimize } from './src/optimize/index.js'
|
|
53
|
+
import { VAL } from './src/reps.js'
|
|
54
|
+
import jzify from './jzify/index.js'
|
|
55
|
+
import { T } from './src/ast.js'
|
|
51
56
|
import {
|
|
52
57
|
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
53
58
|
} from './interop.js'
|
|
@@ -58,6 +63,11 @@ const importsMayReturnExternal = (imports) =>
|
|
|
58
63
|
!!imports && Object.values(imports).some(mod =>
|
|
59
64
|
Object.values(mod || {}).some(spec => typeof spec === 'function'))
|
|
60
65
|
|
|
66
|
+
// WHATWG URL resolution for compile-time import.meta lowering. Injected into
|
|
67
|
+
// ctx.transform (like parse/jzify) so prepare never references the `URL` global
|
|
68
|
+
// directly — keeps the self-host kernel free of host-only built-ins.
|
|
69
|
+
const resolveUrl = (spec, base) => new URL(spec, base).href
|
|
70
|
+
|
|
61
71
|
const nowMs = () => globalThis.performance?.now ? globalThis.performance.now() : Date.now()
|
|
62
72
|
|
|
63
73
|
const compileProfiler = (profile) => {
|
|
@@ -154,9 +164,10 @@ jz.memory = enhanceMemory
|
|
|
154
164
|
* @param {string} code - jz source
|
|
155
165
|
* @param {object} [opts]
|
|
156
166
|
* @param {boolean} [opts.wat] - Return WAT text instead of binary
|
|
157
|
-
* @param {boolean} [opts.strict] -
|
|
158
|
-
*
|
|
159
|
-
*
|
|
167
|
+
* @param {boolean} [opts.strict] - Enforce the pure canonical subset: skip jzify
|
|
168
|
+
* (so full-JS syntax like var/function/class is rejected, not lowered) and reject
|
|
169
|
+
* dynamic features (obj[k], for-in, unknown receiver method calls) at compile time.
|
|
170
|
+
* Avoids pulling dynamic-dispatch stdlib into output; large size win for static programs.
|
|
160
171
|
* @param {WebAssembly.Memory|number} [opts.memory] - Shared memory import, or
|
|
161
172
|
* initial page count. Prefer `memory: N` for owned memory.
|
|
162
173
|
* @param {boolean} [opts.alloc=true] - Export raw allocator helpers
|
|
@@ -172,13 +183,28 @@ jz.memory = enhanceMemory
|
|
|
172
183
|
* mutable globals); trades size for speed.
|
|
173
184
|
* - `{ level?: 0|1|2|3, watr?: bool, hoistAddrBase?: bool, ... }`: per-pass
|
|
174
185
|
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
186
|
+
* @param {object} [opts.warnings] - Optional mutable warning sink populated with
|
|
187
|
+
* `entries: [{ code, message, fn?, line?, column? }]`. Heap-growth advisories
|
|
188
|
+
* fire when a module uses the bump allocator and an export or loop retains
|
|
189
|
+
* allocations without a host-side memory.reset().
|
|
175
190
|
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
176
191
|
* `entries` and `totals` for parse / jzify / prepare / compile / plan / watr phases.
|
|
177
192
|
* Set `profile.names = true` to also emit a standard wasm `name` custom section
|
|
178
193
|
* for profiler/debugger symbolication.
|
|
179
|
-
* @param {
|
|
194
|
+
* @param {Object<string,string>} [opts.modules] - Map of module specifier → source
|
|
195
|
+
* for compile-time `import`/`export` bundling: jz resolves the module graph
|
|
196
|
+
* in-process from this map instead of reading from disk.
|
|
197
|
+
* @param {boolean} [opts.noTailCall] - Disable proper-tail-call emission (self/mutual
|
|
198
|
+
* recursion uses ordinary call frames). For engines/tools without the tail-call proposal.
|
|
199
|
+
* @param {boolean} [opts.nativeTimers] - Emit a blocking `__timer_loop` in `_start` so
|
|
200
|
+
* setTimeout/setInterval fire under a standalone runtime (e.g. the wasmtime CLI) that
|
|
201
|
+
* has no host event loop. Default: timers defer to the JS host.
|
|
180
202
|
* @param {string} [opts.importMetaUrl] - Module URL used to lower `import.meta.url`
|
|
181
203
|
* and static `import.meta.resolve("...")` expressions.
|
|
204
|
+
* @param {number|boolean} [opts.randomSeed] - Seed for `Math.random`. Default: seeded
|
|
205
|
+
* once from host entropy on first use (crypto under `host:'js'`, `random_get` under
|
|
206
|
+
* WASI) — non-reproducible. Pass a number for a fixed, reproducible seed; `true` forces
|
|
207
|
+
* entropy explicitly. The randomness syscall is emitted only when `Math.random` is used.
|
|
182
208
|
* @param {boolean} [opts.inspect] - When true, return `{ wasm, inspect }`
|
|
183
209
|
* (or `{ wat, inspect }` with `opts.wat`) instead of the bare output.
|
|
184
210
|
* `inspect` carries per-function inferred shapes (params, locals, JSON shapes,
|
|
@@ -186,7 +212,14 @@ jz.memory = enhanceMemory
|
|
|
186
212
|
* without re-running the analyzer. Pays a small serialization cost; off by default.
|
|
187
213
|
* @returns {Uint8Array|string|{wasm: Uint8Array, inspect: object}|{wat: string, inspect: object}}
|
|
188
214
|
*/
|
|
215
|
+
// Test-only compile target. When set (by test/index.js under JZ_TEST_TARGET=jz.wasm)
|
|
216
|
+
// every jz.compile / compile / jz() call routes through it instead of the in-process
|
|
217
|
+
// compiler — used to run the whole suite against dist/jz.wasm (the jz compiler
|
|
218
|
+
// compiled to wasm by jz). null in production: one boolean check on a cold path.
|
|
219
|
+
let compileTarget = null
|
|
220
|
+
export const _setCompileTarget = (fn) => { compileTarget = fn }
|
|
189
221
|
jz.compile = (code, opts = {}) => {
|
|
222
|
+
if (compileTarget) return compileTarget(code, opts)
|
|
190
223
|
try {
|
|
191
224
|
return jzCompileInner(code, opts)
|
|
192
225
|
} catch (e) {
|
|
@@ -194,7 +227,8 @@ jz.compile = (code, opts = {}) => {
|
|
|
194
227
|
// codegen leak — surface it as an internal compile error with the source
|
|
195
228
|
// location we were standing on. err()-thrown errors already pass through.
|
|
196
229
|
if (e?.name === 'TypeError' || e?.name === 'ReferenceError' || e?.name === 'RangeError') {
|
|
197
|
-
|
|
230
|
+
// Pass `e` as the cause so the original stack (the real codegen site) survives.
|
|
231
|
+
err(`internal: ${e.message} (jz hit an unsupported case while compiling${ctx.error.node ? '; the AST node above shows the trigger' : ''}). This is a jz bug — please report.`, e)
|
|
198
232
|
}
|
|
199
233
|
throw e
|
|
200
234
|
}
|
|
@@ -274,7 +308,7 @@ const detectOptimizeConfig = (ast, code) => {
|
|
|
274
308
|
// jz's already-optimized IR and inflates output. Disable it automatically.
|
|
275
309
|
const isLarge = s.sourceChars > 4000 || s.funcCount > 40 || s.maxFuncBodySize > 300
|
|
276
310
|
const isMachineLike = s.callSites > 300 && s.stringLiteralCount < 10
|
|
277
|
-
if (isLarge || isMachineLike) cfg.watr = false
|
|
311
|
+
if (isLarge || isMachineLike) { cfg.watr = false; cfg.splitCharScan = false }
|
|
278
312
|
// Typed-array heavy: tighten scalarization thresholds when we see large
|
|
279
313
|
// fixed-size arrays; keep defaults for small/dynamic ones.
|
|
280
314
|
if (s.typedArrayCount > 0 && s.maxTypedArrayLen > 0) {
|
|
@@ -292,13 +326,40 @@ const detectOptimizeConfig = (ast, code) => {
|
|
|
292
326
|
return cfg
|
|
293
327
|
}
|
|
294
328
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
329
|
+
// Test-matrix bridge: when JZ_TEST_* env vars are set, inject them as default
|
|
330
|
+
// opts so the npm test suite can be re-run under varying configurations (opt
|
|
331
|
+
// levels, host, jzify, …) without source changes. User-supplied opts always win
|
|
332
|
+
// — env defaults fill only what the caller left unset. Resolved once at module
|
|
333
|
+
// load; no-op (single boolean check) when the env is empty (production path).
|
|
334
|
+
const TEST_ENV_DEFAULTS = (() => {
|
|
335
|
+
// Guard bare `process` — the compiler bundle must load in browsers/Workers too
|
|
336
|
+
// (this IIFE runs at module import; an unguarded ref throws ReferenceError there).
|
|
337
|
+
const e = (typeof process !== 'undefined' && process.env) || {}
|
|
338
|
+
const out = {}
|
|
339
|
+
if (e.JZ_TEST_OPTIMIZE != null) {
|
|
340
|
+
const v = e.JZ_TEST_OPTIMIZE
|
|
341
|
+
out.optimize = /^-?\d+$/.test(v) ? Number(v) : v === 'false' ? false : v
|
|
342
|
+
}
|
|
343
|
+
if (e.JZ_TEST_HOST) out.host = e.JZ_TEST_HOST
|
|
344
|
+
if (e.JZ_TEST_STRICT) out.strict = e.JZ_TEST_STRICT === '1'
|
|
345
|
+
return out
|
|
346
|
+
})()
|
|
347
|
+
const HAS_TEST_ENV = Object.keys(TEST_ENV_DEFAULTS).length > 0
|
|
348
|
+
|
|
349
|
+
// Shared front-half: reset ctx, wire opts → ctx.transform/memory/module/features,
|
|
350
|
+
// and inject parse/resolveUrl. Called by `jzCompileInner` (the only entry point
|
|
351
|
+
// today). The self-host entry (scripts/self.js) drives reset itself rather than
|
|
352
|
+
// going through this path, since it needs only a minimal, interop-free setup.
|
|
353
|
+
const setupCtx = (code, opts) => {
|
|
354
|
+
if (HAS_TEST_ENV) {
|
|
355
|
+
const merged = { ...opts }
|
|
356
|
+
for (const k of Object.keys(TEST_ENV_DEFAULTS)) if (merged[k] == null) merged[k] = TEST_ENV_DEFAULTS[k]
|
|
357
|
+
opts = merged
|
|
358
|
+
}
|
|
359
|
+
reset(emitter, GLOBALS, { emit, flat, body, bool, idx, spread })
|
|
360
|
+
resetProgramFactsCache()
|
|
300
361
|
ctx.error.src = code
|
|
301
|
-
|
|
362
|
+
initWarnings(opts.warnings)
|
|
302
363
|
if (typeof opts.memory === 'number') ctx.memory.pages = opts.memory
|
|
303
364
|
else if (opts.memory) ctx.memory.shared = true
|
|
304
365
|
if (opts.modules) ctx.module.importSources = opts.modules
|
|
@@ -306,9 +367,14 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
306
367
|
ctx.module.hostImports = opts.imports
|
|
307
368
|
if (importsMayReturnExternal(opts.imports)) ctx.features.external = true
|
|
308
369
|
}
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
370
|
+
// Parser for compile-time import bundling (prepareModule). Injected, not
|
|
371
|
+
// imported by prepare — see ctx.transform.parse note in prepare/index.js.
|
|
372
|
+
ctx.transform.parse = parse
|
|
373
|
+
ctx.transform.resolveUrl = resolveUrl
|
|
374
|
+
// jzify runs by default — accept the full JS subset (function/var/switch lowered to
|
|
375
|
+
// arrows/let/if). `strict: true` skips it, so prepare rejects disallowed JS features
|
|
376
|
+
// and the pure canonical subset is enforced. subscript handles ASI natively.
|
|
377
|
+
if (!opts.strict) ctx.transform.jzify = jzify
|
|
312
378
|
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
313
379
|
if (opts.strict) ctx.transform.strict = true
|
|
314
380
|
if (opts.host) {
|
|
@@ -319,31 +385,64 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
319
385
|
if (opts.alloc === false) ctx.transform.alloc = false
|
|
320
386
|
if (opts.inspect) ctx.transform.inspect = true
|
|
321
387
|
if (opts.importMetaUrl) ctx.transform.importMetaUrl = String(opts.importMetaUrl)
|
|
388
|
+
if (opts.randomSeed !== undefined) {
|
|
389
|
+
if (opts.randomSeed !== true && !Number.isFinite(opts.randomSeed))
|
|
390
|
+
err(`opts.randomSeed must be a finite number (fixed seed — reproducible) or true (seed Math.random from host entropy on first use); got ${typeof opts.randomSeed}`)
|
|
391
|
+
ctx.transform.randomSeed = opts.randomSeed
|
|
392
|
+
}
|
|
322
393
|
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
323
394
|
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
324
|
-
|
|
325
395
|
if (opts._interp) {
|
|
326
396
|
for (const [name, fn] of Object.entries(opts._interp)) {
|
|
327
|
-
if (name.startsWith('__ext_')) continue
|
|
397
|
+
if (name.startsWith('__ext_')) continue
|
|
328
398
|
if (ctx.transform.host === 'wasi') throw new Error(`host:'wasi' does not support _interp['${name}']: env imports are unavailable in WASI. Implement it natively.`)
|
|
329
399
|
ctx.features.external = true
|
|
330
400
|
const params = Array(fn.length).fill(['param', 'f64'])
|
|
331
401
|
ctx.module.imports.push(['import', '"env"', `"${name}"`, ['func', `$${name}`, ...params, ['result', 'f64']]])
|
|
332
402
|
}
|
|
333
403
|
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// U+E000 (T) prefixes every jz-generated local. The JS spec forbids it in
|
|
407
|
+
// identifiers, but subscript's parser is lenient and accepts it — so a user name
|
|
408
|
+
// carrying it could silently alias a compiler temp. Reject it in identifier
|
|
409
|
+
// position on the RAW parse (before jzify, which legitimately mints T-prefixed
|
|
410
|
+
// temps of its own). String-literal nodes are `[null, …]` and skipped, so
|
|
411
|
+
// `"……"` data is fine; only walked when the char is present in source.
|
|
412
|
+
const rejectReservedPrefix = (node) => {
|
|
413
|
+
if (!Array.isArray(node)) return
|
|
414
|
+
if (node.length === 2 && node[0] == null) return // [null, X] — value literal, not an identifier
|
|
415
|
+
for (let i = 1; i < node.length; i++) {
|
|
416
|
+
const v = node[i]
|
|
417
|
+
if (typeof v === 'string') {
|
|
418
|
+
if (v.includes(T)) err(`identifier '${v.split(T).join('\\uE000')}' contains the reserved compiler prefix (U+E000) — jz uses it for generated locals; rename it`)
|
|
419
|
+
} else rejectReservedPrefix(v)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const jzCompileInner = (code, opts = {}) => {
|
|
424
|
+
const profiler = compileProfiler(opts.profile)
|
|
425
|
+
const time = (name, fn) => profiler ? profiler.time(name, fn) : fn()
|
|
426
|
+
|
|
427
|
+
setupCtx(code, opts)
|
|
428
|
+
assertCtxInvariants('post-reset')
|
|
334
429
|
|
|
335
430
|
let parsed = time('parse', () => parse(code))
|
|
336
|
-
if (
|
|
431
|
+
if (typeof code === 'string' && code.includes(T)) rejectReservedPrefix(parsed)
|
|
432
|
+
if (!opts.strict) parsed = time('jzify', () => jzify(parsed))
|
|
337
433
|
const ast = time('prepare', () => prepare(parsed))
|
|
434
|
+
assertCtxInvariants('post-prepare')
|
|
338
435
|
|
|
339
436
|
// Auto-detect optimization tuning from source characteristics when the user
|
|
340
|
-
// hasn't provided any optimize option.
|
|
341
|
-
//
|
|
342
|
-
// watr
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
// machine-
|
|
346
|
-
|
|
437
|
+
// hasn't provided any optimize option. detectOptimizeConfig has two *live*
|
|
438
|
+
// overrides over the level-2 preset: the typed-array scalarization thresholds,
|
|
439
|
+
// and `watr: false` for large/machine-generated code (whose WAT-level CSE/DCE
|
|
440
|
+
// fights jz's already-optimized IR and inflates output). watr is ON at level 2,
|
|
441
|
+
// so that switch is a real override — run the scan when the program either
|
|
442
|
+
// touches typed arrays or is large enough for the machine-code heuristic to
|
|
443
|
+
// bite. `code.length` is the one signal free without scanning; gating on it
|
|
444
|
+
// keeps small programs (the common case) on the no-scan fast path.
|
|
445
|
+
if (opts.optimize == null && (ctx.module.modules.typedarray || code.length > 4000)) {
|
|
347
446
|
const autoCfg = detectOptimizeConfig(ast, code)
|
|
348
447
|
if (Object.keys(autoCfg).length) {
|
|
349
448
|
ctx.transform.optimize = resolveOptimize(autoCfg)
|
|
@@ -351,6 +450,7 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
351
450
|
}
|
|
352
451
|
|
|
353
452
|
const module = time('compile', () => compile(ast, profiler))
|
|
453
|
+
assertCtxInvariants('post-compile')
|
|
354
454
|
|
|
355
455
|
// host: 'wasi' — error if the wasm would import any env.__ext_* helper. Those exist
|
|
356
456
|
// only to defer to a JS host's value-aware semantics; in a wasmtime/wasmer/deno
|
|
@@ -368,24 +468,45 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
368
468
|
}
|
|
369
469
|
|
|
370
470
|
const cfg = ctx.transform.optimize
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
471
|
+
let watrOpts = typeof cfg.watr === 'object' ? { ...cfg.watr } : true
|
|
472
|
+
if (cfg.vectorizeLaneLocal) {
|
|
473
|
+
if (watrOpts === true) watrOpts = { loopify: false }
|
|
474
|
+
else if (typeof watrOpts === 'object' && watrOpts.loopify === undefined) watrOpts.loopify = false
|
|
475
|
+
}
|
|
476
|
+
if (cfg.devirtIndirect) {
|
|
477
|
+
if (watrOpts === true) watrOpts = { devirt: true }
|
|
478
|
+
else if (typeof watrOpts === 'object' && watrOpts.devirt === undefined) watrOpts.devirt = true
|
|
479
|
+
}
|
|
480
|
+
const optimized = cfg.watr ? time('watOptimize', () => watOptimize(module, watrOpts)) : module
|
|
481
|
+
// Stable-pointee module globals: resolve the __ptr_offset once per function.
|
|
482
|
+
// Never-forwarding kinds — every PTR tag outside __ptr_offset's forwarding
|
|
483
|
+
// set {ARRAY, HASH, SET, MAP} — give the same offset for the same bits, so
|
|
484
|
+
// the snapshot only needs the global's VALUE stable through the function:
|
|
485
|
+
// the reachable-writes call graph proves that precisely. Independent of watr
|
|
486
|
+
// (the auto-config turns watr off for large sources — exactly the
|
|
487
|
+
// module-global DSP-state programs this pass exists for: rfft, diffusion);
|
|
488
|
+
// when watr DID run, it goes before the post leaf passes so the snapped
|
|
489
|
+
// local participates in hoistAddrBase/cseScalarLoad.
|
|
490
|
+
if (cfg.hoistGlobalPtrOffset !== false) {
|
|
491
|
+
const stableGlobals = stablePtrGlobalNames()
|
|
492
|
+
if (stableGlobals.size) {
|
|
493
|
+
const funcs = optimized.filter(node => Array.isArray(node) && node[0] === 'func')
|
|
494
|
+
const reach = collectReachableGlobalWrites(funcs)
|
|
495
|
+
for (const node of funcs) hoistGlobalPtrOffset(node, stableGlobals, reach)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// Final peephole pass: watOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
378
499
|
// (e.g. inlined closure body's `i32.wrap_i64 (i64.reinterpret_f64 __env)` next to caller's
|
|
379
500
|
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
380
501
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
381
502
|
if (cfg.watr) {
|
|
382
|
-
const postCfg = { ...cfg, __phase: 'post' }
|
|
383
503
|
// Build global name→type map from ctx.scope.globalTypes for promoteGlobals
|
|
384
504
|
const globalTypesMap = ctx.scope.globalTypes ? new Map([...ctx.scope.globalTypes].map(([k, v]) => [`$${k}`, v])) : null
|
|
385
505
|
time('watrReopt', () => {
|
|
386
506
|
const funcs = optimized.filter(node => Array.isArray(node) && node[0] === 'func')
|
|
387
507
|
const volatileGlobals = collectVolatileGlobals(funcs)
|
|
388
|
-
|
|
508
|
+
const reach = collectReachableGlobalWrites(funcs)
|
|
509
|
+
for (const node of funcs) optimizeFunc(node, cfg, globalTypesMap, volatileGlobals, 'post', reach)
|
|
389
510
|
})
|
|
390
511
|
}
|
|
391
512
|
try {
|
|
@@ -395,7 +516,7 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
395
516
|
}
|
|
396
517
|
const wasm = time('watrCompile', () => watrCompile(optimized))
|
|
397
518
|
let bytes = wasm
|
|
398
|
-
if (opts.
|
|
519
|
+
if (opts.profile?.names) bytes = appendFunctionNames(bytes, optimized)
|
|
399
520
|
return opts.inspect ? { wasm: bytes, inspect: ctx.inspect } : bytes
|
|
400
521
|
} catch (e) {
|
|
401
522
|
// watr surfaces dangling identifiers as "Unknown local|func|global|table|memory $X".
|
|
@@ -427,6 +548,8 @@ export default function jz(code, ...args) {
|
|
|
427
548
|
|
|
428
549
|
// Serialize JS value to jz source literal. Returns null if not serializable.
|
|
429
550
|
const serialize = (v) => {
|
|
551
|
+
if (v === undefined) return 'undefined' // else falls through → treated as a
|
|
552
|
+
// host object → memory.Object(undefined) → Object.keys(undefined) crash
|
|
430
553
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
|
431
554
|
if (v === null) return 'null'
|
|
432
555
|
if (typeof v === 'string') return JSON.stringify(v)
|
|
@@ -484,13 +607,12 @@ export default function jz(code, ...args) {
|
|
|
484
607
|
// String call: jz('code', opts?) — compile + instantiate + wrap
|
|
485
608
|
const callOpts = args[0] || {}
|
|
486
609
|
const out = jz.compile(code, callOpts)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return instantiateRuntime(out, callOpts)
|
|
610
|
+
const wasm = out && typeof out === 'object' && 'wasm' in out ? out.wasm : out
|
|
611
|
+
const result = instantiateRuntime(wasm, callOpts)
|
|
612
|
+
const extra = {}
|
|
613
|
+
if (callOpts.inspect && out && typeof out === 'object' && 'inspect' in out) extra.inspect = out.inspect
|
|
614
|
+
if (callOpts.warnings) extra.warnings = callOpts.warnings.entries
|
|
615
|
+
return Object.keys(extra).length ? Object.assign(result, extra) : result
|
|
494
616
|
}
|
|
495
617
|
|
|
496
618
|
export { jz }
|