@fraczak/k 6.10.6 → 7.0.1
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 +55 -0
- package/CLAUDE.md +1 -1
- package/DOCS/BIG_PICTURE.md +156 -172
- package/DOCS/BINARY_CODEC_STATUS_AND_TODO.md +3 -3
- package/DOCS/CODEC_ARCHITECTURE.md +2 -2
- package/DOCS/DICTIONARY.md +243 -250
- package/DOCS/DIVERGENCE_DETECTION.md +0 -1
- package/DOCS/FILE_FORMATS.md +5 -5
- package/DOCS/KIR_SPECIALIZATION_DESIGN.md +1 -1
- package/DOCS/OBJECT_FILE_AND_PATTERN.md +12 -12
- package/DOCS/PATTERNS.md +17 -17
- package/DOCS/PATTERNS_AND_TYPED_VALUES.md +1 -1
- package/DOCS/REPL.md +9 -19
- package/DOCS/TYPE_DERIVATION.md +2 -8
- package/DOCS/WEB_REPL_REQUIREMENTS.md +8 -8
- package/DOCS/book/01-introduction.md +8 -11
- package/DOCS/book/02-syntax-and-values.md +19 -20
- package/DOCS/book/03-types-as-finite-automata.md +42 -43
- package/DOCS/book/04-partial-functions-and-composition.md +2 -3
- package/DOCS/book/05-typing-filters-and-normalization.md +44 -46
- package/DOCS/book/06-values-in-memory.md +10 -10
- package/DOCS/book/08-operational-semantics-and-execution.md +21 -26
- package/DOCS/book/09-compiler-architecture.md +11 -11
- package/DOCS/book/10-from-ast-to-intermediate-representation.md +6 -7
- package/DOCS/book/14-canonical-serialization.md +2 -2
- package/DOCS/book/appendix-b-incorporating-external-predefined-types-and-functions.md +17 -17
- package/DOCS/book.md +5 -5
- package/Examples/arithmetics.k +49 -153
- package/Examples/bnat.k +13 -13
- package/Examples/byte.k +2 -50
- package/Examples/ieee.k +408 -423
- package/Examples/karatsuba-mult.k +30 -34
- package/Examples/nat.k +5 -6
- package/README.md +54 -49
- package/TODO.md +13 -18
- package/backend-api.mjs +9 -1
- package/backends/llvm/scripts/run-tests.mjs +5 -0
- package/backends/llvm/src/kvm2llvm.mjs +2 -1
- package/backends/llvm/src/llvm.mjs +18 -25
- package/backends/llvm/tests/integration.sh +2 -2
- 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 +38 -17
- package/backends/wasm/tests/perf-support.mjs +4 -4
- package/backends/wasm/tests/test-wasm.mjs +13 -14
- package/browser/vfs-data.mjs +12 -12
- package/codecs/BINARY_FORMAT.md +4 -4
- package/codecs/POLYMORPHIC_BINARY_FORMAT.md +4 -4
- package/codecs/README.md +6 -6
- package/codecs/ieee.mjs +15 -11
- package/codecs/int.mjs +9 -5
- package/codecs/string-codec.mjs +46 -47
- package/core.k +123 -95
- package/kir.mjs +213 -17
- package/object.mjs +0 -7
- package/package.json +5 -2
- package/parser.jison +2 -41
- package/parser.mjs +55 -95
- package/pretty.mjs +11 -4
- package/repl.mjs +8 -63
- package/scripts/build-repl-html.mjs +8 -38
- package/tests/code-derivation/blowup.mjs +2 -4
- package/tests/code-derivation/bnat.mjs +2 -3
- package/tests/code-derivation/dot.mjs +5 -11
- package/tests/code-derivation/nat.mjs +2 -2
- package/tests/code-derivation/not-not.mjs +6 -6
- package/tests/code-derivation/pattern-envelope.mjs +4 -6
- package/tests/code-derivation/product.mjs +3 -6
- package/tests/code-derivation/rlz.mjs +4 -5
- package/tests/test-codecs.mjs +1 -1
- package/tests/test-fingerprint.mjs +36 -41
- package/tests/test-hash-normalization.mjs +4 -6
- package/tests/test-ieee-arithmetic.mjs +6 -7
- package/tests/test-k-object.mjs +11 -15
- package/tests/test-kir.mjs +0 -1
- package/tests/test-kvm-polymorphic.mjs +1 -2
- package/tests/test-kvm.mjs +8 -11
- package/tests/test-repl-html.mjs +20 -2
- package/tests/test-repl.mjs +48 -53
- package/tests/test.mjs +2 -3
|
@@ -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;
|
|
@@ -398,6 +398,14 @@
|
|
|
398
398
|
i32.ge_u
|
|
399
399
|
br_if $break_prod
|
|
400
400
|
|
|
401
|
+
;; Copy field tagId at offset 8 + 4*i
|
|
402
|
+
local.get $dest
|
|
403
|
+
i32.const 8
|
|
404
|
+
local.get $i
|
|
405
|
+
i32.const 4
|
|
406
|
+
i32.mul
|
|
407
|
+
i32.add
|
|
408
|
+
i32.add
|
|
401
409
|
local.get $curr
|
|
402
410
|
i32.const 8
|
|
403
411
|
local.get $i
|
|
@@ -406,17 +414,19 @@
|
|
|
406
414
|
i32.add
|
|
407
415
|
i32.add
|
|
408
416
|
i32.load
|
|
409
|
-
|
|
417
|
+
i32.store
|
|
410
418
|
|
|
411
|
-
|
|
419
|
+
;; Value slot offset is 8 + 4*N + 4*i
|
|
412
420
|
i32.const 8
|
|
413
|
-
local.get $
|
|
421
|
+
local.get $N
|
|
414
422
|
i32.const 4
|
|
415
423
|
i32.mul
|
|
416
424
|
i32.add
|
|
425
|
+
local.get $i
|
|
426
|
+
i32.const 4
|
|
427
|
+
i32.mul
|
|
417
428
|
i32.add
|
|
418
|
-
local.
|
|
419
|
-
i32.store
|
|
429
|
+
local.set $offsetVal
|
|
420
430
|
|
|
421
431
|
local.get $curr
|
|
422
432
|
local.get $offsetVal
|
|
@@ -472,4 +482,146 @@
|
|
|
472
482
|
i32.add
|
|
473
483
|
global.set $arena_free
|
|
474
484
|
)
|
|
485
|
+
|
|
486
|
+
;; Lookup product field by tagId with predictedIndex hint
|
|
487
|
+
(func $product_get (export "product_get") (param $prod i32) (param $targetTagId i32) (param $predictedIndex i32) (result i32)
|
|
488
|
+
(local $size i32)
|
|
489
|
+
(local $N i32)
|
|
490
|
+
(local $i i32)
|
|
491
|
+
|
|
492
|
+
;; 1. Null check
|
|
493
|
+
local.get $prod
|
|
494
|
+
i32.eqz
|
|
495
|
+
if
|
|
496
|
+
i32.const 0
|
|
497
|
+
return
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
;; 2. Read totalSize at prod + 0
|
|
501
|
+
local.get $prod
|
|
502
|
+
i32.load offset=0
|
|
503
|
+
local.set $size
|
|
504
|
+
|
|
505
|
+
;; 3. Validate product header (not a variant and size >= 8)
|
|
506
|
+
local.get $size
|
|
507
|
+
i32.const 12
|
|
508
|
+
i32.eq
|
|
509
|
+
if
|
|
510
|
+
i32.const 0
|
|
511
|
+
return
|
|
512
|
+
end
|
|
513
|
+
local.get $size
|
|
514
|
+
i32.const 8
|
|
515
|
+
i32.lt_u
|
|
516
|
+
if
|
|
517
|
+
i32.const 0
|
|
518
|
+
return
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
;; 4. Read field count N at prod + 4
|
|
522
|
+
local.get $prod
|
|
523
|
+
i32.load offset=4
|
|
524
|
+
local.set $N
|
|
525
|
+
|
|
526
|
+
;; 5. Validate size == 8 + 8 * N
|
|
527
|
+
local.get $size
|
|
528
|
+
i32.const 8
|
|
529
|
+
local.get $N
|
|
530
|
+
i32.const 8
|
|
531
|
+
i32.mul
|
|
532
|
+
i32.add
|
|
533
|
+
i32.ne
|
|
534
|
+
if
|
|
535
|
+
i32.const 0
|
|
536
|
+
return
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
;; 6. Fast path: check predictedIndex if in range [0, N)
|
|
540
|
+
local.get $predictedIndex
|
|
541
|
+
i32.const 0
|
|
542
|
+
i32.ge_s
|
|
543
|
+
if
|
|
544
|
+
local.get $predictedIndex
|
|
545
|
+
local.get $N
|
|
546
|
+
i32.lt_u
|
|
547
|
+
if
|
|
548
|
+
;; Load tagId at prod + 8 + 4 * predictedIndex
|
|
549
|
+
local.get $prod
|
|
550
|
+
i32.const 8
|
|
551
|
+
local.get $predictedIndex
|
|
552
|
+
i32.const 4
|
|
553
|
+
i32.mul
|
|
554
|
+
i32.add
|
|
555
|
+
i32.add
|
|
556
|
+
i32.load
|
|
557
|
+
local.get $targetTagId
|
|
558
|
+
i32.eq
|
|
559
|
+
if
|
|
560
|
+
;; Match! Return value at prod + 8 + 4*N + 4*predictedIndex
|
|
561
|
+
local.get $prod
|
|
562
|
+
i32.const 8
|
|
563
|
+
local.get $N
|
|
564
|
+
i32.const 4
|
|
565
|
+
i32.mul
|
|
566
|
+
i32.add
|
|
567
|
+
local.get $predictedIndex
|
|
568
|
+
i32.const 4
|
|
569
|
+
i32.mul
|
|
570
|
+
i32.add
|
|
571
|
+
i32.add
|
|
572
|
+
i32.load
|
|
573
|
+
return
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
;; 7. Scan path: check i = 0 .. N-1
|
|
579
|
+
i32.const 0
|
|
580
|
+
local.set $i
|
|
581
|
+
(block $break_scan
|
|
582
|
+
(loop $loop_scan
|
|
583
|
+
local.get $i
|
|
584
|
+
local.get $N
|
|
585
|
+
i32.ge_u
|
|
586
|
+
br_if $break_scan
|
|
587
|
+
|
|
588
|
+
;; Load tagId at prod + 8 + 4 * i
|
|
589
|
+
local.get $prod
|
|
590
|
+
i32.const 8
|
|
591
|
+
local.get $i
|
|
592
|
+
i32.const 4
|
|
593
|
+
i32.mul
|
|
594
|
+
i32.add
|
|
595
|
+
i32.add
|
|
596
|
+
i32.load
|
|
597
|
+
local.get $targetTagId
|
|
598
|
+
i32.eq
|
|
599
|
+
if
|
|
600
|
+
;; Match! Return value at prod + 8 + 4*N + 4*i
|
|
601
|
+
local.get $prod
|
|
602
|
+
i32.const 8
|
|
603
|
+
local.get $N
|
|
604
|
+
i32.const 4
|
|
605
|
+
i32.mul
|
|
606
|
+
i32.add
|
|
607
|
+
local.get $i
|
|
608
|
+
i32.const 4
|
|
609
|
+
i32.mul
|
|
610
|
+
i32.add
|
|
611
|
+
i32.add
|
|
612
|
+
i32.load
|
|
613
|
+
return
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
local.get $i
|
|
617
|
+
i32.const 1
|
|
618
|
+
i32.add
|
|
619
|
+
local.set $i
|
|
620
|
+
br $loop_scan
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
;; 8. Not found: return 0
|
|
625
|
+
i32.const 0
|
|
626
|
+
)
|
|
475
627
|
)
|
|
@@ -108,22 +108,28 @@ export function lowerToWasm(relDef, name, options = {}) {
|
|
|
108
108
|
const getPattern = (rName, inputMap = {}) => registerPatterns.get(mappedReg(rName, inputMap));
|
|
109
109
|
|
|
110
110
|
const getProductEdges = (inst, inputMap) => {
|
|
111
|
-
if (typePatternGraph) {
|
|
112
|
-
const inputPatternId = inst.exp.patterns[0];
|
|
113
|
-
const inputPatternNodeId = typePatternGraph.find(inputPatternId);
|
|
114
|
-
const inputPropertyList = patternToPropertyList(exportPatternGraph(typePatternGraph, inputPatternNodeId));
|
|
115
|
-
return inputPropertyList[0][1];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
111
|
const inputPropertyList = getPattern(inst.src, inputMap);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
if (Array.isArray(inputPropertyList) && inputPropertyList.length > 0) {
|
|
113
|
+
const inputRoot = inputPropertyList[0];
|
|
114
|
+
if ((inputRoot[0] === "open-product" || inputRoot[0] === "closed-product") && Array.isArray(inputRoot[1])) {
|
|
115
|
+
return inputRoot[1];
|
|
116
|
+
}
|
|
122
117
|
}
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
|
|
119
|
+
if (typePatternGraph && inst.exp?.patterns?.[0] != null) {
|
|
120
|
+
try {
|
|
121
|
+
const inputPatternId = inst.exp.patterns[0];
|
|
122
|
+
const inputPatternNodeId = typePatternGraph.find(inputPatternId);
|
|
123
|
+
const inputPropertyList = patternToPropertyList(exportPatternGraph(typePatternGraph, inputPatternNodeId));
|
|
124
|
+
if (Array.isArray(inputPropertyList?.[0]?.[1])) {
|
|
125
|
+
return inputPropertyList[0][1];
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Ignore fallback errors
|
|
129
|
+
}
|
|
125
130
|
}
|
|
126
|
-
|
|
131
|
+
|
|
132
|
+
return null;
|
|
127
133
|
};
|
|
128
134
|
|
|
129
135
|
const registers = new Set();
|
|
@@ -205,14 +211,12 @@ export function lowerToWasm(relDef, name, options = {}) {
|
|
|
205
211
|
const lines = [];
|
|
206
212
|
for (const field of inputProductFields) {
|
|
207
213
|
const fieldIndex = inputProductEdges.findIndex(([label]) => label === field.label);
|
|
214
|
+
const tagId = getTagId(field.label);
|
|
208
215
|
lines.push(` ;; cache input field ${field.label} in $${field.local}`);
|
|
209
216
|
lines.push(` local.get $in`);
|
|
210
|
-
lines.push(`
|
|
211
|
-
lines.push(` i32.const ${
|
|
212
|
-
lines.push(`
|
|
213
|
-
lines.push(` i32.load`);
|
|
214
|
-
lines.push(` i32.add`);
|
|
215
|
-
lines.push(` i32.load`);
|
|
217
|
+
lines.push(` i32.const ${tagId}`);
|
|
218
|
+
lines.push(` i32.const ${fieldIndex}`);
|
|
219
|
+
lines.push(` call $product_get`);
|
|
216
220
|
lines.push(` local.set $${field.local}`);
|
|
217
221
|
}
|
|
218
222
|
return lines.join("\n");
|
|
@@ -231,9 +235,10 @@ export function lowerToWasm(relDef, name, options = {}) {
|
|
|
231
235
|
lines.push(` i32.const ${inputProductFields.length}`);
|
|
232
236
|
lines.push(` i32.store offset=4`);
|
|
233
237
|
for (const field of inputProductFields) {
|
|
238
|
+
const tagId = getTagId(field.label);
|
|
234
239
|
const offsetVal = 8 + 4 * inputProductFields.length + 4 * field.index;
|
|
235
240
|
lines.push(` local.get $${dest}`);
|
|
236
|
-
lines.push(` i32.const ${
|
|
241
|
+
lines.push(` i32.const ${tagId}`);
|
|
237
242
|
lines.push(` i32.store offset=${8 + 4 * field.index}`);
|
|
238
243
|
lines.push(` local.get $${dest}`);
|
|
239
244
|
lines.push(` local.get $${field.local}`);
|
|
@@ -314,14 +319,24 @@ export function lowerToWasm(relDef, name, options = {}) {
|
|
|
314
319
|
break;
|
|
315
320
|
}
|
|
316
321
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
let predictedIndex = -1;
|
|
323
|
+
try {
|
|
324
|
+
const edges = getProductEdges(inst, inputMap);
|
|
325
|
+
if (Array.isArray(edges)) {
|
|
326
|
+
predictedIndex = edges.findIndex(([label]) => label === inst.label);
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
predictedIndex = -1;
|
|
321
330
|
}
|
|
331
|
+
const tagId = getTagId(inst.label);
|
|
322
332
|
|
|
323
|
-
lines.push(` ;; project_field ${inst.label} (
|
|
333
|
+
lines.push(` ;; project_field ${inst.label} (tagId ${tagId}, hint ${predictedIndex}) from $${src} to $${dest}`);
|
|
324
334
|
lines.push(` local.get $${src}`);
|
|
335
|
+
lines.push(` i32.const ${tagId}`);
|
|
336
|
+
lines.push(` i32.const ${predictedIndex}`);
|
|
337
|
+
lines.push(` call $product_get`);
|
|
338
|
+
lines.push(` local.set $${dest}`);
|
|
339
|
+
lines.push(` local.get $${dest}`);
|
|
325
340
|
lines.push(` i32.eqz`);
|
|
326
341
|
lines.push(` if`);
|
|
327
342
|
if (failTarget) {
|
|
@@ -332,14 +347,6 @@ export function lowerToWasm(relDef, name, options = {}) {
|
|
|
332
347
|
lines.push(` return`);
|
|
333
348
|
}
|
|
334
349
|
lines.push(` end`);
|
|
335
|
-
lines.push(` local.get $${src}`);
|
|
336
|
-
lines.push(` local.get $${src}`);
|
|
337
|
-
lines.push(` i32.const ${8 + 4 * fieldIndex}`);
|
|
338
|
-
lines.push(` i32.add`);
|
|
339
|
-
lines.push(` i32.load`);
|
|
340
|
-
lines.push(` i32.add`);
|
|
341
|
-
lines.push(` i32.load`);
|
|
342
|
-
lines.push(` local.set $${dest}`);
|
|
343
350
|
setPattern(dest, inst.pattern);
|
|
344
351
|
break;
|
|
345
352
|
}
|
|
@@ -443,11 +450,13 @@ export function lowerToWasm(relDef, name, options = {}) {
|
|
|
443
450
|
lines.push(` i32.store offset=4`);
|
|
444
451
|
|
|
445
452
|
for (let i = 0; i < N; i++) {
|
|
453
|
+
const branch = sortedBranches[i];
|
|
454
|
+
const tagId = getTagId(branch.label);
|
|
446
455
|
const offsetVal = 8 + 4 * N + 4 * i;
|
|
447
456
|
const fieldTmp = `${dest}_f${i}`;
|
|
448
457
|
|
|
449
458
|
lines.push(` local.get $${dest}`);
|
|
450
|
-
lines.push(` i32.const ${
|
|
459
|
+
lines.push(` i32.const ${tagId}`);
|
|
451
460
|
lines.push(` i32.store offset=${8 + 4 * i}`);
|
|
452
461
|
|
|
453
462
|
lines.push(` local.get $${dest}`);
|
|
@@ -501,6 +510,20 @@ export function lowerToWasm(relDef, name, options = {}) {
|
|
|
501
510
|
}
|
|
502
511
|
lines.push(` end`);
|
|
503
512
|
|
|
513
|
+
lines.push(` local.get $${src}`);
|
|
514
|
+
lines.push(` i32.load offset=0`);
|
|
515
|
+
lines.push(` i32.const 12`);
|
|
516
|
+
lines.push(` i32.ne`);
|
|
517
|
+
lines.push(` if`);
|
|
518
|
+
if (failTarget) {
|
|
519
|
+
lines.push(` br ${failTarget}`);
|
|
520
|
+
} else {
|
|
521
|
+
lines.push(` i32.const 0`);
|
|
522
|
+
lines.push(` i32.const 0`);
|
|
523
|
+
lines.push(` return`);
|
|
524
|
+
}
|
|
525
|
+
lines.push(` end`);
|
|
526
|
+
|
|
504
527
|
lines.push(` local.get $${src}`);
|
|
505
528
|
lines.push(` i32.load offset=4`);
|
|
506
529
|
lines.push(` i32.const ${tagId}`);
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
patternToPropertyList,
|
|
14
14
|
propertyListToPattern,
|
|
15
15
|
Value,
|
|
16
|
-
specializeKVM
|
|
16
|
+
specializeKVM,
|
|
17
|
+
specializeObjectRelations
|
|
17
18
|
} from "@fraczak/k/backend-api.mjs";
|
|
18
19
|
import { intersectPropertyListPatterns } from "@fraczak/k/codecs/runtime/codec.mjs";
|
|
19
20
|
import { propertyListToFilter } from "@fraczak/k/codecs/runtime/show-value.mjs";
|
|
@@ -304,7 +305,8 @@ function compileModule(mainRelName, defs) {
|
|
|
304
305
|
|
|
305
306
|
kvmFunc.name = nameMap.get(name) || cleanName(name);
|
|
306
307
|
cleanCallNames(kvmFunc.body, nameMap);
|
|
307
|
-
|
|
308
|
+
const wasmText = lowerToWasm(kvmFunc, kvmFunc.name);
|
|
309
|
+
wats.push(wasmText);
|
|
308
310
|
}
|
|
309
311
|
|
|
310
312
|
return {
|
|
@@ -611,8 +613,10 @@ async function compileWasmArtifactFromObject(object, { entry = null, inputEnvelo
|
|
|
611
613
|
if (!mainRel) throw new Error(`No main relation (${mainRelName}) defined in object`);
|
|
612
614
|
const inputPattern = getPatternPropertyList(mainRel.typePatternGraph, mainRel.def.patterns[0]);
|
|
613
615
|
const outputPattern = getPatternPropertyList(mainRel.typePatternGraph, mainRel.def.patterns[1]);
|
|
614
|
-
|
|
615
|
-
|
|
616
|
+
const typing = classifyTyping(inputPattern, outputPattern);
|
|
617
|
+
if (typing.program === "polymorphic") {
|
|
618
|
+
const specialized = specializeObjectRelations(object, mainRelName, inputEnvelopePattern);
|
|
619
|
+
defs = { rels: specialized.rels, relAlias: specialized.relAlias, compileStats: specialized.compileStats };
|
|
616
620
|
typingMode = "specialized";
|
|
617
621
|
}
|
|
618
622
|
}
|
|
@@ -635,7 +639,9 @@ async function compileWasmArtifact(
|
|
|
635
639
|
const inputPattern = getPatternPropertyList(mainRel.typePatternGraph, mainRel.def.patterns[0]);
|
|
636
640
|
const outputPattern = getPatternPropertyList(mainRel.typePatternGraph, mainRel.def.patterns[1]);
|
|
637
641
|
if (classifyTyping(inputPattern, outputPattern).program === "polymorphic") {
|
|
638
|
-
|
|
642
|
+
const obj = { format: "k-object", rels: defs.rels, relAlias: defs.relAlias, compileStats: defs.compileStats, main: "__main__" };
|
|
643
|
+
const specialized = specializeObjectRelations(obj, "__main__", inputEnvelopePattern);
|
|
644
|
+
defs = { rels: specialized.rels, relAlias: specialized.relAlias, compileStats: specialized.compileStats };
|
|
639
645
|
typingMode = "specialized";
|
|
640
646
|
}
|
|
641
647
|
}
|
|
@@ -723,20 +729,18 @@ function readArenaValue(exports, ptr, pattern, patternNodeId, patternPropertyLis
|
|
|
723
729
|
|
|
724
730
|
if (patternNode.kind === NODE_KIND.OPEN_PRODUCT || patternNode.kind === NODE_KIND.CLOSED_PRODUCT) {
|
|
725
731
|
const N = view.getUint32(frame.ptr + 4, true);
|
|
726
|
-
if (N !== patternNode.edges.length) {
|
|
727
|
-
throw new Error(`Cannot decode product pointer ${frame.ptr}: arena field count ${N} does not match output pattern`);
|
|
728
|
-
}
|
|
729
732
|
const product = {};
|
|
730
733
|
frame.assign(Value.product(product, patternForNode(patternPropertyList, frame.patternNodeId)));
|
|
731
734
|
for (let i = N - 1; i >= 0; i--) {
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
+
const tagId = view.getUint32(frame.ptr + 8 + 4 * i, true);
|
|
736
|
+
const label = tags.getTag(tagId) || patternNode.edges[i]?.label;
|
|
737
|
+
const edge = patternNode.edges.find((candidate) => candidate.label === label);
|
|
738
|
+
const childPtr = view.getUint32(frame.ptr + 8 + 4 * N + 4 * i, true);
|
|
735
739
|
stack.push({
|
|
736
740
|
ptr: wasmPtr(childPtr),
|
|
737
|
-
patternNodeId: edge
|
|
741
|
+
patternNodeId: edge?.target ?? null,
|
|
738
742
|
assign(value) {
|
|
739
|
-
product[
|
|
743
|
+
product[label] = value;
|
|
740
744
|
}
|
|
741
745
|
});
|
|
742
746
|
}
|
|
@@ -787,8 +791,9 @@ function writeValueToArena(exports, value, pattern, patternNodeId, arenaValues,
|
|
|
787
791
|
view.setUint32(frame.ptr, 8 + 8 * N, true);
|
|
788
792
|
view.setUint32(frame.ptr + 4, N, true);
|
|
789
793
|
for (let i = 0; i < N; i++) {
|
|
794
|
+
const tagId = tags.getId(frame.fields[i].label);
|
|
790
795
|
const offset = 8 + 4 * N + 4 * i;
|
|
791
|
-
view.setUint32(frame.ptr + 8 + 4 * i,
|
|
796
|
+
view.setUint32(frame.ptr + 8 + 4 * i, tagId, true);
|
|
792
797
|
view.setUint32(frame.ptr + offset, frame.childPtrs[i], true);
|
|
793
798
|
}
|
|
794
799
|
arenaValues.set(frame.ptr, valueWithPatternNode(frame.value, patternPropertyList, frame.patternNodeId));
|
|
@@ -820,6 +825,13 @@ function writeValueToArena(exports, value, pattern, patternNodeId, arenaValues,
|
|
|
820
825
|
? Object.keys(frame.value.product)
|
|
821
826
|
.sort()
|
|
822
827
|
.map((label) => ({ label, patternNodeId: frame.patternNodeId }))
|
|
828
|
+
: isOpenProduct
|
|
829
|
+
? Object.keys(frame.value.product)
|
|
830
|
+
.sort()
|
|
831
|
+
.map((label) => {
|
|
832
|
+
const edge = patternNode.edges.find((e) => e.label === label);
|
|
833
|
+
return { label, patternNodeId: edge ? edge.target : null };
|
|
834
|
+
})
|
|
823
835
|
: patternNode.edges.map((edge) => ({ label: edge.label, patternNodeId: edge.target }));
|
|
824
836
|
const fieldLabels = new Set(fields.map(({ label }) => label));
|
|
825
837
|
if (isClosedProduct) {
|
|
@@ -829,9 +841,17 @@ function writeValueToArena(exports, value, pattern, patternNodeId, arenaValues,
|
|
|
829
841
|
}
|
|
830
842
|
}
|
|
831
843
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
844
|
+
if (isOpenProduct) {
|
|
845
|
+
for (const edge of patternNode.edges) {
|
|
846
|
+
if (!Object.hasOwn(frame.value.product, edge.label)) {
|
|
847
|
+
throw new Error(`Product field '${edge.label}' is required by input pattern node ${frame.patternNodeId}`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
} else {
|
|
851
|
+
for (const { label } of fields) {
|
|
852
|
+
if (!Object.hasOwn(frame.value.product, label)) {
|
|
853
|
+
throw new Error(`Product field '${label}' is required by input pattern node ${frame.patternNodeId}`);
|
|
854
|
+
}
|
|
835
855
|
}
|
|
836
856
|
}
|
|
837
857
|
|
|
@@ -841,6 +861,7 @@ function writeValueToArena(exports, value, pattern, patternNodeId, arenaValues,
|
|
|
841
861
|
finishProduct: true,
|
|
842
862
|
value: frame.value,
|
|
843
863
|
patternNodeId: frame.patternNodeId,
|
|
864
|
+
fields,
|
|
844
865
|
ptr,
|
|
845
866
|
childPtrs,
|
|
846
867
|
assign: frame.assign
|
|
@@ -200,8 +200,7 @@ export function readArenaValue(exports, rootPtr, pattern, rootPatternNodeId, pat
|
|
|
200
200
|
|
|
201
201
|
for (let i = N - 1; i >= 0; i--) {
|
|
202
202
|
const edge = patternNode.edges[i];
|
|
203
|
-
const
|
|
204
|
-
const childPtr = view.getUint32(ptr + offsetVal, true);
|
|
203
|
+
const childPtr = view.getUint32(ptr + 8 + 4 * N + 4 * i, true);
|
|
205
204
|
const label = edge.label;
|
|
206
205
|
stack.push({
|
|
207
206
|
ptr: childPtr,
|
|
@@ -253,14 +252,14 @@ export function writeValueToArena(exports, rootValue, pattern, rootPatternNodeId
|
|
|
253
252
|
const frame = stack.pop();
|
|
254
253
|
|
|
255
254
|
if (frame.finishProduct) {
|
|
256
|
-
const { ptr, totalSize, N, childPtrs, assign } = frame;
|
|
255
|
+
const { ptr, totalSize, N, keys, childPtrs, assign } = frame;
|
|
257
256
|
const view = new DataView(exports.memory.buffer);
|
|
258
257
|
view.setUint32(ptr, totalSize, true);
|
|
259
258
|
view.setUint32(ptr + 4, N, true);
|
|
260
259
|
|
|
261
260
|
for (let i = 0; i < N; i++) {
|
|
262
261
|
const offsetVal = 8 + 4 * N + 4 * i;
|
|
263
|
-
view.setUint32(ptr + 8 + 4 * i,
|
|
262
|
+
view.setUint32(ptr + 8 + 4 * i, getTagId(keys[i]), true);
|
|
264
263
|
view.setUint32(ptr + offsetVal, childPtrs[i], true);
|
|
265
264
|
}
|
|
266
265
|
assign(ptr);
|
|
@@ -294,6 +293,7 @@ export function writeValueToArena(exports, rootValue, pattern, rootPatternNodeId
|
|
|
294
293
|
ptr,
|
|
295
294
|
totalSize,
|
|
296
295
|
N,
|
|
296
|
+
keys,
|
|
297
297
|
childPtrs,
|
|
298
298
|
assign
|
|
299
299
|
});
|