@scriptc/compiler 0.0.25 → 0.0.27

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 (69) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +15 -7
  2. package/dist/backend/cc.d.ts +22 -0
  3. package/dist/backend/cc.js +128 -10
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-exprs.js +81 -10
  6. package/dist/backend/emission/emit-exprs.js.map +1 -1
  7. package/dist/backend/emission/emitter.d.ts +14 -1
  8. package/dist/backend/emission/emitter.js +126 -13
  9. package/dist/backend/emission/emitter.js.map +1 -1
  10. package/dist/backend/emission/may-throw.js +11 -1
  11. package/dist/backend/emission/may-throw.js.map +1 -1
  12. package/dist/backend/ffi-callbacks.d.ts +11 -0
  13. package/dist/backend/ffi-callbacks.js +33 -0
  14. package/dist/backend/ffi-callbacks.js.map +1 -0
  15. package/dist/backend/llvm/classes.js +21 -21
  16. package/dist/backend/llvm/classes.js.map +1 -1
  17. package/dist/backend/llvm/dyn.d.ts +7 -4
  18. package/dist/backend/llvm/dyn.js +145 -141
  19. package/dist/backend/llvm/dyn.js.map +1 -1
  20. package/dist/backend/llvm/emitter.d.ts +7 -1
  21. package/dist/backend/llvm/emitter.js +961 -303
  22. package/dist/backend/llvm/emitter.js.map +1 -1
  23. package/dist/backend/llvm/shapes.d.ts +4 -0
  24. package/dist/backend/llvm/shapes.js +26 -24
  25. package/dist/backend/llvm/shapes.js.map +1 -1
  26. package/dist/backend/llvm/unsupported.js +1 -1
  27. package/dist/backend/llvm/unsupported.js.map +1 -1
  28. package/dist/backend/llvm/walkers.d.ts +2 -0
  29. package/dist/backend/llvm/walkers.js +50 -46
  30. package/dist/backend/llvm/walkers.js.map +1 -1
  31. package/dist/coverage/surface-manifest.js +1 -1
  32. package/dist/coverage/surface-manifest.js.map +1 -1
  33. package/dist/diagnostics/diagnostic.d.ts +1 -1
  34. package/dist/diagnostics/diagnostic.js +3 -2
  35. package/dist/diagnostics/diagnostic.js.map +1 -1
  36. package/dist/ffi/profile.d.ts +20 -2
  37. package/dist/ffi/profile.js +130 -6
  38. package/dist/ffi/profile.js.map +1 -1
  39. package/dist/frontend/lowering/lower-calls.js +58 -22
  40. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  41. package/dist/frontend/lowering/lower-exprs.js +17 -7
  42. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  43. package/dist/frontend/lowering/lower-server.d.ts +5 -0
  44. package/dist/frontend/lowering/lower-server.js +129 -37
  45. package/dist/frontend/lowering/lower-server.js.map +1 -1
  46. package/dist/frontend/lowering/lower-stmts.js +8 -1
  47. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  48. package/dist/frontend/lowering/surfaces.d.ts +3 -2
  49. package/dist/frontend/lowering/surfaces.js +4 -3
  50. package/dist/frontend/lowering/surfaces.js.map +1 -1
  51. package/dist/frontend/npm.d.ts +16 -0
  52. package/dist/frontend/npm.js +257 -0
  53. package/dist/frontend/npm.js.map +1 -1
  54. package/dist/index.d.ts +7 -4
  55. package/dist/index.js +188 -5
  56. package/dist/index.js.map +1 -1
  57. package/dist/ir/nodes.d.ts +54 -13
  58. package/dist/ir/nodes.js +49 -7
  59. package/dist/ir/nodes.js.map +1 -1
  60. package/dist/ir/validate.js +10 -22
  61. package/dist/ir/validate.js.map +1 -1
  62. package/dist/library/profile.d.ts +11 -0
  63. package/dist/library/profile.js +74 -2
  64. package/dist/library/profile.js.map +1 -1
  65. package/dist/wasi-paths.d.ts +5 -0
  66. package/dist/wasi-paths.js +22 -0
  67. package/dist/wasi-paths.js.map +1 -0
  68. package/package.json +2 -2
  69. package/surface-manifest.json +2 -2
@@ -1,5 +1,69 @@
1
- import { canMarshalFuncIntoIsland, CAUGHT, DYN, F64, islandCallbackRet, islandPromisePayloadTag, isRefCounted, isUnitType, MAY_THROW_LIB_FNS, moduleUsesDynInvoke, moduleUsesFetch, moduleUsesFsWatch, moduleUsesHttpServer, moduleUsesNet, moduleUsesProcessEvents, moduleUsesStream, moduleUsesTls, moduleUsesTlsCa, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, STRING, typeEquals, typeKey, VOID } from "../../ir/nodes.js";
1
+ /* IR → LLVM IR text (.ll). The LLVM backend consumes the SAME in-memory
2
+ * IrModule the C backend does (never the JSON dump — see the -0 lesson in
3
+ * the survey) and produces a textual module that rides compileC's
4
+ * program-TU seat: clang compiles .ll on the exact command line that
5
+ * compiles the .c, linking the same scr_* runtime with the same C ABI.
6
+ *
7
+ * Phase 1 was the TRIVIAL TIER: f64/bool/string locals and params, the
8
+ * scalar operator set, structured control flow, direct calls, interned
9
+ * string literals, and the console protocol. Phase 2 adds the VOLUME
10
+ * TIER: module globals and locals of every in-tier ref kind (arrays,
11
+ * record shapes, unions, function values/closures), the full array and
12
+ * string intrinsic surfaces, per-record-shape RC helpers (cycle headers
13
+ * included — the C emitter's fixpoint, ported in shapes.ts), tagged-union
14
+ * construction/narrowing/equality with interned immortal unit instances,
15
+ * capture boxes and the closure calling convention, switch/for-of
16
+ * lowering, and the non-throwing slice of the libCall table. EVERYTHING
17
+ * ELSE REFUSES loudly at the first unhandled node (LlvmUnsupportedError
18
+ * naming the kind) — this backend never guesses and never emits wrong
19
+ * code for a construct it does not model. compile() surfaces the refusal
20
+ * as diagnostic SC3001.
21
+ *
22
+ * RC ownership discipline: the frame/scope release-point machinery is
23
+ * ported from CEmitter (docs/ir.md) — every refcounted temp holds an owned
24
+ * +1 reference; varDecl/assign/return/call-argument MOVE that ownership;
25
+ * each statement releases its remaining refcounted temps when it ends;
26
+ * each scope releases the refcounted locals declared in it when it exits;
27
+ * callees own their params; return/break/continue release everything the
28
+ * jump bypasses (releaseForJump). Releases are type-directed through
29
+ * shapes.ts (the releaseCallC table's LLVM twin); frame entries can be
30
+ * SLOT-based (the entry names a pointer whose CURRENT value releases —
31
+ * conditional results like optional chains need that indirection).
32
+ *
33
+ * Exceptions (phase 4): the pending-flag unwind protocol, ported from the
34
+ * C emitter. `throw` moves its payload into the runtime's exception cell
35
+ * (scr_throw_*) and unwinds; after every call that can raise (per the
36
+ * SAME computeMayThrow analysis the C backend runs) a pending check tests
37
+ * scr_exc_pending() and unwinds — releasing frames/scopes down to the
38
+ * innermost try handler's depths and branching to its label, or releasing
39
+ * everything and returning a dummy value (never read: callers of a
40
+ * may-throw function test the flag before using the result). No
41
+ * setjmp/longjmp: longjmp would skip the emitted RC releases. try/catch
42
+ * follows emit-stmts.ts's shape exactly — a compile-time tryStack entry
43
+ * per region, the catch block taking the exception (scr_exc_take into the
44
+ * binding's snapshot box, or scr_exc_clear for the bindingless form), the
45
+ * finally body emitted once per path (normal, exception-with-stash,
46
+ * pending-return) with fresh temps each time. Catch bindings ride
47
+ * ScrCaught snapshot boxes (caughtTest/caughtNarrow/caughtCheck read
48
+ * them); TDZ reads test the box's payload slot and throw Node's
49
+ * ReferenceError. main() gains the uncaught epilogue when the entry
50
+ * function may throw.
51
+ *
52
+ * The dyn surface (phase 5): ScrDyn dyn values are in the tier — dyn.ts
53
+ * ports emit-walkers.ts's dyn slice (match/check/toDyn walkers, the
54
+ * String(unknown)/caught→dyn/keyed-read singletons, the checked-dynamic
55
+ * function boundary's thunk/box/adapter triple) and the emitter lowers
56
+ * the dyn expression kinds (dynFrom/dynCall/dynInvoke/dynTest/dynKeyGet/
57
+ * dynCheck/destructuring), the JSON.parse family, dyn record fields and
58
+ * overflow maps, dyn capture boxes, and generator unknown channels.
59
+ * The island surface (jsval/jsExit and embedded npm tables) is in the
60
+ * tier too; the module text and resolution tables use the same compressed,
61
+ * lazy-inflate representation as the C debugging backend.
62
+ */
63
+ import { deflateRawSync } from "node:zlib";
64
+ import { canMarshalFuncIntoIsland, CAUGHT, DYN, F64, ffiCallbackType, islandCallbackRet, islandPromisePayloadTag, isFfiCallbackParam, isFfiContextParam, isRefCounted, isUnitType, MAY_THROW_LIB_FNS, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, moduleUsesDynInvoke, moduleUsesFetch, moduleUsesFsWatch, moduleUsesHttpServer, moduleUsesNet, moduleUsesNodeTest, moduleUsesProcessEvents, moduleUsesStream, moduleUsesTls, moduleUsesTlsCa, NPM_COMPRESS_MIN, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, STRING, typeEquals, typeKey, VOID } from "../../ir/nodes.js";
2
65
  import { matchIntegerBytesForLoop } from "../../ir/integer-loops.js";
66
+ import { allocateFfiCallbackAdapters } from "../ffi-callbacks.js";
3
67
  import { computeMayThrow } from "../emission/may-throw.js";
4
68
  import { mangleArgPack, mangleAsyncSpawn, mangleClassNew, mangleClassObj, mangleClassRetain, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenResThunk, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordNew, mangleRecordStruct, mangleResolveThunk, mangleTrampoline, mangleVtStruct, mangleWrapper } from "../mangle.js";
5
69
  import { BlockBuilder } from "./blocks.js";
@@ -7,10 +71,41 @@ import { buildClassGraph, classFieldIndex, classStructSym, emitClassObjDefs, emi
7
71
  import { DK, LlDyn } from "./dyn.js";
8
72
  import { LlvmUnsupportedError } from "./unsupported.js";
9
73
  import { LlWalkers } from "./walkers.js";
74
+ function ffiNativeTypeLl(cls) {
75
+ switch (cls) {
76
+ case "f64":
77
+ return "double";
78
+ case "bool":
79
+ case "u8":
80
+ return "i8";
81
+ case "u32":
82
+ case "i32":
83
+ return "i32";
84
+ case "string":
85
+ case "bytes":
86
+ throw new Error(`llvm emitter bug: span class '${cls}' has no scalar LLVM type`);
87
+ case "void":
88
+ return "void";
89
+ }
90
+ }
91
+ function ffiCallbackDummyLl(callback) {
92
+ switch (callback.returns) {
93
+ case "void":
94
+ return "void";
95
+ case "f64":
96
+ return "double 0.0";
97
+ case "bool":
98
+ case "u8":
99
+ return "i8 0";
100
+ case "u32":
101
+ case "i32":
102
+ return "i32 0";
103
+ }
104
+ }
10
105
  import { arrNewCall, boxAccess, boxNewCall, computeTraced, elemAccess, emitRecordShapes, FN_ATTRS, llFieldType, mapKeyAccess, mapKeyKindNum, mapValKindNum, releaseSym, retainSym, traceAdapter, traceArg, vAdapters, } from "./shapes.js";
11
106
  export { LlvmUnsupportedError } from "./unsupported.js";
12
- export function emitLlvmModule(mod) {
13
- return new LlEmitter(mod).emit();
107
+ export function emitLlvmModule(mod, options = {}) {
108
+ return new LlEmitter(mod, options).emit();
14
109
  }
15
110
  /** Exact double literal: LLVM's 16-digit hex form round-trips every f64
16
111
  * bit pattern (−0 and the full denormal range included). */
@@ -22,8 +117,7 @@ function f64Lit(n) {
22
117
  const F64_INF = f64Lit(Infinity);
23
118
  /** LLVM c"..." payload for a UTF-8 literal, NUL-terminated like the C
24
119
  * emitter's flexible-array-member initializer. */
25
- function llStrBytes(text) {
26
- const bytes = Buffer.from(text, "utf8");
120
+ function llBytes(bytes) {
27
121
  let s = "";
28
122
  for (const b of bytes) {
29
123
  s +=
@@ -33,6 +127,9 @@ function llStrBytes(text) {
33
127
  }
34
128
  return `${s}\\00`;
35
129
  }
130
+ function llStrBytes(text) {
131
+ return llBytes(Buffer.from(text, "utf8"));
132
+ }
36
133
  /** The non-throwing libCall slice the tier claims: IrLibFn → runtime
37
134
  * symbol, called generically (LLVM arg/result types derive from the call
38
135
  * site's IR types, which is exactly the contract the C prototypes pin).
@@ -549,6 +646,9 @@ const LIB_FN_SYMS = {
549
646
  "http.resWriteHeadPairs": "scr_http_res_write_head_pairs",
550
647
  "http.resWriteHeadDyn": "scr_http_res_write_head_dyn",
551
648
  "http.serverJoinDupHeaders": "scr_http_server_join_duplicate_headers",
649
+ "http.serverTimeoutGet": "scr_net_server_timeout_get",
650
+ "http.serverTimeoutSet": "scr_net_server_timeout_set",
651
+ "http.serverTimeoutOptionSet": "scr_net_server_timeout_option_set",
552
652
  "http.clientWrite": "scr_http_client_write_str",
553
653
  "http.clientWriteBytes": "scr_http_client_write_bytes",
554
654
  "http.clientEnd": "scr_http_client_end",
@@ -771,6 +871,9 @@ const DV_SET_KIND = {
771
871
  };
772
872
  class LlEmitter {
773
873
  mod;
874
+ sizeType;
875
+ cycleColorOffset;
876
+ wasi;
774
877
  /** Interned string literals: UTF-8 text → { symbol, byte length } —
775
878
  * first-use order, the C emitter's determinism discipline. */
776
879
  literals = new Map();
@@ -811,6 +914,9 @@ class LlEmitter {
811
914
  fnByName = new Map();
812
915
  /** Manifest-bound native imports, used by ffiCall emission. */
813
916
  ffiByName = new Map();
917
+ /** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
918
+ * distinct call-scoped TLS closure slots. */
919
+ ffiCallbackAdapters;
814
920
  globalTypes = new Map();
815
921
  /** May-throw analysis (the C emitter's computeMayThrow, shared): pending
816
922
  * checks are emitted only after calls that can actually raise. */
@@ -873,7 +979,7 @@ class LlEmitter {
873
979
  jumpTargets = [];
874
980
  currentLocals = new Map();
875
981
  captureIds = new Set();
876
- /** Active canonical byte-loop induction bindings: local id → i64 slot. */
982
+ /** Active canonical byte-loop induction bindings: local id → size_t slot. */
877
983
  integerLoopBindings = new Map();
878
984
  /** Enclosing try-with-FINALLY regions, innermost last: a `return`
879
985
  * inside one runs every crossed finally (innermost first) before the
@@ -894,6 +1000,9 @@ class LlEmitter {
894
1000
  /** Return type of the function being emitted — the unwind path returns
895
1001
  * a dummy of this type (never read: callers check the flag first). */
896
1002
  currentReturnType = VOID;
1003
+ /** Active only while emitting a wasm32 async body lowered with LLVM's
1004
+ * switched-coroutine intrinsics. */
1005
+ currentWasiCoro = null;
897
1006
  /** The generator channels of the function being emitted (null outside
898
1007
  * generator bodies): yieldExpr emission reads them, and emitTryCatch's
899
1008
  * catch prologue emits the GENRET sentinel re-unwind exactly here. */
@@ -901,12 +1010,20 @@ class LlEmitter {
901
1010
  /** Active optional-chain bind slots, by chain id (chainRecv reads). */
902
1011
  chainSlots = new Map();
903
1012
  logArgSlots = 0;
904
- constructor(mod) {
1013
+ constructor(mod, options) {
905
1014
  this.mod = mod;
1015
+ this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
1016
+ this.wasi = options.wasi === true;
1017
+ // ScrCycHdr is { ptr trace; ptr free; i32 color; i16 buffered;
1018
+ // i16 gen; size_t buf_index }. The object follows it, so color is 12
1019
+ // bytes behind a wasm32 object and 16 bytes behind a 64-bit object.
1020
+ this.cycleColorOffset = options.pointerBits === 32 ? 12 : 16;
1021
+ this.ffiCallbackAdapters = allocateFfiCallbackAdapters(mod.ffiImports ?? []);
906
1022
  for (const fn of mod.functions)
907
1023
  this.fnByName.set(fn.name, fn);
908
- for (const entry of mod.ffiImports ?? [])
1024
+ for (const entry of mod.ffiImports ?? []) {
909
1025
  this.ffiByName.set(entry.name, entry);
1026
+ }
910
1027
  const mt = computeMayThrow(mod);
911
1028
  this.mayThrow = mt.fns;
912
1029
  this.indirectMayThrow = mt.indirect;
@@ -978,9 +1095,9 @@ class LlEmitter {
978
1095
  lib: rec.lib,
979
1096
  });
980
1097
  }
981
- if (mod.embedded !== undefined && mod.embedded.modules.length > 0) {
982
- throw new LlvmUnsupportedError("npmEmbedding");
983
- }
1098
+ }
1099
+ abiOffset(native64, wasm32) {
1100
+ return this.sizeType === "i32" ? wasm32 : native64;
984
1101
  }
985
1102
  // ── types ───────────────────────────────────────────────────────────────
986
1103
  llType(t) {
@@ -1037,6 +1154,93 @@ class LlEmitter {
1037
1154
  }
1038
1155
  }
1039
1156
  // ── module assembly ─────────────────────────────────────────────────────
1157
+ ffiCallbackAdapter(binding, id) {
1158
+ const adapter = this.ffiCallbackAdapters.get(`${binding}:${id}`);
1159
+ if (!adapter)
1160
+ throw new Error(`llvm emitter bug: no callback adapter for ${binding}:${id}`);
1161
+ return adapter;
1162
+ }
1163
+ /** C-callable scalar callback trampolines. A callback with an explicit
1164
+ * context entry receives the closure at that exact ABI position. A raw
1165
+ * callback loads it from a call-scoped TLS slot installed around the
1166
+ * outer native call. */
1167
+ emitFfiCallbackDefs() {
1168
+ const globals = [];
1169
+ const defs = [];
1170
+ if (this.ffiCallbackAdapters.size === 0)
1171
+ return { globals, defs };
1172
+ this.declare(`declare zeroext i1 @scr_exc_pending()`);
1173
+ this.declare(`declare void @scr_trap(ptr)`);
1174
+ const expired = this.cstr("scriptc: native callback invoked outside its call-scoped lifetime\n");
1175
+ for (const adapter of this.ffiCallbackAdapters.values()) {
1176
+ const cb = adapter.callback;
1177
+ if (adapter.tls !== null)
1178
+ globals.push(`@${adapter.tls} = internal thread_local global ptr null`);
1179
+ const params = cb.params.map((param, i) => isFfiContextParam(param) ? `ptr %ctx` : `${ffiNativeTypeLl(param)} %a${i}`);
1180
+ const ret = ffiNativeTypeLl(cb.returns);
1181
+ defs.push(`define internal ${ret} @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, `entry:`, adapter.tls === null ? ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0` : ` %cb = load ptr, ptr @${adapter.tls}`, ` %missing = icmp eq ptr %cb, null`, ` br i1 %missing, label %expired, label %ready`, `expired:`, ` call void @scr_trap(ptr ${expired})`, ` unreachable`, `ready:`, ` %pending = call zeroext i1 @scr_exc_pending()`, ` br i1 %pending, label %skip, label %invoke`, `skip:`, ` ret ${ffiCallbackDummyLl(cb)}`, `invoke:`, ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`);
1182
+ const scriptArgs = [];
1183
+ for (let i = 0; i < cb.params.length; i++) {
1184
+ const param = cb.params[i];
1185
+ if (isFfiContextParam(param))
1186
+ continue;
1187
+ switch (param) {
1188
+ case "f64":
1189
+ scriptArgs.push(`double %a${i}`);
1190
+ break;
1191
+ case "bool":
1192
+ defs.push(` %s${i} = icmp ne i8 %a${i}, 0`);
1193
+ scriptArgs.push(`i1 %s${i}`);
1194
+ break;
1195
+ case "u8":
1196
+ defs.push(` %s${i} = uitofp i8 %a${i} to double`);
1197
+ scriptArgs.push(`double %s${i}`);
1198
+ break;
1199
+ case "u32":
1200
+ defs.push(` %s${i} = uitofp i32 %a${i} to double`);
1201
+ scriptArgs.push(`double %s${i}`);
1202
+ break;
1203
+ case "i32":
1204
+ defs.push(` %s${i} = sitofp i32 %a${i} to double`);
1205
+ scriptArgs.push(`double %s${i}`);
1206
+ break;
1207
+ }
1208
+ }
1209
+ const ft = ffiCallbackType(cb);
1210
+ const internalRet = this.llType(ft.ret);
1211
+ const callArgs = [`ptr %cb`, ...scriptArgs].join(", ");
1212
+ if (cb.returns === "void") {
1213
+ defs.push(` call void %fn(${callArgs})`, ` ret void`, `}`, ``);
1214
+ continue;
1215
+ }
1216
+ defs.push(` %result = call ${internalRet} %fn(${callArgs})`);
1217
+ switch (cb.returns) {
1218
+ case "f64":
1219
+ defs.push(` ret double %result`);
1220
+ break;
1221
+ case "bool":
1222
+ defs.push(` %out = zext i1 %result to i8`, ` ret i8 %out`);
1223
+ break;
1224
+ case "u8":
1225
+ case "u32":
1226
+ this.declare(`declare double @scr_bit_ushr(double, double)`);
1227
+ defs.push(` %coerced = call double @scr_bit_ushr(double %result, double ${f64Lit(0)})`, ` %wide = fptoui double %coerced to i32`);
1228
+ if (cb.returns === "u8") {
1229
+ defs.push(` %out = trunc i32 %wide to i8`, ` ret i8 %out`);
1230
+ }
1231
+ else {
1232
+ defs.push(` ret i32 %wide`);
1233
+ }
1234
+ break;
1235
+ case "i32":
1236
+ this.declare(`declare double @scr_bit_or(double, double)`);
1237
+ defs.push(` %coerced = call double @scr_bit_or(double %result, double ${f64Lit(0)})`, ` %out = fptosi double %coerced to i32`, ` ret i32 %out`);
1238
+ break;
1239
+ }
1240
+ defs.push(`}`, ``);
1241
+ }
1242
+ return { globals, defs };
1243
+ }
1040
1244
  emit() {
1041
1245
  // Function bodies first (the literal/unit/fn-value tables fill as they
1042
1246
  // emit), then the file assembles around them — the C emitter's order.
@@ -1048,6 +1252,24 @@ class LlEmitter {
1048
1252
  const classObjDefs = emitClassObjDefs(this, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t));
1049
1253
  const wrappers = this.emitFnValueDefs();
1050
1254
  const asyncDefs = this.emitAsyncScaffolding();
1255
+ const ffiCallbacks = this.emitFfiCallbackDefs();
1256
+ const embedded = this.mod.embedded;
1257
+ const usesIsland = embedded !== undefined && embedded.modules.length > 0;
1258
+ const storeNpmText = (text) => {
1259
+ const plain = Buffer.from(text, "utf8");
1260
+ if (text.length < NPM_COMPRESS_MIN)
1261
+ return { bytes: plain, raw: 0 };
1262
+ const deflated = deflateRawSync(plain, { level: 9 });
1263
+ return deflated.length < plain.length
1264
+ ? { bytes: deflated, raw: plain.length }
1265
+ : { bytes: plain, raw: 0 };
1266
+ };
1267
+ const npmStored = usesIsland
1268
+ ? embedded.modules.map((m) => ({
1269
+ src: storeNpmText(m.source),
1270
+ esm: m.esm === undefined ? null : storeNpmText(m.esm),
1271
+ }))
1272
+ : [];
1051
1273
  // Module globals, FIRST OCCURRENCE per id: a class-expression static
1052
1274
  // instantiated through several mixin applications registers one global
1053
1275
  // id several times (2041-mixin-values) — C's tentative definitions
@@ -1107,8 +1329,20 @@ class LlEmitter {
1107
1329
  // any island entry (the engine's lazy boot consults it) — cc.ts
1108
1330
  // compiles scr_fetch.c on the same predicate.
1109
1331
  const usesFetch = moduleUsesFetch(this.mod);
1110
- const snapshotsTlsCa = moduleUsesTls(this.mod) || moduleUsesTlsCa(this.mod);
1332
+ const embedsZlib = moduleEmbedsBuiltin(this.mod, "node:zlib");
1333
+ const embedsNet = moduleEmbedsBuiltin(this.mod, "node:http") ||
1334
+ moduleEmbedsBuiltin(this.mod, "node:https") ||
1335
+ moduleEmbedsBuiltin(this.mod, "node:net") ||
1336
+ moduleEmbedsBuiltin(this.mod, "node:tls");
1337
+ const snapshotsTlsCa = moduleUsesTls(this.mod) || moduleUsesTlsCa(this.mod) ||
1338
+ moduleEmbedsBuiltin(this.mod, "node:https") ||
1339
+ moduleEmbedsBuiltin(this.mod, "node:tls");
1111
1340
  const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0;
1341
+ // The process verdict has the same precedence as the C reference
1342
+ // emitter: node:test owns the final status when present; otherwise an
1343
+ // embedded process.exitCode owns it; ordinary programs return zero.
1344
+ const usesNodeTest = moduleUsesNodeTest(this.mod);
1345
+ const programExitUsesIsland = !usesNodeTest && usesIsland;
1112
1346
  // Declared NOW — the extern block flushes before main assembles.
1113
1347
  if (usesEvents)
1114
1348
  this.declare(`declare void @scr_events_install()`);
@@ -1124,6 +1358,20 @@ class LlEmitter {
1124
1358
  this.declare(`declare void @scr_http_dyn_install()`);
1125
1359
  if (usesFetch)
1126
1360
  this.declare(`declare void @scr_fetch_install()`);
1361
+ if (embedsZlib)
1362
+ this.declare(`declare void @scr_zlib_island_install()`);
1363
+ if (embedsNet)
1364
+ this.declare(`declare void @scr_net_island_install()`);
1365
+ if (usesIsland) {
1366
+ this.declare(`declare void @scr_island_modules(ptr, ${this.sizeType}, ptr, ${this.sizeType})`);
1367
+ this.declare(`declare i32 @scr_island_exit_code()`);
1368
+ if (moduleEmbedsCompressedNpm(this.mod)) {
1369
+ this.declare(`declare void @scr_island_set_inflate(ptr)`);
1370
+ this.declare(`declare zeroext i1 @scr_zlib_inflate_exact(ptr, ${this.sizeType}, ptr, ${this.sizeType})`);
1371
+ }
1372
+ }
1373
+ if (usesNodeTest)
1374
+ this.declare(`declare i32 @scr_test_exit_code()`);
1127
1375
  if (snapshotsTlsCa) {
1128
1376
  this.declare(`declare void @scr_tls_ca_install()`);
1129
1377
  }
@@ -1145,11 +1393,12 @@ class LlEmitter {
1145
1393
  const entryMayThrow = this.mayThrow.has(this.mod.entry);
1146
1394
  // The event loop runs when timers appeared OR any async/generator
1147
1395
  // function exists (the C main's hasAsync || hasGenerators ||
1148
- // usesTimers gate; the island stays refused). Generator programs run
1396
+ // usesTimers gate). Generator programs run
1149
1397
  // the loop too: its exit accounting notes still-suspended generator
1150
1398
  // fibers as abandoned, so the RC audit downgrades exactly like the
1151
1399
  // async loop-exhaustion story.
1152
1400
  const runsLoop = this.usesTimers ||
1401
+ usesIsland ||
1153
1402
  this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
1154
1403
  const uncaughtReleases = entryMayThrow && !asyncEntry ? globalReleaseLines("gu") : [];
1155
1404
  const loopReleasesU = runsLoop ? globalReleaseLines("gl") : [];
@@ -1171,7 +1420,40 @@ class LlEmitter {
1171
1420
  this.declare(`declare i32 @scr_promise_finish_top_level(ptr)`);
1172
1421
  this.declare(`declare void @scr_promise_rethrow_top_level(ptr)`);
1173
1422
  this.declare(`declare void @scr_promise_release(ptr)`);
1423
+ this.declare(`declare void @scr_exit_code_note(i32)`);
1424
+ if (programExitUsesIsland && usesEvents && hasRefGlobals) {
1425
+ this.declare(`declare ${this.sizeType} @scr_island_exit_code_version()`);
1426
+ }
1174
1427
  }
1428
+ const topPendingExitLines = () => {
1429
+ if (!asyncEntry)
1430
+ return [];
1431
+ const lines = [];
1432
+ if (usesNodeTest) {
1433
+ lines.push(` %tla_program_exit = call i32 @scr_test_exit_code()`);
1434
+ }
1435
+ else if (usesIsland) {
1436
+ lines.push(` %tla_program_exit = call i32 @scr_island_exit_code()`);
1437
+ }
1438
+ if (usesNodeTest || usesIsland) {
1439
+ lines.push(` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_status, i32 %tla_program_exit`);
1440
+ }
1441
+ const exitStatus = usesNodeTest || usesIsland ? "%tla_exit_status" : "%tla_status";
1442
+ const tracksIslandExit = programExitUsesIsland && usesEvents && hasRefGlobals;
1443
+ if (tracksIslandExit) {
1444
+ lines.push(` %tla_exit_version = call ${this.sizeType} @scr_island_exit_code_version()`);
1445
+ }
1446
+ // finish_top_level initially notes 13. Replace that hint before exit
1447
+ // listeners run when a higher-priority verdict was already selected.
1448
+ lines.push(` call void @scr_exit_code_note(i32 ${exitStatus})`);
1449
+ lines.push(...exitListenerLines("xp"));
1450
+ if (tracksIslandExit) {
1451
+ lines.push(` %tla_exit_version_after = call ${this.sizeType} @scr_island_exit_code_version()`, ` %tla_exit_changed = icmp ne ${this.sizeType} %tla_exit_version_after, %tla_exit_version`, ` br i1 %tla_exit_changed, label %tla_exit_updated, label %tla_exit_unchanged`, `tla_exit_updated:`, ` %tla_listener_exit = call i32 @scr_island_exit_code()`, ` call void @scr_exit_code_note(i32 %tla_listener_exit)`, ` br label %tla_exit_done`, `tla_exit_unchanged:`, ` br label %tla_exit_done`, `tla_exit_done:`, ` %tla_final_exit = phi i32 [ %tla_listener_exit, %tla_exit_updated ], [ ${exitStatus}, %tla_exit_unchanged ]`);
1452
+ }
1453
+ lines.push(...topPendingReleases);
1454
+ lines.push(` ret i32 ${tracksIslandExit ? "%tla_final_exit" : exitStatus}`);
1455
+ return lines;
1456
+ };
1175
1457
  // LIBRARY mode: the runtime entry points the generated library
1176
1458
  // symbols delegate to — declared before the extern block flushes.
1177
1459
  if (this.mod.lib !== undefined) {
@@ -1182,10 +1464,10 @@ class LlEmitter {
1182
1464
  this.declare(`declare void @scr_library_arena_reset()`);
1183
1465
  this.declare(`declare void @scr_library_collect()`);
1184
1466
  if (this.mod.lib.exports.some((e) => e.params.includes("string"))) {
1185
- this.declare(`declare ptr @scr_library_str_in(ptr, i64)`);
1467
+ this.declare(`declare ptr @scr_library_str_in(ptr, ${this.sizeType})`);
1186
1468
  }
1187
1469
  if (this.mod.lib.exports.some((e) => e.params.includes("bytes"))) {
1188
- this.declare(`declare ptr @scr_library_bytes_in(ptr, i64, ptr)`);
1470
+ this.declare(`declare ptr @scr_library_bytes_in(ptr, ${this.sizeType}, ptr)`);
1189
1471
  }
1190
1472
  if (this.mod.lib.exports.some((e) => e.params.includes("i64"))) {
1191
1473
  this.declare(`declare double @scr_library_i64_in(i64, ptr)`);
@@ -1212,59 +1494,69 @@ class LlEmitter {
1212
1494
  // the C layout (4 bytes padding after the tag). ScrUnion/ScrClosure
1213
1495
  // mirror scr_runtime.h field-for-field (tag reads, slot peeks, the
1214
1496
  // fn pointer, and the caps[] tail all address through them).
1215
- `%ScrStr = type { i64, i64, i64 }`,
1497
+ `%ScrStr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType} }`,
1216
1498
  `%ScrLogArg = type { i32, i64 }`,
1217
- `%ScrVt = type { i64, i64, ptr }`,
1218
- `%ScrUnion = type { i64, i32, ptr, ptr, ptr, i64 }`,
1219
- `%ScrClosure = type { i64, ptr, i64, ptr }`,
1220
- `%ScrRegex = type { i64, ptr, ptr, ptr }`,
1499
+ `%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
1500
+ `%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
1501
+ `%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr }`,
1502
+ `%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr }`,
1221
1503
  // ScrArr mirror { rc, len, cap, elem(i32+pad), elem_retain,
1222
1504
  // elem_release, elem_trace, data } — the immortal tagged-template
1223
1505
  // strings objects lay out through it (nothing GEPs into live heap
1224
1506
  // arrays; those stay behind the runtime's own entry points).
1225
- `%ScrArr = type { i64, i64, i64, i32, ptr, ptr, ptr, ptr }`,
1507
+ `%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, ptr, ptr }`,
1226
1508
  // The runtime error prefix { rc, vt, name, message, code } and the
1227
1509
  // class-object shape { rc, pre, post, ctor, name } — field reads on
1228
1510
  // builtin errors and classval loads GEP through these.
1229
- `%ScrError = type { i64, ptr, ptr, ptr, ptr }`,
1230
- `%ScrClassObj = type { i64, i64, i64, ptr, ptr }`,
1511
+ `%ScrError = type { ${this.sizeType}, ptr, ptr, ptr, ptr }`,
1512
+ `%ScrClassObj = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
1231
1513
  // The runtime emitter prefix { rc, vt, reg, cls } — user subclasses
1232
1514
  // embed it (classes.ts), and bare-emitter GEPs address through it.
1233
- `%ScrEmitter = type { i64, ptr, ptr, ptr }`,
1515
+ `%ScrEmitter = type { ${this.sizeType}, ptr, ptr, ptr }`,
1234
1516
  // The runtime stream layout { rc, vt, reg, cls, st } — one struct
1235
1517
  // for all five stream classes; stream subclasses embed it.
1236
- `%ScrStream = type { i64, ptr, ptr, ptr, ptr }`,
1518
+ `%ScrStream = type { ${this.sizeType}, ptr, ptr, ptr, ptr }`,
1237
1519
  // The catch-binding snapshot box { rc, kind, f64, b, payload,
1238
1520
  // retain_fn, release_fn, trace_fn } — caughtTest kind reads and
1239
1521
  // caughtNarrow payload extraction GEP through it (offsets match
1240
1522
  // scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24,
1241
1523
  // payload at 32).
1242
- `%ScrCaught = type { i64, i32, double, i8, ptr, ptr, ptr, ptr }`,
1524
+ `%ScrCaught = type { ${this.sizeType}, i32, double, i8, ptr, ptr, ptr, ptr }`,
1243
1525
  // ScrBytes { rc, len, elem(i32+pad), data, backing }. Indexed
1244
1526
  // typed-array access GEPs through this directly: the IR type already
1245
1527
  // fixes elem, so the hot path needs neither a runtime kind load nor
1246
1528
  // the generic scr_bytes_get/set call.
1247
- `%ScrBytes = type { i64, i64, i32, ptr, ptr }`,
1529
+ `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr }`,
1248
1530
  // The capture box { rc, kind, obj_retain, obj_release, obj_trace,
1249
1531
  // slot } — TDZ reads peek the payload slot (offset 40) directly.
1250
- `%ScrBox = type { i64, i32, ptr, ptr, ptr, i64 }`,
1532
+ `%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
1251
1533
  // The stack buffer of the emitted JSON serializers { data, len, cap }.
1252
- `%ScrJsonBuf = type { ptr, i64, i64, ptr, i64, i64 }`,
1534
+ `%ScrJsonBuf = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ${this.sizeType} }`,
1253
1535
  // The dynCheck error-path spine { parent, key, index } — the emitted
1254
1536
  // builders stack-allocate one per recursion level (dyn.ts).
1255
- `%ScrDynPath = type { ptr, ptr, i64 }`,
1537
+ `%ScrDynPath = type { ptr, ptr, ${this.sizeType} }`,
1538
+ `%ScrIslandModule = type { ptr, ptr, ${this.sizeType}, ${this.sizeType}, i32, ptr, ${this.sizeType}, ${this.sizeType} }`,
1539
+ `%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`,
1256
1540
  ];
1257
1541
  out.push(...shapes.typeDefs);
1258
1542
  out.push(...classShapes.typeDefs);
1259
- out.push(``, `@scr_error_vts = external global [5 x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`);
1543
+ // Thread-instanced library state (abi.instance_per_thread): the
1544
+ // program TU's mutable globals — module globals, run-once guards, the
1545
+ // lazily-compiled regex literal caches — and the runtime globals its
1546
+ // init stamps live in thread-local storage, matching the runtime
1547
+ // objects compiled with -DSCR_THREAD_INSTANCES. Immutable interned
1548
+ // data (string literals, unit arms, template arrays, vtables) stays
1549
+ // shared.
1550
+ const tl = this.mod.lib?.threadInstances === true ? "thread_local " : "";
1551
+ out.push(``, `@scr_error_vts = external ${tl}global [5 x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`);
1260
1552
  for (const d of this.decls)
1261
1553
  out.push(d);
1262
1554
  out.push(``);
1263
1555
  for (const [text, lit] of this.literals) {
1264
1556
  // Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } —
1265
1557
  // the C emitter's static table, retain/release skip rc == SIZE_MAX.
1266
- out.push(`@${lit.sym} = internal global { i64, i64, i64, [${lit.len + 1} x i8] } ` +
1267
- `{ i64 -1, i64 ${lit.len}, i64 ${lit.len}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
1558
+ out.push(`@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` +
1559
+ `{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${lit.len}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
1268
1560
  }
1269
1561
  if (this.literals.size > 0)
1270
1562
  out.push(``);
@@ -1273,7 +1565,7 @@ class LlEmitter {
1273
1565
  // slot and RC entry points zero — retain/release/collector all skip
1274
1566
  // rc == SIZE_MAX, so these never join the RC audit or a trace walk.
1275
1567
  const [unionId, tag] = key.split(":");
1276
- out.push(`@${sym} = internal global %ScrUnion { i64 -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 0 } ; ${unionId} unit arm`);
1568
+ out.push(`@${sym} = internal global %ScrUnion { ${this.sizeType} -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 0 } ; ${unionId} unit arm`);
1277
1569
  }
1278
1570
  if (this.unitInstances.size > 0)
1279
1571
  out.push(``);
@@ -1281,7 +1573,7 @@ class LlEmitter {
1281
1573
  // One immortal ScrRegex per (pattern, flags) literal, pointing at
1282
1574
  // the interned source/flags strings. The bc slot starts null (lazy
1283
1575
  // compile, cached by the runtime) — a mutable global, not constant.
1284
- out.push(`@${re.sym} = internal global %ScrRegex { i64 -1, ptr ${re.src}, ptr ${re.fl}, ptr null } ; ${key.replace(/\n/g, "\\n")}`);
1576
+ out.push(`@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null } ; ${key.replace(/\n/g, "\\n")}`);
1285
1577
  }
1286
1578
  if (this.regexInstances.size > 0)
1287
1579
  out.push(``);
@@ -1291,7 +1583,7 @@ class LlEmitter {
1291
1583
  // over it (rc == SIZE_MAX, len == cap, SCR_ELEM_STR = 2, no REF
1292
1584
  // entry points). Reads retain immortal strings — a no-op.
1293
1585
  const n = inst.slots.length;
1294
- out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym} = internal global %ScrArr { i64 -1, i64 ${n}, i64 ${n}, i32 2, ptr null, ptr null, ptr null, ptr @${inst.sym}_data }`);
1586
+ out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i32 2, ptr null, ptr null, ptr null, ptr @${inst.sym}_data }`);
1295
1587
  }
1296
1588
  if (this.templateStringsInstances.size > 0)
1297
1589
  out.push(``);
@@ -1302,14 +1594,52 @@ class LlEmitter {
1302
1594
  }
1303
1595
  if (this.cstrs.size > 0)
1304
1596
  out.push(``);
1597
+ if (usesIsland) {
1598
+ const fmt = { esm: 0, cjs: 1, json: 2 };
1599
+ const edgeKind = { any: 0, import: 1, require: 2 };
1600
+ embedded.modules.forEach((m, i) => {
1601
+ const stored = npmStored[i];
1602
+ const key = Buffer.from(m.key, "utf8");
1603
+ out.push(`@sc_npm_key_${i} = internal constant [${key.length + 1} x i8] c"${llBytes(key)}"`, `@sc_npm_src_${i} = internal constant [${stored.src.bytes.length + 1} x i8] c"${llBytes(stored.src.bytes)}"`);
1604
+ if (stored.esm !== null) {
1605
+ out.push(`@sc_npm_esm_${i} = internal constant [${stored.esm.bytes.length + 1} x i8] c"${llBytes(stored.esm.bytes)}"`);
1606
+ }
1607
+ });
1608
+ const moduleRows = embedded.modules.map((m, i) => {
1609
+ const stored = npmStored[i];
1610
+ const esm = stored.esm === null
1611
+ ? `ptr null, ${this.sizeType} 0, ${this.sizeType} 0`
1612
+ : `ptr @sc_npm_esm_${i}, ${this.sizeType} ${stored.esm.bytes.length}, ${this.sizeType} ${stored.esm.raw}`;
1613
+ return `%ScrIslandModule { ptr @sc_npm_key_${i}, ptr @sc_npm_src_${i}, ` +
1614
+ `${this.sizeType} ${stored.src.bytes.length}, ${this.sizeType} ${stored.src.raw}, ` +
1615
+ `i32 ${fmt[m.format]}, ${esm} }`;
1616
+ });
1617
+ out.push(`@sc_npm_modules = internal constant [${moduleRows.length} x %ScrIslandModule] [ ${moduleRows.join(", ")} ]`);
1618
+ embedded.edges.forEach((edge, i) => {
1619
+ for (const [part, text] of [["from", edge.from], ["spec", edge.specifier], ["to", edge.to]]) {
1620
+ const bytes = Buffer.from(text, "utf8");
1621
+ out.push(`@sc_npm_edge_${i}_${part} = internal constant [${bytes.length + 1} x i8] c"${llBytes(bytes)}"`);
1622
+ }
1623
+ });
1624
+ if (embedded.edges.length > 0) {
1625
+ const edgeRows = embedded.edges.map((edge, i) => `%ScrIslandEdge { ptr @sc_npm_edge_${i}_from, ptr @sc_npm_edge_${i}_spec, ` +
1626
+ `ptr @sc_npm_edge_${i}_to, i32 ${edgeKind[edge.kind]} }`);
1627
+ out.push(`@sc_npm_edges = internal constant [${edgeRows.length} x %ScrIslandEdge] [ ${edgeRows.join(", ")} ]`);
1628
+ }
1629
+ out.push(``);
1630
+ }
1631
+ out.push(...ffiCallbacks.globals);
1632
+ if (ffiCallbacks.globals.length > 0)
1633
+ out.push(``);
1305
1634
  for (const g of globals) {
1306
1635
  const ty = this.llType(g.type);
1307
1636
  const zero = ty === "double" ? f64Lit(0) : ty === "ptr" ? "null" : "false";
1308
- out.push(`@${mangleGlobal(g.id)} = internal global ${ty} ${zero} ; ${g.name}`);
1637
+ out.push(`@${mangleGlobal(g.id)} = internal ${tl}global ${ty} ${zero} ; ${g.name}`);
1309
1638
  }
1310
1639
  if (globals.length > 0)
1311
1640
  out.push(``);
1312
1641
  out.push(...helpers);
1642
+ out.push(...ffiCallbacks.defs);
1313
1643
  out.push(...shapes.defs);
1314
1644
  out.push(...classShapes.defs);
1315
1645
  out.push(...classObjDefs);
@@ -1327,22 +1657,22 @@ class LlEmitter {
1327
1657
  const stamps = [];
1328
1658
  for (const iv of this.errorIntervals) {
1329
1659
  for (const [field, value] of [[0, iv.pre], [1, iv.post]]) {
1330
- stamps.push(` store i64 ${value}, ptr getelementptr inbounds ([5 x %ScrVt], ptr @scr_error_vts, i64 0, i64 ${iv.kind}, i32 ${field})${field === 1 ? ` ; ${iv.lib}` : ""}`);
1660
+ stamps.push(` store ${this.sizeType} ${value}, ptr getelementptr inbounds ([5 x %ScrVt], ptr @scr_error_vts, i64 0, i64 ${iv.kind}, i32 ${field})${field === 1 ? ` ; ${iv.lib}` : ""}`);
1331
1661
  }
1332
1662
  }
1333
1663
  if (this.emitterInterval !== null) {
1334
1664
  // The runtime emitter vtable's interval (emitterVtStampLines): bare
1335
1665
  // EventEmitter instances answer instanceof and dispatch dynamic
1336
1666
  // teardown under THIS module's preorder numbering.
1337
- out.push(`@scr_emitter_vt = external global %ScrVt`, ``);
1338
- stamps.push(` store i64 ${this.emitterInterval.pre}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 0)`, ` store i64 ${this.emitterInterval.post}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 1) ; EventEmitter`);
1667
+ out.push(`@scr_emitter_vt = external ${tl}global %ScrVt`, ``);
1668
+ stamps.push(` store ${this.sizeType} ${this.emitterInterval.pre}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 0)`, ` store ${this.sizeType} ${this.emitterInterval.post}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 1) ; EventEmitter`);
1339
1669
  }
1340
1670
  for (const iv of this.streamIntervals) {
1341
1671
  // The runtime stream vtables' intervals (streamVtStampLines) — the
1342
1672
  // emitter story: instanceof and dynamic teardown dispatch through
1343
1673
  // them.
1344
1674
  out.push(`@${iv.vt} = external global %ScrVt`);
1345
- stamps.push(` store i64 ${iv.pre}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 0)`, ` store i64 ${iv.post}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 1) ; ${iv.lib}`);
1675
+ stamps.push(` store ${this.sizeType} ${iv.pre}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 0)`, ` store ${this.sizeType} ${iv.post}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 1) ; ${iv.lib}`);
1346
1676
  }
1347
1677
  if (this.streamIntervals.length > 0)
1348
1678
  out.push(``);
@@ -1366,17 +1696,28 @@ class LlEmitter {
1366
1696
  // LIBRARY mode: no @main — the profile-declared external
1367
1697
  // symbols instead, from the same IR facts the C emission consumes.
1368
1698
  out.push(...this.emitLibDefs(globals, globalReleaseLines, stamps));
1369
- out.push(`attributes #0 = { sanitize_address }`, ``);
1699
+ out.push(`attributes #0 = { sanitize_address }`);
1700
+ if (this.wasi)
1701
+ out.push(`attributes #1 = { sanitize_address presplitcoroutine }`);
1702
+ out.push(``);
1370
1703
  return out.join("\n");
1371
1704
  }
1372
- out.push(`define i32 @main(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ` call void @scr_init()`, ...stamps,
1705
+ out.push(`define i32 @${this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ` call void @scr_init()`, ...stamps,
1373
1706
  // Event-surface programs (signal/exit listeners) fill the loop's
1374
1707
  // nullable event hooks before %main — scr_events.c links only when
1375
1708
  // this line is emitted (cc.ts gates on the same predicate).
1376
1709
  ...(usesEvents ? [` call void @scr_events_install()`] : []),
1377
1710
  // fs.watch programs fill the loop's watch hooks the same way —
1378
1711
  // scr_watch.c links only when this line is emitted.
1379
- ...(usesFsWatch ? [` call void @scr_watch_install()`] : []), ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), ...(usesFetch ? [` call void @scr_fetch_install()`] : []), ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), ...(usesStream ? [` call void @scr_stream_install()`] : []), ` call void @scr_lib_init(i32 %argc, ptr %argv)`, ...(asyncEntry
1712
+ ...(usesFsWatch ? [` call void @scr_watch_install()`] : []), ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), ...(usesFetch ? [` call void @scr_fetch_install()`] : []), ...(embedsZlib ? [` call void @scr_zlib_island_install()`] : []), ...(embedsNet ? [` call void @scr_net_island_install()`] : []), ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), ...(usesStream ? [` call void @scr_stream_install()`] : []), ` call void @scr_lib_init(i32 %argc, ptr %argv)`, ...(usesIsland
1713
+ ? [
1714
+ ...(moduleEmbedsCompressedNpm(this.mod)
1715
+ ? [` call void @scr_island_set_inflate(ptr @scr_zlib_inflate_exact)`]
1716
+ : []),
1717
+ ` call void @scr_island_modules(ptr @sc_npm_modules, ${this.sizeType} ${embedded.modules.length}, ` +
1718
+ `ptr ${embedded.edges.length > 0 ? "@sc_npm_edges" : "null"}, ${this.sizeType} ${embedded.edges.length})`,
1719
+ ]
1720
+ : []), ...(asyncEntry
1380
1721
  ? [` %top = call ptr @${mangleAsyncSpawn(this.mod.entry)}()`]
1381
1722
  : [` call void @${mangleFunction(this.mod.entry)}()`]),
1382
1723
  // Uncaught exception from top-level code: Node exits 1.
@@ -1447,18 +1788,20 @@ class LlEmitter {
1447
1788
  ` %tla_pending = icmp eq i32 %tla_status, 13`,
1448
1789
  ` br i1 %tla_pending, label %tla_stuck, label %tla_ok`,
1449
1790
  `tla_stuck:`,
1450
- ...exitListenerLines("xp"),
1451
- ...topPendingReleases,
1452
- ` ret i32 13`,
1791
+ ...topPendingExitLines(),
1453
1792
  `tla_ok:`,
1454
1793
  ]
1455
1794
  : []),
1456
1795
  ]
1457
- : []), ...exitListenerLines("xn"), ...globalReleases, ` ret i32 0`, `}`, ``,
1796
+ : []), ...exitListenerLines("xn"), ...globalReleases, ...(usesNodeTest
1797
+ ? [` %test_exit = call i32 @scr_test_exit_code()`, ` ret i32 %test_exit`]
1798
+ : usesIsland
1799
+ ? [` %island_exit = call i32 @scr_island_exit_code()`, ` ret i32 %island_exit`]
1800
+ : [` ret i32 0`]), `}`, ``,
1458
1801
  // sanitize_address is inert under the plain pipeline; the sanitized
1459
1802
  // lane's -fsanitize=address link activates instrumentation over the
1460
1803
  // emitted functions too (the runtime TUs get theirs from clang).
1461
- `attributes #0 = { sanitize_address }`, ``);
1804
+ `attributes #0 = { sanitize_address }`, ...(this.wasi ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ``);
1462
1805
  return out.join("\n");
1463
1806
  }
1464
1807
  /** LIBRARY mode: the profile-declared external definitions — the
@@ -1510,7 +1853,7 @@ class LlEmitter {
1510
1853
  });
1511
1854
  out.push(ovlCells.length === 0
1512
1855
  ? `@scr_library_trap_overlays = constant [1 x ptr] zeroinitializer`
1513
- : `@scr_library_trap_overlays = constant [${ovlCells.length} x ptr] [${ovlCells.join(", ")}]`, `@scr_library_trap_overlays_len = constant i64 ${lib.trapOverlays.length}`, ``);
1856
+ : `@scr_library_trap_overlays = constant [${ovlCells.length} x ptr] [${ovlCells.join(", ")}]`, `@scr_library_trap_overlays_len = constant ${this.sizeType} ${lib.trapOverlays.length}`, ``);
1514
1857
  // The init entry: full deterministic reset-and-reevaluate. Program
1515
1858
  // globals release and zero first (run-once guards included), then the
1516
1859
  // runtime session reset, the error-vt interval stamps verbatim from
@@ -1596,13 +1939,13 @@ class LlEmitter {
1596
1939
  args.push(`double %c${i}`);
1597
1940
  break;
1598
1941
  case "string":
1599
- params.push(`ptr %a${i}_ptr`, `i64 %a${i}_len`);
1600
- body.push(` %c${i} = call ptr @scr_library_str_in(ptr %a${i}_ptr, i64 %a${i}_len)`);
1942
+ params.push(`ptr %a${i}_ptr`, `${this.sizeType} %a${i}_len`);
1943
+ body.push(` %c${i} = call ptr @scr_library_str_in(ptr %a${i}_ptr, ${this.sizeType} %a${i}_len)`);
1601
1944
  args.push(`ptr %c${i}`);
1602
1945
  break;
1603
1946
  case "bytes":
1604
- params.push(`ptr %a${i}_ptr`, `i64 %a${i}_len`);
1605
- body.push(` %c${i} = call ptr @scr_library_bytes_in(ptr %a${i}_ptr, i64 %a${i}_len, ptr @sc_lib_bytes_trap_${e.symbol})`);
1947
+ params.push(`ptr %a${i}_ptr`, `${this.sizeType} %a${i}_len`);
1948
+ body.push(` %c${i} = call ptr @scr_library_bytes_in(ptr %a${i}_ptr, ${this.sizeType} %a${i}_len, ptr @sc_lib_bytes_trap_${e.symbol})`);
1606
1949
  args.push(`ptr %c${i}`);
1607
1950
  break;
1608
1951
  }
@@ -1676,7 +2019,7 @@ class LlEmitter {
1676
2019
  // scr_box_retain is a static inline (increment-unless-immortal, then
1677
2020
  // mark the cycle header live — every box is collector-headered);
1678
2021
  // emitted once with internal linkage, like the record retains.
1679
- defs.push(`define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, `entry:`, ` %rc = load i64, ptr %b`, ` %imm = icmp eq i64 %rc, -1`, ` br i1 %imm, label %done, label %inc`, `inc:`, ` %n = add i64 %rc, 1`, ` store i64 %n, ptr %b`, ` %colorp = getelementptr i8, ptr %b, i64 -16`, ` store i32 0, ptr %colorp ; mark live`, ` br label %done`, `done:`, ` ret ptr %b`, `}`, ``);
2022
+ defs.push(`define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, `entry:`, ` %rc = load ${this.sizeType}, ptr %b`, ` %imm = icmp eq ${this.sizeType} %rc, -1`, ` br i1 %imm, label %done, label %inc`, `inc:`, ` %n = add ${this.sizeType} %rc, 1`, ` store ${this.sizeType} %n, ptr %b`, ` %colorp = getelementptr i8, ptr %b, ${this.sizeType} -${this.cycleColorOffset}`, ` store i32 0, ptr %colorp ; mark live`, ` br label %done`, `done:`, ` ret ptr %b`, `}`, ``);
1680
2023
  }
1681
2024
  // The declarations these helpers added must land in the extern block,
1682
2025
  // which already flushed — append here instead (LLVM is order-free).
@@ -1696,7 +2039,7 @@ class LlEmitter {
1696
2039
  // never the inner return type.
1697
2040
  const ret = fn.async === true || fn.generator !== undefined ? "ptr" : this.llType(fn.returnType);
1698
2041
  const call = `call ${ret} @${this.callTarget(name)}(${args})`;
1699
- out.push(`define internal ${ret} @${mangleWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value`, `entry:`, ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, `@${mangleFnClosure(name)} = internal global %ScrClosure { i64 -1, ptr @${mangleWrapper(name)}, i64 0, ptr null }`, ``);
2042
+ out.push(`define internal ${ret} @${mangleWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value`, `entry:`, ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, `@${mangleFnClosure(name)} = internal global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null }`, ``);
1700
2043
  }
1701
2044
  return out;
1702
2045
  }
@@ -1709,6 +2052,11 @@ class LlEmitter {
1709
2052
  * fiber eagerly to its first suspension and returns the promise). */
1710
2053
  emitAsyncScaffolding() {
1711
2054
  const out = [];
2055
+ if (this.wasi) {
2056
+ this.declare(`declare void @llvm.coro.resume(ptr)`);
2057
+ this.declare(`declare void @llvm.coro.destroy(ptr)`);
2058
+ out.push(`define void @scr_wasi_coro_resume(ptr %handle) ${FN_ATTRS} {`, `entry:`, ` call void @llvm.coro.resume(ptr %handle)`, ` ret void`, `}`, `define void @scr_wasi_coro_destroy(ptr %handle) ${FN_ATTRS} {`, `entry:`, ` call void @llvm.coro.destroy(ptr %handle)`, ` ret void`, `}`, ``);
2059
+ }
1712
2060
  for (const fn of this.mod.functions) {
1713
2061
  if (fn.async !== true)
1714
2062
  continue;
@@ -1716,9 +2064,9 @@ class LlEmitter {
1716
2064
  const lifted = fn.captures !== undefined;
1717
2065
  const fieldTys = [...(lifted ? ["ptr"] : []), ...fn.params.map((p) => this.llType(p.type))];
1718
2066
  out.push(`%${pack} = type { ${fieldTys.join(", ") || "i8"} } ; ${fn.name} args`);
1719
- const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to i64)`;
2067
+ const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`;
1720
2068
  this.declare(`declare void @free(ptr)`);
1721
- this.declare(`declare ptr @malloc(i64)`);
2069
+ this.declare(`declare ptr @malloc(${this.sizeType})`);
1722
2070
  this.declare(`declare zeroext i1 @scr_exc_pending()`);
1723
2071
  this.declare(`declare ptr @scr_fiber_promise(ptr)`);
1724
2072
  this.declare(`declare ptr @scr_async_spawn(ptr, ptr)`);
@@ -1738,41 +2086,49 @@ class LlEmitter {
1738
2086
  const retTy = this.llType(ret);
1739
2087
  const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${loads.join(", ")})`;
1740
2088
  tr.push(retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`);
1741
- if (lifted) {
1742
- this.declare(`declare void @scr_closure_release(ptr)`);
1743
- tr.push(` call void @scr_closure_release(ptr %a0)`);
2089
+ if (this.wasi) {
2090
+ // The coroutine body settles its own promise and owns the retained
2091
+ // lifted environment until final suspension. Its initial call
2092
+ // returns here at the first suspend (or final suspend).
2093
+ tr.push(` ret void`, `}`, ``);
1744
2094
  }
1745
- tr.push(` %pend = call zeroext i1 @scr_exc_pending()`, ` br i1 %pend, label %thrown, label %clean`, `clean:`, ` %pr = call ptr @scr_fiber_promise(ptr %self)`);
1746
- switch (ret.kind) {
1747
- case "void":
1748
- this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
1749
- tr.push(` call void @scr_promise_fulfill_void(ptr %pr)`);
1750
- break;
1751
- case "f64":
1752
- case "date":
1753
- this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
1754
- tr.push(` call void @scr_promise_fulfill_f64(ptr %pr, double %r)`);
1755
- break;
1756
- case "bool":
1757
- this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
1758
- tr.push(` call void @scr_promise_fulfill_bool(ptr %pr, i1 %r)`);
1759
- break;
1760
- case "string":
1761
- this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
1762
- tr.push(` call void @scr_promise_fulfill_str(ptr %pr, ptr %r) ; moves in`);
1763
- break;
1764
- default: {
1765
- const v = vAdapters(this, ret);
1766
- this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
1767
- tr.push(` call void @scr_promise_fulfill_ref(ptr %pr, ptr %r, ptr ${v.retain}, ptr ${v.release}, ptr ${traceArg(this, ret)})`);
2095
+ else {
2096
+ if (lifted) {
2097
+ this.declare(`declare void @scr_closure_release(ptr)`);
2098
+ tr.push(` call void @scr_closure_release(ptr %a0)`);
1768
2099
  }
2100
+ tr.push(` %pend = call zeroext i1 @scr_exc_pending()`, ` br i1 %pend, label %thrown, label %clean`, `clean:`, ` %pr = call ptr @scr_fiber_promise(ptr %self)`);
2101
+ switch (ret.kind) {
2102
+ case "void":
2103
+ this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
2104
+ tr.push(` call void @scr_promise_fulfill_void(ptr %pr)`);
2105
+ break;
2106
+ case "f64":
2107
+ case "date":
2108
+ this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
2109
+ tr.push(` call void @scr_promise_fulfill_f64(ptr %pr, double %r)`);
2110
+ break;
2111
+ case "bool":
2112
+ this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
2113
+ tr.push(` call void @scr_promise_fulfill_bool(ptr %pr, i1 %r)`);
2114
+ break;
2115
+ case "string":
2116
+ this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
2117
+ tr.push(` call void @scr_promise_fulfill_str(ptr %pr, ptr %r) ; moves in`);
2118
+ break;
2119
+ default: {
2120
+ const v = vAdapters(this, ret);
2121
+ this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
2122
+ tr.push(` call void @scr_promise_fulfill_ref(ptr %pr, ptr %r, ptr ${v.retain}, ptr ${v.release}, ptr ${traceArg(this, ret)})`);
2123
+ }
2124
+ }
2125
+ tr.push(` ret void`, `thrown:`);
2126
+ if (ret.kind !== "void" && isRefCounted(ret)) {
2127
+ // An escaping throw means %r is the never-read dummy (NULL).
2128
+ tr.push(` call void ${releaseSym(this, ret)}(ptr %r)`);
2129
+ }
2130
+ tr.push(` ret void`, `}`, ``);
1769
2131
  }
1770
- tr.push(` ret void`, `thrown:`);
1771
- if (ret.kind !== "void" && isRefCounted(ret)) {
1772
- // An escaping throw means %r is the never-read dummy (NULL).
1773
- tr.push(` call void ${releaseSym(this, ret)}(ptr %r)`);
1774
- }
1775
- tr.push(` ret void`, `}`, ``);
1776
2132
  out.push(...tr);
1777
2133
  // Spawn wrapper: pack the args (+1 moves in), spawn the fiber.
1778
2134
  const params = fieldTys.map((ty, i) => `${ty} %a${i}`);
@@ -1799,7 +2155,7 @@ class LlEmitter {
1799
2155
  `cache_miss:`,
1800
2156
  ]
1801
2157
  : []),
1802
- ` %ap = call ptr @malloc(i64 ${sizeOf})`,
2158
+ ` %ap = call ptr @malloc(${this.sizeType} ${sizeOf})`,
1803
2159
  ` %isnull = icmp eq ptr %ap, null`,
1804
2160
  ` br i1 %isnull, label %oom, label %ok`,
1805
2161
  `oom:`,
@@ -1867,9 +2223,9 @@ class LlEmitter {
1867
2223
  const lifted = fn.captures !== undefined;
1868
2224
  const fieldTys = [...(lifted ? ["ptr"] : []), ...fn.params.map((p) => this.llType(p.type))];
1869
2225
  out.push(`%${pack} = type { ${fieldTys.join(", ") || "i8"} } ; ${fn.name} args`);
1870
- const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to i64)`;
2226
+ const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`;
1871
2227
  this.declare(`declare void @free(ptr)`);
1872
- this.declare(`declare ptr @malloc(i64)`);
2228
+ this.declare(`declare ptr @malloc(${this.sizeType})`);
1873
2229
  this.declare(`declare zeroext i1 @scr_exc_pending()`);
1874
2230
  this.declare(`declare zeroext i1 @scr_exc_genret_pending()`);
1875
2231
  this.declare(`declare void @scr_exc_clear()`);
@@ -1891,7 +2247,7 @@ class LlEmitter {
1891
2247
  const retTy = this.llType(ret);
1892
2248
  const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${loads.join(", ")})`;
1893
2249
  tr.push(retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`);
1894
- if (lifted) {
2250
+ if (lifted && !this.wasi) {
1895
2251
  this.declare(`declare void @scr_closure_release(ptr)`);
1896
2252
  tr.push(` call void @scr_closure_release(ptr %a0)`);
1897
2253
  }
@@ -1899,31 +2255,36 @@ class LlEmitter {
1899
2255
  // with the NONE slot — JS's undefined done-value. A GENRET unwind
1900
2256
  // consumes the sentinel and promotes the parked .return value; a
1901
2257
  // real exception stays pending (the consumer-side resume moves it).
1902
- tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`, ` %pend = call zeroext i1 @scr_exc_pending()`, ` br i1 %pend, label %thrown, label %clean`, `clean:`);
1903
- switch (ret.kind) {
1904
- case "void":
1905
- tr.push(` br label %done ; void body: the done value is undefined (NONE)`);
1906
- break;
1907
- case "f64":
1908
- case "date":
1909
- this.declare(`declare void @scr_gen_out_f64(ptr, double)`);
1910
- tr.push(` call void @scr_gen_out_f64(ptr %g, double %r)`, ` br label %done`);
1911
- break;
1912
- case "bool":
1913
- this.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`);
1914
- tr.push(` call void @scr_gen_out_bool(ptr %g, i1 %r)`, ` br label %done`);
1915
- break;
1916
- default: {
1917
- const v = vAdapters(this, ret);
1918
- this.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`);
1919
- tr.push(` call void @scr_gen_out_ref(ptr %g, ptr %r, ptr ${v.release})`, ` br label %done`);
1920
- }
2258
+ if (this.wasi) {
2259
+ tr.push(` ret void`, `}`, ``);
1921
2260
  }
1922
- tr.push(`thrown:`, ` %genret = call zeroext i1 @scr_exc_genret_pending()`, ` br i1 %genret, label %promote, label %dropdummy`, `promote:`, ` call void @scr_exc_clear()`, ` call void @scr_gen_ret_to_out(ptr %g)`, ` br label %dropdummy`, `dropdummy:`);
1923
- if (ret.kind !== "void" && isRefCounted(ret)) {
1924
- tr.push(` call void ${releaseSym(this, ret)}(ptr %r) ; unwound: the never-read dummy`);
2261
+ else {
2262
+ tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`, ` %pend = call zeroext i1 @scr_exc_pending()`, ` br i1 %pend, label %thrown, label %clean`, `clean:`);
2263
+ switch (ret.kind) {
2264
+ case "void":
2265
+ tr.push(` br label %done ; void body: the done value is undefined (NONE)`);
2266
+ break;
2267
+ case "f64":
2268
+ case "date":
2269
+ this.declare(`declare void @scr_gen_out_f64(ptr, double)`);
2270
+ tr.push(` call void @scr_gen_out_f64(ptr %g, double %r)`, ` br label %done`);
2271
+ break;
2272
+ case "bool":
2273
+ this.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`);
2274
+ tr.push(` call void @scr_gen_out_bool(ptr %g, i1 %r)`, ` br label %done`);
2275
+ break;
2276
+ default: {
2277
+ const v = vAdapters(this, ret);
2278
+ this.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`);
2279
+ tr.push(` call void @scr_gen_out_ref(ptr %g, ptr %r, ptr ${v.release})`, ` br label %done`);
2280
+ }
2281
+ }
2282
+ tr.push(`thrown:`, ` %genret = call zeroext i1 @scr_exc_genret_pending()`, ` br i1 %genret, label %promote, label %dropdummy`, `promote:`, ` call void @scr_exc_clear()`, ` call void @scr_gen_ret_to_out(ptr %g)`, ` br label %dropdummy`, `dropdummy:`);
2283
+ if (ret.kind !== "void" && isRefCounted(ret)) {
2284
+ tr.push(` call void ${releaseSym(this, ret)}(ptr %r) ; unwound: the never-read dummy`);
2285
+ }
2286
+ tr.push(` br label %done`, `done:`, ` ret void`, `}`, ``);
1925
2287
  }
1926
- tr.push(` br label %done`, `done:`, ` ret void`, `}`, ``);
1927
2288
  out.push(...tr);
1928
2289
  // The never-started teardown: drop the packed (+1) arguments.
1929
2290
  const dr = [
@@ -1952,7 +2313,7 @@ class LlEmitter {
1952
2313
  const sp = [
1953
2314
  `define internal ptr @${mangleGenSpawn(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; gen spawn ${fn.name}`,
1954
2315
  `entry:`,
1955
- ` %ap = call ptr @malloc(i64 ${sizeOf})`,
2316
+ ` %ap = call ptr @malloc(${this.sizeType} ${sizeOf})`,
1956
2317
  ` %isnull = icmp eq ptr %ap, null`,
1957
2318
  ` br i1 %isnull, label %oom, label %ok`,
1958
2319
  `oom:`,
@@ -2124,6 +2485,10 @@ class LlEmitter {
2124
2485
  return;
2125
2486
  }
2126
2487
  this.releaseForJump(0, 0);
2488
+ if (this.currentWasiCoro !== null) {
2489
+ this.B.terminate(`br label %${this.currentWasiCoro.finalLabel}`);
2490
+ return;
2491
+ }
2127
2492
  const t = this.currentReturnType;
2128
2493
  if (t.kind === "void")
2129
2494
  this.B.terminate("ret void");
@@ -2207,8 +2572,8 @@ class LlEmitter {
2207
2572
  const len = B.tmp();
2208
2573
  const t = B.tmp();
2209
2574
  B.line(`${lenp} = getelementptr inbounds %ScrStr, ptr ${v.name}, i64 0, i32 1`);
2210
- B.line(`${len} = load i64, ptr ${lenp}`);
2211
- B.line(`${t} = icmp ne i64 ${len}, 0`);
2575
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenp}`);
2576
+ B.line(`${t} = icmp ne ${this.sizeType} ${len}, 0`);
2212
2577
  return t;
2213
2578
  }
2214
2579
  case "date":
@@ -2303,8 +2668,8 @@ class LlEmitter {
2303
2668
  const len = B.tmp();
2304
2669
  const t = B.tmp();
2305
2670
  B.line(`${lenp} = getelementptr inbounds %ScrStr, ptr ${p}, i64 0, i32 1`);
2306
- B.line(`${len} = load i64, ptr ${lenp}`);
2307
- B.line(`${t} = icmp ne i64 ${len}, 0`);
2671
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenp}`);
2672
+ B.line(`${t} = icmp ne ${this.sizeType} ${len}, 0`);
2308
2673
  B.line(`store i1 ${t}, ptr ${slot}`);
2309
2674
  break;
2310
2675
  }
@@ -2459,7 +2824,7 @@ class LlEmitter {
2459
2824
  B.line(`${vtp} = getelementptr inbounds %${classStructSym(staticClassName)}, ptr ${objName}, i64 0, i32 1`);
2460
2825
  B.line(`${vt} = load ptr, ptr ${vtp}`);
2461
2826
  B.line(`${prep} = getelementptr inbounds %ScrVt, ptr ${vt}, i64 0, i32 0`);
2462
- B.line(`${pre} = load i64, ptr ${prep}`);
2827
+ B.line(`${pre} = load ${this.sizeType}, ptr ${prep}`);
2463
2828
  return pre;
2464
2829
  }
2465
2830
  /** The class object's static symbol (classes as values), registering it
@@ -2652,6 +3017,93 @@ class LlEmitter {
2652
3017
  return mangleGenSpawn(fnName);
2653
3018
  return mangleFunction(fnName);
2654
3019
  }
3020
+ /** Queue the current wasm coroutine and suspend it. The runtime decides
3021
+ * whether the promise waiter list or the ready FIFO owns the continuation;
3022
+ * LLVM keeps all live locals in the coroutine frame. */
3023
+ emitWasiSuspend(promise) {
3024
+ const coro = this.currentWasiCoro;
3025
+ if (coro === null)
3026
+ throw new Error("llvm emitter bug: wasm suspension outside async body");
3027
+ if (promise === null) {
3028
+ this.declare(`declare void @scr_wasi_await_hop_prepare(ptr)`);
3029
+ this.B.line(`call void @scr_wasi_await_hop_prepare(ptr ${coro.self})`);
3030
+ }
3031
+ else {
3032
+ this.declare(`declare void @scr_wasi_await_prepare(ptr, ptr)`);
3033
+ this.B.line(`call void @scr_wasi_await_prepare(ptr ${coro.self}, ptr ${promise})`);
3034
+ }
3035
+ this.emitWasiSuspendPrepared();
3036
+ }
3037
+ /** Suspend after a target-specific runtime helper already queued the
3038
+ * continuation (used by module awaits, which skip the hop when settled). */
3039
+ emitWasiSuspendPrepared() {
3040
+ const coro = this.currentWasiCoro;
3041
+ if (coro === null)
3042
+ throw new Error("llvm emitter bug: wasm suspension outside async body");
3043
+ const save = this.B.tmp();
3044
+ const state = this.B.tmp();
3045
+ const resume = this.B.newLabel("coro.resume");
3046
+ this.B.line(`${save} = call token @llvm.coro.save(ptr ${coro.handle})`);
3047
+ this.B.line(`${state} = call i8 @llvm.coro.suspend(token ${save}, i1 false)`);
3048
+ this.B.terminate(`switch i8 ${state}, label %${coro.suspendLabel} [ i8 0, label %${resume} i8 1, label %${coro.cleanupLabel} ]`);
3049
+ this.B.startBlock(resume);
3050
+ }
3051
+ /** Move a clean async return value into the current fiber's promise. */
3052
+ emitWasiFulfill(v) {
3053
+ const coro = this.currentWasiCoro;
3054
+ if (coro === null)
3055
+ throw new Error("llvm emitter bug: wasm fulfillment outside async body");
3056
+ const ret = this.currentReturnType;
3057
+ if (coro.kind === "generator") {
3058
+ this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
3059
+ const gen = this.B.tmp();
3060
+ this.B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${coro.self})`);
3061
+ switch (ret.kind) {
3062
+ case "void":
3063
+ return;
3064
+ case "f64":
3065
+ case "date":
3066
+ this.declare(`declare void @scr_gen_out_f64(ptr, double)`);
3067
+ this.B.line(`call void @scr_gen_out_f64(ptr ${gen}, double ${v.name})`);
3068
+ return;
3069
+ case "bool":
3070
+ this.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`);
3071
+ this.B.line(`call void @scr_gen_out_bool(ptr ${gen}, i1 ${v.name})`);
3072
+ return;
3073
+ default:
3074
+ this.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`);
3075
+ this.B.line(`call void @scr_gen_out_ref(ptr ${gen}, ptr ${v.name}, ptr ${vAdapters(this, ret).release})`);
3076
+ return;
3077
+ }
3078
+ }
3079
+ this.declare(`declare ptr @scr_fiber_promise(ptr)`);
3080
+ const pr = this.B.tmp();
3081
+ this.B.line(`${pr} = call ptr @scr_fiber_promise(ptr ${coro.self})`);
3082
+ switch (ret.kind) {
3083
+ case "void":
3084
+ this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
3085
+ this.B.line(`call void @scr_promise_fulfill_void(ptr ${pr})`);
3086
+ break;
3087
+ case "f64":
3088
+ case "date":
3089
+ this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
3090
+ this.B.line(`call void @scr_promise_fulfill_f64(ptr ${pr}, double ${v.name})`);
3091
+ break;
3092
+ case "bool":
3093
+ this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
3094
+ this.B.line(`call void @scr_promise_fulfill_bool(ptr ${pr}, i1 ${v.name})`);
3095
+ break;
3096
+ case "string":
3097
+ this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
3098
+ this.B.line(`call void @scr_promise_fulfill_str(ptr ${pr}, ptr ${v.name}) ; moves in`);
3099
+ break;
3100
+ default: {
3101
+ const rc = vAdapters(this, ret);
3102
+ this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
3103
+ this.B.line(`call void @scr_promise_fulfill_ref(ptr ${pr}, ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(this, ret)})`);
3104
+ }
3105
+ }
3106
+ }
2655
3107
  emitFunction(fn) {
2656
3108
  const B = new BlockBuilder();
2657
3109
  this.B = B;
@@ -2666,7 +3118,41 @@ class LlEmitter {
2666
3118
  this.tryStack = [];
2667
3119
  this.currentReturnType = fn.returnType;
2668
3120
  this.currentGenerator = fn.generator ?? null;
3121
+ this.currentWasiCoro = null;
2669
3122
  this.logArgSlots = 0;
3123
+ if (this.wasi && (fn.async === true || fn.generator !== undefined)) {
3124
+ this.declare(`declare token @llvm.coro.id(i32, ptr, ptr, ptr)`);
3125
+ this.declare(`declare ${this.sizeType} @llvm.coro.size.${this.sizeType}()`);
3126
+ this.declare(`declare ptr @llvm.coro.begin(token, ptr)`);
3127
+ this.declare(`declare token @llvm.coro.save(ptr)`);
3128
+ this.declare(`declare i8 @llvm.coro.suspend(token, i1)`);
3129
+ this.declare(`declare ptr @llvm.coro.free(token, ptr)`);
3130
+ this.declare(`declare i1 @llvm.coro.end(ptr, i1, token)`);
3131
+ this.declare(`declare ptr @malloc(${this.sizeType})`);
3132
+ this.declare(`declare void @free(ptr)`);
3133
+ this.declare(`declare void @scr_wasi_coro_started(ptr)`);
3134
+ this.declare(`declare ptr @scr_fiber_self()`);
3135
+ const id = B.tmp();
3136
+ const size = B.tmp();
3137
+ const mem = B.tmp();
3138
+ const handle = B.tmp();
3139
+ const self = B.tmp();
3140
+ B.line(`${id} = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)`);
3141
+ B.line(`${size} = call ${this.sizeType} @llvm.coro.size.${this.sizeType}()`);
3142
+ B.line(`${mem} = call ptr @malloc(${this.sizeType} ${size})`);
3143
+ B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
3144
+ B.line(`call void @scr_wasi_coro_started(ptr ${handle})`);
3145
+ B.line(`${self} = call ptr @scr_fiber_self()`);
3146
+ this.currentWasiCoro = {
3147
+ kind: fn.async === true ? "async" : "generator",
3148
+ id,
3149
+ handle,
3150
+ self,
3151
+ finalLabel: B.newLabel("coro.final"),
3152
+ cleanupLabel: B.newLabel("coro.cleanup"),
3153
+ suspendLabel: B.newLabel("coro.suspend"),
3154
+ };
3155
+ }
2670
3156
  const paramIds = new Set(fn.params.map((p) => p.localId));
2671
3157
  for (const local of fn.locals) {
2672
3158
  // Boxed locals' slots hold their capture BOX (a ptr); captured
@@ -2688,9 +3174,11 @@ class LlEmitter {
2688
3174
  // Captured bindings come in through the environment — borrowed for the
2689
3175
  // whole call (the closure owns them): bound here, never released here.
2690
3176
  (fn.captures ?? []).forEach((c, i) => {
3177
+ const caps = B.tmp();
2691
3178
  const p = B.tmp();
2692
3179
  const box = B.tmp();
2693
- B.line(`${p} = getelementptr inbounds i8, ptr %sc_env, i64 ${32 + 8 * i} ; caps[${i}]`);
3180
+ B.line(`${caps} = getelementptr inbounds %ScrClosure, ptr %sc_env, i64 1 ; caps`);
3181
+ B.line(`${p} = getelementptr inbounds ptr, ptr ${caps}, ${this.sizeType} ${i} ; caps[${i}]`);
2694
3182
  B.line(`${box} = load ptr, ptr ${p}`);
2695
3183
  B.line(`store ptr ${box}, ptr %${mangleLocal(c.localId)} ; captured ${c.name}`);
2696
3184
  });
@@ -2720,9 +3208,51 @@ class LlEmitter {
2720
3208
  // whose unwind released everything down to depth 0).
2721
3209
  if (fn.returnType.kind === "void" && !B.isTerminated()) {
2722
3210
  this.releaseScope(this.scopes[0]);
2723
- B.terminate("ret void");
3211
+ if (this.currentWasiCoro !== null) {
3212
+ this.emitWasiFulfill(null);
3213
+ B.terminate(`br label %${this.currentWasiCoro.finalLabel}`);
3214
+ }
3215
+ else {
3216
+ B.terminate("ret void");
3217
+ }
2724
3218
  }
2725
3219
  this.scopes.pop();
3220
+ const coro = this.currentWasiCoro;
3221
+ if (coro !== null) {
3222
+ B.startBlock(coro.finalLabel);
3223
+ if (fn.captures !== undefined) {
3224
+ this.declare(`declare void @scr_closure_release(ptr)`);
3225
+ B.line(`call void @scr_closure_release(ptr %sc_env)`);
3226
+ }
3227
+ if (coro.kind === "generator") {
3228
+ this.declare(`declare void @scr_wasi_gen_finish(ptr)`);
3229
+ B.line(`call void @scr_wasi_gen_finish(ptr ${coro.self})`);
3230
+ }
3231
+ else {
3232
+ this.declare(`declare void @scr_wasi_async_finish(ptr)`);
3233
+ B.line(`call void @scr_wasi_async_finish(ptr ${coro.self})`);
3234
+ }
3235
+ const finalState = B.tmp();
3236
+ B.line(`${finalState} = call i8 @llvm.coro.suspend(token none, i1 true)`);
3237
+ B.terminate(`switch i8 ${finalState}, label %${coro.suspendLabel} [ i8 1, label %${coro.cleanupLabel} ]`);
3238
+ B.startBlock(coro.cleanupLabel);
3239
+ const frame = B.tmp();
3240
+ B.line(`${frame} = call ptr @llvm.coro.free(token ${coro.id}, ptr ${coro.handle})`);
3241
+ B.line(`call void @free(ptr ${frame})`);
3242
+ B.br(coro.suspendLabel);
3243
+ B.startBlock(coro.suspendLabel);
3244
+ const ended = B.tmp();
3245
+ B.line(`${ended} = call i1 @llvm.coro.end(ptr ${coro.handle}, i1 false, token none)`);
3246
+ const ret = this.llType(fn.returnType);
3247
+ if (ret === "void")
3248
+ B.terminate(`ret void`);
3249
+ else if (ret === "double")
3250
+ B.terminate(`ret double ${f64Lit(0)}`);
3251
+ else if (ret === "i1")
3252
+ B.terminate(`ret i1 false`);
3253
+ else
3254
+ B.terminate(`ret ptr null`);
3255
+ }
2726
3256
  if (this.logArgSlots > 0) {
2727
3257
  B.entryAllocas.push(`%logargs = alloca [${this.logArgSlots} x %ScrLogArg]`);
2728
3258
  }
@@ -2731,7 +3261,8 @@ class LlEmitter {
2731
3261
  if (fn.captures !== undefined)
2732
3262
  params.unshift("ptr %sc_env");
2733
3263
  const ret = this.llType(fn.returnType);
2734
- return `define internal ${ret} @${mangleFunction(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; ${fn.name}\n${B.render()}\n}`;
3264
+ const attrs = coro !== null ? "#1" : FN_ATTRS;
3265
+ return `define internal ${ret} @${mangleFunction(fn.name)}(${params.join(", ")}) ${attrs} { ; ${fn.name}\n${B.render()}\n}`;
2735
3266
  }
2736
3267
  // ── statements ──────────────────────────────────────────────────────────
2737
3268
  emitStmts(stmts) {
@@ -2960,7 +3491,7 @@ class LlEmitter {
2960
3491
  B.line(`${kp} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 1`);
2961
3492
  B.line(`store ptr ${this.cstr(f.name)}, ptr ${kp}`);
2962
3493
  B.line(`${ip} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 2`);
2963
- B.line(`store i64 0, ptr ${ip}`);
3494
+ B.line(`store ${this.sizeType} 0, ptr ${ip}`);
2964
3495
  const helper = this.dyn.dynCheckHelper(f.type);
2965
3496
  const fty = this.llType(f.type);
2966
3497
  const nv = B.tmp();
@@ -3129,8 +3660,8 @@ class LlEmitter {
3129
3660
  let integerSlot = null;
3130
3661
  if (integerLoop) {
3131
3662
  integerSlot = B.slot();
3132
- B.entryAllocas.push(`${integerSlot} = alloca i64 ; integer induction ${this.currentLocals.get(integerLoop.localId).name}`);
3133
- B.line(`store i64 0, ptr ${integerSlot}`);
3663
+ B.entryAllocas.push(`${integerSlot} = alloca ${this.sizeType} ; integer induction ${this.currentLocals.get(integerLoop.localId).name}`);
3664
+ B.line(`store ${this.sizeType} 0, ptr ${integerSlot}`);
3134
3665
  this.integerLoopBindings.set(integerLoop.localId, integerSlot);
3135
3666
  }
3136
3667
  else if (s.init) {
@@ -3156,9 +3687,9 @@ class LlEmitter {
3156
3687
  const index = B.tmp();
3157
3688
  const inBounds = B.tmp();
3158
3689
  B.line(`${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver.name}, i64 0, i32 1`);
3159
- B.line(`${len} = load i64, ptr ${lenPtr}`);
3160
- B.line(`${index} = load i64, ptr ${integerSlot}`);
3161
- B.line(`${inBounds} = icmp ult i64 ${index}, ${len}`);
3690
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}`);
3691
+ B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
3692
+ B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
3162
3693
  B.condBr(inBounds, lb, le);
3163
3694
  }
3164
3695
  else if (s.cond)
@@ -3196,9 +3727,9 @@ class LlEmitter {
3196
3727
  if (integerLoop && integerSlot) {
3197
3728
  const old = B.tmp();
3198
3729
  const next = B.tmp();
3199
- B.line(`${old} = load i64, ptr ${integerSlot}`);
3200
- B.line(`${next} = add nuw i64 ${old}, 1`);
3201
- B.line(`store i64 ${next}, ptr ${integerSlot}`);
3730
+ B.line(`${old} = load ${this.sizeType}, ptr ${integerSlot}`);
3731
+ B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
3732
+ B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
3202
3733
  }
3203
3734
  else if (s.update)
3204
3735
  this.emitStmt(s.update);
@@ -3376,10 +3907,16 @@ class LlEmitter {
3376
3907
  else {
3377
3908
  this.releaseForJump(0, 0);
3378
3909
  }
3379
- if (v === null)
3380
- B.terminate("ret void");
3381
- else
3382
- B.terminate(`ret ${this.llType(s.value.type)} ${v.name}`);
3910
+ if (!B.isTerminated()) {
3911
+ if (this.currentWasiCoro !== null) {
3912
+ this.emitWasiFulfill(v);
3913
+ B.terminate(`br label %${this.currentWasiCoro.finalLabel}`);
3914
+ }
3915
+ else if (v === null)
3916
+ B.terminate("ret void");
3917
+ else
3918
+ B.terminate(`ret ${this.llType(s.value.type)} ${v.name}`);
3919
+ }
3383
3920
  break;
3384
3921
  }
3385
3922
  case "throw": {
@@ -3408,8 +3945,8 @@ class LlEmitter {
3408
3945
  // the construct (message) with the SC code stamped on `code`,
3409
3946
  // then unwind exactly like `throw`. SCR_ERR_ERROR = 0.
3410
3947
  const bytes = Buffer.byteLength(s.message, "utf8");
3411
- this.declare(`declare void @scr_throw_error_msg_code(i32, ptr, i64, ptr)`);
3412
- B.line(`call void @scr_throw_error_msg_code(i32 0, ptr ${this.cstr(s.message)}, i64 ${bytes}, ptr ${this.cstr(s.code)})`);
3948
+ this.declare(`declare void @scr_throw_error_msg_code(i32, ptr, ${this.sizeType}, ptr)`);
3949
+ B.line(`call void @scr_throw_error_msg_code(i32 0, ptr ${this.cstr(s.message)}, ${this.sizeType} ${bytes}, ptr ${this.cstr(s.code)})`);
3413
3950
  this.emitUnwind();
3414
3951
  break;
3415
3952
  }
@@ -3689,8 +4226,8 @@ class LlEmitter {
3689
4226
  if (integerSlot !== undefined) {
3690
4227
  const integer = B.tmp();
3691
4228
  const number = B.tmp();
3692
- B.line(`${integer} = load i64, ptr ${integerSlot}`);
3693
- B.line(`${number} = uitofp i64 ${integer} to double`);
4229
+ B.line(`${integer} = load ${this.sizeType}, ptr ${integerSlot}`);
4230
+ B.line(`${number} = uitofp ${this.sizeType} ${integer} to double`);
3694
4231
  return { name: number, type: e.type };
3695
4232
  }
3696
4233
  const b = this.binding(e.localId);
@@ -4817,15 +5354,17 @@ class LlEmitter {
4817
5354
  }
4818
5355
  // Lifted async lambdas enter through their spawn wrapper (which
4819
5356
  // takes sc_env first, like every lifted function).
4820
- this.declare(`declare ptr @scr_closure_new(ptr, i64)`);
5357
+ this.declare(`declare ptr @scr_closure_new(ptr, ${this.sizeType})`);
4821
5358
  const c = B.tmp();
4822
- B.line(`${c} = call ptr @scr_closure_new(ptr @${this.callTarget(e.fnName)}, i64 ${e.captures.length})`);
5359
+ B.line(`${c} = call ptr @scr_closure_new(ptr @${this.callTarget(e.fnName)}, ${this.sizeType} ${e.captures.length})`);
4823
5360
  const out = this.own({ name: c, type: e.type });
4824
5361
  e.captures.forEach((localId, i) => {
4825
5362
  const box = this.loadBox(`%${mangleLocal(localId)}`);
4826
5363
  const retained = this.retainBox(box);
5364
+ const caps = B.tmp();
4827
5365
  const capp = B.tmp();
4828
- B.line(`${capp} = getelementptr inbounds i8, ptr ${c}, i64 ${32 + 8 * i} ; caps[${i}]`);
5366
+ B.line(`${caps} = getelementptr inbounds %ScrClosure, ptr ${c}, i64 1 ; caps`);
5367
+ B.line(`${capp} = getelementptr inbounds ptr, ptr ${caps}, ${this.sizeType} ${i} ; caps[${i}]`);
4829
5368
  B.line(`store ptr ${retained}, ptr ${capp}`);
4830
5369
  });
4831
5370
  return out;
@@ -4904,11 +5443,49 @@ class LlEmitter {
4904
5443
  if (!entry)
4905
5444
  throw new Error(`llvm emitter bug: unknown FFI import ${e.import}`);
4906
5445
  const args = e.args.map((arg) => this.emitExpr(arg));
5446
+ const sourceArgs = new Map();
5447
+ const callbackArgs = new Map();
5448
+ let sourceIndex = 0;
5449
+ entry.params.forEach((param, abiIndex) => {
5450
+ if (isFfiContextParam(param))
5451
+ return;
5452
+ const arg = args[sourceIndex++];
5453
+ sourceArgs.set(abiIndex, arg);
5454
+ if (isFfiCallbackParam(param))
5455
+ callbackArgs.set(param.callback.id, arg);
5456
+ });
5457
+ const rawContexts = [];
5458
+ for (const param of entry.params) {
5459
+ if (!isFfiCallbackParam(param))
5460
+ continue;
5461
+ const adapter = this.ffiCallbackAdapter(entry.name, param.callback.id);
5462
+ if (adapter.tls === null)
5463
+ continue;
5464
+ const callback = callbackArgs.get(param.callback.id);
5465
+ const previous = B.tmp();
5466
+ B.line(`${previous} = load ptr, ptr @${adapter.tls}`);
5467
+ B.line(`store ptr ${callback.name}, ptr @${adapter.tls}`);
5468
+ rawContexts.push({ tls: adapter.tls, previous });
5469
+ }
4907
5470
  const nativeArgs = [];
4908
5471
  const nativeParamTypes = [];
4909
- entry.params.forEach((cls, i) => {
4910
- const arg = args[i];
4911
- switch (cls) {
5472
+ entry.params.forEach((param, i) => {
5473
+ if (isFfiCallbackParam(param)) {
5474
+ const adapter = this.ffiCallbackAdapter(entry.name, param.callback.id);
5475
+ nativeParamTypes.push("ptr");
5476
+ nativeArgs.push(`ptr @${adapter.symbol}`);
5477
+ return;
5478
+ }
5479
+ if (isFfiContextParam(param)) {
5480
+ const callback = callbackArgs.get(param.context);
5481
+ if (!callback)
5482
+ throw new Error(`llvm emitter bug: FFI context '${param.context}' has no callback arg`);
5483
+ nativeParamTypes.push("ptr");
5484
+ nativeArgs.push(`ptr ${callback.name}`);
5485
+ return;
5486
+ }
5487
+ const arg = sourceArgs.get(i);
5488
+ switch (param) {
4912
5489
  case "f64":
4913
5490
  nativeParamTypes.push("double");
4914
5491
  nativeArgs.push(`double ${arg.name}`);
@@ -4927,7 +5504,7 @@ class LlEmitter {
4927
5504
  const asU32 = B.tmp();
4928
5505
  B.line(`${asDouble} = call double @scr_bit_ushr(double ${arg.name}, double ${f64Lit(0)})`);
4929
5506
  B.line(`${asU32} = fptoui double ${asDouble} to i32`);
4930
- if (cls === "u8") {
5507
+ if (param === "u8") {
4931
5508
  const asU8 = B.tmp();
4932
5509
  B.line(`${asU8} = trunc i32 ${asU32} to i8`);
4933
5510
  nativeParamTypes.push("i8");
@@ -4975,29 +5552,47 @@ class LlEmitter {
4975
5552
  }
4976
5553
  }
4977
5554
  });
4978
- const retTy = entry.returns === "f64" ? "double"
4979
- : entry.returns === "bool" || entry.returns === "u8" ? "i8"
4980
- : entry.returns === "u32" || entry.returns === "i32" ? "i32"
4981
- : "void";
5555
+ const retTy = ffiNativeTypeLl(entry.returns);
4982
5556
  this.declare(`declare ${retTy} @${entry.symbol}(${nativeParamTypes.join(", ")})`);
4983
5557
  const call = `call ${retTy} @${entry.symbol}(${nativeArgs.join(", ")})`;
5558
+ const restoreRawContexts = () => {
5559
+ for (let i = rawContexts.length - 1; i >= 0; i--) {
5560
+ const saved = rawContexts[i];
5561
+ B.line(`store ptr ${saved.previous}, ptr @${saved.tls}`);
5562
+ }
5563
+ };
5564
+ const callbacksMayThrow = callbackArgs.size > 0;
4984
5565
  if (entry.returns === "void") {
4985
5566
  B.line(call);
5567
+ restoreRawContexts();
5568
+ if (callbacksMayThrow)
5569
+ this.emitPendingCheck();
4986
5570
  return { name: "", type: e.type };
4987
5571
  }
4988
5572
  const raw = B.tmp();
4989
5573
  B.line(`${raw} = ${call}`);
4990
- if (entry.returns === "f64")
4991
- return { name: raw, type: e.type };
5574
+ restoreRawContexts();
5575
+ if (entry.returns === "f64") {
5576
+ const result = { name: raw, type: e.type };
5577
+ if (callbacksMayThrow)
5578
+ this.emitPendingCheck();
5579
+ return result;
5580
+ }
4992
5581
  if (entry.returns === "bool") {
4993
5582
  const value = B.tmp();
4994
5583
  B.line(`${value} = icmp ne i8 ${raw}, 0`);
4995
- return { name: value, type: e.type };
5584
+ const result = { name: value, type: e.type };
5585
+ if (callbacksMayThrow)
5586
+ this.emitPendingCheck();
5587
+ return result;
4996
5588
  }
4997
5589
  const value = B.tmp();
4998
5590
  const op = entry.returns === "i32" ? "sitofp" : "uitofp";
4999
5591
  B.line(`${value} = ${op} ${retTy} ${raw} to double`);
5000
- return { name: value, type: e.type };
5592
+ const result = { name: value, type: e.type };
5593
+ if (callbacksMayThrow)
5594
+ this.emitPendingCheck();
5595
+ return result;
5001
5596
  }
5002
5597
  case "new": {
5003
5598
  // Allocate (fields zeroed, vt stamped), then run the ctor. The
@@ -5116,8 +5711,8 @@ class LlEmitter {
5116
5711
  const ge = B.tmp();
5117
5712
  const le = B.tmp();
5118
5713
  const t = B.tmp();
5119
- B.line(`${ge} = icmp sge i64 ${pre}, ${target.pre}`);
5120
- B.line(`${le} = icmp sle i64 ${pre}, ${target.post}`);
5714
+ B.line(`${ge} = icmp sge ${this.sizeType} ${pre}, ${target.pre}`);
5715
+ B.line(`${le} = icmp sle ${this.sizeType} ${pre}, ${target.post}`);
5121
5716
  B.line(`${t} = and i1 ${ge}, ${le} ; instanceof ${e.className}`);
5122
5717
  return { name: t, type: e.type };
5123
5718
  }
@@ -5135,14 +5730,14 @@ class LlEmitter {
5135
5730
  const tpostp = B.tmp();
5136
5731
  const tpost = B.tmp();
5137
5732
  B.line(`${tprep} = getelementptr inbounds %ScrClassObj, ptr ${target.name}, i64 0, i32 1`);
5138
- B.line(`${tpre} = load i64, ptr ${tprep}`);
5733
+ B.line(`${tpre} = load ${this.sizeType}, ptr ${tprep}`);
5139
5734
  B.line(`${tpostp} = getelementptr inbounds %ScrClassObj, ptr ${target.name}, i64 0, i32 2`);
5140
- B.line(`${tpost} = load i64, ptr ${tpostp}`);
5735
+ B.line(`${tpost} = load ${this.sizeType}, ptr ${tpostp}`);
5141
5736
  const ge = B.tmp();
5142
5737
  const le = B.tmp();
5143
5738
  const t = B.tmp();
5144
- B.line(`${ge} = icmp sge i64 ${pre}, ${tpre}`);
5145
- B.line(`${le} = icmp sle i64 ${pre}, ${tpost}`);
5739
+ B.line(`${ge} = icmp sge ${this.sizeType} ${pre}, ${tpre}`);
5740
+ B.line(`${le} = icmp sle ${this.sizeType} ${pre}, ${tpost}`);
5146
5741
  B.line(`${t} = and i1 ${ge}, ${le}`);
5147
5742
  return { name: t, type: e.type };
5148
5743
  }
@@ -5154,9 +5749,9 @@ class LlEmitter {
5154
5749
  const c = this.emitExpr(e.value);
5155
5750
  if (e.test === "instanceof") {
5156
5751
  const target = this.classMetaOf(e.className);
5157
- this.declare(`declare zeroext i1 @scr_caught_instanceof(ptr, i64, i64)`);
5752
+ this.declare(`declare zeroext i1 @scr_caught_instanceof(ptr, ${this.sizeType}, ${this.sizeType})`);
5158
5753
  const t = B.tmp();
5159
- B.line(`${t} = call zeroext i1 @scr_caught_instanceof(ptr ${c.name}, i64 ${target.pre}, i64 ${target.post})`);
5754
+ B.line(`${t} = call zeroext i1 @scr_caught_instanceof(ptr ${c.name}, ${this.sizeType} ${target.pre}, ${this.sizeType} ${target.post})`);
5160
5755
  if (e.negated !== true)
5161
5756
  return { name: t, type: e.type };
5162
5757
  const n = B.tmp();
@@ -5221,9 +5816,9 @@ class LlEmitter {
5221
5816
  const c = this.emitExpr(e.value);
5222
5817
  const target = this.classMetaOf(e.className);
5223
5818
  const display = e.className.startsWith("%") ? e.className.slice(1) : e.className;
5224
- this.declare(`declare ptr @scr_caught_check_obj(ptr, i64, i64, ptr)`);
5819
+ this.declare(`declare ptr @scr_caught_check_obj(ptr, ${this.sizeType}, ${this.sizeType}, ptr)`);
5225
5820
  const t = B.tmp();
5226
- B.line(`${t} = call ptr @scr_caught_check_obj(ptr ${c.name}, i64 ${target.pre}, i64 ${target.post}, ptr ${this.cstr(display)})`);
5821
+ B.line(`${t} = call ptr @scr_caught_check_obj(ptr ${c.name}, ${this.sizeType} ${target.pre}, ${this.sizeType} ${target.post}, ptr ${this.cstr(display)})`);
5227
5822
  const out = this.own({ name: t, type: e.type });
5228
5823
  this.emitPendingCheck();
5229
5824
  return out;
@@ -5334,7 +5929,7 @@ class LlEmitter {
5334
5929
  return compact;
5335
5930
  const rewriter = this.walkers.jsonIndentHelper();
5336
5931
  const t2 = B.tmp();
5337
- B.line(`${t2} = call ptr @${rewriter}(ptr ${compact.name}, ptr ${this.cstr(indent)}, i64 ${Buffer.byteLength(indent, "utf8")})`);
5932
+ B.line(`${t2} = call ptr @${rewriter}(ptr ${compact.name}, ptr ${this.cstr(indent)}, ${this.sizeType} ${Buffer.byteLength(indent, "utf8")})`);
5338
5933
  return this.own({ name: t2, type: e.type });
5339
5934
  }
5340
5935
  case "bytesNew": {
@@ -5389,18 +5984,50 @@ class LlEmitter {
5389
5984
  const v = this.emitExpr(e.value);
5390
5985
  const yt = e.value.type;
5391
5986
  if (yt.kind === "f64" || yt.kind === "date") {
5392
- this.declare(`declare void @scr_gen_yield_f64(double)`);
5393
- B.line(`call void @scr_gen_yield_f64(double ${v.name})`);
5987
+ if (this.wasi) {
5988
+ const coro = this.currentWasiCoro;
5989
+ this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
5990
+ this.declare(`declare void @scr_gen_out_f64(ptr, double)`);
5991
+ const g = B.tmp();
5992
+ B.line(`${g} = call ptr @scr_gen_of_fiber(ptr ${coro.self})`);
5993
+ B.line(`call void @scr_gen_out_f64(ptr ${g}, double ${v.name})`);
5994
+ }
5995
+ else {
5996
+ this.declare(`declare void @scr_gen_yield_f64(double)`);
5997
+ B.line(`call void @scr_gen_yield_f64(double ${v.name})`);
5998
+ }
5394
5999
  }
5395
6000
  else if (yt.kind === "bool") {
5396
- this.declare(`declare void @scr_gen_yield_bool(i1 zeroext)`);
5397
- B.line(`call void @scr_gen_yield_bool(i1 ${v.name})`);
6001
+ if (this.wasi) {
6002
+ const coro = this.currentWasiCoro;
6003
+ this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
6004
+ this.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`);
6005
+ const g = B.tmp();
6006
+ B.line(`${g} = call ptr @scr_gen_of_fiber(ptr ${coro.self})`);
6007
+ B.line(`call void @scr_gen_out_bool(ptr ${g}, i1 ${v.name})`);
6008
+ }
6009
+ else {
6010
+ this.declare(`declare void @scr_gen_yield_bool(i1 zeroext)`);
6011
+ B.line(`call void @scr_gen_yield_bool(i1 ${v.name})`);
6012
+ }
5398
6013
  }
5399
6014
  else {
5400
6015
  this.moveTemp(v); // the OUT slot takes ownership
5401
- this.declare(`declare void @scr_gen_yield_ref(ptr, ptr)`);
5402
- B.line(`call void @scr_gen_yield_ref(ptr ${v.name}, ptr ${vAdapters(this, yt).release})`);
6016
+ if (this.wasi) {
6017
+ const coro = this.currentWasiCoro;
6018
+ this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
6019
+ this.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`);
6020
+ const g = B.tmp();
6021
+ B.line(`${g} = call ptr @scr_gen_of_fiber(ptr ${coro.self})`);
6022
+ B.line(`call void @scr_gen_out_ref(ptr ${g}, ptr ${v.name}, ptr ${vAdapters(this, yt).release})`);
6023
+ }
6024
+ else {
6025
+ this.declare(`declare void @scr_gen_yield_ref(ptr, ptr)`);
6026
+ B.line(`call void @scr_gen_yield_ref(ptr ${v.name}, ptr ${vAdapters(this, yt).release})`);
6027
+ }
5403
6028
  }
6029
+ if (this.wasi)
6030
+ this.emitWasiSuspendPrepared();
5404
6031
  this.emitPendingCheck();
5405
6032
  if (e.type.kind === "void") {
5406
6033
  // An undefined next-channel: nothing to read (the frontend
@@ -5532,6 +6159,8 @@ class LlEmitter {
5532
6159
  // refcounted results arrive +1 and join the frame pre-check so an
5533
6160
  // unwind releases the dummy (NULL) harmlessly.
5534
6161
  const pr = this.emitExpr(e.value);
6162
+ if (this.wasi)
6163
+ this.emitWasiSuspend(pr.name);
5535
6164
  if (e.type.kind === "void") {
5536
6165
  this.declare(`declare void @scr_await_void(ptr)`);
5537
6166
  B.line(`call void @scr_await_void(ptr ${pr.name})`);
@@ -5586,10 +6215,18 @@ class LlEmitter {
5586
6215
  B.condBr(isP, lp, lh);
5587
6216
  B.startBlock(lp);
5588
6217
  this.declare(`declare void @scr_await_void(ptr)`);
5589
- B.line(`call void @scr_await_void(ptr ${this.unionPeek(u.name)})`);
6218
+ {
6219
+ const promise = this.unionPeek(u.name);
6220
+ if (this.wasi)
6221
+ this.emitWasiSuspend(promise);
6222
+ B.line(`call void @scr_await_void(ptr ${promise})`);
6223
+ }
5590
6224
  B.br(lj);
5591
6225
  B.startBlock(lh);
5592
- B.line(`call void @scr_await_hop()`);
6226
+ if (this.wasi)
6227
+ this.emitWasiSuspend(null);
6228
+ else
6229
+ B.line(`call void @scr_await_hop()`);
5593
6230
  B.br(lj);
5594
6231
  B.startBlock(lj);
5595
6232
  this.emitPendingCheck();
@@ -5618,6 +6255,8 @@ class LlEmitter {
5618
6255
  B.condBr(isP, lp, lh);
5619
6256
  B.startBlock(lp);
5620
6257
  const peek = this.unionPeek(u.name);
6258
+ if (this.wasi)
6259
+ this.emitWasiSuspend(peek);
5621
6260
  let awaited;
5622
6261
  if (inner.kind === "f64" || inner.kind === "date") {
5623
6262
  this.declare(`declare double @scr_await_f64(ptr)`);
@@ -5646,7 +6285,10 @@ class LlEmitter {
5646
6285
  B.line(`store ptr ${this.unionNewOwned(innerTag, awaited)}, ptr ${slot}`);
5647
6286
  B.br(lj);
5648
6287
  B.startBlock(lh);
5649
- B.line(`call void @scr_await_hop()`);
6288
+ if (this.wasi)
6289
+ this.emitWasiSuspend(null);
6290
+ else
6291
+ B.line(`call void @scr_await_hop()`);
5650
6292
  const unitTags = def.arms.flatMap((a, i) => (isUnitType(a) ? [i] : []));
5651
6293
  if (unitTags.length === 1) {
5652
6294
  B.line(`store ptr ${this.unitInstanceRef(resUnionId, resTagOf(def.arms[unitTags[0]]))}, ptr ${slot}`);
@@ -5772,8 +6414,27 @@ class LlEmitter {
5772
6414
  // Internal ESM dependency wait: pending promises park the module
5773
6415
  // fiber, while settled ones continue synchronously.
5774
6416
  const p = this.emitExpr(e.args[0]);
5775
- this.declare(`declare void @scr_module_await(ptr)`);
5776
- B.line(`call void @scr_module_await(ptr ${p.name})`);
6417
+ if (this.wasi) {
6418
+ const coro = this.currentWasiCoro;
6419
+ if (coro === null)
6420
+ throw new Error("llvm emitter bug: module await outside wasm coroutine");
6421
+ this.declare(`declare zeroext i1 @scr_wasi_module_await_prepare(ptr, ptr)`);
6422
+ const needsSuspend = B.tmp();
6423
+ const suspend = B.newLabel("ma.suspend");
6424
+ const ready = B.newLabel("ma.ready");
6425
+ B.line(`${needsSuspend} = call zeroext i1 @scr_wasi_module_await_prepare(ptr ${coro.self}, ptr ${p.name})`);
6426
+ B.condBr(needsSuspend, suspend, ready);
6427
+ B.startBlock(suspend);
6428
+ this.emitWasiSuspendPrepared();
6429
+ B.br(ready);
6430
+ B.startBlock(ready);
6431
+ this.declare(`declare void @scr_module_await(ptr)`);
6432
+ B.line(`call void @scr_module_await(ptr ${p.name})`);
6433
+ }
6434
+ else {
6435
+ this.declare(`declare void @scr_module_await(ptr)`);
6436
+ B.line(`call void @scr_module_await(ptr ${p.name})`);
6437
+ }
5777
6438
  this.emitPendingCheck();
5778
6439
  return { name: "", type: e.type };
5779
6440
  }
@@ -5807,7 +6468,7 @@ class LlEmitter {
5807
6468
  const len = B.tmp();
5808
6469
  const cap = B.tmp();
5809
6470
  B.line(`${len} = call double @scr_arr_len(ptr ${ps.name})`);
5810
- B.line(`${cap} = fptoui double ${len} to i64`);
6471
+ B.line(`${cap} = fptoui double ${len} to ${this.sizeType}`);
5811
6472
  const vals = B.tmp();
5812
6473
  B.line(`${vals} = ${arrNewCall(this, elem, cap)}`);
5813
6474
  this.own({ name: vals, type: e.type.inner });
@@ -5929,8 +6590,8 @@ class LlEmitter {
5929
6590
  }
5930
6591
  });
5931
6592
  const fn = e.name === "console.error" ? "scr_console_error" : "scr_console_log";
5932
- this.declare(`declare void @${fn}(i64, ptr)`);
5933
- B.line(`call void @${fn}(i64 ${args.length}, ptr %logargs)`);
6593
+ this.declare(`declare void @${fn}(${this.sizeType}, ptr)`);
6594
+ B.line(`call void @${fn}(${this.sizeType} ${args.length}, ptr %logargs)`);
5934
6595
  return { name: "", type: e.type };
5935
6596
  }
5936
6597
  case "dynFrom": {
@@ -5972,9 +6633,9 @@ class LlEmitter {
5972
6633
  this.liveDynRefAdapters.set(key, adapter);
5973
6634
  }
5974
6635
  const rc = vAdapters(this, v.type);
5975
- this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, i64, ptr, ptr)`);
6636
+ this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, ${this.sizeType}, ptr, ptr)`);
5976
6637
  const boxed = B.tmp();
5977
- B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")}, ptr @${adapter.snapshot}, ptr ${adapter.commit})`);
6638
+ B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(key)}, ${this.sizeType} ${Buffer.byteLength(key, "utf8")}, ptr @${adapter.snapshot}, ptr ${adapter.commit})`);
5978
6639
  return this.own({ name: boxed, type: e.type });
5979
6640
  }
5980
6641
  if (v.type.kind === "func") {
@@ -6075,14 +6736,14 @@ class LlEmitter {
6075
6736
  B.entryAllocas.push(`${arr} = alloca [${args.length} x ptr]`);
6076
6737
  args.forEach((a, i) => {
6077
6738
  const p = B.tmp();
6078
- B.line(`${p} = getelementptr inbounds [${args.length} x ptr], ptr ${arr}, i64 0, i64 ${i}`);
6739
+ B.line(`${p} = getelementptr inbounds [${args.length} x ptr], ptr ${arr}, i64 0, ${this.sizeType} ${i}`);
6079
6740
  B.line(`store ptr ${a.name}, ptr ${p}`);
6080
6741
  });
6081
6742
  argsPtr = arr;
6082
6743
  }
6083
- this.declare(`declare ptr @scr_dyn_call(ptr, ptr, i64, ptr)`);
6744
+ this.declare(`declare ptr @scr_dyn_call(ptr, ptr, ${this.sizeType}, ptr)`);
6084
6745
  const t = B.tmp();
6085
- B.line(`${t} = call ptr @scr_dyn_call(ptr ${callee.name}, ptr ${argsPtr}, i64 ${args.length}, ptr ${this.cstr(e.calleeName)})`);
6746
+ B.line(`${t} = call ptr @scr_dyn_call(ptr ${callee.name}, ptr ${argsPtr}, ${this.sizeType} ${args.length}, ptr ${this.cstr(e.calleeName)})`);
6086
6747
  const out = this.own({ name: t, type: e.type });
6087
6748
  this.emitPendingCheck();
6088
6749
  return out;
@@ -6099,14 +6760,14 @@ class LlEmitter {
6099
6760
  B.entryAllocas.push(`${arr} = alloca [${args.length} x ptr]`);
6100
6761
  args.forEach((a, i) => {
6101
6762
  const p = B.tmp();
6102
- B.line(`${p} = getelementptr inbounds [${args.length} x ptr], ptr ${arr}, i64 0, i64 ${i}`);
6763
+ B.line(`${p} = getelementptr inbounds [${args.length} x ptr], ptr ${arr}, i64 0, ${this.sizeType} ${i}`);
6103
6764
  B.line(`store ptr ${a.name}, ptr ${p}`);
6104
6765
  });
6105
6766
  argsPtr = arr;
6106
6767
  }
6107
- this.declare(`declare ptr @scr_dyn_invoke(ptr, ptr, ptr, i64, ptr)`);
6768
+ this.declare(`declare ptr @scr_dyn_invoke(ptr, ptr, ptr, ${this.sizeType}, ptr)`);
6108
6769
  const t = B.tmp();
6109
- B.line(`${t} = call ptr @scr_dyn_invoke(ptr ${recv.name}, ptr ${this.cstr(e.method)}, ptr ${argsPtr}, i64 ${args.length}, ptr ${this.cstr(e.calleeName)})`);
6770
+ B.line(`${t} = call ptr @scr_dyn_invoke(ptr ${recv.name}, ptr ${this.cstr(e.method)}, ptr ${argsPtr}, ${this.sizeType} ${args.length}, ptr ${this.cstr(e.calleeName)})`);
6110
6771
  const out = this.own({ name: t, type: e.type });
6111
6772
  this.emitPendingCheck();
6112
6773
  return out;
@@ -6173,11 +6834,11 @@ class LlEmitter {
6173
6834
  B.line(`${isObj} = icmp eq i32 ${kd}, ${DK.OBJ}`);
6174
6835
  B.condBr(isObj, lObj, lNotObj);
6175
6836
  B.startBlock(lObj);
6176
- this.declare(`declare ptr @scr_dyn_obj_get(ptr, ptr, i64)`);
6837
+ this.declare(`declare ptr @scr_dyn_obj_get(ptr, ptr, ${this.sizeType})`);
6177
6838
  const keyBytes = Buffer.byteLength(e.key, "utf8");
6178
6839
  const m = B.tmp();
6179
6840
  const has = B.tmp();
6180
- B.line(`${m} = call ptr @scr_dyn_obj_get(ptr ${d.name}, ptr ${this.cstr(e.key)}, i64 ${keyBytes})`);
6841
+ B.line(`${m} = call ptr @scr_dyn_obj_get(ptr ${d.name}, ptr ${this.cstr(e.key)}, ${this.sizeType} ${keyBytes})`);
6181
6842
  B.line(`${has} = icmp ne ptr ${m}, null`);
6182
6843
  B.line(`store i1 ${has}, ptr ${slot}`);
6183
6844
  B.br(lj);
@@ -6195,8 +6856,8 @@ class LlEmitter {
6195
6856
  const len = B.tmp();
6196
6857
  const inR = B.tmp();
6197
6858
  B.line(`${lenp} = getelementptr inbounds i8, ptr ${d.name}, i64 16 ; ->v.arr.len`);
6198
- B.line(`${len} = load i64, ptr ${lenp}`);
6199
- B.line(`${inR} = icmp ugt i64 ${len}, ${e.key}`);
6859
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenp}`);
6860
+ B.line(`${inR} = icmp ugt ${this.sizeType} ${len}, ${e.key}`);
6200
6861
  B.line(`store i1 ${inR}, ptr ${slot}`);
6201
6862
  }
6202
6863
  B.br(lj);
@@ -6309,10 +6970,10 @@ class LlEmitter {
6309
6970
  const lj = B.newLabel("dts.j");
6310
6971
  B.condBr(isObj, lObj, lj);
6311
6972
  B.startBlock(lObj);
6312
- this.declare(`declare ptr @scr_dyn_obj_get(ptr, ptr, i64)`);
6973
+ this.declare(`declare ptr @scr_dyn_obj_get(ptr, ptr, ${this.sizeType})`);
6313
6974
  const m = B.tmp();
6314
6975
  const has = B.tmp();
6315
- B.line(`${m} = call ptr @scr_dyn_obj_get(ptr ${d.name}, ptr ${this.cstr("%error")}, i64 6)`);
6976
+ B.line(`${m} = call ptr @scr_dyn_obj_get(ptr ${d.name}, ptr ${this.cstr("%error")}, ${this.sizeType} 6)`);
6316
6977
  B.line(`${has} = icmp ne ptr ${m}, null`);
6317
6978
  B.line(`store i1 ${has}, ptr ${slot}`);
6318
6979
  B.br(lj);
@@ -6420,7 +7081,7 @@ class LlEmitter {
6420
7081
  }
6421
7082
  const helper = this.dyn.dynIterNHelper();
6422
7083
  const t = B.tmp();
6423
- B.line(`${t} = call ptr @${helper}(ptr ${v.name}, i64 ${e.count})`);
7084
+ B.line(`${t} = call ptr @${helper}(ptr ${v.name}, ${this.sizeType} ${e.count})`);
6424
7085
  const out = this.own({ name: t, type: e.type });
6425
7086
  this.emitPendingCheck();
6426
7087
  return out;
@@ -7174,13 +7835,13 @@ class LlEmitter {
7174
7835
  this.resolveThunkDefs.push(...d);
7175
7836
  return sym;
7176
7837
  }
7177
- /** Loads a dyn node's kind tag (i32 at +8) — the dyn expression tests'
7838
+ /** Loads a dyn node's kind tag after its size_t RC word — the dyn expression tests'
7178
7839
  * shared read. */
7179
7840
  dynKind(d) {
7180
7841
  const B = this.B;
7181
7842
  const p = B.tmp();
7182
7843
  const k = B.tmp();
7183
- B.line(`${p} = getelementptr inbounds i8, ptr ${d}, i64 8 ; ->kind`);
7844
+ B.line(`${p} = getelementptr inbounds i8, ptr ${d}, i64 ${this.sizeType === "i32" ? 4 : 8} ; ->kind`);
7184
7845
  B.line(`${k} = load i32, ptr ${p}`);
7185
7846
  return k;
7186
7847
  }
@@ -7486,11 +8147,11 @@ class LlEmitter {
7486
8147
  }
7487
8148
  this.declare(`declare ptr @scr_union_new_f64(i32, double)`);
7488
8149
  this.declare(`declare ptr @scr_union_new_ref(i32, ptr, ptr, ptr, ptr)`);
7489
- this.declare(`declare ptr @scr_str_new(ptr, i64)`);
7490
- this.declare(`declare i64 @strlen(ptr)`);
8150
+ this.declare(`declare ptr @scr_str_new(ptr, ${this.sizeType})`);
8151
+ this.declare(`declare ${this.sizeType} @strlen(ptr)`);
7491
8152
  this.declare(`declare ptr @scr_str_retain_v(ptr)`);
7492
8153
  this.declare(`declare void @scr_str_release_v(ptr)`);
7493
- this.resolveThunkDefs.push(`define internal void @${sym}(ptr %cb, i1 zeroext %has, double %code, ptr %sig) ${FN_ATTRS} { ; child exit (code, signal)`, `entry:`, ` %uslot = alloca ptr`, ` %sslot = alloca ptr`, ` br i1 %has, label %num, label %nonum`, `num:`, ` %u1 = call ptr @scr_union_new_f64(i32 ${f64Tag}, double %code)`, ` store ptr %u1, ptr %uslot`, ` br label %sigq`, `nonum:`, ` store ptr ${this.unitInstanceRef(codeParam.unionId, codeNullTag)}, ptr %uslot`, ` br label %sigq`, `sigq:`, ` %hassig = icmp ne ptr %sig, null`, ` br i1 %hassig, label %sigs, label %signull`, `sigs:`, ` %len = call i64 @strlen(ptr %sig)`, ` %ss = call ptr @scr_str_new(ptr %sig, i64 %len)`, ` %su = call ptr @scr_union_new_ref(i32 ${strTag}, ptr %ss, ptr @scr_str_retain_v, ptr @scr_str_release_v, ptr null)`, ` store ptr %su, ptr %sslot`, ` br label %go`, `signull:`, ` store ptr ${this.unitInstanceRef(sigParam.unionId, sigNullTag)}, ptr %sslot`, ` br label %go`, `go:`, ` %u = load ptr, ptr %uslot`, ` %s = load ptr, ptr %sslot`, ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`, ` call void %fn(ptr %cb, ptr %u, ptr %s)`, ` ret void`, `}`, ``);
8154
+ this.resolveThunkDefs.push(`define internal void @${sym}(ptr %cb, i1 zeroext %has, double %code, ptr %sig) ${FN_ATTRS} { ; child exit (code, signal)`, `entry:`, ` %uslot = alloca ptr`, ` %sslot = alloca ptr`, ` br i1 %has, label %num, label %nonum`, `num:`, ` %u1 = call ptr @scr_union_new_f64(i32 ${f64Tag}, double %code)`, ` store ptr %u1, ptr %uslot`, ` br label %sigq`, `nonum:`, ` store ptr ${this.unitInstanceRef(codeParam.unionId, codeNullTag)}, ptr %uslot`, ` br label %sigq`, `sigq:`, ` %hassig = icmp ne ptr %sig, null`, ` br i1 %hassig, label %sigs, label %signull`, `sigs:`, ` %len = call ${this.sizeType} @strlen(ptr %sig)`, ` %ss = call ptr @scr_str_new(ptr %sig, ${this.sizeType} %len)`, ` %su = call ptr @scr_union_new_ref(i32 ${strTag}, ptr %ss, ptr @scr_str_retain_v, ptr @scr_str_release_v, ptr null)`, ` store ptr %su, ptr %sslot`, ` br label %go`, `signull:`, ` store ptr ${this.unitInstanceRef(sigParam.unionId, sigNullTag)}, ptr %sslot`, ` br label %go`, `go:`, ` %u = load ptr, ptr %uslot`, ` %s = load ptr, ptr %sslot`, ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`, ` call void %fn(ptr %cb, ptr %u, ptr %s)`, ` ret void`, `}`, ``);
7494
8155
  return sym;
7495
8156
  }
7496
8157
  /** Interned per-union child-stream data adapter — emit-async.ts's
@@ -7521,8 +8182,8 @@ class LlEmitter {
7521
8182
  * POINTER-SIZE slots off the emit tuple's va_list (textual LLVM IR
7522
8183
  * cannot va_arg portably) and calls this function behind the wrapper
7523
8184
  * closure's fn. It re-types each slot to the listener's declared
7524
- * parameter (f64 from its i64 bit pattern, bool from the zero-extended
7525
- * slot, refcounted values retained — the callee owns +1 per the
8185
+ * parameter (f64/bool scalars load from the emit site's stack slot,
8186
+ * refcounted values retain the direct pointer — the callee owns +1 per the
7526
8187
  * universal convention) and calls the ORIGINAL listener held by the
7527
8188
  * wrapper's one capture box. A non-void result is discarded (refcounted
7528
8189
  * ones released) — Node ignores listener return values. Interned per
@@ -7556,11 +8217,11 @@ class LlEmitter {
7556
8217
  cbT.params.forEach((p, i) => {
7557
8218
  const ty = this.llType(p);
7558
8219
  if (ty === "double") {
7559
- d.push(` %x${i} = ptrtoint ptr %a${i} to i64`, ` %d${i} = bitcast i64 %x${i} to double`);
8220
+ d.push(` %d${i} = load double, ptr %a${i}`);
7560
8221
  passed.push(`double %d${i}`);
7561
8222
  }
7562
8223
  else if (ty === "i1") {
7563
- d.push(` %x${i} = ptrtoint ptr %a${i} to i64`, ` %b${i} = trunc i64 %x${i} to i1`);
8224
+ d.push(` %b${i} = load i1, ptr %a${i}`);
7564
8225
  passed.push(`i1 %b${i}`);
7565
8226
  }
7566
8227
  else if (isRefCounted(p)) {
@@ -7592,11 +8253,11 @@ class LlEmitter {
7592
8253
  * registration call it feeds always moves it into the registry. */
7593
8254
  wrapEmitterListener(target, adapterFn) {
7594
8255
  const B = this.B;
7595
- this.declare(`declare ptr @scr_closure_new(ptr, i64)`);
8256
+ this.declare(`declare ptr @scr_closure_new(ptr, ${this.sizeType})`);
7596
8257
  this.declare(`declare ptr @scr_box_new(i32)`);
7597
8258
  this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
7598
8259
  const ad = B.tmp();
7599
- B.line(`${ad} = call ptr @scr_closure_new(ptr @${adapterFn}, i64 1)`);
8260
+ B.line(`${ad} = call ptr @scr_closure_new(ptr @${adapterFn}, ${this.sizeType} 1)`);
7600
8261
  const bx = B.tmp();
7601
8262
  B.line(`${bx} = call ptr @scr_box_new(i32 4) ; SCR_BOX_FUNC`);
7602
8263
  const capsp = B.tmp();
@@ -7672,7 +8333,7 @@ class LlEmitter {
7672
8333
  throw new Error("llvm emitter bug: bound-close err union lacks its undefined arm");
7673
8334
  trampoline = `${sym}_cb`;
7674
8335
  this.resolveThunkDefs.push(`define internal void @${trampoline}(ptr %self) ${FN_ATTRS} { ; close cb: fire with no error`, `entry:`, ` %capsp = getelementptr inbounds %ScrClosure, ptr %self, i64 1`, ` %bx = load ptr, ptr %capsp`, ` %inner = call ptr @scr_box_get_ref(ptr %bx) ; +1`, ` %fnp = getelementptr inbounds %ScrClosure, ptr %inner, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`, ` call void %fn(ptr %inner, ptr ${this.unitInstanceRef(errParam.unionId, undefTag)})`, ` call void @scr_closure_release(ptr %inner)`, ` ret void`, `}`, ``);
7675
- this.declare(`declare ptr @scr_closure_new(ptr, i64)`);
8336
+ this.declare(`declare ptr @scr_closure_new(ptr, ${this.sizeType})`);
7676
8337
  this.declare(`declare ptr @scr_box_new(i32)`);
7677
8338
  this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
7678
8339
  }
@@ -7695,7 +8356,7 @@ class LlEmitter {
7695
8356
  ` %pv = load ptr, ptr %pp`,
7696
8357
  ];
7697
8358
  if (oneParam) {
7698
- d.push(` %reg = call ptr @scr_closure_new(ptr @${trampoline}, i64 1)`, ` %rbx = call ptr @scr_box_new(i32 4) ; SCR_BOX_FUNC`, ` %rcp = getelementptr inbounds %ScrClosure, ptr %reg, i64 1`, ` store ptr %rbx, ptr %rcp`, ` %pr = call ptr @scr_closure_retain_v(ptr %pv)`, ` call void @scr_box_set_ref(ptr %rbx, ptr %pr)`, ` store ptr %reg, ptr %regslot`);
8359
+ d.push(` %reg = call ptr @scr_closure_new(ptr @${trampoline}, ${this.sizeType} 1)`, ` %rbx = call ptr @scr_box_new(i32 4) ; SCR_BOX_FUNC`, ` %rcp = getelementptr inbounds %ScrClosure, ptr %reg, i64 1`, ` store ptr %rbx, ptr %rcp`, ` %pr = call ptr @scr_closure_retain_v(ptr %pv)`, ` call void @scr_box_set_ref(ptr %rbx, ptr %pr)`, ` store ptr %reg, ptr %regslot`);
7699
8360
  }
7700
8361
  else {
7701
8362
  d.push(` %reg = call ptr @scr_closure_retain_v(ptr %pv)`, ` store ptr %reg, ptr %regslot`);
@@ -7791,8 +8452,8 @@ class LlEmitter {
7791
8452
  ? "a 'data' listener declaring a Buffer chunk received a string (the stream has an encoding set)"
7792
8453
  : "a 'data' listener declaring a string chunk received a Buffer (call setEncoding, or declare the chunk as a Buffer)";
7793
8454
  const slot = p.kind === "bytes" ? "%a0" : "%a1";
7794
- this.declare(`declare void @scr_throw_error_msg(i32, ptr, i64)`);
7795
- d.push(` %miss = icmp eq ptr ${slot}, null`, ` br i1 %miss, label %bad, label %ok`, `bad:`, ` call void @scr_throw_error_msg(i32 1, ptr ${this.cstr(msg)}, i64 ${Buffer.byteLength(msg, "utf8")})`, ` call void @scr_closure_release(ptr %orig)`, ` ret void`, `ok:`, ` %r0 = call ptr ${retainSym(this, p)}(ptr ${slot})`);
8455
+ this.declare(`declare void @scr_throw_error_msg(i32, ptr, ${this.sizeType})`);
8456
+ d.push(` %miss = icmp eq ptr ${slot}, null`, ` br i1 %miss, label %bad, label %ok`, `bad:`, ` call void @scr_throw_error_msg(i32 1, ptr ${this.cstr(msg)}, ${this.sizeType} ${Buffer.byteLength(msg, "utf8")})`, ` call void @scr_closure_release(ptr %orig)`, ` ret void`, `ok:`, ` %r0 = call ptr ${retainSym(this, p)}(ptr ${slot})`);
7796
8457
  finish("%r0");
7797
8458
  d.push(`}`, ``);
7798
8459
  }
@@ -7988,13 +8649,13 @@ class LlEmitter {
7988
8649
  // The stream-owning completion closure (a 1-cap closure boxing the
7989
8650
  // retained stream) — shared by the typed and dyn done shapes.
7990
8651
  const doneClosure = (fnRef, tag) => {
7991
- this.declare(`declare ptr @scr_closure_new(ptr, i64)`);
8652
+ this.declare(`declare ptr @scr_closure_new(ptr, ${this.sizeType})`);
7992
8653
  this.declare(`declare ptr @scr_box_new_obj(ptr, ptr, ptr)`);
7993
8654
  this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
7994
8655
  this.declare(`declare ptr @scr_stream_retain_v(ptr)`);
7995
8656
  this.declare(`declare void @scr_stream_release_v(ptr)`);
7996
8657
  this.declare(`declare void @scr_stream_trace(ptr, ptr, ptr)`);
7997
- d.push(` %${tag}clo = call ptr @scr_closure_new(ptr ${fnRef}, i64 1)`, ` %${tag}bx = call ptr @scr_box_new_obj(ptr @scr_stream_retain_v, ptr @scr_stream_release_v, ptr @scr_stream_trace)`, ` %${tag}cp = getelementptr inbounds %ScrClosure, ptr %${tag}clo, i64 1`, ` store ptr %${tag}bx, ptr %${tag}cp`, ` %${tag}sr = call ptr @scr_stream_retain_v(ptr %s)`, ` call void @scr_box_set_ref(ptr %${tag}bx, ptr %${tag}sr)`);
8658
+ d.push(` %${tag}clo = call ptr @scr_closure_new(ptr ${fnRef}, ${this.sizeType} 1)`, ` %${tag}bx = call ptr @scr_box_new_obj(ptr @scr_stream_retain_v, ptr @scr_stream_release_v, ptr @scr_stream_trace)`, ` %${tag}cp = getelementptr inbounds %ScrClosure, ptr %${tag}clo, i64 1`, ` store ptr %${tag}bx, ptr %${tag}cp`, ` %${tag}sr = call ptr @scr_stream_retain_v(ptr %s)`, ` call void @scr_box_set_ref(ptr %${tag}bx, ptr %${tag}sr)`);
7998
8659
  return `%${tag}clo`;
7999
8660
  };
8000
8661
  for (let i = off; i < declared.length; i++) {
@@ -8023,10 +8684,10 @@ class LlEmitter {
8023
8684
  passed.push(`ptr %dc${i}`);
8024
8685
  }
8025
8686
  else if (isEncPos) {
8026
- this.declare(`declare ptr @scr_str_new(ptr, i64)`);
8687
+ this.declare(`declare ptr @scr_str_new(ptr, ${this.sizeType})`);
8027
8688
  this.declare(`declare ptr @scr_dyn_new_str(ptr)`);
8028
8689
  this.declare(`declare void @scr_str_release(ptr)`);
8029
- d.push(` %es${i} = call ptr @scr_str_new(ptr ${this.cstr("buffer")}, i64 6)`, ` %ed${i} = call ptr @scr_dyn_new_str(ptr %es${i})`, ` call void @scr_str_release(ptr %es${i})`);
8690
+ d.push(` %es${i} = call ptr @scr_str_new(ptr ${this.cstr("buffer")}, ${this.sizeType} 6)`, ` %ed${i} = call ptr @scr_dyn_new_str(ptr %es${i})`, ` call void @scr_str_release(ptr %es${i})`);
8030
8691
  passed.push(`ptr %ed${i}`);
8031
8692
  }
8032
8693
  else if (isErrPos) {
@@ -8048,7 +8709,7 @@ class LlEmitter {
8048
8709
  }
8049
8710
  else if (isDonePos) {
8050
8711
  const glue = `scr_stream_done_dyn_${kind}`;
8051
- this.declare(`declare ptr @${glue}(ptr, ptr, i64)`);
8712
+ this.declare(`declare ptr @${glue}(ptr, ptr, ${this.sizeType})`);
8052
8713
  this.declare(`declare ptr @scr_dyn_new_func(ptr, ptr, i32, ptr, ptr)`);
8053
8714
  const clo = doneClosure(`@${glue}`, `dd${i}`);
8054
8715
  const arity = kind === "t" || kind === "l" ? 2 : 1;
@@ -8068,8 +8729,8 @@ class LlEmitter {
8068
8729
  }
8069
8730
  else if (isEncPos) {
8070
8731
  // Node's encoding for decoded (Buffer) chunks is 'buffer'.
8071
- this.declare(`declare ptr @scr_str_new(ptr, i64)`);
8072
- d.push(` %en${i} = call ptr @scr_str_new(ptr ${this.cstr("buffer")}, i64 6)`);
8732
+ this.declare(`declare ptr @scr_str_new(ptr, ${this.sizeType})`);
8733
+ d.push(` %en${i} = call ptr @scr_str_new(ptr ${this.cstr("buffer")}, ${this.sizeType} 6)`);
8073
8734
  passed.push(`ptr %en${i}`);
8074
8735
  }
8075
8736
  else if (isErrPos) {
@@ -8872,7 +9533,7 @@ class LlEmitter {
8872
9533
  if (slot === undefined)
8873
9534
  return null;
8874
9535
  const index = this.B.tmp();
8875
- this.B.line(`${index} = load i64, ptr ${slot}`);
9536
+ this.B.line(`${index} = load ${this.sizeType}, ptr ${slot}`);
8876
9537
  return index;
8877
9538
  }
8878
9539
  /** Bounds-check a typed-array element index without an out-of-line call
@@ -8884,16 +9545,16 @@ class LlEmitter {
8884
9545
  const lenPtr = B.tmp();
8885
9546
  const len = B.tmp();
8886
9547
  B.line(`${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`);
8887
- B.line(`${len} = load i64, ptr ${lenPtr}`);
9548
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}`);
8888
9549
  if (integerIndex) {
8889
9550
  const inRange = B.tmp();
8890
- B.line(`${inRange} = icmp ult i64 ${index}, ${len}`);
9551
+ B.line(`${inRange} = icmp ult ${this.sizeType} ${index}, ${len}`);
8891
9552
  const invalid = B.newLabel("bytes.index.invalid");
8892
9553
  const valid = B.newLabel("bytes.index.valid");
8893
9554
  B.condBr(inRange, valid, invalid);
8894
9555
  B.startBlock(invalid);
8895
9556
  const indexF64 = B.tmp();
8896
- B.line(`${indexF64} = uitofp i64 ${index} to double`);
9557
+ B.line(`${indexF64} = uitofp ${this.sizeType} ${index} to double`);
8897
9558
  this.declare(`declare double @scr_bytes_get(ptr, double)`);
8898
9559
  B.line(`call double @scr_bytes_get(ptr ${receiver}, double ${indexF64})`);
8899
9560
  B.terminate("unreachable");
@@ -8904,7 +9565,7 @@ class LlEmitter {
8904
9565
  const nonnegative = B.tmp();
8905
9566
  const belowLen = B.tmp();
8906
9567
  const inRange = B.tmp();
8907
- B.line(`${lenF64} = uitofp i64 ${len} to double`);
9568
+ B.line(`${lenF64} = uitofp ${this.sizeType} ${len} to double`);
8908
9569
  B.line(`${nonnegative} = fcmp oge double ${index}, ${f64Lit(0)}`);
8909
9570
  B.line(`${belowLen} = fcmp olt double ${index}, ${lenF64}`);
8910
9571
  B.line(`${inRange} = and i1 ${nonnegative}, ${belowLen}`);
@@ -8916,8 +9577,8 @@ class LlEmitter {
8916
9577
  const idx = B.tmp();
8917
9578
  const roundTrip = B.tmp();
8918
9579
  const integral = B.tmp();
8919
- B.line(`${idx} = fptoui double ${index} to i64`);
8920
- B.line(`${roundTrip} = uitofp i64 ${idx} to double`);
9580
+ B.line(`${idx} = fptoui double ${index} to ${this.sizeType}`);
9581
+ B.line(`${roundTrip} = uitofp ${this.sizeType} ${idx} to double`);
8921
9582
  B.line(`${integral} = fcmp oeq double ${roundTrip}, ${index}`);
8922
9583
  B.condBr(integral, valid, invalid);
8923
9584
  B.startBlock(invalid);
@@ -8941,12 +9602,12 @@ class LlEmitter {
8941
9602
  const p = B.tmp();
8942
9603
  const len = B.tmp();
8943
9604
  B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`);
8944
- B.line(`${len} = load i64, ptr ${p}`);
9605
+ B.line(`${len} = load ${this.sizeType}, ptr ${p}`);
8945
9606
  const count = bytes && elem !== "u8" ? B.tmp() : len;
8946
9607
  if (count !== len)
8947
- B.line(`${count} = shl i64 ${len}, 2`);
9608
+ B.line(`${count} = shl ${this.sizeType} ${len}, 2`);
8948
9609
  const out = B.tmp();
8949
- B.line(`${out} = uitofp i64 ${count} to double`);
9610
+ B.line(`${out} = uitofp ${this.sizeType} ${count} to double`);
8950
9611
  return { name: out, type: F64 };
8951
9612
  }
8952
9613
  /** Kind-specialized typed-array load. */
@@ -8959,7 +9620,7 @@ class LlEmitter {
8959
9620
  const raw = B.tmp();
8960
9621
  const wide = B.tmp();
8961
9622
  const out = B.tmp();
8962
- B.line(`${p} = getelementptr inbounds i8, ptr ${data}, i64 ${idx}`);
9623
+ B.line(`${p} = getelementptr inbounds i8, ptr ${data}, ${this.sizeType} ${idx}`);
8963
9624
  B.line(`${raw} = load i8, ptr ${p}, align 1`);
8964
9625
  B.line(`${wide} = zext i8 ${raw} to i32`);
8965
9626
  B.line(`${out} = uitofp i32 ${wide} to double`);
@@ -8968,14 +9629,14 @@ class LlEmitter {
8968
9629
  if (elem === "f32") {
8969
9630
  const raw = B.tmp();
8970
9631
  const out = B.tmp();
8971
- B.line(`${p} = getelementptr inbounds float, ptr ${data}, i64 ${idx}`);
9632
+ B.line(`${p} = getelementptr inbounds float, ptr ${data}, ${this.sizeType} ${idx}`);
8972
9633
  B.line(`${raw} = load float, ptr ${p}, align 1`);
8973
9634
  B.line(`${out} = fpext float ${raw} to double`);
8974
9635
  return { name: out, type: F64 };
8975
9636
  }
8976
9637
  const raw = B.tmp();
8977
9638
  const out = B.tmp();
8978
- B.line(`${p} = getelementptr inbounds i32, ptr ${data}, i64 ${idx}`);
9639
+ B.line(`${p} = getelementptr inbounds i32, ptr ${data}, ${this.sizeType} ${idx}`);
8979
9640
  B.line(`${raw} = load i32, ptr ${p}, align 1`);
8980
9641
  B.line(`${out} = ${elem === "i32" ? "sitofp" : "uitofp"} i32 ${raw} to double`);
8981
9642
  return { name: out, type: F64 };
@@ -9053,18 +9714,18 @@ class LlEmitter {
9053
9714
  if (elem === "u8") {
9054
9715
  const byte = B.tmp();
9055
9716
  B.line(`${byte} = trunc i32 ${stored} to i8`);
9056
- B.line(`${p} = getelementptr inbounds i8, ptr ${data}, i64 ${idx}`);
9717
+ B.line(`${p} = getelementptr inbounds i8, ptr ${data}, ${this.sizeType} ${idx}`);
9057
9718
  B.line(`store i8 ${byte}, ptr ${p}, align 1`);
9058
9719
  return;
9059
9720
  }
9060
9721
  if (elem === "f32") {
9061
9722
  const narrowed = B.tmp();
9062
9723
  B.line(`${narrowed} = fptrunc double ${value} to float`);
9063
- B.line(`${p} = getelementptr inbounds float, ptr ${data}, i64 ${idx}`);
9724
+ B.line(`${p} = getelementptr inbounds float, ptr ${data}, ${this.sizeType} ${idx}`);
9064
9725
  B.line(`store float ${narrowed}, ptr ${p}, align 1`);
9065
9726
  return;
9066
9727
  }
9067
- B.line(`${p} = getelementptr inbounds i32, ptr ${data}, i64 ${idx}`);
9728
+ B.line(`${p} = getelementptr inbounds i32, ptr ${data}, ${this.sizeType} ${idx}`);
9068
9729
  B.line(`store i32 ${stored}, ptr ${p}, align 1`);
9069
9730
  }
9070
9731
  /** The bytesIntrinsic surface (emit-exprs.ts's switch, .ll flavored).
@@ -9698,7 +10359,7 @@ class LlEmitter {
9698
10359
  const commit = `${snapshot}_commit`;
9699
10360
  const check = this.dyn.dynCheckHelper(t);
9700
10361
  this.declare(`declare void @scr_arr_release(ptr)`);
9701
- this.resolveThunkDefs.push(`define internal void @${commit}(ptr %target, ptr %d) ${FN_ATTRS} { ; commit live stream element ${typeKey(t)}`, `entry:`, ` %next = call ptr @${check}(ptr %d, ptr null)`, ` %missing = icmp eq ptr %next, null`, ` br i1 %missing, label %done, label %swap`, `swap:`, ` %target_len_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 1`, ` %next_len_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 1`, ` %target_len = load i64, ptr %target_len_ptr`, ` %next_len = load i64, ptr %next_len_ptr`, ` store i64 %next_len, ptr %target_len_ptr`, ` store i64 %target_len, ptr %next_len_ptr`, ` %target_cap_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 2`, ` %next_cap_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 2`, ` %target_cap = load i64, ptr %target_cap_ptr`, ` %next_cap = load i64, ptr %next_cap_ptr`, ` store i64 %next_cap, ptr %target_cap_ptr`, ` store i64 %target_cap, ptr %next_cap_ptr`, ` %target_data_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 7`, ` %next_data_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 7`, ` %target_data = load ptr, ptr %target_data_ptr`, ` %next_data = load ptr, ptr %next_data_ptr`, ` store ptr %next_data, ptr %target_data_ptr`, ` store ptr %target_data, ptr %next_data_ptr`, ` call void @scr_arr_release(ptr %next)`, ` br label %done`, `done:`, ` ret void`, `}`, ``);
10362
+ this.resolveThunkDefs.push(`define internal void @${commit}(ptr %target, ptr %d) ${FN_ATTRS} { ; commit live stream element ${typeKey(t)}`, `entry:`, ` %next = call ptr @${check}(ptr %d, ptr null)`, ` %missing = icmp eq ptr %next, null`, ` br i1 %missing, label %done, label %swap`, `swap:`, ` %target_len_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 1`, ` %next_len_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 1`, ` %target_len = load ${this.sizeType}, ptr %target_len_ptr`, ` %next_len = load ${this.sizeType}, ptr %next_len_ptr`, ` store ${this.sizeType} %next_len, ptr %target_len_ptr`, ` store ${this.sizeType} %target_len, ptr %next_len_ptr`, ` %target_cap_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 2`, ` %next_cap_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 2`, ` %target_cap = load ${this.sizeType}, ptr %target_cap_ptr`, ` %next_cap = load ${this.sizeType}, ptr %next_cap_ptr`, ` store ${this.sizeType} %next_cap, ptr %target_cap_ptr`, ` store ${this.sizeType} %target_cap, ptr %next_cap_ptr`, ` %target_data_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 7`, ` %next_data_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 7`, ` %target_data = load ptr, ptr %target_data_ptr`, ` %next_data = load ptr, ptr %next_data_ptr`, ` store ptr %next_data, ptr %target_data_ptr`, ` store ptr %target_data, ptr %next_data_ptr`, ` call void @scr_arr_release(ptr %next)`, ` br label %done`, `done:`, ` ret void`, `}`, ``);
9702
10363
  return `@${commit}`;
9703
10364
  }
9704
10365
  if (t.kind !== "record")
@@ -9758,7 +10419,7 @@ class LlEmitter {
9758
10419
  }
9759
10420
  const sym = `sc_ldu_${this.liveDynUnionRefAdapters.size}`;
9760
10421
  this.liveDynUnionRefAdapters.set(key, sym);
9761
- this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, i64, ptr, ptr)`);
10422
+ this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, ${this.sizeType}, ptr, ptr)`);
9762
10423
  const adapters = new Map();
9763
10424
  for (const { arm, tag } of mutableArms) {
9764
10425
  const prefix = `${sym}_${tag}`;
@@ -9782,7 +10443,7 @@ class LlEmitter {
9782
10443
  const boxed = B.tmp();
9783
10444
  B.line(`${payloadPtr} = getelementptr inbounds %ScrUnion, ptr %u, i64 0, i32 5`);
9784
10445
  B.line(`${payload} = load ptr, ptr ${payloadPtr}`);
9785
- B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${payload}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(armKey)}, i64 ${Buffer.byteLength(armKey, "utf8")}, ptr @${adapter.snapshot}, ptr ${adapter.commit})`);
10446
+ B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${payload}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(armKey)}, ${this.sizeType} ${Buffer.byteLength(armKey, "utf8")}, ptr @${adapter.snapshot}, ptr ${adapter.commit})`);
9786
10447
  B.terminate(`ret ptr ${boxed}`);
9787
10448
  });
9788
10449
  B.startBlock(fallback);
@@ -9809,8 +10470,8 @@ class LlEmitter {
9809
10470
  const nested = this.streamTypedRefMaterializeAdapter(t, ctx);
9810
10471
  const rc = vAdapters(this, t);
9811
10472
  const key = typeKey(t);
9812
- this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, i64, ptr, ptr)`);
9813
- B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${value}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")}, ptr @${nested.snapshot}, ptr ${nested.commit})`);
10473
+ this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, ${this.sizeType}, ptr, ptr)`);
10474
+ B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${value}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(key)}, ${this.sizeType} ${Buffer.byteLength(key, "utf8")}, ptr @${nested.snapshot}, ptr ${nested.commit})`);
9814
10475
  return boxed;
9815
10476
  }
9816
10477
  /** LLVM twin of the C stream materializer: mutable reference children
@@ -9865,7 +10526,7 @@ class LlEmitter {
9865
10526
  }
9866
10527
  else {
9867
10528
  this.declare(`declare ptr @scr_dyn_new_obj()`);
9868
- this.declare(`declare void @scr_dyn_obj_set(ptr, ptr, i64, ptr)`);
10529
+ this.declare(`declare void @scr_dyn_obj_set(ptr, ptr, ${this.sizeType}, ptr)`);
9869
10530
  const out = B.tmp();
9870
10531
  B.line(`${out} = call ptr @scr_dyn_new_obj()`);
9871
10532
  const byName = new Map(shape.fields.map((field) => [field.name, field]));
@@ -9887,7 +10548,7 @@ class LlEmitter {
9887
10548
  fieldValue = boolValue;
9888
10549
  }
9889
10550
  const boxed = this.streamTypedRefBoxValue(B, field.type, fieldValue, ctx);
9890
- B.line(`call void @scr_dyn_obj_set(ptr ${out}, ptr ${this.cstr(field.name)}, i64 ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`);
10551
+ B.line(`call void @scr_dyn_obj_set(ptr ${out}, ptr ${this.cstr(field.name)}, ${this.sizeType} ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`);
9891
10552
  }
9892
10553
  B.terminate(`ret ptr ${out}`);
9893
10554
  }
@@ -9994,7 +10655,7 @@ class LlEmitter {
9994
10655
  B.line(`${boxed} = call ptr @${this.dyn.toDynHelper(elem)}(ptr ${value})`);
9995
10656
  }
9996
10657
  else {
9997
- this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, i64, ptr, ptr)`);
10658
+ this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, ${this.sizeType}, ptr, ptr)`);
9998
10659
  const boxedSlot = B.slot();
9999
10660
  B.entryAllocas.push(`${boxedSlot} = alloca ptr`);
10000
10661
  const tagPtr = B.tmp();
@@ -10016,7 +10677,7 @@ class LlEmitter {
10016
10677
  const armBoxed = B.tmp();
10017
10678
  B.line(`${payloadPtr} = getelementptr inbounds %ScrUnion, ptr ${value}, i64 0, i32 5`);
10018
10679
  B.line(`${payload} = load ptr, ptr ${payloadPtr}`);
10019
- B.line(`${armBoxed} = call ptr @scr_dyn_new_typed_ref(ptr ${payload}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(armKey)}, i64 ${Buffer.byteLength(armKey, "utf8")}, ptr @${armSnapshot}, ptr ${armCommit})`);
10680
+ B.line(`${armBoxed} = call ptr @scr_dyn_new_typed_ref(ptr ${payload}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(armKey)}, ${this.sizeType} ${Buffer.byteLength(armKey, "utf8")}, ptr @${armSnapshot}, ptr ${armCommit})`);
10020
10681
  B.line(`store ptr ${armBoxed}, ptr ${boxedSlot}`);
10021
10682
  B.br(join);
10022
10683
  this.resolveThunkDefs.push(`define internal ptr @${armSnapshot}(ptr %p) ${FN_ATTRS} { ; materialize stream union arm ${armKey}`, `entry:`, ` %d = call ptr @${this.dyn.toDynHelper(arm)}(ptr %p)`, ` ret ptr %d`, `}`, ``);
@@ -10043,8 +10704,8 @@ class LlEmitter {
10043
10704
  commit = this.streamTypedRefCommitAdapter(elem, snapshot);
10044
10705
  this.resolveThunkDefs.push(`define internal ptr @${snapshot}(ptr %p) ${FN_ATTRS} { ; materialize stream element ${key}`, `entry:`, ` %d = call ptr @${this.dyn.toDynHelper(elem)}(ptr %p)`, ` ret ptr %d`, `}`, ``);
10045
10706
  }
10046
- this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, i64, ptr, ptr)`);
10047
- B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${value}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${keyPtr}, i64 ${Buffer.byteLength(key, "utf8")}, ptr @${snapshot}, ptr ${commit})`);
10707
+ this.declare(`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, ${this.sizeType}, ptr, ptr)`);
10708
+ B.line(`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${value}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${keyPtr}, ${this.sizeType} ${Buffer.byteLength(key, "utf8")}, ptr @${snapshot}, ptr ${commit})`);
10048
10709
  }
10049
10710
  else {
10050
10711
  boxed = B.tmp();
@@ -10785,16 +11446,16 @@ class LlEmitter {
10785
11446
  const tag6 = infoDef.arms.findIndex((a) => a.kind === "record" && this.recordsById.get(a.shapeId)?.fields.find((f) => f.name === "scopeid")?.type.kind === "f64");
10786
11447
  const tag4 = 1 - tag6;
10787
11448
  this.declare(`declare ptr @scr_os_ifaddrs()`);
10788
- this.declare(`declare i64 @scr_os_ifaddrs_count(ptr)`);
10789
- this.declare(`declare ptr @scr_os_ifaddrs_name(ptr, i64)`);
10790
- this.declare(`declare ptr @scr_os_ifaddrs_address(ptr, i64)`);
10791
- this.declare(`declare ptr @scr_os_ifaddrs_netmask(ptr, i64)`);
10792
- this.declare(`declare ptr @scr_os_ifaddrs_family(ptr, i64)`);
10793
- this.declare(`declare ptr @scr_os_ifaddrs_mac(ptr, i64)`);
10794
- this.declare(`declare zeroext i1 @scr_os_ifaddrs_internal(ptr, i64)`);
10795
- this.declare(`declare zeroext i1 @scr_os_ifaddrs_ipv6(ptr, i64)`);
10796
- this.declare(`declare ptr @scr_os_ifaddrs_cidr(ptr, i64)`);
10797
- this.declare(`declare double @scr_os_ifaddrs_scopeid(ptr, i64)`);
11449
+ this.declare(`declare ${this.sizeType} @scr_os_ifaddrs_count(ptr)`);
11450
+ this.declare(`declare ptr @scr_os_ifaddrs_name(ptr, ${this.sizeType})`);
11451
+ this.declare(`declare ptr @scr_os_ifaddrs_address(ptr, ${this.sizeType})`);
11452
+ this.declare(`declare ptr @scr_os_ifaddrs_netmask(ptr, ${this.sizeType})`);
11453
+ this.declare(`declare ptr @scr_os_ifaddrs_family(ptr, ${this.sizeType})`);
11454
+ this.declare(`declare ptr @scr_os_ifaddrs_mac(ptr, ${this.sizeType})`);
11455
+ this.declare(`declare zeroext i1 @scr_os_ifaddrs_internal(ptr, ${this.sizeType})`);
11456
+ this.declare(`declare zeroext i1 @scr_os_ifaddrs_ipv6(ptr, ${this.sizeType})`);
11457
+ this.declare(`declare ptr @scr_os_ifaddrs_cidr(ptr, ${this.sizeType})`);
11458
+ this.declare(`declare double @scr_os_ifaddrs_scopeid(ptr, ${this.sizeType})`);
10798
11459
  this.declare(`declare void @scr_os_ifaddrs_free(ptr)`);
10799
11460
  this.declare(`declare ptr @scr_union_new_ref(i32, ptr, ptr, ptr, ptr)`);
10800
11461
  this.declare(`declare ptr @scr_union_retain_v(ptr)`);
@@ -10813,11 +11474,11 @@ class LlEmitter {
10813
11474
  const snap = B.tmp();
10814
11475
  const cnt = B.tmp();
10815
11476
  B.line(`${snap} = call ptr @scr_os_ifaddrs()`);
10816
- B.line(`${cnt} = call i64 @scr_os_ifaddrs_count(ptr ${snap})`);
11477
+ B.line(`${cnt} = call ${this.sizeType} @scr_os_ifaddrs_count(ptr ${snap})`);
10817
11478
  const iSlot = B.slot();
10818
11479
  const rowSlot = B.slot();
10819
- B.entryAllocas.push(`${iSlot} = alloca i64`, `${rowSlot} = alloca ptr`);
10820
- B.line(`store i64 0, ptr ${iSlot}`);
11480
+ B.entryAllocas.push(`${iSlot} = alloca ${this.sizeType}`, `${rowSlot} = alloca ptr`);
11481
+ B.line(`store ${this.sizeType} 0, ptr ${iSlot}`);
10821
11482
  const lc = B.newLabel("ni.c");
10822
11483
  const lb = B.newLabel("ni.b");
10823
11484
  const le = B.newLabel("ni.e");
@@ -10825,12 +11486,12 @@ class LlEmitter {
10825
11486
  B.startBlock(lc);
10826
11487
  const i = B.tmp();
10827
11488
  const cont = B.tmp();
10828
- B.line(`${i} = load i64, ptr ${iSlot}`);
10829
- B.line(`${cont} = icmp ult i64 ${i}, ${cnt}`);
11489
+ B.line(`${i} = load ${this.sizeType}, ptr ${iSlot}`);
11490
+ B.line(`${cont} = icmp ult ${this.sizeType} ${i}, ${cnt}`);
10830
11491
  B.condBr(cont, lb, le);
10831
11492
  B.startBlock(lb);
10832
11493
  const isV6 = B.tmp();
10833
- B.line(`${isV6} = call zeroext i1 @scr_os_ifaddrs_ipv6(ptr ${snap}, i64 ${i})`);
11494
+ B.line(`${isV6} = call zeroext i1 @scr_os_ifaddrs_ipv6(ptr ${snap}, ${this.sizeType} ${i})`);
10834
11495
  const l6 = B.newLabel("ni.v6");
10835
11496
  const l4 = B.newLabel("ni.v4");
10836
11497
  const lRow = B.newLabel("ni.r");
@@ -10857,14 +11518,14 @@ class LlEmitter {
10857
11518
  ["mac", "scr_os_ifaddrs_mac"],
10858
11519
  ]) {
10859
11520
  const v = B.tmp();
10860
- B.line(`${v} = call ptr @${sym}(ptr ${snap}, i64 ${i}) ; +1`);
11521
+ B.line(`${v} = call ptr @${sym}(ptr ${snap}, ${this.sizeType} ${i}) ; +1`);
10861
11522
  B.line(`store ptr ${v}, ptr ${this.recordFieldPtr(r, t.shapeId, field).ptr}`);
10862
11523
  }
10863
11524
  const internal = B.tmp();
10864
- B.line(`${internal} = call zeroext i1 @scr_os_ifaddrs_internal(ptr ${snap}, i64 ${i})`);
11525
+ B.line(`${internal} = call zeroext i1 @scr_os_ifaddrs_internal(ptr ${snap}, ${this.sizeType} ${i})`);
10865
11526
  this.storeField(this.recordFieldPtr(r, t.shapeId, "internal").ptr, { kind: "bool" }, internal);
10866
11527
  const cs = B.tmp();
10867
- B.line(`${cs} = call ptr @scr_os_ifaddrs_cidr(ptr ${snap}, i64 ${i}) ; +1 or null`);
11528
+ B.line(`${cs} = call ptr @scr_os_ifaddrs_cidr(ptr ${snap}, ${this.sizeType} ${i}) ; +1 or null`);
10868
11529
  const hasCidr = B.tmp();
10869
11530
  B.line(`${hasCidr} = icmp ne ptr ${cs}, null`);
10870
11531
  const lcs = B.newLabel("ni.cs");
@@ -10889,7 +11550,7 @@ class LlEmitter {
10889
11550
  B.line(`store ptr ${cv}, ptr ${this.recordFieldPtr(r, t.shapeId, "cidr").ptr}`);
10890
11551
  if (v6) {
10891
11552
  const sc = B.tmp();
10892
- B.line(`${sc} = call double @scr_os_ifaddrs_scopeid(ptr ${snap}, i64 ${i})`);
11553
+ B.line(`${sc} = call double @scr_os_ifaddrs_scopeid(ptr ${snap}, ${this.sizeType} ${i})`);
10893
11554
  this.storeField(this.recordFieldPtr(r, t.shapeId, "scopeid").ptr, F64, sc);
10894
11555
  }
10895
11556
  else {
@@ -10915,7 +11576,7 @@ class LlEmitter {
10915
11576
  const row = B.tmp();
10916
11577
  B.line(`${row} = load ptr, ptr ${rowSlot}`);
10917
11578
  const nm = B.tmp();
10918
- B.line(`${nm} = call ptr @scr_os_ifaddrs_name(ptr ${snap}, i64 ${i}) ; +1`);
11579
+ B.line(`${nm} = call ptr @scr_os_ifaddrs_name(ptr ${snap}, ${this.sizeType} ${i}) ; +1`);
10919
11580
  const cell = B.tmp();
10920
11581
  B.line(`${cell} = call ptr @scr_map_get_str_ref(ptr ${ovf}, ptr ${nm})`);
10921
11582
  const hasCell = B.tmp();
@@ -10951,8 +11612,8 @@ class LlEmitter {
10951
11612
  B.line(`call void @scr_arr_release(ptr ${rows})`);
10952
11613
  B.line(`call void @scr_str_release(ptr ${nm})`);
10953
11614
  const i2 = B.tmp();
10954
- B.line(`${i2} = add i64 ${i}, 1`);
10955
- B.line(`store i64 ${i2}, ptr ${iSlot}`);
11615
+ B.line(`${i2} = add ${this.sizeType} ${i}, 1`);
11616
+ B.line(`store ${this.sizeType} ${i2}, ptr ${iSlot}`);
10956
11617
  B.br(lc);
10957
11618
  B.startBlock(le);
10958
11619
  B.line(`call void @scr_os_ifaddrs_free(ptr ${snap})`);
@@ -10969,21 +11630,21 @@ class LlEmitter {
10969
11630
  const recT = e.type.elem;
10970
11631
  const path = this.emitExpr(e.args[0]);
10971
11632
  this.declare(`declare ptr @scr_fs_scandir(ptr)`);
10972
- this.declare(`declare i64 @scr_fs_scandir_count(ptr)`);
10973
- this.declare(`declare ptr @scr_fs_scandir_name(ptr, i64)`);
10974
- this.declare(`declare double @scr_fs_scandir_type(ptr, i64)`);
11633
+ this.declare(`declare ${this.sizeType} @scr_fs_scandir_count(ptr)`);
11634
+ this.declare(`declare ptr @scr_fs_scandir_name(ptr, ${this.sizeType})`);
11635
+ this.declare(`declare double @scr_fs_scandir_type(ptr, ${this.sizeType})`);
10975
11636
  this.declare(`declare void @scr_fs_scandir_free(ptr)`);
10976
11637
  const snap = B.tmp();
10977
11638
  B.line(`${snap} = call ptr @scr_fs_scandir(ptr ${path.name})`);
10978
11639
  this.emitPendingCheck();
10979
11640
  const cnt = B.tmp();
10980
- B.line(`${cnt} = call i64 @scr_fs_scandir_count(ptr ${snap})`);
11641
+ B.line(`${cnt} = call ${this.sizeType} @scr_fs_scandir_count(ptr ${snap})`);
10981
11642
  const arr = B.tmp();
10982
11643
  B.line(`${arr} = ${arrNewCall(this, recT, cnt)}`);
10983
11644
  const out = this.own({ name: arr, type: e.type });
10984
11645
  const iSlot = B.slot();
10985
- B.entryAllocas.push(`${iSlot} = alloca i64`);
10986
- B.line(`store i64 0, ptr ${iSlot}`);
11646
+ B.entryAllocas.push(`${iSlot} = alloca ${this.sizeType}`);
11647
+ B.line(`store ${this.sizeType} 0, ptr ${iSlot}`);
10987
11648
  const lc = B.newLabel("sd.c");
10988
11649
  const lb = B.newLabel("sd.b");
10989
11650
  const le = B.newLabel("sd.e");
@@ -10991,23 +11652,23 @@ class LlEmitter {
10991
11652
  B.startBlock(lc);
10992
11653
  const i = B.tmp();
10993
11654
  const cont = B.tmp();
10994
- B.line(`${i} = load i64, ptr ${iSlot}`);
10995
- B.line(`${cont} = icmp ult i64 ${i}, ${cnt}`);
11655
+ B.line(`${i} = load ${this.sizeType}, ptr ${iSlot}`);
11656
+ B.line(`${cont} = icmp ult ${this.sizeType} ${i}, ${cnt}`);
10996
11657
  B.condBr(cont, lb, le);
10997
11658
  B.startBlock(lb);
10998
11659
  const row = B.tmp();
10999
11660
  B.line(`${row} = call ptr @${mangleRecordNew(recT.shapeId)}()`);
11000
11661
  const dt = B.tmp();
11001
- B.line(`${dt} = call double @scr_fs_scandir_type(ptr ${snap}, i64 ${i})`);
11662
+ B.line(`${dt} = call double @scr_fs_scandir_type(ptr ${snap}, ${this.sizeType} ${i})`);
11002
11663
  this.storeField(this.recordFieldPtr(row, recT.shapeId, "%dtype").ptr, F64, dt);
11003
11664
  const nm = B.tmp();
11004
- B.line(`${nm} = call ptr @scr_fs_scandir_name(ptr ${snap}, i64 ${i}) ; +1`);
11665
+ B.line(`${nm} = call ptr @scr_fs_scandir_name(ptr ${snap}, ${this.sizeType} ${i}) ; +1`);
11005
11666
  B.line(`store ptr ${nm}, ptr ${this.recordFieldPtr(row, recT.shapeId, "name").ptr}`);
11006
11667
  B.line(`store ptr ${this.retainValue(path.name, STRING)}, ptr ${this.recordFieldPtr(row, recT.shapeId, "parentPath").ptr}`);
11007
11668
  this.arrPush(arr, "ref", row); // push takes ownership of the row
11008
11669
  const i2 = B.tmp();
11009
- B.line(`${i2} = add i64 ${i}, 1`);
11010
- B.line(`store i64 ${i2}, ptr ${iSlot}`);
11670
+ B.line(`${i2} = add ${this.sizeType} ${i}, 1`);
11671
+ B.line(`store ${this.sizeType} ${i2}, ptr ${iSlot}`);
11011
11672
  B.br(lc);
11012
11673
  B.startBlock(le);
11013
11674
  B.line(`call void @scr_fs_scandir_free(ptr ${snap})`);
@@ -11077,22 +11738,19 @@ class LlEmitter {
11077
11738
  }
11078
11739
  if (e.fn === "emitter.emit") {
11079
11740
  // The variadic dispatch: the event's unified tuple rides the C
11080
- // variadic tail POINTER-CLASSED (f64 as its i64 bit pattern, bool
11081
- // zero-extended — scr_runtime.h's fixed-shim contract; the emitted
11082
- // adapters re-type on the way out), every argument borrowed. May
11741
+ // variadic tail POINTER-CLASSED. Scalar values ride pointers to
11742
+ // call-lived stack slots, while reference values ride directly; the
11743
+ // fixed shim can therefore read one pointer-width slot per argument
11744
+ // on both wasm32 and native targets. Every argument is borrowed. May
11083
11745
  // throw (listeners run inside).
11084
11746
  const args = e.args.map((a) => this.emitExpr(a));
11085
11747
  const tuple = args.slice(2).map((a) => {
11086
11748
  const ty = this.llType(a.type);
11087
- if (ty === "double") {
11088
- const x = B.tmp();
11089
- B.line(`${x} = bitcast double ${a.name} to i64`);
11090
- return `i64 ${x}`;
11091
- }
11092
- if (ty === "i1") {
11093
- const x = B.tmp();
11094
- B.line(`${x} = zext i1 ${a.name} to i64`);
11095
- return `i64 ${x}`;
11749
+ if (ty === "double" || ty === "i1") {
11750
+ const slot = B.slot();
11751
+ B.entryAllocas.push(`${slot} = alloca ${ty} ; EventEmitter scalar argument`);
11752
+ B.line(`store ${ty} ${a.name}, ptr ${slot}`);
11753
+ return `ptr ${slot}`;
11096
11754
  }
11097
11755
  return `ptr ${a.name}`;
11098
11756
  });
@@ -11681,13 +12339,13 @@ class LlEmitter {
11681
12339
  throw new Error("llvm emitter bug: net.serverCloseBind result not a func");
11682
12340
  const args = e.args.map((a) => this.emitExpr(a));
11683
12341
  const fnSym = this.closeBindThunkFor(e.type.params[0], e.type.ret.kind === "netServer");
11684
- this.declare(`declare ptr @scr_closure_new(ptr, i64)`);
12342
+ this.declare(`declare ptr @scr_closure_new(ptr, ${this.sizeType})`);
11685
12343
  this.declare(`declare ptr @scr_box_new_obj(ptr, ptr, ptr)`);
11686
12344
  this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
11687
12345
  this.declare(`declare ptr @scr_net_server_retain_v(ptr)`);
11688
12346
  this.declare(`declare void @scr_net_server_release_v(ptr)`);
11689
12347
  const bound = B.tmp();
11690
- B.line(`${bound} = call ptr @scr_closure_new(ptr @${fnSym}, i64 1)`);
12348
+ B.line(`${bound} = call ptr @scr_closure_new(ptr @${fnSym}, ${this.sizeType} 1)`);
11691
12349
  const bx = B.tmp();
11692
12350
  B.line(`${bx} = call ptr @scr_box_new_obj(ptr @scr_net_server_retain_v, ptr @scr_net_server_release_v, ptr null)`);
11693
12351
  const capp = B.tmp();
@@ -11988,22 +12646,22 @@ class LlEmitter {
11988
12646
  if (ca.type.kind === "string") {
11989
12647
  caData = B.tmp();
11990
12648
  B.line(`${caLenPtr} = getelementptr inbounds %ScrStr, ptr ${ca.name}, i64 0, i32 1`);
11991
- B.line(`${caLen} = load i64, ptr ${caLenPtr}`);
11992
- B.line(`${caData} = getelementptr inbounds i8, ptr ${ca.name}, i64 24`);
12649
+ B.line(`${caLen} = load ${this.sizeType}, ptr ${caLenPtr}`);
12650
+ B.line(`${caData} = getelementptr inbounds i8, ptr ${ca.name}, i64 ${this.abiOffset(24, 12)}`);
11993
12651
  }
11994
12652
  else if (ca.type.kind === "bytes" && ca.type.elem === "u8") {
11995
12653
  const caDataPtr = B.tmp();
11996
12654
  caData = B.tmp();
11997
- B.line(`${caLenPtr} = getelementptr inbounds i8, ptr ${ca.name}, i64 8`);
11998
- B.line(`${caLen} = load i64, ptr ${caLenPtr}`);
11999
- B.line(`${caDataPtr} = getelementptr inbounds i8, ptr ${ca.name}, i64 24`);
12655
+ B.line(`${caLenPtr} = getelementptr inbounds i8, ptr ${ca.name}, i64 ${this.abiOffset(8, 4)}`);
12656
+ B.line(`${caLen} = load ${this.sizeType}, ptr ${caLenPtr}`);
12657
+ B.line(`${caDataPtr} = getelementptr inbounds i8, ptr ${ca.name}, i64 ${this.abiOffset(24, 12)}`);
12000
12658
  B.line(`${caData} = load ptr, ptr ${caDataPtr}`);
12001
12659
  }
12002
12660
  else {
12003
12661
  throw new Error(`llvm emitter bug: ${e.fn} CA is not a string or Buffer`);
12004
12662
  }
12005
- callArgs = [...callArgs.slice(0, 8), `ptr ${caData}`, `i64 ${caLen}`];
12006
- this.declare(`declare ptr @scr_https_request(ptr, double, ptr, ptr, double, ptr, i1 zeroext, i1 zeroext, ptr, i64, ptr, ptr)`);
12663
+ callArgs = [...callArgs.slice(0, 8), `ptr ${caData}`, `${this.sizeType} ${caLen}`];
12664
+ this.declare(`declare ptr @scr_https_request(ptr, double, ptr, ptr, double, ptr, i1 zeroext, i1 zeroext, ptr, ${this.sizeType}, ptr, ptr)`);
12007
12665
  }
12008
12666
  else {
12009
12667
  const decls = head.map((a) => (this.llType(a.type) === "i1" ? "i1 zeroext" : this.llType(a.type)));