@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
@@ -60,14 +60,15 @@
60
60
  // * Reentrance between *related* instances is resolved statically:
61
61
  // `trampoline.rs:116-127` emits an unconditional
62
62
  // `trap(Trap::CannotEnterComponent)` when the lower and lift instances are
63
- // the same or are ancestors of one another. So the flat-instance-tree gap
64
- // recorded in task/mod.ts is NOT load-bearing herewasmtime has already
65
- // decided those cases at translation time, and the remaining runtime check
66
- // is the ordinary "is the callee instance currently executing" one, which
67
- // a flat tree answers correctly.
63
+ // the same or are ancestors of one another. The runtime-side counterpart
64
+ // "is the callee instance currently executing"does not exist at the
65
+ // pinned reference (definitions.py @ 2f13265 has no `may_enter`, CM#705),
66
+ // so the only refusal at these sites is polyengine's poisoned-corpse check
67
+ // (`entryRefusal`). The flat-instance-tree gap recorded in task/mod.ts is
68
+ // doubly not load-bearing here.
68
69
  import { assert_, trap } from "../cabi/trap.js";
69
70
  import { MAX_FLAT_RESULTS } from "../cabi/mod.js";
70
- import { NeedsJspi, currentTask, maybeCurrentTask, needsJspi, notifyInstancePoisoned, packSubtaskResult, PendingCapability, Subtask, SubtaskState, Task, Thread, withPoisonCause, } from "../task/mod.js";
71
+ import { NeedsJspi, currentTask, maybeCurrentTask, needsJspi, notifyInstancePoisoned, packSubtaskResult, PendingCapability, Subtask, SubtaskState, Task, Thread, entryRefusal, } from "../task/mod.js";
71
72
  import { blockCurrentActivation, enterWasm } from "../jspi/mod.js";
72
73
  import { awaitCore, callCore, normalizeCoreValues, runCallbackLoop, } from "../exec/boundary.js";
73
74
  import { traceCopy } from "./stream_builtins.js";
@@ -203,8 +204,9 @@ function mkCalleeTask(input) {
203
204
  // `task_return_type` at all: fact.rs's comment on `PrepareCall.memory` says
204
205
  // the same for the memory check).
205
206
  // `task_return_type` arrives as wasmtime's *own* `TypeTupleIndex` — a
206
- // runtime argument, not a plan field. Plan v3 (contracts/plan-format.md v3
207
- // amendment 3) supplies the dictionary for it: every `task-return`
207
+ // runtime argument, not a plan field. The task-return trampoline's raw
208
+ // `results` key + interned `resultType` (contracts/plan-format.md schema)
209
+ // supplies the dictionary for it: every `task-return`
208
210
  // trampoline decl carries that raw index alongside its interned
209
211
  // `plan.types` entry, so the callee task CAN now carry its declared result
210
212
  // types and `canon_task_return`'s `trap_if(result_type != task.ft.result)`
@@ -287,8 +289,8 @@ function mkCalleeTask(input) {
287
289
  // entry that owns a FACT sync-call bracket: `enter-sync-call` runs here
288
290
  // under this task, and if the callee suspends, the engine resumes it later
289
291
  // with no driver. Without the ambient travelling with the activation the
290
- // matching `exit-sync-call` had no task in scope at all (traced in M2
291
- // phase 3h as `ENTER-SYNC owner=K26` / `EXIT-SYNC owner=EXECUTOR`).
292
+ // matching `exit-sync-call` had no task in scope at all (traced as
293
+ // `ENTER-SYNC owner=K26` / `EXIT-SYNC owner=EXECUTOR`).
292
294
  //
293
295
  // Entries deliberately NOT wrapped: `realloc`, `post-return`, resource
294
296
  // destructors and the `[async-start]`/`[async-return]` copy adapters.
@@ -303,7 +305,7 @@ function mkCalleeTask(input) {
303
305
  // line 12). This is only coherent together with site 1 below blocking
304
306
  // rather than raising `NeedsJspi`, since a promising callee resolves on a
305
307
  // later turn by construction.
306
- // NOTE (M2 stackful round): this wrap is RIGHT for a callee that blocks and
308
+ // NOTE: this wrap is RIGHT for a callee that blocks and
307
309
  // Wrap ONLY a callee that can actually reach a suspension point.
308
310
  //
309
311
  // The wrap is required when the callee blocks: without its own `promising`
@@ -452,35 +454,30 @@ export function createSyncStartCall(decl, ctx) {
452
454
  },
453
455
  lenderScope,
454
456
  });
455
- // Reference `Store.lift`: the reentrance gate, with the *caller* as the
456
- // entering context (definitions.py `entering_set(caller)`).
457
- // A poisoned callee's refusal names the original trap (polyengine#145).
458
- if (!prepared.calleeInst.mayEnterFrom(prepared.callerInst)) {
459
- trap(withPoisonCause(prepared.calleeInst, "cannot enter component instance"));
457
+ // Reference `Store.lift` (@ 2f13265) has no reentrance gate; the only
458
+ // refusal left is polyengine's poisoned-corpse check, whose message names
459
+ // the original trap (polyengine#145).
460
+ {
461
+ const refusal = entryRefusal(prepared.calleeInst, prepared.callerInst, "cannot enter component instance");
462
+ if (refusal !== null)
463
+ trap(refusal);
460
464
  }
461
- prepared.calleeInst.enterFrom(prepared.callerInst);
462
- let ok = false;
463
465
  try {
464
466
  const thread = spawn(task, body);
465
467
  thread.resume();
466
- ok = true;
467
468
  }
468
469
  catch (e) {
469
- // A trap leaves the instance poisoned: `leave_to` is not reached
470
- // (definitions.py `Store.lift`, line 578). A *capability signal* does
471
- // not see the `isCapabilitySignal` note in exec/boundary.ts.
472
- if (e instanceof NeedsJspi || e instanceof PendingCapability) {
473
- prepared.calleeInst.leaveTo(prepared.callerInst);
474
- }
475
- else {
476
- // Retire the poisoned CALLEE's stream/future ends (#66): this is a
477
- // bracket-break site like `Store.tick`'s, and the trap unwinds to a
478
- // hooked site that walks only the CALLER's chain — a composed
479
- // component's callee would otherwise strand its host peers.
470
+ // A trap poisons the callee instance. A *capability signal* does not
471
+ // see the `isCapabilitySignal` note in exec/boundary.ts.
472
+ if (!(e instanceof NeedsJspi) && !(e instanceof PendingCapability)) {
473
+ // Retire the poisoned CALLEE's stream/future ends (#66): the trap
474
+ // unwinds to a hooked site that walks only the CALLER's chain — a
475
+ // composed component's callee would otherwise strand its host peers.
480
476
  notifyInstancePoisoned(prepared.calleeInst, e);
481
477
  }
482
478
  // The lent handles are the CALLER's, and the caller is not poisoned by
483
- // either exit (contracts/intrinsics.md v0.2 amendment 2: this runtime
479
+ // either exit (contracts/intrinsics.md §A's trap-unwind/lender-release
480
+ // obligation: this runtime
484
481
  // deliberately supports post-trap re-entry on the caller side, where
485
482
  // the reference kills the whole store, so the sync-call scopes it
486
483
  // skipped have to be unwound explicitly). Leaving `numLends` elevated
@@ -490,8 +487,6 @@ export function createSyncStartCall(decl, ctx) {
490
487
  lenderScope.releaseLenders();
491
488
  throw e;
492
489
  }
493
- if (ok)
494
- prepared.calleeInst.leaveTo(prepared.callerInst);
495
490
  if (callerResults === null) {
496
491
  // The callee did not resolve within its first activation. definitions.py
497
492
  // `canon_lower`'s sync path blocks here — `thread.wait_until(
@@ -550,7 +545,7 @@ export function createSyncStartCall(decl, ctx) {
550
545
  // sits in `store.waiting`, e.g. the caller's whole host call was
551
546
  // abandoned). No JS runs, so nothing can release; the lent
552
547
  // handles die with the store, which is the reference's own
553
- // outcome. Out of scope for amendment 2 (no non-poisoning exit).
548
+ // outcome. Out of scope for the trap-unwind/lender-release obligation (no non-poisoning exit).
554
549
  // 6. Trap-poisoning of the parked instance: does not settle this
555
550
  // point by itself — it reaches the guest either as (2) (a
556
551
  // produce-time trap) or as (4) (teardown abandons the park), so
@@ -679,25 +674,20 @@ export function createAsyncStartCall(decl, ctx) {
679
674
  subtask.onCancel = (callerInst) => task.requestCancellation(callerInst);
680
675
  subtask.calleeTask = task;
681
676
  // A poisoned callee's refusal names the original trap (polyengine#145).
682
- if (!prepared.calleeInst.mayEnterFrom(prepared.callerInst)) {
683
- trap(withPoisonCause(prepared.calleeInst, "cannot enter component instance"));
677
+ {
678
+ const refusal = entryRefusal(prepared.calleeInst, prepared.callerInst, "cannot enter component instance");
679
+ if (refusal !== null)
680
+ trap(refusal);
684
681
  }
685
- prepared.calleeInst.enterFrom(prepared.callerInst);
686
- let ok = false;
687
682
  let thread;
688
683
  try {
689
684
  thread = spawn(task, body);
690
685
  thread.resume();
691
- ok = true;
692
686
  }
693
687
  catch (e) {
694
688
  // See the sync form above and `isCapabilitySignal` in exec/boundary.ts.
695
- if (e instanceof NeedsJspi || e instanceof PendingCapability) {
696
- prepared.calleeInst.leaveTo(prepared.callerInst);
697
- }
698
- else {
699
- // Bracket-break site — retire the poisoned callee's ends (#66),
700
- // as in the sync form above.
689
+ if (!(e instanceof NeedsJspi) && !(e instanceof PendingCapability)) {
690
+ // Retire the poisoned callee's ends (#66), as in the sync form above.
701
691
  notifyInstancePoisoned(prepared.calleeInst, e);
702
692
  }
703
693
  // The subtask never reached `report()`, so it has no handle in the
@@ -710,8 +700,6 @@ export function createAsyncStartCall(decl, ctx) {
710
700
  unwindSubtaskLenders(subtask);
711
701
  throw e;
712
702
  }
713
- if (ok)
714
- prepared.calleeInst.leaveTo(prepared.callerInst);
715
703
  const report = () => {
716
704
  if (subtask.resolved()) {
717
705
  // Eager completion: no handle, no event (definitions.py line 2293).
@@ -729,7 +717,7 @@ export function createAsyncStartCall(decl, ctx) {
729
717
  // NO WAIT FOR RESOLUTION HERE, deliberately. An async-lowered caller must
730
718
  // not block on its callee's *completion* -- that is the entire point of
731
719
  // async lowering: it takes a subtask handle and learns of completion
732
- // through events. An earlier attempt (M2 "Fix 1") parked the caller here
720
+ // through events. An earlier attempt parked the caller here
733
721
  // until the callee resolved. It made cross-abi-calls agree in both modes,
734
722
  // and it broke the thing it had no business touching: the caller's
735
723
  // activation was now suspended, so the sync-lowered parked caller of
@@ -833,7 +821,7 @@ export function createAsyncStartCall(decl, ctx) {
833
821
  // subtask index (it either threw before `handles.add`, or after it
834
822
  // with the index lost), so nothing will ever deliver this subtask's
835
823
  // resolution — exactly the state `unwindSubtaskLenders` exists for
836
- // (contracts/intrinsics.md v0.2 amendment 2).
824
+ // (contracts/intrinsics.md §A's trap-unwind/lender-release obligation).
837
825
  let produced = false;
838
826
  return blockCurrentActivation({
839
827
  store: prepared.callerInst.store,
@@ -1,23 +1,23 @@
1
1
  // Host trampolines (contracts/intrinsics.md §B) and FACT-adapter intrinsic
2
- // obligations (§A) — the M0 subset, with instantiate-time (never call-time)
3
- // milestone-aware failures for everything else.
2
+ // obligations (§A) — the core subset, with instantiate-time (never
3
+ // call-time) capability-gated failures for everything else.
4
4
  //
5
- // Implemented in M0:
5
+ // Implemented from the core subset:
6
6
  // lower-import host function call through descriptor-IR lift/lower
7
7
  // trap FACT `Trap` import -> ComponentTrap
8
8
  // enter/exit-sync-call degenerate sync-call bookkeeping (assert-and-count)
9
9
  // resource-new/rep/drop sync resource paths over cabi handle tables
10
- // (M1 schedule, implemented early: the resources
11
- // fixture references them at instantiation)
10
+ // (resources capability, implemented early: the
11
+ // resources fixture references them at instantiation)
12
12
  //
13
- // Everything else fails at instantiate time with the milestone at which
14
- // intrinsics.md §B schedules it — "this component needs the M2 task core"
13
+ // Everything else fails at instantiate time naming the capability that
14
+ // intrinsics.md §B schedules it under — "this component needs the task core"
15
15
  // is a feature, not a crash.
16
16
  import { canonResourceDrop, canonResourceNew, canonResourceRep, trap, } from "../cabi/mod.js";
17
17
  import { ResourceHandle } from "../cabi/handles.js";
18
18
  import { trapIf } from "../cabi/trap.js";
19
19
  import { assert_ } from "../cabi/trap.js";
20
- import { maybeCurrentThread, PendingCapability, withPoisonCause } from "../task/mod.js";
20
+ import { dbgId, entryRefusal, maybeCurrentThread } from "../task/mod.js";
21
21
  import { UnsupportedFeatureError } from "./errors.js";
22
22
  import { createBackpressureDec, createBackpressureInc, createSubtaskCancel, createSubtaskDrop, createTaskCancel, createTaskReturn, createThreadYield, createWaitableJoin, createWaitableSetDrop, createWaitableSetNew, createWaitableSetPoll, createWaitableSetWait, } from "./async_builtins.js";
23
23
  import { createAsyncStartCall, createPrepareCall, createSyncStartCall, } from "./fact_calls.js";
@@ -55,21 +55,21 @@ const FACT_TRAP_MESSAGES = {
55
55
  /** Ordinal of `Trap::UncaughtException` in wasmtime's trap encoding. */
56
56
  const TRAP_UNCAUGHT_EXCEPTION = 49;
57
57
  export { UnsupportedFeatureError } from "./errors.js";
58
- /** Milestone at which each trampoline kind stops instantiate-failing. */
59
- const TRAMPOLINE_MILESTONE = {
60
- "lower-import": "M0",
61
- "trap": "M0",
62
- "enter-sync-call": "M0",
63
- "exit-sync-call": "M0",
64
- "resource-new": "M1",
65
- "resource-rep": "M1",
66
- "resource-drop": "M1",
67
- "transcoder": "M1",
68
- "resource-transfer-own": "M1",
69
- "resource-transfer-borrow": "M1",
58
+ /** Capability at which each trampoline kind stops instantiate-failing. */
59
+ const TRAMPOLINE_CAPABILITY = {
60
+ "lower-import": "core",
61
+ "trap": "core",
62
+ "enter-sync-call": "core",
63
+ "exit-sync-call": "core",
64
+ "resource-new": "resources",
65
+ "resource-rep": "resources",
66
+ "resource-drop": "resources",
67
+ "transcoder": "resources",
68
+ "resource-transfer-own": "resources",
69
+ "resource-transfer-borrow": "resources",
70
70
  };
71
- function milestoneOf(kind) {
72
- return TRAMPOLINE_MILESTONE[kind] ?? "M2";
71
+ function capabilityOf(kind) {
72
+ return TRAMPOLINE_CAPABILITY[kind] ?? "task-core";
73
73
  }
74
74
  /**
75
75
  * The borrow bookkeeping of one in-flight synchronous cross-component call,
@@ -119,7 +119,7 @@ export class SyncCallScope {
119
119
  * unsupported kind fails instantiation, not the first call
120
120
  * (plan-format.md "Executor obligations"). Unreferenced trampolines are
121
121
  * never created and therefore never fail (intrinsics.md §B tolerates e.g.
122
- * an unreferenced task-return until M2).
122
+ * an unreferenced task-return until the task core exists).
123
123
  */
124
124
  export function createTrampoline(decl, ctx) {
125
125
  const fn = createTrampolineBody(decl, ctx);
@@ -139,16 +139,6 @@ export function createTrampoline(decl, ctx) {
139
139
  }
140
140
  };
141
141
  }
142
- /**
143
- * A trampoline that instantiates fine but fails at its first call, naming the
144
- * phase that will implement it. See the CONTRACT note at the stream/future
145
- * cases for why these are not instantiate-time failures.
146
- */
147
- function deferredCapability(kind, capability) {
148
- return () => {
149
- throw new PendingCapability(`built-in '${kind}' is not implemented yet: ${capability}`);
150
- };
151
- }
152
142
  /**
153
143
  * The component instance a trampoline is declared in (wasmtime names it in
154
144
  * every instance-scoped `Trampoline` variant). This is the static answer to
@@ -180,23 +170,12 @@ const SCOPE_TRACE = (() => {
180
170
  return false;
181
171
  }
182
172
  })();
183
- const taskIds = new WeakMap();
184
- let nextTaskId = 1;
185
- function taskId(t) {
186
- if (t === undefined || t === null)
187
- return "NONE(->ctx fallback)";
188
- let id = taskIds.get(t);
189
- if (id === undefined) {
190
- id = nextTaskId++;
191
- taskIds.set(t, id);
192
- }
193
- return `T${id}`;
194
- }
195
173
  function syncScopes(ctx, site = "?") {
196
174
  const thread = maybeCurrentThread();
197
175
  const scopes = thread?.syncCallStack ?? ctx.syncCallStack;
198
176
  if (SCOPE_TRACE) {
199
- console.error(`[scope] ${site} act=${taskId(thread)} depth=${scopes.length}`);
177
+ console.error(`[scope] ${site} act=${thread ? dbgId(thread) : "NONE(->ctx fallback)"} ` +
178
+ `depth=${scopes.length}`);
200
179
  }
201
180
  return scopes;
202
181
  }
@@ -227,8 +206,8 @@ function createTrampolineBody(decl, ctx) {
227
206
  ? `FACT adapter trap (code ${code ?? "?"})`
228
207
  : `wasm trap: ${message}`);
229
208
  };
230
- // Sync-call task bookkeeping (intrinsics.md §A: "degenerate-case
231
- // implementation in M0: assert-and-count"). wasmtime 47 signatures:
209
+ // Sync-call task bookkeeping (intrinsics.md §A) — assert-and-count.
210
+ // wasmtime 47 signatures:
232
211
  // enter-sync-call carries the caller/callee instance pair, which is what
233
212
  // the reentrance gate below needs; balance of the bracket is asserted at
234
213
  // component teardown by tests.
@@ -238,72 +217,31 @@ function createTrampolineBody(decl, ctx) {
238
217
  // async.exit-sync-call() -> ()
239
218
  case "enter-sync-call":
240
219
  return (callerInstance, async_, calleeInstance) => {
241
- // Reference reentrance gate. Every guest->guest call in
242
- // definitions.py routes through the callee's lift wrapper:
243
- // canon_lower (line 2312) calls
244
- // `callee(on_start, on_resolve, caller = thread.task.inst)`,
245
- // and `callee` is `Store.lift`'s `func_inst` (lines 578-585), whose
246
- // first act is
247
- // `trap_if(not inst.may_enter_from(caller))` (line 581)
248
- // with `entering_set(caller) = callee.self_and_ancestors()
249
- // - caller.self_and_ancestors()`
250
- // (lines 230-234).
251
- // A sync fused adapter is an *optimization* of that path, so the gate
252
- // belongs here (issue #99).
253
- //
254
- // Note on the shape of the entering set, which is what makes this
255
- // check safe for the legal shapes:
256
- // * caller == callee, or either an ancestor of the other -> the
257
- // entering set is empty and this never traps. Those pairs never
258
- // reach this trampoline anyway: FACT emits an unconditional
259
- // `CannotEnterComponent` trap for them at compile time
260
- // (wasmtime-environ 47.0.3 `fact/trampoline.rs:120-127`), which
261
- // is what `test/async/trap-on-reenter.wast` cases 2 and 3 pin.
262
- // * an *idle* sibling -> `mayEnter` is true, no trap. This is what
263
- // `test/async/sync-barges-in.wast` needs: an async callee that is
264
- // merely blocked has already run `leave_to` (its `canon_lift`
265
- // returned), so a sync sibling may barge in.
266
- // * an *entered* sibling -> trap, which is the A -> C -> A cycle.
220
+ // ENTRY REFUSAL at the fused sync-call boundary.
267
221
  //
268
- // CONTRACT / reachability: a pure guest-to-guest sibling cycle is
269
- // unreachable by construction, because component instance imports
270
- // form a DAG (a callee must be instantiated before its caller, so it
271
- // cannot hold an import of its caller; `wasm-tools` rejects the
272
- // mutual-import composition outright). wasmtime relies on exactly
273
- // that to elide the runtime check in fused adapters -- see the
274
- // comment in `may_enter`, wasmtime 47.0.3
275
- // `runtime/component/concurrent.rs:1876-1886`, and
276
- // `enter_guest_sync_call` (concurrent.rs:1723) which performs no
277
- // reentrance check at all. The gate is kept anyway because the
278
- // reference mandates it and no corpus test pins the permissive
279
- // behaviour; it is cheap, and it is the honest place for the
280
- // invariant to be asserted rather than assumed.
222
+ // The reference has no reentrance gate (CM#705; definitions.py @
223
+ // 2f13265 has no `may_enter`/`entering_set`/`enter_from`): a
224
+ // guest->guest call through `Store.lift` runs `canon_lift`
225
+ // unconditionally, and host-mediated reentrance host -> A.f -> C.g
226
+ // -> host import -> host invokes C.g is simply valid. wasmtime's
227
+ // fused adapters agreed all along: `enter_guest_sync_call`
228
+ // (47.0.3 `runtime/component/concurrent.rs:1723`) performs no
229
+ // reentrance check, and `fact/trampoline.rs:120-127` decides the
230
+ // caller==callee / ancestor pairs statically at compile time (what
231
+ // `test/async/trap-on-reenter.wast` cases 2 and 3 pin a translation
232
+ // -time trap, not this site).
281
233
  //
282
- // Deliberately *not* done here: `enter_from` / `leave_to` around the
283
- // bracket. The reference locks the callee for the duration, which
284
- // would additionally trap host-mediated reentrance (host -> A.f ->
285
- // C.g -> host import -> host invokes C.g).
286
- //
287
- // ADJUDICATED 2026-08-20 (polyengine#165; named divergence in
288
- // docs/architecture.md section 6): accepted — the bracket stays
289
- // omitted. Three grounds: wasmtime parity (`enter_guest_sync_call`
290
- // checks nothing); taking the bracket would create a guest-to-guest
291
- // lock spanning suspension points, reintroducing the await-spanning
292
- // -lock class removed by #156/#160; and upstream is deleting the
293
- // trap outright — CM PR #705 ("CABI: remove the may_enter
294
- // flag/trap") makes previously-trapping reentrance valid, so this
295
- // divergence is a trailing indicator of the removal and
296
- // self-resolves at the submodule pin advance (migration map:
297
- // polyengine#173). Until that pin advance the reference's checks polyengine
298
- // DOES enforce stay in force.
234
+ // What this site does check is polyengine's per-instance poisoning: a
235
+ // callee that trapped is a corpse and may never be entered again,
236
+ // and the refusal names the original trap (polyengine#145). That is
237
+ // the whole content of this check.
299
238
  if (typeof callerInstance === "number" &&
300
239
  typeof calleeInstance === "number") {
301
240
  const callerInst = ctx.componentInstance(callerInstance >>> 0);
302
241
  const calleeInst = ctx.componentInstance(calleeInstance >>> 0);
303
- // A poisoned callee's refusal names the original trap (polyengine#145).
304
- if (!calleeInst.mayEnterFrom(callerInst)) {
305
- trap(withPoisonCause(calleeInst, "cannot enter component instance"));
306
- }
242
+ const refusal = entryRefusal(calleeInst, callerInst, "cannot enter component instance");
243
+ if (refusal !== null)
244
+ trap(refusal);
307
245
  }
308
246
  // `async_` records whether the callee is *async-lifted*. wasmtime
309
247
  // stores it on the guest task it creates here
@@ -374,7 +312,7 @@ function createTrampolineBody(decl, ctx) {
374
312
  // `lift_own`/`lower_own` and `lift_borrow`/`lower_borrow`
375
313
  // (definitions.py) with the src/dst tables named by index rather than
376
314
  // implied by the running instance.
377
- // FACT string transcoders (contracts/intrinsics.md §B "M1"). The plan
315
+ // FACT string transcoders (contracts/intrinsics.md §B). The plan
378
316
  // carries the op name plus the source/destination `RuntimeMemoryIndex`es;
379
317
  // `./transcode.ts` holds the twelve operations.
380
318
  case "transcoder": {
@@ -382,14 +320,14 @@ function createTrampolineBody(decl, ctx) {
382
320
  if (d.from64 || d.to64) {
383
321
  // 64-bit linear memories are out of scope (https://github.com/polymorph-components/polyengine/issues/12); refusing at
384
322
  // instantiate time keeps "instantiate-time, never call-time".
385
- throw new UnsupportedFeatureError("M2", `transcoder '${d.op}' over a 64-bit linear memory`);
323
+ throw new UnsupportedFeatureError("task-core", `transcoder '${d.op}' over a 64-bit linear memory`);
386
324
  }
387
325
  if (!TRANSCODE_OPS.includes(d.op)) {
388
- throw new UnsupportedFeatureError("M2", `unknown string transcode operation '${d.op}'`);
326
+ throw new UnsupportedFeatureError("task-core", `unknown string transcode operation '${d.op}'`);
389
327
  }
390
328
  return createTranscoder(d.op, ctx.runtimeMemory(d.from), ctx.runtimeMemory(d.to));
391
329
  }
392
- // --- 0.3 async built-ins (contracts/intrinsics.md §B "M2") -------------
330
+ // --- 0.3 async built-ins (contracts/intrinsics.md §B) -------------
393
331
  // All ported in ./async_builtins.ts; the ones that would have to block a
394
332
  // wasm frame fail there, at the call site, with a JSPI-shaped message.
395
333
  case "task-return":
@@ -484,7 +422,7 @@ function createTrampolineBody(decl, ctx) {
484
422
  case "resource-transfer-borrow":
485
423
  return (handle, srcTable, dstTable) => transferBorrow(ctx, handle >>> 0, srcTable, dstTable);
486
424
  default:
487
- throw new UnsupportedFeatureError(milestoneOf(decl.kind) === "M1" ? "M1" : "M2", `component requires host trampoline '${decl.kind}'`);
425
+ throw new UnsupportedFeatureError(capabilityOf(decl.kind) === "resources" ? "resources" : "task-core", `component requires host trampoline '${decl.kind}'`);
488
426
  }
489
427
  }
490
428
  // ---------------------------------------------------------------------------
@@ -526,10 +464,9 @@ function transferOwn(ctx, handle, srcTable, dstTable) {
526
464
  * which is this path's stand-in for the callee `Subtask`/`Task` of
527
465
  * definitions.py.
528
466
  */
529
- // CONTRACT: contracts/intrinsics.md §A schedules ResourceTransfer* at "M1,
530
- // resources milestone" and describes them only as "handle-table moves between
531
- // component instances" the borrow-scope interaction is unspecified there.
532
- // The reading implemented here is taken from definitions.py
467
+ // CONTRACT: contracts/intrinsics.md §A describes ResourceTransfer* only as
468
+ // "handle-table moves between component instances" the borrow-scope
469
+ // interaction beyond the lender registration is taken from definitions.py
533
470
  // (`lift_borrow`/`lower_borrow` + `Subtask.lenders`/`Task.num_borrows`) and
534
471
  // is what makes `test/resources/borrows.wast:162` (`lend-trap`) trap.
535
472
  function transferBorrow(ctx, handle, srcTable, dstTable) {
@@ -553,8 +553,8 @@ export function createFutureTransfer(ctx) {
553
553
  * rather than removing it, so the source keeps its own.
554
554
  */
555
555
  /**
556
- * Plan v3 (contracts/plan-format.md v3 amendment 2): `instanceOf` resolves
557
- * through the plan's `errorContextTables` section — the
556
+ * `instanceOf` resolves through the plan's `errorContextTables` section
557
+ * (contracts/plan-format.md schema) — the
558
558
  * `TypeComponentLocalErrorContextTableIndex` space these arguments actually
559
559
  * live in. It replaced a resource-table lookup, which shared neither the
560
560
  * index space nor (in a multi-instance composition) the answer.
@@ -1,5 +1,5 @@
1
1
  // FACT string-transcoder intrinsics (`wasmtime_environ::component::Trampoline
2
- // ::Transcoder`, contracts/intrinsics.md §B "M1").
2
+ // ::Transcoder`, contracts/intrinsics.md §B).
3
3
  //
4
4
  // FACT emits these when a cross-component call has to move a string between
5
5
  // two components whose canonical `string-encoding` options disagree (or, for
@@ -49,7 +49,8 @@ import { withActivation, claimActivationAmbient, dbgId, maybeCurrentThread, rele
49
49
  * `requested` is the embedder's opt-in. We additionally require the engine to
50
50
  * actually implement JSPI: on an engine without it every blocking site falls
51
51
  * back to the precise `NeedsJspi` it raised before this module existed, which
52
- * is the M3 browser-matrix degradation path (docs/milestones.md M3).
52
+ * is the browser-matrix degradation path for engines without JSPI (see
53
+ * `just browsers`).
53
54
  */
54
55
  export function chooseMode(requested, needed) {
55
56
  if (requested === false)
@@ -395,8 +396,8 @@ export class SuspensionPoint {
395
396
  * transition this point takes — produce-success, produce-throw, or
396
397
  * `abandon` (issue #102). It is the seam a blocking built-in uses to
397
398
  * discharge state it owns for the duration of the park (the FACT
398
- * start-calls' borrow-lender scopes, contracts/intrinsics.md v0.2
399
- * amendment 2) without having to trust that `produce` runs.
399
+ * start-calls' borrow-lender scopes, contracts/intrinsics.md §A's
400
+ * trap-unwind/lender-release obligation) without having to trust that `produce` runs.
400
401
  *
401
402
  * INVARIANTS this hook must respect, so bridge.ts's own contracts are
402
403
  * not disturbed:
@@ -1,7 +1,7 @@
1
1
  // The per-declaration suspendability marker (contracts/embedder-api.md
2
- // §"Functions and async", amendments A1/A2; docs/architecture.md §5).
2
+ // §"Functions and async"; docs/architecture.md §5).
3
3
  //
4
- // The canonical definitions live in `@polyengine/protocol` since amendment A9:
4
+ // The canonical definitions live in `@polyengine/protocol` since §"Module identity and @polyengine/protocol":
5
5
  // the mark is a process-global `Symbol.for("polyengine.suspending/1")` brand, so
6
6
  // a function marked by ANY runtime copy is honored by every other copy
7
7
  // (issue #83 — a module-local symbol made a copy-B mark invisible to copy A's
@@ -9,12 +9,12 @@
9
9
  // surfacing far away as `NeedsJspi`).
10
10
  //
11
11
  // Layering: this module was import-free on purpose (jspi/ stays standalone);
12
- // A9 relaxes that to "imports `@polyengine/protocol` only" — the protocol package
12
+ // module identity relaxes that to "imports `@polyengine/protocol` only" — the protocol package
13
13
  // is itself dependency-free, so jspi/ still pulls in no runtime machinery.
14
- // A23 (`deferCancel`/`isDeferCancel`) and A24 (`abortable`/`isAbortable`)
14
+ // cancellation discard (`deferCancel`/`isDeferCancel`) and abortable() (`abortable`/`isAbortable`)
15
15
  // ride the same re-export: they are the other per-declaration host-import
16
16
  // marks, they live in the same dependency-free package, and
17
17
  // `exec/executor.ts` reads all three through `jspi/mod.ts`.
18
18
  // (Host modules import the marks from `@polyengine/protocol` directly —
19
- // the embedder surface stopped re-exporting the vocabulary at A22.)
19
+ // the embedder surface stopped re-exporting the vocabulary at host-ABI version.)
20
20
  export { abortable, anySuspendingImport, deferCancel, isAbortable, isDeferCancel, isSuspending, suspending, } from "@polyengine/protocol";
@@ -2,8 +2,7 @@
2
2
  // of the wire descriptor IR (contracts/descriptor-ir.md JSON) into the cabi
3
3
  // in-memory type model (runtime/src/cabi/types.ts, the normative model).
4
4
  //
5
- // Wire -> in-memory deltas handled here (recorded in the M0 contract
6
- // friction report):
5
+ // Wire -> in-memory deltas handled here:
7
6
  // - `result.err` (wire, per descriptor-ir.md) -> `result.error` (types.ts)
8
7
  // - func params `{label, type}[]` (wire) -> unlabeled `ValType[]`
9
8
  // (types.ts drops ABI-irrelevant names; labels are preserved separately
@@ -56,7 +55,7 @@ export class TranslateError extends Error {
56
55
  * the *resource*-table mapping before, a different space) — and
57
56
  * `task-return`'s `resultType` / raw `results` split, which lets a FACT
58
57
  * callee task carry its declared result type.
59
- * v2 (M2 phase 2c): `streamTables` / `futureTables` — the element types the
58
+ * v2: `streamTables` / `futureTables` — the element types the
60
59
  * stream and future built-ins need to size their copy buffers.
61
60
  * v1 (contracts/plan-format.md v0.3): `CoreDef` gained `"unsafe-intrinsic"`.
62
61
  * The change is purely additive, but the contract's compat rule is a strict
@@ -142,7 +141,7 @@ export function loadPlan(wire) {
142
141
  }
143
142
  }
144
143
  // Identity tokens: one per RESOURCE, aliased through every table that
145
- // names it — NOT one per table. plan-format.md C2 amendment #1: "one
144
+ // names it — NOT one per table. plan-format.md "Type exports index into `resourceTables`": "one
146
145
  // resource type can be reachable through several distinct table indices …
147
146
  // Consumers keying per-resource state must key by `resourceTables[n]
148
147
  // .resource`, treating table indices as aliases." Minting per-table broke
@@ -526,7 +525,8 @@ function validateExport(exp, where) {
526
525
  validateTypeExport(e.type, `${where}.type`);
527
526
  return;
528
527
  case "module":
529
- // plan-format.md v4 amendment 2: exported embedded core module.
528
+ // The `module` export kind (contracts/plan-format.md schema notes):
529
+ // exported embedded core module.
530
530
  expectString(e, "name", where);
531
531
  expectNumber(e, "module", where);
532
532
  return;
@@ -19,8 +19,8 @@ export class Translator {
19
19
  * (docs/architecture.md §10): the wire envelope's `producer` block records
20
20
  * `{shimVersion, wasmtimeEnviron, features}`, which does NOT change when
21
21
  * the shim wasm is rebuilt from the same source versions (e.g. a local
22
- * patch or a different toolchain producing different codegen) — see the
23
- * M3-B dispatch. Digesting the actual bytes is the only sound cache key
22
+ * patch or a different toolchain producing different codegen). Digesting
23
+ * the actual bytes is the only sound cache key
24
24
  * component for translator identity.
25
25
  */
26
26
  buildHash;