@fraczak/k 7.0.1 → 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.
Files changed (53) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/DOCS/BIG_PICTURE.md +5 -5
  3. package/DOCS/FILE_FORMATS.md +5 -5
  4. package/DOCS/OBJECT_FILE_AND_PATTERN.md +9 -9
  5. package/DOCS/PATTERNS.md +3 -3
  6. package/DOCS/PROFILING_AND_TRACING.md +60 -72
  7. package/DOCS/REPL.md +3 -3
  8. package/DOCS/book/03-types-as-finite-automata.md +4 -4
  9. package/DOCS/book/06-values-in-memory.md +9 -9
  10. package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
  11. package/DOCS/book/09-compiler-architecture.md +2 -2
  12. package/DOCS/book/10-from-ast-to-intermediate-representation.md +2 -2
  13. package/DOCS/book/14-canonical-serialization.md +1 -1
  14. package/Examples/arithmetics.k +2 -2
  15. package/Examples/bnat.k +1 -1
  16. package/Examples/karatsuba-mult.k +2 -2
  17. package/README.md +3 -8
  18. package/backends/llvm/runtime/krt.c +33 -0
  19. package/backends/llvm/runtime/krt.h +1 -0
  20. package/backends/llvm/src/executable.mjs +4 -4
  21. package/backends/llvm/src/kvm2llvm.mjs +40 -18
  22. package/backends/llvm/src/llvm.mjs +12 -11
  23. package/backends/llvm/tests/perf-support.mjs +1 -55
  24. package/backends/llvm/tests/test-llvm.mjs +3 -3
  25. package/backends/wasm/src/wasm.mjs +2 -1
  26. package/backends/wasm/tests/test-wasm.mjs +5 -5
  27. package/browser/vfs-data.mjs +5 -5
  28. package/core.k +8 -8
  29. package/harness/format.mjs +2 -2
  30. package/harness/registry.mjs +0 -2
  31. package/kir.mjs +142 -56
  32. package/package.json +1 -5
  33. package/scripts/benchmark-idempotency.mjs +12 -8
  34. package/scripts/benchmark.mjs +5 -9
  35. package/scripts/build-repl-html.mjs +2 -2
  36. package/scripts/perf-ieee.mjs +17 -118
  37. package/scripts/perf-int.mjs +313 -318
  38. package/scripts/perf-multiplication.mjs +10 -20
  39. package/scripts/perf-poly.mjs +29 -183
  40. package/tests/code-derivation/pattern-envelope.mjs +4 -4
  41. package/tests/test-kvm-polymorphic.mjs +2 -2
  42. package/backends/arm64/README.md +0 -103
  43. package/backends/arm64/bin/k-arm64-compile.mjs +0 -93
  44. package/backends/arm64/bin/k-arm64-run.mjs +0 -58
  45. package/backends/arm64/bin/k-arm64.mjs +0 -88
  46. package/backends/arm64/scripts/run-tests.mjs +0 -57
  47. package/backends/arm64/src/arm64.mjs +0 -229
  48. package/backends/arm64/src/cli.mjs +0 -155
  49. package/backends/arm64/src/kvm2arm64.mjs +0 -906
  50. package/backends/arm64/src/runtime.c +0 -744
  51. package/backends/arm64/tests/integration.sh +0 -112
  52. package/backends/arm64/tests/test-arm64.mjs +0 -240
  53. package/harness/adapters/arm64.mjs +0 -105
@@ -1,906 +0,0 @@
1
- import {
2
- exportPatternGraph,
3
- lowerToKVM,
4
- patternToPropertyList
5
- } from "../../../backend-api.mjs";
6
-
7
- const tagMap = new Map();
8
- let nextTagId = 1;
9
-
10
- export function getTagId(tag) {
11
- if (!tagMap.has(tag)) {
12
- tagMap.set(tag, nextTagId++);
13
- }
14
- return tagMap.get(tag);
15
- }
16
-
17
- export function getTagFromId(tagId) {
18
- for (const [tag, id] of tagMap.entries()) {
19
- if (id === tagId) return tag;
20
- }
21
- return null;
22
- }
23
-
24
- export function getTagEntries() {
25
- return [...tagMap.entries()]
26
- .map(([tag, id]) => ({ tag, id }))
27
- .sort((a, b) => a.id - b.id);
28
- }
29
-
30
- export function resetTagIds() {
31
- tagMap.clear();
32
- nextTagId = 1;
33
- }
34
-
35
- export function registerPatternTags(pattern) {
36
- if (!Array.isArray(pattern)) return;
37
- for (const node of pattern) {
38
- if (!Array.isArray(node)) continue;
39
- const [kind, edges] = node;
40
- if (kind === "open-union" || kind === "closed-union") {
41
- if (Array.isArray(edges)) {
42
- for (const edge of edges) {
43
- if (Array.isArray(edge) && edge[0] != null) {
44
- getTagId(String(edge[0]));
45
- }
46
- }
47
- }
48
- }
49
- }
50
- }
51
-
52
- export function collectAllTags(kvmProgram = {}, inputPattern = null, outputPattern = null) {
53
- if (inputPattern) registerPatternTags(inputPattern);
54
- if (outputPattern) registerPatternTags(outputPattern);
55
-
56
- function scanInsts(insts) {
57
- if (!Array.isArray(insts)) return;
58
- for (const inst of insts) {
59
- if (inst.tag) getTagId(String(inst.tag));
60
- if (inst.pattern) registerPatternTags(inst.pattern);
61
- if (inst.branches) {
62
- for (const br of inst.branches) {
63
- scanInsts(br.body);
64
- }
65
- }
66
- }
67
- }
68
-
69
- for (const func of Object.values(kvmProgram)) {
70
- if (func.body) scanInsts(func.body);
71
- if (func.inputPattern) registerPatternTags(func.inputPattern);
72
- if (func.outputPattern) registerPatternTags(func.outputPattern);
73
- }
74
- }
75
-
76
- function cloneInstructions(insts) {
77
- return insts.map(inst => {
78
- const cloned = { ...inst };
79
- if (inst.branches) {
80
- cloned.branches = inst.branches.map(br => ({
81
- label: br.label,
82
- body: cloneInstructions(br.body)
83
- }));
84
- }
85
- return cloned;
86
- });
87
- }
88
-
89
- function renameRegisters(insts, prefix) {
90
- const rename = (r) => {
91
- if (!r) return r;
92
- if (r === "%in" || r === "in") return r;
93
- if (r.startsWith("%")) return "%" + prefix + r.slice(1);
94
- if (r.startsWith("$")) return "$" + prefix + r.slice(1);
95
- return r;
96
- };
97
-
98
- for (const inst of insts) {
99
- if (inst.dest) inst.dest = rename(inst.dest);
100
- if (inst.src) inst.src = rename(inst.src);
101
- if (inst.branches) {
102
- inst.branches.forEach((branch, i) => {
103
- const nextPrefix = `${prefix}b${i}_`;
104
- renameRegisters(branch.body, nextPrefix);
105
- });
106
- }
107
- }
108
- }
109
-
110
- export function lowerToARM64(relDef, name, options = {}) {
111
- const kvmFuncOriginal = relDef.body ? relDef : lowerToKVM(relDef, name, options);
112
- const kvmFunc = {
113
- ...kvmFuncOriginal,
114
- body: cloneInstructions(kvmFuncOriginal.body)
115
- };
116
- renameRegisters(kvmFunc.body, "");
117
- const typePatternGraph = relDef.typePatternGraph || kvmFunc.typePatternGraph;
118
-
119
- const cleanReg = (rName) => rName.replace("%", "").replace("$", "");
120
- const registerPatterns = new Map();
121
- if (Array.isArray(kvmFunc.inputPattern)) {
122
- registerPatterns.set("in", kvmFunc.inputPattern);
123
- }
124
-
125
- const mappedReg = (rName, inputMap = {}) => {
126
- const raw = cleanReg(rName);
127
- return inputMap[raw] || raw;
128
- };
129
-
130
- const setPattern = (rName, pattern) => {
131
- if (rName && Array.isArray(pattern)) {
132
- registerPatterns.set(cleanReg(rName), pattern);
133
- }
134
- };
135
-
136
- const getPattern = (rName, inputMap = {}) => registerPatterns.get(mappedReg(rName, inputMap));
137
-
138
- const getProductEdges = (inst, inputMap) => {
139
- if (typePatternGraph && inst.exp?.patterns?.[0] != null) {
140
- const inputPatternId = inst.exp.patterns[0];
141
- const inputPatternNodeId = typePatternGraph.find(inputPatternId);
142
- const inputPropertyList = patternToPropertyList(exportPatternGraph(typePatternGraph, inputPatternNodeId));
143
- return inputPropertyList[0][1];
144
- }
145
-
146
- const mappedSrc = mappedReg(inst.src, inputMap);
147
- const inputPropertyList = getPattern(inst.src, inputMap);
148
- const inputRoot = inputPropertyList?.[0];
149
- if (Array.isArray(inputRoot) && Array.isArray(inputRoot[1])) {
150
- if (inputRoot[0] === "open-product" || inputRoot[0] === "closed-product") {
151
- return inputRoot[1];
152
- }
153
- }
154
- // Fallback: check function's inputPattern only if inst.src represents the input argument
155
- if (mappedSrc === "in" && Array.isArray(kvmFunc.inputPattern?.[0]?.[1])) {
156
- return kvmFunc.inputPattern[0][1];
157
- }
158
- throw new Error(`ARM64 compiler: cannot infer product edges for field '${inst.label}'`);
159
- };
160
-
161
- const registers = new Set(["in", "call_val", "call_ok"]);
162
- const inputProductEdges = Array.isArray(kvmFunc.inputPattern?.[0]?.[1])
163
- && (kvmFunc.inputPattern[0][0] === "open-product" || kvmFunc.inputPattern[0][0] === "closed-product")
164
- ? kvmFunc.inputPattern[0][1]
165
- : null;
166
- const inputProductFields = inputProductEdges
167
- ? inputProductEdges.map(([label], index) => ({ label, index, local: `tail_in_${index}` }))
168
- : [];
169
- const inputProductFieldByLabel = new Map(inputProductFields.map((field) => [field.label, field]));
170
-
171
- const collectReg = (reg) => {
172
- if (reg && reg.startsWith("%")) {
173
- registers.add(cleanReg(reg));
174
- }
175
- };
176
-
177
- function collectAllRegisters(insts) {
178
- for (const inst of insts) {
179
- collectReg(inst.dest);
180
- collectReg(inst.src);
181
- if (inst.src) {
182
- registers.add(`${cleanReg(inst.src)}_materialized`);
183
- }
184
- if (inst.branches) {
185
- for (const branch of inst.branches) {
186
- collectAllRegisters(branch.body);
187
- }
188
- }
189
- }
190
- }
191
- collectAllRegisters(kvmFunc.body);
192
- registers.add("in_materialized");
193
- for (const field of inputProductFields) {
194
- registers.add(field.local);
195
- }
196
-
197
- let nextUnionId = 0;
198
- const tailLoopLabel = `.Ltail_loop_${name}`;
199
- const funcEpilogueLabel = `.Lepilogue_${name}`;
200
- const funcFailLabel = `.Lfunc_fail_${name}`;
201
-
202
- const mapsToReg = (reg, inputMap) => inputMap[cleanReg(reg)] || cleanReg(reg);
203
-
204
- function tailValueAfter(insts, startIndex, valueReg, inputMap, returnTarget, tailReturnTargets) {
205
- let current = valueReg;
206
- for (let i = startIndex; i < insts.length; i++) {
207
- const inst = insts[i];
208
- if (inst.op === "guard_pattern" || inst.op === "guard_code" || inst.op === "id") {
209
- if (mapsToReg(inst.src, inputMap) !== current) return false;
210
- current = cleanReg(inst.dest);
211
- continue;
212
- }
213
- if (inst.op === "return" && mapsToReg(inst.src, inputMap) === current) {
214
- return returnTarget ? tailReturnTargets.has(returnTarget) : true;
215
- }
216
- return false;
217
- }
218
- return false;
219
- }
220
-
221
- function isSelfTailCall(insts, index, inputMap, returnTarget, tailReturnTargets) {
222
- if (options.trace) return false;
223
- const inst = insts[index];
224
- if (inst.op !== "call" || inst.func !== name) return false;
225
- const dest = cleanReg(inst.dest);
226
- return tailValueAfter(insts, index + 1, dest, inputMap, returnTarget, tailReturnTargets);
227
- }
228
-
229
- function selfTailCallAfterProduct(insts, index, productDest, inputMap, returnTarget, tailReturnTargets) {
230
- if (options.trace || inputProductFields.length === 0) return null;
231
- const callInst = insts[index + 1];
232
- if (!callInst || callInst.op !== "call" || callInst.func !== name) return null;
233
- if (mapsToReg(callInst.src, inputMap) !== productDest) return null;
234
- const callDest = cleanReg(callInst.dest);
235
- if (!tailValueAfter(insts, index + 2, callDest, inputMap, returnTarget, tailReturnTargets)) return null;
236
- return callInst;
237
- }
238
-
239
- // Pre-scan all union branches to allocate union_mark slots in registers
240
- let scanUnionId = 0;
241
- function preScanUnions(insts) {
242
- for (const inst of insts) {
243
- if (inst.op === "union") {
244
- registers.add(`union_mark_${scanUnionId++}`);
245
- }
246
- if (inst.branches) {
247
- for (const branch of inst.branches) {
248
- preScanUnions(branch.body);
249
- }
250
- }
251
- }
252
- }
253
- preScanUnions(kvmFunc.body);
254
-
255
- // Pre-scan all product branches to allocate fieldTmp slots in registers
256
- function preScanProducts(insts) {
257
- for (const inst of insts) {
258
- if (inst.op === "product") {
259
- const dest = cleanReg(inst.dest);
260
- for (let i = 0; i < inst.branches.length; i++) {
261
- registers.add(`${dest}_f${i}`);
262
- }
263
- }
264
- if (inst.branches) {
265
- for (const branch of inst.branches) {
266
- preScanProducts(branch.body);
267
- }
268
- }
269
- }
270
- }
271
- preScanProducts(kvmFunc.body);
272
-
273
- // Assign stack offsets to all registers
274
- // Stack frame layout:
275
- // [sp, #0]: saved FP (x29)
276
- // [sp, #8]: saved LR (x30)
277
- // [sp, #16]: first virtual register...
278
- const regList = [...registers];
279
- const regOffsets = new Map();
280
- regList.forEach((reg, index) => {
281
- regOffsets.set(reg, 16 + 8 * index);
282
- });
283
-
284
- const rawFrameSize = 16 + 8 * regList.length;
285
- const frameSize = (rawFrameSize + 15) & ~15; // 16-byte aligned
286
-
287
- const regOffset = (rName) => {
288
- const clean = cleanReg(rName);
289
- if (!regOffsets.has(clean)) {
290
- throw new Error(`ARM64 compiler: unknown register '${clean}'`);
291
- }
292
- return regOffsets.get(clean);
293
- };
294
-
295
- const ldrReg = (reg, rName, scratch = "x12") => {
296
- const off = regOffset(rName);
297
- if (off <= 32760) {
298
- return `ldr ${reg}, [sp, #${off}]`;
299
- }
300
- return `movz ${scratch}, #${off & 0xffff}\n movk ${scratch}, #${(off >>> 16) & 0xffff}, lsl #16\n ldr ${reg}, [sp, ${scratch}]`;
301
- };
302
-
303
- const strReg = (reg, rName, scratch = "x12") => {
304
- const off = regOffset(rName);
305
- if (off <= 32760) {
306
- return `str ${reg}, [sp, #${off}]`;
307
- }
308
- return `movz ${scratch}, #${off & 0xffff}\n movk ${scratch}, #${(off >>> 16) & 0xffff}, lsl #16\n str ${reg}, [sp, ${scratch}]`;
309
- };
310
-
311
- const allocStack = (scratch = "x9") => {
312
- if (frameSize <= 4095) {
313
- return ` sub sp, sp, #${frameSize}`;
314
- }
315
- return ` movz ${scratch}, #${frameSize & 0xffff}\n movk ${scratch}, #${(frameSize >>> 16) & 0xffff}, lsl #16\n sub sp, sp, ${scratch}`;
316
- };
317
-
318
- const freeStack = (scratch = "x9") => {
319
- if (frameSize <= 4095) {
320
- return ` add sp, sp, #${frameSize}`;
321
- }
322
- return ` movz ${scratch}, #${frameSize & 0xffff}\n movk ${scratch}, #${(frameSize >>> 16) & 0xffff}, lsl #16\n add sp, sp, ${scratch}`;
323
- };
324
-
325
- const addArenaBump = (size, scratch = "x10") => {
326
- if (size <= 4095) {
327
- return ` add x19, x19, #${size}`;
328
- }
329
- return ` movz ${scratch}, #${size & 0xffff}\n movk ${scratch}, #${(size >>> 16) & 0xffff}, lsl #16\n add x19, x19, ${scratch}`;
330
- };
331
-
332
- function initInputProductLocals() {
333
- const lines = [];
334
- for (const field of inputProductFields) {
335
- const fieldIndex = inputProductEdges.findIndex(([label]) => label === field.label);
336
- lines.push(` // cache input field ${field.label} in ${field.local}`);
337
- lines.push(` ${ldrReg("x9", "in")}`);
338
- lines.push(` cbz x9, ${funcFailLabel}`);
339
- lines.push(` ldr x10, [x9, #${8 + 8 * fieldIndex}]`);
340
- lines.push(` ${strReg("x10", field.local)}`);
341
- }
342
- return lines.join("\n");
343
- }
344
-
345
- function materializeInputProduct(lines, dest) {
346
- const N = inputProductFields.length;
347
- const totalSize = (8 + 8 * N + 15) & ~15;
348
- lines.push(` // materialize cached input product into ${dest}`);
349
- lines.push(` mov x9, x19`);
350
- lines.push(addArenaBump(totalSize));
351
- lines.push(` movz w10, #${totalSize}`);
352
- lines.push(` movk w10, #0x0001, lsl #16`);
353
- lines.push(` str w10, [x9, #0]`);
354
- lines.push(` mov w10, #${N}`);
355
- lines.push(` str w10, [x9, #4]`);
356
- for (const field of inputProductFields) {
357
- const offsetVal = 8 + 8 * field.index;
358
- lines.push(` ${ldrReg("x11", field.local)}`);
359
- lines.push(` str x11, [x9, #${offsetVal}]`);
360
- }
361
- lines.push(` ${strReg("x9", dest)}`);
362
- }
363
-
364
- function compileInstructions(
365
- insts,
366
- inputMap = { "in": "in" },
367
- returnTarget = null,
368
- failTarget = null,
369
- tailReturnTargets = new Set(),
370
- inputAliases = new Set(["in"])
371
- ) {
372
- const lines = [];
373
- for (let index = 0; index < insts.length; index++) {
374
- const inst = insts[index];
375
- switch (inst.op) {
376
- case "guard_pattern":
377
- case "guard_code":
378
- case "id": {
379
- const dest = cleanReg(inst.dest);
380
- const rawSrc = cleanReg(inst.src);
381
- const src = inputMap[rawSrc] || rawSrc;
382
- lines.push(` // id/guard: ${dest} = ${src}`);
383
- lines.push(` ${ldrReg("x9", src)}`);
384
- lines.push(` ${strReg("x9", dest)}`);
385
- setPattern(dest, inst.pattern || getPattern(inst.src, inputMap));
386
- if (inputAliases.has(src)) {
387
- inputAliases.add(dest);
388
- } else {
389
- inputAliases.delete(dest);
390
- }
391
- break;
392
- }
393
- case "fail": {
394
- lines.push(` // fail`);
395
- if (failTarget) {
396
- lines.push(` b ${failTarget}`);
397
- } else {
398
- lines.push(` b ${funcFailLabel}`);
399
- }
400
- break;
401
- }
402
- case "return": {
403
- const rawSrc = cleanReg(inst.src);
404
- const src = inputMap[rawSrc] || rawSrc;
405
- const valueSrc = inputAliases.has(src) && inputProductFields.length > 0
406
- ? `${src}_materialized`
407
- : src;
408
- if (valueSrc !== src) {
409
- materializeInputProduct(lines, valueSrc);
410
- }
411
- lines.push(` // return: ${valueSrc}`);
412
- if (returnTarget) {
413
- lines.push(` ${ldrReg("x9", valueSrc)}`);
414
- lines.push(` ${strReg("x9", returnTarget)}`);
415
- setPattern(returnTarget, getPattern(inst.src, inputMap));
416
- } else {
417
- lines.push(` mov x0, #0`);
418
- lines.push(` ${ldrReg("x1", valueSrc)}`);
419
- lines.push(` b ${funcEpilogueLabel}`);
420
- }
421
- break;
422
- }
423
- case "project_field": {
424
- const dest = cleanReg(inst.dest);
425
- const rawSrc = cleanReg(inst.src);
426
- const src = inputMap[rawSrc] || rawSrc;
427
- const inputField = inputAliases.has(src) ? inputProductFieldByLabel.get(inst.label) : null;
428
-
429
- if (inputField) {
430
- lines.push(` // project cached input field ${inst.label} from ${inputField.local} to ${dest}`);
431
- lines.push(` ${ldrReg("x9", inputField.local)}`);
432
- lines.push(` ${strReg("x9", dest)}`);
433
- setPattern(dest, inst.pattern);
434
- break;
435
- }
436
-
437
- const edges = getProductEdges(inst, inputMap);
438
- const fieldIndex = edges.findIndex(([label]) => label === inst.label);
439
- if (fieldIndex === -1) {
440
- throw new Error(`ARM64 compiler: field '${inst.label}' not found in input pattern`);
441
- }
442
-
443
- const currentFail = failTarget || funcFailLabel;
444
- lines.push(` // project_field ${inst.label} (index ${fieldIndex}) from ${src} to ${dest}`);
445
- lines.push(` ${ldrReg("x9", src)}`);
446
- lines.push(` cbz x9, ${currentFail}`);
447
- lines.push(` ldr x10, [x9, #${8 + 8 * fieldIndex}]`);
448
- lines.push(` ${strReg("x10", dest)}`);
449
- setPattern(dest, inst.pattern);
450
- break;
451
- }
452
- case "product": {
453
- const dest = cleanReg(inst.dest);
454
- const rawSrc = cleanReg(inst.src);
455
- const src = inputMap[rawSrc] || rawSrc;
456
- const tailCall = selfTailCallAfterProduct(insts, index, dest, inputMap, returnTarget, tailReturnTargets);
457
- const sortedBranches = [...inst.branches].sort((a, b) => {
458
- if (a.label < b.label) return -1;
459
- if (a.label > b.label) return 1;
460
- return 0;
461
- });
462
- const N = sortedBranches.length;
463
-
464
- lines.push(` // product creation for ${dest} (${N} fields)`);
465
-
466
- if (tailCall) {
467
- if (N !== inputProductFields.length) {
468
- throw new Error(`ARM64 compiler: tail-call ${name} input product field count mismatch`);
469
- }
470
- for (let i = 0; i < N; i++) {
471
- const branch = sortedBranches[i];
472
- if (!inputProductFieldByLabel.has(branch.label)) {
473
- throw new Error(`ARM64 compiler: tail-call ${name} input product is missing field '${branch.label}'`);
474
- }
475
- const fieldTmp = `${dest}_f${i}`;
476
-
477
- lines.push(` // tail-call field ${branch.label}`);
478
- const branchAsm = compileInstructions(
479
- branch.body,
480
- { ...inputMap, "in": src },
481
- fieldTmp,
482
- failTarget,
483
- tailReturnTargets,
484
- new Set(inputAliases)
485
- );
486
- lines.push(branchAsm);
487
- }
488
- for (let i = 0; i < N; i++) {
489
- const branch = sortedBranches[i];
490
- const fieldTmp = `${dest}_f${i}`;
491
- const inputField = inputProductFieldByLabel.get(branch.label);
492
- lines.push(` ${ldrReg("x9", fieldTmp)}`);
493
- lines.push(` ${strReg("x9", inputField.local)}`);
494
- }
495
- lines.push(` b ${tailLoopLabel}`);
496
- index = insts.length;
497
- break;
498
- }
499
-
500
- for (let i = 0; i < N; i++) {
501
- const branch = sortedBranches[i];
502
- const fieldTmp = `${dest}_f${i}`;
503
-
504
- lines.push(` // branch ${i} for field ${branch.label || i}`);
505
- const branchAsm = compileInstructions(
506
- branch.body,
507
- { ...inputMap, "in": src },
508
- fieldTmp,
509
- failTarget,
510
- tailReturnTargets,
511
- new Set(inputAliases)
512
- );
513
- lines.push(branchAsm);
514
- }
515
-
516
- const totalSize = (8 + 8 * N + 15) & ~15;
517
- lines.push(` // allocate product ${dest} (size ${totalSize} bytes)`);
518
- lines.push(` mov x9, x19`);
519
- lines.push(addArenaBump(totalSize));
520
- lines.push(` movz w10, #${totalSize}`);
521
- lines.push(` movk w10, #0x0001, lsl #16`);
522
- lines.push(` str w10, [x9, #0]`);
523
- lines.push(` mov w10, #${N}`);
524
- lines.push(` str w10, [x9, #4]`);
525
-
526
- for (let i = 0; i < N; i++) {
527
- const offsetVal = 8 + 8 * i;
528
- const fieldTmp = `${dest}_f${i}`;
529
- lines.push(` ${ldrReg("x11", fieldTmp)}`);
530
- lines.push(` str x11, [x9, #${offsetVal}]`);
531
- }
532
- lines.push(` ${strReg("x9", dest)}`);
533
- if (inst.pattern) {
534
- setPattern(dest, inst.pattern);
535
- } else {
536
- const productEdges = sortedBranches.map((branch, i) => [branch.label || String(i), i + 1]);
537
- setPattern(dest, [["closed-product", productEdges]]);
538
- }
539
- break;
540
- }
541
- case "make_variant": {
542
- const dest = cleanReg(inst.dest);
543
- const rawSrc = cleanReg(inst.src);
544
- const src = inputMap[rawSrc] || rawSrc;
545
- const tagId = getTagId(inst.tag);
546
-
547
- lines.push(` // make_variant ${inst.tag} (tagId ${tagId}) into ${dest}`);
548
- lines.push(` mov x9, x19`);
549
- lines.push(` add x19, x19, #16`);
550
- lines.push(` movz w10, #16`);
551
- lines.push(` movk w10, #0x0002, lsl #16`);
552
- lines.push(` str w10, [x9, #0]`);
553
- lines.push(` mov w10, #${tagId}`);
554
- lines.push(` str w10, [x9, #4]`);
555
- lines.push(` ${ldrReg("x11", src)}`);
556
- lines.push(` str x11, [x9, #8]`);
557
- lines.push(` ${strReg("x9", dest)}`);
558
- setPattern(dest, inst.pattern);
559
- break;
560
- }
561
- case "project_variant": {
562
- const dest = cleanReg(inst.dest);
563
- const rawSrc = cleanReg(inst.src);
564
- const src = inputMap[rawSrc] || rawSrc;
565
- const tagId = getTagId(inst.tag);
566
- const currentFail = failTarget || funcFailLabel;
567
-
568
- lines.push(` // project_variant ${inst.tag} (tagId ${tagId}) from ${src} to ${dest}`);
569
- lines.push(` ${ldrReg("x9", src)}`);
570
- lines.push(` cbz x9, ${currentFail}`);
571
- lines.push(` ldr w10, [x9, #4]`);
572
- lines.push(` cmp w10, #${tagId}`);
573
- lines.push(` b.ne ${currentFail}`);
574
- lines.push(` ldr x11, [x9, #8]`);
575
- lines.push(` ${strReg("x11", dest)}`);
576
- setPattern(dest, inst.pattern);
577
- break;
578
- }
579
- case "union": {
580
- const dest = cleanReg(inst.dest);
581
- const rawSrc = cleanReg(inst.src);
582
- const src = inputMap[rawSrc] || rawSrc;
583
- const N = inst.branches.length;
584
- const branchTailReturnTargets = tailValueAfter(insts, index + 1, dest, inputMap, returnTarget, tailReturnTargets)
585
- ? new Set([...tailReturnTargets, dest])
586
- : tailReturnTargets;
587
-
588
- const unionId = nextUnionId++;
589
- const arenaMark = `union_mark_${unionId}`;
590
-
591
- lines.push(` // union choice for ${dest} (${N} branches)`);
592
- lines.push(` // save arena bump mark`);
593
- lines.push(` ${strReg("x19", arenaMark)}`);
594
-
595
- for (let i = 0; i < N; i++) {
596
- const branch = inst.branches[i];
597
- const isLast = (i === N - 1);
598
- const branchFail = isLast ? (failTarget || funcFailLabel) : `.Lb${i}_fail_${name}_union_${unionId}`;
599
-
600
- lines.push(` // union branch ${i}`);
601
- const branchAsm = compileInstructions(
602
- branch.body,
603
- { ...inputMap, "in": src },
604
- dest,
605
- branchFail,
606
- branchTailReturnTargets,
607
- new Set(inputAliases)
608
- );
609
- lines.push(branchAsm);
610
- lines.push(` b .Lunion_done_${name}_${unionId}`);
611
-
612
- if (!isLast) {
613
- lines.push(`${branchFail}:`);
614
- lines.push(` // branch ${i} failed: rewind arena`);
615
- lines.push(` ${ldrReg("x19", arenaMark)}`);
616
- }
617
- }
618
-
619
- lines.push(`.Lunion_done_${name}_${unionId}:`);
620
- setPattern(dest, inst.pattern);
621
- break;
622
- }
623
- case "call": {
624
- const dest = cleanReg(inst.dest);
625
- const rawSrc = cleanReg(inst.src);
626
- const src = inputMap[rawSrc] || rawSrc;
627
- const callSrc = inputAliases.has(src) && inputProductFields.length > 0
628
- ? `${src}_materialized`
629
- : src;
630
-
631
- if (isSelfTailCall(insts, index, inputMap, returnTarget, tailReturnTargets)) {
632
- lines.push(` // tail-call ${inst.func}`);
633
- if (callSrc !== src) {
634
- materializeInputProduct(lines, callSrc);
635
- }
636
- lines.push(` ${ldrReg("x9", callSrc)}`);
637
- lines.push(` ${strReg("x9", "in")}`);
638
- if (inputProductFields.length > 0) {
639
- lines.push(initInputProductLocals());
640
- }
641
- lines.push(` b ${tailLoopLabel}`);
642
- setPattern("in", kvmFunc.inputPattern);
643
- setPattern(dest, kvmFunc.outputPattern || inst.pattern);
644
- break;
645
- }
646
-
647
- const currentFail = failTarget || funcFailLabel;
648
- lines.push(` // call ${inst.func}`);
649
- if (callSrc !== src) {
650
- materializeInputProduct(lines, callSrc);
651
- }
652
- lines.push(` ${ldrReg("x0", callSrc)}`);
653
- lines.push(` bl ${inst.func}`);
654
- lines.push(` cbnz x0, ${currentFail}`);
655
- lines.push(` ${strReg("x1", dest)}`);
656
- const targetFunc = options.kvmProgram?.[inst.func];
657
- const callOutputPattern = inst.pattern || targetFunc?.outputPattern;
658
- setPattern(dest, callOutputPattern);
659
- break;
660
- }
661
- default:
662
- throw new Error(`ARM64 compiler: unsupported instruction op '${inst.op}'`);
663
- }
664
- }
665
- return lines.join("\n");
666
- }
667
-
668
- const asm = [
669
- ` .global ${name}`,
670
- ` .type ${name}, %function`,
671
- ` .p2align 3`,
672
- `${name}:`,
673
- ` // Function Prologue`,
674
- allocStack(),
675
- ` stp x29, x30, [sp, #0]`,
676
- ` mov x29, sp`,
677
- ` ${strReg("x0", "in")}`,
678
- inputProductFields.length > 0 ? initInputProductLocals() : "",
679
- `${tailLoopLabel}:`,
680
- options.profile ? [
681
- ` adrp x9, k_prof_${name}`,
682
- ` add x9, x9, :lo12:k_prof_${name}`,
683
- ` ldr x10, [x9]`,
684
- ` add x10, x10, #1`,
685
- ` str x10, [x9]`
686
- ].join("\n") : "",
687
- compileInstructions(kvmFunc.body),
688
- `${funcFailLabel}:`,
689
- ` mov x0, #1`,
690
- ` mov x1, #0`,
691
- ` b ${funcEpilogueLabel}`,
692
- `${funcEpilogueLabel}:`,
693
- ` ldp x29, x30, [sp, #0]`,
694
- freeStack(),
695
- ` ret`,
696
- ` .size ${name}, .-${name}`
697
- ].filter(line => line !== "").join("\n");
698
-
699
- return asm;
700
- }
701
-
702
- export function cleanName(name) {
703
- return "rel_" + name.replace(/[^a-zA-Z0-9_]/g, "_");
704
- }
705
-
706
- export function cleanFunctionNameMap(names) {
707
- const used = new Set();
708
- const nameMap = new Map();
709
- for (const name of names) {
710
- const base = cleanName(name);
711
- let candidate = base;
712
- let suffix = 1;
713
- while (used.has(candidate)) {
714
- candidate = `${base}_${suffix++}`;
715
- }
716
- used.add(candidate);
717
- nameMap.set(name, candidate);
718
- }
719
- return nameMap;
720
- }
721
-
722
- export function cleanCallNames(insts, nameMap = null) {
723
- for (const inst of insts) {
724
- if (inst.op === "call") {
725
- inst.func = nameMap?.get(inst.func) || cleanName(inst.func);
726
- }
727
- if (inst.branches) {
728
- for (const branch of inst.branches) {
729
- cleanCallNames(branch.body, nameMap);
730
- }
731
- }
732
- }
733
- }
734
-
735
- function cloneKVMInstructions(insts) {
736
- return insts.map((inst) => ({
737
- ...inst,
738
- ...(inst.branches
739
- ? {
740
- branches: inst.branches.map((branch) => ({
741
- label: branch.label,
742
- body: cloneKVMInstructions(branch.body)
743
- }))
744
- }
745
- : {})
746
- }));
747
- }
748
-
749
- function cloneKVMFunction(kvmFunc) {
750
- return {
751
- ...kvmFunc,
752
- body: cloneKVMInstructions(kvmFunc.body)
753
- };
754
- }
755
-
756
- function scanCalls(insts, compiled, queue) {
757
- for (const inst of insts) {
758
- if (inst.op === "call" && !compiled.has(inst.func)) {
759
- queue.push(inst.func);
760
- }
761
- if (inst.branches) {
762
- for (const branch of inst.branches) {
763
- scanCalls(branch.body, compiled, queue);
764
- }
765
- }
766
- }
767
- }
768
-
769
- export function compileKVMModuleToARM64(mainRelName, kvmProgram, options = {}) {
770
- collectAllTags(kvmProgram, options.inputPattern, options.outputPattern);
771
-
772
- const compiled = new Set();
773
- const queue = [mainRelName];
774
- const asms = [];
775
- const profileFunctions = [];
776
- const nameMap = cleanFunctionNameMap(Object.keys(kvmProgram));
777
- const cleanKVMProgram = Object.fromEntries(
778
- Object.entries(kvmProgram).map(([name, kvmFunc]) => [nameMap.get(name), kvmFunc])
779
- );
780
-
781
- while (queue.length > 0) {
782
- const name = queue.shift();
783
- if (compiled.has(name)) continue;
784
- compiled.add(name);
785
-
786
- const originalFunc = kvmProgram[name];
787
- if (!originalFunc || !Array.isArray(originalFunc.body)) {
788
- throw new Error(`kVM function ${name} not found`);
789
- }
790
-
791
- scanCalls(originalFunc.body, compiled, queue);
792
-
793
- const kvmFunc = cloneKVMFunction(originalFunc);
794
- kvmFunc.name = nameMap.get(name);
795
- cleanCallNames(kvmFunc.body, nameMap);
796
- if (options.profile) {
797
- profileFunctions.push({ originalName: name, symbolName: kvmFunc.name });
798
- }
799
- asms.push(lowerToARM64(kvmFunc, kvmFunc.name, { ...options, kvmProgram: cleanKVMProgram }));
800
- }
801
-
802
- const entrySymbol = nameMap.get(mainRelName);
803
-
804
- const profileDataSection = options.profile && profileFunctions.length > 0
805
- ? [
806
- " .data",
807
- " .p2align 3",
808
- ...profileFunctions.map(f => ` .global k_prof_${f.symbolName}\nk_prof_${f.symbolName}:\n .quad 0`),
809
- " .text"
810
- ].join("\n")
811
- : "";
812
-
813
- return {
814
- assembly: [
815
- " .arch armv8-a",
816
- " .text",
817
- asms.join("\n\n"),
818
- profileDataSection,
819
- ""
820
- ].filter(Boolean).join("\n"),
821
- entryName: entrySymbol,
822
- profileFunctions
823
- };
824
- }
825
-
826
- const patternKindConstants = {
827
- "any": "KP_ANY",
828
- "open-product": "KP_OPEN_PRODUCT",
829
- "open-union": "KP_OPEN_UNION",
830
- "closed-product": "KP_CLOSED_PRODUCT",
831
- "closed-union": "KP_CLOSED_UNION"
832
- };
833
-
834
- function cByteArray(text) {
835
- const bytes = [...Buffer.from(String(text), "utf8")];
836
- return bytes.length === 0 ? "{0}" : `{${bytes.map((byte) => `0x${byte.toString(16).padStart(2, "0")}`).join(", ")}}`;
837
- }
838
-
839
- export function emitPatternC(name, pattern) {
840
- if (!Array.isArray(pattern)) throw new Error(`${name} pattern must be a property-list array`);
841
- const lines = [];
842
- for (let nodeIndex = 0; nodeIndex < pattern.length; nodeIndex++) {
843
- const node = pattern[nodeIndex];
844
- const [, rawEdges] = node;
845
- const edges = Array.isArray(rawEdges)
846
- ? [...rawEdges].sort((a, b) => Buffer.compare(Buffer.from(String(a[0]), "utf8"), Buffer.from(String(b[0]), "utf8")))
847
- : [];
848
- for (let edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
849
- const edge = edges[edgeIndex];
850
- const [label] = edge;
851
- lines.push(`static const unsigned char ${name}_label_${nodeIndex}_${edgeIndex}[] = ${cByteArray(label)};`);
852
- }
853
- if (edges.length === 0) continue;
854
- lines.push(`static k_pattern_edge ${name}_edges_${nodeIndex}[] = {`);
855
- for (let edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
856
- const edge = edges[edgeIndex];
857
- const [label, target] = edge;
858
- const labelLength = Buffer.byteLength(String(label), "utf8");
859
- const labelRef = `${name}_label_${nodeIndex}_${edgeIndex}`;
860
- lines.push(` {(const char *)${labelRef}, ${labelLength}, ${Number(target)}}${edgeIndex === edges.length - 1 ? "" : ","}`);
861
- }
862
- lines.push("};");
863
- }
864
- lines.push(`static k_pattern_node ${name}_nodes[] = {`);
865
- pattern.forEach(([kind, edges], index) => {
866
- const constant = patternKindConstants[kind];
867
- const edgeRef = edges.length === 0 ? "NULL" : `${name}_edges_${index}`;
868
- lines.push(` {${constant}, ${edges.length}, ${edgeRef}}${index === pattern.length - 1 ? "" : ","}`);
869
- });
870
- lines.push("};");
871
- lines.push(`k_pattern ${name} = {${pattern.length}, ${name}_nodes};`);
872
- return lines.join("\n");
873
- }
874
-
875
- export function emitMetadataC(tags, inputPattern, outputPattern, profileFunctions = []) {
876
- let metaC = `#include "krt.h"\n#include <stddef.h>\n#include <stdint.h>\n\n`;
877
- metaC += `typedef struct { const char *name; size_t length; uint32_t id; } k_arm64_tag_t;\n`;
878
- metaC += `const k_arm64_tag_t k_arm64_tags[] = {\n`;
879
- for (const { tag, id } of tags) {
880
- const escaped = JSON.stringify(tag).slice(1, -1);
881
- metaC += ` { "${escaped}", ${Buffer.byteLength(tag)}, ${id} },\n`;
882
- }
883
- metaC += `};\n`;
884
- metaC += `const size_t k_arm64_tag_count = ${tags.length};\n\n`;
885
- metaC += emitPatternC("compiled_input_pattern", inputPattern) + "\n\n";
886
- metaC += emitPatternC("compiled_output_pattern", outputPattern) + "\n\n";
887
-
888
- metaC += `typedef struct { const char *name; uint64_t *counter; } k_profile_entry_t;\n`;
889
- if (profileFunctions && profileFunctions.length > 0) {
890
- for (const f of profileFunctions) {
891
- metaC += `extern uint64_t k_prof_${f.symbolName};\n`;
892
- }
893
- metaC += `const k_profile_entry_t k_profile_entries[] = {\n`;
894
- for (const f of profileFunctions) {
895
- const escaped = JSON.stringify(f.originalName).slice(1, -1);
896
- metaC += ` { "${escaped}", &k_prof_${f.symbolName} },\n`;
897
- }
898
- metaC += `};\n`;
899
- metaC += `const size_t k_profile_entry_count = ${profileFunctions.length};\n`;
900
- } else {
901
- metaC += `const k_profile_entry_t k_profile_entries[1] = { { NULL, NULL } };\n`;
902
- metaC += `const size_t k_profile_entry_count = 0;\n`;
903
- }
904
-
905
- return metaC;
906
- }