jz 0.8.0 → 0.9.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.
Files changed (73) hide show
  1. package/README.md +56 -9
  2. package/bench/README.md +121 -50
  3. package/bench/bench.svg +27 -27
  4. package/cli.js +3 -1
  5. package/dist/interop.js +1 -1
  6. package/dist/jz.js +7541 -6178
  7. package/index.js +165 -74
  8. package/interop.js +189 -17
  9. package/jzify/arguments.js +32 -1
  10. package/jzify/async.js +409 -0
  11. package/jzify/classes.js +136 -18
  12. package/jzify/generators.js +639 -0
  13. package/jzify/hoist-vars.js +73 -1
  14. package/jzify/index.js +123 -4
  15. package/jzify/names.js +1 -0
  16. package/jzify/transform.js +239 -1
  17. package/layout.js +48 -3
  18. package/module/array.js +318 -43
  19. package/module/atomics.js +144 -0
  20. package/module/collection.js +1429 -155
  21. package/module/core.js +431 -40
  22. package/module/date.js +142 -120
  23. package/module/fs.js +144 -0
  24. package/module/function.js +6 -3
  25. package/module/index.js +4 -1
  26. package/module/json.js +270 -49
  27. package/module/math.js +1032 -145
  28. package/module/number.js +532 -163
  29. package/module/object.js +353 -95
  30. package/module/regex.js +157 -7
  31. package/module/schema.js +100 -2
  32. package/module/string.js +428 -93
  33. package/module/typedarray.js +264 -72
  34. package/module/web.js +36 -0
  35. package/package.json +5 -5
  36. package/src/abi/string.js +82 -11
  37. package/src/ast.js +11 -5
  38. package/src/autoload.js +28 -1
  39. package/src/bridge.js +7 -2
  40. package/src/compile/analyze-scans.js +22 -7
  41. package/src/compile/analyze.js +136 -30
  42. package/src/compile/dyn-closure-tables.js +277 -0
  43. package/src/compile/emit-assign.js +281 -15
  44. package/src/compile/emit.js +1055 -70
  45. package/src/compile/index.js +277 -29
  46. package/src/compile/infer.js +42 -4
  47. package/src/compile/inplace-store.js +329 -0
  48. package/src/compile/loop-recurrence.js +167 -0
  49. package/src/compile/narrow.js +555 -18
  50. package/src/compile/peel-stencil.js +5 -0
  51. package/src/compile/plan/index.js +45 -3
  52. package/src/compile/plan/inline.js +8 -1
  53. package/src/compile/plan/literals.js +39 -0
  54. package/src/compile/plan/scope.js +430 -23
  55. package/src/compile/program-facts.js +732 -38
  56. package/src/ctx.js +64 -9
  57. package/src/helper-counters.js +7 -1
  58. package/src/ir.js +113 -5
  59. package/src/kind-traits.js +66 -3
  60. package/src/kind.js +84 -12
  61. package/src/op-policy.js +7 -4
  62. package/src/optimize/index.js +1179 -704
  63. package/src/optimize/recurse.js +2 -2
  64. package/src/optimize/vectorize.js +982 -67
  65. package/src/prepare/index.js +809 -67
  66. package/src/prepare/math-kernel.js +331 -0
  67. package/src/prepare/pre-eval.js +714 -0
  68. package/src/snapshot.js +194 -0
  69. package/src/type.js +1170 -56
  70. package/src/wat/assemble.js +403 -65
  71. package/src/wat/codegen.js +74 -14
  72. package/transform.js +113 -4
  73. package/wasi.js +3 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Pre-eval tier 3 — module-init snapshotting (V8-snapshot style).
3
+ *
4
+ * Runs the module's OWN top-level init (`__start`) once at COMPILE TIME and
5
+ * bakes the result into the artifact: the post-init heap image becomes the data
6
+ * segment, every mutable global's post-init value becomes its declared
7
+ * initializer, and `__start` is deleted. Instantiation then costs zero init —
8
+ * the init-built tables (watr's OPCODE/IMM dicts, interned atoms, schema
9
+ * registries, lookup tables) are pure data, and the init-vs-runtime storage
10
+ * bug class (durable-dangler landmines) loses its init half entirely.
11
+ *
12
+ * Soundness is proven DYNAMICALLY, not assumed:
13
+ * - the probe instance's env imports are throwing stubs — if init calls ANY
14
+ * host function, instantiation throws and the snapshot is declined (the
15
+ * module compiles exactly as before);
16
+ * - modules whose `__start` textually reads an IMPORTED GLOBAL are declined
17
+ * statically (a read can't be made to throw);
18
+ * - non-returning starts (the `__timer_loop` tail) and shared memories are
19
+ * declined statically.
20
+ * Determinism: everything `__start` can reach without the host is arithmetic
21
+ * over the module's own statics — Date/random/imports all live behind the env
22
+ * boundary the stubs seal.
23
+ *
24
+ * The capture round-trips exact bits: f64 globals are read back through a
25
+ * Float64Array view (a JS number preserves any payload as long as no
26
+ * arithmetic touches it) and re-emitted as `nan:0x…` literals when NaN-boxed,
27
+ * `-0`-aware decimal otherwise.
28
+ *
29
+ * Host-only by construction (needs WebAssembly instantiation of the probe):
30
+ * the self-host kernel never passes the flag; a typeof guard declines cleanly.
31
+ */
32
+ import { i64Hex } from '../layout.js'
33
+
34
+ const findNode = (mod, pred) => mod.find(n => Array.isArray(n) && pred(n))
35
+ const findAll = (mod, pred) => mod.filter(n => Array.isArray(n) && pred(n))
36
+
37
+ // Per-byte escape for a watr data-segment string literal (mirror of
38
+ // compile/index.js's escBytes, over raw bytes instead of charCodes).
39
+ const escImage = (bytes) => {
40
+ let esc = ''
41
+ for (let i = 0; i < bytes.length; i++) {
42
+ const c = bytes[i]
43
+ if (c >= 32 && c < 127 && c !== 34 && c !== 92) esc += String.fromCharCode(c)
44
+ else esc += '\\' + c.toString(16).padStart(2, '0')
45
+ }
46
+ return esc
47
+ }
48
+
49
+ const f64BitsLit = (bits) => {
50
+ bits = BigInt.asUintN(64, bits)
51
+ if ((bits & 0x7FF0000000000000n) === 0x7FF0000000000000n && (bits & 0xFFFFFFFFFFFFFn) !== 0n)
52
+ return `nan:${i64Hex(bits)}` // NaN (boxed or plain): exact payload
53
+ const v = new Float64Array(new BigUint64Array([bits]).buffer)[0]
54
+ if (Object.is(v, -0)) return '-0'
55
+ if (v === Infinity) return 'inf'
56
+ if (v === -Infinity) return '-inf'
57
+ return String(v) // shortest round-trip = exact
58
+ }
59
+ const f32BitsLit = (bits) => {
60
+ const v = new Float32Array(new Int32Array([bits | 0]).buffer)[0]
61
+ if ((bits & 0x7F800000) === 0x7F800000 && (bits & 0x7FFFFF) !== 0)
62
+ return `nan:0x${(bits >>> 0).toString(16)}`
63
+ if (Object.is(v, -0)) return '-0'
64
+ if (v === Infinity) return 'inf'
65
+ if (v === -Infinity) return '-inf'
66
+ return String(v)
67
+ }
68
+
69
+ /** Mutates `module` (the final watr AST) in place. Returns true if snapshotted,
70
+ * false if declined (module untouched). */
71
+ export function snapshotInit(module, watrCompile) {
72
+ if (typeof WebAssembly === 'undefined') return false
73
+ if (!Array.isArray(module) || module[0] !== 'module') return false
74
+
75
+ // Init lives in a `(start)` directive (js host) or, under the WASI reactor
76
+ // convention, in a func exported as `_initialize` (host: 'wasi' never emits a
77
+ // start section — the p1 ABI forbids WASI calls there).
78
+ const startDir = findNode(module, n => n[0] === 'start')
79
+ const startFn = startDir
80
+ ? findNode(module, n => n[0] === 'func' && n[1] === startDir[1])
81
+ : findNode(module, n => n[0] === 'func' && n.some(c => Array.isArray(c) && c[0] === 'export' && c[1] === '"_initialize"'))
82
+ if (!startFn) return false // nothing to snapshot
83
+ const startName = startFn[1]
84
+ const startText = JSON.stringify(startFn)
85
+ if (startText.includes('__timer_loop')) return false // non-returning start
86
+
87
+ // Shared/imported memory: the image belongs to the host — decline.
88
+ const memNode = findNode(module, n => n[0] === 'memory')
89
+ if (!memNode) return false
90
+ const imports = findAll(module, n => n[0] === 'import')
91
+ if (imports.some(n => JSON.stringify(n).includes('"memory"'))) return false
92
+
93
+ // Imported GLOBALS read during init can't be stub-detected — decline statically.
94
+ const importedGlobals = imports
95
+ .filter(n => n.some(c => Array.isArray(c) && c[0] === 'global'))
96
+ .map(n => n.find(c => Array.isArray(c) && c[0] === 'global')?.[1])
97
+ .filter(Boolean)
98
+ if (importedGlobals.some(g => startText.includes(`"${g}"`) || startText.includes(`${g}"`))) return false
99
+
100
+ const globals = findAll(module, n => n[0] === 'global' && typeof n[1] === 'string')
101
+
102
+ // ── probe: synthesize a bit-exact GETTER per global, encode, instantiate with
103
+ // sealing stubs. Direct `WebAssembly.Global.value` reads are useless for the
104
+ // f64 globals that matter most: the JS API canonicalizes NaN payloads at the
105
+ // boundary, wiping every NaN-boxed pointer. An exported func returning
106
+ // `i64.reinterpret_f64` crosses as BigInt — payload intact. f32 → i32 for the
107
+ // same reason; i32/i64 read direct.
108
+ const gtype = (g) => {
109
+ const m = g.find(c => Array.isArray(c) && c[0] === 'mut')
110
+ return m ? m[1] : g.find(c => typeof c === 'string' && c !== g[1] && ['i32', 'i64', 'f32', 'f64', 'v128'].includes(c))
111
+ }
112
+ const getters = []
113
+ for (const g of globals) {
114
+ const ty = gtype(g)
115
+ if (!ty || ty === 'v128') continue
116
+ const body = ty === 'f64' ? ['i64.reinterpret_f64', ['global.get', g[1]]]
117
+ : ty === 'f32' ? ['i32.reinterpret_f32', ['global.get', g[1]]]
118
+ : ['global.get', g[1]]
119
+ getters.push(['func', ['export', `"__snapg${g[1]}"`], ['result', ty === 'f64' ? 'i64' : ty === 'f32' ? 'i32' : ty], body])
120
+ }
121
+ module.push(...getters)
122
+ let inst
123
+ try {
124
+ const bytes = watrCompile(module)
125
+ const stubs = {}
126
+ for (const imp of imports) {
127
+ const fn = imp.find(c => Array.isArray(c) && c[0] === 'func')
128
+ const gl = imp.find(c => Array.isArray(c) && c[0] === 'global')
129
+ const modName = JSON.parse(imp[1]), name = JSON.parse(imp[2]) // keyed by import module: env AND wasi_snapshot_preview1
130
+ if (fn) (stubs[modName] ||= {})[name] = () => { throw new Error('__hermetic__') }
131
+ else if (gl) (stubs[modName] ||= {})[name] = new WebAssembly.Global({ value: 'i64' }, 0n)
132
+ }
133
+ inst = new WebAssembly.Instance(new WebAssembly.Module(bytes), stubs)
134
+ // Reactor form: init doesn't run at instantiation — drive it explicitly so the
135
+ // hermeticity stubs get their chance to throw (start-section form ran above).
136
+ if (!startDir) inst.exports._initialize()
137
+ } catch (e) {
138
+ module.length -= getters.length // restore, decline
139
+ return false
140
+ }
141
+
142
+ // ── capture ──
143
+ const ex = inst.exports
144
+ const heapFn = ex['__snapg$__heap']
145
+ if (!heapFn) { module.length -= getters.length; return false }
146
+ const heapTop = Number(heapFn())
147
+ const image = new Uint8Array(ex.memory.buffer.slice(0, heapTop))
148
+
149
+ const captured = new Map()
150
+ for (const g of globals) {
151
+ const snap = ex[`__snapg${g[1]}`]
152
+ if (snap) captured.set(g[1], snap()) // i64→BigInt (bit-exact), i32→number
153
+ }
154
+
155
+ // ── bake ──
156
+ module.length -= getters.length // drop probe getters
157
+ for (const g of globals) {
158
+ if (!captured.has(g[1])) continue
159
+ const mutIdx = g.findIndex(c => Array.isArray(c) && c[0] === 'mut')
160
+ if (mutIdx === -1) continue // immutable: init already exact
161
+ const ty = g[mutIdx][1]
162
+ const v = captured.get(g[1]) // f64 arrives as raw i64 BITS (BigInt)
163
+ const lit = ty === 'f64' ? f64BitsLit(BigInt(v))
164
+ : ty === 'f32' ? f32BitsLit(Number(v))
165
+ : ty === 'i64' ? String(BigInt(v))
166
+ : String(Number(v) | 0)
167
+ g[g.length - 1] = [`${ty}.const`, lit]
168
+ }
169
+ // data segment ← post-init image (contains the original statics as its prefix)
170
+ const dataNode = findNode(module, n => n[0] === 'data' && Array.isArray(n[1]) && n[1][0] === 'i32.const' && Number(n[1][1]) === 0)
171
+ const imageStr = '"' + escImage(image) + '"'
172
+ if (dataNode) dataNode[2] = imageStr
173
+ else module.push(['data', ['i32.const', '0'], imageStr])
174
+ // memory floor must cover the image
175
+ const pages = Math.max(1, Math.ceil(image.length / 65536))
176
+ for (let i = 1; i < memNode.length; i++)
177
+ if (typeof memNode[i] === 'string' && /^\d+$/.test(memNode[i])) { if (Number(memNode[i]) < pages) memNode[i] = String(pages); break }
178
+ else if (typeof memNode[i] === 'number') { if (memNode[i] < pages) memNode[i] = pages; break }
179
+ // __start is spent
180
+ if (startDir) module.splice(module.indexOf(startDir), 1)
181
+ module.splice(module.indexOf(startFn), 1)
182
+ // Reactor form: WASI command wrappers self-init via a void `call $__start` —
183
+ // init is baked into the image now, so those calls must go with the func.
184
+ const stripCalls = (n) => {
185
+ for (let i = n.length - 1; i >= 0; i--) {
186
+ const c = n[i]
187
+ if (!Array.isArray(c)) continue
188
+ if (c[0] === 'call' && c[1] === startName && c.length === 2) n.splice(i, 1)
189
+ else stripCalls(c)
190
+ }
191
+ }
192
+ for (const f of findAll(module, n => n[0] === 'func')) stripCalls(f)
193
+ return true
194
+ }