@fraczak/k 7.0.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/DOCS/BIG_PICTURE.md +5 -5
- package/DOCS/FILE_FORMATS.md +241 -18
- package/DOCS/OBJECT_FILE_AND_PATTERN.md +9 -9
- package/DOCS/PATTERNS.md +3 -3
- package/DOCS/POLYMORPHIC_IR.md +104 -0
- package/DOCS/PROFILING_AND_TRACING.md +60 -72
- package/DOCS/REPL.md +3 -3
- package/DOCS/book/03-types-as-finite-automata.md +4 -4
- package/DOCS/book/06-values-in-memory.md +9 -9
- package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
- package/DOCS/book/09-compiler-architecture.md +2 -2
- package/DOCS/book/10-from-ast-to-intermediate-representation.md +2 -2
- package/DOCS/book/14-canonical-serialization.md +1 -1
- package/Examples/arithmetics.k +2 -2
- package/Examples/bnat.k +1 -1
- package/Examples/karatsuba-mult.k +2 -2
- package/JOURNAL.md +288 -0
- package/README.md +13 -10
- package/access-table.mjs +343 -0
- package/backend-api.mjs +18 -0
- package/backends/llvm/README.md +20 -21
- package/backends/llvm/bin/k-llvm-build.mjs +105 -32
- package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
- package/backends/llvm/bin/k-llvm.mjs +292 -0
- package/backends/llvm/runtime/krt.c +41 -0
- package/backends/llvm/runtime/krt.h +2 -0
- package/backends/llvm/src/cli.mjs +95 -17
- package/backends/llvm/src/executable.mjs +132 -15
- package/backends/llvm/src/kvm2llvm.mjs +50 -23
- package/backends/llvm/src/llvm.mjs +133 -20
- package/backends/llvm/tests/integration.sh +11 -11
- package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
- package/backends/llvm/tests/perf-support.mjs +25 -58
- package/backends/llvm/tests/test-llvm.mjs +25 -4
- package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
- package/backends/wasm/bin/k-wasm-run.mjs +61 -20
- package/backends/wasm/bin/k-wasm.mjs +47 -13
- package/backends/wasm/src/cli.mjs +141 -19
- package/backends/wasm/src/wasm.mjs +132 -24
- package/backends/wasm/tests/test-wasm.mjs +5 -5
- package/browser/repl-browser.mjs +16 -5
- package/browser/vfs-data.mjs +5 -5
- package/codecs/k-parse.mjs +29 -8
- package/codecs/k-print.mjs +22 -6
- package/codecs/runtime/codec-sdk.mjs +8 -4
- package/codecs/show.mjs +20 -6
- package/core.k +8 -8
- package/harness/format.mjs +2 -2
- package/harness/registry.mjs +0 -2
- package/k.mjs +56 -16
- package/kir.mjs +200 -75
- package/kvm.mjs +49 -12
- package/objects/compile.mjs +37 -23
- package/objects/decompile.mjs +40 -10
- package/objects/extract-aliases.mjs +44 -14
- package/objects/inspect.mjs +19 -14
- package/objects/validate.mjs +18 -11
- package/package.json +10 -7
- package/pkir.mjs +772 -0
- package/repl.mjs +36 -18
- package/scripts/benchmark-idempotency.mjs +12 -8
- package/scripts/benchmark.mjs +5 -9
- package/scripts/build-repl-html.mjs +16 -4
- package/scripts/perf-ieee.mjs +253 -103
- package/scripts/perf-int.mjs +585 -297
- package/scripts/perf-multiplication.mjs +66 -25
- package/scripts/perf-poly.mjs +292 -164
- package/scripts/run-tests.mjs +3 -0
- package/tests/code-derivation/pattern-envelope.mjs +4 -4
- package/tests/test-access-table.mjs +213 -0
- package/tests/test-kvm-polymorphic.mjs +2 -2
- package/tests/test-pkir.mjs +530 -0
- package/tests/test-polymorphic-tdd.mjs +196 -0
- package/tests/test-repl-html.mjs +15 -2
- package/tests/test-repl.mjs +30 -1
- package/backends/arm64/README.md +0 -103
- package/backends/arm64/bin/k-arm64-compile.mjs +0 -93
- package/backends/arm64/bin/k-arm64-run.mjs +0 -58
- package/backends/arm64/bin/k-arm64.mjs +0 -88
- package/backends/arm64/scripts/run-tests.mjs +0 -57
- package/backends/arm64/src/arm64.mjs +0 -229
- package/backends/arm64/src/cli.mjs +0 -155
- package/backends/arm64/src/kvm2arm64.mjs +0 -906
- package/backends/arm64/src/runtime.c +0 -744
- package/backends/arm64/tests/integration.sh +0 -112
- package/backends/arm64/tests/test-arm64.mjs +0 -240
- package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
- package/harness/adapters/arm64.mjs +0 -105
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { createState, evaluateInput } from "../backend-api.mjs";
|
|
3
|
+
|
|
4
|
+
console.log("==> Running Polymorphic TDD Suite (tests/test-polymorphic-tdd.mjs)");
|
|
5
|
+
|
|
6
|
+
const state = createState();
|
|
7
|
+
|
|
8
|
+
async function evalK(snippet) {
|
|
9
|
+
const res = await evaluateInput(snippet, state);
|
|
10
|
+
assert.ok(
|
|
11
|
+
res && res.length > 0 && !res[0].includes("undefined"),
|
|
12
|
+
`Evaluation failed for snippet:\n${snippet}`
|
|
13
|
+
);
|
|
14
|
+
const raw = res[0];
|
|
15
|
+
const splitIdx = raw.indexOf(" ?");
|
|
16
|
+
const val = splitIdx !== -1 ? raw.slice(0, splitIdx) : raw;
|
|
17
|
+
const type = splitIdx !== -1 ? raw.slice(splitIdx + 1) : "";
|
|
18
|
+
return { val, type, raw };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// --------------------------------------------------------------------
|
|
22
|
+
// Test 1: Search - Homogeneous List with Opaque Fields
|
|
23
|
+
// --------------------------------------------------------------------
|
|
24
|
+
{
|
|
25
|
+
console.log("--> Test 1: Search on Homogeneous List with Opaque Fields");
|
|
26
|
+
const script = `
|
|
27
|
+
search = {()list, {}|nil result} search_;
|
|
28
|
+
search_ = ?{X list, X result}
|
|
29
|
+
<
|
|
30
|
+
{.list/nil if, .result then}.then,
|
|
31
|
+
<
|
|
32
|
+
{.list/cons.car.flag/true if, {.list/cons.cdr list, {.list/cons.car car, .result cdr}|cons result} then} .then,
|
|
33
|
+
{.list/cons.cdr list, .result result}
|
|
34
|
+
> search_
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
i1 = {{}|alpha id, {}|true flag, {}|payload_a payload};
|
|
38
|
+
i2 = {{}|beta id, {}|false flag, {}|payload_b payload};
|
|
39
|
+
i3 = {{}|gamma id, {}|true flag, {}|payload_c payload};
|
|
40
|
+
|
|
41
|
+
l_homo = {i1 car, {i2 car, {i3 car, {}|nil cdr}|cons cdr}|cons cdr}|cons;
|
|
42
|
+
l_homo search
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const out = await evalK(script);
|
|
46
|
+
// Expect i3 followed by i1 (accumulator reverses order); i2 is excluded
|
|
47
|
+
assert.match(out.val, /gamma/);
|
|
48
|
+
assert.match(out.val, /alpha/);
|
|
49
|
+
assert.doesNotMatch(out.val, /beta/);
|
|
50
|
+
// Check opaque fields are intact
|
|
51
|
+
assert.match(out.val, /payload_c/);
|
|
52
|
+
assert.match(out.val, /payload_a/);
|
|
53
|
+
console.log(" Passed: Homogeneous list filtered with opaque payload preserved.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --------------------------------------------------------------------
|
|
57
|
+
// Test 2: Search - Pathological Field Ordering & Disparate Field Schemas
|
|
58
|
+
// --------------------------------------------------------------------
|
|
59
|
+
{
|
|
60
|
+
console.log("--> Test 2: Search with Out-of-Order Fields & Disparate Value Types");
|
|
61
|
+
const script = `
|
|
62
|
+
p1 = {{}|large_blob data, {}|true flag, {}|first id};
|
|
63
|
+
p2 = {{}|false flag, {}|second id, {}|medium_blob data};
|
|
64
|
+
p3 = {{}|third id, {}|tiny_blob data, {}|true flag};
|
|
65
|
+
|
|
66
|
+
l_reorder = {p1 car, {p2 car, {p3 car, {}|nil cdr}|cons cdr}|cons cdr}|cons;
|
|
67
|
+
l_reorder search
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const out = await evalK(script);
|
|
71
|
+
// Expect p3 matching first, then p1 matching; p2 excluded
|
|
72
|
+
assert.match(out.val, /third/);
|
|
73
|
+
assert.match(out.val, /tiny_blob/);
|
|
74
|
+
assert.match(out.val, /first/);
|
|
75
|
+
assert.match(out.val, /large_blob/);
|
|
76
|
+
assert.doesNotMatch(out.val, /second/);
|
|
77
|
+
assert.doesNotMatch(out.val, /medium_blob/);
|
|
78
|
+
console.log(" Passed: Out-of-order fields & disparate value types preserved.");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --------------------------------------------------------------------
|
|
82
|
+
// Test 3: Search - Boundary Cases (Empty List, All Match, None Match)
|
|
83
|
+
// --------------------------------------------------------------------
|
|
84
|
+
{
|
|
85
|
+
console.log("--> Test 3: Search Boundary Cases (Empty, None Match, All Match)");
|
|
86
|
+
|
|
87
|
+
// 3a: Empty list
|
|
88
|
+
const outEmpty = await evalK(`{}|nil search`);
|
|
89
|
+
assert.match(outEmpty.val, /nil/);
|
|
90
|
+
|
|
91
|
+
// 3b: None match (all flag: false)
|
|
92
|
+
const outNone = await evalK(`
|
|
93
|
+
n1 = {{}|f1 id, {}|false flag};
|
|
94
|
+
n2 = {{}|f2 id, {}|false flag};
|
|
95
|
+
l_none = {n1 car, {n2 car, {}|nil cdr}|cons cdr}|cons;
|
|
96
|
+
l_none search
|
|
97
|
+
`);
|
|
98
|
+
assert.match(outNone.val, /nil/);
|
|
99
|
+
|
|
100
|
+
// 3c: All match (all flag: true)
|
|
101
|
+
const outAll = await evalK(`
|
|
102
|
+
m1 = {{}|t1 id, {}|true flag};
|
|
103
|
+
m2 = {{}|t2 id, {}|true flag};
|
|
104
|
+
l_all = {m1 car, {m2 car, {}|nil cdr}|cons cdr}|cons;
|
|
105
|
+
l_all search
|
|
106
|
+
`);
|
|
107
|
+
assert.match(outAll.val, /t2.*t1/);
|
|
108
|
+
console.log(" Passed: Boundary cases handled cleanly.");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --------------------------------------------------------------------
|
|
112
|
+
// Test 4: Multi-Variant Dispatch with Varying Offsets (extract_id)
|
|
113
|
+
// --------------------------------------------------------------------
|
|
114
|
+
{
|
|
115
|
+
console.log("--> Test 4: Multi-Variant Dispatch with Varying Offsets");
|
|
116
|
+
const script = `
|
|
117
|
+
extract_id = <
|
|
118
|
+
/click.id,
|
|
119
|
+
/sensor.id,
|
|
120
|
+
/ping.id
|
|
121
|
+
>;
|
|
122
|
+
|
|
123
|
+
evt_c = {{}|evt_click id, {}|active status}|click;
|
|
124
|
+
evt_s = {{}|battery_low status, {}|evt_sensor id, {}|reading data}|sensor;
|
|
125
|
+
evt_p = {{}|evt_ping id}|ping;
|
|
126
|
+
`;
|
|
127
|
+
await evalK(script);
|
|
128
|
+
|
|
129
|
+
const outC = await evalK("evt_c extract_id");
|
|
130
|
+
assert.match(outC.val, /evt_click/);
|
|
131
|
+
|
|
132
|
+
const outS = await evalK("evt_s extract_id");
|
|
133
|
+
assert.match(outS.val, /evt_sensor/);
|
|
134
|
+
|
|
135
|
+
const outP = await evalK("evt_p extract_id");
|
|
136
|
+
assert.match(outP.val, /evt_ping/);
|
|
137
|
+
console.log(" Passed: Common field extracted across variants with distinct layouts.");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --------------------------------------------------------------------
|
|
141
|
+
// Test 5: Polymorphic Zip - Concurrency of Two Independent Types
|
|
142
|
+
// --------------------------------------------------------------------
|
|
143
|
+
{
|
|
144
|
+
console.log("--> Test 5: Polymorphic Zip across Independent Types");
|
|
145
|
+
const script = `
|
|
146
|
+
zip = <
|
|
147
|
+
{.xs /nil if, {}|nil then} .then,
|
|
148
|
+
{.ys /nil if, {}|nil then} .then,
|
|
149
|
+
{
|
|
150
|
+
{.xs/cons.car x, .ys/cons.car y} car,
|
|
151
|
+
{.xs/cons.cdr xs, .ys/cons.cdr ys} zip cdr
|
|
152
|
+
}|cons
|
|
153
|
+
>;
|
|
154
|
+
|
|
155
|
+
xs = {{}|num1 car, {{}|num2 car, {}|nil cdr}|cons cdr}|cons;
|
|
156
|
+
ys = {
|
|
157
|
+
{{}|rec1_name name, {}|flag_true flag} car,
|
|
158
|
+
{{{}|rec2_name name, {}|flag_false flag} car, {}|nil cdr}|cons cdr
|
|
159
|
+
}|cons;
|
|
160
|
+
|
|
161
|
+
{xs xs, ys ys} zip
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const out = await evalK(script);
|
|
165
|
+
assert.match(out.val, /num1.*rec1_name/);
|
|
166
|
+
assert.match(out.val, /num2.*rec2_name/);
|
|
167
|
+
console.log(" Passed: Independent polymorphic streams paired into product.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// --------------------------------------------------------------------
|
|
171
|
+
// Test 6: Polymorphic Reverse with Opaque Records
|
|
172
|
+
// --------------------------------------------------------------------
|
|
173
|
+
{
|
|
174
|
+
console.log("--> Test 6: Polymorphic Reverse with Opaque Records");
|
|
175
|
+
const script = `
|
|
176
|
+
poly_rev = {()xs, {}|nil acc} _poly_rev;
|
|
177
|
+
_poly_rev = ?{X xs, X acc} <
|
|
178
|
+
{.xs/nil if, .acc then} .then,
|
|
179
|
+
{.xs/cons.cdr xs, {.xs/cons.car car, .acc cdr}|cons acc} _poly_rev
|
|
180
|
+
>;
|
|
181
|
+
|
|
182
|
+
r1 = {{}|first item, {}|opaque_1 extra};
|
|
183
|
+
r2 = {{}|second item, {}|opaque_2 extra};
|
|
184
|
+
r3 = {{}|third item, {}|opaque_3 extra};
|
|
185
|
+
list_to_rev = {r1 car, {r2 car, {r3 car, {}|nil cdr}|cons cdr}|cons cdr}|cons;
|
|
186
|
+
|
|
187
|
+
list_to_rev poly_rev
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
const out = await evalK(script);
|
|
191
|
+
assert.match(out.val, /third.*second.*first/);
|
|
192
|
+
assert.match(out.val, /opaque_3.*opaque_2.*opaque_1/);
|
|
193
|
+
console.log(" Passed: Polymorphic list reversed with opaque record payloads intact.");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log("\n==> All Polymorphic TDD Tests Passed Successfully!");
|
package/tests/test-repl-html.mjs
CHANGED
|
@@ -622,9 +622,12 @@ if (chromiumBin) {
|
|
|
622
622
|
assert.strictEqual(narrowNavbarCheck.hasTiming, false, "Help modal should not contain :timing");
|
|
623
623
|
assert.strictEqual(narrowNavbarCheck.hasCodecDefine, false, "Help modal should not contain :codec define");
|
|
624
624
|
|
|
625
|
-
// 6. Test
|
|
625
|
+
// 6. Test 3-way engine switching in browser (wasm -> pkir -> js)
|
|
626
626
|
const engineCheck = await evaluateAsync(`(async () => {
|
|
627
627
|
const initialEngine = window.kRepl.getEngine();
|
|
628
|
+
await window.kRepl.executeCommand(":engine pkir");
|
|
629
|
+
const pkirEngine = window.kRepl.getEngine();
|
|
630
|
+
const badgePkirText = document.getElementById("engine-badge")?.textContent;
|
|
628
631
|
await window.kRepl.executeCommand(":engine js");
|
|
629
632
|
const jsEngine = window.kRepl.getEngine();
|
|
630
633
|
const badgeJsText = document.getElementById("engine-badge")?.textContent;
|
|
@@ -632,9 +635,19 @@ if (chromiumBin) {
|
|
|
632
635
|
await window.kRepl.executeCommand(":engine wasm");
|
|
633
636
|
const wasmEngine = window.kRepl.getEngine();
|
|
634
637
|
const badgeWasmText = document.getElementById("engine-badge")?.textContent;
|
|
635
|
-
return {
|
|
638
|
+
return {
|
|
639
|
+
initialEngine,
|
|
640
|
+
pkirEngine,
|
|
641
|
+
jsEngine,
|
|
642
|
+
wasmEngine,
|
|
643
|
+
badgePkirText,
|
|
644
|
+
badgeJsText,
|
|
645
|
+
badgeWasmText
|
|
646
|
+
};
|
|
636
647
|
})()`);
|
|
637
648
|
assert.strictEqual(engineCheck.initialEngine, "wasm");
|
|
649
|
+
assert.strictEqual(engineCheck.pkirEngine, "pkir");
|
|
650
|
+
assert.strictEqual(engineCheck.badgePkirText, "pkir-wasm");
|
|
638
651
|
assert.strictEqual(engineCheck.jsEngine, "js");
|
|
639
652
|
assert.strictEqual(engineCheck.badgeJsText, "js-in-process");
|
|
640
653
|
assert.strictEqual(engineCheck.wasmEngine, "wasm");
|
package/tests/test-repl.mjs
CHANGED
|
@@ -471,6 +471,10 @@ output = await evaluateInput(":wasm", timingState);
|
|
|
471
471
|
assert.equal(output[0], "engine set to wasm");
|
|
472
472
|
assert.equal(timingState.engine, "wasm");
|
|
473
473
|
|
|
474
|
+
output = await evaluateInput(":pkir", timingState);
|
|
475
|
+
assert.equal(output[0], "engine set to pkir");
|
|
476
|
+
assert.equal(timingState.engine, "pkir");
|
|
477
|
+
|
|
474
478
|
output = await evaluateInput(":js", timingState);
|
|
475
479
|
assert.equal(output[0], "engine set to js");
|
|
476
480
|
assert.equal(timingState.engine, "js");
|
|
@@ -483,12 +487,13 @@ assert.equal(timingState.engine, "js");
|
|
|
483
487
|
// Verify invalid engine error
|
|
484
488
|
await assert.rejects(
|
|
485
489
|
() => evaluateInput(":engine invalid", timingState),
|
|
486
|
-
/Invalid engine 'invalid'\. Valid engines: wasm, js/
|
|
490
|
+
/Invalid engine 'invalid'\. Valid engines: wasm, pkir, js/
|
|
487
491
|
);
|
|
488
492
|
|
|
489
493
|
// Verify autocompletion for :engine
|
|
490
494
|
completions = completeInput(":engine ", timingState)[0];
|
|
491
495
|
assert(completions.includes(":engine wasm"));
|
|
496
|
+
assert(completions.includes(":engine pkir"));
|
|
492
497
|
assert(completions.includes(":engine js"));
|
|
493
498
|
|
|
494
499
|
// Verify evaluating expressions in JS engine
|
|
@@ -509,6 +514,30 @@ output = await evaluateInput("{ 10 int x, 5 int y } plus", jsState);
|
|
|
509
514
|
assert.match(output[0], /^\{\}\|_\|1\|1\|1\|1\|\+/);
|
|
510
515
|
assert.match(output[1], /\(js\)/);
|
|
511
516
|
|
|
517
|
+
// Verify evaluating expressions in PKIR-powered Wasm engine
|
|
518
|
+
const pkirState = createState({ engine: "pkir" });
|
|
519
|
+
assert.equal(pkirState.engine, "pkir");
|
|
520
|
+
|
|
521
|
+
output = await evaluateInput(
|
|
522
|
+
"succ = | succ;\n{} | zero succ succ",
|
|
523
|
+
pkirState
|
|
524
|
+
);
|
|
525
|
+
assert.match(output[0], /^\{\}\|zero\|succ\|succ/);
|
|
526
|
+
assert.match(output[1], /\(pkir\)/);
|
|
527
|
+
|
|
528
|
+
output = await evaluateInput(
|
|
529
|
+
"not = </true | false, /false | true >;\n{} | true not",
|
|
530
|
+
pkirState
|
|
531
|
+
);
|
|
532
|
+
assert.match(output[0], /^\{\}\|false/);
|
|
533
|
+
assert.match(output[1], /\(pkir\)/);
|
|
534
|
+
|
|
535
|
+
output = await evaluateInput(":load Examples/arithmetics.k", pkirState);
|
|
536
|
+
assert.equal(output[0], "loaded Examples/arithmetics.k");
|
|
537
|
+
output = await evaluateInput("{ 10 int x, 5 int y } plus", pkirState);
|
|
538
|
+
assert.match(output[0], /^\{\}\|_\|1\|1\|1\|1\|\+/);
|
|
539
|
+
assert.match(output[1], /\(pkir\)/);
|
|
540
|
+
|
|
512
541
|
// Verify open product projection in wasm engine
|
|
513
542
|
const wasmListState = createState({ engine: "wasm" });
|
|
514
543
|
await evaluateInput(":load Examples/arithmetics.k", wasmListState);
|
package/backends/arm64/README.md
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# k Linux ARM64 Backend
|
|
2
|
-
|
|
3
|
-
`@fraczak/k-arm64` is the native Linux AArch64 (ARM64) backend for
|
|
4
|
-
[`@fraczak/k`](https://github.com/fraczak/k). It lowers typed k programs directly from
|
|
5
|
-
the kVM representation into GNU AArch64 assembly text (`.s`), compiles native ELF
|
|
6
|
-
executables, and provides CLI tools and runners for binary `pattern + value` streams
|
|
7
|
-
and `--json` input/output.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- **Direct kVM to ARM64 Lowering**: Generates pure GNU AArch64 assembly text without requiring LLVM or external code generators.
|
|
12
|
-
- **Calling Convention & Registers (AAPCS64)**:
|
|
13
|
-
- `x0`: Input value pointer on entry; Return status (`0` for success, non-zero for failure) on exit.
|
|
14
|
-
- `x1`: Output value pointer on exit.
|
|
15
|
-
- `x19`: Callee-saved Arena bump pointer preserved and updated across relation calls.
|
|
16
|
-
- Standard 16-byte stack frame alignment with FP (`x29`) and LR (`x30`) chaining.
|
|
17
|
-
- **Flat Arena Memory Representation**:
|
|
18
|
-
- Word 0 (bytes 0–1): Total block size in bytes (16-byte aligned).
|
|
19
|
-
- Word 1 (bytes 2–3): Value kind (`1` = Product, `2` = Variant).
|
|
20
|
-
- Word 2 (bytes 4–7): Product field count OR Variant 32-bit tag ID.
|
|
21
|
-
- Payloads (bytes 8+): 64-bit pointers to child values in the arena.
|
|
22
|
-
- **Self Tail-Call Optimization (TCO)**:
|
|
23
|
-
- Recursion where the tail call passes new product fields directly updates cached field slots and jumps back to `.Ltail_loop` with zero frame overhead, enabling deep recursion without stack overflow.
|
|
24
|
-
- **Union Backtracking & Memory Rollback**:
|
|
25
|
-
- Union branches save the arena bump pointer (`x19`) before evaluation. If a branch fails, `x19` is rewound to cleanly roll back all allocations made in the failed speculative branch before attempting subsequent branches.
|
|
26
|
-
- **Standalone Linux Binaries**:
|
|
27
|
-
- Links with a lightweight C driver (`backends/arm64/src/runtime.c`) and `backends/llvm/runtime/krt.c` to produce independent native ELF executables that execute directly on Linux without Node.js.
|
|
28
|
-
- Supports `--json` for direct text output, stdin/stdout piping, and binary envelope streams.
|
|
29
|
-
|
|
30
|
-
## Quick Start
|
|
31
|
-
|
|
32
|
-
Compile an expression to a standalone native Linux ARM64 executable:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
node ./backends/arm64/bin/k-arm64-compile.mjs -o /tmp/ok '|ok'
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Execute it over unit input and decode output:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
node ./codecs/unit.mjs --parse | /tmp/ok --json
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Output:
|
|
45
|
-
```json
|
|
46
|
-
"ok"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Compile with library dependencies (`--lib` and `--export`):
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
node ./backends/arm64/bin/k-arm64-compile.mjs \
|
|
53
|
-
--lib Examples/arithmetics.k \
|
|
54
|
-
--export plus:+ --export succ --export int \
|
|
55
|
-
'{succ int x,int y}+' \
|
|
56
|
-
-o /tmp/add-one
|
|
57
|
-
|
|
58
|
-
node ./codecs/k-parse.mjs <<< '{"x":{"succ":"zero"},"y":{"succ":{"succ":"zero"}}}' |
|
|
59
|
-
/tmp/add-one --json
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Output:
|
|
63
|
-
```json
|
|
64
|
-
{"+":{"1":"_"}}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## CLI Commands
|
|
68
|
-
|
|
69
|
-
| Command | Purpose |
|
|
70
|
-
| --- | --- |
|
|
71
|
-
| `k-arm64` | Compile k source, `.ko`, or `.kvm` in memory and run immediately |
|
|
72
|
-
| `k-arm64-compile` | Compile k source, `.ko`, or `.kvm` to standalone ELF executable or assembly (`-S`) |
|
|
73
|
-
| `k-arm64-run` | Run a compiled ELF executable over binary wire streams |
|
|
74
|
-
|
|
75
|
-
### Options
|
|
76
|
-
|
|
77
|
-
All tools accept `--help`.
|
|
78
|
-
|
|
79
|
-
- `-S, --assembly`: Emit GNU ARM64 assembly text (`.s`) instead of an ELF binary.
|
|
80
|
-
- `-o, --output <file>`: Specify the output file path.
|
|
81
|
-
- `--lib <file>`: Load one `.klib` or `.k` library dependency.
|
|
82
|
-
- `--export <spec>`: Export library symbols into source scope (`name` or `libname:localname`).
|
|
83
|
-
- `--json`: Format output as JSON instead of binary wire format.
|
|
84
|
-
|
|
85
|
-
## Running Tests
|
|
86
|
-
|
|
87
|
-
Run unit tests:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
node ./backends/arm64/tests/test-arm64.mjs
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Run integration tests:
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
./backends/arm64/tests/integration.sh
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Run the complete ARM64 backend test suite:
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
npm run test:arm64
|
|
103
|
-
```
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import { argv, exit, stdin, stdout } from "node:process";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
compileProgramInput,
|
|
8
|
-
parseCompileOptions,
|
|
9
|
-
resolveProgramInput
|
|
10
|
-
} from "../src/cli.mjs";
|
|
11
|
-
|
|
12
|
-
function usage(stream = console.error) {
|
|
13
|
-
const prog = argv[1] || "k-arm64-compile.mjs";
|
|
14
|
-
stream(`Usage: node ${prog} [options] [source-snippet | input-file [output-file]]`);
|
|
15
|
-
stream("Compile k source, .ko, or .kvm input into a standalone Linux ARM64 executable or assembly.");
|
|
16
|
-
stream("");
|
|
17
|
-
stream("Arguments:");
|
|
18
|
-
stream(" source-snippet Inline k source, in the same style as k.mjs.");
|
|
19
|
-
stream(" input-file Source .k, .ko, or .kvm file. Reads UTF-8 source from stdin when omitted.");
|
|
20
|
-
stream(" output-file Output file path. Writes to stdout when omitted and --assembly is set.");
|
|
21
|
-
stream("");
|
|
22
|
-
stream("Options:");
|
|
23
|
-
stream(" -S, --assembly Emit GNU ARM64 assembly (.s) instead of an executable binary.");
|
|
24
|
-
stream(" -o, --output Specify output file path explicitly.");
|
|
25
|
-
stream(" --lib file Load one .klib dependency before compiling.");
|
|
26
|
-
stream(" --export spec Export a library alias into source scope. May be repeated.");
|
|
27
|
-
stream(" spec is 'name' or 'libname:localname'.");
|
|
28
|
-
stream(" -h, --help Show this help.");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const args = argv.slice(2);
|
|
33
|
-
if (args.includes("-h") || args.includes("--help")) {
|
|
34
|
-
usage(console.log);
|
|
35
|
-
exit(0);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let emitAssembly = false;
|
|
39
|
-
let explicitOutput = null;
|
|
40
|
-
// Extract ARM64-specific flags
|
|
41
|
-
const remainingArgs = [];
|
|
42
|
-
while (args.length > 0) {
|
|
43
|
-
const arg = args[0];
|
|
44
|
-
if (arg === "-S" || arg === "--assembly") {
|
|
45
|
-
emitAssembly = true;
|
|
46
|
-
args.shift();
|
|
47
|
-
} else if (arg === "-o" || arg === "--output") {
|
|
48
|
-
args.shift();
|
|
49
|
-
explicitOutput = args.shift();
|
|
50
|
-
if (!explicitOutput) throw new Error("-o/--output requires a file argument");
|
|
51
|
-
} else {
|
|
52
|
-
remainingArgs.push(args.shift());
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const { libraries, exportSpecs } = parseCompileOptions(remainingArgs);
|
|
57
|
-
const input = resolveProgramInput(remainingArgs, { allowStdinSource: true });
|
|
58
|
-
const positionalOutput = remainingArgs.shift();
|
|
59
|
-
if (remainingArgs.length > 0) throw new Error("Too many arguments");
|
|
60
|
-
|
|
61
|
-
const outputPath = explicitOutput || positionalOutput || null;
|
|
62
|
-
|
|
63
|
-
const result = await compileProgramInput(input, {
|
|
64
|
-
libraries,
|
|
65
|
-
exportSpecs,
|
|
66
|
-
stdin,
|
|
67
|
-
emit: emitAssembly ? "assembly" : null,
|
|
68
|
-
outputPath: (!emitAssembly && outputPath) ? outputPath : null
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
if (emitAssembly) {
|
|
72
|
-
const asmText = typeof result === "string" ? result : result.assembly;
|
|
73
|
-
if (outputPath == null) {
|
|
74
|
-
stdout.write(asmText);
|
|
75
|
-
} else {
|
|
76
|
-
fs.writeFileSync(outputPath, asmText, "utf8");
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
if (outputPath == null) {
|
|
80
|
-
if (Buffer.isBuffer(result)) {
|
|
81
|
-
stdout.write(result);
|
|
82
|
-
} else if (typeof result === "string") {
|
|
83
|
-
stdout.write(fs.readFileSync(result));
|
|
84
|
-
}
|
|
85
|
-
} else if (Buffer.isBuffer(result)) {
|
|
86
|
-
fs.writeFileSync(outputPath, result, { mode: 0o755 });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error(error.stack || error.message || String(error));
|
|
91
|
-
usage();
|
|
92
|
-
exit(1);
|
|
93
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import { argv, exit, stdin, stdout } from "node:process";
|
|
5
|
-
|
|
6
|
-
import { readAll } from "../src/cli.mjs";
|
|
7
|
-
import { runARM64Artifact } from "../src/arm64.mjs";
|
|
8
|
-
|
|
9
|
-
function usage(stream = console.error) {
|
|
10
|
-
const prog = argv[1] || "k-arm64-run.mjs";
|
|
11
|
-
stream(`Usage: node ${prog} [options] executable-file [input-file]`);
|
|
12
|
-
stream("Run a standalone k Linux ARM64 executable over a binary wire stream.");
|
|
13
|
-
stream("");
|
|
14
|
-
stream("Arguments:");
|
|
15
|
-
stream(" executable-file Input compiled ELF binary produced by k-arm64-compile.");
|
|
16
|
-
stream(" input-file Optional binary input wire file. Reads stdin when omitted.");
|
|
17
|
-
stream("");
|
|
18
|
-
stream("Options:");
|
|
19
|
-
stream(" --json Format output as JSON instead of binary wire format.");
|
|
20
|
-
stream(" -h, --help Show this help.");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function main() {
|
|
24
|
-
const args = argv.slice(2);
|
|
25
|
-
if (args.includes("-h") || args.includes("--help")) {
|
|
26
|
-
usage(console.log);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let json = false;
|
|
31
|
-
const filteredArgs = [];
|
|
32
|
-
for (const arg of args) {
|
|
33
|
-
if (arg === "--json") {
|
|
34
|
-
json = true;
|
|
35
|
-
} else {
|
|
36
|
-
filteredArgs.push(arg);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const execPath = filteredArgs.shift();
|
|
41
|
-
if (!execPath) throw new Error("Missing executable artifact path");
|
|
42
|
-
const inputPath = filteredArgs.shift();
|
|
43
|
-
if (filteredArgs.length > 0) throw new Error("Too many arguments");
|
|
44
|
-
|
|
45
|
-
const input = await readAll(inputPath == null ? stdin : fs.createReadStream(inputPath));
|
|
46
|
-
const result = runARM64Artifact(execPath, input, { json });
|
|
47
|
-
if (json) {
|
|
48
|
-
stdout.write(result);
|
|
49
|
-
} else {
|
|
50
|
-
stdout.write(result);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
main().catch((error) => {
|
|
55
|
-
console.error(error.stack || error.message || String(error));
|
|
56
|
-
usage();
|
|
57
|
-
exit(1);
|
|
58
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import { argv, exit, stdin, stdout } from "node:process";
|
|
5
|
-
import { decodeWire } from "../../../backend-api.mjs";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
compileProgramInput,
|
|
9
|
-
parseCompileOptions,
|
|
10
|
-
readAll,
|
|
11
|
-
resolveProgramInput
|
|
12
|
-
} from "../src/cli.mjs";
|
|
13
|
-
import { runARM64Artifact } from "../src/arm64.mjs";
|
|
14
|
-
|
|
15
|
-
function usage(stream = console.error) {
|
|
16
|
-
const prog = argv[1] || "k-arm64.mjs";
|
|
17
|
-
stream(`Usage: node ${prog} [options] ( source-snippet | input-file ) [ input-file ]`);
|
|
18
|
-
stream("Compile k source, .ko, or .kvm input to ARM64 in memory and run it over a binary pattern+value stream.");
|
|
19
|
-
stream("");
|
|
20
|
-
stream("Options:");
|
|
21
|
-
stream(" --json Format output as JSON instead of binary wire format.");
|
|
22
|
-
stream(" --entry name Specify entry relation name.");
|
|
23
|
-
stream(" --lib file Load one .klib dependency before compiling.");
|
|
24
|
-
stream(" --export spec Export a library alias into source scope. May be repeated.");
|
|
25
|
-
stream(" spec is 'name' or 'libname:localname'.");
|
|
26
|
-
stream(" -h, --help Show this help.");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function main() {
|
|
30
|
-
const args = argv.slice(2);
|
|
31
|
-
if (args.includes("-h") || args.includes("--help")) {
|
|
32
|
-
usage(console.log);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let json = false;
|
|
37
|
-
let entry = null;
|
|
38
|
-
const remainingArgs = [];
|
|
39
|
-
while (args.length > 0) {
|
|
40
|
-
const arg = args[0];
|
|
41
|
-
if (arg === "--json") {
|
|
42
|
-
json = true;
|
|
43
|
-
args.shift();
|
|
44
|
-
} else if (arg === "--entry") {
|
|
45
|
-
args.shift();
|
|
46
|
-
entry = args.shift();
|
|
47
|
-
if (!entry) throw new Error("--entry requires a name argument");
|
|
48
|
-
} else {
|
|
49
|
-
remainingArgs.push(args.shift());
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const { libraries, exportSpecs } = parseCompileOptions(remainingArgs);
|
|
54
|
-
const programInput = resolveProgramInput(remainingArgs);
|
|
55
|
-
const inputPath = remainingArgs.shift();
|
|
56
|
-
if (remainingArgs.length > 0) throw new Error("Too many arguments");
|
|
57
|
-
|
|
58
|
-
const inputBuffer = await readAll(inputPath == null ? stdin : fs.createReadStream(inputPath));
|
|
59
|
-
let inputEnvelopePattern = null;
|
|
60
|
-
if (inputBuffer.length > 0) {
|
|
61
|
-
try {
|
|
62
|
-
const decoded = decodeWire(inputBuffer);
|
|
63
|
-
inputEnvelopePattern = decoded.pattern;
|
|
64
|
-
} catch {
|
|
65
|
-
// Not a valid wire buffer or empty
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const artifact = await compileProgramInput(programInput, {
|
|
70
|
-
libraries,
|
|
71
|
-
exportSpecs,
|
|
72
|
-
inputEnvelopePattern,
|
|
73
|
-
entry
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const output = runARM64Artifact(artifact, inputBuffer, { json });
|
|
77
|
-
if (json) {
|
|
78
|
-
stdout.write(output);
|
|
79
|
-
} else {
|
|
80
|
-
stdout.write(output);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
main().catch((error) => {
|
|
85
|
-
console.error(error.stack || error.message || String(error));
|
|
86
|
-
usage();
|
|
87
|
-
exit(1);
|
|
88
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
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
|
-
|
|
10
|
-
function printHelp() {
|
|
11
|
-
console.log(`Usage: node backends/arm64/scripts/run-tests.mjs [options]
|
|
12
|
-
|
|
13
|
-
Runs ARM64 backend test suite and integration tests.
|
|
14
|
-
|
|
15
|
-
Options:
|
|
16
|
-
-h, --help
|
|
17
|
-
Show this help message and exit.`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
21
|
-
printHelp();
|
|
22
|
-
process.exit(0);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const tests = [
|
|
26
|
-
["tests/test-arm64.mjs", node, ["tests/test-arm64.mjs"]],
|
|
27
|
-
["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
function formatDuration(milliseconds) {
|
|
31
|
-
if (milliseconds < 1000) return `${milliseconds.toFixed(0)} ms`;
|
|
32
|
-
return `${(milliseconds / 1000).toFixed(2)} s`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const suiteStarted = performance.now();
|
|
36
|
-
for (const [name, command, args] of tests) {
|
|
37
|
-
console.log(`\n==> ${name}`);
|
|
38
|
-
const started = performance.now();
|
|
39
|
-
const result = spawnSync(command, args, {
|
|
40
|
-
cwd: root,
|
|
41
|
-
stdio: "inherit"
|
|
42
|
-
});
|
|
43
|
-
const duration = formatDuration(performance.now() - started);
|
|
44
|
-
|
|
45
|
-
if (result.error) {
|
|
46
|
-
console.error(`<== ${name} FAILED (${duration})`);
|
|
47
|
-
console.error(result.error.message);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
if (result.status !== 0) {
|
|
51
|
-
console.error(`<== ${name} FAILED (${duration})`);
|
|
52
|
-
process.exit(result.status ?? 1);
|
|
53
|
-
}
|
|
54
|
-
console.log(`<== ${name} passed (${duration})`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
console.log(`\nAll ARM64 tests passed (${formatDuration(performance.now() - suiteStarted)})`);
|