jz 0.1.1 → 0.2.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 +256 -90
- package/cli.js +34 -8
- package/index.js +100 -14
- 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 +783 -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 +439 -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 +627 -133
- package/src/autoload.js +15 -7
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +658 -145
- package/src/host.js +244 -51
- package/src/ir.js +87 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1167 -4
- package/src/prepare.js +322 -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,10 @@ 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. Prefer `memory: N` for owned memory.
|
|
167
|
+
* @param {boolean} [opts.alloc=true] - Export raw allocator helpers
|
|
168
|
+
* (`_alloc`, `_clear`) for JS memory wrapping. Set false for standalone host-run
|
|
110
169
|
* modules that only call exported wasm functions.
|
|
111
170
|
* @param {boolean|number|object} [opts.optimize] - Optimization level/config.
|
|
112
171
|
* - `false` / `0`: nothing. Fastest compile, largest output (live coding).
|
|
@@ -117,6 +176,11 @@ jz.memory = enhanceMemory
|
|
|
117
176
|
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
118
177
|
* @param {object} [opts.profile] - Optional mutable profile sink populated with
|
|
119
178
|
* `entries` and `totals` for parse / jzify / prepare / compile / plan / watr phases.
|
|
179
|
+
* Set `profile.names = true` to also emit a standard wasm `name` custom section
|
|
180
|
+
* for profiler/debugger symbolication.
|
|
181
|
+
* @param {boolean} [opts.profileNames] - Legacy alias for `profile.names`.
|
|
182
|
+
* @param {string} [opts.importMetaUrl] - Module URL used to lower `import.meta.url`
|
|
183
|
+
* and static `import.meta.resolve("...")` expressions.
|
|
120
184
|
* @returns {Uint8Array|string}
|
|
121
185
|
*/
|
|
122
186
|
jz.compile = (code, opts = {}) => {
|
|
@@ -126,8 +190,8 @@ jz.compile = (code, opts = {}) => {
|
|
|
126
190
|
reset(emitter, GLOBALS)
|
|
127
191
|
ctx.error.src = code
|
|
128
192
|
|
|
129
|
-
if (opts.memory) ctx.memory.
|
|
130
|
-
if (opts.
|
|
193
|
+
if (typeof opts.memory === 'number') ctx.memory.pages = opts.memory
|
|
194
|
+
else if (opts.memory) ctx.memory.shared = true
|
|
131
195
|
if (opts.modules) ctx.module.importSources = opts.modules
|
|
132
196
|
if (opts.imports) {
|
|
133
197
|
ctx.module.hostImports = opts.imports
|
|
@@ -138,24 +202,45 @@ jz.compile = (code, opts = {}) => {
|
|
|
138
202
|
if (opts.jzify) ctx.transform.jzify = jzify
|
|
139
203
|
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
140
204
|
if (opts.strict) ctx.transform.strict = true
|
|
141
|
-
if (opts.
|
|
205
|
+
if (opts.host) {
|
|
206
|
+
if (opts.host !== 'js' && opts.host !== 'wasi') err(`Invalid host '${opts.host}'. Expected 'js' or 'wasi'.`)
|
|
207
|
+
ctx.transform.host = opts.host
|
|
208
|
+
}
|
|
209
|
+
if (opts.alloc === false) ctx.transform.alloc = false
|
|
210
|
+
if (opts.importMetaUrl) ctx.transform.importMetaUrl = String(opts.importMetaUrl)
|
|
142
211
|
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
143
212
|
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
144
213
|
|
|
145
214
|
if (opts._interp) {
|
|
146
215
|
for (const [name, fn] of Object.entries(opts._interp)) {
|
|
147
216
|
if (name.startsWith('__ext_')) continue;
|
|
217
|
+
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
218
|
ctx.features.external = true
|
|
149
219
|
const params = Array(fn.length).fill(['param', 'f64'])
|
|
150
220
|
ctx.module.imports.push(['import', '"env"', `"${name}"`, ['func', `$${name}`, ...params, ['result', 'f64']]])
|
|
151
221
|
}
|
|
152
222
|
}
|
|
153
223
|
|
|
154
|
-
let parsed = time('parse', () => parse(
|
|
224
|
+
let parsed = time('parse', () => parse(code))
|
|
155
225
|
if (opts.jzify) parsed = time('jzify', () => jzify(parsed))
|
|
156
226
|
const ast = time('prepare', () => prepare(parsed))
|
|
157
227
|
const module = time('compile', () => compile(ast, profiler))
|
|
158
228
|
|
|
229
|
+
// host: 'wasi' — error if the wasm would import any env.__ext_* helper. Those exist
|
|
230
|
+
// only to defer to a JS host's value-aware semantics; in a wasmtime/wasmer/deno
|
|
231
|
+
// sandbox the imports either go unsatisfied or are stubbed out and silently produce
|
|
232
|
+
// wrong output. Surface the gap at compile so the caller can pick a comparator,
|
|
233
|
+
// type-annotate the receiver, or wait for native lowering. Read `extImports`
|
|
234
|
+
// (populated in pullStdlib) — `core.includes` has had these removed by then.
|
|
235
|
+
if (ctx.transform.host === 'wasi' && ctx.core.extImports?.size) {
|
|
236
|
+
const ext = [...ctx.core.extImports].sort()
|
|
237
|
+
err(
|
|
238
|
+
`host: 'wasi' — compiled wasm would require JS-host imports that wasmtime/wasmer/deno cannot satisfy:\n ` +
|
|
239
|
+
ext.map(n => `env.${n}`).join('\n ') +
|
|
240
|
+
`\nThis happens when jz falls through to dynamic dispatch for a method or property without a native lowering. ` +
|
|
241
|
+
`Either annotate the receiver type, switch to a natively-supported method, or compile with the default host.`)
|
|
242
|
+
}
|
|
243
|
+
|
|
159
244
|
const cfg = ctx.transform.optimize
|
|
160
245
|
const optimized = cfg.watr ? time('watrOptimize', () => watrOptimize(module)) : module
|
|
161
246
|
// Final peephole pass: watrOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
@@ -163,13 +248,14 @@ jz.compile = (code, opts = {}) => {
|
|
|
163
248
|
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
164
249
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
165
250
|
if (cfg.watr) {
|
|
251
|
+
const postCfg = { ...cfg, __phase: 'post' }
|
|
166
252
|
time('watrReopt', () => {
|
|
167
|
-
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node,
|
|
253
|
+
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, postCfg)
|
|
168
254
|
})
|
|
169
255
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
256
|
+
if (opts.wat) return time('watrPrint', () => watrPrint(optimized))
|
|
257
|
+
const wasm = time('watrCompile', () => watrCompile(optimized))
|
|
258
|
+
return (opts.profileNames || opts.profile?.names) ? appendFunctionNames(wasm, optimized) : wasm
|
|
173
259
|
}
|
|
174
260
|
|
|
175
261
|
/**
|