@fraczak/k 6.10.3 → 6.10.5

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 (36) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/DOCS/FILE_FORMATS.md +51 -16
  3. package/Examples/arithmetics.k +222 -110
  4. package/Examples/karatsuba-mult.k +215 -0
  5. package/Examples/poly.k +14 -5
  6. package/backends/arm64/scripts/run-tests.mjs +16 -0
  7. package/backends/llvm/bin/k-llvm-build.mjs +40 -14
  8. package/backends/llvm/bin/k-llvm-compile.mjs +29 -12
  9. package/backends/llvm/runtime/krt.c +10 -10
  10. package/backends/llvm/runtime/krt.h +6 -1
  11. package/backends/llvm/scripts/run-tests.mjs +16 -0
  12. package/backends/llvm/src/cli.mjs +1 -1
  13. package/backends/llvm/src/executable.mjs +36 -0
  14. package/backends/llvm/src/kvm2llvm.mjs +10 -4
  15. package/backends/llvm/tests/integration.sh +11 -0
  16. package/backends/llvm/tests/test-llvm.mjs +15 -1
  17. package/backends/wasm/scripts/run-tests.mjs +16 -0
  18. package/browser/shims/fs.mjs +12 -7
  19. package/browser/vfs-data.mjs +7 -10
  20. package/core.k +25 -18
  21. package/harness/suites/poly.mjs +1 -1
  22. package/package.json +2 -1
  23. package/repl-codecs.mjs +6 -3
  24. package/scripts/benchmark-idempotency.mjs +27 -0
  25. package/scripts/benchmark.mjs +51 -0
  26. package/scripts/build-repl-html.mjs +28 -10
  27. package/scripts/generate-dependency-graph.mjs +25 -1
  28. package/scripts/perf-ieee.mjs +75 -0
  29. package/scripts/perf-int.mjs +67 -0
  30. package/scripts/perf-multiplication.mjs +644 -0
  31. package/scripts/perf-poly.mjs +68 -1
  32. package/scripts/render-benchmark.mjs +25 -0
  33. package/scripts/run-tests.mjs +19 -0
  34. package/tests/test-arithmetics.mjs +96 -0
  35. package/tests/test-karatsuba.mjs +133 -0
  36. package/tests/test-repl-html.mjs +23 -6
@@ -0,0 +1,215 @@
1
+ -- Karatsuba multiplication O(n^1.585)
2
+ -- Self-contained and minimal implementation
3
+ -- for arbitrary-precision integers
4
+
5
+ $ yes_no = < {} yes, {} no >;
6
+ yes = {} | yes $yes_no;
7
+ no = {} | no $yes_no;
8
+
9
+ $ bits = < {} _, bits 0, bits 1 >;
10
+ 0b = {} | _;
11
+ 0 = 0b |0;
12
+ 1 = 0b |1;
13
+
14
+ $ unat = < {} _, unat 1 >;
15
+ 0u = {} | _ $unat;
16
+
17
+ empty = $bits < /_ yes, no >;
18
+ empty? = {empty /yes if, () then} .then;
19
+
20
+ inv = {()x, 0b r} _inv;
21
+ _inv = ${bits x, bits r}
22
+ <
23
+ {.x empty? if, .r then} .then,
24
+ <
25
+ {.x /0 x, .r |0 r},
26
+ {.x /1 x, .r |1 r}
27
+ > _inv
28
+ >;
29
+
30
+ concat = ${bits x, bits y} {.x inv x, .y y} concat_;
31
+ concat_ = ${bits x, bits y}
32
+ <
33
+ {.x empty? if, .y then} .then,
34
+ <
35
+ {.x /0 x, .y |0 y},
36
+ {.x /1 x, .y |1 y}
37
+ > concat_
38
+ >;
39
+
40
+ remove_leading_zeros = < /0 remove_leading_zeros, () >;
41
+ nat = remove_leading_zeros <empty? 0, ()>;
42
+
43
+ $ int = < bits '+', bits '-' >;
44
+ int = nat | '+' $int;
45
+ abs = $int </'+', /'-'>;
46
+
47
+ $ sign = < {} '+', {} '-' >;
48
+ sign_plus = {} | '+' $sign;
49
+ sign_minus = {} | '-' $sign;
50
+ swap_sign = < /'+' | '-', /'-' | '+' >;
51
+
52
+ $ carry = < {} 0, {} 1 >;
53
+ c0 = {} | 0 $carry;
54
+ c1 = {} | 1 $carry;
55
+
56
+ _plus = {.x x, .y y, .c c, 0b r} _plus_;
57
+ _plus_ = ${bits x, bits y, carry c, bits r}
58
+ <
59
+ {.x empty? if, .y empty? and, .c/0 c, .r inv then} .then,
60
+ {.x empty? if, .y empty? and, {.r x, 1 r} _inv then} .then,
61
+ {.x empty? if, .c/0 and, {.r x, .y r} _inv then} .then,
62
+ <
63
+ {.x y, .y empty? x, .c c, .r r},
64
+ {<.c/0 0, 1> x, .y y, .x empty? c0 c, .r r},
65
+ {.x /0 x, .y /0 y, .c/0 c0 c, .r |0 r},
66
+ {.x /0 x, .y /0 y, .c/1 c0 c, .r |1 r},
67
+ {.x /0 x, .y /1 y, .c/0 c0 c, .r |1 r},
68
+ {.x /0 x, .y /1 y, .c/1 c1 c, .r |0 r},
69
+ {.x /1 x, .y /0 y, .c/0 c0 c, .r |1 r},
70
+ {.x /1 x, .y /0 y, .c/1 c1 c, .r |0 r},
71
+ {.x /1 x, .y /1 y, .c/0 c1 c, .r |0 r},
72
+ {.x /1 x, .y /1 y, .c/1 c1 c, .r |1 r}
73
+ > _plus_
74
+ >;
75
+
76
+ _minus = {.x x, .y y, .c c, 0b r} _minus_;
77
+ _minus_ = ${bits x, bits y, carry c, bits r}
78
+ <
79
+ {.y empty? if, .c/0 and, {.r x, .x r} _inv then} .then,
80
+ <
81
+ {.x/0 x, .y empty? y, .c/1 c1 c, .r |1 r},
82
+ {.x/1 x, .y empty? y, .c/1 c0 c, .r |0 r},
83
+ {.x/0 x, .y/0 y, .c/0 c0 c, .r |0 r},
84
+ {.x/0 x, .y/0 y, .c/1 c1 c, .r |1 r},
85
+ {.x/0 x, .y/1 y, .c/0 c1 c, .r |1 r},
86
+ {.x/0 x, .y/1 y, .c/1 c1 c, .r |0 r},
87
+ {.x/1 x, .y/0 y, .c/0 c0 c, .r |1 r},
88
+ {.x/1 x, .y/0 y, .c/1 c0 c, .r |0 r},
89
+ {.x/1 x, .y/1 y, .c/0 c0 c, .r |0 r},
90
+ {.x/1 x, .y/1 y, .c/1 c1 c, .r |1 r}
91
+ > _minus_
92
+ >;
93
+
94
+ plus_bits = ${bits x, bits y} {.x inv x, .y inv y, c0 c} _plus inv nat;
95
+ minus_bits = ${bits x, bits y} {.x inv x, .y inv y, c0 c} _minus inv nat;
96
+
97
+ _times = ${bits result, bits _x, bits y}
98
+ <
99
+ {.y empty? if, .result then} .then,
100
+ <
101
+ {.result |0 result, ._x _x, .y /0 y},
102
+ {{._x x, .result |0 y, c0 c} _plus result, ._x _x, .y /1 y}
103
+ > _times
104
+ >;
105
+ times_bits = ${bits x, bits y} {0 result, .x inv _x, .y y} _times inv nat;
106
+
107
+ unat_to_zeros = $unat < /_ 0b, /1 unat_to_zeros |0 > $bits;
108
+ unat_to_double_zeros = $unat
109
+ < /_ 0b, /1 unat_to_double_zeros |0 |0 > $bits;
110
+
111
+ _peel2 = < </0,/1> </0,/1>, </0,/1>, () >;
112
+ _half_unat = ${bits x, bits y, unat m}
113
+ <
114
+ {.x empty? if, .y empty? and, .m then} .then,
115
+ {.x _peel2 x, .y _peel2 y, .m |1 m} _half_unat
116
+ >;
117
+ half_unat = {.x x, .y y, 0u m} _half_unat;
118
+
119
+ _split_rev_u = ${bits x_rev, unat m, bits low}
120
+ <
121
+ {.m /_ if, {.low nat low, .x_rev inv nat high} then} .then,
122
+ {.x_rev empty? if, {.low nat low, 0 high} then} .then,
123
+ <
124
+ {.x_rev /0 x_rev, .m /1 m, .low |0 low},
125
+ {.x_rev /1 x_rev, .m /1 m, .low |1 low}
126
+ > _split_rev_u
127
+ >;
128
+ split_at = {.x inv x_rev, .m m, 0b low} _split_rev_u;
129
+
130
+ at_least_256_u? = <
131
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
132
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
133
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
134
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
135
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
136
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
137
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
138
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
139
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
140
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
141
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
142
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
143
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
144
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
145
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
146
+ /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1 /1
147
+ yes, no
148
+ >;
149
+
150
+ shift_bits_u = ${bits val, unat m}
151
+ <
152
+ {.val empty? if, 0b then} .then,
153
+ {.val x, .m unat_to_zeros y} concat
154
+ >;
155
+
156
+ shift2_bits_u = ${bits val, unat m}
157
+ <
158
+ {.val empty? if, 0b then} .then,
159
+ {.val x, .m unat_to_double_zeros y} concat
160
+ >;
161
+
162
+ _karatsuba = ${bits x, bits y}
163
+ <
164
+ -- Base case: if m < 256 (< 512 bits), fall back to times_bits
165
+ {{.x x, .y y} half_unat m, .x x, .y y}
166
+ <
167
+ {
168
+ .m at_least_256_u? /no if,
169
+ {.x x, .y y} times_bits then
170
+ } .then,
171
+ -- Recursive Karatsuba:
172
+ {
173
+ {.x x, .m m} split_at sx,
174
+ {.y x, .m m} split_at sy,
175
+ .m m
176
+ }
177
+ {
178
+ {.sx.high x, .sy.high y} _karatsuba z2,
179
+ {.sx.low x, .sy.low y} _karatsuba z0,
180
+ {
181
+ {.sx.high x, .sx.low y} plus_bits x,
182
+ {.sy.high x, .sy.low y} plus_bits y
183
+ } _karatsuba z_sum,
184
+ .m m
185
+ }
186
+ {
187
+ .z2 z2,
188
+ .z0 z0,
189
+ {{.z_sum x, .z2 y} minus_bits x, .z0 y} minus_bits z1,
190
+ .m m
191
+ }
192
+ {
193
+ {.z2 val, .m m} shift2_bits_u z2_s,
194
+ {.z1 val, .m m} shift_bits_u z1_s,
195
+ .z0 z0
196
+ }
197
+ {{.z2_s x, .z1_s y} plus_bits x, .z0 y} plus_bits
198
+ >
199
+ >;
200
+
201
+ karatsuba = ${int x, int y}
202
+ {
203
+ <
204
+ {.x/'+' if, .y /'+' and} sign_plus,
205
+ {.x/'-' if, .y /'-' and} sign_plus,
206
+ sign_minus
207
+ > sign,
208
+ {.x abs x, .y abs y} _karatsuba result
209
+ }
210
+ <
211
+ {.sign/'+' if, .result int then} .then,
212
+ {.sign/'-' if, .result int swap_sign then} .then
213
+ >;
214
+
215
+ ()
package/Examples/poly.k CHANGED
@@ -1,8 +1,16 @@
1
1
  -- Examples/poly.k: Polymorphic list functions in k
2
- -- Uses list constructors/destructors from Examples/list.k
3
- -- and arbitrary-precision integer representations from Examples/arithmetics.k.
4
-
5
- -- Decrement an integer and normalize its representation (stripping leading zeros)
2
+ -- Uses arbitrary-precision integers from Examples/arithmetics.k.
3
+
4
+ -- List constructors and destructors
5
+ list? = ?< {} nil, {X car, Y cdr} cons > = Y;
6
+ nil = {} | nil list?;
7
+ singleton = {() car, nil cdr} | cons list?;
8
+ cons = {.car car, .cdr cdr} | cons list?;
9
+ nil? = list? / nil nil;
10
+ car = list? /cons .car;
11
+ cdr = list? /cons .cdr;
12
+
13
+ -- Decrement an integer and normalize (stripping leading zeros)
6
14
  pred_int = dec /'+' nat | '+';
7
15
 
8
16
  -- Concatenate two lists: {xs, ys} -> list
@@ -82,7 +90,8 @@ gt? =
82
90
  >
83
91
  ;
84
92
 
85
- -- Split list into two halves by alternating elements: xs -> {first, second}
93
+ -- Split list into two halves by alternating elements:
94
+ -- xs -> {first, second}
86
95
  split = {() xs, nil first, nil second} _split;
87
96
  _split = <
88
97
  {.xs /nil if, {.first first, .second second} then} .then,
@@ -6,6 +6,22 @@ import { fileURLToPath } from "node:url";
6
6
 
7
7
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
8
  const node = process.execPath;
9
+
10
+ function printHelp() {
11
+ console.log(`Usage: node backends/arm64/scripts/run-tests.mjs [options]
12
+
13
+ Runs ARM64 backend test suite and integration tests.
14
+
15
+ Options:
16
+ -h, --help
17
+ Show this help message and exit.`);
18
+ }
19
+
20
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
21
+ printHelp();
22
+ process.exit(0);
23
+ }
24
+
9
25
  const tests = [
10
26
  ["tests/test-arm64.mjs", node, ["tests/test-arm64.mjs"]],
11
27
  ["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
@@ -1,21 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import fs from "node:fs";
3
4
  import { argv, exit, stdin } from "node:process";
4
5
  import {
5
6
  compileProgramInputToObject,
6
7
  parseCompileOptions,
7
8
  resolveProgramInput
8
9
  } from "../src/cli.mjs";
9
- import { compileObjectToExecutable } from "../src/executable.mjs";
10
+ import {
11
+ compileKVMToExecutable,
12
+ compileObjectToExecutable
13
+ } from "../src/executable.mjs";
10
14
 
11
15
  function usage(stream = console.error) {
12
16
  const prog = argv[1] || "k-llvm-build.mjs";
13
17
  stream(`Usage: node ${prog} [options] [source-snippet | input-file [output-exe]]`);
14
- stream("Compile k source, .ko, or .klib input into a native executable.");
18
+ stream("Compile k source, .ko, .klib, or .kvm input into a native executable.");
15
19
  stream("");
16
20
  stream("Arguments:");
17
21
  stream(" source-snippet Inline k source, in the same style as k.mjs.");
18
- stream(" input-file Source .k, .ko, or .klib file. Reads UTF-8 source from stdin when omitted.");
22
+ stream(" input-file Source .k, .ko, .klib, or .kvm file. Reads UTF-8 source from stdin when omitted.");
19
23
  stream(" output-exe Output executable path.");
20
24
  stream("");
21
25
  stream("The executable reads a binary k pattern+value envelope from stdin and");
@@ -24,11 +28,21 @@ function usage(stream = console.error) {
24
28
  stream("stdout is encoded with the compiled output pattern.");
25
29
  stream("");
26
30
  stream("Options:");
27
- stream(" -o, --output path Specify output file path explicitly.");
28
- stream(" --lib file Load one .klib dependency before compiling.");
29
- stream(" --export spec Export a library alias into source scope. May be repeated.");
30
- stream(" spec is 'name' or 'libname:localname'.");
31
- stream(" -h, --help Show this help.");
31
+ stream(" -o, --output path Specify output file path explicitly.");
32
+ stream(" --input-pattern pattern Input pattern JSON or file path for specialization.");
33
+ stream(" --lib file Load one .klib dependency before compiling.");
34
+ stream(" --export spec Export a library alias into source scope. May be repeated.");
35
+ stream(" spec is 'name' or 'libname:localname'.");
36
+ stream(" -h, --help Show this help.");
37
+ }
38
+
39
+ function readMaybeFile(textOrPath) {
40
+ return fs.existsSync(textOrPath) ? fs.readFileSync(textOrPath, "utf8") : textOrPath;
41
+ }
42
+
43
+ function readPattern(inputPattern) {
44
+ if (inputPattern == null) return null;
45
+ return JSON.parse(readMaybeFile(inputPattern));
32
46
  }
33
47
 
34
48
  try {
@@ -39,6 +53,7 @@ try {
39
53
  }
40
54
 
41
55
  let explicitOutput = null;
56
+ let rawInputPattern = null;
42
57
  const remainingArgs = [];
43
58
  while (args.length > 0) {
44
59
  const arg = args[0];
@@ -46,11 +61,17 @@ try {
46
61
  args.shift();
47
62
  explicitOutput = args.shift();
48
63
  if (!explicitOutput) throw new Error("-o/--output requires a file argument");
64
+ } else if (arg === "--input-pattern") {
65
+ args.shift();
66
+ rawInputPattern = args.shift();
67
+ if (!rawInputPattern) throw new Error("--input-pattern requires a JSON or file argument");
49
68
  } else {
50
69
  remainingArgs.push(args.shift());
51
70
  }
52
71
  }
53
72
 
73
+ const inputPattern = readPattern(rawInputPattern);
74
+
54
75
  const { libraries, exportSpecs } = parseCompileOptions(remainingArgs);
55
76
  const input = resolveProgramInput(remainingArgs, { allowStdinSource: true });
56
77
  const positionalOutput = remainingArgs.shift();
@@ -59,13 +80,18 @@ try {
59
80
  const outputPath = explicitOutput || positionalOutput || null;
60
81
  if (outputPath == null) throw new Error("Output executable path is required (pass output-exe or -o/--output)");
61
82
 
62
- const object = await compileProgramInputToObject(input, {
63
- libraries,
64
- exportSpecs,
65
- stdin
66
- });
83
+ if (input.kind === "kvm") {
84
+ const kvm = JSON.parse(fs.readFileSync(input.path, "utf8"));
85
+ compileKVMToExecutable(kvm, outputPath, { inputPattern });
86
+ } else {
87
+ const object = await compileProgramInputToObject(input, {
88
+ libraries,
89
+ exportSpecs,
90
+ stdin
91
+ });
67
92
 
68
- compileObjectToExecutable(object, outputPath);
93
+ compileObjectToExecutable(object, outputPath, { inputPattern });
94
+ }
69
95
  } catch (error) {
70
96
  console.error(error.stack || error.message || String(error));
71
97
  usage();
@@ -7,17 +7,20 @@ import {
7
7
  parseCompileOptions,
8
8
  resolveProgramInput
9
9
  } from "../src/cli.mjs";
10
- import { compileObjectToLLVM } from "../src/llvm.mjs";
10
+ import {
11
+ compileObjectToLLVM,
12
+ compileLLVMArtifactFromKVM
13
+ } from "../src/llvm.mjs";
11
14
  import { inputPatternForObjectRelation } from "../src/executable.mjs";
12
15
 
13
16
  function usage(stream = console.error) {
14
17
  const prog = argv[1] || "k-llvm-compile.mjs";
15
18
  stream(`Usage: node ${prog} [options] [source-snippet | input-file [output.ll]]`);
16
- stream("Compile a k .ko/.klib object into prototype LLVM IR.");
19
+ stream("Compile a k .ko/.klib object, source, or .kvm input into prototype LLVM IR.");
17
20
  stream("");
18
21
  stream("Arguments:");
19
22
  stream(" source-snippet Inline k source, in the same style as k.mjs.");
20
- stream(" input-file Source .k, .ko, or .klib file. Reads UTF-8 source from stdin when omitted.");
23
+ stream(" input-file Source .k, .ko, .klib, or .kvm file. Reads UTF-8 source from stdin when omitted.");
21
24
  stream(" output.ll Output LLVM IR path. Writes to stdout when omitted.");
22
25
  stream("");
23
26
  stream("Options:");
@@ -55,16 +58,30 @@ try {
55
58
 
56
59
  const outputPath = explicitOutput || positionalOutput || null;
57
60
 
58
- const object = await compileProgramInputToObject(input, {
59
- libraries,
60
- exportSpecs,
61
- stdin
62
- });
61
+ let llvm;
62
+ if (input.kind === "kvm") {
63
+ const kvm = JSON.parse(fs.readFileSync(input.path, "utf8"));
64
+ const entry = kvm.entry || kvm.relation || "__main__";
65
+ const mainFunc = kvm.functions?.[entry];
66
+ const artifact = compileLLVMArtifactFromKVM(kvm, {
67
+ relation: entry,
68
+ inputPattern: mainFunc?.inputPattern || kvm.inputPattern,
69
+ outputPattern: mainFunc?.outputPattern || kvm.outputPattern
70
+ });
71
+ llvm = typeof artifact === "string" ? artifact : artifact.llvm;
72
+ } else {
73
+ const object = await compileProgramInputToObject(input, {
74
+ libraries,
75
+ exportSpecs,
76
+ stdin
77
+ });
63
78
 
64
- const { llvm } = compileObjectToLLVM(object, {
65
- relation: object.main,
66
- inputPattern: inputPatternForObjectRelation(object, object.main)
67
- });
79
+ const res = compileObjectToLLVM(object, {
80
+ relation: object.main,
81
+ inputPattern: inputPatternForObjectRelation(object, object.main)
82
+ });
83
+ llvm = res.llvm;
84
+ }
68
85
 
69
86
  if (outputPath == null) {
70
87
  stdout.write(llvm);
@@ -553,17 +553,17 @@ static void rt_rewind_and_free(k_rt *rt, k_rt_checkpoint mark) {
553
553
  rt->has_reusable_blocks = 0;
554
554
  }
555
555
 
556
- k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr) {
557
- if (rt == NULL || root == NULL || mark_ptr == NULL) return root;
556
+ k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_tail_mark *tm) {
557
+ if (rt == NULL || root == NULL || tm == NULL) return root;
558
558
 
559
- size_t bytes = bytes_since_mark(rt, *mark_ptr);
559
+ size_t bytes = bytes_since_mark(rt, tm->last_mark);
560
560
  if (bytes < 4194304) {
561
561
  return root;
562
562
  }
563
563
 
564
- if (!is_after_mark(rt, root, *mark_ptr)) {
565
- rt_rewind_and_free(rt, *mark_ptr);
566
- *mark_ptr = k_rt_mark(rt);
564
+ if (!is_after_mark(rt, root, tm->base_mark)) {
565
+ rt_rewind_and_free(rt, tm->base_mark);
566
+ tm->last_mark = tm->base_mark;
567
567
  return root;
568
568
  }
569
569
 
@@ -574,14 +574,14 @@ k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr) {
574
574
 
575
575
  k_fixup_list fixups = {NULL, 0, 0};
576
576
 
577
- k_value *staged_root = copy_value(rt, root, &staging, &staging_used, &staging_cap, &fixups, *mark_ptr);
577
+ k_value *staged_root = copy_value(rt, root, &staging, &staging_used, &staging_cap, &fixups, tm->base_mark);
578
578
 
579
- rt_rewind_and_free(rt, *mark_ptr);
579
+ rt_rewind_and_free(rt, tm->base_mark);
580
580
 
581
581
  if (staging_used == 0) {
582
582
  free(staging);
583
583
  if (fixups.offsets != NULL) free(fixups.offsets);
584
- *mark_ptr = k_rt_mark(rt);
584
+ tm->last_mark = tm->base_mark;
585
585
  return staged_root;
586
586
  }
587
587
 
@@ -605,7 +605,7 @@ k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr) {
605
605
  free(staging);
606
606
  if (fixups.offsets != NULL) free(fixups.offsets);
607
607
 
608
- *mark_ptr = k_rt_mark(rt);
608
+ tm->last_mark = k_rt_mark(rt);
609
609
 
610
610
  return new_root;
611
611
  }
@@ -13,6 +13,11 @@ typedef struct {
13
13
  size_t used;
14
14
  } k_rt_checkpoint;
15
15
 
16
+ typedef struct {
17
+ k_rt_checkpoint base_mark;
18
+ k_rt_checkpoint last_mark;
19
+ } k_rt_tail_mark;
20
+
16
21
  typedef struct {
17
22
  const char *label;
18
23
  size_t label_length;
@@ -66,7 +71,7 @@ k_rt *k_rt_new(void);
66
71
  void k_rt_reset(k_rt *rt);
67
72
  k_rt_checkpoint k_rt_mark(k_rt *rt);
68
73
  void k_rt_rewind(k_rt *rt, k_rt_checkpoint mark);
69
- k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr);
74
+ k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_tail_mark *mark_ptr);
70
75
  void *k_rt_alloc(k_rt *rt, size_t size);
71
76
  void k_rt_free(k_rt *rt);
72
77
 
@@ -6,6 +6,22 @@ import { fileURLToPath } from "node:url";
6
6
 
7
7
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
8
  const node = process.execPath;
9
+
10
+ function printHelp() {
11
+ console.log(`Usage: node backends/llvm/scripts/run-tests.mjs [options]
12
+
13
+ Runs LLVM backend test suite, integration tests, and conformance suite.
14
+
15
+ Options:
16
+ -h, --help
17
+ Show this help message and exit.`);
18
+ }
19
+
20
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
21
+ printHelp();
22
+ process.exit(0);
23
+ }
24
+
9
25
  const tests = [
10
26
  ["tests/test-llvm.mjs", node, ["tests/test-llvm.mjs"]],
11
27
  ["tests/integration.sh", path.join(root, "tests/integration.sh"), []],
@@ -135,7 +135,7 @@ export async function compileProgramInputToObject(
135
135
  } else if (input.kind === "ko" || input.kind === "klib") {
136
136
  object = decodeObject(fs.readFileSync(input.path));
137
137
  } else if (input.kind === "kvm") {
138
- throw new Error("Compiling directly from .kvm to LLVM is not yet supported; use .k or .ko input.");
138
+ throw new Error("Input is already lowered to .kvm bytecode; use compileLLVMArtifactFromKVM or compileKVMToExecutable.");
139
139
  } else {
140
140
  throw new Error(`Unsupported input kind: ${input.kind}`);
141
141
  }
@@ -6,7 +6,9 @@ import { fileURLToPath } from "node:url";
6
6
  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
+ import { specializeKVM } from "@fraczak/k/kvm.mjs";
9
10
  import { compileObjectToLLVM } from "./llvm.mjs";
11
+ import { compileKVMModuleToLLVM } from "./kvm2llvm.mjs";
10
12
 
11
13
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
14
 
@@ -553,6 +555,40 @@ export function compileObjectToExecutable(object, outputPath, { relation = objec
553
555
  });
554
556
  }
555
557
 
558
+ export function compileKVMToExecutable(kvmInput, outputPath, {
559
+ entry = null,
560
+ inputPattern = null,
561
+ runtimeMode = "fast",
562
+ clangOpt = "-O3"
563
+ } = {}) {
564
+ let kvm = kvmInput;
565
+ if (kvm?.format === "k-vm") {
566
+ if (kvm.layer === "KVM-P" && inputPattern) {
567
+ kvm = specializeKVM(kvm, inputPattern);
568
+ }
569
+ }
570
+ const functions = kvm.functions || (typeof kvm === "object" && !Array.isArray(kvm) ? kvm : null);
571
+ if (!functions) throw new Error("Expected .kvm input to contain a functions object");
572
+ const entryFunc = entry || kvm.entry || kvm.relation || "__main__";
573
+ const mainFunc = functions[entryFunc];
574
+ if (!mainFunc) throw new Error(`No kVM entry function '${entryFunc}' found`);
575
+ const compiledInputPattern = inputPattern || mainFunc?.inputPattern || kvm.inputPattern;
576
+ const outputPattern = mainFunc?.outputPattern || kvm.outputPattern;
577
+ const llvm = compileKVMModuleToLLVM(entryFunc, functions, {
578
+ relation: entryFunc,
579
+ inputPattern: compiledInputPattern,
580
+ outputPattern,
581
+ runtimeMode
582
+ });
583
+ compileLLVMToExecutable(llvm, outputPath, {
584
+ driver: stdioDriverSource({
585
+ inputPattern: compiledInputPattern,
586
+ outputPattern
587
+ }),
588
+ clangOpt
589
+ });
590
+ }
591
+
556
592
  export function compileObjectAndRun(object, { relation = object.main, input, expected = null, inputPattern = null }) {
557
593
  const { llvm } = compileObjectToLLVM(object, {
558
594
  relation,
@@ -49,6 +49,7 @@ function cStringBytes(text) {
49
49
  export function runtimeDeclarations() {
50
50
  return [
51
51
  "%k_rt_mark = type { ptr, i64 }",
52
+ "%k_rt_tail_mark = type { %k_rt_mark, %k_rt_mark }",
52
53
  "%k_result = type { i32, ptr }",
53
54
  "",
54
55
  "declare %k_rt_mark @k_rt_mark(ptr)",
@@ -316,7 +317,7 @@ function lowerRtCompact(ctx, val, tailMarkSlot = "%tail_mark_slot") {
316
317
  const hasBlock = ctx.tempName("has_block");
317
318
  const markBlockPtr = ctx.tempName("mark_block_ptr");
318
319
  ctx.lines.push(` ${hasBlock} = icmp ne ptr ${curBlock}, null`);
319
- ctx.lines.push(` ${markBlockPtr} = getelementptr inbounds %k_rt_mark, ptr ${tailMarkSlot}, i32 0, i32 0`);
320
+ ctx.lines.push(` ${markBlockPtr} = getelementptr inbounds %k_rt_tail_mark, ptr ${tailMarkSlot}, i32 0, i32 1, i32 0`);
320
321
  const markBlock = ctx.tempName("mark_block");
321
322
  ctx.lines.push(` ${markBlock} = load ptr, ptr ${markBlockPtr}`);
322
323
 
@@ -340,7 +341,7 @@ function lowerRtCompact(ctx, val, tailMarkSlot = "%tail_mark_slot") {
340
341
  ctx.lines.push(`${loadGrowthBlock}:`);
341
342
  const curUsed = loadI64At(ctx, curBlock, K_ARENA_BLOCK_USED_OFFSET, "cur_used");
342
343
  const markUsedPtr = ctx.tempName("mark_used_ptr");
343
- ctx.lines.push(` ${markUsedPtr} = getelementptr inbounds %k_rt_mark, ptr ${tailMarkSlot}, i32 0, i32 1`);
344
+ ctx.lines.push(` ${markUsedPtr} = getelementptr inbounds %k_rt_tail_mark, ptr ${tailMarkSlot}, i32 0, i32 1, i32 1`);
344
345
  const markUsed = ctx.tempName("mark_used");
345
346
  ctx.lines.push(` ${markUsed} = load i64, ptr ${markUsedPtr}`);
346
347
  const growth = ctx.tempName("arena_growth");
@@ -605,11 +606,16 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
605
606
 
606
607
  if (isCatchTail) {
607
608
  ctx.allocas.push(" %tail_input_slot = alloca ptr");
608
- ctx.allocas.push(" %tail_mark_slot = alloca %k_rt_mark");
609
+ ctx.allocas.push(" %tail_mark_slot = alloca %k_rt_tail_mark");
609
610
  ctx.lines.push(" store ptr %input, ptr %tail_input_slot");
610
611
  const initMark = ctx.tempName("init_mark");
611
612
  ctx.lines.push(` ${initMark} = call %k_rt_mark @k_rt_mark(ptr %rt)`);
612
- ctx.lines.push(` store %k_rt_mark ${initMark}, ptr %tail_mark_slot`);
613
+ const baseMarkPtr = ctx.tempName("base_mark_ptr");
614
+ const lastMarkPtr = ctx.tempName("last_mark_ptr");
615
+ ctx.lines.push(` ${baseMarkPtr} = getelementptr inbounds %k_rt_tail_mark, ptr %tail_mark_slot, i32 0, i32 0`);
616
+ ctx.lines.push(` store %k_rt_mark ${initMark}, ptr ${baseMarkPtr}`);
617
+ ctx.lines.push(` ${lastMarkPtr} = getelementptr inbounds %k_rt_tail_mark, ptr %tail_mark_slot, i32 0, i32 1`);
618
+ ctx.lines.push(` store %k_rt_mark ${initMark}, ptr ${lastMarkPtr}`);
613
619
  ctx.lines.push(" br label %tail_loop");
614
620
  ctx.lines.push("tail_loop:");
615
621
  ctx.lines.push(" %tail_input = load ptr, ptr %tail_input_slot");
@@ -95,6 +95,17 @@ printf '{"x":"left","y":"right"}' \
95
95
 
96
96
  printf '{"x":"left"}' | node ../../codecs/k-parse.mjs | "$TMP_DIR/run-exe" && exit 1 || test "$?" -eq 5
97
97
 
98
+ node ../../objects/compile.mjs '?{<{}=X0 left, ...> x, <X0 right, ...> y} .x' "$TMP_DIR/run-proj.kvm"
99
+ node ./bin/k-llvm-compile.mjs "$TMP_DIR/run-proj.kvm" "$TMP_DIR/run-proj-kvm.ll"
100
+ grep -q '@k_llvm_metadata' "$TMP_DIR/run-proj-kvm.ll"
101
+ grep -q 'define %k_result @k_main' "$TMP_DIR/run-proj-kvm.ll"
102
+ node ./bin/k-llvm-build.mjs "$TMP_DIR/run-proj.kvm" -o "$TMP_DIR/run-kvm-exe"
103
+ printf '{"x":"left","y":"right"}' \
104
+ | node ../../codecs/k-parse.mjs \
105
+ | "$TMP_DIR/run-kvm-exe" \
106
+ | node ../../codecs/k-print.mjs \
107
+ | grep -qx '"left"'
108
+
98
109
  node ./bin/k-llvm-build.mjs '?<{} left, {} right, ...>' -o "$TMP_DIR/id-exe"
99
110
  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));" \
100
111
  | "$TMP_DIR/id-exe" \