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
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,16 +40,24 @@
|
|
|
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 { appendLateStdlib } from './src/wat/assemble.js'
|
|
48
|
+
import { ctx, reset, err, initWarnings, assertCtxInvariants } from './src/ctx.js'
|
|
49
|
+
import prepare, { GLOBALS } from './src/prepare/index.js'
|
|
50
|
+
import { liftIIFEs } from './src/prepare/lift-iife.js'
|
|
51
|
+
import compile from './src/compile/index.js'
|
|
52
|
+
import { resetProgramFactsCache } from './src/compile/program-facts.js'
|
|
53
|
+
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 { optimizeFunc, foldStrDispatchF64, collectVolatileGlobals, collectReachableGlobalWrites, hoistGlobalPtrOffset, stablePtrGlobalNames, resolveOptimize } from './src/optimize/index.js'
|
|
55
|
+
import { findBodyStart } from './src/ir.js'
|
|
56
|
+
import { VAL } from './src/reps.js'
|
|
57
|
+
import jzify from './jzify/index.js'
|
|
58
|
+
import { T } from './src/ast.js'
|
|
51
59
|
import {
|
|
52
|
-
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
60
|
+
memory as enhanceMemory, instantiate as instantiateRuntime, toModule,
|
|
53
61
|
} from './interop.js'
|
|
54
62
|
|
|
55
63
|
// A host import that's a JS function may hand back any value, including a host
|
|
@@ -58,11 +66,52 @@ const importsMayReturnExternal = (imports) =>
|
|
|
58
66
|
!!imports && Object.values(imports).some(mod =>
|
|
59
67
|
Object.values(mod || {}).some(spec => typeof spec === 'function'))
|
|
60
68
|
|
|
69
|
+
// WHATWG URL resolution for compile-time import.meta lowering. Injected into
|
|
70
|
+
// ctx.transform (like parse/jzify) so prepare never references the `URL` global
|
|
71
|
+
// directly — keeps the self-host kernel free of host-only built-ins.
|
|
72
|
+
const resolveUrl = (spec, base) => new URL(spec, base).href
|
|
73
|
+
|
|
74
|
+
// Serialize a JS value to a jz source literal (numbers/booleans/strings/null/
|
|
75
|
+
// undefined + literal arrays/objects). Returns null for anything not expressible
|
|
76
|
+
// as a compile-time literal (functions, host objects, circular). Shared by the
|
|
77
|
+
// `jz\`…${val}\`` template tag (hoists complex args) and opts.define.
|
|
78
|
+
const serialize = (v) => {
|
|
79
|
+
if (v === undefined) return 'undefined'
|
|
80
|
+
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
|
81
|
+
if (v === null) return 'null'
|
|
82
|
+
if (typeof v === 'string') return JSON.stringify(v)
|
|
83
|
+
if (Array.isArray(v)) {
|
|
84
|
+
const elems = v.map(serialize)
|
|
85
|
+
return elems.every(e => e !== null) ? `[${elems.join(', ')}]` : null
|
|
86
|
+
}
|
|
87
|
+
if (typeof v === 'object') {
|
|
88
|
+
const props = Object.keys(v).map(k => {
|
|
89
|
+
const s = serialize(v[k])
|
|
90
|
+
return s !== null ? `${k}: ${s}` : null
|
|
91
|
+
})
|
|
92
|
+
return props.every(p => p !== null) ? `{${props.join(', ')}}` : null
|
|
93
|
+
}
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// opts.define → a one-line `let K = V; …` prelude prepended to source. Kept on a
|
|
98
|
+
// single line (no trailing newline) so user line numbers past line 1 stay exact —
|
|
99
|
+
// same convention the template tag uses for hoisted literals.
|
|
100
|
+
const defineBindings = (define) => {
|
|
101
|
+
const parts = []
|
|
102
|
+
for (const [k, v] of Object.entries(define)) {
|
|
103
|
+
const s = serialize(v)
|
|
104
|
+
if (s === null) err(`opts.define['${k}'] is not a compile-time constant — use a number, boolean, string, null, or a literal array/object`)
|
|
105
|
+
parts.push(`let ${k} = ${s}`)
|
|
106
|
+
}
|
|
107
|
+
return parts.length ? parts.join('; ') + '; ' : ''
|
|
108
|
+
}
|
|
109
|
+
|
|
61
110
|
const nowMs = () => globalThis.performance?.now ? globalThis.performance.now() : Date.now()
|
|
62
111
|
|
|
63
112
|
const compileProfiler = (profile) => {
|
|
64
113
|
if (profile == null) return null
|
|
65
|
-
if (typeof profile !== 'object') throw new TypeError('opts.profile must be an object sink
|
|
114
|
+
if (typeof profile !== 'object') throw new TypeError('opts.profile must be an object sink (populated with compile-phase entries/totals); for a wasm name section use opts.names')
|
|
66
115
|
profile.entries ||= []
|
|
67
116
|
profile.totals ||= {}
|
|
68
117
|
return {
|
|
@@ -154,15 +203,24 @@ jz.memory = enhanceMemory
|
|
|
154
203
|
* @param {string} code - jz source
|
|
155
204
|
* @param {object} [opts]
|
|
156
205
|
* @param {boolean} [opts.wat] - Return WAT text instead of binary
|
|
157
|
-
* @param {boolean} [opts.strict] -
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
206
|
+
* @param {boolean} [opts.strict] - Enforce the pure canonical subset: skip jzify
|
|
207
|
+
* (so full-JS syntax like var/function/class is rejected, not lowered) and reject
|
|
208
|
+
* dynamic features (obj[k], for-in, unknown receiver method calls) at compile time.
|
|
209
|
+
* Avoids pulling dynamic-dispatch stdlib into output; large size win for static programs.
|
|
210
|
+
* @param {WebAssembly.Memory|number} [opts.memory] - Owned memory's initial page
|
|
211
|
+
* count (`memory: N`, 64 KiB/page), or a `WebAssembly.Memory` to share across modules.
|
|
212
|
+
* @param {number} [opts.maxMemory] - Maximum memory pages — emits a ceiling on the
|
|
213
|
+
* memory type so growth traps past it (sandbox cap). Must be ≥ the initial size.
|
|
214
|
+
* Default: unbounded.
|
|
215
|
+
* @param {boolean} [opts.importMemory] - Import `env.memory` instead of exporting an
|
|
216
|
+
* owned memory. For embedding into a host that provides the memory.
|
|
162
217
|
* @param {boolean} [opts.alloc=true] - Export raw allocator helpers
|
|
163
218
|
* (`_alloc`, `_clear`) for JS memory wrapping. Set false for standalone host-run
|
|
164
219
|
* modules that only call exported wasm functions.
|
|
165
|
-
* @param {
|
|
220
|
+
* @param {Object<string,*>} [opts.define] - Compile-time constants injected as
|
|
221
|
+
* top-level bindings before parse, e.g. `{ DEBUG: false, PORT: 8080 }`. Values may
|
|
222
|
+
* be numbers, booleans, strings, null, or literal arrays/objects.
|
|
223
|
+
* @param {boolean|number|string|object} [opts.optimize] - Optimization level/config.
|
|
166
224
|
* - `false` / `0`: nothing. Fastest compile, largest output (live coding).
|
|
167
225
|
* - `1`: encoding-compactness only (treeshake + sortLocalsByUse + fusedRewrite-inline).
|
|
168
226
|
* - `true` / `2` (default): every stable jz pass + full watr (inlineOnce +
|
|
@@ -170,15 +228,46 @@ jz.memory = enhanceMemory
|
|
|
170
228
|
* - `3` / `'speed'`: level 2 + larger array/hash initial caps (`arrayMinCap`,
|
|
171
229
|
* `hashSmallInitCap`) + `hoistConstantPool` off (inline `f64.const` over
|
|
172
230
|
* mutable globals); trades size for speed.
|
|
173
|
-
* - `
|
|
174
|
-
*
|
|
231
|
+
* - `'size'`: full passes with unrolling/SIMD off and tight scalar caps — smallest wasm.
|
|
232
|
+
* - `{ level?, <pass>?: bool, ... }`: per-pass overrides on top of a base level.
|
|
233
|
+
* INTERNAL/unstable — pass names track compiler internals (PASS_NAMES in
|
|
234
|
+
* src/optimize/index.js) and change between versions; prefer the level/string forms.
|
|
235
|
+
* @param {boolean} [opts.noSimd] - Disable auto-vectorization (no jz-emitted v128) for
|
|
236
|
+
* engines without the SIMD proposal. Explicit f32x4/i32x4 intrinsics still compile.
|
|
237
|
+
* @param {boolean} [opts.whyNotSimd] - Diagnostic: emit a `simd-why-not` warning (via
|
|
238
|
+
* opts.warnings) for each canonical loop the auto-vectorizer declined, naming the
|
|
239
|
+
* first blocking op. Finds loops one op away from SIMD. Noisy — off by default.
|
|
240
|
+
* @param {boolean} [opts.experimentalStencil] - Opt-in: vectorize neighbour-load
|
|
241
|
+
* stencils (`b[i]=f(a[i-1],a[i],a[i+1])`, 2-D 5-point) to f64x2. Bit-exact vs scalar.
|
|
242
|
+
* Unstable — off by default until proven across the corpus.
|
|
243
|
+
* @param {boolean} [opts.experimentalOuterStrip] - Opt-in: strip-mine a pixel loop whose
|
|
244
|
+
* per-pixel value is an inner reduction (metaballs-shape) into f64x2 lanes (2 pixels at
|
|
245
|
+
* once). Bit-exact vs scalar. Unstable — off by default.
|
|
246
|
+
* @param {boolean} [opts.experimentalToneMap] - Mixed-lane log-tonemap vectorizer: a flat
|
|
247
|
+
* `i32 dens[i] → f64 Math.log → i32 pack → px[i]` loop lifts to a 2-wide f64x2 island
|
|
248
|
+
* (fern/bifurcation/attractors). Bit-exact vs scalar; default-on at speed, pass `false` to disable.
|
|
249
|
+
* @param {object} [opts.warnings] - Optional mutable warning sink populated with
|
|
250
|
+
* `entries: [{ code, message, fn?, line?, column? }]`. Heap-growth advisories
|
|
251
|
+
* fire when a module uses the bump allocator and an export or loop retains
|
|
252
|
+
* allocations without a host-side memory.reset().
|
|
175
253
|
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
176
254
|
* `entries` and `totals` for parse / jzify / prepare / compile / plan / watr phases.
|
|
177
|
-
*
|
|
178
|
-
* for profiler/debugger symbolication.
|
|
179
|
-
* @param {
|
|
255
|
+
* @param {boolean} [opts.names] - Emit a standard wasm `name` custom section (function
|
|
256
|
+
* symbols) for profiler/debugger symbolication. (Legacy: `profile.names = true`.)
|
|
257
|
+
* @param {Object<string,string>} [opts.modules] - Map of module specifier → source
|
|
258
|
+
* for compile-time `import`/`export` bundling: jz resolves the module graph
|
|
259
|
+
* in-process from this map instead of reading from disk.
|
|
260
|
+
* @param {boolean} [opts.noTailCall] - Disable proper-tail-call emission (self/mutual
|
|
261
|
+
* recursion uses ordinary call frames). For engines/tools without the tail-call proposal.
|
|
262
|
+
* @param {boolean} [opts.nativeTimers] - Emit a blocking `__timer_loop` in `_start` so
|
|
263
|
+
* setTimeout/setInterval fire under a standalone runtime (e.g. the wasmtime CLI) that
|
|
264
|
+
* has no host event loop. Default: timers defer to the JS host.
|
|
180
265
|
* @param {string} [opts.importMetaUrl] - Module URL used to lower `import.meta.url`
|
|
181
266
|
* and static `import.meta.resolve("...")` expressions.
|
|
267
|
+
* @param {number|boolean} [opts.randomSeed] - Seed for `Math.random`. Default: seeded
|
|
268
|
+
* once from host entropy on first use (crypto under `host:'js'`, `random_get` under
|
|
269
|
+
* WASI) — non-reproducible. Pass a number for a fixed, reproducible seed; `true` forces
|
|
270
|
+
* entropy explicitly. The randomness syscall is emitted only when `Math.random` is used.
|
|
182
271
|
* @param {boolean} [opts.inspect] - When true, return `{ wasm, inspect }`
|
|
183
272
|
* (or `{ wat, inspect }` with `opts.wat`) instead of the bare output.
|
|
184
273
|
* `inspect` carries per-function inferred shapes (params, locals, JSON shapes,
|
|
@@ -186,7 +275,14 @@ jz.memory = enhanceMemory
|
|
|
186
275
|
* without re-running the analyzer. Pays a small serialization cost; off by default.
|
|
187
276
|
* @returns {Uint8Array|string|{wasm: Uint8Array, inspect: object}|{wat: string, inspect: object}}
|
|
188
277
|
*/
|
|
278
|
+
// Test-only compile target. When set (by test/index.js under JZ_TEST_TARGET=jz.wasm)
|
|
279
|
+
// every jz.compile / compile / jz() call routes through it instead of the in-process
|
|
280
|
+
// compiler — used to run the whole suite against dist/jz.wasm (the jz compiler
|
|
281
|
+
// compiled to wasm by jz). null in production: one boolean check on a cold path.
|
|
282
|
+
let compileTarget = null
|
|
283
|
+
export const _setCompileTarget = (fn) => { compileTarget = fn }
|
|
189
284
|
jz.compile = (code, opts = {}) => {
|
|
285
|
+
if (compileTarget) return compileTarget(code, opts)
|
|
190
286
|
try {
|
|
191
287
|
return jzCompileInner(code, opts)
|
|
192
288
|
} catch (e) {
|
|
@@ -194,7 +290,8 @@ jz.compile = (code, opts = {}) => {
|
|
|
194
290
|
// codegen leak — surface it as an internal compile error with the source
|
|
195
291
|
// location we were standing on. err()-thrown errors already pass through.
|
|
196
292
|
if (e?.name === 'TypeError' || e?.name === 'ReferenceError' || e?.name === 'RangeError') {
|
|
197
|
-
|
|
293
|
+
// Pass `e` as the cause so the original stack (the real codegen site) survives.
|
|
294
|
+
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
295
|
}
|
|
199
296
|
throw e
|
|
200
297
|
}
|
|
@@ -271,10 +368,16 @@ const detectOptimizeConfig = (ast, code) => {
|
|
|
271
368
|
|
|
272
369
|
const cfg = {}
|
|
273
370
|
// Machine-generated or large code: watr's WAT-level CSE/DCE/inline fights
|
|
274
|
-
// jz's already-optimized IR and inflates output. Disable it automatically
|
|
371
|
+
// jz's already-optimized IR and inflates output. Disable it automatically —
|
|
372
|
+
// EXCEPT when the module uses SIMD intrinsics. A v128 helper call (e.g.
|
|
373
|
+
// f64x2.sin → $math.sin2) leaves v128 params/results across the call boundary;
|
|
374
|
+
// watr's inliner folds the helper into the loop and coalesces those vectors,
|
|
375
|
+
// and without it the v128 spills to memory every iteration (~2× slower on the
|
|
376
|
+
// trig-bound attractors kernel). Explicit SIMD is a perf opt-in — keep watr on.
|
|
377
|
+
const usesSimd = !!ctx.module?.modules?.simd
|
|
275
378
|
const isLarge = s.sourceChars > 4000 || s.funcCount > 40 || s.maxFuncBodySize > 300
|
|
276
379
|
const isMachineLike = s.callSites > 300 && s.stringLiteralCount < 10
|
|
277
|
-
if (isLarge || isMachineLike) cfg.watr = false
|
|
380
|
+
if ((isLarge || isMachineLike) && !usesSimd) { cfg.watr = false; cfg.splitCharScan = false }
|
|
278
381
|
// Typed-array heavy: tighten scalarization thresholds when we see large
|
|
279
382
|
// fixed-size arrays; keep defaults for small/dynamic ones.
|
|
280
383
|
if (s.typedArrayCount > 0 && s.maxTypedArrayLen > 0) {
|
|
@@ -292,23 +395,64 @@ const detectOptimizeConfig = (ast, code) => {
|
|
|
292
395
|
return cfg
|
|
293
396
|
}
|
|
294
397
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
398
|
+
// Test-matrix bridge: when JZ_TEST_* env vars are set, inject them as default
|
|
399
|
+
// opts so the npm test suite can be re-run under varying configurations (opt
|
|
400
|
+
// levels, host, jzify, …) without source changes. User-supplied opts always win
|
|
401
|
+
// — env defaults fill only what the caller left unset. Resolved once at module
|
|
402
|
+
// load; no-op (single boolean check) when the env is empty (production path).
|
|
403
|
+
const TEST_ENV_DEFAULTS = (() => {
|
|
404
|
+
// Guard bare `process` — the compiler bundle must load in browsers/Workers too
|
|
405
|
+
// (this IIFE runs at module import; an unguarded ref throws ReferenceError there).
|
|
406
|
+
const e = (typeof process !== 'undefined' && process.env) || {}
|
|
407
|
+
const out = {}
|
|
408
|
+
if (e.JZ_TEST_OPTIMIZE != null) {
|
|
409
|
+
const v = e.JZ_TEST_OPTIMIZE
|
|
410
|
+
out.optimize = /^-?\d+$/.test(v) ? Number(v) : v === 'false' ? false : v
|
|
411
|
+
}
|
|
412
|
+
if (e.JZ_TEST_HOST) out.host = e.JZ_TEST_HOST
|
|
413
|
+
if (e.JZ_TEST_STRICT) out.strict = e.JZ_TEST_STRICT === '1'
|
|
414
|
+
return out
|
|
415
|
+
})()
|
|
416
|
+
const HAS_TEST_ENV = Object.keys(TEST_ENV_DEFAULTS).length > 0
|
|
417
|
+
|
|
418
|
+
// Shared front-half: reset ctx, wire opts → ctx.transform/memory/module/features,
|
|
419
|
+
// and inject parse/resolveUrl. Called by `jzCompileInner` (the only entry point
|
|
420
|
+
// today). The self-host entry (scripts/self.js) drives reset itself rather than
|
|
421
|
+
// going through this path, since it needs only a minimal, interop-free setup.
|
|
422
|
+
const setupCtx = (code, opts) => {
|
|
423
|
+
if (HAS_TEST_ENV) {
|
|
424
|
+
const merged = { ...opts }
|
|
425
|
+
for (const k of Object.keys(TEST_ENV_DEFAULTS)) if (merged[k] == null) merged[k] = TEST_ENV_DEFAULTS[k]
|
|
426
|
+
opts = merged
|
|
427
|
+
}
|
|
428
|
+
reset(emitter, GLOBALS, { emit, flat, body, bool, idx, spread })
|
|
429
|
+
resetProgramFactsCache()
|
|
300
430
|
ctx.error.src = code
|
|
301
|
-
|
|
431
|
+
initWarnings(opts.warnings)
|
|
302
432
|
if (typeof opts.memory === 'number') ctx.memory.pages = opts.memory
|
|
303
433
|
else if (opts.memory) ctx.memory.shared = true
|
|
434
|
+
if (opts.importMemory) ctx.memory.shared = true // import env.memory instead of exporting own
|
|
435
|
+
if (opts.maxMemory != null) {
|
|
436
|
+
if (!Number.isInteger(opts.maxMemory) || opts.maxMemory < 1)
|
|
437
|
+
err(`opts.maxMemory must be a positive integer page count (each page is 64 KiB); got ${opts.maxMemory}`)
|
|
438
|
+
const initialPages = ctx.memory.pages || 1
|
|
439
|
+
if (opts.maxMemory < initialPages)
|
|
440
|
+
err(`opts.maxMemory (${opts.maxMemory}) is below the initial memory size (${initialPages} pages)`)
|
|
441
|
+
ctx.memory.max = opts.maxMemory
|
|
442
|
+
}
|
|
304
443
|
if (opts.modules) ctx.module.importSources = opts.modules
|
|
305
444
|
if (opts.imports) {
|
|
306
445
|
ctx.module.hostImports = opts.imports
|
|
307
446
|
if (importsMayReturnExternal(opts.imports)) ctx.features.external = true
|
|
308
447
|
}
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
448
|
+
// Parser for compile-time import bundling (prepareModule). Injected, not
|
|
449
|
+
// imported by prepare — see ctx.transform.parse note in prepare/index.js.
|
|
450
|
+
ctx.transform.parse = parse
|
|
451
|
+
ctx.transform.resolveUrl = resolveUrl
|
|
452
|
+
// jzify runs by default — accept the full JS subset (function/var/switch lowered to
|
|
453
|
+
// arrows/let/if). `strict: true` skips it, so prepare rejects disallowed JS features
|
|
454
|
+
// and the pure canonical subset is enforced. subscript handles ASI natively.
|
|
455
|
+
if (!opts.strict) ctx.transform.jzify = jzify
|
|
312
456
|
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
313
457
|
if (opts.strict) ctx.transform.strict = true
|
|
314
458
|
if (opts.host) {
|
|
@@ -319,38 +463,106 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
319
463
|
if (opts.alloc === false) ctx.transform.alloc = false
|
|
320
464
|
if (opts.inspect) ctx.transform.inspect = true
|
|
321
465
|
if (opts.importMetaUrl) ctx.transform.importMetaUrl = String(opts.importMetaUrl)
|
|
466
|
+
if (opts.randomSeed !== undefined) {
|
|
467
|
+
if (opts.randomSeed !== true && !Number.isFinite(opts.randomSeed))
|
|
468
|
+
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}`)
|
|
469
|
+
ctx.transform.randomSeed = opts.randomSeed
|
|
470
|
+
}
|
|
322
471
|
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
323
472
|
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
324
|
-
|
|
325
473
|
if (opts._interp) {
|
|
326
474
|
for (const [name, fn] of Object.entries(opts._interp)) {
|
|
327
|
-
if (name.startsWith('__ext_')) continue
|
|
475
|
+
if (name.startsWith('__ext_')) continue
|
|
328
476
|
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
477
|
ctx.features.external = true
|
|
330
478
|
const params = Array(fn.length).fill(['param', 'f64'])
|
|
331
479
|
ctx.module.imports.push(['import', '"env"', `"${name}"`, ['func', `$${name}`, ...params, ['result', 'f64']]])
|
|
332
480
|
}
|
|
333
481
|
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// U+E000 (T) prefixes every jz-generated local. The JS spec forbids it in
|
|
485
|
+
// identifiers, but subscript's parser is lenient and accepts it — so a user name
|
|
486
|
+
// carrying it could silently alias a compiler temp. Reject it in identifier
|
|
487
|
+
// position on the RAW parse (before jzify, which legitimately mints T-prefixed
|
|
488
|
+
// temps of its own). String-literal nodes are `[null, …]` and skipped, so
|
|
489
|
+
// `"……"` data is fine; only walked when the char is present in source.
|
|
490
|
+
const rejectReservedPrefix = (node) => {
|
|
491
|
+
if (!Array.isArray(node)) return
|
|
492
|
+
if (node.length === 2 && node[0] == null) return // [null, X] — value literal, not an identifier
|
|
493
|
+
for (let i = 1; i < node.length; i++) {
|
|
494
|
+
const v = node[i]
|
|
495
|
+
if (typeof v === 'string') {
|
|
496
|
+
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`)
|
|
497
|
+
} else rejectReservedPrefix(v)
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const jzCompileInner = (code, opts = {}) => {
|
|
502
|
+
if (opts.define) code = defineBindings(opts.define) + code
|
|
503
|
+
const profiler = compileProfiler(opts.profile)
|
|
504
|
+
const time = (name, fn) => profiler ? profiler.time(name, fn) : fn()
|
|
505
|
+
|
|
506
|
+
setupCtx(code, opts)
|
|
507
|
+
assertCtxInvariants('post-reset')
|
|
334
508
|
|
|
335
509
|
let parsed = time('parse', () => parse(code))
|
|
336
|
-
if (
|
|
510
|
+
if (typeof code === 'string' && code.includes(T)) rejectReservedPrefix(parsed)
|
|
511
|
+
// Lambda-lift immediately-invoked arrow literals to typed direct calls — lets SIMD
|
|
512
|
+
// flow through the f64-only closure ABI and drops the closure for every IIFE. Runs
|
|
513
|
+
// BEFORE jzify so it only sees USER arrow IIFEs, not jzify's synthetic wrapper IIFEs
|
|
514
|
+
// (named/recursive function expressions, method shorthand), which keep the closure
|
|
515
|
+
// path. A no-op when there are none.
|
|
516
|
+
parsed = time('liftIIFE', () => liftIIFEs(parsed))
|
|
517
|
+
if (!opts.strict) parsed = time('jzify', () => jzify(parsed))
|
|
337
518
|
const ast = time('prepare', () => prepare(parsed))
|
|
519
|
+
assertCtxInvariants('post-prepare')
|
|
338
520
|
|
|
339
521
|
// 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
|
-
|
|
522
|
+
// hasn't provided any optimize option. detectOptimizeConfig has two *live*
|
|
523
|
+
// overrides over the level-2 preset: the typed-array scalarization thresholds,
|
|
524
|
+
// and `watr: false` for large/machine-generated code (whose WAT-level CSE/DCE
|
|
525
|
+
// fights jz's already-optimized IR and inflates output). watr is ON at level 2,
|
|
526
|
+
// so that switch is a real override — run the scan when the program either
|
|
527
|
+
// touches typed arrays or is large enough for the machine-code heuristic to
|
|
528
|
+
// bite. `code.length` is the one signal free without scanning; gating on it
|
|
529
|
+
// keeps small programs (the common case) on the no-scan fast path.
|
|
530
|
+
if (opts.optimize == null && (ctx.module.modules.typedarray || code.length > 4000)) {
|
|
347
531
|
const autoCfg = detectOptimizeConfig(ast, code)
|
|
348
532
|
if (Object.keys(autoCfg).length) {
|
|
349
533
|
ctx.transform.optimize = resolveOptimize(autoCfg)
|
|
350
534
|
}
|
|
351
535
|
}
|
|
352
536
|
|
|
537
|
+
// opts.noSimd: force auto-vectorization off regardless of opt level — a
|
|
538
|
+
// portability escape hatch for engines without the SIMD proposal (parallels
|
|
539
|
+
// opts.noTailCall). Disabling the vectorizeLaneLocal pass suppresses every
|
|
540
|
+
// jz-emitted v128 (lane maps, reductions incl. reduceUnroll, byte scans);
|
|
541
|
+
// explicit f32x4/i32x4 intrinsics in source are the user's own opt-in and stay.
|
|
542
|
+
// Applied after auto-config so it wins over any re-resolved preset.
|
|
543
|
+
if (opts.noSimd) ctx.transform.optimize.vectorizeLaneLocal = false
|
|
544
|
+
|
|
545
|
+
// opts.whyNotSimd (CLI --why-not-simd): emit a `simd-why-not` warning per
|
|
546
|
+
// canonical loop that the auto-vectorizer declined, naming the blocking op —
|
|
547
|
+
// a diagnostic to find loops that are "one op away" from SIMD. Rides the
|
|
548
|
+
// resolved optimize cfg to the vectorizer; off by default (the report is noisy).
|
|
549
|
+
if (opts.whyNotSimd && ctx.transform.optimize) ctx.transform.optimize.whyNotSimd = true
|
|
550
|
+
|
|
551
|
+
// opts.experimentalStencil: the neighbour-load stencil vectorizer (a[i±1] / 2-D 5-point).
|
|
552
|
+
// Now default-on at optimize:'speed' (proven bit-exact corpus-wide); the opt is two-way so an
|
|
553
|
+
// explicit `false` can still disable it (e.g. to A/B against the scalar path).
|
|
554
|
+
if (opts.experimentalStencil !== undefined && ctx.transform.optimize) ctx.transform.optimize.experimentalStencil = !!opts.experimentalStencil
|
|
555
|
+
|
|
556
|
+
// opts.experimentalOuterStrip: the outer-loop strip-mine vectorizer (2 adjacent pixels in f64x2
|
|
557
|
+
// lanes over an inner reduction). Default-on at speed; two-way like experimentalStencil.
|
|
558
|
+
if (opts.experimentalOuterStrip !== undefined && ctx.transform.optimize) ctx.transform.optimize.experimentalOuterStrip = !!opts.experimentalOuterStrip
|
|
559
|
+
|
|
560
|
+
// opts.experimentalToneMap: the mixed-lane log-tonemap vectorizer (i32 dens[i] → f64 log →
|
|
561
|
+
// i32 pack → px[i], 2-wide f64x2 island). Default-on at speed; two-way like the others.
|
|
562
|
+
if (opts.experimentalToneMap !== undefined && ctx.transform.optimize) ctx.transform.optimize.experimentalToneMap = !!opts.experimentalToneMap
|
|
563
|
+
|
|
353
564
|
const module = time('compile', () => compile(ast, profiler))
|
|
565
|
+
assertCtxInvariants('post-compile')
|
|
354
566
|
|
|
355
567
|
// host: 'wasi' — error if the wasm would import any env.__ext_* helper. Those exist
|
|
356
568
|
// only to defer to a JS host's value-aware semantics; in a wasmtime/wasmer/deno
|
|
@@ -368,25 +580,87 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
368
580
|
}
|
|
369
581
|
|
|
370
582
|
const cfg = ctx.transform.optimize
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
583
|
+
let watrOpts = typeof cfg.watr === 'object' ? { ...cfg.watr } : true
|
|
584
|
+
if (cfg.vectorizeLaneLocal) {
|
|
585
|
+
if (watrOpts === true) watrOpts = { loopify: false }
|
|
586
|
+
else if (typeof watrOpts === 'object' && watrOpts.loopify === undefined) watrOpts.loopify = false
|
|
587
|
+
}
|
|
588
|
+
if (cfg.devirtIndirect) {
|
|
589
|
+
if (watrOpts === true) watrOpts = { devirt: true }
|
|
590
|
+
else if (typeof watrOpts === 'object' && watrOpts.devirt === undefined) watrOpts.devirt = true
|
|
591
|
+
}
|
|
592
|
+
// Multi-caller small-function inlining (size-for-speed): on at the 'speed'/level-3
|
|
593
|
+
// tier only, like devirt above. Removes call overhead from hot inner loops at the
|
|
594
|
+
// cost of duplicating tiny bodies; level 2 (and the size budgets measured there)
|
|
595
|
+
// stay untouched.
|
|
596
|
+
if (cfg.inlineFns) {
|
|
597
|
+
if (watrOpts === true) watrOpts = { inline: true }
|
|
598
|
+
else if (typeof watrOpts === 'object' && watrOpts.inline === undefined) watrOpts.inline = true
|
|
599
|
+
}
|
|
600
|
+
const optimized = cfg.watr ? time('watOptimize', () => watOptimize(module, watrOpts)) : module
|
|
601
|
+
// Stable-pointee module globals: resolve the __ptr_offset once per function.
|
|
602
|
+
// Never-forwarding kinds — every PTR tag outside __ptr_offset's forwarding
|
|
603
|
+
// set {ARRAY, HASH, SET, MAP} — give the same offset for the same bits, so
|
|
604
|
+
// the snapshot only needs the global's VALUE stable through the function:
|
|
605
|
+
// the reachable-writes call graph proves that precisely. Independent of watr
|
|
606
|
+
// (the auto-config turns watr off for large sources — exactly the
|
|
607
|
+
// module-global DSP-state programs this pass exists for: rfft, diffusion);
|
|
608
|
+
// when watr DID run, it goes before the post leaf passes so the snapped
|
|
609
|
+
// local participates in hoistAddrBase/cseScalarLoad.
|
|
610
|
+
if (cfg.hoistGlobalPtrOffset !== false) {
|
|
611
|
+
const stableGlobals = stablePtrGlobalNames()
|
|
612
|
+
if (stableGlobals.size) {
|
|
613
|
+
const funcs = optimized.filter(node => Array.isArray(node) && node[0] === 'func')
|
|
614
|
+
const reach = collectReachableGlobalWrites(funcs)
|
|
615
|
+
for (const node of funcs) hoistGlobalPtrOffset(node, stableGlobals, reach)
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
// Final peephole pass: watOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
378
619
|
// (e.g. inlined closure body's `i32.wrap_i64 (i64.reinterpret_f64 __env)` next to caller's
|
|
379
620
|
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
380
621
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
381
622
|
if (cfg.watr) {
|
|
382
|
-
const postCfg = { ...cfg, __phase: 'post' }
|
|
383
623
|
// Build global name→type map from ctx.scope.globalTypes for promoteGlobals
|
|
384
624
|
const globalTypesMap = ctx.scope.globalTypes ? new Map([...ctx.scope.globalTypes].map(([k, v]) => [`$${k}`, v])) : null
|
|
625
|
+
// Build pure-function map for Phase 2 user-function inline in tryPerPixelColor.
|
|
626
|
+
// A function is "pure for SIMD inline" if its body contains no side effects:
|
|
627
|
+
// no global.set, no memory stores, no calls outside $math.*.
|
|
628
|
+
// foldStrDispatchF64 is run first (idempotent) so the purity check sees the
|
|
629
|
+
// folded body — dead __is_str_key dispatch would otherwise look impure.
|
|
630
|
+
if (cfg.vectorizeLaneLocal === true) {
|
|
631
|
+
const allFuncs = optimized.filter(node => Array.isArray(node) && node[0] === 'func')
|
|
632
|
+
const pureFuncMap = new Map()
|
|
633
|
+
const hasSideEffect = (node) => {
|
|
634
|
+
if (!Array.isArray(node)) return false
|
|
635
|
+
const op = node[0]
|
|
636
|
+
if (op === 'global.set') return true
|
|
637
|
+
if (typeof op === 'string' && (op.endsWith('.store') || op.startsWith('memory.'))) return true
|
|
638
|
+
if (op === 'call' && typeof node[1] === 'string' && !node[1].startsWith('$math.')) return true
|
|
639
|
+
if (op === 'call_indirect' || op === 'call_ref') return true
|
|
640
|
+
return node.some((c, i) => i > 0 && hasSideEffect(c))
|
|
641
|
+
}
|
|
642
|
+
for (const fn of allFuncs) {
|
|
643
|
+
const name = fn[1]
|
|
644
|
+
if (typeof name !== 'string' || name.startsWith('$math.') || name.startsWith('$__')) continue
|
|
645
|
+
// Fold dead str-dispatch blocks so purity check sees the clean form.
|
|
646
|
+
foldStrDispatchF64(fn)
|
|
647
|
+
const bodyStart = findBodyStart(fn)
|
|
648
|
+
if (bodyStart < 0) continue
|
|
649
|
+
let pure = true
|
|
650
|
+
for (let i = bodyStart; i < fn.length; i++) if (hasSideEffect(fn[i])) { pure = false; break }
|
|
651
|
+
if (pure) pureFuncMap.set(name, fn)
|
|
652
|
+
}
|
|
653
|
+
if (pureFuncMap.size) cfg._pureFuncMap = pureFuncMap
|
|
654
|
+
}
|
|
385
655
|
time('watrReopt', () => {
|
|
386
656
|
const funcs = optimized.filter(node => Array.isArray(node) && node[0] === 'func')
|
|
387
657
|
const volatileGlobals = collectVolatileGlobals(funcs)
|
|
388
|
-
|
|
658
|
+
const reach = collectReachableGlobalWrites(funcs)
|
|
659
|
+
for (const node of funcs) optimizeFunc(node, cfg, globalTypesMap, volatileGlobals, 'post', reach)
|
|
389
660
|
})
|
|
661
|
+
// The 'post' lane vectorizer can inject stdlib calls (e.g. the f64x2 trig mirror $math.cos2)
|
|
662
|
+
// absent from the already-pulled+treeshaken module — append any now-referenced helper body.
|
|
663
|
+
appendLateStdlib(optimized)
|
|
390
664
|
}
|
|
391
665
|
try {
|
|
392
666
|
if (opts.wat) {
|
|
@@ -395,7 +669,9 @@ const jzCompileInner = (code, opts = {}) => {
|
|
|
395
669
|
}
|
|
396
670
|
const wasm = time('watrCompile', () => watrCompile(optimized))
|
|
397
671
|
let bytes = wasm
|
|
398
|
-
|
|
672
|
+
// opts.names emits a wasm `name` custom section (symbols for profilers/
|
|
673
|
+
// debuggers). opts.profile.names is the older spelling — still honored.
|
|
674
|
+
if (opts.names || opts.profile?.names) bytes = appendFunctionNames(bytes, optimized)
|
|
399
675
|
return opts.inspect ? { wasm: bytes, inspect: ctx.inspect } : bytes
|
|
400
676
|
} catch (e) {
|
|
401
677
|
// watr surfaces dangling identifiers as "Unknown local|func|global|table|memory $X".
|
|
@@ -425,25 +701,6 @@ export default function jz(code, ...args) {
|
|
|
425
701
|
if (Array.isArray(code)) {
|
|
426
702
|
const interp = {}, data = {}, hoisted = []
|
|
427
703
|
|
|
428
|
-
// Serialize JS value to jz source literal. Returns null if not serializable.
|
|
429
|
-
const serialize = (v) => {
|
|
430
|
-
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
|
431
|
-
if (v === null) return 'null'
|
|
432
|
-
if (typeof v === 'string') return JSON.stringify(v)
|
|
433
|
-
if (Array.isArray(v)) {
|
|
434
|
-
const elems = v.map(serialize)
|
|
435
|
-
return elems.every(e => e !== null) ? `[${elems.join(', ')}]` : null
|
|
436
|
-
}
|
|
437
|
-
if (typeof v === 'object') {
|
|
438
|
-
const props = Object.keys(v).map(k => {
|
|
439
|
-
const s = serialize(v[k])
|
|
440
|
-
return s !== null ? `${k}: ${s}` : null
|
|
441
|
-
})
|
|
442
|
-
return props.every(p => p !== null) ? `{${props.join(', ')}}` : null
|
|
443
|
-
}
|
|
444
|
-
return null
|
|
445
|
-
}
|
|
446
|
-
|
|
447
704
|
let src = code[0]
|
|
448
705
|
for (let i = 0; i < args.length; i++) {
|
|
449
706
|
const v = args[i]
|
|
@@ -484,15 +741,36 @@ export default function jz(code, ...args) {
|
|
|
484
741
|
// String call: jz('code', opts?) — compile + instantiate + wrap
|
|
485
742
|
const callOpts = args[0] || {}
|
|
486
743
|
const out = jz.compile(code, callOpts)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return instantiateRuntime(out, callOpts)
|
|
744
|
+
const wasm = out && typeof out === 'object' && 'wasm' in out ? out.wasm : out
|
|
745
|
+
const result = instantiateRuntime(wasm, callOpts)
|
|
746
|
+
const extra = {}
|
|
747
|
+
if (callOpts.inspect && out && typeof out === 'object' && 'inspect' in out) extra.inspect = out.inspect
|
|
748
|
+
if (callOpts.warnings) extra.warnings = callOpts.warnings.entries
|
|
749
|
+
return Object.keys(extra).length ? Object.assign(result, extra) : result
|
|
494
750
|
}
|
|
495
751
|
|
|
496
752
|
export { jz }
|
|
497
753
|
const jzCompile = jz.compile
|
|
498
754
|
export { jzCompile as compile }
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Compile source to a cached `WebAssembly.Module` (compile + validate once).
|
|
758
|
+
* Pair with `instantiate(module, opts)` to spin up many instances without
|
|
759
|
+
* re-validating the bytes each time — the AOT compile is paid once, so repeated
|
|
760
|
+
* instantiation is cheap. Returns a `WebAssembly.Module`, built with the native
|
|
761
|
+
* `wasm:js-string` builtins fast path where the engine supports it.
|
|
762
|
+
*
|
|
763
|
+
* import { compileModule, instantiate } from 'jz'
|
|
764
|
+
* const mod = compileModule('export let f = x => x * 2')
|
|
765
|
+
* const { exports } = instantiate(mod) // repeat cheaply, no recompile
|
|
766
|
+
*
|
|
767
|
+
* @param {string} code
|
|
768
|
+
* @param {object} [opts] - same options as `compile()`
|
|
769
|
+
* @returns {WebAssembly.Module}
|
|
770
|
+
*/
|
|
771
|
+
export const compileModule = (code, opts = {}) => {
|
|
772
|
+
const out = jzCompile(code, opts)
|
|
773
|
+
return toModule(out && typeof out === 'object' && 'wasm' in out ? out.wasm : out)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export { instantiateRuntime as instantiate }
|