@fraczak/k 6.3.2 → 6.4.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 (156) hide show
  1. package/DOCS/CODECS.md +258 -0
  2. package/DOCS/DICTIONARY.md +1172 -0
  3. package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
  4. package/DOCS/KIR_V1.md +221 -0
  5. package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
  6. package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
  7. package/DOCS/PROFILING_AND_TRACING.md +196 -0
  8. package/DOCS/REPL.md +76 -14
  9. package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
  10. package/DOCS/dependency-graph.md +144 -0
  11. package/Examples/arithmetics.k +51 -48
  12. package/Examples/bnat.k +1 -1
  13. package/Examples/buda.k +44 -0
  14. package/Examples/ieee.k +300 -8671
  15. package/Examples/poly.k +67 -0
  16. package/README.md +68 -19
  17. package/augmentation.mjs +9 -7
  18. package/backend-api.mjs +11 -2
  19. package/backends/arm64/README.md +104 -0
  20. package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
  21. package/backends/arm64/bin/k-arm64-run.mjs +58 -0
  22. package/backends/arm64/bin/k-arm64.mjs +88 -0
  23. package/backends/arm64/scripts/run-tests.mjs +41 -0
  24. package/backends/arm64/src/arm64.mjs +229 -0
  25. package/backends/arm64/src/cli.mjs +155 -0
  26. package/backends/arm64/src/kvm2arm64.mjs +906 -0
  27. package/backends/arm64/src/runtime.c +728 -0
  28. package/backends/arm64/tests/integration.sh +112 -0
  29. package/backends/arm64/tests/test-arm64.mjs +240 -0
  30. package/backends/llvm/README.md +135 -0
  31. package/backends/llvm/bin/k-llvm-build.mjs +71 -0
  32. package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
  33. package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
  34. package/backends/llvm/bin/k-llvm-run.mjs +90 -0
  35. package/backends/llvm/runtime/krt.c +1508 -0
  36. package/backends/llvm/runtime/krt.h +109 -0
  37. package/backends/llvm/scripts/conformance.mjs +76 -0
  38. package/backends/llvm/scripts/run-tests.mjs +28 -0
  39. package/backends/llvm/src/executable.mjs +562 -0
  40. package/backends/llvm/src/kvm2llvm.mjs +998 -0
  41. package/backends/llvm/src/llvm.mjs +1092 -0
  42. package/backends/llvm/tests/composition-driver.c +22 -0
  43. package/backends/llvm/tests/identity-driver.c +27 -0
  44. package/backends/llvm/tests/integration.sh +153 -0
  45. package/backends/llvm/tests/perf-support.mjs +735 -0
  46. package/backends/llvm/tests/product-driver.c +23 -0
  47. package/backends/llvm/tests/projection-driver.c +19 -0
  48. package/backends/llvm/tests/relation-driver.c +29 -0
  49. package/backends/llvm/tests/test-llvm.mjs +240 -0
  50. package/backends/llvm/tests/union-driver.c +31 -0
  51. package/backends/llvm/tests/variant-driver.c +19 -0
  52. package/backends/llvm/tests/variant-projection-driver.c +21 -0
  53. package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
  54. package/backends/wasm/README.md +97 -0
  55. package/backends/wasm/TECHNICAL-DEBT.md +60 -0
  56. package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
  57. package/backends/wasm/bin/k-wasm-run.mjs +140 -0
  58. package/backends/wasm/bin/k-wasm.mjs +49 -0
  59. package/backends/wasm/demo_ieee.mjs +319 -0
  60. package/backends/wasm/runtime.wat +102 -0
  61. package/backends/wasm/scripts/run-tests.mjs +41 -0
  62. package/backends/wasm/src/cli.mjs +133 -0
  63. package/backends/wasm/src/kvm2wasm.mjs +661 -0
  64. package/backends/wasm/src/wasm.mjs +1010 -0
  65. package/backends/wasm/tests/integration.sh +182 -0
  66. package/backends/wasm/tests/perf-support.mjs +336 -0
  67. package/backends/wasm/tests/test-wasm.mjs +617 -0
  68. package/browser/repl-browser.mjs +1584 -0
  69. package/browser/shims/assert.mjs +19 -0
  70. package/browser/shims/buffer.mjs +224 -0
  71. package/browser/shims/crypto.mjs +103 -0
  72. package/browser/shims/fs.mjs +159 -0
  73. package/browser/shims/os.mjs +17 -0
  74. package/browser/shims/path.mjs +54 -0
  75. package/browser/shims/process.mjs +44 -0
  76. package/browser/shims/readline.mjs +11 -0
  77. package/browser/shims/url.mjs +12 -0
  78. package/browser/vfs-data.mjs +19 -0
  79. package/codecs/README.md +3 -0
  80. package/codecs/json.mjs +16 -2
  81. package/codecs/runtime/show-value.mjs +2 -2
  82. package/conformance/helper-reuse/case.json +8 -0
  83. package/conformance/helper-reuse/expected.kv +1 -0
  84. package/conformance/helper-reuse/input.kv +1 -0
  85. package/conformance/helper-reuse/program.k +2 -0
  86. package/conformance/identity/case.json +8 -0
  87. package/conformance/identity/expected.kv +1 -0
  88. package/conformance/identity/input.kv +1 -0
  89. package/conformance/identity/program.k +1 -0
  90. package/conformance/product-projection/case.json +8 -0
  91. package/conformance/product-projection/expected.kv +1 -0
  92. package/conformance/product-projection/input.kv +1 -0
  93. package/conformance/product-projection/program.k +1 -0
  94. package/conformance/run.mjs +149 -0
  95. package/conformance/variant-construction/case.json +8 -0
  96. package/conformance/variant-construction/expected.kv +1 -0
  97. package/conformance/variant-construction/input.kv +1 -0
  98. package/conformance/variant-construction/program.k +1 -0
  99. package/harness/adapters/arm64.mjs +105 -0
  100. package/harness/adapters/kvm.mjs +144 -0
  101. package/harness/adapters/llvm.mjs +118 -0
  102. package/harness/adapters/native.mjs +77 -0
  103. package/harness/adapters/wasm.mjs +169 -0
  104. package/harness/backend.mjs +95 -0
  105. package/harness/cli.mjs +130 -0
  106. package/harness/format.mjs +95 -0
  107. package/harness/index.mjs +25 -0
  108. package/harness/persistent.mjs +121 -0
  109. package/harness/registry.mjs +44 -0
  110. package/harness/suite.mjs +257 -0
  111. package/harness/suites/poly.mjs +156 -0
  112. package/harness/trace.mjs +50 -0
  113. package/harness/util.mjs +92 -0
  114. package/intrinsics.mjs +41 -0
  115. package/kir.mjs +399 -0
  116. package/kvm.mjs +585 -81
  117. package/objects/README.md +37 -3
  118. package/objects/compile.mjs +15 -22
  119. package/objects/inspect.mjs +96 -0
  120. package/objects/validate.mjs +415 -0
  121. package/package.json +51 -19
  122. package/repl-codecs.mjs +133 -20
  123. package/repl.mjs +408 -59
  124. package/run.mjs +2 -7
  125. package/scripts/benchmark.mjs +406 -22
  126. package/scripts/build-repl-html.mjs +1784 -0
  127. package/scripts/perf-ieee.mjs +586 -0
  128. package/scripts/perf-int.mjs +531 -0
  129. package/scripts/perf-poly.mjs +945 -0
  130. package/scripts/render-benchmark.mjs +12 -8
  131. package/scripts/run-tests.mjs +6 -0
  132. package/tests/integration.sh +19 -2
  133. package/tests/test-arithmetics.mjs +54 -0
  134. package/tests/test-ieee-arithmetic.mjs +6 -2
  135. package/tests/test-kir.mjs +85 -0
  136. package/tests/test-kvm-polymorphic.mjs +278 -0
  137. package/tests/test-kvm.mjs +25 -6
  138. package/tests/test-poly.mjs +129 -0
  139. package/tests/test-repl-html.mjs +303 -0
  140. package/tests/test-repl.mjs +175 -1
  141. package/tests/test-validate-object.mjs +40 -0
  142. package/a.klib +0 -1
  143. package/a.kvm +0 -142676
  144. package/a.wasm +0 -0
  145. package/benchmark-results.html +0 -532
  146. package/benchmark-results.json +0 -480
  147. package/k-vscode/.vscode/launch.json +0 -14
  148. package/k-vscode/README.md +0 -75
  149. package/k-vscode/extension.js +0 -273
  150. package/k-vscode/language-configuration.json +0 -42
  151. package/k-vscode/package.json +0 -79
  152. package/k-vscode/snippets/k.json +0 -92
  153. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  154. package/k-vscode/test/background-ranges.test.js +0 -50
  155. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  156. package/scripts/git-hooks/pre-commit +0 -10
@@ -0,0 +1,617 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import wabtFactory from "wabt";
4
+
5
+ console.log("==> Initializing WebAssembly TDD Harness");
6
+
7
+ let wabtInstance;
8
+ try {
9
+ wabtInstance = await wabtFactory();
10
+ } catch (error) {
11
+ console.error("Failed to load WABT:", error);
12
+ process.exit(1);
13
+ }
14
+
15
+ // Helper to compile WAT string to a binary Uint8Array
16
+ export function compileWat(filename, watText) {
17
+ const watModule = wabtInstance.parseWat(filename, watText, {
18
+ mutable_globals: true,
19
+ sat_float_to_int: true,
20
+ sign_extension: true,
21
+ multi_value: true,
22
+ bulk_memory: true,
23
+ reference_types: true
24
+ });
25
+ watModule.resolveNames();
26
+ watModule.validate();
27
+ const binary = watModule.toBinary({
28
+ log: false,
29
+ canonicalize_lebs: true,
30
+ relocatable: false,
31
+ write_debug_names: true
32
+ });
33
+ return binary.buffer;
34
+ }
35
+
36
+ // Helper to instantiate compiled WASM buffer with standard imports
37
+ export async function instantiateWasm(binaryBuffer, imports = {}) {
38
+ const module = await WebAssembly.compile(binaryBuffer);
39
+ const instance = await WebAssembly.instantiate(module, imports);
40
+ return instance.exports;
41
+ }
42
+
43
+ console.log("==> Running smoke test: Basic WAT Compilation & Execution");
44
+ {
45
+ const smokeWat = `
46
+ (module
47
+ (func (export "add") (param $x i32) (param $y i32) (result i32)
48
+ local.get $x
49
+ local.get $y
50
+ i32.add
51
+ )
52
+ )
53
+ `;
54
+
55
+ const binary = compileWat("smoke.wat", smokeWat);
56
+ const exports = await instantiateWasm(binary);
57
+
58
+ const res = exports.add(40, 2);
59
+ console.log(`Add output: 40 + 2 = ${res}`);
60
+ assert.equal(res, 42, "Smoke test: 40 + 2 must equal 42");
61
+ console.log("Smoke test passed!");
62
+ }
63
+
64
+ console.log("==> Running runtime.wat Core Allocator Tests");
65
+ {
66
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
67
+ const binary = compileWat("runtime.wat", runtimeWat);
68
+ const runtimeExports = await instantiateWasm(binary);
69
+
70
+ // Test 1: First allocation starts at 1024 (aligned)
71
+ const ptr1 = runtimeExports.alloc(10);
72
+ assert.equal(ptr1, 1024, "Allocation 1 must start at 1024");
73
+ assert.equal(ptr1 % 8, 0, "Allocation 1 must be 8-byte aligned");
74
+
75
+ // Test 2: Second allocation must account for 10 bytes aligned to 16 bytes
76
+ const ptr2 = runtimeExports.alloc(12);
77
+ assert.equal(ptr2, 1040, "Allocation 2 must start at 1040");
78
+ assert.equal(ptr2 % 8, 0, "Allocation 2 must be 8-byte aligned");
79
+
80
+ // Test 3: Resetting to a mark must make temporary arena space reusable
81
+ const mark = runtimeExports.arena_mark();
82
+ assert.equal(mark, 1056, "Arena mark must point to the next free byte");
83
+ assert.equal(runtimeExports.alloc(32), mark, "Temporary allocation must start at the mark");
84
+ runtimeExports.arena_reset(mark);
85
+ assert.equal(runtimeExports.alloc(32), mark, "Reset arena space must be reused");
86
+
87
+ // Test 4: Large allocation causing memory growth beyond 64KB (page 1 limit)
88
+ const ptr3 = runtimeExports.alloc(70000);
89
+ assert.ok(ptr3 > 1040, "Large allocation must return a valid pointer");
90
+ assert.equal(ptr3 % 8, 0, "Large allocation must be 8-byte aligned");
91
+ console.log("Core Allocator tests passed successfully!");
92
+ }
93
+
94
+ import { lowerToWasm } from "../src/kvm2wasm.mjs";
95
+
96
+ console.log("==> Running Compiler Scaffolding Tests (id, fail, return)");
97
+ {
98
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
99
+
100
+ // Compile Identity function: %v1 = %in; return %v1;
101
+ const kvmIdentity = {
102
+ body: [
103
+ { op: "id", dest: "%v1", src: "%in" },
104
+ { op: "return", src: "%v1" }
105
+ ]
106
+ };
107
+ const identityWat = lowerToWasm(kvmIdentity, "test_identity");
108
+ const fullIdentityWat = runtimeWat.trim().slice(0, -1) + "\n" + identityWat + "\n)";
109
+ const binaryIdentity = compileWat("identity.wat", fullIdentityWat);
110
+ const exportsIdentity = await instantiateWasm(binaryIdentity);
111
+
112
+ // Invoke identity: input value 1234 should return (1234, 1) -> success
113
+ const resIdentity = exportsIdentity.test_identity(1234);
114
+ // WebAssembly multi-value returns are returned as array in some JS environments, or directly if supported.
115
+ // Note: Node 16+ supports multi-value return as array.
116
+ assert.deepEqual(resIdentity, [1234, 1], "Identity test: must return [1234, 1]");
117
+
118
+ // Compile Failure function: fail;
119
+ const kvmFailure = {
120
+ body: [
121
+ { op: "fail" }
122
+ ]
123
+ };
124
+ const failureWat = lowerToWasm(kvmFailure, "test_failure");
125
+ const fullFailureWat = runtimeWat.trim().slice(0, -1) + "\n" + failureWat + "\n)";
126
+ const binaryFailure = compileWat("failure.wat", fullFailureWat);
127
+ const exportsFailure = await instantiateWasm(binaryFailure);
128
+
129
+ // Invoke failure: input value 1234 should return (0, 0) -> failure
130
+ const resFailure = exportsFailure.test_failure(1234);
131
+ assert.deepEqual(resFailure, [0, 0], "Failure test: must return [0, 0]");
132
+
133
+ console.log("Compiler Scaffolding tests passed successfully!");
134
+ }
135
+
136
+ import { annotate, objectToKVMArtifact } from "@fraczak/k/backend-api.mjs";
137
+ import { compileObjectBuffer, decodeObject } from "@fraczak/k/object.mjs";
138
+ import { compileWasmArtifactFromKVM, metadataFromModule } from "../src/wasm.mjs";
139
+ import { getTagId } from "../src/kvm2wasm.mjs";
140
+
141
+ console.log("==> Running Wrapped kVM Artifact Test");
142
+ {
143
+ const object = decodeObject(compileObjectBuffer("()", { source: "wasm-kvm-artifact.k" }));
144
+ const artifact = objectToKVMArtifact(object, "__main__", [["closed-product", []]]);
145
+ assert.equal(artifact.layer, "KVM");
146
+ assert.equal(artifact.kir.layer, "KIR-P");
147
+ assert(!("instanceKey" in artifact));
148
+ const binary = await compileWasmArtifactFromKVM(artifact);
149
+ const metadata = metadataFromModule(await WebAssembly.compile(binary));
150
+ assert.equal(metadata.entry, "rel___main__");
151
+ assert.equal(metadata.relation, "__main__");
152
+ assert(!("instanceKey" in metadata));
153
+ assert.deepEqual(metadata.inputPattern, artifact.inputPattern);
154
+ assert.deepEqual(metadata.outputPattern, artifact.outputPattern);
155
+ }
156
+
157
+ console.log("==> Running Product Operations Integration Tests");
158
+ {
159
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
160
+
161
+ // Compile a relation that creates a product { .x x, .y y } and another that projects .y
162
+ const productDefs = annotate(`
163
+ make_prod = { .x x, .y y };
164
+ proj_y = make_prod .y;
165
+ ()
166
+ `, { convergence: { strategy: "auto" } });
167
+
168
+ const makeProdWat = lowerToWasm(productDefs.rels.make_prod, "make_prod");
169
+ const projYWat = lowerToWasm(productDefs.rels.proj_y, "proj_y");
170
+
171
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" + makeProdWat + "\n" + projYWat + "\n)";
172
+ const binary = compileWat("product_tests.wat", fullWat);
173
+ const exports = await instantiateWasm(binary);
174
+
175
+ // Helper to write a product to the arena
176
+ function writeProductToArena(fieldsMap) {
177
+ const keys = Object.keys(fieldsMap).sort();
178
+ const N = keys.length;
179
+ const totalSize = 8 + 8 * N;
180
+ const ptr = exports.alloc(totalSize);
181
+ const view = new DataView(exports.memory.buffer);
182
+
183
+ view.setUint32(ptr, totalSize, true);
184
+ view.setUint32(ptr + 4, N, true);
185
+
186
+ for (let i = 0; i < N; i++) {
187
+ const offsetVal = 8 + 4 * N + 4 * i;
188
+ const val = fieldsMap[keys[i]];
189
+ view.setUint32(ptr + 8 + 4 * i, offsetVal, true);
190
+ view.setUint32(ptr + offsetVal, val, true);
191
+ }
192
+ return ptr;
193
+ }
194
+
195
+ // 1. Test manual product creation & compiled projection
196
+ const ptrIn = writeProductToArena({ x: 101, y: 202 });
197
+ const resProj = exports.proj_y(ptrIn);
198
+ assert.deepEqual(resProj, [202, 1], "Projection of y should return [202, 1]");
199
+
200
+ // 2. Test compiled product creation & compiled projection
201
+ // Passing ptrIn to make_prod should construct a new product using make_prod's logic
202
+ const resMake = exports.make_prod(ptrIn);
203
+ assert.equal(resMake[1], 1, "make_prod should succeed");
204
+ const ptrOut = resMake[0];
205
+
206
+ // Inspect memory of ptrOut
207
+ const view = new DataView(exports.memory.buffer);
208
+ assert.equal(view.getUint32(ptrOut, true), 24, "Product total size should be 24");
209
+ assert.equal(view.getUint32(ptrOut + 4, true), 2, "Product field count should be 2");
210
+ assert.equal(view.getUint32(ptrOut + 8, true), 16, "Offset of first field (x) should be 16");
211
+ assert.equal(view.getUint32(ptrOut + 12, true), 20, "Offset of second field (y) should be 20");
212
+
213
+ // The first field value should be the projection of x from ptrIn (101)
214
+ const valXPtr = view.getUint32(ptrOut + 16, true);
215
+ assert.equal(valXPtr, 101, "First field value should be 101");
216
+ // The second field value should be the projection of y from ptrIn (202)
217
+ const valYPtr = view.getUint32(ptrOut + 20, true);
218
+ assert.equal(valYPtr, 202, "Second field value should be 202");
219
+
220
+ console.log("Product Operations integration tests passed successfully!");
221
+ }
222
+
223
+ console.log("==> Running Variant Operations Integration Tests");
224
+ {
225
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
226
+
227
+ const variantDefs = annotate(`
228
+ make_foo = |foo;
229
+ proj_foo = /foo;
230
+ proj_bar = /bar;
231
+ ()
232
+ `, { convergence: { strategy: "auto" } });
233
+
234
+ const makeFooWat = lowerToWasm(variantDefs.rels.make_foo, "make_foo");
235
+ const projFooWat = lowerToWasm(variantDefs.rels.proj_foo, "proj_foo");
236
+ const projBarWat = lowerToWasm(variantDefs.rels.proj_bar, "proj_bar");
237
+
238
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" +
239
+ makeFooWat + "\n" +
240
+ projFooWat + "\n" +
241
+ projBarWat + "\n)";
242
+ const binary = compileWat("variant_tests.wat", fullWat);
243
+ const exports = await instantiateWasm(binary);
244
+
245
+ // 1. Test compilation of variant tag creation
246
+ const resMake = exports.make_foo(999);
247
+ assert.equal(resMake[1], 1, "make_foo should succeed");
248
+ const ptrVar = resMake[0];
249
+
250
+ const view = new DataView(exports.memory.buffer);
251
+ assert.equal(view.getUint32(ptrVar, true), 12, "Variant total size should be 12");
252
+ assert.equal(view.getUint32(ptrVar + 4, true), getTagId("foo"), "Variant tagId should match 'foo'");
253
+ assert.equal(view.getUint32(ptrVar + 8, true), 999, "Variant payload should be 999");
254
+
255
+ // 2. Test variant projection success
256
+ const resProjSuccess = exports.proj_foo(ptrVar);
257
+ assert.deepEqual(resProjSuccess, [999, 1], "Projecting 'foo' from variant 'foo' should return [999, 1]");
258
+
259
+ // 3. Test variant projection failure
260
+ const resProjFail = exports.proj_bar(ptrVar);
261
+ assert.deepEqual(resProjFail, [0, 0], "Projecting 'bar' from variant 'foo' should fail and return [0, 0]");
262
+
263
+ console.log("Variant Operations integration tests passed successfully!");
264
+ }
265
+
266
+ console.log("==> Running Union & Choice Integration Tests");
267
+ {
268
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
269
+
270
+ const unionDefs = annotate(`
271
+ choose = < /x, /y >;
272
+ ()
273
+ `, { convergence: { strategy: "auto" } });
274
+
275
+ const chooseWat = lowerToWasm(unionDefs.rels.choose, "choose");
276
+ const chooseWithRollbackWat = lowerToWasm({
277
+ body: [
278
+ {
279
+ op: "union",
280
+ dest: "%v0",
281
+ src: "%in",
282
+ branches: [
283
+ {
284
+ body: [
285
+ { op: "make_variant", dest: "%v1", tag: "temporary", src: "%in" },
286
+ { op: "fail" }
287
+ ]
288
+ },
289
+ {
290
+ body: [
291
+ { op: "id", dest: "%v2", src: "%in" },
292
+ { op: "return", src: "%v2" }
293
+ ]
294
+ }
295
+ ]
296
+ },
297
+ { op: "return", src: "%v0" }
298
+ ]
299
+ }, "choose_with_rollback");
300
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" + chooseWat + "\n" + chooseWithRollbackWat + "\n)";
301
+ const binary = compileWat("union_tests.wat", fullWat);
302
+ const exports = await instantiateWasm(binary);
303
+
304
+ // Helper to write a variant to the arena
305
+ function writeVariantToArena(tag, payloadVal) {
306
+ const tagId = getTagId(tag);
307
+ const ptr = exports.alloc(12);
308
+ const view = new DataView(exports.memory.buffer);
309
+ view.setUint32(ptr, 12, true);
310
+ view.setUint32(ptr + 4, tagId, true);
311
+ view.setUint32(ptr + 8, payloadVal, true);
312
+ return ptr;
313
+ }
314
+
315
+ // 1. Pass variant with tag 'x': should match branch 1 and succeed
316
+ const ptrX = writeVariantToArena("x", 777);
317
+ const resX = exports.choose(ptrX);
318
+ assert.deepEqual(resX, [777, 1], "choose with tag 'x' should return [777, 1]");
319
+
320
+ // 2. Pass variant with tag 'y': should fail branch 1, match branch 2, and succeed
321
+ const ptrY = writeVariantToArena("y", 888);
322
+ const resY = exports.choose(ptrY);
323
+ assert.deepEqual(resY, [888, 1], "choose with tag 'y' should return [888, 1]");
324
+
325
+ // 3. Pass variant with tag 'z': should fail both branches and return failure
326
+ const ptrZ = writeVariantToArena("z", 999);
327
+ const resZ = exports.choose(ptrZ);
328
+ assert.deepEqual(resZ, [0, 0], "choose with tag 'z' should return [0, 0]");
329
+
330
+ // 4. Failed branches must release scratch allocations before trying the next choice
331
+ const mark = exports.arena_mark();
332
+ assert.deepEqual(exports.choose_with_rollback(999), [999, 1], "Fallback branch should return the input");
333
+ assert.equal(exports.arena_mark(), mark, "Failed union branch allocations must be reclaimed");
334
+
335
+ console.log("Union & Choice integration tests passed successfully!");
336
+ }
337
+
338
+ import {
339
+ exportPatternGraph,
340
+ fromObject,
341
+ isProduct,
342
+ isVariant,
343
+ NODE_KIND,
344
+ patternToPropertyList,
345
+ propertyListToPattern,
346
+ Value
347
+ } from "@fraczak/k/backend-api.mjs";
348
+ import { getTagFromId } from "../src/kvm2wasm.mjs";
349
+ import { readArenaValue, wasmPtr, writeValueToArena } from "../src/wasm.mjs";
350
+
351
+ console.log("==> Running Stack-Safe Arena Serialization Tests");
352
+ {
353
+ assert.equal(wasmPtr(-2061758672), 2233208624, "Exported Wasm i32 pointers must be treated as unsigned offsets");
354
+
355
+ const depth = 12000;
356
+ const memory = { buffer: new ArrayBuffer(1024 + 8 + 16 * (depth + 2)) };
357
+ const view = new DataView(memory.buffer);
358
+ const propertyList = [
359
+ ["closed-union", [["+", 1]]],
360
+ ["closed-union", [["_", 2], ["1", 1]]],
361
+ ["closed-product", []]
362
+ ];
363
+ const pattern = {
364
+ nodes: [
365
+ { kind: NODE_KIND.CLOSED_UNION, edges: [{ label: "+", target: 1 }] },
366
+ { kind: NODE_KIND.CLOSED_UNION, edges: [{ label: "_", target: 2 }, { label: "1", target: 1 }] },
367
+ { kind: NODE_KIND.CLOSED_PRODUCT, edges: [] }
368
+ ]
369
+ };
370
+ const tagNames = new Map([[1, "+"], [2, "_"], [3, "1"]]);
371
+ const tagIds = new Map([["+", 1], ["_", 2], ["1", 3]]);
372
+ const tags = {
373
+ getId: (tag) => tagIds.get(tag) ?? null,
374
+ getTag: (id) => tagNames.get(id) ?? null
375
+ };
376
+ let free = 1024;
377
+
378
+ function writeVariant(tagId, payloadPtr) {
379
+ const ptr = free;
380
+ free += 12;
381
+ view.setUint32(ptr, 12, true);
382
+ view.setUint32(ptr + 4, tagId, true);
383
+ view.setUint32(ptr + 8, payloadPtr, true);
384
+ return ptr;
385
+ }
386
+
387
+ const unitPtr = free;
388
+ free += 8;
389
+ view.setUint32(unitPtr, 8, true);
390
+ view.setUint32(unitPtr + 4, 0, true);
391
+
392
+ let ptr = writeVariant(2, unitPtr);
393
+ for (let i = 0; i < depth; i++) {
394
+ ptr = writeVariant(3, ptr);
395
+ }
396
+ ptr = writeVariant(1, ptr);
397
+
398
+ function assertDeepValue(output) {
399
+ assert.equal(output.tag, "+");
400
+ let bits = output.value;
401
+ for (let i = 0; i < depth; i++) {
402
+ assert.equal(bits.tag, "1");
403
+ bits = bits.value;
404
+ }
405
+ assert.equal(bits.tag, "_");
406
+ assert.deepEqual(bits.value.product, {});
407
+ }
408
+
409
+ assertDeepValue(readArenaValue({ memory }, ptr, pattern, 0, propertyList, new Map(), tags));
410
+
411
+ let input = Value.variant("_", Value.product({}));
412
+ for (let i = 0; i < depth; i++) {
413
+ input = Value.variant("1", input);
414
+ }
415
+ input = Value.variant("+", input);
416
+
417
+ free = 1024;
418
+ const exports = {
419
+ memory,
420
+ alloc(size) {
421
+ const ptr = free;
422
+ free += (size + 7) & -8;
423
+ return ptr;
424
+ }
425
+ };
426
+ const arenaValues = new Map();
427
+ ptr = writeValueToArena(exports, input, pattern, 0, arenaValues, tags);
428
+ const output = readArenaValue(exports, ptr, pattern, 0, propertyList, arenaValues, tags);
429
+ assertDeepValue(output);
430
+ console.log("Stack-safe arena serialization tests passed successfully!");
431
+ }
432
+
433
+ console.log("==> Running End-to-End Peano Addition & Serialization Tests");
434
+ {
435
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
436
+
437
+ const script = `
438
+ $ nat = < {} 0, nat +1 >;
439
+ 0 = {} | 0 $ nat;
440
+ inc = | +1 $ nat;
441
+ dec = $ nat / +1;
442
+ add = $ { nat x, nat y } <
443
+ { . x dec x, . y inc y } add,
444
+ . y
445
+ >;
446
+ ()
447
+ `;
448
+
449
+ const defs = annotate(script, { convergence: { strategy: "auto" } });
450
+
451
+ // Compile relations to WAT
452
+ const incWat = lowerToWasm(defs.rels.inc, "inc");
453
+ const decWat = lowerToWasm(defs.rels.dec, "dec");
454
+ const addWat = lowerToWasm(defs.rels.add, "add");
455
+ assert.match(addWat, /\bbr \$tail_loop\b/, "Recursive add should be lowered as a tail jump");
456
+ assert.doesNotMatch(addWat, /\bcall \$add\b/, "Recursive add should not emit a Wasm self-call");
457
+
458
+ // Combine and compile
459
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" +
460
+ incWat + "\n" +
461
+ decWat + "\n" +
462
+ addWat + "\n)";
463
+ const binary = compileWat("add_tests.wat", fullWat);
464
+ const exports = await instantiateWasm(binary);
465
+
466
+ // JS reader function for Wasm Execution Arena
467
+ function readArenaValue(ptr, pattern, patternNodeId) {
468
+ const patternNode = pattern.nodes[patternNodeId];
469
+ const view = new DataView(exports.memory.buffer);
470
+
471
+ if (patternNode.kind === 1 || patternNode.kind === 3) { // OPEN_PRODUCT or CLOSED_PRODUCT
472
+ const size = view.getUint32(ptr, true);
473
+ const N = view.getUint32(ptr + 4, true);
474
+ const productObj = {};
475
+
476
+ for (let i = 0; i < N; i++) {
477
+ const edge = patternNode.edges[i];
478
+ const offsetVal = view.getUint32(ptr + 8 + 4 * i, true);
479
+ const childPtr = view.getUint32(ptr + offsetVal, true);
480
+ productObj[edge.label] = readArenaValue(childPtr, pattern, edge.target);
481
+ }
482
+ return Value.product(productObj, pattern);
483
+ } else if (patternNode.kind === 2 || patternNode.kind === 4) { // OPEN_UNION or CLOSED_UNION
484
+ const size = view.getUint32(ptr, true);
485
+ const tagId = view.getUint32(ptr + 4, true);
486
+ const payloadPtr = view.getUint32(ptr + 8, true);
487
+
488
+ const tag = getTagFromId(tagId);
489
+ const edge = patternNode.edges.find(e => e.label === tag);
490
+ if (!edge) {
491
+ throw new Error(`Variant tag '${tag}' not found in pattern edges`);
492
+ }
493
+ const payloadVal = readArenaValue(payloadPtr, pattern, edge.target);
494
+ return Value.variant(tag, payloadVal, pattern);
495
+ }
496
+ throw new Error(`Unsupported pattern kind: ${patternNode.kind}`);
497
+ }
498
+
499
+ // JS writer function for Wasm Execution Arena
500
+ function writeValueToArena(value, pattern, patternNodeId) {
501
+ const patternNode = pattern.nodes[patternNodeId];
502
+
503
+ if (isProduct(value)) {
504
+ const keys = Object.keys(value.product).sort();
505
+ const N = keys.length;
506
+ const totalSize = 8 + 8 * N;
507
+
508
+ // Evaluate and allocate all children first
509
+ const childPtrs = [];
510
+ for (let i = 0; i < N; i++) {
511
+ const label = keys[i];
512
+ const edge = patternNode.edges.find(e => e.label === label);
513
+ const childPtr = writeValueToArena(value.product[label], pattern, edge.target);
514
+ childPtrs.push(childPtr);
515
+ }
516
+
517
+ const ptr = exports.alloc(totalSize);
518
+ const view = new DataView(exports.memory.buffer);
519
+
520
+ view.setUint32(ptr, totalSize, true);
521
+ view.setUint32(ptr + 4, N, true);
522
+
523
+ for (let i = 0; i < N; i++) {
524
+ const offsetVal = 8 + 4 * N + 4 * i;
525
+ view.setUint32(ptr + 8 + 4 * i, offsetVal, true);
526
+ view.setUint32(ptr + offsetVal, childPtrs[i], true);
527
+ }
528
+ return ptr;
529
+ } else if (isVariant(value)) {
530
+ const tagId = getTagId(value.tag);
531
+ const edge = patternNode.edges.find(e => e.label === value.tag);
532
+ const childPtr = writeValueToArena(value.value, pattern, edge.target);
533
+
534
+ const ptr = exports.alloc(12);
535
+ const view = new DataView(exports.memory.buffer);
536
+
537
+ view.setUint32(ptr, 12, true);
538
+ view.setUint32(ptr + 4, tagId, true);
539
+ view.setUint32(ptr + 8, childPtr, true);
540
+ return ptr;
541
+ }
542
+ throw new Error(`Unsupported value type: ${value}`);
543
+ }
544
+
545
+ function writeUnitProductToArena() {
546
+ const ptr = exports.alloc(8);
547
+ const view = new DataView(exports.memory.buffer);
548
+ view.setUint32(ptr, 8, true);
549
+ view.setUint32(ptr + 4, 0, true);
550
+ return ptr;
551
+ }
552
+
553
+ function writeVariantPtrToArena(tag, payloadPtr) {
554
+ const ptr = exports.alloc(12);
555
+ const view = new DataView(exports.memory.buffer);
556
+ view.setUint32(ptr, 12, true);
557
+ view.setUint32(ptr + 4, getTagId(tag), true);
558
+ view.setUint32(ptr + 8, payloadPtr, true);
559
+ return ptr;
560
+ }
561
+
562
+ function writeNatPtrToArena(n) {
563
+ let ptr = writeVariantPtrToArena("0", writeUnitProductToArena());
564
+ for (let i = 0; i < n; i++) {
565
+ ptr = writeVariantPtrToArena("+1", ptr);
566
+ }
567
+ return ptr;
568
+ }
569
+
570
+ function writeAddInputPtrToArena(xPtr, yPtr) {
571
+ const ptr = exports.alloc(24);
572
+ const view = new DataView(exports.memory.buffer);
573
+ view.setUint32(ptr, 24, true);
574
+ view.setUint32(ptr + 4, 2, true);
575
+ view.setUint32(ptr + 8, 16, true);
576
+ view.setUint32(ptr + 12, 20, true);
577
+ view.setUint32(ptr + 16, xPtr, true);
578
+ view.setUint32(ptr + 20, yPtr, true);
579
+ return ptr;
580
+ }
581
+
582
+ // Helper to extract property list pattern for a pattern ID in graph
583
+ function getPattern(graph, patternId) {
584
+ const nodeId = graph.find(patternId);
585
+ return propertyListToPattern(patternToPropertyList(exportPatternGraph(graph, nodeId)));
586
+ }
587
+
588
+ // Prepare input values: 2 + 1 = 3
589
+ const inputData = {
590
+ x: { "+1": { "+1": { "0": {} } } }, // 2
591
+ y: { "+1": { "0": {} } } // 1
592
+ };
593
+ const valInput = fromObject(inputData);
594
+
595
+ const graph = defs.rels.add.typePatternGraph;
596
+ const inputPattern = getPattern(graph, defs.rels.add.def.patterns[0]);
597
+ const outputPattern = getPattern(graph, defs.rels.add.def.patterns[1]);
598
+
599
+ const ptrIn = writeValueToArena(valInput, inputPattern, 0);
600
+
601
+ // Call compiled recursive Peano addition
602
+ const res = exports.add(ptrIn);
603
+ assert.equal(res[1], 1, "Peano addition must succeed");
604
+
605
+ const valOutput = readArenaValue(res[0], outputPattern, 0);
606
+
607
+ // Assert equivalence with reference math
608
+ assert.deepEqual(valOutput.toJSON(), { "+1": { "+1": { "+1": "0" } } }, "Addition of 2 and 1 must equal 3");
609
+
610
+ const deepInputPtr = writeAddInputPtrToArena(writeNatPtrToArena(5000), writeNatPtrToArena(0));
611
+ const deepRes = exports.add(deepInputPtr);
612
+ assert.equal(deepRes[1], 1, "Tail-recursive Peano addition must handle deep inputs without overflowing the Wasm call stack");
613
+
614
+ console.log("End-to-End Peano Addition & Serialization tests passed successfully!");
615
+ }
616
+
617
+ console.log("==> Harness ready.");