jz 0.1.0 → 0.2.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 +221 -62
- package/cli.js +34 -8
- package/index.js +133 -14
- package/module/array.js +399 -131
- package/module/collection.js +457 -212
- package/module/console.js +170 -87
- package/module/core.js +247 -143
- package/module/date.js +691 -0
- package/module/function.js +84 -23
- package/module/index.js +2 -1
- package/module/json.js +485 -138
- package/module/math.js +81 -40
- package/module/number.js +189 -83
- package/module/object.js +228 -46
- package/module/regex.js +34 -30
- package/module/schema.js +17 -18
- package/module/string.js +483 -227
- package/module/symbol.js +6 -4
- package/module/timer.js +90 -32
- package/module/typedarray.js +176 -44
- package/package.json +5 -10
- package/src/analyze.js +639 -124
- package/src/autoload.js +183 -0
- package/src/compile.js +448 -1083
- package/src/ctx.js +42 -12
- package/src/emit.js +646 -147
- package/src/host.js +273 -68
- package/src/ir.js +81 -31
- package/src/jzify.js +220 -36
- package/src/narrow.js +956 -0
- package/src/optimize.js +628 -42
- package/src/plan.js +1233 -0
- package/src/prepare.js +438 -256
- package/src/vectorize.js +1016 -0
- package/wasi.js +23 -4
package/index.js
CHANGED
|
@@ -38,11 +38,12 @@
|
|
|
38
38
|
* @module jz
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import { parse } from 'subscript/jessie'
|
|
41
|
+
import { parse } from 'subscript/feature/jessie'
|
|
42
42
|
import { compile as watrCompile, print as watrPrint, optimize as watrOptimize } from "watr";
|
|
43
|
-
import { ctx, reset } from './src/ctx.js'
|
|
43
|
+
import { ctx, reset, err } from './src/ctx.js'
|
|
44
44
|
import prepare, { GLOBALS } from './src/prepare.js'
|
|
45
|
-
import compile
|
|
45
|
+
import compile from './src/compile.js'
|
|
46
|
+
import { emitter } from './src/emit.js'
|
|
46
47
|
import { optimizeFunc, resolveOptimize } from './src/optimize.js'
|
|
47
48
|
import jzify from './src/jzify.js'
|
|
48
49
|
import {
|
|
@@ -62,6 +63,83 @@ const importsMayReturnExternal = (imports) => {
|
|
|
62
63
|
return false
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
const nowMs = () => globalThis.performance?.now ? globalThis.performance.now() : Date.now()
|
|
67
|
+
|
|
68
|
+
const compileProfiler = (profile) => {
|
|
69
|
+
if (profile == null) return null
|
|
70
|
+
if (typeof profile !== 'object') throw new TypeError('opts.profile must be an object sink; use { profile: { names: true } } to emit a wasm name section')
|
|
71
|
+
profile.entries ||= []
|
|
72
|
+
profile.totals ||= {}
|
|
73
|
+
return {
|
|
74
|
+
time(name, fn) {
|
|
75
|
+
const start = nowMs()
|
|
76
|
+
try { return fn() }
|
|
77
|
+
finally {
|
|
78
|
+
const ms = nowMs() - start
|
|
79
|
+
profile.entries.push({ name, ms })
|
|
80
|
+
profile.totals[name] = (profile.totals[name] || 0) + ms
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const uleb = (n) => {
|
|
87
|
+
const out = []
|
|
88
|
+
do {
|
|
89
|
+
let b = n & 0x7f
|
|
90
|
+
n >>>= 7
|
|
91
|
+
if (n) b |= 0x80
|
|
92
|
+
out.push(b)
|
|
93
|
+
} while (n)
|
|
94
|
+
return out
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const utf8Bytes = (s) => [...new TextEncoder().encode(s)]
|
|
98
|
+
const nameBytes = (s) => {
|
|
99
|
+
const bytes = utf8Bytes(s)
|
|
100
|
+
return [...uleb(bytes.length), ...bytes]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const watName = (s) => typeof s === 'string' && s.startsWith('$') ? s.slice(1) : null
|
|
104
|
+
const quotedName = (s) => typeof s === 'string' && /^".*"$/.test(s) ? s.slice(1, -1) : null
|
|
105
|
+
|
|
106
|
+
const importFuncName = (node) => {
|
|
107
|
+
if (!Array.isArray(node) || node[0] !== 'import') return null
|
|
108
|
+
const desc = node[3]
|
|
109
|
+
if (!Array.isArray(desc) || desc[0] !== 'func') return null
|
|
110
|
+
return watName(desc[1]) || quotedName(node[2])
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const functionNameSection = (module) => {
|
|
114
|
+
const entries = []
|
|
115
|
+
let funcIdx = 0
|
|
116
|
+
for (const node of module) {
|
|
117
|
+
if (!Array.isArray(node)) continue
|
|
118
|
+
if (node[0] === 'import') {
|
|
119
|
+
const name = importFuncName(node)
|
|
120
|
+
if (name != null) entries.push([funcIdx++, name])
|
|
121
|
+
} else if (node[0] === 'func') {
|
|
122
|
+
const name = watName(node[1])
|
|
123
|
+
if (name != null) entries.push([funcIdx, name])
|
|
124
|
+
funcIdx++
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!entries.length) return null
|
|
128
|
+
const map = [...uleb(entries.length)]
|
|
129
|
+
for (const [idx, name] of entries) map.push(...uleb(idx), ...nameBytes(name))
|
|
130
|
+
const payload = [...nameBytes('name'), 1, ...uleb(map.length), ...map]
|
|
131
|
+
return Uint8Array.from([0, ...uleb(payload.length), ...payload])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const appendFunctionNames = (wasm, module) => {
|
|
135
|
+
const section = functionNameSection(module)
|
|
136
|
+
if (!section) return wasm
|
|
137
|
+
const out = new Uint8Array(wasm.length + section.length)
|
|
138
|
+
out.set(wasm)
|
|
139
|
+
out.set(section, wasm.length)
|
|
140
|
+
return out
|
|
141
|
+
}
|
|
142
|
+
|
|
65
143
|
/**
|
|
66
144
|
* jz — JS subset → WASM compiler.
|
|
67
145
|
*
|
|
@@ -84,8 +162,12 @@ jz.memory = enhanceMemory
|
|
|
84
162
|
* @param {boolean} [opts.strict] - Reject dynamic features (obj[k], for-in, unknown
|
|
85
163
|
* receiver method calls) at compile time. Avoids pulling dynamic-dispatch stdlib
|
|
86
164
|
* into output; large size win for static programs.
|
|
87
|
-
* @param {
|
|
88
|
-
*
|
|
165
|
+
* @param {WebAssembly.Memory|number} [opts.memory] - Shared memory import, or
|
|
166
|
+
* initial page count for owned memory when a number is passed.
|
|
167
|
+
* @param {number} [opts.memoryPages] - Legacy alias for owned/imported memory
|
|
168
|
+
* initial page count. Prefer `memory: N` for owned memory.
|
|
169
|
+
* @param {boolean} [opts.alloc=true] - Export raw allocator helpers
|
|
170
|
+
* (`_alloc`, `_clear`) for JS memory wrapping. Set false for standalone host-run
|
|
89
171
|
* modules that only call exported wasm functions.
|
|
90
172
|
* @param {boolean|number|object} [opts.optimize] - Optimization level/config.
|
|
91
173
|
* - `false` / `0`: nothing. Fastest compile, largest output (live coding).
|
|
@@ -94,13 +176,24 @@ jz.memory = enhanceMemory
|
|
|
94
176
|
* - `3`: reserved for future aggressive passes (currently == 2).
|
|
95
177
|
* - `{ level?: 0|1|2|3, watr?: bool, hoistAddrBase?: bool, ... }`: per-pass
|
|
96
178
|
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
179
|
+
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
180
|
+
* `entries` and `totals` for parse / jzify / prepare / compile / plan / watr phases.
|
|
181
|
+
* Set `profile.names = true` to also emit a standard wasm `name` custom section
|
|
182
|
+
* for profiler/debugger symbolication.
|
|
183
|
+
* @param {boolean} [opts.profileNames] - Legacy alias for `profile.names`.
|
|
184
|
+
* @param {string} [opts.importMetaUrl] - Module URL used to lower `import.meta.url`
|
|
185
|
+
* and static `import.meta.resolve("...")` expressions.
|
|
97
186
|
* @returns {Uint8Array|string}
|
|
98
187
|
*/
|
|
99
188
|
jz.compile = (code, opts = {}) => {
|
|
189
|
+
const profiler = compileProfiler(opts.profile)
|
|
190
|
+
const time = (name, fn) => profiler ? profiler.time(name, fn) : fn()
|
|
191
|
+
|
|
100
192
|
reset(emitter, GLOBALS)
|
|
101
193
|
ctx.error.src = code
|
|
102
194
|
|
|
103
|
-
if (opts.memory) ctx.memory.
|
|
195
|
+
if (typeof opts.memory === 'number') ctx.memory.pages = opts.memory
|
|
196
|
+
else if (opts.memory) ctx.memory.shared = true
|
|
104
197
|
if (opts.memoryPages) ctx.memory.pages = opts.memoryPages
|
|
105
198
|
if (opts.modules) ctx.module.importSources = opts.modules
|
|
106
199
|
if (opts.imports) {
|
|
@@ -112,34 +205,60 @@ jz.compile = (code, opts = {}) => {
|
|
|
112
205
|
if (opts.jzify) ctx.transform.jzify = jzify
|
|
113
206
|
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
114
207
|
if (opts.strict) ctx.transform.strict = true
|
|
115
|
-
if (opts.
|
|
208
|
+
if (opts.host) {
|
|
209
|
+
if (opts.host !== 'js' && opts.host !== 'wasi') err(`Invalid host '${opts.host}'. Expected 'js' or 'wasi'.`)
|
|
210
|
+
ctx.transform.host = opts.host
|
|
211
|
+
}
|
|
212
|
+
if (opts.alloc === false) ctx.transform.alloc = false
|
|
213
|
+
if (opts.importMetaUrl) ctx.transform.importMetaUrl = String(opts.importMetaUrl)
|
|
116
214
|
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
117
215
|
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
118
216
|
|
|
119
217
|
if (opts._interp) {
|
|
120
218
|
for (const [name, fn] of Object.entries(opts._interp)) {
|
|
121
219
|
if (name.startsWith('__ext_')) continue;
|
|
220
|
+
if (ctx.transform.host === 'wasi') throw new Error(`host:'wasi' does not support _interp['${name}']: env imports are unavailable in WASI. Implement it natively.`)
|
|
122
221
|
ctx.features.external = true
|
|
123
222
|
const params = Array(fn.length).fill(['param', 'f64'])
|
|
124
223
|
ctx.module.imports.push(['import', '"env"', `"${name}"`, ['func', `$${name}`, ...params, ['result', 'f64']]])
|
|
125
224
|
}
|
|
126
225
|
}
|
|
127
226
|
|
|
128
|
-
let parsed = parse(code)
|
|
129
|
-
if (opts.jzify) parsed = jzify(parsed)
|
|
130
|
-
const ast = prepare(parsed)
|
|
131
|
-
const module = compile(ast)
|
|
227
|
+
let parsed = time('parse', () => parse(code))
|
|
228
|
+
if (opts.jzify) parsed = time('jzify', () => jzify(parsed))
|
|
229
|
+
const ast = time('prepare', () => prepare(parsed))
|
|
230
|
+
const module = time('compile', () => compile(ast, profiler))
|
|
231
|
+
|
|
232
|
+
// host: 'wasi' — error if the wasm would import any env.__ext_* helper. Those exist
|
|
233
|
+
// only to defer to a JS host's value-aware semantics; in a wasmtime/wasmer/deno
|
|
234
|
+
// sandbox the imports either go unsatisfied or are stubbed out and silently produce
|
|
235
|
+
// wrong output. Surface the gap at compile so the caller can pick a comparator,
|
|
236
|
+
// type-annotate the receiver, or wait for native lowering. Read `extImports`
|
|
237
|
+
// (populated in pullStdlib) — `core.includes` has had these removed by then.
|
|
238
|
+
if (ctx.transform.host === 'wasi' && ctx.core.extImports?.size) {
|
|
239
|
+
const ext = [...ctx.core.extImports].sort()
|
|
240
|
+
err(
|
|
241
|
+
`host: 'wasi' — compiled wasm would require JS-host imports that wasmtime/wasmer/deno cannot satisfy:\n ` +
|
|
242
|
+
ext.map(n => `env.${n}`).join('\n ') +
|
|
243
|
+
`\nThis happens when jz falls through to dynamic dispatch for a method or property without a native lowering. ` +
|
|
244
|
+
`Either annotate the receiver type, switch to a natively-supported method, or compile with the default host.`)
|
|
245
|
+
}
|
|
132
246
|
|
|
133
247
|
const cfg = ctx.transform.optimize
|
|
134
|
-
const optimized = cfg.watr ? watrOptimize(module) : module
|
|
248
|
+
const optimized = cfg.watr ? time('watrOptimize', () => watrOptimize(module)) : module
|
|
135
249
|
// Final peephole pass: watrOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
136
250
|
// (e.g. inlined closure body's `i32.wrap_i64 (i64.reinterpret_f64 __env)` next to caller's
|
|
137
251
|
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
138
252
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
139
253
|
if (cfg.watr) {
|
|
140
|
-
|
|
254
|
+
const postCfg = { ...cfg, __phase: 'post' }
|
|
255
|
+
time('watrReopt', () => {
|
|
256
|
+
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, postCfg)
|
|
257
|
+
})
|
|
141
258
|
}
|
|
142
|
-
|
|
259
|
+
if (opts.wat) return time('watrPrint', () => watrPrint(optimized))
|
|
260
|
+
const wasm = time('watrCompile', () => watrCompile(optimized))
|
|
261
|
+
return (opts.profileNames || opts.profile?.names) ? appendFunctionNames(wasm, optimized) : wasm
|
|
143
262
|
}
|
|
144
263
|
|
|
145
264
|
/**
|