@polyengine/runtime 0.5.1 → 0.6.1

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 (81) hide show
  1. package/esm/cabi/async_values.js +6 -5
  2. package/esm/cabi/bulk_lists.js +0 -5
  3. package/esm/cabi/context.js +13 -3
  4. package/esm/cabi/flatten.js +41 -9
  5. package/esm/cabi/handles.js +57 -54
  6. package/esm/cabi/layout.js +113 -52
  7. package/esm/cabi/load.js +31 -23
  8. package/esm/cabi/store.js +33 -26
  9. package/esm/cabi/trap.js +2 -2
  10. package/esm/cabi/types.js +138 -25
  11. package/esm/cabi/values.js +25 -7
  12. package/esm/cache/core.js +2 -11
  13. package/esm/digest/digest.js +10 -8
  14. package/esm/digest/mod.js +1 -1
  15. package/esm/digest/verify.js +6 -86
  16. package/esm/embedder/casing.js +24 -9
  17. package/esm/embedder/copy.js +6 -6
  18. package/esm/embedder/errors.js +2 -2
  19. package/esm/embedder/imports.js +3 -3
  20. package/esm/embedder/instantiate.js +132 -37
  21. package/esm/embedder/mod.js +9 -8
  22. package/esm/embedder/resources.js +39 -16
  23. package/esm/embedder/streams.js +36 -37
  24. package/esm/embedder/sync.js +242 -0
  25. package/esm/embedder/values.js +84 -22
  26. package/esm/embedder/version.js +9 -9
  27. package/esm/exec/boundary.js +123 -161
  28. package/esm/exec/executor.js +37 -25
  29. package/esm/exec/host_streams.js +31 -31
  30. package/esm/intrinsics/async_builtins.js +15 -7
  31. package/esm/intrinsics/context.js +1 -1
  32. package/esm/intrinsics/errors.js +9 -9
  33. package/esm/intrinsics/fact_calls.js +37 -49
  34. package/esm/intrinsics/mod.js +54 -117
  35. package/esm/intrinsics/stream_builtins.js +2 -2
  36. package/esm/intrinsics/transcode.js +1 -1
  37. package/esm/jspi/bridge.js +4 -3
  38. package/esm/jspi/suspending.js +5 -5
  39. package/esm/plan/loader.js +5 -5
  40. package/esm/shim/translator.js +2 -2
  41. package/esm/task/mod.js +45 -182
  42. package/esm/task/scheduler.js +154 -185
  43. package/esm/task/streams.js +39 -54
  44. package/esm/task/subtask.js +2 -2
  45. package/esm/task/thread.js +20 -41
  46. package/esm/task/waitable.js +0 -1
  47. package/package.json +2 -2
  48. package/types/cabi/async_values.d.ts +3 -2
  49. package/types/cabi/bulk_lists.d.ts +0 -2
  50. package/types/cabi/context.d.ts +15 -5
  51. package/types/cabi/flatten.d.ts +2 -2
  52. package/types/cabi/handles.d.ts +15 -26
  53. package/types/cabi/layout.d.ts +22 -1
  54. package/types/cabi/load.d.ts +10 -2
  55. package/types/cabi/store.d.ts +4 -2
  56. package/types/cabi/types.d.ts +22 -3
  57. package/types/digest/mod.d.ts +1 -1
  58. package/types/digest/verify.d.ts +3 -19
  59. package/types/embedder/casing.d.ts +9 -1
  60. package/types/embedder/copy.d.ts +4 -4
  61. package/types/embedder/instantiate.d.ts +4 -4
  62. package/types/embedder/mod.d.ts +3 -2
  63. package/types/embedder/resources.d.ts +20 -7
  64. package/types/embedder/streams.d.ts +5 -6
  65. package/types/embedder/sync.d.ts +81 -0
  66. package/types/embedder/values.d.ts +2 -2
  67. package/types/exec/boundary.d.ts +55 -44
  68. package/types/exec/executor.d.ts +3 -2
  69. package/types/exec/host_streams.d.ts +8 -8
  70. package/types/intrinsics/errors.d.ts +3 -3
  71. package/types/intrinsics/mod.d.ts +1 -1
  72. package/types/intrinsics/stream_builtins.d.ts +2 -2
  73. package/types/jspi/bridge.d.ts +6 -5
  74. package/types/plan/format.d.ts +11 -10
  75. package/types/plan/loader.d.ts +2 -2
  76. package/types/shim/translator.d.ts +2 -2
  77. package/types/task/mod.d.ts +26 -97
  78. package/types/task/scheduler.d.ts +81 -62
  79. package/types/task/streams.d.ts +23 -38
  80. package/types/task/subtask.d.ts +2 -2
  81. package/types/task/waitable.d.ts +0 -1
@@ -12,7 +12,7 @@ import { Trap } from "../cabi/trap.js";
12
12
  import { ComponentInstanceState, Store } from "../task/mod.js";
13
13
  import { anySuspendingImport, assertModeConsistent, chooseMode, isAbortable, isDeferCancel, isSuspending, planNeedsSuspension, suspendingImport, trampolineCanBlock, trampolineNeedsSuspension, } from "../jspi/mod.js";
14
14
  import { loadPlan, PlanError, resourceIndexOfDefined, } from "../plan/loader.js";
15
- import { CONSTRUCTOR_SYNC_ENTRY, createDtorEntry, createLiftedFunction, createLoweredImport, LiveMemory, newStats, } from "./boundary.js";
15
+ import { SYNC_ENTRY, createDtorEntry, createLiftedFunction, createLoweredImport, LiveMemory, newStats, } from "./boundary.js";
16
16
  import { createTrampoline, createUnsafeIntrinsic, TranscodeMemory, } from "../intrinsics/mod.js";
17
17
  /**
18
18
  * Standing probe (CE_COPY_TRACE): log which import made a core instance
@@ -179,7 +179,7 @@ class Executor {
179
179
  /** Scratch: set by `importValue` while one module's imports are resolved. */
180
180
  sawBlockingImport = false;
181
181
  /** LoweredIndex-es whose host functions carry the `suspending()` brand —
182
- * populated by `buildLoweredImport`, read by `importValue` (A1). */
182
+ * populated by `buildLoweredImport`, read by `importValue`. */
183
183
  suspendableLowerings = new Set();
184
184
  /** Host trap held across a FACT exception barrier (see `HostTrapState`). */
185
185
  trapState = { pending: undefined };
@@ -192,13 +192,13 @@ class Executor {
192
192
  this.adapterBytes = input.adapters ?? new Map();
193
193
  this.hostImports = input.imports ?? {};
194
194
  this.verifyHash = input.verifyHash ?? true;
195
- // AUTO-DETECTION IS ON (M2 exit). `chooseMode` picks jspi when the
195
+ // AUTO-DETECTION IS ON by default. `chooseMode` picks jspi when the
196
196
  // embedder opts in OR when the plan needs suspension: a stackful async
197
197
  // lift, or a genuinely blocking built-in — classified per DECLARATION
198
198
  // (`trampolineNeedsSuspension`; the async form of a copy/cancel built-in
199
199
  // never blocks and is not evidence). An explicit `jspi: false` still
200
200
  // forces plain, and a sync-only component never detects as needing
201
- // suspension, so the M1 synchronous API is untouched (pinned by
201
+ // suspension, so the synchronous API is untouched (pinned by
202
202
  // bridge_test "plain mode: lifted exports still return values" and the
203
203
  // planNeedsSuspension(hello) === false pin beside it).
204
204
  //
@@ -223,7 +223,7 @@ class Executor {
223
223
  // stackful async lift or a blocking built-in — per-declaration), and
224
224
  // the IMPORTS RECORD (a `suspending()`-marked host function: the
225
225
  // embedder's declared intent to park a sync-lowered frame, which no
226
- // plan field can express — embedder-api.md amendment A1).
226
+ // plan field can express — contracts/embedder-api.md §"Functions and async").
227
227
  planNeedsSuspension(loaded.wire) || anySuspendingImport(this.hostImports));
228
228
  }
229
229
  async verifyComponent() {
@@ -492,15 +492,15 @@ class Executor {
492
492
  case "resource": {
493
493
  // Wire the dtor + implementing instance into every concrete
494
494
  // resource-table token for this defined resource
495
- // (tolerate-if-unreferenced for M0; plan-format.md open item).
495
+ // (tolerate-if-unreferenced; plan-format.md open item).
496
496
  const dtor = init.dtor === null
497
497
  ? null
498
498
  : this.resolveFunction(init.dtor, `resource ${init.index} dtor`);
499
499
  const inst = this.componentInstance(init.instance);
500
500
  // `init.index` is a DefinedResourceIndex; resource *tables* key off
501
501
  // the component-wide ResourceIndex, which counts imported resources
502
- // first (plan-format.md v0.1 amendment #2 / v0.2
503
- // `importedResources`; wasmtime `Component::resource_index`).
502
+ // first (the `importedResources` field, contracts/plan-format.md
503
+ // schema; wasmtime `Component::resource_index`).
504
504
  const resourceIndex = resourceIndexOfDefined(this.loaded, init.index);
505
505
  this.wire.resourceTables.forEach((table, tableIndex) => {
506
506
  if (table.kind === "concrete" && table.resource === resourceIndex) {
@@ -622,17 +622,22 @@ class Executor {
622
622
  syncCallStack: this.syncCallStack,
623
623
  allInstances: () => this.componentInstances.values(),
624
624
  });
625
- // Constructor exports additionally carry a plain-entered variant
626
- // (see CONSTRUCTOR_SYNC_ENTRY): in jspi mode the promising-wrapped
627
- // entry above necessarily returns a Promise, which a JS class
628
- // constructor cannot await. Deliberately NOT noteEntry()-recorded —
629
- // this is the one documented exception to the bridge invariant
630
- // (entries wrapped iff imports wrapped), safe because a
631
- // synchronously-completing activation never reaches the Suspending
632
- // seam.
633
- if (this.suspensionMode === "jspi" &&
634
- exp.name.startsWith("[constructor]")) {
635
- value[CONSTRUCTOR_SYNC_ENTRY] = createLiftedFunction({
625
+ // Every SYNC-TYPED export additionally carries a plain-entered
626
+ // variant (see SYNC_ENTRY, contracts/embedder-api.md §"Functions and async"):
627
+ // in jspi mode the promising-wrapped entry above necessarily returns
628
+ // a Promise, which some host contexts cannot use however promptly it
629
+ // resolves a JS class constructor cannot await it at all, and the
630
+ // embedder's `sync()` adapter exists to ask for the synchronous form
631
+ // of any sync-typed export. Async-typed exports have no synchronous
632
+ // form by definition and get none.
633
+ //
634
+ // Deliberately NOT noteEntry()-recorded — this is the documented
635
+ // exception to the bridge invariant (entries wrapped iff imports
636
+ // wrapped), safe because a synchronously-completing activation never
637
+ // reaches the Suspending seam. sync() extends the exception from
638
+ // constructors to all sync entries.
639
+ if (this.suspensionMode === "jspi" && !ft.async) {
640
+ value[SYNC_ENTRY] = createLiftedFunction({
636
641
  name: `${path} (sync entry)`,
637
642
  ft,
638
643
  opts,
@@ -642,6 +647,12 @@ class Executor {
642
647
  trapState: this.trapState,
643
648
  syncCallStack: this.syncCallStack,
644
649
  allInstances: () => this.componentInstances.values(),
650
+ // sync() arm 2: a synchronous caller cannot be deferred by the
651
+ // hop-quiescence gate, so it refuses (SyncEntryBusy) instead.
652
+ // This deliberately changes constructor behaviour: the
653
+ // constructor sync entry previously bypassed the gate
654
+ // entirely, a latent lift-corruption window.
655
+ refuseOnEntryHops: true,
645
656
  });
646
657
  }
647
658
  return { kind: "value", value };
@@ -664,8 +675,9 @@ class Executor {
664
675
  reason: "type export: no runtime surface",
665
676
  };
666
677
  case "module": {
667
- // plan-format.md v4 amendment 2: an exported embedded core module
668
- // surfaces as the already-compiled `WebAssembly.Module` — the same
678
+ // The `module` export kind (contracts/plan-format.md schema notes):
679
+ // an exported embedded core module surfaces as the already-compiled
680
+ // `WebAssembly.Module` — the same
669
681
  // compilation `instantiate-module` initializers use.
670
682
  const module = this.modules[exp.module];
671
683
  if (module === undefined) {
@@ -747,7 +759,7 @@ class Executor {
747
759
  return value;
748
760
  const optionsAsync = (i) => this.wire.canonicalOptions[i]?.async === true;
749
761
  const d = decl;
750
- // Host lowers (A1): `trampolineCanBlock` classifies DECLARATIONS and a
762
+ // Host lowers: `trampolineCanBlock` classifies DECLARATIONS and a
751
763
  // `lower-import` declaration says nothing about the host's intent — the
752
764
  // evidence is the `suspending()` brand on the host function, recorded by
753
765
  // `buildLoweredImport` into `suspendableLowerings` (which resolving this
@@ -931,14 +943,14 @@ class Executor {
931
943
  const ft = this.funcType(decl.type, `import '${label}'`);
932
944
  const opts = this.resolveOptions(decl.options);
933
945
  const suspendable = isSuspending(value);
934
- // A23 (contracts/embedder-api.md §"Functions and async"): does this import
946
+ // cancellation discard (contracts/embedder-api.md §"Functions and async"): does this import
935
947
  // opt out of cancel-discard? Unlike `suspendable` above, this needs no
936
948
  // executor-state detour — the brand is consumed by `createLoweredImport`
937
949
  // itself (it only decides which `onCancel` the lowered import installs, not
938
950
  // whether the CoreFn gets wrapped), so nothing downstream has to read a
939
951
  // brand off a replaced function identity.
940
952
  const deferCancel = isDeferCancel(value);
941
- // A24 (same section): does this import want a per-call `AbortSignal`?
953
+ // abortable() (same section): does this import want a per-call `AbortSignal`?
942
954
  // Read exactly like `deferCancel` above and for the same reason — the
943
955
  // brand is consumed inside `createLoweredImport`, which mints the
944
956
  // controller and appends the signal itself, so no function identity is
@@ -968,7 +980,7 @@ class Executor {
968
980
  /**
969
981
  * Resolve one plan import against the host-provided import record: index by
970
982
  * the component's exact import string, then walk `path` (instance imports —
971
- * plan-format.md v0.1 amendment #4).
983
+ * `imports[].path`, contracts/plan-format.md schema).
972
984
  */
973
985
  lookupHostImport(name, path, label) {
974
986
  if (!(name in this.hostImports)) {
@@ -42,7 +42,7 @@
42
42
  // the event loop".
43
43
  //
44
44
  // Retention, stated as the rule the arm implements (#162, embedder-api
45
- // amendment A15): the arm is live iff the host holds a retained end, a parked
45
+ // §"Streams and futures"): the arm is live iff the host holds a retained end, a parked
46
46
  // host operation, or an unfinished producer pump. Which ends the host holds
47
47
  // follows from where the wrapper came from — a host-CREATED stream keeps its
48
48
  // writable end across every lower (only readable ends transfer,
@@ -56,12 +56,12 @@
56
56
  // a guest and then never writes to it or drops it will *hang* rather than
57
57
  // trap. That is the honest outcome — the component is not deadlocked, the
58
58
  // embedder simply has not done its half — and it matches how any other
59
- // unresolved Promise behaves in JS. That policy is unchanged by A15; what
59
+ // unresolved Promise behaves in JS. That policy is unchanged by deadlock-verdict suppression; what
60
60
  // changed is that the claim now EXPIRES with retention, so a store that once
61
61
  // round-tripped a stream through the host no longer misreports every later
62
62
  // genuine deadlock as this hang.
63
63
  //
64
- // The inverse case is NOT a hang (#66, embedder-api amendment A7): when the
64
+ // The inverse case is NOT a hang (#66, contracts/embedder-api.md §"Streams and futures"): when the
65
65
  // GUEST side dies — a trap poisons the instance holding the peer end — the
66
66
  // poisoned table's ends are retired (task/streams.ts
67
67
  // `retireInstanceAsyncEnds`), so a parked host operation settles DROPPED-
@@ -186,14 +186,14 @@ export class HostBuffer {
186
186
  }
187
187
  return out;
188
188
  }
189
- // --- A21 `ByteWindow` (embedder-api amendment A21, polyengine#128) ---
189
+ // --- `ByteWindow` (embedder-api.md §"Streams and futures" ("Direct-access byte edges"), polyengine#128) ---
190
190
  //
191
191
  // A host buffer can be the PEER of a direct session on the other end of a
192
192
  // host↔host rendezvous. Which of the two shapes it takes follows from the
193
193
  // direction it was built for, exactly as `read`/`write` above do:
194
194
  //
195
195
  // * SOURCE (`values !== null`, a parked `write`): the window is a view of
196
- // the offered chunk itself — the A5 borrow, scoped to the callback. No
196
+ // the offered chunk itself — the stream/future round-trip borrow, scoped to the callback. No
197
197
  // extra copy at all.
198
198
  // * DESTINATION (`values === null`, a parked/arriving `read(max)`): there
199
199
  // is no landing zone to view, so the window is a fresh scratch; the
@@ -255,14 +255,14 @@ export class HostBuffer {
255
255
  * driving loop treats "waiting for the embedder" as progress-is-possible
256
256
  * rather than deadlock. Re-arms after every notification.
257
257
  *
258
- * RETENTION IS THE LIVENESS RULE (#162, embedder-api amendment A15). The arm
258
+ * RETENTION IS THE LIVENESS RULE (#162, contracts/embedder-api.md §"Streams and futures"). The arm
259
259
  * is live iff the host retains a way to act on this shared object: a retained
260
260
  * end, a parked host operation, or an unfinished producer pump. The claim it
261
261
  * makes to the deadlock verdicts — "the embedder may still act" — therefore
262
262
  * *expires*. Three state transitions implement it:
263
263
  *
264
264
  * * `close()` — terminal: DROPPED, an explicit drop, or the shared object's
265
- * drop observers (either end, the A7 teardown walk). Nothing can revive
265
+ * drop observers (either end, the loud component fault teardown walk). Nothing can revive
266
266
  * the wrapper.
267
267
  * * `disarm()` — NON-terminal: the host handed its last end back to a guest
268
268
  * (a lifted stream/future lowered back in — the identity round trip). The
@@ -319,9 +319,9 @@ class HostActivity {
319
319
  * outstanding — by handing the store to the *same* loop an export call
320
320
  * would have used, `driveStoreAsync`. Without the asynchronous half a guest
321
321
  * parked in a background forwarding task would never be resumed to consume
322
- * what we just offered, and the host read would await forever (C0 finding
323
- * R-1: the previous local drain only serviced `store.awaiting` and never
324
- * awaited `store.pendingHostCalls`, so a writer parked on a
322
+ * what we just offered, and the host read would await forever (host-pump
323
+ * starvation: the previous local drain only serviced `store.awaiting` and
324
+ * never awaited `store.pendingHostCalls`, so a writer parked on a
325
325
  * Promise-returning host import stalled the reader).
326
326
  *
327
327
  * Traps from the synchronous half propagate to the caller of the host
@@ -435,7 +435,7 @@ class HostActivity {
435
435
  }
436
436
  /**
437
437
  * The host retains no way to act: its lifted end was lowered back into a
438
- * guest, which now owns it (#162, amendment A15). NON-terminal — a re-lift
438
+ * guest, which now owns it (#162, §"Streams and futures"). NON-terminal — a re-lift
439
439
  * of the same shared object restores retention via `rearm()`.
440
440
  *
441
441
  * Resolving the stale arm is required, not tidiness: a `driveAsync` parked
@@ -455,7 +455,7 @@ class HostActivity {
455
455
  r?.();
456
456
  }
457
457
  /**
458
- * A lift handed the host the readable end again — the A5 cache-hit wrapper
458
+ * A lift handed the host the readable end again — the stream/future round-trip cache-hit wrapper
459
459
  * for a shared object that round-tripped back out of the guest (#162).
460
460
  * A no-op for a closed activity (the object is gone for good) and for one
461
461
  * that was never disarmed.
@@ -504,7 +504,7 @@ class DirectScope {
504
504
  if (this.marked + n > this.capacity) {
505
505
  throw new TypeError(`${who}(${n}) would take the invocation's cumulative mark to ` +
506
506
  `${this.marked + n}, past the ${this.capacity} byte(s) the view ` +
507
- `held on entry (embedder-api amendment A21)`);
507
+ `held on entry (embedder-api.md §"Streams and futures" ("Direct-access byte edges"))`);
508
508
  }
509
509
  this.marked += n;
510
510
  }
@@ -512,8 +512,8 @@ class DirectScope {
512
512
  if (!this.#live) {
513
513
  throw new TypeError("this direct-access view is dead: a DirectDestination/DirectSource " +
514
514
  "is scoped to the synchronous callback invocation it was passed " +
515
- "to, and retaining one past its return is misuse (embedder-api " +
516
- "amendment A21, polyengine#128)");
515
+ "to, and retaining one past its return is misuse (embedder-api.md " +
516
+ "Streams and futures" ("Direct-access byte edges"), polyengine#128)`);
517
517
  }
518
518
  }
519
519
  /**
@@ -544,7 +544,7 @@ class DirectSession {
544
544
  total = 0;
545
545
  /** The callback said `"done"`, or the session failed / was settled. */
546
546
  ended = false;
547
- /** `ended` because the callback said so (A7 precision; see `DirectSessionInfo`). */
547
+ /** `ended` because the callback said so (loud component fault precision; see `DirectSessionInfo`). */
548
548
  endedByVerdict = false;
549
549
  /** Installed in the shared object's pending slot right now. */
550
550
  pending = false;
@@ -567,10 +567,10 @@ class DirectSession {
567
567
  return false;
568
568
  }
569
569
  read(_n) {
570
- throw new Error("internal: a direct session must go through the A21 seam");
570
+ throw new Error("internal: a direct session must go through the direct-access byte edge seam");
571
571
  }
572
572
  write(_vs) {
573
- throw new Error("internal: a direct session must go through the A21 seam");
573
+ throw new Error("internal: a direct session must go through the direct-access byte edge seam");
574
574
  }
575
575
  // --- the direct protocol ---
576
576
  runDirect(peer, n) {
@@ -600,7 +600,7 @@ class DirectSession {
600
600
  scope.die();
601
601
  if (verdict !== "more" && verdict !== "done") {
602
602
  this.#fail(new TypeError(`a direct-access callback must return "more" or "done", got ` +
603
- `${JSON.stringify(verdict)} (embedder-api amendment A21)`));
603
+ `${JSON.stringify(verdict)} (embedder-api.md §"Streams and futures" ("Direct-access byte edges"))`));
604
604
  return "failed";
605
605
  }
606
606
  const k = scope.marked;
@@ -614,7 +614,7 @@ class DirectSession {
614
614
  }
615
615
  this.#fail(new TypeError('a direct-access callback returned "more" without marking any ' +
616
616
  "bytes; a session that has nothing to offer retracts by " +
617
- 'returning "done" (embedder-api amendment A21, polyengine#128)'));
617
+ 'returning "done" (embedder-api.md §"Streams and futures" ("Direct-access byte edges") (polyengine#128))'));
618
618
  return "failed";
619
619
  }
620
620
  // Marks acknowledge ON CLEAN RETURN ONLY: this is the first and only
@@ -662,18 +662,18 @@ class DirectSession {
662
662
  s?.("reissue");
663
663
  }
664
664
  }
665
- /** A21 is `stream<u8>` only; `null` (zero-width) is not u8 either. */
665
+ /** direct-access byte edge is `stream<u8>` only; `null` (zero-width) is not u8 either. */
666
666
  function requireU8Element(t, who) {
667
667
  if (t === null || despecialize(t).kind !== "u8") {
668
668
  throw new TypeError(`${who} is available on stream<u8> only; this stream's element type ` +
669
669
  `is ${t === null ? "the zero-width payload" : despecialize(t).kind} ` +
670
- `(embedder-api amendment A21, polyengine#128)`);
670
+ `(embedder-api.md §"Streams and futures" ("Direct-access byte edges") (polyengine#128))`);
671
671
  }
672
672
  }
673
673
  /**
674
674
  * Attach host-activity bookkeeping to a shared object at the CABI seam.
675
675
  *
676
- * `kind` is the retention model (#162, amendment A15) — WHICH ends the host
676
+ * `kind` is the retention model (#162, §"Streams and futures") — WHICH ends the host
677
677
  * holds, which is decided entirely by where the wrapper came from:
678
678
  *
679
679
  * * `"created"` — `hostStream()`/`hostFuture()`. Only READABLE ends
@@ -700,7 +700,7 @@ function bindOnLower(shared, activity, kind, alsoOnLowered) {
700
700
  // activity binding for future lowers (review advisory, host-streams
701
701
  // round). The public entry points cannot get here with a wrapped object —
702
702
  // `hostStreamFor`/`hostFutureFor` return the cached wrapper instead
703
- // (amendment A5) — so a trip here is a bug in this module. The class field
703
+ // (§"Streams and futures") — so a trip here is a bug in this module. The class field
704
704
  // initializes to null; == null covers both sentinels.
705
705
  assert_(holder.onLowered == null, "internal: a second host wrapper was built for an already-wrapped " +
706
706
  "stream/future (the wrapper cache should have returned the first)");
@@ -768,13 +768,13 @@ function mkStreamEnds(shared, activity) {
768
768
  else
769
769
  activity.notify();
770
770
  };
771
- /** The live direct session on each end, if any (A21, polyengine#128). */
771
+ /** The live direct session on each end, if any (direct-access byte edge, polyengine#128). */
772
772
  const direct = {
773
773
  read: null,
774
774
  write: null,
775
775
  };
776
776
  /**
777
- * Drive one direct session from park to end (A21).
777
+ * Drive one direct session from park to end.
778
778
  *
779
779
  * Two shapes reach us, and the difference is *which side arrived second*:
780
780
  *
@@ -841,8 +841,8 @@ function mkStreamEnds(shared, activity) {
841
841
  };
842
842
  /** Shared tail of `cancelWrite`/`cancelRead` for a parked direct session. */
843
843
  const cancelDirect = (session) => {
844
- // A21: cancelling RETRACTS the session — it resolves with its running
845
- // total (A8's indistinguishability caveats unchanged). `shared.cancel()`
844
+ // direct-access byte edge: cancelling RETRACTS the session — it resolves with its running
845
+ // total (future abandonment's indistinguishability caveats unchanged). `shared.cancel()`
846
846
  // only when the session actually holds the pending slot: a session caught
847
847
  // between two issuances holds nothing, and `SharedBase.cancel` asserts
848
848
  // that something is pending.
@@ -919,7 +919,7 @@ function mkStreamEnds(shared, activity) {
919
919
  },
920
920
  writeDirect(produce, info) {
921
921
  // Same one-in-flight-per-end rule, same wording shape as `write`:
922
- // `writeDirect` participates in it exactly as `write` does (A21).
922
+ // `writeDirect` participates in it exactly as `write` does.
923
923
  if (parked.write) {
924
924
  throw new TypeError("a write is already in flight on this stream's writable end; " +
925
925
  "await it or cancelWrite() first");
@@ -1025,8 +1025,8 @@ function mkStreamEnds(shared, activity) {
1025
1025
  };
1026
1026
  }
1027
1027
  /**
1028
- * One host wrapper per shared object, by identity (embedder-api amendment
1029
- * A5). A stream/future value that round-trips host → guest → host lifts back
1028
+ * One host wrapper per shared object, by identity (contracts/embedder-api.md
1029
+ * §"Streams and futures"). A stream/future value that round-trips host → guest → host lifts back
1030
1030
  * as the SAME wrapper the host already holds, so wrapping is idempotent —
1031
1031
  * there is never a second `HostActivity` competing to pump one shared object
1032
1032
  * (the hazard the old double-wrap assert guarded against), and the readable
@@ -1,5 +1,5 @@
1
1
  // The 0.3 async canonical built-ins, as host trampolines
2
- // (contracts/intrinsics.md §B "M2"): task.{return,cancel},
2
+ // (contracts/intrinsics.md §B): task.{return,cancel},
3
3
  // backpressure.{set,inc,dec}, waitable-set.{new,wait,poll,drop},
4
4
  // waitable.join, subtask.{drop,cancel} and thread.yield.
5
5
  //
@@ -77,8 +77,9 @@ export function createTaskReturn(decl, ctx) {
77
77
  //
78
78
  // Plan v3 enables this for FACT cross-component tasks too: the callee
79
79
  // task's declared result type is now resolvable from the raw
80
- // `TypeTupleIndex` `prepare-call` carried (contracts/plan-format.md v3
81
- // amendment 3, wired in fact_calls.ts). It remains skipped for the one
80
+ // `TypeTupleIndex` `prepare-call` carried (the task-return trampoline's
81
+ // raw `results` key + interned `resultType`, contracts/plan-format.md
82
+ // schema; wired in fact_calls.ts). It remains skipped for the one
82
83
  // case v3 does not answer — a callee the plan maps no `task.return`
83
84
  // tuple for, where `ft.results` is a placeholder rather than a
84
85
  // declaration (`factResultTypesKnown === false`); comparing against a
@@ -415,7 +416,7 @@ export function createSubtaskCancel(decl, inst, mode = "plain") {
415
416
  // cancel sits parked non-cancellably, which is determinate, so the
416
417
  // genuine BLOCKED answer is still immediate. Host-import subtasks
417
418
  // carry no callee task, and their state cannot be mid-hop: the
418
- // default (A23) onCancel resolves them before this branch is ever
419
+ // default onCancel resolves them before this branch is ever
419
420
  // reached, and a `deferCancel` import's no-op onCancel leaves them
420
421
  // simply unresolved — either way the pre-jspi immediate answer
421
422
  // stands.
@@ -495,6 +496,13 @@ function requireWaitableSet(inst, si, what) {
495
496
  trapIf(!(wset instanceof WaitableSet), `${what}: handle ${si} is not a waitable set`);
496
497
  return wset;
497
498
  }
499
+ /**
500
+ * The two event payload words. Hoisted out of `unpackEvent` because
501
+ * cabi/layout.ts and cabi/types.ts memoize on `ValType` identity (issue
502
+ * #261): a fresh literal per call is a guaranteed cache miss plus a
503
+ * `WeakMap.set` on immediate garbage, twice per event delivered.
504
+ */
505
+ const EVENT_PAYLOAD_TYPE = Object.freeze({ kind: "u32" });
498
506
  /**
499
507
  * definitions.py `unpack_event` (line 2422): store the two payload words at
500
508
  * `ptr` and return the event code.
@@ -502,11 +510,11 @@ function requireWaitableSet(inst, si, what) {
502
510
  function unpackEvent(opts, inst, ptr, e) {
503
511
  const [event, p1, p2] = e;
504
512
  const cx = new LiftLowerContext(cabiOptions(opts), inst, null);
505
- storeValue(cx, p1, { kind: "u32" }, ptr);
506
- storeValue(cx, p2, { kind: "u32" }, ptr + 4);
513
+ storeValue(cx, p1, EVENT_PAYLOAD_TYPE, ptr);
514
+ storeValue(cx, p2, EVENT_PAYLOAD_TYPE, ptr + 4);
507
515
  return event;
508
516
  }
509
- // Structural `ValType` equality (the C2-D bugfix) moved to cabi/types.ts
517
+ // Structural `ValType` equality (the circular-structure bugfix) moved to cabi/types.ts
510
518
  // (`valTypesEqual`) when the #18 tls smoke found its stream-element sibling;
511
519
  // the contract note lives there now.
512
520
  /** Unused-import guard: `trap` is re-exported for symmetry with cabi. */
@@ -83,7 +83,7 @@ export function createUnsafeIntrinsic(symbol,
83
83
  inst) {
84
84
  const match = /^context-(get|set)-i32-(\d+)$/.exec(symbol);
85
85
  if (match === null) {
86
- throw new UnsupportedFeatureError("M2", `component imports the unsafe intrinsic '${symbol}', which has no ` +
86
+ throw new UnsupportedFeatureError("task-core", `component imports the unsafe intrinsic '${symbol}', which has no ` +
87
87
  `portable meaning in a JS host (only context.{get,set} do)`);
88
88
  }
89
89
  const slot = Number(match[2]);
@@ -1,13 +1,13 @@
1
- // Shared failure type for trampolines/intrinsics scheduled after the current
2
- // milestone. Split out of ./mod.ts so sibling intrinsic modules can raise it
3
- // without importing the (much larger) trampoline dispatcher.
4
- /** Instantiate-time failure for functionality scheduled after M0. */
1
+ // Shared failure type for trampolines/intrinsics gated on a not-yet-built
2
+ // runtime capability. Split out of ./mod.ts so sibling intrinsic modules can
3
+ // raise it without importing the (much larger) trampoline dispatcher.
4
+ /** Instantiate-time failure for functionality gated on a missing capability. */
5
5
  export class UnsupportedFeatureError extends Error {
6
- milestone;
7
- constructor(milestone, what) {
8
- super(`${what} — scheduled for ${milestone}, not implemented in the current ` +
9
- `executor (contracts/intrinsics.md §B)`);
10
- this.milestone = milestone;
6
+ capability;
7
+ constructor(capability, what) {
8
+ super(`${what} — needs the "${capability}" capability, not yet implemented ` +
9
+ `in the current executor (contracts/intrinsics.md §B)`);
10
+ this.capability = capability;
11
11
  this.name = "UnsupportedFeatureError";
12
12
  }
13
13
  }