@fraczak/k 6.3.3 → 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 +61 -4
- 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/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 +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,319 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import wabtFactory from "wabt";
|
|
4
|
+
import {
|
|
5
|
+
codes,
|
|
6
|
+
createState,
|
|
7
|
+
evaluateInput,
|
|
8
|
+
executeKVM,
|
|
9
|
+
exportPatternGraph,
|
|
10
|
+
fromObject,
|
|
11
|
+
isProduct,
|
|
12
|
+
isVariant,
|
|
13
|
+
lowerToKVM,
|
|
14
|
+
parseFloat64,
|
|
15
|
+
patternToPropertyList,
|
|
16
|
+
printFloat64,
|
|
17
|
+
propertyListToPattern,
|
|
18
|
+
valueForCode,
|
|
19
|
+
Value
|
|
20
|
+
} from "@fraczak/k/backend-api.mjs";
|
|
21
|
+
import { lowerToWasm, getTagId, getTagFromId, getFuncNameFromId } from "./src/kvm2wasm.mjs";
|
|
22
|
+
|
|
23
|
+
console.log("==> Starting Wasm IEEE-754 Square Demo");
|
|
24
|
+
|
|
25
|
+
// Initialize WABT
|
|
26
|
+
const wabtInstance = await wabtFactory();
|
|
27
|
+
|
|
28
|
+
function compileWat(filename, watText) {
|
|
29
|
+
const watModule = wabtInstance.parseWat(filename, watText, {
|
|
30
|
+
mutable_globals: true,
|
|
31
|
+
sat_float_to_int: true,
|
|
32
|
+
sign_extension: true,
|
|
33
|
+
multi_value: true,
|
|
34
|
+
bulk_memory: true,
|
|
35
|
+
reference_types: true
|
|
36
|
+
});
|
|
37
|
+
watModule.resolveNames();
|
|
38
|
+
watModule.validate();
|
|
39
|
+
const binary = watModule.toBinary({
|
|
40
|
+
log: false,
|
|
41
|
+
canonicalize_lebs: true,
|
|
42
|
+
relocatable: false,
|
|
43
|
+
write_debug_names: true
|
|
44
|
+
});
|
|
45
|
+
return binary.buffer;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 1. Create REPL state and load ieee.k library
|
|
49
|
+
console.log("Loading IEEE-754 arithmetic library (@fraczak/k/Examples/ieee.k)...");
|
|
50
|
+
const state = createState();
|
|
51
|
+
const ieeePath = fileURLToPath(import.meta.resolve("@fraczak/k/Examples/ieee.k"));
|
|
52
|
+
await evaluateInput(`:load ${ieeePath}`, state);
|
|
53
|
+
|
|
54
|
+
// 2. Define the squaring relation: square = { () x, () y } mul;
|
|
55
|
+
console.log("Defining relation: square = { () x, () y } mul;");
|
|
56
|
+
await evaluateInput("square = { () x, () y } mul;", state);
|
|
57
|
+
await evaluateInput("get_sign = $float64 .sign;", state);
|
|
58
|
+
|
|
59
|
+
// 3. Compile and Link relations recursively starting from square
|
|
60
|
+
console.log("Compiling and linking Wasm module...");
|
|
61
|
+
|
|
62
|
+
const cleanName = (h) => "rel_" + h.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
63
|
+
|
|
64
|
+
function compileModule(mainRelHash) {
|
|
65
|
+
const compiled = new Set();
|
|
66
|
+
const queue = [mainRelHash];
|
|
67
|
+
const wats = [];
|
|
68
|
+
|
|
69
|
+
while (queue.length > 0) {
|
|
70
|
+
const hash = queue.shift();
|
|
71
|
+
if (compiled.has(hash)) continue;
|
|
72
|
+
compiled.add(hash);
|
|
73
|
+
|
|
74
|
+
const relDef = state.rels[hash];
|
|
75
|
+
if (!relDef) {
|
|
76
|
+
throw new Error(`Relation hash ${hash} not found`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const kvmFunc = lowerToKVM(relDef, hash);
|
|
80
|
+
kvmFunc.typePatternGraph = relDef.typePatternGraph;
|
|
81
|
+
|
|
82
|
+
// Scan for call dependencies using raw hashes
|
|
83
|
+
for (const inst of kvmFunc.body) {
|
|
84
|
+
if (inst.op === "call") {
|
|
85
|
+
if (!compiled.has(inst.func) && !queue.includes(inst.func)) {
|
|
86
|
+
queue.push(inst.func);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (inst.op === "call_intrinsic") {
|
|
90
|
+
console.log(`FOUND INTRINSIC: ${inst.symbol} in relation ${hash}`);
|
|
91
|
+
}
|
|
92
|
+
if (inst.branches) {
|
|
93
|
+
const scan = (insts) => {
|
|
94
|
+
for (const sub of insts) {
|
|
95
|
+
if (sub.op === "call") {
|
|
96
|
+
if (!compiled.has(sub.func) && !queue.includes(sub.func)) {
|
|
97
|
+
queue.push(sub.func);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (sub.op === "call_intrinsic") {
|
|
101
|
+
console.log(`FOUND INTRINSIC: ${sub.symbol} in relation ${hash}`);
|
|
102
|
+
}
|
|
103
|
+
if (sub.branches) {
|
|
104
|
+
for (const br of sub.branches) {
|
|
105
|
+
scan(br.body);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
for (const br of inst.branches) {
|
|
111
|
+
scan(br.body);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Rename functions in KVM IR to valid Wasm identifiers
|
|
117
|
+
kvmFunc.name = cleanName(hash);
|
|
118
|
+
const cleanInsts = (insts) => {
|
|
119
|
+
for (const inst of insts) {
|
|
120
|
+
if (inst.op === "call") {
|
|
121
|
+
inst.func = cleanName(inst.func);
|
|
122
|
+
}
|
|
123
|
+
if (inst.branches) {
|
|
124
|
+
for (const br of inst.branches) {
|
|
125
|
+
cleanInsts(br.body);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
cleanInsts(kvmFunc.body);
|
|
131
|
+
|
|
132
|
+
const wat = lowerToWasm(kvmFunc, kvmFunc.name, { trace: true });
|
|
133
|
+
if (hash === squareHash) {
|
|
134
|
+
console.log("==> KVM IR for square:", JSON.stringify(kvmFunc.body, null, 2));
|
|
135
|
+
console.log("==> Compiled WAT for square:\n", wat);
|
|
136
|
+
}
|
|
137
|
+
wats.push(wat);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return wats.join("\n\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const squareHash = state.relAliases["square"];
|
|
144
|
+
const getSignHash = state.relAliases["get_sign"];
|
|
145
|
+
const moduleWatBody = compileModule(squareHash);
|
|
146
|
+
const getSignWat = lowerToWasm(state.rels[getSignHash], "get_sign", { trace: true });
|
|
147
|
+
console.log("==> Compiled WAT for get_sign:\n", getSignWat);
|
|
148
|
+
const runtimeWat = fs.readFileSync("runtime.wat", "utf8");
|
|
149
|
+
|
|
150
|
+
// Combine Wasm runtime and compiled relations
|
|
151
|
+
const declarations = `
|
|
152
|
+
(import "env" "log_call" (func $log_call (param i32 i32)))
|
|
153
|
+
(import "env" "log_ret" (func $log_ret (param i32 i32 i32)))
|
|
154
|
+
`;
|
|
155
|
+
const runtimeWatWithImports = runtimeWat.replace("(module", "(module\n" + declarations);
|
|
156
|
+
const fullWat = runtimeWatWithImports.trim().slice(0, -1) + "\n" + moduleWatBody + "\n" + getSignWat + "\n)";
|
|
157
|
+
fs.writeFileSync("debug_full.wat", fullWat);
|
|
158
|
+
const binary = compileWat("square_demo.wat", fullWat);
|
|
159
|
+
const module = await WebAssembly.compile(binary);
|
|
160
|
+
const imports = {
|
|
161
|
+
env: {
|
|
162
|
+
log_call: (funcId, ptrIn) => {
|
|
163
|
+
const funcName = getFuncNameFromId(funcId);
|
|
164
|
+
console.log(`[Wasm Trace] Calling ${funcName} with input ptr ${ptrIn}`);
|
|
165
|
+
},
|
|
166
|
+
log_ret: (funcId, ptrOut, ok) => {
|
|
167
|
+
const funcName = getFuncNameFromId(funcId);
|
|
168
|
+
console.log(`[Wasm Trace] Returned from ${funcName} -> ptr ${ptrOut}, ok ${ok}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const instance = await WebAssembly.instantiate(module, imports);
|
|
173
|
+
const exports = instance.exports;
|
|
174
|
+
|
|
175
|
+
// JS Arena helpers
|
|
176
|
+
function readArenaValue(exports, ptr, pattern, patternNodeId) {
|
|
177
|
+
const patternNode = pattern.nodes[patternNodeId];
|
|
178
|
+
const view = new DataView(exports.memory.buffer);
|
|
179
|
+
|
|
180
|
+
if (patternNode.kind === 1 || patternNode.kind === 3) {
|
|
181
|
+
const size = view.getUint32(ptr, true);
|
|
182
|
+
const N = view.getUint32(ptr + 4, true);
|
|
183
|
+
const productObj = {};
|
|
184
|
+
|
|
185
|
+
for (let i = 0; i < N; i++) {
|
|
186
|
+
const edge = patternNode.edges[i];
|
|
187
|
+
const offsetVal = view.getUint32(ptr + 8 + 4 * i, true);
|
|
188
|
+
const childPtr = view.getUint32(ptr + offsetVal, true);
|
|
189
|
+
productObj[edge.label] = readArenaValue(exports, childPtr, pattern, edge.target);
|
|
190
|
+
}
|
|
191
|
+
return Value.product(productObj, pattern);
|
|
192
|
+
} else if (patternNode.kind === 2 || patternNode.kind === 4) {
|
|
193
|
+
const size = view.getUint32(ptr, true);
|
|
194
|
+
const tagId = view.getUint32(ptr + 4, true);
|
|
195
|
+
const payloadPtr = view.getUint32(ptr + 8, true);
|
|
196
|
+
|
|
197
|
+
const tag = getTagFromId(tagId);
|
|
198
|
+
console.log(`[Debug Arena] Read Variant at ptr=${ptr}: size=${size}, tagId=${tagId}, tag=${tag}`);
|
|
199
|
+
const edge = patternNode.edges.find(e => e.label === tag);
|
|
200
|
+
if (!edge) {
|
|
201
|
+
throw new Error(`Variant tag '${tag}' not found in pattern edges`);
|
|
202
|
+
}
|
|
203
|
+
const payloadVal = readArenaValue(exports, payloadPtr, pattern, edge.target);
|
|
204
|
+
return Value.variant(tag, payloadVal, pattern);
|
|
205
|
+
}
|
|
206
|
+
throw new Error(`Unsupported pattern kind: ${patternNode.kind}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function writeValueToArena(value, pattern, patternNodeId) {
|
|
210
|
+
const patternNode = pattern.nodes[patternNodeId];
|
|
211
|
+
|
|
212
|
+
if (isProduct(value)) {
|
|
213
|
+
const keys = Object.keys(value.product).sort();
|
|
214
|
+
const N = keys.length;
|
|
215
|
+
const totalSize = 8 + 8 * N;
|
|
216
|
+
|
|
217
|
+
// Evaluate and allocate all children first
|
|
218
|
+
const childPtrs = [];
|
|
219
|
+
for (let i = 0; i < N; i++) {
|
|
220
|
+
const label = keys[i];
|
|
221
|
+
const edge = patternNode.edges.find(e => e.label === label);
|
|
222
|
+
const childPtr = writeValueToArena(value.product[label], pattern, edge.target);
|
|
223
|
+
childPtrs.push(childPtr);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const ptr = exports.alloc(totalSize);
|
|
227
|
+
const view = new DataView(exports.memory.buffer);
|
|
228
|
+
|
|
229
|
+
view.setUint32(ptr, totalSize, true);
|
|
230
|
+
view.setUint32(ptr + 4, N, true);
|
|
231
|
+
|
|
232
|
+
for (let i = 0; i < N; i++) {
|
|
233
|
+
const offsetVal = 8 + 4 * N + 4 * i;
|
|
234
|
+
view.setUint32(ptr + 8 + 4 * i, offsetVal, true);
|
|
235
|
+
view.setUint32(ptr + offsetVal, childPtrs[i], true);
|
|
236
|
+
}
|
|
237
|
+
return ptr;
|
|
238
|
+
} else if (isVariant(value)) {
|
|
239
|
+
const tagId = getTagId(value.tag);
|
|
240
|
+
const edge = patternNode.edges.find(e => e.label === value.tag);
|
|
241
|
+
const childPtr = writeValueToArena(value.value, pattern, edge.target);
|
|
242
|
+
|
|
243
|
+
const ptr = exports.alloc(12);
|
|
244
|
+
const view = new DataView(exports.memory.buffer);
|
|
245
|
+
|
|
246
|
+
view.setUint32(ptr, 12, true);
|
|
247
|
+
view.setUint32(ptr + 4, tagId, true);
|
|
248
|
+
view.setUint32(ptr + 8, childPtr, true);
|
|
249
|
+
console.log(`[Debug Arena] Wrote Variant at ptr=${ptr}: tag=${value.tag}, tagId=${tagId}`);
|
|
250
|
+
return ptr;
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`Unsupported value type: ${value}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getPattern(graph, patternId) {
|
|
256
|
+
const nodeId = graph.find(patternId);
|
|
257
|
+
return propertyListToPattern(patternToPropertyList(exportPatternGraph(graph, nodeId)));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 4. Run on an input
|
|
261
|
+
const inputValStr = "1.5";
|
|
262
|
+
console.log(`\nInput float64 value: ${inputValStr}`);
|
|
263
|
+
|
|
264
|
+
// Convert input string to IEEE float64 tree
|
|
265
|
+
const float64Hash = state.typeAliases.float64;
|
|
266
|
+
const parsedFloatVal = valueForCode(parseFloat64(inputValStr), float64Hash, codes.find);
|
|
267
|
+
|
|
268
|
+
// Resolve patterns
|
|
269
|
+
const squareRel = state.rels[squareHash];
|
|
270
|
+
const graph = squareRel.typePatternGraph;
|
|
271
|
+
const inputPattern = getPattern(graph, squareRel.def.patterns[0]);
|
|
272
|
+
const outputPattern = getPattern(graph, squareRel.def.patterns[1]);
|
|
273
|
+
|
|
274
|
+
// Run the JS interpreter first to check
|
|
275
|
+
const kvmFuncSquare = lowerToKVM(squareRel, squareHash);
|
|
276
|
+
const context = { rels: state.rels, findCode: codes.find, options: { envelopeFree: true, trace: false } };
|
|
277
|
+
const jsRes = executeKVM(kvmFuncSquare, parsedFloatVal, context);
|
|
278
|
+
console.log("JS Interpreter result:", jsRes ? printFloat64(jsRes.product.result) : "failed");
|
|
279
|
+
|
|
280
|
+
// Print keys and patterns for debugging
|
|
281
|
+
console.log("==> parsedFloatVal keys:", Object.keys(parsedFloatVal.product));
|
|
282
|
+
console.log("==> inputPattern node 0:", JSON.stringify(inputPattern.nodes[0], null, 2));
|
|
283
|
+
console.log("==> outputPattern node 0:", JSON.stringify(outputPattern.nodes[0], null, 2));
|
|
284
|
+
|
|
285
|
+
// Write input to the arena
|
|
286
|
+
const ptrIn = writeValueToArena(parsedFloatVal, inputPattern, 0);
|
|
287
|
+
|
|
288
|
+
// Call get_sign to test basic projection
|
|
289
|
+
console.log("Calling Wasm function 'get_sign'...");
|
|
290
|
+
const getSignRes = exports.get_sign(ptrIn);
|
|
291
|
+
if (getSignRes[1] === 1) {
|
|
292
|
+
const getSignRel = state.rels[getSignHash];
|
|
293
|
+
const getSignPattern = getPattern(getSignRel.typePatternGraph, getSignRel.def.patterns[1]);
|
|
294
|
+
const getSignVal = readArenaValue(exports, getSignRes[0], getSignPattern, 0);
|
|
295
|
+
console.log("get_sign output tag:", getSignVal.tag);
|
|
296
|
+
} else {
|
|
297
|
+
console.log("get_sign failed!");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Run the Wasm function
|
|
301
|
+
console.log("Calling Wasm function 'square'...");
|
|
302
|
+
const mainFuncName = cleanName(squareHash);
|
|
303
|
+
const result = exports[mainFuncName](ptrIn);
|
|
304
|
+
|
|
305
|
+
if (result[1] !== 1) {
|
|
306
|
+
console.error("Wasm execution failed!");
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Read and decode output from the arena
|
|
311
|
+
const outputVal = readArenaValue(exports, result[0], outputPattern, 0);
|
|
312
|
+
|
|
313
|
+
// Print the result using ieee print float64 codec helper
|
|
314
|
+
const outputResultFloat = outputVal.product.result;
|
|
315
|
+
const printResult = printFloat64(outputResultFloat);
|
|
316
|
+
|
|
317
|
+
console.log(`Result of (${inputValStr} * ${inputValStr}): ${printResult}`);
|
|
318
|
+
console.log("Flags:", outputVal.product.flags.tag);
|
|
319
|
+
console.log("==> Demo finished successfully!");
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
(module
|
|
2
|
+
(memory (export "memory") 1)
|
|
3
|
+
|
|
4
|
+
;; Start allocation at byte offset 1024 to leave a safety margin and avoid returning 0 (nullptr)
|
|
5
|
+
(global $arena_free (mut i32) (i32.const 1024))
|
|
6
|
+
;; Maximum address of the currently allocated pages (1 page = 64KB)
|
|
7
|
+
(global $arena_max (mut i32) (i32.const 65536))
|
|
8
|
+
|
|
9
|
+
;; Bump allocator function: aligns allocation size to 8 bytes, grows memory if needed, and returns the pointer
|
|
10
|
+
(func $alloc (export "alloc") (param $size i32) (result i32)
|
|
11
|
+
(local $old_free i32)
|
|
12
|
+
(local $new_free i32)
|
|
13
|
+
(local $pages_needed i32)
|
|
14
|
+
(local $grow_res i32)
|
|
15
|
+
|
|
16
|
+
;; 1. Align size to 8 bytes: size = (size + 7) & ~8
|
|
17
|
+
local.get $size
|
|
18
|
+
i32.const 7
|
|
19
|
+
i32.add
|
|
20
|
+
i32.const -8
|
|
21
|
+
i32.and
|
|
22
|
+
local.set $size
|
|
23
|
+
|
|
24
|
+
;; Get current free pointer
|
|
25
|
+
global.get $arena_free
|
|
26
|
+
local.set $old_free
|
|
27
|
+
|
|
28
|
+
;; Calculate proposed new free pointer
|
|
29
|
+
local.get $old_free
|
|
30
|
+
local.get $size
|
|
31
|
+
i32.add
|
|
32
|
+
local.set $new_free
|
|
33
|
+
|
|
34
|
+
;; 2. Check if we exceed current memory size
|
|
35
|
+
local.get $new_free
|
|
36
|
+
global.get $arena_max
|
|
37
|
+
i32.gt_u
|
|
38
|
+
if
|
|
39
|
+
;; Calculate how many additional pages we need to grow
|
|
40
|
+
local.get $new_free
|
|
41
|
+
global.get $arena_max
|
|
42
|
+
i32.sub
|
|
43
|
+
i32.const 65535
|
|
44
|
+
i32.add
|
|
45
|
+
i32.const 65536
|
|
46
|
+
i32.div_u
|
|
47
|
+
local.set $pages_needed
|
|
48
|
+
|
|
49
|
+
;; Try to grow memory by $pages_needed
|
|
50
|
+
local.get $pages_needed
|
|
51
|
+
memory.grow
|
|
52
|
+
local.set $grow_res
|
|
53
|
+
|
|
54
|
+
;; If grow returns -1, we are out of memory
|
|
55
|
+
local.get $grow_res
|
|
56
|
+
i32.const -1
|
|
57
|
+
i32.eq
|
|
58
|
+
if
|
|
59
|
+
unreachable
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
;; Update arena_max: arena_max = arena_max + pages_needed * 65536
|
|
63
|
+
global.get $arena_max
|
|
64
|
+
local.get $pages_needed
|
|
65
|
+
i32.const 65536
|
|
66
|
+
i32.mul
|
|
67
|
+
i32.add
|
|
68
|
+
global.set $arena_max
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
;; 3. Perform the allocation
|
|
72
|
+
local.get $new_free
|
|
73
|
+
global.set $arena_free
|
|
74
|
+
|
|
75
|
+
local.get $old_free
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
;; Return a checkpoint that can be restored after a temporary result is consumed.
|
|
79
|
+
(func $arena_mark (export "arena_mark") (result i32)
|
|
80
|
+
global.get $arena_free
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
;; Bulk-free allocations made after a checkpoint. Memory pages remain available for reuse.
|
|
84
|
+
(func $arena_reset (export "arena_reset") (param $mark i32)
|
|
85
|
+
local.get $mark
|
|
86
|
+
i32.const 1024
|
|
87
|
+
i32.lt_u
|
|
88
|
+
if
|
|
89
|
+
unreachable
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
local.get $mark
|
|
93
|
+
global.get $arena_free
|
|
94
|
+
i32.gt_u
|
|
95
|
+
if
|
|
96
|
+
unreachable
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
local.get $mark
|
|
100
|
+
global.set $arena_free
|
|
101
|
+
)
|
|
102
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const node = process.execPath;
|
|
9
|
+
const tests = [
|
|
10
|
+
["tests/test-wasm.mjs", node, ["tests/test-wasm.mjs"]],
|
|
11
|
+
["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function formatDuration(milliseconds) {
|
|
15
|
+
if (milliseconds < 1000) return `${milliseconds.toFixed(0)} ms`;
|
|
16
|
+
return `${(milliseconds / 1000).toFixed(2)} s`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const suiteStarted = performance.now();
|
|
20
|
+
for (const [name, command, args] of tests) {
|
|
21
|
+
console.log(`\n==> ${name}`);
|
|
22
|
+
const started = performance.now();
|
|
23
|
+
const result = spawnSync(command, args, {
|
|
24
|
+
cwd: root,
|
|
25
|
+
stdio: "inherit"
|
|
26
|
+
});
|
|
27
|
+
const duration = formatDuration(performance.now() - started);
|
|
28
|
+
|
|
29
|
+
if (result.error) {
|
|
30
|
+
console.error(`<== ${name} FAILED (${duration})`);
|
|
31
|
+
console.error(result.error.message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (result.status !== 0) {
|
|
35
|
+
console.error(`<== ${name} FAILED (${duration})`);
|
|
36
|
+
process.exit(result.status ?? 1);
|
|
37
|
+
}
|
|
38
|
+
console.log(`<== ${name} passed (${duration})`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`\nAll tests passed (${formatDuration(performance.now() - suiteStarted)})`);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { decodeObject, loadLibrary } from "@fraczak/k/backend-api.mjs";
|
|
5
|
+
import {
|
|
6
|
+
compileWasmArtifact,
|
|
7
|
+
compileWasmArtifactFromKVM,
|
|
8
|
+
compileWasmArtifactFromObject
|
|
9
|
+
} from "./wasm.mjs";
|
|
10
|
+
|
|
11
|
+
function readAll(stream) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
stream.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
15
|
+
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
|
16
|
+
stream.on("error", reject);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseCompileOptions(args) {
|
|
21
|
+
const libraries = [];
|
|
22
|
+
const exportSpecs = [];
|
|
23
|
+
let loadedLibrary = false;
|
|
24
|
+
|
|
25
|
+
while (args.length > 0) {
|
|
26
|
+
if (args[0] === "--") {
|
|
27
|
+
args.shift();
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
if (args[0] === "--lib") {
|
|
31
|
+
if (loadedLibrary) throw new Error("--lib may only be specified once");
|
|
32
|
+
args.shift();
|
|
33
|
+
const libPath = args.shift();
|
|
34
|
+
if (!libPath) throw new Error("--lib requires a file argument");
|
|
35
|
+
libraries.push(loadLibrary(decodeObject(fs.readFileSync(libPath))));
|
|
36
|
+
loadedLibrary = true;
|
|
37
|
+
} else if (args[0] === "--export") {
|
|
38
|
+
args.shift();
|
|
39
|
+
const spec = args.shift();
|
|
40
|
+
if (!spec) throw new Error("--export requires a spec argument (name or libname:localname)");
|
|
41
|
+
exportSpecs.push(spec);
|
|
42
|
+
} else if (args[0].startsWith("--")) {
|
|
43
|
+
throw new Error(`Unknown option: ${args[0]}`);
|
|
44
|
+
} else {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { libraries, exportSpecs };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fileInputType(filePath) {
|
|
53
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
54
|
+
if (ext === ".ko") return "ko";
|
|
55
|
+
if (ext === ".kvm") return "kvm";
|
|
56
|
+
if (ext === ".klib") return "klib";
|
|
57
|
+
return "source-file";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveProgramInput(args, { allowStdinSource = false } = {}) {
|
|
61
|
+
const inputArg = args.shift() ?? null;
|
|
62
|
+
if (inputArg == null) {
|
|
63
|
+
if (!allowStdinSource) throw new Error("Missing script argument");
|
|
64
|
+
return { kind: "stdin-source", sourceName: "<stdin>" };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ext = path.extname(inputArg).toLowerCase();
|
|
68
|
+
if (fs.existsSync(inputArg)) {
|
|
69
|
+
const kind = fileInputType(inputArg);
|
|
70
|
+
if (kind === "klib") {
|
|
71
|
+
throw new Error("Cannot compile a .klib library to WebAssembly without a main relation; load it with --lib.");
|
|
72
|
+
}
|
|
73
|
+
return { kind, path: inputArg, sourceName: inputArg };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if ([".k", ".ko", ".kvm", ".klib", ".wasm"].includes(ext)) {
|
|
77
|
+
throw new Error(`Input file not found: ${inputArg}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { kind: "inline-source", source: inputArg, sourceName: "<inline>" };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function compileProgramInput(
|
|
84
|
+
input,
|
|
85
|
+
{ libraries = [], exportSpecs = [], stdin = null, inputEnvelopePattern = null } = {}
|
|
86
|
+
) {
|
|
87
|
+
if (input.kind === "inline-source") {
|
|
88
|
+
return compileWasmArtifact(input.source, {
|
|
89
|
+
libraries,
|
|
90
|
+
exports: exportSpecs,
|
|
91
|
+
source: input.sourceName,
|
|
92
|
+
inputEnvelopePattern
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (input.kind === "stdin-source") {
|
|
97
|
+
if (!stdin) throw new Error("stdin source requested without a stdin stream");
|
|
98
|
+
return compileWasmArtifact((await readAll(stdin)).toString("utf8"), {
|
|
99
|
+
libraries,
|
|
100
|
+
exports: exportSpecs,
|
|
101
|
+
source: input.sourceName,
|
|
102
|
+
inputEnvelopePattern
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (input.kind === "source-file") {
|
|
107
|
+
return compileWasmArtifact(fs.readFileSync(input.path, "utf8"), {
|
|
108
|
+
libraries,
|
|
109
|
+
exports: exportSpecs,
|
|
110
|
+
source: input.sourceName,
|
|
111
|
+
inputEnvelopePattern
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (input.kind === "ko") {
|
|
116
|
+
return compileWasmArtifactFromObject(decodeObject(fs.readFileSync(input.path)), {
|
|
117
|
+
inputEnvelopePattern
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (input.kind === "kvm") {
|
|
122
|
+
return compileWasmArtifactFromKVM(JSON.parse(fs.readFileSync(input.path, "utf8")));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new Error(`Unsupported input kind: ${input.kind}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
compileProgramInput,
|
|
130
|
+
parseCompileOptions,
|
|
131
|
+
readAll,
|
|
132
|
+
resolveProgramInput
|
|
133
|
+
};
|