@fraczak/k 6.3.3 → 6.5.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 (157) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/DOCS/CODECS.md +258 -0
  3. package/DOCS/DICTIONARY.md +1172 -0
  4. package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
  5. package/DOCS/KIR_V1.md +221 -0
  6. package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
  7. package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
  8. package/DOCS/PROFILING_AND_TRACING.md +196 -0
  9. package/DOCS/REPL.md +61 -4
  10. package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
  11. package/DOCS/dependency-graph.md +144 -0
  12. package/Examples/arithmetics.k +108 -82
  13. package/Examples/bnat.k +1 -1
  14. package/Examples/buda.k +44 -0
  15. package/Examples/ieee.k +300 -8671
  16. package/Examples/poly.k +67 -0
  17. package/README.md +68 -19
  18. package/Value.mjs +8 -2
  19. package/augmentation.mjs +9 -7
  20. package/backend-api.mjs +11 -2
  21. package/backends/arm64/README.md +104 -0
  22. package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
  23. package/backends/arm64/bin/k-arm64-run.mjs +58 -0
  24. package/backends/arm64/bin/k-arm64.mjs +88 -0
  25. package/backends/arm64/scripts/run-tests.mjs +41 -0
  26. package/backends/arm64/src/arm64.mjs +229 -0
  27. package/backends/arm64/src/cli.mjs +155 -0
  28. package/backends/arm64/src/kvm2arm64.mjs +906 -0
  29. package/backends/arm64/src/runtime.c +728 -0
  30. package/backends/arm64/tests/integration.sh +112 -0
  31. package/backends/arm64/tests/test-arm64.mjs +240 -0
  32. package/backends/llvm/README.md +135 -0
  33. package/backends/llvm/bin/k-llvm-build.mjs +71 -0
  34. package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
  35. package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
  36. package/backends/llvm/bin/k-llvm-run.mjs +90 -0
  37. package/backends/llvm/runtime/krt.c +1508 -0
  38. package/backends/llvm/runtime/krt.h +109 -0
  39. package/backends/llvm/scripts/conformance.mjs +76 -0
  40. package/backends/llvm/scripts/run-tests.mjs +28 -0
  41. package/backends/llvm/src/executable.mjs +562 -0
  42. package/backends/llvm/src/kvm2llvm.mjs +998 -0
  43. package/backends/llvm/src/llvm.mjs +1092 -0
  44. package/backends/llvm/tests/composition-driver.c +22 -0
  45. package/backends/llvm/tests/identity-driver.c +27 -0
  46. package/backends/llvm/tests/integration.sh +153 -0
  47. package/backends/llvm/tests/perf-support.mjs +735 -0
  48. package/backends/llvm/tests/product-driver.c +23 -0
  49. package/backends/llvm/tests/projection-driver.c +19 -0
  50. package/backends/llvm/tests/relation-driver.c +29 -0
  51. package/backends/llvm/tests/test-llvm.mjs +240 -0
  52. package/backends/llvm/tests/union-driver.c +31 -0
  53. package/backends/llvm/tests/variant-driver.c +19 -0
  54. package/backends/llvm/tests/variant-projection-driver.c +21 -0
  55. package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
  56. package/backends/wasm/README.md +97 -0
  57. package/backends/wasm/TECHNICAL-DEBT.md +58 -0
  58. package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
  59. package/backends/wasm/bin/k-wasm-run.mjs +140 -0
  60. package/backends/wasm/bin/k-wasm.mjs +49 -0
  61. package/backends/wasm/demo_ieee.mjs +319 -0
  62. package/backends/wasm/runtime.wat +475 -0
  63. package/backends/wasm/scripts/run-tests.mjs +41 -0
  64. package/backends/wasm/src/cli.mjs +133 -0
  65. package/backends/wasm/src/kvm2wasm.mjs +697 -0
  66. package/backends/wasm/src/wasm.mjs +1054 -0
  67. package/backends/wasm/tests/integration.sh +182 -0
  68. package/backends/wasm/tests/perf-support.mjs +336 -0
  69. package/backends/wasm/tests/test-wasm.mjs +625 -0
  70. package/browser/repl-browser.mjs +1584 -0
  71. package/browser/shims/assert.mjs +19 -0
  72. package/browser/shims/buffer.mjs +224 -0
  73. package/browser/shims/crypto.mjs +103 -0
  74. package/browser/shims/fs.mjs +159 -0
  75. package/browser/shims/os.mjs +17 -0
  76. package/browser/shims/path.mjs +54 -0
  77. package/browser/shims/process.mjs +44 -0
  78. package/browser/shims/readline.mjs +11 -0
  79. package/browser/shims/url.mjs +12 -0
  80. package/browser/vfs-data.mjs +19 -0
  81. package/codecs/README.md +3 -0
  82. package/codecs/runtime/show-value.mjs +12 -3
  83. package/conformance/helper-reuse/case.json +8 -0
  84. package/conformance/helper-reuse/expected.kv +1 -0
  85. package/conformance/helper-reuse/input.kv +1 -0
  86. package/conformance/helper-reuse/program.k +2 -0
  87. package/conformance/identity/case.json +8 -0
  88. package/conformance/identity/expected.kv +1 -0
  89. package/conformance/identity/input.kv +1 -0
  90. package/conformance/identity/program.k +1 -0
  91. package/conformance/product-projection/case.json +8 -0
  92. package/conformance/product-projection/expected.kv +1 -0
  93. package/conformance/product-projection/input.kv +1 -0
  94. package/conformance/product-projection/program.k +1 -0
  95. package/conformance/run.mjs +149 -0
  96. package/conformance/variant-construction/case.json +8 -0
  97. package/conformance/variant-construction/expected.kv +1 -0
  98. package/conformance/variant-construction/input.kv +1 -0
  99. package/conformance/variant-construction/program.k +1 -0
  100. package/harness/adapters/arm64.mjs +105 -0
  101. package/harness/adapters/kvm.mjs +144 -0
  102. package/harness/adapters/llvm.mjs +118 -0
  103. package/harness/adapters/native.mjs +77 -0
  104. package/harness/adapters/wasm.mjs +169 -0
  105. package/harness/backend.mjs +95 -0
  106. package/harness/cli.mjs +130 -0
  107. package/harness/format.mjs +95 -0
  108. package/harness/index.mjs +25 -0
  109. package/harness/persistent.mjs +121 -0
  110. package/harness/registry.mjs +44 -0
  111. package/harness/suite.mjs +257 -0
  112. package/harness/suites/poly.mjs +156 -0
  113. package/harness/trace.mjs +50 -0
  114. package/harness/util.mjs +92 -0
  115. package/intrinsics.mjs +41 -0
  116. package/kir.mjs +399 -0
  117. package/kvm.mjs +585 -81
  118. package/objects/README.md +37 -3
  119. package/objects/compile.mjs +15 -22
  120. package/objects/inspect.mjs +96 -0
  121. package/objects/validate.mjs +415 -0
  122. package/package.json +51 -19
  123. package/repl-codecs.mjs +105 -6
  124. package/repl.mjs +335 -49
  125. package/run.mjs +2 -7
  126. package/scripts/benchmark.mjs +406 -22
  127. package/scripts/build-repl-html.mjs +1784 -0
  128. package/scripts/perf-ieee.mjs +586 -0
  129. package/scripts/perf-int.mjs +531 -0
  130. package/scripts/perf-poly.mjs +945 -0
  131. package/scripts/render-benchmark.mjs +12 -8
  132. package/scripts/run-tests.mjs +6 -0
  133. package/tests/integration.sh +19 -2
  134. package/tests/test-arithmetics.mjs +54 -0
  135. package/tests/test-ieee-arithmetic.mjs +4 -0
  136. package/tests/test-kir.mjs +85 -0
  137. package/tests/test-kvm-polymorphic.mjs +278 -0
  138. package/tests/test-kvm.mjs +25 -6
  139. package/tests/test-poly.mjs +129 -0
  140. package/tests/test-repl-html.mjs +303 -0
  141. package/tests/test-repl.mjs +144 -1
  142. package/tests/test-validate-object.mjs +40 -0
  143. package/a.klib +0 -1
  144. package/a.kvm +0 -142676
  145. package/a.wasm +0 -0
  146. package/benchmark-results.html +0 -532
  147. package/benchmark-results.json +0 -480
  148. package/k-vscode/.vscode/launch.json +0 -14
  149. package/k-vscode/README.md +0 -75
  150. package/k-vscode/extension.js +0 -273
  151. package/k-vscode/language-configuration.json +0 -42
  152. package/k-vscode/package.json +0 -79
  153. package/k-vscode/snippets/k.json +0 -92
  154. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  155. package/k-vscode/test/background-ranges.test.js +0 -50
  156. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  157. package/scripts/git-hooks/pre-commit +0 -10
@@ -0,0 +1,121 @@
1
+ import { spawn } from "node:child_process";
2
+ import { parseTraceLine } from "./trace.mjs";
3
+
4
+ export class PersistentExecutable {
5
+ constructor(exePath, { env = {} } = {}) {
6
+ this.exePath = exePath;
7
+ this.child = spawn(exePath, ["--server"], {
8
+ stdio: ["pipe", "pipe", "pipe"],
9
+ env: { ...process.env, K_TRACE: "1", ...env }
10
+ });
11
+ this.buffer = Buffer.alloc(0);
12
+ this.pending = [];
13
+ this.stderr = [];
14
+ this.stderrRemainder = "";
15
+ this.traces = [];
16
+ this.waitingForTrace = [];
17
+ this.closed = false;
18
+ this.currentProfile = null;
19
+ this.lastProfile = null;
20
+
21
+ this.child.stdout.on("data", chunk => {
22
+ this.buffer = Buffer.concat([this.buffer, chunk]);
23
+ this.drainOutput();
24
+ });
25
+
26
+ this.child.stderr.on("data", chunk => {
27
+ this.stderr.push(chunk);
28
+ this.stderrRemainder += chunk.toString("utf8");
29
+ let idx;
30
+ while ((idx = this.stderrRemainder.indexOf("\n")) !== -1) {
31
+ const line = this.stderrRemainder.slice(0, idx).trim();
32
+ this.stderrRemainder = this.stderrRemainder.slice(idx + 1);
33
+ if (line === "K_PROFILE_BEGIN") {
34
+ this.currentProfile = {};
35
+ } else if (line.startsWith("K_FUNC_CALL ") && this.currentProfile) {
36
+ const match = line.match(/^K_FUNC_CALL name=(\S+) count=(\d+)$/);
37
+ if (match) this.currentProfile[match[1]] = Number(match[2]);
38
+ } else if (line === "K_PROFILE_END") {
39
+ this.lastProfile = this.currentProfile;
40
+ this.currentProfile = null;
41
+ } else if (line.startsWith("K_TRACE_PHASES ")) {
42
+ const parsed = parseTraceLine(line);
43
+ if (parsed) {
44
+ if (this.lastProfile) {
45
+ parsed.profile = this.lastProfile;
46
+ this.lastProfile = null;
47
+ }
48
+ if (this.waitingForTrace.length > 0) {
49
+ const item = this.waitingForTrace.shift();
50
+ item.resolve({ payload: item.payload, trace: parsed });
51
+ } else {
52
+ this.traces.push(parsed);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ });
58
+
59
+ this.child.on("error", error => this.fail(error));
60
+ this.child.on("close", status => {
61
+ this.closed = true;
62
+ if (status !== 0) {
63
+ this.fail(new Error(`${this.exePath} --server failed with status ${status}\n${Buffer.concat(this.stderr).toString("utf8")}`.trim()));
64
+ } else if (this.pending.length > 0 || this.waitingForTrace.length > 0) {
65
+ this.fail(new Error(`${this.exePath} --server closed with pending request(s)`));
66
+ }
67
+ });
68
+ }
69
+
70
+ fail(error) {
71
+ while (this.pending.length > 0) this.pending.shift().reject(error);
72
+ while (this.waitingForTrace.length > 0) this.waitingForTrace.shift().reject(error);
73
+ }
74
+
75
+ drainOutput() {
76
+ while (this.pending.length > 0 && this.buffer.length >= 4) {
77
+ const length = this.buffer.readUInt32BE(0);
78
+ if (this.buffer.length < 4 + length) return;
79
+ const payload = Buffer.from(this.buffer.subarray(4, 4 + length));
80
+ this.buffer = this.buffer.subarray(4 + length);
81
+ const req = this.pending.shift();
82
+ if (!req.includeTrace) {
83
+ req.resolve(payload);
84
+ } else if (this.traces.length > 0) {
85
+ const trace = this.traces.shift();
86
+ req.resolve({ payload, trace });
87
+ } else {
88
+ this.waitingForTrace.push({ payload, resolve: req.resolve, reject: req.reject });
89
+ }
90
+ }
91
+ }
92
+
93
+ request(payloadWire) {
94
+ if (this.closed) return Promise.reject(new Error(`${this.exePath} --server is closed`));
95
+ return new Promise((resolve, reject) => {
96
+ this.pending.push({ resolve, reject, includeTrace: false });
97
+ const lengthHeader = Buffer.alloc(4);
98
+ lengthHeader.writeUInt32BE(payloadWire.length, 0);
99
+ this.child.stdin.write(Buffer.concat([lengthHeader, payloadWire]));
100
+ });
101
+ }
102
+
103
+ requestWithTrace(payloadWire) {
104
+ if (this.closed) return Promise.reject(new Error(`${this.exePath} --server is closed`));
105
+ return new Promise((resolve, reject) => {
106
+ this.pending.push({ resolve, reject, includeTrace: true });
107
+ const lengthHeader = Buffer.alloc(4);
108
+ lengthHeader.writeUInt32BE(payloadWire.length, 0);
109
+ this.child.stdin.write(Buffer.concat([lengthHeader, payloadWire]));
110
+ });
111
+ }
112
+
113
+ close() {
114
+ if (this.closed) return;
115
+ this.closed = true;
116
+ try {
117
+ this.child.stdin.end();
118
+ } catch {}
119
+ this.child.kill();
120
+ }
121
+ }
@@ -0,0 +1,44 @@
1
+ import { NativeAdapter } from "./adapters/native.mjs";
2
+ import { KVMAdapter } from "./adapters/kvm.mjs";
3
+ import { WasmAdapter } from "./adapters/wasm.mjs";
4
+ import { ARM64Adapter } from "./adapters/arm64.mjs";
5
+ import { LLVMAdapter } from "./adapters/llvm.mjs";
6
+
7
+ export function createRegistry(config = {}) {
8
+ const adapters = new Map();
9
+
10
+ function register(id, factory) {
11
+ adapters.set(id, factory);
12
+ }
13
+
14
+ register("native-aware", () => new NativeAdapter("aware", config.state));
15
+ register("native-free", () => new NativeAdapter("free", config.state));
16
+ register("kvm", () => new KVMAdapter(config.state));
17
+ register("wasm", () => new WasmAdapter(config.wasm || {}));
18
+ register("wasm-in-process", () => new WasmAdapter({ id: "wasm-in-process", mode: "in-process", ...(config.wasm || {}) }));
19
+ register("arm64", () => new ARM64Adapter(config.arm64 || {}));
20
+ register("llvm", () => new LLVMAdapter(config.llvm || {}));
21
+
22
+ return {
23
+ getBackend(id) {
24
+ const factory = adapters.get(id);
25
+ if (!factory) throw new Error(`Unknown backend ID: '${id}'`);
26
+ return factory();
27
+ },
28
+
29
+ listBackendIds() {
30
+ return [...adapters.keys()];
31
+ },
32
+
33
+ getAvailableBackends() {
34
+ const list = [];
35
+ for (const [id, factory] of adapters.entries()) {
36
+ const backend = factory();
37
+ if (backend.isAvailable()) {
38
+ list.push(backend);
39
+ }
40
+ }
41
+ return list;
42
+ }
43
+ };
44
+ }
@@ -0,0 +1,257 @@
1
+ import { createRegistry } from "./registry.mjs";
2
+ import { safeDeepEqual, average, min, max, stdDev, makeCacheDir } from "./util.mjs";
3
+ import { averageTrace } from "./trace.mjs";
4
+
5
+ export class BenchmarkSuite {
6
+ constructor(name, options = {}) {
7
+ this.name = name;
8
+ this.options = options;
9
+ this.cases = [];
10
+ this.state = options.state || null;
11
+ this.registry = createRegistry({ state: this.state, ...options });
12
+ this.compiled = new Map(); // backendId -> Array<compiledCase>
13
+ this.runners = new Map(); // backendId -> runner
14
+ this.cacheDir = options.cacheDir || makeCacheDir(`k-harness-${name}-`);
15
+ }
16
+
17
+ addCase(testCase) {
18
+ this.cases.push(testCase);
19
+ }
20
+
21
+ getBackend(id) {
22
+ return this.registry.getBackend(id);
23
+ }
24
+
25
+ getAvailableBackendIds() {
26
+ return this.registry.getAvailableBackends().map(b => b.id);
27
+ }
28
+
29
+ async compileAll(backendIds, options = {}) {
30
+ const results = {};
31
+ for (const id of backendIds) {
32
+ const backend = this.getBackend(id);
33
+ if (!backend.isAvailable()) {
34
+ results[id] = { status: "unavailable", reason: "not supported on this platform" };
35
+ continue;
36
+ }
37
+
38
+ const compiledCases = [];
39
+ let allOk = true;
40
+ let failureError = null;
41
+
42
+ for (const tc of this.cases) {
43
+ const res = await backend.compile(tc, {
44
+ cacheDir: this.cacheDir,
45
+ profile: options.profile,
46
+ trace: options.trace
47
+ });
48
+ if (res.status !== "ok") {
49
+ allOk = false;
50
+ failureError = res.error;
51
+ }
52
+ compiledCases.push({ ...tc, ...res });
53
+ }
54
+
55
+ this.compiled.set(id, compiledCases);
56
+ results[id] = {
57
+ status: allOk ? "ok" : "failed",
58
+ total: this.cases.length,
59
+ ready: compiledCases.filter(c => c.status === "ok").length,
60
+ error: failureError
61
+ };
62
+ }
63
+ return results;
64
+ }
65
+
66
+ async getRunner(backendId) {
67
+ let runner = this.runners.get(backendId);
68
+ if (!runner) {
69
+ const backend = this.getBackend(backendId);
70
+ const compiledCases = this.compiled.get(backendId);
71
+ if (!compiledCases) throw new Error(`Backend '${backendId}' not compiled yet`);
72
+ runner = await backend.createRunner(compiledCases);
73
+ this.runners.set(backendId, runner);
74
+ }
75
+ return runner;
76
+ }
77
+
78
+ async checkConformance(backendIds) {
79
+ const baselineId = backendIds.includes("native-aware")
80
+ ? "native-aware"
81
+ : (backendIds.includes("kvm") ? "kvm" : backendIds[0]);
82
+
83
+ const results = [];
84
+
85
+ for (let i = 0; i < this.cases.length; i++) {
86
+ const tc = this.cases[i];
87
+ const caseResult = {
88
+ name: tc.name || tc.op,
89
+ conformance: {},
90
+ passed: true
91
+ };
92
+
93
+ let baselineVal = tc.expectedVal;
94
+
95
+ for (const id of backendIds) {
96
+ const compiledCases = this.compiled.get(id);
97
+ if (!compiledCases || compiledCases[i]?.status !== "ok") {
98
+ caseResult.conformance[id] = { status: "compile_failed" };
99
+ caseResult.passed = false;
100
+ continue;
101
+ }
102
+
103
+ const runner = await this.getRunner(id);
104
+ try {
105
+ const res = await runner.runSingle(compiledCases[i], { trace: false });
106
+ const val = res.outputVal;
107
+
108
+ if (id === baselineId && baselineVal == null) {
109
+ baselineVal = val;
110
+ }
111
+
112
+ const matches = baselineVal == null || safeDeepEqual(val, baselineVal);
113
+ caseResult.conformance[id] = {
114
+ status: matches ? "ok" : "mismatch",
115
+ val
116
+ };
117
+ if (!matches) caseResult.passed = false;
118
+ } catch (err) {
119
+ caseResult.conformance[id] = {
120
+ status: "error",
121
+ error: err.message
122
+ };
123
+ caseResult.passed = false;
124
+ }
125
+ }
126
+
127
+ results.push(caseResult);
128
+ }
129
+
130
+ return results;
131
+ }
132
+
133
+ async runTrace(backendIds, reps = 5) {
134
+ const traceResults = [];
135
+
136
+ for (let i = 0; i < this.cases.length; i++) {
137
+ const tc = this.cases[i];
138
+ const caseTrace = {
139
+ op: tc.op || tc.name,
140
+ label: tc.label || ""
141
+ };
142
+
143
+ for (const id of backendIds) {
144
+ const compiledCases = this.compiled.get(id);
145
+ if (!compiledCases || compiledCases[i]?.status !== "ok") continue;
146
+
147
+ const runner = await this.getRunner(id);
148
+ const samples = [];
149
+
150
+ // Warmup 2 reps
151
+ for (let w = 0; w < 2; w++) {
152
+ await runner.runSingle(compiledCases[i], { trace: true });
153
+ }
154
+
155
+ // Measured reps
156
+ for (let r = 0; r < reps; r++) {
157
+ const res = await runner.runSingle(compiledCases[i], { trace: true });
158
+ if (res.trace) samples.push(res.trace);
159
+ }
160
+
161
+ caseTrace[id] = averageTrace(samples);
162
+ }
163
+
164
+ traceResults.push(caseTrace);
165
+ }
166
+
167
+ return traceResults;
168
+ }
169
+
170
+ async runProfile(backendIds) {
171
+ const profileResults = [];
172
+
173
+ for (let i = 0; i < this.cases.length; i++) {
174
+ const tc = this.cases[i];
175
+ const caseProfile = {
176
+ op: tc.op || tc.name
177
+ };
178
+
179
+ for (const id of backendIds) {
180
+ const compiledCases = this.compiled.get(id);
181
+ if (!compiledCases || compiledCases[i]?.status !== "ok") continue;
182
+
183
+ const runner = await this.getRunner(id);
184
+ const res = await runner.runSingle(compiledCases[i], { profile: true });
185
+ if (res.profile) {
186
+ caseProfile[id] = res.profile;
187
+ }
188
+ }
189
+
190
+ profileResults.push(caseProfile);
191
+ }
192
+
193
+ return profileResults;
194
+ }
195
+
196
+ async runBenchmark(backendIds, { warmup = 1, iterations = 3 } = {}) {
197
+ const benchResults = [];
198
+
199
+ for (const id of backendIds) {
200
+ const compiledCases = this.compiled.get(id);
201
+ const readyCases = (compiledCases || []).filter(c => c.status === "ok");
202
+ if (!readyCases || readyCases.length === 0) continue;
203
+
204
+ const runner = await this.getRunner(id);
205
+ const backend = this.getBackend(id);
206
+
207
+ // Warmup
208
+ if (warmup > 0) {
209
+ for (let w = 0; w < warmup; w++) {
210
+ await runner.runBatch(readyCases);
211
+ }
212
+ }
213
+
214
+ // Measured iterations
215
+ const iterRes = await runner.runIterations(readyCases, iterations);
216
+
217
+ // Sample pure evaluation time (Phase 4) across cases
218
+ let pureEvalMs = null;
219
+ try {
220
+ let totalEvalNs = 0;
221
+ let count = 0;
222
+ for (const c of readyCases) {
223
+ const sample = await runner.runSingle(c, { trace: true });
224
+ if (sample?.trace?.evalNs != null) {
225
+ totalEvalNs += sample.trace.evalNs;
226
+ count++;
227
+ }
228
+ }
229
+ if (count > 0 && totalEvalNs > 0) {
230
+ pureEvalMs = totalEvalNs / 1000000;
231
+ }
232
+ } catch {}
233
+
234
+ benchResults.push({
235
+ id,
236
+ name: backend.getLaneName(),
237
+ totalNs: iterRes.totalNs,
238
+ perIterationMs: iterRes.perIterationMs,
239
+ pureEvalMs,
240
+ iterationTimes: iterRes.iterationTimes,
241
+ minMs: min(iterRes.iterationTimes),
242
+ maxMs: max(iterRes.iterationTimes),
243
+ stdDevMs: stdDev(iterRes.iterationTimes),
244
+ samples: iterations
245
+ });
246
+ }
247
+
248
+ return benchResults;
249
+ }
250
+
251
+ async close() {
252
+ for (const runner of this.runners.values()) {
253
+ await runner.close();
254
+ }
255
+ this.runners.clear();
256
+ }
257
+ }
@@ -0,0 +1,156 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import {
6
+ codes,
7
+ decodeWire,
8
+ encodeToWire,
9
+ executeKVM,
10
+ lowerToKVM,
11
+ run,
12
+ run_converged,
13
+ valueForCode,
14
+ Value
15
+ } from "../../backend-api.mjs";
16
+ import { compileLibrary, loadLibrary, decodeObject, compileObjectBuffer } from "../../object.mjs";
17
+ import { parse as parseIntValue } from "../../codecs/int.mjs";
18
+ import { BenchmarkSuite } from "../suite.mjs";
19
+ import { inputPatternForObjectRelation } from "../../backends/llvm/src/executable.mjs";
20
+ import { prepareRelation } from "../../backends/llvm/tests/perf-support.mjs";
21
+
22
+ export async function createPolySuite({ listLength = 40, cacheDir = null } = {}) {
23
+ const ops = ["reverse", "concat", "split_by", "get_nth", "length"];
24
+ const benchNames = Object.fromEntries(ops.map(op => [op, `bench_${op}`]));
25
+
26
+ const arithmeticsPath = fileURLToPath(import.meta.resolve("../../Examples/arithmetics.k"));
27
+ const arithmeticsSource = fs.readFileSync(arithmeticsPath, "utf8");
28
+ const arithmeticsLib = loadLibrary(compileLibrary(arithmeticsSource, { source: arithmeticsPath }));
29
+
30
+ const polyPath = fileURLToPath(import.meta.resolve("../../Examples/poly.k"));
31
+ const polySource = fs.readFileSync(polyPath, "utf8");
32
+
33
+ function buildExportPreamble(exports, libraries) {
34
+ const aliasMap = {};
35
+ for (const lib of libraries) {
36
+ for (const [name, hash] of Object.entries(lib.relAlias || {})) {
37
+ if (name !== "__main__") aliasMap[name] = hash;
38
+ }
39
+ }
40
+ const lines = [];
41
+ for (const spec of exports) {
42
+ const [libName, localName] = spec.includes(":") ? spec.split(":", 2) : [spec, spec];
43
+ const hash = aliasMap[libName];
44
+ if (!hash) throw new Error(`--export: '${libName}' not found in loaded libraries`);
45
+ const body = hash.startsWith("@") ? hash.slice(1) : hash;
46
+ lines.push(`${localName} = @${body};`);
47
+ }
48
+ return lines.join("\n") + "\n";
49
+ }
50
+
51
+ const neededExports = ["0", "int", "inc", "dec", "nat", "zero_int?", "nil", "cons", "car", "cdr"];
52
+ const exportPreamble = buildExportPreamble(neededExports, [arithmeticsLib]);
53
+ const polyLib = loadLibrary(compileLibrary(exportPreamble + polySource, {
54
+ source: polyPath,
55
+ libraries: [arithmeticsLib]
56
+ }));
57
+
58
+ const state = {
59
+ codes: { ...arithmeticsLib.codes, ...polyLib.codes },
60
+ rels: { ...arithmeticsLib.rels, ...polyLib.rels },
61
+ typeAliases: {},
62
+ relAliases: { ...polyLib.relAlias },
63
+ meta: { ...arithmeticsLib.meta, ...polyLib.meta }
64
+ };
65
+
66
+ for (const [hash, entry] of Object.entries(arithmeticsLib.meta)) {
67
+ if (entry.type === "code") {
68
+ for (const origin of entry.origins || []) {
69
+ if (origin.name) state.typeAliases[origin.name] = hash;
70
+ }
71
+ }
72
+ }
73
+
74
+ for (const op of ops) {
75
+ state.relAliases[benchNames[op]] = polyLib.relAlias[op];
76
+ }
77
+
78
+ function intValue(text) {
79
+ return valueForCode(parseIntValue(text), state.typeAliases.int, codes.find);
80
+ }
81
+
82
+ function makeList(elements) {
83
+ let curr = Value.variant("nil", Value.product({}));
84
+ for (let i = elements.length - 1; i >= 0; i--) {
85
+ curr = Value.variant("cons", Value.product({
86
+ car: elements[i],
87
+ cdr: curr
88
+ }));
89
+ }
90
+ return curr;
91
+ }
92
+
93
+ const relations = {};
94
+ for (const op of ops) {
95
+ const name = benchNames[op];
96
+ relations[op] = prepareRelation(state, name);
97
+ }
98
+ codes.load(state.codes);
99
+
100
+ const intElements = Array.from({ length: listLength }, (_, i) => intValue(String(i + 1)));
101
+ const fullList = makeList(intElements);
102
+ const halfLen = Math.floor(listLength / 2);
103
+ const firstHalf = makeList(intElements.slice(0, halfLen));
104
+ const secondHalf = makeList(intElements.slice(halfLen));
105
+ const midIdx = intValue(String(halfLen));
106
+
107
+ const rawCases = [
108
+ { op: "reverse", label: `len ${listLength}`, inputVal: fullList },
109
+ { op: "concat", label: `2x len ${halfLen}`, inputVal: Value.product({ xs: firstHalf, ys: secondHalf }) },
110
+ { op: "split_by", label: `at ${halfLen} of ${listLength}`, inputVal: Value.product({ n: midIdx, xs: fullList }) },
111
+ { op: "get_nth", label: `idx ${halfLen} of ${listLength}`, inputVal: Value.product({ n: midIdx, xs: fullList }) },
112
+ { op: "length", label: `len ${listLength}`, inputVal: fullList }
113
+ ];
114
+
115
+ const suite = new BenchmarkSuite("poly", { state, cacheDir });
116
+
117
+ for (const tc of rawCases) {
118
+ const relation = relations[tc.op];
119
+ let expected;
120
+ try {
121
+ run.defs = state;
122
+ run_converged.defs = state;
123
+ expected = run_converged(codes.find, relation.relDef.def, tc.inputVal, relation.relDef.typePatternGraph);
124
+ } catch {
125
+ expected = executeKVM(relation.kvmFunc, tc.inputVal, {
126
+ rels: state.rels,
127
+ codes: state.codes,
128
+ options: { envelopeFree: true }
129
+ });
130
+ }
131
+
132
+ const inferredPat = inputPatternForObjectRelation(relation.object, relation.relationName);
133
+ const inputWire = encodeToWire(tc.inputVal, inferredPat);
134
+ const inputPattern = decodeWire(inputWire).pattern;
135
+
136
+ const outWire = encodeToWire(expected);
137
+ const outputPattern = decodeWire(outWire).pattern;
138
+
139
+ suite.addCase({
140
+ op: tc.op,
141
+ label: tc.label,
142
+ relName: relation.relationName,
143
+ relHash: relation.relHash,
144
+ relDef: relation.relDef,
145
+ object: relation.object,
146
+ kvmFunc: relation.kvmFunc,
147
+ inputVal: tc.inputVal,
148
+ inputWire,
149
+ inputPattern,
150
+ outputPattern,
151
+ expectedVal: expected
152
+ });
153
+ }
154
+
155
+ return suite;
156
+ }
@@ -0,0 +1,50 @@
1
+ // 7-phase trace protocol and aggregation
2
+
3
+ export const PHASES = [
4
+ { name: "1. IPC Read (Pipe In)", key: "ipcReadNs" },
5
+ { name: "2. Wire Decode & Validate", key: "decodeNs" },
6
+ { name: "3. Flat Input Prep (Arena)", key: "flatInNs" },
7
+ { name: "4. Pure Evaluation", key: "evalNs" },
8
+ { name: "5. Flat Output Prep (Arena)", key: "flatOutNs" },
9
+ { name: "6. Wire Encode", key: "encodeNs" },
10
+ { name: "7. IPC Write (Pipe Out)", key: "ipcWriteNs" },
11
+ { name: "Total Request Time", key: "totalNs", isTotal: true }
12
+ ];
13
+
14
+ export function parseTraceLine(line) {
15
+ const parts = line.trim().split(/\s+/);
16
+ if (parts[0] !== "K_TRACE_PHASES") return null;
17
+ const result = {};
18
+ for (let i = 1; i < parts.length; i++) {
19
+ const [key, val] = parts[i].split("=");
20
+ if (!key || val == null) continue;
21
+ if (key === "backend") {
22
+ result.backend = val;
23
+ } else {
24
+ const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
25
+ result[camelKey] = Number(val);
26
+ }
27
+ }
28
+ return result;
29
+ }
30
+
31
+ export function averageTrace(samples) {
32
+ if (!samples || samples.length === 0) return null;
33
+ const result = {};
34
+ for (const phase of PHASES) {
35
+ const key = phase.key;
36
+ const values = samples.map(s => s[key]).filter(v => v != null && !isNaN(v));
37
+ if (values.length > 0) {
38
+ result[key] = values.reduce((sum, v) => sum + v, 0) / values.length;
39
+ } else {
40
+ result[key] = 0;
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+
46
+ export function formatUs(ns) {
47
+ if (ns == null || ns === 0) return " - ";
48
+ const us = ns / 1000;
49
+ return `${us.toFixed(2)} µs`.padStart(12);
50
+ }