@scriptc/compiler 0.0.23 → 0.0.25

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 (65) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +100 -11
  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 +31 -1
  7. package/dist/backend/emission/emit-async.js.map +1 -1
  8. package/dist/backend/emission/emit-exprs.js +147 -14
  9. package/dist/backend/emission/emit-exprs.js.map +1 -1
  10. package/dist/backend/emission/emit-shapes.js +1 -0
  11. package/dist/backend/emission/emit-shapes.js.map +1 -1
  12. package/dist/backend/emission/emit-stmts.js +24 -5
  13. package/dist/backend/emission/emit-stmts.js.map +1 -1
  14. package/dist/backend/emission/emit-types.js +10 -0
  15. package/dist/backend/emission/emit-types.js.map +1 -1
  16. package/dist/backend/emission/emitter.d.ts +10 -2
  17. package/dist/backend/emission/emitter.js +49 -30
  18. package/dist/backend/emission/emitter.js.map +1 -1
  19. package/dist/backend/llvm/emitter.js +284 -25
  20. package/dist/backend/llvm/emitter.js.map +1 -1
  21. package/dist/backend/llvm/shapes.js +9 -1
  22. package/dist/backend/llvm/shapes.js.map +1 -1
  23. package/dist/backend/mangle.d.ts +3 -0
  24. package/dist/backend/mangle.js +5 -0
  25. package/dist/backend/mangle.js.map +1 -1
  26. package/dist/frontend/lowering/lower-builtins.d.ts +14 -9
  27. package/dist/frontend/lowering/lower-builtins.js +737 -62
  28. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  29. package/dist/frontend/lowering/lower-calls.js +2 -1
  30. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  31. package/dist/frontend/lowering/lower-containers.d.ts +2 -1
  32. package/dist/frontend/lowering/lower-containers.js +99 -32
  33. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  34. package/dist/frontend/lowering/lower-exprs.js +53 -2
  35. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  36. package/dist/frontend/lowering/lower-inspect.js +59 -1
  37. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  38. package/dist/frontend/lowering/lower-server.d.ts +5 -0
  39. package/dist/frontend/lowering/lower-server.js +1 -1
  40. package/dist/frontend/lowering/lower-server.js.map +1 -1
  41. package/dist/frontend/lowering/lower-stmts.d.ts +6 -2
  42. package/dist/frontend/lowering/lower-stmts.js +45 -10
  43. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  44. package/dist/frontend/lowering/lowerer.d.ts +2 -2
  45. package/dist/frontend/lowering/lowerer.js +11 -6
  46. package/dist/frontend/lowering/lowerer.js.map +1 -1
  47. package/dist/frontend/lowering/surfaces.js +65 -13
  48. package/dist/frontend/lowering/surfaces.js.map +1 -1
  49. package/dist/frontend/types.d.ts +3 -0
  50. package/dist/frontend/types.js +78 -0
  51. package/dist/frontend/types.js.map +1 -1
  52. package/dist/index.js +8 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/ir/integer-loops.d.ts +23 -0
  55. package/dist/ir/integer-loops.js +82 -0
  56. package/dist/ir/integer-loops.js.map +1 -0
  57. package/dist/ir/nodes.d.ts +84 -21
  58. package/dist/ir/nodes.js +84 -1
  59. package/dist/ir/nodes.js.map +1 -1
  60. package/dist/ir/validate.js +92 -18
  61. package/dist/ir/validate.js.map +1 -1
  62. package/dist/library/fence-eval.js +4 -3
  63. package/dist/library/fence-eval.js.map +1 -1
  64. package/package.json +2 -2
  65. package/surface-manifest.json +50 -2
@@ -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",
@@ -174,6 +176,7 @@ const LIB_FN_SYMS = {
174
176
  "fs.unlinkSync": "scr_fs_unlink",
175
177
  "fs.chmodSync": "scr_fs_chmod",
176
178
  "fs.copyFileSync": "scr_fs_copyfile",
179
+ "fs.renameSync": "scr_fs_rename",
177
180
  "fs.accessSync": "scr_fs_access",
178
181
  // node:assert (scr_assert.c): all args borrowed; failures throw the
179
182
  // catchable AssertionError. The never-throwing members ride along.
@@ -236,6 +239,8 @@ const LIB_FN_SYMS = {
236
239
  "fs.lstatSync": "scr_fs_lstat",
237
240
  "fs.openSync": "scr_fs_open",
238
241
  "fs.readSync": "scr_fs_read_sync",
242
+ "fs.writeSync": "scr_fs_write_sync",
243
+ "fs.writeStrSync": "scr_fs_write_str_sync",
239
244
  "fs.closeSync": "scr_fs_close",
240
245
  // string_decoder (scr_bytes.c): stateless helpers over (enc, pending,
241
246
  // chunk) — never throw.
@@ -246,8 +251,12 @@ const LIB_FN_SYMS = {
246
251
  "stats.isDirectory": "scr_stats_is_dir",
247
252
  "stats.isSymbolicLink": "scr_stats_is_symlink",
248
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",
249
257
  "stats.mtimeMs": "scr_stats_mtime_ms",
250
258
  "text.decode": "scr_text_decode",
259
+ "text.decodeLegacy": "scr_text_decode_legacy",
251
260
  "zlib.deflateSync": "scr_zlib_deflate",
252
261
  "zlib.inflateSync": "scr_zlib_inflate",
253
262
  // The CA-store unit. get/set throw (an unknown type name, a
@@ -355,15 +364,25 @@ const LIB_FN_SYMS = {
355
364
  // so it is special-cased in emitLibCall (the fs.readFileSync pattern).
356
365
  "fsp.readFileBytes": "scr_fsp_read_file_bytes",
357
366
  "fsp.writeFile": "scr_fsp_write_file",
367
+ "fsp.writeFileMode": "scr_fsp_write_file_mode",
358
368
  "fsp.mkdir": "scr_fsp_mkdir",
359
369
  "fsp.mkdirMode": "scr_fsp_mkdir_mode",
360
370
  "fsp.mkdirRecursive": "scr_fsp_mkdir_recursive",
361
371
  "fsp.mkdirRecursiveMode": "scr_fsp_mkdir_recursive_mode",
362
372
  "fsp.unlink": "scr_fsp_unlink",
363
373
  "fsp.chmod": "scr_fsp_chmod",
374
+ "fsp.rename": "scr_fsp_rename",
364
375
  "fsp.readdir": "scr_fsp_readdir",
365
376
  "fsp.rm": "scr_fsp_rm",
366
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",
367
386
  "sym.new": "scr_sym_new",
368
387
  "sym.for": "scr_sym_for",
369
388
  "sym.toString": "scr_sym_to_string",
@@ -854,6 +873,8 @@ class LlEmitter {
854
873
  jumpTargets = [];
855
874
  currentLocals = new Map();
856
875
  captureIds = new Set();
876
+ /** Active canonical byte-loop induction bindings: local id → i64 slot. */
877
+ integerLoopBindings = new Map();
857
878
  /** Enclosing try-with-FINALLY regions, innermost last: a `return`
858
879
  * inside one runs every crossed finally (innermost first) before the
859
880
  * actual ret — the C emitter's pending-return path, with the finally
@@ -983,6 +1004,7 @@ class LlEmitter {
983
1004
  case "url":
984
1005
  case "searchParams":
985
1006
  case "stats":
1007
+ case "fileHandle":
986
1008
  case "spawnRes":
987
1009
  case "child":
988
1010
  case "childStream":
@@ -2205,6 +2227,7 @@ class LlEmitter {
2205
2227
  case "url":
2206
2228
  case "searchParams":
2207
2229
  case "stats":
2230
+ case "fileHandle":
2208
2231
  case "spawnRes":
2209
2232
  case "child":
2210
2233
  case "childStream":
@@ -2302,6 +2325,7 @@ class LlEmitter {
2302
2325
  case "url":
2303
2326
  case "searchParams":
2304
2327
  case "stats":
2328
+ case "fileHandle":
2305
2329
  case "spawnRes":
2306
2330
  case "child":
2307
2331
  case "childStream":
@@ -2636,6 +2660,7 @@ class LlEmitter {
2636
2660
  this.jumpTargets = [];
2637
2661
  this.currentLocals = new Map(fn.locals.map((l) => [l.id, l]));
2638
2662
  this.captureIds = new Set((fn.captures ?? []).map((c) => c.localId));
2663
+ this.integerLoopBindings.clear();
2639
2664
  this.chainSlots.clear();
2640
2665
  this.finallyStack = [];
2641
2666
  this.tryStack = [];
@@ -2837,11 +2862,12 @@ class LlEmitter {
2837
2862
  // append. IrBytesElem is static, so never rediscover it through the
2838
2863
  // generic runtime switch in a hot loop.
2839
2864
  const arr = this.emitBytesReceiver(s.arr, [s.index, s.value]);
2840
- const idx = this.emitExpr(s.index);
2865
+ const integerIndex = this.emitIntegerLoopIndex(s.index);
2866
+ const idx = integerIndex ?? this.emitExpr(s.index).name;
2841
2867
  const v = this.emitExpr(s.value);
2842
2868
  if (s.arr.type.kind !== "bytes")
2843
2869
  throw new Error("llvm emitter bug: bytesSet on non-bytes");
2844
- this.emitBytesSet(s.arr.type.elem, arr.name, idx.name, v.name);
2870
+ this.emitBytesSet(s.arr.type.elem, arr.name, idx, v.name, integerIndex !== null);
2845
2871
  break;
2846
2872
  }
2847
2873
  case "fieldSet":
@@ -3098,9 +3124,18 @@ class LlEmitter {
3098
3124
  case "for": {
3099
3125
  // The init's scope wraps the whole loop (break/continue must NOT
3100
3126
  // release it — scopeDepth captured after the push, C parity).
3127
+ const integerLoop = matchIntegerBytesForLoop(s, this.currentLocals);
3101
3128
  this.scopes.push([]);
3102
- 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) {
3103
3137
  this.emitStmt(s.init);
3138
+ }
3104
3139
  const lc = B.newLabel("loop.c");
3105
3140
  const lb = B.newLabel("loop.b");
3106
3141
  const le = B.newLabel("loop.e");
@@ -3114,7 +3149,19 @@ class LlEmitter {
3114
3149
  const lu = s.update || freshens ? B.newLabel("loop.u") : lc;
3115
3150
  B.br(lc);
3116
3151
  B.startBlock(lc);
3117
- 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)
3118
3165
  B.condBr(this.emitCondition(s.cond), lb, le);
3119
3166
  else
3120
3167
  B.br(lb);
@@ -3146,12 +3193,21 @@ class LlEmitter {
3146
3193
  // The wrapper scope's entry releases whatever the slot points
3147
3194
  // to at loop exit — now the freshest binding. Nothing to fix.
3148
3195
  }
3149
- 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)
3150
3204
  this.emitStmt(s.update);
3151
3205
  B.br(lc);
3152
3206
  }
3153
3207
  B.startBlock(le);
3154
3208
  this.releaseScope(this.scopes.pop());
3209
+ if (integerLoop)
3210
+ this.integerLoopBindings.delete(integerLoop.localId);
3155
3211
  break;
3156
3212
  }
3157
3213
  case "forOf": {
@@ -3629,6 +3685,14 @@ class LlEmitter {
3629
3685
  // tag-only); one reaching the generic dispatch escaped its wrap.
3630
3686
  throw new Error(`llvm emitter bug: bare unitLit '${e.unit}'`);
3631
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
+ }
3632
3696
  const b = this.binding(e.localId);
3633
3697
  if (b.kind === "boxed") {
3634
3698
  // Reads go through the shared binding; ref kinds come out +1.
@@ -7834,6 +7898,52 @@ class LlEmitter {
7834
7898
  this.resolveThunkDefs.push(...d);
7835
7899
  return sym;
7836
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
+ }
7837
7947
  /** The stream option-callback invoke adapter for one (kind, callback
7838
7948
  * type) — emit-async.ts's streamCbThunkFor: the runtime calls the
7839
7949
  * user's read/write/final/destroy/transform/flush (or the finished/
@@ -8139,7 +8249,7 @@ class LlEmitter {
8139
8249
  case "trimEnd":
8140
8250
  return call("scr_str_trim_end", "ptr (ptr)", `ptr ${r.name}`, "ptr", true);
8141
8251
  case "split":
8142
- 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);
8143
8253
  case "padStart":
8144
8254
  return call("scr_str_pad_start", "ptr (ptr, double, ptr)", `ptr ${r.name}, double ${args[0].name}, ptr ${args[1].name}`, "ptr", true);
8145
8255
  case "padEnd":
@@ -8211,6 +8321,33 @@ class LlEmitter {
8211
8321
  B.line(`${t} = call double @scr_arr_len(ptr ${r.name})`);
8212
8322
  return { name: t, type: e.type };
8213
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
+ }
8214
8351
  case "pop": {
8215
8352
  // Ownership of a refcounted element moves OUT of the array to
8216
8353
  // this temp (+1 to us, the runtime does not release it).
@@ -8270,6 +8407,13 @@ class LlEmitter {
8270
8407
  B.line(`${t} = call ptr @scr_arr_to_reversed(ptr ${r.name})`);
8271
8408
  return this.own({ name: t, type: e.type });
8272
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
+ }
8273
8417
  case "toSpliced": {
8274
8418
  const start = this.emitExpr(e.args[0]);
8275
8419
  const count = this.emitExpr(e.args[1]);
@@ -8719,20 +8863,47 @@ class LlEmitter {
8719
8863
  }
8720
8864
  return this.emitExpr(receiver);
8721
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
+ }
8722
8878
  /** Bounds-check a typed-array element index without an out-of-line call
8723
8879
  * on the valid path. The range test rejects NaN/infinity before fptoui;
8724
8880
  * the integer round trip then rejects fractions. The error block retains
8725
8881
  * the runtime's exact trap text. Leaves the builder in the valid block. */
8726
- emitBytesIndex(receiver, index) {
8882
+ emitBytesIndex(receiver, index, integerIndex = false) {
8727
8883
  const B = this.B;
8728
8884
  const lenPtr = B.tmp();
8729
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
+ }
8730
8903
  const lenF64 = B.tmp();
8731
8904
  const nonnegative = B.tmp();
8732
8905
  const belowLen = B.tmp();
8733
8906
  const inRange = B.tmp();
8734
- B.line(`${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`);
8735
- B.line(`${len} = load i64, ptr ${lenPtr}`);
8736
8907
  B.line(`${lenF64} = uitofp i64 ${len} to double`);
8737
8908
  B.line(`${nonnegative} = fcmp oge double ${index}, ${f64Lit(0)}`);
8738
8909
  B.line(`${belowLen} = fcmp olt double ${index}, ${lenF64}`);
@@ -8779,9 +8950,9 @@ class LlEmitter {
8779
8950
  return { name: out, type: F64 };
8780
8951
  }
8781
8952
  /** Kind-specialized typed-array load. */
8782
- emitBytesGet(elem, receiver, index) {
8953
+ emitBytesGet(elem, receiver, index, integerIndex = false) {
8783
8954
  const B = this.B;
8784
- const idx = this.emitBytesIndex(receiver, index);
8955
+ const idx = this.emitBytesIndex(receiver, index, integerIndex);
8785
8956
  const data = this.emitBytesData(receiver);
8786
8957
  const p = B.tmp();
8787
8958
  if (elem === "u8") {
@@ -8873,9 +9044,9 @@ class LlEmitter {
8873
9044
  return out;
8874
9045
  }
8875
9046
  /** Kind-specialized typed-array store. */
8876
- emitBytesSet(elem, receiver, index, value) {
9047
+ emitBytesSet(elem, receiver, index, value, integerIndex = false) {
8877
9048
  const B = this.B;
8878
- const idx = this.emitBytesIndex(receiver, index);
9049
+ const idx = this.emitBytesIndex(receiver, index, integerIndex);
8879
9050
  const stored = elem === "f32" ? null : this.emitBytesU32(value);
8880
9051
  const data = this.emitBytesData(receiver);
8881
9052
  const p = B.tmp();
@@ -8948,7 +9119,8 @@ class LlEmitter {
8948
9119
  const r = directElementAccess
8949
9120
  ? this.emitBytesReceiver(e.receiver, e.args)
8950
9121
  : this.emitExpr(e.receiver);
8951
- const args = e.args.map((a) => this.emitExpr(a));
9122
+ const integerIndex = method === "get" ? this.emitIntegerLoopIndex(e.args[0]) : null;
9123
+ const args = integerIndex === null ? e.args.map((a) => this.emitExpr(a)) : [];
8952
9124
  const NAN = f64Lit(NaN);
8953
9125
  switch (method) {
8954
9126
  case "length":
@@ -8966,7 +9138,7 @@ class LlEmitter {
8966
9138
  if (e.receiver.type.kind !== "bytes") {
8967
9139
  throw new Error("llvm emitter bug: bytesIntrinsic get on non-bytes");
8968
9140
  }
8969
- return this.emitBytesGet(e.receiver.type.elem, r.name, args[0].name);
9141
+ return this.emitBytesGet(e.receiver.type.elem, r.name, integerIndex ?? args[0].name, integerIndex !== null);
8970
9142
  case "slice":
8971
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);
8972
9144
  case "subarray":
@@ -8985,21 +9157,26 @@ class LlEmitter {
8985
9157
  // dst.set(src, offset?) — void; throws Node's RangeError on
8986
9158
  // overflow.
8987
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);
8988
- case "toString": {
9160
+ case "toString":
9161
+ case "toStringVar": {
8989
9162
  // The encoding arg is always present (the frontend completes an
8990
- // omitted one to "utf8"). Never throws; +1 string. Range forms:
8991
- // [enc, start] decodes to the buffer's end (the element count —
8992
- // 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";
8993
9170
  if (e.args.length === 3) {
8994
- 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);
8995
9172
  }
8996
9173
  if (e.args.length === 2) {
8997
9174
  this.declare(`declare double @scr_bytes_len(ptr)`);
8998
9175
  const len = B.tmp();
8999
9176
  B.line(`${len} = call double @scr_bytes_len(ptr ${r.name})`);
9000
- 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);
9001
9178
  }
9002
- 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);
9003
9180
  }
9004
9181
  case "equals":
9005
9182
  return call("scr_bytes_equals", "zeroext i1 (ptr, ptr)", `ptr ${r.name}, ptr ${args[0].name}`, false, false);
@@ -9128,7 +9305,14 @@ class LlEmitter {
9128
9305
  case "replaceAll":
9129
9306
  return fallible("scr_regex_replace_all", `ptr ${r.name}, ptr ${args[0].name}, ptr ${args[1].name}`);
9130
9307
  case "split":
9131
- 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
+ }
9132
9316
  case "test": {
9133
9317
  this.declare(`declare zeroext i1 @scr_regex_test(ptr, ptr)`);
9134
9318
  const t = B.tmp();
@@ -9666,9 +9850,14 @@ class LlEmitter {
9666
9850
  for (const field of fields) {
9667
9851
  const index = shape.fields.indexOf(field) + 1;
9668
9852
  const fieldPtr = B.tmp();
9669
- const fieldValue = B.tmp();
9853
+ let fieldValue = B.tmp();
9670
9854
  B.line(`${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`);
9671
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
+ }
9672
9861
  const boxed = this.streamTypedRefBoxValue(B, field.type, fieldValue, ctx);
9673
9862
  B.line(`call void @scr_dyn_arr_push(ptr ${out}, ptr ${boxed})`);
9674
9863
  }
@@ -9689,9 +9878,14 @@ class LlEmitter {
9689
9878
  for (const field of fields) {
9690
9879
  const index = shape.fields.indexOf(field) + 1;
9691
9880
  const fieldPtr = B.tmp();
9692
- const fieldValue = B.tmp();
9881
+ let fieldValue = B.tmp();
9693
9882
  B.line(`${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`);
9694
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
+ }
9695
9889
  const boxed = this.streamTypedRefBoxValue(B, field.type, fieldValue, ctx);
9696
9890
  B.line(`call void @scr_dyn_obj_set(ptr ${out}, ptr ${this.cstr(field.name)}, i64 ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`);
9697
9891
  }
@@ -9875,6 +10069,71 @@ class LlEmitter {
9875
10069
  // Loop liveness first (one table for generic and special shapes).
9876
10070
  if (USES_TIMERS_LIB_FNS.has(e.fn))
9877
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
+ }
9878
10137
  if (e.fn === "fetch.responseText" || e.fn === "fetch.responseBytes") {
9879
10138
  if (e.type.kind !== "promise") {
9880
10139
  throw new Error(`llvm emitter bug: ${e.fn} result`);