@fraczak/k 6.3.3 → 6.5.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 (157) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/DOCS/CODECS.md +258 -0
  3. package/DOCS/DICTIONARY.md +1172 -0
  4. package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
  5. package/DOCS/KIR_V1.md +221 -0
  6. package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
  7. package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
  8. package/DOCS/PROFILING_AND_TRACING.md +196 -0
  9. package/DOCS/REPL.md +61 -4
  10. package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
  11. package/DOCS/dependency-graph.md +144 -0
  12. package/Examples/arithmetics.k +108 -82
  13. package/Examples/bnat.k +1 -1
  14. package/Examples/buda.k +44 -0
  15. package/Examples/ieee.k +300 -8671
  16. package/Examples/poly.k +67 -0
  17. package/README.md +68 -19
  18. package/Value.mjs +8 -2
  19. package/augmentation.mjs +9 -7
  20. package/backend-api.mjs +11 -2
  21. package/backends/arm64/README.md +104 -0
  22. package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
  23. package/backends/arm64/bin/k-arm64-run.mjs +58 -0
  24. package/backends/arm64/bin/k-arm64.mjs +88 -0
  25. package/backends/arm64/scripts/run-tests.mjs +41 -0
  26. package/backends/arm64/src/arm64.mjs +229 -0
  27. package/backends/arm64/src/cli.mjs +155 -0
  28. package/backends/arm64/src/kvm2arm64.mjs +906 -0
  29. package/backends/arm64/src/runtime.c +728 -0
  30. package/backends/arm64/tests/integration.sh +112 -0
  31. package/backends/arm64/tests/test-arm64.mjs +240 -0
  32. package/backends/llvm/README.md +135 -0
  33. package/backends/llvm/bin/k-llvm-build.mjs +71 -0
  34. package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
  35. package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
  36. package/backends/llvm/bin/k-llvm-run.mjs +90 -0
  37. package/backends/llvm/runtime/krt.c +1508 -0
  38. package/backends/llvm/runtime/krt.h +109 -0
  39. package/backends/llvm/scripts/conformance.mjs +76 -0
  40. package/backends/llvm/scripts/run-tests.mjs +28 -0
  41. package/backends/llvm/src/executable.mjs +562 -0
  42. package/backends/llvm/src/kvm2llvm.mjs +998 -0
  43. package/backends/llvm/src/llvm.mjs +1092 -0
  44. package/backends/llvm/tests/composition-driver.c +22 -0
  45. package/backends/llvm/tests/identity-driver.c +27 -0
  46. package/backends/llvm/tests/integration.sh +153 -0
  47. package/backends/llvm/tests/perf-support.mjs +735 -0
  48. package/backends/llvm/tests/product-driver.c +23 -0
  49. package/backends/llvm/tests/projection-driver.c +19 -0
  50. package/backends/llvm/tests/relation-driver.c +29 -0
  51. package/backends/llvm/tests/test-llvm.mjs +240 -0
  52. package/backends/llvm/tests/union-driver.c +31 -0
  53. package/backends/llvm/tests/variant-driver.c +19 -0
  54. package/backends/llvm/tests/variant-projection-driver.c +21 -0
  55. package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
  56. package/backends/wasm/README.md +97 -0
  57. package/backends/wasm/TECHNICAL-DEBT.md +58 -0
  58. package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
  59. package/backends/wasm/bin/k-wasm-run.mjs +140 -0
  60. package/backends/wasm/bin/k-wasm.mjs +49 -0
  61. package/backends/wasm/demo_ieee.mjs +319 -0
  62. package/backends/wasm/runtime.wat +475 -0
  63. package/backends/wasm/scripts/run-tests.mjs +41 -0
  64. package/backends/wasm/src/cli.mjs +133 -0
  65. package/backends/wasm/src/kvm2wasm.mjs +697 -0
  66. package/backends/wasm/src/wasm.mjs +1054 -0
  67. package/backends/wasm/tests/integration.sh +182 -0
  68. package/backends/wasm/tests/perf-support.mjs +336 -0
  69. package/backends/wasm/tests/test-wasm.mjs +625 -0
  70. package/browser/repl-browser.mjs +1584 -0
  71. package/browser/shims/assert.mjs +19 -0
  72. package/browser/shims/buffer.mjs +224 -0
  73. package/browser/shims/crypto.mjs +103 -0
  74. package/browser/shims/fs.mjs +159 -0
  75. package/browser/shims/os.mjs +17 -0
  76. package/browser/shims/path.mjs +54 -0
  77. package/browser/shims/process.mjs +44 -0
  78. package/browser/shims/readline.mjs +11 -0
  79. package/browser/shims/url.mjs +12 -0
  80. package/browser/vfs-data.mjs +19 -0
  81. package/codecs/README.md +3 -0
  82. package/codecs/runtime/show-value.mjs +12 -3
  83. package/conformance/helper-reuse/case.json +8 -0
  84. package/conformance/helper-reuse/expected.kv +1 -0
  85. package/conformance/helper-reuse/input.kv +1 -0
  86. package/conformance/helper-reuse/program.k +2 -0
  87. package/conformance/identity/case.json +8 -0
  88. package/conformance/identity/expected.kv +1 -0
  89. package/conformance/identity/input.kv +1 -0
  90. package/conformance/identity/program.k +1 -0
  91. package/conformance/product-projection/case.json +8 -0
  92. package/conformance/product-projection/expected.kv +1 -0
  93. package/conformance/product-projection/input.kv +1 -0
  94. package/conformance/product-projection/program.k +1 -0
  95. package/conformance/run.mjs +149 -0
  96. package/conformance/variant-construction/case.json +8 -0
  97. package/conformance/variant-construction/expected.kv +1 -0
  98. package/conformance/variant-construction/input.kv +1 -0
  99. package/conformance/variant-construction/program.k +1 -0
  100. package/harness/adapters/arm64.mjs +105 -0
  101. package/harness/adapters/kvm.mjs +144 -0
  102. package/harness/adapters/llvm.mjs +118 -0
  103. package/harness/adapters/native.mjs +77 -0
  104. package/harness/adapters/wasm.mjs +169 -0
  105. package/harness/backend.mjs +95 -0
  106. package/harness/cli.mjs +130 -0
  107. package/harness/format.mjs +95 -0
  108. package/harness/index.mjs +25 -0
  109. package/harness/persistent.mjs +121 -0
  110. package/harness/registry.mjs +44 -0
  111. package/harness/suite.mjs +257 -0
  112. package/harness/suites/poly.mjs +156 -0
  113. package/harness/trace.mjs +50 -0
  114. package/harness/util.mjs +92 -0
  115. package/intrinsics.mjs +41 -0
  116. package/kir.mjs +399 -0
  117. package/kvm.mjs +585 -81
  118. package/objects/README.md +37 -3
  119. package/objects/compile.mjs +15 -22
  120. package/objects/inspect.mjs +96 -0
  121. package/objects/validate.mjs +415 -0
  122. package/package.json +51 -19
  123. package/repl-codecs.mjs +105 -6
  124. package/repl.mjs +335 -49
  125. package/run.mjs +2 -7
  126. package/scripts/benchmark.mjs +406 -22
  127. package/scripts/build-repl-html.mjs +1784 -0
  128. package/scripts/perf-ieee.mjs +586 -0
  129. package/scripts/perf-int.mjs +531 -0
  130. package/scripts/perf-poly.mjs +945 -0
  131. package/scripts/render-benchmark.mjs +12 -8
  132. package/scripts/run-tests.mjs +6 -0
  133. package/tests/integration.sh +19 -2
  134. package/tests/test-arithmetics.mjs +54 -0
  135. package/tests/test-ieee-arithmetic.mjs +4 -0
  136. package/tests/test-kir.mjs +85 -0
  137. package/tests/test-kvm-polymorphic.mjs +278 -0
  138. package/tests/test-kvm.mjs +25 -6
  139. package/tests/test-poly.mjs +129 -0
  140. package/tests/test-repl-html.mjs +303 -0
  141. package/tests/test-repl.mjs +144 -1
  142. package/tests/test-validate-object.mjs +40 -0
  143. package/a.klib +0 -1
  144. package/a.kvm +0 -142676
  145. package/a.wasm +0 -0
  146. package/benchmark-results.html +0 -532
  147. package/benchmark-results.json +0 -480
  148. package/k-vscode/.vscode/launch.json +0 -14
  149. package/k-vscode/README.md +0 -75
  150. package/k-vscode/extension.js +0 -273
  151. package/k-vscode/language-configuration.json +0 -42
  152. package/k-vscode/package.json +0 -79
  153. package/k-vscode/snippets/k.json +0 -92
  154. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  155. package/k-vscode/test/background-ranges.test.js +0 -50
  156. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  157. package/scripts/git-hooks/pre-commit +0 -10
@@ -0,0 +1,697 @@
1
+ import {
2
+ exportPatternGraph,
3
+ lowerToKVM,
4
+ patternToPropertyList
5
+ } from "@fraczak/k/backend-api.mjs";
6
+
7
+ const tagMap = new Map();
8
+ let nextTagId = 1;
9
+ function getTagId(tag) {
10
+ if (!tagMap.has(tag)) {
11
+ tagMap.set(tag, nextTagId++);
12
+ }
13
+ return tagMap.get(tag);
14
+ }
15
+ function getTagFromId(tagId) {
16
+ for (const [tag, id] of tagMap.entries()) {
17
+ if (id === tagId) return tag;
18
+ }
19
+ return null;
20
+ }
21
+ function getTagEntries() {
22
+ return [...tagMap.entries()]
23
+ .map(([tag, id]) => ({ tag, id }))
24
+ .sort((a, b) => a.id - b.id);
25
+ }
26
+ function resetTagIds() {
27
+ tagMap.clear();
28
+ nextTagId = 1;
29
+ }
30
+
31
+ const funcMap = new Map();
32
+ let nextFuncId = 1;
33
+ function getFuncId(name) {
34
+ if (!funcMap.has(name)) {
35
+ funcMap.set(name, nextFuncId++);
36
+ }
37
+ return funcMap.get(name);
38
+ }
39
+ function getFuncNameFromId(id) {
40
+ for (const [name, fId] of funcMap.entries()) {
41
+ if (fId === id) return name;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function cloneInstructions(insts) {
47
+ return insts.map(inst => {
48
+ const cloned = { ...inst };
49
+ if (inst.branches) {
50
+ cloned.branches = inst.branches.map(br => ({
51
+ label: br.label,
52
+ body: cloneInstructions(br.body)
53
+ }));
54
+ }
55
+ return cloned;
56
+ });
57
+ }
58
+
59
+ function renameRegisters(insts, prefix) {
60
+ const rename = (r) => {
61
+ if (!r) return r;
62
+ if (r === "%in" || r === "in") return r;
63
+ if (r.startsWith("%")) return "%" + prefix + r.slice(1);
64
+ if (r.startsWith("$")) return "$" + prefix + r.slice(1);
65
+ return r;
66
+ };
67
+
68
+ for (const inst of insts) {
69
+ if (inst.dest) inst.dest = rename(inst.dest);
70
+ if (inst.src) inst.src = rename(inst.src);
71
+ if (inst.branches) {
72
+ inst.branches.forEach((branch, i) => {
73
+ const nextPrefix = `${prefix}b${i}_`;
74
+ renameRegisters(branch.body, nextPrefix);
75
+ });
76
+ }
77
+ }
78
+ }
79
+
80
+ export function lowerToWasm(relDef, name, options = {}) {
81
+ const kvmFuncOriginal = relDef.body ? relDef : lowerToKVM(relDef, name, options);
82
+ // Clone to avoid modifying the original definition in the REPL state
83
+ const kvmFunc = {
84
+ ...kvmFuncOriginal,
85
+ body: cloneInstructions(kvmFuncOriginal.body)
86
+ };
87
+ renameRegisters(kvmFunc.body, "");
88
+ const typePatternGraph = relDef.typePatternGraph || kvmFunc.typePatternGraph;
89
+ const kvmProgram = options.kvmProgram || {};
90
+
91
+ const cleanReg = (rName) => rName.replace("%", "").replace("$", "");
92
+ const registerPatterns = new Map();
93
+ if (Array.isArray(kvmFunc.inputPattern)) {
94
+ registerPatterns.set("in", kvmFunc.inputPattern);
95
+ }
96
+
97
+ const mappedReg = (rName, inputMap = {}) => {
98
+ const raw = cleanReg(rName);
99
+ return inputMap[raw] || raw;
100
+ };
101
+
102
+ const setPattern = (rName, pattern) => {
103
+ if (rName && Array.isArray(pattern)) {
104
+ registerPatterns.set(cleanReg(rName), pattern);
105
+ }
106
+ };
107
+
108
+ const getPattern = (rName, inputMap = {}) => registerPatterns.get(mappedReg(rName, inputMap));
109
+
110
+ const getProductEdges = (inst, inputMap) => {
111
+ if (typePatternGraph) {
112
+ const inputPatternId = inst.exp.patterns[0];
113
+ const inputPatternNodeId = typePatternGraph.find(inputPatternId);
114
+ const inputPropertyList = patternToPropertyList(exportPatternGraph(typePatternGraph, inputPatternNodeId));
115
+ return inputPropertyList[0][1];
116
+ }
117
+
118
+ const inputPropertyList = getPattern(inst.src, inputMap);
119
+ const inputRoot = inputPropertyList?.[0];
120
+ if (!Array.isArray(inputRoot) || !Array.isArray(inputRoot[1])) {
121
+ throw new Error(`Wasm compiler: cannot infer input pattern for field '${inst.label}'`);
122
+ }
123
+ if (inputRoot[0] !== "open-product" && inputRoot[0] !== "closed-product") {
124
+ throw new Error(`Wasm compiler: field '${inst.label}' requires a product input pattern`);
125
+ }
126
+ return inputRoot[1];
127
+ };
128
+
129
+ const registers = new Set();
130
+ registers.add("call_val");
131
+ registers.add("call_ok");
132
+ const inputProductEdges = Array.isArray(kvmFunc.inputPattern?.[0]?.[1])
133
+ && (kvmFunc.inputPattern[0][0] === "open-product" || kvmFunc.inputPattern[0][0] === "closed-product")
134
+ ? kvmFunc.inputPattern[0][1]
135
+ : null;
136
+ const inputProductFields = inputProductEdges
137
+ ? inputProductEdges.map(([label], index) => ({ label, index, local: `tail_in_${index}` }))
138
+ : [];
139
+ const inputProductFieldByLabel = new Map(inputProductFields.map((field) => [field.label, field]));
140
+
141
+ const collectReg = (reg) => {
142
+ if (reg && reg.startsWith("%")) {
143
+ registers.add(cleanReg(reg));
144
+ }
145
+ };
146
+
147
+ function collectAllRegisters(insts) {
148
+ for (const inst of insts) {
149
+ collectReg(inst.dest);
150
+ collectReg(inst.src);
151
+ if (inst.branches) {
152
+ for (const branch of inst.branches) {
153
+ collectAllRegisters(branch.body);
154
+ }
155
+ }
156
+ }
157
+ }
158
+ collectAllRegisters(kvmFunc.body);
159
+ for (const field of inputProductFields) {
160
+ registers.add(field.local);
161
+ }
162
+ registers.add("tail_loop_mark");
163
+
164
+ let nextUnionId = 0;
165
+ const tailLoopLabel = "$tail_loop";
166
+
167
+ const mapsToReg = (reg, inputMap) => inputMap[cleanReg(reg)] || cleanReg(reg);
168
+
169
+ function tailValueAfter(insts, startIndex, valueReg, inputMap, returnTarget, tailReturnTargets) {
170
+ let current = valueReg;
171
+ for (let i = startIndex; i < insts.length; i++) {
172
+ const inst = insts[i];
173
+ if (inst.op === "guard_pattern" || inst.op === "guard_code" || inst.op === "id") {
174
+ if (mapsToReg(inst.src, inputMap) !== current) return false;
175
+ current = cleanReg(inst.dest);
176
+ continue;
177
+ }
178
+ if (inst.op === "return" && mapsToReg(inst.src, inputMap) === current) {
179
+ return returnTarget ? tailReturnTargets.has(returnTarget) : true;
180
+ }
181
+ return false;
182
+ }
183
+ return false;
184
+ }
185
+
186
+ function isSelfTailCall(insts, index, inputMap, returnTarget, tailReturnTargets) {
187
+ if (options.trace) return false;
188
+ const inst = insts[index];
189
+ if (inst.op !== "call" || inst.func !== name) return false;
190
+ const dest = cleanReg(inst.dest);
191
+ return tailValueAfter(insts, index + 1, dest, inputMap, returnTarget, tailReturnTargets);
192
+ }
193
+
194
+ function selfTailCallAfterProduct(insts, index, productDest, inputMap, returnTarget, tailReturnTargets) {
195
+ if (options.trace || inputProductFields.length === 0) return null;
196
+ const callInst = insts[index + 1];
197
+ if (!callInst || callInst.op !== "call" || callInst.func !== name) return null;
198
+ if (mapsToReg(callInst.src, inputMap) !== productDest) return null;
199
+ const callDest = cleanReg(callInst.dest);
200
+ if (!tailValueAfter(insts, index + 2, callDest, inputMap, returnTarget, tailReturnTargets)) return null;
201
+ return callInst;
202
+ }
203
+
204
+ function initInputProductLocals() {
205
+ const lines = [];
206
+ for (const field of inputProductFields) {
207
+ const fieldIndex = inputProductEdges.findIndex(([label]) => label === field.label);
208
+ lines.push(` ;; cache input field ${field.label} in $${field.local}`);
209
+ lines.push(` local.get $in`);
210
+ lines.push(` local.get $in`);
211
+ lines.push(` i32.const ${8 + 4 * fieldIndex}`);
212
+ lines.push(` i32.add`);
213
+ lines.push(` i32.load`);
214
+ lines.push(` i32.add`);
215
+ lines.push(` i32.load`);
216
+ lines.push(` local.set $${field.local}`);
217
+ }
218
+ return lines.join("\n");
219
+ }
220
+
221
+ function materializeInputProduct(lines, dest) {
222
+ const totalSize = 8 + 8 * inputProductFields.length;
223
+ lines.push(` ;; materialize cached input product into $${dest}`);
224
+ lines.push(` i32.const ${totalSize}`);
225
+ lines.push(` call $alloc`);
226
+ lines.push(` local.set $${dest}`);
227
+ lines.push(` local.get $${dest}`);
228
+ lines.push(` i32.const ${totalSize}`);
229
+ lines.push(` i32.store offset=0`);
230
+ lines.push(` local.get $${dest}`);
231
+ lines.push(` i32.const ${inputProductFields.length}`);
232
+ lines.push(` i32.store offset=4`);
233
+ for (const field of inputProductFields) {
234
+ const offsetVal = 8 + 4 * inputProductFields.length + 4 * field.index;
235
+ lines.push(` local.get $${dest}`);
236
+ lines.push(` i32.const ${offsetVal}`);
237
+ lines.push(` i32.store offset=${8 + 4 * field.index}`);
238
+ lines.push(` local.get $${dest}`);
239
+ lines.push(` local.get $${field.local}`);
240
+ lines.push(` i32.store offset=${offsetVal}`);
241
+ }
242
+ }
243
+
244
+ function compileInstructions(
245
+ insts,
246
+ inputMap = { "in": "in" },
247
+ returnTarget = null,
248
+ failTarget = null,
249
+ tailReturnTargets = new Set(),
250
+ inputAliases = new Set(["in"])
251
+ ) {
252
+ const lines = [];
253
+ for (let index = 0; index < insts.length; index++) {
254
+ const inst = insts[index];
255
+ switch (inst.op) {
256
+ case "guard_pattern":
257
+ case "guard_code":
258
+ case "id": {
259
+ const dest = cleanReg(inst.dest);
260
+ const rawSrc = cleanReg(inst.src);
261
+ const src = inputMap[rawSrc] || rawSrc;
262
+ lines.push(` local.get $${src}`);
263
+ lines.push(` local.set $${dest}`);
264
+ setPattern(dest, inst.pattern || getPattern(inst.src, inputMap));
265
+ if (inputAliases.has(src)) {
266
+ inputAliases.add(dest);
267
+ } else {
268
+ inputAliases.delete(dest);
269
+ }
270
+ break;
271
+ }
272
+ case "fail": {
273
+ if (failTarget) {
274
+ lines.push(` br ${failTarget}`);
275
+ } else {
276
+ lines.push(` i32.const 0`);
277
+ lines.push(` i32.const 0`);
278
+ lines.push(` return`);
279
+ }
280
+ break;
281
+ }
282
+ case "return": {
283
+ const rawSrc = cleanReg(inst.src);
284
+ const src = inputMap[rawSrc] || rawSrc;
285
+ const valueSrc = inputAliases.has(src) && inputProductFields.length > 0
286
+ ? `${src}_materialized`
287
+ : src;
288
+ if (valueSrc !== src) {
289
+ registers.add(valueSrc);
290
+ materializeInputProduct(lines, valueSrc);
291
+ }
292
+ if (returnTarget) {
293
+ lines.push(` local.get $${valueSrc}`);
294
+ lines.push(` local.set $${returnTarget}`);
295
+ setPattern(returnTarget, getPattern(inst.src, inputMap));
296
+ } else {
297
+ lines.push(` local.get $${valueSrc}`);
298
+ lines.push(` i32.const 1`);
299
+ lines.push(` return`);
300
+ }
301
+ break;
302
+ }
303
+ case "project_field": {
304
+ const dest = cleanReg(inst.dest);
305
+ const rawSrc = cleanReg(inst.src);
306
+ const src = inputMap[rawSrc] || rawSrc;
307
+ const inputField = inputAliases.has(src) ? inputProductFieldByLabel.get(inst.label) : null;
308
+
309
+ if (inputField) {
310
+ lines.push(` ;; project cached input field ${inst.label} from $${inputField.local} to $${dest}`);
311
+ lines.push(` local.get $${inputField.local}`);
312
+ lines.push(` local.set $${dest}`);
313
+ setPattern(dest, inst.pattern);
314
+ break;
315
+ }
316
+
317
+ const edges = getProductEdges(inst, inputMap);
318
+ const fieldIndex = edges.findIndex(([label]) => label === inst.label);
319
+ if (fieldIndex === -1) {
320
+ throw new Error(`Wasm compiler: field '${inst.label}' not found in input pattern`);
321
+ }
322
+
323
+ lines.push(` ;; project_field ${inst.label} (index ${fieldIndex}) from $${src} to $${dest}`);
324
+ lines.push(` local.get $${src}`);
325
+ lines.push(` i32.eqz`);
326
+ lines.push(` if`);
327
+ if (failTarget) {
328
+ lines.push(` br ${failTarget}`);
329
+ } else {
330
+ lines.push(` i32.const 0`);
331
+ lines.push(` i32.const 0`);
332
+ lines.push(` return`);
333
+ }
334
+ lines.push(` end`);
335
+ lines.push(` local.get $${src}`);
336
+ lines.push(` local.get $${src}`);
337
+ lines.push(` i32.const ${8 + 4 * fieldIndex}`);
338
+ lines.push(` i32.add`);
339
+ lines.push(` i32.load`);
340
+ lines.push(` i32.add`);
341
+ lines.push(` i32.load`);
342
+ lines.push(` local.set $${dest}`);
343
+ setPattern(dest, inst.pattern);
344
+ break;
345
+ }
346
+ case "product": {
347
+ const dest = cleanReg(inst.dest);
348
+ const rawSrc = cleanReg(inst.src);
349
+ const src = inputMap[rawSrc] || rawSrc;
350
+ const tailCall = selfTailCallAfterProduct(insts, index, dest, inputMap, returnTarget, tailReturnTargets);
351
+ const sortedBranches = [...inst.branches].sort((a, b) => {
352
+ if (a.label < b.label) return -1;
353
+ if (a.label > b.label) return 1;
354
+ return 0;
355
+ });
356
+ const N = sortedBranches.length;
357
+
358
+ lines.push(` ;; product creation for $${dest}`);
359
+
360
+ if (tailCall) {
361
+ if (N !== inputProductFields.length) {
362
+ throw new Error(`Wasm compiler: tail-call ${name} input product field count mismatch`);
363
+ }
364
+ for (let i = 0; i < N; i++) {
365
+ const branch = sortedBranches[i];
366
+ if (!inputProductFieldByLabel.has(branch.label)) {
367
+ throw new Error(`Wasm compiler: tail-call ${name} input product is missing field '${branch.label}'`);
368
+ }
369
+ const fieldTmp = `${dest}_f${i}`;
370
+ registers.add(fieldTmp);
371
+
372
+ lines.push(` ;; tail-call field ${branch.label}`);
373
+ const branchWat = compileInstructions(
374
+ branch.body,
375
+ { ...inputMap, "in": src },
376
+ fieldTmp,
377
+ failTarget,
378
+ tailReturnTargets,
379
+ new Set(inputAliases)
380
+ );
381
+ lines.push(branchWat);
382
+ }
383
+ lines.push(` ;; compact if arena growth exceeds 1MB`);
384
+ lines.push(` global.get $arena_free`);
385
+ lines.push(` local.get $tail_loop_mark`);
386
+ lines.push(` i32.sub`);
387
+ lines.push(` i32.const 1048576`);
388
+ lines.push(` i32.gt_u`);
389
+ lines.push(` if`);
390
+ lines.push(` call $compact_start`);
391
+ for (let i = 0; i < N; i++) {
392
+ const fieldTmp = `${dest}_f${i}`;
393
+ lines.push(` local.get $${fieldTmp}`);
394
+ lines.push(` local.get $tail_loop_mark`);
395
+ lines.push(` call $compact_obj`);
396
+ lines.push(` local.set $${fieldTmp}`);
397
+ }
398
+ lines.push(` local.get $tail_loop_mark`);
399
+ lines.push(` call $compact_finish`);
400
+ lines.push(` end`);
401
+ for (let i = 0; i < N; i++) {
402
+ const branch = sortedBranches[i];
403
+ const fieldTmp = `${dest}_f${i}`;
404
+ const inputField = inputProductFieldByLabel.get(branch.label);
405
+ lines.push(` local.get $${fieldTmp}`);
406
+ lines.push(` local.set $${inputField.local}`);
407
+ }
408
+ lines.push(` br ${tailLoopLabel}`);
409
+ index = insts.length;
410
+ break;
411
+ }
412
+
413
+ for (let i = 0; i < N; i++) {
414
+ const branch = sortedBranches[i];
415
+ const fieldTmp = `${dest}_f${i}`;
416
+ registers.add(fieldTmp);
417
+
418
+ lines.push(` ;; branch ${i} for field ${branch.label || i}`);
419
+ const branchWat = compileInstructions(
420
+ branch.body,
421
+ { ...inputMap, "in": src },
422
+ fieldTmp,
423
+ failTarget,
424
+ tailReturnTargets,
425
+ new Set(inputAliases)
426
+ );
427
+ lines.push(branchWat);
428
+ }
429
+
430
+ const totalSize = 8 + 8 * N;
431
+ lines.push(` i32.const ${totalSize}`);
432
+ lines.push(` call $alloc`);
433
+ lines.push(` local.set $${dest}`);
434
+
435
+ lines.push(` local.get $${dest}`);
436
+ lines.push(` i32.const ${totalSize}`);
437
+ lines.push(` i32.store offset=0`);
438
+
439
+ lines.push(` local.get $${dest}`);
440
+ lines.push(` i32.const ${N}`);
441
+ lines.push(` i32.store offset=4`);
442
+
443
+ for (let i = 0; i < N; i++) {
444
+ const offsetVal = 8 + 4 * N + 4 * i;
445
+ const fieldTmp = `${dest}_f${i}`;
446
+
447
+ lines.push(` local.get $${dest}`);
448
+ lines.push(` i32.const ${offsetVal}`);
449
+ lines.push(` i32.store offset=${8 + 4 * i}`);
450
+
451
+ lines.push(` local.get $${dest}`);
452
+ lines.push(` local.get $${fieldTmp}`);
453
+ lines.push(` i32.store offset=${offsetVal}`);
454
+ }
455
+ setPattern(dest, inst.pattern);
456
+ break;
457
+ }
458
+ case "make_variant": {
459
+ const dest = cleanReg(inst.dest);
460
+ const rawSrc = cleanReg(inst.src);
461
+ const src = inputMap[rawSrc] || rawSrc;
462
+ const tagId = getTagId(inst.tag);
463
+
464
+ lines.push(` ;; make_variant ${inst.tag} (tagId ${tagId})`);
465
+ lines.push(` i32.const 12`);
466
+ lines.push(` call $alloc`);
467
+ lines.push(` local.set $${dest}`);
468
+
469
+ lines.push(` local.get $${dest}`);
470
+ lines.push(` i32.const 12`);
471
+ lines.push(` i32.store offset=0`);
472
+
473
+ lines.push(` local.get $${dest}`);
474
+ lines.push(` i32.const ${tagId}`);
475
+ lines.push(` i32.store offset=4`);
476
+
477
+ lines.push(` local.get $${dest}`);
478
+ lines.push(` local.get $${src}`);
479
+ lines.push(` i32.store offset=8`);
480
+ setPattern(dest, inst.pattern);
481
+ break;
482
+ }
483
+ case "project_variant": {
484
+ const dest = cleanReg(inst.dest);
485
+ const rawSrc = cleanReg(inst.src);
486
+ const src = inputMap[rawSrc] || rawSrc;
487
+ const tagId = getTagId(inst.tag);
488
+
489
+ lines.push(` ;; project_variant ${inst.tag} (tagId ${tagId}) from $${src} to $${dest}`);
490
+ lines.push(` local.get $${src}`);
491
+ lines.push(` i32.eqz`);
492
+ lines.push(` if`);
493
+ if (failTarget) {
494
+ lines.push(` br ${failTarget}`);
495
+ } else {
496
+ lines.push(` i32.const 0`);
497
+ lines.push(` i32.const 0`);
498
+ lines.push(` return`);
499
+ }
500
+ lines.push(` end`);
501
+
502
+ lines.push(` local.get $${src}`);
503
+ lines.push(` i32.load offset=4`);
504
+ lines.push(` i32.const ${tagId}`);
505
+ lines.push(` i32.ne`);
506
+ lines.push(` if`);
507
+ if (failTarget) {
508
+ lines.push(` br ${failTarget}`);
509
+ } else {
510
+ lines.push(` i32.const 0`);
511
+ lines.push(` i32.const 0`);
512
+ lines.push(` return`);
513
+ }
514
+ lines.push(` end`);
515
+
516
+ lines.push(` local.get $${src}`);
517
+ lines.push(` i32.load offset=8`);
518
+ lines.push(` local.set $${dest}`);
519
+ setPattern(dest, inst.pattern);
520
+ break;
521
+ }
522
+ case "union": {
523
+ const dest = cleanReg(inst.dest);
524
+ const rawSrc = cleanReg(inst.src);
525
+ const src = inputMap[rawSrc] || rawSrc;
526
+ const N = inst.branches.length;
527
+ const branchTailReturnTargets = tailValueAfter(insts, index + 1, dest, inputMap, returnTarget, tailReturnTargets)
528
+ ? new Set([...tailReturnTargets, dest])
529
+ : tailReturnTargets;
530
+
531
+ const unionId = nextUnionId++;
532
+ const arenaMark = `union_mark_${unionId}`;
533
+ registers.add(arenaMark);
534
+
535
+ lines.push(` ;; union choice for $${dest}`);
536
+ lines.push(` (block $union_done_${unionId}`);
537
+
538
+ for (let i = N - 1; i >= 0; i--) {
539
+ lines.push(` (block $b${i}_try_${unionId}`);
540
+ }
541
+
542
+ for (let i = 0; i < N; i++) {
543
+ const branch = inst.branches[i];
544
+ lines.push(` global.get $arena_free`);
545
+ lines.push(` local.set $${arenaMark}`);
546
+ const branchWat = compileInstructions(
547
+ branch.body,
548
+ { ...inputMap, "in": src },
549
+ dest,
550
+ `$b${i}_try_${unionId}`,
551
+ branchTailReturnTargets,
552
+ new Set(inputAliases)
553
+ );
554
+ lines.push(branchWat);
555
+ lines.push(` br $union_done_${unionId}`);
556
+ lines.push(` ) ;; end $b${i}_try_${unionId}`);
557
+ lines.push(` local.get $${arenaMark}`);
558
+ lines.push(` global.set $arena_free`);
559
+ }
560
+
561
+ // If we reach here, all branches failed
562
+ if (failTarget) {
563
+ lines.push(` br ${failTarget}`);
564
+ } else {
565
+ lines.push(` i32.const 0`);
566
+ lines.push(` i32.const 0`);
567
+ lines.push(` return`);
568
+ }
569
+ lines.push(` ) ;; end $union_done_${unionId}`);
570
+ setPattern(dest, inst.pattern);
571
+ break;
572
+ }
573
+ case "call": {
574
+ const dest = cleanReg(inst.dest);
575
+ const rawSrc = cleanReg(inst.src);
576
+ const src = inputMap[rawSrc] || rawSrc;
577
+ const callSrc = inputAliases.has(src) && inputProductFields.length > 0
578
+ ? `${src}_materialized`
579
+ : src;
580
+
581
+ if (isSelfTailCall(insts, index, inputMap, returnTarget, tailReturnTargets)) {
582
+ lines.push(` ;; tail-call ${inst.func}`);
583
+ if (callSrc !== src) {
584
+ registers.add(callSrc);
585
+ materializeInputProduct(lines, callSrc);
586
+ }
587
+ lines.push(` ;; compact if arena growth exceeds 1MB`);
588
+ lines.push(` global.get $arena_free`);
589
+ lines.push(` local.get $tail_loop_mark`);
590
+ lines.push(` i32.sub`);
591
+ lines.push(` i32.const 1048576`);
592
+ lines.push(` i32.gt_u`);
593
+ lines.push(` if`);
594
+ lines.push(` call $compact_start`);
595
+ lines.push(` local.get $${callSrc}`);
596
+ lines.push(` local.get $tail_loop_mark`);
597
+ lines.push(` call $compact_obj`);
598
+ lines.push(` local.set $${callSrc}`);
599
+ lines.push(` local.get $tail_loop_mark`);
600
+ lines.push(` call $compact_finish`);
601
+ lines.push(` end`);
602
+ lines.push(` local.get $${callSrc}`);
603
+ lines.push(` local.set $in`);
604
+ lines.push(initInputProductLocals());
605
+ lines.push(` br ${tailLoopLabel}`);
606
+ setPattern("in", kvmFunc.inputPattern);
607
+ setPattern(dest, kvmFunc.outputPattern || inst.pattern);
608
+ break;
609
+ }
610
+
611
+ lines.push(` ;; call ${inst.func}`);
612
+ if (options.trace) {
613
+ const funcId = getFuncId(inst.func);
614
+ lines.push(` i32.const ${funcId}`);
615
+ if (callSrc !== src) {
616
+ registers.add(callSrc);
617
+ materializeInputProduct(lines, callSrc);
618
+ }
619
+ lines.push(` local.get $${callSrc}`);
620
+ lines.push(` call $log_call`);
621
+ }
622
+ if (callSrc !== src && !options.trace) {
623
+ registers.add(callSrc);
624
+ materializeInputProduct(lines, callSrc);
625
+ }
626
+ lines.push(` local.get $${callSrc}`);
627
+ lines.push(` call $${inst.func}`);
628
+ lines.push(` local.set $call_ok`);
629
+ lines.push(` local.set $call_val`);
630
+ if (options.trace) {
631
+ const funcId = getFuncId(inst.func);
632
+ lines.push(` i32.const ${funcId}`);
633
+ lines.push(` local.get $call_val`);
634
+ lines.push(` local.get $call_ok`);
635
+ lines.push(` call $log_ret`);
636
+ }
637
+ lines.push(` local.get $call_ok`);
638
+ lines.push(` i32.eqz`);
639
+ lines.push(` if`);
640
+ if (failTarget) {
641
+ lines.push(` br ${failTarget}`);
642
+ } else {
643
+ lines.push(` i32.const 0`);
644
+ lines.push(` i32.const 0`);
645
+ lines.push(` return`);
646
+ }
647
+ lines.push(` end`);
648
+ lines.push(` local.get $call_val`);
649
+ lines.push(` local.set $${dest}`);
650
+ setPattern(dest, kvmProgram[inst.func]?.outputPattern || inst.pattern);
651
+ break;
652
+ }
653
+ case "call_intrinsic": {
654
+ throw new Error(`Wasm compiler: unsupported intrinsic '${inst.symbol}'`);
655
+ }
656
+ default:
657
+ throw new Error(`Wasm compiler: unsupported instruction op '${inst.op}'`);
658
+ }
659
+ }
660
+ return lines.join("\n");
661
+ }
662
+
663
+ const bodyWat = compileInstructions(kvmFunc.body);
664
+ const inputProductLocalInit = initInputProductLocals();
665
+
666
+ const localDecls = Array.from(registers)
667
+ .filter(r => r !== "in")
668
+ .map(r => ` (local $${r} i32)`)
669
+ .join("\n");
670
+
671
+ const profWat = options.profile
672
+ ? ` global.get $prof_${name}\n i64.const 1\n i64.add\n global.set $prof_${name}\n`
673
+ : "";
674
+
675
+ const wat = `(func $${name} (export "${name}") (param $in i32) (result i32 i32)
676
+ ${localDecls}
677
+ ${inputProductLocalInit}
678
+ global.get $arena_free
679
+ local.set $tail_loop_mark
680
+ (loop ${tailLoopLabel}
681
+ ${profWat}${bodyWat}
682
+ )
683
+ unreachable
684
+ )`;
685
+
686
+ return wat;
687
+ }
688
+
689
+ export { getTagId, getTagFromId, getTagEntries, resetTagIds, getFuncNameFromId };
690
+ export default {
691
+ lowerToWasm,
692
+ getTagId,
693
+ getTagFromId,
694
+ getTagEntries,
695
+ resetTagIds,
696
+ getFuncNameFromId
697
+ };