@fraczak/k 7.0.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/DOCS/BIG_PICTURE.md +5 -5
  3. package/DOCS/FILE_FORMATS.md +241 -18
  4. package/DOCS/OBJECT_FILE_AND_PATTERN.md +9 -9
  5. package/DOCS/PATTERNS.md +3 -3
  6. package/DOCS/POLYMORPHIC_IR.md +104 -0
  7. package/DOCS/PROFILING_AND_TRACING.md +60 -72
  8. package/DOCS/REPL.md +3 -3
  9. package/DOCS/book/03-types-as-finite-automata.md +4 -4
  10. package/DOCS/book/06-values-in-memory.md +9 -9
  11. package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
  12. package/DOCS/book/09-compiler-architecture.md +2 -2
  13. package/DOCS/book/10-from-ast-to-intermediate-representation.md +2 -2
  14. package/DOCS/book/14-canonical-serialization.md +1 -1
  15. package/Examples/arithmetics.k +2 -2
  16. package/Examples/bnat.k +1 -1
  17. package/Examples/karatsuba-mult.k +2 -2
  18. package/JOURNAL.md +288 -0
  19. package/README.md +13 -10
  20. package/access-table.mjs +343 -0
  21. package/backend-api.mjs +18 -0
  22. package/backends/llvm/README.md +20 -21
  23. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  24. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  25. package/backends/llvm/bin/k-llvm.mjs +292 -0
  26. package/backends/llvm/runtime/krt.c +41 -0
  27. package/backends/llvm/runtime/krt.h +2 -0
  28. package/backends/llvm/src/cli.mjs +95 -17
  29. package/backends/llvm/src/executable.mjs +132 -15
  30. package/backends/llvm/src/kvm2llvm.mjs +50 -23
  31. package/backends/llvm/src/llvm.mjs +133 -20
  32. package/backends/llvm/tests/integration.sh +11 -11
  33. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  34. package/backends/llvm/tests/perf-support.mjs +25 -58
  35. package/backends/llvm/tests/test-llvm.mjs +25 -4
  36. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  37. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  38. package/backends/wasm/bin/k-wasm.mjs +47 -13
  39. package/backends/wasm/src/cli.mjs +141 -19
  40. package/backends/wasm/src/wasm.mjs +132 -24
  41. package/backends/wasm/tests/test-wasm.mjs +5 -5
  42. package/browser/repl-browser.mjs +16 -5
  43. package/browser/vfs-data.mjs +5 -5
  44. package/codecs/k-parse.mjs +29 -8
  45. package/codecs/k-print.mjs +22 -6
  46. package/codecs/runtime/codec-sdk.mjs +8 -4
  47. package/codecs/show.mjs +20 -6
  48. package/core.k +8 -8
  49. package/harness/format.mjs +2 -2
  50. package/harness/registry.mjs +0 -2
  51. package/k.mjs +56 -16
  52. package/kir.mjs +200 -75
  53. package/kvm.mjs +49 -12
  54. package/objects/compile.mjs +37 -23
  55. package/objects/decompile.mjs +40 -10
  56. package/objects/extract-aliases.mjs +44 -14
  57. package/objects/inspect.mjs +19 -14
  58. package/objects/validate.mjs +18 -11
  59. package/package.json +10 -7
  60. package/pkir.mjs +772 -0
  61. package/repl.mjs +36 -18
  62. package/scripts/benchmark-idempotency.mjs +12 -8
  63. package/scripts/benchmark.mjs +5 -9
  64. package/scripts/build-repl-html.mjs +16 -4
  65. package/scripts/perf-ieee.mjs +253 -103
  66. package/scripts/perf-int.mjs +585 -297
  67. package/scripts/perf-multiplication.mjs +66 -25
  68. package/scripts/perf-poly.mjs +292 -164
  69. package/scripts/run-tests.mjs +3 -0
  70. package/tests/code-derivation/pattern-envelope.mjs +4 -4
  71. package/tests/test-access-table.mjs +213 -0
  72. package/tests/test-kvm-polymorphic.mjs +2 -2
  73. package/tests/test-pkir.mjs +530 -0
  74. package/tests/test-polymorphic-tdd.mjs +196 -0
  75. package/tests/test-repl-html.mjs +15 -2
  76. package/tests/test-repl.mjs +30 -1
  77. package/backends/arm64/README.md +0 -103
  78. package/backends/arm64/bin/k-arm64-compile.mjs +0 -93
  79. package/backends/arm64/bin/k-arm64-run.mjs +0 -58
  80. package/backends/arm64/bin/k-arm64.mjs +0 -88
  81. package/backends/arm64/scripts/run-tests.mjs +0 -57
  82. package/backends/arm64/src/arm64.mjs +0 -229
  83. package/backends/arm64/src/cli.mjs +0 -155
  84. package/backends/arm64/src/kvm2arm64.mjs +0 -906
  85. package/backends/arm64/src/runtime.c +0 -744
  86. package/backends/arm64/tests/integration.sh +0 -112
  87. package/backends/arm64/tests/test-arm64.mjs +0 -240
  88. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
  89. package/harness/adapters/arm64.mjs +0 -105
@@ -7,7 +7,8 @@ import { isProduct, isVariant } from "@fraczak/k/Value.mjs";
7
7
  import { exportPatternGraph } from "@fraczak/k/codecs/runtime/codec.mjs";
8
8
  import { patternToPropertyList } from "@fraczak/k/codecs/runtime/pattern-json.mjs";
9
9
  import { specializeKVM } from "@fraczak/k/kvm.mjs";
10
- import { compileObjectToLLVM } from "./llvm.mjs";
10
+ import { lowerPKIRToKVM } from "@fraczak/k/backend-api.mjs";
11
+ import { compileObjectToLLVM, compileLLVMArtifactFromPKIR } from "./llvm.mjs";
11
12
  import { compileKVMModuleToLLVM } from "./kvm2llvm.mjs";
12
13
 
13
14
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -263,6 +264,9 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
263
264
  "static int write_frame(k_wire_prefix *prefix, k_pattern *pattern, k_value *value) {",
264
265
  " size_t length = 0;",
265
266
  " unsigned char *payload = k_encode_wire_as_with_prefix(prefix, pattern, value, &length);",
267
+ " if (payload == NULL) {",
268
+ " payload = k_encode_wire(value, &length);",
269
+ " }",
266
270
  " if (payload == NULL || length > UINT32_MAX) {",
267
271
  " free(payload);",
268
272
  " return 0;",
@@ -292,7 +296,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
292
296
  " k_wire_input_free(input);",
293
297
  " return 2;",
294
298
  " }",
295
- " if (!k_pattern_equal(input.pattern, &compiled_input_pattern)) {",
299
+ " if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
296
300
  " k_wire_input_free(input);",
297
301
  " return 5;",
298
302
  " }",
@@ -350,7 +354,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
350
354
  " k_value *in_val = k_decode_wire_value_with_prefix(payload, length, &input_prefix, &compiled_input_pattern, rt);",
351
355
  " if (in_val == 0) {",
352
356
  " 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)) {",
357
+ " if (envelope.value != 0 && envelope.pattern != 0 && k_pattern_compatible(envelope.pattern, &compiled_input_pattern)) {",
354
358
  " in_val = envelope.value;",
355
359
  " } else {",
356
360
  " k_wire_input_free(envelope);",
@@ -376,6 +380,9 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
376
380
  " }",
377
381
  " size_t out_len = 0;",
378
382
  " unsigned char *out_payload = k_encode_wire_as_with_prefix(&output_prefix, &compiled_output_pattern, result.value, &out_len);",
383
+ " if (out_payload == NULL) {",
384
+ " out_payload = k_encode_wire(result.value, &out_len);",
385
+ " }",
379
386
  " if (tracing) t4 = monotonic_ns();",
380
387
  " int ok = 0;",
381
388
  " uint64_t write_start = t4;",
@@ -437,7 +444,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
437
444
  " k_rt_free(input_rt);",
438
445
  " return 2;",
439
446
  " }",
440
- " if (!k_pattern_equal(input.pattern, &compiled_input_pattern)) {",
447
+ " if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
441
448
  " k_wire_input_free(input);",
442
449
  " k_rt_free(run_rt);",
443
450
  " k_rt_free(input_rt);",
@@ -478,7 +485,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
478
485
  " k_rt_free(rt);",
479
486
  " return 2;",
480
487
  " }",
481
- " if (!k_pattern_equal(input.pattern, &compiled_input_pattern)) {",
488
+ " if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
482
489
  " k_wire_input_free(input);",
483
490
  " k_wire_prefix_free(output_prefix);",
484
491
  " k_rt_free(rt);",
@@ -495,6 +502,9 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
495
502
  " }",
496
503
  " size_t output_length = 0;",
497
504
  " unsigned char *output_payload = k_encode_wire_as_with_prefix(&output_prefix, &compiled_output_pattern, result.value, &output_length);",
505
+ " if (output_payload == NULL) {",
506
+ " output_payload = k_encode_wire(result.value, &output_length);",
507
+ " }",
498
508
  " if (tracing) t3 = monotonic_ns();",
499
509
  " int ok = output_payload != NULL && fwrite(output_payload, 1, output_length, stdout) == output_length && fflush(stdout) == 0;",
500
510
  " if (tracing) {",
@@ -540,12 +550,25 @@ export function compileLLVMToExecutable(llvm, outputPath, { driver = stdioDriver
540
550
  }
541
551
  }
542
552
 
543
- export function compileObjectToExecutable(object, outputPath, { relation = object.main, inputPattern = null, runtimeMode = "fast", clangOpt = "-O3" } = {}) {
544
- const { inputPattern: compiledInputPattern, outputPattern, llvm } = compileObjectToLLVM(object, {
545
- relation,
546
- inputPattern: inputPattern || inputPatternForObjectRelation(object, relation),
547
- runtimeMode
548
- });
553
+ export function compileObjectToExecutable(
554
+ object,
555
+ outputPath,
556
+ {
557
+ relation = object.main,
558
+ inputPattern = null,
559
+ runtimeMode = "fast",
560
+ clangOpt = "-O3",
561
+ usePKIR = false
562
+ } = {}
563
+ ) {
564
+ const { inputPattern: compiledInputPattern, outputPattern, llvm } =
565
+ compileObjectToLLVM(object, {
566
+ relation,
567
+ inputPattern:
568
+ inputPattern || inputPatternForObjectRelation(object, relation),
569
+ runtimeMode,
570
+ usePKIR
571
+ });
549
572
  compileLLVMToExecutable(llvm, outputPath, {
550
573
  driver: stdioDriverSource({
551
574
  inputPattern: compiledInputPattern,
@@ -559,7 +582,8 @@ export function compileKVMToExecutable(kvmInput, outputPath, {
559
582
  entry = null,
560
583
  inputPattern = null,
561
584
  runtimeMode = "fast",
562
- clangOpt = "-O3"
585
+ clangOpt = "-O3",
586
+ usePKIR = false
563
587
  } = {}) {
564
588
  let kvm = kvmInput;
565
589
  if (kvm?.format === "k-vm") {
@@ -578,7 +602,8 @@ export function compileKVMToExecutable(kvmInput, outputPath, {
578
602
  relation: entryFunc,
579
603
  inputPattern: compiledInputPattern,
580
604
  outputPattern,
581
- runtimeMode
605
+ runtimeMode,
606
+ usePKIR
582
607
  });
583
608
  compileLLVMToExecutable(llvm, outputPath, {
584
609
  driver: stdioDriverSource({
@@ -589,10 +614,102 @@ export function compileKVMToExecutable(kvmInput, outputPath, {
589
614
  });
590
615
  }
591
616
 
592
- export function compileObjectAndRun(object, { relation = object.main, input, expected = null, inputPattern = null }) {
617
+ export function compilePKIRToExecutable(
618
+ pkirInput,
619
+ outputPath,
620
+ {
621
+ entry = null,
622
+ inputPattern = null,
623
+ runtimeMode = "fast",
624
+ clangOpt = "-O3"
625
+ } = {}
626
+ ) {
627
+ let kvmProgram;
628
+ let entryFunc;
629
+ if (
630
+ (pkirInput.format === "pk-ir" || pkirInput.format === "k-pkir") &&
631
+ pkirInput.functions
632
+ ) {
633
+ kvmProgram = {};
634
+ for (const [name, f] of Object.entries(pkirInput.functions)) {
635
+ kvmProgram[name] = lowerPKIRToKVM(f);
636
+ }
637
+ entryFunc =
638
+ entry ||
639
+ pkirInput.entry ||
640
+ Object.keys(kvmProgram)[0] ||
641
+ "__main__";
642
+ } else if (pkirInput.instructions) {
643
+ const kvm = lowerPKIRToKVM(pkirInput);
644
+ entryFunc = pkirInput.name || entry || "__main__";
645
+ kvmProgram = { [entryFunc]: kvm };
646
+ const scan = (insts) => {
647
+ for (const inst of insts || []) {
648
+ if (inst.op === "call" && inst.func && !kvmProgram[inst.func]) {
649
+ kvmProgram[inst.func] = kvm;
650
+ }
651
+ if (inst.branches) {
652
+ for (const b of inst.branches) scan(b.body);
653
+ }
654
+ }
655
+ };
656
+ scan(kvm.body);
657
+ } else if (pkirInput.functions) {
658
+ kvmProgram = {};
659
+ for (const [name, f] of Object.entries(pkirInput.functions)) {
660
+ kvmProgram[name] = lowerPKIRToKVM(f);
661
+ }
662
+ entryFunc =
663
+ entry ||
664
+ pkirInput.entry ||
665
+ Object.keys(kvmProgram)[0] ||
666
+ "__main__";
667
+ } else {
668
+ const kvm = lowerPKIRToKVM(pkirInput);
669
+ entryFunc = pkirInput.name || entry || "__main__";
670
+ kvmProgram = { [entryFunc]: kvm };
671
+ }
672
+ return compileKVMToExecutable(
673
+ {
674
+ format: "k-vm",
675
+ layer: "KVM",
676
+ entry: entryFunc,
677
+ functions: kvmProgram
678
+ },
679
+ outputPath,
680
+ {
681
+ entry: entryFunc,
682
+ inputPattern,
683
+ runtimeMode,
684
+ clangOpt,
685
+ usePKIR: true
686
+ }
687
+ );
688
+ }
689
+
690
+ export function compileObjectAndRun(
691
+ object,
692
+ {
693
+ relation = object.main,
694
+ input,
695
+ expected = null,
696
+ inputPattern = null,
697
+ usePKIR = false
698
+ }
699
+ ) {
593
700
  const { llvm } = compileObjectToLLVM(object, {
594
701
  relation,
595
- inputPattern: inputPattern || inputPatternForObjectValue(object, input, relation)
702
+ inputPattern:
703
+ inputPattern || inputPatternForObjectValue(object, input, relation),
704
+ usePKIR
596
705
  });
597
706
  return compileAndRunLLVM(llvm, { input, expected });
598
707
  }
708
+
709
+ export function compilePKIRAndRun(
710
+ pkirInput,
711
+ { input, expected = null, options = {} } = {}
712
+ ) {
713
+ const llvm = compileLLVMArtifactFromPKIR(pkirInput, options);
714
+ return compileAndRunLLVM(llvm, { input, expected });
715
+ }
@@ -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() {
@@ -449,12 +451,15 @@ function hasSelfTailCall(insts, funcName, isTail = true) {
449
451
  return false;
450
452
  }
451
453
 
452
- function getFieldEdgeIndex(inst, regPatterns, kvmFunc) {
454
+ function getFieldEdgeIndex(inst, regPatterns, kvmFunc, ctx = {}) {
453
455
  const srcReg = cleanReg(inst.src);
454
456
  const srcPattern = regPatterns.get(srcReg) || (srcReg === "in" ? kvmFunc.inputPattern : null);
455
457
  if (Array.isArray(srcPattern) && srcPattern.length > 0) {
456
458
  const root = srcPattern[0];
457
- if ((root[0] === "open-product" || root[0] === "closed-product") && Array.isArray(root[1])) {
459
+ const allowed = ctx.usePKIR
460
+ ? root[0] === "closed-product"
461
+ : (root[0] === "open-product" || root[0] === "closed-product");
462
+ if (allowed && Array.isArray(root[1])) {
458
463
  const idx = root[1].findIndex(([l]) => l === inst.label);
459
464
  if (idx >= 0) return idx;
460
465
  }
@@ -512,6 +517,7 @@ export function compileKVMModuleToLLVM(entryName, kvmProgram, options = {}) {
512
517
  labels,
513
518
  functionNames,
514
519
  runtimeMode,
520
+ usePKIR: Boolean(options.usePKIR),
515
521
  armIndex: 0,
516
522
  unionExprIndex: 0,
517
523
  syntheticFunctions: [],
@@ -551,8 +557,7 @@ export function compileKVMModuleToLLVM(entryName, kvmProgram, options = {}) {
551
557
  const payload = JSON.stringify({
552
558
  format: ARTIFACT_FORMAT,
553
559
  version: ARTIFACT_VERSION,
554
- relation,
555
- kvm: kvmProgram
560
+ relation
556
561
  });
557
562
  const payloadBytes = Buffer.byteLength(payload, "utf8") + 1;
558
563
  const symbol = llvmIdentifier(options.symbol || relation);
@@ -583,6 +588,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
583
588
  labels: moduleCtx.labels,
584
589
  functionNames: moduleCtx.functionNames,
585
590
  runtimeMode: moduleCtx.runtimeMode,
591
+ usePKIR: moduleCtx.usePKIR,
586
592
  allocas: [],
587
593
  lines: [],
588
594
  currentBlock: "entry",
@@ -674,7 +680,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
674
680
  case "project_field": {
675
681
  const input = getReg(inst.src);
676
682
  nullCheck(ctx, input, failTarget);
677
- const edgeIndex = getFieldEdgeIndex(inst, regPatterns, kvmFunc);
683
+ const edgeIndex = getFieldEdgeIndex(inst, regPatterns, kvmFunc, ctx);
678
684
  const label = ctx.labelRef(inst.label);
679
685
  let val;
680
686
  if (edgeIndex >= 0 && ctx.runtimeMode !== "compact") {
@@ -815,11 +821,32 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
815
821
  storeI64At(ctx, product, K_VALUE_PRODUCT_CAPACITY_OFFSET, N);
816
822
  storePtrAt(ctx, product, K_VALUE_PRODUCT_FIELDS_OFFSET, fields);
817
823
 
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
- });
824
+ let sortedFields;
825
+ if (Array.isArray(inst.pattern) && inst.pattern.length > 0) {
826
+ const root = inst.pattern[0];
827
+ if (
828
+ (root[0] === "open-product" ||
829
+ root[0] === "closed-product") &&
830
+ Array.isArray(root[1])
831
+ ) {
832
+ const order = new Map(root[1].map(([l], i) => [l, i]));
833
+ sortedFields = [...fieldValues].sort((a, b) => {
834
+ const aI = order.has(a.label)
835
+ ? order.get(a.label)
836
+ : 999999;
837
+ const bI = order.has(b.label)
838
+ ? order.get(b.label)
839
+ : 999999;
840
+ if (aI !== bI) return aI - bI;
841
+ return a.label.localeCompare(b.label);
842
+ });
843
+ }
844
+ }
845
+ if (!sortedFields) {
846
+ sortedFields = [...fieldValues].sort((a, b) =>
847
+ a.label.localeCompare(b.label)
848
+ );
849
+ }
823
850
 
824
851
  for (let sIdx = 0; sIdx < N; sIdx++) {
825
852
  const { label: fLabel, val: fVal } = sortedFields[sIdx];
@@ -838,12 +865,12 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
838
865
  const uInput = getReg(inst.src);
839
866
 
840
867
  if (!isTopLevel) {
841
- // Nested union: synthesize a helper function @k_union_expr_${index}
868
+ // Nested union: synthesize helper @k_union_expr_${index}
842
869
  const exprIndex = moduleCtx.unionExprIndex++;
843
870
  const exprName = `@k_union_expr_${exprIndex}`;
844
871
  const exprFunc = {
845
872
  name: `union_expr_${exprIndex}`,
846
- inputPattern: kvmFunc.inputPattern,
873
+ inputPattern: regPatterns.get(cleanReg(inst.src)) || null,
847
874
  outputPattern: kvmFunc.outputPattern,
848
875
  body: [
849
876
  { op: "id", dest: "%v0", src: "%in" },
@@ -863,7 +890,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
863
890
  break;
864
891
  }
865
892
 
866
- // Top-level union: synthesize arm functions @k_union_arm_${index}
893
+ // Top-level union: synthesize arms @k_union_arm_${index}
867
894
  const mark = lowerRtMark(ctx);
868
895
  const unionDone = ctx.blockName("union_done");
869
896
  const N = inst.branches.length;
@@ -877,7 +904,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
877
904
 
878
905
  const armFunc = {
879
906
  name: `union_arm_${armIndex}`,
880
- inputPattern: kvmFunc.inputPattern,
907
+ inputPattern: regPatterns.get(cleanReg(inst.src)) || null,
881
908
  outputPattern: kvmFunc.outputPattern,
882
909
  body: branch.body
883
910
  };
@@ -7,7 +7,11 @@ import {
7
7
  objectToKVMArtifact,
8
8
  specializeKVM,
9
9
  lowerKIRToKVM,
10
- objectToKIRP
10
+ objectToKIRP,
11
+ compileRelationToPKIR,
12
+ lowerPKIRToKVM,
13
+ exportPatternGraph,
14
+ patternToPropertyList
11
15
  } from "@fraczak/k/backend-api.mjs";
12
16
  import { compileKVMModuleToLLVM } from "./kvm2llvm.mjs";
13
17
 
@@ -45,15 +49,17 @@ function alignArenaSize(size) {
45
49
  }
46
50
 
47
51
  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("");
52
+ const buf = Buffer.from(text, "utf8");
53
+ let out = "";
54
+ for (let i = 0; i < buf.length; i++) {
55
+ const byte = buf[i];
56
+ if (byte === 10) out += "\\0A";
57
+ else if (byte === 34) out += "\\22";
58
+ else if (byte === 92) out += "\\5C";
59
+ else if (byte >= 32 && byte <= 126) out += String.fromCharCode(byte);
60
+ else out += `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
61
+ }
62
+ return out + "\\00";
57
63
  }
58
64
 
59
65
  function readPattern(inputPattern) {
@@ -921,7 +927,8 @@ function emitFunctionBody(symbol, body, ctx, linkage = "", options = {}) {
921
927
  }
922
928
  const value = lowerExpr(ctx, body, input, { tail: options.tail !== false });
923
929
  if (value === false) {
924
- // The expression emitted complete control flow, including all returns.
930
+ // The expression emitted complete control flow, including all
931
+ // returns.
925
932
  } else if (value == null) {
926
933
  unsupported(ctx);
927
934
  } else {
@@ -986,8 +993,7 @@ export function emitLLVMModule(input, options = {}) {
986
993
  const payload = JSON.stringify({
987
994
  format: ARTIFACT_FORMAT,
988
995
  version: ARTIFACT_VERSION,
989
- relation,
990
- kir
996
+ relation
991
997
  });
992
998
  const payloadBytes = Buffer.byteLength(payload, "utf8") + 1;
993
999
  const symbol = llvmIdentifier(options.symbol || relation);
@@ -1009,25 +1015,74 @@ export function emitLLVMModule(input, options = {}) {
1009
1015
  }
1010
1016
 
1011
1017
  export function compileObjectToLLVM(object, options = {}) {
1012
- const inputPattern = options.inputPattern ? readPattern(options.inputPattern) : null;
1018
+ const inputPattern = options.inputPattern
1019
+ ? readPattern(options.inputPattern)
1020
+ : null;
1013
1021
  const relation = options.relation || object.main;
1014
1022
  let targetObject = object;
1015
1023
  let entryInputPattern = inputPattern;
1016
1024
  let outputPattern = null;
1017
1025
 
1018
- if (inputPattern) {
1019
- const retyped = retypeObjectRelationForBackend(object, relation, inputPattern, {
1020
- source: options.source || "<k-llvm>"
1021
- });
1026
+ if (options.usePKIR) {
1027
+ const rel = object.rels?.[relation] ||
1028
+ (object.relAlias?.[relation] &&
1029
+ object.rels?.[object.relAlias[relation]]) ||
1030
+ object.rels?.[object.main];
1031
+ if (rel?.typePatternGraph) {
1032
+ const graph = rel.typePatternGraph;
1033
+ if (!entryInputPattern &&
1034
+ rel.def?.patterns?.[0] !== undefined) {
1035
+ const inId = graph.find(rel.def.patterns[0]);
1036
+ entryInputPattern =
1037
+ patternToPropertyList(exportPatternGraph(graph, inId));
1038
+ }
1039
+ if (rel.def?.patterns?.[1] !== undefined) {
1040
+ const outId = graph.find(rel.def.patterns[1]);
1041
+ outputPattern =
1042
+ patternToPropertyList(exportPatternGraph(graph, outId));
1043
+ }
1044
+ }
1045
+ } else if (inputPattern) {
1046
+ const retyped = retypeObjectRelationForBackend(
1047
+ object, relation, inputPattern, {
1048
+ source: options.source || "<k-llvm>"
1049
+ }
1050
+ );
1022
1051
  targetObject = retyped.retypedObject;
1023
1052
  entryInputPattern = retyped.inputPattern;
1024
1053
  outputPattern = retyped.outputPattern;
1025
1054
  }
1026
1055
 
1027
- const retypedKir = objectToKIRP(targetObject);
1028
- const functions = lowerKIRToKVM(retypedKir);
1056
+ let functions;
1057
+ let retypedKir = null;
1029
1058
  const entry = relation || targetObject.main || "__main__";
1030
1059
 
1060
+ if (options.usePKIR) {
1061
+ functions = {};
1062
+ for (const [name, relDef] of Object.entries(targetObject.rels || {})) {
1063
+ const pkir = compileRelationToPKIR(relDef, name);
1064
+ functions[name] = lowerPKIRToKVM(pkir);
1065
+ }
1066
+ for (const [alias, target] of Object.entries(targetObject.relAlias || {})) {
1067
+ if (functions[target] && !functions[alias]) {
1068
+ functions[alias] = functions[target];
1069
+ }
1070
+ const stripped = target.startsWith("@") ? target.slice(1) : target;
1071
+ if (functions[stripped] && !functions[alias]) {
1072
+ functions[alias] = functions[stripped];
1073
+ }
1074
+ if (functions[alias] && !functions[target]) {
1075
+ functions[target] = functions[alias];
1076
+ }
1077
+ }
1078
+ if (relation && !functions[relation] && functions[targetObject.main]) {
1079
+ functions[relation] = functions[targetObject.main];
1080
+ }
1081
+ } else {
1082
+ retypedKir = objectToKIRP(targetObject);
1083
+ functions = lowerKIRToKVM(retypedKir);
1084
+ }
1085
+
1031
1086
  const specializedKvm = {
1032
1087
  format: "k-vm",
1033
1088
  version: 1,
@@ -1064,6 +1119,63 @@ export function compileLLVMArtifactFromKVM(kvmInput, options = {}) {
1064
1119
  return compileKVMModuleToLLVM(entry, functions, options);
1065
1120
  }
1066
1121
 
1122
+ export function compileLLVMArtifactFromPKIR(pkirInput, options = {}) {
1123
+ let kvmProgram;
1124
+ let entry;
1125
+ if (
1126
+ (pkirInput.format === "pk-ir" || pkirInput.format === "k-pkir") &&
1127
+ pkirInput.functions
1128
+ ) {
1129
+ entry =
1130
+ pkirInput.entry ||
1131
+ options.entry ||
1132
+ Object.keys(pkirInput.functions)[0] ||
1133
+ "__main__";
1134
+ kvmProgram = {};
1135
+ for (const [name, f] of Object.entries(pkirInput.functions)) {
1136
+ kvmProgram[name] = lowerPKIRToKVM(f);
1137
+ }
1138
+ } else if (pkirInput.instructions) {
1139
+ const kvm = lowerPKIRToKVM(pkirInput);
1140
+ entry = pkirInput.name || options.entry || "__main__";
1141
+ kvmProgram = { [entry]: kvm };
1142
+ const scan = (insts) => {
1143
+ for (const inst of insts || []) {
1144
+ if (inst.op === "call" && inst.func && !kvmProgram[inst.func]) {
1145
+ kvmProgram[inst.func] = kvm;
1146
+ }
1147
+ if (inst.branches) {
1148
+ for (const b of inst.branches) scan(b.body);
1149
+ }
1150
+ }
1151
+ };
1152
+ scan(kvm.body);
1153
+ } else if (pkirInput.functions) {
1154
+ entry =
1155
+ pkirInput.entry ||
1156
+ options.entry ||
1157
+ Object.keys(pkirInput.functions)[0] ||
1158
+ "__main__";
1159
+ kvmProgram = {};
1160
+ for (const [name, f] of Object.entries(pkirInput.functions)) {
1161
+ kvmProgram[name] = lowerPKIRToKVM(f);
1162
+ }
1163
+ } else {
1164
+ const kvm = lowerPKIRToKVM(pkirInput);
1165
+ entry = pkirInput.name || options.entry || "__main__";
1166
+ kvmProgram = { [entry]: kvm };
1167
+ }
1168
+ return compileLLVMArtifactFromKVM(
1169
+ {
1170
+ format: "k-vm",
1171
+ layer: "KVM",
1172
+ entry,
1173
+ functions: kvmProgram
1174
+ },
1175
+ options
1176
+ );
1177
+ }
1178
+
1067
1179
  export function compileLLVMArtifactFromObject(object, options = {}) {
1068
1180
  return compileObjectToLLVM(object, options);
1069
1181
  }
@@ -1079,6 +1191,7 @@ export default {
1079
1191
  compileBufferToLLVM,
1080
1192
  compileObjectToLLVM,
1081
1193
  compileLLVMArtifactFromKVM,
1194
+ compileLLVMArtifactFromPKIR,
1082
1195
  compileLLVMArtifactFromObject,
1083
1196
  emitLLVMModule,
1084
1197
  llvmIdentifier
@@ -72,19 +72,19 @@ node ./bin/k-llvm-compile.mjs --help | grep -q 'Compile a k .ko/.klib object'
72
72
 
73
73
  node ./bin/k-llvm-build.mjs --help | grep -q 'binary k pattern+value envelope'
74
74
 
75
- node ./bin/k-llvm-jit.mjs --help | grep -q 'stdin binary envelope'
75
+ node ./bin/k-llvm.mjs --help | grep -q 'binary pattern+value wire stream'
76
76
 
77
- node ./bin/k-llvm-run.mjs --help | grep -q 'Compile and execute a k .ko/.klib object'
77
+ node ./tests/k-llvm-run.mjs --help | grep -q 'Compile and execute a k .ko/.klib object'
78
78
 
79
79
  node ../../objects/compile.mjs '.x' "$TMP_DIR/run.ko"
80
80
  printf '{"x":"left","y":"right"}' > "$TMP_DIR/run-input.kv"
81
81
  printf '"left"' > "$TMP_DIR/run-expected.kv"
82
- node ./bin/k-llvm-run.mjs --expect "$TMP_DIR/run-expected.kv" "$TMP_DIR/run.ko" "$TMP_DIR/run-input.kv" | grep -qx 'OK'
83
- node ./bin/k-llvm-run.mjs "$TMP_DIR/run.ko" "$TMP_DIR/run-input.kv" | grep -qx '"left"'
82
+ node ./tests/k-llvm-run.mjs --expect "$TMP_DIR/run-expected.kv" "$TMP_DIR/run.ko" "$TMP_DIR/run-input.kv" | grep -qx 'OK'
83
+ node ./tests/k-llvm-run.mjs "$TMP_DIR/run.ko" "$TMP_DIR/run-input.kv" | grep -qx '"left"'
84
84
 
85
85
  node ../../objects/compile.mjs '{ .x fieldA, .y fieldB }' "$TMP_DIR/run-product.ko"
86
86
  printf '{"x":"left","y":"right"}' > "$TMP_DIR/run-product-input.kv"
87
- node ./bin/k-llvm-run.mjs "$TMP_DIR/run-product.ko" "$TMP_DIR/run-product-input.kv" | grep -qx '{"fieldA":"left","fieldB":"right"}'
87
+ node ./tests/k-llvm-run.mjs "$TMP_DIR/run-product.ko" "$TMP_DIR/run-product-input.kv" | grep -qx '{"fieldA":"left","fieldB":"right"}'
88
88
 
89
89
  node ./bin/k-llvm-build.mjs '?{<{}=X0 left, ...> x, <X0 right, ...> y} .x' -o "$TMP_DIR/run-exe"
90
90
  printf '{"x":"left","y":"right"}' \
@@ -130,31 +130,31 @@ printf '987654321' \
130
130
 
131
131
  printf '{"x":"left","y":"right"}' \
132
132
  | node ../../codecs/k-parse.mjs \
133
- | node ./bin/k-llvm-jit.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/run.ko" \
133
+ | node ./bin/k-llvm.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/run.ko" \
134
134
  | node ../../codecs/k-print.mjs \
135
135
  | grep -qx '"left"'
136
136
 
137
137
  node --input-type=module -e "import { stdout } from 'node:process'; import { encodeToWire } from '../../codecs/runtime/prefix-codec.mjs'; import { Value } from '../../Value.mjs'; const pattern = [[\"open-union\",[[\"left\",1],[\"right\",1]]],[\"closed-product\",[]]]; stdout.write(encodeToWire(Value.variant(\"left\", Value.product({}), pattern), pattern));" \
138
- | node ./bin/k-llvm-jit.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/id.ko" \
138
+ | node ./bin/k-llvm.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/id.ko" \
139
139
  | 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)));" \
140
140
  | grep -Fqx '[["open-union",[["left",1],["right",1]]],["closed-product",[]]]'
141
141
 
142
142
  node ../../objects/compile.mjs '/"ह" |"😀"' "$TMP_DIR/unicode-variant.ko"
143
143
  node --input-type=module -e "import { stdout } from 'node:process'; import { encodeToWire } from '../../codecs/runtime/prefix-codec.mjs'; import { Value } from '../../Value.mjs'; const pattern = [[\"closed-union\", [[\"ह\", 1]]], [\"closed-product\", []]]; stdout.write(encodeToWire(Value.variant('ह', Value.product({}), pattern), pattern));" \
144
- | node ./bin/k-llvm-jit.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/unicode-variant.ko" \
144
+ | node ./bin/k-llvm.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/unicode-variant.ko" \
145
145
  | node ../../codecs/k-print.mjs \
146
146
  | grep -qx '"😀"'
147
147
 
148
148
  node ../../objects/compile.mjs '/"a\u0000b" |"nul-ok"' "$TMP_DIR/nul-variant.ko"
149
149
  node --input-type=module -e "import { stdout } from 'node:process'; import { encodeToWire } from '../../codecs/runtime/prefix-codec.mjs'; import { Value } from '../../Value.mjs'; const tag = 'a\u0000b'; const pattern = [[\"closed-union\", [[tag, 1]]], [\"closed-product\", []]]; stdout.write(encodeToWire(Value.variant(tag, Value.product({}), pattern), pattern));" \
150
- | node ./bin/k-llvm-jit.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/nul-variant.ko" \
150
+ | node ./bin/k-llvm.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/nul-variant.ko" \
151
151
  | node ../../codecs/k-print.mjs \
152
152
  | grep -qx '"nul-ok"'
153
153
 
154
154
  node --input-type=module -e "import { stdout } from 'node:process'; import { encodeToWire } from '../../codecs/runtime/prefix-codec.mjs'; import { Value } from '../../Value.mjs'; const labels = ['a', 'é', 'ह', '\uE000', '😀', '𠀀', 'a\u0000b']; const pattern = [[\"closed-product\", labels.map((label) => [label, 1])], [\"closed-product\", []]]; const value = Value.product(Object.fromEntries(labels.map((label) => [label, Value.product({})])), pattern); stdout.write(encodeToWire(value, pattern));" \
155
- | node ./bin/k-llvm-jit.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/id.ko" \
155
+ | node ./bin/k-llvm.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/id.ko" \
156
156
  | node --input-type=module -e "import assert from 'node:assert/strict'; import { stdin } from 'node:process'; import { decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; const chunks = []; for await (const chunk of stdin) chunks.push(chunk); const labels = ['a', 'é', 'ह', '\uE000', '😀', '𠀀', 'a\u0000b']; const { pattern } = decodeWire(Buffer.concat(chunks)); assert.equal(pattern[0][0], 'closed-product'); assert.deepEqual(new Set(pattern[0][1].map(([label]) => label)), new Set(labels));"
157
157
 
158
158
  node --input-type=module -e "import { stdout } from 'node:process'; import { encodeToWire } from '../../codecs/runtime/prefix-codec.mjs'; import { Value } from '../../Value.mjs'; const labels = ['a', 'é', 'ह', '\uE000', '😀', '𠀀', 'a\u0000b']; const pattern = [[\"open-union\", labels.map((label) => [label, 1])], [\"closed-product\", []]]; stdout.write(encodeToWire(Value.variant('a\u0000b', Value.product({}), pattern), pattern));" \
159
- | node ./bin/k-llvm-jit.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/id.ko" \
159
+ | node ./bin/k-llvm.mjs --cache-dir "$TMP_DIR/jit-cache" "$TMP_DIR/id.ko" \
160
160
  | node --input-type=module -e "import assert from 'node:assert/strict'; import { stdin } from 'node:process'; import { decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; const chunks = []; for await (const chunk of stdin) chunks.push(chunk); const labels = ['a', 'é', 'ह', '\uE000', '😀', '𠀀', 'a\u0000b']; const { pattern, value } = decodeWire(Buffer.concat(chunks)); assert.equal(pattern[0][0], 'open-union'); assert.deepEqual(new Set(pattern[0][1].map(([label]) => label)), new Set(labels)); assert.equal(value.tag, 'a\u0000b');"