@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,1249 @@
1
+ import { funcOf, isRefCounted, isUnitType, mapOf, moduleEmbedsCompressedNpm, moduleUsesDgram, moduleUsesDynInvoke, moduleEmbedsBuiltin, moduleUsesFetch, moduleUsesFsWatch, moduleUsesHttp2, moduleUsesHttpServer, moduleUsesNet, moduleUsesNodeTest, moduleUsesProcessEvents, moduleUsesStream, RUNTIME_EMITTER_CLASS, STRING, VOID } from "../../ir/nodes.js";
2
+ import { mangleAsyncSpawn, mangleGenSpawn, mangleClassObj, mangleField, mangleGlobal, mangleFnClosure, mangleFunction, mangleLocal, mangleRawParam, mangleVtSlot, mangleWrapper, } from "../mangle.js";
3
+ import { cType, releaseCallC, cStringLiteral, cDecl } from "./emit-types.js";
4
+ import { computeMayThrow } from "./may-throw.js";
5
+ import { dynDesc, unionTruthyHelper, unionEqHelper, unionToStrHelper, unionJoinHelper, jsonWriteHelper, jsonIndentHelper, dynMatchHelper, dynCheckHelper, dynFuncBoxHelper, dynToStrHelper, caughtToDynHelper, toDynHelper, recordKeyGetHelper, recordKeySetHelper } from "./emit-walkers.js";
6
+ import { emitStructDefs, vtEntriesFor, vtSlotParams, emitVtableDecls, emitVtableInstances, emitVtAdapterDefs, emitHierarchyClassHelpers, emitClassObjs, emitCtorThunkDefs, errorVtStampLines, emitterVtStampLines, streamVtStampLines, traceAdapterC, traceArgC, boxNewC, arrNewC } from "./emit-shapes.js";
7
+ import { emitAsyncScaffolding, childDataThunkFor, childExitThunkFor, childExitThunkFor2, closeBindThunkFor, connectSockThunkFor, closeOverrideWrapFor, dgramMsgThunkFor, dnsLookupThunkFor, netLookupAnswerThunkFor, emitterInvokeThunkFor, streamCbThunkFor, streamDataThunkFor, raceAdapterFor, resolveThunkFor, sniAnswerThunkFor } from "./emit-async.js";
8
+ import { emitNpmEmbedding, islandAdapter, islandTypedAdapter } from "./emit-island.js";
9
+ import { emitFunction, emitBlock, emitStmts, emitStmt, emitTryCatch, emitSwitch, mergeBrace, emitBranchInto, emitCondition } from "./emit-stmts.js";
10
+ import { emitExpr } from "./emit-exprs.js";
11
+ export function emitModule(mod, sourceText) {
12
+ return new CEmitter(mod, sourceText).emit();
13
+ }
14
+ export class CEmitter {
15
+ mod;
16
+ lines = [];
17
+ indent = 0;
18
+ tempCounter = 0;
19
+ /** Interned string literals: UTF-8 text → static symbol name. */
20
+ literals = new Map();
21
+ /** Interned unit-armed union instances: "unionId:tag" → static symbol.
22
+ * A unit arm (undefined/null) has no payload, so every instance of one
23
+ * (union, tag) pair is identical — ONE immortal (rc == SIZE_MAX) static
24
+ * serves them all. Immortals are skipped by retain/release and by the
25
+ * cycle collector's child filter (SCR_CYC_SKIP), so the missing cycle
26
+ * header is fine even when a traced container points at one. */
27
+ unitInstances = new Map();
28
+ /** Interned regex literals: "<flags>/<pattern>" → static symbol. One
29
+ * immortal (rc == SIZE_MAX) ScrRegex per distinct (pattern, flags) pair —
30
+ * the bytecode slot starts NULL and the runtime compiles it lazily on
31
+ * first use. The source/flags strings ride the ordinary literal table. */
32
+ regexInstances = new Map();
33
+ /** Interned tagged-template strings objects: per-site key → symbol +
34
+ * cooked spans. One immortal (rc == SIZE_MAX) ScrArr of interned string
35
+ * literals per template-literal SITE — the spec's per-occurrence
36
+ * identity: the same site evaluated twice hands the tag the same array,
37
+ * two sites never share even with identical text. */
38
+ templateStringsInstances = new Map();
39
+ /** Class objects (classes as first-class values): className → static
40
+ * symbol. One immortal ScrClassObj per class some classRef names —
41
+ * preorder interval baked as constants, the .name string in the literal
42
+ * table, and a per-class construct thunk (void *sc_ct_*) newValue
43
+ * dispatches through. Registered during body emission (the regex
44
+ * pattern); the statics and thunks are assembled around the bodies. */
45
+ classObjs = new Map();
46
+ /** Stack of statement frames: refcounted temps not yet released or moved. */
47
+ frames = [];
48
+ /** Stack of scopes: refcounted locals (with types) declared in each. */
49
+ scopes = [];
50
+ /** The function being emitted: local table (for boxedness) and whether
51
+ * each local arrived through the closure environment (env captures are
52
+ * borrowed — never declared, never released here). */
53
+ currentLocals = new Map();
54
+ captureIds = new Set();
55
+ /** Declared functions referenced as values: each needs an env-signature
56
+ * wrapper + an interned immortal closure (so `f === f` holds). */
57
+ fnValues = new Set();
58
+ /** Emitted ref-kind resolve thunks for new Promise, interned per inner
59
+ * typeKey → thunk symbol. */
60
+ resolveThunks = new Map();
61
+ genResThunks = new Map();
62
+ /** Emitted child exit adapters, interned per union id (childExitThunkFor). */
63
+ childExitThunks = new Map();
64
+ /** Emitted child-stream data adapters, interned per union id
65
+ * (childDataThunkFor). */
66
+ childDataThunks = new Map();
67
+ /** Emitted bound-close adapters, interned per union id
68
+ * (closeBindThunkFor). */
69
+ closeBindThunks = new Map();
70
+ /** Emitted close-override wrappers, interned per (union id, ret kind)
71
+ * (closeOverrideWrapFor). */
72
+ closeOverrideWraps = new Map();
73
+ /** Emitted dgram message adapters, interned per rinfo shape id
74
+ * (dgramMsgThunkFor). */
75
+ dgramMsgThunks = new Map();
76
+ /** Emitted dns.lookup callback adapters, interned per union id + param
77
+ * count (dnsLookupThunkFor). */
78
+ dnsLookupThunks = new Map();
79
+ /** Emitted SNI answer-closure thunks, interned per cb func-type key
80
+ * (sniAnswerThunkFor). */
81
+ sniAnswerThunks = new Map();
82
+ /** Emitted net.connect lookup answer thunks, interned per cb func-type
83
+ * key (netLookupAnswerThunkFor). */
84
+ netLookupAnswerThunks = new Map();
85
+ /** Emitted EventEmitter listener invoke adapters, interned per listener
86
+ * func-type key (emitterInvokeThunkFor). */
87
+ emitterInvokeThunks = new Map();
88
+ /** Emitted stream option-callback invoke adapters (read/write/final/
89
+ * destroy/transform/flush — the leading-`this` closures), interned per
90
+ * (kind, callback func-type) key (streamCbThunkFor). */
91
+ streamCbThunks = new Map();
92
+ /** Emitted stream completion-callback closure fns (the `callback` a
93
+ * user's write/final/destroy/transform/flush receives and calls),
94
+ * interned per (kind, done func-type) key (streamDoneFnFor). */
95
+ streamDoneFns = new Map();
96
+ /** Emitted CONNECT-listener union-socket adapters, interned per cb
97
+ * func-type key (connectSockThunkFor). */
98
+ connectSockThunks = new Map();
99
+ /** Emitted Promise.race fulfillment adapters, interned per
100
+ * `entryInner=>resultInner` typeKey pair (raceAdapterFor). */
101
+ raceThunks = new Map();
102
+ /** setTimeout appeared somewhere: main must run the event loop even in
103
+ * programs with no async functions. */
104
+ usesTimers = false;
105
+ fnByName = new Map();
106
+ globalsById = new Map();
107
+ unionsById = new Map();
108
+ /** Active optional-chain bind temps, by chain id (chainRecv reads). */
109
+ chainTemps = new Map();
110
+ recordsById = new Map();
111
+ /** Type-directed JSON walkers, interned per typeKey — one serializer per
112
+ * type used in jsonStringify position (sc_jw_*), one match predicate
113
+ * (sc_dm_*) and one checked builder (sc_dc_*) per type used in dynCheck
114
+ * position. Emitted as prototypes + definitions after the struct block
115
+ * (they reference struct types, per-shape RC helpers, and each other, so
116
+ * the prototypes make definition order irrelevant). */
117
+ jsonWriters = new Map();
118
+ /** The pretty-print re-indenter (type-independent, interned once):
119
+ * emitted on the first `JSON.stringify(v, null, space)` site. */
120
+ jsonIndentFn = null;
121
+ /** The dyn ToString pair (type-independent, interned once): emitted on
122
+ * the first String(unknown) / `${unknown}` site. */
123
+ dynToStrFn = null;
124
+ /** The caught→dyn converter (type-independent, interned once): emitted
125
+ * on the first catch binding flowing into an `unknown` slot. */
126
+ caughtToDynFn = null;
127
+ /** Interned per-union helpers: unionId → emitted function name. */
128
+ unionTruthyFns = new Map();
129
+ unionEqFns = new Map();
130
+ unionToStrFns = new Map();
131
+ unionJoinFns = new Map();
132
+ dynMatchers = new Map();
133
+ dynBuilders = new Map();
134
+ /** Static→DOM converters (sc_td_*), per typeKey; dynamic-keyed record
135
+ * read helpers (sc_rkg_*), per shapeId|result typeKey; dynamic-keyed
136
+ * write helpers (sc_rks_*), per shapeId. */
137
+ toDynFns = new Map();
138
+ /** The checked-dynamic function boundary's per-signature helpers (see
139
+ * emit-walkers.ts): call thunks (sc_dfk_*), box builders (sc_dfb_*),
140
+ * and dynCheck adapters (sc_dfa_*), each per func typeKey. */
141
+ dynFuncThunks = new Map();
142
+ dynFuncBoxes = new Map();
143
+ dynFuncAdapters = new Map();
144
+ /** DOM-promise settle adapters (sc_pda_*), per INNER typeKey: convert a
145
+ * typed fulfillment payload into the boxed destination's DOM payload
146
+ * (scr_dyn_new_promise_adapting's callback — toDynHelper's promise arm). */
147
+ promiseDynAdapters = new Map();
148
+ recordKeyGetFns = new Map();
149
+ recordKeySetFns = new Map();
150
+ walkerProtos = [];
151
+ walkerDefs = [];
152
+ /** Island host-call adapters, interned per (arity, void-ness): the one
153
+ * uniform shape scr_jsval_from_closure calls — unpack the cell array,
154
+ * call the closure through its real ABI (which CONSUMES its params, so
155
+ * each cell is retained in), return the +1 result cell or NULL for void.
156
+ * Definitions ride walkerDefs (self-contained over the closure ABI). */
157
+ islandAdapters = new Map();
158
+ /** TYPED island host-call adapters, interned per full signature (param
159
+ * typeKeys + return classification): each incoming engine argument
160
+ * converts to the param's static type through the exit machinery before
161
+ * the closure runs — see islandTypedAdapter (emit-island.ts). */
162
+ islandTypedAdapters = new Map();
163
+ /** Enclosing break/continue targets. An unlabeled `break` binds to the
164
+ * innermost loop-or-switch entry (labeled BLOCK entries are skipped); an
165
+ * unlabeled `continue` searches inward-out for the innermost LOOP,
166
+ * skipping switches and blocks. A LABELED jump binds to the entry whose
167
+ * `labels` contains its label: `break lbl` jumps to the target's
168
+ * endLabel (loops allocate one lazily via `usedEnd` — a C `break` only
169
+ * exits the innermost loop), `continue lbl` to the target loop's
170
+ * continueLabel. Loops: `continueLabel` is null when C `continue` is
171
+ * correct (unlabeled while/forOf); for/do-while loops need a goto label
172
+ * so `continue` still runs the update/condition, and LABELED loops of
173
+ * every shape allocate one up front (a labeled continue from a nested
174
+ * loop needs a goto). Switches are emitted as goto chains — never as C
175
+ * `switch` — so a C `break`/`continue` inside an emitted switch region
176
+ * still binds to the enclosing C loop; a break targeting the switch
177
+ * itself jumps to `endLabel` instead. Labeled blocks carry only an
178
+ * endLabel (`break lbl` is the only jump that can target them).
179
+ * `scopeDepth` = scopes.length at entry — a break/continue releases
180
+ * every scope pushed after that before jumping. `frameDepth` =
181
+ * frames.length at entry: statements the jump exits may still hold
182
+ * pending refcounted temps in their frames (a switch's discriminant,
183
+ * most notably), whose normal end-of-statement releases sit on the
184
+ * fall-through path the jump bypasses — the jump releases every frame
185
+ * pushed after the target's own before jumping. */
186
+ jumpTargets = [];
187
+ labelCounter = 0;
188
+ returnTypeByFn = new Map();
189
+ lineStarts = null;
190
+ /** May-throw analysis results (computeMayThrow): pending-exception checks
191
+ * are emitted only after calls that can actually raise. */
192
+ mayThrow;
193
+ indirectMayThrow;
194
+ /** Enclosing try contexts, innermost last — the compile-time analogue of
195
+ * the jump-target stack for UNWINDING: a pending check (or `throw`) inside
196
+ * a try releases frames/scopes down to the recorded depths and jumps to
197
+ * `label` (the catch, or the exception-path finally) instead of returning
198
+ * out of the function. Purely compile-time: entering a try emits no code. */
199
+ tryStack = [];
200
+ /** Enclosing try-with-FINALLY regions, innermost last — the pending-
201
+ * return analogue of tryStack: a `return` inside one snapshots its value
202
+ * into the function's pending-return slot (sc_pret), releases down to
203
+ * the region's depths, and jumps to `label` (the region's pending-return
204
+ * finally copy), whose tail dispatches to the next region out or emits
205
+ * the actual return. Spans tryBody and catchBody; the finally body
206
+ * itself is outside (the frontend fences jumps there). */
207
+ finallyStack = [];
208
+ /** Return type of the function being emitted — the unwind path returns a
209
+ * dummy of this type (never read: callers check the pending flag first). */
210
+ currentReturnType = VOID;
211
+ /** The generator channels of the function being emitted (null outside
212
+ * generator bodies): yieldExpr emission reads them, and emitTryCatch's
213
+ * catch prologue emits the GENRET sentinel re-unwind exactly here. */
214
+ currentGenerator = null;
215
+ /** Cycle-capable shapes ("object:<name>" / "record:<id>") and unions
216
+ * (unionId): instances get a cycle header + emitted trace/teardown, and
217
+ * fields/payloads of these types are visited by container traces.
218
+ * Computed as a greatest fixpoint in the constructor — see there. */
219
+ tracedShapes = new Set();
220
+ tracedUnions = new Set();
221
+ /** The class graph (single inheritance): base/children links, hierarchy
222
+ * membership (extends anywhere ⇒ vtable word + dynamic release), the
223
+ * whole-program preorder numbering behind O(1) instanceof, and the
224
+ * per-hierarchy virtual slot lists (root classes only). Computed once in
225
+ * the constructor. */
226
+ classMeta = new Map();
227
+ /** Method names with at least one may-throw implementation: a
228
+ * virtualCall's pending check keys on this (same over-approximation as
229
+ * computeMayThrow's callee cover). */
230
+ mayThrowMethods = new Set();
231
+ /** Vtable slot adapters to define after the function signatures (they
232
+ * call sc_f_* bodies): dedupe key "implClass.method". */
233
+ vtAdapters = new Map();
234
+ constructor(mod, sourceText) {
235
+ this.mod = mod;
236
+ for (const fn of mod.functions) {
237
+ this.returnTypeByFn.set(fn.name, fn.returnType);
238
+ this.fnByName.set(fn.name, fn);
239
+ }
240
+ const mt = computeMayThrow(mod);
241
+ this.mayThrow = mt.fns;
242
+ this.indirectMayThrow = mt.indirect;
243
+ for (const g of mod.globals ?? [])
244
+ this.globalsById.set(g.id, g);
245
+ for (const u of mod.unions ?? [])
246
+ this.unionsById.set(u.id, u);
247
+ for (const r of mod.records ?? [])
248
+ this.recordsById.set(r.id, r);
249
+ // The class graph. Link base/children, number the forest in preorder
250
+ // (roots and children in module class order — deterministic), and
251
+ // compute each hierarchy's virtual slots: a class's method gets a slot
252
+ // iff no ancestor declares it (root-most) AND some strict descendant
253
+ // redeclares it — never-overridden methods stay direct calls
254
+ // everywhere (whole-program devirtualization).
255
+ for (const cls of mod.classes ?? []) {
256
+ const meta = {
257
+ def: cls,
258
+ base: null,
259
+ children: [],
260
+ root: undefined,
261
+ pre: 0,
262
+ post: 0,
263
+ hierarchy: false,
264
+ slots: [],
265
+ };
266
+ this.classMeta.set(cls.name, meta);
267
+ }
268
+ for (const meta of this.classMeta.values()) {
269
+ if (meta.def.base === undefined)
270
+ continue;
271
+ const base = this.classMeta.get(meta.def.base);
272
+ if (!base)
273
+ throw new Error(`emitter bug: undeclared base class ${meta.def.base}`);
274
+ meta.base = base;
275
+ base.children.push(meta);
276
+ }
277
+ let preCounter = 0;
278
+ const number = (meta, root) => {
279
+ meta.root = root;
280
+ meta.pre = preCounter++;
281
+ for (const c of meta.children)
282
+ number(c, root);
283
+ meta.post = preCounter - 1; // max pre in the subtree (inclusive)
284
+ };
285
+ for (const meta of this.classMeta.values()) {
286
+ if (meta.base === null)
287
+ number(meta, meta);
288
+ // The runtime emitter class is ALWAYS a hierarchy member: ScrEmitter
289
+ // carries its vtable word whether or not the program subclasses it.
290
+ meta.hierarchy = meta.base !== null || meta.children.length > 0 ||
291
+ meta.def.name === RUNTIME_EMITTER_CLASS;
292
+ }
293
+ const declares = (m, method) => m.def.methods?.includes(method) ?? false;
294
+ const declaredBelow = (m, method) => m.children.some((c) => declares(c, method) || declaredBelow(c, method));
295
+ const collectSlots = (m, root, seen) => {
296
+ for (const method of m.def.methods ?? []) {
297
+ let inherited = false;
298
+ for (let a = m.base; a; a = a.base)
299
+ inherited ||= declares(a, method);
300
+ if (!inherited && declaredBelow(m, method)) {
301
+ let fn = this.fnByName.get(`%${m.def.name}.${method}`);
302
+ if (!fn && m.def.abstractMethods?.includes(method)) {
303
+ // An ABSTRACT declarer has no function; the slot's ABI
304
+ // signature comes from any concrete descendant implementation
305
+ // (the frontend's override exactness makes them identical).
306
+ // No concrete implementation anywhere in the subtree means no
307
+ // instance can dispatch the slot (only abstract classes
308
+ // declare it, and abstract classes never instantiate) — skip.
309
+ const findImpl = (c) => {
310
+ for (const child of c.children) {
311
+ const f = declares(child, method) && !child.def.abstractMethods?.includes(method)
312
+ ? this.fnByName.get(`%${child.def.name}.${method}`)
313
+ : undefined;
314
+ const found = f ?? findImpl(child);
315
+ if (found)
316
+ return found;
317
+ }
318
+ return undefined;
319
+ };
320
+ fn = findImpl(m);
321
+ if (!fn)
322
+ continue;
323
+ }
324
+ if (!fn)
325
+ throw new Error(`emitter bug: missing method function %${m.def.name}.${method}`);
326
+ // Sibling branches can each own a slot for the same method name
327
+ // (each root-most in its own subtree — mixin layers make this
328
+ // routine): the member name disambiguates by occurrence.
329
+ const occurrence = seen.get(method) ?? 0;
330
+ seen.set(method, occurrence + 1);
331
+ root.slots.push({ method, declarer: m, fn, member: mangleVtSlot(method, occurrence) });
332
+ }
333
+ }
334
+ for (const c of m.children)
335
+ collectSlots(c, root, seen);
336
+ };
337
+ for (const meta of this.classMeta.values()) {
338
+ if (meta.base === null && meta.hierarchy)
339
+ collectSlots(meta, meta, new Map());
340
+ }
341
+ for (const cls of mod.classes ?? []) {
342
+ for (const m of cls.methods ?? []) {
343
+ if (this.mayThrow.has(`%${cls.name}.${m}`))
344
+ this.mayThrowMethods.add(m);
345
+ }
346
+ }
347
+ // Cycle capability, as a greatest fixpoint over shapes and unions:
348
+ // start optimistic (everything cycle-capable), then repeatedly drop
349
+ // shapes with no cycle-capable field and unions with no cycle-capable
350
+ // arm until stable. Closures and promises are always cycle-capable
351
+ // (a captured box can hold anything; a rejection payload is an
352
+ // arbitrary thrown value); strings never are, and arrays/maps inherit
353
+ // their element/value type's capability (a record element can point
354
+ // back at the array holding it). The optimistic start is what keeps
355
+ // self- and mutually-recursive classes traced (`class A { next: A }`).
356
+ const shapeDefs = [
357
+ // The emitter class carries a synthetic closure-typed pseudo-field:
358
+ // its runtime registry OWNS listener closures, so the emitter
359
+ // hierarchy is unconditionally cycle-capable — the fixpoint must
360
+ // never drop it (the pseudo-field never reaches struct emission;
361
+ // runtime classes emit no structs).
362
+ ...(mod.classes ?? []).map((c) => ({
363
+ key: `object:${c.name}`,
364
+ fields: c.name === RUNTIME_EMITTER_CLASS
365
+ ? [...c.fields, { name: "<listeners>", type: funcOf([], VOID) }]
366
+ : c.fields,
367
+ })),
368
+ // An index-signature shape's overflow map participates like a field
369
+ // of map type: the shape is cycle-capable when the overflow VALUE
370
+ // type is (a record/object/union value in the map can point back at
371
+ // the record embedding it) — cycleCapable's map rule answers that.
372
+ ...(mod.records ?? []).map((r) => ({
373
+ key: `record:${r.id}`,
374
+ fields: r.indexValue
375
+ ? [...r.fields, { name: "<overflow>", type: mapOf(STRING, r.indexValue) }]
376
+ : r.fields,
377
+ })),
378
+ ];
379
+ for (const s of shapeDefs)
380
+ this.tracedShapes.add(s.key);
381
+ // A hierarchy is ONE unit of cycle capability: a base-typed slot can
382
+ // hold any subclass and retain touches the cycle header, so header
383
+ // presence must be uniform across an extends-hierarchy — it is
384
+ // cycle-capable iff ANY member is. Standalone classes and records are
385
+ // singleton units (today's behavior exactly).
386
+ const unitKeyOf = (key) => {
387
+ if (!key.startsWith("object:"))
388
+ return key;
389
+ const meta = this.classMeta.get(key.slice("object:".length));
390
+ return meta && meta.hierarchy ? `object:${meta.root.def.name}` : key;
391
+ };
392
+ const units = new Map();
393
+ for (const s of shapeDefs) {
394
+ const unit = unitKeyOf(s.key);
395
+ let members = units.get(unit);
396
+ if (!members)
397
+ units.set(unit, (members = []));
398
+ members.push(s);
399
+ }
400
+ for (const u of mod.unions ?? [])
401
+ this.tracedUnions.add(u.id);
402
+ const cycleCapable = (t) => {
403
+ switch (t.kind) {
404
+ case "func":
405
+ case "promise":
406
+ return true;
407
+ case "object":
408
+ return this.tracedShapes.has(`object:${t.className}`);
409
+ case "record":
410
+ return this.tracedShapes.has(`record:${t.shapeId}`);
411
+ case "union":
412
+ return this.tracedUnions.has(t.unionId);
413
+ // A map is cycle-capable exactly when its VALUE type is: a record/
414
+ // object/union value can hold the map that owns it, while string/
415
+ // array/scalar values cannot point back (array elements are fenced
416
+ // to kinds that cannot reach a map). Terminates: map values cannot
417
+ // be maps, and the record/union cases read the fixpoint sets.
418
+ case "map":
419
+ return cycleCapable(t.value);
420
+ // An array is cycle-capable exactly when its ELEMENT type is —
421
+ // record/object/union elements (and cycle-capable inner arrays)
422
+ // can point back at the array. Terminates: element types are
423
+ // finite trees, and the record/union cases read the fixpoint sets.
424
+ case "array":
425
+ return cycleCapable(t.elem);
426
+ default:
427
+ return false;
428
+ }
429
+ };
430
+ let shrunk = true;
431
+ while (shrunk) {
432
+ shrunk = false;
433
+ for (const members of units.values()) {
434
+ if (this.tracedShapes.has(members[0].key) &&
435
+ !members.some((s) => s.fields.some((f) => cycleCapable(f.type)))) {
436
+ for (const s of members)
437
+ this.tracedShapes.delete(s.key);
438
+ shrunk = true;
439
+ }
440
+ }
441
+ for (const u of mod.unions ?? []) {
442
+ if (this.tracedUnions.has(u.id) && !u.arms.some(cycleCapable)) {
443
+ this.tracedUnions.delete(u.id);
444
+ shrunk = true;
445
+ }
446
+ }
447
+ }
448
+ if (sourceText !== undefined) {
449
+ this.lineStarts = [0];
450
+ for (let i = 0; i < sourceText.length; i++) {
451
+ if (sourceText[i] === "\n")
452
+ this.lineStarts.push(i + 1);
453
+ }
454
+ }
455
+ }
456
+ emit() {
457
+ const body = [];
458
+ // Function bodies are emitted first (into this.lines) so the literal
459
+ // table is complete; the file is then assembled around them.
460
+ for (const fn of this.mod.functions) {
461
+ this.emitFunction(fn);
462
+ body.push(...this.lines);
463
+ this.lines.length = 0;
464
+ }
465
+ const out = [
466
+ `/* Generated by scriptc from ${this.mod.sourceFile}. Do not edit. */`,
467
+ `#include "scr_runtime.h"`,
468
+ `#include <math.h>`,
469
+ `#include <stdio.h>`,
470
+ `#include <stdlib.h>`,
471
+ ``,
472
+ ];
473
+ // Struct defs render into their own buffer BEFORE the unit-instance
474
+ // table flushes: class newFns point undefined-armed union fields at
475
+ // interned unit instances (fields start as JS's undefined, not NULL),
476
+ // so the instances they intern must both exist in the map by flush
477
+ // time and be DEFINED earlier in the file than the newFn bodies.
478
+ const structDefs = [];
479
+ this.emitStructDefs(structDefs);
480
+ for (const [text, sym] of this.literals) {
481
+ const bytes = Buffer.from(text, "utf8");
482
+ out.push(`static struct { size_t rc; size_t len; size_t cap; char data[${bytes.length + 1}]; } ${sym} =`, ` { SIZE_MAX, ${bytes.length}, ${bytes.length}, ${cStringLiteral(bytes)} };`);
483
+ }
484
+ if (this.literals.size > 0)
485
+ out.push("");
486
+ for (const [key, sym] of this.unitInstances) {
487
+ // One immortal instance per unit-armed (union, tag): tag set, payload
488
+ // slot and RC entry points zero — retain/release/collector all skip
489
+ // rc == SIZE_MAX, so these never join the RC audit or a trace walk.
490
+ const [unionId, tag] = key.split(":");
491
+ out.push(`static ScrUnion ${sym} = { .rc = SIZE_MAX, .tag = ${tag} }; /* ${unionId} unit arm */`);
492
+ }
493
+ if (this.unitInstances.size > 0)
494
+ out.push("");
495
+ out.push(...structDefs);
496
+ for (const [key, sym] of this.regexInstances) {
497
+ // One immortal ScrRegex per (pattern, flags) literal, pointing at the
498
+ // interned source/flags strings. `.bc` starts NULL: the runtime
499
+ // compiles the pattern lazily on first use and caches it here (and
500
+ // frees it at exit), so unexecuted regexes cost nothing. NOT const —
501
+ // the bc slot mutates.
502
+ const sep = key.indexOf("/");
503
+ const flags = key.slice(0, sep);
504
+ const pattern = key.slice(sep + 1);
505
+ const src = this.internLiteral(pattern);
506
+ const fl = this.internLiteral(flags);
507
+ // "*/" inside the pattern would close the trailing comment.
508
+ const safe = `/${pattern}/${flags}`.split("*/").join("* /");
509
+ out.push(`static ScrRegex ${sym} = { .rc = SIZE_MAX, .source = (ScrStr *)&${src}, ` +
510
+ `.flags = (ScrStr *)&${fl}, .bc = NULL }; /* ${safe} */`);
511
+ }
512
+ if (this.regexInstances.size > 0)
513
+ out.push("");
514
+ for (const [, { sym, cooked }] of this.templateStringsInstances) {
515
+ // One immortal ScrArr per tagged-template site: the data slots point
516
+ // at the interned cooked-string literals (address constants — fully
517
+ // static, no lazy init; reads retain immortal strings, a no-op).
518
+ // len == cap and nothing ever mutates it (TemplateStringsArray is
519
+ // ReadonlyArray, tsc rejects the mutating spellings).
520
+ const slots = cooked.map((s) => `(void *)&${this.internLiteral(s)}`).join(", ");
521
+ out.push(`static void *${sym}_data[${cooked.length}] = { ${slots} };`, `static ScrArr ${sym} = { .rc = SIZE_MAX, .len = ${cooked.length}, .cap = ${cooked.length}, ` +
522
+ `.elem = SCR_ELEM_STR, .elem_retain = NULL, .elem_release = NULL, .elem_trace = NULL, ` +
523
+ `.data = (uint64_t *)${sym}_data };`);
524
+ }
525
+ if (this.templateStringsInstances.size > 0)
526
+ out.push("");
527
+ const embedded = this.mod.embedded;
528
+ emitNpmEmbedding(this, out);
529
+ const globals = this.mod.globals ?? [];
530
+ for (const g of globals) {
531
+ // File-scope statics: zero/NULL-initialized, assigned by %init
532
+ // functions, released (refcounted ones) before the RC audit runs.
533
+ out.push(`static ${cDecl(g.type, mangleGlobal(g.id))}; /* ${g.name} */`);
534
+ }
535
+ if (globals.length > 0)
536
+ out.push("");
537
+ for (const fn of this.mod.functions)
538
+ out.push(this.signature(fn) + ";");
539
+ // Class objects (classes as values): construct-thunk prototypes plus
540
+ // the immortal statics that take their addresses — after the function
541
+ // signatures (the thunks call sc_new_*/the constructors), before
542
+ // anything that references &sc_co_*.
543
+ emitClassObjs(this, out);
544
+ // Vtable slot adapters: prototyped with the vtables (emitStructDefs),
545
+ // defined here where the method bodies they call are declared.
546
+ this.emitVtAdapterDefs(out);
547
+ // Wrappers + interned closures for declared functions used as values.
548
+ // Placed after the forward declarations (they call sc_f_*) and before
549
+ // the bodies (which reference &sc_fc_*).
550
+ this.emitAsyncScaffolding(out);
551
+ for (const name of this.fnValues) {
552
+ const fn = this.fnByName.get(name);
553
+ const params = ["ScrClosure *sc_env", ...fn.params.map((p) => cDecl(p.type, mangleLocal(p.localId)))];
554
+ const call = `${this.callTargetC(name)}(${fn.params.map((p) => mangleLocal(p.localId)).join(", ")})`;
555
+ const retType = fn.async ? "ScrPromise *" : fn.generator ? "ScrGen *" : cType(fn.returnType);
556
+ out.push(``, `static ${retType}${retType.endsWith("*") ? "" : " "}${mangleWrapper(name)}(${params.join(", ")}) {`, ` (void)sc_env;`, fn.returnType.kind === "void" && !fn.async && !fn.generator ? ` ${call};` : ` return ${call};`, `}`, `static struct { size_t rc; void *fn; size_t ncaps; ScrBox *props; } ${mangleFnClosure(name)} =`, ` { SIZE_MAX, (void *)&${mangleWrapper(name)}, 0, NULL };`);
557
+ }
558
+ // Construct-thunk definitions (prototyped with the class objects
559
+ // above): they call sc_new_* and the constructors, both declared.
560
+ emitCtorThunkDefs(this, out);
561
+ // Type-directed JSON walkers (jsonStringify serializers, dynCheck
562
+ // matchers/builders), interned per type during body emission above.
563
+ if (this.walkerProtos.length > 0) {
564
+ out.push("", ...this.walkerProtos, "", ...this.walkerDefs);
565
+ }
566
+ out.push("", ...body);
567
+ const refGlobals = globals.filter((g) => isRefCounted(g.type));
568
+ // Interned function-value closures are IMMORTAL (rc == SIZE_MAX), so
569
+ // an own-property table Object.defineProperties hung on one would
570
+ // outlive the RC audit — release it with the globals. Only emitted
571
+ // when the dispatch unit is even linked (defineProps is the only
572
+ // writer).
573
+ const fnValueProps = moduleUsesDynInvoke(this.mod) ? [...this.fnValues] : [];
574
+ if (refGlobals.length > 0 || fnValueProps.length > 0) {
575
+ out.push(`static void sc_release_globals(void) {`);
576
+ for (const g of refGlobals) {
577
+ out.push(` ${releaseCallC(g.type, mangleGlobal(g.id))};`);
578
+ }
579
+ for (const name of fnValueProps) {
580
+ out.push(` if (${mangleFnClosure(name)}.props) { scr_box_release(${mangleFnClosure(name)}.props); ${mangleFnClosure(name)}.props = NULL; }`);
581
+ }
582
+ out.push(`}`, ``);
583
+ }
584
+ const hasAsync = this.mod.functions.some((f) => f.async);
585
+ // Generator programs run the loop too (an empty pass when nothing is
586
+ // pending): its exit accounting notes still-suspended generator
587
+ // fibers as abandoned, so the RC audit downgrades exactly like the
588
+ // async loop-exhaustion story.
589
+ const hasGenerators = this.mod.functions.some((f) => f.generator !== undefined);
590
+ // Embedded npm code can leave island promise chains pending when %main
591
+ // returns (a package function's async work) — the loop's io hook
592
+ // drains the engine's job queue at quiescence, so npm-importing
593
+ // programs always run the loop, like Node always runs its own.
594
+ const usesIsland = embedded !== undefined && embedded.modules.length > 0;
595
+ // Exit listeners can read MODULE GLOBALS directly (test/common's
596
+ // runCallChecks over its mustCallChecks ledger — an interned top-level
597
+ // closure, no capture boxes keeping anything alive), so they must run
598
+ // BEFORE sc_release_globals — the atexit half alone would fire after
599
+ // main freed them (observed use-after-free). scr_run_exit_listeners
600
+ // is idempotent (scr_exit_ran), so the atexit becomes a no-op; the
601
+ // code argument is the hint the failure reporters maintain — exactly
602
+ // what the atexit path would have passed.
603
+ const needsRelease = refGlobals.length > 0 || fnValueProps.length > 0;
604
+ const runExitListeners = moduleUsesProcessEvents(this.mod) && needsRelease
605
+ ? "scr_run_exit_listeners((double)scr_exit_code_hint_get()); "
606
+ : "";
607
+ const releaseGlobals = needsRelease
608
+ ? ` ${runExitListeners}sc_release_globals();`
609
+ : ` /* no refcounted globals */`;
610
+ const uncaught = (indent) => [
611
+ `${indent}if (scr_exc_pending()) {`,
612
+ `${indent} scr_exc_print_uncaught();`,
613
+ `${indent} ${needsRelease ? `${runExitListeners}sc_release_globals(); ` : ""}return 1;`,
614
+ `${indent}}`,
615
+ ];
616
+ out.push(
617
+ // Real argc/argv feed the library's interned process.argv (see
618
+ // scr_lib_init — lazy, so argv-free programs allocate nothing).
619
+ `int main(int argc, char **argv) {`, ` scr_init();`,
620
+ // The builtin error classes' preorder intervals are program-dependent
621
+ // (they share this module's class-forest numbering, so instanceof and
622
+ // the uncaught printer's Error-range test agree between runtime-made
623
+ // and compiled error objects) — stamp them before any code runs.
624
+ ...this.errorVtStampLines(),
625
+ // The runtime emitter vtable's interval, when the program touches
626
+ // node:events (the class def rides the module exactly then).
627
+ ...emitterVtStampLines(this),
628
+ // The runtime stream vtables' intervals, when the program touches
629
+ // node:stream (the emitter story — instanceof and dynamic teardown
630
+ // both dispatch through them).
631
+ ...streamVtStampLines(this), ` scr_lib_init(argc, argv);`,
632
+ // Fetch-referencing programs register the native fetch bridge before any
633
+ // island entry (the engine's lazy boot consults it): the ONLY
634
+ // reference to scr_fetch.c, so fetch-free builds never compile or
635
+ // link it (cc.ts gates on the same predicate). moduleUsesFetch is
636
+ // true only for fetch-referencing embedded graphs and for USER-code
637
+ // fetch (the island-backed ambient's globalGet) — both boot the
638
+ // engine before the global is read.
639
+ ...(moduleUsesFetch(this.mod) ? [` scr_fetch_install();`] : []),
640
+ // Embedded graphs that import node:zlib register the island's zlib
641
+ // bridge before any island entry — the ONLY reference to
642
+ // scr_zlib_island.c (cc.ts compiles it on the same predicate), so
643
+ // zlib-free dynamic builds keep the island's clear refusal.
644
+ ...(moduleEmbedsBuiltin(this.mod, "node:zlib") ? [` scr_zlib_island_install();`] : []),
645
+ // Embedded graphs that import node:http/https register the island's
646
+ // http client bridge (scr_net_island.c — cc.ts compiles it and the
647
+ // socket units on the same predicate; native-fetch builds also
648
+ // register it from scr_fetch_install, idempotently).
649
+ ...(moduleEmbedsBuiltin(this.mod, "node:http") || moduleEmbedsBuiltin(this.mod, "node:https")
650
+ ? [` scr_net_island_install();`]
651
+ : []),
652
+ // Event-surface programs (signal/exit listeners, stdin events) fill
653
+ // the loop's nullable event hooks before %main — the events unit
654
+ // (scr_events.c) links only when this line is emitted (cc.ts gates
655
+ // on the same predicate, like fetch).
656
+ ...(moduleUsesProcessEvents(this.mod) ? [` scr_events_install();`] : []),
657
+ // Net-surface programs fill the loop's net hooks before %main — the
658
+ // net unit (scr_net.c) links only when this line is emitted (cc.ts
659
+ // gates on the same predicate, like events). The dyn-install twin
660
+ // stamps the netSocket handle-dispatch ops into the DOM core so
661
+ // sockets can cross the checked-dynamic boundary (SCR_DYN_HANDLE).
662
+ ...(moduleUsesNet(this.mod) ? [` scr_net_install();`, ` scr_net_dyn_install();`] : []),
663
+ // Http-surface programs additionally stamp the httpReq/httpRes
664
+ // handle-dispatch ops (scr_http.c links exactly when this line is
665
+ // emitted — cc.ts's http gate).
666
+ ...(moduleUsesHttpServer(this.mod) ? [` scr_http_dyn_install();`] : []),
667
+ // http2-surface programs stamp the h2 session/stream handle-dispatch
668
+ // ops (scr_http2.c links exactly when this line is emitted — cc.ts's
669
+ // http2 gate).
670
+ ...(moduleUsesHttp2(this.mod) ? [` scr_http2_dyn_install();`] : []),
671
+ // Stream-surface programs fill the loop's stream hook (the deferred
672
+ // next-tick emissions) before %main — scr_stream.c links only when
673
+ // this line is emitted (cc.ts gates on the same predicate).
674
+ ...(moduleUsesStream(this.mod) ? [` scr_stream_install();`] : []),
675
+ // Dgram/dns-surface programs fill the loop's dgram hooks the same
676
+ // way — scr_dgram.c links only when this line is emitted.
677
+ ...(moduleUsesDgram(this.mod) ? [` scr_dgram_install();`] : []),
678
+ // fs.watch programs fill the loop's watch hooks the same way —
679
+ // scr_watch.c links only when this line is emitted.
680
+ ...(moduleUsesFsWatch(this.mod) ? [` scr_watch_install();`] : []),
681
+ // The embedded npm tables must be registered before %main: the %init
682
+ // functions it calls import from them. Static data only — the engine
683
+ // still boots lazily, on the first island entry. Compressed module
684
+ // text (emit-island.ts stores big sources as raw DEFLATE) needs the
685
+ // inflater installed first — scr_zlib.c joins the link on the same
686
+ // moduleEmbedsCompressedNpm predicate (index.ts's zlib switch), so
687
+ // compression-free dynamic builds keep their exact link line.
688
+ ...(embedded && embedded.modules.length > 0
689
+ ? [
690
+ ...(moduleEmbedsCompressedNpm(this.mod)
691
+ ? [` scr_island_set_inflate(scr_zlib_inflate_exact);`]
692
+ : []),
693
+ ` scr_island_modules(sc_npm_modules, ${embedded.modules.length}, ` +
694
+ `${embedded.edges.length > 0 ? "sc_npm_edges" : "NULL"}, ${embedded.edges.length});`,
695
+ ]
696
+ : []), ` ${mangleFunction(this.mod.entry)}();`,
697
+ // Uncaught exception from top-level code: Node exits 1.
698
+ ...(this.mayThrow.has(this.mod.entry) ? uncaught(" ") : []),
699
+ // The event loop runs to exhaustion (microtasks before timers). A
700
+ // throw escaping a timer callback and unhandled promise rejections
701
+ // both exit 1, like Node.
702
+ ...(hasAsync || hasGenerators || this.usesTimers || usesIsland
703
+ ? [
704
+ ` scr_loop_run();`,
705
+ ...uncaught(" "),
706
+ ` if (scr_report_unhandled_rejections()) {`,
707
+ ` ${needsRelease ? `${runExitListeners}sc_release_globals(); ` : ""}return 1;`,
708
+ ` }`,
709
+ ]
710
+ : []), releaseGlobals,
711
+ // node:test programs exit through the runner's verdict (Node's
712
+ // contract: 1 when any non-todo test failed). The loop-run above is
713
+ // guaranteed for these programs — every registration libCall sets
714
+ // usesTimers, so the runner fiber always drains before this line.
715
+ // Island programs exit with process.exitCode when the embedded
716
+ // graph set it (Node's implicit exit status: set it, return
717
+ // normally, exit with it) — 0 when never set.
718
+ ...(moduleUsesNodeTest(this.mod)
719
+ ? [` return scr_test_exit_code();`]
720
+ : usesIsland
721
+ ? [` return scr_island_exit_code();`]
722
+ : [` return 0;`]), `}`, ``);
723
+ return out.join("\n");
724
+ }
725
+ emitAsyncScaffolding(out) {
726
+ return emitAsyncScaffolding(this, out);
727
+ }
728
+ emitStructDefs(out) {
729
+ return emitStructDefs(this, out);
730
+ }
731
+ /* ── vtables (class hierarchies) ──────────────────────────────────────
732
+ * One vtable struct type per hierarchy (named after the root): a ScrVt
733
+ * head — the class's preorder interval for instanceof and its DIRECT
734
+ * release for dynamic teardown — plus one member per virtual slot. Only
735
+ * methods actually overridden somewhere have slots; the slot's signature
736
+ * is the root-most declaring class's, and overriding implementations sit
737
+ * behind reinterpreting adapters (prefix layout makes the `this` cast
738
+ * sound). Each class gets one static const vtable instance stamped into
739
+ * every object it allocates. */
740
+ vtEntriesFor(meta) {
741
+ return vtEntriesFor(this, meta);
742
+ }
743
+ vtSlotParams(slot, named) {
744
+ return vtSlotParams(this, slot, named);
745
+ }
746
+ emitVtableDecls(out, hierarchyClasses) {
747
+ return emitVtableDecls(this, out, hierarchyClasses);
748
+ }
749
+ emitVtableInstances(out, hierarchyClasses) {
750
+ return emitVtableInstances(this, out, hierarchyClasses);
751
+ }
752
+ emitVtAdapterDefs(out) {
753
+ return emitVtAdapterDefs(this, out);
754
+ }
755
+ emitHierarchyClassHelpers(out, meta, s) {
756
+ return emitHierarchyClassHelpers(this, out, meta, s);
757
+ }
758
+ /* ── type-directed JSON walkers (jsonStringify / dynCheck) ────────────
759
+ * No DOM is built for stringify and no tags are consulted: the STATIC IR
760
+ * type drives everything, one emitted helper per typeKey (interned, like
761
+ * the array-HOF desugars). dynCheck helpers walk the runtime DOM (ScrDyn)
762
+ * against the target type: match predicates (sc_dm_*) answer "does this
763
+ * DOM fit?" without throwing (union arms are tried in canonical order —
764
+ * first FULL match wins), and builders (sc_dc_*) construct the typed
765
+ * value (+1) or throw a path-annotated TypeError through the exception
766
+ * cell. Recursion terminates because recursive shapes/unions are rejected
767
+ * by the frontend. */
768
+ dynDesc(t) {
769
+ return dynDesc(this, t);
770
+ }
771
+ islandAdapter(arity, retKind) {
772
+ return islandAdapter(this, arity, retKind);
773
+ }
774
+ islandTypedAdapter(fn) {
775
+ return islandTypedAdapter(this, fn);
776
+ }
777
+ unionTruthyHelper(unionId) {
778
+ return unionTruthyHelper(this, unionId);
779
+ }
780
+ unionEqHelper(unionId) {
781
+ return unionEqHelper(this, unionId);
782
+ }
783
+ unionToStrHelper(unionId) {
784
+ return unionToStrHelper(this, unionId);
785
+ }
786
+ unionJoinHelper(unionId) {
787
+ return unionJoinHelper(this, unionId);
788
+ }
789
+ jsonWriteHelper(t) {
790
+ return jsonWriteHelper(this, t);
791
+ }
792
+ jsonIndentHelper() {
793
+ return jsonIndentHelper(this);
794
+ }
795
+ dynMatchHelper(t) {
796
+ return dynMatchHelper(this, t);
797
+ }
798
+ dynCheckHelper(t) {
799
+ return dynCheckHelper(this, t);
800
+ }
801
+ toDynHelper(t) {
802
+ return toDynHelper(this, t);
803
+ }
804
+ dynFuncBoxHelper(t) {
805
+ return dynFuncBoxHelper(this, t);
806
+ }
807
+ recordKeyGetHelper(shapeId, t, overflowOnly = false) {
808
+ return recordKeyGetHelper(this, shapeId, t, overflowOnly);
809
+ }
810
+ recordKeySetHelper(shapeId) {
811
+ return recordKeySetHelper(this, shapeId);
812
+ }
813
+ dynToStrHelper() {
814
+ return dynToStrHelper(this);
815
+ }
816
+ caughtToDynHelper() {
817
+ return caughtToDynHelper(this);
818
+ }
819
+ /* ── cycle-collection wiring ──────────────────────────────────────────
820
+ * Containers that store a payload's RC entry points as function pointers
821
+ * (obj-kind boxes, union ref arms, promise payloads, the exception cell)
822
+ * also store the payload type's TRACE entry point — non-NULL exactly when
823
+ * the payload type carries a cycle header, which is what the container's
824
+ * own trace keys on (visit when present, release at teardown when not).
825
+ * Closures/unions/promises always carry one (runtime-provided traces);
826
+ * classes/records carry one iff their shape can participate in a cycle
827
+ * (emitted trace); strings/arrays/dyn never can. */
828
+ traceAdapterC(t) {
829
+ return traceAdapterC(this, t);
830
+ }
831
+ traceArgC(t) {
832
+ return traceArgC(this, t);
833
+ }
834
+ boxNewC(t) {
835
+ return boxNewC(this, t);
836
+ }
837
+ arrNewC(elem, capExpr) {
838
+ return arrNewC(this, elem, capExpr);
839
+ }
840
+ /* ── plumbing ─────────────────────────────────────────────────────── */
841
+ line(text) {
842
+ this.lines.push(" ".repeat(this.indent) + text);
843
+ }
844
+ srcComment(loc) {
845
+ if (!this.lineStarts)
846
+ return "";
847
+ let lo = 0, hi = this.lineStarts.length - 1;
848
+ while (lo < hi) {
849
+ const mid = (lo + hi + 1) >> 1;
850
+ if (this.lineStarts[mid] <= loc.start)
851
+ lo = mid;
852
+ else
853
+ hi = mid - 1;
854
+ }
855
+ return ` /* ${this.mod.sourceFile}:${lo + 1} */`;
856
+ }
857
+ newTemp(type, init) {
858
+ const name = `sc_t${this.tempCounter++}`;
859
+ this.line(`${cDecl(type, name)} = ${init};`);
860
+ if (isRefCounted(type))
861
+ this.currentFrame().push({ name, type });
862
+ return { name, type };
863
+ }
864
+ /** newTemp for a MAY-THROW runtime call: the result joins its frame
865
+ * BEFORE the standard pending check, so an unwind releases the dummy
866
+ * (NULL for refcounted kinds) harmlessly and the value is only read past
867
+ * the check. The shared shape of every fallible boundary call — jsOp,
868
+ * jsExit, composite jsMarshal, dynCheck, await reads. */
869
+ fallibleTemp(type, call) {
870
+ const t = this.newTemp(type, call);
871
+ this.emitPendingCheck();
872
+ return t;
873
+ }
874
+ currentFrame() {
875
+ const frame = this.frames[this.frames.length - 1];
876
+ if (!frame)
877
+ throw new Error("emitter bug: no active statement frame");
878
+ return frame;
879
+ }
880
+ /** Strike a refcounted temp from its frame: ownership is being moved. */
881
+ moveTemp(t) {
882
+ if (!isRefCounted(t.type))
883
+ return;
884
+ for (let i = this.frames.length - 1; i >= 0; i--) {
885
+ const idx = this.frames[i].findIndex((e) => e.name === t.name);
886
+ if (idx >= 0) {
887
+ this.frames[i].splice(idx, 1);
888
+ return;
889
+ }
890
+ }
891
+ throw new Error(`emitter bug: moved temp ${t.name} not found in any frame`);
892
+ }
893
+ /** The release call for one owned refcounted value. */
894
+ releaseValue(name, type) {
895
+ this.line(`${releaseCallC(type, name)};`);
896
+ }
897
+ releaseFrame(frame) {
898
+ for (const t of frame) {
899
+ if (t.boxed)
900
+ this.line(`scr_box_release(${t.name});`);
901
+ else
902
+ this.releaseValue(t.name, t.type);
903
+ }
904
+ }
905
+ /** THE release-on-jump path (break/continue/return): pending statement
906
+ * frames and entered scopes down to (and excluding nothing above) the
907
+ * given depths, innermost first — everything whose normal fall-through
908
+ * releases the jump bypasses. The jump target's own frame/scope stay
909
+ * live: a loop's releases after the loop and a switch's after its end
910
+ * label are still on the jump's path (return passes 0/0 — it leaves the
911
+ * whole function). */
912
+ releaseForJump(frameDepth, scopeDepth) {
913
+ for (let i = this.frames.length - 1; i >= frameDepth; i--)
914
+ this.releaseFrame(this.frames[i]);
915
+ for (let i = this.scopes.length - 1; i >= scopeDepth; i--)
916
+ this.releaseFrame(this.scopes[i]);
917
+ }
918
+ /** True when the last statement is a jump — its emission already released
919
+ * everything it had to, and the fall-through releases after it would be
920
+ * dead double-release code. `throw` counts: it unwinds (to a handler or
921
+ * out of the function) through the same release path. */
922
+ endsWithJump(stmts) {
923
+ const last = stmts[stmts.length - 1]?.kind;
924
+ return last === "return" || last === "break" || last === "continue" || last === "throw" || last === "rethrow" || last === "runtimeFence";
925
+ }
926
+ /** THE unwind path at a point where an exception is pending: release
927
+ * everything between here and the innermost try handler — or the whole
928
+ * function — via releaseForJump, then jump to the handler / return a
929
+ * dummy value (never read: callers of a may-throw function test the
930
+ * pending flag before using the result). Callers own the surrounding
931
+ * `if (scr_exc_pending())`; a `throw` unwinds unconditionally. */
932
+ emitUnwind() {
933
+ const target = this.tryStack[this.tryStack.length - 1];
934
+ if (target) {
935
+ this.releaseForJump(target.frameDepth, target.scopeDepth);
936
+ target.used = true;
937
+ this.line(`goto ${target.label};`);
938
+ return;
939
+ }
940
+ this.releaseForJump(0, 0);
941
+ const t = this.currentReturnType;
942
+ if (t.kind === "void")
943
+ this.line(`return;`);
944
+ else if (t.kind === "f64")
945
+ this.line(`return 0;`);
946
+ else if (t.kind === "bool")
947
+ this.line(`return false;`);
948
+ else
949
+ this.line(`return NULL;`);
950
+ }
951
+ errorVtStampLines() {
952
+ return errorVtStampLines(this);
953
+ }
954
+ /** The C symbol a direct call or closure enters a function through:
955
+ * async bodies are entered via their emitted spawn wrapper (which runs
956
+ * the fiber eagerly to its first suspension and returns the promise);
957
+ * generator bodies via theirs (which only ALLOCATES the suspended
958
+ * fiber and returns the generator object). */
959
+ callTargetC(fnName) {
960
+ const fn = this.fnByName.get(fnName);
961
+ if (fn?.async === true)
962
+ return mangleAsyncSpawn(fnName);
963
+ if (fn?.generator !== undefined)
964
+ return mangleGenSpawn(fnName);
965
+ return mangleFunction(fnName);
966
+ }
967
+ /** The emitter contract for exceptions: after EVERY call that can throw
968
+ * (per the may-throw analysis), test the pending flag and unwind. */
969
+ emitPendingCheck() {
970
+ this.line(`if (scr_exc_pending()) {`);
971
+ this.indent++;
972
+ this.emitUnwind();
973
+ this.indent--;
974
+ this.line(`}`);
975
+ }
976
+ internLiteral(text) {
977
+ let sym = this.literals.get(text);
978
+ if (!sym) {
979
+ sym = `sc_lit_${this.literals.size}`;
980
+ this.literals.set(text, sym);
981
+ }
982
+ return sym;
983
+ }
984
+ /** The class object's static symbol (classes as values), registering it
985
+ * for assembly on first use and interning the .name literal while the
986
+ * literal table is still open (bodies emit before the file assembles —
987
+ * the regex-literal discipline). */
988
+ classObjSym(className) {
989
+ let sym = this.classObjs.get(className);
990
+ if (!sym) {
991
+ const meta = this.classMeta.get(className);
992
+ if (!meta)
993
+ throw new Error(`emitter bug: classRef to unknown class ${className}`);
994
+ this.internLiteral(meta.def.jsName ?? "");
995
+ sym = mangleClassObj(className);
996
+ this.classObjs.set(className, sym);
997
+ }
998
+ return sym;
999
+ }
1000
+ /** True when construction through a classval of `className` can throw:
1001
+ * the runtime callee is the static class's constructor or any strict
1002
+ * descendant's (classval flows never leave the subtree). */
1003
+ newValueMayThrow(className) {
1004
+ const meta = this.classMeta.get(className);
1005
+ if (!meta)
1006
+ throw new Error(`emitter bug: newValue on unknown class ${className}`);
1007
+ const any = (m) => this.mayThrow.has(`%${m.def.name}.constructor`) || m.children.some(any);
1008
+ return any(meta);
1009
+ }
1010
+ /** The undefined arm's tag of a union type, or -1 (not a union / no
1011
+ * undefined arm). A record FIELD with such a type is optional-flavored:
1012
+ * the JSON serializer DROPS it while it holds the undefined arm and the
1013
+ * dynCheck builder produces the undefined arm for a MISSING key — both
1014
+ * exactly Node's optional-field behavior. */
1015
+ undefinedArmTag(t) {
1016
+ if (t.kind !== "union")
1017
+ return -1;
1018
+ const def = this.unionsById.get(t.unionId);
1019
+ return def ? def.arms.findIndex((a) => a.kind === "undefinedT") : -1;
1020
+ }
1021
+ /** The interned immortal instance for a UNIT arm of a union — asserts the
1022
+ * arm really is payload-less (undefined/null). */
1023
+ internUnitInstance(unionId, tag) {
1024
+ const arm = this.unionsById.get(unionId)?.arms[tag];
1025
+ if (!arm || !isUnitType(arm)) {
1026
+ throw new Error(`emitter bug: unit instance for non-unit arm ${tag} of ${unionId}`);
1027
+ }
1028
+ const key = `${unionId}:${tag}`;
1029
+ let sym = this.unitInstances.get(key);
1030
+ if (!sym) {
1031
+ sym = `sc_unit_${this.unitInstances.size}`;
1032
+ this.unitInstances.set(key, sym);
1033
+ }
1034
+ return sym;
1035
+ }
1036
+ /** The C expression for that instance — the one pointer every unit-arm
1037
+ * value of this (union, tag) is: rc == SIZE_MAX, so RC entry points and
1038
+ * the collector both skip it and no retain is ever owed. */
1039
+ unitInstanceRef(unionId, tag) {
1040
+ return `(ScrUnion *)&${this.internUnitInstance(unionId, tag)}`;
1041
+ }
1042
+ /** The class-newFn initialization line for a field whose type ADMITS
1043
+ * undefined — such fields start as JS's `undefined`, never the calloc
1044
+ * NULL: tsc's strictPropertyInitialization accepts them with no
1045
+ * initializer and no constructor assignment (undefined is in the type),
1046
+ * so a fresh instance is readable before any assignment runs — a method
1047
+ * assigns later, a constructor branch skips it, a base constructor's
1048
+ * virtual call reads a derived field before super() returns. Node reads
1049
+ * `undefined` there; a NULL payload pointer would be a segfault (union
1050
+ * fields) or a silent nothing (jsval fields). Undefined-armed unions get
1051
+ * the interned immortal unit instance (free; releases skip it); jsval
1052
+ * (`any`) fields get an engine undefined cell — such classes exist only
1053
+ * in --dynamic builds, and the field's release balances it. Empty for
1054
+ * every type that cannot hold undefined (tsc's SPI guards those) and for
1055
+ * record shapes' construction paths, which write every field. */
1056
+ undefFieldInitLineC(name, t) {
1057
+ if (t.kind === "jsval") {
1058
+ return [` o->${mangleField(name)} = scr_jsval_undefined(); /* ${name} starts undefined */`];
1059
+ }
1060
+ const tag = this.undefinedArmTag(t);
1061
+ if (tag < 0 || t.kind !== "union")
1062
+ return [];
1063
+ return [` o->${mangleField(name)} = ${this.unitInstanceRef(t.unionId, tag)}; /* ${name} starts undefined */`];
1064
+ }
1065
+ /* ── functions ────────────────────────────────────────────────────── */
1066
+ signature(fn) {
1067
+ const boxedIds = new Set(fn.locals.filter((l) => l.boxed).map((l) => l.id));
1068
+ const parts = fn.params.map((p) =>
1069
+ // A boxed param's sc_l_ name is its box; the raw value arrives under
1070
+ // a sc_p_ name and is moved into the box in the prologue.
1071
+ cDecl(p.type, boxedIds.has(p.localId) ? mangleRawParam(p.localId) : mangleLocal(p.localId)));
1072
+ // Lifted functions receive their closure first.
1073
+ if (fn.captures !== undefined)
1074
+ parts.unshift("ScrClosure *sc_env");
1075
+ const params = parts.length ? parts.join(", ") : "void";
1076
+ return `static ${cType(fn.returnType)} ${mangleFunction(fn.name)}(${params})`;
1077
+ }
1078
+ emitFunction(fn) {
1079
+ return emitFunction(this, fn);
1080
+ }
1081
+ /* ── statements ───────────────────────────────────────────────────── */
1082
+ emitBlock(stmts, setup) {
1083
+ return emitBlock(this, stmts, setup);
1084
+ }
1085
+ emitStmts(stmts) {
1086
+ return emitStmts(this, stmts);
1087
+ }
1088
+ emitStmt(s) {
1089
+ return emitStmt(this, s);
1090
+ }
1091
+ /** A fresh loop jump-target entry. `continueLabel` null means "C
1092
+ * continue is correct" (while/forOf) — but a LABELED loop always
1093
+ * allocates one (a labeled continue arriving from a nested loop needs a
1094
+ * goto), and every labeled loop gets a lazy end label for labeled break
1095
+ * (a C break only exits the innermost loop). */
1096
+ loopTarget(continueLabel, labels) {
1097
+ return {
1098
+ kind: "loop",
1099
+ continueLabel: continueLabel === null && labels !== undefined ? `sc_cont_${this.labelCounter++}` : continueLabel,
1100
+ usedContinue: false,
1101
+ endLabel: labels !== undefined ? `sc_end_${this.labelCounter++}` : null,
1102
+ usedEnd: false,
1103
+ ...(labels !== undefined && { labels }),
1104
+ scopeDepth: this.scopes.length,
1105
+ frameDepth: this.frames.length,
1106
+ };
1107
+ }
1108
+ emitTryCatch(s) {
1109
+ return emitTryCatch(this, s);
1110
+ }
1111
+ emitSwitch(s) {
1112
+ return emitSwitch(this, s);
1113
+ }
1114
+ mergeBrace(emitBlockFn) {
1115
+ return mergeBrace(this, emitBlockFn);
1116
+ }
1117
+ emitBranchInto(target, expr) {
1118
+ return emitBranchInto(this, target, expr);
1119
+ }
1120
+ emitCondition(cond) {
1121
+ return emitCondition(this, cond);
1122
+ }
1123
+ /** C expression testing JS truthiness of a temp (falsy: 0, -0, NaN, "").
1124
+ * `x == x` rejects NaN, `x != 0` rejects both zeros; strings only need
1125
+ * their length — no runtime call, no ownership change. */
1126
+ truthyC(t) {
1127
+ switch (t.type.kind) {
1128
+ case "bool":
1129
+ return t.name;
1130
+ case "f64":
1131
+ return `${t.name} == ${t.name} && ${t.name} != 0`;
1132
+ case "string":
1133
+ return `${t.name}->len != 0`;
1134
+ case "jsval":
1135
+ // Island truthiness: the engine's ToBoolean (never throws, no
1136
+ // ownership change) — jsval operands are legal in `logical`.
1137
+ return `(scr_jsval_truthy(${t.name}) != 0)`;
1138
+ case "union":
1139
+ // The ARM value's ToBoolean, answered by a per-union interned
1140
+ // helper (switch on tag: unit arms false, scalar/string arms by
1141
+ // value, ref arms true, jsval arms ask the engine).
1142
+ return `${this.unionTruthyHelper(t.type.unionId)}(${t.name})`;
1143
+ case "array":
1144
+ case "map":
1145
+ case "set":
1146
+ case "regex":
1147
+ case "url":
1148
+ case "searchParams":
1149
+ case "symbol":
1150
+ case "stats":
1151
+ case "spawnRes":
1152
+ case "child":
1153
+ case "netServer":
1154
+ case "netSocket":
1155
+ case "http2Session":
1156
+ case "http2Stream":
1157
+ case "dgramSocket":
1158
+ case "testCtx":
1159
+ case "httpReq":
1160
+ case "httpRes":
1161
+ case "httpClientReq":
1162
+ case "secureCtx":
1163
+ case "fsWatcher":
1164
+ case "childStream":
1165
+ case "bytes":
1166
+ case "func":
1167
+ case "object":
1168
+ case "classval":
1169
+ case "record":
1170
+ case "promise":
1171
+ case "generator":
1172
+ // JS objects are ALWAYS truthy ([] and {} included). These are
1173
+ // non-NULL pointers, so the honest constant reads as a pointer
1174
+ // test (no unused-value warnings, operand still evaluated).
1175
+ return `${t.name} != NULL`;
1176
+ case "procStream":
1177
+ // A stream value is a JS object (always truthy); the scalar fd
1178
+ // representation is 1 or 2, so the honest constant reads as its
1179
+ // own non-zero test.
1180
+ return `${t.name} != 0`;
1181
+ case "dyn":
1182
+ // ToBoolean over the DOM kind (scr_dyn_truthy — JS-exact for
1183
+ // every kind; borrowed, never throws): `v || dflt` and condition
1184
+ // descent on checked-dynamic values.
1185
+ return `scr_dyn_truthy(${t.name})`;
1186
+ case "undefinedT":
1187
+ case "nullT":
1188
+ case "caught":
1189
+ throw new Error(`emitter bug: truthiness of ${t.type.kind}`);
1190
+ case "void":
1191
+ throw new Error("emitter bug: truthiness of void");
1192
+ default: {
1193
+ const _exhaustive = t.type;
1194
+ void _exhaustive;
1195
+ throw new Error("unreachable");
1196
+ }
1197
+ }
1198
+ }
1199
+ /* ── expressions ──────────────────────────────────────────────────── */
1200
+ emitExpr(e) {
1201
+ return emitExpr(this, e);
1202
+ }
1203
+ childExitThunkFor(param) {
1204
+ return childExitThunkFor(this, param);
1205
+ }
1206
+ childDataThunkFor(param) {
1207
+ return childDataThunkFor(this, param);
1208
+ }
1209
+ closeBindThunkFor(cbUnion, retServer) {
1210
+ return closeBindThunkFor(this, cbUnion, retServer);
1211
+ }
1212
+ closeOverrideWrapFor(cbUnion, retServer) {
1213
+ return closeOverrideWrapFor(this, cbUnion, retServer);
1214
+ }
1215
+ childExitThunkFor2(codeParam, sigParam) {
1216
+ return childExitThunkFor2(this, codeParam, sigParam);
1217
+ }
1218
+ dgramMsgThunkFor(param) {
1219
+ return dgramMsgThunkFor(this, param);
1220
+ }
1221
+ dnsLookupThunkFor(cbT) {
1222
+ return dnsLookupThunkFor(this, cbT);
1223
+ }
1224
+ sniAnswerThunkFor(cbT) {
1225
+ return sniAnswerThunkFor(this, cbT);
1226
+ }
1227
+ netLookupAnswerThunkFor(cbT) {
1228
+ return netLookupAnswerThunkFor(this, cbT);
1229
+ }
1230
+ emitterInvokeThunkFor(cbT) {
1231
+ return emitterInvokeThunkFor(this, cbT);
1232
+ }
1233
+ streamDataThunkFor(cbT) {
1234
+ return streamDataThunkFor(this, cbT);
1235
+ }
1236
+ streamCbThunkFor(kind, cbT) {
1237
+ return streamCbThunkFor(this, kind, cbT);
1238
+ }
1239
+ connectSockThunkFor(cbT) {
1240
+ return connectSockThunkFor(this, cbT);
1241
+ }
1242
+ raceAdapterFor(from, to) {
1243
+ return raceAdapterFor(this, from, to);
1244
+ }
1245
+ resolveThunkFor(inner) {
1246
+ return resolveThunkFor(this, inner);
1247
+ }
1248
+ }
1249
+ //# sourceMappingURL=emitter.js.map