@scriptc/compiler 0.3.5 → 0.3.6

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 (181) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +278 -32
  2. package/dist/backend/llvm/dyn.js +31 -1
  3. package/dist/backend/llvm/dyn.js.map +1 -1
  4. package/dist/backend/llvm/emitter.d.ts +45 -1
  5. package/dist/backend/llvm/emitter.js +412 -104
  6. package/dist/backend/llvm/emitter.js.map +1 -1
  7. package/dist/backend/llvm/expr-dispatch.js +1 -0
  8. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  9. package/dist/backend/llvm/expr-dynamic.js +25 -1
  10. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  11. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  12. package/dist/backend/llvm/expr-primitives.js +11 -0
  13. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  14. package/dist/backend/llvm/lib-dispatch.js +28 -1
  15. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  16. package/dist/backend/llvm/lib-filesystem.js +61 -1
  17. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  18. package/dist/backend/llvm/lib-network.js +21 -3
  19. package/dist/backend/llvm/lib-network.js.map +1 -1
  20. package/dist/backend/llvm/lib-process.js +10 -0
  21. package/dist/backend/llvm/lib-process.js.map +1 -1
  22. package/dist/backend/llvm/lib-shared.js +43 -0
  23. package/dist/backend/llvm/lib-shared.js.map +1 -1
  24. package/dist/backend/llvm/lib-test.js +16 -3
  25. package/dist/backend/llvm/lib-test.js.map +1 -1
  26. package/dist/backend/llvm/nullable-unions.d.ts +6 -3
  27. package/dist/backend/llvm/nullable-unions.js +2 -0
  28. package/dist/backend/llvm/nullable-unions.js.map +1 -1
  29. package/dist/backend/llvm/record-results.d.ts +1 -1
  30. package/dist/backend/llvm/record-results.js +2 -2
  31. package/dist/backend/llvm/record-results.js.map +1 -1
  32. package/dist/backend/llvm/reference-writes.js +1 -0
  33. package/dist/backend/llvm/reference-writes.js.map +1 -1
  34. package/dist/backend/llvm/shapes.d.ts +3 -0
  35. package/dist/backend/llvm/shapes.js +5 -0
  36. package/dist/backend/llvm/shapes.js.map +1 -1
  37. package/dist/backend/llvm/speculative-integers.d.ts +74 -0
  38. package/dist/backend/llvm/speculative-integers.js +571 -0
  39. package/dist/backend/llvm/speculative-integers.js.map +1 -0
  40. package/dist/backend/llvm/speculative-integers.test.d.ts +1 -0
  41. package/dist/backend/llvm/speculative-integers.test.js +148 -0
  42. package/dist/backend/llvm/speculative-integers.test.js.map +1 -0
  43. package/dist/backend/may-throw.js +2 -0
  44. package/dist/backend/may-throw.js.map +1 -1
  45. package/dist/backend/native/executable.js +3 -0
  46. package/dist/backend/native/executable.js.map +1 -1
  47. package/dist/backend/native/runtime-inputs.d.ts +1 -1
  48. package/dist/backend/native/runtime-inputs.js +1 -0
  49. package/dist/backend/native/runtime-inputs.js.map +1 -1
  50. package/dist/backend/runtime-pack.d.ts +7 -3
  51. package/dist/backend/runtime-pack.js +12 -6
  52. package/dist/backend/runtime-pack.js.map +1 -1
  53. package/dist/backend/runtime-pack.test.js +15 -6
  54. package/dist/backend/runtime-pack.test.js.map +1 -1
  55. package/dist/contracts/check.js +1 -0
  56. package/dist/contracts/check.js.map +1 -1
  57. package/dist/coverage/surface-manifest.js +3 -3
  58. package/dist/coverage/surface-manifest.js.map +1 -1
  59. package/dist/diagnostics/diagnostic.d.ts +1 -1
  60. package/dist/diagnostics/diagnostic.js +6 -2
  61. package/dist/diagnostics/diagnostic.js.map +1 -1
  62. package/dist/frontend/lowering/builtin-calls.js +10 -0
  63. package/dist/frontend/lowering/builtin-calls.js.map +1 -1
  64. package/dist/frontend/lowering/builtins/callable-aliases.js +31 -3
  65. package/dist/frontend/lowering/builtins/callable-aliases.js.map +1 -1
  66. package/dist/frontend/lowering/builtins/compression.d.ts +9 -0
  67. package/dist/frontend/lowering/builtins/compression.js +145 -30
  68. package/dist/frontend/lowering/builtins/compression.js.map +1 -1
  69. package/dist/frontend/lowering/builtins/crypto.js +12 -1
  70. package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
  71. package/dist/frontend/lowering/builtins/errors.js +5 -3
  72. package/dist/frontend/lowering/builtins/errors.js.map +1 -1
  73. package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +4 -0
  74. package/dist/frontend/lowering/builtins/filesystem-calls.js +11 -0
  75. package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
  76. package/dist/frontend/lowering/builtins/filesystem.d.ts +8 -0
  77. package/dist/frontend/lowering/builtins/filesystem.js +139 -2
  78. package/dist/frontend/lowering/builtins/filesystem.js.map +1 -1
  79. package/dist/frontend/lowering/builtins/hash-stream.d.ts +12 -0
  80. package/dist/frontend/lowering/builtins/hash-stream.js +80 -0
  81. package/dist/frontend/lowering/builtins/hash-stream.js.map +1 -0
  82. package/dist/frontend/lowering/builtins/module-calls.js +13 -3
  83. package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
  84. package/dist/frontend/lowering/builtins/process.js +9 -2
  85. package/dist/frontend/lowering/builtins/process.js.map +1 -1
  86. package/dist/frontend/lowering/builtins/readline.d.ts +45 -13
  87. package/dist/frontend/lowering/builtins/readline.js +812 -47
  88. package/dist/frontend/lowering/builtins/readline.js.map +1 -1
  89. package/dist/frontend/lowering/builtins/workers.d.ts +4 -0
  90. package/dist/frontend/lowering/builtins/workers.js +58 -0
  91. package/dist/frontend/lowering/builtins/workers.js.map +1 -1
  92. package/dist/frontend/lowering/class-protocols.d.ts +55 -0
  93. package/dist/frontend/lowering/class-protocols.js +735 -0
  94. package/dist/frontend/lowering/class-protocols.js.map +1 -1
  95. package/dist/frontend/lowering/containers/array-from-async.d.ts +18 -0
  96. package/dist/frontend/lowering/containers/array-from-async.js +282 -0
  97. package/dist/frontend/lowering/containers/array-from-async.js.map +1 -0
  98. package/dist/frontend/lowering/containers/collection-grouping.js +2 -1
  99. package/dist/frontend/lowering/containers/collection-grouping.js.map +1 -1
  100. package/dist/frontend/lowering/containers/collection-methods.js +27 -23
  101. package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
  102. package/dist/frontend/lowering/divergences.js +188 -15
  103. package/dist/frontend/lowering/divergences.js.map +1 -1
  104. package/dist/frontend/lowering/lower-calls.js +71 -22
  105. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  106. package/dist/frontend/lowering/lower-classes.js +39 -1
  107. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  108. package/dist/frontend/lowering/lower-event-emitter.js +10 -1
  109. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  110. package/dist/frontend/lowering/lower-exprs.js +107 -13
  111. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  112. package/dist/frontend/lowering/lower-inspect.js +23 -3
  113. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  114. package/dist/frontend/lowering/lower-mock.d.ts +11 -1
  115. package/dist/frontend/lowering/lower-mock.js +377 -61
  116. package/dist/frontend/lowering/lower-mock.js.map +1 -1
  117. package/dist/frontend/lowering/lower-modules.js +1 -0
  118. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  119. package/dist/frontend/lowering/lower-server.js +161 -16
  120. package/dist/frontend/lowering/lower-server.js.map +1 -1
  121. package/dist/frontend/lowering/lower-stmts.js +48 -7
  122. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  123. package/dist/frontend/lowering/lower-stream.d.ts +1 -3
  124. package/dist/frontend/lowering/lower-stream.js +317 -20
  125. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  126. package/dist/frontend/lowering/lower-test.js +147 -48
  127. package/dist/frontend/lowering/lower-test.js.map +1 -1
  128. package/dist/frontend/lowering/lowerer.d.ts +10 -0
  129. package/dist/frontend/lowering/lowerer.js +8 -0
  130. package/dist/frontend/lowering/lowerer.js.map +1 -1
  131. package/dist/frontend/lowering/null-prototype.d.ts +13 -0
  132. package/dist/frontend/lowering/null-prototype.js +33 -0
  133. package/dist/frontend/lowering/null-prototype.js.map +1 -0
  134. package/dist/frontend/lowering/surfaces.d.ts +3 -3
  135. package/dist/frontend/lowering/surfaces.js +27 -5
  136. package/dist/frontend/lowering/surfaces.js.map +1 -1
  137. package/dist/frontend/mock-shapes.d.ts +5 -0
  138. package/dist/frontend/mock-shapes.js +27 -2
  139. package/dist/frontend/mock-shapes.js.map +1 -1
  140. package/dist/frontend/program.js +5 -4
  141. package/dist/frontend/program.js.map +1 -1
  142. package/dist/frontend/type-mapper.js +95 -4
  143. package/dist/frontend/type-mapper.js.map +1 -1
  144. package/dist/index.js +18 -8
  145. package/dist/index.js.map +1 -1
  146. package/dist/ir/builtin-effects.js +36 -0
  147. package/dist/ir/builtin-effects.js.map +1 -1
  148. package/dist/ir/builtin-signatures.d.ts +1 -1
  149. package/dist/ir/builtin-signatures.js +167 -6
  150. package/dist/ir/builtin-signatures.js.map +1 -1
  151. package/dist/ir/function-references.test.js +1 -0
  152. package/dist/ir/function-references.test.js.map +1 -1
  153. package/dist/ir/int32-slots.js +22 -0
  154. package/dist/ir/int32-slots.js.map +1 -1
  155. package/dist/ir/int32-slots.test.js +28 -0
  156. package/dist/ir/int32-slots.test.js.map +1 -1
  157. package/dist/ir/integer-loop-facts.d.ts +14 -6
  158. package/dist/ir/integer-loop-facts.js +119 -35
  159. package/dist/ir/integer-loop-facts.js.map +1 -1
  160. package/dist/ir/integer-ranges.js +1 -5
  161. package/dist/ir/integer-ranges.js.map +1 -1
  162. package/dist/ir/integer-ranges.test.js +60 -0
  163. package/dist/ir/integer-ranges.test.js.map +1 -1
  164. package/dist/ir/ir.d.ts +13 -0
  165. package/dist/ir/ir.js +4 -0
  166. package/dist/ir/ir.js.map +1 -1
  167. package/dist/ir/runtime-features.d.ts +3 -1
  168. package/dist/ir/runtime-features.js +96 -6
  169. package/dist/ir/runtime-features.js.map +1 -1
  170. package/dist/ir/runtime-features.test.js +62 -0
  171. package/dist/ir/runtime-features.test.js.map +1 -1
  172. package/dist/ir/traverse.js +4 -0
  173. package/dist/ir/traverse.js.map +1 -1
  174. package/dist/ir/validate.js +45 -3
  175. package/dist/ir/validate.js.map +1 -1
  176. package/dist/ir/validate.test.js +38 -0
  177. package/dist/ir/validate.test.js.map +1 -1
  178. package/dist/library/ir-policy.js +5 -0
  179. package/dist/library/ir-policy.js.map +1 -1
  180. package/package.json +20 -20
  181. package/surface-manifest.json +31 -19
@@ -13,6 +13,7 @@ import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCou
13
13
  import { moduleRuntimeFeatures } from "../../ir/runtime-features.js";
14
14
  import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
15
15
  import { emitCountedLoopLimit } from "./counted-loops.js";
16
+ import { emitSafeIntegerCheck, emitSpeculativeInteger, planSpeculativeLoop, speculativeReads, speculativeWriteTarget, SPECULATIVE_RANGE, } from "./speculative-integers.js";
16
17
  import { emitLiteralSwitch } from "./switch-dispatch.js";
17
18
  import { findBytesBounds } from "./bytes-bounds.js";
18
19
  import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow, } from "./byte-windows.js";
@@ -101,7 +102,7 @@ import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
101
102
  import { MAP_ITER_DECL, NUMBER_MAP_ENTRY_DECL, STRING_MAP_ENTRY_DECL, } from "./map-number-lookup.js";
102
103
  import { STRING_READ_HELPERS, stringScanDecl } from "./string-reads.js";
103
104
  import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
104
- import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, limitInlineDynReleases, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
105
+ import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, limitInlineDynReleases, llFieldType, mapFlagsOffset, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
105
106
  import { INLINE_LITERAL_BYTES, literalEqualityDefinition } from "./string-literal-equality.js";
106
107
  export { LlvmUnsupportedError } from "./unsupported.js";
107
108
  function ffiCallbackDummyLl(callback) {
@@ -421,6 +422,9 @@ export class LlEmitter {
421
422
  stableGlobals = new Set();
422
423
  tdzGlobals = new Map();
423
424
  constantNumericTables;
425
+ /** Constant-key reads on checked-dynamic objects, each with its own
426
+ * zero-initialized ScrDynReadIc (scr_runtime.h). */
427
+ dynReadIcSites = 0;
424
428
  /** May-throw analysis (computeMayThrow): pending
425
429
  * checks are emitted only after calls that can actually raise. */
426
430
  mayThrow;
@@ -562,6 +566,17 @@ export class LlEmitter {
562
566
  /** Active induction bindings retain their width independently of size_t. */
563
567
  integerLoopBindings = new Map();
564
568
  countedLoopsEnabled = false;
569
+ /** Speculative integer loops (speculative-integers.ts) run in this function. */
570
+ speculativeLoopsEnabled = false;
571
+ /** The fast copy of a speculative integer loop being emitted: its
572
+ * integer-computed writes and where each resumes in the ordinary copy. */
573
+ speculation = null;
574
+ /** The ordinary copy being emitted: resume labels still to place. */
575
+ speculationResume = null;
576
+ /** The function being emitted, for speculative globals. */
577
+ speculativeFunction = "";
578
+ /** Every function that writes each number global (computed on demand). */
579
+ globalWriters = null;
565
580
  /** Whole-program final reference fields (ir/final-fields.ts). */
566
581
  finalFields;
567
582
  /** The current function may borrow final fields of borrowable objects:
@@ -1691,6 +1706,11 @@ export class LlEmitter {
1691
1706
  out.push(d);
1692
1707
  out.push(``);
1693
1708
  const flushedDecls = new Set(this.decls);
1709
+ {
1710
+ const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
1711
+ for (let site = 0; site < this.dynReadIcSites; site++)
1712
+ out.push(`@sc_dyn_read_ic_${site} = internal ${tl}global { ptr, ${this.sizeType}, ${this.sizeType}, ptr } zeroinitializer`);
1713
+ }
1694
1714
  for (const table of this.constantNumericTables.values()) {
1695
1715
  const n = table.values.length;
1696
1716
  let tableTmp = 0;
@@ -3723,6 +3743,259 @@ export class LlEmitter {
3723
3743
  this.B.line(`store i32 0, ptr ${slot}`);
3724
3744
  return slot;
3725
3745
  }
3746
+ /** Emit a loop's condition, body and update through `emitCopy`, twice
3747
+ * when speculative-integers.ts plans a fast integer copy. Both copies
3748
+ * share the loop's setup and exit; the ordinary copy comes first so the
3749
+ * fast copy is only emitted when every resume point exists. */
3750
+ emitLoopCopies(loop, allowed, exit, emitCopy) {
3751
+ const plan = allowed &&
3752
+ this.speculativeLoopsEnabled &&
3753
+ this.speculation === null &&
3754
+ this.speculationResume === null
3755
+ ? planSpeculativeLoop(loop, (id) => this.speculativeLocal(id), (id) => this.integerLoopBindings.has(id) || this.int32Locals.has(id), (e) => {
3756
+ const range = this.integerRanges.get(e);
3757
+ return (range !== undefined &&
3758
+ range !== null &&
3759
+ range.min >= -Number.MAX_SAFE_INTEGER &&
3760
+ range.max <= Number.MAX_SAFE_INTEGER);
3761
+ }, (id) => this.speculativeLocal(id) || this.speculativeInvariantGlobal(id))
3762
+ : null;
3763
+ if (!plan) {
3764
+ emitCopy(exit);
3765
+ return;
3766
+ }
3767
+ const B = this.B;
3768
+ const entries = new Map();
3769
+ let ok = "true";
3770
+ for (const [id, live] of plan.locals) {
3771
+ if (!live)
3772
+ continue;
3773
+ const value = B.tmp();
3774
+ B.line(`${value} = load double, ptr ${this.binding(id).slot}`);
3775
+ const [integer, exact] = emitSafeIntegerCheck(this, value);
3776
+ entries.set(id, integer);
3777
+ if (ok === "true")
3778
+ ok = exact;
3779
+ else {
3780
+ const both = B.tmp();
3781
+ B.line(`${both} = and i1 ${ok}, ${exact}`);
3782
+ ok = both;
3783
+ }
3784
+ }
3785
+ const fast = B.newLabel("spec.fast"), slow = B.newLabel("spec.slow");
3786
+ B.condBr(ok, fast, slow);
3787
+ const resume = new Map();
3788
+ for (const s of plan.writes)
3789
+ resume.set(s, { label: B.newLabel("spec.resume"), placed: false });
3790
+ B.startBlock(slow);
3791
+ this.speculationResume = resume;
3792
+ try {
3793
+ emitCopy(exit);
3794
+ }
3795
+ finally {
3796
+ this.speculationResume = null;
3797
+ }
3798
+ B.startBlock(fast);
3799
+ if (![...resume.values()].every((r) => r.placed)) {
3800
+ B.br(slow);
3801
+ return;
3802
+ }
3803
+ for (const id of plan.locals.keys()) {
3804
+ const shadow = B.slot();
3805
+ B.entryAllocas.push(`${shadow} = alloca i64 ; speculative integer ${this.currentLocals.get(id)?.name ?? id}`);
3806
+ const entry = entries.get(id);
3807
+ if (entry !== undefined) {
3808
+ // The same double, now visibly an integer conversion: the double
3809
+ // phis of the fast copy fold into integer phis.
3810
+ const exact = B.tmp();
3811
+ B.line(`store i64 ${entry}, ptr ${shadow}`);
3812
+ B.line(`${exact} = sitofp i64 ${entry} to double`);
3813
+ B.line(`store double ${exact}, ptr ${this.binding(id).slot}`);
3814
+ }
3815
+ else
3816
+ B.line(`store i64 0, ptr ${shadow}`);
3817
+ this.integerLoopBindings.set(id, {
3818
+ slot: shadow,
3819
+ type: "i64",
3820
+ signed: true,
3821
+ range: SPECULATIVE_RANGE,
3822
+ });
3823
+ }
3824
+ // A global's store stays in the loop (calls may read it). When nothing
3825
+ // in the loop can observe it, its double is written once on leaving the
3826
+ // copy instead, so the loop keeps it in a register as the double copy
3827
+ // would.
3828
+ const deferred = new Set();
3829
+ // Worker loops also leave through their cancellation poll.
3830
+ if (plan.observerFree && !this.mod.workers)
3831
+ for (const s of plan.writes) {
3832
+ const id = speculativeWriteTarget(s).localId;
3833
+ if (!this.currentLocals.has(id))
3834
+ deferred.add(id);
3835
+ }
3836
+ this.speculation = {
3837
+ writes: plan.writes,
3838
+ resume: new Map([...resume].map(([s, r]) => [s, r.label])),
3839
+ deferred,
3840
+ };
3841
+ try {
3842
+ if (deferred.size === 0)
3843
+ emitCopy(exit);
3844
+ else {
3845
+ const leave = B.newLabel("spec.leave");
3846
+ emitCopy(leave);
3847
+ B.startBlock(leave);
3848
+ this.writeBackSpeculativeGlobals(deferred);
3849
+ B.br(exit);
3850
+ }
3851
+ }
3852
+ finally {
3853
+ this.speculation = null;
3854
+ for (const id of plan.locals.keys())
3855
+ this.integerLoopBindings.delete(id);
3856
+ }
3857
+ }
3858
+ writeBackSpeculativeGlobals(deferred) {
3859
+ const B = this.B;
3860
+ for (const id of deferred) {
3861
+ const integer = B.tmp(), number = B.tmp();
3862
+ B.line(`${integer} = load i64, ptr ${this.integerLoopBindings.get(id).slot}`);
3863
+ B.line(`${number} = sitofp i64 ${integer} to double`);
3864
+ B.line(`store double ${number}, ptr ${this.binding(id).slot}`);
3865
+ }
3866
+ }
3867
+ /** A number local whose only storage is its own double slot, so an i64
3868
+ * shadow beside it can stand for it within one loop. */
3869
+ speculativeLocal(id) {
3870
+ const local = this.currentLocals.get(id);
3871
+ if (!local)
3872
+ return this.speculativeGlobal(id);
3873
+ if (local.type.kind !== "f64" || local.boxed || local.tdz)
3874
+ return false;
3875
+ return !(this.captureIds.has(id) ||
3876
+ this.int32Locals.has(id) ||
3877
+ this.integerLoopBindings.has(id) ||
3878
+ this.integerViews.has(id) ||
3879
+ this.localUnionStorage.has(id) ||
3880
+ this.reboundOwnerSlots.has(id) ||
3881
+ this.lazyCaptureBoxes.has(id) ||
3882
+ this.privateSplitLocals.has(id));
3883
+ }
3884
+ /** A module-level number written only by the file's top-level code that
3885
+ * is emitting this loop. Calls may still read it: the fast copy keeps its
3886
+ * double current. An init function runs once and is never re-entered, so
3887
+ * no call from the loop can write the global behind the shadow. */
3888
+ speculativeGlobal(id) {
3889
+ if (!this.speculativeFunction.startsWith("%init."))
3890
+ return false;
3891
+ const writers = this.numberGlobalWriters(id);
3892
+ return (writers !== null &&
3893
+ (writers.size === 0 || (writers.size === 1 && writers.has(this.speculativeFunction))));
3894
+ }
3895
+ /** A module-level number a loop of any function only reads, written by
3896
+ * nothing but top-level code: an init function never runs during a loop
3897
+ * of another function, so the entry value stays its value. */
3898
+ speculativeInvariantGlobal(id) {
3899
+ const writers = this.numberGlobalWriters(id);
3900
+ return writers !== null && [...writers].every((name) => name.startsWith("%init."));
3901
+ }
3902
+ /** The functions writing a number global a shadow may stand for, or null
3903
+ * when the global is not such a number. */
3904
+ numberGlobalWriters(id) {
3905
+ if (this.globalTypes.get(id)?.kind !== "f64")
3906
+ return null;
3907
+ if (this.tdzGlobals.has(id) || this.integerViews.has(id))
3908
+ return null;
3909
+ if (!this.globalWriters) {
3910
+ const writers = new Map();
3911
+ for (const fn of this.mod.functions) {
3912
+ const note = (localId) => {
3913
+ if (!localId.startsWith("%g."))
3914
+ return;
3915
+ const set = writers.get(localId) ?? new Set();
3916
+ set.add(fn.name);
3917
+ writers.set(localId, set);
3918
+ };
3919
+ everyStmtList(fn.body, {
3920
+ stmt: (s) => {
3921
+ if (s.kind === "assign" || s.kind === "varDecl")
3922
+ note(s.localId);
3923
+ return true;
3924
+ },
3925
+ expr: (e) => {
3926
+ if (e.kind === "assignExpr" || e.kind === "incDec")
3927
+ note(e.localId);
3928
+ return true;
3929
+ },
3930
+ });
3931
+ }
3932
+ this.globalWriters = writers;
3933
+ }
3934
+ return this.globalWriters.get(id) ?? new Set();
3935
+ }
3936
+ /** A speculated write in the fast copy: compute the value as a safe
3937
+ * integer, store it to the shadow and its exact double to the local.
3938
+ * Any failed check re-executes the statement in the ordinary copy. */
3939
+ emitSpeculativeWrite(s, resume) {
3940
+ const B = this.B;
3941
+ const target = speculativeWriteTarget(s);
3942
+ const binding = this.integerLoopBindings.get(target.localId);
3943
+ const failed = B.newLabel("spec.bail");
3944
+ let bails = false;
3945
+ this.declare("declare i1 @llvm.expect.i1(i1, i1)");
3946
+ const bail = (condition) => {
3947
+ if (condition === "false")
3948
+ return;
3949
+ bails = true;
3950
+ const expected = B.tmp();
3951
+ B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${condition}, i1 false)`);
3952
+ const next = B.newLabel("spec.ok");
3953
+ B.condBr(expected, failed, next);
3954
+ B.startBlock(next);
3955
+ };
3956
+ // Heap reads come first, before any check, and release their temporaries
3957
+ // at once: a bailing path then owns nothing, and reads have no effect
3958
+ // whose reordering before the arithmetic could be observed.
3959
+ const reads = new Map();
3960
+ if (target.value !== null) {
3961
+ this.frames.push([]);
3962
+ for (const read of speculativeReads(target.value))
3963
+ reads.set(read, this.emitExpr(read));
3964
+ this.releaseFrame(this.frames.pop());
3965
+ }
3966
+ this.frames.push([]);
3967
+ let result;
3968
+ if (target.value === null) {
3969
+ const old = B.tmp();
3970
+ result = B.tmp();
3971
+ B.line(`${old} = load i64, ptr ${binding.slot}`);
3972
+ B.line(`${result} = ${target.op === "+" ? "add" : "sub"} nsw i64 ${old}, 1`);
3973
+ const biased = B.tmp(), outside = B.tmp();
3974
+ B.line(`${biased} = add i64 ${result}, ${Number.MAX_SAFE_INTEGER}`);
3975
+ B.line(`${outside} = icmp ugt i64 ${biased}, ${2 * Number.MAX_SAFE_INTEGER}`);
3976
+ bail(outside);
3977
+ }
3978
+ else
3979
+ result = emitSpeculativeInteger(this, target.value, bail, reads);
3980
+ // Speculable values read only numbers past this point.
3981
+ if (this.frames.pop().length > 0)
3982
+ throw new InternalCompilerError("llvm emitter bug: speculative write owns a temporary");
3983
+ B.line(`store i64 ${result}, ptr ${binding.slot}`);
3984
+ const deferred = this.speculation.deferred;
3985
+ if (!deferred.has(target.localId)) {
3986
+ const number = B.tmp();
3987
+ B.line(`${number} = sitofp i64 ${result} to double`);
3988
+ B.line(`store double ${number}, ptr ${this.binding(target.localId).slot}`);
3989
+ }
3990
+ if (!bails)
3991
+ return;
3992
+ const done = B.newLabel("spec.done");
3993
+ B.br(done);
3994
+ B.startBlock(failed);
3995
+ this.writeBackSpeculativeGlobals(deferred);
3996
+ B.br(resume);
3997
+ B.startBlock(done);
3998
+ }
3726
3999
  /** The amortized cancellation poll, emitted on the loop's back edge (the
3727
4000
  * continue target), after the body. A poll in the header would be a loop
3728
4001
  * exit ahead of the body; LLVM then cannot rotate the loop, so loads that
@@ -4161,6 +4434,14 @@ export class LlEmitter {
4161
4434
  this.B.line(`${v} = load ptr, ptr ${p}`);
4162
4435
  return v;
4163
4436
  }
4437
+ /** The pointer to ScrMap.flags of an index-signature record's overflow
4438
+ * map (byte offset sizeof(size_t) + 3; SCR_MAP_NULL_PROTO is bit 0). */
4439
+ recordMapFlagsPtr(objName, shapeId) {
4440
+ const ovf = this.recordOvfPtr(objName, shapeId);
4441
+ const p = this.B.tmp();
4442
+ this.B.line(`${p} = getelementptr inbounds i8, ptr ${ovf}, i64 ${mapFlagsOffset(this.sizeType)}`);
4443
+ return p;
4444
+ }
4164
4445
  /** Loads a record field (i8-stored bools trunc to i1). An inline union
4165
4446
  * field answers a private stack box with the slot's own bits, ABSENT
4166
4447
  * included: it may only be borrowed. */
@@ -4750,6 +5031,10 @@ export class LlEmitter {
4750
5031
  fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
4751
5032
  this.integerLoopBindings.clear();
4752
5033
  this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
5034
+ this.speculativeLoopsEnabled = this.countedLoopsEnabled;
5035
+ this.speculativeFunction = fn.name;
5036
+ this.speculation = null;
5037
+ this.speculationResume = null;
4753
5038
  this.finalFieldBorrows =
4754
5039
  !fn.async && !fn.generator && constructorClass(fn.name) === null && this.finalFields.size > 0;
4755
5040
  this.byteWindowsEnabled = this.countedLoopsEnabled;
@@ -5727,6 +6012,17 @@ export class LlEmitter {
5727
6012
  this.releaseScope(scope);
5728
6013
  }
5729
6014
  emitStmt(s) {
6015
+ const resume = this.speculationResume?.get(s);
6016
+ if (resume && !resume.placed && !this.B.isTerminated()) {
6017
+ // The fast copy's failed checks re-execute this statement here.
6018
+ resume.placed = true;
6019
+ this.B.br(resume.label);
6020
+ this.B.startBlock(resume.label);
6021
+ }
6022
+ if (this.speculation?.writes.has(s)) {
6023
+ this.emitSpeculativeWrite(s, this.speculation.resume.get(s));
6024
+ return;
6025
+ }
5730
6026
  const previous = this.B.debugLocation;
5731
6027
  this.B.debugLocation = this.debug?.location(s.loc, this.debugScope) ?? null;
5732
6028
  const loop = s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf";
@@ -6357,57 +6653,61 @@ export class LlEmitter {
6357
6653
  }
6358
6654
  case "while": {
6359
6655
  const workerBudget = this.workerLoopBudget(s);
6360
- const lc = B.newLabel("loop.c");
6361
- const lb = B.newLabel("loop.b");
6362
6656
  const le = B.newLabel("loop.e");
6363
- const lp = workerBudget !== null ? B.newLabel("loop.p") : lc;
6364
- B.br(lc);
6365
- B.startBlock(lc);
6366
- B.condBr(this.emitCondition(s.cond), lb, le);
6367
- B.startBlock(lb);
6368
- this.jumpTargets.push({
6369
- kind: "loop",
6370
- brkLabel: le,
6371
- contLabel: lp,
6372
- labels: s.labels,
6373
- frameDepth: this.frames.length,
6374
- scopeDepth: this.scopes.length,
6375
- finallyDepth: this.finallyStack.length,
6376
- });
6377
- this.emitBlock(s.body);
6378
- this.jumpTargets.pop();
6379
- B.br(lp);
6380
- if (lp !== lc) {
6381
- B.startBlock(lp);
6382
- this.emitWorkerLoopCheck(workerBudget);
6657
+ this.emitLoopCopies(s, true, le, (exit) => {
6658
+ const lc = B.newLabel("loop.c");
6659
+ const lb = B.newLabel("loop.b");
6660
+ const lp = workerBudget !== null ? B.newLabel("loop.p") : lc;
6383
6661
  B.br(lc);
6384
- }
6662
+ B.startBlock(lc);
6663
+ B.condBr(this.emitCondition(s.cond), lb, exit);
6664
+ B.startBlock(lb);
6665
+ this.jumpTargets.push({
6666
+ kind: "loop",
6667
+ brkLabel: exit,
6668
+ contLabel: lp,
6669
+ labels: s.labels,
6670
+ frameDepth: this.frames.length,
6671
+ scopeDepth: this.scopes.length,
6672
+ finallyDepth: this.finallyStack.length,
6673
+ });
6674
+ this.emitBlock(s.body);
6675
+ this.jumpTargets.pop();
6676
+ B.br(lp);
6677
+ if (lp !== lc) {
6678
+ B.startBlock(lp);
6679
+ this.emitWorkerLoopCheck(workerBudget);
6680
+ B.br(lc);
6681
+ }
6682
+ });
6385
6683
  B.startBlock(le);
6386
6684
  break;
6387
6685
  }
6388
6686
  case "doWhile": {
6389
6687
  const workerBudget = this.workerLoopBudget(s);
6390
6688
  // Body first (runs at least once); continue jumps to the CONDITION.
6391
- const lb = B.newLabel("loop.b");
6392
- const lc = B.newLabel("loop.c");
6393
6689
  const le = B.newLabel("loop.e");
6394
- B.br(lb);
6395
- B.startBlock(lb);
6396
- this.jumpTargets.push({
6397
- kind: "loop",
6398
- brkLabel: le,
6399
- contLabel: lc,
6400
- labels: s.labels,
6401
- frameDepth: this.frames.length,
6402
- scopeDepth: this.scopes.length,
6403
- finallyDepth: this.finallyStack.length,
6690
+ this.emitLoopCopies(s, true, le, (exit) => {
6691
+ const lb = B.newLabel("loop.b");
6692
+ const lc = B.newLabel("loop.c");
6693
+ B.br(lb);
6694
+ B.startBlock(lb);
6695
+ this.jumpTargets.push({
6696
+ kind: "loop",
6697
+ brkLabel: exit,
6698
+ contLabel: lc,
6699
+ labels: s.labels,
6700
+ frameDepth: this.frames.length,
6701
+ scopeDepth: this.scopes.length,
6702
+ finallyDepth: this.finallyStack.length,
6703
+ });
6704
+ this.emitBlock(s.body);
6705
+ this.jumpTargets.pop();
6706
+ B.br(lc);
6707
+ B.startBlock(lc);
6708
+ this.emitWorkerLoopCheck(workerBudget);
6709
+ B.condBr(this.emitCondition(s.cond), lb, exit);
6404
6710
  });
6405
- this.emitBlock(s.body);
6406
- this.jumpTargets.pop();
6407
- B.br(lc);
6408
- B.startBlock(lc);
6409
- this.emitWorkerLoopCheck(workerBudget);
6410
- B.condBr(this.emitCondition(s.cond), lb, le);
6411
6711
  B.startBlock(le);
6412
6712
  break;
6413
6713
  }
@@ -6539,8 +6839,6 @@ export class LlEmitter {
6539
6839
  for (const initializer of initializers)
6540
6840
  this.emitStmt(initializer);
6541
6841
  }
6542
- const lc = B.newLabel("loop.c");
6543
- const lb = B.newLabel("loop.b");
6544
6842
  const le = B.newLabel("loop.e");
6545
6843
  // JS `for (let i ...)`: each iteration gets a FRESH binding holding
6546
6844
  // a copy of the previous one (closures made in iteration k keep
@@ -6567,70 +6865,76 @@ export class LlEmitter {
6567
6865
  }
6568
6866
  };
6569
6867
  const workerBudget = this.workerLoopBudget(s);
6570
- const lu = s.update || capturedLets.length > 0 || workerBudget !== null ? B.newLabel("loop.u") : lc;
6571
6868
  freshenBindings();
6572
- B.br(lc);
6573
- B.startBlock(lc);
6574
- if (integerLoop && integerSlot) {
6575
- const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
6576
- const lenPtr = B.tmp();
6577
- const len = B.tmp();
6578
- const index = B.tmp();
6579
- const inBounds = B.tmp();
6580
- B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
6581
- if (integerArrayLoop)
6582
- this.markMemoryPointer(lenPtr, "array:header");
6583
- B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
6584
- B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
6585
- B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
6586
- B.condBr(inBounds, lb, le);
6587
- }
6588
- else if (countedLoop && integerSlot && countedLimit) {
6589
- const index = B.tmp();
6590
- const inBounds = B.tmp();
6591
- B.line(`${index} = load i64, ptr ${integerSlot}`);
6592
- B.line(`${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`);
6593
- B.condBr(inBounds, lb, le);
6594
- }
6595
- else if (s.cond)
6596
- B.condBr(this.emitCondition(s.cond), lb, le);
6597
- else
6598
- B.br(lb);
6599
- B.startBlock(lb);
6600
- this.jumpTargets.push({
6601
- kind: "loop",
6602
- brkLabel: le,
6603
- contLabel: lu,
6604
- labels: s.labels,
6605
- frameDepth: this.frames.length,
6606
- scopeDepth: this.scopes.length,
6607
- finallyDepth: this.finallyStack.length,
6608
- });
6609
- this.emitBlock(s.body);
6610
- this.jumpTargets.pop();
6611
- B.br(lu);
6612
- if (lu !== lc) {
6613
- B.startBlock(lu);
6614
- this.emitWorkerLoopCheck(workerBudget);
6615
- freshenBindings();
6869
+ this.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => {
6870
+ const lc = B.newLabel("loop.c");
6871
+ const lb = B.newLabel("loop.b");
6872
+ const lu = s.update || capturedLets.length > 0 || workerBudget !== null
6873
+ ? B.newLabel("loop.u")
6874
+ : lc;
6875
+ B.br(lc);
6876
+ B.startBlock(lc);
6616
6877
  if (integerLoop && integerSlot) {
6617
- const old = B.tmp();
6618
- const next = B.tmp();
6619
- B.line(`${old} = load ${this.sizeType}, ptr ${integerSlot}`);
6620
- B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
6621
- B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
6878
+ const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
6879
+ const lenPtr = B.tmp();
6880
+ const len = B.tmp();
6881
+ const index = B.tmp();
6882
+ const inBounds = B.tmp();
6883
+ B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
6884
+ if (integerArrayLoop)
6885
+ this.markMemoryPointer(lenPtr, "array:header");
6886
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
6887
+ B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
6888
+ B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
6889
+ B.condBr(inBounds, lb, exit);
6622
6890
  }
6623
- else if (countedLoop && integerSlot) {
6624
- const old = B.tmp();
6625
- const next = B.tmp();
6626
- B.line(`${old} = load i64, ptr ${integerSlot}`);
6627
- B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`);
6628
- B.line(`store i64 ${next}, ptr ${integerSlot}`);
6891
+ else if (countedLoop && integerSlot && countedLimit) {
6892
+ const index = B.tmp();
6893
+ const inBounds = B.tmp();
6894
+ B.line(`${index} = load i64, ptr ${integerSlot}`);
6895
+ B.line(`${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`);
6896
+ B.condBr(inBounds, lb, exit);
6629
6897
  }
6630
- else if (s.update)
6631
- this.emitStmt(s.update);
6632
- B.br(lc);
6633
- }
6898
+ else if (s.cond)
6899
+ B.condBr(this.emitCondition(s.cond), lb, exit);
6900
+ else
6901
+ B.br(lb);
6902
+ B.startBlock(lb);
6903
+ this.jumpTargets.push({
6904
+ kind: "loop",
6905
+ brkLabel: exit,
6906
+ contLabel: lu,
6907
+ labels: s.labels,
6908
+ frameDepth: this.frames.length,
6909
+ scopeDepth: this.scopes.length,
6910
+ finallyDepth: this.finallyStack.length,
6911
+ });
6912
+ this.emitBlock(s.body);
6913
+ this.jumpTargets.pop();
6914
+ B.br(lu);
6915
+ if (lu !== lc) {
6916
+ B.startBlock(lu);
6917
+ this.emitWorkerLoopCheck(workerBudget);
6918
+ freshenBindings();
6919
+ if (integerLoop && integerSlot) {
6920
+ const old = B.tmp();
6921
+ const next = B.tmp();
6922
+ B.line(`${old} = load ${this.sizeType}, ptr ${integerSlot}`);
6923
+ B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
6924
+ B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
6925
+ }
6926
+ else if (countedLoop && integerSlot) {
6927
+ const old = B.tmp();
6928
+ const next = B.tmp();
6929
+ B.line(`${old} = load i64, ptr ${integerSlot}`);
6930
+ B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`);
6931
+ B.line(`store i64 ${next}, ptr ${integerSlot}`);
6932
+ }
6933
+ else if (s.update)
6934
+ this.emitStmt(s.update);
6935
+ B.br(lc);
6936
+ }
6937
+ });
6634
6938
  B.startBlock(le);
6635
6939
  this.releaseScope(this.scopes.pop());
6636
6940
  if (integerLoop)
@@ -8424,6 +8728,10 @@ export class LlEmitter {
8424
8728
  emitExpr(e) {
8425
8729
  return emitExpr(this, e);
8426
8730
  }
8731
+ /** A fresh read-site cache for one constant-key dyn read. */
8732
+ dynReadIcSite() {
8733
+ return `@sc_dyn_read_ic_${this.dynReadIcSites++}`;
8734
+ }
8427
8735
  dynKind(d) {
8428
8736
  return dynKind(this, d);
8429
8737
  }