jz 0.4.0 → 0.5.1
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 +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +277 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +601 -130
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +545 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +705 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +851 -132
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +833 -153
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/cli.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* JZ CLI - Command-line interface for JZ compiler
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { readFileSync, writeFileSync
|
|
8
|
-
import {
|
|
7
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
8
|
+
import { resolve } from 'path'
|
|
9
9
|
import { pathToFileURL } from 'url'
|
|
10
|
-
import { execFileSync } from 'child_process'
|
|
11
10
|
import { parse } from 'subscript/feature/jessie'
|
|
12
11
|
import jz, { compile } from './index.js'
|
|
13
12
|
import jzifyFn from './src/jzify.js'
|
|
14
13
|
import { codegen } from './src/codegen.js'
|
|
14
|
+
import { resolveModuleGraph } from './src/resolve.js'
|
|
15
15
|
import { createRequire } from 'module'
|
|
16
16
|
|
|
17
17
|
const jzRequire = createRequire(import.meta.url)
|
|
@@ -149,53 +149,10 @@ async function handleCompile(args) {
|
|
|
149
149
|
if (!outputFile) outputFile = inputFile.replace(/\.(js|jz)$/, wat ? '.wat' : '.wasm')
|
|
150
150
|
if (outputFile.endsWith('.wat')) wat = true
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const modules = {}
|
|
157
|
-
|
|
158
|
-
const pkgFile = join(dir, 'package.json')
|
|
159
|
-
if (existsSync(pkgFile)) {
|
|
160
|
-
try {
|
|
161
|
-
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'))
|
|
162
|
-
if (pkg.imports) for (const [spec, path] of Object.entries(pkg.imports)) {
|
|
163
|
-
const full = resolve(dir, path)
|
|
164
|
-
try { modules[spec] = readFileSync(full, 'utf8') } catch {}
|
|
165
|
-
}
|
|
166
|
-
} catch {}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Recursively resolve relative imports from entry file and all discovered modules
|
|
170
|
-
const importRe = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g
|
|
171
|
-
const resolveBareModule = (specifier, fromDir) => execFileSync(
|
|
172
|
-
process.execPath,
|
|
173
|
-
['--input-type=module', '-e', 'process.stdout.write(import.meta.resolve(process.argv[1]))', specifier],
|
|
174
|
-
{ cwd: fromDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
175
|
-
).trim()
|
|
176
|
-
const resolveModule = (specifier, fromDir) => {
|
|
177
|
-
if (modules[specifier]) return
|
|
178
|
-
// Relative imports: resolve from filesystem
|
|
179
|
-
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
180
|
-
const full = resolve(fromDir, specifier)
|
|
181
|
-
let src
|
|
182
|
-
try { src = readFileSync(full, 'utf8') }
|
|
183
|
-
catch { try { src = readFileSync(full + '.js', 'utf8') } catch { return } }
|
|
184
|
-
modules[specifier] = src
|
|
185
|
-
let m; importRe.lastIndex = 0
|
|
186
|
-
while ((m = importRe.exec(src)) !== null) resolveModule(m[1], dirname(full))
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
// Bare specifiers: opt-in Node.js resolution
|
|
190
|
-
if (resolveNode) {
|
|
191
|
-
try {
|
|
192
|
-
const resolved = resolveBareModule(specifier, fromDir)
|
|
193
|
-
if (resolved.startsWith('file:')) modules[specifier] = readFileSync(new URL(resolved), 'utf8')
|
|
194
|
-
} catch {}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
let m; importRe.lastIndex = 0
|
|
198
|
-
while ((m = importRe.exec(code)) !== null) resolveModule(m[1], dir)
|
|
152
|
+
// Resolve imports — canonicalize every specifier to an absolute path so the
|
|
153
|
+
// same physical file always produces one module instance (see src/resolve.js).
|
|
154
|
+
const { code: codeRewritten, modules } = resolveModuleGraph(inputFile, { resolveNode })
|
|
155
|
+
if (process.env.JZ_DEBUG_MODULES === '1') console.error('modules:', Object.keys(modules))
|
|
199
156
|
|
|
200
157
|
// .jz = strict (no auto-transform), .js = auto-jzify
|
|
201
158
|
// --strict forces strict for any extension
|
|
@@ -216,7 +173,7 @@ async function handleCompile(args) {
|
|
|
216
173
|
opts.imports = JSON.parse(readFileSync(importsPath, 'utf8'))
|
|
217
174
|
}
|
|
218
175
|
|
|
219
|
-
const result = compile(
|
|
176
|
+
const result = compile(codeRewritten, opts)
|
|
220
177
|
|
|
221
178
|
if (outputFile === '-') {
|
|
222
179
|
process.stdout.write(result)
|
package/index.js
CHANGED
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
* Feature flags (ctx.features.*) gate conditional stdlib branches for dead-code elimination.
|
|
34
34
|
* Capability hooks (ctx.schema.register, ctx.closure.make) are installed by capability modules.
|
|
35
35
|
*
|
|
36
|
-
* Interop host layer (memory marshaling, wrap, instantiate) lives in
|
|
36
|
+
* Interop host layer (memory marshaling, wrap, instantiate) lives in
|
|
37
|
+
* interop.js — also exported as the standalone `jz/interop` subpath for
|
|
38
|
+
* hosts that want to run prebuilt jz wasm without pulling the compiler.
|
|
37
39
|
*
|
|
38
40
|
* @module jz
|
|
39
41
|
*/
|
|
@@ -44,12 +46,11 @@ import { ctx, reset, err } from './src/ctx.js'
|
|
|
44
46
|
import prepare, { GLOBALS } from './src/prepare.js'
|
|
45
47
|
import compile from './src/compile.js'
|
|
46
48
|
import { emitter } from './src/emit.js'
|
|
47
|
-
import { optimizeFunc, resolveOptimize } from './src/optimize.js'
|
|
48
|
-
import { detectOptimizeConfig } from './src/auto-config.js'
|
|
49
|
+
import { optimizeFunc, collectVolatileGlobals, resolveOptimize } from './src/optimize.js'
|
|
49
50
|
import jzify from './src/jzify.js'
|
|
50
51
|
import {
|
|
51
52
|
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
52
|
-
} from './
|
|
53
|
+
} from './interop.js'
|
|
53
54
|
|
|
54
55
|
// A host import that's a JS function may hand back any value, including a host
|
|
55
56
|
// object — which arrives in wasm as a PTR.EXTERNAL ref. Constants/typed specs can't.
|
|
@@ -164,8 +165,11 @@ jz.memory = enhanceMemory
|
|
|
164
165
|
* @param {boolean|number|object} [opts.optimize] - Optimization level/config.
|
|
165
166
|
* - `false` / `0`: nothing. Fastest compile, largest output (live coding).
|
|
166
167
|
* - `1`: encoding-compactness only (treeshake + sortLocalsByUse + fusedRewrite-inline).
|
|
167
|
-
* - `true` / `2` (default):
|
|
168
|
-
*
|
|
168
|
+
* - `true` / `2` (default): every stable jz pass + full watr (inlineOnce +
|
|
169
|
+
* coalesce on; `inline` stays off per watr's own default).
|
|
170
|
+
* - `3` / `'speed'`: level 2 + larger array/hash initial caps (`arrayMinCap`,
|
|
171
|
+
* `hashSmallInitCap`) + `hoistConstantPool` off (inline `f64.const` over
|
|
172
|
+
* mutable globals); trades size for speed.
|
|
169
173
|
* - `{ level?: 0|1|2|3, watr?: bool, hoistAddrBase?: bool, ... }`: per-pass
|
|
170
174
|
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
171
175
|
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
@@ -175,9 +179,120 @@ jz.memory = enhanceMemory
|
|
|
175
179
|
* @param {boolean} [opts.profileNames] - Legacy alias for `profile.names`.
|
|
176
180
|
* @param {string} [opts.importMetaUrl] - Module URL used to lower `import.meta.url`
|
|
177
181
|
* and static `import.meta.resolve("...")` expressions.
|
|
178
|
-
* @
|
|
182
|
+
* @param {boolean} [opts.inspect] - When true, return `{ wasm, inspect }`
|
|
183
|
+
* (or `{ wat, inspect }` with `opts.wat`) instead of the bare output.
|
|
184
|
+
* `inspect` carries per-function inferred shapes (params, locals, JSON shapes,
|
|
185
|
+
* cross-call paramReps) for editor hosts to drive inlay hints / hover types
|
|
186
|
+
* without re-running the analyzer. Pays a small serialization cost; off by default.
|
|
187
|
+
* @returns {Uint8Array|string|{wasm: Uint8Array, inspect: object}|{wat: string, inspect: object}}
|
|
179
188
|
*/
|
|
180
189
|
jz.compile = (code, opts = {}) => {
|
|
190
|
+
try {
|
|
191
|
+
return jzCompileInner(code, opts)
|
|
192
|
+
} catch (e) {
|
|
193
|
+
// Any uncaught native exception (TypeError, ReferenceError, etc.) is a jz
|
|
194
|
+
// codegen leak — surface it as an internal compile error with the source
|
|
195
|
+
// location we were standing on. err()-thrown errors already pass through.
|
|
196
|
+
if (e?.name === 'TypeError' || e?.name === 'ReferenceError' || e?.name === 'RangeError') {
|
|
197
|
+
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.`)
|
|
198
|
+
}
|
|
199
|
+
throw e
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// =============================================================================
|
|
204
|
+
// Optimization auto-tuning: scan prepared AST + ctx.func.list to infer program
|
|
205
|
+
// properties, then emit per-pass overrides. When the user does not explicitly
|
|
206
|
+
// configure individual passes, the result is merged in before resolveOptimize()
|
|
207
|
+
// so the compiler self-tunes.
|
|
208
|
+
// =============================================================================
|
|
209
|
+
|
|
210
|
+
const AUTO_CFG_LOOP_OPS = new Set(['for', 'while', 'do', 'do-while'])
|
|
211
|
+
const AUTO_CFG_TYPED_CTORS = new Set([
|
|
212
|
+
'new.Float32Array', 'new.Float64Array', 'new.Int8Array', 'new.Int16Array',
|
|
213
|
+
'new.Int32Array', 'new.Uint8Array', 'new.Uint16Array', 'new.Uint32Array',
|
|
214
|
+
'new.Uint8ClampedArray',
|
|
215
|
+
])
|
|
216
|
+
|
|
217
|
+
const autoCfgNodeSize = (node) => {
|
|
218
|
+
if (!Array.isArray(node)) return 1
|
|
219
|
+
let n = 1
|
|
220
|
+
for (let i = 1; i < node.length; i++) n += autoCfgNodeSize(node[i])
|
|
221
|
+
return n
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const autoCfgScanNode = (node, stats, loopDepth) => {
|
|
225
|
+
if (!Array.isArray(node)) return
|
|
226
|
+
const op = node[0]
|
|
227
|
+
if (AUTO_CFG_LOOP_OPS.has(op)) {
|
|
228
|
+
stats.loopCount++
|
|
229
|
+
const d = loopDepth + 1
|
|
230
|
+
if (d > stats.maxLoopDepth) stats.maxLoopDepth = d
|
|
231
|
+
for (let i = 1; i < node.length; i++) autoCfgScanNode(node[i], stats, d)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
if (op === '()') {
|
|
235
|
+
stats.callSites++
|
|
236
|
+
const callee = node[1]
|
|
237
|
+
if (typeof callee === 'string' && AUTO_CFG_TYPED_CTORS.has(callee)) {
|
|
238
|
+
stats.typedArrayCount++
|
|
239
|
+
const args = node[2]
|
|
240
|
+
const argList = args == null ? [] : (Array.isArray(args) && args[0] === ',') ? args.slice(1) : [args]
|
|
241
|
+
const lenLit = typeof argList[0] === 'number' ? argList[0] : null
|
|
242
|
+
if (lenLit != null && lenLit > stats.maxTypedArrayLen) stats.maxTypedArrayLen = lenLit
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (op === 'str') stats.stringLiteralCount++
|
|
246
|
+
if (op === '=>') stats.closureCount++
|
|
247
|
+
for (let i = 1; i < node.length; i++) autoCfgScanNode(node[i], stats, loopDepth)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Detect optimization config from source characteristics.
|
|
251
|
+
* Returns an object of pass overrides; empty object means "use defaults". */
|
|
252
|
+
const detectOptimizeConfig = (ast, code) => {
|
|
253
|
+
const s = {
|
|
254
|
+
sourceChars: code?.length || 0,
|
|
255
|
+
funcCount: 0, maxFuncBodySize: 0,
|
|
256
|
+
loopCount: 0, maxLoopDepth: 0,
|
|
257
|
+
typedArrayCount: 0, maxTypedArrayLen: 0,
|
|
258
|
+
stringLiteralCount: 0, closureCount: 0, callSites: 0,
|
|
259
|
+
}
|
|
260
|
+
if (ctx.func?.list) {
|
|
261
|
+
s.funcCount = ctx.func.list.length
|
|
262
|
+
for (const f of ctx.func.list) {
|
|
263
|
+
if (f.body) {
|
|
264
|
+
const sz = autoCfgNodeSize(f.body)
|
|
265
|
+
if (sz > s.maxFuncBodySize) s.maxFuncBodySize = sz
|
|
266
|
+
autoCfgScanNode(f.body, s, 0)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (ast) autoCfgScanNode(ast, s, 0)
|
|
271
|
+
|
|
272
|
+
const cfg = {}
|
|
273
|
+
// 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.
|
|
275
|
+
const isLarge = s.sourceChars > 4000 || s.funcCount > 40 || s.maxFuncBodySize > 300
|
|
276
|
+
const isMachineLike = s.callSites > 300 && s.stringLiteralCount < 10
|
|
277
|
+
if (isLarge || isMachineLike) cfg.watr = false
|
|
278
|
+
// Typed-array heavy: tighten scalarization thresholds when we see large
|
|
279
|
+
// fixed-size arrays; keep defaults for small/dynamic ones.
|
|
280
|
+
if (s.typedArrayCount > 0 && s.maxTypedArrayLen > 0) {
|
|
281
|
+
cfg.scalarTypedArrayLen = Math.min(32, Math.max(8, s.maxTypedArrayLen + 4))
|
|
282
|
+
cfg.scalarTypedLoopUnroll = s.maxLoopDepth > 1 ? 8 : 16
|
|
283
|
+
cfg.scalarTypedNestedUnroll = s.maxLoopDepth > 1 ? 32 : 128
|
|
284
|
+
}
|
|
285
|
+
// String-heavy: ensure pool sorting is on (already default, but explicit).
|
|
286
|
+
if (s.stringLiteralCount > 30) cfg.sortStrPoolByFreq = true
|
|
287
|
+
// Closure-heavy: ptr hoists pay off.
|
|
288
|
+
if (s.closureCount > 4) {
|
|
289
|
+
cfg.hoistPtrType = true
|
|
290
|
+
cfg.hoistInvariantPtrOffset = true
|
|
291
|
+
}
|
|
292
|
+
return cfg
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const jzCompileInner = (code, opts = {}) => {
|
|
181
296
|
const profiler = compileProfiler(opts.profile)
|
|
182
297
|
const time = (name, fn) => profiler ? profiler.time(name, fn) : fn()
|
|
183
298
|
|
|
@@ -197,10 +312,12 @@ jz.compile = (code, opts = {}) => {
|
|
|
197
312
|
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
198
313
|
if (opts.strict) ctx.transform.strict = true
|
|
199
314
|
if (opts.host) {
|
|
200
|
-
if (opts.host
|
|
315
|
+
if (opts.host === 'gc') err(`host:'gc' is reserved for a planned wasm-gc backend, not yet implemented. Use 'js' (default — JS host with externref/js-string interop) or 'wasi' (standalone runtimes — no env imports).`)
|
|
316
|
+
if (opts.host !== 'js' && opts.host !== 'wasi') err(`Invalid host '${opts.host}'. Expected 'js' (default) or 'wasi'.`)
|
|
201
317
|
ctx.transform.host = opts.host
|
|
202
318
|
}
|
|
203
319
|
if (opts.alloc === false) ctx.transform.alloc = false
|
|
320
|
+
if (opts.inspect) ctx.transform.inspect = true
|
|
204
321
|
if (opts.importMetaUrl) ctx.transform.importMetaUrl = String(opts.importMetaUrl)
|
|
205
322
|
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
206
323
|
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
@@ -253,23 +370,9 @@ jz.compile = (code, opts = {}) => {
|
|
|
253
370
|
const cfg = ctx.transform.optimize
|
|
254
371
|
// watr's `loopify` collapses the `(block $brk (loop … (br_if $brk !cond) … (br $loop)))`
|
|
255
372
|
// idiom into `(loop … (if cond …))` — sound, but destroys the exact shape jz's
|
|
256
|
-
// post-watr vectorizer scans for. Disable loopify when vectorize is going to run
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
// true / 'full' → all default passes (includes `inlineOnce` / `inline` / `coalesce`)
|
|
260
|
-
// 'light' → everything except inlining + coalesce. Default at level 2.
|
|
261
|
-
// `inlineOnce` reshapes codegen the way slot-type / LICM tests scan for,
|
|
262
|
-
// and miscompiles regex `split(/\s+/)`. `coalesceLocals` on its own (i.e.
|
|
263
|
-
// without the cleanup that follows inlining) breaks `/a.+b/.test("ab")`
|
|
264
|
-
// — likely a stale alias the post-inline propagate sweep normally tidies
|
|
265
|
-
// up at L3. Dropping both still keeps treeshake / dedupe / dedupTypes /
|
|
266
|
-
// propagate / packData / fold / peephole / vacuum / mergeBlocks / brif /
|
|
267
|
-
// loopify / offset / unbranch / identity / strength / globals etc.
|
|
268
|
-
// false → skip watr entirely (level 0/1).
|
|
269
|
-
let watrOpts
|
|
270
|
-
if (cfg.watr === 'light') watrOpts = { inline: false, inlineOnce: false, coalesce: false, loopify: !cfg.vectorizeLaneLocal }
|
|
271
|
-
else if (cfg.vectorizeLaneLocal) watrOpts = { loopify: false }
|
|
272
|
-
else watrOpts = true
|
|
373
|
+
// post-watr vectorizer scans for. Disable loopify when vectorize is going to run;
|
|
374
|
+
// otherwise hand watr its full default pass set (inlineOnce + coalesce on, inline off).
|
|
375
|
+
const watrOpts = cfg.vectorizeLaneLocal ? { loopify: false } : true
|
|
273
376
|
const optimized = cfg.watr ? time('watrOptimize', () => watrOptimize(module, watrOpts)) : module
|
|
274
377
|
// Final peephole pass: watrOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
275
378
|
// (e.g. inlined closure body's `i32.wrap_i64 (i64.reinterpret_f64 __env)` next to caller's
|
|
@@ -280,12 +383,35 @@ jz.compile = (code, opts = {}) => {
|
|
|
280
383
|
// Build global name→type map from ctx.scope.globalTypes for promoteGlobals
|
|
281
384
|
const globalTypesMap = ctx.scope.globalTypes ? new Map([...ctx.scope.globalTypes].map(([k, v]) => [`$${k}`, v])) : null
|
|
282
385
|
time('watrReopt', () => {
|
|
283
|
-
|
|
386
|
+
const funcs = optimized.filter(node => Array.isArray(node) && node[0] === 'func')
|
|
387
|
+
const volatileGlobals = collectVolatileGlobals(funcs)
|
|
388
|
+
for (const node of funcs) optimizeFunc(node, postCfg, globalTypesMap, volatileGlobals)
|
|
284
389
|
})
|
|
285
390
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
391
|
+
try {
|
|
392
|
+
if (opts.wat) {
|
|
393
|
+
const wat = time('watrPrint', () => watrPrint(optimized))
|
|
394
|
+
return opts.inspect ? { wat, inspect: ctx.inspect } : wat
|
|
395
|
+
}
|
|
396
|
+
const wasm = time('watrCompile', () => watrCompile(optimized))
|
|
397
|
+
let bytes = wasm
|
|
398
|
+
if (opts.profileNames || opts.profile?.names) bytes = appendFunctionNames(bytes, optimized)
|
|
399
|
+
return opts.inspect ? { wasm: bytes, inspect: ctx.inspect } : bytes
|
|
400
|
+
} catch (e) {
|
|
401
|
+
// watr surfaces dangling identifiers as "Unknown local|func|global|table|memory $X".
|
|
402
|
+
// That's always a jz codegen leak — we emitted IR that references something never
|
|
403
|
+
// declared (typically: a built-in / stdlib we don't implement). Rewrite to a clean
|
|
404
|
+
// user-facing message instead of leaking watr internals.
|
|
405
|
+
const m = /Unknown (local|func|global|table|memory|type) \$?(\S+)/.exec(e?.message || '')
|
|
406
|
+
if (m) {
|
|
407
|
+
const [, kind, name] = m
|
|
408
|
+
const friendly = kind === 'func' ? `'${name}' is not a known function or built-in`
|
|
409
|
+
: kind === 'global' ? `'${name}' is not a known global or imported binding`
|
|
410
|
+
: `'${name}' is not in scope`
|
|
411
|
+
err(`${friendly} — jz emitted a reference it cannot resolve (likely an unsupported built-in or missing import).`)
|
|
412
|
+
}
|
|
413
|
+
throw e
|
|
414
|
+
}
|
|
289
415
|
}
|
|
290
416
|
|
|
291
417
|
/**
|
|
@@ -344,7 +470,8 @@ export default function jz(code, ...args) {
|
|
|
344
470
|
}
|
|
345
471
|
if (hoisted.length) src = hoisted.join('; ') + '; ' + src
|
|
346
472
|
const hasInterp = Object.keys(interp).length
|
|
347
|
-
const
|
|
473
|
+
const tplOpts = { _interp: hasInterp ? interp : null }
|
|
474
|
+
const result = instantiateRuntime(jz.compile(src, tplOpts), tplOpts)
|
|
348
475
|
// Patch data getters: allocate values in WASM memory, update closure refs
|
|
349
476
|
for (const [, { val, ref }] of Object.entries(data)) {
|
|
350
477
|
if (typeof val === 'string') ref.ptr = result.memory.String(val)
|
|
@@ -355,7 +482,15 @@ export default function jz(code, ...args) {
|
|
|
355
482
|
}
|
|
356
483
|
|
|
357
484
|
// String call: jz('code', opts?) — compile + instantiate + wrap
|
|
358
|
-
|
|
485
|
+
const callOpts = args[0] || {}
|
|
486
|
+
const out = jz.compile(code, callOpts)
|
|
487
|
+
// inspect:true returns { wasm, inspect } from jz.compile — unwrap the bytes for
|
|
488
|
+
// instantiation and surface inspect on the runtime result alongside exports/memory.
|
|
489
|
+
if (callOpts.inspect && out && typeof out === 'object' && 'wasm' in out) {
|
|
490
|
+
const result = instantiateRuntime(out.wasm, callOpts)
|
|
491
|
+
return Object.assign(result, { inspect: out.inspect })
|
|
492
|
+
}
|
|
493
|
+
return instantiateRuntime(out, callOpts)
|
|
359
494
|
}
|
|
360
495
|
|
|
361
496
|
export { jz }
|