@fraczak/k 7.0.2 → 7.2.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 (71) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/DOCS/CODECS.md +37 -2
  3. package/DOCS/CODEC_ARCHITECTURE.md +12 -5
  4. package/DOCS/FILE_FORMATS.md +238 -15
  5. package/DOCS/POLYMORPHIC_IR.md +104 -0
  6. package/DOCS/REPL.md +11 -3
  7. package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
  8. package/JOURNAL.md +288 -0
  9. package/README.md +12 -3
  10. package/Value.mjs +28 -3
  11. package/access-table.mjs +343 -0
  12. package/backend-api.mjs +23 -1
  13. package/backends/llvm/README.md +20 -21
  14. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  15. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  16. package/backends/llvm/bin/k-llvm.mjs +292 -0
  17. package/backends/llvm/runtime/krt.c +8 -0
  18. package/backends/llvm/runtime/krt.h +1 -0
  19. package/backends/llvm/src/cli.mjs +95 -17
  20. package/backends/llvm/src/executable.mjs +128 -11
  21. package/backends/llvm/src/kvm2llvm.mjs +10 -5
  22. package/backends/llvm/src/llvm.mjs +121 -9
  23. package/backends/llvm/tests/integration.sh +11 -11
  24. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  25. package/backends/llvm/tests/perf-support.mjs +24 -3
  26. package/backends/llvm/tests/test-llvm.mjs +22 -1
  27. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  28. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  29. package/backends/wasm/bin/k-wasm.mjs +47 -13
  30. package/backends/wasm/src/cli.mjs +141 -19
  31. package/backends/wasm/src/wasm.mjs +130 -23
  32. package/browser/repl-browser.mjs +16 -5
  33. package/browser/tree-view.mjs +47 -8
  34. package/browser/vfs-data.mjs +6 -5
  35. package/codecs/README.md +1 -0
  36. package/codecs/djson.mjs +270 -0
  37. package/codecs/int.mjs +2 -1
  38. package/codecs/json.mjs +7 -2
  39. package/codecs/k-parse.mjs +36 -9
  40. package/codecs/k-print.mjs +26 -6
  41. package/codecs/runtime/codec-sdk.mjs +24 -6
  42. package/codecs/runtime/pattern-json.mjs +80 -1
  43. package/codecs/runtime/prefix-codec.mjs +629 -67
  44. package/codecs/runtime/show-value.mjs +93 -51
  45. package/codecs/runtime/unicode-string.mjs +29 -6
  46. package/codecs/show.mjs +24 -6
  47. package/codecs/utf16.mjs +2 -1
  48. package/k.mjs +56 -16
  49. package/kir.mjs +58 -19
  50. package/kvm.mjs +49 -12
  51. package/objects/compile.mjs +37 -23
  52. package/objects/decompile.mjs +40 -10
  53. package/objects/extract-aliases.mjs +44 -14
  54. package/objects/inspect.mjs +19 -14
  55. package/objects/validate.mjs +18 -11
  56. package/package.json +11 -3
  57. package/pkir.mjs +772 -0
  58. package/repl.mjs +92 -23
  59. package/scripts/build-repl-html.mjs +22 -3
  60. package/scripts/perf-ieee.mjs +264 -13
  61. package/scripts/perf-int.mjs +315 -22
  62. package/scripts/perf-multiplication.mjs +58 -7
  63. package/scripts/perf-poly.mjs +306 -24
  64. package/scripts/run-tests.mjs +3 -0
  65. package/tests/test-access-table.mjs +213 -0
  66. package/tests/test-codecs.mjs +168 -0
  67. package/tests/test-pkir.mjs +530 -0
  68. package/tests/test-polymorphic-tdd.mjs +196 -0
  69. package/tests/test-repl-html.mjs +16 -2
  70. package/tests/test-repl.mjs +63 -1
  71. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
@@ -58,23 +58,27 @@ function printHelp() {
58
58
 
59
59
  Big Integer Arithmetic Performance Benchmark Harness.
60
60
  Benchmarks plus, minus, times, and squaring scale on arithmetics.k
61
- across Native JS, kVM interpreter, LLVM, and Wasm backends.
61
+ across Native JS, kVM interpreter, LLVM, Wasm, and PKIR backends.
62
62
 
63
63
  Options:
64
64
  --wasm-in-process
65
65
  Run WebAssembly in-process instead of persistent subprocess runner.
66
66
  --squaring-llvm-only
67
67
  Run squaring benchmark on LLVM only.
68
+ --pkir-only
69
+ Run only PKIR backend.
68
70
  -h, --help
69
71
  Show this help message and exit.
70
72
 
71
73
  Environment Variables:
72
74
  BACKENDS
73
- Target backend to run: 'llvm' or 'wasm'.
75
+ Target backend to run: 'llvm', 'wasm', or 'pkir'.
74
76
  LLVM_ONLY
75
77
  Run only LLVM backend ('1' to enable).
76
78
  WASM_ONLY
77
79
  Run only WebAssembly backend ('1' to enable).
80
+ PKIR_ONLY
81
+ Run only PKIR backend ('1' to enable).
78
82
  BACKENDS_ONLY
79
83
  Skip JS and kVM interpreter baselines ('1' to enable).
80
84
  SQUARING_LLVM_ONLY
@@ -116,12 +120,57 @@ if (process.argv.includes("-h") || process.argv.includes("--help")) {
116
120
 
117
121
  // Backend selection
118
122
  const backendsEnv = process.env.BACKENDS?.toLowerCase();
119
- const llvmOnly = process.env.LLVM_ONLY === "1" || backendsEnv === "llvm" || process.argv.includes("--llvm-only");
120
- const wasmOnly = process.env.WASM_ONLY === "1" || backendsEnv === "wasm" || process.argv.includes("--wasm-only");
121
- const runLLVM = !wasmOnly;
122
- const runWasm = !llvmOnly;
123
- const runBaselines = !process.env.BACKENDS_ONLY && (process.env.LLVM_ONLY !== "1") && (process.env.WASM_ONLY !== "1");
124
- const squaringLlvmOnly = llvmOnly || process.env.SQUARING_LLVM_ONLY === "1" || process.argv.includes("--squaring-llvm-only");
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
+ }
125
174
 
126
175
  const ops = ["plus", "minus", "times"];
127
176
  const benchNames = Object.fromEntries(ops.map(op => [op, `bench_${op}`]));
@@ -149,6 +198,9 @@ function wasmLaneName() {
149
198
  const mode = wasmPipe ? "persistent" : "in-process";
150
199
  return `WebAssembly (${mode})`;
151
200
  }
201
+ function pkirLaneName() {
202
+ return "PKIR (Wasm persistent)";
203
+ }
152
204
 
153
205
  console.log("==> Initializing state and loading @fraczak/k/Examples/arithmetics.k");
154
206
  const state = createState();
@@ -212,13 +264,37 @@ if (runLLVM) {
212
264
  relHash: relation.relHash,
213
265
  inputPattern: tc.inputPattern,
214
266
  cacheDir,
215
- sourceLabel: `@fraczak/k/Examples/arithmetics.k#${relation.relationName}`
267
+ sourceLabel:
268
+ `@fraczak/k/Examples/arithmetics.k#${relation.relationName}`,
269
+ usePKIR: false
216
270
  });
217
271
  }
218
272
  codes.load(state.codes);
219
273
  printCompileFailures(testSuite);
220
274
  }
221
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
+
222
298
  // Setup Wasm
223
299
  let wasmExports = null;
224
300
  const wasmExes = new Map();
@@ -272,6 +348,35 @@ if (runWasm) {
272
348
  }
273
349
  }
274
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
+
275
380
  function createWasmRunner(testSuite) {
276
381
  const wasmCases = testSuite.filter(tc => tc.wasm?.status === "ok");
277
382
  const servers = new Map();
@@ -300,6 +405,34 @@ function createWasmRunner(testSuite) {
300
405
  };
301
406
  }
302
407
 
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);
416
+ }
417
+ return server;
418
+ }
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();
432
+ }
433
+ };
434
+ }
435
+
303
436
  function printBenchmarkDescription() {
304
437
  const lanes = [];
305
438
  if (runBaselines) {
@@ -307,8 +440,10 @@ function printBenchmarkDescription() {
307
440
  lanes.push("Native JS (Envelope-Free)");
308
441
  lanes.push("kVM Interpreter (Env-Free)");
309
442
  }
310
- if (runLLVM) lanes.push(llvmLaneName());
443
+ if (runLLVM) lanes.push(llvmLaneNameStr());
444
+ if (runLLVMPKIR) lanes.push(llvmPkirLaneNameStr());
311
445
  if (runWasm) lanes.push(wasmLaneName());
446
+ if (runPKIR) lanes.push(pkirLaneName());
312
447
 
313
448
  console.log("==> Benchmark description");
314
449
  console.log(" source: @fraczak/k/Examples/arithmetics.k");
@@ -320,11 +455,18 @@ function printBenchmarkDescription() {
320
455
  console.log(` llvm-ready cases: ${testSuite.filter(tc => tc.llvm?.status === "ok").length}`);
321
456
  console.log(` llvm warmup iterations: ${llvmWarmupIterations}`);
322
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
+ }
323
462
  if (runWasm) {
324
463
  console.log(` wasm runner mode: ${wasmPipe ? "persistent pipe" : "in-process"}`);
325
464
  console.log(` wasm warmup iterations: ${wasmWarmupIterations}`);
326
465
  if (!wasmPipe) console.log(` wasm arena reset: ${wasmReset ? "yes" : "no"}`);
327
466
  }
467
+ if (runPKIR) {
468
+ console.log(` pkir warmup iterations: ${wasmWarmupIterations}`);
469
+ }
328
470
  console.log(` iterations: ${iterations}`);
329
471
  console.log(` benchmark lanes: ${lanes.join("; ")}`);
330
472
  console.log(" conformance: all outputs are compared to native expected values");
@@ -359,6 +501,22 @@ if (runLLVM) {
359
501
  }
360
502
  }
361
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
+
362
520
  // 3. Wasm lane
363
521
  let wasmResult = null;
364
522
  if (runWasm) {
@@ -403,6 +561,25 @@ if (runWasm) {
403
561
  }
404
562
  }
405
563
 
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);
570
+ try {
571
+ if (wasmWarmupIterations > 0) {
572
+ console.log(`==> Warming PKIR (${wasmWarmupIterations} iterations)...`);
573
+ await pkirRunner.run(wasmWarmupIterations);
574
+ }
575
+ console.log(`==> Running PKIR (${iterations} iterations)...`);
576
+ pkirResult = await pkirRunner.run(iterations);
577
+ } finally {
578
+ pkirRunner.close();
579
+ }
580
+ }
581
+ }
582
+
406
583
  console.log("\n=================== INTEGER BENCHMARK RESULTS ===================");
407
584
  console.log(`Operation calls per iteration: ${testSuite.length}`);
408
585
  console.log(`Total requested operation calls: ${iterations * testSuite.length}`);
@@ -414,11 +591,19 @@ if (runBaselines) {
414
591
  console.log(`${laneIndex++}. kVM Interpreter (Env-Free): ${formatTiming(kvmFreeResult)}`);
415
592
  }
416
593
  if (runLLVM) {
417
- 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)}`);
418
600
  }
419
601
  if (runWasm) {
420
602
  console.log(`${laneIndex++}. ${wasmLaneName().padEnd(29)} ${formatTiming(wasmResult)}`);
421
603
  }
604
+ if (runPKIR) {
605
+ console.log(`${laneIndex++}. ${pkirLaneName().padEnd(29)} ${formatTiming(pkirResult)}`);
606
+ }
422
607
  console.log("=================================================================\n");
423
608
 
424
609
  // Conformance Validation
@@ -446,6 +631,24 @@ for (const tc of testSuite) {
446
631
  }
447
632
  }
448
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
+
449
652
  if (runWasm) {
450
653
  if (wasmPipe) {
451
654
  if (tc.wasm?.status !== "ok") {
@@ -484,6 +687,24 @@ for (const tc of testSuite) {
484
687
  }
485
688
  }
486
689
 
690
+ if (runPKIR) {
691
+ if (tc.pkir?.status !== "ok") {
692
+ tc.pkirConformance = "compile-failed";
693
+ } else {
694
+ try {
695
+ const outputWire = await runExecutable(tc.pkir.exePath, tc.inputWire);
696
+ const actual = decodeWire(outputWire).value;
697
+ assert.deepEqual(toPlainObject(actual), toPlainObject(tc.expected));
698
+ tc.pkirConformance = "ok";
699
+ } catch (error) {
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"));
704
+ }
705
+ }
706
+ }
707
+
487
708
  }
488
709
 
489
710
  const validationSummary = [];
@@ -492,14 +713,24 @@ if (runLLVM) {
492
713
  const llvmOk = testSuite.filter(tc => tc.llvmConformance === "ok").length;
493
714
  validationSummary.push(`LLVM ${llvmOk}/${testSuite.length}`);
494
715
  }
716
+ if (runLLVMPKIR) {
717
+ const pkirOk = testSuite.filter(tc => tc.llvmPkirConformance === "ok").length;
718
+ validationSummary.push(`LLVM-PKIR ${pkirOk}/${testSuite.length}`);
719
+ }
495
720
  if (runWasm) {
496
721
  const wasmOk = testSuite.filter(tc => tc.wasmConformance === "ok").length;
497
722
  validationSummary.push(`Wasm ${wasmOk}/${testSuite.length}`);
498
723
  }
724
+ if (runPKIR) {
725
+ const pkirOk = testSuite.filter(tc => tc.pkirConformance === "ok").length;
726
+ validationSummary.push(`PKIR ${pkirOk}/${testSuite.length}`);
727
+ }
499
728
 
500
729
  console.log(`Conformance validation: ${validationSummary.join(", ")} cases match expected values.`);
501
730
  if (runLLVM && shouldStrictFail(testSuite)) process.exitCode = 1;
731
+ if (runLLVMPKIR && shouldStrictFail(testSuite.map(tc => ({ ...tc, llvm: tc.llvmPKIR })))) process.exitCode = 1;
502
732
  if (runWasm && testSuite.some(tc => tc.wasmConformance !== "ok")) process.exitCode = 1;
733
+ if (runPKIR && testSuite.some(tc => tc.pkirConformance !== "ok")) process.exitCode = 1;
503
734
 
504
735
  // =======================================================================
505
736
  // Squaring Scale & Memory Profiling Benchmark
@@ -596,13 +827,31 @@ for (const algo of squaringAlgos) {
596
827
  relHash: algo.rel.relHash,
597
828
  inputPattern: sInputPattern,
598
829
  cacheDir,
599
- sourceLabel: algo.sourceLabel
830
+ sourceLabel: algo.sourceLabel,
831
+ usePKIR: false
600
832
  });
601
833
  if (sLlvmCompiled.status === "ok") {
602
834
  algo.llvmServer = new PersistentExecutable(sLlvmCompiled.exePath);
603
835
  }
604
836
  }
605
837
 
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
848
+ });
849
+ if (sLlvmPkirCompiled.status === "ok") {
850
+ algo.llvmPkirServer =
851
+ new PersistentExecutable(sLlvmPkirCompiled.exePath);
852
+ }
853
+ }
854
+
606
855
  algo.wasmServer = null;
607
856
  if (runWasm && !squaringLlvmOnly) {
608
857
  try {
@@ -618,6 +867,24 @@ for (const algo of squaringAlgos) {
618
867
  }
619
868
  }
620
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
+
621
888
  algo.results = {};
622
889
  }
623
890
 
@@ -628,11 +895,27 @@ if (runBaselines && !squaringLlvmOnly) {
628
895
  squaringLanes.push({ id: "kvm", name: "kVM (Env-Free)", type: "kvm", memLabel: "heap" });
629
896
  }
630
897
  if (runLLVM) {
631
- squaringLanes.push({ id: "llvm", name: llvmLaneName(), type: "persistent", memLabel: "peak RSS" });
898
+ squaringLanes.push({
899
+ id: "llvm",
900
+ name: llvmLaneNameStr(),
901
+ type: "persistent",
902
+ memLabel: "peak RSS"
903
+ });
904
+ }
905
+ if (runLLVMPKIR) {
906
+ squaringLanes.push({
907
+ id: "llvm-pkir",
908
+ name: llvmPkirLaneNameStr(),
909
+ type: "persistent",
910
+ memLabel: "peak RSS"
911
+ });
632
912
  }
633
913
  if (runWasm && !squaringLlvmOnly) {
634
914
  squaringLanes.push({ id: "wasm", name: wasmLaneName(), type: "persistent", memLabel: "peak RSS" });
635
915
  }
916
+ if (runPKIR && !squaringLlvmOnly) {
917
+ squaringLanes.push({ id: "pkir", name: pkirLaneName(), type: "persistent", memLabel: "peak RSS" });
918
+ }
636
919
 
637
920
  for (const algo of squaringAlgos) {
638
921
  for (const lane of squaringLanes) {
@@ -642,10 +925,16 @@ for (const algo of squaringAlgos) {
642
925
  const stepMetadata = [];
643
926
 
644
927
  try {
645
- // 1. Persistent child processes (Wasm, LLVM)
928
+ // 1. Persistent child processes (Wasm, PKIR, LLVM)
646
929
  for (const lane of squaringLanes.filter(l => l.type === "persistent")) {
647
930
  for (const algo of squaringAlgos) {
648
- const server = lane.id === "llvm" ? algo.llvmServer : algo.wasmServer;
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;
649
938
  if (!server) continue;
650
939
  console.log(`==> Running ${lane.name} squaring [${algo.name}]...`);
651
940
  let curWire = sInitWire;
@@ -746,7 +1035,9 @@ try {
746
1035
  } finally {
747
1036
  for (const algo of squaringAlgos) {
748
1037
  if (algo.wasmServer) algo.wasmServer.close();
1038
+ if (algo.pkirServer) algo.pkirServer.close();
749
1039
  if (algo.llvmServer) algo.llvmServer.close();
1040
+ if (algo.llvmPkirServer) algo.llvmPkirServer.close();
750
1041
  }
751
1042
  }
752
1043
 
@@ -765,11 +1056,13 @@ for (let step = 1; step <= squaringSteps; step++) {
765
1056
  const sep130 = "=".repeat(130);
766
1057
  const line130 = "-".repeat(130);
767
1058
 
768
- if (squaringLanes.length === 1 && squaringLanes[0].id === "llvm") {
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;
769
1062
  // LLVM Only side-by-side comparison table
770
1063
  console.log(`\n${sep130}`);
771
- console.log("=================== SQUARING SCALE & ALGORITHM BENCHMARK (LLVM) ===================");
772
- console.log(`Algorithms: times vs karatsuba on ${squaringLanes[0].name}`);
1064
+ console.log(`=================== SQUARING SCALE & ALGORITHM BENCHMARK (${laneName}) ===================`);
1065
+ console.log(`Algorithms: times vs karatsuba on ${laneName}`);
773
1066
  console.log(`Initial: ${squaringInputText} (${countBitsValue(sInitialVal)} bits)`);
774
1067
  console.log(line130);
775
1068
 
@@ -780,8 +1073,8 @@ if (squaringLanes.length === 1 && squaringLanes[0].id === "llvm") {
780
1073
  "Step",
781
1074
  "Bits",
782
1075
  "Wire(B)",
783
- "LLVM times (peak RSS)",
784
- "LLVM karatsuba (peak RSS)",
1076
+ `${laneName} times (peak RSS)`,
1077
+ `${laneName} karatsuba (peak RSS)`,
785
1078
  "Speedup (T/K)"
786
1079
  ];
787
1080
  console.log(hCols.map((h, i) => (i < 3 ? h.padStart(colW[i]) : h.padEnd(colW[i]))).join(" | "));
@@ -789,8 +1082,8 @@ if (squaringLanes.length === 1 && squaringLanes[0].id === "llvm") {
789
1082
 
790
1083
  for (let step = 1; step <= squaringSteps; step++) {
791
1084
  const meta = stepMetadata[step - 1] || { bits: "?", wireLen: "?" };
792
- const tRes = tAlgo.results.llvm[step - 1];
793
- const kRes = kAlgo.results.llvm[step - 1];
1085
+ const tRes = tAlgo.results[laneId][step - 1];
1086
+ const kRes = kAlgo.results[laneId][step - 1];
794
1087
 
795
1088
  const formatCell = (res, w) => {
796
1089
  if (!res) return "-".padEnd(w);
@@ -7,6 +7,7 @@
7
7
  * across available execution backends:
8
8
  * - LLVM (native binary via Clang/LLVM)
9
9
  * - WebAssembly (persistent runner via Wasm)
10
+ * - PKIR (persistent runner via Wasm/PKIR)
10
11
  * - kVM Interpreter (envelope-free bytecode engine)
11
12
  * - Native JS (envelope-free convergence engine)
12
13
  *
@@ -66,7 +67,7 @@ function printHelp() {
66
67
 
67
68
  Multiplication Algorithm & Multi-Backend Performance Harness.
68
69
  Compares multiplication algorithms ('times' vs 'karatsuba') across
69
- available execution backends (LLVM, Wasm, kVM, JS).
70
+ available execution backends (LLVM, Wasm, PKIR, kVM, JS).
70
71
 
71
72
  Options:
72
73
  --mode <squaring|sizes>
@@ -75,7 +76,8 @@ Options:
75
76
  (default: squaring)
76
77
  --backends <csv>
77
78
  Comma-separated list of execution backends to test:
78
- llvm, wasm, kvm, js. (default: llvm,wasm,kvm,js)
79
+ llvm, llvm-pkir, wasm, pkir, kvm, js.
80
+ (default: llvm,llvm-pkir,wasm,pkir,kvm,js)
79
81
  --algos <csv>
80
82
  Comma-separated list of multiplication algorithms to test:
81
83
  times, karatsuba. (default: times,karatsuba)
@@ -114,7 +116,11 @@ if (hasFlag("-h") || hasFlag("--help")) {
114
116
  }
115
117
 
116
118
  const mode = getArg("--mode", "squaring"); // 'squaring' or 'sizes'
117
- const backendsArg = getArg("--backends", "llvm,wasm,kvm,js");
119
+ const defaultBackends = "llvm,llvm-pkir,wasm,pkir,kvm,js";
120
+ const backendsArg = getArg(
121
+ "--backends",
122
+ process.env.BACKENDS || defaultBackends
123
+ );
118
124
  const selectedBackends = backendsArg
119
125
  .split(",").map(b => b.trim().toLowerCase()).filter(Boolean);
120
126
  const algosArg = getArg("--algos", "times,karatsuba");
@@ -239,7 +245,7 @@ const cacheDir = makeCacheDir("k-mult-perf-");
239
245
  // 2. Prepare Squaring Relations & Binaries
240
246
  // For squaring: relation name `s_${algo} = {()x, ()y} ${algo}; s_${algo}`
241
247
  const algoRelations = {};
242
- const algoServers = { llvm: {}, wasm: {} };
248
+ const algoServers = { llvm: {}, "llvm-pkir": {}, wasm: {}, pkir: {} };
243
249
 
244
250
  let commonState = null;
245
251
 
@@ -285,6 +291,26 @@ for (const algo of selectedAlgos) {
285
291
  }
286
292
  }
287
293
 
294
+ // LLVM (via PKIR)
295
+ if (selectedBackends.includes("llvm-pkir")) {
296
+ const exePath = path.join(cacheDir, `perf_mult_llvm_pkir_${algo}`);
297
+ try {
298
+ compileObjectToExecutable(
299
+ algoRelations[algo].object, exePath, {
300
+ clangOpt: "-O2",
301
+ inputPattern: parseIntValue("0").pattern,
302
+ usePKIR: true
303
+ }
304
+ );
305
+ algoServers["llvm-pkir"][algo] =
306
+ new PersistentExecutable(exePath);
307
+ } catch (e) {
308
+ console.warn(
309
+ `[WARN] Failed to compile LLVM-PKIR for ${algo}: ${e.message}`
310
+ );
311
+ }
312
+ }
313
+
288
314
  // Wasm
289
315
  if (selectedBackends.includes("wasm")) {
290
316
  const wasmPath = path.join(cacheDir, `perf_mult_wasm_${algo}.wasm`);
@@ -309,6 +335,31 @@ for (const algo of selectedAlgos) {
309
335
  );
310
336
  }
311
337
  }
338
+
339
+ // PKIR
340
+ if (selectedBackends.includes("pkir")) {
341
+ const pkirPath = path.join(cacheDir, `perf_mult_pkir_${algo}.wasm`);
342
+ const exePath = path.join(cacheDir, `perf_mult_pkir_${algo}.exe`);
343
+ try {
344
+ const wasmBuf = await compileWasmArtifactFromObject(
345
+ algoRelations[algo].object, { entry: "__main__", usePKIR: true }
346
+ );
347
+ fs.writeFileSync(pkirPath, wasmBuf);
348
+ const runBin = fileURLToPath(
349
+ import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs")
350
+ );
351
+ fs.writeFileSync(
352
+ exePath,
353
+ `#!/bin/sh\nexec node "${runBin}" "${pkirPath}" "$@"\n`,
354
+ { mode: 0o755 }
355
+ );
356
+ algoServers.pkir[algo] = new PersistentExecutable(exePath);
357
+ } catch (e) {
358
+ console.warn(
359
+ `[WARN] Failed to compile PKIR for ${algo}: ${e.message}`
360
+ );
361
+ }
362
+ }
312
363
  }
313
364
 
314
365
  // 3. Squaring Mode Benchmark
@@ -327,8 +378,8 @@ if (mode === "squaring") {
327
378
  const stepBits = [];
328
379
 
329
380
  try {
330
- // A. Run persistent backends (LLVM, Wasm)
331
- const activeBackends = ["llvm", "wasm"]
381
+ // A. Run persistent backends (LLVM, LLVM-PKIR, Wasm, PKIR)
382
+ const activeBackends = ["llvm", "llvm-pkir", "wasm", "pkir"]
332
383
  .filter(b => selectedBackends.includes(b));
333
384
  for (const backend of activeBackends) {
334
385
  for (const algo of selectedAlgos) {
@@ -541,7 +592,7 @@ if (mode === "squaring") {
541
592
 
542
593
  } finally {
543
594
  // Teardown persistent servers
544
- for (const backend of ["llvm", "wasm"]) {
595
+ for (const backend of ["llvm", "llvm-pkir", "wasm", "pkir"]) {
545
596
  for (const algo of selectedAlgos) {
546
597
  algoServers[backend]?.[algo]?.child?.kill();
547
598
  }