@fraczak/k 7.0.0 → 7.0.2

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 (63) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/DOCS/BIG_PICTURE.md +5 -5
  3. package/DOCS/FILE_FORMATS.md +5 -5
  4. package/DOCS/OBJECT_FILE_AND_PATTERN.md +9 -9
  5. package/DOCS/PATTERNS.md +3 -3
  6. package/DOCS/PROFILING_AND_TRACING.md +60 -72
  7. package/DOCS/REPL.md +3 -3
  8. package/DOCS/book/03-types-as-finite-automata.md +4 -4
  9. package/DOCS/book/06-values-in-memory.md +9 -9
  10. package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
  11. package/DOCS/book/09-compiler-architecture.md +2 -2
  12. package/DOCS/book/10-from-ast-to-intermediate-representation.md +2 -2
  13. package/DOCS/book/14-canonical-serialization.md +1 -1
  14. package/Examples/arithmetics.k +2 -2
  15. package/Examples/bnat.k +1 -1
  16. package/Examples/karatsuba-mult.k +2 -2
  17. package/README.md +3 -8
  18. package/backend-api.mjs +9 -1
  19. package/backends/llvm/runtime/krt.c +33 -0
  20. package/backends/llvm/runtime/krt.h +1 -0
  21. package/backends/llvm/scripts/run-tests.mjs +5 -0
  22. package/backends/llvm/src/executable.mjs +4 -4
  23. package/backends/llvm/src/kvm2llvm.mjs +42 -19
  24. package/backends/llvm/src/llvm.mjs +30 -36
  25. package/backends/llvm/tests/integration.sh +2 -2
  26. package/backends/llvm/tests/perf-support.mjs +1 -55
  27. package/backends/llvm/tests/test-llvm.mjs +3 -3
  28. package/backends/llvm/tests/test-open-product-misalignment.mjs +140 -0
  29. package/backends/wasm/demo_ieee.mjs +2 -3
  30. package/backends/wasm/runtime.wat +157 -5
  31. package/backends/wasm/src/kvm2wasm.mjs +57 -34
  32. package/backends/wasm/src/wasm.mjs +40 -18
  33. package/backends/wasm/tests/perf-support.mjs +4 -4
  34. package/backends/wasm/tests/test-wasm.mjs +13 -14
  35. package/browser/vfs-data.mjs +6 -6
  36. package/core.k +8 -8
  37. package/harness/format.mjs +2 -2
  38. package/harness/registry.mjs +0 -2
  39. package/kir.mjs +299 -17
  40. package/package.json +1 -5
  41. package/scripts/benchmark-idempotency.mjs +12 -8
  42. package/scripts/benchmark.mjs +5 -9
  43. package/scripts/build-repl-html.mjs +2 -2
  44. package/scripts/perf-ieee.mjs +17 -118
  45. package/scripts/perf-int.mjs +313 -318
  46. package/scripts/perf-multiplication.mjs +10 -20
  47. package/scripts/perf-poly.mjs +29 -183
  48. package/tests/code-derivation/pattern-envelope.mjs +4 -4
  49. package/tests/test-kvm-polymorphic.mjs +2 -2
  50. package/tests/test-repl-html.mjs +15 -0
  51. package/tests/test-repl.mjs +10 -0
  52. package/backends/arm64/README.md +0 -103
  53. package/backends/arm64/bin/k-arm64-compile.mjs +0 -93
  54. package/backends/arm64/bin/k-arm64-run.mjs +0 -58
  55. package/backends/arm64/bin/k-arm64.mjs +0 -88
  56. package/backends/arm64/scripts/run-tests.mjs +0 -57
  57. package/backends/arm64/src/arm64.mjs +0 -229
  58. package/backends/arm64/src/cli.mjs +0 -155
  59. package/backends/arm64/src/kvm2arm64.mjs +0 -906
  60. package/backends/arm64/src/runtime.c +0 -744
  61. package/backends/arm64/tests/integration.sh +0 -112
  62. package/backends/arm64/tests/test-arm64.mjs +0 -240
  63. 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,31 @@ 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, and Wasm 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.
69
68
  -h, --help
70
69
  Show this help message and exit.
71
70
 
72
71
  Environment Variables:
73
72
  BACKENDS
74
- Target backend to run: 'llvm', 'wasm', or 'arm64'.
73
+ Target backend to run: 'llvm' or 'wasm'.
75
74
  LLVM_ONLY
76
75
  Run only LLVM backend ('1' to enable).
77
76
  WASM_ONLY
78
77
  Run only WebAssembly backend ('1' to enable).
79
- ARM64_ONLY
80
- Run only ARM64 backend ('1' to enable).
81
78
  BACKENDS_ONLY
82
79
  Skip JS and kVM interpreter baselines ('1' to enable).
80
+ SQUARING_LLVM_ONLY
81
+ Run squaring benchmark on LLVM only ('1' to enable).
83
82
  INPUTS
84
83
  Comma-separated integer strings to benchmark.
85
84
  ITERATIONS
86
85
  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
86
  LLVM_WARMUP_ITERATIONS
96
87
  Warmup iterations for LLVM backend (default: 1).
97
88
  LLVM_SPAWN_PER_CALL
@@ -125,15 +116,12 @@ if (process.argv.includes("-h") || process.argv.includes("--help")) {
125
116
 
126
117
  // Backend selection
127
118
  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");
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");
137
125
 
138
126
  const ops = ["plus", "minus", "times"];
139
127
  const benchNames = Object.fromEntries(ops.map(op => [op, `bench_${op}`]));
@@ -143,9 +131,6 @@ const inputTexts = csvEnv(
143
131
  );
144
132
  const iterations = parsePositiveIntEnv("ITERATIONS", 3);
145
133
 
146
- // ARM64 options
147
- const arm64WarmupIterations = parseNonNegativeIntEnv("ARM64_WARMUP_ITERATIONS", 1);
148
- const arm64OptLevel = process.env.ARM64_OPT || "-O2";
149
134
  if (!process.env.K_ARENA_CAPACITY_MB && !process.env.K_ARENA_MB) {
150
135
  process.env.K_ARENA_CAPACITY_MB = "4096";
151
136
  }
@@ -175,7 +160,7 @@ for (const op of ops) {
175
160
  }
176
161
 
177
162
  function intValue(text) {
178
- return valueForCode(parseIntValue(text), state.typeAliases.int, codes.find);
163
+ return parseIntValue(text);
179
164
  }
180
165
 
181
166
  console.log("==> Preparing relations");
@@ -315,39 +300,6 @@ function createWasmRunner(testSuite) {
315
300
  };
316
301
  }
317
302
 
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
- });
337
- }
338
- }
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]}`);
347
- }
348
- }
349
- }
350
-
351
303
  function printBenchmarkDescription() {
352
304
  const lanes = [];
353
305
  if (runBaselines) {
@@ -357,7 +309,6 @@ function printBenchmarkDescription() {
357
309
  }
358
310
  if (runLLVM) lanes.push(llvmLaneName());
359
311
  if (runWasm) lanes.push(wasmLaneName());
360
- if (runARM64) lanes.push(arm64LaneName(arm64OptLevel));
361
312
 
362
313
  console.log("==> Benchmark description");
363
314
  console.log(" source: @fraczak/k/Examples/arithmetics.k");
@@ -374,12 +325,6 @@ function printBenchmarkDescription() {
374
325
  console.log(` wasm warmup iterations: ${wasmWarmupIterations}`);
375
326
  if (!wasmPipe) console.log(` wasm arena reset: ${wasmReset ? "yes" : "no"}`);
376
327
  }
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"}`);
382
- }
383
328
  console.log(` iterations: ${iterations}`);
384
329
  console.log(` benchmark lanes: ${lanes.join("; ")}`);
385
330
  console.log(" conformance: all outputs are compared to native expected values");
@@ -458,26 +403,6 @@ if (runWasm) {
458
403
  }
459
404
  }
460
405
 
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);
467
- try {
468
- if (arm64WarmupIterations > 0) {
469
- console.log(`==> Warming Linux ARM64 (${arm64WarmupIterations} iterations)...`);
470
- await arm64Runner.run(arm64WarmupIterations);
471
- }
472
-
473
- console.log(`==> Running Linux ARM64 (${iterations} iterations)...`);
474
- arm64Result = await arm64Runner.run(iterations);
475
- } finally {
476
- arm64Runner.close();
477
- }
478
- }
479
- }
480
-
481
406
  console.log("\n=================== INTEGER BENCHMARK RESULTS ===================");
482
407
  console.log(`Operation calls per iteration: ${testSuite.length}`);
483
408
  console.log(`Total requested operation calls: ${iterations * testSuite.length}`);
@@ -494,10 +419,6 @@ if (runLLVM) {
494
419
  if (runWasm) {
495
420
  console.log(`${laneIndex++}. ${wasmLaneName().padEnd(29)} ${formatTiming(wasmResult)}`);
496
421
  }
497
- if (runARM64) {
498
- const timingStr = arm64Result ? formatTiming(arm64Result) : "compile failed";
499
- console.log(`${laneIndex++}. ${arm64LaneName(arm64OptLevel).padEnd(29)} ${timingStr}`);
500
- }
501
422
  console.log("=================================================================\n");
502
423
 
503
424
  // Conformance Validation
@@ -563,23 +484,6 @@ for (const tc of testSuite) {
563
484
  }
564
485
  }
565
486
 
566
- if (runARM64) {
567
- if (tc.arm64?.status !== "ok") {
568
- tc.arm64Conformance = "compile-failed";
569
- } else {
570
- try {
571
- const outputWire = await runExecutable(tc.arm64.exePath, tc.inputWire);
572
- const actual = decodeWire(outputWire).value;
573
- assert.deepEqual(toPlainObject(actual), toPlainObject(tc.expected));
574
- tc.arm64Conformance = "ok";
575
- } 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"));
580
- }
581
- }
582
- }
583
487
  }
584
488
 
585
489
  const validationSummary = [];
@@ -592,20 +496,15 @@ if (runWasm) {
592
496
  const wasmOk = testSuite.filter(tc => tc.wasmConformance === "ok").length;
593
497
  validationSummary.push(`Wasm ${wasmOk}/${testSuite.length}`);
594
498
  }
595
- if (runARM64) {
596
- const arm64Ok = testSuite.filter(tc => tc.arm64Conformance === "ok").length;
597
- validationSummary.push(`Linux ARM64 ${arm64Ok}/${testSuite.length}`);
598
- }
599
499
 
600
500
  console.log(`Conformance validation: ${validationSummary.join(", ")} cases match expected values.`);
601
501
  if (runLLVM && shouldStrictFail(testSuite)) process.exitCode = 1;
602
502
  if (runWasm && testSuite.some(tc => tc.wasmConformance !== "ok")) process.exitCode = 1;
603
- if (runARM64 && testSuite.some(tc => tc.arm64Conformance !== "ok")) process.exitCode = 1;
604
503
 
605
- // =========================================================================
504
+ // =======================================================================
606
505
  // Squaring Scale & Memory Profiling Benchmark
607
- // Expression: s = {()x,()y}times; 987654321 s s s s s s s s s s
608
- // =========================================================================
506
+ // Expressions: s = {()x,()y}times; and s = {()x,()y}karatsuba;
507
+ // =======================================================================
609
508
 
610
509
  function countBitsValue(val) {
611
510
  let count = 0;
@@ -644,189 +543,214 @@ const squaringInputText = process.env.SQUARING_INPUT || "987654321";
644
543
  const squaringStepTimeoutMs = parsePositiveIntEnv("SQUARING_STEP_TIMEOUT_MS", 60000);
645
544
 
646
545
  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`);
546
+ console.log(`Algorithms: times vs karatsuba (${squaringSteps} successive squarings doubling bit length)`);
649
547
 
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
- });
548
+ // Load and initialize Karatsuba multiplication relation
549
+ const karatsubaPath = fileURLToPath(import.meta.resolve("@fraczak/k/Examples/karatsuba-mult.k"));
550
+ const karatsubaSource = fs.readFileSync(karatsubaPath, "utf8");
551
+ const karatsubaLibrarySource = karatsubaSource.replace(/\s*\(\)\s*$/, "\n");
552
+ const stateKara = createState();
553
+ await evaluateInput(`:load ${karatsubaPath}`, stateKara);
554
+ codes.load(stateKara.codes);
656
555
 
657
556
  const sInitialVal = intValue(squaringInputText);
658
557
  const { inputWire: sInitWire, inputPattern: sInputPattern } = wireInput(sInitialVal);
659
558
 
559
+ const squaringAlgos = [
560
+ {
561
+ id: "times",
562
+ name: "times",
563
+ title: "Long Multiplication (times)",
564
+ expr: "s = {()x,()y}times;",
565
+ librarySource: arithmeticsLibrarySource,
566
+ sourceLabel: "@fraczak/k/Examples/arithmetics.k#s",
567
+ state: state,
568
+ relName: "s"
569
+ },
570
+ {
571
+ id: "karatsuba",
572
+ name: "karatsuba",
573
+ title: "Karatsuba Multiplication (karatsuba)",
574
+ expr: "s = {()x,()y}karatsuba;",
575
+ librarySource: karatsubaLibrarySource,
576
+ sourceLabel: "@fraczak/k/Examples/karatsuba-mult.k#s",
577
+ state: stateKara,
578
+ relName: "s"
579
+ }
580
+ ];
581
+
660
582
  // 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"
583
+ for (const algo of squaringAlgos) {
584
+ await evaluateInput(algo.expr, algo.state);
585
+ codes.load(algo.state.codes);
586
+ algo.rel = prepareRelation(algo.state, algo.relName, {
587
+ source: `${algo.librarySource}\n${algo.expr}\n${algo.relName}`,
588
+ sourceLabel: algo.sourceLabel
670
589
  });
671
- if (sLlvmCompiled.status === "ok") {
672
- sLlvmServer = new PersistentExecutable(sLlvmCompiled.exePath);
673
- }
674
- }
675
590
 
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);
591
+ algo.llvmServer = null;
592
+ if (runLLVM) {
593
+ const sLlvmCompiled = tryCompileCase({
594
+ object: algo.rel.object,
595
+ relationName: algo.rel.relationName,
596
+ relHash: algo.rel.relHash,
597
+ inputPattern: sInputPattern,
598
+ cacheDir,
599
+ sourceLabel: algo.sourceLabel
600
+ });
601
+ if (sLlvmCompiled.status === "ok") {
602
+ algo.llvmServer = new PersistentExecutable(sLlvmCompiled.exePath);
603
+ }
688
604
  }
689
- }
690
605
 
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
699
- });
700
- sArm64Server = new PersistentExecutable(sArm64Exe);
701
- } catch (e) {
702
- console.log("Failed to build ARM64 executable for 's':", e.message);
606
+ algo.wasmServer = null;
607
+ if (runWasm && !squaringLlvmOnly) {
608
+ try {
609
+ const sWasmPath = path.join(cacheDir, `perf_int_wasm_${algo.id}.wasm`);
610
+ const sWasmExe = path.join(cacheDir, `perf_int_wasm_${algo.id}.exe`);
611
+ const wasmBuf = await compileWasmArtifactFromObject(algo.rel.object, { entry: "__main__" });
612
+ fs.writeFileSync(sWasmPath, wasmBuf);
613
+ const runBin = fileURLToPath(import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs"));
614
+ fs.writeFileSync(sWasmExe, `#!/bin/sh\nexec node "${runBin}" "${sWasmPath}" "$@"\n`, { mode: 0o755 });
615
+ algo.wasmServer = new PersistentExecutable(sWasmExe);
616
+ } catch (e) {
617
+ console.log(`Failed to build Wasm executable for '${algo.id}':`, e.message);
618
+ }
703
619
  }
620
+
621
+ algo.results = {};
704
622
  }
705
623
 
706
624
  const squaringLanes = [];
707
- if (runBaselines) {
625
+ if (runBaselines && !squaringLlvmOnly) {
708
626
  squaringLanes.push({ id: "jsAware", name: "Native JS (Aware)", type: "jsAware", memLabel: "heap" });
709
627
  squaringLanes.push({ id: "jsFree", name: "Native JS (Free)", type: "jsFree", memLabel: "heap" });
710
628
  squaringLanes.push({ id: "kvm", name: "kVM (Env-Free)", type: "kvm", memLabel: "heap" });
711
629
  }
712
- if (runLLVM && sLlvmServer) {
713
- squaringLanes.push({ id: "llvm", name: llvmLaneName(), type: "persistent", server: sLlvmServer, memLabel: "peak RSS" });
714
- }
715
- if (runWasm && sWasmServer) {
716
- squaringLanes.push({ id: "wasm", name: wasmLaneName(), type: "persistent", server: sWasmServer, memLabel: "peak RSS" });
630
+ if (runLLVM) {
631
+ squaringLanes.push({ id: "llvm", name: llvmLaneName(), type: "persistent", memLabel: "peak RSS" });
717
632
  }
718
- if (runARM64 && sArm64Server) {
719
- squaringLanes.push({ id: "arm64", name: arm64LaneName(arm64OptLevel), type: "persistent", server: sArm64Server, memLabel: "peak RSS" });
633
+ if (runWasm && !squaringLlvmOnly) {
634
+ squaringLanes.push({ id: "wasm", name: wasmLaneName(), type: "persistent", memLabel: "peak RSS" });
720
635
  }
721
636
 
722
- const squaringResults = {};
723
- for (const lane of squaringLanes) squaringResults[lane.id] = [];
637
+ for (const algo of squaringAlgos) {
638
+ for (const lane of squaringLanes) {
639
+ algo.results[lane.id] = [];
640
+ }
641
+ }
724
642
  const stepMetadata = [];
725
643
 
726
644
  try {
727
- // 1. Persistent child processes (ARM64, Wasm, LLVM)
645
+ // 1. Persistent child processes (Wasm, LLVM)
728
646
  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
- };
647
+ for (const algo of squaringAlgos) {
648
+ const server = lane.id === "llvm" ? algo.llvmServer : algo.wasmServer;
649
+ if (!server) continue;
650
+ console.log(`==> Running ${lane.name} squaring [${algo.name}]...`);
651
+ let curWire = sInitWire;
652
+ for (let step = 1; step <= squaringSteps; step++) {
653
+ try {
654
+ const t0 = performance.now();
655
+ curWire = await server.request(curWire);
656
+ const t1 = performance.now();
657
+ const timeMs = t1 - t0;
658
+ const mem = getProcessMemory(server.child.pid);
659
+ algo.results[lane.id].push({ status: "ok", timeMs, memMb: mem.peakRssMb, wire: curWire });
660
+ if (!stepMetadata[step - 1]) {
661
+ stepMetadata[step - 1] = {
662
+ wireLen: curWire.length,
663
+ bits: null,
664
+ canonicalWire: curWire
665
+ };
666
+ }
667
+ if (timeMs > squaringStepTimeoutMs) break;
668
+ } catch (e) {
669
+ algo.results[lane.id].push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
670
+ break;
745
671
  }
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
672
  }
751
673
  }
752
674
  }
753
675
 
754
676
  // 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);
677
+ if (runBaselines && !squaringLlvmOnly) {
678
+ for (const algo of squaringAlgos) {
679
+ // JS Aware
680
+ console.log(`==> Running Native JS (Aware) squaring [${algo.name}]...`);
681
+ let curValAware = sInitialVal;
682
+ run.defs = algo.state;
683
+ for (let step = 1; step <= squaringSteps; step++) {
684
+ try {
685
+ const t0 = performance.now();
686
+ curValAware = run(codes.find, algo.rel.relDef.def, curValAware, algo.rel.relDef.typePatternGraph);
687
+ const t1 = performance.now();
688
+ const timeMs = t1 - t0;
689
+ const mem = getNodeMemory();
690
+ algo.results.jsAware.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValAware });
691
+ if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
692
+ stepMetadata[step - 1].bits = countBitsValue(curValAware);
693
+ }
694
+ if (timeMs > squaringStepTimeoutMs) break;
695
+ } catch (e) {
696
+ algo.results.jsAware.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
697
+ break;
770
698
  }
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
699
  }
776
- }
777
700
 
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);
701
+ // JS Free
702
+ console.log(`==> Running Native JS (Free) squaring [${algo.name}]...`);
703
+ let curValFree = sInitialVal;
704
+ run_converged.defs = algo.state;
705
+ for (let step = 1; step <= squaringSteps; step++) {
706
+ try {
707
+ const t0 = performance.now();
708
+ curValFree = run_converged(codes.find, algo.rel.relDef.def, curValFree, algo.rel.relDef.typePatternGraph);
709
+ const t1 = performance.now();
710
+ const timeMs = t1 - t0;
711
+ const mem = getNodeMemory();
712
+ algo.results.jsFree.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValFree });
713
+ if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
714
+ stepMetadata[step - 1].bits = countBitsValue(curValFree);
715
+ }
716
+ if (timeMs > squaringStepTimeoutMs) break;
717
+ } catch (e) {
718
+ algo.results.jsFree.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
719
+ break;
792
720
  }
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
721
  }
798
- }
799
722
 
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);
723
+ // kVM
724
+ console.log(`==> Running kVM Interpreter squaring [${algo.name}]...`);
725
+ let curValKvm = sInitialVal;
726
+ const kvmContext = { rels: algo.state.rels, findCode: codes.find, options: { envelopeFree: true } };
727
+ for (let step = 1; step <= squaringSteps; step++) {
728
+ try {
729
+ const t0 = performance.now();
730
+ curValKvm = executeKVM(algo.rel.kvmFunc, curValKvm, kvmContext);
731
+ const t1 = performance.now();
732
+ const timeMs = t1 - t0;
733
+ const mem = getNodeMemory();
734
+ algo.results.kvm.push({ status: "ok", timeMs, memMb: mem.heapMb, value: curValKvm });
735
+ if (stepMetadata[step - 1] && stepMetadata[step - 1].bits == null) {
736
+ stepMetadata[step - 1].bits = countBitsValue(curValKvm);
737
+ }
738
+ if (timeMs > squaringStepTimeoutMs) break;
739
+ } catch (e) {
740
+ algo.results.kvm.push({ status: "failed", error: e.message?.split("\n")[0] || String(e) });
741
+ break;
814
742
  }
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
743
  }
820
744
  }
821
745
  }
822
-
823
746
  } finally {
824
- if (sArm64Server) sArm64Server.close();
825
- if (sWasmServer) sWasmServer.close();
826
- if (sLlvmServer) sLlvmServer.close();
747
+ for (const algo of squaringAlgos) {
748
+ if (algo.wasmServer) algo.wasmServer.close();
749
+ if (algo.llvmServer) algo.llvmServer.close();
750
+ }
827
751
  }
828
752
 
829
- // For any remaining steps without bit counts, decode canonicalWire safely to get bits:
753
+ // For any remaining steps without bit counts, decode canonicalWire safely
830
754
  for (let step = 1; step <= squaringSteps; step++) {
831
755
  const meta = stepMetadata[step - 1];
832
756
  if (meta && meta.bits == null && meta.canonicalWire) {
@@ -838,87 +762,158 @@ for (let step = 1; step <= squaringSteps; step++) {
838
762
  }
839
763
 
840
764
  // 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])
765
+ const sep130 = "=".repeat(130);
766
+ const line130 = "-".repeat(130);
767
+
768
+ if (squaringLanes.length === 1 && squaringLanes[0].id === "llvm") {
769
+ // LLVM Only side-by-side comparison table
770
+ console.log(`\n${sep130}`);
771
+ console.log("=================== SQUARING SCALE & ALGORITHM BENCHMARK (LLVM) ===================");
772
+ console.log(`Algorithms: times vs karatsuba on ${squaringLanes[0].name}`);
773
+ console.log(`Initial: ${squaringInputText} (${countBitsValue(sInitialVal)} bits)`);
774
+ console.log(line130);
775
+
776
+ const tAlgo = squaringAlgos[0];
777
+ const kAlgo = squaringAlgos[1];
778
+ const colW = [4, 6, 7, 34, 34, 15];
779
+ const hCols = [
780
+ "Step",
781
+ "Bits",
782
+ "Wire(B)",
783
+ "LLVM times (peak RSS)",
784
+ "LLVM karatsuba (peak RSS)",
785
+ "Speedup (T/K)"
856
786
  ];
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";
787
+ console.log(hCols.map((h, i) => (i < 3 ? h.padStart(colW[i]) : h.padEnd(colW[i]))).join(" | "));
788
+ console.log(colW.map(w => "-".repeat(w)).join("-|-"));
789
+
790
+ for (let step = 1; step <= squaringSteps; step++) {
791
+ const meta = stepMetadata[step - 1] || { bits: "?", wireLen: "?" };
792
+ const tRes = tAlgo.results.llvm[step - 1];
793
+ const kRes = kAlgo.results.llvm[step - 1];
794
+
795
+ const formatCell = (res, w) => {
796
+ if (!res) return "-".padEnd(w);
797
+ if (res.status === "ok") {
798
+ const t = res.timeMs < 10 ? `${res.timeMs.toFixed(2)}ms` : `${res.timeMs.toFixed(1)}ms`;
799
+ const m = res.memMb != null ? `${res.memMb}MB` : "";
800
+ return `${t} / ${m}`.padEnd(w);
801
+ }
802
+ let errText = res.error || "error";
803
+ if (errText.includes("Maximum call stack")) errText = "Stack overflow (V8)";
804
+ else if (errText.includes("status null") || errText.includes("failed with status")) errText = "Arena limit reached";
805
+ return errText.slice(0, w - 1).padEnd(w);
806
+ };
807
+
808
+ let speedupStr = "-";
809
+ if (tRes?.status === "ok" && kRes?.status === "ok" && kRes.timeMs > 0) {
810
+ speedupStr = `${(tRes.timeMs / kRes.timeMs).toFixed(2)}x`;
811
+ }
812
+
813
+ const row = [
814
+ String(step).padStart(colW[0]),
815
+ String(meta.bits ?? "?").padStart(colW[1]),
816
+ String(meta.wireLen ?? "?").padStart(colW[2]),
817
+ formatCell(tRes, colW[3]),
818
+ formatCell(kRes, colW[4]),
819
+ speedupStr.padEnd(colW[5])
820
+ ];
821
+ console.log(row.join(" | "));
822
+ }
823
+ console.log(sep130);
824
+ } else {
825
+ // Print each algorithm table
826
+ for (const algo of squaringAlgos) {
827
+ console.log(`\n${sep130}`);
828
+ console.log(`=================== SQUARING SCALE: ${algo.title.toUpperCase()} ===================`);
829
+ console.log(`Expression: ${algo.expr} ${squaringInputText} ${"s ".repeat(squaringSteps).trim()}`);
830
+ console.log(`Initial: ${squaringInputText} (${countBitsValue(sInitialVal)} bits)`);
831
+ console.log(line130);
832
+
833
+ const headerCols = ["Step", "Bits", "Wire(B)", ...squaringLanes.map(l => `${l.name} (${l.memLabel})`)];
834
+ const colWidths = [4, 6, 7, ...squaringLanes.map(l => Math.max(22, l.name.length + l.memLabel.length + 5))];
835
+ console.log(headerCols.map((h, i) => (i < 3 ? h.padStart(colWidths[i]) : h.padEnd(colWidths[i]))).join(" | "));
836
+ console.log(colWidths.map(w => "-".repeat(w)).join("-|-"));
837
+
838
+ for (let step = 1; step <= squaringSteps; step++) {
839
+ const meta = stepMetadata[step - 1] || { bits: "?", wireLen: "?" };
840
+ const cols = [
841
+ String(step).padStart(colWidths[0]),
842
+ String(meta.bits ?? "?").padStart(colWidths[1]),
843
+ String(meta.wireLen ?? "?").padStart(colWidths[2])
844
+ ];
845
+ for (let li = 0; li < squaringLanes.length; li++) {
846
+ const lane = squaringLanes[li];
847
+ const res = algo.results[lane.id][step - 1];
848
+ const w = colWidths[3 + li];
849
+ if (!res) {
850
+ cols.push("-".padEnd(w));
851
+ } else if (res.status === "ok") {
852
+ const t = res.timeMs < 10 ? `${res.timeMs.toFixed(2)}ms` : `${res.timeMs.toFixed(1)}ms`;
853
+ const m = res.memMb != null ? `${res.memMb}MB` : "";
854
+ cols.push(`${t} / ${m}`.padEnd(w));
855
+ } else {
856
+ let errText = res.error;
857
+ if (errText.includes("Maximum call stack")) {
858
+ errText = "Stack overflow (V8)";
859
+ } else if (errText.includes("status null") || errText.includes("failed with status")) {
860
+ errText = "Arena limit reached";
861
+ }
862
+ cols.push(errText.slice(0, w - 1).padEnd(w));
863
+ }
873
864
  }
874
- cols.push(errText.slice(0, w - 1).padEnd(w));
865
+ console.log(cols.join(" | "));
875
866
  }
867
+ console.log(sep130);
876
868
  }
877
- console.log(cols.join(" | "));
878
869
  }
879
- console.log("==================================================================================================================================");
880
870
 
881
- // Conformance check across all computed steps
871
+ // Conformance check across all computed steps and algorithms
882
872
  let squaringConformanceOk = true;
883
873
  for (let step = 1; step <= squaringSteps; step++) {
884
874
  const meta = stepMetadata[step - 1];
885
875
  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)) {
876
+ for (const algo of squaringAlgos) {
877
+ for (const lane of squaringLanes) {
878
+ const res = algo.results[lane.id][step - 1];
879
+ if (res?.status === "ok") {
880
+ if (res.wire) {
881
+ if (Buffer.compare(res.wire, meta.canonicalWire) !== 0) {
898
882
  squaringConformanceOk = false;
899
- console.log(`Squaring conformance mismatch on step ${step} (${lane.name})`);
883
+ console.log(`Squaring conformance mismatch on step ${step} (${algo.name} / ${lane.name})`);
900
884
  }
901
- } catch {}
885
+ } else if (res.value) {
886
+ try {
887
+ const wireVal = decodeWire(meta.canonicalWire).value;
888
+ if (!safeDeepEqual(res.value, wireVal)) {
889
+ squaringConformanceOk = false;
890
+ console.log(`Squaring conformance mismatch on step ${step} (${algo.name} / ${lane.name})`);
891
+ }
892
+ } catch {}
893
+ }
902
894
  }
903
895
  }
904
896
  }
905
897
  }
906
898
 
907
899
  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)})`;
900
+ for (const algo of squaringAlgos) {
901
+ console.log(` Algorithm: ${algo.title}`);
902
+ for (const lane of squaringLanes) {
903
+ const okSteps = algo.results[lane.id].filter(r => r.status === "ok");
904
+ const maxStep = okSteps.length;
905
+ const lastOk = okSteps[okSteps.length - 1];
906
+ const peakMem = okSteps.reduce((max, r) => Math.max(max, r.memMb || 0), 0);
907
+ const failureReason = algo.results[lane.id][maxStep]?.error;
908
+ let statusDetail = "";
909
+ if (failureReason) {
910
+ if (failureReason.includes("Maximum call stack")) statusDetail = " (V8 call stack overflow)";
911
+ else if (failureReason.includes("status null") || failureReason.includes("failed with status")) statusDetail = " (arena capacity limit reached)";
912
+ else statusDetail = ` (${failureReason.slice(0, 30)})`;
913
+ }
914
+ const timeStr = lastOk ? ` (step ${maxStep} in ${lastOk.timeMs < 10 ? lastOk.timeMs.toFixed(2) : lastOk.timeMs.toFixed(1)}ms)` : "";
915
+ console.log(` - ${lane.name.padEnd(32)}: reached Step ${String(maxStep).padStart(2)}/${squaringSteps}${timeStr}, peak ${lane.memLabel}: ${peakMem}MB${statusDetail}`);
919
916
  }
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}`);
922
917
  }
923
918
 
924
919
  if (squaringConformanceOk) {