@fraczak/k 7.0.2 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/DOCS/CODECS.md +37 -2
  3. package/DOCS/CODEC_ARCHITECTURE.md +12 -5
  4. package/DOCS/FILE_FORMATS.md +238 -15
  5. package/DOCS/POLYMORPHIC_IR.md +104 -0
  6. package/DOCS/REPL.md +11 -3
  7. package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
  8. package/JOURNAL.md +288 -0
  9. package/README.md +12 -3
  10. package/Value.mjs +28 -3
  11. package/access-table.mjs +343 -0
  12. package/backend-api.mjs +23 -1
  13. package/backends/llvm/README.md +20 -21
  14. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  15. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  16. package/backends/llvm/bin/k-llvm.mjs +292 -0
  17. package/backends/llvm/runtime/krt.c +8 -0
  18. package/backends/llvm/runtime/krt.h +1 -0
  19. package/backends/llvm/src/cli.mjs +95 -17
  20. package/backends/llvm/src/executable.mjs +128 -11
  21. package/backends/llvm/src/kvm2llvm.mjs +10 -5
  22. package/backends/llvm/src/llvm.mjs +121 -9
  23. package/backends/llvm/tests/integration.sh +11 -11
  24. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  25. package/backends/llvm/tests/perf-support.mjs +24 -3
  26. package/backends/llvm/tests/test-llvm.mjs +22 -1
  27. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  28. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  29. package/backends/wasm/bin/k-wasm.mjs +47 -13
  30. package/backends/wasm/src/cli.mjs +141 -19
  31. package/backends/wasm/src/wasm.mjs +130 -23
  32. package/browser/repl-browser.mjs +16 -5
  33. package/browser/tree-view.mjs +47 -8
  34. package/browser/vfs-data.mjs +6 -5
  35. package/codecs/README.md +1 -0
  36. package/codecs/djson.mjs +270 -0
  37. package/codecs/int.mjs +2 -1
  38. package/codecs/json.mjs +7 -2
  39. package/codecs/k-parse.mjs +36 -9
  40. package/codecs/k-print.mjs +26 -6
  41. package/codecs/runtime/codec-sdk.mjs +24 -6
  42. package/codecs/runtime/pattern-json.mjs +80 -1
  43. package/codecs/runtime/prefix-codec.mjs +629 -67
  44. package/codecs/runtime/show-value.mjs +93 -51
  45. package/codecs/runtime/unicode-string.mjs +29 -6
  46. package/codecs/show.mjs +24 -6
  47. package/codecs/utf16.mjs +2 -1
  48. package/k.mjs +56 -16
  49. package/kir.mjs +58 -19
  50. package/kvm.mjs +49 -12
  51. package/objects/compile.mjs +37 -23
  52. package/objects/decompile.mjs +40 -10
  53. package/objects/extract-aliases.mjs +44 -14
  54. package/objects/inspect.mjs +19 -14
  55. package/objects/validate.mjs +18 -11
  56. package/package.json +11 -3
  57. package/pkir.mjs +772 -0
  58. package/repl.mjs +92 -23
  59. package/scripts/build-repl-html.mjs +22 -3
  60. package/scripts/perf-ieee.mjs +264 -13
  61. package/scripts/perf-int.mjs +315 -22
  62. package/scripts/perf-multiplication.mjs +58 -7
  63. package/scripts/perf-poly.mjs +306 -24
  64. package/scripts/run-tests.mjs +3 -0
  65. package/tests/test-access-table.mjs +213 -0
  66. package/tests/test-codecs.mjs +168 -0
  67. package/tests/test-pkir.mjs +530 -0
  68. package/tests/test-polymorphic-tdd.mjs +196 -0
  69. package/tests/test-repl-html.mjs +16 -2
  70. package/tests/test-repl.mjs +63 -1
  71. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
package/pkir.mjs ADDED
@@ -0,0 +1,772 @@
1
+ // pkir.mjs: Polymorphic K Intermediate Representation (PKIR)
2
+ // State-indexed, slot-addressed intermediate representation for
3
+ // polymorphic relations in K. Enables direct execution with AccessTable
4
+ // and fast specialization to monomorphic machine code.
5
+
6
+ import { buildAccessTable } from "./access-table.mjs";
7
+ import { Value, isProduct, isVariant } from "./Value.mjs";
8
+ import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
9
+ import { exportPatternGraph } from "./codecs/runtime/codec.mjs";
10
+
11
+ export const PKIR_FORMAT = "pk-ir";
12
+ export const PKIR_VERSION = 1;
13
+
14
+ /**
15
+ * Instruction builder for PKIR compilation.
16
+ */
17
+ class PKIRBuilder {
18
+ constructor(patternGraph, relName) {
19
+ this.patternGraph = patternGraph;
20
+ this.relName = relName;
21
+ this.regCount = 0;
22
+ this.instructions = [];
23
+ // Access profile per pattern state:
24
+ // state -> { fields: string[], variants: string[] }
25
+ this.accessProfile = new Map();
26
+ }
27
+
28
+ nextReg() {
29
+ return `%v${this.regCount++}`;
30
+ }
31
+
32
+ emit(inst) {
33
+ this.instructions.push(inst);
34
+ }
35
+
36
+ getProfile(state) {
37
+ if (!this.accessProfile.has(state)) {
38
+ this.accessProfile.set(state, { fields: [], variants: [] });
39
+ }
40
+ return this.accessProfile.get(state);
41
+ }
42
+
43
+ registerFieldAccess(state, label) {
44
+ const prof = this.getProfile(state);
45
+ let slot = prof.fields.indexOf(label);
46
+ if (slot === -1) {
47
+ slot = prof.fields.length;
48
+ prof.fields.push(label);
49
+ }
50
+ return slot;
51
+ }
52
+
53
+ registerVariantMatch(state, tag) {
54
+ const prof = this.getProfile(state);
55
+ let branch = prof.variants.indexOf(tag);
56
+ if (branch === -1) {
57
+ branch = prof.variants.length;
58
+ prof.variants.push(tag);
59
+ }
60
+ return branch;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Lowers a relational expression AST into PKIR instructions.
66
+ */
67
+ function compileExpToPKIR(exp, inputReg, builder) {
68
+ if (!exp) return inputReg;
69
+
70
+ switch (exp.op) {
71
+ case "identity": {
72
+ const dest = builder.nextReg();
73
+ builder.emit({ op: "id", dest, src: inputReg });
74
+ return dest;
75
+ }
76
+
77
+ case "empty": {
78
+ builder.emit({ op: "fail" });
79
+ return builder.nextReg();
80
+ }
81
+
82
+ case "dot": {
83
+ const dest = builder.nextReg();
84
+ const state = exp.patterns?.[0] ?? 0;
85
+ const slot = builder.registerFieldAccess(state, exp.dot || exp.label);
86
+ builder.emit({
87
+ op: "project_slot",
88
+ dest,
89
+ src: inputReg,
90
+ state,
91
+ slot,
92
+ label: exp.dot || exp.label
93
+ });
94
+ return dest;
95
+ }
96
+
97
+ case "div": {
98
+ const dest = builder.nextReg();
99
+ const state = exp.patterns?.[0] ?? 0;
100
+ const tag = exp.div || exp.tag;
101
+ const branchId = builder.registerVariantMatch(state, tag);
102
+ builder.emit({
103
+ op: "match_variant",
104
+ dest,
105
+ src: inputReg,
106
+ state,
107
+ tag,
108
+ branchId
109
+ });
110
+ return dest;
111
+ }
112
+
113
+ case "vid": {
114
+ const dest = builder.nextReg();
115
+ const tag = exp.vid || exp.tag;
116
+ builder.emit({
117
+ op: "make_variant",
118
+ dest,
119
+ src: inputReg,
120
+ tag
121
+ });
122
+ return dest;
123
+ }
124
+
125
+ case "comp": {
126
+ let currentReg = inputReg;
127
+ for (const child of exp.comp || exp.items || []) {
128
+ currentReg = compileExpToPKIR(child, currentReg, builder);
129
+ }
130
+ return currentReg;
131
+ }
132
+
133
+ case "union": {
134
+ const dest = builder.nextReg();
135
+ const branches = [];
136
+ for (const child of exp.union || exp.items || []) {
137
+ const branchBuilder = new PKIRBuilder(
138
+ builder.patternGraph,
139
+ builder.relName
140
+ );
141
+ branchBuilder.regCount = builder.regCount;
142
+ const outReg = compileExpToPKIR(child, inputReg, branchBuilder);
143
+ builder.regCount = branchBuilder.regCount;
144
+ // Merge child profiles into parent
145
+ for (const [st, p] of branchBuilder.accessProfile.entries()) {
146
+ p.fields.forEach((f) => builder.registerFieldAccess(st, f));
147
+ p.variants.forEach((v) => builder.registerVariantMatch(st, v));
148
+ }
149
+ branches.push({
150
+ instructions: branchBuilder.instructions,
151
+ outReg
152
+ });
153
+ }
154
+ builder.emit({
155
+ op: "union",
156
+ dest,
157
+ src: inputReg,
158
+ branches
159
+ });
160
+ return dest;
161
+ }
162
+
163
+ case "product": {
164
+ const dest = builder.nextReg();
165
+ const fieldEntries = [];
166
+ const fieldsList = exp.product || exp.fields || [];
167
+
168
+ for (const field of fieldsList) {
169
+ const label = field.label;
170
+ const childExp = field.exp || field.expr;
171
+ const branchBuilder = new PKIRBuilder(
172
+ builder.patternGraph,
173
+ builder.relName
174
+ );
175
+ branchBuilder.regCount = builder.regCount;
176
+ const outReg = compileExpToPKIR(childExp, inputReg, branchBuilder);
177
+ builder.regCount = branchBuilder.regCount;
178
+
179
+ for (const [st, p] of branchBuilder.accessProfile.entries()) {
180
+ p.fields.forEach((f) => builder.registerFieldAccess(st, f));
181
+ p.variants.forEach((v) => builder.registerVariantMatch(st, v));
182
+ }
183
+
184
+ fieldEntries.push({
185
+ label,
186
+ instructions: branchBuilder.instructions,
187
+ outReg
188
+ });
189
+ }
190
+
191
+ builder.emit({
192
+ op: "product",
193
+ dest,
194
+ src: inputReg,
195
+ fields: fieldEntries
196
+ });
197
+ return dest;
198
+ }
199
+
200
+ case "ref": {
201
+ const dest = builder.nextReg();
202
+ builder.emit({
203
+ op: "call_rel",
204
+ dest,
205
+ ref: exp.ref,
206
+ src: inputReg
207
+ });
208
+ return dest;
209
+ }
210
+
211
+ case "filter": {
212
+ const dest = builder.nextReg();
213
+ builder.emit({
214
+ op: "filter",
215
+ dest,
216
+ src: inputReg,
217
+ filter: exp.filter
218
+ });
219
+ return dest;
220
+ }
221
+
222
+ default:
223
+ throw new Error(`Unsupported expression op in PKIR compiler: ${exp.op}`);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Compiles a relation definition into a PKIR relation artifact.
229
+ */
230
+ export function compileRelationToPKIR(relDef, relName = "rel") {
231
+ const patternGraph = relDef.patternGraph || relDef.typePatternGraph;
232
+ const builder = new PKIRBuilder(patternGraph, relName);
233
+ const inputReg = "%v0";
234
+ builder.regCount = 1;
235
+
236
+ const bodyAst = relDef.def || relDef.body;
237
+ const outputReg = compileExpToPKIR(bodyAst, inputReg, builder);
238
+
239
+ const [inPat, outPat] = bodyAst?.patterns || [0, 0];
240
+
241
+ return {
242
+ format: PKIR_FORMAT,
243
+ version: PKIR_VERSION,
244
+ name: relName,
245
+ inputReg,
246
+ outputReg,
247
+ inputPattern: inPat,
248
+ outputPattern: outPat,
249
+ patternGraph,
250
+ accessProfile: Object.fromEntries(builder.accessProfile),
251
+ instructions: builder.instructions
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Creates a mock/arena KVal word representation for runtime execution.
257
+ */
258
+ export function createMaterializedKVal(tag, words, state = 0) {
259
+ return {
260
+ repr: "materialized",
261
+ state,
262
+ tag, // numeric discriminator or null for product
263
+ words // array of child KVal references
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Converts a JS Value (from Value.mjs) into a KVal reference.
269
+ */
270
+ export function valueToKVal(value, state = 0) {
271
+ if (value == null) return null;
272
+ if (isVariant(value)) {
273
+ return {
274
+ repr: "materialized",
275
+ state,
276
+ tag: value.tag,
277
+ words: [valueToKVal(value.value, state)]
278
+ };
279
+ }
280
+ if (isProduct(value)) {
281
+ // Sort field entries alphabetically for canonical physical layout
282
+ const entries = Object.entries(value.product || {}).sort(([a], [b]) =>
283
+ a.localeCompare(b)
284
+ );
285
+ return {
286
+ repr: "materialized",
287
+ state,
288
+ tag: null,
289
+ fieldNames: entries.map(([k]) => k),
290
+ words: entries.map(([_, v]) => valueToKVal(v, state))
291
+ };
292
+ }
293
+ // Primitive or unit
294
+ return {
295
+ repr: "materialized",
296
+ state,
297
+ tag: null,
298
+ words: []
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Converts a KVal reference back into a Value or JS object.
304
+ */
305
+ export function kvalToValue(kval) {
306
+ if (!kval) return null;
307
+ if (kval.tag != null) {
308
+ const childVal = kval.words[0] ? kvalToValue(kval.words[0]) : {};
309
+ return Value.variant(kval.tag, childVal);
310
+ }
311
+ if (kval.fieldNames && Array.isArray(kval.words)) {
312
+ const obj = {};
313
+ kval.fieldNames.forEach((name, i) => {
314
+ obj[name] = kvalToValue(kval.words[i]);
315
+ });
316
+ return Value.product(obj);
317
+ }
318
+ return Value.product({});
319
+ }
320
+
321
+ /**
322
+ * Executes a list of PKIR instructions on a KVal reference using
323
+ * an AccessTable. Implements pure partial function semantics.
324
+ */
325
+ export function executePKIR(pkirFunc, inputVal, accessTable, context = {}) {
326
+ const regs = new Map();
327
+ regs.set(pkirFunc.inputReg, inputVal);
328
+
329
+ function evalInstructions(instructions) {
330
+ for (const inst of instructions) {
331
+ switch (inst.op) {
332
+ case "id": {
333
+ const val = regs.get(inst.src);
334
+ regs.set(inst.dest, val);
335
+ break;
336
+ }
337
+
338
+ case "fail": {
339
+ return false;
340
+ }
341
+
342
+ case "project_slot": {
343
+ const src = regs.get(inst.src);
344
+ if (!src || !src.words) return false;
345
+
346
+ let byteOffset, childState;
347
+ if (accessTable) {
348
+ const meta = accessTable.resolveField(src.state, inst.label);
349
+ byteOffset = meta.byteOffset;
350
+ childState = meta.childState;
351
+ } else {
352
+ // Direct physical index fallback
353
+ const idx = src.fieldNames
354
+ ? src.fieldNames.indexOf(inst.label)
355
+ : inst.slot;
356
+ if (idx === -1) return false;
357
+ byteOffset = idx * 8;
358
+ childState = src.state;
359
+ }
360
+
361
+ const wordIdx = Math.floor(byteOffset / 8);
362
+ const childVal = src.words[wordIdx];
363
+ if (!childVal) return false;
364
+
365
+ // Propagate new state to projected child
366
+ regs.set(inst.dest, { ...childVal, state: childState });
367
+ break;
368
+ }
369
+
370
+ case "project_offset": {
371
+ const src = regs.get(inst.src);
372
+ if (!src || !src.words) return false;
373
+ const wordIdx = Math.floor(inst.offset / 8);
374
+ const childVal = src.words[wordIdx];
375
+ if (!childVal) return false;
376
+ regs.set(inst.dest, { ...childVal, state: inst.childState });
377
+ break;
378
+ }
379
+
380
+ case "match_variant": {
381
+ const src = regs.get(inst.src);
382
+ if (!src || src.tag == null) return false;
383
+
384
+ let branchId, childState;
385
+ if (accessTable) {
386
+ const meta = accessTable.resolveVariant(src.state, src.tag);
387
+ branchId = meta.branchId;
388
+ childState = meta.childState;
389
+ } else {
390
+ branchId = src.tag === inst.tag ? inst.branchId : -1;
391
+ childState = src.state;
392
+ }
393
+
394
+ if (branchId !== inst.branchId && src.tag !== inst.tag) {
395
+ return false;
396
+ }
397
+
398
+ const childVal = src.words[0] || {
399
+ repr: "materialized",
400
+ state: childState,
401
+ tag: null,
402
+ words: []
403
+ };
404
+ regs.set(inst.dest, { ...childVal, state: childState });
405
+ break;
406
+ }
407
+
408
+ case "match_tag_imm": {
409
+ const src = regs.get(inst.src);
410
+ if (!src || src.tag == null) return false;
411
+ if (src.tag !== inst.tag) return false;
412
+ const childVal = src.words[0] || {
413
+ repr: "materialized",
414
+ state: inst.childState,
415
+ tag: null,
416
+ words: []
417
+ };
418
+ regs.set(inst.dest, { ...childVal, state: inst.childState });
419
+ break;
420
+ }
421
+
422
+ case "make_variant": {
423
+ const src = regs.get(inst.src);
424
+ const varVal = {
425
+ repr: "materialized",
426
+ state: 0,
427
+ tag: inst.tag,
428
+ words: [src]
429
+ };
430
+ regs.set(inst.dest, varVal);
431
+ break;
432
+ }
433
+
434
+ case "product": {
435
+ const src = regs.get(inst.src);
436
+ const words = [];
437
+ const fieldNames = [];
438
+
439
+ for (const field of inst.fields) {
440
+ const savedRegs = new Map(regs);
441
+ const ok = evalInstructions(field.instructions);
442
+ if (!ok) {
443
+ regs.clear();
444
+ for (const [k, v] of savedRegs) regs.set(k, v);
445
+ return false;
446
+ }
447
+ const fieldVal = regs.get(field.outReg);
448
+ words.push(fieldVal);
449
+ fieldNames.push(field.label);
450
+ }
451
+
452
+ regs.set(inst.dest, {
453
+ repr: "materialized",
454
+ state: 0,
455
+ tag: null,
456
+ fieldNames,
457
+ words
458
+ });
459
+ break;
460
+ }
461
+
462
+ case "union": {
463
+ let matched = false;
464
+ const savedRegs = new Map(regs);
465
+
466
+ for (const branch of inst.branches) {
467
+ regs.clear();
468
+ for (const [k, v] of savedRegs) regs.set(k, v);
469
+
470
+ const ok = evalInstructions(branch.instructions);
471
+ if (ok) {
472
+ const res = regs.get(branch.outReg);
473
+ regs.clear();
474
+ for (const [k, v] of savedRegs) regs.set(k, v);
475
+ regs.set(inst.dest, res);
476
+ matched = true;
477
+ break;
478
+ }
479
+ }
480
+
481
+ if (!matched) return false;
482
+ break;
483
+ }
484
+
485
+ case "call_rel": {
486
+ const src = regs.get(inst.src);
487
+ const targetRel = context.rels?.[inst.ref];
488
+ if (!targetRel) {
489
+ throw new Error(`Relation not found in context: ${inst.ref}`);
490
+ }
491
+ const res = executePKIR(targetRel, src, accessTable, context);
492
+ if (res == null) return false;
493
+ regs.set(inst.dest, res);
494
+ break;
495
+ }
496
+
497
+ case "filter": {
498
+ const src = regs.get(inst.src);
499
+ regs.set(inst.dest, src);
500
+ break;
501
+ }
502
+
503
+ default:
504
+ throw new Error(`Unknown PKIR instruction opcode: ${inst.op}`);
505
+ }
506
+ }
507
+ return true;
508
+ }
509
+
510
+ const ok = evalInstructions(pkirFunc.instructions);
511
+ return ok ? regs.get(pkirFunc.outputReg) : null;
512
+ }
513
+
514
+ /**
515
+ * Specializes a PKIR function by baking an AccessTable into monomorphic
516
+ * machine instructions with static offsets and pruned dead branches.
517
+ */
518
+ export function specializePKIR(pkirFunc, accessTable) {
519
+ function specializeList(instructions, initialRegStates) {
520
+ const regStates = new Map(initialRegStates);
521
+ const result = [];
522
+
523
+ for (const inst of instructions) {
524
+ switch (inst.op) {
525
+ case "id": {
526
+ const st = regStates.get(inst.src);
527
+ if (st != null) regStates.set(inst.dest, st);
528
+ result.push({ ...inst });
529
+ break;
530
+ }
531
+
532
+ case "project_slot": {
533
+ const srcState = regStates.get(inst.src) ?? 0;
534
+ let meta = null;
535
+ try {
536
+ meta = accessTable.resolveField(srcState, inst.label);
537
+ } catch {
538
+ meta = null;
539
+ }
540
+
541
+ if (meta) {
542
+ regStates.set(inst.dest, meta.childState);
543
+ result.push({
544
+ op: "project_offset",
545
+ dest: inst.dest,
546
+ src: inst.src,
547
+ offset: meta.byteOffset,
548
+ skipCount: meta.skipCount,
549
+ childState: meta.childState,
550
+ label: inst.label
551
+ });
552
+ } else {
553
+ result.push({ op: "fail" });
554
+ }
555
+ break;
556
+ }
557
+
558
+ case "match_variant": {
559
+ const srcState = regStates.get(inst.src) ?? 0;
560
+ let meta = null;
561
+ try {
562
+ meta = accessTable.resolveVariant(srcState, inst.tag);
563
+ } catch {
564
+ meta = null;
565
+ }
566
+
567
+ if (meta && meta.branchId !== -1) {
568
+ regStates.set(inst.dest, meta.childState);
569
+ result.push({
570
+ op: "match_tag_imm",
571
+ dest: inst.dest,
572
+ src: inst.src,
573
+ tagId: meta.tagId,
574
+ branchId: meta.branchId,
575
+ childState: meta.childState,
576
+ tag: inst.tag
577
+ });
578
+ } else {
579
+ // Disjoint variant: cannot match under schema S; prune
580
+ result.push({ op: "fail" });
581
+ }
582
+ break;
583
+ }
584
+
585
+ case "make_variant": {
586
+ regStates.set(inst.dest, 0);
587
+ result.push({ ...inst });
588
+ break;
589
+ }
590
+
591
+ case "union": {
592
+ const specializedBranches = [];
593
+ for (const b of inst.branches) {
594
+ const branchInsts = specializeList(b.instructions, regStates);
595
+ if (branchInsts.length > 0 && branchInsts[0].op === "fail") {
596
+ // Dead branch eliminated under schema S
597
+ continue;
598
+ }
599
+ specializedBranches.push({
600
+ instructions: branchInsts,
601
+ outReg: b.outReg
602
+ });
603
+ }
604
+ result.push({
605
+ ...inst,
606
+ branches: specializedBranches
607
+ });
608
+ break;
609
+ }
610
+
611
+ case "product": {
612
+ const specializedFields = inst.fields.map((f) => ({
613
+ ...f,
614
+ instructions: specializeList(f.instructions, regStates)
615
+ }));
616
+ result.push({
617
+ ...inst,
618
+ fields: specializedFields
619
+ });
620
+ break;
621
+ }
622
+
623
+ default:
624
+ result.push({ ...inst });
625
+ break;
626
+ }
627
+ }
628
+ return result;
629
+ }
630
+
631
+ const initialStates = new Map();
632
+ initialStates.set(pkirFunc.inputReg, accessTable.rootState);
633
+
634
+ return {
635
+ ...pkirFunc,
636
+ isSpecialized: true,
637
+ instructions: specializeList(pkirFunc.instructions, initialStates)
638
+ };
639
+ }
640
+
641
+ /**
642
+ * Lowers a PKIR function into a KVM function representation compatible
643
+ * with WebAssembly and machine code generators.
644
+ */
645
+ export function lowerPKIRToKVM(pkir) {
646
+ function convert(insts, branchInReg = null) {
647
+ function mapReg(r) {
648
+ if (branchInReg != null && r === branchInReg) return "%in";
649
+ if (r === pkir.inputReg) return "%in";
650
+ return r;
651
+ }
652
+
653
+ const res = [];
654
+ for (const inst of insts) {
655
+ switch (inst.op) {
656
+ case "id":
657
+ case "filter":
658
+ res.push({
659
+ op: "id",
660
+ dest: mapReg(inst.dest),
661
+ src: mapReg(inst.src)
662
+ });
663
+ break;
664
+ case "fail":
665
+ res.push({ op: "fail" });
666
+ break;
667
+ case "project_slot":
668
+ case "project_offset":
669
+ res.push({
670
+ op: "project_field",
671
+ dest: mapReg(inst.dest),
672
+ src: mapReg(inst.src),
673
+ label: inst.label,
674
+ slot: inst.slot ?? inst.skipCount
675
+ });
676
+ break;
677
+ case "match_variant":
678
+ case "match_tag_imm":
679
+ res.push({
680
+ op: "project_variant",
681
+ dest: mapReg(inst.dest),
682
+ src: mapReg(inst.src),
683
+ tag: inst.tag
684
+ });
685
+ break;
686
+ case "make_variant":
687
+ res.push({
688
+ op: "make_variant",
689
+ dest: mapReg(inst.dest),
690
+ src: mapReg(inst.src),
691
+ tag: inst.tag
692
+ });
693
+ break;
694
+ case "product":
695
+ res.push({
696
+ op: "product",
697
+ dest: mapReg(inst.dest),
698
+ src: mapReg(inst.src),
699
+ branches: inst.fields.map((f) => {
700
+ const body = convert(f.instructions, inst.src);
701
+ const branchOut = f.outReg === inst.src
702
+ ? "%in"
703
+ : mapReg(f.outReg);
704
+ body.push({ op: "return", src: branchOut });
705
+ return { label: f.label, body };
706
+ })
707
+ });
708
+ break;
709
+ case "union":
710
+ res.push({
711
+ op: "union",
712
+ dest: mapReg(inst.dest),
713
+ src: mapReg(inst.src),
714
+ branches: inst.branches.map((b) => {
715
+ const body = convert(b.instructions, inst.src);
716
+ const branchOut = b.outReg === inst.src
717
+ ? "%in"
718
+ : mapReg(b.outReg);
719
+ body.push({ op: "return", src: branchOut });
720
+ return { body };
721
+ })
722
+ });
723
+ break;
724
+ case "call_rel":
725
+ res.push({
726
+ op: "call",
727
+ dest: mapReg(inst.dest),
728
+ func: inst.ref,
729
+ src: mapReg(inst.src)
730
+ });
731
+ break;
732
+ }
733
+ }
734
+ return res;
735
+ }
736
+
737
+ const body = convert(pkir.instructions);
738
+ body.push({
739
+ op: "return",
740
+ src: pkir.outputReg === pkir.inputReg ? "%in" : pkir.outputReg
741
+ });
742
+
743
+ let inputPattern = [["any", []]];
744
+ let outputPattern = [["any", []]];
745
+ const graph = pkir.patternGraph;
746
+ if (graph) {
747
+ if (pkir.inputPattern != null) {
748
+ try {
749
+ const inId = graph.find(pkir.inputPattern);
750
+ inputPattern = patternToPropertyList(
751
+ exportPatternGraph(graph, inId)
752
+ );
753
+ } catch {}
754
+ }
755
+ if (pkir.outputPattern != null) {
756
+ try {
757
+ const outId = graph.find(pkir.outputPattern);
758
+ outputPattern = patternToPropertyList(
759
+ exportPatternGraph(graph, outId)
760
+ );
761
+ } catch {}
762
+ }
763
+ }
764
+
765
+ return {
766
+ name: pkir.name,
767
+ body,
768
+ inputPattern,
769
+ outputPattern,
770
+ typePatternGraph: graph
771
+ };
772
+ }