jz 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +381 -0
- package/cli.js +163 -0
- package/index.js +217 -0
- package/module/array.js +1317 -0
- package/module/collection.js +791 -0
- package/module/console.js +190 -0
- package/module/core.js +642 -0
- package/module/function.js +180 -0
- package/module/index.js +15 -0
- package/module/json.js +504 -0
- package/module/math.js +389 -0
- package/module/number.js +605 -0
- package/module/object.js +357 -0
- package/module/regex.js +913 -0
- package/module/schema.js +104 -0
- package/module/string.js +928 -0
- package/module/symbol.js +54 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +711 -0
- package/package.json +54 -5
- package/src/analyze.js +1906 -0
- package/src/compile.js +2175 -0
- package/src/ctx.js +243 -0
- package/src/emit.js +2095 -0
- package/src/host.js +524 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +391 -0
- package/src/optimize.js +1352 -0
- package/src/prepare.js +1598 -0
- package/wasi.js +74 -0
package/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jz - JS subset → WASM compiler.
|
|
3
|
+
*
|
|
4
|
+
* # Pipeline stages + contracts
|
|
5
|
+
*
|
|
6
|
+
* source (string)
|
|
7
|
+
* ↓ parse (subscript/jessie) — lexing + expression-oriented AST
|
|
8
|
+
* raw AST: nested arrays `[op, ...args]`, no ctx mutation
|
|
9
|
+
* ↓ jzify (opt-in via opts.jzify) — lower full-JS subset (var/function/class/switch) to jz-native
|
|
10
|
+
* desugared AST: arrow functions + let/const/if only
|
|
11
|
+
* ↓ prepare — validate (reject disallowed ops), normalize (++/--→+=/-=, scope rename),
|
|
12
|
+
* extract (functions→ctx.func.list with sig), resolve (imports→ctx.module.imports),
|
|
13
|
+
* track (object-literal schemas via ctx.schema.register)
|
|
14
|
+
* prepared AST: normalized, with `ctx.func.list` / `ctx.module.imports` / `ctx.schema.list`
|
|
15
|
+
* populated. Arrow bodies carry no type info yet.
|
|
16
|
+
* ↓ compile — drives per-function emit, interleaves analysis (locals/valTypes/captures/
|
|
17
|
+
* narrowing fixpoint) with IR generation via the emitter table (src/emit.js).
|
|
18
|
+
* Writes: `ctx.func.valTypes`/`.locals`, `ctx.types.*`, `ctx.runtime.*`, `ctx.core.includes`.
|
|
19
|
+
* Also calls optimizeFunc (src/optimize.js): `hoistPtrType` + fused peephole/inline/memarg walk.
|
|
20
|
+
* WAT IR: watr S-expression `['module', ...sections]`, every instruction node carries `.type`.
|
|
21
|
+
* ↓ watrOptimize (opt-out via opts.optimize=false) — CSE, DCE, const folding at WAT level
|
|
22
|
+
* ↓ optimizeFunc 2nd pass — re-folds rebox/unbox roundtrips that watrOptimize's inliner
|
|
23
|
+
* re-introduces at inline boundaries (caller's boxPtrIR meets callee's
|
|
24
|
+
* i32.wrap_i64(i64.reinterpret_f64 __env)). watr's peephole doesn't cover this.
|
|
25
|
+
* ↓ watrPrint (opts.wat=true) → WAT text, or watrCompile → Uint8Array binary
|
|
26
|
+
*
|
|
27
|
+
* # State
|
|
28
|
+
* Single shared `ctx` (src/ctx.js). Reset at compile() entry via `reset(emitter, GLOBALS)`.
|
|
29
|
+
* Each subkey has a declared lifecycle + ownership — see ctx.js docstring for the table.
|
|
30
|
+
*
|
|
31
|
+
* # Extension
|
|
32
|
+
* Modules in module/ register operator handlers on ctx.core.emit and stdlibs on ctx.core.stdlib.
|
|
33
|
+
* Feature flags (ctx.features.*) gate conditional stdlib branches for dead-code elimination.
|
|
34
|
+
* Capability hooks (ctx.schema.register, ctx.closure.make) are installed by capability modules.
|
|
35
|
+
*
|
|
36
|
+
* Interop host layer (memory marshaling, wrap, instantiate) lives in src/host.js.
|
|
37
|
+
*
|
|
38
|
+
* @module jz
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { parse } from 'subscript/jessie'
|
|
42
|
+
import { compile as watrCompile, print as watrPrint, optimize as watrOptimize } from "watr";
|
|
43
|
+
import { ctx, reset } from './src/ctx.js'
|
|
44
|
+
import prepare, { GLOBALS } from './src/prepare.js'
|
|
45
|
+
import compile, { emitter } from './src/compile.js'
|
|
46
|
+
import { optimizeFunc, resolveOptimize } from './src/optimize.js'
|
|
47
|
+
import jzify from './src/jzify.js'
|
|
48
|
+
import {
|
|
49
|
+
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
50
|
+
} from './src/host.js'
|
|
51
|
+
|
|
52
|
+
const importSpecMayReturnExternal = (spec) => {
|
|
53
|
+
if (typeof spec === 'function') return true
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const importsMayReturnExternal = (imports) => {
|
|
58
|
+
if (!imports) return false
|
|
59
|
+
for (const mod of Object.values(imports))
|
|
60
|
+
for (const spec of Object.values(mod || {}))
|
|
61
|
+
if (importSpecMayReturnExternal(spec)) return true
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* jz — JS subset → WASM compiler.
|
|
67
|
+
*
|
|
68
|
+
* jz('code') or jz`code` → { exports, memory, instance, module }
|
|
69
|
+
* jz.compile('code') → Uint8Array (raw WASM binary)
|
|
70
|
+
* jz.compile('code', { wat: true }) → string (WAT text)
|
|
71
|
+
* jz.memory([src]) → enhanced WebAssembly.Memory (read/write JS↔WASM values)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* const { exports: { add } } = jz('export let add = (a, b) => a + b')
|
|
75
|
+
* add(2, 3) // 5
|
|
76
|
+
*/
|
|
77
|
+
jz.memory = enhanceMemory
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compile jz source to WASM binary or WAT text. Low-level — no instantiation.
|
|
81
|
+
* @param {string} code - jz source
|
|
82
|
+
* @param {object} [opts]
|
|
83
|
+
* @param {boolean} [opts.wat] - Return WAT text instead of binary
|
|
84
|
+
* @param {boolean} [opts.strict] - Reject dynamic features (obj[k], for-in, unknown
|
|
85
|
+
* receiver method calls) at compile time. Avoids pulling dynamic-dispatch stdlib
|
|
86
|
+
* into output; large size win for static programs.
|
|
87
|
+
* @param {boolean} [opts.runtimeExports=true] - Export runtime allocator helpers
|
|
88
|
+
* (`_alloc`, `_reset`) for JS memory wrapping. Set false for standalone host-run
|
|
89
|
+
* modules that only call exported wasm functions.
|
|
90
|
+
* @param {boolean|number|object} [opts.optimize] - Optimization level/config.
|
|
91
|
+
* - `false` / `0`: nothing. Fastest compile, largest output (live coding).
|
|
92
|
+
* - `1`: encoding-compactness only (treeshake + sortLocalsByUse + fusedRewrite-inline).
|
|
93
|
+
* - `true` / `2` (default): all current passes (watr CSE/DCE/inline + every jz pass).
|
|
94
|
+
* - `3`: reserved for future aggressive passes (currently == 2).
|
|
95
|
+
* - `{ level?: 0|1|2|3, watr?: bool, hoistAddrBase?: bool, ... }`: per-pass
|
|
96
|
+
* overrides on top of the chosen level. See PASS_NAMES in src/optimize.js.
|
|
97
|
+
* @returns {Uint8Array|string}
|
|
98
|
+
*/
|
|
99
|
+
jz.compile = (code, opts = {}) => {
|
|
100
|
+
reset(emitter, GLOBALS)
|
|
101
|
+
ctx.error.src = code
|
|
102
|
+
|
|
103
|
+
if (opts.memory) ctx.memory.shared = true
|
|
104
|
+
if (opts.memoryPages) ctx.memory.pages = opts.memoryPages
|
|
105
|
+
if (opts.modules) ctx.module.importSources = opts.modules
|
|
106
|
+
if (opts.imports) {
|
|
107
|
+
ctx.module.hostImports = opts.imports
|
|
108
|
+
if (importsMayReturnExternal(opts.imports)) ctx.features.external = true
|
|
109
|
+
}
|
|
110
|
+
// jzify: true → accept full JS subset (function/var/switch lowered to arrows/let/if).
|
|
111
|
+
// Default: strict jz (prepare rejects disallowed JS features). subscript handles ASI natively.
|
|
112
|
+
if (opts.jzify) ctx.transform.jzify = jzify
|
|
113
|
+
if (opts.noTailCall) ctx.transform.noTailCall = true
|
|
114
|
+
if (opts.strict) ctx.transform.strict = true
|
|
115
|
+
if (opts.runtimeExports === false) ctx.transform.runtimeExports = false
|
|
116
|
+
if (opts.nativeTimers) ctx.features.blockingTimers = true // wasmtime CLI: include __timer_loop in _start
|
|
117
|
+
ctx.transform.optimize = resolveOptimize(opts.optimize)
|
|
118
|
+
|
|
119
|
+
if (opts._interp) {
|
|
120
|
+
for (const [name, fn] of Object.entries(opts._interp)) {
|
|
121
|
+
if (name.startsWith('__ext_')) continue;
|
|
122
|
+
ctx.features.external = true
|
|
123
|
+
const params = Array(fn.length).fill(['param', 'f64'])
|
|
124
|
+
ctx.module.imports.push(['import', '"env"', `"${name}"`, ['func', `$${name}`, ...params, ['result', 'f64']]])
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let parsed = parse(code)
|
|
129
|
+
if (opts.jzify) parsed = jzify(parsed)
|
|
130
|
+
const ast = prepare(parsed)
|
|
131
|
+
const module = compile(ast)
|
|
132
|
+
|
|
133
|
+
const cfg = ctx.transform.optimize
|
|
134
|
+
const optimized = cfg.watr ? watrOptimize(module) : module
|
|
135
|
+
// Final peephole pass: watrOptimize's inliner can re-introduce rebox/unbox at boundaries
|
|
136
|
+
// (e.g. inlined closure body's `i32.wrap_i64 (i64.reinterpret_f64 __env)` next to caller's
|
|
137
|
+
// `boxPtrIR(g)` rebox). Our fusedRewrite folds these, watr's peephole doesn't.
|
|
138
|
+
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
139
|
+
if (cfg.watr) {
|
|
140
|
+
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, cfg)
|
|
141
|
+
}
|
|
142
|
+
return opts.wat ? watrPrint(optimized) : watrCompile(optimized)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compile, instantiate, and wrap. Works as both jz('code') and jz`code ${val}`.
|
|
147
|
+
* @param {string|TemplateStringsArray} code
|
|
148
|
+
* @param {...any} args - Interpolation values (template tag) or options (string call)
|
|
149
|
+
* @returns {{exports, memory, instance, module}}
|
|
150
|
+
*/
|
|
151
|
+
export default function jz(code, ...args) {
|
|
152
|
+
// Template tag: jz`code ${val}` — numbers, functions, strings, arrays, objects
|
|
153
|
+
if (Array.isArray(code)) {
|
|
154
|
+
const interp = {}, data = {}, hoisted = []
|
|
155
|
+
|
|
156
|
+
// Serialize JS value to jz source literal. Returns null if not serializable.
|
|
157
|
+
const serialize = (v) => {
|
|
158
|
+
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
|
159
|
+
if (v === null) return 'null'
|
|
160
|
+
if (typeof v === 'string') return JSON.stringify(v)
|
|
161
|
+
if (Array.isArray(v)) {
|
|
162
|
+
const elems = v.map(serialize)
|
|
163
|
+
return elems.every(e => e !== null) ? `[${elems.join(', ')}]` : null
|
|
164
|
+
}
|
|
165
|
+
if (typeof v === 'object') {
|
|
166
|
+
const props = Object.keys(v).map(k => {
|
|
167
|
+
const s = serialize(v[k])
|
|
168
|
+
return s !== null ? `${k}: ${s}` : null
|
|
169
|
+
})
|
|
170
|
+
return props.every(p => p !== null) ? `{${props.join(', ')}}` : null
|
|
171
|
+
}
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let src = code[0]
|
|
176
|
+
for (let i = 0; i < args.length; i++) {
|
|
177
|
+
const v = args[i]
|
|
178
|
+
if (typeof v === 'function') {
|
|
179
|
+
const key = `$$${i}`; interp[key] = v; src += key
|
|
180
|
+
} else {
|
|
181
|
+
const s = serialize(v)
|
|
182
|
+
if (s !== null && (typeof v === 'number' || typeof v === 'boolean')) {
|
|
183
|
+
// Scalars inline directly
|
|
184
|
+
src += s
|
|
185
|
+
} else if (s !== null) {
|
|
186
|
+
// Strings, arrays, objects — hoist as compile-time literal
|
|
187
|
+
const key = `$$${i}`
|
|
188
|
+
hoisted.push(`let ${key} = ${s}`)
|
|
189
|
+
src += key
|
|
190
|
+
} else {
|
|
191
|
+
// Non-serializable (host objects, etc.) — post-instantiation getter
|
|
192
|
+
const key = `$$${i}`, ref = { ptr: 0 }
|
|
193
|
+
data[key] = { val: v, ref }; interp[key] = () => ref.ptr
|
|
194
|
+
src += `${key}()`
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
src += code[i + 1]
|
|
198
|
+
}
|
|
199
|
+
if (hoisted.length) src = hoisted.join('; ') + '; ' + src
|
|
200
|
+
const hasInterp = Object.keys(interp).length
|
|
201
|
+
const result = instantiateRuntime(jz.compile, src, { _interp: hasInterp ? interp : null })
|
|
202
|
+
// Patch data getters: allocate values in WASM memory, update closure refs
|
|
203
|
+
for (const [, { val, ref }] of Object.entries(data)) {
|
|
204
|
+
if (typeof val === 'string') ref.ptr = result.memory.String(val)
|
|
205
|
+
else if (Array.isArray(val)) ref.ptr = result.memory.Array(val)
|
|
206
|
+
else ref.ptr = result.memory.Object(val)
|
|
207
|
+
}
|
|
208
|
+
return result
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// String call: jz('code', opts?) — compile + instantiate + wrap
|
|
212
|
+
return instantiateRuntime(jz.compile, code, args[0] || {})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { jz }
|
|
216
|
+
const jzCompile = jz.compile
|
|
217
|
+
export { jzCompile as compile }
|