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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function module — closures, first-class functions, call_indirect.
|
|
3
|
+
*
|
|
4
|
+
* Closures are NaN-boxed pointers: type=10 (PTR.CLOSURE), aux=funcIdx, offset=envPtr.
|
|
5
|
+
* Closure body: (env: f64, ...params: f64) → f64 — env is pointer to captured values.
|
|
6
|
+
* Captured variables stored as f64 in memory at envPtr.
|
|
7
|
+
*
|
|
8
|
+
* Auto-included when inner functions reference outer variables.
|
|
9
|
+
*
|
|
10
|
+
* @module fn
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { emit, typed, asF64, asI32, T, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN, lookupValType, repOf } from '../src/compile.js'
|
|
14
|
+
import { isReassigned } from '../src/emit.js'
|
|
15
|
+
import { PTR, inc, err } from '../src/ctx.js'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export default (ctx) => {
|
|
19
|
+
inc('__mkptr', '__alloc', '__len', '__ptr_offset')
|
|
20
|
+
|
|
21
|
+
// Uniform closure convention: (env f64, argc i32, a0..a{MAX-1} f64) → f64
|
|
22
|
+
if (!ctx.closure.types) ctx.closure.types = new Set()
|
|
23
|
+
if (!ctx.closure.table) ctx.closure.table = []
|
|
24
|
+
if (!ctx.closure.bodies) ctx.closure.bodies = []
|
|
25
|
+
|
|
26
|
+
ctx.closure.types.add(1) // presence triggers $ftN type emission
|
|
27
|
+
|
|
28
|
+
const addToTable = (name) => {
|
|
29
|
+
let idx = ctx.closure.table.indexOf(name)
|
|
30
|
+
if (idx === -1) { idx = ctx.closure.table.length; ctx.closure.table.push(name) }
|
|
31
|
+
return idx
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a closure: compile inner function as closure body, capture outer vars.
|
|
36
|
+
* @param {{ params: string[], body, captures: string[], restParam: string|null }} info
|
|
37
|
+
* @returns {WasmNode} NaN-boxed closure pointer
|
|
38
|
+
*/
|
|
39
|
+
ctx.closure.make = ({ params, body, captures, restParam, defaults }) => {
|
|
40
|
+
const fixedN = params.length - (restParam ? 1 : 0)
|
|
41
|
+
if (fixedN > MAX_CLOSURE_ARITY) err(`Closure with ${fixedN} fixed params exceeds MAX_CLOSURE_ARITY=${MAX_CLOSURE_ARITY}`)
|
|
42
|
+
if (restParam && fixedN >= MAX_CLOSURE_ARITY) err(`Closure with rest param needs at least one free slot — ${fixedN} fixed params leaves none (MAX_CLOSURE_ARITY=${MAX_CLOSURE_ARITY})`)
|
|
43
|
+
// Generate closure body function name
|
|
44
|
+
const fnName = `${T}closure${ctx.closure.table.length}`
|
|
45
|
+
const captureValTypes = new Map()
|
|
46
|
+
const captureSchemaVars = new Map()
|
|
47
|
+
const captureTypedElems = new Map()
|
|
48
|
+
// Propagate parent's directClosures across captures: a const-bound closure captured
|
|
49
|
+
// by an inner arrow can still be direct-dispatched in the inner body (skip
|
|
50
|
+
// call_indirect on the captured pointer). Gated on isReassigned over the inner body
|
|
51
|
+
// so a local rewrite of the captured name disables propagation.
|
|
52
|
+
const captureDirectClosures = new Map()
|
|
53
|
+
for (const name of captures) {
|
|
54
|
+
const vt = lookupValType(name)
|
|
55
|
+
if (vt != null) captureValTypes.set(name, vt)
|
|
56
|
+
const schemaId = ctx.schema.idOf(name)
|
|
57
|
+
if (schemaId != null) captureSchemaVars.set(name, schemaId)
|
|
58
|
+
const elemType = ctx.types.typedElem?.get(name)
|
|
59
|
+
if (elemType != null) captureTypedElems.set(name, elemType)
|
|
60
|
+
const bodyName = ctx.func.directClosures?.get(name)
|
|
61
|
+
if (bodyName && !isReassigned(body, name)) captureDirectClosures.set(name, bodyName)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// All closures use uniform convention: (env: f64, args_array: f64) → f64
|
|
65
|
+
// The body unpacks individual params from the args array
|
|
66
|
+
const boxedCaptures = captures.filter(c => ctx.func.boxed?.has(c))
|
|
67
|
+
const bodyFn = { name: fnName, params, body, captures, arity: 1,
|
|
68
|
+
...(restParam && { rest: restParam }),
|
|
69
|
+
...(defaults && { defaults }),
|
|
70
|
+
...(boxedCaptures.length && { boxed: new Set(boxedCaptures) }),
|
|
71
|
+
...(captureValTypes.size && { valTypes: captureValTypes }),
|
|
72
|
+
...(captureSchemaVars.size && { schemaVars: captureSchemaVars }),
|
|
73
|
+
...(captureTypedElems.size && { typedElems: captureTypedElems }),
|
|
74
|
+
...(captureDirectClosures.size && { directClosures: captureDirectClosures }) }
|
|
75
|
+
ctx.closure.bodies.push(bodyFn)
|
|
76
|
+
|
|
77
|
+
const tableIdx = addToTable(fnName)
|
|
78
|
+
|
|
79
|
+
// At call site: allocate env, store captured values, return NaN-boxed pointer.
|
|
80
|
+
// Tag IR with .closureBodyName so emitDecl can register the binding for direct dispatch
|
|
81
|
+
// (skip call_indirect on a const-bound, non-escaping closure local). See emit.js '()' handler.
|
|
82
|
+
ctx.features.closure = true
|
|
83
|
+
if (captures.length === 0) {
|
|
84
|
+
// No captures — just a function reference
|
|
85
|
+
const ir = mkPtrIR(PTR.CLOSURE, tableIdx, 0)
|
|
86
|
+
ir.closureBodyName = fnName
|
|
87
|
+
ir.closureFuncIdx = tableIdx
|
|
88
|
+
return ir
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const t = tempI32('env')
|
|
92
|
+
|
|
93
|
+
const block = [
|
|
94
|
+
['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', captures.length * 8]]],
|
|
95
|
+
]
|
|
96
|
+
// Store captured values in env: boxed cells as raw i32 in low 4 bytes, others as f64.
|
|
97
|
+
// Avoids i32↔f64 roundtrip; body loads via i32.load/f64.load using the same branch.
|
|
98
|
+
for (let i = 0; i < captures.length; i++) {
|
|
99
|
+
const addr = ['i32.add', ['local.get', `$${t}`], ['i32.const', i * 8]]
|
|
100
|
+
if (ctx.func.boxed?.has(captures[i]))
|
|
101
|
+
block.push(['i32.store', addr, ['local.get', `$${ctx.func.boxed.get(captures[i])}`]])
|
|
102
|
+
else
|
|
103
|
+
block.push(['f64.store', addr, asF64(emit(captures[i]))])
|
|
104
|
+
}
|
|
105
|
+
block.push(mkPtrIR(PTR.CLOSURE, tableIdx, ['local.get', `$${t}`]))
|
|
106
|
+
|
|
107
|
+
const ir = typed(['block', ['result', 'f64'], ...block], 'f64')
|
|
108
|
+
ir.closureBodyName = fnName
|
|
109
|
+
ir.closureFuncIdx = tableIdx
|
|
110
|
+
return ir
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const UNDEF_LIT = () => ['f64.const', `nan:${UNDEF_NAN}`]
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Call a closure value: pass args inline as a0..a{MAX-1} + argc, call_indirect.
|
|
117
|
+
* @param {WasmNode} closureExpr - Already-emitted closure pointer expression
|
|
118
|
+
* @param {any[]} args - AST nodes (will be emitted) OR pre-emitted nodes (if .type is set)
|
|
119
|
+
* @param {boolean} prebuiltArray - args[0] is a pre-built args array (spread path)
|
|
120
|
+
*/
|
|
121
|
+
ctx.closure.call = (closureExpr, args, prebuiltArray) => {
|
|
122
|
+
const t = temp('clos')
|
|
123
|
+
|
|
124
|
+
if (prebuiltArray) {
|
|
125
|
+
// Spread path: decode array into inline slots. Slots beyond array len padded with UNDEF.
|
|
126
|
+
// Rest-param closures receive up to (MAX - fixedParams) spread elements (overflow lost).
|
|
127
|
+
const arrT = tempI32('sa')
|
|
128
|
+
const lenL = tempI32('sl')
|
|
129
|
+
const setup = [
|
|
130
|
+
['local.set', `$${arrT}`, ['call', '$__ptr_offset', asF64(args[0])]],
|
|
131
|
+
['local.set', `$${lenL}`, ['call', '$__len', ['local.get', `$${t}`]]], // placeholder — set below
|
|
132
|
+
]
|
|
133
|
+
// Rebuild setup properly since we need the array ptr before len call
|
|
134
|
+
setup.length = 0
|
|
135
|
+
const arrPtrF64 = temp('sp')
|
|
136
|
+
setup.push(['local.set', `$${arrPtrF64}`, asF64(args[0])])
|
|
137
|
+
setup.push(['local.set', `$${arrT}`, ['call', '$__ptr_offset', ['local.get', `$${arrPtrF64}`]]])
|
|
138
|
+
setup.push(['local.set', `$${lenL}`, ['call', '$__len', ['local.get', `$${arrPtrF64}`]]])
|
|
139
|
+
|
|
140
|
+
const W = ctx.closure.width ?? MAX_CLOSURE_ARITY
|
|
141
|
+
const slots = []
|
|
142
|
+
for (let i = 0; i < W; i++) {
|
|
143
|
+
slots.push(['if', ['result', 'f64'],
|
|
144
|
+
['i32.gt_s', ['local.get', `$${lenL}`], ['i32.const', i]],
|
|
145
|
+
['then', ['f64.load', ['i32.add', ['local.get', `$${arrT}`], ['i32.const', i * 8]]]],
|
|
146
|
+
['else', UNDEF_LIT()]])
|
|
147
|
+
}
|
|
148
|
+
return typed(['block', ['result', 'f64'],
|
|
149
|
+
...setup,
|
|
150
|
+
['local.set', `$${t}`, asF64(closureExpr)],
|
|
151
|
+
['call_indirect', ['type', '$ftN'],
|
|
152
|
+
['local.get', `$${t}`],
|
|
153
|
+
['local.get', `$${lenL}`],
|
|
154
|
+
...slots,
|
|
155
|
+
// Inline __ptr_aux for CLOSURE pointer: aux = bits 32..46 holds funcIdx.
|
|
156
|
+
['i32.wrap_i64', ['i64.and',
|
|
157
|
+
['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', 32]],
|
|
158
|
+
['i64.const', 0x7FFF]]]]], 'f64')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Inline path: emit each arg, pad missing slots with UNDEF
|
|
162
|
+
const n = args.length
|
|
163
|
+
if (n > MAX_CLOSURE_ARITY) err(`Closure call with ${n} args exceeds MAX_CLOSURE_ARITY=${MAX_CLOSURE_ARITY}`)
|
|
164
|
+
const W = ctx.closure.width ?? MAX_CLOSURE_ARITY
|
|
165
|
+
const slots = []
|
|
166
|
+
for (let i = 0; i < n; i++) slots.push(asF64(args[i]?.type ? args[i] : emit(args[i])))
|
|
167
|
+
for (let i = n; i < W; i++) slots.push(UNDEF_LIT())
|
|
168
|
+
|
|
169
|
+
return typed(['block', ['result', 'f64'],
|
|
170
|
+
['local.set', `$${t}`, asF64(closureExpr)],
|
|
171
|
+
['call_indirect', ['type', '$ftN'],
|
|
172
|
+
['local.get', `$${t}`],
|
|
173
|
+
['i32.const', n],
|
|
174
|
+
...slots,
|
|
175
|
+
// Inline __ptr_aux for CLOSURE pointer: aux = bits 32..46 holds funcIdx.
|
|
176
|
+
['i32.wrap_i64', ['i64.and',
|
|
177
|
+
['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', 32]],
|
|
178
|
+
['i64.const', 0x7FFF]]]]], 'f64')
|
|
179
|
+
}
|
|
180
|
+
}
|
package/module/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import math from './math.js'
|
|
2
|
+
import core from './core.js'
|
|
3
|
+
import array from './array.js'
|
|
4
|
+
import object from './object.js'
|
|
5
|
+
import string from './string.js'
|
|
6
|
+
import number from './number.js'
|
|
7
|
+
import fn from './function.js'
|
|
8
|
+
import typedarray from './typedarray.js'
|
|
9
|
+
import collection from './collection.js'
|
|
10
|
+
import symbol from './symbol.js'
|
|
11
|
+
import console from './console.js'
|
|
12
|
+
import json from './json.js'
|
|
13
|
+
import regex from './regex.js'
|
|
14
|
+
import timer from './timer.js'
|
|
15
|
+
export { math, core, array, object, string, number, fn, typedarray, collection, symbol, console, json, regex, timer }
|