@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
@@ -11,9 +11,16 @@ import { performance } from "node:perf_hooks";
11
11
  import {
12
12
  codes,
13
13
  createState,
14
+ decodeWire,
15
+ encodeToWire,
14
16
  evaluateInput,
15
17
  executeKVM,
18
+ exportPatternGraph,
16
19
  lowerToKVM,
20
+ objectToKVMArtifact,
21
+ retypeObjectRelationForBackend,
22
+ patternToPropertyList,
23
+ propertyListToPattern,
17
24
  parseFloat64,
18
25
  run,
19
26
  run_converged,
@@ -21,9 +28,24 @@ import {
21
28
  Value
22
29
  } from "../backend-api.mjs";
23
30
  import { parse as parseIntValue } from "../codecs/int.mjs";
31
+ import {
32
+ compileObjectBuffer as compileBackendObjectBuffer,
33
+ decodeObject as decodeBackendObject
34
+ } from "../object.mjs";
35
+ import {
36
+ compileWasmArtifactFromKVM,
37
+ metadataFromModule,
38
+ readArenaValue as readWasmArenaValue,
39
+ wasmPtr,
40
+ writeValueToArena as writeWasmValueToArena
41
+ } from "../backends/wasm/src/wasm.mjs";
42
+ import { emitLLVMModule } from "../backends/llvm/src/llvm.mjs";
43
+ import { compileLLVMToExecutable, stdioDriverSource } from "../backends/llvm/src/executable.mjs";
24
44
 
25
45
  const scriptPath = fileURLToPath(import.meta.url);
26
46
  const root = path.resolve(path.dirname(scriptPath), "..");
47
+ const kRoot = root;
48
+ const llvmRoot = path.join(root, "backends/llvm");
27
49
  const resultsPath = path.join(root, "benchmark-results.json");
28
50
 
29
51
  const bigA = "1234567890".repeat(22);
@@ -47,9 +69,14 @@ const cases = [
47
69
  const modes = [
48
70
  { name: "Native JS (Envelope-Aware)", kind: "native-aware" },
49
71
  { name: "Native JS (Envelope-Free)", kind: "native-free" },
50
- { name: "kVM Interpreter (Env-Free)", kind: "kvm-free" }
72
+ { name: "kVM Interpreter (Env-Free)", kind: "kvm-free" },
73
+ { name: "KIR-P Export", kind: "kir-p" },
74
+ { name: "k-wasm", kind: "wasm" },
75
+ { name: "k-llvm-jit", kind: "llvm-jit" }
51
76
  ];
52
77
 
78
+ let cachedLLVMBackendFingerprint = null;
79
+
53
80
  function csvEnv(name, fallback) {
54
81
  return (process.env[name] || fallback)
55
82
  .split(",")
@@ -93,6 +120,15 @@ function digestValue(value) {
93
120
  };
94
121
  }
95
122
 
123
+ function digestResult(value, relDef) {
124
+ const outputPattern = relationPattern(relDef, 1);
125
+ const wire = encodeToWire(value, outputPattern);
126
+ return {
127
+ digest: crypto.createHash("sha256").update(wire).digest("hex").slice(0, 16),
128
+ bytes: wire.length
129
+ };
130
+ }
131
+
96
132
  function formatMs(value) {
97
133
  return value == null ? "-" : value.toFixed(2);
98
134
  }
@@ -106,10 +142,320 @@ function pad(value, width) {
106
142
  return text.length >= width ? text : text + " ".repeat(width - text.length);
107
143
  }
108
144
 
145
+ function sha256() {
146
+ return crypto.createHash("sha256");
147
+ }
148
+
149
+ function suiteSourcePath(suite) {
150
+ return path.join(kRoot, suite === "ieee" ? "Examples/ieee.k" : "Examples/arithmetics.k");
151
+ }
152
+
153
+ function sourceNameForSuite(suite) {
154
+ return suite === "ieee" ? "Examples/ieee.k" : "Examples/arithmetics.k";
155
+ }
156
+
157
+ function stripTrailingUnitMain(source) {
158
+ return source.replace(/\s*\(\)\s*$/, "\n");
159
+ }
160
+
161
+ function sourceForCase(testCase) {
162
+ const sourcePath = suiteSourcePath(testCase.suite);
163
+ const source = stripTrailingUnitMain(fs.readFileSync(sourcePath, "utf8"));
164
+ return `${source}\n${testCase.rel}\n`;
165
+ }
166
+
167
+ function relationPattern(relDef, index) {
168
+ const graph = relDef.typePatternGraph;
169
+ const patternId = graph.find(relDef.def.patterns[index]);
170
+ return patternToPropertyList(exportPatternGraph(graph, patternId));
171
+ }
172
+
173
+ function canonicalPattern(propertyList) {
174
+ return patternToPropertyList(propertyListToPattern(propertyList));
175
+ }
176
+
177
+ function wireInput(inputVal, relDef) {
178
+ const inputPattern = relationPattern(relDef, 0);
179
+ const inputWire = encodeToWire(inputVal, inputPattern);
180
+ return { inputPattern, inputWire };
181
+ }
182
+
183
+ function backendObjectForCase(testCase) {
184
+ const source = sourceForCase(testCase);
185
+ return decodeBackendObject(compileBackendObjectBuffer(source, {
186
+ source: `${sourceNameForSuite(testCase.suite)}#${testCase.rel}`
187
+ }));
188
+ }
189
+
190
+ function backendKIRP(testCase, inputPattern, object = null) {
191
+ const backendObject = object || backendObjectForCase(testCase);
192
+ return retypeObjectRelationForBackend(backendObject, backendObject.main, inputPattern, {
193
+ source: `${sourceNameForSuite(testCase.suite)}#${testCase.rel}`
194
+ }).kir;
195
+ }
196
+
197
+ function backendKVMArtifact(testCase, inputPattern, object = null) {
198
+ const backendObject = object || backendObjectForCase(testCase);
199
+ return objectToKVMArtifact(backendObject, backendObject.main, inputPattern, {
200
+ source: `${sourceNameForSuite(testCase.suite)}#${testCase.rel}`
201
+ });
202
+ }
203
+
204
+ function createTagRegistry(entries) {
205
+ const tagToId = new Map();
206
+ const idToTag = new Map();
207
+ let nextId = 1;
208
+
209
+ for (const entry of entries) {
210
+ if (!entry || typeof entry.tag !== "string" || !Number.isInteger(entry.id) || entry.id < 1) {
211
+ throw new Error("WebAssembly artifact metadata contains an invalid tag entry");
212
+ }
213
+ if (tagToId.has(entry.tag) || idToTag.has(entry.id)) {
214
+ throw new Error("WebAssembly artifact metadata contains a duplicate tag entry");
215
+ }
216
+ tagToId.set(entry.tag, entry.id);
217
+ idToTag.set(entry.id, entry.tag);
218
+ nextId = Math.max(nextId, entry.id + 1);
219
+ }
220
+
221
+ return {
222
+ getId(tag) {
223
+ if (!tagToId.has(tag)) {
224
+ tagToId.set(tag, nextId);
225
+ idToTag.set(nextId, tag);
226
+ nextId++;
227
+ }
228
+ return tagToId.get(tag);
229
+ },
230
+ getTag(id) {
231
+ return idToTag.get(id) ?? null;
232
+ }
233
+ };
234
+ }
235
+
236
+ async function prepareWasmRunner(testCase, relation, inputVal, state) {
237
+ const relDef = relation.relDef;
238
+ const { inputPattern, inputWire } = wireInput(inputVal, relDef);
239
+ const kvmArtifact = backendKVMArtifact(testCase, inputPattern);
240
+ const artifact = await compileWasmArtifactFromKVM(kvmArtifact);
241
+ const module = await WebAssembly.compile(artifact);
242
+ const metadata = metadataFromModule(module);
243
+ const instance = await WebAssembly.instantiate(module);
244
+ const wasmExports = instance.exports;
245
+ const tags = createTagRegistry(metadata.tags);
246
+ const inputPatternGraph = propertyListToPattern(metadata.inputPattern);
247
+ const outputPatternGraph = propertyListToPattern(metadata.outputPattern);
248
+ const { value } = decodeWire(inputWire);
249
+ const arenaInputValues = new Map();
250
+ const inputPtr = writeWasmValueToArena(
251
+ wasmExports,
252
+ value,
253
+ inputPatternGraph,
254
+ 0,
255
+ arenaInputValues,
256
+ tags,
257
+ metadata.inputPattern
258
+ );
259
+
260
+ return {
261
+ run() {
262
+ const mark = typeof wasmExports.arena_mark === "function" ? wasmExports.arena_mark() : null;
263
+ const result = wasmExports[metadata.entry](inputPtr);
264
+ if (result[1] !== 1) {
265
+ throw new Error("Wasm relation execution failed (returned false)");
266
+ }
267
+ const output = readWasmArenaValue(
268
+ wasmExports,
269
+ wasmPtr(result[0]),
270
+ outputPatternGraph,
271
+ 0,
272
+ metadata.outputPattern,
273
+ new Map(arenaInputValues),
274
+ tags
275
+ );
276
+ if (mark != null && typeof wasmExports.arena_reset === "function") {
277
+ wasmExports.arena_reset(mark);
278
+ }
279
+ return output;
280
+ },
281
+ cleanup() {}
282
+ };
283
+ }
284
+
285
+ function llvmBackendFingerprint() {
286
+ if (cachedLLVMBackendFingerprint != null) return cachedLLVMBackendFingerprint;
287
+ const hash = sha256();
288
+ for (const relPath of ["src/llvm.mjs", "src/executable.mjs", "runtime/krt.c", "runtime/krt.h"]) {
289
+ hash.update(relPath);
290
+ hash.update("\0");
291
+ hash.update(fs.readFileSync(path.join(llvmRoot, relPath)));
292
+ hash.update("\0");
293
+ }
294
+ cachedLLVMBackendFingerprint = hash.digest("hex").slice(0, 16);
295
+ return cachedLLVMBackendFingerprint;
296
+ }
297
+
298
+ function llvmRuntimeMode(suite) {
299
+ if (suite === "ieee") {
300
+ return process.env.K_LLVM_IEEE_RUNTIME_MODE || process.env.K_LLVM_RUNTIME_MODE || "compact";
301
+ }
302
+ return process.env.K_LLVM_RUNTIME_MODE || "fast";
303
+ }
304
+
305
+ function llvmClangOpt(suite) {
306
+ if (suite === "ieee") {
307
+ return process.env.K_LLVM_IEEE_CLANG_OPT || process.env.K_LLVM_CLANG_OPT || "-O0";
308
+ }
309
+ return process.env.K_LLVM_CLANG_OPT || "-O3";
310
+ }
311
+
312
+ function llvmCacheDir() {
313
+ const dir = process.env.K_LLVM_CACHE_DIR || path.join(os.tmpdir(), "k-parent-benchmark-llvm-cache");
314
+ fs.mkdirSync(dir, { recursive: true });
315
+ return dir;
316
+ }
317
+
318
+ function compileLLVMExecutable({ kvmArtifact, testCase }) {
319
+ const runtimeMode = llvmRuntimeMode(testCase.suite);
320
+ const clangOpt = llvmClangOpt(testCase.suite);
321
+ const artifactBytes = Buffer.from(JSON.stringify(kvmArtifact));
322
+ const hash = sha256();
323
+ hash.update("k-parent-benchmark-llvm-jit-v3\0");
324
+ hash.update(llvmBackendFingerprint());
325
+ hash.update("\0");
326
+ hash.update(runtimeMode);
327
+ hash.update("\0");
328
+ hash.update(clangOpt);
329
+ hash.update("\0");
330
+ hash.update(artifactBytes);
331
+ const exePath = path.join(llvmCacheDir(), `${hash.digest("hex")}.exe`);
332
+ if (!fs.existsSync(exePath)) {
333
+ const tmpPath = path.join(llvmCacheDir(), `${path.basename(exePath)}.${process.pid}.tmp`);
334
+ const llvm = emitLLVMModule(kvmArtifact.kir, {
335
+ relation: kvmArtifact.relation,
336
+ runtimeMode
337
+ });
338
+ compileLLVMToExecutable(llvm, tmpPath, {
339
+ driver: stdioDriverSource({
340
+ inputPattern: canonicalPattern(kvmArtifact.inputPattern),
341
+ outputPattern: canonicalPattern(kvmArtifact.outputPattern)
342
+ }),
343
+ clangOpt
344
+ });
345
+ fs.renameSync(tmpPath, exePath);
346
+ }
347
+ return exePath;
348
+ }
349
+
350
+ class PersistentExecutable {
351
+ constructor(exePath) {
352
+ this.exePath = exePath;
353
+ this.child = spawn(exePath, ["--server"], { stdio: ["pipe", "pipe", "pipe"] });
354
+ this.buffer = Buffer.alloc(0);
355
+ this.pending = [];
356
+ this.stderr = [];
357
+ this.closed = false;
358
+
359
+ this.child.stdout.on("data", (chunk) => {
360
+ this.buffer = Buffer.concat([this.buffer, chunk]);
361
+ this.drainOutput();
362
+ });
363
+ this.child.stderr.on("data", chunk => this.stderr.push(chunk));
364
+ this.child.on("error", error => this.fail(error));
365
+ this.child.on("close", (status, signal) => {
366
+ this.closed = true;
367
+ if (status !== 0) {
368
+ const detail = signal == null ? `status ${status}` : `status ${status}, signal ${signal}`;
369
+ this.fail(new Error(`${this.exePath} --server failed with ${detail}\n${Buffer.concat(this.stderr).toString("utf8")}`.trim()));
370
+ } else if (this.pending.length > 0) {
371
+ this.fail(new Error(`${this.exePath} --server closed with ${this.pending.length} pending request(s)`));
372
+ }
373
+ });
374
+ }
375
+
376
+ fail(error) {
377
+ while (this.pending.length > 0) this.pending.shift().reject(error);
378
+ }
379
+
380
+ drainOutput() {
381
+ while (this.pending.length > 0 && this.buffer.length >= 4) {
382
+ const length = this.buffer.readUInt32BE(0);
383
+ if (this.buffer.length < 4 + length) return;
384
+ const payload = this.buffer.subarray(4, 4 + length);
385
+ this.buffer = this.buffer.subarray(4 + length);
386
+ this.pending.shift().resolve(Buffer.from(payload));
387
+ }
388
+ }
389
+
390
+ request(inputWire) {
391
+ if (this.closed) {
392
+ return Promise.reject(new Error(`${this.exePath} --server is closed`));
393
+ }
394
+ const header = Buffer.alloc(4);
395
+ header.writeUInt32BE(inputWire.length);
396
+ const frame = Buffer.concat([header, inputWire]);
397
+ return new Promise((resolve, reject) => {
398
+ this.pending.push({ resolve, reject });
399
+ this.child.stdin.write(frame, error => {
400
+ if (error) {
401
+ const index = this.pending.findIndex(entry => entry.resolve === resolve);
402
+ if (index !== -1) this.pending.splice(index, 1);
403
+ reject(error);
404
+ }
405
+ });
406
+ });
407
+ }
408
+
409
+ close() {
410
+ this.child.stdin.end();
411
+ }
412
+ }
413
+
414
+ async function prepareLLVMJITRunner(testCase, relDef, inputVal, state) {
415
+ const { inputPattern } = wireInput(inputVal, relDef);
416
+ const kvmArtifact = backendKVMArtifact(testCase, inputPattern);
417
+ const inputWire = encodeToWire(inputVal, canonicalPattern(kvmArtifact.inputPattern));
418
+ const exePath = compileLLVMExecutable({ kvmArtifact, testCase });
419
+ const runner = new PersistentExecutable(exePath);
420
+
421
+ return {
422
+ async run() {
423
+ const outputWire = await runner.request(inputWire);
424
+ return decodeWire(outputWire).value;
425
+ },
426
+ cleanup() {
427
+ runner.close();
428
+ }
429
+ };
430
+ }
431
+
432
+ function prepareKIRPExportRunner(testCase, relDef, inputVal) {
433
+ const { inputPattern } = wireInput(inputVal, relDef);
434
+ const object = backendObjectForCase(testCase);
435
+ return {
436
+ run() {
437
+ return backendKIRP(testCase, inputPattern, object);
438
+ },
439
+ cleanup() {}
440
+ };
441
+ }
442
+
443
+ async function prepareBackendRunner(job, relation, inputVal, state) {
444
+ if (job.mode.kind === "kir-p") {
445
+ return prepareKIRPExportRunner(job.case, relation.relDef, inputVal);
446
+ }
447
+ if (job.mode.kind === "wasm") {
448
+ return prepareWasmRunner(job.case, relation, inputVal, state);
449
+ }
450
+ if (job.mode.kind === "llvm-jit") {
451
+ return prepareLLVMJITRunner(job.case, relation.relDef, inputVal, state);
452
+ }
453
+ return null;
454
+ }
455
+
109
456
  async function prepareState(suite) {
110
457
  const state = createState();
111
- const source = suite === "ieee" ? "Examples/ieee.k" : "Examples/arithmetics.k";
112
- await evaluateInput(`:load ${path.join(root, source)}`, state);
458
+ await evaluateInput(`:load ${suiteSourcePath(suite)}`, state);
113
459
  return state;
114
460
  }
115
461
 
@@ -149,13 +495,14 @@ function prepareRelation(testCase, state) {
149
495
  const relDef = state.rels[hash];
150
496
  if (!relDef) throw new Error(`Relation hash '${hash}' not found`);
151
497
  return {
498
+ relHash: hash,
152
499
  relDef,
153
500
  kvmFunc: lowerToKVM(relDef, testCase.rel)
154
501
  };
155
502
  }
156
503
 
157
- function runOnce(job, prepared) {
158
- const { relDef, kvmFunc, inputVal, state } = prepared;
504
+ async function runOnce(job, prepared) {
505
+ const { relDef, kvmFunc, inputVal, state, backend } = prepared;
159
506
  run.defs = state;
160
507
  run_converged.defs = state;
161
508
 
@@ -175,6 +522,10 @@ function runOnce(job, prepared) {
175
522
  });
176
523
  }
177
524
 
525
+ if (job.mode.kind === "kir-p" || job.mode.kind === "wasm" || job.mode.kind === "llvm-jit") {
526
+ return backend.run();
527
+ }
528
+
178
529
  throw new Error(`Unknown benchmark mode '${job.mode.name}'`);
179
530
  }
180
531
 
@@ -184,25 +535,41 @@ async function workerMain() {
184
535
  const state = await prepareState(job.case.suite);
185
536
  const relation = prepareRelation(job.case, state);
186
537
  const inputVal = job.case.suite === "ieee" ? ieeeInput(job.case, state) : arithmeticInput(job.case);
187
- const prepared = { ...relation, inputVal, state };
538
+ const backend = await prepareBackendRunner(job, relation, inputVal, state);
539
+ const prepared = { ...relation, inputVal, state, backend };
188
540
  const setupMs = performance.now() - setupStartedAt;
189
541
 
190
542
  const samples = [];
191
543
  let lastResult;
192
- const runStartedHeap = process.memoryUsage().heapUsed;
193
- for (let i = 0; i < job.samples; i++) {
194
- const startedAt = performance.now();
195
- lastResult = runOnce(job, prepared);
196
- samples.push(performance.now() - startedAt);
197
- if (lastResult === undefined) {
198
- throw new Error("benchmark operation returned undefined");
544
+ let warmupMs = 0;
545
+ let runStartedHeap = process.memoryUsage().heapUsed;
546
+ try {
547
+ const warmupStartedAt = performance.now();
548
+ const warmupResult = await runOnce(job, prepared);
549
+ warmupMs = performance.now() - warmupStartedAt;
550
+ if (warmupResult === undefined) {
551
+ throw new Error("benchmark warmup operation returned undefined");
552
+ }
553
+ runStartedHeap = process.memoryUsage().heapUsed;
554
+
555
+ for (let i = 0; i < job.samples; i++) {
556
+ const startedAt = performance.now();
557
+ lastResult = await runOnce(job, prepared);
558
+ samples.push(performance.now() - startedAt);
559
+ if (lastResult === undefined) {
560
+ throw new Error("benchmark operation returned undefined");
561
+ }
199
562
  }
563
+ } finally {
564
+ backend?.cleanup();
200
565
  }
201
566
 
202
- const resultInfo = digestValue(lastResult);
567
+ const resultInfo = job.mode.kind === "kir-p"
568
+ ? digestValue(lastResult)
569
+ : digestResult(lastResult, relation.relDef);
203
570
  const memory = process.memoryUsage();
204
571
  const usage = process.resourceUsage();
205
- process.stdout.write(JSON.stringify({
572
+ const payload = {
206
573
  status: "ok",
207
574
  mode: job.mode.name,
208
575
  case: job.case.name,
@@ -211,22 +578,34 @@ async function workerMain() {
211
578
  minMs: Math.min(...samples),
212
579
  maxMs: Math.max(...samples),
213
580
  setupMs,
581
+ warmupMs,
214
582
  maxRssMiB: maxRssMiB(usage.maxRSS),
215
583
  heapUsedMiB: memory.heapUsed / 1024 / 1024,
216
584
  heapDeltaMiB: (memory.heapUsed - runStartedHeap) / 1024 / 1024,
217
585
  resultDigest: resultInfo.digest,
218
586
  resultBytes: resultInfo.bytes
219
- }) + "\n");
587
+ };
588
+ const output = JSON.stringify(payload) + "\n";
589
+ if (process.env.K_BENCHMARK_RESULT_PATH) {
590
+ fs.writeFileSync(process.env.K_BENCHMARK_RESULT_PATH, output);
591
+ } else {
592
+ await new Promise((resolve, reject) => {
593
+ process.stdout.write(output, (error) => error ? reject(error) : resolve());
594
+ });
595
+ }
220
596
  }
221
597
 
222
598
  function spawnJob(job, timeoutMs) {
223
599
  return new Promise((resolve) => {
600
+ const resultDir = fs.mkdtempSync(path.join(os.tmpdir(), "k-benchmark-job-"));
601
+ const resultPath = path.join(resultDir, "result.json");
224
602
  const child = spawn(process.execPath, [scriptPath, "--worker"], {
225
603
  cwd: root,
226
604
  env: {
227
605
  ...process.env,
228
606
  K_BENCHMARK_WORKER: "1",
229
- K_BENCHMARK_JOB: JSON.stringify(job)
607
+ K_BENCHMARK_JOB: JSON.stringify(job),
608
+ K_BENCHMARK_RESULT_PATH: resultPath
230
609
  },
231
610
  stdio: ["ignore", "pipe", "pipe"]
232
611
  });
@@ -246,6 +625,10 @@ function spawnJob(job, timeoutMs) {
246
625
  child.stderr.on("data", chunk => { stderr += chunk.toString("utf8"); });
247
626
  child.on("close", (code, signal) => {
248
627
  clearTimeout(timer);
628
+ const resultText = fs.existsSync(resultPath)
629
+ ? fs.readFileSync(resultPath, "utf8")
630
+ : stdout;
631
+ fs.rmSync(resultDir, { recursive: true, force: true });
249
632
  if (timedOut) {
250
633
  resolve({ status: "timeout", mode: job.mode.name, case: job.case.name, timeoutMs });
251
634
  return;
@@ -262,13 +645,13 @@ function spawnJob(job, timeoutMs) {
262
645
  return;
263
646
  }
264
647
  try {
265
- resolve(JSON.parse(stdout.trim().split("\n").at(-1)));
648
+ resolve(JSON.parse(resultText.trim().split("\n").at(-1)));
266
649
  } catch (error) {
267
650
  resolve({
268
651
  status: "failed",
269
652
  mode: job.mode.name,
270
653
  case: job.case.name,
271
- stderr: `Could not parse worker output: ${error.message}\n${stdout}\n${stderr}`
654
+ stderr: `Could not parse worker output: ${error.message}\n${resultText}\n${stderr}`
272
655
  });
273
656
  }
274
657
  });
@@ -286,7 +669,7 @@ async function runPool(jobs, concurrency, timeoutMs) {
286
669
  results.push(result);
287
670
  const label = `${result.mode}/${result.case}`;
288
671
  if (result.status === "ok") {
289
- console.log(`[ok] ${label} ${formatMs(result.avgMs)} ms avg, ${formatMiB(result.maxRssMiB)} MiB max RSS`);
672
+ console.log(`[ok] ${label} ${formatMs(result.avgMs)} ms avg, ${formatMs(result.warmupMs)} ms warmup, ${formatMiB(result.maxRssMiB)} MiB max RSS`);
290
673
  } else {
291
674
  console.log(`[${result.status}] ${label}`);
292
675
  }
@@ -322,7 +705,7 @@ function printSummary(document) {
322
705
  console.log(`parallel jobs: ${document.parallelJobs}`);
323
706
  console.log(`results file: ${path.relative(root, resultsPath)}`);
324
707
  console.log("-------------------------------------------------------------");
325
- console.log(`${pad("case", 20)} ${pad("mode", 24)} ${pad("status", 8)} ${pad("avg ms", 10)} ${pad("min", 9)} ${pad("max", 9)} ${pad("rss MiB", 9)} ${pad("heap MiB", 9)} digest`);
708
+ console.log(`${pad("case", 20)} ${pad("mode", 24)} ${pad("status", 8)} ${pad("avg ms", 10)} ${pad("min", 9)} ${pad("max", 9)} ${pad("warmup", 9)} ${pad("rss MiB", 9)} ${pad("heap MiB", 9)} digest`);
326
709
  for (const result of document.results) {
327
710
  console.log([
328
711
  pad(result.case, 20),
@@ -331,6 +714,7 @@ function printSummary(document) {
331
714
  pad(formatMs(result.avgMs), 10),
332
715
  pad(formatMs(result.minMs), 9),
333
716
  pad(formatMs(result.maxMs), 9),
717
+ pad(formatMs(result.warmupMs), 9),
334
718
  pad(formatMiB(result.maxRssMiB), 9),
335
719
  pad(formatMiB(result.heapUsedMiB), 9),
336
720
  result.resultDigest || ""
@@ -359,7 +743,7 @@ async function main() {
359
743
  const selectedBenchmarkModes = selectedModes();
360
744
  const samples = process.env.BENCHMARK_SAMPLES ? Number(process.env.BENCHMARK_SAMPLES) : 3;
361
745
  const timeoutMs = process.env.BENCHMARK_TIMEOUT_MS ? Number(process.env.BENCHMARK_TIMEOUT_MS) : 2 * 60 * 1000;
362
- const requestedJobs = process.env.BENCHMARK_JOBS ? Number(process.env.BENCHMARK_JOBS) : os.availableParallelism();
746
+ const requestedJobs = process.env.BENCHMARK_JOBS ? Number(process.env.BENCHMARK_JOBS) : 1;
363
747
  const concurrency = Math.max(1, Math.min(requestedJobs, os.availableParallelism(), selected.length * selectedBenchmarkModes.length));
364
748
 
365
749
  if (!Number.isInteger(samples) || samples <= 0) throw new Error("BENCHMARK_SAMPLES must be a positive integer");