jz 0.1.0 → 0.2.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.
@@ -10,13 +10,54 @@
10
10
  * @module fn
11
11
  */
12
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'
13
+ import { typed, asF64, asI32, mkPtrIR, temp, tempI32, MAX_CLOSURE_ARITY, UNDEF_NAN } from '../src/ir.js'
14
+ import { emit, isReassigned } from '../src/emit.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
+ }
16
57
 
17
58
 
18
59
  export default (ctx) => {
19
- inc('__mkptr', '__alloc', '__len', '__ptr_offset')
60
+ inc('__mkptr', '__alloc', '__len', '__ptr_offset', '__ptr_type')
20
61
 
21
62
  // Uniform closure convention: (env f64, argc i32, a0..a{MAX-1} f64) → f64
22
63
  if (!ctx.closure.types) ctx.closure.types = new Set()
@@ -42,6 +83,13 @@ export default (ctx) => {
42
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})`)
43
84
  // Generate closure body function name
44
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
45
93
  const captureValTypes = new Map()
46
94
  const captureSchemaVars = new Map()
47
95
  const captureTypedElems = new Map()
@@ -50,7 +98,7 @@ export default (ctx) => {
50
98
  // call_indirect on the captured pointer). Gated on isReassigned over the inner body
51
99
  // so a local rewrite of the captured name disables propagation.
52
100
  const captureDirectClosures = new Map()
53
- for (const name of captures) {
101
+ for (const name of envCaptures) {
54
102
  const vt = lookupValType(name)
55
103
  if (vt != null) captureValTypes.set(name, vt)
56
104
  const schemaId = ctx.schema.idOf(name)
@@ -61,13 +109,26 @@ export default (ctx) => {
61
109
  if (bodyName && !isReassigned(body, name)) captureDirectClosures.set(name, bodyName)
62
110
  }
63
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
+
64
124
  // All closures use uniform convention: (env: f64, args_array: f64) → f64
65
125
  // 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,
126
+ const boxedCaptures = envCaptures.filter(c => ctx.func.boxed?.has(c))
127
+ const bodyFn = { name: fnName, params, body, captures: envCaptures, arity: 1,
68
128
  ...(restParam && { rest: restParam }),
69
129
  ...(defaults && { defaults }),
70
130
  ...(boxedCaptures.length && { boxed: new Set(boxedCaptures) }),
131
+ ...(captureIntConsts.size && { intConsts: captureIntConsts }),
71
132
  ...(captureValTypes.size && { valTypes: captureValTypes }),
72
133
  ...(captureSchemaVars.size && { schemaVars: captureSchemaVars }),
73
134
  ...(captureTypedElems.size && { typedElems: captureTypedElems }),
@@ -80,7 +141,7 @@ export default (ctx) => {
80
141
  // Tag IR with .closureBodyName so emitDecl can register the binding for direct dispatch
81
142
  // (skip call_indirect on a const-bound, non-escaping closure local). See emit.js '()' handler.
82
143
  ctx.features.closure = true
83
- if (captures.length === 0) {
144
+ if (envCaptures.length === 0) {
84
145
  // No captures — just a function reference
85
146
  const ir = mkPtrIR(PTR.CLOSURE, tableIdx, 0)
86
147
  ir.closureBodyName = fnName
@@ -91,16 +152,16 @@ export default (ctx) => {
91
152
  const t = tempI32('env')
92
153
 
93
154
  const block = [
94
- ['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', captures.length * 8]]],
155
+ ['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', envCaptures.length * 8]]],
95
156
  ]
96
157
  // Store captured values in env: boxed cells as raw i32 in low 4 bytes, others as f64.
97
158
  // Avoids i32↔f64 roundtrip; body loads via i32.load/f64.load using the same branch.
98
- for (let i = 0; i < captures.length; i++) {
159
+ for (let i = 0; i < envCaptures.length; i++) {
99
160
  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])}`]])
161
+ if (ctx.func.boxed?.has(envCaptures[i]))
162
+ block.push(['i32.store', addr, ['local.get', `$${ctx.func.boxed.get(envCaptures[i])}`]])
102
163
  else
103
- block.push(['f64.store', addr, asF64(emit(captures[i]))])
164
+ block.push(['f64.store', addr, asF64(emit(envCaptures[i]))])
104
165
  }
105
166
  block.push(mkPtrIR(PTR.CLOSURE, tableIdx, ['local.get', `$${t}`]))
106
167
 
@@ -127,15 +188,15 @@ export default (ctx) => {
127
188
  const arrT = tempI32('sa')
128
189
  const lenL = tempI32('sl')
129
190
  const setup = [
130
- ['local.set', `$${arrT}`, ['call', '$__ptr_offset', asF64(args[0])]],
131
- ['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
132
193
  ]
133
194
  // Rebuild setup properly since we need the array ptr before len call
134
195
  setup.length = 0
135
196
  const arrPtrF64 = temp('sp')
136
197
  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}`]]])
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}`]]]])
139
200
 
140
201
  const W = ctx.closure.width ?? MAX_CLOSURE_ARITY
141
202
  const slots = []
@@ -152,10 +213,10 @@ export default (ctx) => {
152
213
  ['local.get', `$${t}`],
153
214
  ['local.get', `$${lenL}`],
154
215
  ...slots,
155
- // Inline __ptr_aux for CLOSURE pointer: aux = bits 32..46 holds funcIdx.
216
+ // Inline __ptr_aux for CLOSURE pointer: aux holds funcIdx.
156
217
  ['i32.wrap_i64', ['i64.and',
157
- ['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', 32]],
158
- ['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')
159
220
  }
160
221
 
161
222
  // Inline path: emit each arg, pad missing slots with UNDEF
@@ -172,9 +233,9 @@ export default (ctx) => {
172
233
  ['local.get', `$${t}`],
173
234
  ['i32.const', n],
174
235
  ...slots,
175
- // Inline __ptr_aux for CLOSURE pointer: aux = bits 32..46 holds funcIdx.
236
+ // Inline __ptr_aux for CLOSURE pointer: aux holds funcIdx.
176
237
  ['i32.wrap_i64', ['i64.and',
177
- ['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', 32]],
178
- ['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')
179
240
  }
180
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 }