@fraczak/k 6.3.2 → 6.4.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 (156) hide show
  1. package/DOCS/CODECS.md +258 -0
  2. package/DOCS/DICTIONARY.md +1172 -0
  3. package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
  4. package/DOCS/KIR_V1.md +221 -0
  5. package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
  6. package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
  7. package/DOCS/PROFILING_AND_TRACING.md +196 -0
  8. package/DOCS/REPL.md +76 -14
  9. package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
  10. package/DOCS/dependency-graph.md +144 -0
  11. package/Examples/arithmetics.k +51 -48
  12. package/Examples/bnat.k +1 -1
  13. package/Examples/buda.k +44 -0
  14. package/Examples/ieee.k +300 -8671
  15. package/Examples/poly.k +67 -0
  16. package/README.md +68 -19
  17. package/augmentation.mjs +9 -7
  18. package/backend-api.mjs +11 -2
  19. package/backends/arm64/README.md +104 -0
  20. package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
  21. package/backends/arm64/bin/k-arm64-run.mjs +58 -0
  22. package/backends/arm64/bin/k-arm64.mjs +88 -0
  23. package/backends/arm64/scripts/run-tests.mjs +41 -0
  24. package/backends/arm64/src/arm64.mjs +229 -0
  25. package/backends/arm64/src/cli.mjs +155 -0
  26. package/backends/arm64/src/kvm2arm64.mjs +906 -0
  27. package/backends/arm64/src/runtime.c +728 -0
  28. package/backends/arm64/tests/integration.sh +112 -0
  29. package/backends/arm64/tests/test-arm64.mjs +240 -0
  30. package/backends/llvm/README.md +135 -0
  31. package/backends/llvm/bin/k-llvm-build.mjs +71 -0
  32. package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
  33. package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
  34. package/backends/llvm/bin/k-llvm-run.mjs +90 -0
  35. package/backends/llvm/runtime/krt.c +1508 -0
  36. package/backends/llvm/runtime/krt.h +109 -0
  37. package/backends/llvm/scripts/conformance.mjs +76 -0
  38. package/backends/llvm/scripts/run-tests.mjs +28 -0
  39. package/backends/llvm/src/executable.mjs +562 -0
  40. package/backends/llvm/src/kvm2llvm.mjs +998 -0
  41. package/backends/llvm/src/llvm.mjs +1092 -0
  42. package/backends/llvm/tests/composition-driver.c +22 -0
  43. package/backends/llvm/tests/identity-driver.c +27 -0
  44. package/backends/llvm/tests/integration.sh +153 -0
  45. package/backends/llvm/tests/perf-support.mjs +735 -0
  46. package/backends/llvm/tests/product-driver.c +23 -0
  47. package/backends/llvm/tests/projection-driver.c +19 -0
  48. package/backends/llvm/tests/relation-driver.c +29 -0
  49. package/backends/llvm/tests/test-llvm.mjs +240 -0
  50. package/backends/llvm/tests/union-driver.c +31 -0
  51. package/backends/llvm/tests/variant-driver.c +19 -0
  52. package/backends/llvm/tests/variant-projection-driver.c +21 -0
  53. package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
  54. package/backends/wasm/README.md +97 -0
  55. package/backends/wasm/TECHNICAL-DEBT.md +60 -0
  56. package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
  57. package/backends/wasm/bin/k-wasm-run.mjs +140 -0
  58. package/backends/wasm/bin/k-wasm.mjs +49 -0
  59. package/backends/wasm/demo_ieee.mjs +319 -0
  60. package/backends/wasm/runtime.wat +102 -0
  61. package/backends/wasm/scripts/run-tests.mjs +41 -0
  62. package/backends/wasm/src/cli.mjs +133 -0
  63. package/backends/wasm/src/kvm2wasm.mjs +661 -0
  64. package/backends/wasm/src/wasm.mjs +1010 -0
  65. package/backends/wasm/tests/integration.sh +182 -0
  66. package/backends/wasm/tests/perf-support.mjs +336 -0
  67. package/backends/wasm/tests/test-wasm.mjs +617 -0
  68. package/browser/repl-browser.mjs +1584 -0
  69. package/browser/shims/assert.mjs +19 -0
  70. package/browser/shims/buffer.mjs +224 -0
  71. package/browser/shims/crypto.mjs +103 -0
  72. package/browser/shims/fs.mjs +159 -0
  73. package/browser/shims/os.mjs +17 -0
  74. package/browser/shims/path.mjs +54 -0
  75. package/browser/shims/process.mjs +44 -0
  76. package/browser/shims/readline.mjs +11 -0
  77. package/browser/shims/url.mjs +12 -0
  78. package/browser/vfs-data.mjs +19 -0
  79. package/codecs/README.md +3 -0
  80. package/codecs/json.mjs +16 -2
  81. package/codecs/runtime/show-value.mjs +2 -2
  82. package/conformance/helper-reuse/case.json +8 -0
  83. package/conformance/helper-reuse/expected.kv +1 -0
  84. package/conformance/helper-reuse/input.kv +1 -0
  85. package/conformance/helper-reuse/program.k +2 -0
  86. package/conformance/identity/case.json +8 -0
  87. package/conformance/identity/expected.kv +1 -0
  88. package/conformance/identity/input.kv +1 -0
  89. package/conformance/identity/program.k +1 -0
  90. package/conformance/product-projection/case.json +8 -0
  91. package/conformance/product-projection/expected.kv +1 -0
  92. package/conformance/product-projection/input.kv +1 -0
  93. package/conformance/product-projection/program.k +1 -0
  94. package/conformance/run.mjs +149 -0
  95. package/conformance/variant-construction/case.json +8 -0
  96. package/conformance/variant-construction/expected.kv +1 -0
  97. package/conformance/variant-construction/input.kv +1 -0
  98. package/conformance/variant-construction/program.k +1 -0
  99. package/harness/adapters/arm64.mjs +105 -0
  100. package/harness/adapters/kvm.mjs +144 -0
  101. package/harness/adapters/llvm.mjs +118 -0
  102. package/harness/adapters/native.mjs +77 -0
  103. package/harness/adapters/wasm.mjs +169 -0
  104. package/harness/backend.mjs +95 -0
  105. package/harness/cli.mjs +130 -0
  106. package/harness/format.mjs +95 -0
  107. package/harness/index.mjs +25 -0
  108. package/harness/persistent.mjs +121 -0
  109. package/harness/registry.mjs +44 -0
  110. package/harness/suite.mjs +257 -0
  111. package/harness/suites/poly.mjs +156 -0
  112. package/harness/trace.mjs +50 -0
  113. package/harness/util.mjs +92 -0
  114. package/intrinsics.mjs +41 -0
  115. package/kir.mjs +399 -0
  116. package/kvm.mjs +585 -81
  117. package/objects/README.md +37 -3
  118. package/objects/compile.mjs +15 -22
  119. package/objects/inspect.mjs +96 -0
  120. package/objects/validate.mjs +415 -0
  121. package/package.json +51 -19
  122. package/repl-codecs.mjs +133 -20
  123. package/repl.mjs +408 -59
  124. package/run.mjs +2 -7
  125. package/scripts/benchmark.mjs +406 -22
  126. package/scripts/build-repl-html.mjs +1784 -0
  127. package/scripts/perf-ieee.mjs +586 -0
  128. package/scripts/perf-int.mjs +531 -0
  129. package/scripts/perf-poly.mjs +945 -0
  130. package/scripts/render-benchmark.mjs +12 -8
  131. package/scripts/run-tests.mjs +6 -0
  132. package/tests/integration.sh +19 -2
  133. package/tests/test-arithmetics.mjs +54 -0
  134. package/tests/test-ieee-arithmetic.mjs +6 -2
  135. package/tests/test-kir.mjs +85 -0
  136. package/tests/test-kvm-polymorphic.mjs +278 -0
  137. package/tests/test-kvm.mjs +25 -6
  138. package/tests/test-poly.mjs +129 -0
  139. package/tests/test-repl-html.mjs +303 -0
  140. package/tests/test-repl.mjs +175 -1
  141. package/tests/test-validate-object.mjs +40 -0
  142. package/a.klib +0 -1
  143. package/a.kvm +0 -142676
  144. package/a.wasm +0 -0
  145. package/benchmark-results.html +0 -532
  146. package/benchmark-results.json +0 -480
  147. package/k-vscode/.vscode/launch.json +0 -14
  148. package/k-vscode/README.md +0 -75
  149. package/k-vscode/extension.js +0 -273
  150. package/k-vscode/language-configuration.json +0 -42
  151. package/k-vscode/package.json +0 -79
  152. package/k-vscode/snippets/k.json +0 -92
  153. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  154. package/k-vscode/test/background-ranges.test.js +0 -50
  155. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  156. package/scripts/git-hooks/pre-commit +0 -10
@@ -0,0 +1,998 @@
1
+ import fs from "node:fs";
2
+ import { isIntrinsic, unsupportedIntrinsic } from "@fraczak/k/backend-api.mjs";
3
+ import { llvmIdentifier } from "./llvm.mjs";
4
+
5
+ export const ARTIFACT_FORMAT = "k-llvm";
6
+ export const ARTIFACT_VERSION = 1;
7
+
8
+ const K_VALUE_SIZE = 40;
9
+ const K_VALUE_ALLOC_SIZE = 48;
10
+ const K_VALUE_KIND_PRODUCT = 1;
11
+ const K_VALUE_KIND_VARIANT = 2;
12
+ const K_VALUE_KIND_OFFSET = 0;
13
+ const K_VALUE_RT_OFFSET = 8;
14
+ const K_VALUE_PRODUCT_COUNT_OFFSET = 16;
15
+ const K_VALUE_PRODUCT_CAPACITY_OFFSET = 24;
16
+ const K_VALUE_PRODUCT_FIELDS_OFFSET = 32;
17
+ const K_VALUE_VARIANT_TAG_OFFSET = 16;
18
+ const K_VALUE_VARIANT_TAG_LENGTH_OFFSET = 24;
19
+ const K_VALUE_VARIANT_PAYLOAD_OFFSET = 32;
20
+ const K_PRODUCT_HEADER_SIZE = 48;
21
+ const K_FIELD_SIZE = 24;
22
+ const K_FIELD_LABEL_OFFSET = 0;
23
+ const K_FIELD_LABEL_LENGTH_OFFSET = 8;
24
+ const K_FIELD_VALUE_OFFSET = 16;
25
+
26
+ const K_RT_BLOCKS_OFFSET = 0;
27
+ const K_RT_UNIT_CACHE_OFFSET = 8;
28
+ const K_RT_BIT0_CACHE_OFFSET = 16;
29
+ const K_RT_BIT1_CACHE_OFFSET = 24;
30
+ const K_RT_HAS_REUSABLE_BLOCKS_OFFSET = 32;
31
+ const K_ARENA_BLOCK_USED_OFFSET = 8;
32
+
33
+ function alignArenaSize(size) {
34
+ return (size + 15) & ~15;
35
+ }
36
+
37
+ function cStringBytes(text) {
38
+ return [...Buffer.from(text, "utf8"), 0]
39
+ .map((byte) => {
40
+ if (byte === 10) return "\\0A";
41
+ if (byte === 34) return "\\22";
42
+ if (byte === 92) return "\\5C";
43
+ if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
44
+ return `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
45
+ })
46
+ .join("");
47
+ }
48
+
49
+ export function runtimeDeclarations() {
50
+ return [
51
+ "%k_rt_mark = type { ptr, i64 }",
52
+ "%k_result = type { i32, ptr }",
53
+ "",
54
+ "declare %k_rt_mark @k_rt_mark(ptr)",
55
+ "declare void @k_rt_rewind(ptr, %k_rt_mark)",
56
+ "declare ptr @k_rt_alloc(ptr, i64)",
57
+ "declare ptr @k_unit(ptr)",
58
+ "declare ptr @k_bit0(ptr)",
59
+ "declare ptr @k_bit1(ptr)",
60
+ "declare ptr @k_product(ptr, i64)",
61
+ "declare void @k_product_set(ptr, ptr, ptr)",
62
+ "declare void @k_product_set_n(ptr, ptr, i64, ptr)",
63
+ "declare void @k_product_set_borrowed_n(ptr, ptr, i64, ptr)",
64
+ "declare void @k_product_set_at(ptr, i64, ptr, i64, ptr)",
65
+ "declare ptr @k_product_get(ptr, ptr)",
66
+ "declare ptr @k_product_get_n(ptr, ptr, i64)",
67
+ "declare ptr @k_product_get_at(ptr, i64)",
68
+ "declare ptr @k_variant(ptr, ptr, ptr)",
69
+ "declare ptr @k_variant_n(ptr, ptr, i64, ptr)",
70
+ "declare ptr @k_variant_borrowed_n(ptr, ptr, i64, ptr)",
71
+ "declare ptr @k_variant_borrowed_direct_n(ptr, ptr, i64, ptr)",
72
+ "declare ptr @k_variant_unit_borrowed_n(ptr, ptr, i64)",
73
+ "declare ptr @k_variant_tag(ptr)",
74
+ "declare ptr @k_variant_payload(ptr)",
75
+ "declare i32 @k_equal(ptr, ptr)",
76
+ "declare i32 @k_variant_tag_matches(ptr, ptr, i64)"
77
+ ];
78
+ }
79
+
80
+ function cleanReg(r) {
81
+ if (!r) return "";
82
+ return String(r).replace(/^[%$]/, "");
83
+ }
84
+
85
+ function bytePtr(ctx, base, offset, prefix = "slot") {
86
+ const slot = ctx.tempName(prefix);
87
+ ctx.lines.push(` ${slot} = getelementptr inbounds i8, ptr ${base}, i64 ${offset}`);
88
+ return slot;
89
+ }
90
+
91
+ function storeI32At(ctx, base, offset, value) {
92
+ const slot = bytePtr(ctx, base, offset, "i32_slot");
93
+ ctx.lines.push(` store i32 ${value}, ptr ${slot}`);
94
+ }
95
+
96
+ function storeI64At(ctx, base, offset, value) {
97
+ const slot = bytePtr(ctx, base, offset, "i64_slot");
98
+ ctx.lines.push(` store i64 ${value}, ptr ${slot}`);
99
+ }
100
+
101
+ function storePtrAt(ctx, base, offset, value) {
102
+ const slot = bytePtr(ctx, base, offset, "ptr_slot");
103
+ ctx.lines.push(` store ptr ${value}, ptr ${slot}`);
104
+ }
105
+
106
+ function loadI32At(ctx, base, offset, prefix = "i32_val") {
107
+ const slot = bytePtr(ctx, base, offset, `${prefix}_slot`);
108
+ const value = ctx.tempName(prefix);
109
+ ctx.lines.push(` ${value} = load i32, ptr ${slot}`);
110
+ return value;
111
+ }
112
+
113
+ function loadI64At(ctx, base, offset, prefix = "i64_val") {
114
+ const slot = bytePtr(ctx, base, offset, `${prefix}_slot`);
115
+ const value = ctx.tempName(prefix);
116
+ ctx.lines.push(` ${value} = load i64, ptr ${slot}`);
117
+ return value;
118
+ }
119
+
120
+ function loadPtrAt(ctx, base, offset, prefix = "ptr_val") {
121
+ const slot = bytePtr(ctx, base, offset, `${prefix}_slot`);
122
+ const value = ctx.tempName(prefix);
123
+ ctx.lines.push(` ${value} = load ptr, ptr ${slot}`);
124
+ return value;
125
+ }
126
+
127
+ function lowerRawAlloc(ctx, requestedSize, alignedSize) {
128
+ const pointer = ctx.tempName("alloc");
129
+ ctx.lines.push(` ${pointer} = call ptr @k_rt_alloc(ptr %rt, i64 ${requestedSize})`);
130
+ return pointer;
131
+ }
132
+
133
+ function lowerRawVariant(ctx, tag, payload) {
134
+ const label = ctx.labelRef(tag);
135
+ if (ctx.runtimeMode === "compact") {
136
+ const variant = ctx.tempName("variant");
137
+ ctx.lines.push(` ${variant} = call ptr @k_variant_borrowed_direct_n(ptr %rt, ptr ${label.pointer}, i64 ${label.length}, ptr ${payload})`);
138
+ return variant;
139
+ }
140
+ const variant = lowerRawAlloc(ctx, K_VALUE_SIZE, K_VALUE_ALLOC_SIZE);
141
+ storeI32At(ctx, variant, K_VALUE_KIND_OFFSET, K_VALUE_KIND_VARIANT);
142
+ storePtrAt(ctx, variant, K_VALUE_RT_OFFSET, "%rt");
143
+ storePtrAt(ctx, variant, K_VALUE_VARIANT_TAG_OFFSET, label.pointer);
144
+ storeI64At(ctx, variant, K_VALUE_VARIANT_TAG_LENGTH_OFFSET, label.length);
145
+ storePtrAt(ctx, variant, K_VALUE_VARIANT_PAYLOAD_OFFSET, payload);
146
+ return variant;
147
+ }
148
+
149
+ function lowerProductGetAt(ctx, input, edgeIndex) {
150
+ const nonNullBlock = ctx.blockName("product_non_null");
151
+ const productBlock = ctx.blockName("product_kind");
152
+ const fieldBlock = ctx.blockName("product_field");
153
+ const missingBlock = ctx.blockName("product_missing");
154
+ const doneBlock = ctx.blockName("product_done");
155
+ const isMissing = ctx.tempName("product_is_missing");
156
+ ctx.lines.push(` ${isMissing} = icmp eq ptr ${input}, null`);
157
+ ctx.lines.push(` br i1 ${isMissing}, label %${missingBlock}, label %${nonNullBlock}`);
158
+
159
+ ctx.lines.push(`${nonNullBlock}:`);
160
+ const kind = loadI32At(ctx, input, K_VALUE_KIND_OFFSET, "product_kind");
161
+ const isProduct = ctx.tempName("product_kind_ok");
162
+ ctx.lines.push(` ${isProduct} = icmp eq i32 ${kind}, ${K_VALUE_KIND_PRODUCT}`);
163
+ ctx.lines.push(` br i1 ${isProduct}, label %${productBlock}, label %${missingBlock}`);
164
+
165
+ ctx.lines.push(`${productBlock}:`);
166
+ const count = loadI64At(ctx, input, K_VALUE_PRODUCT_COUNT_OFFSET, "product_count");
167
+ const inRange = ctx.tempName("product_index_ok");
168
+ ctx.lines.push(` ${inRange} = icmp ugt i64 ${count}, ${edgeIndex}`);
169
+ ctx.lines.push(` br i1 ${inRange}, label %${fieldBlock}, label %${missingBlock}`);
170
+
171
+ ctx.lines.push(`${fieldBlock}:`);
172
+ const fields = loadPtrAt(ctx, input, K_VALUE_PRODUCT_FIELDS_OFFSET, "product_fields_ptr");
173
+ const slot = bytePtr(ctx, fields, edgeIndex * K_FIELD_SIZE, "field_slot");
174
+ const value = loadPtrAt(ctx, slot, K_FIELD_VALUE_OFFSET, "field");
175
+ ctx.lines.push(` br label %${doneBlock}`);
176
+
177
+ ctx.lines.push(`${missingBlock}:`);
178
+ ctx.lines.push(` br label %${doneBlock}`);
179
+
180
+ ctx.lines.push(`${doneBlock}:`);
181
+ const resultValue = ctx.tempName("field_value");
182
+ ctx.lines.push(` ${resultValue} = phi ptr [${value}, %${fieldBlock}], [null, %${missingBlock}]`);
183
+ ctx.currentBlock = doneBlock;
184
+ return resultValue;
185
+ }
186
+
187
+ function lowerVariantMatchBranch(ctx, input, label, matchBlock, mismatchBlock) {
188
+ const nonNullBlock = ctx.blockName("tag_non_null");
189
+ const variantBlock = ctx.blockName("tag_variant");
190
+ const lengthBlock = ctx.blockName("tag_length");
191
+ const isMissing = ctx.tempName("tag_is_missing");
192
+ ctx.lines.push(` ${isMissing} = icmp eq ptr ${input}, null`);
193
+ ctx.lines.push(` br i1 ${isMissing}, label %${mismatchBlock}, label %${nonNullBlock}`);
194
+
195
+ ctx.lines.push(`${nonNullBlock}:`);
196
+ const kind = loadI32At(ctx, input, K_VALUE_KIND_OFFSET, "tag_kind");
197
+ const isVariant = ctx.tempName("tag_kind_ok");
198
+ ctx.lines.push(` ${isVariant} = icmp eq i32 ${kind}, ${K_VALUE_KIND_VARIANT}`);
199
+ ctx.lines.push(` br i1 ${isVariant}, label %${variantBlock}, label %${mismatchBlock}`);
200
+
201
+ ctx.lines.push(`${variantBlock}:`);
202
+ const tagLength = loadI64At(ctx, input, K_VALUE_VARIANT_TAG_LENGTH_OFFSET, "tag_length");
203
+ const lengthMatches = ctx.tempName("tag_length_ok");
204
+ ctx.lines.push(` ${lengthMatches} = icmp eq i64 ${tagLength}, ${label.length}`);
205
+
206
+ const bytes = [...Buffer.from(label.text, "utf8")];
207
+ if (bytes.length === 0) {
208
+ ctx.lines.push(` br i1 ${lengthMatches}, label %${matchBlock}, label %${mismatchBlock}`);
209
+ return;
210
+ }
211
+
212
+ ctx.lines.push(` br i1 ${lengthMatches}, label %${lengthBlock}, label %${mismatchBlock}`);
213
+ ctx.lines.push(`${lengthBlock}:`);
214
+ const tagPointer = loadPtrAt(ctx, input, K_VALUE_VARIANT_TAG_OFFSET, "tag_ptr");
215
+
216
+ for (let index = 0; index < bytes.length; index++) {
217
+ const tagBytePointer = bytePtr(ctx, tagPointer, index, "tag_byte_ptr");
218
+ const tagByte = ctx.tempName("tag_byte");
219
+ const byteMatches = ctx.tempName("tag_byte_ok");
220
+ const nextBlock = index === bytes.length - 1 ? matchBlock : ctx.blockName("tag_byte");
221
+ ctx.lines.push(` ${tagByte} = load i8, ptr ${tagBytePointer}`);
222
+ ctx.lines.push(` ${byteMatches} = icmp eq i8 ${tagByte}, ${bytes[index]}`);
223
+ ctx.lines.push(` br i1 ${byteMatches}, label %${nextBlock}, label %${mismatchBlock}`);
224
+ if (index !== bytes.length - 1) {
225
+ ctx.lines.push(`${nextBlock}:`);
226
+ }
227
+ }
228
+ }
229
+
230
+ function lowerVariantPayload(ctx, input) {
231
+ return loadPtrAt(ctx, input, K_VALUE_VARIANT_PAYLOAD_OFFSET, "payload");
232
+ }
233
+
234
+ function lowerRtMark(ctx) {
235
+ const block = loadPtrAt(ctx, "%rt", K_RT_BLOCKS_OFFSET, "mark_block");
236
+ const hasBlock = ctx.tempName("mark_has_block");
237
+ const loadBlock = ctx.blockName("mark_load");
238
+ const emptyBlock = ctx.blockName("mark_empty");
239
+ const doneBlock = ctx.blockName("mark_done");
240
+ ctx.lines.push(` ${hasBlock} = icmp ne ptr ${block}, null`);
241
+ ctx.lines.push(` br i1 ${hasBlock}, label %${loadBlock}, label %${emptyBlock}`);
242
+
243
+ ctx.lines.push(`${loadBlock}:`);
244
+ const usedValue = loadI64At(ctx, block, K_ARENA_BLOCK_USED_OFFSET, "mark_used_value");
245
+ ctx.lines.push(` br label %${doneBlock}`);
246
+
247
+ ctx.lines.push(`${emptyBlock}:`);
248
+ ctx.lines.push(` br label %${doneBlock}`);
249
+
250
+ ctx.lines.push(`${doneBlock}:`);
251
+ const used = ctx.tempName("mark_used");
252
+ const partial = ctx.tempName("mark");
253
+ const aggregate = ctx.tempName("mark");
254
+ ctx.lines.push(` ${used} = phi i64 [${usedValue}, %${loadBlock}], [0, %${emptyBlock}]`);
255
+ ctx.lines.push(` ${partial} = insertvalue %k_rt_mark undef, ptr ${block}, 0`);
256
+ ctx.lines.push(` ${aggregate} = insertvalue %k_rt_mark ${partial}, i64 ${used}, 1`);
257
+ ctx.currentBlock = doneBlock;
258
+ return { aggregate, block, used };
259
+ }
260
+
261
+ function lowerRtRewind(ctx, mark) {
262
+ const currentBlock = loadPtrAt(ctx, "%rt", K_RT_BLOCKS_OFFSET, "rewind_block");
263
+ const hasMark = ctx.tempName("rewind_has_mark");
264
+ const sameBlock = ctx.tempName("rewind_same_block");
265
+ const fast = ctx.tempName("rewind_fast_ok");
266
+ const fastBlock = ctx.blockName("rewind_fast");
267
+ const slowBlock = ctx.blockName("rewind_slow");
268
+ const doneBlock = ctx.blockName("rewind_done");
269
+ ctx.lines.push(` ${hasMark} = icmp ne ptr ${mark.block}, null`);
270
+ ctx.lines.push(` ${sameBlock} = icmp eq ptr ${currentBlock}, ${mark.block}`);
271
+ ctx.lines.push(` ${fast} = and i1 ${hasMark}, ${sameBlock}`);
272
+ ctx.lines.push(` br i1 ${fast}, label %${fastBlock}, label %${slowBlock}`);
273
+
274
+ ctx.lines.push(`${fastBlock}:`);
275
+ storeI64At(ctx, currentBlock, K_ARENA_BLOCK_USED_OFFSET, mark.used);
276
+ storePtrAt(ctx, "%rt", K_RT_UNIT_CACHE_OFFSET, "null");
277
+ storePtrAt(ctx, "%rt", K_RT_BIT0_CACHE_OFFSET, "null");
278
+ storePtrAt(ctx, "%rt", K_RT_BIT1_CACHE_OFFSET, "null");
279
+ storeI32At(ctx, "%rt", K_RT_HAS_REUSABLE_BLOCKS_OFFSET, 0);
280
+ ctx.lines.push(` br label %${doneBlock}`);
281
+
282
+ ctx.lines.push(`${slowBlock}:`);
283
+ ctx.lines.push(` call void @k_rt_rewind(ptr %rt, %k_rt_mark ${mark.aggregate})`);
284
+ ctx.lines.push(` br label %${doneBlock}`);
285
+
286
+ ctx.lines.push(`${doneBlock}:`);
287
+ ctx.currentBlock = doneBlock;
288
+ }
289
+
290
+ function nullCheck(ctx, val, failTarget) {
291
+ const isMissing = ctx.tempName("missing");
292
+ const okBlock = ctx.blockName("nonnull");
293
+ ctx.lines.push(` ${isMissing} = icmp eq ptr ${val}, null`);
294
+ ctx.lines.push(` br i1 ${isMissing}, label %${failTarget}, label %${okBlock}`);
295
+ ctx.lines.push(`${okBlock}:`);
296
+ ctx.currentBlock = okBlock;
297
+ }
298
+
299
+ function sameFuncName(a, b) {
300
+ if (!a || !b) return false;
301
+ if (a === b) return true;
302
+ const ca = cleanReg(a).replace(/^@/, "");
303
+ const cb = cleanReg(b).replace(/^@/, "");
304
+ return ca === cb;
305
+ }
306
+
307
+ function statusCheck(ctx, callResult, failTarget = "func_fail") {
308
+ const status = ctx.tempName("status");
309
+ const failed = ctx.tempName("failed");
310
+ const okBlock = ctx.blockName("ok");
311
+ const failedBlock = ctx.blockName("failed");
312
+ ctx.lines.push(` ${status} = extractvalue %k_result ${callResult}, 0`);
313
+ ctx.lines.push(` ${failed} = icmp ne i32 ${status}, 0`);
314
+ ctx.lines.push(` br i1 ${failed}, label %${failedBlock}, label %${okBlock}`);
315
+ ctx.lines.push(`${failedBlock}:`);
316
+ const isTail = ctx.tempName("is_tail");
317
+ const tailBlock = ctx.blockName("tail_return");
318
+ const reallyFailedBlock = ctx.blockName("really_failed");
319
+ ctx.lines.push(` ${isTail} = icmp eq i32 ${status}, 2`);
320
+ ctx.lines.push(` br i1 ${isTail}, label %${tailBlock}, label %${reallyFailedBlock}`);
321
+ ctx.lines.push(`${tailBlock}:`);
322
+ if (ctx.catchTail) {
323
+ const tailInput = ctx.tempName("tail_arg");
324
+ ctx.lines.push(` ${tailInput} = extractvalue %k_result ${callResult}, 1`);
325
+ ctx.lines.push(` store ptr ${tailInput}, ptr %tail_input_slot`);
326
+ ctx.lines.push(" br label %tail_loop");
327
+ } else {
328
+ ctx.lines.push(` ret %k_result ${callResult}`);
329
+ }
330
+ ctx.lines.push(`${reallyFailedBlock}:`);
331
+ ctx.lines.push(` br label %${failTarget}`);
332
+ ctx.lines.push(`${okBlock}:`);
333
+ ctx.currentBlock = okBlock;
334
+ }
335
+
336
+ function tailValueAfter(insts, startIndex, valueReg) {
337
+ let current = cleanReg(valueReg);
338
+ for (let i = startIndex; i < insts.length; i++) {
339
+ const inst = insts[i];
340
+ if (inst.op === "guard_pattern" || inst.op === "guard_code" || inst.op === "id") {
341
+ if (cleanReg(inst.src) !== current) return false;
342
+ current = cleanReg(inst.dest);
343
+ continue;
344
+ }
345
+ if (inst.op === "return" && cleanReg(inst.src) === current) {
346
+ return true;
347
+ }
348
+ return false;
349
+ }
350
+ return false;
351
+ }
352
+
353
+ function isSelfTailCall(insts, index, currentFuncName) {
354
+ if (!currentFuncName) return false;
355
+ const inst = insts[index];
356
+ if (inst.op !== "call" || !sameFuncName(inst.func, currentFuncName)) return false;
357
+ const dest = cleanReg(inst.dest);
358
+ return tailValueAfter(insts, index + 1, dest);
359
+ }
360
+
361
+ function hasSelfTailCall(insts, funcName, isTail = true) {
362
+ if (!Array.isArray(insts) || !funcName) return false;
363
+ for (let i = 0; i < insts.length; i++) {
364
+ const inst = insts[i];
365
+ if (isTail && isSelfTailCall(insts, i, funcName)) return true;
366
+ if (inst.branches) {
367
+ const unionIsTail = isTail && tailValueAfter(insts, i + 1, inst.dest);
368
+ for (let bIdx = 0; bIdx < inst.branches.length; bIdx++) {
369
+ const isLast = (bIdx === inst.branches.length - 1);
370
+ if (hasSelfTailCall(inst.branches[bIdx].body, funcName, unionIsTail && isLast)) return true;
371
+ }
372
+ }
373
+ }
374
+ return false;
375
+ }
376
+
377
+ function getFieldEdgeIndex(inst, regPatterns, kvmFunc) {
378
+ const srcReg = cleanReg(inst.src);
379
+ const srcPattern = regPatterns.get(srcReg) || (srcReg === "in" ? kvmFunc.inputPattern : null);
380
+ if (Array.isArray(srcPattern) && srcPattern.length > 0) {
381
+ const root = srcPattern[0];
382
+ if ((root[0] === "open-product" || root[0] === "closed-product") && Array.isArray(root[1])) {
383
+ const idx = root[1].findIndex(([l]) => l === inst.label);
384
+ if (idx >= 0) return idx;
385
+ }
386
+ }
387
+ if (Array.isArray(inst.pattern) && inst.pattern.length > 0) {
388
+ const root = inst.pattern[0];
389
+ if ((root[0] === "open-product" || root[0] === "closed-product") && Array.isArray(root[1])) {
390
+ const idx = root[1].findIndex(([l]) => l === inst.label);
391
+ if (idx >= 0) return idx;
392
+ }
393
+ }
394
+ return -1;
395
+ }
396
+
397
+ function scanCalls(insts, compiled, queue) {
398
+ if (!Array.isArray(insts)) return;
399
+ for (const inst of insts) {
400
+ if (inst.op === "call" && !compiled.has(inst.func) && !queue.includes(inst.func)) {
401
+ queue.push(inst.func);
402
+ }
403
+ if (inst.branches) {
404
+ for (const b of inst.branches) {
405
+ scanCalls(b.body, compiled, queue);
406
+ }
407
+ }
408
+ }
409
+ }
410
+
411
+ export function compileKVMModuleToLLVM(entryName, kvmProgram, options = {}) {
412
+ const functions = kvmProgram.functions || (typeof kvmProgram === "object" && !Array.isArray(kvmProgram) ? kvmProgram : null);
413
+ if (!functions) throw new Error("Expected .kvm input to contain functions");
414
+
415
+ const entry = entryName || kvmProgram.entry || "__main__";
416
+ const entryFunc = functions[entry];
417
+ if (!entryFunc) throw new Error(`kVM entry function '${entry}' not found in program`);
418
+
419
+ const compiled = new Set();
420
+ const queue = [entry];
421
+ while (queue.length > 0) {
422
+ const name = queue.shift();
423
+ if (compiled.has(name)) continue;
424
+ compiled.add(name);
425
+ const fn = functions[name];
426
+ if (fn?.body) scanCalls(fn.body, compiled, queue);
427
+ }
428
+
429
+ const functionNames = new Map();
430
+ functionNames.set(entry, "@k_main");
431
+ let relIndex = 0;
432
+ for (const name of compiled) {
433
+ if (name !== entry) {
434
+ functionNames.set(name, `@k_rel_${llvmIdentifier(name)}_${relIndex++}`);
435
+ }
436
+ }
437
+
438
+ const labels = new Map();
439
+ const runtimeMode = options.runtimeMode || "fast";
440
+
441
+ const moduleCtx = {
442
+ temp: 0,
443
+ block: 0,
444
+ labels,
445
+ functionNames,
446
+ runtimeMode,
447
+ armIndex: 0,
448
+ unionExprIndex: 0,
449
+ syntheticFunctions: [],
450
+ tempName(prefix) { return `%${prefix}${this.temp++}`; },
451
+ blockName(prefix) { return `bb_${prefix}${this.block++}`; },
452
+ labelRef(text) {
453
+ const sText = String(text);
454
+ let global = this.labels.get(sText);
455
+ if (!global) {
456
+ const index = this.labels.size;
457
+ global = {
458
+ name: `@k_label_${index}`,
459
+ length: Buffer.byteLength(sText, "utf8") + 1,
460
+ byteLength: Buffer.byteLength(sText, "utf8"),
461
+ text: sText
462
+ };
463
+ this.labels.set(sText, global);
464
+ }
465
+ const pointer = this.tempName("label");
466
+ this.lines.push(` ${pointer} = getelementptr inbounds [${global.length} x i8], ptr ${global.name}, i64 0, i64 0`);
467
+ return { pointer, length: global.byteLength, text: sText };
468
+ }
469
+ };
470
+
471
+ const functionBodies = [];
472
+
473
+ for (const name of compiled) {
474
+ const kvmFunc = functions[name];
475
+ const symbol = functionNames.get(name);
476
+ const linkage = name === entry ? "" : "internal";
477
+
478
+ const funcBody = lowerKVMFunction(kvmFunc, symbol, name, moduleCtx, linkage, { ...options, tailRef: name });
479
+ functionBodies.push(funcBody);
480
+ }
481
+
482
+ const relation = options.relation || kvmProgram.relation || entry;
483
+ const payload = JSON.stringify({
484
+ format: ARTIFACT_FORMAT,
485
+ version: ARTIFACT_VERSION,
486
+ relation,
487
+ kvm: kvmProgram
488
+ });
489
+ const payloadBytes = Buffer.byteLength(payload, "utf8") + 1;
490
+ const symbol = llvmIdentifier(options.symbol || relation);
491
+
492
+ const labelGlobalLines = [...labels.values()].map((label) =>
493
+ `${label.name} = private unnamed_addr constant [${label.length} x i8] c"${cStringBytes(label.text)}", align 1`
494
+ );
495
+
496
+ return [
497
+ "; k-llvm prototype artifact (via kVM)",
498
+ `; relation: ${relation}`,
499
+ `source_filename = "k-llvm:${symbol}"`,
500
+ "",
501
+ `@k_llvm_metadata = private unnamed_addr constant [${payloadBytes} x i8] c"${cStringBytes(payload)}", align 1`,
502
+ ...labelGlobalLines,
503
+ "",
504
+ ...runtimeDeclarations(),
505
+ "",
506
+ ...moduleCtx.syntheticFunctions,
507
+ ...functionBodies
508
+ ].join("\n");
509
+ }
510
+
511
+ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", options = {}) {
512
+ const ctx = {
513
+ temp: moduleCtx.temp,
514
+ block: moduleCtx.block,
515
+ labels: moduleCtx.labels,
516
+ functionNames: moduleCtx.functionNames,
517
+ runtimeMode: moduleCtx.runtimeMode,
518
+ allocas: [],
519
+ lines: [],
520
+ currentBlock: "entry",
521
+ tempName(prefix) {
522
+ const t = moduleCtx.temp++;
523
+ return `%${prefix}${t}`;
524
+ },
525
+ blockName(prefix) {
526
+ const b = moduleCtx.block++;
527
+ return `bb_${prefix}${b}`;
528
+ },
529
+ labelRef(text) {
530
+ return moduleCtx.labelRef.call(this, text);
531
+ }
532
+ };
533
+
534
+ const tailRef = options.tailRef !== undefined ? options.tailRef : funcName;
535
+ const isCatchTail = (sameFuncName(funcName, tailRef) || !options.tailRef) && hasSelfTailCall(kvmFunc.body, tailRef);
536
+ ctx.catchTail = isCatchTail;
537
+ const inputVar = isCatchTail ? "%tail_input" : "%input";
538
+
539
+ if (isCatchTail) {
540
+ ctx.allocas.push(" %tail_input_slot = alloca ptr");
541
+ ctx.lines.push(" store ptr %input, ptr %tail_input_slot");
542
+ ctx.lines.push(" br label %tail_loop");
543
+ ctx.lines.push("tail_loop:");
544
+ ctx.lines.push(" %tail_input = load ptr, ptr %tail_input_slot");
545
+ ctx.currentBlock = "tail_loop";
546
+ }
547
+
548
+ const rootRegValues = new Map();
549
+ const rootRegPatterns = new Map();
550
+ rootRegValues.set("in", inputVar);
551
+ if (Array.isArray(kvmFunc.inputPattern)) {
552
+ rootRegPatterns.set("in", kvmFunc.inputPattern);
553
+ }
554
+
555
+ function compileInstList(insts, compOptions = {}) {
556
+ const failTarget = compOptions.failTarget || "func_fail";
557
+ const returnTarget = compOptions.returnTarget || null;
558
+ const nextBlock = compOptions.nextBlock || null;
559
+ const isTopLevel = compOptions.isTopLevel !== false;
560
+ const regValues = compOptions.regValues || rootRegValues;
561
+ const regPatterns = compOptions.regPatterns || rootRegPatterns;
562
+ const getReg = (r) => regValues.get(cleanReg(r)) || regValues.get("in") || inputVar;
563
+
564
+ for (let i = 0; i < insts.length; i++) {
565
+ const inst = insts[i];
566
+ switch (inst.op) {
567
+ case "guard_pattern":
568
+ case "guard_code":
569
+ case "id": {
570
+ const rawSrc = cleanReg(inst.src);
571
+ const dest = cleanReg(inst.dest);
572
+ const v = getReg(rawSrc);
573
+ regValues.set(dest, v);
574
+ const pat = inst.pattern || regPatterns.get(rawSrc);
575
+ if (pat) regPatterns.set(dest, pat);
576
+ break;
577
+ }
578
+ case "fail": {
579
+ ctx.lines.push(` br label %${failTarget}`);
580
+ return;
581
+ }
582
+ case "return": {
583
+ const rawSrc = cleanReg(inst.src);
584
+ const v = getReg(rawSrc);
585
+ if (returnTarget) {
586
+ regValues.set(cleanReg(returnTarget), v);
587
+ if (nextBlock) ctx.lines.push(` br label %${nextBlock}`);
588
+ } else {
589
+ const status = ctx.tempName("status");
590
+ const result = ctx.tempName("result");
591
+ ctx.lines.push(` ${status} = insertvalue %k_result undef, i32 0, 0`);
592
+ ctx.lines.push(` ${result} = insertvalue %k_result ${status}, ptr ${v}, 1`);
593
+ ctx.lines.push(` ret %k_result ${result}`);
594
+ }
595
+ return;
596
+ }
597
+ case "project_field": {
598
+ const input = getReg(inst.src);
599
+ nullCheck(ctx, input, failTarget);
600
+ const edgeIndex = getFieldEdgeIndex(inst, regPatterns, kvmFunc);
601
+ let val;
602
+ if (edgeIndex >= 0 && ctx.runtimeMode !== "compact") {
603
+ val = lowerProductGetAt(ctx, input, edgeIndex);
604
+ } else {
605
+ const label = ctx.labelRef(inst.label);
606
+ val = ctx.tempName("field");
607
+ if (edgeIndex >= 0) {
608
+ ctx.lines.push(` ${val} = call ptr @k_product_get_at(ptr ${input}, i64 ${edgeIndex})`);
609
+ } else {
610
+ ctx.lines.push(` ${val} = call ptr @k_product_get_n(ptr ${input}, ptr ${label.pointer}, i64 ${label.length})`);
611
+ }
612
+ }
613
+ nullCheck(ctx, val, failTarget);
614
+ regValues.set(cleanReg(inst.dest), val);
615
+ if (inst.pattern) regPatterns.set(cleanReg(inst.dest), inst.pattern);
616
+ break;
617
+ }
618
+ case "project_variant": {
619
+ const input = getReg(inst.src);
620
+ nullCheck(ctx, input, failTarget);
621
+ const label = ctx.labelRef(inst.tag);
622
+ const matchBlock = ctx.blockName("tag_match");
623
+ const mismatchBlock = ctx.blockName("tag_mismatch");
624
+ if (ctx.runtimeMode === "compact") {
625
+ const compare = ctx.tempName("tagcmp");
626
+ const matches = ctx.tempName("tagmatch");
627
+ ctx.lines.push(` ${compare} = call i32 @k_variant_tag_matches(ptr ${input}, ptr ${label.pointer}, i64 ${label.length})`);
628
+ ctx.lines.push(` ${matches} = icmp ne i32 ${compare}, 0`);
629
+ ctx.lines.push(` br i1 ${matches}, label %${matchBlock}, label %${mismatchBlock}`);
630
+ } else {
631
+ lowerVariantMatchBranch(ctx, input, label, matchBlock, mismatchBlock);
632
+ }
633
+ ctx.lines.push(`${mismatchBlock}:`);
634
+ ctx.lines.push(` br label %${failTarget}`);
635
+ ctx.lines.push(`${matchBlock}:`);
636
+ ctx.currentBlock = matchBlock;
637
+ let payload;
638
+ if (ctx.runtimeMode === "compact") {
639
+ payload = ctx.tempName("payload");
640
+ ctx.lines.push(` ${payload} = call ptr @k_variant_payload(ptr ${input})`);
641
+ } else {
642
+ payload = lowerVariantPayload(ctx, input);
643
+ }
644
+ nullCheck(ctx, payload, failTarget);
645
+ regValues.set(cleanReg(inst.dest), payload);
646
+ if (inst.pattern) regPatterns.set(cleanReg(inst.dest), inst.pattern);
647
+ break;
648
+ }
649
+ case "make_variant": {
650
+ const payload = getReg(inst.src);
651
+ const variant = lowerRawVariant(ctx, inst.tag, payload);
652
+ regValues.set(cleanReg(inst.dest), variant);
653
+ break;
654
+ }
655
+ case "call": {
656
+ const v = getReg(inst.src);
657
+ if (isSelfTailCall(insts, i, tailRef)) {
658
+ if (ctx.catchTail) {
659
+ ctx.lines.push(` store ptr ${v}, ptr %tail_input_slot`);
660
+ ctx.lines.push(" br label %tail_loop");
661
+ return;
662
+ } else {
663
+ const tailStatus = ctx.tempName("tail_status");
664
+ const tailResult = ctx.tempName("tail_result");
665
+ ctx.lines.push(` ${tailStatus} = insertvalue %k_result undef, i32 2, 0`);
666
+ ctx.lines.push(` ${tailResult} = insertvalue %k_result ${tailStatus}, ptr ${v}, 1`);
667
+ ctx.lines.push(` ret %k_result ${tailResult}`);
668
+ return;
669
+ }
670
+ }
671
+ const targetSymbol = ctx.functionNames.get(inst.func) || `@k_rel_${llvmIdentifier(inst.func)}`;
672
+ const callRes = ctx.tempName("call");
673
+ ctx.lines.push(` ${callRes} = call %k_result ${targetSymbol}(ptr %rt, ptr ${v})`);
674
+ statusCheck(ctx, callRes, failTarget);
675
+ const retVal = ctx.tempName("call_val");
676
+ ctx.lines.push(` ${retVal} = extractvalue %k_result ${callRes}, 1`);
677
+ regValues.set(cleanReg(inst.dest), retVal);
678
+ break;
679
+ }
680
+ case "call_intrinsic": {
681
+ const v = getReg(inst.src);
682
+ const intrinsicName = inst.intrinsic || inst.name;
683
+ const res = lowerIntrinsicCall(ctx, intrinsicName, v, failTarget);
684
+ regValues.set(cleanReg(inst.dest), res);
685
+ break;
686
+ }
687
+ case "product": {
688
+ const N = inst.branches.length;
689
+ const fieldValues = [];
690
+ const pSrc = getReg(inst.src);
691
+ const pPattern = regPatterns.get(cleanReg(inst.src)) || (cleanReg(inst.src) === "in" ? regPatterns.get("in") : null);
692
+
693
+ for (let bIdx = 0; bIdx < N; bIdx++) {
694
+ const branch = inst.branches[bIdx];
695
+ const branchRes = ctx.tempName(`f${bIdx}_val`);
696
+ const branchBlock = ctx.blockName(`field_${bIdx}`);
697
+ const branchDone = ctx.blockName(`field_${bIdx}_done`);
698
+
699
+ ctx.lines.push(` br label %${branchBlock}`);
700
+ ctx.lines.push(`${branchBlock}:`);
701
+ ctx.currentBlock = branchBlock;
702
+
703
+ const branchRegValues = new Map(regValues);
704
+ const branchRegPatterns = new Map(regPatterns);
705
+ branchRegValues.set("in", pSrc);
706
+ if (pPattern) branchRegPatterns.set("in", pPattern);
707
+ else branchRegPatterns.delete("in");
708
+
709
+ compileInstList(branch.body, {
710
+ isTopLevel: false,
711
+ regValues: branchRegValues,
712
+ regPatterns: branchRegPatterns,
713
+ returnTarget: branchRes,
714
+ failTarget,
715
+ nextBlock: branchDone
716
+ });
717
+
718
+ ctx.lines.push(`${branchDone}:`);
719
+ ctx.currentBlock = branchDone;
720
+ const fVal = branchRegValues.get(cleanReg(branchRes)) || branchRes;
721
+ fieldValues.push({ label: branch.label, val: fVal });
722
+ }
723
+
724
+ const requestedSize = K_PRODUCT_HEADER_SIZE + (K_FIELD_SIZE * N);
725
+ const product = lowerRawAlloc(ctx, requestedSize, alignArenaSize(requestedSize));
726
+ nullCheck(ctx, product, failTarget);
727
+ const fields = bytePtr(ctx, product, K_PRODUCT_HEADER_SIZE, "product_fields");
728
+ storeI32At(ctx, product, K_VALUE_KIND_OFFSET, K_VALUE_KIND_PRODUCT);
729
+ storePtrAt(ctx, product, K_VALUE_RT_OFFSET, "%rt");
730
+ storeI64At(ctx, product, K_VALUE_PRODUCT_COUNT_OFFSET, N);
731
+ storeI64At(ctx, product, K_VALUE_PRODUCT_CAPACITY_OFFSET, N);
732
+ storePtrAt(ctx, product, K_VALUE_PRODUCT_FIELDS_OFFSET, fields);
733
+
734
+ const sortedFields = [...fieldValues].sort((a, b) => {
735
+ if (a.label < b.label) return -1;
736
+ if (a.label > b.label) return 1;
737
+ return 0;
738
+ });
739
+
740
+ for (let sIdx = 0; sIdx < N; sIdx++) {
741
+ const { label: fLabel, val: fVal } = sortedFields[sIdx];
742
+ const labelRef = ctx.labelRef(fLabel);
743
+ const fieldByteSlot = bytePtr(ctx, fields, sIdx * K_FIELD_SIZE, "field_slot");
744
+ storePtrAt(ctx, fieldByteSlot, K_FIELD_LABEL_OFFSET, labelRef.pointer);
745
+ storeI64At(ctx, fieldByteSlot, K_FIELD_LABEL_LENGTH_OFFSET, labelRef.length);
746
+ storePtrAt(ctx, fieldByteSlot, K_FIELD_VALUE_OFFSET, fVal);
747
+ }
748
+
749
+ regValues.set(cleanReg(inst.dest), product);
750
+ break;
751
+ }
752
+ case "union": {
753
+ const uInput = getReg(inst.src);
754
+
755
+ if (!isTopLevel) {
756
+ // Nested union: synthesize a helper function @k_union_expr_${index}
757
+ const exprIndex = moduleCtx.unionExprIndex++;
758
+ const exprName = `@k_union_expr_${exprIndex}`;
759
+ const exprFunc = {
760
+ name: `union_expr_${exprIndex}`,
761
+ inputPattern: kvmFunc.inputPattern,
762
+ outputPattern: kvmFunc.outputPattern,
763
+ body: [
764
+ { op: "id", dest: "%v0", src: "%in" },
765
+ { ...inst, dest: "%v1", src: "%v0" },
766
+ { op: "return", src: "%v1" }
767
+ ]
768
+ };
769
+ const exprBody = lowerKVMFunction(exprFunc, exprName, exprFunc.name, moduleCtx, "internal", { ...options, tailRef: null });
770
+ moduleCtx.syntheticFunctions.push(exprBody);
771
+
772
+ const unionCall = ctx.tempName("call");
773
+ ctx.lines.push(` ${unionCall} = call %k_result ${exprName}(ptr %rt, ptr ${uInput})`);
774
+ statusCheck(ctx, unionCall, failTarget);
775
+ const unionValue = ctx.tempName("union_value");
776
+ ctx.lines.push(` ${unionValue} = extractvalue %k_result ${unionCall}, 1`);
777
+ regValues.set(cleanReg(inst.dest), unionValue);
778
+ break;
779
+ }
780
+
781
+ // Top-level union: synthesize arm functions @k_union_arm_${index}
782
+ const mark = lowerRtMark(ctx);
783
+ const unionDone = ctx.blockName("union_done");
784
+ const N = inst.branches.length;
785
+ const armResults = [];
786
+
787
+ for (let bIdx = 0; bIdx < N; bIdx++) {
788
+ const branch = inst.branches[bIdx];
789
+ const isLast = (bIdx === N - 1);
790
+ const armIndex = moduleCtx.armIndex++;
791
+ const armName = `@k_union_arm_${armIndex}`;
792
+
793
+ const armFunc = {
794
+ name: `union_arm_${armIndex}`,
795
+ inputPattern: kvmFunc.inputPattern,
796
+ outputPattern: kvmFunc.outputPattern,
797
+ body: branch.body
798
+ };
799
+ const armTailRef = (isLast && tailValueAfter(insts, i + 1, inst.dest)) ? tailRef : null;
800
+ const armBody = lowerKVMFunction(armFunc, armName, armFunc.name, moduleCtx, "internal", { ...options, tailRef: armTailRef });
801
+ moduleCtx.syntheticFunctions.push(armBody);
802
+
803
+ const armCall = ctx.tempName("call");
804
+ ctx.lines.push(` ${armCall} = call %k_result ${armName}(ptr %rt, ptr ${uInput})`);
805
+ const status = ctx.tempName("status");
806
+ const ok = ctx.tempName("ok");
807
+ const isTail = ctx.tempName("is_tail");
808
+ const tailBlock = ctx.blockName("arm_tail");
809
+ const nonTailBlock = ctx.blockName("arm_nontail");
810
+ const okBlock = ctx.blockName("arm_ok");
811
+ const failBlock = isLast ? failTarget : ctx.blockName("arm_fail");
812
+
813
+ ctx.lines.push(` ${status} = extractvalue %k_result ${armCall}, 0`);
814
+ ctx.lines.push(` ${isTail} = icmp eq i32 ${status}, 2`);
815
+ ctx.lines.push(` br i1 ${isTail}, label %${tailBlock}, label %${nonTailBlock}`);
816
+
817
+ ctx.lines.push(`${tailBlock}:`);
818
+ if (ctx.catchTail) {
819
+ const tailArg = ctx.tempName("arm_tail_arg");
820
+ ctx.lines.push(` ${tailArg} = extractvalue %k_result ${armCall}, 1`);
821
+ ctx.lines.push(` store ptr ${tailArg}, ptr %tail_input_slot`);
822
+ ctx.lines.push(" br label %tail_loop");
823
+ } else {
824
+ ctx.lines.push(` ret %k_result ${armCall}`);
825
+ }
826
+
827
+ ctx.lines.push(`${nonTailBlock}:`);
828
+ ctx.currentBlock = nonTailBlock;
829
+ ctx.lines.push(` ${ok} = icmp eq i32 ${status}, 0`);
830
+ ctx.lines.push(` br i1 ${ok}, label %${okBlock}, label %${failBlock}`);
831
+
832
+ ctx.lines.push(`${okBlock}:`);
833
+ ctx.currentBlock = okBlock;
834
+ const armVal = ctx.tempName("arm_val");
835
+ ctx.lines.push(` ${armVal} = extractvalue %k_result ${armCall}, 1`);
836
+ armResults.push({ block: okBlock, val: armVal });
837
+ ctx.lines.push(` br label %${unionDone}`);
838
+
839
+ if (!isLast) {
840
+ ctx.lines.push(`${failBlock}:`);
841
+ ctx.currentBlock = failBlock;
842
+ lowerRtRewind(ctx, mark);
843
+ }
844
+ }
845
+
846
+ ctx.lines.push(`${unionDone}:`);
847
+ ctx.currentBlock = unionDone;
848
+ const unionPhi = ctx.tempName("union_res");
849
+ const phiSources = armResults.map((r) => `[${r.val}, %${r.block}]`).join(", ");
850
+ ctx.lines.push(` ${unionPhi} = phi ptr ${phiSources}`);
851
+ regValues.set(cleanReg(inst.dest), unionPhi);
852
+ break;
853
+ }
854
+ default:
855
+ throw new Error(`Unsupported kVM instruction in LLVM backend: ${inst.op}`);
856
+ }
857
+ }
858
+ }
859
+
860
+ compileInstList(kvmFunc.body, { isTopLevel: true });
861
+
862
+ ctx.lines.push("func_fail:");
863
+ const failStatus = ctx.tempName("fail_status");
864
+ const failResult = ctx.tempName("fail_result");
865
+ ctx.lines.push(` ${failStatus} = insertvalue %k_result undef, i32 1, 0`);
866
+ ctx.lines.push(` ${failResult} = insertvalue %k_result ${failStatus}, ptr null, 1`);
867
+ ctx.lines.push(` ret %k_result ${failResult}`);
868
+
869
+ const prefix = linkage ? `define ${linkage} %k_result` : "define %k_result";
870
+ return [
871
+ `${prefix} ${symbol}(ptr %rt, ptr %input) {`,
872
+ "entry:",
873
+ ...ctx.allocas,
874
+ ...ctx.lines,
875
+ "}",
876
+ ""
877
+ ].join("\n");
878
+ }
879
+
880
+ function lowerIntrinsicCall(ctx, name, input, failTarget) {
881
+ if (!isIntrinsic(name)) {
882
+ throw unsupportedIntrinsic("LLVM kVM compiler", name);
883
+ }
884
+
885
+ if (name.startsWith("float64_")) {
886
+ const rawVal = loadPtrAt(ctx, input, K_VALUE_VARIANT_PAYLOAD_OFFSET, "f64_payload");
887
+ const fVal = ctx.tempName("f64");
888
+ ctx.lines.push(` ${fVal} = load double, ptr ${rawVal}`);
889
+
890
+ switch (name) {
891
+ case "float64_neg": {
892
+ const res = ctx.tempName("f64_neg");
893
+ ctx.lines.push(` ${res} = fneg double ${fVal}`);
894
+ return lowerBoxFloat64(ctx, res);
895
+ }
896
+ case "float64_add":
897
+ case "float64_sub":
898
+ case "float64_mul":
899
+ case "float64_div": {
900
+ const leftPtr = lowerProductGetAt(ctx, input, 0);
901
+ const rightPtr = lowerProductGetAt(ctx, input, 1);
902
+ const lRaw = loadPtrAt(ctx, leftPtr, K_VALUE_VARIANT_PAYLOAD_OFFSET, "f64_lpayload");
903
+ const rRaw = loadPtrAt(ctx, rightPtr, K_VALUE_VARIANT_PAYLOAD_OFFSET, "f64_rpayload");
904
+ const lVal = ctx.tempName("f64_l");
905
+ const rVal = ctx.tempName("f64_r");
906
+ ctx.lines.push(` ${lVal} = load double, ptr ${lRaw}`);
907
+ ctx.lines.push(` ${rVal} = load double, ptr ${rRaw}`);
908
+ const op = name === "float64_add" ? "fadd" : name === "float64_sub" ? "fsub" : name === "float64_mul" ? "fmul" : "fdiv";
909
+ const res = ctx.tempName("f64_res");
910
+ ctx.lines.push(` ${res} = ${op} double ${lVal}, ${rVal}`);
911
+ return lowerBoxFloat64(ctx, res);
912
+ }
913
+ }
914
+ }
915
+
916
+ if (name.startsWith("int32_")) {
917
+ const leftPtr = lowerProductGetAt(ctx, input, 0);
918
+ const rightPtr = lowerProductGetAt(ctx, input, 1);
919
+ const lVal = loadI32At(ctx, leftPtr, K_VALUE_VARIANT_PAYLOAD_OFFSET, "i32_l");
920
+ const rVal = loadI32At(ctx, rightPtr, K_VALUE_VARIANT_PAYLOAD_OFFSET, "i32_r");
921
+
922
+ switch (name) {
923
+ case "int32_add":
924
+ case "int32_sub":
925
+ case "int32_mul":
926
+ case "int32_div":
927
+ case "int32_mod":
928
+ case "int32_bit_and":
929
+ case "int32_bit_or":
930
+ case "int32_bit_xor":
931
+ case "int32_bit_shl":
932
+ case "int32_bit_shr": {
933
+ const opMap = {
934
+ int32_add: "add",
935
+ int32_sub: "sub",
936
+ int32_mul: "mul",
937
+ int32_div: "sdiv",
938
+ int32_mod: "srem",
939
+ int32_bit_and: "and",
940
+ int32_bit_or: "or",
941
+ int32_bit_xor: "xor",
942
+ int32_bit_shl: "shl",
943
+ int32_bit_shr: "ashr"
944
+ };
945
+ const op = opMap[name];
946
+ const res = ctx.tempName("i32_res");
947
+ ctx.lines.push(` ${res} = ${op} i32 ${lVal}, ${rVal}`);
948
+ return lowerBoxInt32(ctx, res);
949
+ }
950
+ case "int32_eq":
951
+ case "int32_lt":
952
+ case "int32_gt": {
953
+ const condMap = { int32_eq: "eq", int32_lt: "slt", int32_gt: "sgt" };
954
+ const cond = condMap[name];
955
+ const cmp = ctx.tempName("i32_cmp");
956
+ ctx.lines.push(` ${cmp} = icmp ${cond} i32 ${lVal}, ${rVal}`);
957
+ return lowerBoxBool(ctx, cmp);
958
+ }
959
+ }
960
+ }
961
+
962
+ throw unsupportedIntrinsic("LLVM kVM compiler", name);
963
+ }
964
+
965
+ function lowerBoxFloat64(ctx, val) {
966
+ const payloadAlloc = lowerRawAlloc(ctx, 8, 8);
967
+ ctx.lines.push(` store double ${val}, ptr ${payloadAlloc}`);
968
+ return lowerRawVariant(ctx, "Float64", payloadAlloc);
969
+ }
970
+
971
+ function lowerBoxInt32(ctx, val) {
972
+ const payloadAlloc = lowerRawAlloc(ctx, 4, 4);
973
+ ctx.lines.push(` store i32 ${val}, ptr ${payloadAlloc}`);
974
+ return lowerRawVariant(ctx, "Int32", payloadAlloc);
975
+ }
976
+
977
+ function lowerBoxBool(ctx, cmpVal) {
978
+ const trueBlock = ctx.blockName("bool_true");
979
+ const falseBlock = ctx.blockName("bool_false");
980
+ const doneBlock = ctx.blockName("bool_done");
981
+
982
+ ctx.lines.push(` br i1 ${cmpVal}, label %${trueBlock}, label %${falseBlock}`);
983
+ ctx.lines.push(`${trueBlock}:`);
984
+ const trueVal = ctx.tempName("true_val");
985
+ ctx.lines.push(` ${trueVal} = call ptr @k_bit1(ptr %rt)`);
986
+ ctx.lines.push(` br label %${doneBlock}`);
987
+
988
+ ctx.lines.push(`${falseBlock}:`);
989
+ const falseVal = ctx.tempName("false_val");
990
+ ctx.lines.push(` ${falseVal} = call ptr @k_bit0(ptr %rt)`);
991
+ ctx.lines.push(` br label %${doneBlock}`);
992
+
993
+ ctx.lines.push(`${doneBlock}:`);
994
+ ctx.currentBlock = doneBlock;
995
+ const phi = ctx.tempName("bool_res");
996
+ ctx.lines.push(` ${phi} = phi ptr [${trueVal}, %${trueBlock}], [${falseVal}, %${falseBlock}]`);
997
+ return phi;
998
+ }