@scriptc/compiler 0.0.22 → 0.0.23

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 (68) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +39 -3
  2. package/dist/backend/emission/emit-async.js +3 -1
  3. package/dist/backend/emission/emit-async.js.map +1 -1
  4. package/dist/backend/emission/emit-exprs.d.ts +10 -0
  5. package/dist/backend/emission/emit-exprs.js +127 -10
  6. package/dist/backend/emission/emit-exprs.js.map +1 -1
  7. package/dist/backend/emission/emit-stmts.js +13 -5
  8. package/dist/backend/emission/emit-stmts.js.map +1 -1
  9. package/dist/backend/emission/emit-types.js +4 -1
  10. package/dist/backend/emission/emit-types.js.map +1 -1
  11. package/dist/backend/emission/emitter.d.ts +11 -1
  12. package/dist/backend/emission/emitter.js +61 -1
  13. package/dist/backend/emission/emitter.js.map +1 -1
  14. package/dist/backend/llvm/emitter.js +304 -21
  15. package/dist/backend/llvm/emitter.js.map +1 -1
  16. package/dist/backend/llvm/shapes.js +3 -2
  17. package/dist/backend/llvm/shapes.js.map +1 -1
  18. package/dist/compat/fetch-profile.d.ts +10 -0
  19. package/dist/compat/fetch-profile.js +55 -6
  20. package/dist/compat/fetch-profile.js.map +1 -1
  21. package/dist/coverage/surface-manifest.js +16 -1
  22. package/dist/coverage/surface-manifest.js.map +1 -1
  23. package/dist/diagnostics/diagnostic.d.ts +10 -0
  24. package/dist/diagnostics/diagnostic.js +27 -1
  25. package/dist/diagnostics/diagnostic.js.map +1 -1
  26. package/dist/frontend/lowering/lower-builtins.d.ts +3 -7
  27. package/dist/frontend/lowering/lower-builtins.js +80 -55
  28. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  29. package/dist/frontend/lowering/lower-calls.js +1 -0
  30. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  31. package/dist/frontend/lowering/lower-classes.js +28 -8
  32. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  33. package/dist/frontend/lowering/lower-exprs.d.ts +1 -1
  34. package/dist/frontend/lowering/lower-exprs.js +25 -4
  35. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  36. package/dist/frontend/lowering/lower-generators.js +7 -1
  37. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  38. package/dist/frontend/lowering/lower-island.d.ts +21 -0
  39. package/dist/frontend/lowering/lower-island.js +284 -10
  40. package/dist/frontend/lowering/lower-island.js.map +1 -1
  41. package/dist/frontend/lowering/lower-stmts.js +8 -4
  42. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  43. package/dist/frontend/lowering/lowerer.d.ts +1 -0
  44. package/dist/frontend/lowering/lowerer.js +6 -2
  45. package/dist/frontend/lowering/lowerer.js.map +1 -1
  46. package/dist/frontend/lowering/surfaces.js +43 -12
  47. package/dist/frontend/lowering/surfaces.js.map +1 -1
  48. package/dist/frontend/program.d.ts +4 -4
  49. package/dist/frontend/program.js +264 -46
  50. package/dist/frontend/program.js.map +1 -1
  51. package/dist/frontend/resolve.d.ts +10 -0
  52. package/dist/frontend/resolve.js +37 -0
  53. package/dist/frontend/resolve.js.map +1 -1
  54. package/dist/frontend/ts7/enums.d.ts +9 -0
  55. package/dist/frontend/ts7/enums.js +8 -6
  56. package/dist/frontend/ts7/enums.js.map +1 -1
  57. package/dist/frontend/ts7/program.js +4 -1
  58. package/dist/frontend/ts7/program.js.map +1 -1
  59. package/dist/frontend/types.d.ts +4 -4
  60. package/dist/frontend/types.js +25 -8
  61. package/dist/frontend/types.js.map +1 -1
  62. package/dist/ir/nodes.d.ts +24 -11
  63. package/dist/ir/nodes.js +6 -1
  64. package/dist/ir/nodes.js.map +1 -1
  65. package/dist/ir/validate.js +45 -8
  66. package/dist/ir/validate.js.map +1 -1
  67. package/package.json +2 -2
  68. package/surface-manifest.json +137 -28
@@ -129,13 +129,36 @@ const LIB_FN_SYMS = {
129
129
  "process.stderrWrite": "scr_process_stderr_write",
130
130
  "process.isTTY": "scr_process_is_tty",
131
131
  "date.now": "scr_date_now",
132
+ "date.newNow": "scr_date_now",
133
+ "date.newMs": "scr_date_new_ms",
134
+ "date.newString": "scr_date_parse_get_time",
135
+ "date.getTime": "scr_date_get_time",
136
+ "date.valueOf": "scr_date_get_time",
132
137
  "date.parseGetTime": "scr_date_parse_get_time",
133
138
  "date.utc": "scr_date_utc",
139
+ "date.getFullYear": "scr_date_get_full_year_local",
140
+ "date.getUTCFullYear": "scr_date_get_full_year_utc",
141
+ "date.getMonth": "scr_date_get_month_local",
142
+ "date.getUTCMonth": "scr_date_get_month_utc",
143
+ "date.getDate": "scr_date_get_date_local",
144
+ "date.getUTCDate": "scr_date_get_date_utc",
145
+ "date.getDay": "scr_date_get_day_local",
146
+ "date.getUTCDay": "scr_date_get_day_utc",
147
+ "date.getHours": "scr_date_get_hours_local",
148
+ "date.getUTCHours": "scr_date_get_hours_utc",
149
+ "date.getMinutes": "scr_date_get_minutes_local",
150
+ "date.getUTCMinutes": "scr_date_get_minutes_utc",
151
+ "date.getSeconds": "scr_date_get_seconds_local",
152
+ "date.getUTCSeconds": "scr_date_get_seconds_utc",
153
+ "date.getMilliseconds": "scr_date_get_milliseconds",
154
+ "date.getUTCMilliseconds": "scr_date_get_milliseconds",
155
+ "date.getTimezoneOffset": "scr_date_get_timezone_offset",
134
156
  "fs.existsSync": "scr_fs_exists",
135
157
  // ── the throwing slice (MAY_THROW_LIB_FNS members): the generic path
136
158
  // emits the standard pending check after each — emit-exprs.ts's finish.
137
159
  "process.cpuPrevValidate": "scr_cpu_prev_validate",
138
160
  "date.toISOString": "scr_date_to_iso",
161
+ "date.toISOStringValue": "scr_date_to_iso",
139
162
  "process.chdir": "scr_process_chdir",
140
163
  "fs.writeFileSync": "scr_fs_write_file",
141
164
  "fs.appendFileSync": "scr_fs_append_file",
@@ -352,9 +375,11 @@ const LIB_FN_SYMS = {
352
375
  // the generic path emits the standard pending check). typeof and the
353
376
  // ambient-this read never throw. The fs dyn read is the sync-fs story.
354
377
  "fetch.start": "scr_fetch_static",
378
+ "fetch.responseNew": "scr_fetch_response_new",
355
379
  "fetch.responseJson": "scr_fetch_response_json",
356
380
  "fetch.responseText": "scr_fetch_response_text",
357
381
  "fetch.responseBytes": "scr_fetch_response_bytes",
382
+ "fetch.abortControllerNew": "scr_fetch_abort_controller_new",
358
383
  "fetch.abortTimeout": "scr_fetch_abort_timeout",
359
384
  "fetch.abortNow": "scr_fetch_abort_now",
360
385
  "fetch.abortAny": "scr_fetch_abort_any",
@@ -940,6 +965,7 @@ class LlEmitter {
940
965
  llType(t) {
941
966
  switch (t.kind) {
942
967
  case "f64":
968
+ case "date":
943
969
  return "double";
944
970
  case "bool":
945
971
  return "i1";
@@ -1192,6 +1218,11 @@ class LlEmitter {
1192
1218
  // scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24,
1193
1219
  // payload at 32).
1194
1220
  `%ScrCaught = type { i64, i32, double, i8, ptr, ptr, ptr, ptr }`,
1221
+ // ScrBytes { rc, len, elem(i32+pad), data, backing }. Indexed
1222
+ // typed-array access GEPs through this directly: the IR type already
1223
+ // fixes elem, so the hot path needs neither a runtime kind load nor
1224
+ // the generic scr_bytes_get/set call.
1225
+ `%ScrBytes = type { i64, i64, i32, ptr, ptr }`,
1195
1226
  // The capture box { rc, kind, obj_retain, obj_release, obj_trace,
1196
1227
  // slot } — TDZ reads peek the payload slot (offset 40) directly.
1197
1228
  `%ScrBox = type { i64, i32, ptr, ptr, ptr, i64 }`,
@@ -1696,6 +1727,7 @@ class LlEmitter {
1696
1727
  tr.push(` call void @scr_promise_fulfill_void(ptr %pr)`);
1697
1728
  break;
1698
1729
  case "f64":
1730
+ case "date":
1699
1731
  this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
1700
1732
  tr.push(` call void @scr_promise_fulfill_f64(ptr %pr, double %r)`);
1701
1733
  break;
@@ -1851,6 +1883,7 @@ class LlEmitter {
1851
1883
  tr.push(` br label %done ; void body: the done value is undefined (NONE)`);
1852
1884
  break;
1853
1885
  case "f64":
1886
+ case "date":
1854
1887
  this.declare(`declare void @scr_gen_out_f64(ptr, double)`);
1855
1888
  tr.push(` call void @scr_gen_out_f64(ptr %g, double %r)`, ` br label %done`);
1856
1889
  break;
@@ -2072,7 +2105,7 @@ class LlEmitter {
2072
2105
  const t = this.currentReturnType;
2073
2106
  if (t.kind === "void")
2074
2107
  this.B.terminate("ret void");
2075
- else if (t.kind === "f64")
2108
+ else if (t.kind === "f64" || t.kind === "date")
2076
2109
  this.B.terminate(`ret double ${f64Lit(0)}`);
2077
2110
  else if (t.kind === "bool")
2078
2111
  this.B.terminate("ret i1 false");
@@ -2104,7 +2137,10 @@ class LlEmitter {
2104
2137
  emitThrowValue(v) {
2105
2138
  const B = this.B;
2106
2139
  const t = v.type;
2107
- if (t.kind === "f64") {
2140
+ if (t.kind === "date") {
2141
+ throw new Error("LLVM emitter bug: Date throw reached backend");
2142
+ }
2143
+ else if (t.kind === "f64") {
2108
2144
  this.declare(`declare void @scr_throw_f64(double)`);
2109
2145
  B.line(`call void @scr_throw_f64(double ${v.name})`);
2110
2146
  }
@@ -2153,6 +2189,8 @@ class LlEmitter {
2153
2189
  B.line(`${t} = icmp ne i64 ${len}, 0`);
2154
2190
  return t;
2155
2191
  }
2192
+ case "date":
2193
+ return "true";
2156
2194
  case "array":
2157
2195
  case "record":
2158
2196
  case "object":
@@ -2247,6 +2285,9 @@ class LlEmitter {
2247
2285
  B.line(`store i1 ${t}, ptr ${slot}`);
2248
2286
  break;
2249
2287
  }
2288
+ case "date":
2289
+ B.line(`store i1 true, ptr ${slot} ; Date objects are truthy`);
2290
+ break;
2250
2291
  case "array":
2251
2292
  case "record":
2252
2293
  case "object":
@@ -2791,13 +2832,16 @@ class LlEmitter {
2791
2832
  }
2792
2833
  case "bytesSet": {
2793
2834
  // 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);
2835
+ // the value is a scalar (the kind-specific inline path coerces
2836
+ // JS-exactly), so no ownership moves. Any invalid index traps — no
2837
+ // append. IrBytesElem is static, so never rediscover it through the
2838
+ // generic runtime switch in a hot loop.
2839
+ const arr = this.emitBytesReceiver(s.arr, [s.index, s.value]);
2797
2840
  const idx = this.emitExpr(s.index);
2798
2841
  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})`);
2842
+ if (s.arr.type.kind !== "bytes")
2843
+ throw new Error("llvm emitter bug: bytesSet on non-bytes");
2844
+ this.emitBytesSet(s.arr.type.elem, arr.name, idx.name, v.name);
2801
2845
  break;
2802
2846
  }
2803
2847
  case "fieldSet":
@@ -5280,7 +5324,7 @@ class LlEmitter {
5280
5324
  throw new Error("llvm emitter bug: yieldExpr with no operand (frontend fills undefined)");
5281
5325
  const v = this.emitExpr(e.value);
5282
5326
  const yt = e.value.type;
5283
- if (yt.kind === "f64") {
5327
+ if (yt.kind === "f64" || yt.kind === "date") {
5284
5328
  this.declare(`declare void @scr_gen_yield_f64(double)`);
5285
5329
  B.line(`call void @scr_gen_yield_f64(double ${v.name})`);
5286
5330
  }
@@ -5300,7 +5344,7 @@ class LlEmitter {
5300
5344
  return { name: "", type: e.type };
5301
5345
  }
5302
5346
  const t = B.tmp();
5303
- if (e.type.kind === "f64") {
5347
+ if (e.type.kind === "f64" || e.type.kind === "date") {
5304
5348
  this.declare(`declare double @scr_gen_take_in_f64()`);
5305
5349
  B.line(`${t} = call double @scr_gen_take_in_f64()`);
5306
5350
  return { name: t, type: e.type };
@@ -5332,7 +5376,7 @@ class LlEmitter {
5332
5376
  store(a.name, e.arg.type);
5333
5377
  };
5334
5378
  const parkIn = (name, t) => {
5335
- if (t.kind === "f64") {
5379
+ if (t.kind === "f64" || t.kind === "date") {
5336
5380
  this.declare(`declare void @scr_gen_in_f64(ptr, double)`);
5337
5381
  B.line(`call void @scr_gen_in_f64(ptr ${g.name}, double ${name})`);
5338
5382
  }
@@ -5378,7 +5422,7 @@ class LlEmitter {
5378
5422
  }
5379
5423
  else {
5380
5424
  sendArg((name, t) => {
5381
- if (t.kind === "f64") {
5425
+ if (t.kind === "f64" || t.kind === "date") {
5382
5426
  this.declare(`declare void @scr_gen_ret_f64(ptr, double)`);
5383
5427
  B.line(`call void @scr_gen_ret_f64(ptr ${g.name}, double ${name})`);
5384
5428
  }
@@ -5431,7 +5475,7 @@ class LlEmitter {
5431
5475
  return { name: "", type: e.type };
5432
5476
  }
5433
5477
  const t = B.tmp();
5434
- if (e.type.kind === "f64") {
5478
+ if (e.type.kind === "f64" || e.type.kind === "date") {
5435
5479
  this.declare(`declare double @scr_await_f64(ptr)`);
5436
5480
  B.line(`${t} = call double @scr_await_f64(ptr ${pr.name})`);
5437
5481
  }
@@ -5511,7 +5555,7 @@ class LlEmitter {
5511
5555
  B.startBlock(lp);
5512
5556
  const peek = this.unionPeek(u.name);
5513
5557
  let awaited;
5514
- if (inner.kind === "f64") {
5558
+ if (inner.kind === "f64" || inner.kind === "date") {
5515
5559
  this.declare(`declare double @scr_await_f64(ptr)`);
5516
5560
  const x = B.tmp();
5517
5561
  B.line(`${x} = call double @scr_await_f64(ptr ${peek})`);
@@ -5588,7 +5632,7 @@ class LlEmitter {
5588
5632
  return out;
5589
5633
  }
5590
5634
  let resolve;
5591
- const kindNums = { f64: 0, bool: 1, string: 2, void: 3 };
5635
+ const kindNums = { f64: 0, date: 0, bool: 1, string: 2, void: 3 };
5592
5636
  const kindNum = kindNums[inner.kind];
5593
5637
  if (kindNum !== undefined) {
5594
5638
  this.declare(`declare ptr @scr_make_resolve(ptr, i32)`);
@@ -5634,7 +5678,7 @@ class LlEmitter {
5634
5678
  this.declare(`declare ptr @scr_make_reject(ptr)`);
5635
5679
  const p = B.tmp();
5636
5680
  B.line(`${p} = call ptr @scr_promise_new()`);
5637
- const kindNums = { f64: 0, bool: 1, string: 2, void: 3 };
5681
+ const kindNums = { f64: 0, date: 0, bool: 1, string: 2, void: 3 };
5638
5682
  const kindNum = kindNums[inner.kind];
5639
5683
  const resolve = B.tmp();
5640
5684
  if (kindNum !== undefined) {
@@ -5749,7 +5793,7 @@ class LlEmitter {
5749
5793
  }
5750
5794
  const v = this.emitExpr(e.args[0]);
5751
5795
  const t = e.args[0].type;
5752
- if (t.kind === "f64") {
5796
+ if (t.kind === "f64" || t.kind === "date") {
5753
5797
  this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
5754
5798
  B.line(`call void @scr_promise_fulfill_f64(ptr ${p}, double ${v.name})`);
5755
5799
  }
@@ -8625,6 +8669,233 @@ class LlEmitter {
8625
8669
  }
8626
8670
  }
8627
8671
  }
8672
+ /** Whether an index/value expression can overwrite the bytes receiver
8673
+ * binding. Kept deliberately conservative; uncertain or calling shapes
8674
+ * retain the ordinary owned-receiver snapshot. */
8675
+ isStableBytesOperand(e, receiverLocalId) {
8676
+ switch (e.kind) {
8677
+ case "numLit":
8678
+ case "boolLit":
8679
+ case "varRef":
8680
+ case "incDec":
8681
+ return true;
8682
+ case "assignExpr":
8683
+ // A bytes assignment nested under truthiness/ternary can still
8684
+ // produce the numeric index/value expected by the outer access.
8685
+ return (e.localId !== receiverLocalId &&
8686
+ this.isStableBytesOperand(e.value, receiverLocalId));
8687
+ case "bin":
8688
+ return (this.isStableBytesOperand(e.left, receiverLocalId) &&
8689
+ this.isStableBytesOperand(e.right, receiverLocalId));
8690
+ case "unary":
8691
+ case "toBool":
8692
+ return this.isStableBytesOperand(e.operand, receiverLocalId);
8693
+ case "logical":
8694
+ return (this.isStableBytesOperand(e.left, receiverLocalId) &&
8695
+ this.isStableBytesOperand(e.right, receiverLocalId));
8696
+ case "ternary":
8697
+ return (this.isStableBytesOperand(e.cond, receiverLocalId) &&
8698
+ this.isStableBytesOperand(e.then, receiverLocalId) &&
8699
+ this.isStableBytesOperand(e.else_, receiverLocalId));
8700
+ case "bytesIntrinsic":
8701
+ return ((e.method === "get" || e.method === "length" || e.method === "byteLength") &&
8702
+ e.receiver.kind === "varRef" &&
8703
+ e.args.every((arg) => this.isStableBytesOperand(arg, receiverLocalId)));
8704
+ default:
8705
+ return false;
8706
+ }
8707
+ }
8708
+ /** Borrow a direct, unboxed bytes binding when later operands are stable.
8709
+ * Its scope/global owner keeps it alive through the access. */
8710
+ emitBytesReceiver(receiver, following) {
8711
+ if (receiver.kind === "varRef" &&
8712
+ following.every((operand) => this.isStableBytesOperand(operand, receiver.localId))) {
8713
+ const b = this.binding(receiver.localId);
8714
+ if (b.kind !== "boxed") {
8715
+ const value = this.B.tmp();
8716
+ this.B.line(`${value} = load ptr, ptr ${b.slot}`);
8717
+ return { name: value, type: receiver.type };
8718
+ }
8719
+ }
8720
+ return this.emitExpr(receiver);
8721
+ }
8722
+ /** Bounds-check a typed-array element index without an out-of-line call
8723
+ * on the valid path. The range test rejects NaN/infinity before fptoui;
8724
+ * the integer round trip then rejects fractions. The error block retains
8725
+ * the runtime's exact trap text. Leaves the builder in the valid block. */
8726
+ emitBytesIndex(receiver, index) {
8727
+ const B = this.B;
8728
+ const lenPtr = B.tmp();
8729
+ const len = B.tmp();
8730
+ const lenF64 = B.tmp();
8731
+ const nonnegative = B.tmp();
8732
+ const belowLen = B.tmp();
8733
+ 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
+ B.line(`${lenF64} = uitofp i64 ${len} to double`);
8737
+ B.line(`${nonnegative} = fcmp oge double ${index}, ${f64Lit(0)}`);
8738
+ B.line(`${belowLen} = fcmp olt double ${index}, ${lenF64}`);
8739
+ B.line(`${inRange} = and i1 ${nonnegative}, ${belowLen}`);
8740
+ const rangeOk = B.newLabel("bytes.index.range");
8741
+ const invalid = B.newLabel("bytes.index.invalid");
8742
+ const valid = B.newLabel("bytes.index.valid");
8743
+ B.condBr(inRange, rangeOk, invalid);
8744
+ B.startBlock(rangeOk);
8745
+ const idx = B.tmp();
8746
+ const roundTrip = B.tmp();
8747
+ const integral = B.tmp();
8748
+ B.line(`${idx} = fptoui double ${index} to i64`);
8749
+ B.line(`${roundTrip} = uitofp i64 ${idx} to double`);
8750
+ B.line(`${integral} = fcmp oeq double ${roundTrip}, ${index}`);
8751
+ B.condBr(integral, valid, invalid);
8752
+ B.startBlock(invalid);
8753
+ this.declare(`declare double @scr_bytes_get(ptr, double)`);
8754
+ B.line(`call double @scr_bytes_get(ptr ${receiver}, double ${index})`);
8755
+ B.terminate("unreachable");
8756
+ B.startBlock(valid);
8757
+ return idx;
8758
+ }
8759
+ /** Load the raw data pointer from ScrBytes after the bounds check. */
8760
+ emitBytesData(receiver) {
8761
+ const p = this.B.tmp();
8762
+ const data = this.B.tmp();
8763
+ this.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 3`);
8764
+ this.B.line(`${data} = load ptr, ptr ${p}`);
8765
+ return data;
8766
+ }
8767
+ /** Static typed-array length/byteLength load. */
8768
+ emitBytesLength(elem, receiver, bytes) {
8769
+ const B = this.B;
8770
+ const p = B.tmp();
8771
+ const len = B.tmp();
8772
+ B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`);
8773
+ B.line(`${len} = load i64, ptr ${p}`);
8774
+ const count = bytes && elem !== "u8" ? B.tmp() : len;
8775
+ if (count !== len)
8776
+ B.line(`${count} = shl i64 ${len}, 2`);
8777
+ const out = B.tmp();
8778
+ B.line(`${out} = uitofp i64 ${count} to double`);
8779
+ return { name: out, type: F64 };
8780
+ }
8781
+ /** Kind-specialized typed-array load. */
8782
+ emitBytesGet(elem, receiver, index) {
8783
+ const B = this.B;
8784
+ const idx = this.emitBytesIndex(receiver, index);
8785
+ const data = this.emitBytesData(receiver);
8786
+ const p = B.tmp();
8787
+ if (elem === "u8") {
8788
+ const raw = B.tmp();
8789
+ const wide = B.tmp();
8790
+ const out = B.tmp();
8791
+ B.line(`${p} = getelementptr inbounds i8, ptr ${data}, i64 ${idx}`);
8792
+ B.line(`${raw} = load i8, ptr ${p}, align 1`);
8793
+ B.line(`${wide} = zext i8 ${raw} to i32`);
8794
+ B.line(`${out} = uitofp i32 ${wide} to double`);
8795
+ return { name: out, type: F64 };
8796
+ }
8797
+ if (elem === "f32") {
8798
+ const raw = B.tmp();
8799
+ const out = B.tmp();
8800
+ B.line(`${p} = getelementptr inbounds float, ptr ${data}, i64 ${idx}`);
8801
+ B.line(`${raw} = load float, ptr ${p}, align 1`);
8802
+ B.line(`${out} = fpext float ${raw} to double`);
8803
+ return { name: out, type: F64 };
8804
+ }
8805
+ const raw = B.tmp();
8806
+ const out = B.tmp();
8807
+ B.line(`${p} = getelementptr inbounds i32, ptr ${data}, i64 ${idx}`);
8808
+ B.line(`${raw} = load i32, ptr ${p}, align 1`);
8809
+ B.line(`${out} = ${elem === "i32" ? "sitofp" : "uitofp"} i32 ${raw} to double`);
8810
+ return { name: out, type: F64 };
8811
+ }
8812
+ /** ToUint32/ToInt32 coercion with the overwhelmingly common finite,
8813
+ * exactly-representable JS-number range on a cast-only path. Exceptional
8814
+ * and huge values remain inline in the program TU so unrelated runtime
8815
+ * binaries do not acquire another exported helper. */
8816
+ emitBytesU32(value) {
8817
+ const B = this.B;
8818
+ const aboveMin = B.tmp();
8819
+ const belowMax = B.tmp();
8820
+ const fast = B.tmp();
8821
+ B.line(`${aboveMin} = fcmp oge double ${value}, ${f64Lit(-9007199254740992)}`);
8822
+ B.line(`${belowMax} = fcmp ole double ${value}, ${f64Lit(9007199254740992)}`);
8823
+ B.line(`${fast} = and i1 ${aboveMin}, ${belowMax}`);
8824
+ const fastLabel = B.newLabel("bytes.coerce.fast");
8825
+ const slowLabel = B.newLabel("bytes.coerce.slow");
8826
+ const done = B.newLabel("bytes.coerce.done");
8827
+ B.condBr(fast, fastLabel, slowLabel);
8828
+ B.startBlock(fastLabel);
8829
+ const signed = B.tmp();
8830
+ const fastU32 = B.tmp();
8831
+ B.line(`${signed} = fptosi double ${value} to i64`);
8832
+ B.line(`${fastU32} = trunc i64 ${signed} to i32`);
8833
+ B.br(done);
8834
+ B.startBlock(slowLabel);
8835
+ const ordered = B.tmp();
8836
+ const belowInf = B.tmp();
8837
+ const aboveNegInf = B.tmp();
8838
+ const finiteRange = B.tmp();
8839
+ const finite = B.tmp();
8840
+ B.line(`${ordered} = fcmp ord double ${value}, ${value}`);
8841
+ B.line(`${belowInf} = fcmp olt double ${value}, ${F64_INF}`);
8842
+ B.line(`${aboveNegInf} = fcmp ogt double ${value}, ${f64Lit(-Infinity)}`);
8843
+ B.line(`${finiteRange} = and i1 ${belowInf}, ${aboveNegInf}`);
8844
+ B.line(`${finite} = and i1 ${ordered}, ${finiteRange}`);
8845
+ const finiteLabel = B.newLabel("bytes.coerce.finite");
8846
+ const nonfiniteLabel = B.newLabel("bytes.coerce.nonfinite");
8847
+ const slowDone = B.newLabel("bytes.coerce.slow.done");
8848
+ B.condBr(finite, finiteLabel, nonfiniteLabel);
8849
+ B.startBlock(finiteLabel);
8850
+ this.declare(`declare double @llvm.trunc.f64(double)`);
8851
+ const truncated = B.tmp();
8852
+ const residue = B.tmp();
8853
+ const negative = B.tmp();
8854
+ const wrapped = B.tmp();
8855
+ const normalized = B.tmp();
8856
+ const finiteU32 = B.tmp();
8857
+ B.line(`${truncated} = call double @llvm.trunc.f64(double ${value})`);
8858
+ B.line(`${residue} = frem double ${truncated}, ${f64Lit(4294967296)}`);
8859
+ B.line(`${negative} = fcmp olt double ${residue}, ${f64Lit(0)}`);
8860
+ B.line(`${wrapped} = fadd double ${residue}, ${f64Lit(4294967296)}`);
8861
+ B.line(`${normalized} = select i1 ${negative}, double ${wrapped}, double ${residue}`);
8862
+ B.line(`${finiteU32} = fptoui double ${normalized} to i32`);
8863
+ B.br(slowDone);
8864
+ B.startBlock(nonfiniteLabel);
8865
+ B.br(slowDone);
8866
+ B.startBlock(slowDone);
8867
+ const slowU32 = B.tmp();
8868
+ B.line(`${slowU32} = phi i32 [ ${finiteU32}, %${finiteLabel} ], [ 0, %${nonfiniteLabel} ]`);
8869
+ B.br(done);
8870
+ B.startBlock(done);
8871
+ const out = B.tmp();
8872
+ B.line(`${out} = phi i32 [ ${fastU32}, %${fastLabel} ], [ ${slowU32}, %${slowDone} ]`);
8873
+ return out;
8874
+ }
8875
+ /** Kind-specialized typed-array store. */
8876
+ emitBytesSet(elem, receiver, index, value) {
8877
+ const B = this.B;
8878
+ const idx = this.emitBytesIndex(receiver, index);
8879
+ const stored = elem === "f32" ? null : this.emitBytesU32(value);
8880
+ const data = this.emitBytesData(receiver);
8881
+ const p = B.tmp();
8882
+ if (elem === "u8") {
8883
+ const byte = B.tmp();
8884
+ B.line(`${byte} = trunc i32 ${stored} to i8`);
8885
+ B.line(`${p} = getelementptr inbounds i8, ptr ${data}, i64 ${idx}`);
8886
+ B.line(`store i8 ${byte}, ptr ${p}, align 1`);
8887
+ return;
8888
+ }
8889
+ if (elem === "f32") {
8890
+ const narrowed = B.tmp();
8891
+ B.line(`${narrowed} = fptrunc double ${value} to float`);
8892
+ B.line(`${p} = getelementptr inbounds float, ptr ${data}, i64 ${idx}`);
8893
+ B.line(`store float ${narrowed}, ptr ${p}, align 1`);
8894
+ return;
8895
+ }
8896
+ B.line(`${p} = getelementptr inbounds i32, ptr ${data}, i64 ${idx}`);
8897
+ B.line(`store i32 ${stored}, ptr ${p}, align 1`);
8898
+ }
8628
8899
  /** The bytesIntrinsic surface (emit-exprs.ts's switch, .ll flavored).
8629
8900
  * Receiver and args are borrowed frame temps; string/bytes results come
8630
8901
  * back +1. The MAY_THROW methods get the standard pending check after
@@ -8672,18 +8943,30 @@ class LlEmitter {
8672
8943
  ? 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
8944
  : 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
8945
  }
8675
- const r = this.emitExpr(e.receiver);
8676
- const args = e.args.map((a) => this.emitExpr(a));
8677
8946
  const method = e.method;
8947
+ const directElementAccess = method === "length" || method === "byteLength" || method === "get";
8948
+ const r = directElementAccess
8949
+ ? this.emitBytesReceiver(e.receiver, e.args)
8950
+ : this.emitExpr(e.receiver);
8951
+ const args = e.args.map((a) => this.emitExpr(a));
8678
8952
  const NAN = f64Lit(NaN);
8679
8953
  switch (method) {
8680
8954
  case "length":
8681
- return call("scr_bytes_len", "double (ptr)", `ptr ${r.name}`, false, false);
8955
+ if (e.receiver.type.kind !== "bytes") {
8956
+ throw new Error("llvm emitter bug: bytesIntrinsic length on non-bytes");
8957
+ }
8958
+ return this.emitBytesLength(e.receiver.type.elem, r.name, false);
8682
8959
  case "byteLength":
8683
- return call("scr_bytes_byte_len", "double (ptr)", `ptr ${r.name}`, false, false);
8960
+ if (e.receiver.type.kind !== "bytes") {
8961
+ throw new Error("llvm emitter bug: bytesIntrinsic byteLength on non-bytes");
8962
+ }
8963
+ return this.emitBytesLength(e.receiver.type.elem, r.name, true);
8684
8964
  case "get":
8685
8965
  // 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);
8966
+ if (e.receiver.type.kind !== "bytes") {
8967
+ throw new Error("llvm emitter bug: bytesIntrinsic get on non-bytes");
8968
+ }
8969
+ return this.emitBytesGet(e.receiver.type.elem, r.name, args[0].name);
8687
8970
  case "slice":
8688
8971
  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
8972
  case "subarray":