@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.
- package/CHANGELOG.md +17 -0
- package/DOCS/CODECS.md +258 -0
- package/DOCS/DICTIONARY.md +1172 -0
- package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
- package/DOCS/KIR_V1.md +221 -0
- package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
- package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
- package/DOCS/PROFILING_AND_TRACING.md +196 -0
- package/DOCS/REPL.md +61 -4
- package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
- package/DOCS/dependency-graph.md +144 -0
- package/Examples/arithmetics.k +108 -82
- package/Examples/bnat.k +1 -1
- package/Examples/buda.k +44 -0
- package/Examples/ieee.k +300 -8671
- package/Examples/poly.k +67 -0
- package/README.md +68 -19
- package/Value.mjs +8 -2
- package/augmentation.mjs +9 -7
- package/backend-api.mjs +11 -2
- package/backends/arm64/README.md +104 -0
- package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
- package/backends/arm64/bin/k-arm64-run.mjs +58 -0
- package/backends/arm64/bin/k-arm64.mjs +88 -0
- package/backends/arm64/scripts/run-tests.mjs +41 -0
- package/backends/arm64/src/arm64.mjs +229 -0
- package/backends/arm64/src/cli.mjs +155 -0
- package/backends/arm64/src/kvm2arm64.mjs +906 -0
- package/backends/arm64/src/runtime.c +728 -0
- package/backends/arm64/tests/integration.sh +112 -0
- package/backends/arm64/tests/test-arm64.mjs +240 -0
- package/backends/llvm/README.md +135 -0
- package/backends/llvm/bin/k-llvm-build.mjs +71 -0
- package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
- package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
- package/backends/llvm/bin/k-llvm-run.mjs +90 -0
- package/backends/llvm/runtime/krt.c +1508 -0
- package/backends/llvm/runtime/krt.h +109 -0
- package/backends/llvm/scripts/conformance.mjs +76 -0
- package/backends/llvm/scripts/run-tests.mjs +28 -0
- package/backends/llvm/src/executable.mjs +562 -0
- package/backends/llvm/src/kvm2llvm.mjs +998 -0
- package/backends/llvm/src/llvm.mjs +1092 -0
- package/backends/llvm/tests/composition-driver.c +22 -0
- package/backends/llvm/tests/identity-driver.c +27 -0
- package/backends/llvm/tests/integration.sh +153 -0
- package/backends/llvm/tests/perf-support.mjs +735 -0
- package/backends/llvm/tests/product-driver.c +23 -0
- package/backends/llvm/tests/projection-driver.c +19 -0
- package/backends/llvm/tests/relation-driver.c +29 -0
- package/backends/llvm/tests/test-llvm.mjs +240 -0
- package/backends/llvm/tests/union-driver.c +31 -0
- package/backends/llvm/tests/variant-driver.c +19 -0
- package/backends/llvm/tests/variant-projection-driver.c +21 -0
- package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
- package/backends/wasm/README.md +97 -0
- package/backends/wasm/TECHNICAL-DEBT.md +58 -0
- package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
- package/backends/wasm/bin/k-wasm-run.mjs +140 -0
- package/backends/wasm/bin/k-wasm.mjs +49 -0
- package/backends/wasm/demo_ieee.mjs +319 -0
- package/backends/wasm/runtime.wat +475 -0
- package/backends/wasm/scripts/run-tests.mjs +41 -0
- package/backends/wasm/src/cli.mjs +133 -0
- package/backends/wasm/src/kvm2wasm.mjs +697 -0
- package/backends/wasm/src/wasm.mjs +1054 -0
- package/backends/wasm/tests/integration.sh +182 -0
- package/backends/wasm/tests/perf-support.mjs +336 -0
- package/backends/wasm/tests/test-wasm.mjs +625 -0
- package/browser/repl-browser.mjs +1584 -0
- package/browser/shims/assert.mjs +19 -0
- package/browser/shims/buffer.mjs +224 -0
- package/browser/shims/crypto.mjs +103 -0
- package/browser/shims/fs.mjs +159 -0
- package/browser/shims/os.mjs +17 -0
- package/browser/shims/path.mjs +54 -0
- package/browser/shims/process.mjs +44 -0
- package/browser/shims/readline.mjs +11 -0
- package/browser/shims/url.mjs +12 -0
- package/browser/vfs-data.mjs +19 -0
- package/codecs/README.md +3 -0
- package/codecs/runtime/show-value.mjs +12 -3
- package/conformance/helper-reuse/case.json +8 -0
- package/conformance/helper-reuse/expected.kv +1 -0
- package/conformance/helper-reuse/input.kv +1 -0
- package/conformance/helper-reuse/program.k +2 -0
- package/conformance/identity/case.json +8 -0
- package/conformance/identity/expected.kv +1 -0
- package/conformance/identity/input.kv +1 -0
- package/conformance/identity/program.k +1 -0
- package/conformance/product-projection/case.json +8 -0
- package/conformance/product-projection/expected.kv +1 -0
- package/conformance/product-projection/input.kv +1 -0
- package/conformance/product-projection/program.k +1 -0
- package/conformance/run.mjs +149 -0
- package/conformance/variant-construction/case.json +8 -0
- package/conformance/variant-construction/expected.kv +1 -0
- package/conformance/variant-construction/input.kv +1 -0
- package/conformance/variant-construction/program.k +1 -0
- package/harness/adapters/arm64.mjs +105 -0
- package/harness/adapters/kvm.mjs +144 -0
- package/harness/adapters/llvm.mjs +118 -0
- package/harness/adapters/native.mjs +77 -0
- package/harness/adapters/wasm.mjs +169 -0
- package/harness/backend.mjs +95 -0
- package/harness/cli.mjs +130 -0
- package/harness/format.mjs +95 -0
- package/harness/index.mjs +25 -0
- package/harness/persistent.mjs +121 -0
- package/harness/registry.mjs +44 -0
- package/harness/suite.mjs +257 -0
- package/harness/suites/poly.mjs +156 -0
- package/harness/trace.mjs +50 -0
- package/harness/util.mjs +92 -0
- package/intrinsics.mjs +41 -0
- package/kir.mjs +399 -0
- package/kvm.mjs +585 -81
- package/objects/README.md +37 -3
- package/objects/compile.mjs +15 -22
- package/objects/inspect.mjs +96 -0
- package/objects/validate.mjs +415 -0
- package/package.json +51 -19
- package/repl-codecs.mjs +105 -6
- package/repl.mjs +335 -49
- package/run.mjs +2 -7
- package/scripts/benchmark.mjs +406 -22
- package/scripts/build-repl-html.mjs +1784 -0
- package/scripts/perf-ieee.mjs +586 -0
- package/scripts/perf-int.mjs +531 -0
- package/scripts/perf-poly.mjs +945 -0
- package/scripts/render-benchmark.mjs +12 -8
- package/scripts/run-tests.mjs +6 -0
- package/tests/integration.sh +19 -2
- package/tests/test-arithmetics.mjs +54 -0
- package/tests/test-ieee-arithmetic.mjs +4 -0
- package/tests/test-kir.mjs +85 -0
- package/tests/test-kvm-polymorphic.mjs +278 -0
- package/tests/test-kvm.mjs +25 -6
- package/tests/test-poly.mjs +129 -0
- package/tests/test-repl-html.mjs +303 -0
- package/tests/test-repl.mjs +144 -1
- package/tests/test-validate-object.mjs +40 -0
- package/a.klib +0 -1
- package/a.kvm +0 -142676
- package/a.wasm +0 -0
- package/benchmark-results.html +0 -532
- package/benchmark-results.json +0 -480
- package/k-vscode/.vscode/launch.json +0 -14
- package/k-vscode/README.md +0 -75
- package/k-vscode/extension.js +0 -273
- package/k-vscode/language-configuration.json +0 -42
- package/k-vscode/package.json +0 -79
- package/k-vscode/snippets/k.json +0 -92
- package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
- package/k-vscode/test/background-ranges.test.js +0 -50
- package/k-vscode/themes/k-contrast-color-theme.json +0 -104
- package/scripts/git-hooks/pre-commit +0 -10
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { performance } from "node:perf_hooks";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
decodeWire,
|
|
11
|
+
encodeToWire,
|
|
12
|
+
executeKVM,
|
|
13
|
+
lowerToKVM,
|
|
14
|
+
run,
|
|
15
|
+
run_converged
|
|
16
|
+
} from "@fraczak/k/backend-api.mjs";
|
|
17
|
+
import {
|
|
18
|
+
compileObjectBuffer,
|
|
19
|
+
decodeObject,
|
|
20
|
+
encodeObject
|
|
21
|
+
} from "@fraczak/k/object.mjs";
|
|
22
|
+
|
|
23
|
+
import { compileObjectToExecutable } from "../src/executable.mjs";
|
|
24
|
+
|
|
25
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
26
|
+
|
|
27
|
+
let cachedBackendFingerprint = null;
|
|
28
|
+
|
|
29
|
+
function sha256(text) {
|
|
30
|
+
return crypto.createHash("sha256").update(text).digest("hex");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function backendFingerprint() {
|
|
34
|
+
if (cachedBackendFingerprint != null) return cachedBackendFingerprint;
|
|
35
|
+
const hash = crypto.createHash("sha256");
|
|
36
|
+
for (const relPath of ["src/llvm.mjs", "src/executable.mjs", "runtime/krt.c", "runtime/krt.h"]) {
|
|
37
|
+
hash.update(relPath);
|
|
38
|
+
hash.update("\0");
|
|
39
|
+
hash.update(fs.readFileSync(path.join(root, relPath)));
|
|
40
|
+
hash.update("\0");
|
|
41
|
+
}
|
|
42
|
+
cachedBackendFingerprint = hash.digest("hex").slice(0, 16);
|
|
43
|
+
return cachedBackendFingerprint;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function cloneObjectPayload(object) {
|
|
47
|
+
return decodeObject(encodeObject(object));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parsePositiveIntEnv(name, fallback) {
|
|
51
|
+
const value = process.env[name] ? Number(process.env[name]) : fallback;
|
|
52
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
53
|
+
throw new Error(`${name} must be a positive integer`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseNonNegativeIntEnv(name, fallback) {
|
|
59
|
+
const value = process.env[name] ? Number(process.env[name]) : fallback;
|
|
60
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
61
|
+
throw new Error(`${name} must be a non-negative integer`);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function csvEnv(name, fallback) {
|
|
67
|
+
return (process.env[name] || fallback)
|
|
68
|
+
.split(",")
|
|
69
|
+
.map(text => text.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function makeCacheDir(prefix) {
|
|
74
|
+
if (process.env.K_LLVM_CACHE_DIR) {
|
|
75
|
+
fs.mkdirSync(process.env.K_LLVM_CACHE_DIR, { recursive: true });
|
|
76
|
+
return process.env.K_LLVM_CACHE_DIR;
|
|
77
|
+
}
|
|
78
|
+
const defaultDir = path.join(os.tmpdir(), prefix ? `${prefix}cache` : "k-llvm-perf-cache");
|
|
79
|
+
fs.mkdirSync(defaultDir, { recursive: true });
|
|
80
|
+
return defaultDir;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function prepareRelation(state, relationName, { source = null, sourceLabel = "<benchmark>" } = {}) {
|
|
84
|
+
const relHash = state.relAliases[relationName];
|
|
85
|
+
if (!relHash) throw new Error(`Relation '${relationName}' not found`);
|
|
86
|
+
const relDef = state.rels[relHash];
|
|
87
|
+
if (!relDef) throw new Error(`Relation hash '${relHash}' not found`);
|
|
88
|
+
const object = source == null
|
|
89
|
+
? prepareKObject(state, relationName, relHash)
|
|
90
|
+
: decodeObject(compileObjectBuffer(source, { source: sourceLabel }));
|
|
91
|
+
return {
|
|
92
|
+
relationName: object.main || relationName,
|
|
93
|
+
relHash,
|
|
94
|
+
relDef,
|
|
95
|
+
kvmFunc: lowerToKVM(relDef, relationName),
|
|
96
|
+
object
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stripRuntimeCaches(exp) {
|
|
101
|
+
if (!exp || typeof exp !== "object") return exp;
|
|
102
|
+
const { _compiledRun, _compiledConvergedRun, ...rest } = exp;
|
|
103
|
+
switch (rest.op) {
|
|
104
|
+
case "comp":
|
|
105
|
+
return { ...rest, comp: rest.comp.map(stripRuntimeCaches) };
|
|
106
|
+
case "union":
|
|
107
|
+
return { ...rest, union: rest.union.map(stripRuntimeCaches) };
|
|
108
|
+
case "product":
|
|
109
|
+
return {
|
|
110
|
+
...rest,
|
|
111
|
+
product: rest.product.map(({ label, exp: child }) => ({
|
|
112
|
+
label,
|
|
113
|
+
exp: stripRuntimeCaches(child)
|
|
114
|
+
}))
|
|
115
|
+
};
|
|
116
|
+
default:
|
|
117
|
+
return rest;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function prepareKObject(state, relationName, relHash) {
|
|
122
|
+
const cleanRels = {};
|
|
123
|
+
for (const [hash, rel] of Object.entries(state.rels)) {
|
|
124
|
+
const { _compiledRunRel, ...relRest } = rel;
|
|
125
|
+
cleanRels[hash] = {
|
|
126
|
+
...relRest,
|
|
127
|
+
def: stripRuntimeCaches(rel.def)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
format: "k-object",
|
|
132
|
+
codes: state.codes,
|
|
133
|
+
rels: cleanRels,
|
|
134
|
+
relAlias: {
|
|
135
|
+
[relationName]: relHash
|
|
136
|
+
},
|
|
137
|
+
compileStats: { sccs: [], sccCount: 0 },
|
|
138
|
+
meta: state.meta || {},
|
|
139
|
+
main: relHash
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function wireInput(value, pattern = value.pattern) {
|
|
144
|
+
const inputWire = encodeToWire(value, pattern);
|
|
145
|
+
return {
|
|
146
|
+
inputWire,
|
|
147
|
+
inputPattern: decodeWire(inputWire).pattern
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function compileCaseExecutable({ object, relationName, relHash, inputPattern, cacheDir, sourceLabel, runtimeMode = "fast", clangOpt = "-O3" }) {
|
|
152
|
+
const key = sha256([
|
|
153
|
+
"k-llvm-perf-v1",
|
|
154
|
+
backendFingerprint(),
|
|
155
|
+
runtimeMode,
|
|
156
|
+
clangOpt,
|
|
157
|
+
sourceLabel,
|
|
158
|
+
relationName,
|
|
159
|
+
relHash,
|
|
160
|
+
JSON.stringify(inputPattern)
|
|
161
|
+
].join("\0"));
|
|
162
|
+
const exePath = path.join(cacheDir, `${key}.exe`);
|
|
163
|
+
if (!fs.existsSync(exePath)) {
|
|
164
|
+
const tmpPath = path.join(cacheDir, `${key}.${process.pid}.tmp`);
|
|
165
|
+
compileObjectToExecutable(cloneObjectPayload(object), tmpPath, { relation: relationName, inputPattern, runtimeMode, clangOpt });
|
|
166
|
+
fs.renameSync(tmpPath, exePath);
|
|
167
|
+
}
|
|
168
|
+
return exePath;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function tryCompileCase(options) {
|
|
172
|
+
try {
|
|
173
|
+
return {
|
|
174
|
+
status: "ok",
|
|
175
|
+
exePath: compileCaseExecutable(options)
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
status: "failed",
|
|
180
|
+
error: error.stack || error.message || String(error)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function runExecutable(exePath, inputWire) {
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
const child = spawn(exePath, [], { stdio: ["pipe", "pipe", "pipe"] });
|
|
188
|
+
const stdout = [];
|
|
189
|
+
const stderr = [];
|
|
190
|
+
|
|
191
|
+
child.stdout.on("data", chunk => stdout.push(chunk));
|
|
192
|
+
child.stderr.on("data", chunk => stderr.push(chunk));
|
|
193
|
+
child.on("error", reject);
|
|
194
|
+
child.on("close", status => {
|
|
195
|
+
if (status !== 0) {
|
|
196
|
+
reject(new Error(`${exePath} failed with status ${status}\n${Buffer.concat(stderr).toString("utf8")}`.trim()));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
resolve(Buffer.concat(stdout));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
child.stdin.end(inputWire);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function runExecutableMainBench(exePath, inputWire, calls) {
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
const child = spawn(exePath, ["--bench-main", String(calls)], { stdio: ["pipe", "ignore", "pipe"] });
|
|
209
|
+
const stderr = [];
|
|
210
|
+
|
|
211
|
+
child.stderr.on("data", chunk => stderr.push(chunk));
|
|
212
|
+
child.on("error", reject);
|
|
213
|
+
child.on("close", status => {
|
|
214
|
+
const text = Buffer.concat(stderr).toString("utf8");
|
|
215
|
+
if (status !== 0) {
|
|
216
|
+
reject(new Error(`${exePath} --bench-main failed with status ${status}\n${text}`.trim()));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const match = text.match(/K_(?:LLVM|ARM64)_BENCH_MAIN calls=(\d+) total_ns=(\d+) per_call_ns=([0-9.]+)/);
|
|
220
|
+
if (!match) {
|
|
221
|
+
reject(new Error(`${exePath} --bench-main did not report timing\n${text}`.trim()));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
resolve({
|
|
225
|
+
calls: Number(match[1]),
|
|
226
|
+
totalNs: Number(match[2]),
|
|
227
|
+
perCallNs: Number(match[3])
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
child.stdin.end(inputWire);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function parseTraceLine(line) {
|
|
236
|
+
const parts = line.trim().split(/\s+/);
|
|
237
|
+
if (parts[0] !== "K_TRACE_PHASES") return null;
|
|
238
|
+
const result = {};
|
|
239
|
+
for (let i = 1; i < parts.length; i++) {
|
|
240
|
+
const [key, val] = parts[i].split("=");
|
|
241
|
+
if (!key || val == null) continue;
|
|
242
|
+
if (key === "backend") {
|
|
243
|
+
result.backend = val;
|
|
244
|
+
} else {
|
|
245
|
+
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
246
|
+
result[camelKey] = Number(val);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function runExecutableWithTrace(exePath, inputWire) {
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
const child = spawn(exePath, [], {
|
|
255
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
256
|
+
env: { ...process.env, K_TRACE: "1" }
|
|
257
|
+
});
|
|
258
|
+
const stdout = [];
|
|
259
|
+
const stderr = [];
|
|
260
|
+
|
|
261
|
+
child.stdout.on("data", chunk => stdout.push(chunk));
|
|
262
|
+
child.stderr.on("data", chunk => stderr.push(chunk));
|
|
263
|
+
child.on("error", reject);
|
|
264
|
+
child.on("close", status => {
|
|
265
|
+
const errText = Buffer.concat(stderr).toString("utf8");
|
|
266
|
+
if (status !== 0) {
|
|
267
|
+
reject(new Error(`${exePath} failed with status ${status}\n${errText}`.trim()));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
let trace = null;
|
|
271
|
+
let currentProfile = null;
|
|
272
|
+
for (const line of errText.split("\n")) {
|
|
273
|
+
const trimmed = line.trim();
|
|
274
|
+
if (trimmed === "K_PROFILE_BEGIN") {
|
|
275
|
+
currentProfile = {};
|
|
276
|
+
} else if (trimmed.startsWith("K_FUNC_CALL ") && currentProfile) {
|
|
277
|
+
const match = trimmed.match(/^K_FUNC_CALL name=(\S+) count=(\d+)$/);
|
|
278
|
+
if (match) currentProfile[match[1]] = Number(match[2]);
|
|
279
|
+
} else if (trimmed === "K_PROFILE_END") {
|
|
280
|
+
// Completed profile block
|
|
281
|
+
} else if (trimmed.startsWith("K_TRACE_PHASES ")) {
|
|
282
|
+
trace = parseTraceLine(trimmed);
|
|
283
|
+
if (trace && currentProfile) {
|
|
284
|
+
trace.profile = currentProfile;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
resolve({
|
|
289
|
+
payload: Buffer.concat(stdout),
|
|
290
|
+
trace
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
child.stdin.end(inputWire);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export class PersistentExecutable {
|
|
299
|
+
constructor(exePath) {
|
|
300
|
+
this.exePath = exePath;
|
|
301
|
+
this.child = spawn(exePath, ["--server"], {
|
|
302
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
303
|
+
env: { ...process.env, K_TRACE: "1" }
|
|
304
|
+
});
|
|
305
|
+
this.buffer = Buffer.alloc(0);
|
|
306
|
+
this.pending = [];
|
|
307
|
+
this.stderr = [];
|
|
308
|
+
this.stderrRemainder = "";
|
|
309
|
+
this.traces = [];
|
|
310
|
+
this.waitingForTrace = [];
|
|
311
|
+
this.closed = false;
|
|
312
|
+
this.currentProfile = null;
|
|
313
|
+
this.lastProfile = null;
|
|
314
|
+
|
|
315
|
+
this.child.stdout.on("data", chunk => {
|
|
316
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
317
|
+
this.drainOutput();
|
|
318
|
+
});
|
|
319
|
+
this.child.stderr.on("data", chunk => {
|
|
320
|
+
this.stderr.push(chunk);
|
|
321
|
+
this.stderrRemainder += chunk.toString("utf8");
|
|
322
|
+
let idx;
|
|
323
|
+
while ((idx = this.stderrRemainder.indexOf("\n")) !== -1) {
|
|
324
|
+
const line = this.stderrRemainder.slice(0, idx).trim();
|
|
325
|
+
this.stderrRemainder = this.stderrRemainder.slice(idx + 1);
|
|
326
|
+
if (line === "K_PROFILE_BEGIN") {
|
|
327
|
+
this.currentProfile = {};
|
|
328
|
+
} else if (line.startsWith("K_FUNC_CALL ") && this.currentProfile) {
|
|
329
|
+
const match = line.match(/^K_FUNC_CALL name=(\S+) count=(\d+)$/);
|
|
330
|
+
if (match) this.currentProfile[match[1]] = Number(match[2]);
|
|
331
|
+
} else if (line === "K_PROFILE_END") {
|
|
332
|
+
this.lastProfile = this.currentProfile;
|
|
333
|
+
this.currentProfile = null;
|
|
334
|
+
} else if (line.startsWith("K_TRACE_PHASES ")) {
|
|
335
|
+
const parsed = parseTraceLine(line);
|
|
336
|
+
if (parsed) {
|
|
337
|
+
if (this.lastProfile) {
|
|
338
|
+
parsed.profile = this.lastProfile;
|
|
339
|
+
this.lastProfile = null;
|
|
340
|
+
}
|
|
341
|
+
if (this.waitingForTrace.length > 0) {
|
|
342
|
+
const item = this.waitingForTrace.shift();
|
|
343
|
+
item.resolve({ payload: item.payload, trace: parsed });
|
|
344
|
+
} else {
|
|
345
|
+
this.traces.push(parsed);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
this.child.on("error", error => this.fail(error));
|
|
352
|
+
this.child.on("close", status => {
|
|
353
|
+
this.closed = true;
|
|
354
|
+
if (status !== 0) {
|
|
355
|
+
this.fail(new Error(`${this.exePath} --server failed with status ${status}\n${Buffer.concat(this.stderr).toString("utf8")}`.trim()));
|
|
356
|
+
} else if (this.pending.length > 0 || this.waitingForTrace.length > 0) {
|
|
357
|
+
this.fail(new Error(`${this.exePath} --server closed with pending request(s)`));
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
fail(error) {
|
|
363
|
+
while (this.pending.length > 0) this.pending.shift().reject(error);
|
|
364
|
+
while (this.waitingForTrace.length > 0) this.waitingForTrace.shift().reject(error);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
drainOutput() {
|
|
368
|
+
while (this.pending.length > 0 && this.buffer.length >= 4) {
|
|
369
|
+
const length = this.buffer.readUInt32BE(0);
|
|
370
|
+
if (this.buffer.length < 4 + length) return;
|
|
371
|
+
const payload = Buffer.from(this.buffer.subarray(4, 4 + length));
|
|
372
|
+
this.buffer = this.buffer.subarray(4 + length);
|
|
373
|
+
const req = this.pending.shift();
|
|
374
|
+
if (!req.includeTrace) {
|
|
375
|
+
req.resolve(payload);
|
|
376
|
+
} else if (this.traces.length > 0) {
|
|
377
|
+
const trace = this.traces.shift();
|
|
378
|
+
req.resolve({ payload, trace });
|
|
379
|
+
} else {
|
|
380
|
+
this.waitingForTrace.push({ payload, resolve: req.resolve, reject: req.reject });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
request(inputWire) {
|
|
386
|
+
return this._sendRequest(inputWire, false);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
requestWithTrace(inputWire) {
|
|
390
|
+
return this._sendRequest(inputWire, true);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
_sendRequest(inputWire, includeTrace) {
|
|
394
|
+
if (this.closed) {
|
|
395
|
+
return Promise.reject(new Error(`${this.exePath} --server is closed`));
|
|
396
|
+
}
|
|
397
|
+
const header = Buffer.alloc(4);
|
|
398
|
+
header.writeUInt32BE(inputWire.length);
|
|
399
|
+
const frame = Buffer.concat([header, inputWire]);
|
|
400
|
+
return new Promise((resolve, reject) => {
|
|
401
|
+
this.pending.push({ resolve, reject, includeTrace });
|
|
402
|
+
this.child.stdin.write(frame, error => {
|
|
403
|
+
if (error) {
|
|
404
|
+
const index = this.pending.findIndex(entry => entry.resolve === resolve);
|
|
405
|
+
if (index !== -1) this.pending.splice(index, 1);
|
|
406
|
+
reject(error);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
close() {
|
|
413
|
+
this.child.stdin.end();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function runTimedIterations(iterations, body) {
|
|
418
|
+
const iterationTimes = [];
|
|
419
|
+
const startedAt = performance.now();
|
|
420
|
+
|
|
421
|
+
for (let i = 0; i < iterations; i++) {
|
|
422
|
+
const iterationStartedAt = performance.now();
|
|
423
|
+
try {
|
|
424
|
+
body();
|
|
425
|
+
} catch (err) {
|
|
426
|
+
if (err instanceof RangeError) {
|
|
427
|
+
return { status: "overflow", error: "stack overflow (no TCO)" };
|
|
428
|
+
}
|
|
429
|
+
throw err;
|
|
430
|
+
}
|
|
431
|
+
iterationTimes.push(performance.now() - iterationStartedAt);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
time: performance.now() - startedAt,
|
|
436
|
+
iterationTimes
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export async function runTimedIterationsAsync(iterations, body) {
|
|
441
|
+
const iterationTimes = [];
|
|
442
|
+
const startedAt = performance.now();
|
|
443
|
+
|
|
444
|
+
for (let i = 0; i < iterations; i++) {
|
|
445
|
+
const iterationStartedAt = performance.now();
|
|
446
|
+
await body();
|
|
447
|
+
iterationTimes.push(performance.now() - iterationStartedAt);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
time: performance.now() - startedAt,
|
|
452
|
+
iterationTimes
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function runNativeAwareIterations(iterations, testSuite, relations, state, codes) {
|
|
457
|
+
return runTimedIterations(iterations, () => {
|
|
458
|
+
for (const tc of testSuite) {
|
|
459
|
+
const relDef = relations[tc.op].relDef;
|
|
460
|
+
run.defs = state;
|
|
461
|
+
const result = run(codes.find, relDef.def, tc.inputVal, relDef.typePatternGraph);
|
|
462
|
+
if (result === undefined) throw new Error(`${tc.op}(${tc.label}) returned undefined`);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function runNativeFreeIterations(iterations, testSuite, relations, state, codes) {
|
|
468
|
+
return runTimedIterations(iterations, () => {
|
|
469
|
+
for (const tc of testSuite) {
|
|
470
|
+
const relDef = relations[tc.op].relDef;
|
|
471
|
+
run_converged.defs = state;
|
|
472
|
+
const result = run_converged(codes.find, relDef.def, tc.inputVal, relDef.typePatternGraph);
|
|
473
|
+
if (result === undefined) throw new Error(`${tc.op}(${tc.label}) returned undefined`);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export function runKVMIterations(iterations, testSuite, relations, state, codes) {
|
|
479
|
+
const contextFree = {
|
|
480
|
+
rels: state.rels,
|
|
481
|
+
findCode: codes.find,
|
|
482
|
+
options: { envelopeFree: true }
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
return runTimedIterations(iterations, () => {
|
|
486
|
+
for (const tc of testSuite) {
|
|
487
|
+
const result = executeKVM(relations[tc.op].kvmFunc, tc.inputVal, contextFree);
|
|
488
|
+
if (result === undefined) throw new Error(`${tc.op}(${tc.label}) returned undefined`);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export function runKVMCase(testCase, relations, state, codes) {
|
|
494
|
+
return executeKVM(relations[testCase.op].kvmFunc, testCase.inputVal, {
|
|
495
|
+
rels: state.rels,
|
|
496
|
+
findCode: codes.find,
|
|
497
|
+
options: { envelopeFree: true }
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function runLLVMSpawnIterations(iterations, testSuite) {
|
|
502
|
+
const llvmCases = testSuite.filter(tc => tc.llvm?.status === "ok");
|
|
503
|
+
if (llvmCases.length === 0) return null;
|
|
504
|
+
|
|
505
|
+
return runTimedIterationsAsync(iterations, async () => {
|
|
506
|
+
for (const tc of llvmCases) {
|
|
507
|
+
await runExecutable(tc.llvm.exePath, tc.inputWire);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function runLLVMPersistentIterations(iterations, testSuite) {
|
|
513
|
+
const runner = createLLVMPersistentRunner(testSuite);
|
|
514
|
+
try {
|
|
515
|
+
return await runner.run(iterations);
|
|
516
|
+
} finally {
|
|
517
|
+
runner.close();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function createLLVMPersistentRunner(testSuite) {
|
|
522
|
+
const llvmCases = testSuite.filter(tc => tc.llvm?.status === "ok");
|
|
523
|
+
const servers = new Map();
|
|
524
|
+
function serverFor(exePath) {
|
|
525
|
+
let server = servers.get(exePath);
|
|
526
|
+
if (server == null) {
|
|
527
|
+
server = new PersistentExecutable(exePath);
|
|
528
|
+
servers.set(exePath, server);
|
|
529
|
+
}
|
|
530
|
+
return server;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
async run(iterations) {
|
|
535
|
+
if (llvmCases.length === 0) return null;
|
|
536
|
+
return runTimedIterationsAsync(iterations, async () => {
|
|
537
|
+
if (process.env.LLVM_PIPELINE === "1") {
|
|
538
|
+
const pending = [];
|
|
539
|
+
for (const tc of llvmCases) {
|
|
540
|
+
pending.push(serverFor(tc.llvm.exePath).request(tc.inputWire));
|
|
541
|
+
}
|
|
542
|
+
await Promise.all(pending);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
for (const tc of llvmCases) {
|
|
546
|
+
await serverFor(tc.llvm.exePath).request(tc.inputWire);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
},
|
|
550
|
+
close() {
|
|
551
|
+
for (const server of servers.values()) server.close();
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function createLLVMRunner(testSuite) {
|
|
557
|
+
if (process.env.LLVM_SPAWN_PER_CALL === "1") {
|
|
558
|
+
return {
|
|
559
|
+
run(iterations) {
|
|
560
|
+
return runLLVMSpawnIterations(iterations, testSuite);
|
|
561
|
+
},
|
|
562
|
+
close() {}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return createLLVMPersistentRunner(testSuite);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export async function runLLVMIterations(iterations, testSuite) {
|
|
569
|
+
const runner = createLLVMRunner(testSuite);
|
|
570
|
+
try {
|
|
571
|
+
return await runner.run(iterations);
|
|
572
|
+
} finally {
|
|
573
|
+
runner.close();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export async function runLLVMMainBench(testSuite, calls) {
|
|
578
|
+
const llvmCases = testSuite.filter(tc => tc.llvm?.status === "ok");
|
|
579
|
+
if (llvmCases.length === 0) return null;
|
|
580
|
+
const results = [];
|
|
581
|
+
for (const tc of llvmCases) {
|
|
582
|
+
const timing = await runExecutableMainBench(tc.llvm.exePath, tc.inputWire, calls);
|
|
583
|
+
results.push({ testCase: tc, timing });
|
|
584
|
+
}
|
|
585
|
+
const totalNs = results.reduce((sum, item) => sum + item.timing.totalNs, 0);
|
|
586
|
+
return {
|
|
587
|
+
calls,
|
|
588
|
+
cases: results.length,
|
|
589
|
+
totalNs,
|
|
590
|
+
perIterationMs: totalNs / calls / 1000000,
|
|
591
|
+
results
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function llvmLaneName() {
|
|
596
|
+
if (process.env.LLVM_SPAWN_PER_CALL === "1") return "LLVM Executable (spawn/call)";
|
|
597
|
+
return process.env.LLVM_PIPELINE === "1"
|
|
598
|
+
? "LLVM Executable (persistent, pipelined)"
|
|
599
|
+
: "LLVM Executable (persistent)";
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function arm64LaneName(optLevel = "-O2") {
|
|
603
|
+
const mode = process.env.ARM64_SPAWN_PER_CALL === "1"
|
|
604
|
+
? "spawn/call"
|
|
605
|
+
: (process.env.ARM64_PIPELINE === "1" ? "persistent, pipelined" : "persistent");
|
|
606
|
+
return `Linux ARM64 ${optLevel} (${mode})`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function createARM64Runner(testSuite) {
|
|
610
|
+
const arm64Cases = testSuite.filter(tc => tc.arm64?.status === "ok");
|
|
611
|
+
if (process.env.ARM64_SPAWN_PER_CALL === "1") {
|
|
612
|
+
return {
|
|
613
|
+
run(iterations) {
|
|
614
|
+
if (arm64Cases.length === 0) return null;
|
|
615
|
+
return runTimedIterationsAsync(iterations, async () => {
|
|
616
|
+
for (const tc of arm64Cases) {
|
|
617
|
+
await runExecutable(tc.arm64.exePath, tc.inputWire);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
},
|
|
621
|
+
close() {}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const servers = new Map();
|
|
626
|
+
function serverFor(exePath) {
|
|
627
|
+
let server = servers.get(exePath);
|
|
628
|
+
if (server == null) {
|
|
629
|
+
server = new PersistentExecutable(exePath);
|
|
630
|
+
servers.set(exePath, server);
|
|
631
|
+
}
|
|
632
|
+
return server;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return {
|
|
636
|
+
async run(iterations) {
|
|
637
|
+
if (arm64Cases.length === 0) return null;
|
|
638
|
+
return runTimedIterationsAsync(iterations, async () => {
|
|
639
|
+
if (process.env.ARM64_PIPELINE === "1") {
|
|
640
|
+
const pending = [];
|
|
641
|
+
for (const tc of arm64Cases) {
|
|
642
|
+
pending.push(serverFor(tc.arm64.exePath).request(tc.inputWire));
|
|
643
|
+
}
|
|
644
|
+
await Promise.all(pending);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
for (const tc of arm64Cases) {
|
|
648
|
+
await serverFor(tc.arm64.exePath).request(tc.inputWire);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
},
|
|
652
|
+
close() {
|
|
653
|
+
for (const server of servers.values()) server.close();
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
export function average(times) {
|
|
659
|
+
return times.reduce((sum, time) => sum + time, 0) / times.length;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export function formatTiming(result) {
|
|
663
|
+
if (result == null) return "unavailable";
|
|
664
|
+
if (result.status === "overflow") return "stack overflow (no TCO)";
|
|
665
|
+
if (!result.iterationTimes) return "unavailable";
|
|
666
|
+
const samples = result.iterationTimes.map(time => time.toFixed(2)).join(", ");
|
|
667
|
+
return `(${samples}) ~ ${average(result.iterationTimes).toFixed(2)} ms/iteration`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export function toPlainObject(value) {
|
|
671
|
+
return JSON.parse(JSON.stringify(value));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function safeDeepEqual(a, b) {
|
|
675
|
+
const stack = [[a, b]];
|
|
676
|
+
while (stack.length > 0) {
|
|
677
|
+
const [x, y] = stack.pop();
|
|
678
|
+
if (x === y) continue;
|
|
679
|
+
if (x == null || y == null) {
|
|
680
|
+
if (x !== y) return false;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (typeof x !== "object" || typeof y !== "object") {
|
|
684
|
+
if (x !== y) return false;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (x.type !== undefined || y.type !== undefined) {
|
|
688
|
+
if (x.type !== y.type) return false;
|
|
689
|
+
if (x.tag !== y.tag) return false;
|
|
690
|
+
if (x.value !== undefined || y.value !== undefined) {
|
|
691
|
+
stack.push([x.value, y.value]);
|
|
692
|
+
}
|
|
693
|
+
if (x.product !== undefined || y.product !== undefined) {
|
|
694
|
+
const xp = x.product || {};
|
|
695
|
+
const yp = y.product || {};
|
|
696
|
+
const xk = Object.keys(xp);
|
|
697
|
+
const yk = Object.keys(yp);
|
|
698
|
+
if (xk.length !== yk.length) return false;
|
|
699
|
+
for (const k of xk) {
|
|
700
|
+
if (!Object.hasOwn(yp, k)) return false;
|
|
701
|
+
stack.push([xp[k], yp[k]]);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
const xk = Object.keys(x);
|
|
707
|
+
const yk = Object.keys(y);
|
|
708
|
+
if (xk.length !== yk.length) return false;
|
|
709
|
+
for (const k of xk) {
|
|
710
|
+
if (!Object.hasOwn(y, k)) return false;
|
|
711
|
+
stack.push([x[k], y[k]]);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function printCompileFailures(testSuite, limit = 3) {
|
|
718
|
+
const failed = testSuite.filter(tc => tc.llvm?.status === "failed");
|
|
719
|
+
if (failed.length === 0) return;
|
|
720
|
+
|
|
721
|
+
console.log(`==> LLVM compile failures: ${failed.length}/${testSuite.length} cases`);
|
|
722
|
+
for (const tc of failed.slice(0, limit)) {
|
|
723
|
+
const firstLines = tc.llvm.error.split("\n").slice(0, 8).join("\n");
|
|
724
|
+
console.log(`-- ${tc.op}(${tc.label})`);
|
|
725
|
+
console.log(firstLines);
|
|
726
|
+
}
|
|
727
|
+
if (failed.length > limit) {
|
|
728
|
+
console.log(`-- ${failed.length - limit} more failures omitted`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
export function shouldStrictFail(testSuite) {
|
|
733
|
+
return process.env.LLVM_STRICT === "1" &&
|
|
734
|
+
testSuite.some(tc => tc.llvm?.status !== "ok" || tc.llvmConformance !== "ok");
|
|
735
|
+
}
|