jz 0.1.1 → 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 +257 -90
- package/cli.js +34 -8
- package/index.js +102 -13
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +691 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +434 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +626 -132
- package/src/autoload.js +14 -6
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +634 -145
- package/src/host.js +244 -51
- package/src/ir.js +81 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1132 -4
- package/src/prepare.js +321 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/index.js
CHANGED
|
@@ -38,15 +38,14 @@
|
|
|
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
45
|
import compile from './src/compile.js'
|
|
46
46
|
import { emitter } from './src/emit.js'
|
|
47
47
|
import { optimizeFunc, resolveOptimize } from './src/optimize.js'
|
|
48
48
|
import jzify from './src/jzify.js'
|
|
49
|
-
import { normalizeSource } from './src/source.js'
|
|
50
49
|
import {
|
|
51
50
|
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
52
51
|
} from './src/host.js'
|
|
@@ -67,7 +66,8 @@ const importsMayReturnExternal = (imports) => {
|
|
|
67
66
|
const nowMs = () => globalThis.performance?.now ? globalThis.performance.now() : Date.now()
|
|
68
67
|
|
|
69
68
|
const compileProfiler = (profile) => {
|
|
70
|
-
if (
|
|
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
71
|
profile.entries ||= []
|
|
72
72
|
profile.totals ||= {}
|
|
73
73
|
return {
|
|
@@ -83,6 +83,63 @@ const compileProfiler = (profile) => {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
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
|
+
|
|
86
143
|
/**
|
|
87
144
|
* jz — JS subset → WASM compiler.
|
|
88
145
|
*
|
|
@@ -105,8 +162,12 @@ jz.memory = enhanceMemory
|
|
|
105
162
|
* @param {boolean} [opts.strict] - Reject dynamic features (obj[k], for-in, unknown
|
|
106
163
|
* receiver method calls) at compile time. Avoids pulling dynamic-dispatch stdlib
|
|
107
164
|
* into output; large size win for static programs.
|
|
108
|
-
* @param {
|
|
109
|
-
*
|
|
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
|
|
110
171
|
* modules that only call exported wasm functions.
|
|
111
172
|
* @param {boolean|number|object} [opts.optimize] - Optimization level/config.
|
|
112
173
|
* - `false` / `0`: nothing. Fastest compile, largest output (live coding).
|
|
@@ -117,6 +178,11 @@ jz.memory = enhanceMemory
|
|
|
117
178
|
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
118
179
|
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
119
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.
|
|
120
186
|
* @returns {Uint8Array|string}
|
|
121
187
|
*/
|
|
122
188
|
jz.compile = (code, opts = {}) => {
|
|
@@ -126,7 +192,8 @@ jz.compile = (code, opts = {}) => {
|
|
|
126
192
|
reset(emitter, GLOBALS)
|
|
127
193
|
ctx.error.src = code
|
|
128
194
|
|
|
129
|
-
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
|
|
130
197
|
if (opts.memoryPages) ctx.memory.pages = opts.memoryPages
|
|
131
198
|
if (opts.modules) ctx.module.importSources = opts.modules
|
|
132
199
|
if (opts.imports) {
|
|
@@ -138,24 +205,45 @@ jz.compile = (code, opts = {}) => {
|
|
|
138
205
|
if (opts.jzify) ctx.transform.jzify = jzify
|
|
139
206
|
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
140
207
|
if (opts.strict) ctx.transform.strict = true
|
|
141
|
-
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)
|
|
142
214
|
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
143
215
|
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
144
216
|
|
|
145
217
|
if (opts._interp) {
|
|
146
218
|
for (const [name, fn] of Object.entries(opts._interp)) {
|
|
147
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.`)
|
|
148
221
|
ctx.features.external = true
|
|
149
222
|
const params = Array(fn.length).fill(['param', 'f64'])
|
|
150
223
|
ctx.module.imports.push(['import', '"env"', `"${name}"`, ['func', `$${name}`, ...params, ['result', 'f64']]])
|
|
151
224
|
}
|
|
152
225
|
}
|
|
153
226
|
|
|
154
|
-
let parsed = time('parse', () => parse(
|
|
227
|
+
let parsed = time('parse', () => parse(code))
|
|
155
228
|
if (opts.jzify) parsed = time('jzify', () => jzify(parsed))
|
|
156
229
|
const ast = time('prepare', () => prepare(parsed))
|
|
157
230
|
const module = time('compile', () => compile(ast, profiler))
|
|
158
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
|
+
}
|
|
246
|
+
|
|
159
247
|
const cfg = ctx.transform.optimize
|
|
160
248
|
const optimized = cfg.watr ? time('watrOptimize', () => watrOptimize(module)) : module
|
|
161
249
|
// Final peephole pass: watrOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
@@ -163,13 +251,14 @@ jz.compile = (code, opts = {}) => {
|
|
|
163
251
|
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
164
252
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
165
253
|
if (cfg.watr) {
|
|
254
|
+
const postCfg = { ...cfg, __phase: 'post' }
|
|
166
255
|
time('watrReopt', () => {
|
|
167
|
-
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node,
|
|
256
|
+
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, postCfg)
|
|
168
257
|
})
|
|
169
258
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
173
262
|
}
|
|
174
263
|
|
|
175
264
|
/**
|