@fraczak/k 6.3.2 → 6.4.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/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 +76 -14
- package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
- package/DOCS/dependency-graph.md +144 -0
- package/Examples/arithmetics.k +51 -48
- 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/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 +60 -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 +102 -0
- package/backends/wasm/scripts/run-tests.mjs +41 -0
- package/backends/wasm/src/cli.mjs +133 -0
- package/backends/wasm/src/kvm2wasm.mjs +661 -0
- package/backends/wasm/src/wasm.mjs +1010 -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 +617 -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/json.mjs +16 -2
- package/codecs/runtime/show-value.mjs +2 -2
- 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 +133 -20
- package/repl.mjs +408 -59
- 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 +6 -2
- 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 +175 -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,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codes,
|
|
3
|
+
decodeWire,
|
|
4
|
+
encodeToWire,
|
|
5
|
+
run,
|
|
6
|
+
run_converged
|
|
7
|
+
} from "../../backend-api.mjs";
|
|
8
|
+
import { BackendAdapter, BackendRunner } from "../backend.mjs";
|
|
9
|
+
|
|
10
|
+
class NativeRunner extends BackendRunner {
|
|
11
|
+
constructor(backendId, mode, state) {
|
|
12
|
+
super(backendId);
|
|
13
|
+
this.mode = mode; // "aware" or "free"
|
|
14
|
+
this.state = state;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async runSingle(testCase, options = {}) {
|
|
18
|
+
const relDef = testCase.relDef;
|
|
19
|
+
const inputVal = testCase.inputVal;
|
|
20
|
+
const findCode = codes.find;
|
|
21
|
+
|
|
22
|
+
if (this.mode === "free") {
|
|
23
|
+
run_converged.defs = this.state;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const t0 = process.hrtime.bigint();
|
|
27
|
+
const result = this.mode === "aware"
|
|
28
|
+
? run(findCode, relDef.def, inputVal, relDef.typePatternGraph)
|
|
29
|
+
: run_converged(findCode, relDef.def, inputVal, relDef.typePatternGraph);
|
|
30
|
+
const t1 = process.hrtime.bigint();
|
|
31
|
+
|
|
32
|
+
const evalNs = Number(t1 - t0);
|
|
33
|
+
|
|
34
|
+
const trace = options.trace ? {
|
|
35
|
+
ipcReadNs: 0,
|
|
36
|
+
decodeNs: 0,
|
|
37
|
+
flatInNs: 0,
|
|
38
|
+
evalNs,
|
|
39
|
+
flatOutNs: 0,
|
|
40
|
+
encodeNs: 0,
|
|
41
|
+
ipcWriteNs: 0,
|
|
42
|
+
totalNs: evalNs
|
|
43
|
+
} : null;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
outputVal: result,
|
|
47
|
+
trace
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class NativeAdapter extends BackendAdapter {
|
|
53
|
+
constructor(mode = "free", state = null) {
|
|
54
|
+
const id = mode === "aware" ? "native-aware" : "native-free";
|
|
55
|
+
const name = mode === "aware" ? "Native JS (Envelope-Aware)" : "Native JS (Envelope-Free)";
|
|
56
|
+
super(id, name);
|
|
57
|
+
this.mode = mode;
|
|
58
|
+
this.state = state;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setState(state) {
|
|
62
|
+
this.state = state;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async compile(testCase) {
|
|
66
|
+
return {
|
|
67
|
+
status: "ok",
|
|
68
|
+
relDef: testCase.relDef,
|
|
69
|
+
inputVal: testCase.inputVal,
|
|
70
|
+
outputPattern: testCase.outputPattern
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async createRunner(compiledCases) {
|
|
75
|
+
return new NativeRunner(this.id, this.mode, this.state);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { BackendAdapter, BackendRunner } from "../backend.mjs";
|
|
5
|
+
import { PersistentExecutable } from "../persistent.mjs";
|
|
6
|
+
import {
|
|
7
|
+
compileWasmArtifactFromObject,
|
|
8
|
+
instantiateWasmArtifact
|
|
9
|
+
} from "../../backends/wasm/src/wasm.mjs";
|
|
10
|
+
import { decodeWire } from "../../backend-api.mjs";
|
|
11
|
+
|
|
12
|
+
class WasmInProcessRunner extends BackendRunner {
|
|
13
|
+
constructor(backendId) {
|
|
14
|
+
super(backendId);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async runSingle(testCase, options = {}) {
|
|
18
|
+
const runner = testCase.runner;
|
|
19
|
+
if (options.trace) {
|
|
20
|
+
const res = runner.execute(testCase.inputWire, { trace: true });
|
|
21
|
+
const decoded = decodeWire(res.outputWire);
|
|
22
|
+
return {
|
|
23
|
+
outputWire: res.outputWire,
|
|
24
|
+
outputVal: decoded.value,
|
|
25
|
+
outputPattern: decoded.pattern,
|
|
26
|
+
trace: {
|
|
27
|
+
ipcReadNs: 0,
|
|
28
|
+
decodeNs: res.trace.decodeNs,
|
|
29
|
+
flatInNs: res.trace.flatInNs,
|
|
30
|
+
evalNs: res.trace.evalNs,
|
|
31
|
+
flatOutNs: res.trace.flatOutNs,
|
|
32
|
+
encodeNs: res.trace.encodeNs,
|
|
33
|
+
ipcWriteNs: 0,
|
|
34
|
+
totalNs: res.trace.totalNs
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
} else {
|
|
38
|
+
const outWire = runner.execute(testCase.inputWire);
|
|
39
|
+
const decoded = decodeWire(outWire);
|
|
40
|
+
return {
|
|
41
|
+
outputWire: outWire,
|
|
42
|
+
outputVal: decoded.value,
|
|
43
|
+
outputPattern: decoded.pattern
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async runBatch(compiledCases) {
|
|
49
|
+
for (const c of compiledCases) {
|
|
50
|
+
c.runner.execute(c.inputWire);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async close() {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class WasmPipeRunner extends BackendRunner {
|
|
58
|
+
constructor(backendId, servers = new Map()) {
|
|
59
|
+
super(backendId);
|
|
60
|
+
this.servers = servers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getServer(exePath) {
|
|
64
|
+
let server = this.servers.get(exePath);
|
|
65
|
+
if (!server) {
|
|
66
|
+
server = new PersistentExecutable(exePath);
|
|
67
|
+
this.servers.set(exePath, server);
|
|
68
|
+
}
|
|
69
|
+
return server;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async runSingle(testCase, options = {}) {
|
|
73
|
+
const server = this.getServer(testCase.exePath);
|
|
74
|
+
if (options.trace) {
|
|
75
|
+
const res = await server.requestWithTrace(testCase.inputWire);
|
|
76
|
+
const decoded = decodeWire(res.payload);
|
|
77
|
+
return {
|
|
78
|
+
outputWire: res.payload,
|
|
79
|
+
outputVal: decoded.value,
|
|
80
|
+
outputPattern: decoded.pattern,
|
|
81
|
+
trace: res.trace,
|
|
82
|
+
profile: res.trace?.profile
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
const payload = await server.request(testCase.inputWire);
|
|
86
|
+
const decoded = decodeWire(payload);
|
|
87
|
+
return {
|
|
88
|
+
outputWire: payload,
|
|
89
|
+
outputVal: decoded.value,
|
|
90
|
+
outputPattern: decoded.pattern
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async runBatch(compiledCases) {
|
|
96
|
+
for (const c of compiledCases) {
|
|
97
|
+
const server = this.getServer(c.exePath);
|
|
98
|
+
await server.request(c.inputWire);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async close() {
|
|
103
|
+
for (const server of this.servers.values()) {
|
|
104
|
+
server.close();
|
|
105
|
+
}
|
|
106
|
+
this.servers.clear();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class WasmAdapter extends BackendAdapter {
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
const mode = options.mode || "pipe"; // "pipe" or "in-process"
|
|
113
|
+
const id = options.id || (mode === "in-process" ? "wasm-in-process" : "wasm");
|
|
114
|
+
const name = options.name || `WebAssembly (${mode === "pipe" ? "persistent pipe" : "in-process"})`;
|
|
115
|
+
super(id, name, options);
|
|
116
|
+
this.mode = mode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isAvailable() {
|
|
120
|
+
return true; // Runs in Node.js via WABT / V8 Wasm
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async compile(testCase, options = {}) {
|
|
124
|
+
const cacheDir = options.cacheDir || path.join(process.cwd(), ".cache");
|
|
125
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
126
|
+
const name = testCase.op || testCase.relName || "case";
|
|
127
|
+
const wasmPath = path.join(cacheDir, `harness_wasm_${name}.wasm`);
|
|
128
|
+
const exePath = path.join(cacheDir, `harness_wasm_${name}.exe`);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const entry = testCase.relHash || testCase.relName || testCase.op;
|
|
132
|
+
const wasmBuf = await compileWasmArtifactFromObject(testCase.object, {
|
|
133
|
+
entry,
|
|
134
|
+
profile: Boolean(options.profile)
|
|
135
|
+
});
|
|
136
|
+
fs.writeFileSync(wasmPath, wasmBuf);
|
|
137
|
+
|
|
138
|
+
const runBin = fileURLToPath(import.meta.resolve("../../backends/wasm/bin/k-wasm-run.mjs"));
|
|
139
|
+
fs.writeFileSync(exePath, `#!/bin/sh\nexec node "${runBin}" "${wasmPath}" "$@"\n`, { mode: 0o755 });
|
|
140
|
+
|
|
141
|
+
let inProcessRunner = null;
|
|
142
|
+
if (this.mode === "in-process") {
|
|
143
|
+
inProcessRunner = await instantiateWasmArtifact(wasmBuf);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
status: "ok",
|
|
148
|
+
wasmPath,
|
|
149
|
+
exePath,
|
|
150
|
+
runner: inProcessRunner,
|
|
151
|
+
inputWire: testCase.inputWire,
|
|
152
|
+
inputVal: testCase.inputVal,
|
|
153
|
+
outputPattern: testCase.outputPattern
|
|
154
|
+
};
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return {
|
|
157
|
+
status: "failed",
|
|
158
|
+
error: error.stack || error.message || String(error)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async createRunner(compiledCases) {
|
|
164
|
+
if (this.mode === "in-process") {
|
|
165
|
+
return new WasmInProcessRunner(this.id);
|
|
166
|
+
}
|
|
167
|
+
return new WasmPipeRunner(this.id);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Base class for all backend adapters
|
|
2
|
+
|
|
3
|
+
export class BackendAdapter {
|
|
4
|
+
constructor(id, name, options = {}) {
|
|
5
|
+
this.id = id;
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
isAvailable() {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getLaneName() {
|
|
15
|
+
return this.name;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Compiles or prepares a test case for execution.
|
|
20
|
+
* @param {object} testCase
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @returns {Promise<object>} compiled artifact/handle
|
|
23
|
+
*/
|
|
24
|
+
async compile(testCase, options = {}) {
|
|
25
|
+
throw new Error(`compile() not implemented for backend '${this.id}'`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a runner for a suite of compiled cases.
|
|
30
|
+
* @param {Array<object>} compiledCases
|
|
31
|
+
* @param {object} options
|
|
32
|
+
* @returns {Promise<BackendRunner>}
|
|
33
|
+
*/
|
|
34
|
+
async createRunner(compiledCases, options = {}) {
|
|
35
|
+
throw new Error(`createRunner() not implemented for backend '${this.id}'`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class BackendRunner {
|
|
40
|
+
constructor(backendId, options = {}) {
|
|
41
|
+
this.backendId = backendId;
|
|
42
|
+
this.options = options;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run a single case.
|
|
47
|
+
* @param {object} compiledCase
|
|
48
|
+
* @param {object} options { trace?: boolean, profile?: boolean }
|
|
49
|
+
* @returns {Promise<{ outputWire?: Buffer, outputVal?: any, trace?: object, profile?: object }>}
|
|
50
|
+
*/
|
|
51
|
+
async runSingle(compiledCase, options = {}) {
|
|
52
|
+
throw new Error(`runSingle() not implemented for runner '${this.backendId}'`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run all cases once.
|
|
57
|
+
* @param {Array<object>} compiledCases
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
async runBatch(compiledCases) {
|
|
61
|
+
for (const c of compiledCases) {
|
|
62
|
+
await this.runSingle(c);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Run timed iterations over all cases.
|
|
68
|
+
* @param {Array<object>} compiledCases
|
|
69
|
+
* @param {number} iterations
|
|
70
|
+
* @returns {Promise<{ totalNs: number, perIterationMs: number, iterationTimes: number[] }>}
|
|
71
|
+
*/
|
|
72
|
+
async runIterations(compiledCases, iterations) {
|
|
73
|
+
const iterationTimes = [];
|
|
74
|
+
let totalNs = 0;
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < iterations; i++) {
|
|
77
|
+
const t0 = process.hrtime.bigint();
|
|
78
|
+
await this.runBatch(compiledCases);
|
|
79
|
+
const t1 = process.hrtime.bigint();
|
|
80
|
+
const diffNs = Number(t1 - t0);
|
|
81
|
+
totalNs += diffNs;
|
|
82
|
+
iterationTimes.push(diffNs / 1000000);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
totalNs,
|
|
87
|
+
perIterationMs: iterations > 0 ? (totalNs / iterations / 1000000) : 0,
|
|
88
|
+
iterationTimes
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async close() {
|
|
93
|
+
// Optional cleanup
|
|
94
|
+
}
|
|
95
|
+
}
|
package/harness/cli.mjs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { createPolySuite } from "./suites/poly.mjs";
|
|
5
|
+
import {
|
|
6
|
+
formatPhaseTable,
|
|
7
|
+
formatProfileTable,
|
|
8
|
+
formatBenchmarkTable
|
|
9
|
+
} from "./format.mjs";
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const options = {
|
|
13
|
+
suite: { type: "string", default: "poly" },
|
|
14
|
+
backends: { type: "string", short: "b" },
|
|
15
|
+
iterations: { type: "string", short: "i", default: "3" },
|
|
16
|
+
warmup: { type: "string", short: "w", default: "1" },
|
|
17
|
+
trace: { type: "boolean", default: false },
|
|
18
|
+
profile: { type: "boolean", default: false },
|
|
19
|
+
conformance: { type: "boolean", default: false },
|
|
20
|
+
all: { type: "boolean", default: false },
|
|
21
|
+
format: { type: "string", default: "text" },
|
|
22
|
+
listLength: { type: "string", default: "40" },
|
|
23
|
+
help: { type: "boolean", short: "h", default: false }
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const { values } = parseArgs({ options, allowPositionals: true });
|
|
27
|
+
|
|
28
|
+
if (values.help) {
|
|
29
|
+
console.log(`Usage: node harness/cli.mjs [options]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--suite <name> Benchmark suite: poly (default: poly)
|
|
33
|
+
-b, --backends <csv> Comma-separated backends (default: all available)
|
|
34
|
+
-i, --iterations <n> Measured iterations (default: 3)
|
|
35
|
+
-w, --warmup <n> Warmup iterations (default: 1)
|
|
36
|
+
--trace Measure 7-phase nanosecond execution timing
|
|
37
|
+
--profile Measure per-function call count profiling
|
|
38
|
+
--conformance Verify output equality across all backends
|
|
39
|
+
--all Run conformance, profile, trace, and benchmark
|
|
40
|
+
--format <fmt> Output format: text, json (default: text)
|
|
41
|
+
--listLength <n> Workload size for list operations (default: 40)
|
|
42
|
+
-h, --help Show this help message
|
|
43
|
+
`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const listLength = parseInt(values.listLength, 10);
|
|
48
|
+
const iterations = parseInt(values.iterations, 10);
|
|
49
|
+
const warmup = parseInt(values.warmup, 10);
|
|
50
|
+
const doTrace = values.trace || values.all;
|
|
51
|
+
const doProfile = values.profile || values.all;
|
|
52
|
+
const doConformance = values.conformance || values.all;
|
|
53
|
+
const doBenchmark = !values.trace && !values.profile && !values.conformance ? true : values.all;
|
|
54
|
+
|
|
55
|
+
console.log(`==> Initializing test suite: ${values.suite} (listLength=${listLength})...`);
|
|
56
|
+
let suite;
|
|
57
|
+
if (values.suite === "poly") {
|
|
58
|
+
suite = await createPolySuite({ listLength });
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(`Unknown suite: ${values.suite}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const available = suite.getAvailableBackendIds();
|
|
64
|
+
const selectedBackends = values.backends
|
|
65
|
+
? values.backends.split(",").map(b => b.trim()).filter(Boolean)
|
|
66
|
+
: available;
|
|
67
|
+
|
|
68
|
+
console.log(`==> Target backends: ${selectedBackends.join(", ")}`);
|
|
69
|
+
console.log(`==> Compiling test cases for backends...`);
|
|
70
|
+
const compileResults = await suite.compileAll(selectedBackends, {
|
|
71
|
+
profile: doProfile,
|
|
72
|
+
trace: doTrace
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
for (const [id, res] of Object.entries(compileResults)) {
|
|
76
|
+
console.log(` [${id}] ${res.status}: ${res.ready}/${res.total} cases ready`);
|
|
77
|
+
if (res.status !== "ok" && res.error) {
|
|
78
|
+
console.log(` Error: ${res.error.split("\n")[0]}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const readyBackends = selectedBackends.filter(id => compileResults[id]?.status === "ok");
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// 1. Conformance
|
|
86
|
+
if (doConformance) {
|
|
87
|
+
console.log("\n==> Running Conformance Verification...");
|
|
88
|
+
const confResults = await suite.checkConformance(readyBackends);
|
|
89
|
+
let allPassed = true;
|
|
90
|
+
for (const r of confResults) {
|
|
91
|
+
const statuses = Object.entries(r.conformance).map(([id, c]) => `${id}:${c.status}`).join(" ");
|
|
92
|
+
console.log(` ${r.name.padEnd(15)} -> ${statuses}`);
|
|
93
|
+
if (!r.passed) allPassed = false;
|
|
94
|
+
}
|
|
95
|
+
console.log(allPassed ? " All backends conform!" : " Conformance failure detected!");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 2. Profile
|
|
99
|
+
if (doProfile) {
|
|
100
|
+
console.log("\n==> Running Per-Function Call Profiling...");
|
|
101
|
+
const profResults = await suite.runProfile(readyBackends);
|
|
102
|
+
console.log(formatProfileTable(profResults, readyBackends));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. Trace
|
|
106
|
+
if (doTrace) {
|
|
107
|
+
console.log("\n==> Running 7-Phase Execution Tracing...");
|
|
108
|
+
const traceResults = await suite.runTrace(readyBackends, 3);
|
|
109
|
+
console.log(formatPhaseTable(traceResults, readyBackends));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. Benchmark
|
|
113
|
+
if (doBenchmark) {
|
|
114
|
+
console.log(`\n==> Running Benchmark (${iterations} iterations, ${warmup} warmup)...`);
|
|
115
|
+
const benchResults = await suite.runBenchmark(readyBackends, { warmup, iterations });
|
|
116
|
+
if (values.format === "json") {
|
|
117
|
+
console.log(JSON.stringify(benchResults, null, 2));
|
|
118
|
+
} else {
|
|
119
|
+
console.log(formatBenchmarkTable(benchResults));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
await suite.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main().catch(err => {
|
|
128
|
+
console.error("Harness error:", err);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { PHASES, formatUs } from "./trace.mjs";
|
|
2
|
+
|
|
3
|
+
export function formatPhaseTable(phaseResults, backendIds = ["arm64", "llvm", "wasm", "kvm"]) {
|
|
4
|
+
const lines = [];
|
|
5
|
+
|
|
6
|
+
for (const item of phaseResults) {
|
|
7
|
+
lines.push(`\n--- Operation: ${item.op || item.name} (${item.label || ""}) ---`);
|
|
8
|
+
const header = ["Phase".padEnd(25), ...backendIds.map(id => id.padEnd(14))].join(" | ");
|
|
9
|
+
lines.push(header);
|
|
10
|
+
lines.push("-".repeat(header.length));
|
|
11
|
+
|
|
12
|
+
for (const phase of PHASES) {
|
|
13
|
+
if (phase.isTotal) {
|
|
14
|
+
lines.push("-".repeat(header.length));
|
|
15
|
+
}
|
|
16
|
+
const pName = phase.name.padEnd(25);
|
|
17
|
+
const cols = backendIds.map(id => {
|
|
18
|
+
const val = item[id]?.[phase.key];
|
|
19
|
+
return formatUs(val).padEnd(14);
|
|
20
|
+
});
|
|
21
|
+
lines.push([pName, ...cols].join(" | "));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatProfileTable(profileResults, backendIds = ["arm64", "wasm", "kvm"]) {
|
|
29
|
+
const lines = [];
|
|
30
|
+
|
|
31
|
+
for (const item of profileResults) {
|
|
32
|
+
lines.push(`\nFunction Call Profile: ${item.op || item.name}`);
|
|
33
|
+
const header = ["Function Name".padEnd(30), ...backendIds.map(id => `${id} Calls`.padEnd(18))].join(" | ");
|
|
34
|
+
lines.push(header);
|
|
35
|
+
lines.push("-".repeat(header.length));
|
|
36
|
+
|
|
37
|
+
const funcNames = new Set();
|
|
38
|
+
for (const id of backendIds) {
|
|
39
|
+
const prof = item[id] || {};
|
|
40
|
+
for (const fn of Object.keys(prof)) funcNames.add(fn);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const sortedFuncs = [...funcNames].sort();
|
|
44
|
+
const totals = Object.fromEntries(backendIds.map(id => [id, 0]));
|
|
45
|
+
|
|
46
|
+
for (const fn of sortedFuncs) {
|
|
47
|
+
const rowCols = backendIds.map(id => {
|
|
48
|
+
const count = item[id]?.[fn];
|
|
49
|
+
if (count != null) {
|
|
50
|
+
totals[id] += count;
|
|
51
|
+
return String(count).padStart(18);
|
|
52
|
+
}
|
|
53
|
+
return " - ".padStart(18);
|
|
54
|
+
});
|
|
55
|
+
lines.push([fn.padEnd(30), ...rowCols].join(" | "));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lines.push("-".repeat(header.length));
|
|
59
|
+
const totalRow = backendIds.map(id => String(totals[id]).padStart(18));
|
|
60
|
+
lines.push(["Total Calls".padEnd(30), ...totalRow].join(" | "));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatBenchmarkTable(benchResults) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
const hasPureEval = benchResults.some(r => r.pureEvalMs != null);
|
|
69
|
+
const header = [
|
|
70
|
+
"Backend".padEnd(35),
|
|
71
|
+
"Mean (ms/iter)".padStart(15),
|
|
72
|
+
...(hasPureEval ? ["Pure Eval (ms)".padStart(15)] : []),
|
|
73
|
+
"Min (ms)".padStart(12),
|
|
74
|
+
"Max (ms)".padStart(12),
|
|
75
|
+
"Samples".padStart(10)
|
|
76
|
+
].join(" | ");
|
|
77
|
+
|
|
78
|
+
lines.push(header);
|
|
79
|
+
lines.push("-".repeat(header.length));
|
|
80
|
+
|
|
81
|
+
for (const res of benchResults) {
|
|
82
|
+
const name = (res.name || res.id).padEnd(35);
|
|
83
|
+
const mean = (res.perIterationMs != null ? res.perIterationMs.toFixed(3) : "N/A").padStart(15);
|
|
84
|
+
const pureEval = hasPureEval
|
|
85
|
+
? (res.pureEvalMs != null ? res.pureEvalMs.toFixed(3) : "-").padStart(15)
|
|
86
|
+
: null;
|
|
87
|
+
const min = (res.minMs != null ? res.minMs.toFixed(3) : "-").padStart(12);
|
|
88
|
+
const max = (res.maxMs != null ? res.maxMs.toFixed(3) : "-").padStart(12);
|
|
89
|
+
const samples = (res.samples != null ? String(res.samples) : "0").padStart(10);
|
|
90
|
+
const cols = [name, mean, ...(pureEval ? [pureEval] : []), min, max, samples];
|
|
91
|
+
lines.push(cols.join(" | "));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { BenchmarkSuite } from "./suite.mjs";
|
|
2
|
+
export { createRegistry } from "./registry.mjs";
|
|
3
|
+
export { BackendAdapter, BackendRunner } from "./backend.mjs";
|
|
4
|
+
export { PersistentExecutable } from "./persistent.mjs";
|
|
5
|
+
export {
|
|
6
|
+
safeDeepEqual,
|
|
7
|
+
toPlainObject,
|
|
8
|
+
average,
|
|
9
|
+
stdDev,
|
|
10
|
+
min,
|
|
11
|
+
max,
|
|
12
|
+
formatTiming,
|
|
13
|
+
makeCacheDir
|
|
14
|
+
} from "./util.mjs";
|
|
15
|
+
export {
|
|
16
|
+
PHASES,
|
|
17
|
+
parseTraceLine,
|
|
18
|
+
averageTrace,
|
|
19
|
+
formatUs
|
|
20
|
+
} from "./trace.mjs";
|
|
21
|
+
export {
|
|
22
|
+
formatPhaseTable,
|
|
23
|
+
formatProfileTable,
|
|
24
|
+
formatBenchmarkTable
|
|
25
|
+
} from "./format.mjs";
|