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.
@@ -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
+ }
@@ -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 }