@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,625 @@
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
+
92
+ // Test 5: Out of Memory flag recording on overflow
93
+ assert.equal(runtimeExports.oom_flag.value, 0, "OOM flag must be 0 initially");
94
+ assert.throws(() => {
95
+ runtimeExports.alloc(-1);
96
+ }, /unreachable/);
97
+ assert.equal(runtimeExports.oom_flag.value, 1, "OOM flag must be 1 after allocation failure");
98
+ assert.equal(runtimeExports.oom_size.value, -1, "OOM size must record requested size");
99
+ console.log("Core Allocator tests passed successfully!");
100
+ }
101
+
102
+ import { lowerToWasm } from "../src/kvm2wasm.mjs";
103
+
104
+ console.log("==> Running Compiler Scaffolding Tests (id, fail, return)");
105
+ {
106
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
107
+
108
+ // Compile Identity function: %v1 = %in; return %v1;
109
+ const kvmIdentity = {
110
+ body: [
111
+ { op: "id", dest: "%v1", src: "%in" },
112
+ { op: "return", src: "%v1" }
113
+ ]
114
+ };
115
+ const identityWat = lowerToWasm(kvmIdentity, "test_identity");
116
+ const fullIdentityWat = runtimeWat.trim().slice(0, -1) + "\n" + identityWat + "\n)";
117
+ const binaryIdentity = compileWat("identity.wat", fullIdentityWat);
118
+ const exportsIdentity = await instantiateWasm(binaryIdentity);
119
+
120
+ // Invoke identity: input value 1234 should return (1234, 1) -> success
121
+ const resIdentity = exportsIdentity.test_identity(1234);
122
+ // WebAssembly multi-value returns are returned as array in some JS environments, or directly if supported.
123
+ // Note: Node 16+ supports multi-value return as array.
124
+ assert.deepEqual(resIdentity, [1234, 1], "Identity test: must return [1234, 1]");
125
+
126
+ // Compile Failure function: fail;
127
+ const kvmFailure = {
128
+ body: [
129
+ { op: "fail" }
130
+ ]
131
+ };
132
+ const failureWat = lowerToWasm(kvmFailure, "test_failure");
133
+ const fullFailureWat = runtimeWat.trim().slice(0, -1) + "\n" + failureWat + "\n)";
134
+ const binaryFailure = compileWat("failure.wat", fullFailureWat);
135
+ const exportsFailure = await instantiateWasm(binaryFailure);
136
+
137
+ // Invoke failure: input value 1234 should return (0, 0) -> failure
138
+ const resFailure = exportsFailure.test_failure(1234);
139
+ assert.deepEqual(resFailure, [0, 0], "Failure test: must return [0, 0]");
140
+
141
+ console.log("Compiler Scaffolding tests passed successfully!");
142
+ }
143
+
144
+ import { annotate, objectToKVMArtifact } from "@fraczak/k/backend-api.mjs";
145
+ import { compileObjectBuffer, decodeObject } from "@fraczak/k/object.mjs";
146
+ import { compileWasmArtifactFromKVM, metadataFromModule } from "../src/wasm.mjs";
147
+ import { getTagId } from "../src/kvm2wasm.mjs";
148
+
149
+ console.log("==> Running Wrapped kVM Artifact Test");
150
+ {
151
+ const object = decodeObject(compileObjectBuffer("()", { source: "wasm-kvm-artifact.k" }));
152
+ const artifact = objectToKVMArtifact(object, "__main__", [["closed-product", []]]);
153
+ assert.equal(artifact.layer, "KVM");
154
+ assert.equal(artifact.kir.layer, "KIR-P");
155
+ assert(!("instanceKey" in artifact));
156
+ const binary = await compileWasmArtifactFromKVM(artifact);
157
+ const metadata = metadataFromModule(await WebAssembly.compile(binary));
158
+ assert.equal(metadata.entry, "rel___main__");
159
+ assert.equal(metadata.relation, "__main__");
160
+ assert(!("instanceKey" in metadata));
161
+ assert.deepEqual(metadata.inputPattern, artifact.inputPattern);
162
+ assert.deepEqual(metadata.outputPattern, artifact.outputPattern);
163
+ }
164
+
165
+ console.log("==> Running Product Operations Integration Tests");
166
+ {
167
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
168
+
169
+ // Compile a relation that creates a product { .x x, .y y } and another that projects .y
170
+ const productDefs = annotate(`
171
+ make_prod = { .x x, .y y };
172
+ proj_y = make_prod .y;
173
+ ()
174
+ `, { convergence: { strategy: "auto" } });
175
+
176
+ const makeProdWat = lowerToWasm(productDefs.rels.make_prod, "make_prod");
177
+ const projYWat = lowerToWasm(productDefs.rels.proj_y, "proj_y");
178
+
179
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" + makeProdWat + "\n" + projYWat + "\n)";
180
+ const binary = compileWat("product_tests.wat", fullWat);
181
+ const exports = await instantiateWasm(binary);
182
+
183
+ // Helper to write a product to the arena
184
+ function writeProductToArena(fieldsMap) {
185
+ const keys = Object.keys(fieldsMap).sort();
186
+ const N = keys.length;
187
+ const totalSize = 8 + 8 * N;
188
+ const ptr = exports.alloc(totalSize);
189
+ const view = new DataView(exports.memory.buffer);
190
+
191
+ view.setUint32(ptr, totalSize, true);
192
+ view.setUint32(ptr + 4, N, true);
193
+
194
+ for (let i = 0; i < N; i++) {
195
+ const offsetVal = 8 + 4 * N + 4 * i;
196
+ const val = fieldsMap[keys[i]];
197
+ view.setUint32(ptr + 8 + 4 * i, offsetVal, true);
198
+ view.setUint32(ptr + offsetVal, val, true);
199
+ }
200
+ return ptr;
201
+ }
202
+
203
+ // 1. Test manual product creation & compiled projection
204
+ const ptrIn = writeProductToArena({ x: 101, y: 202 });
205
+ const resProj = exports.proj_y(ptrIn);
206
+ assert.deepEqual(resProj, [202, 1], "Projection of y should return [202, 1]");
207
+
208
+ // 2. Test compiled product creation & compiled projection
209
+ // Passing ptrIn to make_prod should construct a new product using make_prod's logic
210
+ const resMake = exports.make_prod(ptrIn);
211
+ assert.equal(resMake[1], 1, "make_prod should succeed");
212
+ const ptrOut = resMake[0];
213
+
214
+ // Inspect memory of ptrOut
215
+ const view = new DataView(exports.memory.buffer);
216
+ assert.equal(view.getUint32(ptrOut, true), 24, "Product total size should be 24");
217
+ assert.equal(view.getUint32(ptrOut + 4, true), 2, "Product field count should be 2");
218
+ assert.equal(view.getUint32(ptrOut + 8, true), 16, "Offset of first field (x) should be 16");
219
+ assert.equal(view.getUint32(ptrOut + 12, true), 20, "Offset of second field (y) should be 20");
220
+
221
+ // The first field value should be the projection of x from ptrIn (101)
222
+ const valXPtr = view.getUint32(ptrOut + 16, true);
223
+ assert.equal(valXPtr, 101, "First field value should be 101");
224
+ // The second field value should be the projection of y from ptrIn (202)
225
+ const valYPtr = view.getUint32(ptrOut + 20, true);
226
+ assert.equal(valYPtr, 202, "Second field value should be 202");
227
+
228
+ console.log("Product Operations integration tests passed successfully!");
229
+ }
230
+
231
+ console.log("==> Running Variant Operations Integration Tests");
232
+ {
233
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
234
+
235
+ const variantDefs = annotate(`
236
+ make_foo = |foo;
237
+ proj_foo = /foo;
238
+ proj_bar = /bar;
239
+ ()
240
+ `, { convergence: { strategy: "auto" } });
241
+
242
+ const makeFooWat = lowerToWasm(variantDefs.rels.make_foo, "make_foo");
243
+ const projFooWat = lowerToWasm(variantDefs.rels.proj_foo, "proj_foo");
244
+ const projBarWat = lowerToWasm(variantDefs.rels.proj_bar, "proj_bar");
245
+
246
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" +
247
+ makeFooWat + "\n" +
248
+ projFooWat + "\n" +
249
+ projBarWat + "\n)";
250
+ const binary = compileWat("variant_tests.wat", fullWat);
251
+ const exports = await instantiateWasm(binary);
252
+
253
+ // 1. Test compilation of variant tag creation
254
+ const resMake = exports.make_foo(999);
255
+ assert.equal(resMake[1], 1, "make_foo should succeed");
256
+ const ptrVar = resMake[0];
257
+
258
+ const view = new DataView(exports.memory.buffer);
259
+ assert.equal(view.getUint32(ptrVar, true), 12, "Variant total size should be 12");
260
+ assert.equal(view.getUint32(ptrVar + 4, true), getTagId("foo"), "Variant tagId should match 'foo'");
261
+ assert.equal(view.getUint32(ptrVar + 8, true), 999, "Variant payload should be 999");
262
+
263
+ // 2. Test variant projection success
264
+ const resProjSuccess = exports.proj_foo(ptrVar);
265
+ assert.deepEqual(resProjSuccess, [999, 1], "Projecting 'foo' from variant 'foo' should return [999, 1]");
266
+
267
+ // 3. Test variant projection failure
268
+ const resProjFail = exports.proj_bar(ptrVar);
269
+ assert.deepEqual(resProjFail, [0, 0], "Projecting 'bar' from variant 'foo' should fail and return [0, 0]");
270
+
271
+ console.log("Variant Operations integration tests passed successfully!");
272
+ }
273
+
274
+ console.log("==> Running Union & Choice Integration Tests");
275
+ {
276
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
277
+
278
+ const unionDefs = annotate(`
279
+ choose = < /x, /y >;
280
+ ()
281
+ `, { convergence: { strategy: "auto" } });
282
+
283
+ const chooseWat = lowerToWasm(unionDefs.rels.choose, "choose");
284
+ const chooseWithRollbackWat = lowerToWasm({
285
+ body: [
286
+ {
287
+ op: "union",
288
+ dest: "%v0",
289
+ src: "%in",
290
+ branches: [
291
+ {
292
+ body: [
293
+ { op: "make_variant", dest: "%v1", tag: "temporary", src: "%in" },
294
+ { op: "fail" }
295
+ ]
296
+ },
297
+ {
298
+ body: [
299
+ { op: "id", dest: "%v2", src: "%in" },
300
+ { op: "return", src: "%v2" }
301
+ ]
302
+ }
303
+ ]
304
+ },
305
+ { op: "return", src: "%v0" }
306
+ ]
307
+ }, "choose_with_rollback");
308
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" + chooseWat + "\n" + chooseWithRollbackWat + "\n)";
309
+ const binary = compileWat("union_tests.wat", fullWat);
310
+ const exports = await instantiateWasm(binary);
311
+
312
+ // Helper to write a variant to the arena
313
+ function writeVariantToArena(tag, payloadVal) {
314
+ const tagId = getTagId(tag);
315
+ const ptr = exports.alloc(12);
316
+ const view = new DataView(exports.memory.buffer);
317
+ view.setUint32(ptr, 12, true);
318
+ view.setUint32(ptr + 4, tagId, true);
319
+ view.setUint32(ptr + 8, payloadVal, true);
320
+ return ptr;
321
+ }
322
+
323
+ // 1. Pass variant with tag 'x': should match branch 1 and succeed
324
+ const ptrX = writeVariantToArena("x", 777);
325
+ const resX = exports.choose(ptrX);
326
+ assert.deepEqual(resX, [777, 1], "choose with tag 'x' should return [777, 1]");
327
+
328
+ // 2. Pass variant with tag 'y': should fail branch 1, match branch 2, and succeed
329
+ const ptrY = writeVariantToArena("y", 888);
330
+ const resY = exports.choose(ptrY);
331
+ assert.deepEqual(resY, [888, 1], "choose with tag 'y' should return [888, 1]");
332
+
333
+ // 3. Pass variant with tag 'z': should fail both branches and return failure
334
+ const ptrZ = writeVariantToArena("z", 999);
335
+ const resZ = exports.choose(ptrZ);
336
+ assert.deepEqual(resZ, [0, 0], "choose with tag 'z' should return [0, 0]");
337
+
338
+ // 4. Failed branches must release scratch allocations before trying the next choice
339
+ const mark = exports.arena_mark();
340
+ assert.deepEqual(exports.choose_with_rollback(999), [999, 1], "Fallback branch should return the input");
341
+ assert.equal(exports.arena_mark(), mark, "Failed union branch allocations must be reclaimed");
342
+
343
+ console.log("Union & Choice integration tests passed successfully!");
344
+ }
345
+
346
+ import {
347
+ exportPatternGraph,
348
+ fromObject,
349
+ isProduct,
350
+ isVariant,
351
+ NODE_KIND,
352
+ patternToPropertyList,
353
+ propertyListToPattern,
354
+ Value
355
+ } from "@fraczak/k/backend-api.mjs";
356
+ import { getTagFromId } from "../src/kvm2wasm.mjs";
357
+ import { readArenaValue, wasmPtr, writeValueToArena } from "../src/wasm.mjs";
358
+
359
+ console.log("==> Running Stack-Safe Arena Serialization Tests");
360
+ {
361
+ assert.equal(wasmPtr(-2061758672), 2233208624, "Exported Wasm i32 pointers must be treated as unsigned offsets");
362
+
363
+ const depth = 12000;
364
+ const memory = { buffer: new ArrayBuffer(1024 + 8 + 16 * (depth + 2)) };
365
+ const view = new DataView(memory.buffer);
366
+ const propertyList = [
367
+ ["closed-union", [["+", 1]]],
368
+ ["closed-union", [["_", 2], ["1", 1]]],
369
+ ["closed-product", []]
370
+ ];
371
+ const pattern = {
372
+ nodes: [
373
+ { kind: NODE_KIND.CLOSED_UNION, edges: [{ label: "+", target: 1 }] },
374
+ { kind: NODE_KIND.CLOSED_UNION, edges: [{ label: "_", target: 2 }, { label: "1", target: 1 }] },
375
+ { kind: NODE_KIND.CLOSED_PRODUCT, edges: [] }
376
+ ]
377
+ };
378
+ const tagNames = new Map([[1, "+"], [2, "_"], [3, "1"]]);
379
+ const tagIds = new Map([["+", 1], ["_", 2], ["1", 3]]);
380
+ const tags = {
381
+ getId: (tag) => tagIds.get(tag) ?? null,
382
+ getTag: (id) => tagNames.get(id) ?? null
383
+ };
384
+ let free = 1024;
385
+
386
+ function writeVariant(tagId, payloadPtr) {
387
+ const ptr = free;
388
+ free += 12;
389
+ view.setUint32(ptr, 12, true);
390
+ view.setUint32(ptr + 4, tagId, true);
391
+ view.setUint32(ptr + 8, payloadPtr, true);
392
+ return ptr;
393
+ }
394
+
395
+ const unitPtr = free;
396
+ free += 8;
397
+ view.setUint32(unitPtr, 8, true);
398
+ view.setUint32(unitPtr + 4, 0, true);
399
+
400
+ let ptr = writeVariant(2, unitPtr);
401
+ for (let i = 0; i < depth; i++) {
402
+ ptr = writeVariant(3, ptr);
403
+ }
404
+ ptr = writeVariant(1, ptr);
405
+
406
+ function assertDeepValue(output) {
407
+ assert.equal(output.tag, "+");
408
+ let bits = output.value;
409
+ for (let i = 0; i < depth; i++) {
410
+ assert.equal(bits.tag, "1");
411
+ bits = bits.value;
412
+ }
413
+ assert.equal(bits.tag, "_");
414
+ assert.deepEqual(bits.value.product, {});
415
+ }
416
+
417
+ assertDeepValue(readArenaValue({ memory }, ptr, pattern, 0, propertyList, new Map(), tags));
418
+
419
+ let input = Value.variant("_", Value.product({}));
420
+ for (let i = 0; i < depth; i++) {
421
+ input = Value.variant("1", input);
422
+ }
423
+ input = Value.variant("+", input);
424
+
425
+ free = 1024;
426
+ const exports = {
427
+ memory,
428
+ alloc(size) {
429
+ const ptr = free;
430
+ free += (size + 7) & -8;
431
+ return ptr;
432
+ }
433
+ };
434
+ const arenaValues = new Map();
435
+ ptr = writeValueToArena(exports, input, pattern, 0, arenaValues, tags);
436
+ const output = readArenaValue(exports, ptr, pattern, 0, propertyList, arenaValues, tags);
437
+ assertDeepValue(output);
438
+ console.log("Stack-safe arena serialization tests passed successfully!");
439
+ }
440
+
441
+ console.log("==> Running End-to-End Peano Addition & Serialization Tests");
442
+ {
443
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
444
+
445
+ const script = `
446
+ $ nat = < {} 0, nat +1 >;
447
+ 0 = {} | 0 $ nat;
448
+ inc = | +1 $ nat;
449
+ dec = $ nat / +1;
450
+ add = $ { nat x, nat y } <
451
+ { . x dec x, . y inc y } add,
452
+ . y
453
+ >;
454
+ ()
455
+ `;
456
+
457
+ const defs = annotate(script, { convergence: { strategy: "auto" } });
458
+
459
+ // Compile relations to WAT
460
+ const incWat = lowerToWasm(defs.rels.inc, "inc");
461
+ const decWat = lowerToWasm(defs.rels.dec, "dec");
462
+ const addWat = lowerToWasm(defs.rels.add, "add");
463
+ assert.match(addWat, /\bbr \$tail_loop\b/, "Recursive add should be lowered as a tail jump");
464
+ assert.doesNotMatch(addWat, /\bcall \$add\b/, "Recursive add should not emit a Wasm self-call");
465
+
466
+ // Combine and compile
467
+ const fullWat = runtimeWat.trim().slice(0, -1) + "\n" +
468
+ incWat + "\n" +
469
+ decWat + "\n" +
470
+ addWat + "\n)";
471
+ const binary = compileWat("add_tests.wat", fullWat);
472
+ const exports = await instantiateWasm(binary);
473
+
474
+ // JS reader function for Wasm Execution Arena
475
+ function readArenaValue(ptr, pattern, patternNodeId) {
476
+ const patternNode = pattern.nodes[patternNodeId];
477
+ const view = new DataView(exports.memory.buffer);
478
+
479
+ if (patternNode.kind === 1 || patternNode.kind === 3) { // OPEN_PRODUCT or CLOSED_PRODUCT
480
+ const size = view.getUint32(ptr, true);
481
+ const N = view.getUint32(ptr + 4, true);
482
+ const productObj = {};
483
+
484
+ for (let i = 0; i < N; i++) {
485
+ const edge = patternNode.edges[i];
486
+ const offsetVal = view.getUint32(ptr + 8 + 4 * i, true);
487
+ const childPtr = view.getUint32(ptr + offsetVal, true);
488
+ productObj[edge.label] = readArenaValue(childPtr, pattern, edge.target);
489
+ }
490
+ return Value.product(productObj, pattern);
491
+ } else if (patternNode.kind === 2 || patternNode.kind === 4) { // OPEN_UNION or CLOSED_UNION
492
+ const size = view.getUint32(ptr, true);
493
+ const tagId = view.getUint32(ptr + 4, true);
494
+ const payloadPtr = view.getUint32(ptr + 8, true);
495
+
496
+ const tag = getTagFromId(tagId);
497
+ const edge = patternNode.edges.find(e => e.label === tag);
498
+ if (!edge) {
499
+ throw new Error(`Variant tag '${tag}' not found in pattern edges`);
500
+ }
501
+ const payloadVal = readArenaValue(payloadPtr, pattern, edge.target);
502
+ return Value.variant(tag, payloadVal, pattern);
503
+ }
504
+ throw new Error(`Unsupported pattern kind: ${patternNode.kind}`);
505
+ }
506
+
507
+ // JS writer function for Wasm Execution Arena
508
+ function writeValueToArena(value, pattern, patternNodeId) {
509
+ const patternNode = pattern.nodes[patternNodeId];
510
+
511
+ if (isProduct(value)) {
512
+ const keys = Object.keys(value.product).sort();
513
+ const N = keys.length;
514
+ const totalSize = 8 + 8 * N;
515
+
516
+ // Evaluate and allocate all children first
517
+ const childPtrs = [];
518
+ for (let i = 0; i < N; i++) {
519
+ const label = keys[i];
520
+ const edge = patternNode.edges.find(e => e.label === label);
521
+ const childPtr = writeValueToArena(value.product[label], pattern, edge.target);
522
+ childPtrs.push(childPtr);
523
+ }
524
+
525
+ const ptr = exports.alloc(totalSize);
526
+ const view = new DataView(exports.memory.buffer);
527
+
528
+ view.setUint32(ptr, totalSize, true);
529
+ view.setUint32(ptr + 4, N, true);
530
+
531
+ for (let i = 0; i < N; i++) {
532
+ const offsetVal = 8 + 4 * N + 4 * i;
533
+ view.setUint32(ptr + 8 + 4 * i, offsetVal, true);
534
+ view.setUint32(ptr + offsetVal, childPtrs[i], true);
535
+ }
536
+ return ptr;
537
+ } else if (isVariant(value)) {
538
+ const tagId = getTagId(value.tag);
539
+ const edge = patternNode.edges.find(e => e.label === value.tag);
540
+ const childPtr = writeValueToArena(value.value, pattern, edge.target);
541
+
542
+ const ptr = exports.alloc(12);
543
+ const view = new DataView(exports.memory.buffer);
544
+
545
+ view.setUint32(ptr, 12, true);
546
+ view.setUint32(ptr + 4, tagId, true);
547
+ view.setUint32(ptr + 8, childPtr, true);
548
+ return ptr;
549
+ }
550
+ throw new Error(`Unsupported value type: ${value}`);
551
+ }
552
+
553
+ function writeUnitProductToArena() {
554
+ const ptr = exports.alloc(8);
555
+ const view = new DataView(exports.memory.buffer);
556
+ view.setUint32(ptr, 8, true);
557
+ view.setUint32(ptr + 4, 0, true);
558
+ return ptr;
559
+ }
560
+
561
+ function writeVariantPtrToArena(tag, payloadPtr) {
562
+ const ptr = exports.alloc(12);
563
+ const view = new DataView(exports.memory.buffer);
564
+ view.setUint32(ptr, 12, true);
565
+ view.setUint32(ptr + 4, getTagId(tag), true);
566
+ view.setUint32(ptr + 8, payloadPtr, true);
567
+ return ptr;
568
+ }
569
+
570
+ function writeNatPtrToArena(n) {
571
+ let ptr = writeVariantPtrToArena("0", writeUnitProductToArena());
572
+ for (let i = 0; i < n; i++) {
573
+ ptr = writeVariantPtrToArena("+1", ptr);
574
+ }
575
+ return ptr;
576
+ }
577
+
578
+ function writeAddInputPtrToArena(xPtr, yPtr) {
579
+ const ptr = exports.alloc(24);
580
+ const view = new DataView(exports.memory.buffer);
581
+ view.setUint32(ptr, 24, true);
582
+ view.setUint32(ptr + 4, 2, true);
583
+ view.setUint32(ptr + 8, 16, true);
584
+ view.setUint32(ptr + 12, 20, true);
585
+ view.setUint32(ptr + 16, xPtr, true);
586
+ view.setUint32(ptr + 20, yPtr, true);
587
+ return ptr;
588
+ }
589
+
590
+ // Helper to extract property list pattern for a pattern ID in graph
591
+ function getPattern(graph, patternId) {
592
+ const nodeId = graph.find(patternId);
593
+ return propertyListToPattern(patternToPropertyList(exportPatternGraph(graph, nodeId)));
594
+ }
595
+
596
+ // Prepare input values: 2 + 1 = 3
597
+ const inputData = {
598
+ x: { "+1": { "+1": { "0": {} } } }, // 2
599
+ y: { "+1": { "0": {} } } // 1
600
+ };
601
+ const valInput = fromObject(inputData);
602
+
603
+ const graph = defs.rels.add.typePatternGraph;
604
+ const inputPattern = getPattern(graph, defs.rels.add.def.patterns[0]);
605
+ const outputPattern = getPattern(graph, defs.rels.add.def.patterns[1]);
606
+
607
+ const ptrIn = writeValueToArena(valInput, inputPattern, 0);
608
+
609
+ // Call compiled recursive Peano addition
610
+ const res = exports.add(ptrIn);
611
+ assert.equal(res[1], 1, "Peano addition must succeed");
612
+
613
+ const valOutput = readArenaValue(res[0], outputPattern, 0);
614
+
615
+ // Assert equivalence with reference math
616
+ assert.deepEqual(valOutput.toJSON(), { "+1": { "+1": { "+1": "0" } } }, "Addition of 2 and 1 must equal 3");
617
+
618
+ const deepInputPtr = writeAddInputPtrToArena(writeNatPtrToArena(5000), writeNatPtrToArena(0));
619
+ const deepRes = exports.add(deepInputPtr);
620
+ assert.equal(deepRes[1], 1, "Tail-recursive Peano addition must handle deep inputs without overflowing the Wasm call stack");
621
+
622
+ console.log("End-to-End Peano Addition & Serialization tests passed successfully!");
623
+ }
624
+
625
+ console.log("==> Harness ready.");