jz 0.3.1 → 0.5.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 +277 -258
- package/cli.js +10 -52
- package/index.js +181 -29
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +195 -76
- package/module/collection.js +249 -20
- package/module/console.js +1 -1
- package/module/core.js +267 -119
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +367 -61
- package/module/math.js +568 -128
- package/module/number.js +390 -227
- package/module/object.js +352 -39
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +555 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +12 -6
- 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 +1872 -487
- package/src/assemble.js +58 -20
- package/src/autoload.js +13 -1
- package/src/codegen.js +177 -0
- package/src/compile.js +462 -186
- package/src/ctx.js +85 -18
- package/src/emit.js +668 -197
- package/src/infer.js +548 -0
- package/src/ir.js +302 -27
- package/src/jzify.js +898 -259
- package/src/narrow.js +455 -246
- package/src/optimize.js +263 -99
- package/src/plan.js +713 -35
- package/src/prepare.js +818 -293
- 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/cli.js
CHANGED
|
@@ -4,13 +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
|
-
import jzifyFn
|
|
12
|
+
import jzifyFn from './src/jzify.js'
|
|
13
|
+
import { codegen } from './src/codegen.js'
|
|
14
|
+
import { resolveModuleGraph } from './src/resolve.js'
|
|
14
15
|
import { createRequire } from 'module'
|
|
15
16
|
|
|
16
17
|
const jzRequire = createRequire(import.meta.url)
|
|
@@ -148,53 +149,10 @@ async function handleCompile(args) {
|
|
|
148
149
|
if (!outputFile) outputFile = inputFile.replace(/\.(js|jz)$/, wat ? '.wat' : '.wasm')
|
|
149
150
|
if (outputFile.endsWith('.wat')) wat = true
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const modules = {}
|
|
156
|
-
|
|
157
|
-
const pkgFile = join(dir, 'package.json')
|
|
158
|
-
if (existsSync(pkgFile)) {
|
|
159
|
-
try {
|
|
160
|
-
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'))
|
|
161
|
-
if (pkg.imports) for (const [spec, path] of Object.entries(pkg.imports)) {
|
|
162
|
-
const full = resolve(dir, path)
|
|
163
|
-
try { modules[spec] = readFileSync(full, 'utf8') } catch {}
|
|
164
|
-
}
|
|
165
|
-
} catch {}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Recursively resolve relative imports from entry file and all discovered modules
|
|
169
|
-
const importRe = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g
|
|
170
|
-
const resolveBareModule = (specifier, fromDir) => execFileSync(
|
|
171
|
-
process.execPath,
|
|
172
|
-
['--input-type=module', '-e', 'process.stdout.write(import.meta.resolve(process.argv[1]))', specifier],
|
|
173
|
-
{ cwd: fromDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
174
|
-
).trim()
|
|
175
|
-
const resolveModule = (specifier, fromDir) => {
|
|
176
|
-
if (modules[specifier]) return
|
|
177
|
-
// Relative imports: resolve from filesystem
|
|
178
|
-
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
179
|
-
const full = resolve(fromDir, specifier)
|
|
180
|
-
let src
|
|
181
|
-
try { src = readFileSync(full, 'utf8') }
|
|
182
|
-
catch { try { src = readFileSync(full + '.js', 'utf8') } catch { return } }
|
|
183
|
-
modules[specifier] = src
|
|
184
|
-
let m; importRe.lastIndex = 0
|
|
185
|
-
while ((m = importRe.exec(src)) !== null) resolveModule(m[1], dirname(full))
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
// Bare specifiers: opt-in Node.js resolution
|
|
189
|
-
if (resolveNode) {
|
|
190
|
-
try {
|
|
191
|
-
const resolved = resolveBareModule(specifier, fromDir)
|
|
192
|
-
if (resolved.startsWith('file:')) modules[specifier] = readFileSync(new URL(resolved), 'utf8')
|
|
193
|
-
} catch {}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
let m; importRe.lastIndex = 0
|
|
197
|
-
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))
|
|
198
156
|
|
|
199
157
|
// .jz = strict (no auto-transform), .js = auto-jzify
|
|
200
158
|
// --strict forces strict for any extension
|
|
@@ -215,7 +173,7 @@ async function handleCompile(args) {
|
|
|
215
173
|
opts.imports = JSON.parse(readFileSync(importsPath, 'utf8'))
|
|
216
174
|
}
|
|
217
175
|
|
|
218
|
-
const result = compile(
|
|
176
|
+
const result = compile(codeRewritten, opts)
|
|
219
177
|
|
|
220
178
|
if (outputFile === '-') {
|
|
221
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,25 +46,17 @@ 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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const importsMayReturnExternal = (imports) => {
|
|
60
|
-
if (!imports) return false
|
|
61
|
-
for (const mod of Object.values(imports))
|
|
62
|
-
for (const spec of Object.values(mod || {}))
|
|
63
|
-
if (importSpecMayReturnExternal(spec)) return true
|
|
64
|
-
return false
|
|
65
|
-
}
|
|
55
|
+
// A host import that's a JS function may hand back any value, including a host
|
|
56
|
+
// object — which arrives in wasm as a PTR.EXTERNAL ref. Constants/typed specs can't.
|
|
57
|
+
const importsMayReturnExternal = (imports) =>
|
|
58
|
+
!!imports && Object.values(imports).some(mod =>
|
|
59
|
+
Object.values(mod || {}).some(spec => typeof spec === 'function'))
|
|
66
60
|
|
|
67
61
|
const nowMs = () => globalThis.performance?.now ? globalThis.performance.now() : Date.now()
|
|
68
62
|
|
|
@@ -171,8 +165,11 @@ jz.memory = enhanceMemory
|
|
|
171
165
|
* @param {boolean|number|object} [opts.optimize] - Optimization level/config.
|
|
172
166
|
* - `false` / `0`: nothing. Fastest compile, largest output (live coding).
|
|
173
167
|
* - `1`: encoding-compactness only (treeshake + sortLocalsByUse + fusedRewrite-inline).
|
|
174
|
-
* - `true` / `2` (default):
|
|
175
|
-
*
|
|
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.
|
|
176
173
|
* - `{ level?: 0|1|2|3, watr?: bool, hoistAddrBase?: bool, ... }`: per-pass
|
|
177
174
|
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
178
175
|
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
@@ -182,9 +179,120 @@ jz.memory = enhanceMemory
|
|
|
182
179
|
* @param {boolean} [opts.profileNames] - Legacy alias for `profile.names`.
|
|
183
180
|
* @param {string} [opts.importMetaUrl] - Module URL used to lower `import.meta.url`
|
|
184
181
|
* and static `import.meta.resolve("...")` expressions.
|
|
185
|
-
* @
|
|
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}}
|
|
186
188
|
*/
|
|
187
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 = {}) => {
|
|
188
296
|
const profiler = compileProfiler(opts.profile)
|
|
189
297
|
const time = (name, fn) => profiler ? profiler.time(name, fn) : fn()
|
|
190
298
|
|
|
@@ -204,10 +312,12 @@ jz.compile = (code, opts = {}) => {
|
|
|
204
312
|
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
205
313
|
if (opts.strict) ctx.transform.strict = true
|
|
206
314
|
if (opts.host) {
|
|
207
|
-
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'.`)
|
|
208
317
|
ctx.transform.host = opts.host
|
|
209
318
|
}
|
|
210
319
|
if (opts.alloc === false) ctx.transform.alloc = false
|
|
320
|
+
if (opts.inspect) ctx.transform.inspect = true
|
|
211
321
|
if (opts.importMetaUrl) ctx.transform.importMetaUrl = String(opts.importMetaUrl)
|
|
212
322
|
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
213
323
|
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
@@ -227,8 +337,13 @@ jz.compile = (code, opts = {}) => {
|
|
|
227
337
|
const ast = time('prepare', () => prepare(parsed))
|
|
228
338
|
|
|
229
339
|
// Auto-detect optimization tuning from source characteristics when the user
|
|
230
|
-
// hasn't provided any optimize option.
|
|
231
|
-
|
|
340
|
+
// hasn't provided any optimize option. At the default level its only *live*
|
|
341
|
+
// output is the typed-array scalarization thresholds (every other override —
|
|
342
|
+
// watr off, sortStrPool/hoistPtr on — already matches the level-2 preset), so
|
|
343
|
+
// skip the AST scan entirely unless the program touches typed arrays.
|
|
344
|
+
// NOTE: if the level-2 default ever flips watr on, drop this guard so the
|
|
345
|
+
// machine-generated-code heuristic in detectOptimizeConfig can take effect.
|
|
346
|
+
if (opts.optimize == null && ctx.module.modules.typedarray) {
|
|
232
347
|
const autoCfg = detectOptimizeConfig(ast, code)
|
|
233
348
|
if (Object.keys(autoCfg).length) {
|
|
234
349
|
ctx.transform.optimize = resolveOptimize(autoCfg)
|
|
@@ -253,7 +368,12 @@ jz.compile = (code, opts = {}) => {
|
|
|
253
368
|
}
|
|
254
369
|
|
|
255
370
|
const cfg = ctx.transform.optimize
|
|
256
|
-
|
|
371
|
+
// watr's `loopify` collapses the `(block $brk (loop … (br_if $brk !cond) … (br $loop)))`
|
|
372
|
+
// idiom into `(loop … (if cond …))` — sound, but destroys the exact shape jz's
|
|
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
|
|
376
|
+
const optimized = cfg.watr ? time('watrOptimize', () => watrOptimize(module, watrOpts)) : module
|
|
257
377
|
// Final peephole pass: watrOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
258
378
|
// (e.g. inlined closure body's `i32.wrap_i64 (i64.reinterpret_f64 __env)` next to caller's
|
|
259
379
|
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
@@ -263,12 +383,35 @@ jz.compile = (code, opts = {}) => {
|
|
|
263
383
|
// Build global name→type map from ctx.scope.globalTypes for promoteGlobals
|
|
264
384
|
const globalTypesMap = ctx.scope.globalTypes ? new Map([...ctx.scope.globalTypes].map(([k, v]) => [`$${k}`, v])) : null
|
|
265
385
|
time('watrReopt', () => {
|
|
266
|
-
|
|
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)
|
|
267
389
|
})
|
|
268
390
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
}
|
|
272
415
|
}
|
|
273
416
|
|
|
274
417
|
/**
|
|
@@ -327,7 +470,8 @@ export default function jz(code, ...args) {
|
|
|
327
470
|
}
|
|
328
471
|
if (hoisted.length) src = hoisted.join('; ') + '; ' + src
|
|
329
472
|
const hasInterp = Object.keys(interp).length
|
|
330
|
-
const
|
|
473
|
+
const tplOpts = { _interp: hasInterp ? interp : null }
|
|
474
|
+
const result = instantiateRuntime(jz.compile(src, tplOpts), tplOpts)
|
|
331
475
|
// Patch data getters: allocate values in WASM memory, update closure refs
|
|
332
476
|
for (const [, { val, ref }] of Object.entries(data)) {
|
|
333
477
|
if (typeof val === 'string') ref.ptr = result.memory.String(val)
|
|
@@ -338,7 +482,15 @@ export default function jz(code, ...args) {
|
|
|
338
482
|
}
|
|
339
483
|
|
|
340
484
|
// String call: jz('code', opts?) — compile + instantiate + wrap
|
|
341
|
-
|
|
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)
|
|
342
494
|
}
|
|
343
495
|
|
|
344
496
|
export { jz }
|