@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
@@ -60,6 +60,73 @@ import { compileARM64ArtifactFromObject } from "../backends/arm64/src/arm64.mjs"
60
60
  import { compileWasmArtifactFromObject } from "../backends/wasm/src/wasm.mjs";
61
61
  import { inputPatternForObjectRelation } from "../backends/llvm/src/executable.mjs";
62
62
 
63
+ function printHelp() {
64
+ console.log(`Usage: node scripts/perf-poly.mjs [options]
65
+
66
+ Polymorphic List Operations Benchmark Harness.
67
+ Benchmarks reverse, concat, split_by, get_nth, and length on
68
+ Examples/poly.k across Native JS, kVM interpreter, LLVM, Wasm, and ARM64.
69
+
70
+ Options:
71
+ --trace
72
+ Enable 7-phase nanosecond execution timing.
73
+ --profile
74
+ Enable per-function call count profiling.
75
+ --wasm-in-process
76
+ Run WebAssembly in-process instead of persistent subprocess runner.
77
+ -h, --help
78
+ Show this help message and exit.
79
+
80
+ Environment Variables:
81
+ BACKENDS
82
+ Target backend to run: 'llvm', 'wasm', or 'arm64'.
83
+ LLVM_ONLY
84
+ Run only LLVM backend ('1' to enable).
85
+ WASM_ONLY
86
+ Run only WebAssembly backend ('1' to enable).
87
+ ARM64_ONLY
88
+ Run only ARM64 backend ('1' to enable).
89
+ BACKENDS_ONLY
90
+ Skip JS and kVM interpreter baselines ('1' to enable).
91
+ LIST_LENGTH
92
+ Workload size for list operations (default: 40).
93
+ ITERATIONS
94
+ Measured iterations per benchmark (default: 3).
95
+ TRACE, K_TRACE
96
+ Enable nanosecond execution tracing ('1' to enable).
97
+ PROFILE, K_PROFILE
98
+ Enable per-function call count profiling ('1' to enable).
99
+ ARM64_WARMUP_ITERATIONS
100
+ Warmup iterations for ARM64 backend (default: 1).
101
+ ARM64_OPT
102
+ ARM64 backend optimization flags (default: -O2).
103
+ ARM64_SPAWN_PER_CALL
104
+ Spawn process per ARM64 call instead of persistent ('1' or '0').
105
+ ARM64_PIPELINE
106
+ Use persistent pipelined runner for ARM64 ('1' or '0').
107
+ LLVM_WARMUP_ITERATIONS
108
+ Warmup iterations for LLVM backend (default: 1).
109
+ LLVM_SPAWN_PER_CALL
110
+ Spawn process per LLVM call instead of persistent ('1' or '0').
111
+ LLVM_PIPELINE
112
+ Use persistent pipelined runner for LLVM ('1' or '0').
113
+ K_LLVM_CACHE_DIR
114
+ Directory to cache compiled LLVM executable binaries.
115
+ WASM_WARMUP_ITERATIONS
116
+ Warmup iterations for WebAssembly backend (default: 3).
117
+ WASM_RESET
118
+ Reset WebAssembly memory between iterations (default: 1).
119
+ WASM_PIPE
120
+ Use persistent subprocess for Wasm (default: 1, '0': in-process).
121
+ WASM_IN_PROCESS
122
+ Run WebAssembly in-process ('1' to enable).`);
123
+ }
124
+
125
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
126
+ printHelp();
127
+ process.exit(0);
128
+ }
129
+
63
130
  // Backend selection
64
131
  const backendsEnv = process.env.BACKENDS?.toLowerCase();
65
132
  const llvmOnly = process.env.LLVM_ONLY === "1" || backendsEnv === "llvm";
@@ -126,7 +193,7 @@ function buildExportPreamble(exports, libraries) {
126
193
  return lines.join("\n") + "\n";
127
194
  }
128
195
 
129
- const neededExports = ["0", "int", "inc", "dec", "nat", "zero_int?", "nil", "cons", "car", "cdr"];
196
+ const neededExports = ["0", "int", "inc", "dec", "nat", "zero_int?"];
130
197
  const exportPreamble = buildExportPreamble(neededExports, [arithmeticsLib]);
131
198
  const polyLib = loadLibrary(compileLibrary(exportPreamble + polySource, {
132
199
  source: polyPath,
@@ -5,6 +5,31 @@ import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+
9
+ function printHelp() {
10
+ console.log(
11
+ `Usage: node scripts/render-benchmark.mjs [in.json] [out.html]
12
+
13
+ Renders benchmark results JSON into a standalone interactive HTML report.
14
+
15
+ Arguments:
16
+ input-json
17
+ Path to benchmark results JSON file.
18
+ (default: benchmark-results.json)
19
+ output-html
20
+ Destination path for generated HTML report.
21
+ (default: benchmark-results.html)
22
+
23
+ Options:
24
+ -h, --help
25
+ Show this help message and exit.`);
26
+ }
27
+
28
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
29
+ printHelp();
30
+ process.exit(0);
31
+ }
32
+
8
33
  const inputPath = path.resolve(root, process.argv[2] || "benchmark-results.json");
9
34
  const outputPath = path.resolve(root, process.argv[3] || "benchmark-results.html");
10
35
 
@@ -7,6 +7,24 @@ import { fileURLToPath } from "node:url";
7
7
 
8
8
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
9
  const node = process.execPath;
10
+
11
+ function printHelp() {
12
+ console.log(`Usage: node scripts/run-tests.mjs [options]
13
+
14
+ Runs the comprehensive K language test suite, including core unit tests,
15
+ type derivation, codecs, object encoding, conformance, REPL, interpreter,
16
+ and arithmetic suites.
17
+
18
+ Options:
19
+ -h, --help
20
+ Show this help message and exit.`);
21
+ }
22
+
23
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
24
+ printHelp();
25
+ process.exit(0);
26
+ }
27
+
10
28
  const derivationTests = fs.readdirSync(path.join(root, "tests/code-derivation"))
11
29
  .filter((name) => name.endsWith(".mjs"))
12
30
  .sort()
@@ -31,6 +49,7 @@ const tests = [
31
49
  ["tests/test-kvm.mjs", node, ["tests/test-kvm.mjs"]],
32
50
  ["tests/test-kvm-polymorphic.mjs", node, ["tests/test-kvm-polymorphic.mjs"]],
33
51
  ["tests/test-arithmetics.mjs", node, ["tests/test-arithmetics.mjs"]],
52
+ ["tests/test-karatsuba.mjs", node, ["tests/test-karatsuba.mjs"]],
34
53
  ["tests/test-poly.mjs", node, ["tests/test-poly.mjs"]],
35
54
  ["tests/test-ieee-arithmetic.mjs", node, ["tests/test-ieee-arithmetic.mjs"]],
36
55
  ["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
@@ -100,10 +100,13 @@ try {
100
100
  assertOk(resDec, "2 int dec dec zero_int? should succeed");
101
101
 
102
102
  const divTestCases = [
103
+ { x: "0", yAlias: "7", yVal: 7n },
104
+ { x: "3", yAlias: "7", yVal: 7n },
103
105
  { x: "4", yAlias: "1", yVal: 1n },
104
106
  { x: "10", yAlias: "2", yVal: 2n },
105
107
  { x: "12", yAlias: "3", yVal: 3n },
106
108
  { x: "11", yAlias: "3", yVal: 3n },
109
+ { x: "12", yAlias: "12", yVal: 12n },
107
110
  { x: "100", yAlias: "7", yVal: 7n },
108
111
  { x: "-100", yAlias: "7", yVal: 7n },
109
112
  { x: "1000", yAlias: "10", yVal: 10n }
@@ -143,6 +146,99 @@ try {
143
146
  assert.equal(remOut, expectedRem, `div(${x}, ${yAlias}) remainder should match BigInt oracle`);
144
147
  }
145
148
 
149
+ // Regression tests for EQ
150
+ const dummyWire = parseIntWire("0");
151
+ const resEqTrue = run(node, [
152
+ "k.mjs",
153
+ "--lib", libPath,
154
+ "--export", "EQ:EQ",
155
+ "--export", "int:int",
156
+ "--export", "2:two",
157
+ "{two x, two y} EQ .x int"
158
+ ], { input: dummyWire });
159
+ assertOk(resEqTrue, "{2, 2} EQ should succeed");
160
+
161
+ const resEqFalse = run(node, [
162
+ "k.mjs",
163
+ "--lib", libPath,
164
+ "--export", "EQ:EQ",
165
+ "--export", "int:int",
166
+ "--export", "2:two",
167
+ "--export", "3:three",
168
+ "{two x, three y} EQ .x int"
169
+ ], { input: dummyWire });
170
+ assert.notEqual(resEqFalse.status, 0, "{2, 3} EQ should fail");
171
+
172
+ // Regression tests for pos?
173
+ const resPosTrue = run(node, [
174
+ "k.mjs",
175
+ "--lib", libPath,
176
+ "--export", "pos?:pos?",
177
+ "--export", "int:int",
178
+ "--export", "2:two",
179
+ "two int pos?"
180
+ ], { input: dummyWire });
181
+ assertOk(resPosTrue, "2 int pos? should succeed");
182
+
183
+ const resPosZero = run(node, [
184
+ "k.mjs",
185
+ "--lib", libPath,
186
+ "--export", "pos?:pos?",
187
+ "--export", "int:int",
188
+ "--export", "0:zero",
189
+ "zero int pos?"
190
+ ], { input: dummyWire });
191
+ assert.notEqual(resPosZero.status, 0, "0 int pos? should fail");
192
+
193
+ // Regression tests for mod, gcd, lcm
194
+ const resMod = run(node, [
195
+ "k.mjs",
196
+ "--lib", libPath,
197
+ "--export", "mod:mod",
198
+ "--export", "int:int",
199
+ "--export", "10:ten",
200
+ "--export", "3:three",
201
+ "{ten x, three y} mod int"
202
+ ], { input: dummyWire });
203
+ assertOk(resMod, "10 % 3 mod");
204
+ assert.equal(printIntWire(resMod.stdout, "mod"), "1");
205
+
206
+ const resGcd = run(node, [
207
+ "k.mjs",
208
+ "--lib", libPath,
209
+ "--export", "gcd:gcd",
210
+ "--export", "int:int",
211
+ "--export", "12:twelve",
212
+ "--export", "8:eight",
213
+ "{twelve x, eight y} gcd int"
214
+ ], { input: dummyWire });
215
+ assertOk(resGcd, "gcd(12, 8)");
216
+ assert.equal(printIntWire(resGcd.stdout, "gcd"), "4");
217
+
218
+ const resGcdZero = run(node, [
219
+ "k.mjs",
220
+ "--lib", libPath,
221
+ "--export", "gcd:gcd",
222
+ "--export", "int:int",
223
+ "--export", "4:four",
224
+ "--export", "0b:zero_b",
225
+ "{four x, zero_b y} gcd int"
226
+ ], { input: dummyWire });
227
+ assertOk(resGcdZero, "gcd(4, 0b)");
228
+ assert.equal(printIntWire(resGcdZero.stdout, "gcd 0b"), "4");
229
+
230
+ const resLcm = run(node, [
231
+ "k.mjs",
232
+ "--lib", libPath,
233
+ "--export", "lcm:lcm",
234
+ "--export", "int:int",
235
+ "--export", "4:four",
236
+ "--export", "6:six",
237
+ "{four x, six y} lcm int"
238
+ ], { input: dummyWire });
239
+ assertOk(resLcm, "lcm(4, 6)");
240
+ assert.equal(printIntWire(resLcm.stdout, "lcm"), "12");
241
+
146
242
  console.log("OK");
147
243
  } finally {
148
244
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -0,0 +1,133 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
+ const root = path.resolve(scriptDir, "..");
10
+ const node = process.execPath;
11
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "k-karatsuba-"));
12
+ const libPath = path.join(tmpDir, "karatsuba-mult.klib");
13
+ const kSource = path.join(root, "Examples/karatsuba-mult.k");
14
+ const program = "{() x, () y} karatsuba";
15
+
16
+ const smallInput =
17
+ "111111122222233333344444445555556666667777777888888999999000000" +
18
+ "11111122222233333344444455555";
19
+
20
+ function run(command, args, options = {}) {
21
+ const result = spawnSync(command, args, {
22
+ cwd: root,
23
+ input: options.input,
24
+ encoding: options.encoding ?? null,
25
+ maxBuffer: 64 * 1024 * 1024
26
+ });
27
+ if (result.error && result.status == null) throw result.error;
28
+ return result;
29
+ }
30
+
31
+ function assertOk(result, label) {
32
+ if (result.status !== 0) {
33
+ const stderr = Buffer.isBuffer(result.stderr)
34
+ ? result.stderr.toString("utf8")
35
+ : String(result.stderr ?? "");
36
+ const stdout = Buffer.isBuffer(result.stdout)
37
+ ? result.stdout.toString("utf8")
38
+ : String(result.stdout ?? "");
39
+ assert.fail(
40
+ `${label} failed (status ${result.status})\n` +
41
+ `STDERR:\n${stderr}\nSTDOUT:\n${stdout}`
42
+ );
43
+ }
44
+ }
45
+
46
+ function parseIntWire(text) {
47
+ const result = run(node, ["codecs/int.mjs", "--parse"], {
48
+ input: `${text}\n`
49
+ });
50
+ assertOk(result, `parse int ${text}`);
51
+ return result.stdout;
52
+ }
53
+
54
+ function printIntWire(wire, label) {
55
+ const result = run(node, ["codecs/int.mjs", "--print"], {
56
+ input: wire,
57
+ encoding: "utf8"
58
+ });
59
+ assertOk(result, `${label} print`);
60
+ return result.stdout.trim();
61
+ }
62
+
63
+ function runKaratsuba(inputText) {
64
+ const wire = parseIntWire(inputText);
65
+ const result = run(node, [
66
+ "k.mjs",
67
+ "--lib", libPath,
68
+ "--export", "karatsuba:karatsuba",
69
+ program
70
+ ], { input: wire });
71
+ assertOk(result, `native karatsuba(${inputText})`);
72
+ return printIntWire(result.stdout, `native karatsuba`);
73
+ }
74
+
75
+ function expectedSquare(inputText) {
76
+ const n = BigInt(inputText);
77
+ return (n * n).toString();
78
+ }
79
+
80
+ try {
81
+ assertOk(
82
+ run(node, ["objects/compile.mjs", kSource, libPath], {
83
+ encoding: "utf8"
84
+ }),
85
+ "compile Examples/karatsuba-mult.k"
86
+ );
87
+
88
+ const testInputs = [
89
+ "0",
90
+ "1",
91
+ "-1",
92
+ "7",
93
+ "-7",
94
+ "42",
95
+ "-42",
96
+ "123456789",
97
+ "-123456789",
98
+ "987654321987654321",
99
+ "-987654321987654321",
100
+ smallInput,
101
+ `-${smallInput}`
102
+ ];
103
+
104
+ for (const input of testInputs) {
105
+ assert.equal(
106
+ runKaratsuba(input),
107
+ expectedSquare(input),
108
+ `karatsuba square(${input.slice(0, 20)}...) mismatch`
109
+ );
110
+ }
111
+
112
+ // Test asymmetric pair multiplication with mixed signs via int list
113
+ const pairWire = parseIntWire(
114
+ "[12345678901234567890, -98765432109876543210]"
115
+ );
116
+ const pairProgram = "/cons { .car x, .cdr /cons .car y } karatsuba";
117
+ const resPair = run(node, [
118
+ "k.mjs",
119
+ "--lib", libPath,
120
+ "--export", "karatsuba:karatsuba",
121
+ pairProgram
122
+ ], { input: pairWire });
123
+ assertOk(resPair, "pair multiplication");
124
+ assert.equal(
125
+ printIntWire(resPair.stdout, "pair multiplication"),
126
+ (12345678901234567890n * -98765432109876543210n).toString(),
127
+ "asymmetric pair multiplication should match BigInt oracle"
128
+ );
129
+
130
+ console.log("OK");
131
+ } finally {
132
+ fs.rmSync(tmpDir, { recursive: true, force: true });
133
+ }
@@ -139,14 +139,15 @@ if (chromiumBin) {
139
139
  const vfsList = await evaluateAsync(`window.kRepl.getAllVfsFiles().sort()`);
140
140
  const expectedFiles = [
141
141
  "arithmetics.k",
142
+ "codecs/ieee.mjs",
143
+ "codecs/int.mjs",
144
+ "codecs/json.mjs",
145
+ "codecs/unit.mjs",
146
+ "codecs/utf8.mjs",
142
147
  "core.k",
143
148
  "ieee.k",
144
- "ieee.mjs",
145
- "int.mjs",
146
- "json.mjs",
147
- "poly.k",
148
- "unit.mjs",
149
- "utf8.mjs"
149
+ "karatsuba-mult.k",
150
+ "poly.k"
150
151
  ].sort();
151
152
  assert.deepStrictEqual(vfsList, expectedFiles, `getAllVfsFiles() must match curated files list, got: ${JSON.stringify(vfsList)}`);
152
153
 
@@ -158,6 +159,22 @@ if (chromiumBin) {
158
159
  })()`);
159
160
  assert(loadOut.includes("loaded arithmetics.k"), `Expected loaded arithmetics.k, got: ${loadOut}`);
160
161
 
162
+ // Test :load karatsuba-mult.k
163
+ const loadKaratsubaOut = await evaluateAsync(`(async () => {
164
+ await window.kRepl.executeCommand(":load karatsuba-mult.k");
165
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
166
+ return lines[lines.length - 1];
167
+ })()`);
168
+ assert(loadKaratsubaOut.includes("loaded karatsuba-mult.k"), `Expected loaded karatsuba-mult.k, got: ${loadKaratsubaOut}`);
169
+
170
+ // Test evaluating {1 int x, 1 int y} karatsuba
171
+ const karatsubaVal = await evaluateAsync(`(async () => {
172
+ await window.kRepl.executeCommand("{1 int x, 1 int y} karatsuba");
173
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
174
+ return lines[lines.length - 1];
175
+ })()`);
176
+ assert(karatsubaVal.includes("{}|_|1|+"), `Expected {}|_|1|+ in output, got: ${karatsubaVal}`);
177
+
161
178
  // Test evaluating 10
162
179
  const valOut = await evaluateAsync(`(async () => {
163
180
  await window.kRepl.executeCommand("10");