@fraczak/k 7.0.0 → 7.0.2
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 +50 -0
- package/DOCS/BIG_PICTURE.md +5 -5
- package/DOCS/FILE_FORMATS.md +5 -5
- package/DOCS/OBJECT_FILE_AND_PATTERN.md +9 -9
- package/DOCS/PATTERNS.md +3 -3
- 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/README.md +3 -8
- package/backend-api.mjs +9 -1
- package/backends/llvm/runtime/krt.c +33 -0
- package/backends/llvm/runtime/krt.h +1 -0
- package/backends/llvm/scripts/run-tests.mjs +5 -0
- package/backends/llvm/src/executable.mjs +4 -4
- package/backends/llvm/src/kvm2llvm.mjs +42 -19
- package/backends/llvm/src/llvm.mjs +30 -36
- package/backends/llvm/tests/integration.sh +2 -2
- package/backends/llvm/tests/perf-support.mjs +1 -55
- package/backends/llvm/tests/test-llvm.mjs +3 -3
- package/backends/llvm/tests/test-open-product-misalignment.mjs +140 -0
- package/backends/wasm/demo_ieee.mjs +2 -3
- package/backends/wasm/runtime.wat +157 -5
- package/backends/wasm/src/kvm2wasm.mjs +57 -34
- package/backends/wasm/src/wasm.mjs +40 -18
- package/backends/wasm/tests/perf-support.mjs +4 -4
- package/backends/wasm/tests/test-wasm.mjs +13 -14
- package/browser/vfs-data.mjs +6 -6
- package/core.k +8 -8
- package/harness/format.mjs +2 -2
- package/harness/registry.mjs +0 -2
- package/kir.mjs +299 -17
- package/package.json +1 -5
- package/scripts/benchmark-idempotency.mjs +12 -8
- package/scripts/benchmark.mjs +5 -9
- package/scripts/build-repl-html.mjs +2 -2
- package/scripts/perf-ieee.mjs +17 -118
- package/scripts/perf-int.mjs +313 -318
- package/scripts/perf-multiplication.mjs +10 -20
- package/scripts/perf-poly.mjs +29 -183
- package/tests/code-derivation/pattern-envelope.mjs +4 -4
- package/tests/test-kvm-polymorphic.mjs +2 -2
- package/tests/test-repl-html.mjs +15 -0
- package/tests/test-repl.mjs +10 -0
- 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/harness/adapters/arm64.mjs +0 -105
|
@@ -1489,6 +1489,39 @@ k_value *k_read_wire(FILE *in, k_rt *rt) {
|
|
|
1489
1489
|
return value;
|
|
1490
1490
|
}
|
|
1491
1491
|
|
|
1492
|
+
static int is_union_kind(int kind) {
|
|
1493
|
+
return kind == KP_OPEN_UNION || kind == KP_CLOSED_UNION;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
static int is_product_kind(int kind) {
|
|
1497
|
+
return kind == KP_OPEN_PRODUCT || kind == KP_CLOSED_PRODUCT;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
int k_pattern_compatible(k_pattern *a, k_pattern *b) {
|
|
1501
|
+
if (a == b) return 1;
|
|
1502
|
+
if (a == NULL || b == NULL) return 0;
|
|
1503
|
+
if (a->node_count != b->node_count) return 0;
|
|
1504
|
+
for (size_t i = 0; i < a->node_count; i++) {
|
|
1505
|
+
k_pattern_node *left = &a->nodes[i];
|
|
1506
|
+
k_pattern_node *right = &b->nodes[i];
|
|
1507
|
+
if (left->kind != right->kind) {
|
|
1508
|
+
if (!(is_union_kind(left->kind) && is_union_kind(right->kind)) &&
|
|
1509
|
+
!(is_product_kind(left->kind) && is_product_kind(right->kind))) {
|
|
1510
|
+
return 0;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (left->edge_count != right->edge_count) return 0;
|
|
1514
|
+
for (size_t j = 0; j < left->edge_count; j++) {
|
|
1515
|
+
if (left->edges[j].target != right->edges[j].target) return 0;
|
|
1516
|
+
if (!bytes_equal(left->edges[j].label, left->edges[j].label_length,
|
|
1517
|
+
right->edges[j].label, right->edges[j].label_length)) {
|
|
1518
|
+
return 0;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return 1;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1492
1525
|
int k_pattern_equal(k_pattern *a, k_pattern *b) {
|
|
1493
1526
|
if (a == b) return 1;
|
|
1494
1527
|
if (a == NULL || b == NULL) return 0;
|
|
@@ -104,6 +104,7 @@ k_wire_input k_read_wire_envelope(FILE *in, k_rt *rt);
|
|
|
104
104
|
k_wire_input k_decode_wire_envelope(const unsigned char *bytes, size_t length, k_rt *rt);
|
|
105
105
|
void k_wire_input_free(k_wire_input input);
|
|
106
106
|
int k_pattern_equal(k_pattern *a, k_pattern *b);
|
|
107
|
+
int k_pattern_compatible(k_pattern *a, k_pattern *b);
|
|
107
108
|
k_wire_prefix k_wire_prefix_for_pattern(k_pattern *pattern);
|
|
108
109
|
void k_wire_prefix_free(k_wire_prefix prefix);
|
|
109
110
|
k_value *k_decode_wire_value_with_prefix(const unsigned char *bytes, size_t length, k_wire_prefix *prefix, k_pattern *pattern, k_rt *rt);
|
|
@@ -24,6 +24,11 @@ if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
|
24
24
|
|
|
25
25
|
const tests = [
|
|
26
26
|
["tests/test-llvm.mjs", node, ["tests/test-llvm.mjs"]],
|
|
27
|
+
[
|
|
28
|
+
"tests/test-open-product-misalignment.mjs",
|
|
29
|
+
node,
|
|
30
|
+
["tests/test-open-product-misalignment.mjs", "--assert"]
|
|
31
|
+
],
|
|
27
32
|
["tests/integration.sh", path.join(root, "tests/integration.sh"), []],
|
|
28
33
|
["scripts/conformance.mjs", node, ["scripts/conformance.mjs"]]
|
|
29
34
|
];
|
|
@@ -292,7 +292,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
|
|
|
292
292
|
" k_wire_input_free(input);",
|
|
293
293
|
" return 2;",
|
|
294
294
|
" }",
|
|
295
|
-
" if (!
|
|
295
|
+
" if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
|
|
296
296
|
" k_wire_input_free(input);",
|
|
297
297
|
" return 5;",
|
|
298
298
|
" }",
|
|
@@ -350,7 +350,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
|
|
|
350
350
|
" k_value *in_val = k_decode_wire_value_with_prefix(payload, length, &input_prefix, &compiled_input_pattern, rt);",
|
|
351
351
|
" if (in_val == 0) {",
|
|
352
352
|
" k_wire_input envelope = k_decode_wire_envelope(payload, length, rt);",
|
|
353
|
-
" if (envelope.value != 0 && envelope.pattern != 0 &&
|
|
353
|
+
" if (envelope.value != 0 && envelope.pattern != 0 && k_pattern_compatible(envelope.pattern, &compiled_input_pattern)) {",
|
|
354
354
|
" in_val = envelope.value;",
|
|
355
355
|
" } else {",
|
|
356
356
|
" k_wire_input_free(envelope);",
|
|
@@ -437,7 +437,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
|
|
|
437
437
|
" k_rt_free(input_rt);",
|
|
438
438
|
" return 2;",
|
|
439
439
|
" }",
|
|
440
|
-
" if (!
|
|
440
|
+
" if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
|
|
441
441
|
" k_wire_input_free(input);",
|
|
442
442
|
" k_rt_free(run_rt);",
|
|
443
443
|
" k_rt_free(input_rt);",
|
|
@@ -478,7 +478,7 @@ export function stdioDriverSource({ inputPattern = null, outputPattern = null }
|
|
|
478
478
|
" k_rt_free(rt);",
|
|
479
479
|
" return 2;",
|
|
480
480
|
" }",
|
|
481
|
-
" if (!
|
|
481
|
+
" if (!k_pattern_compatible(input.pattern, &compiled_input_pattern)) {",
|
|
482
482
|
" k_wire_input_free(input);",
|
|
483
483
|
" k_wire_prefix_free(output_prefix);",
|
|
484
484
|
" k_rt_free(rt);",
|
|
@@ -35,15 +35,17 @@ function alignArenaSize(size) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function cStringBytes(text) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.
|
|
38
|
+
const buf = Buffer.from(text, "utf8");
|
|
39
|
+
let out = "";
|
|
40
|
+
for (let i = 0; i < buf.length; i++) {
|
|
41
|
+
const byte = buf[i];
|
|
42
|
+
if (byte === 10) out += "\\0A";
|
|
43
|
+
else if (byte === 34) out += "\\22";
|
|
44
|
+
else if (byte === 92) out += "\\5C";
|
|
45
|
+
else if (byte >= 32 && byte <= 126) out += String.fromCharCode(byte);
|
|
46
|
+
else out += `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
|
|
47
|
+
}
|
|
48
|
+
return out + "\\00";
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export function runtimeDeclarations() {
|
|
@@ -551,8 +553,7 @@ export function compileKVMModuleToLLVM(entryName, kvmProgram, options = {}) {
|
|
|
551
553
|
const payload = JSON.stringify({
|
|
552
554
|
format: ARTIFACT_FORMAT,
|
|
553
555
|
version: ARTIFACT_VERSION,
|
|
554
|
-
relation
|
|
555
|
-
kvm: kvmProgram
|
|
556
|
+
relation
|
|
556
557
|
});
|
|
557
558
|
const payloadBytes = Buffer.byteLength(payload, "utf8") + 1;
|
|
558
559
|
const symbol = llvmIdentifier(options.symbol || relation);
|
|
@@ -678,7 +679,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
678
679
|
const label = ctx.labelRef(inst.label);
|
|
679
680
|
let val;
|
|
680
681
|
if (edgeIndex >= 0 && ctx.runtimeMode !== "compact") {
|
|
681
|
-
val = lowerProductGetAt(ctx, input, edgeIndex,
|
|
682
|
+
val = lowerProductGetAt(ctx, input, edgeIndex, null);
|
|
682
683
|
} else {
|
|
683
684
|
val = ctx.tempName("field");
|
|
684
685
|
ctx.lines.push(` ${val} = call ptr @k_product_get_n(ptr ${input}, ptr ${label.pointer}, i64 ${label.length})`);
|
|
@@ -815,11 +816,32 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
815
816
|
storeI64At(ctx, product, K_VALUE_PRODUCT_CAPACITY_OFFSET, N);
|
|
816
817
|
storePtrAt(ctx, product, K_VALUE_PRODUCT_FIELDS_OFFSET, fields);
|
|
817
818
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
819
|
+
let sortedFields;
|
|
820
|
+
if (Array.isArray(inst.pattern) && inst.pattern.length > 0) {
|
|
821
|
+
const root = inst.pattern[0];
|
|
822
|
+
if (
|
|
823
|
+
(root[0] === "open-product" ||
|
|
824
|
+
root[0] === "closed-product") &&
|
|
825
|
+
Array.isArray(root[1])
|
|
826
|
+
) {
|
|
827
|
+
const order = new Map(root[1].map(([l], i) => [l, i]));
|
|
828
|
+
sortedFields = [...fieldValues].sort((a, b) => {
|
|
829
|
+
const aI = order.has(a.label)
|
|
830
|
+
? order.get(a.label)
|
|
831
|
+
: 999999;
|
|
832
|
+
const bI = order.has(b.label)
|
|
833
|
+
? order.get(b.label)
|
|
834
|
+
: 999999;
|
|
835
|
+
if (aI !== bI) return aI - bI;
|
|
836
|
+
return a.label.localeCompare(b.label);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (!sortedFields) {
|
|
841
|
+
sortedFields = [...fieldValues].sort((a, b) =>
|
|
842
|
+
a.label.localeCompare(b.label)
|
|
843
|
+
);
|
|
844
|
+
}
|
|
823
845
|
|
|
824
846
|
for (let sIdx = 0; sIdx < N; sIdx++) {
|
|
825
847
|
const { label: fLabel, val: fVal } = sortedFields[sIdx];
|
|
@@ -831,13 +853,14 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
831
853
|
}
|
|
832
854
|
|
|
833
855
|
regValues.set(cleanReg(inst.dest), product);
|
|
856
|
+
if (inst.pattern) regPatterns.set(cleanReg(inst.dest), inst.pattern);
|
|
834
857
|
break;
|
|
835
858
|
}
|
|
836
859
|
case "union": {
|
|
837
860
|
const uInput = getReg(inst.src);
|
|
838
861
|
|
|
839
862
|
if (!isTopLevel) {
|
|
840
|
-
// Nested union: synthesize
|
|
863
|
+
// Nested union: synthesize helper @k_union_expr_${index}
|
|
841
864
|
const exprIndex = moduleCtx.unionExprIndex++;
|
|
842
865
|
const exprName = `@k_union_expr_${exprIndex}`;
|
|
843
866
|
const exprFunc = {
|
|
@@ -862,7 +885,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
862
885
|
break;
|
|
863
886
|
}
|
|
864
887
|
|
|
865
|
-
// Top-level union: synthesize
|
|
888
|
+
// Top-level union: synthesize arms @k_union_arm_${index}
|
|
866
889
|
const mark = lowerRtMark(ctx);
|
|
867
890
|
const unionDone = ctx.blockName("union_done");
|
|
868
891
|
const N = inst.branches.length;
|
|
@@ -45,15 +45,17 @@ function alignArenaSize(size) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function cStringBytes(text) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.
|
|
48
|
+
const buf = Buffer.from(text, "utf8");
|
|
49
|
+
let out = "";
|
|
50
|
+
for (let i = 0; i < buf.length; i++) {
|
|
51
|
+
const byte = buf[i];
|
|
52
|
+
if (byte === 10) out += "\\0A";
|
|
53
|
+
else if (byte === 34) out += "\\22";
|
|
54
|
+
else if (byte === 92) out += "\\5C";
|
|
55
|
+
else if (byte >= 32 && byte <= 126) out += String.fromCharCode(byte);
|
|
56
|
+
else out += `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
|
|
57
|
+
}
|
|
58
|
+
return out + "\\00";
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
function readPattern(inputPattern) {
|
|
@@ -986,8 +988,7 @@ export function emitLLVMModule(input, options = {}) {
|
|
|
986
988
|
const payload = JSON.stringify({
|
|
987
989
|
format: ARTIFACT_FORMAT,
|
|
988
990
|
version: ARTIFACT_VERSION,
|
|
989
|
-
relation
|
|
990
|
-
kir
|
|
991
|
+
relation
|
|
991
992
|
});
|
|
992
993
|
const payloadBytes = Buffer.byteLength(payload, "utf8") + 1;
|
|
993
994
|
const symbol = llvmIdentifier(options.symbol || relation);
|
|
@@ -1011,8 +1012,7 @@ export function emitLLVMModule(input, options = {}) {
|
|
|
1011
1012
|
export function compileObjectToLLVM(object, options = {}) {
|
|
1012
1013
|
const inputPattern = options.inputPattern ? readPattern(options.inputPattern) : null;
|
|
1013
1014
|
const relation = options.relation || object.main;
|
|
1014
|
-
|
|
1015
|
-
let retypedKir = null;
|
|
1015
|
+
let targetObject = object;
|
|
1016
1016
|
let entryInputPattern = inputPattern;
|
|
1017
1017
|
let outputPattern = null;
|
|
1018
1018
|
|
|
@@ -1020,43 +1020,37 @@ export function compileObjectToLLVM(object, options = {}) {
|
|
|
1020
1020
|
const retyped = retypeObjectRelationForBackend(object, relation, inputPattern, {
|
|
1021
1021
|
source: options.source || "<k-llvm>"
|
|
1022
1022
|
});
|
|
1023
|
-
|
|
1023
|
+
targetObject = retyped.retypedObject;
|
|
1024
1024
|
entryInputPattern = retyped.inputPattern;
|
|
1025
1025
|
outputPattern = retyped.outputPattern;
|
|
1026
|
-
} else {
|
|
1027
|
-
retypedKir = objectToKIRP(object);
|
|
1028
1026
|
}
|
|
1029
1027
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
const entry = specializedKvm.entry || relation;
|
|
1045
|
-
const functions = specializedKvm.functions;
|
|
1028
|
+
const retypedKir = objectToKIRP(targetObject);
|
|
1029
|
+
const functions = lowerKIRToKVM(retypedKir);
|
|
1030
|
+
const entry = relation || targetObject.main || "__main__";
|
|
1031
|
+
|
|
1032
|
+
const specializedKvm = {
|
|
1033
|
+
format: "k-vm",
|
|
1034
|
+
version: 1,
|
|
1035
|
+
entry,
|
|
1036
|
+
functions,
|
|
1037
|
+
inputPattern: entryInputPattern,
|
|
1038
|
+
outputPattern
|
|
1039
|
+
};
|
|
1046
1040
|
|
|
1047
1041
|
const llvm = compileKVMModuleToLLVM(entry, functions, {
|
|
1048
1042
|
...options,
|
|
1049
1043
|
relation,
|
|
1050
|
-
inputPattern: entryInputPattern
|
|
1051
|
-
outputPattern
|
|
1044
|
+
inputPattern: entryInputPattern,
|
|
1045
|
+
outputPattern
|
|
1052
1046
|
});
|
|
1053
1047
|
|
|
1054
1048
|
return {
|
|
1055
1049
|
kir: retypedKir,
|
|
1056
1050
|
kvm: specializedKvm,
|
|
1057
1051
|
relation,
|
|
1058
|
-
inputPattern: entryInputPattern
|
|
1059
|
-
outputPattern
|
|
1052
|
+
inputPattern: entryInputPattern,
|
|
1053
|
+
outputPattern,
|
|
1060
1054
|
llvm
|
|
1061
1055
|
};
|
|
1062
1056
|
}
|
|
@@ -112,7 +112,7 @@ node --input-type=module -e "import { stdout } from 'node:process'; import { enc
|
|
|
112
112
|
| node --input-type=module -e "import { stdin } from 'node:process'; import { decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; const chunks = []; stdin.on('data', (chunk) => chunks.push(chunk)); stdin.on('end', () => console.log(JSON.stringify(decodeWire(Buffer.concat(chunks)).pattern)));" \
|
|
113
113
|
| grep -Fqx '[["open-union",[["left",1],["right",1]]],["closed-product",[]]]'
|
|
114
114
|
|
|
115
|
-
node ./bin/k-llvm-build.mjs '
|
|
115
|
+
node ./bin/k-llvm-build.mjs '?< <bits 0, bits 1, {} _>=bits "+", bits "-">' -o "$TMP_DIR/id-int-exe"
|
|
116
116
|
printf '2' \
|
|
117
117
|
| node ../../codecs/int.mjs --parse \
|
|
118
118
|
| "$TMP_DIR/id-int-exe" \
|
|
@@ -121,7 +121,7 @@ printf '2' \
|
|
|
121
121
|
|
|
122
122
|
node --input-type=module -e "import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { encodeToWire, decodeWire } from '../../codecs/runtime/prefix-codec.mjs'; import { parse, INT_PATTERN } from '../../codecs/int.mjs'; const input = encodeToWire(parse('2'), INT_PATTERN); const header = Buffer.alloc(4); header.writeUInt32BE(input.length); const child = spawn(process.argv[1], ['--server']); const chunks = []; const stderr = []; child.stdout.on('data', (chunk) => chunks.push(chunk)); child.stderr.on('data', (chunk) => stderr.push(chunk)); child.stdin.end(Buffer.concat([header, input])); const status = await new Promise((resolve, reject) => { child.on('error', reject); child.on('close', resolve); }); assert.equal(status, 0, Buffer.concat(stderr).toString('utf8')); const output = Buffer.concat(chunks); const length = output.readUInt32BE(0); assert.equal(output.length, 4 + length); assert.deepEqual(decodeWire(output.subarray(4)).value.toJSON(), parse('2').toJSON());" "$TMP_DIR/id-int-exe"
|
|
123
123
|
|
|
124
|
-
node ./bin/k-llvm-build.mjs --lib ../../Examples/arithmetics.k --export times 's = {()x, ()y} times; s' -o "$TMP_DIR/square-exe"
|
|
124
|
+
node ./bin/k-llvm-build.mjs --lib ../../Examples/arithmetics.k --export times 's = ?< <bits 0, bits 1, {} _>=bits "+", bits "-" > {()x, ()y} times; s' -o "$TMP_DIR/square-exe"
|
|
125
125
|
printf '987654321' \
|
|
126
126
|
| node ../../codecs/int.mjs --parse \
|
|
127
127
|
| "$TMP_DIR/square-exe" \
|
|
@@ -216,7 +216,7 @@ export function runExecutableMainBench(exePath, inputWire, calls) {
|
|
|
216
216
|
reject(new Error(`${exePath} --bench-main failed with status ${status}\n${text}`.trim()));
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
|
-
const match = text.match(/
|
|
219
|
+
const match = text.match(/K_LLVM_BENCH_MAIN calls=(\d+) total_ns=(\d+) per_call_ns=([0-9.]+)/);
|
|
220
220
|
if (!match) {
|
|
221
221
|
reject(new Error(`${exePath} --bench-main did not report timing\n${text}`.trim()));
|
|
222
222
|
return;
|
|
@@ -599,61 +599,7 @@ export function llvmLaneName() {
|
|
|
599
599
|
: "LLVM Executable (persistent)";
|
|
600
600
|
}
|
|
601
601
|
|
|
602
|
-
export function arm64LaneName(optLevel = "-O2") {
|
|
603
|
-
const mode = process.env.ARM64_SPAWN_PER_CALL === "1"
|
|
604
|
-
? "spawn/call"
|
|
605
|
-
: (process.env.ARM64_PIPELINE === "1" ? "persistent, pipelined" : "persistent");
|
|
606
|
-
return `Linux ARM64 ${optLevel} (${mode})`;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
export function createARM64Runner(testSuite) {
|
|
610
|
-
const arm64Cases = testSuite.filter(tc => tc.arm64?.status === "ok");
|
|
611
|
-
if (process.env.ARM64_SPAWN_PER_CALL === "1") {
|
|
612
|
-
return {
|
|
613
|
-
run(iterations) {
|
|
614
|
-
if (arm64Cases.length === 0) return null;
|
|
615
|
-
return runTimedIterationsAsync(iterations, async () => {
|
|
616
|
-
for (const tc of arm64Cases) {
|
|
617
|
-
await runExecutable(tc.arm64.exePath, tc.inputWire);
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
},
|
|
621
|
-
close() {}
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
602
|
|
|
625
|
-
const servers = new Map();
|
|
626
|
-
function serverFor(exePath) {
|
|
627
|
-
let server = servers.get(exePath);
|
|
628
|
-
if (server == null) {
|
|
629
|
-
server = new PersistentExecutable(exePath);
|
|
630
|
-
servers.set(exePath, server);
|
|
631
|
-
}
|
|
632
|
-
return server;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return {
|
|
636
|
-
async run(iterations) {
|
|
637
|
-
if (arm64Cases.length === 0) return null;
|
|
638
|
-
return runTimedIterationsAsync(iterations, async () => {
|
|
639
|
-
if (process.env.ARM64_PIPELINE === "1") {
|
|
640
|
-
const pending = [];
|
|
641
|
-
for (const tc of arm64Cases) {
|
|
642
|
-
pending.push(serverFor(tc.arm64.exePath).request(tc.inputWire));
|
|
643
|
-
}
|
|
644
|
-
await Promise.all(pending);
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
for (const tc of arm64Cases) {
|
|
648
|
-
await serverFor(tc.arm64.exePath).request(tc.inputWire);
|
|
649
|
-
}
|
|
650
|
-
});
|
|
651
|
-
},
|
|
652
|
-
close() {
|
|
653
|
-
for (const server of servers.values()) server.close();
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
603
|
|
|
658
604
|
export function average(times) {
|
|
659
605
|
return times.reduce((sum, time) => sum + time, 0) / times.length;
|
|
@@ -239,11 +239,11 @@ assert.doesNotMatch(nestedUnionLLVM, /call void @k_product_set_at\(ptr %product\
|
|
|
239
239
|
assert.doesNotMatch(nestedUnionLLVM, /ptr false/);
|
|
240
240
|
|
|
241
241
|
const productRecurseObject = decodeObject(compileObjectBuffer(`
|
|
242
|
-
|
|
243
|
-
foo =
|
|
242
|
+
list? = ?< {} nil, { {} car, list? cdr } cons >;
|
|
243
|
+
foo = list? <
|
|
244
244
|
/nil |nil,
|
|
245
245
|
/cons { .car car, .cdr foo cdr } |cons
|
|
246
|
-
>
|
|
246
|
+
> list?;
|
|
247
247
|
foo
|
|
248
248
|
`, { source: "llvm-product-recurse.k" }));
|
|
249
249
|
const { llvm: productRecurseLLVM } = compileObjectToLLVM(productRecurseObject, {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { compileObjectBuffer, decodeObject } from "@fraczak/k/object.mjs";
|
|
3
|
+
import { compileObjectToLLVM } from "../src/llvm.mjs";
|
|
4
|
+
import { compileAndRunLLVM } from "../src/executable.mjs";
|
|
5
|
+
import { Value } from "@fraczak/k/Value.mjs";
|
|
6
|
+
|
|
7
|
+
console.log("==> LLVM Open Product Field Alignment Test");
|
|
8
|
+
|
|
9
|
+
// Test expression from user:
|
|
10
|
+
// len_ = ?X <{.list/nil if, .len then} .then,
|
|
11
|
+
// {.list/cons.cdr list, .len inc len} ?X len_ >;
|
|
12
|
+
const src = `
|
|
13
|
+
0 = {}|0;
|
|
14
|
+
inc = |1;
|
|
15
|
+
len = {()list, 0 len} len_;
|
|
16
|
+
len_ = ?X <{.list/nil if, .len then} .then, {.list/cons.cdr list, .len inc len} ?X len_ >;
|
|
17
|
+
len
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
// Input schema for list:
|
|
21
|
+
// cons is a closed product with fields 'car' (index 0) and
|
|
22
|
+
// 'cdr' (index 1).
|
|
23
|
+
const listPattern = [
|
|
24
|
+
["closed-union", [["cons", 1], ["nil", 2]]],
|
|
25
|
+
["closed-product", [["car", 3], ["cdr", 0]]],
|
|
26
|
+
["closed-product", []],
|
|
27
|
+
["any", []]
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const obj = decodeObject(
|
|
31
|
+
compileObjectBuffer(src, { source: "open-product-misalignment.k" })
|
|
32
|
+
);
|
|
33
|
+
const { llvm } = compileObjectToLLVM(obj, { inputPattern: listPattern });
|
|
34
|
+
|
|
35
|
+
// -----------------------------------------------------------------------
|
|
36
|
+
// Test 1: Static IR Field Index for .cdr
|
|
37
|
+
// -----------------------------------------------------------------------
|
|
38
|
+
// In cons payload { car, cdr }, 'car' is index 0 (byte offset 0) and
|
|
39
|
+
// 'cdr' is index 1 (byte offset 24 = 1 * K_FIELD_SIZE).
|
|
40
|
+
// The specialized LLVM IR for projecting .cdr MUST access byte offset 24.
|
|
41
|
+
// When misaligned, it accesses byte offset 0 (which is 'car').
|
|
42
|
+
|
|
43
|
+
const cdrLabelMatch = llvm.match(
|
|
44
|
+
/(@k_label_\d+)\s*=\s*private unnamed_addr constant \[4 x i8\] c"cdr\\00"/
|
|
45
|
+
);
|
|
46
|
+
assert.ok(cdrLabelMatch, "LLVM IR should define label constant for 'cdr'");
|
|
47
|
+
const cdrLabelGlobal = cdrLabelMatch[1];
|
|
48
|
+
|
|
49
|
+
const lines = llvm.split("\n");
|
|
50
|
+
let cdrFieldSlotOffset = null;
|
|
51
|
+
let cdrDynamicFallbackEmitted = false;
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
if (
|
|
55
|
+
lines[i].includes(cdrLabelGlobal) &&
|
|
56
|
+
lines[i].includes("getelementptr inbounds [4 x i8]")
|
|
57
|
+
) {
|
|
58
|
+
const localLabelVar = lines[i].trim().split("=")[0].trim();
|
|
59
|
+
for (let j = i; j < Math.min(lines.length, i + 35); j++) {
|
|
60
|
+
if (
|
|
61
|
+
lines[j].includes("getelementptr inbounds i8, ptr %product_fields_ptr") &&
|
|
62
|
+
lines[j].includes(", i64 ")
|
|
63
|
+
) {
|
|
64
|
+
const offsetMatch = lines[j].match(/, i64 (\d+)/);
|
|
65
|
+
if (offsetMatch) cdrFieldSlotOffset = Number(offsetMatch[1]);
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
lines[j].includes("call ptr @k_product_get_n") &&
|
|
69
|
+
lines[j].includes(localLabelVar)
|
|
70
|
+
) {
|
|
71
|
+
cdrDynamicFallbackEmitted = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (cdrFieldSlotOffset !== null) break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`- Detected .cdr field slot byte offset: ${cdrFieldSlotOffset} (expected 24)`);
|
|
79
|
+
console.log(`- Detected dynamic fallback (@k_product_get_n) emitted for .cdr: ${cdrDynamicFallbackEmitted}`);
|
|
80
|
+
|
|
81
|
+
// -----------------------------------------------------------------------
|
|
82
|
+
// Test 2: Execution without dynamic fallback
|
|
83
|
+
// -----------------------------------------------------------------------
|
|
84
|
+
// When dynamic fallback (@k_product_get_n) is removed from the emitted
|
|
85
|
+
// LLVM IR (forcing runtime to rely on the statically generated index),
|
|
86
|
+
// the misalignment causes .cdr projection to load 'car' (a leaf),
|
|
87
|
+
// causing the next iteration of len_ to fail because 'car' is not a list.
|
|
88
|
+
const llvmNoFallback = llvm.replace(
|
|
89
|
+
/call ptr @k_product_get_n\([^)]+\)/g,
|
|
90
|
+
"bitcast ptr null to ptr"
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Build test list: [1, 2] = cons(car: 1, cdr: cons(car: 2, cdr: nil))
|
|
94
|
+
const nil = Value.variant("nil", Value.product({}));
|
|
95
|
+
const cons2 = Value.variant("cons", Value.product({
|
|
96
|
+
car: Value.variant("1", Value.product({})),
|
|
97
|
+
cdr: nil
|
|
98
|
+
}));
|
|
99
|
+
const cons1 = Value.variant("cons", Value.product({
|
|
100
|
+
car: Value.variant("1", Value.product({})),
|
|
101
|
+
cdr: cons2
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
let runtimeStatusNoFallback = null;
|
|
105
|
+
try {
|
|
106
|
+
const res = compileAndRunLLVM(llvmNoFallback, { input: cons1 });
|
|
107
|
+
runtimeStatusNoFallback = res.status;
|
|
108
|
+
} catch (err) {
|
|
109
|
+
runtimeStatusNoFallback = err.status ?? 2;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`- Runtime exit status without fallback: ${runtimeStatusNoFallback} (expected 0)`);
|
|
113
|
+
|
|
114
|
+
// -----------------------------------------------------------------------
|
|
115
|
+
// Assertions to expose misalignment
|
|
116
|
+
// -----------------------------------------------------------------------
|
|
117
|
+
const exposeMode = process.argv.includes("--assert");
|
|
118
|
+
|
|
119
|
+
if (exposeMode) {
|
|
120
|
+
assert.equal(
|
|
121
|
+
cdrFieldSlotOffset,
|
|
122
|
+
24,
|
|
123
|
+
`Misalignment: .cdr projected at byte offset ${cdrFieldSlotOffset} (car) instead of 24 (cdr)`
|
|
124
|
+
);
|
|
125
|
+
assert.equal(
|
|
126
|
+
cdrDynamicFallbackEmitted,
|
|
127
|
+
false,
|
|
128
|
+
"Dynamic fallback: LLVM emits @k_product_get_n fallback to mask misalignment"
|
|
129
|
+
);
|
|
130
|
+
assert.equal(
|
|
131
|
+
runtimeStatusNoFallback,
|
|
132
|
+
0,
|
|
133
|
+
`Runtime failure: status ${runtimeStatusNoFallback} when relying on static index`
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
console.log("\nSummary:");
|
|
137
|
+
console.log(` Offset misalignment: ${cdrFieldSlotOffset === 24 ? "ALIGNED" : "MISALIGNED (offset 0 instead of 24)"}`);
|
|
138
|
+
console.log(` Dynamic fallback used: ${cdrDynamicFallbackEmitted ? "YES (papers over bug at runtime)" : "NO"}`);
|
|
139
|
+
console.log(` Status without fallback: ${runtimeStatusNoFallback === 0 ? "OK" : "FAILS (status " + runtimeStatusNoFallback + ")"}`);
|
|
140
|
+
}
|
|
@@ -184,8 +184,7 @@ function readArenaValue(exports, ptr, pattern, patternNodeId) {
|
|
|
184
184
|
|
|
185
185
|
for (let i = 0; i < N; i++) {
|
|
186
186
|
const edge = patternNode.edges[i];
|
|
187
|
-
const
|
|
188
|
-
const childPtr = view.getUint32(ptr + offsetVal, true);
|
|
187
|
+
const childPtr = view.getUint32(ptr + 8 + 4 * N + 4 * i, true);
|
|
189
188
|
productObj[edge.label] = readArenaValue(exports, childPtr, pattern, edge.target);
|
|
190
189
|
}
|
|
191
190
|
return Value.product(productObj, pattern);
|
|
@@ -231,7 +230,7 @@ function writeValueToArena(value, pattern, patternNodeId) {
|
|
|
231
230
|
|
|
232
231
|
for (let i = 0; i < N; i++) {
|
|
233
232
|
const offsetVal = 8 + 4 * N + 4 * i;
|
|
234
|
-
view.setUint32(ptr + 8 + 4 * i,
|
|
233
|
+
view.setUint32(ptr + 8 + 4 * i, getTagId(keys[i]), true);
|
|
235
234
|
view.setUint32(ptr + offsetVal, childPtrs[i], true);
|
|
236
235
|
}
|
|
237
236
|
return ptr;
|