@scriptc/compiler 0.0.0 → 0.0.1
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.
- package/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5947 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10087 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +64 -0
- package/dist/coverage/report.js +226 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6281 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +483 -0
- package/dist/frontend/lowering/lower-containers.js +6281 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8099 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1069 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6256 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2172 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +60 -0
- package/dist/frontend/shared.js +232 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +424 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4884 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- 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
|