jz 0.8.0 → 0.9.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 +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6178
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +318 -43
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +1032 -145
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +428 -93
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +82 -11
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +1055 -70
- package/src/compile/index.js +277 -29
- package/src/compile/infer.js +42 -4
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/narrow.js +555 -18
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1179 -704
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +982 -67
- package/src/prepare/index.js +809 -67
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- package/wasi.js +3 -0
package/index.js
CHANGED
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
* ↓ compile — drives per-function emit, interleaves analysis (locals/valTypes/captures/
|
|
17
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
|
-
*
|
|
19
|
+
* The emit phase (src/wat/assemble.js optimizeModule) then runs jz's ONLY optimizer pass —
|
|
20
|
+
* optimizeFunc (src/optimize/index.js): `hoistPtrType` + fused peephole/inline/memarg walk +
|
|
21
|
+
* auto-vectorization. All lowering, incl. SIMD, happens here — BEFORE watr.
|
|
20
22
|
* WAT IR: watr S-expression `['module', ...sections]`, every instruction node carries `.type`.
|
|
21
|
-
* ↓ watOptimize (opt-out via opts.optimize=false) — CSE, DCE, const
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* i32.wrap_i64(i64.reinterpret_f64 __env)). watr's peephole doesn't cover this.
|
|
23
|
+
* ↓ watOptimize (opt-out via opts.optimize=false) — the SOLE, FINAL optimizer: CSE, DCE, const
|
|
24
|
+
* fold, inline, coalesce. Runs ONCE, as a fixpoint. No jz pass touches WAT after it (bar the
|
|
25
|
+
* stable-global-offset hoist, a phase-2 watr-migration candidate). See .work/research.md.
|
|
25
26
|
* ↓ watrPrint (opts.wat=true) → WAT text, or watrCompile → Uint8Array binary
|
|
26
27
|
*
|
|
27
28
|
* # State
|
|
@@ -42,17 +43,17 @@
|
|
|
42
43
|
|
|
43
44
|
import { parse } from './src/parse.js'
|
|
44
45
|
import watrCompile from "watr/compile";
|
|
46
|
+
import { snapshotInit } from "./src/snapshot.js";
|
|
45
47
|
import watrPrint from "watr/print";
|
|
46
48
|
import watOptimize from "watr/optimize";
|
|
47
|
-
import { appendLateStdlib } from './src/wat/assemble.js'
|
|
48
49
|
import { ctx, reset, err, initWarnings, assertCtxInvariants } from './src/ctx.js'
|
|
49
50
|
import prepare, { GLOBALS } from './src/prepare/index.js'
|
|
50
51
|
import { liftIIFEs } from './src/prepare/lift-iife.js'
|
|
52
|
+
import { preEval } from './src/prepare/pre-eval.js'
|
|
51
53
|
import compile from './src/compile/index.js'
|
|
52
54
|
import { resetProgramFactsCache } from './src/compile/program-facts.js'
|
|
53
55
|
import { emit, emitter, emitVoid as flat, emitBlockBody as body, emitBoolStr as bool, emitIndex as idx, buildArrayWithSpreads as spread } from './src/compile/emit.js'
|
|
54
|
-
import {
|
|
55
|
-
import { findBodyStart } from './src/ir.js'
|
|
56
|
+
import { collectReachableGlobalWrites, hoistGlobalPtrOffset, stablePtrGlobalNames, resolveOptimize, SIMD_PINNED } from './src/optimize/index.js'
|
|
56
57
|
import { VAL } from './src/reps.js'
|
|
57
58
|
import jzify from './jzify/index.js'
|
|
58
59
|
import { T } from './src/ast.js'
|
|
@@ -198,6 +199,80 @@ const appendFunctionNames = (wasm, module) => {
|
|
|
198
199
|
*/
|
|
199
200
|
jz.memory = enhanceMemory
|
|
200
201
|
|
|
202
|
+
/**
|
|
203
|
+
* jz.pool(source, opts) — SPMD worker pool over ONE shared memory (Workers v1).
|
|
204
|
+
*
|
|
205
|
+
* Compiles `source` with { sharedMemory: true }, instantiates it on the main
|
|
206
|
+
* thread AND in `threads` node worker_threads, all linked to the same
|
|
207
|
+
* WebAssembly.Memory({ shared: true }). Kernels coordinate through shared
|
|
208
|
+
* typed arrays + Atomics (module/atomics.js); strings/objects stay
|
|
209
|
+
* thread-local by the v1 contract.
|
|
210
|
+
*
|
|
211
|
+
* Worker instances link `env.memory` ONLY — a kernel needing other host
|
|
212
|
+
* imports fails instantiation loudly (pure-compute contract).
|
|
213
|
+
*
|
|
214
|
+
* const p = await jz.pool(src, { threads: 4, pages: 16, maxPages: 256 })
|
|
215
|
+
* p.exports.setup(...) // main-thread instance
|
|
216
|
+
* await p.run('tile') // every worker: tile(workerIndex, threads)
|
|
217
|
+
* await p.run('tile', a, b) // extra args broadcast to each worker
|
|
218
|
+
* p.memory // the shared, jz-enhanced memory
|
|
219
|
+
* await p.terminate()
|
|
220
|
+
*
|
|
221
|
+
* @param {string} source - jz source (compiled once; module shared with workers)
|
|
222
|
+
* @param {object} [opts] - { threads=4, pages=16, maxPages=16384, ...compile opts }
|
|
223
|
+
* @returns {Promise<{exports, memory, module, threads, run, terminate}>}
|
|
224
|
+
*/
|
|
225
|
+
jz.pool = async function pool(source, opts = {}) {
|
|
226
|
+
const { threads = 4, pages = 16, maxPages = 16384, ...rest } = opts
|
|
227
|
+
const memory = new WebAssembly.Memory({ initial: pages, maximum: maxPages, shared: true })
|
|
228
|
+
const main = jz(source, { ...rest, sharedMemory: true, memory })
|
|
229
|
+
// Computed specifier keeps bundlers (esbuild web dist) from resolving the
|
|
230
|
+
// node builtin statically; browsers get a clear v1 error instead.
|
|
231
|
+
const { Worker } = await import('node:' + 'worker_threads').catch(() => {
|
|
232
|
+
throw new Error('jz.pool v1 runs on node worker_threads; browser Worker support is a follow-up')
|
|
233
|
+
})
|
|
234
|
+
// The worker shim honors the jz:i64exp lane map (exact-bits ABI): an i64 lane
|
|
235
|
+
// takes a BigInt — scalars convert to their f64 bits, boxed pointers (BigInt
|
|
236
|
+
// args from jz.memory.* on the main thread) pass through; i64 results
|
|
237
|
+
// reinterpret back to numbers (v1 kernels return scalars).
|
|
238
|
+
const workerSrc = `
|
|
239
|
+
const { parentPort, workerData } = require('node:worker_threads')
|
|
240
|
+
const inst = new WebAssembly.Instance(workerData.module, { env: { memory: workerData.memory } })
|
|
241
|
+
const dv = new DataView(new ArrayBuffer(8))
|
|
242
|
+
const bits = (x) => (dv.setFloat64(0, x), dv.getBigUint64(0))
|
|
243
|
+
const unbits = (b) => (dv.setBigUint64(0, BigInt.asUintN(64, b)), dv.getFloat64(0))
|
|
244
|
+
const lanes = new Map()
|
|
245
|
+
for (const s of WebAssembly.Module.customSections(workerData.module, 'jz:i64exp'))
|
|
246
|
+
try { for (const e of JSON.parse(new TextDecoder().decode(s))) lanes.set(e.name, e) } catch {}
|
|
247
|
+
parentPort.on('message', (m) => {
|
|
248
|
+
try {
|
|
249
|
+
const sig = lanes.get(m.fn), p = new Set(sig?.p || [])
|
|
250
|
+
const args = m.args.map((x, i) => p.has(i) && typeof x === 'number' ? bits(x) : x)
|
|
251
|
+
let r = inst.exports[m.fn](...args)
|
|
252
|
+
if (sig?.r && typeof r === 'bigint') r = unbits(r)
|
|
253
|
+
parentPort.postMessage({ id: m.id, r })
|
|
254
|
+
} catch (e) { parentPort.postMessage({ id: m.id, e: String(e) }) }
|
|
255
|
+
})
|
|
256
|
+
parentPort.postMessage({ ready: true })`
|
|
257
|
+
const workers = Array.from({ length: threads }, () =>
|
|
258
|
+
new Worker(workerSrc, { eval: true, workerData: { module: main.module, memory } }))
|
|
259
|
+
await Promise.all(workers.map(w => new Promise((res, rej) => {
|
|
260
|
+
w.once('message', res); w.once('error', rej)
|
|
261
|
+
})))
|
|
262
|
+
let seq = 0
|
|
263
|
+
const call = (w, fn, args) => new Promise((res, rej) => {
|
|
264
|
+
const id = seq++
|
|
265
|
+
const on = (m) => { if (m.id !== id) return; w.off('message', on); m.e ? rej(new Error(m.e)) : res(m.r) }
|
|
266
|
+
w.on('message', on)
|
|
267
|
+
w.postMessage({ id, fn, args })
|
|
268
|
+
})
|
|
269
|
+
return {
|
|
270
|
+
exports: main.exports, memory: main.memory, module: main.module, threads,
|
|
271
|
+
run: (fn, ...args) => Promise.all(workers.map((w, i) => call(w, fn, [i, threads, ...args]))),
|
|
272
|
+
terminate: () => Promise.all(workers.map(w => w.terminate())),
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
201
276
|
/**
|
|
202
277
|
* Compile jz source to WASM binary or WAT text. Low-level — no instantiation.
|
|
203
278
|
* @param {string} code - jz source
|
|
@@ -212,6 +287,9 @@ jz.memory = enhanceMemory
|
|
|
212
287
|
* @param {number} [opts.maxMemory] - Maximum memory pages — emits a ceiling on the
|
|
213
288
|
* memory type so growth traps past it (sandbox cap). Must be ≥ the initial size.
|
|
214
289
|
* Default: unbounded.
|
|
290
|
+
* @param {boolean} [opts.sharedMemory] - Import `env.memory` as a SHARED memory (wasm
|
|
291
|
+
* threads): atomic heap bump, `shared` memtype (max defaults to the 4 GiB ceiling).
|
|
292
|
+
* Link with `new WebAssembly.Memory({ initial, maximum, shared: true })`.
|
|
215
293
|
* @param {boolean} [opts.importMemory] - Import `env.memory` instead of exporting an
|
|
216
294
|
* owned memory. For embedding into a host that provides the memory.
|
|
217
295
|
* @param {boolean} [opts.alloc=true] - Export raw allocator helpers
|
|
@@ -385,8 +463,6 @@ const detectOptimizeConfig = (ast, code) => {
|
|
|
385
463
|
cfg.scalarTypedLoopUnroll = s.maxLoopDepth > 1 ? 8 : 16
|
|
386
464
|
cfg.scalarTypedNestedUnroll = s.maxLoopDepth > 1 ? 32 : 128
|
|
387
465
|
}
|
|
388
|
-
// String-heavy: ensure pool sorting is on (already default, but explicit).
|
|
389
|
-
if (s.stringLiteralCount > 30) cfg.sortStrPoolByFreq = true
|
|
390
466
|
// Closure-heavy: ptr hoists pay off.
|
|
391
467
|
if (s.closureCount > 4) {
|
|
392
468
|
cfg.hoistPtrType = true
|
|
@@ -432,6 +508,11 @@ const setupCtx = (code, opts) => {
|
|
|
432
508
|
if (typeof opts.memory === 'number') ctx.memory.pages = opts.memory
|
|
433
509
|
else if (opts.memory) ctx.memory.shared = true
|
|
434
510
|
if (opts.importMemory) ctx.memory.shared = true // import env.memory instead of exporting own
|
|
511
|
+
// True cross-thread sharing (Workers v1): import env.memory declared with the
|
|
512
|
+
// wasm `shared` memtype and switch the heap bump to atomic RMW. Distinct from
|
|
513
|
+
// importMemory — a plain imported (non-shared) Memory must NOT declare shared
|
|
514
|
+
// or linking fails in the other direction.
|
|
515
|
+
if (opts.sharedMemory) { ctx.memory.shared = true; ctx.memory.atomic = true }
|
|
435
516
|
if (opts.maxMemory != null) {
|
|
436
517
|
if (!Number.isInteger(opts.maxMemory) || opts.maxMemory < 1)
|
|
437
518
|
err(`opts.maxMemory must be a positive integer page count (each page is 64 KiB); got ${opts.maxMemory}`)
|
|
@@ -517,8 +598,12 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
517
598
|
// path. A no-op when there are none.
|
|
518
599
|
parsed = time('liftIIFE', () => liftIIFEs(parsed))
|
|
519
600
|
if (!opts.strict) parsed = time('jzify', () => jzify(parsed))
|
|
520
|
-
|
|
601
|
+
let ast = time('prepare', () => prepare(parsed))
|
|
521
602
|
assertCtxInvariants('post-prepare')
|
|
603
|
+
// preEval: fold every statically-evaluable construct (numeric/string/bool chains,
|
|
604
|
+
// pure Math.* calls, zero-arg pure calls incl. lift-iife's IIFEs) down to literals,
|
|
605
|
+
// over the prepared AST + every ctx.func.list body, before compile ever sees them.
|
|
606
|
+
ast = time('preEval', () => preEval(ast))
|
|
522
607
|
|
|
523
608
|
// Auto-detect optimization tuning from source characteristics when the user
|
|
524
609
|
// hasn't provided any optimize option. detectOptimizeConfig has two *live*
|
|
@@ -609,35 +694,65 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
609
694
|
if (watrOpts === true) watrOpts = { inline: 'simd' }
|
|
610
695
|
else if (typeof watrOpts === 'object' && watrOpts.inline === undefined) watrOpts.inline = 'simd'
|
|
611
696
|
}
|
|
697
|
+
// Wrapper elision (speed tier): dissolve adapter frames — closure-ABI
|
|
698
|
+
// trampolines for functions-as-values, thin dispatch heads like
|
|
699
|
+
// __dyn_get_expr — into their target at the wrapper site. The recorded
|
|
700
|
+
// "table entries native against ftN" restructure, done mechanically in the
|
|
701
|
+
// optimizer instead of per-case emit surgery (one wrapper = one duplicated
|
|
702
|
+
// target; treeshake collects whichever copy ends up unreferenced).
|
|
703
|
+
if (cfg.inlineFns) {
|
|
704
|
+
if (typeof watrOpts === 'object' && watrOpts.inlineWrappers === undefined) watrOpts.inlineWrappers = true
|
|
705
|
+
}
|
|
706
|
+
// watr LICM (speed tier): hoist loop-invariant pure arithmetic AFTER watr's inlining exposes it
|
|
707
|
+
// (the raytrace per-iteration NaN/Inf guard pairs). Mechanical — lives in watr, jz just enables it.
|
|
708
|
+
if (cfg.watrLicm) {
|
|
709
|
+
if (watrOpts === true) watrOpts = { licm: true }
|
|
710
|
+
else if (typeof watrOpts === 'object' && watrOpts.licm === undefined) watrOpts.licm = true
|
|
711
|
+
}
|
|
612
712
|
// guardRefine folds jz's NaN-box tag reads — default-off in watr (general WAT
|
|
613
713
|
// never matches it), so jz always enables it explicitly.
|
|
614
714
|
if (watrOpts === true) watrOpts = { guardRefine: true }
|
|
615
715
|
else if (typeof watrOpts === 'object' && watrOpts.guardRefine === undefined) watrOpts.guardRefine = true
|
|
716
|
+
// watr's ifset (one-armed conditional update → select) rides jz's select
|
|
717
|
+
// tiering: an unconditional-update trade belongs to the speed tier exactly
|
|
718
|
+
// like boolConvertToSelect. jz's DEFAULT tier also passes watrProfile:'speed'
|
|
719
|
+
// (for the outline/tailmerge shape), which would enable ifset via the
|
|
720
|
+
// profile — the explicit flag here overrides it in both directions.
|
|
721
|
+
if (typeof watrOpts === 'object' && watrOpts.ifset === undefined)
|
|
722
|
+
watrOpts.ifset = cfg.boolConvertToSelect === true || cfg.watrIfset === true
|
|
723
|
+
// jz's promise is runtime speed, but watr's OWN profile default leans size — outline/
|
|
724
|
+
// tailmerge/rettail fold repeated sequences into out-of-line calls (measured 1.433→1.316
|
|
725
|
+
// on the self-host kernel with them off, watr ≥5.2.0). Every speed-tier preset carries
|
|
726
|
+
// watrProfile:'speed' (src/optimize/index.js LEVEL_PRESETS); the 'size' preset keeps
|
|
727
|
+
// watr's size-leaning default. An explicit user profile always wins.
|
|
728
|
+
if (watrOpts.profile === undefined && cfg.watrProfile) watrOpts.profile = cfg.watrProfile
|
|
729
|
+
// Speed tier waives watr's size-revert guard (two full binary encodes per
|
|
730
|
+
// optimize — its costliest fixed overhead): inlining growth is the shape this
|
|
731
|
+
// tier asks for, and jz's own perf/size gates own the size budget. Level 2
|
|
732
|
+
// (default) and 'size' keep the guard — watr's never-inflate contract stands
|
|
733
|
+
// where the user didn't opt into speed-for-size.
|
|
734
|
+
if (watrOpts.guard === undefined && cfg.watrGuard === false) watrOpts.guard = false
|
|
616
735
|
// Pin jz's scalar transcendentals (the PPC_CALL2 keys the auto-vectorizer rewrites to f64x2
|
|
617
736
|
// mirrors) so watr's inline passes don't dissolve the call nodes the lift needs. The protection
|
|
618
737
|
// policy lives here in jz — watr just honours the `pin` list (no jz names hardcoded in watr).
|
|
619
738
|
if (cfg.watr) {
|
|
620
739
|
if (watrOpts === true) watrOpts = {}
|
|
621
740
|
watrOpts.pin = watrOpts.pin ? [...watrOpts.pin, ...SIMD_PINNED] : SIMD_PINNED
|
|
741
|
+
// Boundary-wrapped functions (`$name`/`$name$exp`) the vectorizer just lifted: keep
|
|
742
|
+
// watr's default inlineOnce from merging $name into $name$exp, which would otherwise
|
|
743
|
+
// rename every $__ppc*/$__esc*/... local & label the lift produced with its own
|
|
744
|
+
// $__inl{N}_ prefix, burying the marker's leading `$` (cosmetic-only cost: one
|
|
745
|
+
// un-eliminated wrapper call per top-level invocation, not per loop iteration).
|
|
746
|
+
if (cfg._vectorizedFnNames?.size) watrOpts.pin = [...watrOpts.pin, ...cfg._vectorizedFnNames]
|
|
622
747
|
}
|
|
623
|
-
// Capture the per-func alias facts (param-distinctness) the emit phase stamped as JS
|
|
624
|
-
// expandos — watOptimize round-trips the module through watr's printer/parser, which
|
|
625
|
-
// rebuilds plain arrays and DROPS every non-index property. Without re-attaching, the
|
|
626
|
-
// 'post' leaf passes (splitLoopPrivateScratch, hoistInvariantLoop) lose the proven
|
|
627
|
-
// alias model and fall back to weaker/heuristic invariance — the exact reason raytrace's
|
|
628
|
-
// read-only sphere loads couldn't be SOUNDLY proven loop-invariant after watr ran.
|
|
629
|
-
const aliasFacts = cfg.watr ? new Map(module.filter(n => Array.isArray(n) && n[0] === 'func' && n.distinctParams).map(n => [n[1], n.distinctParams])) : null
|
|
630
748
|
const optimized = cfg.watr ? time('watOptimize', () => watOptimize(module, watrOpts)) : module
|
|
631
|
-
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
// the
|
|
637
|
-
// (
|
|
638
|
-
// module-global DSP-state programs this pass exists for: rfft, diffusion);
|
|
639
|
-
// when watr DID run, it goes before the post leaf passes so the snapped
|
|
640
|
-
// local participates in hoistAddrBase/cseScalarLoad.
|
|
749
|
+
// Stable-pointee module globals: resolve the __ptr_offset once per function. Never-forwarding
|
|
750
|
+
// kinds — every PTR tag outside __ptr_offset's forwarding set {ARRAY, HASH, SET, MAP} — give the
|
|
751
|
+
// same offset for the same bits, so the snapshot only needs the global's VALUE stable through the
|
|
752
|
+
// function: the reachable-writes call graph proves that precisely. This is the ONE jz pass still
|
|
753
|
+
// touching WAT after watr; it exists because watr has no stable-global-offset hoist of its own
|
|
754
|
+
// (the module-global DSP programs it serves — rfft, diffusion — run with watr auto-OFF anyway).
|
|
755
|
+
// TODO(phase-2): migrate into watr so the "watr is the sole optimizer" invariant holds fully.
|
|
641
756
|
if (cfg.hoistGlobalPtrOffset !== false) {
|
|
642
757
|
const stableGlobals = stablePtrGlobalNames()
|
|
643
758
|
if (stableGlobals.size) {
|
|
@@ -646,52 +761,20 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
646
761
|
for (const node of funcs) hoistGlobalPtrOffset(node, stableGlobals, reach)
|
|
647
762
|
}
|
|
648
763
|
}
|
|
649
|
-
//
|
|
650
|
-
// (
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const pureFuncMap = new Map()
|
|
664
|
-
const hasSideEffect = (node) => {
|
|
665
|
-
if (!Array.isArray(node)) return false
|
|
666
|
-
const op = node[0]
|
|
667
|
-
if (op === 'global.set') return true
|
|
668
|
-
if (typeof op === 'string' && (op.endsWith('.store') || op.startsWith('memory.'))) return true
|
|
669
|
-
if (op === 'call' && typeof node[1] === 'string' && !node[1].startsWith('$math.')) return true
|
|
670
|
-
if (op === 'call_indirect' || op === 'call_ref') return true
|
|
671
|
-
return node.some((c, i) => i > 0 && hasSideEffect(c))
|
|
672
|
-
}
|
|
673
|
-
for (const fn of allFuncs) {
|
|
674
|
-
const name = fn[1]
|
|
675
|
-
if (typeof name !== 'string' || name.startsWith('$math.') || name.startsWith('$__')) continue
|
|
676
|
-
// Fold dead str-dispatch blocks so purity check sees the clean form.
|
|
677
|
-
foldStrDispatchF64(fn)
|
|
678
|
-
const bodyStart = findBodyStart(fn)
|
|
679
|
-
if (bodyStart < 0) continue
|
|
680
|
-
let pure = true
|
|
681
|
-
for (let i = bodyStart; i < fn.length; i++) if (hasSideEffect(fn[i])) { pure = false; break }
|
|
682
|
-
if (pure) pureFuncMap.set(name, fn)
|
|
683
|
-
}
|
|
684
|
-
if (pureFuncMap.size) cfg._pureFuncMap = pureFuncMap
|
|
685
|
-
}
|
|
686
|
-
time('watrReopt', () => {
|
|
687
|
-
const funcs = optimized.filter(node => Array.isArray(node) && node[0] === 'func')
|
|
688
|
-
const volatileGlobals = collectVolatileGlobals(funcs)
|
|
689
|
-
const reach = collectReachableGlobalWrites(funcs)
|
|
690
|
-
for (const node of funcs) optimizeFunc(node, cfg, globalTypesMap, volatileGlobals, 'post', reach)
|
|
691
|
-
})
|
|
692
|
-
// The 'post' lane vectorizer can inject stdlib calls (e.g. the f64x2 trig mirror $math.cos2)
|
|
693
|
-
// absent from the already-pulled+treeshaken module — append any now-referenced helper body.
|
|
694
|
-
appendLateStdlib(optimized)
|
|
764
|
+
// NO post-watr optimizer. jz does ALL lowering — including auto-vectorization — BEFORE watr
|
|
765
|
+
// (src/wat/assemble.js optimizeModule → optimizeFunc 'pre'); watr is the sole optimizer and runs
|
|
766
|
+
// exactly ONCE, as a fixpoint. Re-running jz's leaf passes on watr's output (the former 'post'
|
|
767
|
+
// phase) both violated that architecture AND miscompiled: its propagate/fold sweep dropped a
|
|
768
|
+
// reassigned-param `local.tee` write and corrupted the divergent-escape SIMD frame (mandelbrot).
|
|
769
|
+
// The f64x2 stdlib mirrors the pre-watr vectorizer injects are appended in the emit phase
|
|
770
|
+
// (optimizeModule's appendLateStdlib); nothing post-watr needs to touch the module.
|
|
771
|
+
// Pre-eval tier 3 — module-init snapshotting (src/snapshot.js): run __start once
|
|
772
|
+
// NOW, bake the post-init heap image + global values into the artifact, delete
|
|
773
|
+
// __start. Opt-in (optimize.snapshotInit); declined cleanly (dynamically-proven
|
|
774
|
+
// hermeticity) when init touches the host, loops forever, or memory is shared.
|
|
775
|
+
if (cfg.snapshotInit) {
|
|
776
|
+
const took = time('snapshotInit', () => snapshotInit(optimized, watrCompile))
|
|
777
|
+
if (!took && opts.warnings) ctx.warnings?.push?.({ code: 'snapshot-declined', message: 'init snapshot declined (host-touching, timer, or shared-memory init) — compiled without it' })
|
|
695
778
|
}
|
|
696
779
|
try {
|
|
697
780
|
if (opts.wat) {
|
|
@@ -805,3 +888,11 @@ export const compileModule = (code, opts = {}) => {
|
|
|
805
888
|
}
|
|
806
889
|
|
|
807
890
|
export { instantiateRuntime as instantiate }
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* jzify as a standalone source→source transform: full JS in, canonical jz out.
|
|
894
|
+
* Same jzify/ module the compiler runs on every parse (default-on) — re-exported
|
|
895
|
+
* here so browser bundles (dist/jz.js: REPL auto-jzify on paste) and node users
|
|
896
|
+
* (`import { transform } from 'jz'`, also `jz/transform`) share one lowering.
|
|
897
|
+
*/
|
|
898
|
+
export { default as transform } from './transform.js'
|