@fraczak/k 6.3.3 → 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 (155) 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 +61 -4
  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/runtime/show-value.mjs +2 -2
  81. package/conformance/helper-reuse/case.json +8 -0
  82. package/conformance/helper-reuse/expected.kv +1 -0
  83. package/conformance/helper-reuse/input.kv +1 -0
  84. package/conformance/helper-reuse/program.k +2 -0
  85. package/conformance/identity/case.json +8 -0
  86. package/conformance/identity/expected.kv +1 -0
  87. package/conformance/identity/input.kv +1 -0
  88. package/conformance/identity/program.k +1 -0
  89. package/conformance/product-projection/case.json +8 -0
  90. package/conformance/product-projection/expected.kv +1 -0
  91. package/conformance/product-projection/input.kv +1 -0
  92. package/conformance/product-projection/program.k +1 -0
  93. package/conformance/run.mjs +149 -0
  94. package/conformance/variant-construction/case.json +8 -0
  95. package/conformance/variant-construction/expected.kv +1 -0
  96. package/conformance/variant-construction/input.kv +1 -0
  97. package/conformance/variant-construction/program.k +1 -0
  98. package/harness/adapters/arm64.mjs +105 -0
  99. package/harness/adapters/kvm.mjs +144 -0
  100. package/harness/adapters/llvm.mjs +118 -0
  101. package/harness/adapters/native.mjs +77 -0
  102. package/harness/adapters/wasm.mjs +169 -0
  103. package/harness/backend.mjs +95 -0
  104. package/harness/cli.mjs +130 -0
  105. package/harness/format.mjs +95 -0
  106. package/harness/index.mjs +25 -0
  107. package/harness/persistent.mjs +121 -0
  108. package/harness/registry.mjs +44 -0
  109. package/harness/suite.mjs +257 -0
  110. package/harness/suites/poly.mjs +156 -0
  111. package/harness/trace.mjs +50 -0
  112. package/harness/util.mjs +92 -0
  113. package/intrinsics.mjs +41 -0
  114. package/kir.mjs +399 -0
  115. package/kvm.mjs +585 -81
  116. package/objects/README.md +37 -3
  117. package/objects/compile.mjs +15 -22
  118. package/objects/inspect.mjs +96 -0
  119. package/objects/validate.mjs +415 -0
  120. package/package.json +51 -19
  121. package/repl-codecs.mjs +105 -6
  122. package/repl.mjs +335 -49
  123. package/run.mjs +2 -7
  124. package/scripts/benchmark.mjs +406 -22
  125. package/scripts/build-repl-html.mjs +1784 -0
  126. package/scripts/perf-ieee.mjs +586 -0
  127. package/scripts/perf-int.mjs +531 -0
  128. package/scripts/perf-poly.mjs +945 -0
  129. package/scripts/render-benchmark.mjs +12 -8
  130. package/scripts/run-tests.mjs +6 -0
  131. package/tests/integration.sh +19 -2
  132. package/tests/test-arithmetics.mjs +54 -0
  133. package/tests/test-ieee-arithmetic.mjs +4 -0
  134. package/tests/test-kir.mjs +85 -0
  135. package/tests/test-kvm-polymorphic.mjs +278 -0
  136. package/tests/test-kvm.mjs +25 -6
  137. package/tests/test-poly.mjs +129 -0
  138. package/tests/test-repl-html.mjs +303 -0
  139. package/tests/test-repl.mjs +144 -1
  140. package/tests/test-validate-object.mjs +40 -0
  141. package/a.klib +0 -1
  142. package/a.kvm +0 -142676
  143. package/a.wasm +0 -0
  144. package/benchmark-results.html +0 -532
  145. package/benchmark-results.json +0 -480
  146. package/k-vscode/.vscode/launch.json +0 -14
  147. package/k-vscode/README.md +0 -75
  148. package/k-vscode/extension.js +0 -273
  149. package/k-vscode/language-configuration.json +0 -42
  150. package/k-vscode/package.json +0 -79
  151. package/k-vscode/snippets/k.json +0 -92
  152. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  153. package/k-vscode/test/background-ranges.test.js +0 -50
  154. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  155. package/scripts/git-hooks/pre-commit +0 -10
@@ -0,0 +1,319 @@
1
+ import fs from "node:fs";
2
+ import assert from "node:assert/strict";
3
+ import wabtFactory from "wabt";
4
+ import {
5
+ codes,
6
+ createState,
7
+ evaluateInput,
8
+ executeKVM,
9
+ exportPatternGraph,
10
+ fromObject,
11
+ isProduct,
12
+ isVariant,
13
+ lowerToKVM,
14
+ parseFloat64,
15
+ patternToPropertyList,
16
+ printFloat64,
17
+ propertyListToPattern,
18
+ valueForCode,
19
+ Value
20
+ } from "@fraczak/k/backend-api.mjs";
21
+ import { lowerToWasm, getTagId, getTagFromId, getFuncNameFromId } from "./src/kvm2wasm.mjs";
22
+
23
+ console.log("==> Starting Wasm IEEE-754 Square Demo");
24
+
25
+ // Initialize WABT
26
+ const wabtInstance = await wabtFactory();
27
+
28
+ function compileWat(filename, watText) {
29
+ const watModule = wabtInstance.parseWat(filename, watText, {
30
+ mutable_globals: true,
31
+ sat_float_to_int: true,
32
+ sign_extension: true,
33
+ multi_value: true,
34
+ bulk_memory: true,
35
+ reference_types: true
36
+ });
37
+ watModule.resolveNames();
38
+ watModule.validate();
39
+ const binary = watModule.toBinary({
40
+ log: false,
41
+ canonicalize_lebs: true,
42
+ relocatable: false,
43
+ write_debug_names: true
44
+ });
45
+ return binary.buffer;
46
+ }
47
+
48
+ // 1. Create REPL state and load ieee.k library
49
+ console.log("Loading IEEE-754 arithmetic library (@fraczak/k/Examples/ieee.k)...");
50
+ const state = createState();
51
+ const ieeePath = fileURLToPath(import.meta.resolve("@fraczak/k/Examples/ieee.k"));
52
+ await evaluateInput(`:load ${ieeePath}`, state);
53
+
54
+ // 2. Define the squaring relation: square = { () x, () y } mul;
55
+ console.log("Defining relation: square = { () x, () y } mul;");
56
+ await evaluateInput("square = { () x, () y } mul;", state);
57
+ await evaluateInput("get_sign = $float64 .sign;", state);
58
+
59
+ // 3. Compile and Link relations recursively starting from square
60
+ console.log("Compiling and linking Wasm module...");
61
+
62
+ const cleanName = (h) => "rel_" + h.replace(/[^a-zA-Z0-9_]/g, "_");
63
+
64
+ function compileModule(mainRelHash) {
65
+ const compiled = new Set();
66
+ const queue = [mainRelHash];
67
+ const wats = [];
68
+
69
+ while (queue.length > 0) {
70
+ const hash = queue.shift();
71
+ if (compiled.has(hash)) continue;
72
+ compiled.add(hash);
73
+
74
+ const relDef = state.rels[hash];
75
+ if (!relDef) {
76
+ throw new Error(`Relation hash ${hash} not found`);
77
+ }
78
+
79
+ const kvmFunc = lowerToKVM(relDef, hash);
80
+ kvmFunc.typePatternGraph = relDef.typePatternGraph;
81
+
82
+ // Scan for call dependencies using raw hashes
83
+ for (const inst of kvmFunc.body) {
84
+ if (inst.op === "call") {
85
+ if (!compiled.has(inst.func) && !queue.includes(inst.func)) {
86
+ queue.push(inst.func);
87
+ }
88
+ }
89
+ if (inst.op === "call_intrinsic") {
90
+ console.log(`FOUND INTRINSIC: ${inst.symbol} in relation ${hash}`);
91
+ }
92
+ if (inst.branches) {
93
+ const scan = (insts) => {
94
+ for (const sub of insts) {
95
+ if (sub.op === "call") {
96
+ if (!compiled.has(sub.func) && !queue.includes(sub.func)) {
97
+ queue.push(sub.func);
98
+ }
99
+ }
100
+ if (sub.op === "call_intrinsic") {
101
+ console.log(`FOUND INTRINSIC: ${sub.symbol} in relation ${hash}`);
102
+ }
103
+ if (sub.branches) {
104
+ for (const br of sub.branches) {
105
+ scan(br.body);
106
+ }
107
+ }
108
+ }
109
+ };
110
+ for (const br of inst.branches) {
111
+ scan(br.body);
112
+ }
113
+ }
114
+ }
115
+
116
+ // Rename functions in KVM IR to valid Wasm identifiers
117
+ kvmFunc.name = cleanName(hash);
118
+ const cleanInsts = (insts) => {
119
+ for (const inst of insts) {
120
+ if (inst.op === "call") {
121
+ inst.func = cleanName(inst.func);
122
+ }
123
+ if (inst.branches) {
124
+ for (const br of inst.branches) {
125
+ cleanInsts(br.body);
126
+ }
127
+ }
128
+ }
129
+ };
130
+ cleanInsts(kvmFunc.body);
131
+
132
+ const wat = lowerToWasm(kvmFunc, kvmFunc.name, { trace: true });
133
+ if (hash === squareHash) {
134
+ console.log("==> KVM IR for square:", JSON.stringify(kvmFunc.body, null, 2));
135
+ console.log("==> Compiled WAT for square:\n", wat);
136
+ }
137
+ wats.push(wat);
138
+ }
139
+
140
+ return wats.join("\n\n");
141
+ }
142
+
143
+ const squareHash = state.relAliases["square"];
144
+ const getSignHash = state.relAliases["get_sign"];
145
+ const moduleWatBody = compileModule(squareHash);
146
+ const getSignWat = lowerToWasm(state.rels[getSignHash], "get_sign", { trace: true });
147
+ console.log("==> Compiled WAT for get_sign:\n", getSignWat);
148
+ const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
149
+
150
+ // Combine Wasm runtime and compiled relations
151
+ const declarations = `
152
+ (import "env" "log_call" (func $log_call (param i32 i32)))
153
+ (import "env" "log_ret" (func $log_ret (param i32 i32 i32)))
154
+ `;
155
+ const runtimeWatWithImports = runtimeWat.replace("(module", "(module\n" + declarations);
156
+ const fullWat = runtimeWatWithImports.trim().slice(0, -1) + "\n" + moduleWatBody + "\n" + getSignWat + "\n)";
157
+ fs.writeFileSync("debug_full.wat", fullWat);
158
+ const binary = compileWat("square_demo.wat", fullWat);
159
+ const module = await WebAssembly.compile(binary);
160
+ const imports = {
161
+ env: {
162
+ log_call: (funcId, ptrIn) => {
163
+ const funcName = getFuncNameFromId(funcId);
164
+ console.log(`[Wasm Trace] Calling ${funcName} with input ptr ${ptrIn}`);
165
+ },
166
+ log_ret: (funcId, ptrOut, ok) => {
167
+ const funcName = getFuncNameFromId(funcId);
168
+ console.log(`[Wasm Trace] Returned from ${funcName} -> ptr ${ptrOut}, ok ${ok}`);
169
+ }
170
+ }
171
+ };
172
+ const instance = await WebAssembly.instantiate(module, imports);
173
+ const exports = instance.exports;
174
+
175
+ // JS Arena helpers
176
+ function readArenaValue(exports, ptr, pattern, patternNodeId) {
177
+ const patternNode = pattern.nodes[patternNodeId];
178
+ const view = new DataView(exports.memory.buffer);
179
+
180
+ if (patternNode.kind === 1 || patternNode.kind === 3) {
181
+ const size = view.getUint32(ptr, true);
182
+ const N = view.getUint32(ptr + 4, true);
183
+ const productObj = {};
184
+
185
+ for (let i = 0; i < N; i++) {
186
+ const edge = patternNode.edges[i];
187
+ const offsetVal = view.getUint32(ptr + 8 + 4 * i, true);
188
+ const childPtr = view.getUint32(ptr + offsetVal, true);
189
+ productObj[edge.label] = readArenaValue(exports, childPtr, pattern, edge.target);
190
+ }
191
+ return Value.product(productObj, pattern);
192
+ } else if (patternNode.kind === 2 || patternNode.kind === 4) {
193
+ const size = view.getUint32(ptr, true);
194
+ const tagId = view.getUint32(ptr + 4, true);
195
+ const payloadPtr = view.getUint32(ptr + 8, true);
196
+
197
+ const tag = getTagFromId(tagId);
198
+ console.log(`[Debug Arena] Read Variant at ptr=${ptr}: size=${size}, tagId=${tagId}, tag=${tag}`);
199
+ const edge = patternNode.edges.find(e => e.label === tag);
200
+ if (!edge) {
201
+ throw new Error(`Variant tag '${tag}' not found in pattern edges`);
202
+ }
203
+ const payloadVal = readArenaValue(exports, payloadPtr, pattern, edge.target);
204
+ return Value.variant(tag, payloadVal, pattern);
205
+ }
206
+ throw new Error(`Unsupported pattern kind: ${patternNode.kind}`);
207
+ }
208
+
209
+ function writeValueToArena(value, pattern, patternNodeId) {
210
+ const patternNode = pattern.nodes[patternNodeId];
211
+
212
+ if (isProduct(value)) {
213
+ const keys = Object.keys(value.product).sort();
214
+ const N = keys.length;
215
+ const totalSize = 8 + 8 * N;
216
+
217
+ // Evaluate and allocate all children first
218
+ const childPtrs = [];
219
+ for (let i = 0; i < N; i++) {
220
+ const label = keys[i];
221
+ const edge = patternNode.edges.find(e => e.label === label);
222
+ const childPtr = writeValueToArena(value.product[label], pattern, edge.target);
223
+ childPtrs.push(childPtr);
224
+ }
225
+
226
+ const ptr = exports.alloc(totalSize);
227
+ const view = new DataView(exports.memory.buffer);
228
+
229
+ view.setUint32(ptr, totalSize, true);
230
+ view.setUint32(ptr + 4, N, true);
231
+
232
+ for (let i = 0; i < N; i++) {
233
+ const offsetVal = 8 + 4 * N + 4 * i;
234
+ view.setUint32(ptr + 8 + 4 * i, offsetVal, true);
235
+ view.setUint32(ptr + offsetVal, childPtrs[i], true);
236
+ }
237
+ return ptr;
238
+ } else if (isVariant(value)) {
239
+ const tagId = getTagId(value.tag);
240
+ const edge = patternNode.edges.find(e => e.label === value.tag);
241
+ const childPtr = writeValueToArena(value.value, pattern, edge.target);
242
+
243
+ const ptr = exports.alloc(12);
244
+ const view = new DataView(exports.memory.buffer);
245
+
246
+ view.setUint32(ptr, 12, true);
247
+ view.setUint32(ptr + 4, tagId, true);
248
+ view.setUint32(ptr + 8, childPtr, true);
249
+ console.log(`[Debug Arena] Wrote Variant at ptr=${ptr}: tag=${value.tag}, tagId=${tagId}`);
250
+ return ptr;
251
+ }
252
+ throw new Error(`Unsupported value type: ${value}`);
253
+ }
254
+
255
+ function getPattern(graph, patternId) {
256
+ const nodeId = graph.find(patternId);
257
+ return propertyListToPattern(patternToPropertyList(exportPatternGraph(graph, nodeId)));
258
+ }
259
+
260
+ // 4. Run on an input
261
+ const inputValStr = "1.5";
262
+ console.log(`\nInput float64 value: ${inputValStr}`);
263
+
264
+ // Convert input string to IEEE float64 tree
265
+ const float64Hash = state.typeAliases.float64;
266
+ const parsedFloatVal = valueForCode(parseFloat64(inputValStr), float64Hash, codes.find);
267
+
268
+ // Resolve patterns
269
+ const squareRel = state.rels[squareHash];
270
+ const graph = squareRel.typePatternGraph;
271
+ const inputPattern = getPattern(graph, squareRel.def.patterns[0]);
272
+ const outputPattern = getPattern(graph, squareRel.def.patterns[1]);
273
+
274
+ // Run the JS interpreter first to check
275
+ const kvmFuncSquare = lowerToKVM(squareRel, squareHash);
276
+ const context = { rels: state.rels, findCode: codes.find, options: { envelopeFree: true, trace: false } };
277
+ const jsRes = executeKVM(kvmFuncSquare, parsedFloatVal, context);
278
+ console.log("JS Interpreter result:", jsRes ? printFloat64(jsRes.product.result) : "failed");
279
+
280
+ // Print keys and patterns for debugging
281
+ console.log("==> parsedFloatVal keys:", Object.keys(parsedFloatVal.product));
282
+ console.log("==> inputPattern node 0:", JSON.stringify(inputPattern.nodes[0], null, 2));
283
+ console.log("==> outputPattern node 0:", JSON.stringify(outputPattern.nodes[0], null, 2));
284
+
285
+ // Write input to the arena
286
+ const ptrIn = writeValueToArena(parsedFloatVal, inputPattern, 0);
287
+
288
+ // Call get_sign to test basic projection
289
+ console.log("Calling Wasm function 'get_sign'...");
290
+ const getSignRes = exports.get_sign(ptrIn);
291
+ if (getSignRes[1] === 1) {
292
+ const getSignRel = state.rels[getSignHash];
293
+ const getSignPattern = getPattern(getSignRel.typePatternGraph, getSignRel.def.patterns[1]);
294
+ const getSignVal = readArenaValue(exports, getSignRes[0], getSignPattern, 0);
295
+ console.log("get_sign output tag:", getSignVal.tag);
296
+ } else {
297
+ console.log("get_sign failed!");
298
+ }
299
+
300
+ // Run the Wasm function
301
+ console.log("Calling Wasm function 'square'...");
302
+ const mainFuncName = cleanName(squareHash);
303
+ const result = exports[mainFuncName](ptrIn);
304
+
305
+ if (result[1] !== 1) {
306
+ console.error("Wasm execution failed!");
307
+ process.exit(1);
308
+ }
309
+
310
+ // Read and decode output from the arena
311
+ const outputVal = readArenaValue(exports, result[0], outputPattern, 0);
312
+
313
+ // Print the result using ieee print float64 codec helper
314
+ const outputResultFloat = outputVal.product.result;
315
+ const printResult = printFloat64(outputResultFloat);
316
+
317
+ console.log(`Result of (${inputValStr} * ${inputValStr}): ${printResult}`);
318
+ console.log("Flags:", outputVal.product.flags.tag);
319
+ console.log("==> Demo finished successfully!");
@@ -0,0 +1,102 @@
1
+ (module
2
+ (memory (export "memory") 1)
3
+
4
+ ;; Start allocation at byte offset 1024 to leave a safety margin and avoid returning 0 (nullptr)
5
+ (global $arena_free (mut i32) (i32.const 1024))
6
+ ;; Maximum address of the currently allocated pages (1 page = 64KB)
7
+ (global $arena_max (mut i32) (i32.const 65536))
8
+
9
+ ;; Bump allocator function: aligns allocation size to 8 bytes, grows memory if needed, and returns the pointer
10
+ (func $alloc (export "alloc") (param $size i32) (result i32)
11
+ (local $old_free i32)
12
+ (local $new_free i32)
13
+ (local $pages_needed i32)
14
+ (local $grow_res i32)
15
+
16
+ ;; 1. Align size to 8 bytes: size = (size + 7) & ~8
17
+ local.get $size
18
+ i32.const 7
19
+ i32.add
20
+ i32.const -8
21
+ i32.and
22
+ local.set $size
23
+
24
+ ;; Get current free pointer
25
+ global.get $arena_free
26
+ local.set $old_free
27
+
28
+ ;; Calculate proposed new free pointer
29
+ local.get $old_free
30
+ local.get $size
31
+ i32.add
32
+ local.set $new_free
33
+
34
+ ;; 2. Check if we exceed current memory size
35
+ local.get $new_free
36
+ global.get $arena_max
37
+ i32.gt_u
38
+ if
39
+ ;; Calculate how many additional pages we need to grow
40
+ local.get $new_free
41
+ global.get $arena_max
42
+ i32.sub
43
+ i32.const 65535
44
+ i32.add
45
+ i32.const 65536
46
+ i32.div_u
47
+ local.set $pages_needed
48
+
49
+ ;; Try to grow memory by $pages_needed
50
+ local.get $pages_needed
51
+ memory.grow
52
+ local.set $grow_res
53
+
54
+ ;; If grow returns -1, we are out of memory
55
+ local.get $grow_res
56
+ i32.const -1
57
+ i32.eq
58
+ if
59
+ unreachable
60
+ end
61
+
62
+ ;; Update arena_max: arena_max = arena_max + pages_needed * 65536
63
+ global.get $arena_max
64
+ local.get $pages_needed
65
+ i32.const 65536
66
+ i32.mul
67
+ i32.add
68
+ global.set $arena_max
69
+ end
70
+
71
+ ;; 3. Perform the allocation
72
+ local.get $new_free
73
+ global.set $arena_free
74
+
75
+ local.get $old_free
76
+ )
77
+
78
+ ;; Return a checkpoint that can be restored after a temporary result is consumed.
79
+ (func $arena_mark (export "arena_mark") (result i32)
80
+ global.get $arena_free
81
+ )
82
+
83
+ ;; Bulk-free allocations made after a checkpoint. Memory pages remain available for reuse.
84
+ (func $arena_reset (export "arena_reset") (param $mark i32)
85
+ local.get $mark
86
+ i32.const 1024
87
+ i32.lt_u
88
+ if
89
+ unreachable
90
+ end
91
+
92
+ local.get $mark
93
+ global.get $arena_free
94
+ i32.gt_u
95
+ if
96
+ unreachable
97
+ end
98
+
99
+ local.get $mark
100
+ global.set $arena_free
101
+ )
102
+ )
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+ const node = process.execPath;
9
+ const tests = [
10
+ ["tests/test-wasm.mjs", node, ["tests/test-wasm.mjs"]],
11
+ ["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
12
+ ];
13
+
14
+ function formatDuration(milliseconds) {
15
+ if (milliseconds < 1000) return `${milliseconds.toFixed(0)} ms`;
16
+ return `${(milliseconds / 1000).toFixed(2)} s`;
17
+ }
18
+
19
+ const suiteStarted = performance.now();
20
+ for (const [name, command, args] of tests) {
21
+ console.log(`\n==> ${name}`);
22
+ const started = performance.now();
23
+ const result = spawnSync(command, args, {
24
+ cwd: root,
25
+ stdio: "inherit"
26
+ });
27
+ const duration = formatDuration(performance.now() - started);
28
+
29
+ if (result.error) {
30
+ console.error(`<== ${name} FAILED (${duration})`);
31
+ console.error(result.error.message);
32
+ process.exit(1);
33
+ }
34
+ if (result.status !== 0) {
35
+ console.error(`<== ${name} FAILED (${duration})`);
36
+ process.exit(result.status ?? 1);
37
+ }
38
+ console.log(`<== ${name} passed (${duration})`);
39
+ }
40
+
41
+ console.log(`\nAll tests passed (${formatDuration(performance.now() - suiteStarted)})`);
@@ -0,0 +1,133 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { decodeObject, loadLibrary } from "@fraczak/k/backend-api.mjs";
5
+ import {
6
+ compileWasmArtifact,
7
+ compileWasmArtifactFromKVM,
8
+ compileWasmArtifactFromObject
9
+ } from "./wasm.mjs";
10
+
11
+ function readAll(stream) {
12
+ return new Promise((resolve, reject) => {
13
+ const chunks = [];
14
+ stream.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
15
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
16
+ stream.on("error", reject);
17
+ });
18
+ }
19
+
20
+ function parseCompileOptions(args) {
21
+ const libraries = [];
22
+ const exportSpecs = [];
23
+ let loadedLibrary = false;
24
+
25
+ while (args.length > 0) {
26
+ if (args[0] === "--") {
27
+ args.shift();
28
+ break;
29
+ }
30
+ if (args[0] === "--lib") {
31
+ if (loadedLibrary) throw new Error("--lib may only be specified once");
32
+ args.shift();
33
+ const libPath = args.shift();
34
+ if (!libPath) throw new Error("--lib requires a file argument");
35
+ libraries.push(loadLibrary(decodeObject(fs.readFileSync(libPath))));
36
+ loadedLibrary = true;
37
+ } else if (args[0] === "--export") {
38
+ args.shift();
39
+ const spec = args.shift();
40
+ if (!spec) throw new Error("--export requires a spec argument (name or libname:localname)");
41
+ exportSpecs.push(spec);
42
+ } else if (args[0].startsWith("--")) {
43
+ throw new Error(`Unknown option: ${args[0]}`);
44
+ } else {
45
+ break;
46
+ }
47
+ }
48
+
49
+ return { libraries, exportSpecs };
50
+ }
51
+
52
+ function fileInputType(filePath) {
53
+ const ext = path.extname(filePath).toLowerCase();
54
+ if (ext === ".ko") return "ko";
55
+ if (ext === ".kvm") return "kvm";
56
+ if (ext === ".klib") return "klib";
57
+ return "source-file";
58
+ }
59
+
60
+ function resolveProgramInput(args, { allowStdinSource = false } = {}) {
61
+ const inputArg = args.shift() ?? null;
62
+ if (inputArg == null) {
63
+ if (!allowStdinSource) throw new Error("Missing script argument");
64
+ return { kind: "stdin-source", sourceName: "<stdin>" };
65
+ }
66
+
67
+ const ext = path.extname(inputArg).toLowerCase();
68
+ if (fs.existsSync(inputArg)) {
69
+ const kind = fileInputType(inputArg);
70
+ if (kind === "klib") {
71
+ throw new Error("Cannot compile a .klib library to WebAssembly without a main relation; load it with --lib.");
72
+ }
73
+ return { kind, path: inputArg, sourceName: inputArg };
74
+ }
75
+
76
+ if ([".k", ".ko", ".kvm", ".klib", ".wasm"].includes(ext)) {
77
+ throw new Error(`Input file not found: ${inputArg}`);
78
+ }
79
+
80
+ return { kind: "inline-source", source: inputArg, sourceName: "<inline>" };
81
+ }
82
+
83
+ async function compileProgramInput(
84
+ input,
85
+ { libraries = [], exportSpecs = [], stdin = null, inputEnvelopePattern = null } = {}
86
+ ) {
87
+ if (input.kind === "inline-source") {
88
+ return compileWasmArtifact(input.source, {
89
+ libraries,
90
+ exports: exportSpecs,
91
+ source: input.sourceName,
92
+ inputEnvelopePattern
93
+ });
94
+ }
95
+
96
+ if (input.kind === "stdin-source") {
97
+ if (!stdin) throw new Error("stdin source requested without a stdin stream");
98
+ return compileWasmArtifact((await readAll(stdin)).toString("utf8"), {
99
+ libraries,
100
+ exports: exportSpecs,
101
+ source: input.sourceName,
102
+ inputEnvelopePattern
103
+ });
104
+ }
105
+
106
+ if (input.kind === "source-file") {
107
+ return compileWasmArtifact(fs.readFileSync(input.path, "utf8"), {
108
+ libraries,
109
+ exports: exportSpecs,
110
+ source: input.sourceName,
111
+ inputEnvelopePattern
112
+ });
113
+ }
114
+
115
+ if (input.kind === "ko") {
116
+ return compileWasmArtifactFromObject(decodeObject(fs.readFileSync(input.path)), {
117
+ inputEnvelopePattern
118
+ });
119
+ }
120
+
121
+ if (input.kind === "kvm") {
122
+ return compileWasmArtifactFromKVM(JSON.parse(fs.readFileSync(input.path, "utf8")));
123
+ }
124
+
125
+ throw new Error(`Unsupported input kind: ${input.kind}`);
126
+ }
127
+
128
+ export {
129
+ compileProgramInput,
130
+ parseCompileOptions,
131
+ readAll,
132
+ resolveProgramInput
133
+ };