jz 0.1.0 → 0.1.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 +64 -72
- package/index.js +38 -8
- package/module/array.js +7 -2
- package/module/collection.js +3 -1
- package/module/console.js +3 -1
- package/module/core.js +3 -1
- package/module/function.js +4 -3
- package/module/json.js +3 -1
- package/module/math.js +2 -1
- package/module/number.js +3 -7
- package/module/object.js +3 -1
- package/module/regex.js +2 -1
- package/module/schema.js +3 -1
- package/module/string.js +63 -6
- package/module/symbol.js +2 -1
- package/module/timer.js +2 -2
- package/module/typedarray.js +3 -1
- package/package.json +4 -2
- package/src/analyze.js +28 -7
- package/src/autoload.js +175 -0
- package/src/compile.js +58 -1022
- package/src/ctx.js +1 -0
- package/src/emit.js +16 -6
- package/src/host.js +38 -26
- package/src/jzify.js +45 -18
- package/src/key.js +73 -0
- package/src/narrow.js +928 -0
- package/src/plan.js +105 -0
- package/src/prepare.js +151 -215
- package/src/source.js +76 -0
- package/wasi.js +10 -4
package/src/ctx.js
CHANGED
package/src/emit.js
CHANGED
|
@@ -1877,20 +1877,30 @@ export const emitter = {
|
|
|
1877
1877
|
const combined = reconstructArgsWithSpreads(parsed.normal, parsed.spreads)
|
|
1878
1878
|
const arrayIR = buildArrayWithSpreads(combined)
|
|
1879
1879
|
const propRead = typed(['call', '$__dyn_get_expr', ['local.get', `$${objTmp}`], asF64(emit(['str', method]))], 'f64')
|
|
1880
|
+
const propTmp = `${T}mprop${ctx.func.uniq++}`
|
|
1881
|
+
ctx.func.locals.set(propTmp, 'f64')
|
|
1880
1882
|
if (usesDynProps(vt)) {
|
|
1881
|
-
inc('__dyn_get_expr')
|
|
1883
|
+
inc('__dyn_get_expr', '__ptr_type')
|
|
1882
1884
|
return typed(['block', ['result', 'f64'],
|
|
1883
1885
|
['local.set', `$${objTmp}`, asF64(emit(obj))],
|
|
1884
|
-
|
|
1886
|
+
['local.set', `$${propTmp}`, propRead],
|
|
1887
|
+
['if', ['result', 'f64'],
|
|
1888
|
+
['i32.eq', ['call', '$__ptr_type', ['local.get', `$${propTmp}`]], ['i32.const', PTR.CLOSURE]],
|
|
1889
|
+
['then', ctx.closure.call(typed(['local.get', `$${propTmp}`], 'f64'), [arrayIR], true)],
|
|
1890
|
+
['else', undefExpr()]]], 'f64')
|
|
1885
1891
|
}
|
|
1886
|
-
inc('__dyn_get_expr', '__ext_call')
|
|
1892
|
+
inc('__dyn_get_expr', '__ext_call', '__ptr_type')
|
|
1887
1893
|
ctx.features.external = true
|
|
1888
1894
|
return typed(['block', ['result', 'f64'],
|
|
1889
1895
|
['local.set', `$${objTmp}`, asF64(emit(obj))],
|
|
1896
|
+
['local.set', `$${propTmp}`, propRead],
|
|
1890
1897
|
['if', ['result', 'f64'],
|
|
1891
|
-
['i32.eq', ['call', '$__ptr_type', ['local.get', `$${
|
|
1892
|
-
['then',
|
|
1893
|
-
['else',
|
|
1898
|
+
['i32.eq', ['call', '$__ptr_type', ['local.get', `$${propTmp}`]], ['i32.const', PTR.CLOSURE]],
|
|
1899
|
+
['then', ctx.closure.call(typed(['local.get', `$${propTmp}`], 'f64'), [arrayIR], true)],
|
|
1900
|
+
['else', ['if', ['result', 'f64'],
|
|
1901
|
+
['i32.eq', ['call', '$__ptr_type', ['local.get', `$${objTmp}`]], ['i32.const', PTR.EXTERNAL]],
|
|
1902
|
+
['then', ['call', '$__ext_call', ['local.get', `$${objTmp}`], asF64(emit(['str', method])), arrayIR]],
|
|
1903
|
+
['else', undefExpr()]]]]], 'f64')
|
|
1894
1904
|
}
|
|
1895
1905
|
|
|
1896
1906
|
// Unknown callee - assume external method
|
package/src/host.js
CHANGED
|
@@ -435,36 +435,31 @@ export const wrap = (memSrc, inst) => {
|
|
|
435
435
|
return exports
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
* `compile` is the jz.compile function (injected to avoid importing the compiler core).
|
|
441
|
-
*/
|
|
442
|
-
export const instantiate = (compile, code, opts = {}) => {
|
|
443
|
-
const extMap = [null]
|
|
444
|
-
let mem = null
|
|
438
|
+
const prepareInterop = (opts) => {
|
|
439
|
+
const state = { extMap: [null], mem: null }
|
|
445
440
|
opts._interp = opts._interp || {}
|
|
446
441
|
opts._interp.__ext_prop = (objPtr, propPtr) => {
|
|
447
|
-
const obj = extMap[offset(objPtr)]
|
|
448
|
-
const prop = mem.read(propPtr)
|
|
449
|
-
return mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop])
|
|
442
|
+
const obj = state.extMap[offset(objPtr)]
|
|
443
|
+
const prop = state.mem.read(propPtr)
|
|
444
|
+
return state.mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop])
|
|
450
445
|
}
|
|
451
446
|
opts._interp.__ext_has = (objPtr, propPtr) => {
|
|
452
|
-
return (mem.read(propPtr) in extMap[offset(objPtr)]) ? 1 : 0
|
|
447
|
+
return (state.mem.read(propPtr) in state.extMap[offset(objPtr)]) ? 1 : 0
|
|
453
448
|
}
|
|
454
449
|
opts._interp.__ext_set = (objPtr, propPtr, valPtr) => {
|
|
455
|
-
extMap[offset(objPtr)][mem.read(propPtr)] = mem.read(valPtr)
|
|
450
|
+
state.extMap[offset(objPtr)][state.mem.read(propPtr)] = state.mem.read(valPtr)
|
|
456
451
|
return 1
|
|
457
452
|
}
|
|
458
453
|
opts._interp.__ext_call = (objPtr, propPtr, argsPtr) => {
|
|
459
|
-
const obj = extMap[offset(objPtr)]
|
|
460
|
-
const prop = mem.read(propPtr)
|
|
461
|
-
const args = mem.read(argsPtr)
|
|
462
|
-
return mem.wrapVal(obj[prop].apply(obj, args))
|
|
454
|
+
const obj = state.extMap[offset(objPtr)]
|
|
455
|
+
const prop = state.mem.read(propPtr)
|
|
456
|
+
const args = state.mem.read(argsPtr)
|
|
457
|
+
return state.mem.wrapVal(obj[prop].apply(obj, args))
|
|
463
458
|
}
|
|
459
|
+
return state
|
|
460
|
+
}
|
|
464
461
|
|
|
465
|
-
|
|
466
|
-
opts.extMap = extMap
|
|
467
|
-
const mod = new WebAssembly.Module(wasm)
|
|
462
|
+
const buildImports = (mod, opts, state) => {
|
|
468
463
|
const needsWasi = WebAssembly.Module.imports(mod).some(i => i.module === 'wasi_snapshot_preview1')
|
|
469
464
|
const imports = needsWasi ? wasi(opts) : {}
|
|
470
465
|
if (opts._interp) imports.env = { ...imports.env, ...opts._interp }
|
|
@@ -477,8 +472,8 @@ export const instantiate = (compile, code, opts = {}) => {
|
|
|
477
472
|
const fn = typeof spec === 'function' ? spec : (spec && typeof spec === 'object' ? spec.fn : null)
|
|
478
473
|
if (typeof fn === 'function')
|
|
479
474
|
imports[modName][name] = (...args) => {
|
|
480
|
-
const ret = fn.call(fns, ...args.map(a => mem ? mem.read(a) : a))
|
|
481
|
-
return mem ? mem.wrapVal(ret) : coerce(ret)
|
|
475
|
+
const ret = fn.call(fns, ...args.map(a => state.mem ? state.mem.read(a) : a))
|
|
476
|
+
return state.mem ? state.mem.wrapVal(ret) : coerce(ret)
|
|
482
477
|
}
|
|
483
478
|
}
|
|
484
479
|
}
|
|
@@ -495,13 +490,15 @@ export const instantiate = (compile, code, opts = {}) => {
|
|
|
495
490
|
const host = globalThis[imp.name]
|
|
496
491
|
if (host !== undefined) {
|
|
497
492
|
if (!imports.env) imports.env = {}
|
|
498
|
-
let id = extMap.indexOf(host); if (id === -1) { id = extMap.length; extMap.push(host) }
|
|
493
|
+
let id = state.extMap.indexOf(host); if (id === -1) { id = state.extMap.length; state.extMap.push(host) }
|
|
499
494
|
imports.env[imp.name] = new WebAssembly.Global({ value: 'f64', mutable: true }, ptr(11, 0, id))
|
|
500
495
|
}
|
|
501
496
|
}
|
|
502
497
|
}
|
|
503
|
-
|
|
504
|
-
|
|
498
|
+
return { imports, needsWasi }
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
505
502
|
if (needsWasi) imports._setMemory(inst.exports.memory)
|
|
506
503
|
|
|
507
504
|
// Drive WASM timer queue via JS scheduling (non-blocking)
|
|
@@ -517,8 +514,23 @@ export const instantiate = (compile, code, opts = {}) => {
|
|
|
517
514
|
|
|
518
515
|
// For shared memory, resolve memory from import; for own memory, from export
|
|
519
516
|
const rawMemory = opts.memory || inst.exports.memory
|
|
520
|
-
const memSrc = { module: mod, instance: inst, exports: { ...inst.exports, memory: rawMemory }, extMap }
|
|
517
|
+
const memSrc = { module: mod, instance: inst, exports: { ...inst.exports, memory: rawMemory }, extMap: state.extMap }
|
|
521
518
|
const enhanced = memory(memSrc)
|
|
522
|
-
mem = enhanced
|
|
519
|
+
state.mem = enhanced
|
|
523
520
|
return { exports: wrap(memSrc), memory: enhanced, instance: inst, module: mod }
|
|
524
521
|
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Compile, instantiate, and wrap exports (with WASI + rest-param support).
|
|
525
|
+
* `compile` is the jz.compile function (injected to avoid importing the compiler core).
|
|
526
|
+
*/
|
|
527
|
+
export const instantiate = (compile, code, opts = {}) => {
|
|
528
|
+
const state = prepareInterop(opts)
|
|
529
|
+
const wasm = compile(code, opts)
|
|
530
|
+
opts.extMap = state.extMap
|
|
531
|
+
const mod = new WebAssembly.Module(wasm)
|
|
532
|
+
const { imports, needsWasi } = buildImports(mod, opts, state)
|
|
533
|
+
const hasImports = Object.keys(imports).some(k => k !== '_setMemory')
|
|
534
|
+
const inst = new WebAssembly.Instance(mod, hasImports ? imports : undefined)
|
|
535
|
+
return finishInstantiation(mod, inst, imports, needsWasi, opts, state)
|
|
536
|
+
}
|
package/src/jzify.js
CHANGED
|
@@ -122,21 +122,33 @@ function paramList(params) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
function lowerArguments(params, body) {
|
|
125
|
-
if (!usesArguments(body)) return [params, body]
|
|
125
|
+
if (!usesArguments(params) && !usesArguments(body)) return [params, body]
|
|
126
126
|
const name = `\uE001arg${argsIdx++}`
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
const decls = []
|
|
128
|
+
for (const [idx, param] of paramList(params).entries()) {
|
|
129
|
+
if (Array.isArray(param) && param[0] === '...') {
|
|
130
|
+
decls.push(['=', param[1], ['()', ['.', name, 'slice'], [null, idx]]])
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(param) && param[0] === '=') {
|
|
134
|
+
decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
decls.push(['=', param, ['[]', name, [null, idx]]])
|
|
138
|
+
}
|
|
139
|
+
const renamed = renameArguments(body, name)
|
|
140
|
+
return [['()', ['...', name]], decls.length ? [';', ['let', ...decls], renamed] : renamed]
|
|
131
141
|
}
|
|
132
142
|
|
|
143
|
+
const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
|
|
144
|
+
|
|
133
145
|
const handlers = {
|
|
134
146
|
// Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
|
|
135
147
|
'()'(callee, ...rest) {
|
|
136
148
|
if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
|
|
137
149
|
const [, name, params, body] = callee[1]
|
|
138
150
|
const [p2, b2] = lowerArguments(params, body)
|
|
139
|
-
return [';', ['let', ['=', name, ['=>', p2, wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
|
|
151
|
+
return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
|
|
140
152
|
}
|
|
141
153
|
},
|
|
142
154
|
|
|
@@ -148,7 +160,14 @@ const handlers = {
|
|
|
148
160
|
return arrow
|
|
149
161
|
},
|
|
150
162
|
|
|
151
|
-
'var'(...args) {
|
|
163
|
+
'var'(...args) {
|
|
164
|
+
// for-in/for-of: ['var', ['in', 'k', obj]] → ['in', ['let', 'k'], obj]
|
|
165
|
+
if (args.length === 1 && Array.isArray(args[0]) && (args[0][0] === 'in' || args[0][0] === 'of')) {
|
|
166
|
+
const [, name, src] = args[0]
|
|
167
|
+
return [args[0][0], ['let', typeof name === 'string' ? name : transform(name)], transform(src)]
|
|
168
|
+
}
|
|
169
|
+
return ['let', ...args.map(transform)]
|
|
170
|
+
},
|
|
152
171
|
|
|
153
172
|
'='(lhs, rhs) {
|
|
154
173
|
// var assignment: ['=', ['var', name], init] → let
|
|
@@ -205,16 +224,14 @@ const handlers = {
|
|
|
205
224
|
return ['===', ['typeof', t], [null, 'object']]
|
|
206
225
|
},
|
|
207
226
|
|
|
208
|
-
// do { body } while (cond) →
|
|
209
|
-
// Avoids body duplication
|
|
227
|
+
// do { body } while (cond) → let _once = true; while (_once || cond) { _once = false; body }
|
|
228
|
+
// Avoids body duplication and preserves continue: `continue` jumps back to the
|
|
229
|
+
// while condition after the one-shot flag has been cleared.
|
|
210
230
|
'do'(body, cond) {
|
|
211
231
|
const flag = `do${doIdx++}`
|
|
212
|
-
return ['
|
|
213
|
-
['
|
|
214
|
-
|
|
215
|
-
['||', flag, transform(cond)],
|
|
216
|
-
['=', flag, [null, false]]],
|
|
217
|
-
transform(body)]
|
|
232
|
+
return [';',
|
|
233
|
+
['let', ['=', flag, [null, true]]],
|
|
234
|
+
['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
|
|
218
235
|
},
|
|
219
236
|
|
|
220
237
|
// Block body: recurse as scope for hoisting
|
|
@@ -316,6 +333,9 @@ export function codegen(node, depth = 0) {
|
|
|
316
333
|
const [head, body] = a
|
|
317
334
|
if (Array.isArray(head) && (head[0] === 'of' || head[0] === 'in'))
|
|
318
335
|
return 'for (' + codegen(head[1]) + ' ' + head[0] + ' ' + codegen(head[2]) + ') ' + codegen(body, depth)
|
|
336
|
+
// ['let'/'const', ['in'/'of', name, obj]] — subscript wraps var→let around in/of
|
|
337
|
+
if (Array.isArray(head) && (head[0] === 'let' || head[0] === 'const') && Array.isArray(head[1]) && (head[1][0] === 'in' || head[1][0] === 'of'))
|
|
338
|
+
return 'for (' + head[0] + ' ' + codegen(head[1][1]) + ' ' + head[1][0] + ' ' + codegen(head[1][2]) + ') ' + codegen(body, depth)
|
|
319
339
|
return 'for (' + codegen(head) + ') ' + codegen(body, depth)
|
|
320
340
|
}
|
|
321
341
|
return 'for (' + (codegen(a[0]) || '') + '; ' + (codegen(a[1]) || '') + '; ' + (codegen(a[2]) || '') + ') ' + codegen(a[3], depth)
|
|
@@ -324,7 +344,11 @@ export function codegen(node, depth = 0) {
|
|
|
324
344
|
if (op === 'throw') return 'throw ' + codegen(a[0])
|
|
325
345
|
if (op === 'break') return 'break'
|
|
326
346
|
if (op === 'continue') return 'continue'
|
|
327
|
-
|
|
347
|
+
// catch with optional binding: ['catch', tryBlock, catchBody] or ['catch', tryBlock, paramName, catchBody]
|
|
348
|
+
if (op === 'catch') {
|
|
349
|
+
if (a.length === 3) return 'try ' + codegen(a[0], depth) + ' catch (' + a[1] + ') ' + codegen(a[2], depth)
|
|
350
|
+
return 'try ' + codegen(a[0], depth) + ' catch ' + codegen(a[1], depth)
|
|
351
|
+
}
|
|
328
352
|
|
|
329
353
|
// Arrow
|
|
330
354
|
if (op === '=>') {
|
|
@@ -358,8 +382,11 @@ export function codegen(node, depth = 0) {
|
|
|
358
382
|
|
|
359
383
|
// Comma
|
|
360
384
|
if (op === ',') return a.map(x => codegen(x)).join(', ')
|
|
361
|
-
// Template literal
|
|
362
|
-
if (op === '`') return '`' + a.map(
|
|
385
|
+
// Template literal: alternating string/expr parts. String parts are [null, "str"], expr parts are AST nodes.
|
|
386
|
+
if (op === '`') return '`' + a.map(p => {
|
|
387
|
+
if (Array.isArray(p) && p[0] == null && typeof p[1] === 'string') return p[1].replace(/[`\\$]/g, c => '\\' + c)
|
|
388
|
+
return '${' + codegen(p) + '}'
|
|
389
|
+
}).join('') + '`'
|
|
363
390
|
|
|
364
391
|
// Spread
|
|
365
392
|
if (op === '...') return '...' + codegen(a[0])
|
package/src/key.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Static property-key evaluation for computed member names. */
|
|
2
|
+
|
|
3
|
+
const NO_VALUE = Symbol('no-static-property-key')
|
|
4
|
+
|
|
5
|
+
export function staticPropertyKey(node) {
|
|
6
|
+
const value = staticValue(node)
|
|
7
|
+
return value === NO_VALUE ? null : String(value)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function staticValue(node) {
|
|
11
|
+
if (node === undefined) return undefined
|
|
12
|
+
if (node === null || typeof node === 'number' || typeof node === 'string' || typeof node === 'boolean') return node
|
|
13
|
+
if (!Array.isArray(node)) return NO_VALUE
|
|
14
|
+
|
|
15
|
+
const [op, ...args] = node
|
|
16
|
+
if (op == null) return args.length ? args[0] : undefined
|
|
17
|
+
if (op === 'str') return args[0]
|
|
18
|
+
if (op === '[]' && args.length === 1) return staticValue(args[0])
|
|
19
|
+
if (op === '()' && args[0] === 'String' && args.length === 2) {
|
|
20
|
+
const value = staticValue(args[1])
|
|
21
|
+
return value === NO_VALUE ? NO_VALUE : String(value)
|
|
22
|
+
}
|
|
23
|
+
if (op === '()' && args[0] === 'Number' && args.length === 2) {
|
|
24
|
+
const value = staticValue(args[1])
|
|
25
|
+
return value === NO_VALUE ? NO_VALUE : Number(value)
|
|
26
|
+
}
|
|
27
|
+
if (op === '?:' || op === '?') {
|
|
28
|
+
const cond = staticValue(args[0])
|
|
29
|
+
return cond === NO_VALUE ? NO_VALUE : staticValue(cond ? args[1] : args[2])
|
|
30
|
+
}
|
|
31
|
+
if (op === '&&' || op === '||') {
|
|
32
|
+
const left = staticValue(args[0])
|
|
33
|
+
if (left === NO_VALUE) return NO_VALUE
|
|
34
|
+
return op === '&&' ? (left ? staticValue(args[1]) : left) : (left ? left : staticValue(args[1]))
|
|
35
|
+
}
|
|
36
|
+
if (op === '??') {
|
|
37
|
+
const left = staticValue(args[0])
|
|
38
|
+
return left === NO_VALUE ? NO_VALUE : left == null ? staticValue(args[1]) : left
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.length === 1) {
|
|
42
|
+
const value = staticValue(args[0])
|
|
43
|
+
if (value === NO_VALUE) return NO_VALUE
|
|
44
|
+
if (op === 'u+') return +value
|
|
45
|
+
if (op === 'u-') return -value
|
|
46
|
+
if (op === '!') return !value
|
|
47
|
+
if (op === '~') return ~value
|
|
48
|
+
return NO_VALUE
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (args.length === 2) {
|
|
52
|
+
const left = staticValue(args[0])
|
|
53
|
+
const right = staticValue(args[1])
|
|
54
|
+
if (left === NO_VALUE || right === NO_VALUE) return NO_VALUE
|
|
55
|
+
switch (op) {
|
|
56
|
+
case '+': return typeof left === 'string' || typeof right === 'string' ? String(left) + String(right) : Number(left) + Number(right)
|
|
57
|
+
case '-': return Number(left) - Number(right)
|
|
58
|
+
case '*': return Number(left) * Number(right)
|
|
59
|
+
case '/': return Number(left) / Number(right)
|
|
60
|
+
case '%': return Number(left) % Number(right)
|
|
61
|
+
case '**': return Number(left) ** Number(right)
|
|
62
|
+
case '&': return Number(left) & Number(right)
|
|
63
|
+
case '|': return Number(left) | Number(right)
|
|
64
|
+
case '^': return Number(left) ^ Number(right)
|
|
65
|
+
case '<<': return Number(left) << Number(right)
|
|
66
|
+
case '>>': return Number(left) >> Number(right)
|
|
67
|
+
case '>>>': return Number(left) >>> Number(right)
|
|
68
|
+
default: return NO_VALUE
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return NO_VALUE
|
|
73
|
+
}
|