@scriptc/compiler 0.0.0 → 0.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 (206) hide show
  1. package/LICENSE +202 -0
  2. package/ambient/package.json +3 -0
  3. package/ambient/scriptc-node-fallback.d.ts +3001 -0
  4. package/ambient/scriptc-overrides.d.ts +95 -0
  5. package/ambient/scriptc.d.ts +74 -0
  6. package/dist/backend/cc.d.ts +169 -0
  7. package/dist/backend/cc.js +897 -0
  8. package/dist/backend/cc.js.map +1 -0
  9. package/dist/backend/emission/emit-async.d.ts +145 -0
  10. package/dist/backend/emission/emit-async.js +996 -0
  11. package/dist/backend/emission/emit-async.js.map +1 -0
  12. package/dist/backend/emission/emit-exprs.d.ts +3 -0
  13. package/dist/backend/emission/emit-exprs.js +5949 -0
  14. package/dist/backend/emission/emit-exprs.js.map +1 -0
  15. package/dist/backend/emission/emit-island.d.ts +45 -0
  16. package/dist/backend/emission/emit-island.js +271 -0
  17. package/dist/backend/emission/emit-island.js.map +1 -0
  18. package/dist/backend/emission/emit-shapes.d.ts +142 -0
  19. package/dist/backend/emission/emit-shapes.js +575 -0
  20. package/dist/backend/emission/emit-shapes.js.map +1 -0
  21. package/dist/backend/emission/emit-stmts.d.ts +78 -0
  22. package/dist/backend/emission/emit-stmts.js +960 -0
  23. package/dist/backend/emission/emit-stmts.js.map +1 -0
  24. package/dist/backend/emission/emit-types.d.ts +65 -0
  25. package/dist/backend/emission/emit-types.js +652 -0
  26. package/dist/backend/emission/emit-types.js.map +1 -0
  27. package/dist/backend/emission/emit-walkers.d.ts +154 -0
  28. package/dist/backend/emission/emit-walkers.js +1587 -0
  29. package/dist/backend/emission/emit-walkers.js.map +1 -0
  30. package/dist/backend/emission/emitter.d.ts +452 -0
  31. package/dist/backend/emission/emitter.js +1249 -0
  32. package/dist/backend/emission/emitter.js.map +1 -0
  33. package/dist/backend/emission/may-throw.d.ts +15 -0
  34. package/dist/backend/emission/may-throw.js +250 -0
  35. package/dist/backend/emission/may-throw.js.map +1 -0
  36. package/dist/backend/llvm/blocks.d.ts +21 -0
  37. package/dist/backend/llvm/blocks.js +68 -0
  38. package/dist/backend/llvm/blocks.js.map +1 -0
  39. package/dist/backend/llvm/classes.d.ts +84 -0
  40. package/dist/backend/llvm/classes.js +438 -0
  41. package/dist/backend/llvm/classes.js.map +1 -0
  42. package/dist/backend/llvm/dyn.d.ts +123 -0
  43. package/dist/backend/llvm/dyn.js +2624 -0
  44. package/dist/backend/llvm/dyn.js.map +1 -0
  45. package/dist/backend/llvm/emitter.d.ts +3 -0
  46. package/dist/backend/llvm/emitter.js +10102 -0
  47. package/dist/backend/llvm/emitter.js.map +1 -0
  48. package/dist/backend/llvm/shapes.d.ts +78 -0
  49. package/dist/backend/llvm/shapes.js +754 -0
  50. package/dist/backend/llvm/shapes.js.map +1 -0
  51. package/dist/backend/llvm/unsupported.d.ts +6 -0
  52. package/dist/backend/llvm/unsupported.js +12 -0
  53. package/dist/backend/llvm/unsupported.js.map +1 -0
  54. package/dist/backend/llvm/walkers.d.ts +58 -0
  55. package/dist/backend/llvm/walkers.js +793 -0
  56. package/dist/backend/llvm/walkers.js.map +1 -0
  57. package/dist/backend/mangle.d.ts +124 -0
  58. package/dist/backend/mangle.js +232 -0
  59. package/dist/backend/mangle.js.map +1 -0
  60. package/dist/coverage/report.d.ts +65 -0
  61. package/dist/coverage/report.js +238 -0
  62. package/dist/coverage/report.js.map +1 -0
  63. package/dist/diagnostics/diagnostic.d.ts +140 -0
  64. package/dist/diagnostics/diagnostic.js +458 -0
  65. package/dist/diagnostics/diagnostic.js.map +1 -0
  66. package/dist/diagnostics/render.d.ts +11 -0
  67. package/dist/diagnostics/render.js +58 -0
  68. package/dist/diagnostics/render.js.map +1 -0
  69. package/dist/frontend/cjs-lexer.d.ts +20 -0
  70. package/dist/frontend/cjs-lexer.js +813 -0
  71. package/dist/frontend/cjs-lexer.js.map +1 -0
  72. package/dist/frontend/lowering/http2-constants.d.ts +1 -0
  73. package/dist/frontend/lowering/http2-constants.js +251 -0
  74. package/dist/frontend/lowering/http2-constants.js.map +1 -0
  75. package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
  76. package/dist/frontend/lowering/lib-boundary.js +143 -0
  77. package/dist/frontend/lowering/lib-boundary.js.map +1 -0
  78. package/dist/frontend/lowering/lower-assert.d.ts +18 -0
  79. package/dist/frontend/lowering/lower-assert.js +1269 -0
  80. package/dist/frontend/lowering/lower-assert.js.map +1 -0
  81. package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
  82. package/dist/frontend/lowering/lower-builtins.js +5249 -0
  83. package/dist/frontend/lowering/lower-builtins.js.map +1 -0
  84. package/dist/frontend/lowering/lower-calls.d.ts +581 -0
  85. package/dist/frontend/lowering/lower-calls.js +6286 -0
  86. package/dist/frontend/lowering/lower-calls.js.map +1 -0
  87. package/dist/frontend/lowering/lower-classes.d.ts +727 -0
  88. package/dist/frontend/lowering/lower-classes.js +4701 -0
  89. package/dist/frontend/lowering/lower-classes.js.map +1 -0
  90. package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
  91. package/dist/frontend/lowering/lower-comptime.js +244 -0
  92. package/dist/frontend/lowering/lower-comptime.js.map +1 -0
  93. package/dist/frontend/lowering/lower-containers.d.ts +486 -0
  94. package/dist/frontend/lowering/lower-containers.js +6345 -0
  95. package/dist/frontend/lowering/lower-containers.js.map +1 -0
  96. package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
  97. package/dist/frontend/lowering/lower-dgram.js +319 -0
  98. package/dist/frontend/lowering/lower-dgram.js.map +1 -0
  99. package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
  100. package/dist/frontend/lowering/lower-emitter.js +688 -0
  101. package/dist/frontend/lowering/lower-emitter.js.map +1 -0
  102. package/dist/frontend/lowering/lower-enums.d.ts +22 -0
  103. package/dist/frontend/lowering/lower-enums.js +235 -0
  104. package/dist/frontend/lowering/lower-enums.js.map +1 -0
  105. package/dist/frontend/lowering/lower-expando.d.ts +35 -0
  106. package/dist/frontend/lowering/lower-expando.js +276 -0
  107. package/dist/frontend/lowering/lower-expando.js.map +1 -0
  108. package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
  109. package/dist/frontend/lowering/lower-exprs.js +8111 -0
  110. package/dist/frontend/lowering/lower-exprs.js.map +1 -0
  111. package/dist/frontend/lowering/lower-generators.d.ts +50 -0
  112. package/dist/frontend/lowering/lower-generators.js +421 -0
  113. package/dist/frontend/lowering/lower-generators.js.map +1 -0
  114. package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
  115. package/dist/frontend/lowering/lower-inspect.js +1074 -0
  116. package/dist/frontend/lowering/lower-inspect.js.map +1 -0
  117. package/dist/frontend/lowering/lower-island.d.ts +108 -0
  118. package/dist/frontend/lowering/lower-island.js +650 -0
  119. package/dist/frontend/lowering/lower-island.js.map +1 -0
  120. package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
  121. package/dist/frontend/lowering/lower-mixins.js +534 -0
  122. package/dist/frontend/lowering/lower-mixins.js.map +1 -0
  123. package/dist/frontend/lowering/lower-modules.d.ts +135 -0
  124. package/dist/frontend/lowering/lower-modules.js +1613 -0
  125. package/dist/frontend/lowering/lower-modules.js.map +1 -0
  126. package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
  127. package/dist/frontend/lowering/lower-namespaces.js +706 -0
  128. package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
  129. package/dist/frontend/lowering/lower-server.d.ts +90 -0
  130. package/dist/frontend/lowering/lower-server.js +3471 -0
  131. package/dist/frontend/lowering/lower-server.js.map +1 -0
  132. package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
  133. package/dist/frontend/lowering/lower-stmts.js +5341 -0
  134. package/dist/frontend/lowering/lower-stmts.js.map +1 -0
  135. package/dist/frontend/lowering/lower-stream.d.ts +88 -0
  136. package/dist/frontend/lowering/lower-stream.js +1533 -0
  137. package/dist/frontend/lowering/lower-stream.js.map +1 -0
  138. package/dist/frontend/lowering/lower-test.d.ts +27 -0
  139. package/dist/frontend/lowering/lower-test.js +353 -0
  140. package/dist/frontend/lowering/lower-test.js.map +1 -0
  141. package/dist/frontend/lowering/lowerer.d.ts +1748 -0
  142. package/dist/frontend/lowering/lowerer.js +6257 -0
  143. package/dist/frontend/lowering/lowerer.js.map +1 -0
  144. package/dist/frontend/lowering/surfaces.d.ts +272 -0
  145. package/dist/frontend/lowering/surfaces.js +1096 -0
  146. package/dist/frontend/lowering/surfaces.js.map +1 -0
  147. package/dist/frontend/npm-static.d.ts +55 -0
  148. package/dist/frontend/npm-static.js +300 -0
  149. package/dist/frontend/npm-static.js.map +1 -0
  150. package/dist/frontend/npm.d.ts +330 -0
  151. package/dist/frontend/npm.js +1259 -0
  152. package/dist/frontend/npm.js.map +1 -0
  153. package/dist/frontend/program.d.ts +186 -0
  154. package/dist/frontend/program.js +2255 -0
  155. package/dist/frontend/program.js.map +1 -0
  156. package/dist/frontend/provenance-registry.d.ts +48 -0
  157. package/dist/frontend/provenance-registry.js +87 -0
  158. package/dist/frontend/provenance-registry.js.map +1 -0
  159. package/dist/frontend/provenance.d.ts +6 -0
  160. package/dist/frontend/provenance.js +459 -0
  161. package/dist/frontend/provenance.js.map +1 -0
  162. package/dist/frontend/resolve.d.ts +56 -0
  163. package/dist/frontend/resolve.js +682 -0
  164. package/dist/frontend/resolve.js.map +1 -0
  165. package/dist/frontend/shared.d.ts +67 -0
  166. package/dist/frontend/shared.js +241 -0
  167. package/dist/frontend/shared.js.map +1 -0
  168. package/dist/frontend/ts7/adapter.d.ts +7 -0
  169. package/dist/frontend/ts7/adapter.js +54 -0
  170. package/dist/frontend/ts7/adapter.js.map +1 -0
  171. package/dist/frontend/ts7/ast.d.ts +50 -0
  172. package/dist/frontend/ts7/ast.js +211 -0
  173. package/dist/frontend/ts7/ast.js.map +1 -0
  174. package/dist/frontend/ts7/census-check.d.ts +1 -0
  175. package/dist/frontend/ts7/census-check.js +12 -0
  176. package/dist/frontend/ts7/census-check.js.map +1 -0
  177. package/dist/frontend/ts7/checker.d.ts +140 -0
  178. package/dist/frontend/ts7/checker.js +544 -0
  179. package/dist/frontend/ts7/checker.js.map +1 -0
  180. package/dist/frontend/ts7/enums.d.ts +18 -0
  181. package/dist/frontend/ts7/enums.js +43 -0
  182. package/dist/frontend/ts7/enums.js.map +1 -0
  183. package/dist/frontend/ts7/program.d.ts +99 -0
  184. package/dist/frontend/ts7/program.js +278 -0
  185. package/dist/frontend/ts7/program.js.map +1 -0
  186. package/dist/frontend/ts7/world-check.d.ts +3 -0
  187. package/dist/frontend/ts7/world-check.js +34 -0
  188. package/dist/frontend/ts7/world-check.js.map +1 -0
  189. package/dist/frontend/types.d.ts +205 -0
  190. package/dist/frontend/types.js +2485 -0
  191. package/dist/frontend/types.js.map +1 -0
  192. package/dist/index.d.ts +85 -0
  193. package/dist/index.js +429 -0
  194. package/dist/index.js.map +1 -0
  195. package/dist/ir/nodes.d.ts +4334 -0
  196. package/dist/ir/nodes.js +1906 -0
  197. package/dist/ir/nodes.js.map +1 -0
  198. package/dist/ir/serialize.d.ts +4 -0
  199. package/dist/ir/serialize.js +34 -0
  200. package/dist/ir/serialize.js.map +1 -0
  201. package/dist/ir/validate.d.ts +36 -0
  202. package/dist/ir/validate.js +4885 -0
  203. package/dist/ir/validate.js.map +1 -0
  204. package/package.json +30 -6
  205. package/README.md +0 -3
  206. package/index.js +0 -2
@@ -0,0 +1,793 @@
1
+ import { isRefCounted, typeKey } from "../../ir/nodes.js";
2
+ import { mangleRecordStruct } from "../mangle.js";
3
+ import { BlockBuilder } from "./blocks.js";
4
+ import { llFieldType, releaseSym } from "./shapes.js";
5
+ import { LlvmUnsupportedError } from "./unsupported.js";
6
+ /** Exact double literal (the emitter's f64Lit — duplicated to avoid a
7
+ * cyclic import; both spell every bit pattern identically). */
8
+ function f64Lit(n) {
9
+ const buf = new ArrayBuffer(8);
10
+ new DataView(buf).setFloat64(0, n);
11
+ return `0x${[...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("").toUpperCase()}`;
12
+ }
13
+ const FN_ATTRS = "#0";
14
+ export class LlWalkers {
15
+ host;
16
+ jsonWriters = new Map();
17
+ unionToStrFns = new Map();
18
+ unionJoinFns = new Map();
19
+ indentFn = null;
20
+ /** Emitted function definitions, in interning order. */
21
+ defs = [];
22
+ constructor(host) {
23
+ this.host = host;
24
+ }
25
+ /** The value-parameter LLVM type of a writer for `t`. */
26
+ valTy(t) {
27
+ return t.kind === "f64" ? "double" : t.kind === "bool" ? "i1" : "ptr";
28
+ }
29
+ /* ── the byte plumbing shared by every walker ─────────────────────────── */
30
+ putc(B, buf, byte) {
31
+ this.host.declare(`declare void @scr_jb_putc(ptr, i8)`);
32
+ B.line(`call void @scr_jb_putc(ptr ${buf}, i8 ${byte})`);
33
+ }
34
+ puts(B, buf, text) {
35
+ this.host.declare(`declare void @scr_jb_puts(ptr, ptr)`);
36
+ B.line(`call void @scr_jb_puts(ptr ${buf}, ptr ${this.host.cstr(text)}) ; ${JSON.stringify(text)}`);
37
+ }
38
+ /** Appends an ScrStr's bytes: for (j < s->len) putc(s->data[j]). */
39
+ putScrStr(B, buf, s) {
40
+ const lenp = B.tmp();
41
+ const len = B.tmp();
42
+ const data = B.tmp();
43
+ B.line(`${lenp} = getelementptr inbounds %ScrStr, ptr ${s}, i64 0, i32 1`);
44
+ B.line(`${len} = load i64, ptr ${lenp}`);
45
+ B.line(`${data} = getelementptr inbounds i8, ptr ${s}, i64 24 ; ->data`);
46
+ const jSlot = B.slot();
47
+ B.entryAllocas.push(`${jSlot} = alloca i64`);
48
+ B.line(`store i64 0, ptr ${jSlot}`);
49
+ const lc = B.newLabel("ps.c");
50
+ const lb = B.newLabel("ps.b");
51
+ const le = B.newLabel("ps.e");
52
+ B.br(lc);
53
+ B.startBlock(lc);
54
+ const j = B.tmp();
55
+ const cont = B.tmp();
56
+ B.line(`${j} = load i64, ptr ${jSlot}`);
57
+ B.line(`${cont} = icmp ult i64 ${j}, ${len}`);
58
+ B.condBr(cont, lb, le);
59
+ B.startBlock(lb);
60
+ const cp = B.tmp();
61
+ const c = B.tmp();
62
+ B.line(`${cp} = getelementptr inbounds i8, ptr ${data}, i64 ${j}`);
63
+ B.line(`${c} = load i8, ptr ${cp}`);
64
+ this.putc(B, buf, c);
65
+ const j2 = B.tmp();
66
+ B.line(`${j2} = add i64 ${j}, 1`);
67
+ B.line(`store i64 ${j2}, ptr ${jSlot}`);
68
+ B.br(lc);
69
+ B.startBlock(le);
70
+ }
71
+ /** Loads a union box's tag. */
72
+ unionTag(B, uName) {
73
+ const p = B.tmp();
74
+ const t = B.tmp();
75
+ B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 1`);
76
+ B.line(`${t} = load i32, ptr ${p}`);
77
+ return t;
78
+ }
79
+ unionPeek(B, uName) {
80
+ const p = B.tmp();
81
+ const t = B.tmp();
82
+ B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 5`);
83
+ B.line(`${t} = load ptr, ptr ${p}`);
84
+ return t;
85
+ }
86
+ /* ── type-directed JSON serializers (jsonWriteHelper, ported) ─────────── */
87
+ jsonWriteHelper(t) {
88
+ const key = typeKey(t);
89
+ const existing = this.jsonWriters.get(key);
90
+ if (existing)
91
+ return existing;
92
+ const name = `sc_jw_${this.jsonWriters.size}`;
93
+ this.jsonWriters.set(key, name);
94
+ const B = new BlockBuilder();
95
+ switch (t.kind) {
96
+ case "f64":
97
+ this.host.declare(`declare void @scr_jb_put_f64(ptr, double)`);
98
+ B.line(`call void @scr_jb_put_f64(ptr %b, double %v) ; NaN/Infinity -> null, -0 -> 0, like JS`);
99
+ break;
100
+ case "bool": {
101
+ const s = B.tmp();
102
+ this.host.declare(`declare void @scr_jb_puts(ptr, ptr)`);
103
+ B.line(`${s} = select i1 %v, ptr ${this.host.cstr("true")}, ptr ${this.host.cstr("false")}`);
104
+ B.line(`call void @scr_jb_puts(ptr %b, ptr ${s})`);
105
+ break;
106
+ }
107
+ case "string":
108
+ this.host.declare(`declare void @scr_jb_put_json_str(ptr, ptr)`);
109
+ B.line(`call void @scr_jb_put_json_str(ptr %b, ptr %v)`);
110
+ break;
111
+ case "dyn":
112
+ // Overflow values under an `unknown` index signature: the DOM
113
+ // serializes itself (runtime walker). Bare stringify of dyn stays
114
+ // frontend-fenced; this writer is reachable only through overflow
115
+ // entries.
116
+ this.host.declare(`declare void @scr_jb_put_dyn(ptr, ptr)`);
117
+ B.line(`call void @scr_jb_put_dyn(ptr %b, ptr %v)`);
118
+ break;
119
+ case "record":
120
+ this.emitRecordWriter(B, t.shapeId);
121
+ break;
122
+ case "array":
123
+ this.emitArrayWriter(B, t.elem);
124
+ break;
125
+ case "union":
126
+ this.emitUnionWriter(B, t.unionId);
127
+ break;
128
+ default:
129
+ throw new LlvmUnsupportedError(`jsonStringify:${t.kind}`);
130
+ }
131
+ B.terminate("ret void");
132
+ this.defs.push(`define internal void @${name}(ptr %b, ${this.valTy(t)} %v) ${FN_ATTRS} { ; stringify ${key}`, B.render(), `}`, ``);
133
+ return name;
134
+ }
135
+ /** Loads a record field slot (i8-stored bools trunc to i1). */
136
+ loadField(B, recName, shapeId, index, t) {
137
+ const p = B.tmp();
138
+ B.line(`${p} = getelementptr inbounds %${mangleRecordStruct(shapeId)}, ptr ${recName}, i64 0, i32 ${index}`);
139
+ const fieldTy = llFieldType(t);
140
+ const raw = B.tmp();
141
+ B.line(`${raw} = load ${fieldTy}, ptr ${p}`);
142
+ if (fieldTy !== "i8")
143
+ return raw;
144
+ const b = B.tmp();
145
+ B.line(`${b} = trunc i8 ${raw} to i1`);
146
+ return b;
147
+ }
148
+ emitRecordWriter(B, shapeId) {
149
+ const shape = this.host.recordsById.get(shapeId);
150
+ if (!shape)
151
+ throw new Error(`llvm emitter bug: jsonStringify of unknown shape ${shapeId}`);
152
+ const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + 1]));
153
+ // A tuple serializes as a JSON ARRAY in index order — JS-exact. Every
154
+ // position is required, so commas are static.
155
+ if (shape.tuple) {
156
+ const byIndex = [...shape.fields].sort((a, b) => Number(a.name) - Number(b.name));
157
+ this.putc(B, "%b", "91"); // '['
158
+ byIndex.forEach((f, i) => {
159
+ if (i > 0)
160
+ this.putc(B, "%b", "44"); // ','
161
+ const v = this.loadField(B, "%v", shapeId, fieldIndex.get(f.name), f.type);
162
+ B.line(`call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; [${f.name}]`);
163
+ });
164
+ this.putc(B, "%b", "93"); // ']'
165
+ return;
166
+ }
167
+ // Fields serialize in DECLARED order (JS insertion order); internal
168
+ // '%'-fields stay hidden — jsonWriteHelper's contract, ported comment
169
+ // and all.
170
+ const order = shape.declaredOrder ?? shape.fields.map((f) => f.name);
171
+ const inOrder = new Set(order);
172
+ if (shape.fields.some((f) => !inOrder.has(f.name) && !f.name.startsWith("%"))) {
173
+ throw new Error(`llvm emitter bug: declaredOrder of shape ${shapeId} omits a non-internal field`);
174
+ }
175
+ const byName = new Map(shape.fields.map((f) => [f.name, f]));
176
+ const emitFields = order.map((n) => byName.get(n)).filter((f) => f !== undefined);
177
+ const droppable = emitFields.some((f) => this.host.undefinedArmTag(f.type) >= 0) || !!shape.indexValue;
178
+ this.putc(B, "%b", "123"); // '{'
179
+ if (!droppable) {
180
+ emitFields.forEach((f, i) => {
181
+ this.puts(B, "%b", `${i > 0 ? "," : ""}"${f.name}":`);
182
+ const v = this.loadField(B, "%v", shapeId, fieldIndex.get(f.name), f.type);
183
+ B.line(`call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${f.name}`);
184
+ });
185
+ }
186
+ else {
187
+ const first = B.slot();
188
+ B.entryAllocas.push(`${first} = alloca i1`);
189
+ B.line(`store i1 true, ptr ${first}`);
190
+ const comma = () => {
191
+ const isf = B.tmp();
192
+ const lc = B.newLabel("jwc.c");
193
+ const lj = B.newLabel("jwc.j");
194
+ B.line(`${isf} = load i1, ptr ${first}`);
195
+ B.condBr(isf, lj, lc);
196
+ B.startBlock(lc);
197
+ this.putc(B, "%b", "44"); // ','
198
+ B.br(lj);
199
+ B.startBlock(lj);
200
+ B.line(`store i1 false, ptr ${first}`);
201
+ };
202
+ for (const f of emitFields) {
203
+ const utag = this.host.undefinedArmTag(f.type);
204
+ const v = this.loadField(B, "%v", shapeId, fieldIndex.get(f.name), f.type);
205
+ let skip = null;
206
+ if (utag >= 0) {
207
+ // Undefined-valued field: dropped, like Node.
208
+ const tag = this.unionTag(B, v);
209
+ const isu = B.tmp();
210
+ B.line(`${isu} = icmp eq i32 ${tag}, ${utag}`);
211
+ const lw = B.newLabel("jwf.w");
212
+ skip = B.newLabel("jwf.s");
213
+ B.condBr(isu, skip, lw);
214
+ B.startBlock(lw);
215
+ }
216
+ comma();
217
+ this.puts(B, "%b", `"${f.name}":`);
218
+ B.line(`call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${f.name}`);
219
+ if (skip !== null) {
220
+ B.br(skip);
221
+ B.startBlock(skip);
222
+ }
223
+ }
224
+ if (shape.indexValue)
225
+ this.emitOverflowEntries(B, shape, first);
226
+ }
227
+ this.putc(B, "%b", "125"); // '}'
228
+ }
229
+ /** Overflow entries follow the declared fields, in JS OWN-KEY order
230
+ * (scr_map_keys_js_order); keys escape like any JSON string;
231
+ * undefined-valued entries drop (the optional-field rule). */
232
+ emitOverflowEntries(B, shape, first) {
233
+ const iv = shape.indexValue;
234
+ const host = this.host;
235
+ const ovfp = B.tmp();
236
+ const ovf = B.tmp();
237
+ B.line(`${ovfp} = getelementptr inbounds %${mangleRecordStruct(shape.id)}, ptr %v, i64 0, i32 ${shape.fields.length + 1}`);
238
+ B.line(`${ovf} = load ptr, ptr ${ovfp} ; overflow map`);
239
+ host.declare(`declare ptr @scr_map_keys_js_order(ptr)`);
240
+ host.declare(`declare double @scr_arr_len(ptr)`);
241
+ host.declare(`declare ptr @scr_arr_get_ref(ptr, double)`);
242
+ host.declare(`declare void @scr_str_release(ptr)`);
243
+ const ks = B.tmp();
244
+ const len = B.tmp();
245
+ B.line(`${ks} = call ptr @scr_map_keys_js_order(ptr ${ovf})`);
246
+ B.line(`${len} = call double @scr_arr_len(ptr ${ks})`);
247
+ const iSlot = B.slot();
248
+ B.entryAllocas.push(`${iSlot} = alloca double`);
249
+ B.line(`store double ${f64Lit(0)}, ptr ${iSlot}`);
250
+ const lc = B.newLabel("ovf.c");
251
+ const lb = B.newLabel("ovf.b");
252
+ const ln = B.newLabel("ovf.n");
253
+ const le = B.newLabel("ovf.e");
254
+ B.br(lc);
255
+ B.startBlock(lc);
256
+ const i = B.tmp();
257
+ const cont = B.tmp();
258
+ B.line(`${i} = load double, ptr ${iSlot}`);
259
+ B.line(`${cont} = fcmp olt double ${i}, ${len}`);
260
+ B.condBr(cont, lb, le);
261
+ B.startBlock(lb);
262
+ const k = B.tmp();
263
+ B.line(`${k} = call ptr @scr_arr_get_ref(ptr ${ks}, double ${i}) ; key (+1)`);
264
+ // The entry value, type-directed off the overflow VALUE type.
265
+ let val;
266
+ if (iv.kind === "f64" || iv.kind === "bool") {
267
+ const outTy = iv.kind === "f64" ? "double" : "i8";
268
+ const outSlot = B.slot();
269
+ B.entryAllocas.push(`${outSlot} = alloca ${outTy}`);
270
+ B.line(`store ${outTy} ${iv.kind === "f64" ? f64Lit(0) : "0"}, ptr ${outSlot}`);
271
+ host.declare(`declare zeroext i1 @scr_map_get_str_${iv.kind === "f64" ? "f64" : "bool"}(ptr, ptr, ptr)`);
272
+ const found = B.tmp();
273
+ B.line(`${found} = call zeroext i1 @scr_map_get_str_${iv.kind === "f64" ? "f64" : "bool"}(ptr ${ovf}, ptr ${k}, ptr ${outSlot})`);
274
+ const raw = B.tmp();
275
+ B.line(`${raw} = load ${outTy}, ptr ${outSlot}`);
276
+ if (iv.kind === "bool") {
277
+ val = B.tmp();
278
+ B.line(`${val} = trunc i8 ${raw} to i1`);
279
+ }
280
+ else {
281
+ val = raw;
282
+ }
283
+ }
284
+ else {
285
+ host.declare(`declare ptr @scr_map_get_str_ref(ptr, ptr)`);
286
+ val = B.tmp();
287
+ B.line(`${val} = call ptr @scr_map_get_str_ref(ptr ${ovf}, ptr ${k}) ; value (+1)`);
288
+ }
289
+ // Undefined-valued entries drop, exactly the optional-field rule: a
290
+ // dyn value whose DOM kind is SCR_DYN_UNDEF (i32 at offset 8, enum
291
+ // member 6 — scr_runtime.h's ScrDynKind), or a union holding its
292
+ // undefined arm.
293
+ let skipUndef = null;
294
+ if (iv.kind === "dyn") {
295
+ const kp = B.tmp();
296
+ const kd = B.tmp();
297
+ const isu = B.tmp();
298
+ B.line(`${kp} = getelementptr inbounds i8, ptr ${val}, i64 8 ; ->kind`);
299
+ B.line(`${kd} = load i32, ptr ${kp}`);
300
+ B.line(`${isu} = icmp eq i32 ${kd}, 6 ; SCR_DYN_UNDEF (NULL is 0 — null members DO serialize)`);
301
+ skipUndef = isu;
302
+ }
303
+ else if (this.host.undefinedArmTag(iv) >= 0) {
304
+ const tag = this.unionTag(B, val);
305
+ const isu = B.tmp();
306
+ B.line(`${isu} = icmp eq i32 ${tag}, ${this.host.undefinedArmTag(iv)}`);
307
+ skipUndef = isu;
308
+ }
309
+ if (skipUndef !== null) {
310
+ const ld = B.newLabel("ovf.d");
311
+ const write = B.newLabel("ovf.w");
312
+ B.condBr(skipUndef, ld, write);
313
+ B.startBlock(ld);
314
+ if (isRefCounted(iv))
315
+ B.line(`call void ${releaseSym(host, iv)}(ptr ${val})`);
316
+ B.line(`call void @scr_str_release(ptr ${k})`);
317
+ B.br(ln);
318
+ B.startBlock(write);
319
+ }
320
+ // The comma dance over the shared `first` flag.
321
+ {
322
+ const isf = B.tmp();
323
+ const lcm = B.newLabel("ovf.cm");
324
+ const lj = B.newLabel("ovf.cj");
325
+ B.line(`${isf} = load i1, ptr ${first}`);
326
+ B.condBr(isf, lj, lcm);
327
+ B.startBlock(lcm);
328
+ this.putc(B, "%b", "44"); // ','
329
+ B.br(lj);
330
+ B.startBlock(lj);
331
+ B.line(`store i1 false, ptr ${first}`);
332
+ }
333
+ host.declare(`declare void @scr_jb_put_json_str(ptr, ptr)`);
334
+ B.line(`call void @scr_jb_put_json_str(ptr %b, ptr ${k})`);
335
+ this.putc(B, "%b", "58"); // ':'
336
+ B.line(`call void @${this.jsonWriteHelper(iv)}(ptr %b, ${this.valTy(iv)} ${val})`);
337
+ B.line(`call void @scr_str_release(ptr ${k})`);
338
+ if (isRefCounted(iv))
339
+ B.line(`call void ${releaseSym(host, iv)}(ptr ${val})`);
340
+ B.br(ln);
341
+ B.startBlock(ln);
342
+ const i2 = B.tmp();
343
+ B.line(`${i2} = fadd double ${i}, ${f64Lit(1)}`);
344
+ B.line(`store double ${i2}, ptr ${iSlot}`);
345
+ B.br(lc);
346
+ B.startBlock(le);
347
+ host.declare(`declare void @scr_arr_release(ptr)`);
348
+ B.line(`call void @scr_arr_release(ptr ${ks})`);
349
+ }
350
+ emitArrayWriter(B, elem) {
351
+ const host = this.host;
352
+ const w = this.jsonWriteHelper(elem);
353
+ host.declare(`declare double @scr_arr_len(ptr)`);
354
+ this.putc(B, "%b", "91"); // '['
355
+ const len = B.tmp();
356
+ B.line(`${len} = call double @scr_arr_len(ptr %v)`);
357
+ const iSlot = B.slot();
358
+ B.entryAllocas.push(`${iSlot} = alloca double`);
359
+ B.line(`store double ${f64Lit(0)}, ptr ${iSlot}`);
360
+ const lc = B.newLabel("jwa.c");
361
+ const lb = B.newLabel("jwa.b");
362
+ const le = B.newLabel("jwa.e");
363
+ B.br(lc);
364
+ B.startBlock(lc);
365
+ const i = B.tmp();
366
+ const cont = B.tmp();
367
+ B.line(`${i} = load double, ptr ${iSlot}`);
368
+ B.line(`${cont} = fcmp olt double ${i}, ${len}`);
369
+ B.condBr(cont, lb, le);
370
+ B.startBlock(lb);
371
+ const nz = B.tmp();
372
+ const lcm = B.newLabel("jwa.cm");
373
+ const lj = B.newLabel("jwa.cj");
374
+ B.line(`${nz} = fcmp ogt double ${i}, ${f64Lit(0)}`);
375
+ B.condBr(nz, lcm, lj);
376
+ B.startBlock(lcm);
377
+ this.putc(B, "%b", "44"); // ','
378
+ B.br(lj);
379
+ B.startBlock(lj);
380
+ if (elem.kind === "f64" || elem.kind === "bool") {
381
+ const acc = elem.kind;
382
+ const accTy = elem.kind === "f64" ? "double" : "i1";
383
+ host.declare(`declare ${elem.kind === "bool" ? "zeroext i1" : accTy} @scr_arr_get_${acc}(ptr, double)`);
384
+ const v = B.tmp();
385
+ B.line(`${v} = call ${accTy} @scr_arr_get_${acc}(ptr %v, double ${i})`);
386
+ B.line(`call void @${w}(ptr %b, ${accTy} ${v})`);
387
+ }
388
+ else {
389
+ // _get_ref returns +1; release after writing.
390
+ host.declare(`declare ptr @scr_arr_get_ref(ptr, double)`);
391
+ const v = B.tmp();
392
+ B.line(`${v} = call ptr @scr_arr_get_ref(ptr %v, double ${i})`);
393
+ B.line(`call void @${w}(ptr %b, ptr ${v})`);
394
+ B.line(`call void ${releaseSym(host, elem)}(ptr ${v})`);
395
+ }
396
+ const i2 = B.tmp();
397
+ B.line(`${i2} = fadd double ${i}, ${f64Lit(1)}`);
398
+ B.line(`store double ${i2}, ptr ${iSlot}`);
399
+ B.br(lc);
400
+ B.startBlock(le);
401
+ this.putc(B, "%b", "93"); // ']'
402
+ }
403
+ emitUnionWriter(B, unionId) {
404
+ const def = this.host.unionsById.get(unionId);
405
+ if (!def)
406
+ throw new Error(`llvm emitter bug: jsonStringify of unknown union ${unionId}`);
407
+ const tag = this.unionTag(B, "%v");
408
+ const bad = B.newLabel("jwu.bad");
409
+ const done = B.newLabel("jwu.d");
410
+ const labels = def.arms.map(() => B.newLabel("jwu.a"));
411
+ B.terminate(`switch i32 ${tag}, label %${bad} [ ${def.arms.map((_, i) => `i32 ${i}, label %${labels[i]}`).join(" ")} ]`);
412
+ def.arms.forEach((arm, i) => {
413
+ B.startBlock(labels[i]);
414
+ if (arm.kind === "nullT") {
415
+ // Payload-less arm: JSON.stringify(null) is the text `null`.
416
+ this.puts(B, "%b", "null");
417
+ B.br(done);
418
+ return;
419
+ }
420
+ if (arm.kind === "undefinedT") {
421
+ // Reachable only as a record FIELD's serializer, and the record
422
+ // writer drops the field while it holds this tag before calling —
423
+ // so the tag can never arrive here.
424
+ B.br(bad);
425
+ return;
426
+ }
427
+ const w = this.jsonWriteHelper(arm);
428
+ if (arm.kind === "f64") {
429
+ this.host.declare(`declare double @scr_union_get_f64(ptr)`);
430
+ const x = B.tmp();
431
+ B.line(`${x} = call double @scr_union_get_f64(ptr %v)`);
432
+ B.line(`call void @${w}(ptr %b, double ${x})`);
433
+ }
434
+ else if (arm.kind === "bool") {
435
+ this.host.declare(`declare zeroext i1 @scr_union_get_bool(ptr)`);
436
+ const x = B.tmp();
437
+ B.line(`${x} = call zeroext i1 @scr_union_get_bool(ptr %v)`);
438
+ B.line(`call void @${w}(ptr %b, i1 ${x})`);
439
+ }
440
+ else {
441
+ // Payload is BORROWED out of the box for the write.
442
+ const p = this.unionPeek(B, "%v");
443
+ B.line(`call void @${w}(ptr %b, ptr ${p}) ; ${arm.kind}`);
444
+ }
445
+ B.br(done);
446
+ });
447
+ B.startBlock(bad);
448
+ this.host.needBadTag();
449
+ B.line(`call void @sc_bad_tag()`);
450
+ B.terminate(`unreachable`);
451
+ B.startBlock(done);
452
+ }
453
+ /* ── the pretty-print re-indenter (jsonIndentHelper, ported) ──────────── */
454
+ /** `JSON.stringify(v, null, space)` as a REWRITE of the compact text —
455
+ * Node's gap algorithm exactly (see emit-walkers.ts's sc_ji). The indent
456
+ * rides as a NUL-terminated constant + byte length. */
457
+ jsonIndentHelper() {
458
+ if (this.indentFn)
459
+ return this.indentFn;
460
+ const name = "sc_ji";
461
+ this.indentFn = name;
462
+ const host = this.host;
463
+ host.declare(`declare void @scr_jb_init(ptr)`);
464
+ host.declare(`declare ptr @scr_jb_finish(ptr)`);
465
+ host.declare(`declare void @scr_jb_putc(ptr, i8)`);
466
+ // The newline + depth * indent writer, shared by the three break sites.
467
+ this.defs.push(`define internal void @sc_ji_ind(ptr %b, i64 %depth, ptr %ind, i64 %ilen) ${FN_ATTRS} { ; stringify gap indent`, `entry:`, ` call void @scr_jb_putc(ptr %b, i8 10)`, ` br label %oc`, `oc:`, ` %d = phi i64 [ 0, %entry ], [ %d2, %ie ]`, ` %ocont = icmp ult i64 %d, %depth`, ` br i1 %ocont, label %ic0, label %done`, `ic0:`, ` br label %ic`, `ic:`, ` %k = phi i64 [ 0, %ic0 ], [ %k2, %ib ]`, ` %icont = icmp ult i64 %k, %ilen`, ` br i1 %icont, label %ib, label %ie`, `ib:`, ` %cp = getelementptr inbounds i8, ptr %ind, i64 %k`, ` %c = load i8, ptr %cp`, ` call void @scr_jb_putc(ptr %b, i8 %c)`, ` %k2 = add i64 %k, 1`, ` br label %ic`, `ie:`, ` %d2 = add i64 %d, 1`, ` br label %oc`, `done:`, ` ret void`, `}`, ``);
468
+ const B = new BlockBuilder();
469
+ const buf = "%jb";
470
+ B.entryAllocas.push(`${buf} = alloca %ScrJsonBuf`);
471
+ const depth = "%depth";
472
+ const instr = "%instr";
473
+ const iSlot = "%i";
474
+ B.entryAllocas.push(`${depth} = alloca i64`, `${instr} = alloca i1`, `${iSlot} = alloca i64`);
475
+ B.line(`call void @scr_jb_init(ptr ${buf})`);
476
+ B.line(`store i64 0, ptr ${depth}`);
477
+ B.line(`store i1 false, ptr ${instr}`);
478
+ B.line(`store i64 0, ptr ${iSlot}`);
479
+ const np = B.tmp();
480
+ const n = B.tmp();
481
+ const data = B.tmp();
482
+ B.line(`${np} = getelementptr inbounds %ScrStr, ptr %compact, i64 0, i32 1`);
483
+ B.line(`${n} = load i64, ptr ${np}`);
484
+ B.line(`${data} = getelementptr inbounds i8, ptr %compact, i64 24 ; ->data`);
485
+ const loop = B.newLabel("ji.c");
486
+ const body = B.newLabel("ji.b");
487
+ const inc = B.newLabel("ji.n");
488
+ const end = B.newLabel("ji.e");
489
+ B.br(loop);
490
+ B.startBlock(loop);
491
+ const i = B.tmp();
492
+ const cont = B.tmp();
493
+ B.line(`${i} = load i64, ptr ${iSlot}`);
494
+ B.line(`${cont} = icmp ult i64 ${i}, ${n}`);
495
+ B.condBr(cont, body, end);
496
+ B.startBlock(body);
497
+ const cp = B.tmp();
498
+ const c = B.tmp();
499
+ B.line(`${cp} = getelementptr inbounds i8, ptr ${data}, i64 ${i}`);
500
+ B.line(`${c} = load i8, ptr ${cp}`);
501
+ const ins = B.tmp();
502
+ B.line(`${ins} = load i1, ptr ${instr}`);
503
+ const inStr = B.newLabel("ji.s");
504
+ const outStr = B.newLabel("ji.o");
505
+ B.condBr(ins, inStr, outStr);
506
+ // In-string state: emit verbatim, skip the escaped char, leave on '"'.
507
+ B.startBlock(inStr);
508
+ this.putc(B, buf, c);
509
+ const isbs = B.tmp();
510
+ const i1p = B.tmp();
511
+ const hasNext = B.tmp();
512
+ const escTake = B.tmp();
513
+ B.line(`${isbs} = icmp eq i8 ${c}, 92 ; backslash`);
514
+ B.line(`${i1p} = add i64 ${i}, 1`);
515
+ B.line(`${hasNext} = icmp ult i64 ${i1p}, ${n}`);
516
+ B.line(`${escTake} = and i1 ${isbs}, ${hasNext}`);
517
+ const esc = B.newLabel("ji.se");
518
+ const noesc = B.newLabel("ji.sq");
519
+ B.condBr(escTake, esc, noesc);
520
+ B.startBlock(esc);
521
+ const ecp = B.tmp();
522
+ const ec = B.tmp();
523
+ B.line(`${ecp} = getelementptr inbounds i8, ptr ${data}, i64 ${i1p}`);
524
+ B.line(`${ec} = load i8, ptr ${ecp}`);
525
+ this.putc(B, buf, ec);
526
+ B.line(`store i64 ${i1p}, ptr ${iSlot} ; consumed the escaped char`);
527
+ B.br(inc);
528
+ B.startBlock(noesc);
529
+ const isq = B.tmp();
530
+ const stay = B.tmp();
531
+ B.line(`${isq} = icmp eq i8 ${c}, 34 ; '"'`);
532
+ B.line(`${stay} = xor i1 ${isq}, true`);
533
+ B.line(`store i1 ${stay}, ptr ${instr}`);
534
+ B.br(inc);
535
+ // Out of string: the character switch.
536
+ B.startBlock(outStr);
537
+ const lQuote = B.newLabel("ji.q");
538
+ const lOpenB = B.newLabel("ji.ob");
539
+ const lOpenK = B.newLabel("ji.ok");
540
+ const lClose = B.newLabel("ji.cl");
541
+ const lComma = B.newLabel("ji.cm");
542
+ const lColon = B.newLabel("ji.co");
543
+ const lOther = B.newLabel("ji.ot");
544
+ B.terminate(`switch i8 ${c}, label %${lOther} [ i8 34, label %${lQuote} i8 123, label %${lOpenB} i8 91, label %${lOpenK} ` +
545
+ `i8 125, label %${lClose} i8 93, label %${lClose} i8 44, label %${lComma} i8 58, label %${lColon} ]`);
546
+ B.startBlock(lQuote);
547
+ B.line(`store i1 true, ptr ${instr}`);
548
+ this.putc(B, buf, c);
549
+ B.br(inc);
550
+ // '{' / '[': emit, then `{}`/`[]` stay inline (like Node) or a break
551
+ // one level deeper.
552
+ const openBody = (label, closer) => {
553
+ B.startBlock(label);
554
+ this.putc(B, buf, c);
555
+ const i1b = B.tmp();
556
+ const hasNextB = B.tmp();
557
+ B.line(`${i1b} = add i64 ${i}, 1`);
558
+ B.line(`${hasNextB} = icmp ult i64 ${i1b}, ${n}`);
559
+ const chk = B.newLabel("ji.opc");
560
+ const deep = B.newLabel("ji.opd");
561
+ const inline = B.newLabel("ji.opi");
562
+ B.condBr(hasNextB, chk, deep);
563
+ B.startBlock(chk);
564
+ const ncp = B.tmp();
565
+ const nc = B.tmp();
566
+ const isClose = B.tmp();
567
+ B.line(`${ncp} = getelementptr inbounds i8, ptr ${data}, i64 ${i1b}`);
568
+ B.line(`${nc} = load i8, ptr ${ncp}`);
569
+ B.line(`${isClose} = icmp eq i8 ${nc}, ${closer}`);
570
+ B.condBr(isClose, inline, deep);
571
+ B.startBlock(inline);
572
+ const nc2p = B.tmp();
573
+ const nc2 = B.tmp();
574
+ B.line(`${nc2p} = getelementptr inbounds i8, ptr ${data}, i64 ${i1b}`);
575
+ B.line(`${nc2} = load i8, ptr ${nc2p}`);
576
+ this.putc(B, buf, nc2);
577
+ B.line(`store i64 ${i1b}, ptr ${iSlot} ; empty {} / [] stay inline, like Node`);
578
+ B.br(inc);
579
+ B.startBlock(deep);
580
+ const d0 = B.tmp();
581
+ const d1 = B.tmp();
582
+ B.line(`${d0} = load i64, ptr ${depth}`);
583
+ B.line(`${d1} = add i64 ${d0}, 1`);
584
+ B.line(`store i64 ${d1}, ptr ${depth}`);
585
+ B.line(`call void @sc_ji_ind(ptr ${buf}, i64 ${d1}, ptr %ind, i64 %ilen)`);
586
+ B.br(inc);
587
+ };
588
+ openBody(lOpenB, 125); // '{' closes with '}'
589
+ openBody(lOpenK, 93); // '[' closes with ']'
590
+ B.startBlock(lClose);
591
+ const dc0 = B.tmp();
592
+ const dc1 = B.tmp();
593
+ B.line(`${dc0} = load i64, ptr ${depth}`);
594
+ B.line(`${dc1} = sub i64 ${dc0}, 1`);
595
+ B.line(`store i64 ${dc1}, ptr ${depth}`);
596
+ B.line(`call void @sc_ji_ind(ptr ${buf}, i64 ${dc1}, ptr %ind, i64 %ilen)`);
597
+ this.putc(B, buf, c);
598
+ B.br(inc);
599
+ B.startBlock(lComma);
600
+ this.putc(B, buf, "44");
601
+ const dm = B.tmp();
602
+ B.line(`${dm} = load i64, ptr ${depth}`);
603
+ B.line(`call void @sc_ji_ind(ptr ${buf}, i64 ${dm}, ptr %ind, i64 %ilen)`);
604
+ B.br(inc);
605
+ B.startBlock(lColon);
606
+ this.putc(B, buf, "58");
607
+ this.putc(B, buf, "32"); // ' '
608
+ B.br(inc);
609
+ B.startBlock(lOther);
610
+ this.putc(B, buf, c);
611
+ B.br(inc);
612
+ B.startBlock(inc);
613
+ const iN = B.tmp();
614
+ const iN2 = B.tmp();
615
+ B.line(`${iN} = load i64, ptr ${iSlot}`);
616
+ B.line(`${iN2} = add i64 ${iN}, 1`);
617
+ B.line(`store i64 ${iN2}, ptr ${iSlot}`);
618
+ B.br(loop);
619
+ B.startBlock(end);
620
+ const r = B.tmp();
621
+ B.line(`${r} = call ptr @scr_jb_finish(ptr ${buf})`);
622
+ B.terminate(`ret ptr ${r}`);
623
+ this.defs.push(`define internal ptr @${name}(ptr %compact, ptr %ind, i64 %ilen) ${FN_ATTRS} { ; stringify space re-indent (Node's gap algorithm)`, B.render(), `}`, ``);
624
+ return name;
625
+ }
626
+ /* ── the per-union ToString / Array#join pair (ported) ────────────────── */
627
+ /** The per-union ToString helper: unit arms return the interned
628
+ * "undefined"/"null" texts, string arms retain the payload, f64/bool
629
+ * arms format (String(x) semantics). Ref arms never arrive (frontend
630
+ * fence). Borrows the operand; the result is owned (+1). */
631
+ unionToStrHelper(unionId) {
632
+ const existing = this.unionToStrFns.get(unionId);
633
+ if (existing)
634
+ return existing;
635
+ const def = this.host.unionsById.get(unionId);
636
+ if (!def)
637
+ throw new Error(`llvm emitter bug: ToString of unknown union ${unionId}`);
638
+ const name = `sc_us_${this.unionToStrFns.size}`;
639
+ this.unionToStrFns.set(unionId, name);
640
+ const host = this.host;
641
+ const B = new BlockBuilder();
642
+ const tag = this.unionTag(B, "%v");
643
+ const bad = B.newLabel("us.bad");
644
+ const labels = def.arms.map(() => B.newLabel("us.a"));
645
+ B.terminate(`switch i32 ${tag}, label %${bad} [ ${def.arms.map((_, i) => `i32 ${i}, label %${labels[i]}`).join(" ")} ]`);
646
+ def.arms.forEach((arm, i) => {
647
+ B.startBlock(labels[i]);
648
+ switch (arm.kind) {
649
+ case "undefinedT":
650
+ case "nullT": {
651
+ const lit = host.internLiteral(arm.kind === "undefinedT" ? "undefined" : "null");
652
+ host.declare(`declare ptr @scr_str_retain_v(ptr)`);
653
+ const r = B.tmp();
654
+ B.line(`${r} = call ptr @scr_str_retain_v(ptr ${lit})`);
655
+ B.terminate(`ret ptr ${r}`);
656
+ break;
657
+ }
658
+ case "string": {
659
+ const p = this.unionPeek(B, "%v");
660
+ host.declare(`declare ptr @scr_str_retain_v(ptr)`);
661
+ const r = B.tmp();
662
+ B.line(`${r} = call ptr @scr_str_retain_v(ptr ${p})`);
663
+ B.terminate(`ret ptr ${r}`);
664
+ break;
665
+ }
666
+ case "f64": {
667
+ host.declare(`declare double @scr_union_get_f64(ptr)`);
668
+ host.declare(`declare ptr @scr_f64_to_scrstr(double)`);
669
+ const x = B.tmp();
670
+ const r = B.tmp();
671
+ B.line(`${x} = call double @scr_union_get_f64(ptr %v)`);
672
+ B.line(`${r} = call ptr @scr_f64_to_scrstr(double ${x})`);
673
+ B.terminate(`ret ptr ${r}`);
674
+ break;
675
+ }
676
+ case "bool": {
677
+ host.declare(`declare zeroext i1 @scr_union_get_bool(ptr)`);
678
+ host.declare(`declare ptr @scr_bool_to_scrstr(i1 zeroext)`);
679
+ const x = B.tmp();
680
+ const r = B.tmp();
681
+ B.line(`${x} = call zeroext i1 @scr_union_get_bool(ptr %v)`);
682
+ B.line(`${r} = call ptr @scr_bool_to_scrstr(i1 zeroext ${x})`);
683
+ B.terminate(`ret ptr ${r}`);
684
+ break;
685
+ }
686
+ default:
687
+ throw new LlvmUnsupportedError(`unionToStr:${arm.kind}`);
688
+ }
689
+ });
690
+ B.startBlock(bad);
691
+ host.needBadTag();
692
+ B.line(`call void @sc_bad_tag()`);
693
+ B.terminate(`unreachable`);
694
+ this.defs.push(`define internal ptr @${name}(ptr %v) ${FN_ATTRS} { ; ToString ${unionId}`, B.render(), `}`, ``);
695
+ return name;
696
+ }
697
+ /** Array.prototype.join over union elements: undefined/null arms print
698
+ * EMPTY (exactly JS's join), every other arm goes through the per-union
699
+ * ToString walker. Borrows the array and separator; result +1. */
700
+ unionJoinHelper(unionId) {
701
+ const existing = this.unionJoinFns.get(unionId);
702
+ if (existing)
703
+ return existing;
704
+ const def = this.host.unionsById.get(unionId);
705
+ if (!def)
706
+ throw new Error(`llvm emitter bug: join of unknown union ${unionId}`);
707
+ const name = `sc_uj_${this.unionJoinFns.size}`;
708
+ this.unionJoinFns.set(unionId, name);
709
+ const toStr = this.unionToStrHelper(unionId);
710
+ const unitTags = def.arms.flatMap((a, i) => (a.kind === "undefinedT" || a.kind === "nullT" ? [i] : []));
711
+ const host = this.host;
712
+ host.declare(`declare void @scr_jb_init(ptr)`);
713
+ host.declare(`declare ptr @scr_jb_finish(ptr)`);
714
+ host.declare(`declare double @scr_arr_len(ptr)`);
715
+ host.declare(`declare ptr @scr_arr_get_ref(ptr, double)`);
716
+ host.declare(`declare void @scr_union_release(ptr)`);
717
+ host.declare(`declare void @scr_str_release(ptr)`);
718
+ const B = new BlockBuilder();
719
+ const buf = "%jb";
720
+ const iSlot = "%i";
721
+ B.entryAllocas.push(`${buf} = alloca %ScrJsonBuf`, `${iSlot} = alloca double`);
722
+ B.line(`call void @scr_jb_init(ptr ${buf})`);
723
+ B.line(`store double ${f64Lit(0)}, ptr ${iSlot}`);
724
+ const len = B.tmp();
725
+ B.line(`${len} = call double @scr_arr_len(ptr %a)`);
726
+ const lc = B.newLabel("uj.c");
727
+ const lb = B.newLabel("uj.b");
728
+ const ln = B.newLabel("uj.n");
729
+ const le = B.newLabel("uj.e");
730
+ B.br(lc);
731
+ B.startBlock(lc);
732
+ const i = B.tmp();
733
+ const cont = B.tmp();
734
+ B.line(`${i} = load double, ptr ${iSlot}`);
735
+ B.line(`${cont} = fcmp olt double ${i}, ${len}`);
736
+ B.condBr(cont, lb, le);
737
+ B.startBlock(lb);
738
+ // `if (i) put sep bytes` — separators between elements only.
739
+ const nz = B.tmp();
740
+ const lsep = B.newLabel("uj.s");
741
+ const lel = B.newLabel("uj.v");
742
+ B.line(`${nz} = fcmp ogt double ${i}, ${f64Lit(0)}`);
743
+ B.condBr(nz, lsep, lel);
744
+ B.startBlock(lsep);
745
+ this.putScrStr(B, buf, "%sep");
746
+ B.br(lel);
747
+ B.startBlock(lel);
748
+ const u = B.tmp();
749
+ B.line(`${u} = call ptr @scr_arr_get_ref(ptr %a, double ${i}) ; element (+1)`);
750
+ if (unitTags.length > 0) {
751
+ // Nullish arms print empty, exactly JS's join.
752
+ const tag = this.unionTag(B, u);
753
+ let acc = "";
754
+ for (const t of unitTags) {
755
+ const cnd = B.tmp();
756
+ B.line(`${cnd} = icmp eq i32 ${tag}, ${t}`);
757
+ if (acc === "") {
758
+ acc = cnd;
759
+ }
760
+ else {
761
+ const o = B.tmp();
762
+ B.line(`${o} = or i1 ${acc}, ${cnd}`);
763
+ acc = o;
764
+ }
765
+ }
766
+ const lskip = B.newLabel("uj.k");
767
+ const lwrite = B.newLabel("uj.w");
768
+ B.condBr(acc, lskip, lwrite);
769
+ B.startBlock(lskip);
770
+ B.line(`call void @scr_union_release(ptr ${u})`);
771
+ B.br(ln);
772
+ B.startBlock(lwrite);
773
+ }
774
+ const s = B.tmp();
775
+ B.line(`${s} = call ptr @${toStr}(ptr ${u})`);
776
+ this.putScrStr(B, buf, s);
777
+ B.line(`call void @scr_str_release(ptr ${s})`);
778
+ B.line(`call void @scr_union_release(ptr ${u})`);
779
+ B.br(ln);
780
+ B.startBlock(ln);
781
+ const i2 = B.tmp();
782
+ B.line(`${i2} = fadd double ${i}, ${f64Lit(1)}`);
783
+ B.line(`store double ${i2}, ptr ${iSlot}`);
784
+ B.br(lc);
785
+ B.startBlock(le);
786
+ const r = B.tmp();
787
+ B.line(`${r} = call ptr @scr_jb_finish(ptr ${buf})`);
788
+ B.terminate(`ret ptr ${r}`);
789
+ this.defs.push(`define internal ptr @${name}(ptr %a, ptr %sep) ${FN_ATTRS} { ; Array#join over ${unionId}: nullish arms print empty`, B.render(), `}`, ``);
790
+ return name;
791
+ }
792
+ }
793
+ //# sourceMappingURL=walkers.js.map