@scriptc/compiler 0.0.22 → 0.0.24

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 (93) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +139 -14
  2. package/dist/backend/cc.d.ts +13 -0
  3. package/dist/backend/cc.js +11 -0
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-async.d.ts +4 -0
  6. package/dist/backend/emission/emit-async.js +34 -2
  7. package/dist/backend/emission/emit-async.js.map +1 -1
  8. package/dist/backend/emission/emit-exprs.d.ts +10 -0
  9. package/dist/backend/emission/emit-exprs.js +272 -22
  10. package/dist/backend/emission/emit-exprs.js.map +1 -1
  11. package/dist/backend/emission/emit-shapes.js +1 -0
  12. package/dist/backend/emission/emit-shapes.js.map +1 -1
  13. package/dist/backend/emission/emit-stmts.js +36 -9
  14. package/dist/backend/emission/emit-stmts.js.map +1 -1
  15. package/dist/backend/emission/emit-types.js +14 -1
  16. package/dist/backend/emission/emit-types.js.map +1 -1
  17. package/dist/backend/emission/emitter.d.ts +19 -1
  18. package/dist/backend/emission/emitter.js +81 -2
  19. package/dist/backend/emission/emitter.js.map +1 -1
  20. package/dist/backend/llvm/emitter.js +578 -36
  21. package/dist/backend/llvm/emitter.js.map +1 -1
  22. package/dist/backend/llvm/shapes.js +12 -3
  23. package/dist/backend/llvm/shapes.js.map +1 -1
  24. package/dist/backend/mangle.d.ts +3 -0
  25. package/dist/backend/mangle.js +5 -0
  26. package/dist/backend/mangle.js.map +1 -1
  27. package/dist/compat/fetch-profile.d.ts +10 -0
  28. package/dist/compat/fetch-profile.js +55 -6
  29. package/dist/compat/fetch-profile.js.map +1 -1
  30. package/dist/coverage/surface-manifest.js +16 -1
  31. package/dist/coverage/surface-manifest.js.map +1 -1
  32. package/dist/diagnostics/diagnostic.d.ts +10 -0
  33. package/dist/diagnostics/diagnostic.js +27 -1
  34. package/dist/diagnostics/diagnostic.js.map +1 -1
  35. package/dist/frontend/lowering/lower-builtins.d.ts +17 -16
  36. package/dist/frontend/lowering/lower-builtins.js +817 -117
  37. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  38. package/dist/frontend/lowering/lower-calls.js +3 -1
  39. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  40. package/dist/frontend/lowering/lower-classes.js +28 -8
  41. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  42. package/dist/frontend/lowering/lower-containers.d.ts +2 -1
  43. package/dist/frontend/lowering/lower-containers.js +99 -32
  44. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  45. package/dist/frontend/lowering/lower-exprs.d.ts +1 -1
  46. package/dist/frontend/lowering/lower-exprs.js +78 -6
  47. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  48. package/dist/frontend/lowering/lower-generators.js +7 -1
  49. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  50. package/dist/frontend/lowering/lower-inspect.js +59 -1
  51. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  52. package/dist/frontend/lowering/lower-island.d.ts +21 -0
  53. package/dist/frontend/lowering/lower-island.js +284 -10
  54. package/dist/frontend/lowering/lower-island.js.map +1 -1
  55. package/dist/frontend/lowering/lower-server.d.ts +5 -0
  56. package/dist/frontend/lowering/lower-server.js +1 -1
  57. package/dist/frontend/lowering/lower-server.js.map +1 -1
  58. package/dist/frontend/lowering/lower-stmts.d.ts +6 -2
  59. package/dist/frontend/lowering/lower-stmts.js +53 -14
  60. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  61. package/dist/frontend/lowering/lowerer.d.ts +3 -2
  62. package/dist/frontend/lowering/lowerer.js +17 -8
  63. package/dist/frontend/lowering/lowerer.js.map +1 -1
  64. package/dist/frontend/lowering/surfaces.js +108 -25
  65. package/dist/frontend/lowering/surfaces.js.map +1 -1
  66. package/dist/frontend/program.d.ts +4 -4
  67. package/dist/frontend/program.js +264 -46
  68. package/dist/frontend/program.js.map +1 -1
  69. package/dist/frontend/resolve.d.ts +10 -0
  70. package/dist/frontend/resolve.js +37 -0
  71. package/dist/frontend/resolve.js.map +1 -1
  72. package/dist/frontend/ts7/enums.d.ts +9 -0
  73. package/dist/frontend/ts7/enums.js +8 -6
  74. package/dist/frontend/ts7/enums.js.map +1 -1
  75. package/dist/frontend/ts7/program.js +4 -1
  76. package/dist/frontend/ts7/program.js.map +1 -1
  77. package/dist/frontend/types.d.ts +7 -4
  78. package/dist/frontend/types.js +103 -8
  79. package/dist/frontend/types.js.map +1 -1
  80. package/dist/index.js +8 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/ir/integer-loops.d.ts +23 -0
  83. package/dist/ir/integer-loops.js +82 -0
  84. package/dist/ir/integer-loops.js.map +1 -0
  85. package/dist/ir/nodes.d.ts +104 -28
  86. package/dist/ir/nodes.js +89 -1
  87. package/dist/ir/nodes.js.map +1 -1
  88. package/dist/ir/validate.js +136 -25
  89. package/dist/ir/validate.js.map +1 -1
  90. package/dist/library/fence-eval.js +4 -3
  91. package/dist/library/fence-eval.js.map +1 -1
  92. package/package.json +2 -2
  93. package/surface-manifest.json +186 -29
@@ -1,4 +1,5 @@
1
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";
2
+ import { matchIntegerBytesForLoop } from "../../ir/integer-loops.js";
2
3
  import { computeMayThrow } from "../emission/may-throw.js";
3
4
  import { mangleArgPack, mangleAsyncSpawn, mangleClassNew, mangleClassObj, mangleClassRetain, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenResThunk, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordNew, mangleRecordStruct, mangleResolveThunk, mangleTrampoline, mangleVtStruct, mangleWrapper } from "../mangle.js";
4
5
  import { BlockBuilder } from "./blocks.js";
@@ -38,6 +39,7 @@ function llStrBytes(text) {
38
39
  * Throwing members (MAY_THROW_LIB_FNS) and everything unlisted refuse by
39
40
  * name. */
40
41
  const LIB_FN_SYMS = {
42
+ "util.parseArgs": "scr_util_parse_args",
41
43
  "math.maxArr": "scr_math_max_arr",
42
44
  "math.minArr": "scr_math_min_arr",
43
45
  "math.min": "scr_math_min",
@@ -129,13 +131,36 @@ const LIB_FN_SYMS = {
129
131
  "process.stderrWrite": "scr_process_stderr_write",
130
132
  "process.isTTY": "scr_process_is_tty",
131
133
  "date.now": "scr_date_now",
134
+ "date.newNow": "scr_date_now",
135
+ "date.newMs": "scr_date_new_ms",
136
+ "date.newString": "scr_date_parse_get_time",
137
+ "date.getTime": "scr_date_get_time",
138
+ "date.valueOf": "scr_date_get_time",
132
139
  "date.parseGetTime": "scr_date_parse_get_time",
133
140
  "date.utc": "scr_date_utc",
141
+ "date.getFullYear": "scr_date_get_full_year_local",
142
+ "date.getUTCFullYear": "scr_date_get_full_year_utc",
143
+ "date.getMonth": "scr_date_get_month_local",
144
+ "date.getUTCMonth": "scr_date_get_month_utc",
145
+ "date.getDate": "scr_date_get_date_local",
146
+ "date.getUTCDate": "scr_date_get_date_utc",
147
+ "date.getDay": "scr_date_get_day_local",
148
+ "date.getUTCDay": "scr_date_get_day_utc",
149
+ "date.getHours": "scr_date_get_hours_local",
150
+ "date.getUTCHours": "scr_date_get_hours_utc",
151
+ "date.getMinutes": "scr_date_get_minutes_local",
152
+ "date.getUTCMinutes": "scr_date_get_minutes_utc",
153
+ "date.getSeconds": "scr_date_get_seconds_local",
154
+ "date.getUTCSeconds": "scr_date_get_seconds_utc",
155
+ "date.getMilliseconds": "scr_date_get_milliseconds",
156
+ "date.getUTCMilliseconds": "scr_date_get_milliseconds",
157
+ "date.getTimezoneOffset": "scr_date_get_timezone_offset",
134
158
  "fs.existsSync": "scr_fs_exists",
135
159
  // ── the throwing slice (MAY_THROW_LIB_FNS members): the generic path
136
160
  // emits the standard pending check after each — emit-exprs.ts's finish.
137
161
  "process.cpuPrevValidate": "scr_cpu_prev_validate",
138
162
  "date.toISOString": "scr_date_to_iso",
163
+ "date.toISOStringValue": "scr_date_to_iso",
139
164
  "process.chdir": "scr_process_chdir",
140
165
  "fs.writeFileSync": "scr_fs_write_file",
141
166
  "fs.appendFileSync": "scr_fs_append_file",
@@ -151,6 +176,7 @@ const LIB_FN_SYMS = {
151
176
  "fs.unlinkSync": "scr_fs_unlink",
152
177
  "fs.chmodSync": "scr_fs_chmod",
153
178
  "fs.copyFileSync": "scr_fs_copyfile",
179
+ "fs.renameSync": "scr_fs_rename",
154
180
  "fs.accessSync": "scr_fs_access",
155
181
  // node:assert (scr_assert.c): all args borrowed; failures throw the
156
182
  // catchable AssertionError. The never-throwing members ride along.
@@ -213,6 +239,8 @@ const LIB_FN_SYMS = {
213
239
  "fs.lstatSync": "scr_fs_lstat",
214
240
  "fs.openSync": "scr_fs_open",
215
241
  "fs.readSync": "scr_fs_read_sync",
242
+ "fs.writeSync": "scr_fs_write_sync",
243
+ "fs.writeStrSync": "scr_fs_write_str_sync",
216
244
  "fs.closeSync": "scr_fs_close",
217
245
  // string_decoder (scr_bytes.c): stateless helpers over (enc, pending,
218
246
  // chunk) — never throw.
@@ -223,8 +251,12 @@ const LIB_FN_SYMS = {
223
251
  "stats.isDirectory": "scr_stats_is_dir",
224
252
  "stats.isSymbolicLink": "scr_stats_is_symlink",
225
253
  "stats.size": "scr_stats_size",
254
+ "stats.blocks": "scr_stats_blocks",
255
+ "stats.nlink": "scr_stats_nlink",
256
+ "stats.atimeMs": "scr_stats_atime_ms",
226
257
  "stats.mtimeMs": "scr_stats_mtime_ms",
227
258
  "text.decode": "scr_text_decode",
259
+ "text.decodeLegacy": "scr_text_decode_legacy",
228
260
  "zlib.deflateSync": "scr_zlib_deflate",
229
261
  "zlib.inflateSync": "scr_zlib_inflate",
230
262
  // The CA-store unit. get/set throw (an unknown type name, a
@@ -332,15 +364,25 @@ const LIB_FN_SYMS = {
332
364
  // so it is special-cased in emitLibCall (the fs.readFileSync pattern).
333
365
  "fsp.readFileBytes": "scr_fsp_read_file_bytes",
334
366
  "fsp.writeFile": "scr_fsp_write_file",
367
+ "fsp.writeFileMode": "scr_fsp_write_file_mode",
335
368
  "fsp.mkdir": "scr_fsp_mkdir",
336
369
  "fsp.mkdirMode": "scr_fsp_mkdir_mode",
337
370
  "fsp.mkdirRecursive": "scr_fsp_mkdir_recursive",
338
371
  "fsp.mkdirRecursiveMode": "scr_fsp_mkdir_recursive_mode",
339
372
  "fsp.unlink": "scr_fsp_unlink",
340
373
  "fsp.chmod": "scr_fsp_chmod",
374
+ "fsp.rename": "scr_fsp_rename",
341
375
  "fsp.readdir": "scr_fsp_readdir",
342
376
  "fsp.rm": "scr_fsp_rm",
343
377
  "fsp.stat": "scr_fsp_stat",
378
+ "fsp.open": "scr_fsp_open",
379
+ "fileHandle.fd": "scr_file_handle_fd",
380
+ "fileHandle.close": "scr_file_handle_close_promise",
381
+ "fileHandle.readFile": "scr_file_handle_read_file_promise",
382
+ "fileHandle.readFileBytes": "scr_file_handle_read_file_bytes_promise",
383
+ "fileHandle.writeFile": "scr_file_handle_write_file_promise",
384
+ "fileHandle.writeFileBytes": "scr_file_handle_write_file_bytes_promise",
385
+ "fileHandle.stat": "scr_file_handle_stat_promise",
344
386
  "sym.new": "scr_sym_new",
345
387
  "sym.for": "scr_sym_for",
346
388
  "sym.toString": "scr_sym_to_string",
@@ -352,9 +394,11 @@ const LIB_FN_SYMS = {
352
394
  // the generic path emits the standard pending check). typeof and the
353
395
  // ambient-this read never throw. The fs dyn read is the sync-fs story.
354
396
  "fetch.start": "scr_fetch_static",
397
+ "fetch.responseNew": "scr_fetch_response_new",
355
398
  "fetch.responseJson": "scr_fetch_response_json",
356
399
  "fetch.responseText": "scr_fetch_response_text",
357
400
  "fetch.responseBytes": "scr_fetch_response_bytes",
401
+ "fetch.abortControllerNew": "scr_fetch_abort_controller_new",
358
402
  "fetch.abortTimeout": "scr_fetch_abort_timeout",
359
403
  "fetch.abortNow": "scr_fetch_abort_now",
360
404
  "fetch.abortAny": "scr_fetch_abort_any",
@@ -829,6 +873,8 @@ class LlEmitter {
829
873
  jumpTargets = [];
830
874
  currentLocals = new Map();
831
875
  captureIds = new Set();
876
+ /** Active canonical byte-loop induction bindings: local id → i64 slot. */
877
+ integerLoopBindings = new Map();
832
878
  /** Enclosing try-with-FINALLY regions, innermost last: a `return`
833
879
  * inside one runs every crossed finally (innermost first) before the
834
880
  * actual ret — the C emitter's pending-return path, with the finally
@@ -940,6 +986,7 @@ class LlEmitter {
940
986
  llType(t) {
941
987
  switch (t.kind) {
942
988
  case "f64":
989
+ case "date":
943
990
  return "double";
944
991
  case "bool":
945
992
  return "i1";
@@ -957,6 +1004,7 @@ class LlEmitter {
957
1004
  case "url":
958
1005
  case "searchParams":
959
1006
  case "stats":
1007
+ case "fileHandle":
960
1008
  case "spawnRes":
961
1009
  case "child":
962
1010
  case "childStream":
@@ -1192,6 +1240,11 @@ class LlEmitter {
1192
1240
  // scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24,
1193
1241
  // payload at 32).
1194
1242
  `%ScrCaught = type { i64, i32, double, i8, ptr, ptr, ptr, ptr }`,
1243
+ // ScrBytes { rc, len, elem(i32+pad), data, backing }. Indexed
1244
+ // typed-array access GEPs through this directly: the IR type already
1245
+ // fixes elem, so the hot path needs neither a runtime kind load nor
1246
+ // the generic scr_bytes_get/set call.
1247
+ `%ScrBytes = type { i64, i64, i32, ptr, ptr }`,
1195
1248
  // The capture box { rc, kind, obj_retain, obj_release, obj_trace,
1196
1249
  // slot } — TDZ reads peek the payload slot (offset 40) directly.
1197
1250
  `%ScrBox = type { i64, i32, ptr, ptr, ptr, i64 }`,
@@ -1696,6 +1749,7 @@ class LlEmitter {
1696
1749
  tr.push(` call void @scr_promise_fulfill_void(ptr %pr)`);
1697
1750
  break;
1698
1751
  case "f64":
1752
+ case "date":
1699
1753
  this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
1700
1754
  tr.push(` call void @scr_promise_fulfill_f64(ptr %pr, double %r)`);
1701
1755
  break;
@@ -1851,6 +1905,7 @@ class LlEmitter {
1851
1905
  tr.push(` br label %done ; void body: the done value is undefined (NONE)`);
1852
1906
  break;
1853
1907
  case "f64":
1908
+ case "date":
1854
1909
  this.declare(`declare void @scr_gen_out_f64(ptr, double)`);
1855
1910
  tr.push(` call void @scr_gen_out_f64(ptr %g, double %r)`, ` br label %done`);
1856
1911
  break;
@@ -2072,7 +2127,7 @@ class LlEmitter {
2072
2127
  const t = this.currentReturnType;
2073
2128
  if (t.kind === "void")
2074
2129
  this.B.terminate("ret void");
2075
- else if (t.kind === "f64")
2130
+ else if (t.kind === "f64" || t.kind === "date")
2076
2131
  this.B.terminate(`ret double ${f64Lit(0)}`);
2077
2132
  else if (t.kind === "bool")
2078
2133
  this.B.terminate("ret i1 false");
@@ -2104,7 +2159,10 @@ class LlEmitter {
2104
2159
  emitThrowValue(v) {
2105
2160
  const B = this.B;
2106
2161
  const t = v.type;
2107
- if (t.kind === "f64") {
2162
+ if (t.kind === "date") {
2163
+ throw new Error("LLVM emitter bug: Date throw reached backend");
2164
+ }
2165
+ else if (t.kind === "f64") {
2108
2166
  this.declare(`declare void @scr_throw_f64(double)`);
2109
2167
  B.line(`call void @scr_throw_f64(double ${v.name})`);
2110
2168
  }
@@ -2153,6 +2211,8 @@ class LlEmitter {
2153
2211
  B.line(`${t} = icmp ne i64 ${len}, 0`);
2154
2212
  return t;
2155
2213
  }
2214
+ case "date":
2215
+ return "true";
2156
2216
  case "array":
2157
2217
  case "record":
2158
2218
  case "object":
@@ -2167,6 +2227,7 @@ class LlEmitter {
2167
2227
  case "url":
2168
2228
  case "searchParams":
2169
2229
  case "stats":
2230
+ case "fileHandle":
2170
2231
  case "spawnRes":
2171
2232
  case "child":
2172
2233
  case "childStream":
@@ -2247,6 +2308,9 @@ class LlEmitter {
2247
2308
  B.line(`store i1 ${t}, ptr ${slot}`);
2248
2309
  break;
2249
2310
  }
2311
+ case "date":
2312
+ B.line(`store i1 true, ptr ${slot} ; Date objects are truthy`);
2313
+ break;
2250
2314
  case "array":
2251
2315
  case "record":
2252
2316
  case "object":
@@ -2261,6 +2325,7 @@ class LlEmitter {
2261
2325
  case "url":
2262
2326
  case "searchParams":
2263
2327
  case "stats":
2328
+ case "fileHandle":
2264
2329
  case "spawnRes":
2265
2330
  case "child":
2266
2331
  case "childStream":
@@ -2595,6 +2660,7 @@ class LlEmitter {
2595
2660
  this.jumpTargets = [];
2596
2661
  this.currentLocals = new Map(fn.locals.map((l) => [l.id, l]));
2597
2662
  this.captureIds = new Set((fn.captures ?? []).map((c) => c.localId));
2663
+ this.integerLoopBindings.clear();
2598
2664
  this.chainSlots.clear();
2599
2665
  this.finallyStack = [];
2600
2666
  this.tryStack = [];
@@ -2791,13 +2857,17 @@ class LlEmitter {
2791
2857
  }
2792
2858
  case "bytesSet": {
2793
2859
  // Typed-array element write: same evaluation order as arraySet;
2794
- // the value is a scalar (the runtime coerces JS-exactly), so no
2795
- // ownership moves. Any invalid index traps — no append.
2796
- const arr = this.emitExpr(s.arr);
2797
- const idx = this.emitExpr(s.index);
2860
+ // the value is a scalar (the kind-specific inline path coerces
2861
+ // JS-exactly), so no ownership moves. Any invalid index traps — no
2862
+ // append. IrBytesElem is static, so never rediscover it through the
2863
+ // generic runtime switch in a hot loop.
2864
+ const arr = this.emitBytesReceiver(s.arr, [s.index, s.value]);
2865
+ const integerIndex = this.emitIntegerLoopIndex(s.index);
2866
+ const idx = integerIndex ?? this.emitExpr(s.index).name;
2798
2867
  const v = this.emitExpr(s.value);
2799
- this.declare(`declare void @scr_bytes_set(ptr, double, double)`);
2800
- B.line(`call void @scr_bytes_set(ptr ${arr.name}, double ${idx.name}, double ${v.name})`);
2868
+ if (s.arr.type.kind !== "bytes")
2869
+ throw new Error("llvm emitter bug: bytesSet on non-bytes");
2870
+ this.emitBytesSet(s.arr.type.elem, arr.name, idx, v.name, integerIndex !== null);
2801
2871
  break;
2802
2872
  }
2803
2873
  case "fieldSet":
@@ -3054,9 +3124,18 @@ class LlEmitter {
3054
3124
  case "for": {
3055
3125
  // The init's scope wraps the whole loop (break/continue must NOT
3056
3126
  // release it — scopeDepth captured after the push, C parity).
3127
+ const integerLoop = matchIntegerBytesForLoop(s, this.currentLocals);
3057
3128
  this.scopes.push([]);
3058
- if (s.init)
3129
+ let integerSlot = null;
3130
+ if (integerLoop) {
3131
+ 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}`);
3134
+ this.integerLoopBindings.set(integerLoop.localId, integerSlot);
3135
+ }
3136
+ else if (s.init) {
3059
3137
  this.emitStmt(s.init);
3138
+ }
3060
3139
  const lc = B.newLabel("loop.c");
3061
3140
  const lb = B.newLabel("loop.b");
3062
3141
  const le = B.newLabel("loop.e");
@@ -3070,7 +3149,19 @@ class LlEmitter {
3070
3149
  const lu = s.update || freshens ? B.newLabel("loop.u") : lc;
3071
3150
  B.br(lc);
3072
3151
  B.startBlock(lc);
3073
- if (s.cond)
3152
+ if (integerLoop && integerSlot) {
3153
+ const receiver = this.emitBytesReceiver(integerLoop.limitReceiver, []);
3154
+ const lenPtr = B.tmp();
3155
+ const len = B.tmp();
3156
+ const index = B.tmp();
3157
+ const inBounds = B.tmp();
3158
+ 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}`);
3162
+ B.condBr(inBounds, lb, le);
3163
+ }
3164
+ else if (s.cond)
3074
3165
  B.condBr(this.emitCondition(s.cond), lb, le);
3075
3166
  else
3076
3167
  B.br(lb);
@@ -3102,12 +3193,21 @@ class LlEmitter {
3102
3193
  // The wrapper scope's entry releases whatever the slot points
3103
3194
  // to at loop exit — now the freshest binding. Nothing to fix.
3104
3195
  }
3105
- if (s.update)
3196
+ if (integerLoop && integerSlot) {
3197
+ const old = B.tmp();
3198
+ 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}`);
3202
+ }
3203
+ else if (s.update)
3106
3204
  this.emitStmt(s.update);
3107
3205
  B.br(lc);
3108
3206
  }
3109
3207
  B.startBlock(le);
3110
3208
  this.releaseScope(this.scopes.pop());
3209
+ if (integerLoop)
3210
+ this.integerLoopBindings.delete(integerLoop.localId);
3111
3211
  break;
3112
3212
  }
3113
3213
  case "forOf": {
@@ -3585,6 +3685,14 @@ class LlEmitter {
3585
3685
  // tag-only); one reaching the generic dispatch escaped its wrap.
3586
3686
  throw new Error(`llvm emitter bug: bare unitLit '${e.unit}'`);
3587
3687
  case "varRef": {
3688
+ const integerSlot = this.integerLoopBindings.get(e.localId);
3689
+ if (integerSlot !== undefined) {
3690
+ const integer = B.tmp();
3691
+ const number = B.tmp();
3692
+ B.line(`${integer} = load i64, ptr ${integerSlot}`);
3693
+ B.line(`${number} = uitofp i64 ${integer} to double`);
3694
+ return { name: number, type: e.type };
3695
+ }
3588
3696
  const b = this.binding(e.localId);
3589
3697
  if (b.kind === "boxed") {
3590
3698
  // Reads go through the shared binding; ref kinds come out +1.
@@ -5280,7 +5388,7 @@ class LlEmitter {
5280
5388
  throw new Error("llvm emitter bug: yieldExpr with no operand (frontend fills undefined)");
5281
5389
  const v = this.emitExpr(e.value);
5282
5390
  const yt = e.value.type;
5283
- if (yt.kind === "f64") {
5391
+ if (yt.kind === "f64" || yt.kind === "date") {
5284
5392
  this.declare(`declare void @scr_gen_yield_f64(double)`);
5285
5393
  B.line(`call void @scr_gen_yield_f64(double ${v.name})`);
5286
5394
  }
@@ -5300,7 +5408,7 @@ class LlEmitter {
5300
5408
  return { name: "", type: e.type };
5301
5409
  }
5302
5410
  const t = B.tmp();
5303
- if (e.type.kind === "f64") {
5411
+ if (e.type.kind === "f64" || e.type.kind === "date") {
5304
5412
  this.declare(`declare double @scr_gen_take_in_f64()`);
5305
5413
  B.line(`${t} = call double @scr_gen_take_in_f64()`);
5306
5414
  return { name: t, type: e.type };
@@ -5332,7 +5440,7 @@ class LlEmitter {
5332
5440
  store(a.name, e.arg.type);
5333
5441
  };
5334
5442
  const parkIn = (name, t) => {
5335
- if (t.kind === "f64") {
5443
+ if (t.kind === "f64" || t.kind === "date") {
5336
5444
  this.declare(`declare void @scr_gen_in_f64(ptr, double)`);
5337
5445
  B.line(`call void @scr_gen_in_f64(ptr ${g.name}, double ${name})`);
5338
5446
  }
@@ -5378,7 +5486,7 @@ class LlEmitter {
5378
5486
  }
5379
5487
  else {
5380
5488
  sendArg((name, t) => {
5381
- if (t.kind === "f64") {
5489
+ if (t.kind === "f64" || t.kind === "date") {
5382
5490
  this.declare(`declare void @scr_gen_ret_f64(ptr, double)`);
5383
5491
  B.line(`call void @scr_gen_ret_f64(ptr ${g.name}, double ${name})`);
5384
5492
  }
@@ -5431,7 +5539,7 @@ class LlEmitter {
5431
5539
  return { name: "", type: e.type };
5432
5540
  }
5433
5541
  const t = B.tmp();
5434
- if (e.type.kind === "f64") {
5542
+ if (e.type.kind === "f64" || e.type.kind === "date") {
5435
5543
  this.declare(`declare double @scr_await_f64(ptr)`);
5436
5544
  B.line(`${t} = call double @scr_await_f64(ptr ${pr.name})`);
5437
5545
  }
@@ -5511,7 +5619,7 @@ class LlEmitter {
5511
5619
  B.startBlock(lp);
5512
5620
  const peek = this.unionPeek(u.name);
5513
5621
  let awaited;
5514
- if (inner.kind === "f64") {
5622
+ if (inner.kind === "f64" || inner.kind === "date") {
5515
5623
  this.declare(`declare double @scr_await_f64(ptr)`);
5516
5624
  const x = B.tmp();
5517
5625
  B.line(`${x} = call double @scr_await_f64(ptr ${peek})`);
@@ -5588,7 +5696,7 @@ class LlEmitter {
5588
5696
  return out;
5589
5697
  }
5590
5698
  let resolve;
5591
- const kindNums = { f64: 0, bool: 1, string: 2, void: 3 };
5699
+ const kindNums = { f64: 0, date: 0, bool: 1, string: 2, void: 3 };
5592
5700
  const kindNum = kindNums[inner.kind];
5593
5701
  if (kindNum !== undefined) {
5594
5702
  this.declare(`declare ptr @scr_make_resolve(ptr, i32)`);
@@ -5634,7 +5742,7 @@ class LlEmitter {
5634
5742
  this.declare(`declare ptr @scr_make_reject(ptr)`);
5635
5743
  const p = B.tmp();
5636
5744
  B.line(`${p} = call ptr @scr_promise_new()`);
5637
- const kindNums = { f64: 0, bool: 1, string: 2, void: 3 };
5745
+ const kindNums = { f64: 0, date: 0, bool: 1, string: 2, void: 3 };
5638
5746
  const kindNum = kindNums[inner.kind];
5639
5747
  const resolve = B.tmp();
5640
5748
  if (kindNum !== undefined) {
@@ -5749,7 +5857,7 @@ class LlEmitter {
5749
5857
  }
5750
5858
  const v = this.emitExpr(e.args[0]);
5751
5859
  const t = e.args[0].type;
5752
- if (t.kind === "f64") {
5860
+ if (t.kind === "f64" || t.kind === "date") {
5753
5861
  this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
5754
5862
  B.line(`call void @scr_promise_fulfill_f64(ptr ${p}, double ${v.name})`);
5755
5863
  }
@@ -7790,6 +7898,52 @@ class LlEmitter {
7790
7898
  this.resolveThunkDefs.push(...d);
7791
7899
  return sym;
7792
7900
  }
7901
+ /** The fs.rename callback adapter: the runtime passes a borrowed Error
7902
+ * or NULL and the thunk constructs the callback's owned, program-shaped
7903
+ * Error | null union (or a dyn error/null in checkJs programs). */
7904
+ fsRenameThunkFor(cbT) {
7905
+ if (cbT.params.length === 0) {
7906
+ this.declare(`declare void @scr_fs_rename_thunk0(ptr, ptr)`);
7907
+ return "scr_fs_rename_thunk0";
7908
+ }
7909
+ const key = `fsren:${typeKey(cbT)}`;
7910
+ let sym = this.resolveThunks.get(key);
7911
+ if (sym)
7912
+ return sym;
7913
+ sym = `sc_fsren_${this.resolveThunks.size}`;
7914
+ this.resolveThunks.set(key, sym);
7915
+ const param = cbT.params[0];
7916
+ const d = [
7917
+ `define internal void @${sym}(ptr %cb, ptr %err) ${FN_ATTRS} { ; fs.rename callback ${typeKey(cbT)}`,
7918
+ `entry:`,
7919
+ ` %slot = alloca ptr`,
7920
+ ` %has = icmp ne ptr %err, null`,
7921
+ ` br i1 %has, label %yes, label %no`,
7922
+ `yes:`,
7923
+ ];
7924
+ if (param.kind === "dyn") {
7925
+ this.declare(`declare ptr @scr_dyn_from_error(ptr)`);
7926
+ this.declare(`declare ptr @scr_dyn_new_null()`);
7927
+ d.push(` %de = call ptr @scr_dyn_from_error(ptr %err)`, ` store ptr %de, ptr %slot`, ` br label %go`, `no:`, ` %dn = call ptr @scr_dyn_new_null()`, ` store ptr %dn, ptr %slot`);
7928
+ }
7929
+ else {
7930
+ if (param.kind !== "union")
7931
+ throw new Error("llvm emitter bug: fs.rename error param not a union");
7932
+ const def = this.unionsById.get(param.unionId);
7933
+ const errTag = def ? def.arms.findIndex((a) => a.kind === "object" && a.className === "%Error") : -1;
7934
+ const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1;
7935
+ if (errTag < 0 || nullTag < 0)
7936
+ throw new Error("llvm emitter bug: fs.rename error union lacks its arms");
7937
+ this.declare(`declare ptr @scr_error_retain(ptr)`);
7938
+ this.declare(`declare ptr @scr_error_retain_v(ptr)`);
7939
+ this.declare(`declare void @scr_error_release_v(ptr)`);
7940
+ this.declare(`declare ptr @scr_union_new_ref(i32, ptr, ptr, ptr, ptr)`);
7941
+ d.push(` %er = call ptr @scr_error_retain(ptr %err)`, ` %eu = call ptr @scr_union_new_ref(i32 ${errTag}, ptr %er, ptr @scr_error_retain_v, ptr @scr_error_release_v, ptr null)`, ` store ptr %eu, ptr %slot`, ` br label %go`, `no:`, ` store ptr ${this.unitInstanceRef(param.unionId, nullTag)}, ptr %slot`);
7942
+ }
7943
+ d.push(` br label %go`, `go:`, ` %arg = load ptr, ptr %slot`, ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`, ` call void %fn(ptr %cb, ptr %arg)`, ` ret void`, `}`, ``);
7944
+ this.resolveThunkDefs.push(...d);
7945
+ return sym;
7946
+ }
7793
7947
  /** The stream option-callback invoke adapter for one (kind, callback
7794
7948
  * type) — emit-async.ts's streamCbThunkFor: the runtime calls the
7795
7949
  * user's read/write/final/destroy/transform/flush (or the finished/
@@ -8095,7 +8249,7 @@ class LlEmitter {
8095
8249
  case "trimEnd":
8096
8250
  return call("scr_str_trim_end", "ptr (ptr)", `ptr ${r.name}`, "ptr", true);
8097
8251
  case "split":
8098
- return call("scr_str_split", "ptr (ptr, ptr)", `ptr ${r.name}, ptr ${args[0].name}`, "ptr", true);
8252
+ return call("scr_str_split_limit", "ptr (ptr, ptr, double)", `ptr ${r.name}, ptr ${args[0].name}, double ${args[1].name}`, "ptr", true);
8099
8253
  case "padStart":
8100
8254
  return call("scr_str_pad_start", "ptr (ptr, double, ptr)", `ptr ${r.name}, double ${args[0].name}, ptr ${args[1].name}`, "ptr", true);
8101
8255
  case "padEnd":
@@ -8167,6 +8321,33 @@ class LlEmitter {
8167
8321
  B.line(`${t} = call double @scr_arr_len(ptr ${r.name})`);
8168
8322
  return { name: t, type: e.type };
8169
8323
  }
8324
+ case "unshift": {
8325
+ // Evaluate every argument before the first mutation, then insert
8326
+ // from right to left so the final front order is source order.
8327
+ const vs = e.args.map((a) => this.emitExpr(a));
8328
+ if (acc === "ref")
8329
+ vs.forEach((v) => this.moveTemp(v));
8330
+ this.declare(`declare double @scr_arr_unshift_${acc}(ptr, ${accArg})`);
8331
+ let last = "";
8332
+ for (let i = vs.length - 1; i >= 0; i--) {
8333
+ last = B.tmp();
8334
+ B.line(`${last} = call double @scr_arr_unshift_${acc}(ptr ${r.name}, ${accTy} ${vs[i].name})`);
8335
+ }
8336
+ if (last !== "")
8337
+ return { name: last, type: e.type };
8338
+ this.declare(`declare double @scr_arr_len(ptr)`);
8339
+ const t = B.tmp();
8340
+ B.line(`${t} = call double @scr_arr_len(ptr ${r.name})`);
8341
+ return { name: t, type: e.type };
8342
+ }
8343
+ case "unshiftSpread": {
8344
+ // The runtime snapshots the borrowed source and handles self-spread.
8345
+ const src = this.emitExpr(e.args[0]);
8346
+ this.declare(`declare double @scr_arr_unshift_spread(ptr, ptr)`);
8347
+ const t = B.tmp();
8348
+ B.line(`${t} = call double @scr_arr_unshift_spread(ptr ${r.name}, ptr ${src.name})`);
8349
+ return { name: t, type: e.type };
8350
+ }
8170
8351
  case "pop": {
8171
8352
  // Ownership of a refcounted element moves OUT of the array to
8172
8353
  // this temp (+1 to us, the runtime does not release it).
@@ -8226,6 +8407,13 @@ class LlEmitter {
8226
8407
  B.line(`${t} = call ptr @scr_arr_to_reversed(ptr ${r.name})`);
8227
8408
  return this.own({ name: t, type: e.type });
8228
8409
  }
8410
+ case "reverse": {
8411
+ // Mutates in place and returns the same receiver as a fresh +1.
8412
+ this.declare(`declare ptr @scr_arr_reverse(ptr)`);
8413
+ const t = B.tmp();
8414
+ B.line(`${t} = call ptr @scr_arr_reverse(ptr ${r.name})`);
8415
+ return this.own({ name: t, type: e.type });
8416
+ }
8229
8417
  case "toSpliced": {
8230
8418
  const start = this.emitExpr(e.args[0]);
8231
8419
  const count = this.emitExpr(e.args[1]);
@@ -8625,6 +8813,260 @@ class LlEmitter {
8625
8813
  }
8626
8814
  }
8627
8815
  }
8816
+ /** Whether an index/value expression can overwrite the bytes receiver
8817
+ * binding. Kept deliberately conservative; uncertain or calling shapes
8818
+ * retain the ordinary owned-receiver snapshot. */
8819
+ isStableBytesOperand(e, receiverLocalId) {
8820
+ switch (e.kind) {
8821
+ case "numLit":
8822
+ case "boolLit":
8823
+ case "varRef":
8824
+ case "incDec":
8825
+ return true;
8826
+ case "assignExpr":
8827
+ // A bytes assignment nested under truthiness/ternary can still
8828
+ // produce the numeric index/value expected by the outer access.
8829
+ return (e.localId !== receiverLocalId &&
8830
+ this.isStableBytesOperand(e.value, receiverLocalId));
8831
+ case "bin":
8832
+ return (this.isStableBytesOperand(e.left, receiverLocalId) &&
8833
+ this.isStableBytesOperand(e.right, receiverLocalId));
8834
+ case "unary":
8835
+ case "toBool":
8836
+ return this.isStableBytesOperand(e.operand, receiverLocalId);
8837
+ case "logical":
8838
+ return (this.isStableBytesOperand(e.left, receiverLocalId) &&
8839
+ this.isStableBytesOperand(e.right, receiverLocalId));
8840
+ case "ternary":
8841
+ return (this.isStableBytesOperand(e.cond, receiverLocalId) &&
8842
+ this.isStableBytesOperand(e.then, receiverLocalId) &&
8843
+ this.isStableBytesOperand(e.else_, receiverLocalId));
8844
+ case "bytesIntrinsic":
8845
+ return ((e.method === "get" || e.method === "length" || e.method === "byteLength") &&
8846
+ e.receiver.kind === "varRef" &&
8847
+ e.args.every((arg) => this.isStableBytesOperand(arg, receiverLocalId)));
8848
+ default:
8849
+ return false;
8850
+ }
8851
+ }
8852
+ /** Borrow a direct, unboxed bytes binding when later operands are stable.
8853
+ * Its scope/global owner keeps it alive through the access. */
8854
+ emitBytesReceiver(receiver, following) {
8855
+ if (receiver.kind === "varRef" &&
8856
+ following.every((operand) => this.isStableBytesOperand(operand, receiver.localId))) {
8857
+ const b = this.binding(receiver.localId);
8858
+ if (b.kind !== "boxed") {
8859
+ const value = this.B.tmp();
8860
+ this.B.line(`${value} = load ptr, ptr ${b.slot}`);
8861
+ return { name: value, type: receiver.type };
8862
+ }
8863
+ }
8864
+ return this.emitExpr(receiver);
8865
+ }
8866
+ /** Load a canonical integer-loop induction value for a direct index use.
8867
+ * Other expression shapes retain ordinary f64 evaluation. */
8868
+ emitIntegerLoopIndex(expr) {
8869
+ if (expr.kind !== "varRef")
8870
+ return null;
8871
+ const slot = this.integerLoopBindings.get(expr.localId);
8872
+ if (slot === undefined)
8873
+ return null;
8874
+ const index = this.B.tmp();
8875
+ this.B.line(`${index} = load i64, ptr ${slot}`);
8876
+ return index;
8877
+ }
8878
+ /** Bounds-check a typed-array element index without an out-of-line call
8879
+ * on the valid path. The range test rejects NaN/infinity before fptoui;
8880
+ * the integer round trip then rejects fractions. The error block retains
8881
+ * the runtime's exact trap text. Leaves the builder in the valid block. */
8882
+ emitBytesIndex(receiver, index, integerIndex = false) {
8883
+ const B = this.B;
8884
+ const lenPtr = B.tmp();
8885
+ const len = B.tmp();
8886
+ B.line(`${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`);
8887
+ B.line(`${len} = load i64, ptr ${lenPtr}`);
8888
+ if (integerIndex) {
8889
+ const inRange = B.tmp();
8890
+ B.line(`${inRange} = icmp ult i64 ${index}, ${len}`);
8891
+ const invalid = B.newLabel("bytes.index.invalid");
8892
+ const valid = B.newLabel("bytes.index.valid");
8893
+ B.condBr(inRange, valid, invalid);
8894
+ B.startBlock(invalid);
8895
+ const indexF64 = B.tmp();
8896
+ B.line(`${indexF64} = uitofp i64 ${index} to double`);
8897
+ this.declare(`declare double @scr_bytes_get(ptr, double)`);
8898
+ B.line(`call double @scr_bytes_get(ptr ${receiver}, double ${indexF64})`);
8899
+ B.terminate("unreachable");
8900
+ B.startBlock(valid);
8901
+ return index;
8902
+ }
8903
+ const lenF64 = B.tmp();
8904
+ const nonnegative = B.tmp();
8905
+ const belowLen = B.tmp();
8906
+ const inRange = B.tmp();
8907
+ B.line(`${lenF64} = uitofp i64 ${len} to double`);
8908
+ B.line(`${nonnegative} = fcmp oge double ${index}, ${f64Lit(0)}`);
8909
+ B.line(`${belowLen} = fcmp olt double ${index}, ${lenF64}`);
8910
+ B.line(`${inRange} = and i1 ${nonnegative}, ${belowLen}`);
8911
+ const rangeOk = B.newLabel("bytes.index.range");
8912
+ const invalid = B.newLabel("bytes.index.invalid");
8913
+ const valid = B.newLabel("bytes.index.valid");
8914
+ B.condBr(inRange, rangeOk, invalid);
8915
+ B.startBlock(rangeOk);
8916
+ const idx = B.tmp();
8917
+ const roundTrip = B.tmp();
8918
+ const integral = B.tmp();
8919
+ B.line(`${idx} = fptoui double ${index} to i64`);
8920
+ B.line(`${roundTrip} = uitofp i64 ${idx} to double`);
8921
+ B.line(`${integral} = fcmp oeq double ${roundTrip}, ${index}`);
8922
+ B.condBr(integral, valid, invalid);
8923
+ B.startBlock(invalid);
8924
+ this.declare(`declare double @scr_bytes_get(ptr, double)`);
8925
+ B.line(`call double @scr_bytes_get(ptr ${receiver}, double ${index})`);
8926
+ B.terminate("unreachable");
8927
+ B.startBlock(valid);
8928
+ return idx;
8929
+ }
8930
+ /** Load the raw data pointer from ScrBytes after the bounds check. */
8931
+ emitBytesData(receiver) {
8932
+ const p = this.B.tmp();
8933
+ const data = this.B.tmp();
8934
+ this.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 3`);
8935
+ this.B.line(`${data} = load ptr, ptr ${p}`);
8936
+ return data;
8937
+ }
8938
+ /** Static typed-array length/byteLength load. */
8939
+ emitBytesLength(elem, receiver, bytes) {
8940
+ const B = this.B;
8941
+ const p = B.tmp();
8942
+ const len = B.tmp();
8943
+ B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`);
8944
+ B.line(`${len} = load i64, ptr ${p}`);
8945
+ const count = bytes && elem !== "u8" ? B.tmp() : len;
8946
+ if (count !== len)
8947
+ B.line(`${count} = shl i64 ${len}, 2`);
8948
+ const out = B.tmp();
8949
+ B.line(`${out} = uitofp i64 ${count} to double`);
8950
+ return { name: out, type: F64 };
8951
+ }
8952
+ /** Kind-specialized typed-array load. */
8953
+ emitBytesGet(elem, receiver, index, integerIndex = false) {
8954
+ const B = this.B;
8955
+ const idx = this.emitBytesIndex(receiver, index, integerIndex);
8956
+ const data = this.emitBytesData(receiver);
8957
+ const p = B.tmp();
8958
+ if (elem === "u8") {
8959
+ const raw = B.tmp();
8960
+ const wide = B.tmp();
8961
+ const out = B.tmp();
8962
+ B.line(`${p} = getelementptr inbounds i8, ptr ${data}, i64 ${idx}`);
8963
+ B.line(`${raw} = load i8, ptr ${p}, align 1`);
8964
+ B.line(`${wide} = zext i8 ${raw} to i32`);
8965
+ B.line(`${out} = uitofp i32 ${wide} to double`);
8966
+ return { name: out, type: F64 };
8967
+ }
8968
+ if (elem === "f32") {
8969
+ const raw = B.tmp();
8970
+ const out = B.tmp();
8971
+ B.line(`${p} = getelementptr inbounds float, ptr ${data}, i64 ${idx}`);
8972
+ B.line(`${raw} = load float, ptr ${p}, align 1`);
8973
+ B.line(`${out} = fpext float ${raw} to double`);
8974
+ return { name: out, type: F64 };
8975
+ }
8976
+ const raw = B.tmp();
8977
+ const out = B.tmp();
8978
+ B.line(`${p} = getelementptr inbounds i32, ptr ${data}, i64 ${idx}`);
8979
+ B.line(`${raw} = load i32, ptr ${p}, align 1`);
8980
+ B.line(`${out} = ${elem === "i32" ? "sitofp" : "uitofp"} i32 ${raw} to double`);
8981
+ return { name: out, type: F64 };
8982
+ }
8983
+ /** ToUint32/ToInt32 coercion with the overwhelmingly common finite,
8984
+ * exactly-representable JS-number range on a cast-only path. Exceptional
8985
+ * and huge values remain inline in the program TU so unrelated runtime
8986
+ * binaries do not acquire another exported helper. */
8987
+ emitBytesU32(value) {
8988
+ const B = this.B;
8989
+ const aboveMin = B.tmp();
8990
+ const belowMax = B.tmp();
8991
+ const fast = B.tmp();
8992
+ B.line(`${aboveMin} = fcmp oge double ${value}, ${f64Lit(-9007199254740992)}`);
8993
+ B.line(`${belowMax} = fcmp ole double ${value}, ${f64Lit(9007199254740992)}`);
8994
+ B.line(`${fast} = and i1 ${aboveMin}, ${belowMax}`);
8995
+ const fastLabel = B.newLabel("bytes.coerce.fast");
8996
+ const slowLabel = B.newLabel("bytes.coerce.slow");
8997
+ const done = B.newLabel("bytes.coerce.done");
8998
+ B.condBr(fast, fastLabel, slowLabel);
8999
+ B.startBlock(fastLabel);
9000
+ const signed = B.tmp();
9001
+ const fastU32 = B.tmp();
9002
+ B.line(`${signed} = fptosi double ${value} to i64`);
9003
+ B.line(`${fastU32} = trunc i64 ${signed} to i32`);
9004
+ B.br(done);
9005
+ B.startBlock(slowLabel);
9006
+ const ordered = B.tmp();
9007
+ const belowInf = B.tmp();
9008
+ const aboveNegInf = B.tmp();
9009
+ const finiteRange = B.tmp();
9010
+ const finite = B.tmp();
9011
+ B.line(`${ordered} = fcmp ord double ${value}, ${value}`);
9012
+ B.line(`${belowInf} = fcmp olt double ${value}, ${F64_INF}`);
9013
+ B.line(`${aboveNegInf} = fcmp ogt double ${value}, ${f64Lit(-Infinity)}`);
9014
+ B.line(`${finiteRange} = and i1 ${belowInf}, ${aboveNegInf}`);
9015
+ B.line(`${finite} = and i1 ${ordered}, ${finiteRange}`);
9016
+ const finiteLabel = B.newLabel("bytes.coerce.finite");
9017
+ const nonfiniteLabel = B.newLabel("bytes.coerce.nonfinite");
9018
+ const slowDone = B.newLabel("bytes.coerce.slow.done");
9019
+ B.condBr(finite, finiteLabel, nonfiniteLabel);
9020
+ B.startBlock(finiteLabel);
9021
+ this.declare(`declare double @llvm.trunc.f64(double)`);
9022
+ const truncated = B.tmp();
9023
+ const residue = B.tmp();
9024
+ const negative = B.tmp();
9025
+ const wrapped = B.tmp();
9026
+ const normalized = B.tmp();
9027
+ const finiteU32 = B.tmp();
9028
+ B.line(`${truncated} = call double @llvm.trunc.f64(double ${value})`);
9029
+ B.line(`${residue} = frem double ${truncated}, ${f64Lit(4294967296)}`);
9030
+ B.line(`${negative} = fcmp olt double ${residue}, ${f64Lit(0)}`);
9031
+ B.line(`${wrapped} = fadd double ${residue}, ${f64Lit(4294967296)}`);
9032
+ B.line(`${normalized} = select i1 ${negative}, double ${wrapped}, double ${residue}`);
9033
+ B.line(`${finiteU32} = fptoui double ${normalized} to i32`);
9034
+ B.br(slowDone);
9035
+ B.startBlock(nonfiniteLabel);
9036
+ B.br(slowDone);
9037
+ B.startBlock(slowDone);
9038
+ const slowU32 = B.tmp();
9039
+ B.line(`${slowU32} = phi i32 [ ${finiteU32}, %${finiteLabel} ], [ 0, %${nonfiniteLabel} ]`);
9040
+ B.br(done);
9041
+ B.startBlock(done);
9042
+ const out = B.tmp();
9043
+ B.line(`${out} = phi i32 [ ${fastU32}, %${fastLabel} ], [ ${slowU32}, %${slowDone} ]`);
9044
+ return out;
9045
+ }
9046
+ /** Kind-specialized typed-array store. */
9047
+ emitBytesSet(elem, receiver, index, value, integerIndex = false) {
9048
+ const B = this.B;
9049
+ const idx = this.emitBytesIndex(receiver, index, integerIndex);
9050
+ const stored = elem === "f32" ? null : this.emitBytesU32(value);
9051
+ const data = this.emitBytesData(receiver);
9052
+ const p = B.tmp();
9053
+ if (elem === "u8") {
9054
+ const byte = B.tmp();
9055
+ B.line(`${byte} = trunc i32 ${stored} to i8`);
9056
+ B.line(`${p} = getelementptr inbounds i8, ptr ${data}, i64 ${idx}`);
9057
+ B.line(`store i8 ${byte}, ptr ${p}, align 1`);
9058
+ return;
9059
+ }
9060
+ if (elem === "f32") {
9061
+ const narrowed = B.tmp();
9062
+ B.line(`${narrowed} = fptrunc double ${value} to float`);
9063
+ B.line(`${p} = getelementptr inbounds float, ptr ${data}, i64 ${idx}`);
9064
+ B.line(`store float ${narrowed}, ptr ${p}, align 1`);
9065
+ return;
9066
+ }
9067
+ B.line(`${p} = getelementptr inbounds i32, ptr ${data}, i64 ${idx}`);
9068
+ B.line(`store i32 ${stored}, ptr ${p}, align 1`);
9069
+ }
8628
9070
  /** The bytesIntrinsic surface (emit-exprs.ts's switch, .ll flavored).
8629
9071
  * Receiver and args are borrowed frame temps; string/bytes results come
8630
9072
  * back +1. The MAY_THROW methods get the standard pending check after
@@ -8672,18 +9114,31 @@ class LlEmitter {
8672
9114
  ? call("scr_bytes_read_var", "double (ptr, double, double, i1 zeroext, i1 zeroext)", `ptr ${r0.name}, double ${rest[0].name}, double ${rest[1].name}, i1 ${spec.sign}, i1 ${spec.le}`, false, true)
8673
9115
  : call("scr_bytes_write_var", "double (ptr, double, double, double, i1 zeroext, i1 zeroext)", `ptr ${r0.name}, double ${rest[0].name}, double ${rest[1].name}, double ${rest[2].name}, i1 ${spec.sign}, i1 ${spec.le}`, false, true);
8674
9116
  }
8675
- const r = this.emitExpr(e.receiver);
8676
- const args = e.args.map((a) => this.emitExpr(a));
8677
9117
  const method = e.method;
9118
+ const directElementAccess = method === "length" || method === "byteLength" || method === "get";
9119
+ const r = directElementAccess
9120
+ ? this.emitBytesReceiver(e.receiver, e.args)
9121
+ : this.emitExpr(e.receiver);
9122
+ const integerIndex = method === "get" ? this.emitIntegerLoopIndex(e.args[0]) : null;
9123
+ const args = integerIndex === null ? e.args.map((a) => this.emitExpr(a)) : [];
8678
9124
  const NAN = f64Lit(NaN);
8679
9125
  switch (method) {
8680
9126
  case "length":
8681
- return call("scr_bytes_len", "double (ptr)", `ptr ${r.name}`, false, false);
9127
+ if (e.receiver.type.kind !== "bytes") {
9128
+ throw new Error("llvm emitter bug: bytesIntrinsic length on non-bytes");
9129
+ }
9130
+ return this.emitBytesLength(e.receiver.type.elem, r.name, false);
8682
9131
  case "byteLength":
8683
- return call("scr_bytes_byte_len", "double (ptr)", `ptr ${r.name}`, false, false);
9132
+ if (e.receiver.type.kind !== "bytes") {
9133
+ throw new Error("llvm emitter bug: bytesIntrinsic byteLength on non-bytes");
9134
+ }
9135
+ return this.emitBytesLength(e.receiver.type.elem, r.name, true);
8684
9136
  case "get":
8685
9137
  // Any invalid index traps (the array runtime's discipline).
8686
- return call("scr_bytes_get", "double (ptr, double)", `ptr ${r.name}, double ${args[0].name}`, false, false);
9138
+ if (e.receiver.type.kind !== "bytes") {
9139
+ throw new Error("llvm emitter bug: bytesIntrinsic get on non-bytes");
9140
+ }
9141
+ return this.emitBytesGet(e.receiver.type.elem, r.name, integerIndex ?? args[0].name, integerIndex !== null);
8687
9142
  case "slice":
8688
9143
  return call("scr_bytes_slice", "ptr (ptr, double, double)", `ptr ${r.name}, double ${args[0]?.name ?? f64Lit(0)}, double ${args[1]?.name ?? F64_INF}`, true, false);
8689
9144
  case "subarray":
@@ -8702,21 +9157,26 @@ class LlEmitter {
8702
9157
  // dst.set(src, offset?) — void; throws Node's RangeError on
8703
9158
  // overflow.
8704
9159
  return call("scr_bytes_set_from", "void (ptr, ptr, double)", `ptr ${r.name}, ptr ${args[0].name}, double ${args[1]?.name ?? f64Lit(0)}`, false, true);
8705
- case "toString": {
9160
+ case "toString":
9161
+ case "toStringVar": {
8706
9162
  // The encoding arg is always present (the frontend completes an
8707
- // omitted one to "utf8"). Never throws; +1 string. Range forms:
8708
- // [enc, start] decodes to the buffer's end (the element count —
8709
- // r->len in the C spelling); [enc, start, end] clamps.
9163
+ // omitted one to "utf8"). The runtime-valued form validates and
9164
+ // may throw ERR_UNKNOWN_ENCODING; the literal form is canonical
9165
+ // and cannot throw. Both return +1. Range forms: [enc, start]
9166
+ // decode to the buffer's end (the element count — r->len in the C
9167
+ // spelling); [enc, start, end] clamps.
9168
+ const checked = method === "toStringVar";
9169
+ const stem = checked ? "scr_bytes_to_str_checked" : "scr_bytes_to_str";
8710
9170
  if (e.args.length === 3) {
8711
- return call("scr_bytes_to_str_range", "ptr (ptr, ptr, double, double)", `ptr ${r.name}, ptr ${args[0].name}, double ${args[1].name}, double ${args[2].name}`, true, false);
9171
+ return call(`${stem}_range`, "ptr (ptr, ptr, double, double)", `ptr ${r.name}, ptr ${args[0].name}, double ${args[1].name}, double ${args[2].name}`, true, checked);
8712
9172
  }
8713
9173
  if (e.args.length === 2) {
8714
9174
  this.declare(`declare double @scr_bytes_len(ptr)`);
8715
9175
  const len = B.tmp();
8716
9176
  B.line(`${len} = call double @scr_bytes_len(ptr ${r.name})`);
8717
- return call("scr_bytes_to_str_range", "ptr (ptr, ptr, double, double)", `ptr ${r.name}, ptr ${args[0].name}, double ${args[1].name}, double ${len}`, true, false);
9177
+ return call(`${stem}_range`, "ptr (ptr, ptr, double, double)", `ptr ${r.name}, ptr ${args[0].name}, double ${args[1].name}, double ${len}`, true, checked);
8718
9178
  }
8719
- return call("scr_bytes_to_str", "ptr (ptr, ptr)", `ptr ${r.name}, ptr ${args[0].name}`, true, false);
9179
+ return call(stem, "ptr (ptr, ptr)", `ptr ${r.name}, ptr ${args[0].name}`, true, checked);
8720
9180
  }
8721
9181
  case "equals":
8722
9182
  return call("scr_bytes_equals", "zeroext i1 (ptr, ptr)", `ptr ${r.name}, ptr ${args[0].name}`, false, false);
@@ -8845,7 +9305,14 @@ class LlEmitter {
8845
9305
  case "replaceAll":
8846
9306
  return fallible("scr_regex_replace_all", `ptr ${r.name}, ptr ${args[0].name}, ptr ${args[1].name}`);
8847
9307
  case "split":
8848
- return fallible("scr_regex_split", `ptr ${r.name}, ptr ${args[0].name}`);
9308
+ this.declare(`declare ptr @scr_regex_split_limit(ptr, ptr, double)`);
9309
+ {
9310
+ const t = B.tmp();
9311
+ B.line(`${t} = call ptr @scr_regex_split_limit(ptr ${r.name}, ptr ${args[0].name}, double ${args[1].name})`);
9312
+ const out = this.own({ name: t, type: e.type });
9313
+ this.emitPendingCheck();
9314
+ return out;
9315
+ }
8849
9316
  case "test": {
8850
9317
  this.declare(`declare zeroext i1 @scr_regex_test(ptr, ptr)`);
8851
9318
  const t = B.tmp();
@@ -9383,9 +9850,14 @@ class LlEmitter {
9383
9850
  for (const field of fields) {
9384
9851
  const index = shape.fields.indexOf(field) + 1;
9385
9852
  const fieldPtr = B.tmp();
9386
- const fieldValue = B.tmp();
9853
+ let fieldValue = B.tmp();
9387
9854
  B.line(`${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`);
9388
9855
  B.line(`${fieldValue} = load ${llFieldType(field.type)}, ptr ${fieldPtr}`);
9856
+ if (llFieldType(field.type) === "i8") {
9857
+ const boolValue = B.tmp();
9858
+ B.line(`${boolValue} = trunc i8 ${fieldValue} to i1`);
9859
+ fieldValue = boolValue;
9860
+ }
9389
9861
  const boxed = this.streamTypedRefBoxValue(B, field.type, fieldValue, ctx);
9390
9862
  B.line(`call void @scr_dyn_arr_push(ptr ${out}, ptr ${boxed})`);
9391
9863
  }
@@ -9406,9 +9878,14 @@ class LlEmitter {
9406
9878
  for (const field of fields) {
9407
9879
  const index = shape.fields.indexOf(field) + 1;
9408
9880
  const fieldPtr = B.tmp();
9409
- const fieldValue = B.tmp();
9881
+ let fieldValue = B.tmp();
9410
9882
  B.line(`${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`);
9411
9883
  B.line(`${fieldValue} = load ${llFieldType(field.type)}, ptr ${fieldPtr}`);
9884
+ if (llFieldType(field.type) === "i8") {
9885
+ const boolValue = B.tmp();
9886
+ B.line(`${boolValue} = trunc i8 ${fieldValue} to i1`);
9887
+ fieldValue = boolValue;
9888
+ }
9412
9889
  const boxed = this.streamTypedRefBoxValue(B, field.type, fieldValue, ctx);
9413
9890
  B.line(`call void @scr_dyn_obj_set(ptr ${out}, ptr ${this.cstr(field.name)}, i64 ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`);
9414
9891
  }
@@ -9592,6 +10069,71 @@ class LlEmitter {
9592
10069
  // Loop liveness first (one table for generic and special shapes).
9593
10070
  if (USES_TIMERS_LIB_FNS.has(e.fn))
9594
10071
  this.usesTimers = true;
10072
+ if (e.fn === "process.stdoutWriteBytesCb" || e.fn === "process.stderrWriteBytesCb") {
10073
+ // All arguments evaluate before bytes are submitted. The callback
10074
+ // then moves into the next-tick entry and its error-first adapter
10075
+ // constructs Node's success `null` argument when the tick fires.
10076
+ this.usesTimers = true;
10077
+ const args = e.args.map((a) => this.emitExpr(a));
10078
+ const cbT = e.args[2].type;
10079
+ if (cbT.kind !== "func")
10080
+ throw new Error("llvm emitter bug: process write callback not a func");
10081
+ this.moveTemp(args[2]);
10082
+ const adapter = this.fsRenameThunkFor(cbT);
10083
+ const write = e.fn === "process.stdoutWriteBytesCb"
10084
+ ? "scr_process_stdout_write_bytes"
10085
+ : "scr_process_stderr_write_bytes";
10086
+ this.declare(`declare zeroext i1 @${write}(ptr, ptr)`);
10087
+ this.declare(`declare void @scr_process_write_callback(ptr, ptr)`);
10088
+ const out = B.tmp();
10089
+ B.line(`${out} = call i1 @${write}(ptr ${args[0].name}, ptr ${args[1].name})`);
10090
+ B.line(`call void @scr_process_write_callback(ptr ${args[2].name}, ptr @${adapter})`);
10091
+ return { name: out, type: e.type };
10092
+ }
10093
+ if (e.fn === "fs.renameCb") {
10094
+ // The callback MOVES into the next-turn operation. Its emitted
10095
+ // adapter materializes the callback's Error | null union (or the
10096
+ // checkJs dyn argument) from the runtime's borrowed ScrError.
10097
+ this.usesTimers = true;
10098
+ const args = e.args.map((a) => this.emitExpr(a));
10099
+ const cbT = e.args[2].type;
10100
+ if (cbT.kind !== "func")
10101
+ throw new Error("llvm emitter bug: fs.rename callback not a func");
10102
+ this.moveTemp(args[2]);
10103
+ const adapter = this.fsRenameThunkFor(cbT);
10104
+ this.declare(`declare void @scr_fs_rename_async(ptr, ptr, ptr, ptr)`);
10105
+ B.line(`call void @scr_fs_rename_async(ptr ${args[0].name}, ptr ${args[1].name}, ptr ${args[2].name}, ptr @${adapter})`);
10106
+ return { name: "", type: e.type };
10107
+ }
10108
+ if (e.fn === "fileHandle.read" || e.fn === "fileHandle.writeBytes" || e.fn === "fileHandle.writeStr") {
10109
+ if (e.type.kind !== "promise" || e.type.inner.kind !== "record") {
10110
+ throw new Error(`llvm emitter bug: ${e.fn} result`);
10111
+ }
10112
+ const args = e.args.map((a) => this.emitExpr(a));
10113
+ const sym = e.fn === "fileHandle.read"
10114
+ ? "scr_file_handle_read"
10115
+ : e.fn === "fileHandle.writeBytes"
10116
+ ? "scr_file_handle_write_bytes"
10117
+ : "scr_file_handle_write_str";
10118
+ const tail = e.fn === "fileHandle.writeStr"
10119
+ ? "ptr, ptr, double, ptr"
10120
+ : "ptr, ptr, double, double, double, i1 zeroext";
10121
+ this.declare(`declare double @${sym}(${tail})`);
10122
+ const count = B.tmp();
10123
+ B.line(`${count} = call double @${sym}(${args.map((a) => `${this.llType(a.type)} ${a.name}`).join(", ")})`);
10124
+ const inner = e.type.inner;
10125
+ const rec = B.tmp();
10126
+ B.line(`${rec} = call ptr @${mangleRecordNew(inner.shapeId)}()`);
10127
+ const countField = e.fn === "fileHandle.read" ? "bytesRead" : "bytesWritten";
10128
+ this.storeField(this.recordFieldPtr(rec, inner.shapeId, countField).ptr, F64, count);
10129
+ const payload = this.retainValue(args[1].name, e.args[1].type);
10130
+ B.line(`store ptr ${payload}, ptr ${this.recordFieldPtr(rec, inner.shapeId, "buffer").ptr}`);
10131
+ const rc = vAdapters(this, inner);
10132
+ this.declare(`declare ptr @scr_promise_settled_ref(ptr, ptr, ptr, ptr)`);
10133
+ const result = B.tmp();
10134
+ B.line(`${result} = call ptr @scr_promise_settled_ref(ptr ${rec}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(this, inner)})`);
10135
+ return this.own({ name: result, type: e.type });
10136
+ }
9595
10137
  if (e.fn === "fetch.responseText" || e.fn === "fetch.responseBytes") {
9596
10138
  if (e.type.kind !== "promise") {
9597
10139
  throw new Error(`llvm emitter bug: ${e.fn} result`);