@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,945 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import wabtFactory from "wabt";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
codes,
|
|
9
|
+
decodeWire,
|
|
10
|
+
encodeToWire,
|
|
11
|
+
executeKVM,
|
|
12
|
+
exportPatternGraph,
|
|
13
|
+
patternToPropertyList,
|
|
14
|
+
propertyListToPattern,
|
|
15
|
+
run,
|
|
16
|
+
run_converged,
|
|
17
|
+
valueForCode,
|
|
18
|
+
Value
|
|
19
|
+
} from "@fraczak/k/backend-api.mjs";
|
|
20
|
+
import { compileLibrary, loadLibrary } from "@fraczak/k/object.mjs";
|
|
21
|
+
import { parse as parseIntValue } from "@fraczak/k/codecs/int.mjs";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
csvEnv,
|
|
25
|
+
formatTiming,
|
|
26
|
+
createLLVMRunner,
|
|
27
|
+
createARM64Runner,
|
|
28
|
+
arm64LaneName,
|
|
29
|
+
llvmLaneName,
|
|
30
|
+
makeCacheDir,
|
|
31
|
+
parseNonNegativeIntEnv,
|
|
32
|
+
parsePositiveIntEnv,
|
|
33
|
+
prepareRelation,
|
|
34
|
+
printCompileFailures,
|
|
35
|
+
runExecutable,
|
|
36
|
+
runKVMCase,
|
|
37
|
+
runKVMIterations,
|
|
38
|
+
runNativeAwareIterations,
|
|
39
|
+
runNativeFreeIterations,
|
|
40
|
+
runTimedIterations,
|
|
41
|
+
runTimedIterationsAsync,
|
|
42
|
+
shouldStrictFail,
|
|
43
|
+
toPlainObject,
|
|
44
|
+
safeDeepEqual,
|
|
45
|
+
tryCompileCase,
|
|
46
|
+
wireInput,
|
|
47
|
+
PersistentExecutable
|
|
48
|
+
} from "../backends/llvm/tests/perf-support.mjs";
|
|
49
|
+
|
|
50
|
+
import {
|
|
51
|
+
cleanName,
|
|
52
|
+
instantiateWasmModule,
|
|
53
|
+
readArenaValue,
|
|
54
|
+
readWasmProfile,
|
|
55
|
+
resetWasmProfile,
|
|
56
|
+
writeValueToArena
|
|
57
|
+
} from "../backends/wasm/tests/perf-support.mjs";
|
|
58
|
+
|
|
59
|
+
import { compileARM64ArtifactFromObject } from "../backends/arm64/src/arm64.mjs";
|
|
60
|
+
import { compileWasmArtifactFromObject } from "../backends/wasm/src/wasm.mjs";
|
|
61
|
+
import { inputPatternForObjectRelation } from "../backends/llvm/src/executable.mjs";
|
|
62
|
+
|
|
63
|
+
// Backend selection
|
|
64
|
+
const backendsEnv = process.env.BACKENDS?.toLowerCase();
|
|
65
|
+
const llvmOnly = process.env.LLVM_ONLY === "1" || backendsEnv === "llvm";
|
|
66
|
+
const wasmOnly = process.env.WASM_ONLY === "1" || backendsEnv === "wasm";
|
|
67
|
+
const arm64Only = process.env.ARM64_ONLY === "1" || backendsEnv === "arm64";
|
|
68
|
+
|
|
69
|
+
const isArm64Host = process.arch === "arm64" && process.platform === "linux";
|
|
70
|
+
const runARM64 = !wasmOnly && !llvmOnly && (isArm64Host || arm64Only);
|
|
71
|
+
const runLLVM = !wasmOnly && !arm64Only;
|
|
72
|
+
const runWasm = !llvmOnly && !arm64Only;
|
|
73
|
+
const runBaselines = !process.env.BACKENDS_ONLY && (process.env.LLVM_ONLY !== "1") && (process.env.WASM_ONLY !== "1") && (process.env.ARM64_ONLY !== "1");
|
|
74
|
+
|
|
75
|
+
const ops = ["reverse", "concat", "split_by", "get_nth", "length"];
|
|
76
|
+
const benchNames = Object.fromEntries(ops.map(op => [op, `bench_${op}`]));
|
|
77
|
+
const listLength = parsePositiveIntEnv("LIST_LENGTH", 40);
|
|
78
|
+
const iterations = parsePositiveIntEnv("ITERATIONS", 3);
|
|
79
|
+
const runTrace = process.argv.includes("--trace") || process.env.TRACE === "1" || process.env.K_TRACE === "1";
|
|
80
|
+
const runProfile = runTrace || process.argv.includes("--profile") || process.env.K_PROFILE === "1" || process.env.PROFILE === "1";
|
|
81
|
+
|
|
82
|
+
// ARM64 options
|
|
83
|
+
const arm64WarmupIterations = parseNonNegativeIntEnv("ARM64_WARMUP_ITERATIONS", 1);
|
|
84
|
+
const arm64OptLevel = process.env.ARM64_OPT || "-O2";
|
|
85
|
+
|
|
86
|
+
// LLVM options
|
|
87
|
+
const llvmWarmupIterations = parseNonNegativeIntEnv("LLVM_WARMUP_ITERATIONS", 1);
|
|
88
|
+
const cacheDir = makeCacheDir("k-poly-perf-");
|
|
89
|
+
|
|
90
|
+
// Wasm options
|
|
91
|
+
const wasmWarmupIterations = parseNonNegativeIntEnv("WASM_WARMUP_ITERATIONS", 3);
|
|
92
|
+
const wasmReset = process.env.WASM_RESET !== "0";
|
|
93
|
+
const wasmPipe = process.env.WASM_PIPE === "0" || process.env.WASM_IN_PROCESS === "1" || process.argv.includes("--wasm-in-process")
|
|
94
|
+
? false
|
|
95
|
+
: true;
|
|
96
|
+
function wasmLaneName() {
|
|
97
|
+
const mode = wasmPipe ? "persistent" : "in-process";
|
|
98
|
+
return `WebAssembly (${mode})`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
console.log("==> Loading @fraczak/k/Examples/arithmetics.k as library");
|
|
103
|
+
const arithmeticsPath = fileURLToPath(import.meta.resolve("@fraczak/k/Examples/arithmetics.k"));
|
|
104
|
+
const arithmeticsSource = fs.readFileSync(arithmeticsPath, "utf8");
|
|
105
|
+
const arithmeticsLib = loadLibrary(compileLibrary(arithmeticsSource, { source: arithmeticsPath }));
|
|
106
|
+
|
|
107
|
+
console.log("==> Compiling @fraczak/k/Examples/poly.k with explicit library exports");
|
|
108
|
+
const polyPath = fileURLToPath(import.meta.resolve("@fraczak/k/Examples/poly.k"));
|
|
109
|
+
const polySource = fs.readFileSync(polyPath, "utf8");
|
|
110
|
+
|
|
111
|
+
function buildExportPreamble(exports, libraries) {
|
|
112
|
+
const aliasMap = {};
|
|
113
|
+
for (const lib of libraries) {
|
|
114
|
+
for (const [name, hash] of Object.entries(lib.relAlias || {})) {
|
|
115
|
+
if (name !== "__main__") aliasMap[name] = hash;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const lines = [];
|
|
119
|
+
for (const spec of exports) {
|
|
120
|
+
const [libName, localName] = spec.includes(":") ? spec.split(":", 2) : [spec, spec];
|
|
121
|
+
const hash = aliasMap[libName];
|
|
122
|
+
if (!hash) throw new Error(`--export: '${libName}' not found in loaded libraries`);
|
|
123
|
+
const body = hash.startsWith("@") ? hash.slice(1) : hash;
|
|
124
|
+
lines.push(`${localName} = @${body};`);
|
|
125
|
+
}
|
|
126
|
+
return lines.join("\n") + "\n";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const neededExports = ["0", "int", "inc", "dec", "nat", "zero_int?", "nil", "cons", "car", "cdr"];
|
|
130
|
+
const exportPreamble = buildExportPreamble(neededExports, [arithmeticsLib]);
|
|
131
|
+
const polyLib = loadLibrary(compileLibrary(exportPreamble + polySource, {
|
|
132
|
+
source: polyPath,
|
|
133
|
+
libraries: [arithmeticsLib]
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
const state = {
|
|
137
|
+
codes: { ...arithmeticsLib.codes, ...polyLib.codes },
|
|
138
|
+
rels: { ...arithmeticsLib.rels, ...polyLib.rels },
|
|
139
|
+
typeAliases: {},
|
|
140
|
+
relAliases: { ...polyLib.relAlias },
|
|
141
|
+
meta: { ...arithmeticsLib.meta, ...polyLib.meta }
|
|
142
|
+
};
|
|
143
|
+
for (const [hash, entry] of Object.entries(arithmeticsLib.meta)) {
|
|
144
|
+
if (entry.type === "code") {
|
|
145
|
+
for (const origin of entry.origins || []) {
|
|
146
|
+
if (origin.name) state.typeAliases[origin.name] = hash;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const op of ops) {
|
|
151
|
+
state.relAliases[benchNames[op]] = polyLib.relAlias[op];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function intValue(text) {
|
|
155
|
+
return valueForCode(parseIntValue(text), state.typeAliases.int, codes.find);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function makeList(elements) {
|
|
159
|
+
let curr = Value.variant("nil", Value.product({}));
|
|
160
|
+
for (let i = elements.length - 1; i >= 0; i--) {
|
|
161
|
+
curr = Value.variant("cons", Value.product({
|
|
162
|
+
car: elements[i],
|
|
163
|
+
cdr: curr
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
return curr;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log("==> Preparing relations");
|
|
170
|
+
const relations = {};
|
|
171
|
+
for (const op of ops) {
|
|
172
|
+
const name = benchNames[op];
|
|
173
|
+
relations[op] = prepareRelation(state, name);
|
|
174
|
+
}
|
|
175
|
+
codes.load(state.codes);
|
|
176
|
+
|
|
177
|
+
console.log(`==> Generating test cases for list length ${listLength}`);
|
|
178
|
+
const intElements = Array.from({ length: listLength }, (_, i) => intValue(String(i + 1)));
|
|
179
|
+
const fullList = makeList(intElements);
|
|
180
|
+
const halfLen = Math.floor(listLength / 2);
|
|
181
|
+
const firstHalf = makeList(intElements.slice(0, halfLen));
|
|
182
|
+
const secondHalf = makeList(intElements.slice(halfLen));
|
|
183
|
+
const midIdx = intValue(String(halfLen));
|
|
184
|
+
|
|
185
|
+
const rawCases = [
|
|
186
|
+
{ op: "reverse", label: `len ${listLength}`, inputVal: fullList },
|
|
187
|
+
{ op: "concat", label: `2x len ${halfLen}`, inputVal: Value.product({ xs: firstHalf, ys: secondHalf }) },
|
|
188
|
+
{ op: "split_by", label: `at ${halfLen} of ${listLength}`, inputVal: Value.product({ n: midIdx, xs: fullList }) },
|
|
189
|
+
{ op: "get_nth", label: `idx ${halfLen} of ${listLength}`, inputVal: Value.product({ n: midIdx, xs: fullList }) },
|
|
190
|
+
{ op: "length", label: `len ${listLength}`, inputVal: fullList }
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
const testSuite = [];
|
|
194
|
+
for (const tc of rawCases) {
|
|
195
|
+
const relation = relations[tc.op];
|
|
196
|
+
let expected;
|
|
197
|
+
if (!runBaselines || listLength > 400) {
|
|
198
|
+
expected = executeKVM(relation.kvmFunc, tc.inputVal, {
|
|
199
|
+
rels: state.rels,
|
|
200
|
+
codes: state.codes,
|
|
201
|
+
options: { envelopeFree: true }
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
try {
|
|
205
|
+
run.defs = state;
|
|
206
|
+
run_converged.defs = state;
|
|
207
|
+
expected = run_converged(codes.find, relation.relDef.def, tc.inputVal, relation.relDef.typePatternGraph);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
if (err instanceof RangeError) {
|
|
210
|
+
expected = executeKVM(relation.kvmFunc, tc.inputVal, {
|
|
211
|
+
rels: state.rels,
|
|
212
|
+
codes: state.codes,
|
|
213
|
+
options: { envelopeFree: true }
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
assert.ok(expected !== undefined, `${tc.op} should produce a value`);
|
|
221
|
+
|
|
222
|
+
const inferredPat = inputPatternForObjectRelation(relation.object, relation.relationName);
|
|
223
|
+
const inputWire = encodeToWire(tc.inputVal, inferredPat);
|
|
224
|
+
const inputPattern = decodeWire(inputWire).pattern;
|
|
225
|
+
|
|
226
|
+
const outWire = encodeToWire(expected);
|
|
227
|
+
const outputPattern = decodeWire(outWire).pattern;
|
|
228
|
+
|
|
229
|
+
testSuite.push({
|
|
230
|
+
op: tc.op,
|
|
231
|
+
label: tc.label,
|
|
232
|
+
inputVal: tc.inputVal,
|
|
233
|
+
inputWire,
|
|
234
|
+
inputPattern,
|
|
235
|
+
outputPattern,
|
|
236
|
+
expected
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Setup LLVM
|
|
241
|
+
if (runLLVM) {
|
|
242
|
+
console.log("==> Compiling LLVM executables");
|
|
243
|
+
for (const tc of testSuite) {
|
|
244
|
+
const relation = relations[tc.op];
|
|
245
|
+
tc.llvm = tryCompileCase({
|
|
246
|
+
object: relation.object,
|
|
247
|
+
relationName: relation.relationName,
|
|
248
|
+
relHash: relation.relHash,
|
|
249
|
+
inputPattern: tc.inputPattern,
|
|
250
|
+
cacheDir,
|
|
251
|
+
sourceLabel: `@fraczak/k/Examples/poly.k#${relation.relationName}`
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
codes.load(state.codes);
|
|
255
|
+
printCompileFailures(testSuite);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Setup Wasm
|
|
259
|
+
let wasmExports = null;
|
|
260
|
+
let wasmProfileFunctions = [];
|
|
261
|
+
const wasmExes = new Map();
|
|
262
|
+
if (runWasm) {
|
|
263
|
+
if (wasmPipe) {
|
|
264
|
+
console.log("==> Compiling WebAssembly executables (persistent runner)...");
|
|
265
|
+
for (const tc of testSuite) {
|
|
266
|
+
const relation = relations[tc.op];
|
|
267
|
+
try {
|
|
268
|
+
const wasmPath = path.join(cacheDir, `perf_poly_wasm_${tc.op}.wasm`);
|
|
269
|
+
const exePath = path.join(cacheDir, `perf_poly_wasm_${tc.op}.exe`);
|
|
270
|
+
const wasmBuf = await compileWasmArtifactFromObject(relation.object, {
|
|
271
|
+
entry: relation.relHash
|
|
272
|
+
});
|
|
273
|
+
fs.writeFileSync(wasmPath, wasmBuf);
|
|
274
|
+
const runBin = fileURLToPath(import.meta.resolve("../backends/wasm/bin/k-wasm-run.mjs"));
|
|
275
|
+
fs.writeFileSync(exePath, `#!/bin/sh\nexec node "${runBin}" "${wasmPath}" "$@"\n`, { mode: 0o755 });
|
|
276
|
+
tc.wasm = { status: "ok", exePath, wasmPath };
|
|
277
|
+
wasmExes.set(tc.op, tc.wasm);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
tc.wasm = {
|
|
280
|
+
status: "failed",
|
|
281
|
+
error: error.stack || error.message || String(error)
|
|
282
|
+
};
|
|
283
|
+
wasmExes.set(tc.op, tc.wasm);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
console.log("==> Compiling WebAssembly module...");
|
|
288
|
+
const wabtInstance = await wabtFactory();
|
|
289
|
+
const opHashes = ops.map(op => state.relAliases[benchNames[op]]);
|
|
290
|
+
const wasmModule = await instantiateWasmModule(opHashes, state, wabtInstance, { profile: runProfile });
|
|
291
|
+
wasmExports = wasmModule.exports;
|
|
292
|
+
wasmProfileFunctions = wasmModule.profileFunctions || [];
|
|
293
|
+
|
|
294
|
+
for (const tc of testSuite) {
|
|
295
|
+
tc.wasmPtrIn = writeValueToArena(wasmExports, tc.inputVal, propertyListToPattern(tc.inputPattern), 0);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function createWasmRunner(testSuite) {
|
|
301
|
+
const wasmCases = testSuite.filter(tc => tc.wasm?.status === "ok");
|
|
302
|
+
const servers = new Map();
|
|
303
|
+
function serverFor(exePath) {
|
|
304
|
+
let server = servers.get(exePath);
|
|
305
|
+
if (server == null) {
|
|
306
|
+
server = new PersistentExecutable(exePath);
|
|
307
|
+
servers.set(exePath, server);
|
|
308
|
+
}
|
|
309
|
+
return server;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
async run(iterations) {
|
|
314
|
+
if (wasmCases.length === 0) return null;
|
|
315
|
+
return runTimedIterationsAsync(iterations, async () => {
|
|
316
|
+
for (const tc of wasmCases) {
|
|
317
|
+
await serverFor(tc.wasm.exePath).request(tc.inputWire);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
},
|
|
321
|
+
serverFor,
|
|
322
|
+
close() {
|
|
323
|
+
for (const server of servers.values()) server.close();
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Setup ARM64
|
|
329
|
+
const arm64Exes = new Map();
|
|
330
|
+
if (runARM64) {
|
|
331
|
+
console.log(`==> Compiling Linux ARM64 executables (${arm64OptLevel})...`);
|
|
332
|
+
for (const tc of testSuite) {
|
|
333
|
+
const relation = relations[tc.op];
|
|
334
|
+
try {
|
|
335
|
+
const exePath = path.join(cacheDir, `perf_poly_arm64_${tc.op}`);
|
|
336
|
+
compileARM64ArtifactFromObject(relation.object, {
|
|
337
|
+
entry: relation.relationName,
|
|
338
|
+
inputPattern: tc.inputPattern,
|
|
339
|
+
outputPattern: tc.outputPattern,
|
|
340
|
+
outputPath: exePath,
|
|
341
|
+
optLevel: arm64OptLevel,
|
|
342
|
+
profile: runProfile
|
|
343
|
+
});
|
|
344
|
+
tc.arm64 = { status: "ok", exePath };
|
|
345
|
+
arm64Exes.set(tc.op, tc.arm64);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
tc.arm64 = {
|
|
348
|
+
status: "failed",
|
|
349
|
+
error: error.stack || error.message || String(error)
|
|
350
|
+
};
|
|
351
|
+
arm64Exes.set(tc.op, tc.arm64);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const failedOps = [...arm64Exes.entries()].filter(([_, v]) => v.status !== "ok");
|
|
355
|
+
if (failedOps.length > 0) {
|
|
356
|
+
console.log("Linux ARM64 compilation failures:");
|
|
357
|
+
for (const [op, res] of failedOps) {
|
|
358
|
+
console.log(` ${op}: ${res.error.split("\n")[0]}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function printBenchmarkDescription() {
|
|
364
|
+
const lanes = [];
|
|
365
|
+
if (runBaselines) {
|
|
366
|
+
lanes.push("Native JS (Envelope-Aware)");
|
|
367
|
+
lanes.push("Native JS (Envelope-Free)");
|
|
368
|
+
lanes.push("kVM Interpreter (Env-Free)");
|
|
369
|
+
}
|
|
370
|
+
if (runLLVM) lanes.push(llvmLaneName());
|
|
371
|
+
if (runWasm) lanes.push(wasmLaneName());
|
|
372
|
+
if (runARM64) lanes.push(arm64LaneName(arm64OptLevel));
|
|
373
|
+
|
|
374
|
+
console.log("==> Benchmark description");
|
|
375
|
+
console.log(" source: @fraczak/k/Examples/poly.k");
|
|
376
|
+
console.log(` benchmark relations: ${Object.values(benchNames).join(", ")}`);
|
|
377
|
+
console.log(` operations: ${ops.join(", ")}`);
|
|
378
|
+
console.log(` list length: ${listLength} elements`);
|
|
379
|
+
console.log(` cases: ${testSuite.length} polymorphic operations`);
|
|
380
|
+
if (runLLVM) {
|
|
381
|
+
console.log(` llvm-ready cases: ${testSuite.filter(tc => tc.llvm?.status === "ok").length}`);
|
|
382
|
+
console.log(` llvm warmup iterations: ${llvmWarmupIterations}`);
|
|
383
|
+
}
|
|
384
|
+
if (runWasm) {
|
|
385
|
+
console.log(` wasm runner mode: ${wasmPipe ? "persistent pipe" : "in-process"}`);
|
|
386
|
+
console.log(` wasm warmup iterations: ${wasmWarmupIterations}`);
|
|
387
|
+
if (!wasmPipe) console.log(` wasm arena reset: ${wasmReset ? "yes" : "no"}`);
|
|
388
|
+
}
|
|
389
|
+
if (runARM64) {
|
|
390
|
+
console.log(` arm64-ready cases: ${testSuite.filter(tc => tc.arm64?.status === "ok").length}`);
|
|
391
|
+
console.log(` arm64 warmup iterations: ${arm64WarmupIterations}`);
|
|
392
|
+
console.log(` arm64 opt level: ${arm64OptLevel}`);
|
|
393
|
+
console.log(` arm64 runner mode: ${process.env.ARM64_SPAWN_PER_CALL === "1" ? "spawn per call" : "persistent"}`);
|
|
394
|
+
}
|
|
395
|
+
console.log(` iterations: ${iterations}`);
|
|
396
|
+
console.log(` benchmark lanes: ${lanes.join("; ")}`);
|
|
397
|
+
console.log(" conformance: all outputs are compared to native expected values");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
printBenchmarkDescription();
|
|
401
|
+
console.log(`==> Running Polymorphic Performance Test (${testSuite.length} cases, Iterations: ${iterations})...`);
|
|
402
|
+
|
|
403
|
+
// 1. Common baseline lanes
|
|
404
|
+
let nativeAwareResult = null;
|
|
405
|
+
let nativeFreeResult = null;
|
|
406
|
+
let kvmFreeResult = null;
|
|
407
|
+
|
|
408
|
+
if (runBaselines) {
|
|
409
|
+
nativeAwareResult = runNativeAwareIterations(iterations, testSuite, relations, state, codes);
|
|
410
|
+
nativeFreeResult = runNativeFreeIterations(iterations, testSuite, relations, state, codes);
|
|
411
|
+
kvmFreeResult = runKVMIterations(iterations, testSuite, relations, state, codes);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 2. LLVM lane
|
|
415
|
+
let llvmResult = null;
|
|
416
|
+
if (runLLVM) {
|
|
417
|
+
const llvmRunner = createLLVMRunner(testSuite);
|
|
418
|
+
try {
|
|
419
|
+
if (llvmWarmupIterations > 0) {
|
|
420
|
+
console.log(`==> Warming LLVM executables (${llvmWarmupIterations} iterations)...`);
|
|
421
|
+
await llvmRunner.run(llvmWarmupIterations);
|
|
422
|
+
}
|
|
423
|
+
console.log(`==> Running LLVM (${iterations} iterations)...`);
|
|
424
|
+
llvmResult = await llvmRunner.run(iterations);
|
|
425
|
+
} finally {
|
|
426
|
+
llvmRunner.close();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 3. Wasm lane
|
|
431
|
+
let wasmResult = null;
|
|
432
|
+
if (runWasm) {
|
|
433
|
+
if (wasmPipe) {
|
|
434
|
+
const wasmCases = testSuite.filter(tc => tc.wasm?.status === "ok");
|
|
435
|
+
if (wasmCases.length === testSuite.length) {
|
|
436
|
+
const wasmRunner = createWasmRunner(testSuite);
|
|
437
|
+
try {
|
|
438
|
+
if (wasmWarmupIterations > 0) {
|
|
439
|
+
console.log(`==> Warming WebAssembly (${wasmWarmupIterations} iterations)...`);
|
|
440
|
+
await wasmRunner.run(wasmWarmupIterations);
|
|
441
|
+
}
|
|
442
|
+
console.log(`==> Running WebAssembly (${iterations} iterations)...`);
|
|
443
|
+
wasmResult = await wasmRunner.run(iterations);
|
|
444
|
+
} finally {
|
|
445
|
+
wasmRunner.close();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
if (wasmWarmupIterations > 0) {
|
|
450
|
+
console.log(`==> Warming WebAssembly (${wasmWarmupIterations} iterations)...`);
|
|
451
|
+
for (let i = 0; i < wasmWarmupIterations; i++) {
|
|
452
|
+
for (const tc of testSuite) {
|
|
453
|
+
const mark = wasmReset ? wasmExports.arena_mark() : 0;
|
|
454
|
+
const funcName = cleanName(state.relAliases[benchNames[tc.op]]);
|
|
455
|
+
const res = wasmExports[funcName](tc.wasmPtrIn);
|
|
456
|
+
assert.ok(res[1] === 1);
|
|
457
|
+
if (wasmReset) wasmExports.arena_reset(mark);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
console.log(`==> Running WebAssembly (${iterations} iterations)...`);
|
|
463
|
+
wasmResult = runTimedIterations(iterations, () => {
|
|
464
|
+
for (const tc of testSuite) {
|
|
465
|
+
const mark = wasmReset ? wasmExports.arena_mark() : 0;
|
|
466
|
+
const funcName = cleanName(state.relAliases[benchNames[tc.op]]);
|
|
467
|
+
const res = wasmExports[funcName](tc.wasmPtrIn);
|
|
468
|
+
assert.ok(res[1] === 1);
|
|
469
|
+
if (wasmReset) wasmExports.arena_reset(mark);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 4. Linux ARM64 lane
|
|
476
|
+
let arm64Result = null;
|
|
477
|
+
if (runARM64) {
|
|
478
|
+
const arm64Cases = testSuite.filter(tc => tc.arm64?.status === "ok");
|
|
479
|
+
if (arm64Cases.length === testSuite.length) {
|
|
480
|
+
const arm64Runner = createARM64Runner(testSuite);
|
|
481
|
+
try {
|
|
482
|
+
if (arm64WarmupIterations > 0) {
|
|
483
|
+
console.log(`==> Warming Linux ARM64 (${arm64WarmupIterations} iterations)...`);
|
|
484
|
+
await arm64Runner.run(arm64WarmupIterations);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
console.log(`==> Running Linux ARM64 (${iterations} iterations)...`);
|
|
488
|
+
arm64Result = await arm64Runner.run(iterations);
|
|
489
|
+
} finally {
|
|
490
|
+
arm64Runner.close();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
console.log("\n=================== POLYMORPHIC BENCHMARK RESULTS ===================");
|
|
496
|
+
console.log(`Polymorphic operations per iteration: ${testSuite.length}`);
|
|
497
|
+
console.log(`Total requested operation calls: ${iterations * testSuite.length}`);
|
|
498
|
+
console.log("---------------------------------------------------------------------");
|
|
499
|
+
let laneIndex = 1;
|
|
500
|
+
if (runBaselines) {
|
|
501
|
+
console.log(`${laneIndex++}. Native JS (Envelope-Aware): ${formatTiming(nativeAwareResult)}`);
|
|
502
|
+
console.log(`${laneIndex++}. Native JS (Envelope-Free): ${formatTiming(nativeFreeResult)}`);
|
|
503
|
+
console.log(`${laneIndex++}. kVM Interpreter (Env-Free): ${formatTiming(kvmFreeResult)}`);
|
|
504
|
+
}
|
|
505
|
+
if (runLLVM) {
|
|
506
|
+
console.log(`${laneIndex++}. ${llvmLaneName().padEnd(29)} ${formatTiming(llvmResult)}`);
|
|
507
|
+
}
|
|
508
|
+
if (runWasm) {
|
|
509
|
+
console.log(`${laneIndex++}. ${wasmLaneName().padEnd(29)} ${formatTiming(wasmResult)}`);
|
|
510
|
+
}
|
|
511
|
+
if (runARM64) {
|
|
512
|
+
const timingStr = arm64Result ? formatTiming(arm64Result) : "compile failed";
|
|
513
|
+
console.log(`${laneIndex++}. ${arm64LaneName(arm64OptLevel).padEnd(29)} ${timingStr}`);
|
|
514
|
+
}
|
|
515
|
+
console.log("=====================================================================\n");
|
|
516
|
+
|
|
517
|
+
// Conformance Validation
|
|
518
|
+
for (const tc of testSuite) {
|
|
519
|
+
if (runBaselines) {
|
|
520
|
+
const kvmActual = runKVMCase(tc, relations, state, codes);
|
|
521
|
+
assert.ok(safeDeepEqual(kvmActual, tc.expected), `kVM output mismatch for ${tc.op}`);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (runLLVM) {
|
|
525
|
+
if (tc.llvm.status !== "ok") {
|
|
526
|
+
tc.llvmConformance = "compile-failed";
|
|
527
|
+
} else {
|
|
528
|
+
try {
|
|
529
|
+
const outputWire = await runExecutable(tc.llvm.exePath, tc.inputWire);
|
|
530
|
+
const actual = decodeWire(outputWire).value;
|
|
531
|
+
assert.ok(safeDeepEqual(actual, tc.expected), `LLVM output mismatch for ${tc.op}`);
|
|
532
|
+
tc.llvmConformance = "ok";
|
|
533
|
+
} catch (error) {
|
|
534
|
+
tc.llvmConformance = "failed";
|
|
535
|
+
tc.llvm.error = error.stack || error.message || String(error);
|
|
536
|
+
console.log(`LLVM conformance failure for ${tc.op}:`, tc.llvm.error.split("\n")[0]);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (runWasm) {
|
|
542
|
+
if (wasmPipe) {
|
|
543
|
+
if (tc.wasm?.status !== "ok") {
|
|
544
|
+
tc.wasmConformance = "compile-failed";
|
|
545
|
+
} else {
|
|
546
|
+
try {
|
|
547
|
+
const outputWire = await runExecutable(tc.wasm.exePath, tc.inputWire);
|
|
548
|
+
const actual = decodeWire(outputWire).value;
|
|
549
|
+
assert.ok(safeDeepEqual(actual, tc.expected), `Wasm output mismatch for ${tc.op}`);
|
|
550
|
+
tc.wasmConformance = "ok";
|
|
551
|
+
} catch (error) {
|
|
552
|
+
tc.wasmConformance = "failed";
|
|
553
|
+
tc.wasm.error = error.stack || error.message || String(error);
|
|
554
|
+
console.log(`Wasm conformance failure for ${tc.op}:`, tc.wasm.error.split("\n")[0]);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
try {
|
|
559
|
+
const mark = wasmReset ? wasmExports.arena_mark() : 0;
|
|
560
|
+
const funcName = cleanName(state.relAliases[benchNames[tc.op]]);
|
|
561
|
+
const res = wasmExports[funcName](tc.wasmPtrIn);
|
|
562
|
+
assert.ok(res[1] === 1, `Wasm function ${funcName} failed`);
|
|
563
|
+
const actual = readArenaValue(
|
|
564
|
+
wasmExports,
|
|
565
|
+
res[0],
|
|
566
|
+
propertyListToPattern(tc.outputPattern),
|
|
567
|
+
0,
|
|
568
|
+
tc.outputPattern
|
|
569
|
+
);
|
|
570
|
+
assert.ok(safeDeepEqual(actual, tc.expected), `Wasm output mismatch for ${tc.op}`);
|
|
571
|
+
if (wasmReset) wasmExports.arena_reset(mark);
|
|
572
|
+
tc.wasmConformance = "ok";
|
|
573
|
+
} catch (error) {
|
|
574
|
+
tc.wasmConformance = "failed";
|
|
575
|
+
console.log(`Wasm conformance failure for ${tc.op}:`, error);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (runARM64) {
|
|
581
|
+
if (tc.arm64?.status !== "ok") {
|
|
582
|
+
tc.arm64Conformance = "compile-failed";
|
|
583
|
+
} else {
|
|
584
|
+
try {
|
|
585
|
+
const outputWire = await runExecutable(tc.arm64.exePath, tc.inputWire);
|
|
586
|
+
const actual = decodeWire(outputWire).value;
|
|
587
|
+
assert.ok(safeDeepEqual(actual, tc.expected), `ARM64 output mismatch for ${tc.op}`);
|
|
588
|
+
tc.arm64Conformance = "ok";
|
|
589
|
+
} catch (error) {
|
|
590
|
+
tc.arm64Conformance = "failed";
|
|
591
|
+
tc.arm64.error = error.stack || error.message || String(error);
|
|
592
|
+
console.log(`Linux ARM64 conformance failure for ${tc.op}:`, tc.arm64.error.split("\n")[0]);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const validationSummary = [];
|
|
599
|
+
if (runBaselines) validationSummary.push("kVM");
|
|
600
|
+
if (runLLVM) {
|
|
601
|
+
const llvmOk = testSuite.filter(tc => tc.llvmConformance === "ok").length;
|
|
602
|
+
validationSummary.push(`LLVM ${llvmOk}/${testSuite.length}`);
|
|
603
|
+
}
|
|
604
|
+
if (runWasm) {
|
|
605
|
+
const wasmOk = testSuite.filter(tc => tc.wasmConformance === "ok").length;
|
|
606
|
+
validationSummary.push(`Wasm ${wasmOk}/${testSuite.length}`);
|
|
607
|
+
}
|
|
608
|
+
if (runARM64) {
|
|
609
|
+
const arm64Ok = testSuite.filter(tc => tc.arm64Conformance === "ok").length;
|
|
610
|
+
validationSummary.push(`Linux ARM64 ${arm64Ok}/${testSuite.length}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
console.log(`Conformance validation: ${validationSummary.join(", ")} cases match expected values.`);
|
|
614
|
+
if (runLLVM && shouldStrictFail(testSuite)) process.exitCode = 1;
|
|
615
|
+
if (runWasm && testSuite.some(tc => tc.wasmConformance !== "ok")) process.exitCode = 1;
|
|
616
|
+
if (runARM64 && testSuite.some(tc => tc.arm64Conformance !== "ok")) process.exitCode = 1;
|
|
617
|
+
|
|
618
|
+
if (runTrace && process.exitCode !== 1) {
|
|
619
|
+
console.log("\n=================== RUNNING MACRO-PHASE TRACING ===================");
|
|
620
|
+
const sampleReps = parsePositiveIntEnv("TRACE_SAMPLES", 10);
|
|
621
|
+
console.log(`Collecting phase timings (averaged over 3 warmup + ${sampleReps} sample calls)...`);
|
|
622
|
+
|
|
623
|
+
const llvmServers = new Map();
|
|
624
|
+
if (runLLVM) {
|
|
625
|
+
for (const tc of testSuite) {
|
|
626
|
+
if (tc.llvm?.status === "ok") {
|
|
627
|
+
llvmServers.set(tc.op, new PersistentExecutable(tc.llvm.exePath));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const wasmServers = new Map();
|
|
633
|
+
if (runWasm && wasmPipe) {
|
|
634
|
+
for (const tc of testSuite) {
|
|
635
|
+
if (tc.wasm?.status === "ok") {
|
|
636
|
+
wasmServers.set(tc.op, new PersistentExecutable(tc.wasm.exePath));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const arm64Servers = new Map();
|
|
642
|
+
if (runARM64) {
|
|
643
|
+
for (const tc of testSuite) {
|
|
644
|
+
if (tc.arm64?.status === "ok") {
|
|
645
|
+
arm64Servers.set(tc.op, new PersistentExecutable(tc.arm64.exePath));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const averageTrace = (samples) => {
|
|
651
|
+
if (!samples || samples.length === 0) return null;
|
|
652
|
+
const avg = {};
|
|
653
|
+
const keys = ["ipcReadNs", "decodeNs", "flatInNs", "evalNs", "flatOutNs", "encodeNs", "ipcWriteNs", "totalNs"];
|
|
654
|
+
for (const key of keys) {
|
|
655
|
+
avg[key] = samples.reduce((sum, s) => sum + (s[key] || 0), 0) / samples.length;
|
|
656
|
+
}
|
|
657
|
+
return avg;
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const kvmHashToName = new Map();
|
|
661
|
+
for (const [alias, h] of Object.entries(state.relAliases)) {
|
|
662
|
+
const current = kvmHashToName.get(h);
|
|
663
|
+
if (!current) {
|
|
664
|
+
kvmHashToName.set(h, alias);
|
|
665
|
+
} else {
|
|
666
|
+
const currentIsBench = current.startsWith("bench_");
|
|
667
|
+
const aliasIsBench = alias.startsWith("bench_");
|
|
668
|
+
if (currentIsBench && !aliasIsBench) {
|
|
669
|
+
kvmHashToName.set(h, alias);
|
|
670
|
+
} else if (currentIsBench === aliasIsBench && alias.length < current.length) {
|
|
671
|
+
kvmHashToName.set(h, alias);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const phaseResults = [];
|
|
677
|
+
|
|
678
|
+
for (const tc of testSuite) {
|
|
679
|
+
const op = tc.op;
|
|
680
|
+
const opData = { op, label: tc.label };
|
|
681
|
+
|
|
682
|
+
// 1. ARM64
|
|
683
|
+
if (arm64Servers.has(op)) {
|
|
684
|
+
const server = arm64Servers.get(op);
|
|
685
|
+
for (let i = 0; i < 3; i++) await server.requestWithTrace(tc.inputWire);
|
|
686
|
+
const samples = [];
|
|
687
|
+
let arm64Profile = null;
|
|
688
|
+
for (let i = 0; i < sampleReps; i++) {
|
|
689
|
+
const res = await server.requestWithTrace(tc.inputWire);
|
|
690
|
+
if (res.trace) {
|
|
691
|
+
samples.push(res.trace);
|
|
692
|
+
if (res.trace.profile) arm64Profile = res.trace.profile;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
opData.arm64 = averageTrace(samples);
|
|
696
|
+
if (arm64Profile) {
|
|
697
|
+
if (arm64Profile.__main__ !== undefined) {
|
|
698
|
+
if (arm64Profile[op] !== undefined) {
|
|
699
|
+
arm64Profile[benchNames[op]] = arm64Profile.__main__;
|
|
700
|
+
} else {
|
|
701
|
+
arm64Profile[op] = arm64Profile.__main__;
|
|
702
|
+
}
|
|
703
|
+
delete arm64Profile.__main__;
|
|
704
|
+
}
|
|
705
|
+
opData.arm64Profile = arm64Profile;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// 2. LLVM
|
|
710
|
+
if (llvmServers.has(op)) {
|
|
711
|
+
const server = llvmServers.get(op);
|
|
712
|
+
for (let i = 0; i < 3; i++) await server.requestWithTrace(tc.inputWire);
|
|
713
|
+
const samples = [];
|
|
714
|
+
for (let i = 0; i < sampleReps; i++) {
|
|
715
|
+
const res = await server.requestWithTrace(tc.inputWire);
|
|
716
|
+
if (res.trace) samples.push(res.trace);
|
|
717
|
+
}
|
|
718
|
+
opData.llvm = averageTrace(samples);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// 3. Wasm
|
|
722
|
+
if (runWasm) {
|
|
723
|
+
if (wasmPipe) {
|
|
724
|
+
if (wasmServers.has(op)) {
|
|
725
|
+
const server = wasmServers.get(op);
|
|
726
|
+
for (let i = 0; i < 3; i++) await server.requestWithTrace(tc.inputWire);
|
|
727
|
+
const samples = [];
|
|
728
|
+
for (let i = 0; i < sampleReps; i++) {
|
|
729
|
+
const res = await server.requestWithTrace(tc.inputWire);
|
|
730
|
+
if (res.trace) samples.push(res.trace);
|
|
731
|
+
}
|
|
732
|
+
opData.wasm = averageTrace(samples);
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
const funcName = cleanName(state.relAliases[benchNames[op]]);
|
|
736
|
+
const samples = [];
|
|
737
|
+
for (let i = 0; i < sampleReps + 3; i++) {
|
|
738
|
+
const t0 = process.hrtime.bigint();
|
|
739
|
+
const wasmPtrIn = writeValueToArena(wasmExports, tc.inputVal, propertyListToPattern(tc.inputPattern), 0);
|
|
740
|
+
const t1 = process.hrtime.bigint();
|
|
741
|
+
const mark = wasmReset ? wasmExports.arena_mark() : 0;
|
|
742
|
+
const res = wasmExports[funcName](wasmPtrIn);
|
|
743
|
+
const t2 = process.hrtime.bigint();
|
|
744
|
+
const actual = readArenaValue(wasmExports, res[0], propertyListToPattern(tc.outputPattern), 0, tc.outputPattern);
|
|
745
|
+
const t3 = process.hrtime.bigint();
|
|
746
|
+
if (wasmReset) wasmExports.arena_reset(mark);
|
|
747
|
+
const t4 = process.hrtime.bigint();
|
|
748
|
+
|
|
749
|
+
if (i >= 3) {
|
|
750
|
+
samples.push({
|
|
751
|
+
ipcReadNs: 0,
|
|
752
|
+
decodeNs: 0,
|
|
753
|
+
flatInNs: Number(t1 - t0),
|
|
754
|
+
evalNs: Number(t2 - t1),
|
|
755
|
+
flatOutNs: Number(t3 - t2),
|
|
756
|
+
encodeNs: 0,
|
|
757
|
+
ipcWriteNs: Number(t4 - t3),
|
|
758
|
+
totalNs: Number(t4 - t0)
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
opData.wasm = averageTrace(samples);
|
|
763
|
+
|
|
764
|
+
if (runProfile && wasmProfileFunctions.length > 0) {
|
|
765
|
+
resetWasmProfile(wasmExports, wasmProfileFunctions);
|
|
766
|
+
const mark = wasmReset ? wasmExports.arena_mark() : 0;
|
|
767
|
+
wasmExports[funcName](tc.wasmPtrIn);
|
|
768
|
+
const wProf = readWasmProfile(wasmExports, wasmProfileFunctions);
|
|
769
|
+
if (wasmReset) wasmExports.arena_reset(mark);
|
|
770
|
+
if (wProf) {
|
|
771
|
+
if (wProf[benchNames[op]] !== undefined && wProf[op] === undefined) {
|
|
772
|
+
wProf[op] = wProf[benchNames[op]];
|
|
773
|
+
delete wProf[benchNames[op]];
|
|
774
|
+
}
|
|
775
|
+
opData.wasmProfile = wProf;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// 4. kVM
|
|
782
|
+
if (runBaselines) {
|
|
783
|
+
const relDef = relations[op].relDef;
|
|
784
|
+
const samples = [];
|
|
785
|
+
run_converged.defs = state;
|
|
786
|
+
for (let i = 0; i < sampleReps + 2; i++) {
|
|
787
|
+
const t0 = process.hrtime.bigint();
|
|
788
|
+
const expected = run_converged(codes.find, relDef.def, tc.inputVal, relDef.typePatternGraph);
|
|
789
|
+
const t1 = process.hrtime.bigint();
|
|
790
|
+
if (i >= 2) {
|
|
791
|
+
samples.push({
|
|
792
|
+
ipcReadNs: 0,
|
|
793
|
+
decodeNs: 0,
|
|
794
|
+
flatInNs: 0,
|
|
795
|
+
evalNs: Number(t1 - t0),
|
|
796
|
+
flatOutNs: 0,
|
|
797
|
+
encodeNs: 0,
|
|
798
|
+
ipcWriteNs: 0,
|
|
799
|
+
totalNs: Number(t1 - t0)
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
opData.kvm = averageTrace(samples);
|
|
804
|
+
|
|
805
|
+
if (runProfile) {
|
|
806
|
+
const kProf = {};
|
|
807
|
+
executeKVM(relations[op].kvmFunc, tc.inputVal, {
|
|
808
|
+
rels: state.rels,
|
|
809
|
+
codes: state.codes,
|
|
810
|
+
findCode: codes.find,
|
|
811
|
+
profile: kProf,
|
|
812
|
+
hashToName: kvmHashToName,
|
|
813
|
+
options: { envelopeFree: true }
|
|
814
|
+
});
|
|
815
|
+
if (kProf[benchNames[op]] !== undefined && kProf[op] === undefined) {
|
|
816
|
+
kProf[op] = kProf[benchNames[op]];
|
|
817
|
+
delete kProf[benchNames[op]];
|
|
818
|
+
}
|
|
819
|
+
opData.kvmProfile = kProf;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
phaseResults.push(opData);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
for (const s of llvmServers.values()) s.close();
|
|
827
|
+
for (const s of arm64Servers.values()) s.close();
|
|
828
|
+
for (const s of wasmServers.values()) s.close();
|
|
829
|
+
|
|
830
|
+
function formatUs(ns) {
|
|
831
|
+
if (ns == null || ns === 0) return " - ";
|
|
832
|
+
const us = ns / 1000;
|
|
833
|
+
return `${us.toFixed(2)} µs`.padStart(12);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const phases = [
|
|
837
|
+
{ name: "1. IPC Read (Pipe In)", key: "ipcReadNs" },
|
|
838
|
+
{ name: "2. Wire Decode & Validate", key: "decodeNs" },
|
|
839
|
+
{ name: "3. Flat Input Prep (Arena)", key: "flatInNs" },
|
|
840
|
+
{ name: "4. Pure Evaluation", key: "evalNs" },
|
|
841
|
+
{ name: "5. Flat Output Prep (Arena)", key: "flatOutNs" },
|
|
842
|
+
{ name: "6. Wire Encode", key: "encodeNs" },
|
|
843
|
+
{ name: "7. IPC Write (Pipe Out)", key: "ipcWriteNs" },
|
|
844
|
+
{ name: "Total Request Time", key: "totalNs", isTotal: true }
|
|
845
|
+
];
|
|
846
|
+
|
|
847
|
+
for (const item of phaseResults) {
|
|
848
|
+
console.log(`\n--- Operation: ${item.op} (${item.label}) ---`);
|
|
849
|
+
console.log("Phase | Linux ARM64 | LLVM | WebAssembly | kVM Interp");
|
|
850
|
+
console.log("-------------------------+---------------+---------------+---------------+---------------");
|
|
851
|
+
for (const phase of phases) {
|
|
852
|
+
if (phase.isTotal) {
|
|
853
|
+
console.log("-------------------------+---------------+---------------+---------------+---------------");
|
|
854
|
+
}
|
|
855
|
+
const pName = phase.name.padEnd(24);
|
|
856
|
+
const arm64Col = formatUs(item.arm64?.[phase.key]);
|
|
857
|
+
const llvmCol = formatUs(item.llvm?.[phase.key]);
|
|
858
|
+
const wasmCol = formatUs(item.wasm?.[phase.key]);
|
|
859
|
+
const kvmCol = formatUs(item.kvm?.[phase.key]);
|
|
860
|
+
console.log(`${pName} | ${arm64Col} | ${llvmCol} | ${wasmCol} | ${kvmCol}`);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const hasProfile = item.arm64Profile || item.wasmProfile || item.kvmProfile;
|
|
864
|
+
if (hasProfile) {
|
|
865
|
+
const allFuncs = new Set([
|
|
866
|
+
...Object.keys(item.arm64Profile || {}),
|
|
867
|
+
...Object.keys(item.wasmProfile || {}),
|
|
868
|
+
...Object.keys(item.kvmProfile || {})
|
|
869
|
+
]);
|
|
870
|
+
|
|
871
|
+
const sortedFuncs = Array.from(allFuncs).sort((a, b) => {
|
|
872
|
+
const bName = benchNames[item.op];
|
|
873
|
+
if (a === bName) return -1;
|
|
874
|
+
if (b === bName) return 1;
|
|
875
|
+
if (a === item.op) return -1;
|
|
876
|
+
if (b === item.op) return 1;
|
|
877
|
+
return a.localeCompare(b);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
console.log(`\n Function Call Profile: ${item.op}`);
|
|
881
|
+
console.log(" Function Name | Linux ARM64 Calls | WebAssembly Calls | kVM Interp Calls");
|
|
882
|
+
console.log(" -------------------------+-------------------+-------------------+------------------");
|
|
883
|
+
let arm64Total = 0, wasmTotal = 0, kvmTotal = 0;
|
|
884
|
+
for (const fn of sortedFuncs) {
|
|
885
|
+
const aCount = item.arm64Profile?.[fn] ?? 0;
|
|
886
|
+
const wCount = item.wasmProfile?.[fn] ?? 0;
|
|
887
|
+
const kCount = item.kvmProfile?.[fn] ?? 0;
|
|
888
|
+
arm64Total += aCount;
|
|
889
|
+
wasmTotal += wCount;
|
|
890
|
+
kvmTotal += kCount;
|
|
891
|
+
const fnCol = fn.padEnd(24);
|
|
892
|
+
const aStr = (aCount > 0 ? String(aCount) : "-").padStart(17);
|
|
893
|
+
const wStr = (wCount > 0 ? String(wCount) : "-").padStart(17);
|
|
894
|
+
const kStr = (kCount > 0 ? String(kCount) : "-").padStart(16);
|
|
895
|
+
console.log(` ${fnCol} | ${aStr} | ${wStr} | ${kStr}`);
|
|
896
|
+
}
|
|
897
|
+
console.log(" -------------------------+-------------------+-------------------+------------------");
|
|
898
|
+
const totCol = "Total Calls".padEnd(24);
|
|
899
|
+
const aTotStr = String(arm64Total).padStart(17);
|
|
900
|
+
const wTotStr = String(wasmTotal).padStart(17);
|
|
901
|
+
const kTotStr = String(kvmTotal).padStart(16);
|
|
902
|
+
console.log(` ${totCol} | ${aTotStr} | ${wTotStr} | ${kTotStr}`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
console.log(`\n=== AVERAGE ACROSS ALL ${phaseResults.length} OPERATIONS ===`);
|
|
907
|
+
console.log("Phase | Linux ARM64 | LLVM | WebAssembly | kVM Interp");
|
|
908
|
+
console.log("-------------------------+---------------+---------------+---------------+---------------");
|
|
909
|
+
for (const phase of phases) {
|
|
910
|
+
if (phase.isTotal) {
|
|
911
|
+
console.log("-------------------------+---------------+---------------+---------------+---------------");
|
|
912
|
+
}
|
|
913
|
+
const pName = phase.name.padEnd(24);
|
|
914
|
+
const avg = (backend) => {
|
|
915
|
+
const vals = phaseResults.map(r => r[backend]?.[phase.key]).filter(v => v != null && v > 0);
|
|
916
|
+
if (vals.length === 0) return null;
|
|
917
|
+
return vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
918
|
+
};
|
|
919
|
+
const arm64Col = formatUs(avg("arm64"));
|
|
920
|
+
const llvmCol = formatUs(avg("llvm"));
|
|
921
|
+
const wasmCol = formatUs(avg("wasm"));
|
|
922
|
+
const kvmCol = formatUs(avg("kvm"));
|
|
923
|
+
console.log(`${pName} | ${arm64Col} | ${llvmCol} | ${wasmCol} | ${kvmCol}`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (phaseResults.some(r => r.arm64Profile || r.wasmProfile || r.kvmProfile)) {
|
|
927
|
+
console.log("\n=== FUNCTION CALL COUNT SUMMARY ACROSS ALL OPERATIONS ===");
|
|
928
|
+
console.log("Operation (Case) | Linux ARM64 Calls | WebAssembly Calls | kVM Interp Calls");
|
|
929
|
+
console.log("-------------------------+-------------------+-------------------+------------------");
|
|
930
|
+
let arm64Grand = 0, wasmGrand = 0, kvmGrand = 0;
|
|
931
|
+
for (const r of phaseResults) {
|
|
932
|
+
const aSum = Object.values(r.arm64Profile || {}).reduce((a, b) => a + b, 0);
|
|
933
|
+
const wSum = Object.values(r.wasmProfile || {}).reduce((a, b) => a + b, 0);
|
|
934
|
+
const kSum = Object.values(r.kvmProfile || {}).reduce((a, b) => a + b, 0);
|
|
935
|
+
arm64Grand += aSum;
|
|
936
|
+
wasmGrand += wSum;
|
|
937
|
+
kvmGrand += kSum;
|
|
938
|
+
const opCol = `${r.op} (${r.label})`.slice(0, 24).padEnd(24);
|
|
939
|
+
console.log(`${opCol} | ${String(aSum).padStart(17)} | ${String(wSum).padStart(17)} | ${String(kSum).padStart(16)}`);
|
|
940
|
+
}
|
|
941
|
+
console.log("-------------------------+-------------------+-------------------+------------------");
|
|
942
|
+
console.log(`${"Total Calls".padEnd(24)} | ${String(arm64Grand).padStart(17)} | ${String(wasmGrand).padStart(17)} | ${String(kvmGrand).padStart(16)}`);
|
|
943
|
+
}
|
|
944
|
+
console.log("=====================================================================\n");
|
|
945
|
+
}
|