@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
@@ -1489,6 +1489,39 @@ k_value *k_read_wire(FILE *in, k_rt *rt) {
1489
1489
  return value;
1490
1490
  }
1491
1491
 
1492
+ static int is_union_kind(int kind) {
1493
+ return kind == KP_OPEN_UNION || kind == KP_CLOSED_UNION;
1494
+ }
1495
+
1496
+ static int is_product_kind(int kind) {
1497
+ return kind == KP_OPEN_PRODUCT || kind == KP_CLOSED_PRODUCT;
1498
+ }
1499
+
1500
+ int k_pattern_compatible(k_pattern *a, k_pattern *b) {
1501
+ if (a == b) return 1;
1502
+ if (a == NULL || b == NULL) return 0;
1503
+ if (a->node_count != b->node_count) return 0;
1504
+ for (size_t i = 0; i < a->node_count; i++) {
1505
+ k_pattern_node *left = &a->nodes[i];
1506
+ k_pattern_node *right = &b->nodes[i];
1507
+ if (left->kind != right->kind) {
1508
+ if (!(is_union_kind(left->kind) && is_union_kind(right->kind)) &&
1509
+ !(is_product_kind(left->kind) && is_product_kind(right->kind))) {
1510
+ return 0;
1511
+ }
1512
+ }
1513
+ if (left->edge_count != right->edge_count) return 0;
1514
+ for (size_t j = 0; j < left->edge_count; j++) {
1515
+ if (left->edges[j].target != right->edges[j].target) return 0;
1516
+ if (!bytes_equal(left->edges[j].label, left->edges[j].label_length,
1517
+ right->edges[j].label, right->edges[j].label_length)) {
1518
+ return 0;
1519
+ }
1520
+ }
1521
+ }
1522
+ return 1;
1523
+ }
1524
+
1492
1525
  int k_pattern_equal(k_pattern *a, k_pattern *b) {
1493
1526
  if (a == b) return 1;
1494
1527
  if (a == NULL || b == NULL) return 0;
@@ -104,6 +104,7 @@ k_wire_input k_read_wire_envelope(FILE *in, k_rt *rt);
104
104
  k_wire_input k_decode_wire_envelope(const unsigned char *bytes, size_t length, k_rt *rt);
105
105
  void k_wire_input_free(k_wire_input input);
106
106
  int k_pattern_equal(k_pattern *a, k_pattern *b);
107
+ int k_pattern_compatible(k_pattern *a, k_pattern *b);
107
108
  k_wire_prefix k_wire_prefix_for_pattern(k_pattern *pattern);
108
109
  void k_wire_prefix_free(k_wire_prefix prefix);
109
110
  k_value *k_decode_wire_value_with_prefix(const unsigned char *bytes, size_t length, k_wire_prefix *prefix, k_pattern *pattern, k_rt *rt);
@@ -24,6 +24,11 @@ if (process.argv.includes("-h") || process.argv.includes("--help")) {
24
24
 
25
25
  const tests = [
26
26
  ["tests/test-llvm.mjs", node, ["tests/test-llvm.mjs"]],
27
+ [
28
+ "tests/test-open-product-misalignment.mjs",
29
+ node,
30
+ ["tests/test-open-product-misalignment.mjs", "--assert"]
31
+ ],
27
32
  ["tests/integration.sh", path.join(root, "tests/integration.sh"), []],
28
33
  ["scripts/conformance.mjs", node, ["scripts/conformance.mjs"]]
29
34
  ];
@@ -292,7 +292,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
292
292
  " k_wire_input_free(input);",
293
293
  " return 2;",
294
294
  " }",
295
- " if (!k_pattern_equal(input.pattern, &compiled_input_pattern)) {",
295
+ " if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
296
296
  " k_wire_input_free(input);",
297
297
  " return 5;",
298
298
  " }",
@@ -350,7 +350,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
350
350
  " k_value *in_val = k_decode_wire_value_with_prefix(payload, length, &input_prefix, &compiled_input_pattern, rt);",
351
351
  " if (in_val == 0) {",
352
352
  " k_wire_input envelope = k_decode_wire_envelope(payload, length, rt);",
353
- " if (envelope.value != 0 && envelope.pattern != 0 && k_pattern_equal(envelope.pattern, &compiled_input_pattern)) {",
353
+ " if (envelope.value != 0 && envelope.pattern != 0 && k_pattern_compatible(envelope.pattern, &compiled_input_pattern)) {",
354
354
  " in_val = envelope.value;",
355
355
  " } else {",
356
356
  " k_wire_input_free(envelope);",
@@ -437,7 +437,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
437
437
  " k_rt_free(input_rt);",
438
438
  " return 2;",
439
439
  " }",
440
- " if (!k_pattern_equal(input.pattern, &compiled_input_pattern)) {",
440
+ " if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
441
441
  " k_wire_input_free(input);",
442
442
  " k_rt_free(run_rt);",
443
443
  " k_rt_free(input_rt);",
@@ -478,7 +478,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
478
478
  " k_rt_free(rt);",
479
479
  " return 2;",
480
480
  " }",
481
- " if (!k_pattern_equal(input.pattern, &compiled_input_pattern)) {",
481
+ " if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
482
482
  " k_wire_input_free(input);",
483
483
  " k_wire_prefix_free(output_prefix);",
484
484
  " k_rt_free(rt);",
@@ -35,15 +35,17 @@ function alignArenaSize(size) {
35
35
  }
36
36
 
37
37
  function cStringBytes(text) {
38
- return [...Buffer.from(text, "utf8"), 0]
39
- .map((byte) => {
40
- if (byte === 10) return "\\0A";
41
- if (byte === 34) return "\\22";
42
- if (byte === 92) return "\\5C";
43
- if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
44
- return `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
45
- })
46
- .join("");
38
+ const buf = Buffer.from(text, "utf8");
39
+ let out = "";
40
+ for (let i = 0; i < buf.length; i++) {
41
+ const byte = buf[i];
42
+ if (byte === 10) out += "\\0A";
43
+ else if (byte === 34) out += "\\22";
44
+ else if (byte === 92) out += "\\5C";
45
+ else if (byte >= 32 && byte <= 126) out += String.fromCharCode(byte);
46
+ else out += `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
47
+ }
48
+ return out + "\\00";
47
49
  }
48
50
 
49
51
  export function runtimeDeclarations() {
@@ -551,8 +553,7 @@ export function compileKVMModuleToLLVM(entryName, kvmProgram, options = {}) {
551
553
  const payload = JSON.stringify({
552
554
  format: ARTIFACT_FORMAT,
553
555
  version: ARTIFACT_VERSION,
554
- relation,
555
- kvm: kvmProgram
556
+ relation
556
557
  });
557
558
  const payloadBytes = Buffer.byteLength(payload, "utf8") + 1;
558
559
  const symbol = llvmIdentifier(options.symbol || relation);
@@ -678,7 +679,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
678
679
  const label = ctx.labelRef(inst.label);
679
680
  let val;
680
681
  if (edgeIndex >= 0 && ctx.runtimeMode !== "compact") {
681
- val = lowerProductGetAt(ctx, input, edgeIndex, label);
682
+ val = lowerProductGetAt(ctx, input, edgeIndex, null);
682
683
  } else {
683
684
  val = ctx.tempName("field");
684
685
  ctx.lines.push(` ${val} = call ptr @k_product_get_n(ptr ${input}, ptr ${label.pointer}, i64 ${label.length})`);
@@ -815,11 +816,32 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
815
816
  storeI64At(ctx, product, K_VALUE_PRODUCT_CAPACITY_OFFSET, N);
816
817
  storePtrAt(ctx, product, K_VALUE_PRODUCT_FIELDS_OFFSET, fields);
817
818
 
818
- const sortedFields = [...fieldValues].sort((a, b) => {
819
- if (a.label < b.label) return -1;
820
- if (a.label > b.label) return 1;
821
- return 0;
822
- });
819
+ let sortedFields;
820
+ if (Array.isArray(inst.pattern) && inst.pattern.length > 0) {
821
+ const root = inst.pattern[0];
822
+ if (
823
+ (root[0] === "open-product" ||
824
+ root[0] === "closed-product") &&
825
+ Array.isArray(root[1])
826
+ ) {
827
+ const order = new Map(root[1].map(([l], i) => [l, i]));
828
+ sortedFields = [...fieldValues].sort((a, b) => {
829
+ const aI = order.has(a.label)
830
+ ? order.get(a.label)
831
+ : 999999;
832
+ const bI = order.has(b.label)
833
+ ? order.get(b.label)
834
+ : 999999;
835
+ if (aI !== bI) return aI - bI;
836
+ return a.label.localeCompare(b.label);
837
+ });
838
+ }
839
+ }
840
+ if (!sortedFields) {
841
+ sortedFields = [...fieldValues].sort((a, b) =>
842
+ a.label.localeCompare(b.label)
843
+ );
844
+ }
823
845
 
824
846
  for (let sIdx = 0; sIdx < N; sIdx++) {
825
847
  const { label: fLabel, val: fVal } = sortedFields[sIdx];
@@ -831,13 +853,14 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
831
853
  }
832
854
 
833
855
  regValues.set(cleanReg(inst.dest), product);
856
+ if (inst.pattern) regPatterns.set(cleanReg(inst.dest), inst.pattern);
834
857
  break;
835
858
  }
836
859
  case "union": {
837
860
  const uInput = getReg(inst.src);
838
861
 
839
862
  if (!isTopLevel) {
840
- // Nested union: synthesize a helper function @k_union_expr_${index}
863
+ // Nested union: synthesize helper @k_union_expr_${index}
841
864
  const exprIndex = moduleCtx.unionExprIndex++;
842
865
  const exprName = `@k_union_expr_${exprIndex}`;
843
866
  const exprFunc = {
@@ -862,7 +885,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
862
885
  break;
863
886
  }
864
887
 
865
- // Top-level union: synthesize arm functions @k_union_arm_${index}
888
+ // Top-level union: synthesize arms @k_union_arm_${index}
866
889
  const mark = lowerRtMark(ctx);
867
890
  const unionDone = ctx.blockName("union_done");
868
891
  const N = inst.branches.length;
@@ -45,15 +45,17 @@ function alignArenaSize(size) {
45
45
  }
46
46
 
47
47
  function cStringBytes(text) {
48
- return [...Buffer.from(text, "utf8"), 0]
49
- .map((byte) => {
50
- if (byte === 10) return "\\0A";
51
- if (byte === 34) return "\\22";
52
- if (byte === 92) return "\\5C";
53
- if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
54
- return `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
55
- })
56
- .join("");
48
+ const buf = Buffer.from(text, "utf8");
49
+ let out = "";
50
+ for (let i = 0; i < buf.length; i++) {
51
+ const byte = buf[i];
52
+ if (byte === 10) out += "\\0A";
53
+ else if (byte === 34) out += "\\22";
54
+ else if (byte === 92) out += "\\5C";
55
+ else if (byte >= 32 && byte <= 126) out += String.fromCharCode(byte);
56
+ else out += `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
57
+ }
58
+ return out + "\\00";
57
59
  }
58
60
 
59
61
  function readPattern(inputPattern) {
@@ -986,8 +988,7 @@ export function emitLLVMModule(input, options = {}) {
986
988
  const payload = JSON.stringify({
987
989
  format: ARTIFACT_FORMAT,
988
990
  version: ARTIFACT_VERSION,
989
- relation,
990
- kir
991
+ relation
991
992
  });
992
993
  const payloadBytes = Buffer.byteLength(payload, "utf8") + 1;
993
994
  const symbol = llvmIdentifier(options.symbol || relation);
@@ -1011,8 +1012,7 @@ export function emitLLVMModule(input, options = {}) {
1011
1012
  export function compileObjectToLLVM(object, options = {}) {
1012
1013
  const inputPattern = options.inputPattern ? readPattern(options.inputPattern) : null;
1013
1014
  const relation = options.relation || object.main;
1014
-
1015
- let retypedKir = null;
1015
+ let targetObject = object;
1016
1016
  let entryInputPattern = inputPattern;
1017
1017
  let outputPattern = null;
1018
1018
 
@@ -1020,43 +1020,37 @@ export function compileObjectToLLVM(object, options = {}) {
1020
1020
  const retyped = retypeObjectRelationForBackend(object, relation, inputPattern, {
1021
1021
  source: options.source || "<k-llvm>"
1022
1022
  });
1023
- retypedKir = retyped.kir;
1023
+ targetObject = retyped.retypedObject;
1024
1024
  entryInputPattern = retyped.inputPattern;
1025
1025
  outputPattern = retyped.outputPattern;
1026
- } else {
1027
- retypedKir = objectToKIRP(object);
1028
1026
  }
1029
1027
 
1030
- let kvmArtifact = objectToKVMArtifact(object, relation, null, options);
1031
- let specializedKvm = kvmArtifact;
1032
- if (inputPattern) {
1033
- try {
1034
- specializedKvm = specializeKVM(kvmArtifact, inputPattern);
1035
- } catch {
1036
- specializedKvm = {
1037
- ...kvmArtifact,
1038
- functions: lowerKIRToKVM(retypedKir),
1039
- entry: relation
1040
- };
1041
- }
1042
- }
1043
-
1044
- const entry = specializedKvm.entry || relation;
1045
- const functions = specializedKvm.functions;
1028
+ const retypedKir = objectToKIRP(targetObject);
1029
+ const functions = lowerKIRToKVM(retypedKir);
1030
+ const entry = relation || targetObject.main || "__main__";
1031
+
1032
+ const specializedKvm = {
1033
+ format: "k-vm",
1034
+ version: 1,
1035
+ entry,
1036
+ functions,
1037
+ inputPattern: entryInputPattern,
1038
+ outputPattern
1039
+ };
1046
1040
 
1047
1041
  const llvm = compileKVMModuleToLLVM(entry, functions, {
1048
1042
  ...options,
1049
1043
  relation,
1050
- inputPattern: entryInputPattern || specializedKvm.inputPattern,
1051
- outputPattern: outputPattern || specializedKvm.outputPattern
1044
+ inputPattern: entryInputPattern,
1045
+ outputPattern
1052
1046
  });
1053
1047
 
1054
1048
  return {
1055
1049
  kir: retypedKir,
1056
1050
  kvm: specializedKvm,
1057
1051
  relation,
1058
- inputPattern: entryInputPattern || specializedKvm.inputPattern,
1059
- outputPattern: outputPattern || specializedKvm.outputPattern,
1052
+ inputPattern: entryInputPattern,
1053
+ outputPattern,
1060
1054
  llvm
1061
1055
  };
1062
1056
  }
@@ -112,7 +112,7 @@ node --input-type=module -e "import { stdout } from 'node:process'; import { enc
112
112
  | node --input-type=module -e "import { stdin } from 'node:process'; import { decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; const chunks = []; stdin.on('data', (chunk) => chunks.push(chunk)); stdin.on('end', () => console.log(JSON.stringify(decodeWire(Buffer.concat(chunks)).pattern)));" \
113
113
  | grep -Fqx '[["open-union",[["left",1],["right",1]]],["closed-product",[]]]'
114
114
 
115
- node ./bin/k-llvm-build.mjs '$bits = < {} _, bits 0, bits 1 >; $int = < bits "+", bits "-" >; $int' -o "$TMP_DIR/id-int-exe"
115
+ node ./bin/k-llvm-build.mjs '?< <bits 0, bits 1, {} _>=bits "+", bits "-">' -o "$TMP_DIR/id-int-exe"
116
116
  printf '2' \
117
117
  | node ../../codecs/int.mjs --parse \
118
118
  | "$TMP_DIR/id-int-exe" \
@@ -121,7 +121,7 @@ printf '2' \
121
121
 
122
122
  node --input-type=module -e "import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { encodeToWire, decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; import { parse, INT_PATTERN } from '../../codecs/int.mjs'; const input = encodeToWire(parse('2'), INT_PATTERN); const header = Buffer.alloc(4); header.writeUInt32BE(input.length); const child = spawn(process.argv[1], ['--server']); const chunks = []; const stderr = []; child.stdout.on('data', (chunk) => chunks.push(chunk)); child.stderr.on('data', (chunk) => stderr.push(chunk)); child.stdin.end(Buffer.concat([header, input])); const status = await new Promise((resolve, reject) => { child.on('error', reject); child.on('close', resolve); }); assert.equal(status, 0, Buffer.concat(stderr).toString('utf8')); const output = Buffer.concat(chunks); const length = output.readUInt32BE(0); assert.equal(output.length, 4 + length); assert.deepEqual(decodeWire(output.subarray(4)).value.toJSON(), parse('2').toJSON());" "$TMP_DIR/id-int-exe"
123
123
 
124
- node ./bin/k-llvm-build.mjs --lib ../../Examples/arithmetics.k --export times 's = {()x, ()y} times; s' -o "$TMP_DIR/square-exe"
124
+ node ./bin/k-llvm-build.mjs --lib ../../Examples/arithmetics.k --export times 's = ?< <bits 0, bits 1, {} _>=bits "+", bits "-" > {()x, ()y} times; s' -o "$TMP_DIR/square-exe"
125
125
  printf '987654321' \
126
126
  | node ../../codecs/int.mjs --parse \
127
127
  | "$TMP_DIR/square-exe" \
@@ -216,7 +216,7 @@ export function runExecutableMainBench(exePath, inputWire, calls) {
216
216
  reject(new Error(`${exePath} --bench-main failed with status ${status}\n${text}`.trim()));
217
217
  return;
218
218
  }
219
- const match = text.match(/K_(?:LLVM|ARM64)_BENCH_MAIN calls=(\d+) total_ns=(\d+) per_call_ns=([0-9.]+)/);
219
+ const match = text.match(/K_LLVM_BENCH_MAIN calls=(\d+) total_ns=(\d+) per_call_ns=([0-9.]+)/);
220
220
  if (!match) {
221
221
  reject(new Error(`${exePath} --bench-main did not report timing\n${text}`.trim()));
222
222
  return;
@@ -599,61 +599,7 @@ export function llvmLaneName() {
599
599
  : "LLVM Executable (persistent)";
600
600
  }
601
601
 
602
- export function arm64LaneName(optLevel = "-O2") {
603
- const mode = process.env.ARM64_SPAWN_PER_CALL === "1"
604
- ? "spawn/call"
605
- : (process.env.ARM64_PIPELINE === "1" ? "persistent, pipelined" : "persistent");
606
- return `Linux ARM64 ${optLevel} (${mode})`;
607
- }
608
-
609
- export function createARM64Runner(testSuite) {
610
- const arm64Cases = testSuite.filter(tc => tc.arm64?.status === "ok");
611
- if (process.env.ARM64_SPAWN_PER_CALL === "1") {
612
- return {
613
- run(iterations) {
614
- if (arm64Cases.length === 0) return null;
615
- return runTimedIterationsAsync(iterations, async () => {
616
- for (const tc of arm64Cases) {
617
- await runExecutable(tc.arm64.exePath, tc.inputWire);
618
- }
619
- });
620
- },
621
- close() {}
622
- };
623
- }
624
602
 
625
- const servers = new Map();
626
- function serverFor(exePath) {
627
- let server = servers.get(exePath);
628
- if (server == null) {
629
- server = new PersistentExecutable(exePath);
630
- servers.set(exePath, server);
631
- }
632
- return server;
633
- }
634
-
635
- return {
636
- async run(iterations) {
637
- if (arm64Cases.length === 0) return null;
638
- return runTimedIterationsAsync(iterations, async () => {
639
- if (process.env.ARM64_PIPELINE === "1") {
640
- const pending = [];
641
- for (const tc of arm64Cases) {
642
- pending.push(serverFor(tc.arm64.exePath).request(tc.inputWire));
643
- }
644
- await Promise.all(pending);
645
- return;
646
- }
647
- for (const tc of arm64Cases) {
648
- await serverFor(tc.arm64.exePath).request(tc.inputWire);
649
- }
650
- });
651
- },
652
- close() {
653
- for (const server of servers.values()) server.close();
654
- }
655
- };
656
- }
657
603
 
658
604
  export function average(times) {
659
605
  return times.reduce((sum, time) => sum + time, 0) / times.length;
@@ -239,11 +239,11 @@ assert.doesNotMatch(nestedUnionLLVM, /call void @k_product_set_at\(ptr %product\
239
239
  assert.doesNotMatch(nestedUnionLLVM, /ptr false/);
240
240
 
241
241
  const productRecurseObject = decodeObject(compileObjectBuffer(`
242
- $ list = < {} nil, { {} car, list cdr } cons >;
243
- foo = $list <
242
+ list? = ?< {} nil, { {} car, list? cdr } cons >;
243
+ foo = list? <
244
244
  /nil |nil,
245
245
  /cons { .car car, .cdr foo cdr } |cons
246
- > $list;
246
+ > list?;
247
247
  foo
248
248
  `, { source: "llvm-product-recurse.k" }));
249
249
  const { llvm: productRecurseLLVM } = compileObjectToLLVM(productRecurseObject, {
@@ -0,0 +1,140 @@
1
+ import assert from "node:assert/strict";
2
+ import { compileObjectBuffer, decodeObject } from "@fraczak/k/object.mjs";
3
+ import { compileObjectToLLVM } from "../src/llvm.mjs";
4
+ import { compileAndRunLLVM } from "../src/executable.mjs";
5
+ import { Value } from "@fraczak/k/Value.mjs";
6
+
7
+ console.log("==> LLVM Open Product Field Alignment Test");
8
+
9
+ // Test expression from user:
10
+ // len_ = ?X <{.list/nil if, .len then} .then,
11
+ // {.list/cons.cdr list, .len inc len} ?X len_ >;
12
+ const src = `
13
+ 0 = {}|0;
14
+ inc = |1;
15
+ len = {()list, 0 len} len_;
16
+ len_ = ?X <{.list/nil if, .len then} .then, {.list/cons.cdr list, .len inc len} ?X len_ >;
17
+ len
18
+ `;
19
+
20
+ // Input schema for list:
21
+ // cons is a closed product with fields 'car' (index 0) and
22
+ // 'cdr' (index 1).
23
+ const listPattern = [
24
+ ["closed-union", [["cons", 1], ["nil", 2]]],
25
+ ["closed-product", [["car", 3], ["cdr", 0]]],
26
+ ["closed-product", []],
27
+ ["any", []]
28
+ ];
29
+
30
+ const obj = decodeObject(
31
+ compileObjectBuffer(src, { source: "open-product-misalignment.k" })
32
+ );
33
+ const { llvm } = compileObjectToLLVM(obj, { inputPattern: listPattern });
34
+
35
+ // -----------------------------------------------------------------------
36
+ // Test 1: Static IR Field Index for .cdr
37
+ // -----------------------------------------------------------------------
38
+ // In cons payload { car, cdr }, 'car' is index 0 (byte offset 0) and
39
+ // 'cdr' is index 1 (byte offset 24 = 1 * K_FIELD_SIZE).
40
+ // The specialized LLVM IR for projecting .cdr MUST access byte offset 24.
41
+ // When misaligned, it accesses byte offset 0 (which is 'car').
42
+
43
+ const cdrLabelMatch = llvm.match(
44
+ /(@k_label_\d+)\s*=\s*private unnamed_addr constant \[4 x i8\] c"cdr\\00"/
45
+ );
46
+ assert.ok(cdrLabelMatch, "LLVM IR should define label constant for 'cdr'");
47
+ const cdrLabelGlobal = cdrLabelMatch[1];
48
+
49
+ const lines = llvm.split("\n");
50
+ let cdrFieldSlotOffset = null;
51
+ let cdrDynamicFallbackEmitted = false;
52
+
53
+ for (let i = 0; i < lines.length; i++) {
54
+ if (
55
+ lines[i].includes(cdrLabelGlobal) &&
56
+ lines[i].includes("getelementptr inbounds [4 x i8]")
57
+ ) {
58
+ const localLabelVar = lines[i].trim().split("=")[0].trim();
59
+ for (let j = i; j < Math.min(lines.length, i + 35); j++) {
60
+ if (
61
+ lines[j].includes("getelementptr inbounds i8, ptr %product_fields_ptr") &&
62
+ lines[j].includes(", i64 ")
63
+ ) {
64
+ const offsetMatch = lines[j].match(/, i64 (\d+)/);
65
+ if (offsetMatch) cdrFieldSlotOffset = Number(offsetMatch[1]);
66
+ }
67
+ if (
68
+ lines[j].includes("call ptr @k_product_get_n") &&
69
+ lines[j].includes(localLabelVar)
70
+ ) {
71
+ cdrDynamicFallbackEmitted = true;
72
+ }
73
+ }
74
+ if (cdrFieldSlotOffset !== null) break;
75
+ }
76
+ }
77
+
78
+ console.log(`- Detected .cdr field slot byte offset: ${cdrFieldSlotOffset} (expected 24)`);
79
+ console.log(`- Detected dynamic fallback (@k_product_get_n) emitted for .cdr: ${cdrDynamicFallbackEmitted}`);
80
+
81
+ // -----------------------------------------------------------------------
82
+ // Test 2: Execution without dynamic fallback
83
+ // -----------------------------------------------------------------------
84
+ // When dynamic fallback (@k_product_get_n) is removed from the emitted
85
+ // LLVM IR (forcing runtime to rely on the statically generated index),
86
+ // the misalignment causes .cdr projection to load 'car' (a leaf),
87
+ // causing the next iteration of len_ to fail because 'car' is not a list.
88
+ const llvmNoFallback = llvm.replace(
89
+ /call ptr @k_product_get_n\([^)]+\)/g,
90
+ "bitcast ptr null to ptr"
91
+ );
92
+
93
+ // Build test list: [1, 2] = cons(car: 1, cdr: cons(car: 2, cdr: nil))
94
+ const nil = Value.variant("nil", Value.product({}));
95
+ const cons2 = Value.variant("cons", Value.product({
96
+ car: Value.variant("1", Value.product({})),
97
+ cdr: nil
98
+ }));
99
+ const cons1 = Value.variant("cons", Value.product({
100
+ car: Value.variant("1", Value.product({})),
101
+ cdr: cons2
102
+ }));
103
+
104
+ let runtimeStatusNoFallback = null;
105
+ try {
106
+ const res = compileAndRunLLVM(llvmNoFallback, { input: cons1 });
107
+ runtimeStatusNoFallback = res.status;
108
+ } catch (err) {
109
+ runtimeStatusNoFallback = err.status ?? 2;
110
+ }
111
+
112
+ console.log(`- Runtime exit status without fallback: ${runtimeStatusNoFallback} (expected 0)`);
113
+
114
+ // -----------------------------------------------------------------------
115
+ // Assertions to expose misalignment
116
+ // -----------------------------------------------------------------------
117
+ const exposeMode = process.argv.includes("--assert");
118
+
119
+ if (exposeMode) {
120
+ assert.equal(
121
+ cdrFieldSlotOffset,
122
+ 24,
123
+ `Misalignment: .cdr projected at byte offset ${cdrFieldSlotOffset} (car) instead of 24 (cdr)`
124
+ );
125
+ assert.equal(
126
+ cdrDynamicFallbackEmitted,
127
+ false,
128
+ "Dynamic fallback: LLVM emits @k_product_get_n fallback to mask misalignment"
129
+ );
130
+ assert.equal(
131
+ runtimeStatusNoFallback,
132
+ 0,
133
+ `Runtime failure: status ${runtimeStatusNoFallback} when relying on static index`
134
+ );
135
+ } else {
136
+ console.log("\nSummary:");
137
+ console.log(` Offset misalignment: ${cdrFieldSlotOffset === 24 ? "ALIGNED" : "MISALIGNED (offset 0 instead of 24)"}`);
138
+ console.log(` Dynamic fallback used: ${cdrDynamicFallbackEmitted ? "YES (papers over bug at runtime)" : "NO"}`);
139
+ console.log(` Status without fallback: ${runtimeStatusNoFallback === 0 ? "OK" : "FAILS (status " + runtimeStatusNoFallback + ")"}`);
140
+ }
@@ -184,8 +184,7 @@ function readArenaValue(exports, ptr, pattern, patternNodeId) {
184
184
 
185
185
  for (let i = 0; i < N; i++) {
186
186
  const edge = patternNode.edges[i];
187
- const offsetVal = view.getUint32(ptr + 8 + 4 * i, true);
188
- const childPtr = view.getUint32(ptr + offsetVal, true);
187
+ const childPtr = view.getUint32(ptr + 8 + 4 * N + 4 * i, true);
189
188
  productObj[edge.label] = readArenaValue(exports, childPtr, pattern, edge.target);
190
189
  }
191
190
  return Value.product(productObj, pattern);
@@ -231,7 +230,7 @@ function writeValueToArena(value, pattern, patternNodeId) {
231
230
 
232
231
  for (let i = 0; i < N; i++) {
233
232
  const offsetVal = 8 + 4 * N + 4 * i;
234
- view.setUint32(ptr + 8 + 4 * i, offsetVal, true);
233
+ view.setUint32(ptr + 8 + 4 * i, getTagId(keys[i]), true);
235
234
  view.setUint32(ptr + offsetVal, childPtrs[i], true);
236
235
  }
237
236
  return ptr;