@fraczak/k 7.0.1 → 7.1.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 (89) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/DOCS/BIG_PICTURE.md +5 -5
  3. package/DOCS/FILE_FORMATS.md +241 -18
  4. package/DOCS/OBJECT_FILE_AND_PATTERN.md +9 -9
  5. package/DOCS/PATTERNS.md +3 -3
  6. package/DOCS/POLYMORPHIC_IR.md +104 -0
  7. package/DOCS/PROFILING_AND_TRACING.md +60 -72
  8. package/DOCS/REPL.md +3 -3
  9. package/DOCS/book/03-types-as-finite-automata.md +4 -4
  10. package/DOCS/book/06-values-in-memory.md +9 -9
  11. package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
  12. package/DOCS/book/09-compiler-architecture.md +2 -2
  13. package/DOCS/book/10-from-ast-to-intermediate-representation.md +2 -2
  14. package/DOCS/book/14-canonical-serialization.md +1 -1
  15. package/Examples/arithmetics.k +2 -2
  16. package/Examples/bnat.k +1 -1
  17. package/Examples/karatsuba-mult.k +2 -2
  18. package/JOURNAL.md +288 -0
  19. package/README.md +13 -10
  20. package/access-table.mjs +343 -0
  21. package/backend-api.mjs +18 -0
  22. package/backends/llvm/README.md +20 -21
  23. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  24. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  25. package/backends/llvm/bin/k-llvm.mjs +292 -0
  26. package/backends/llvm/runtime/krt.c +41 -0
  27. package/backends/llvm/runtime/krt.h +2 -0
  28. package/backends/llvm/src/cli.mjs +95 -17
  29. package/backends/llvm/src/executable.mjs +132 -15
  30. package/backends/llvm/src/kvm2llvm.mjs +50 -23
  31. package/backends/llvm/src/llvm.mjs +133 -20
  32. package/backends/llvm/tests/integration.sh +11 -11
  33. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  34. package/backends/llvm/tests/perf-support.mjs +25 -58
  35. package/backends/llvm/tests/test-llvm.mjs +25 -4
  36. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  37. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  38. package/backends/wasm/bin/k-wasm.mjs +47 -13
  39. package/backends/wasm/src/cli.mjs +141 -19
  40. package/backends/wasm/src/wasm.mjs +132 -24
  41. package/backends/wasm/tests/test-wasm.mjs +5 -5
  42. package/browser/repl-browser.mjs +16 -5
  43. package/browser/vfs-data.mjs +5 -5
  44. package/codecs/k-parse.mjs +29 -8
  45. package/codecs/k-print.mjs +22 -6
  46. package/codecs/runtime/codec-sdk.mjs +8 -4
  47. package/codecs/show.mjs +20 -6
  48. package/core.k +8 -8
  49. package/harness/format.mjs +2 -2
  50. package/harness/registry.mjs +0 -2
  51. package/k.mjs +56 -16
  52. package/kir.mjs +200 -75
  53. package/kvm.mjs +49 -12
  54. package/objects/compile.mjs +37 -23
  55. package/objects/decompile.mjs +40 -10
  56. package/objects/extract-aliases.mjs +44 -14
  57. package/objects/inspect.mjs +19 -14
  58. package/objects/validate.mjs +18 -11
  59. package/package.json +10 -7
  60. package/pkir.mjs +772 -0
  61. package/repl.mjs +36 -18
  62. package/scripts/benchmark-idempotency.mjs +12 -8
  63. package/scripts/benchmark.mjs +5 -9
  64. package/scripts/build-repl-html.mjs +16 -4
  65. package/scripts/perf-ieee.mjs +253 -103
  66. package/scripts/perf-int.mjs +585 -297
  67. package/scripts/perf-multiplication.mjs +66 -25
  68. package/scripts/perf-poly.mjs +292 -164
  69. package/scripts/run-tests.mjs +3 -0
  70. package/tests/code-derivation/pattern-envelope.mjs +4 -4
  71. package/tests/test-access-table.mjs +213 -0
  72. package/tests/test-kvm-polymorphic.mjs +2 -2
  73. package/tests/test-pkir.mjs +530 -0
  74. package/tests/test-polymorphic-tdd.mjs +196 -0
  75. package/tests/test-repl-html.mjs +15 -2
  76. package/tests/test-repl.mjs +30 -1
  77. package/backends/arm64/README.md +0 -103
  78. package/backends/arm64/bin/k-arm64-compile.mjs +0 -93
  79. package/backends/arm64/bin/k-arm64-run.mjs +0 -58
  80. package/backends/arm64/bin/k-arm64.mjs +0 -88
  81. package/backends/arm64/scripts/run-tests.mjs +0 -57
  82. package/backends/arm64/src/arm64.mjs +0 -229
  83. package/backends/arm64/src/cli.mjs +0 -155
  84. package/backends/arm64/src/kvm2arm64.mjs +0 -906
  85. package/backends/arm64/src/runtime.c +0 -744
  86. package/backends/arm64/tests/integration.sh +0 -112
  87. package/backends/arm64/tests/test-arm64.mjs +0 -240
  88. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
  89. package/harness/adapters/arm64.mjs +0 -105
@@ -23,8 +23,6 @@ import {
23
23
  csvEnv,
24
24
  formatTiming,
25
25
  createLLVMRunner,
26
- createARM64Runner,
27
- arm64LaneName,
28
26
  llvmLaneName,
29
27
  makeCacheDir,
30
28
  parseNonNegativeIntEnv,
@@ -53,7 +51,6 @@ import {
53
51
  writeValueToArena
54
52
  } from "../backends/wasm/tests/perf-support.mjs";
55
53
 
56
- import { compileARM64ArtifactFromObject } from "../backends/arm64/src/arm64.mjs";
57
54
  import { compileWasmArtifactFromObject } from "../backends/wasm/src/wasm.mjs";
58
55
 
59
56
  function printHelp() {
@@ -61,37 +58,35 @@ function printHelp() {
61
58
 
62
59
  Big Integer Arithmetic Performance Benchmark Harness.
63
60
  Benchmarks plus, minus, times, and squaring scale on arithmetics.k
64
- across Native JS, kVM interpreter, LLVM, Wasm, and ARM64 backends.
61
+ across Native JS, kVM interpreter, LLVM, Wasm, and PKIR backends.
65
62
 
66
63
  Options:
67
64
  --wasm-in-process
68
65
  Run WebAssembly in-process instead of persistent subprocess runner.
66
+ --squaring-llvm-only
67
+ Run squaring benchmark on LLVM only.
68
+ --pkir-only
69
+ Run only PKIR backend.
69
70
  -h, --help
70
71
  Show this help message and exit.
71
72
 
72
73
  Environment Variables:
73
74
  BACKENDS
74
- Target backend to run: 'llvm', 'wasm', or 'arm64'.
75
+ Target backend to run: 'llvm', 'wasm', or 'pkir'.
75
76
  LLVM_ONLY
76
77
  Run only LLVM backend ('1' to enable).
77
78
  WASM_ONLY
78
79
  Run only WebAssembly backend ('1' to enable).
79
- ARM64_ONLY
80
- Run only ARM64 backend ('1' to enable).
80
+ PKIR_ONLY
81
+ Run only PKIR backend ('1' to enable).
81
82
  BACKENDS_ONLY
82
83
  Skip JS and kVM interpreter baselines ('1' to enable).
84
+ SQUARING_LLVM_ONLY
85
+ Run squaring benchmark on LLVM only ('1' to enable).
83
86
  INPUTS
84
87
  Comma-separated integer strings to benchmark.
85
88
  ITERATIONS
86
89
  Measured iterations per benchmark (default: 3).
87
- ARM64_WARMUP_ITERATIONS
88
- Warmup iterations for ARM64 backend (default: 1).
89
- ARM64_OPT
90
- ARM64 backend optimization flags (default: -O2).
91
- ARM64_SPAWN_PER_CALL
92
- Spawn process per ARM64 call instead of persistent ('1' or '0').
93
- ARM64_PIPELINE
94
- Use persistent pipelined runner for ARM64 ('1' or '0').
95
90
  LLVM_WARMUP_ITERATIONS
96
91
  Warmup iterations for LLVM backend (default: 1).
97
92
  LLVM_SPAWN_PER_CALL
@@ -125,15 +120,57 @@ if (process.argv.includes("-h") || process.argv.includes("--help")) {
125
120
 
126
121
  // Backend selection
127
122
  const backendsEnv = process.env.BACKENDS?.toLowerCase();
128
- const llvmOnly = process.env.LLVM_ONLY === "1" || backendsEnv === "llvm";
129
- const wasmOnly = process.env.WASM_ONLY === "1" || backendsEnv === "wasm";
130
- const arm64Only = process.env.ARM64_ONLY === "1" || backendsEnv === "arm64";
131
-
132
- const isArm64Host = process.arch === "arm64" && process.platform === "linux";
133
- const runARM64 = !wasmOnly && !llvmOnly && (isArm64Host || arm64Only);
134
- const runLLVM = !wasmOnly && !arm64Only;
135
- const runWasm = !llvmOnly && !arm64Only;
136
- const runBaselines = !process.env.BACKENDS_ONLY && (process.env.LLVM_ONLY !== "1") && (process.env.WASM_ONLY !== "1") && (process.env.ARM64_ONLY !== "1");
123
+ const backendsList = backendsEnv
124
+ ? backendsEnv.split(",").map(s => s.trim())
125
+ : null;
126
+ const llvmPkirOnly = process.env.LLVM_PKIR_ONLY === "1"
127
+ || (backendsList && backendsList.length === 1 &&
128
+ backendsList[0] === "llvm-pkir")
129
+ || process.argv.includes("--llvm-pkir-only");
130
+ const pkirOnly = !llvmPkirOnly && (
131
+ process.env.PKIR_ONLY === "1"
132
+ || (backendsList && backendsList.length === 1 &&
133
+ backendsList[0] === "pkir")
134
+ || process.argv.includes("--pkir-only")
135
+ );
136
+ const llvmOnly = !pkirOnly && !llvmPkirOnly && (
137
+ process.env.LLVM_ONLY === "1"
138
+ || (backendsList && backendsList.length === 1 &&
139
+ backendsList[0] === "llvm")
140
+ || process.argv.includes("--llvm-only")
141
+ );
142
+ const wasmOnly = !pkirOnly && !llvmPkirOnly && !llvmOnly && (
143
+ process.env.WASM_ONLY === "1"
144
+ || (backendsList && backendsList.length === 1 &&
145
+ backendsList[0] === "wasm")
146
+ || process.argv.includes("--wasm-only")
147
+ );
148
+ const runLLVM = backendsList
149
+ ? backendsList.includes("llvm")
150
+ : !wasmOnly && !pkirOnly && !llvmPkirOnly;
151
+ const runLLVMPKIR = backendsList
152
+ ? backendsList.includes("llvm-pkir")
153
+ : !wasmOnly && !pkirOnly && !llvmOnly;
154
+ const runWasm = backendsList
155
+ ? backendsList.includes("wasm")
156
+ : !llvmOnly && !pkirOnly && !llvmPkirOnly;
157
+ const runPKIR = backendsList
158
+ ? backendsList.includes("pkir")
159
+ : !llvmOnly && !wasmOnly && !llvmPkirOnly;
160
+ const runBaselines = !process.env.BACKENDS_ONLY
161
+ && (backendsList == null || backendsList.includes("kvm") ||
162
+ backendsList.includes("js"))
163
+ && !llvmOnly && !wasmOnly && !pkirOnly && !llvmPkirOnly;
164
+ const squaringLlvmOnly = llvmOnly || llvmPkirOnly
165
+ || process.env.SQUARING_LLVM_ONLY === "1"
166
+ || process.argv.includes("--squaring-llvm-only");
167
+
168
+ function llvmLaneNameStr() {
169
+ return llvmLaneName();
170
+ }
171
+ function llvmPkirLaneNameStr() {
172
+ return llvmLaneName().replace("LLVM", "LLVM-PKIR");
173
+ }
137
174
 
138
175
  const ops = ["plus", "minus", "times"];
139
176
  const benchNames = Object.fromEntries(ops.map(op => [op, `bench_${op}`]));
@@ -143,9 +180,6 @@ const inputTexts = csvEnv(
143
180
  );
144
181
  const iterations = parsePositiveIntEnv("ITERATIONS", 3);
145
182
 
146
- // ARM64 options
147
- const arm64WarmupIterations = parseNonNegativeIntEnv("ARM64_WARMUP_ITERATIONS", 1);
148
- const arm64OptLevel = process.env.ARM64_OPT || "-O2";
149
183
  if (!process.env.K_ARENA_CAPACITY_MB && !process.env.K_ARENA_MB) {
150
184
  process.env.K_ARENA_CAPACITY_MB = "4096";
151
185
  }
@@ -164,6 +198,9 @@ function wasmLaneName() {
164
198
  const mode = wasmPipe ? "persistent" : "in-process";
165
199
  return `WebAssembly (${mode})`;
166
200
  }
201
+ function pkirLaneName() {
202
+ return "PKIR (Wasm persistent)";
203
+ }
167
204
 
168
205
  console.log("==> Initializing state and loading @fraczak/k/Examples/arithmetics.k");
169
206
  const state = createState();
@@ -175,7 +212,7 @@ for (const op of ops) {
175
212
  }
176
213
 
177
214
  function intValue(text) {
178
- return valueForCode(parseIntValue(text), state.typeAliases.int, codes.find);
215
+ return parseIntValue(text);
179
216
  }
180
217
 
181
218
  console.log("==> Preparing relations");
@@ -227,13 +264,37 @@ if (runLLVM) {
227
264
  relHash: relation.relHash,
228
265
  inputPattern: tc.inputPattern,
229
266
  cacheDir,
230
- sourceLabel: `@fraczak/k/Examples/arithmetics.k#${relation.relationName}`
267
+ sourceLabel:
268
+ `@fraczak/k/Examples/arithmetics.k#${relation.relationName}`,
269
+ usePKIR: false
231
270
  });
232
271
  }
233
272
  codes.load(state.codes);
234
273
  printCompileFailures(testSuite);
235
274
  }
236
275
 
276
+ // Setup LLVM-PKIR
277
+ if (runLLVMPKIR) {
278
+ console.log("==> Compiling LLVM-PKIR executables");
279
+ for (const tc of testSuite) {
280
+ const relation = relations[tc.op];
281
+ tc.llvmPKIR = tryCompileCase({
282
+ object: relation.object,
283
+ relationName: relation.relationName,
284
+ relHash: relation.relHash,
285
+ inputPattern: tc.inputPattern,
286
+ cacheDir,
287
+ sourceLabel:
288
+ `@fraczak/k/Examples/arithmetics.k#${relation.relationName}`,
289
+ usePKIR: true
290
+ });
291
+ }
292
+ codes.load(state.codes);
293
+ printCompileFailures(
294
+ testSuite.map(tc => ({ ...tc, llvm: tc.llvmPKIR }))
295
+ );
296
+ }
297
+
237
298
  // Setup Wasm
238
299
  let wasmExports = null;
239
300
  const wasmExes = new Map();
@@ -287,6 +348,35 @@ if (runWasm) {
287
348
  }
288
349
  }
289
350
 
351
+ // Setup PKIR (persistent runner)
352
+ const pkirExes = new Map();
353
+ if (runPKIR) {
354
+ console.log("==> Compiling PKIR executables (persistent runner)...");
355
+ for (const op of ops) {
356
+ const relation = relations[op];
357
+ try {
358
+ const wasmPath = path.join(cacheDir, `perf_int_pkir_${op}.wasm`);
359
+ const exePath = path.join(cacheDir, `perf_int_pkir_${op}.exe`);
360
+ const wasmBuf = await compileWasmArtifactFromObject(relation.object, {
361
+ entry: "__main__",
362
+ usePKIR: true
363
+ });
364
+ fs.writeFileSync(wasmPath, wasmBuf);
365
+ const runBin = fileURLToPath(import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs"));
366
+ fs.writeFileSync(exePath, `#!/bin/sh\nexec node "${runBin}" "${wasmPath}" "$@"\n`, { mode: 0o755 });
367
+ pkirExes.set(op, { status: "ok", exePath, wasmPath });
368
+ } catch (error) {
369
+ pkirExes.set(op, {
370
+ status: "failed",
371
+ error: error.stack || error.message || String(error)
372
+ });
373
+ }
374
+ }
375
+ for (const tc of testSuite) {
376
+ tc.pkir = pkirExes.get(tc.op);
377
+ }
378
+ }
379
+
290
380
  function createWasmRunner(testSuite) {
291
381
  const wasmCases = testSuite.filter(tc => tc.wasm?.status === "ok");
292
382
  const servers = new Map();
@@ -315,37 +405,32 @@ function createWasmRunner(testSuite) {
315
405
  };
316
406
  }
317
407
 
318
- // Setup ARM64
319
- const arm64Exes = new Map();
320
- if (runARM64) {
321
- console.log(`==> Compiling Linux ARM64 executables (${arm64OptLevel})...`);
322
- for (const op of ops) {
323
- const relation = relations[op];
324
- try {
325
- const exePath = path.join(cacheDir, `perf_int_arm64_${op}`);
326
- compileARM64ArtifactFromObject(relation.object, {
327
- entry: "__main__",
328
- outputPath: exePath,
329
- optLevel: arm64OptLevel
330
- });
331
- arm64Exes.set(op, { status: "ok", exePath });
332
- } catch (error) {
333
- arm64Exes.set(op, {
334
- status: "failed",
335
- error: error.stack || error.message || String(error)
336
- });
408
+ function createPKIRRunner(testSuite) {
409
+ const pkirCases = testSuite.filter(tc => tc.pkir?.status === "ok");
410
+ const servers = new Map();
411
+ function serverFor(exePath) {
412
+ let server = servers.get(exePath);
413
+ if (server == null) {
414
+ server = new PersistentExecutable(exePath);
415
+ servers.set(exePath, server);
337
416
  }
417
+ return server;
338
418
  }
339
- for (const tc of testSuite) {
340
- tc.arm64 = arm64Exes.get(tc.op);
341
- }
342
- const failedOps = [...arm64Exes.entries()].filter(([_, v]) => v.status !== "ok");
343
- if (failedOps.length > 0) {
344
- console.log("Linux ARM64 compilation failures:");
345
- for (const [op, res] of failedOps) {
346
- console.log(` ${op}: ${res.error.split("\n")[0]}`);
419
+
420
+ return {
421
+ async run(iterations) {
422
+ if (pkirCases.length === 0) return null;
423
+ return runTimedIterationsAsync(iterations, async () => {
424
+ for (const tc of pkirCases) {
425
+ await serverFor(tc.pkir.exePath).request(tc.inputWire);
426
+ }
427
+ });
428
+ },
429
+ serverFor,
430
+ close() {
431
+ for (const server of servers.values()) server.close();
347
432
  }
348
- }
433
+ };
349
434
  }
350
435
 
351
436
  function printBenchmarkDescription() {
@@ -355,9 +440,10 @@ function printBenchmarkDescription() {
355
440
  lanes.push("Native JS (Envelope-Free)");
356
441
  lanes.push("kVM Interpreter (Env-Free)");
357
442
  }
358
- if (runLLVM) lanes.push(llvmLaneName());
443
+ if (runLLVM) lanes.push(llvmLaneNameStr());
444
+ if (runLLVMPKIR) lanes.push(llvmPkirLaneNameStr());
359
445
  if (runWasm) lanes.push(wasmLaneName());
360
- if (runARM64) lanes.push(arm64LaneName(arm64OptLevel));
446
+ if (runPKIR) lanes.push(pkirLaneName());
361
447
 
362
448
  console.log("==> Benchmark description");
363
449
  console.log(" source: @fraczak/k/Examples/arithmetics.k");
@@ -369,16 +455,17 @@ function printBenchmarkDescription() {
369
455
  console.log(` llvm-ready cases: ${testSuite.filter(tc => tc.llvm?.status === "ok").length}`);
370
456
  console.log(` llvm warmup iterations: ${llvmWarmupIterations}`);
371
457
  }
458
+ if (runLLVMPKIR) {
459
+ console.log(` llvm-pkir ready cases: ${testSuite.filter(tc => tc.llvmPKIR?.status === "ok").length}`);
460
+ console.log(` llvm-pkir warmup iterations: ${llvmWarmupIterations}`);
461
+ }
372
462
  if (runWasm) {
373
463
  console.log(` wasm runner mode: ${wasmPipe ? "persistent pipe" : "in-process"}`);
374
464
  console.log(` wasm warmup iterations: ${wasmWarmupIterations}`);
375
465
  if (!wasmPipe) console.log(` wasm arena reset: ${wasmReset ? "yes" : "no"}`);
376
466
  }
377
- if (runARM64) {
378
- console.log(` arm64-ready cases: ${testSuite.filter(tc => tc.arm64?.status === "ok").length}`);
379
- console.log(` arm64 warmup iterations: ${arm64WarmupIterations}`);
380
- console.log(` arm64 opt level: ${arm64OptLevel}`);
381
- console.log(` arm64 runner mode: ${process.env.ARM64_SPAWN_PER_CALL === "1" ? "spawn per call" : "persistent"}`);
467
+ if (runPKIR) {
468
+ console.log(` pkir warmup iterations: ${wasmWarmupIterations}`);
382
469
  }
383
470
  console.log(` iterations: ${iterations}`);
384
471
  console.log(` benchmark lanes: ${lanes.join("; ")}`);
@@ -414,6 +501,22 @@ if (runLLVM) {
414
501
  }
415
502
  }
416
503
 
504
+ // 2b. LLVM-PKIR lane
505
+ let llvmPkirResult = null;
506
+ if (runLLVMPKIR) {
507
+ const llvmPkirCases = testSuite.map(tc => ({ ...tc, llvm: tc.llvmPKIR }));
508
+ const llvmPkirRunner = createLLVMRunner(llvmPkirCases);
509
+ try {
510
+ if (llvmWarmupIterations > 0) {
511
+ console.log(`==> Warming LLVM-PKIR executables (${llvmWarmupIterations} iterations)...`);
512
+ await llvmPkirRunner.run(llvmWarmupIterations);
513
+ }
514
+ llvmPkirResult = await llvmPkirRunner.run(iterations);
515
+ } finally {
516
+ llvmPkirRunner.close();
517
+ }
518
+ }
519
+
417
520
  // 3. Wasm lane
418
521
  let wasmResult = null;
419
522
  if (runWasm) {
@@ -458,22 +561,21 @@ if (runWasm) {
458
561
  }
459
562
  }
460
563
 
461
- // 4. Linux ARM64 lane
462
- let arm64Result = null;
463
- if (runARM64) {
464
- const arm64Cases = testSuite.filter(tc => tc.arm64?.status === "ok");
465
- if (arm64Cases.length === testSuite.length) {
466
- const arm64Runner = createARM64Runner(testSuite);
564
+ // 4. PKIR lane
565
+ let pkirResult = null;
566
+ if (runPKIR) {
567
+ const pkirCases = testSuite.filter(tc => tc.pkir?.status === "ok");
568
+ if (pkirCases.length === testSuite.length) {
569
+ const pkirRunner = createPKIRRunner(testSuite);
467
570
  try {
468
- if (arm64WarmupIterations > 0) {
469
- console.log(`==> Warming Linux ARM64 (${arm64WarmupIterations} iterations)...`);
470
- await arm64Runner.run(arm64WarmupIterations);
571
+ if (wasmWarmupIterations > 0) {
572
+ console.log(`==> Warming PKIR (${wasmWarmupIterations} iterations)...`);
573
+ await pkirRunner.run(wasmWarmupIterations);
471
574
  }
472
-
473
- console.log(`==> Running Linux ARM64 (${iterations} iterations)...`);
474
- arm64Result = await arm64Runner.run(iterations);
575
+ console.log(`==> Running PKIR (${iterations} iterations)...`);
576
+ pkirResult = await pkirRunner.run(iterations);
475
577
  } finally {
476
- arm64Runner.close();
578
+ pkirRunner.close();
477
579
  }
478
580
  }
479
581
  }
@@ -489,14 +591,18 @@ if (runBaselines) {
489
591
  console.log(`${laneIndex++}. kVM Interpreter (Env-Free): ${formatTiming(kvmFreeResult)}`);
490
592
  }
491
593
  if (runLLVM) {
492
- console.log(`${laneIndex++}. ${llvmLaneName().padEnd(29)} ${formatTiming(llvmResult)}`);
594
+ const name = llvmLaneNameStr().padEnd(29);
595
+ console.log(`${laneIndex++}. ${name} ${formatTiming(llvmResult)}`);
596
+ }
597
+ if (runLLVMPKIR) {
598
+ const name = llvmPkirLaneNameStr().padEnd(29);
599
+ console.log(`${laneIndex++}. ${name} ${formatTiming(llvmPkirResult)}`);
493
600
  }
494
601
  if (runWasm) {
495
602
  console.log(`${laneIndex++}. ${wasmLaneName().padEnd(29)} ${formatTiming(wasmResult)}`);
496
603
  }
497
- if (runARM64) {
498
- const timingStr = arm64Result ? formatTiming(arm64Result) : "compile failed";
499
- console.log(`${laneIndex++}. ${arm64LaneName(arm64OptLevel).padEnd(29)} ${timingStr}`);
604
+ if (runPKIR) {
605
+ console.log(`${laneIndex++}. ${pkirLaneName().padEnd(29)} ${formatTiming(pkirResult)}`);
500
606
  }
501
607
  console.log("=================================================================\n");
502
608
 
@@ -525,6 +631,24 @@ for (const tc of testSuite) {
525
631
  }
526
632
  }
527
633
 
634
+ if (runLLVMPKIR) {
635
+ if (tc.llvmPKIR.status !== "ok") {
636
+ tc.llvmPkirConformance = "compile-failed";
637
+ } else {
638
+ try {
639
+ const outputWire = await runExecutable(tc.llvmPKIR.exePath, tc.inputWire);
640
+ const actual = decodeWire(outputWire).value;
641
+ assert.deepEqual(toPlainObject(actual), toPlainObject(tc.expected));
642
+ tc.llvmPkirConformance = "ok";
643
+ } catch (error) {
644
+ tc.llvmPkirConformance = "failed";
645
+ tc.llvmPKIR.error = error.stack || error.message || String(error);
646
+ console.log(`LLVM-PKIR conformance failure for ${tc.op}(${tc.label}):`);
647
+ console.log(tc.llvmPKIR.error.split("\n").slice(0, 8).join("\n"));
648
+ }
649
+ }
650
+ }
651
+
528
652
  if (runWasm) {
529
653
  if (wasmPipe) {
530
654
  if (tc.wasm?.status !== "ok") {
@@ -563,23 +687,24 @@ for (const tc of testSuite) {
563
687
  }
564
688
  }
565
689
 
566
- if (runARM64) {
567
- if (tc.arm64?.status !== "ok") {
568
- tc.arm64Conformance = "compile-failed";
690
+ if (runPKIR) {
691
+ if (tc.pkir?.status !== "ok") {
692
+ tc.pkirConformance = "compile-failed";
569
693
  } else {
570
694
  try {
571
- const outputWire = await runExecutable(tc.arm64.exePath, tc.inputWire);
695
+ const outputWire = await runExecutable(tc.pkir.exePath, tc.inputWire);
572
696
  const actual = decodeWire(outputWire).value;
573
697
  assert.deepEqual(toPlainObject(actual), toPlainObject(tc.expected));
574
- tc.arm64Conformance = "ok";
698
+ tc.pkirConformance = "ok";
575
699
  } catch (error) {
576
- tc.arm64Conformance = "failed";
577
- tc.arm64.error = error.stack || error.message || String(error);
578
- console.log(`Linux ARM64 conformance failure for ${tc.op}(${tc.label}):`);
579
- console.log(tc.arm64.error.split("\n").slice(0, 8).join("\n"));
700
+ tc.pkirConformance = "failed";
701
+ tc.pkir.error = error.stack || error.message || String(error);
702
+ console.log(`PKIR conformance failure for ${tc.op}(${tc.label}):`);
703
+ console.log(tc.pkir.error.split("\n").slice(0, 8).join("\n"));
580
704
  }
581
705
  }
582
706
  }
707
+
583
708
  }
584
709
 
585
710
  const validationSummary = [];
@@ -588,24 +713,29 @@ if (runLLVM) {
588
713
  const llvmOk = testSuite.filter(tc => tc.llvmConformance === "ok").length;
589
714
  validationSummary.push(`LLVM ${llvmOk}/${testSuite.length}`);
590
715
  }
716
+ if (runLLVMPKIR) {
717
+ const pkirOk = testSuite.filter(tc => tc.llvmPkirConformance === "ok").length;
718
+ validationSummary.push(`LLVM-PKIR ${pkirOk}/${testSuite.length}`);
719
+ }
591
720
  if (runWasm) {
592
721
  const wasmOk = testSuite.filter(tc => tc.wasmConformance === "ok").length;
593
722
  validationSummary.push(`Wasm ${wasmOk}/${testSuite.length}`);
594
723
  }
595
- if (runARM64) {
596
- const arm64Ok = testSuite.filter(tc => tc.arm64Conformance === "ok").length;
597
- validationSummary.push(`Linux ARM64 ${arm64Ok}/${testSuite.length}`);
724
+ if (runPKIR) {
725
+ const pkirOk = testSuite.filter(tc => tc.pkirConformance === "ok").length;
726
+ validationSummary.push(`PKIR ${pkirOk}/${testSuite.length}`);
598
727
  }
599
728
 
600
729
  console.log(`Conformance validation: ${validationSummary.join(", ")} cases match expected values.`);
601
730
  if (runLLVM && shouldStrictFail(testSuite)) process.exitCode = 1;
731
+ if (runLLVMPKIR && shouldStrictFail(testSuite.map(tc => ({ ...tc, llvm: tc.llvmPKIR })))) process.exitCode = 1;
602
732
  if (runWasm && testSuite.some(tc => tc.wasmConformance !== "ok")) process.exitCode = 1;
603
- if (runARM64 && testSuite.some(tc => tc.arm64Conformance !== "ok")) process.exitCode = 1;
733
+ if (runPKIR && testSuite.some(tc => tc.pkirConformance !== "ok")) process.exitCode = 1;
604
734
 
605
- // =========================================================================
735
+ // =======================================================================
606
736
  // Squaring Scale & Memory Profiling Benchmark
607
- // Expression: s = {()x,()y}times; 987654321 s s s s s s s s s s
608
- // =========================================================================
737
+ // Expressions: s = {()x,()y}times; and s = {()x,()y}karatsuba;
738
+ // =======================================================================
609
739
 
610
740
  function countBitsValue(val) {
611
741
  let count = 0;
@@ -644,189 +774,274 @@ const squaringInputText = process.env.SQUARING_INPUT || "987654321";
644
774
  const squaringStepTimeoutMs = parsePositiveIntEnv("SQUARING_STEP_TIMEOUT_MS", 60000);
645
775
 
646
776
  console.log("\n=================== SQUARING SCALE & MEMORY BENCHMARK ===================");
647
- console.log(`Expression: s = {()x,()y}times; ${squaringInputText} ${"s ".repeat(squaringSteps).trim()}`);
648
- console.log(`Evaluation: ${squaringSteps} successive squarings doubling bit length each step`);
777
+ console.log(`Algorithms: times vs karatsuba (${squaringSteps} successive squarings doubling bit length)`);
649
778
 
650
- await evaluateInput("s = {()x,()y}times;", state);
651
- codes.load(state.codes);
652
- const sRel = prepareRelation(state, "s", {
653
- source: `${arithmeticsLibrarySource}\ns = {()x,()y}times;\ns`,
654
- sourceLabel: "@fraczak/k/Examples/arithmetics.k#s"
655
- });
779
+ // Load and initialize Karatsuba multiplication relation
780
+ const karatsubaPath = fileURLToPath(import.meta.resolve("@fraczak/k/Examples/karatsuba-mult.k"));
781
+ const karatsubaSource = fs.readFileSync(karatsubaPath, "utf8");
782
+ const karatsubaLibrarySource = karatsubaSource.replace(/\s*\(\)\s*$/, "\n");
783
+ const stateKara = createState();
784
+ await evaluateInput(`:load ${karatsubaPath}`, stateKara);
785
+ codes.load(stateKara.codes);
656
786
 
657
787
  const sInitialVal = intValue(squaringInputText);
658
788
  const { inputWire: sInitWire, inputPattern: sInputPattern } = wireInput(sInitialVal);
659
789
 
790
+ const squaringAlgos = [
791
+ {
792
+ id: "times",
793
+ name: "times",
794
+ title: "Long Multiplication (times)",
795
+ expr: "s = {()x,()y}times;",
796
+ librarySource: arithmeticsLibrarySource,
797
+ sourceLabel: "@fraczak/k/Examples/arithmetics.k#s",
798
+ state: state,
799
+ relName: "s"
800
+ },
801
+ {
802
+ id: "karatsuba",
803
+ name: "karatsuba",
804
+ title: "Karatsuba Multiplication (karatsuba)",
805
+ expr: "s = {()x,()y}karatsuba;",
806
+ librarySource: karatsubaLibrarySource,
807
+ sourceLabel: "@fraczak/k/Examples/karatsuba-mult.k#s",
808
+ state: stateKara,
809
+ relName: "s"
810
+ }
811
+ ];
812
+
660
813
  // Build executables for persistent servers
661
- let sLlvmServer = null;
662
- if (runLLVM) {
663
- const sLlvmCompiled = tryCompileCase({
664
- object: sRel.object,
665
- relationName: sRel.relationName,
666
- relHash: sRel.relHash,
667
- inputPattern: sInputPattern,
668
- cacheDir,
669
- sourceLabel: "@fraczak/k/Examples/arithmetics.k#s"
814
+ for (const algo of squaringAlgos) {
815
+ await evaluateInput(algo.expr, algo.state);
816
+ codes.load(algo.state.codes);
817
+ algo.rel = prepareRelation(algo.state, algo.relName, {
818
+ source: `${algo.librarySource}\n${algo.expr}\n${algo.relName}`,
819
+ sourceLabel: algo.sourceLabel
670
820
  });
671
- if (sLlvmCompiled.status === "ok") {
672
- sLlvmServer = new PersistentExecutable(sLlvmCompiled.exePath);
673
- }
674
- }
675
821
 
676
- let sWasmServer = null;
677
- if (runWasm) {
678
- try {
679
- const sWasmPath = path.join(cacheDir, "perf_int_wasm_s.wasm");
680
- const sWasmExe = path.join(cacheDir, "perf_int_wasm_s.exe");
681
- const wasmBuf = await compileWasmArtifactFromObject(sRel.object, { entry: "__main__" });
682
- fs.writeFileSync(sWasmPath, wasmBuf);
683
- const runBin = fileURLToPath(import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs"));
684
- fs.writeFileSync(sWasmExe, `#!/bin/sh\nexec node "${runBin}" "${sWasmPath}" "$@"\n`, { mode: 0o755 });
685
- sWasmServer = new PersistentExecutable(sWasmExe);
686
- } catch (e) {
687
- console.log("Failed to build Wasm executable for 's':", e.message);
822
+ algo.llvmServer = null;
823
+ if (runLLVM) {
824
+ const sLlvmCompiled = tryCompileCase({
825
+ object: algo.rel.object,
826
+ relationName: algo.rel.relationName,
827
+ relHash: algo.rel.relHash,
828
+ inputPattern: sInputPattern,
829
+ cacheDir,
830
+ sourceLabel: algo.sourceLabel,
831
+ usePKIR: false
832
+ });
833
+ if (sLlvmCompiled.status === "ok") {
834
+ algo.llvmServer = new PersistentExecutable(sLlvmCompiled.exePath);
835
+ }
688
836
  }
689
- }
690
837
 
691
- let sArm64Server = null;
692
- if (runARM64) {
693
- try {
694
- const sArm64Exe = path.join(cacheDir, "perf_int_arm64_s");
695
- compileARM64ArtifactFromObject(sRel.object, {
696
- entry: "__main__",
697
- outputPath: sArm64Exe,
698
- optLevel: arm64OptLevel
838
+ algo.llvmPkirServer = null;
839
+ if (runLLVMPKIR) {
840
+ const sLlvmPkirCompiled = tryCompileCase({
841
+ object: algo.rel.object,
842
+ relationName: algo.rel.relationName,
843
+ relHash: algo.rel.relHash,
844
+ inputPattern: sInputPattern,
845
+ cacheDir,
846
+ sourceLabel: algo.sourceLabel,
847
+ usePKIR: true
699
848
  });
700
- sArm64Server = new PersistentExecutable(sArm64Exe);
701
- } catch (e) {
702
- console.log("Failed to build ARM64 executable for 's':", e.message);
849
+ if (sLlvmPkirCompiled.status === "ok") {
850
+ algo.llvmPkirServer =
851
+ new PersistentExecutable(sLlvmPkirCompiled.exePath);
852
+ }
703
853
  }
854
+
855
+ algo.wasmServer = null;
856
+ if (runWasm && !squaringLlvmOnly) {
857
+ try {
858
+ const sWasmPath = path.join(cacheDir, `perf_int_wasm_${algo.id}.wasm`);
859
+ const sWasmExe = path.join(cacheDir, `perf_int_wasm_${algo.id}.exe`);
860
+ const wasmBuf = await compileWasmArtifactFromObject(algo.rel.object, { entry: "__main__" });
861
+ fs.writeFileSync(sWasmPath, wasmBuf);
862
+ const runBin = fileURLToPath(import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs"));
863
+ fs.writeFileSync(sWasmExe, `#!/bin/sh\nexec node "${runBin}" "${sWasmPath}" "$@"\n`, { mode: 0o755 });
864
+ algo.wasmServer = new PersistentExecutable(sWasmExe);
865
+ } catch (e) {
866
+ console.log(`Failed to build Wasm executable for '${algo.id}':`, e.message);
867
+ }
868
+ }
869
+
870
+ algo.pkirServer = null;
871
+ if (runPKIR && !squaringLlvmOnly) {
872
+ try {
873
+ const sPkirPath = path.join(cacheDir, `perf_int_pkir_${algo.id}.wasm`);
874
+ const sPkirExe = path.join(cacheDir, `perf_int_pkir_${algo.id}.exe`);
875
+ const wasmBuf = await compileWasmArtifactFromObject(algo.rel.object, {
876
+ entry: "__main__",
877
+ usePKIR: true
878
+ });
879
+ fs.writeFileSync(sPkirPath, wasmBuf);
880
+ const runBin = fileURLToPath(import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs"));
881
+ fs.writeFileSync(sPkirExe, `#!/bin/sh\nexec node "${runBin}" "${sPkirPath}" "$@"\n`, { mode: 0o755 });
882
+ algo.pkirServer = new PersistentExecutable(sPkirExe);
883
+ } catch (e) {
884
+ console.log(`Failed to build PKIR executable for '${algo.id}':`, e.message);
885
+ }
886
+ }
887
+
888
+ algo.results = {};
704
889
  }
705
890
 
706
891
  const squaringLanes = [];
707
- if (runBaselines) {
892
+ if (runBaselines && !squaringLlvmOnly) {
708
893
  squaringLanes.push({ id: "jsAware", name: "Native JS (Aware)", type: "jsAware", memLabel: "heap" });
709
894
  squaringLanes.push({ id: "jsFree", name: "Native JS (Free)", type: "jsFree", memLabel: "heap" });
710
895
  squaringLanes.push({ id: "kvm", name: "kVM (Env-Free)", type: "kvm", memLabel: "heap" });
711
896
  }
712
- if (runLLVM && sLlvmServer) {
713
- squaringLanes.push({ id: "llvm", name: llvmLaneName(), type: "persistent", server: sLlvmServer, memLabel: "peak RSS" });
897
+ if (runLLVM) {
898
+ squaringLanes.push({
899
+ id: "llvm",
900
+ name: llvmLaneNameStr(),
901
+ type: "persistent",
902
+ memLabel: "peak RSS"
903
+ });
714
904
  }
715
- if (runWasm && sWasmServer) {
716
- squaringLanes.push({ id: "wasm", name: wasmLaneName(), type: "persistent", server: sWasmServer, memLabel: "peak RSS" });
905
+ if (runLLVMPKIR) {
906
+ squaringLanes.push({
907
+ id: "llvm-pkir",
908
+ name: llvmPkirLaneNameStr(),
909
+ type: "persistent",
910
+ memLabel: "peak RSS"
911
+ });
717
912
  }
718
- if (runARM64 && sArm64Server) {
719
- squaringLanes.push({ id: "arm64", name: arm64LaneName(arm64OptLevel), type: "persistent", server: sArm64Server, memLabel: "peak RSS" });
913
+ if (runWasm && !squaringLlvmOnly) {
914
+ squaringLanes.push({ id: "wasm", name: wasmLaneName(), type: "persistent", memLabel: "peak RSS" });
915
+ }
916
+ if (runPKIR && !squaringLlvmOnly) {
917
+ squaringLanes.push({ id: "pkir", name: pkirLaneName(), type: "persistent", memLabel: "peak RSS" });
720
918
  }
721
919
 
722
- const squaringResults = {};
723
- for (const lane of squaringLanes) squaringResults[lane.id] = [];
920
+ for (const algo of squaringAlgos) {
921
+ for (const lane of squaringLanes) {
922
+ algo.results[lane.id] = [];
923
+ }
924
+ }
724
925
  const stepMetadata = [];
725
926
 
726
927
  try {
727
- // 1. Persistent child processes (ARM64, Wasm, LLVM)
928
+ // 1. Persistent child processes (Wasm, PKIR, LLVM)
728
929
  for (const lane of squaringLanes.filter(l => l.type === "persistent")) {
729
- console.log(`==> Running ${lane.name} squaring lane...`);
730
- let curWire = sInitWire;
731
- for (let step = 1; step <= squaringSteps; step++) {
732
- try {
733
- const t0 = performance.now();
734
- curWire = await lane.server.request(curWire);
735
- const t1 = performance.now();
736
- const timeMs = t1 - t0;
737
- const mem = getProcessMemory(lane.server.child.pid);
738
- squaringResults[lane.id].push({ status: "ok", timeMs, memMb: mem.peakRssMb, wire: curWire });
739
- if (!stepMetadata[step - 1]) {
740
- stepMetadata[step - 1] = {
741
- wireLen: curWire.length,
742
- bits: null,
743
- canonicalWire: curWire
744
- };
930
+ for (const algo of squaringAlgos) {
931
+ const server = lane.id === "llvm"
932
+ ? algo.llvmServer
933
+ : lane.id === "llvm-pkir"
934
+ ? algo.llvmPkirServer
935
+ : lane.id === "pkir"
936
+ ? algo.pkirServer
937
+ : algo.wasmServer;
938
+ if (!server) continue;
939
+ console.log(`==> Running ${lane.name} squaring [${algo.name}]...`);
940
+ let curWire = sInitWire;
941
+ for (let step = 1; step <= squaringSteps; step++) {
942
+ try {
943
+ const t0 = performance.now();
944
+ curWire = await server.request(curWire);
945
+ const t1 = performance.now();
946
+ const timeMs = t1 - t0;
947
+ const mem = getProcessMemory(server.child.pid);
948
+ algo.results[lane.id].push({ status: "ok", timeMs, memMb: mem.peakRssMb, wire: curWire });
949
+ if (!stepMetadata[step - 1]) {
950
+ stepMetadata[step - 1] = {
951
+ wireLen: curWire.length,
952
+ bits: null,
953
+ canonicalWire: curWire
954
+ };
955
+ }
956
+ if (timeMs > squaringStepTimeoutMs) break;
957
+ } catch (e) {
958
+ algo.results[lane.id].push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
959
+ break;
745
960
  }
746
- if (timeMs > squaringStepTimeoutMs) break;
747
- } catch (e) {
748
- squaringResults[lane.id].push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
749
- break;
750
961
  }
751
962
  }
752
963
  }
753
964
 
754
965
  // 2. In-process baselines (JS Aware, JS Free, kVM)
755
- if (runBaselines) {
756
- // JS Aware
757
- console.log("==> Running Native JS (Envelope-Aware) squaring lane...");
758
- let curValAware = sInitialVal;
759
- run.defs = state;
760
- for (let step = 1; step <= squaringSteps; step++) {
761
- try {
762
- const t0 = performance.now();
763
- curValAware = run(codes.find, sRel.relDef.def, curValAware, sRel.relDef.typePatternGraph);
764
- const t1 = performance.now();
765
- const timeMs = t1 - t0;
766
- const mem = getNodeMemory();
767
- squaringResults.jsAware.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValAware });
768
- if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
769
- stepMetadata[step - 1].bits = countBitsValue(curValAware);
966
+ if (runBaselines && !squaringLlvmOnly) {
967
+ for (const algo of squaringAlgos) {
968
+ // JS Aware
969
+ console.log(`==> Running Native JS (Aware) squaring [${algo.name}]...`);
970
+ let curValAware = sInitialVal;
971
+ run.defs = algo.state;
972
+ for (let step = 1; step <= squaringSteps; step++) {
973
+ try {
974
+ const t0 = performance.now();
975
+ curValAware = run(codes.find, algo.rel.relDef.def, curValAware, algo.rel.relDef.typePatternGraph);
976
+ const t1 = performance.now();
977
+ const timeMs = t1 - t0;
978
+ const mem = getNodeMemory();
979
+ algo.results.jsAware.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValAware });
980
+ if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
981
+ stepMetadata[step - 1].bits = countBitsValue(curValAware);
982
+ }
983
+ if (timeMs > squaringStepTimeoutMs) break;
984
+ } catch (e) {
985
+ algo.results.jsAware.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
986
+ break;
770
987
  }
771
- if (timeMs > squaringStepTimeoutMs) break;
772
- } catch (e) {
773
- squaringResults.jsAware.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
774
- break;
775
988
  }
776
- }
777
989
 
778
- // JS Free
779
- console.log("==> Running Native JS (Envelope-Free) squaring lane...");
780
- let curValFree = sInitialVal;
781
- run_converged.defs = state;
782
- for (let step = 1; step <= squaringSteps; step++) {
783
- try {
784
- const t0 = performance.now();
785
- curValFree = run_converged(codes.find, sRel.relDef.def, curValFree, sRel.relDef.typePatternGraph);
786
- const t1 = performance.now();
787
- const timeMs = t1 - t0;
788
- const mem = getNodeMemory();
789
- squaringResults.jsFree.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValFree });
790
- if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
791
- stepMetadata[step - 1].bits = countBitsValue(curValFree);
990
+ // JS Free
991
+ console.log(`==> Running Native JS (Free) squaring [${algo.name}]...`);
992
+ let curValFree = sInitialVal;
993
+ run_converged.defs = algo.state;
994
+ for (let step = 1; step <= squaringSteps; step++) {
995
+ try {
996
+ const t0 = performance.now();
997
+ curValFree = run_converged(codes.find, algo.rel.relDef.def, curValFree, algo.rel.relDef.typePatternGraph);
998
+ const t1 = performance.now();
999
+ const timeMs = t1 - t0;
1000
+ const mem = getNodeMemory();
1001
+ algo.results.jsFree.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValFree });
1002
+ if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
1003
+ stepMetadata[step - 1].bits = countBitsValue(curValFree);
1004
+ }
1005
+ if (timeMs > squaringStepTimeoutMs) break;
1006
+ } catch (e) {
1007
+ algo.results.jsFree.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
1008
+ break;
792
1009
  }
793
- if (timeMs > squaringStepTimeoutMs) break;
794
- } catch (e) {
795
- squaringResults.jsFree.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
796
- break;
797
1010
  }
798
- }
799
1011
 
800
- // kVM
801
- console.log("==> Running kVM Interpreter (Env-Free) squaring lane...");
802
- let curValKvm = sInitialVal;
803
- const kvmContext = { rels: state.rels, findCode: codes.find, options: { envelopeFree: true } };
804
- for (let step = 1; step <= squaringSteps; step++) {
805
- try {
806
- const t0 = performance.now();
807
- curValKvm = executeKVM(sRel.kvmFunc, curValKvm, kvmContext);
808
- const t1 = performance.now();
809
- const timeMs = t1 - t0;
810
- const mem = getNodeMemory();
811
- squaringResults.kvm.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValKvm });
812
- if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
813
- stepMetadata[step - 1].bits = countBitsValue(curValKvm);
1012
+ // kVM
1013
+ console.log(`==> Running kVM Interpreter squaring [${algo.name}]...`);
1014
+ let curValKvm = sInitialVal;
1015
+ const kvmContext = { rels: algo.state.rels, findCode: codes.find, options: { envelopeFree: true } };
1016
+ for (let step = 1; step <= squaringSteps; step++) {
1017
+ try {
1018
+ const t0 = performance.now();
1019
+ curValKvm = executeKVM(algo.rel.kvmFunc, curValKvm, kvmContext);
1020
+ const t1 = performance.now();
1021
+ const timeMs = t1 - t0;
1022
+ const mem = getNodeMemory();
1023
+ algo.results.kvm.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValKvm });
1024
+ if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
1025
+ stepMetadata[step - 1].bits = countBitsValue(curValKvm);
1026
+ }
1027
+ if (timeMs > squaringStepTimeoutMs) break;
1028
+ } catch (e) {
1029
+ algo.results.kvm.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
1030
+ break;
814
1031
  }
815
- if (timeMs > squaringStepTimeoutMs) break;
816
- } catch (e) {
817
- squaringResults.kvm.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
818
- break;
819
1032
  }
820
1033
  }
821
1034
  }
822
-
823
1035
  } finally {
824
- if (sArm64Server) sArm64Server.close();
825
- if (sWasmServer) sWasmServer.close();
826
- if (sLlvmServer) sLlvmServer.close();
1036
+ for (const algo of squaringAlgos) {
1037
+ if (algo.wasmServer) algo.wasmServer.close();
1038
+ if (algo.pkirServer) algo.pkirServer.close();
1039
+ if (algo.llvmServer) algo.llvmServer.close();
1040
+ if (algo.llvmPkirServer) algo.llvmPkirServer.close();
1041
+ }
827
1042
  }
828
1043
 
829
- // For any remaining steps without bit counts, decode canonicalWire safely to get bits:
1044
+ // For any remaining steps without bit counts, decode canonicalWire safely
830
1045
  for (let step = 1; step <= squaringSteps; step++) {
831
1046
  const meta = stepMetadata[step - 1];
832
1047
  if (meta && meta.bits == null && meta.canonicalWire) {
@@ -838,87 +1053,160 @@ for (let step = 1; step <= squaringSteps; step++) {
838
1053
  }
839
1054
 
840
1055
  // Display results table
841
- console.log("\n======================== SQUARING SCALING & MEMORY BENCHMARK ========================");
842
- console.log(`Expression: s = {()x,()y}times; ${squaringInputText} ${"s ".repeat(squaringSteps).trim()}`);
843
- console.log(`Initial: ${squaringInputText} (${countBitsValue(sInitialVal)} bits)`);
844
- console.log("----------------------------------------------------------------------------------------------------------------------------------");
845
- const headerCols = ["Step", "Bits", "Wire(B)", ...squaringLanes.map(l => `${l.name} (${l.memLabel})`)];
846
- const colWidths = [4, 6, 7, ...squaringLanes.map(l => Math.max(22, l.name.length + l.memLabel.length + 5))];
847
- console.log(headerCols.map((h, i) => i < 3 ? h.padStart(colWidths[i]) : h.padEnd(colWidths[i])).join(" | "));
848
- console.log(colWidths.map(w => "-".repeat(w)).join("-|-"));
849
-
850
- for (let step = 1; step <= squaringSteps; step++) {
851
- const meta = stepMetadata[step - 1] || { bits: "?", wireLen: "?" };
852
- const cols = [
853
- String(step).padStart(colWidths[0]),
854
- String(meta.bits ?? "?").padStart(colWidths[1]),
855
- String(meta.wireLen ?? "?").padStart(colWidths[2])
1056
+ const sep130 = "=".repeat(130);
1057
+ const line130 = "-".repeat(130);
1058
+
1059
+ if (squaringLanes.length === 1 && (squaringLanes[0].id === "llvm" || squaringLanes[0].id === "llvm-pkir")) {
1060
+ const laneId = squaringLanes[0].id;
1061
+ const laneName = squaringLanes[0].name;
1062
+ // LLVM Only side-by-side comparison table
1063
+ console.log(`\n${sep130}`);
1064
+ console.log(`=================== SQUARING SCALE & ALGORITHM BENCHMARK (${laneName}) ===================`);
1065
+ console.log(`Algorithms: times vs karatsuba on ${laneName}`);
1066
+ console.log(`Initial: ${squaringInputText} (${countBitsValue(sInitialVal)} bits)`);
1067
+ console.log(line130);
1068
+
1069
+ const tAlgo = squaringAlgos[0];
1070
+ const kAlgo = squaringAlgos[1];
1071
+ const colW = [4, 6, 7, 34, 34, 15];
1072
+ const hCols = [
1073
+ "Step",
1074
+ "Bits",
1075
+ "Wire(B)",
1076
+ `${laneName} times (peak RSS)`,
1077
+ `${laneName} karatsuba (peak RSS)`,
1078
+ "Speedup (T/K)"
856
1079
  ];
857
- for (let li = 0; li < squaringLanes.length; li++) {
858
- const lane = squaringLanes[li];
859
- const res = squaringResults[lane.id][step - 1];
860
- const w = colWidths[3 + li];
861
- if (!res) {
862
- cols.push("-".padEnd(w));
863
- } else if (res.status === "ok") {
864
- const t = res.timeMs < 10 ? `${res.timeMs.toFixed(2)}ms` : `${res.timeMs.toFixed(1)}ms`;
865
- const m = res.memMb != null ? `${res.memMb}MB` : "";
866
- cols.push(`${t} / ${m}`.padEnd(w));
867
- } else {
868
- let errText = res.error;
869
- if (errText.includes("Maximum call stack")) {
870
- errText = "Stack overflow (V8)";
871
- } else if (errText.includes("status null") || errText.includes("failed with status")) {
872
- errText = "Arena limit reached";
1080
+ console.log(hCols.map((h, i) => (i < 3 ? h.padStart(colW[i]) : h.padEnd(colW[i]))).join(" | "));
1081
+ console.log(colW.map(w => "-".repeat(w)).join("-|-"));
1082
+
1083
+ for (let step = 1; step <= squaringSteps; step++) {
1084
+ const meta = stepMetadata[step - 1] || { bits: "?", wireLen: "?" };
1085
+ const tRes = tAlgo.results[laneId][step - 1];
1086
+ const kRes = kAlgo.results[laneId][step - 1];
1087
+
1088
+ const formatCell = (res, w) => {
1089
+ if (!res) return "-".padEnd(w);
1090
+ if (res.status === "ok") {
1091
+ const t = res.timeMs < 10 ? `${res.timeMs.toFixed(2)}ms` : `${res.timeMs.toFixed(1)}ms`;
1092
+ const m = res.memMb != null ? `${res.memMb}MB` : "";
1093
+ return `${t} / ${m}`.padEnd(w);
1094
+ }
1095
+ let errText = res.error || "error";
1096
+ if (errText.includes("Maximum call stack")) errText = "Stack overflow (V8)";
1097
+ else if (errText.includes("status null") || errText.includes("failed with status")) errText = "Arena limit reached";
1098
+ return errText.slice(0, w - 1).padEnd(w);
1099
+ };
1100
+
1101
+ let speedupStr = "-";
1102
+ if (tRes?.status === "ok" && kRes?.status === "ok" && kRes.timeMs > 0) {
1103
+ speedupStr = `${(tRes.timeMs / kRes.timeMs).toFixed(2)}x`;
1104
+ }
1105
+
1106
+ const row = [
1107
+ String(step).padStart(colW[0]),
1108
+ String(meta.bits ?? "?").padStart(colW[1]),
1109
+ String(meta.wireLen ?? "?").padStart(colW[2]),
1110
+ formatCell(tRes, colW[3]),
1111
+ formatCell(kRes, colW[4]),
1112
+ speedupStr.padEnd(colW[5])
1113
+ ];
1114
+ console.log(row.join(" | "));
1115
+ }
1116
+ console.log(sep130);
1117
+ } else {
1118
+ // Print each algorithm table
1119
+ for (const algo of squaringAlgos) {
1120
+ console.log(`\n${sep130}`);
1121
+ console.log(`=================== SQUARING SCALE: ${algo.title.toUpperCase()} ===================`);
1122
+ console.log(`Expression: ${algo.expr} ${squaringInputText} ${"s ".repeat(squaringSteps).trim()}`);
1123
+ console.log(`Initial: ${squaringInputText} (${countBitsValue(sInitialVal)} bits)`);
1124
+ console.log(line130);
1125
+
1126
+ const headerCols = ["Step", "Bits", "Wire(B)", ...squaringLanes.map(l => `${l.name} (${l.memLabel})`)];
1127
+ const colWidths = [4, 6, 7, ...squaringLanes.map(l => Math.max(22, l.name.length + l.memLabel.length + 5))];
1128
+ console.log(headerCols.map((h, i) => (i < 3 ? h.padStart(colWidths[i]) : h.padEnd(colWidths[i]))).join(" | "));
1129
+ console.log(colWidths.map(w => "-".repeat(w)).join("-|-"));
1130
+
1131
+ for (let step = 1; step <= squaringSteps; step++) {
1132
+ const meta = stepMetadata[step - 1] || { bits: "?", wireLen: "?" };
1133
+ const cols = [
1134
+ String(step).padStart(colWidths[0]),
1135
+ String(meta.bits ?? "?").padStart(colWidths[1]),
1136
+ String(meta.wireLen ?? "?").padStart(colWidths[2])
1137
+ ];
1138
+ for (let li = 0; li < squaringLanes.length; li++) {
1139
+ const lane = squaringLanes[li];
1140
+ const res = algo.results[lane.id][step - 1];
1141
+ const w = colWidths[3 + li];
1142
+ if (!res) {
1143
+ cols.push("-".padEnd(w));
1144
+ } else if (res.status === "ok") {
1145
+ const t = res.timeMs < 10 ? `${res.timeMs.toFixed(2)}ms` : `${res.timeMs.toFixed(1)}ms`;
1146
+ const m = res.memMb != null ? `${res.memMb}MB` : "";
1147
+ cols.push(`${t} / ${m}`.padEnd(w));
1148
+ } else {
1149
+ let errText = res.error;
1150
+ if (errText.includes("Maximum call stack")) {
1151
+ errText = "Stack overflow (V8)";
1152
+ } else if (errText.includes("status null") || errText.includes("failed with status")) {
1153
+ errText = "Arena limit reached";
1154
+ }
1155
+ cols.push(errText.slice(0, w - 1).padEnd(w));
1156
+ }
873
1157
  }
874
- cols.push(errText.slice(0, w - 1).padEnd(w));
1158
+ console.log(cols.join(" | "));
875
1159
  }
1160
+ console.log(sep130);
876
1161
  }
877
- console.log(cols.join(" | "));
878
1162
  }
879
- console.log("==================================================================================================================================");
880
1163
 
881
- // Conformance check across all computed steps
1164
+ // Conformance check across all computed steps and algorithms
882
1165
  let squaringConformanceOk = true;
883
1166
  for (let step = 1; step <= squaringSteps; step++) {
884
1167
  const meta = stepMetadata[step - 1];
885
1168
  if (!meta?.canonicalWire) continue;
886
- for (const lane of squaringLanes) {
887
- const res = squaringResults[lane.id][step - 1];
888
- if (res?.status === "ok") {
889
- if (res.wire) {
890
- if (Buffer.compare(res.wire, meta.canonicalWire) !== 0) {
891
- squaringConformanceOk = false;
892
- console.log(`Squaring conformance mismatch on step ${step} (${lane.name})`);
893
- }
894
- } else if (res.value) {
895
- try {
896
- const wireVal = decodeWire(meta.canonicalWire).value;
897
- if (!safeDeepEqual(res.value, wireVal)) {
1169
+ for (const algo of squaringAlgos) {
1170
+ for (const lane of squaringLanes) {
1171
+ const res = algo.results[lane.id][step - 1];
1172
+ if (res?.status === "ok") {
1173
+ if (res.wire) {
1174
+ if (Buffer.compare(res.wire, meta.canonicalWire) !== 0) {
898
1175
  squaringConformanceOk = false;
899
- console.log(`Squaring conformance mismatch on step ${step} (${lane.name})`);
1176
+ console.log(`Squaring conformance mismatch on step ${step} (${algo.name} / ${lane.name})`);
900
1177
  }
901
- } catch {}
1178
+ } else if (res.value) {
1179
+ try {
1180
+ const wireVal = decodeWire(meta.canonicalWire).value;
1181
+ if (!safeDeepEqual(res.value, wireVal)) {
1182
+ squaringConformanceOk = false;
1183
+ console.log(`Squaring conformance mismatch on step ${step} (${algo.name} / ${lane.name})`);
1184
+ }
1185
+ } catch {}
1186
+ }
902
1187
  }
903
1188
  }
904
1189
  }
905
1190
  }
906
1191
 
907
1192
  console.log("\nSquaring Performance & Scaling Summary:");
908
- for (const lane of squaringLanes) {
909
- const okSteps = squaringResults[lane.id].filter(r => r.status === "ok");
910
- const maxStep = okSteps.length;
911
- const lastOk = okSteps[okSteps.length - 1];
912
- const peakMem = okSteps.reduce((max, r) => Math.max(max, r.memMb || 0), 0);
913
- const failureReason = squaringResults[lane.id][maxStep]?.error;
914
- let statusDetail = "";
915
- if (failureReason) {
916
- if (failureReason.includes("Maximum call stack")) statusDetail = " (V8 call stack overflow)";
917
- else if (failureReason.includes("status null") || failureReason.includes("failed with status")) statusDetail = " (arena capacity limit reached)";
918
- else statusDetail = ` (${failureReason.slice(0, 30)})`;
919
- }
920
- const timeStr = lastOk ? ` (step ${maxStep} in ${lastOk.timeMs < 10 ? lastOk.timeMs.toFixed(2) : lastOk.timeMs.toFixed(1)}ms)` : "";
921
- console.log(` - ${lane.name.padEnd(32)}: reached Step ${String(maxStep).padStart(2)}/${squaringSteps}${timeStr}, peak ${lane.memLabel}: ${peakMem}MB${statusDetail}`);
1193
+ for (const algo of squaringAlgos) {
1194
+ console.log(` Algorithm: ${algo.title}`);
1195
+ for (const lane of squaringLanes) {
1196
+ const okSteps = algo.results[lane.id].filter(r => r.status === "ok");
1197
+ const maxStep = okSteps.length;
1198
+ const lastOk = okSteps[okSteps.length - 1];
1199
+ const peakMem = okSteps.reduce((max, r) => Math.max(max, r.memMb || 0), 0);
1200
+ const failureReason = algo.results[lane.id][maxStep]?.error;
1201
+ let statusDetail = "";
1202
+ if (failureReason) {
1203
+ if (failureReason.includes("Maximum call stack")) statusDetail = " (V8 call stack overflow)";
1204
+ else if (failureReason.includes("status null") || failureReason.includes("failed with status")) statusDetail = " (arena capacity limit reached)";
1205
+ else statusDetail = ` (${failureReason.slice(0, 30)})`;
1206
+ }
1207
+ const timeStr = lastOk ? ` (step ${maxStep} in ${lastOk.timeMs < 10 ? lastOk.timeMs.toFixed(2) : lastOk.timeMs.toFixed(1)}ms)` : "";
1208
+ console.log(` - ${lane.name.padEnd(32)}: reached Step ${String(maxStep).padStart(2)}/${squaringSteps}${timeStr}, peak ${lane.memLabel}: ${peakMem}MB${statusDetail}`);
1209
+ }
922
1210
  }
923
1211
 
924
1212
  if (squaringConformanceOk) {