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.
@@ -12,8 +12,48 @@
12
12
 
13
13
  import { typed, asF64, asI32, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN } from '../src/ir.js'
14
14
  import { emit, isReassigned } from '../src/emit.js'
15
- import { T, lookupValType, repOf } from '../src/analyze.js'
16
- import { PTR, inc, err } from '../src/ctx.js'
15
+ import { T, lookupValType, repOf, findFreeVars } from '../src/analyze.js'
16
+ import { PTR, LAYOUT, inc, err } from '../src/ctx.js'
17
+
18
+ const intConstExpr = (node) => {
19
+ if (typeof node === 'number' && Number.isInteger(node)) return node
20
+ if (Array.isArray(node) && node[0] == null && Number.isInteger(node[1])) return node[1]
21
+ if (!Array.isArray(node)) return null
22
+ const [op, a, b] = node
23
+ const av = intConstExpr(a)
24
+ if (op === 'u-' || (op === '-' && b === undefined)) return av == null ? null : -av
25
+ const bv = intConstExpr(b)
26
+ if (av == null || bv == null) return null
27
+ switch (op) {
28
+ case '+': return av + bv
29
+ case '-': return av - bv
30
+ case '*': return av * bv
31
+ case '&': return av & bv
32
+ case '|': return av | bv
33
+ case '^': return av ^ bv
34
+ case '<<': return av << bv
35
+ case '>>': return av >> bv
36
+ case '>>>': return av >>> bv
37
+ default: return null
38
+ }
39
+ }
40
+
41
+ const topLevelIntConsts = (body) => {
42
+ const inner = Array.isArray(body) && body[0] === '{}' ? body[1] : body
43
+ const stmts = Array.isArray(inner) && inner[0] === ';' ? inner.slice(1) : []
44
+ const out = new Map()
45
+ for (const stmt of stmts) {
46
+ if (!Array.isArray(stmt) || (stmt[0] !== 'const' && stmt[0] !== 'let')) continue
47
+ for (let i = 1; i < stmt.length; i++) {
48
+ const decl = stmt[i]
49
+ if (!Array.isArray(decl) || decl[0] !== '=' || typeof decl[1] !== 'string') continue
50
+ if (stmt[0] === 'let' && isReassigned(body, decl[1])) continue
51
+ const v = intConstExpr(decl[2])
52
+ if (v != null && v >= -2147483648 && v <= 2147483647) out.set(decl[1], v)
53
+ }
54
+ }
55
+ return out
56
+ }
17
57
 
18
58
 
19
59
  export default (ctx) => {
@@ -43,6 +83,13 @@ export default (ctx) => {
43
83
  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})`)
44
84
  // Generate closure body function name
45
85
  const fnName = `${T}closure${ctx.closure.table.length}`
86
+ const localIntConsts = ctx.func.body ? topLevelIntConsts(ctx.func.body) : new Map()
87
+ const captureIntConsts = new Map()
88
+ for (const name of captures) {
89
+ const v = ctx.scope.constInts?.get(name) ?? localIntConsts.get(name)
90
+ if (v != null && !ctx.func.boxed?.has(name)) captureIntConsts.set(name, v)
91
+ }
92
+ const envCaptures = captureIntConsts.size ? captures.filter(name => !captureIntConsts.has(name)) : captures
46
93
  const captureValTypes = new Map()
47
94
  const captureSchemaVars = new Map()
48
95
  const captureTypedElems = new Map()
@@ -51,7 +98,7 @@ export default (ctx) => {
51
98
  // call_indirect on the captured pointer). Gated on isReassigned over the inner body
52
99
  // so a local rewrite of the captured name disables propagation.
53
100
  const captureDirectClosures = new Map()
54
- for (const name of captures) {
101
+ for (const name of envCaptures) {
55
102
  const vt = lookupValType(name)
56
103
  if (vt != null) captureValTypes.set(name, vt)
57
104
  const schemaId = ctx.schema.idOf(name)
@@ -62,13 +109,26 @@ export default (ctx) => {
62
109
  if (bodyName && !isReassigned(body, name)) captureDirectClosures.set(name, bodyName)
63
110
  }
64
111
 
112
+ const schemaNames = ctx.schema.vars?.size ? new Set(ctx.schema.vars.keys()) : null
113
+ if (schemaNames?.size) {
114
+ const refs = []
115
+ findFreeVars(body, new Set(params), refs, schemaNames)
116
+ for (const def of Object.values(defaults || {})) findFreeVars(def, new Set(params), refs, schemaNames)
117
+ for (const name of refs) {
118
+ if (captureSchemaVars.has(name)) continue
119
+ const schemaId = ctx.schema.idOf(name)
120
+ if (schemaId != null) captureSchemaVars.set(name, schemaId)
121
+ }
122
+ }
123
+
65
124
  // All closures use uniform convention: (env: f64, args_array: f64) → f64
66
125
  // The body unpacks individual params from the args array
67
- const boxedCaptures = captures.filter(c => ctx.func.boxed?.has(c))
68
- const bodyFn = { name: fnName, params, body, captures, arity: 1,
126
+ const boxedCaptures = envCaptures.filter(c => ctx.func.boxed?.has(c))
127
+ const bodyFn = { name: fnName, params, body, captures: envCaptures, arity: 1,
69
128
  ...(restParam && { rest: restParam }),
70
129
  ...(defaults && { defaults }),
71
130
  ...(boxedCaptures.length && { boxed: new Set(boxedCaptures) }),
131
+ ...(captureIntConsts.size && { intConsts: captureIntConsts }),
72
132
  ...(captureValTypes.size && { valTypes: captureValTypes }),
73
133
  ...(captureSchemaVars.size && { schemaVars: captureSchemaVars }),
74
134
  ...(captureTypedElems.size && { typedElems: captureTypedElems }),
@@ -81,7 +141,7 @@ export default (ctx) => {
81
141
  // Tag IR with .closureBodyName so emitDecl can register the binding for direct dispatch
82
142
  // (skip call_indirect on a const-bound, non-escaping closure local). See emit.js '()' handler.
83
143
  ctx.features.closure = true
84
- if (captures.length === 0) {
144
+ if (envCaptures.length === 0) {
85
145
  // No captures — just a function reference
86
146
  const ir = mkPtrIR(PTR.CLOSURE, tableIdx, 0)
87
147
  ir.closureBodyName = fnName
@@ -92,16 +152,16 @@ export default (ctx) => {
92
152
  const t = tempI32('env')
93
153
 
94
154
  const block = [
95
- ['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', captures.length * 8]]],
155
+ ['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', envCaptures.length * 8]]],
96
156
  ]
97
157
  // Store captured values in env: boxed cells as raw i32 in low 4 bytes, others as f64.
98
158
  // Avoids i32↔f64 roundtrip; body loads via i32.load/f64.load using the same branch.
99
- for (let i = 0; i < captures.length; i++) {
159
+ for (let i = 0; i < envCaptures.length; i++) {
100
160
  const addr = ['i32.add', ['local.get', `$${t}`], ['i32.const', i * 8]]
101
- if (ctx.func.boxed?.has(captures[i]))
102
- block.push(['i32.store', addr, ['local.get', `$${ctx.func.boxed.get(captures[i])}`]])
161
+ if (ctx.func.boxed?.has(envCaptures[i]))
162
+ block.push(['i32.store', addr, ['local.get', `$${ctx.func.boxed.get(envCaptures[i])}`]])
103
163
  else
104
- block.push(['f64.store', addr, asF64(emit(captures[i]))])
164
+ block.push(['f64.store', addr, asF64(emit(envCaptures[i]))])
105
165
  }
106
166
  block.push(mkPtrIR(PTR.CLOSURE, tableIdx, ['local.get', `$${t}`]))
107
167
 
@@ -128,15 +188,15 @@ export default (ctx) => {
128
188
  const arrT = tempI32('sa')
129
189
  const lenL = tempI32('sl')
130
190
  const setup = [
131
- ['local.set', `$${arrT}`, ['call', '$__ptr_offset', asF64(args[0])]],
132
- ['local.set', `$${lenL}`, ['call', '$__len', ['local.get', `$${t}`]]], // placeholder — set below
191
+ ['local.set', `$${arrT}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(args[0])]]],
192
+ ['local.set', `$${lenL}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]], // placeholder — set below
133
193
  ]
134
194
  // Rebuild setup properly since we need the array ptr before len call
135
195
  setup.length = 0
136
196
  const arrPtrF64 = temp('sp')
137
197
  setup.push(['local.set', `$${arrPtrF64}`, asF64(args[0])])
138
- setup.push(['local.set', `$${arrT}`, ['call', '$__ptr_offset', ['local.get', `$${arrPtrF64}`]]])
139
- setup.push(['local.set', `$${lenL}`, ['call', '$__len', ['local.get', `$${arrPtrF64}`]]])
198
+ setup.push(['local.set', `$${arrT}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${arrPtrF64}`]]]])
199
+ setup.push(['local.set', `$${lenL}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${arrPtrF64}`]]]])
140
200
 
141
201
  const W = ctx.closure.width ?? MAX_CLOSURE_ARITY
142
202
  const slots = []
@@ -153,10 +213,10 @@ export default (ctx) => {
153
213
  ['local.get', `$${t}`],
154
214
  ['local.get', `$${lenL}`],
155
215
  ...slots,
156
- // Inline __ptr_aux for CLOSURE pointer: aux = bits 32..46 holds funcIdx.
216
+ // Inline __ptr_aux for CLOSURE pointer: aux holds funcIdx.
157
217
  ['i32.wrap_i64', ['i64.and',
158
- ['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', 32]],
159
- ['i64.const', 0x7FFF]]]]], 'f64')
218
+ ['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', LAYOUT.AUX_SHIFT]],
219
+ ['i64.const', LAYOUT.AUX_MASK]]]]], 'f64')
160
220
  }
161
221
 
162
222
  // Inline path: emit each arg, pad missing slots with UNDEF
@@ -173,9 +233,9 @@ export default (ctx) => {
173
233
  ['local.get', `$${t}`],
174
234
  ['i32.const', n],
175
235
  ...slots,
176
- // Inline __ptr_aux for CLOSURE pointer: aux = bits 32..46 holds funcIdx.
236
+ // Inline __ptr_aux for CLOSURE pointer: aux holds funcIdx.
177
237
  ['i32.wrap_i64', ['i64.and',
178
- ['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', 32]],
179
- ['i64.const', 0x7FFF]]]]], 'f64')
238
+ ['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', LAYOUT.AUX_SHIFT]],
239
+ ['i64.const', LAYOUT.AUX_MASK]]]]], 'f64')
180
240
  }
181
241
  }
package/module/index.js CHANGED
@@ -12,4 +12,5 @@ import console from './console.js'
12
12
  import json from './json.js'
13
13
  import regex from './regex.js'
14
14
  import timer from './timer.js'
15
- export { math, core, array, object, string, number, fn, typedarray, collection, symbol, console, json, regex, timer }
15
+ import date from './date.js'
16
+ export { math, core, array, object, string, number, fn, typedarray, collection, symbol, console, json, regex, timer, date }