@polyengine/runtime 0.5.1 โ†’ 0.6.0

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
package/esm/task/mod.js CHANGED
@@ -12,8 +12,9 @@
12
12
  // and friends, ๐Ÿงต) are absent rather than approximated โ€” https://github.com/polymorph-components/polyengine/issues/12
13
13
  // defers that feature with memory64.
14
14
  import { Table } from "../cabi/handles.js";
15
+ import { COMPONENT_INSTANCE } from "../cabi/context.js";
15
16
  import { assert_, trapIf } from "../cabi/trap.js";
16
- import { CANCELLED_TRUE, chooseCandidate, Store, dbgId, NeedsJspi, notifyInstancePoisoned, PendingCapability, } from "./scheduler.js";
17
+ import { CANCELLED_TRUE, chooseCandidate, isInstancePoisoned, Store, dbgId, NeedsJspi, notifyInstancePoisoned, PendingCapability, } from "./scheduler.js";
17
18
  import { Thread } from "./thread.js";
18
19
  import { Waitable, WaitableSet } from "./waitable.js";
19
20
  import { Subtask } from "./subtask.js";
@@ -22,16 +23,6 @@ export * from "./thread.js";
22
23
  export * from "./waitable.js";
23
24
  export * from "./subtask.js";
24
25
  export * from "./streams.js";
25
- /**
26
- * Synthetic-root registry: one root per `Store` (see
27
- * `ComponentInstanceState.rootOf`). A `WeakMap` so a dead store's root dies
28
- * with it.
29
- */
30
- const syntheticRoots = new WeakMap();
31
- /** `index` of the synthetic root โ€” outside the real instance index space. */
32
- const ROOT_INDEX = -1;
33
- /** Constructor marker: "this one IS the root, do not give it a parent". */
34
- const ROOT_TOKEN = Symbol("synthetic-root");
35
26
  /**
36
27
  * Per-component-instance runtime state (definitions.py `ComponentInstance`,
37
28
  * line 191).
@@ -39,8 +30,19 @@ const ROOT_TOKEN = Symbol("synthetic-root");
39
30
  * `mayLeave` is backed by a real `WebAssembly.Global(i32, mutable)` because
40
31
  * FACT adapters import that global (`flags` namespace) and read/write it as
41
32
  * the may_leave boolean (wasmtime 47 FACT treats the whole flags global as
42
- * may_leave; there is no bitmask). Initial value 1 (true). `mayEnter` is
43
- * host-side state: nothing wasm-visible reads it.
33
+ * may_leave; there is no bitmask). Initial value 1 (true).
34
+ *
35
+ * There is no `may_enter` counterpart and no instance tree: at the pinned
36
+ * reference (definitions.py @ 2f13265, CM#705) there is no `may_enter`,
37
+ * `parent`, `entering_set`, `enter_from` or `leave_to`, so nothing gates
38
+ * entry into a live instance. What polyengine adds beyond the reference is
39
+ * per-instance POISONING โ€” a named divergence
40
+ * living entirely in ./scheduler.ts (`isInstancePoisoned`, `entryRefusal`),
41
+ * not in any state on this class.
42
+ *
43
+ * `COMPONENT_INSTANCE` brands this class as a real component instance for
44
+ * the layers that only see the structural `ComponentInstanceLike`
45
+ * (cabi/handles.ts `isComponentInstance`; cabi must not import task/).
44
46
  */
45
47
  export class ComponentInstanceState {
46
48
  index;
@@ -48,80 +50,19 @@ export class ComponentInstanceState {
48
50
  handles = new Table();
49
51
  /** definitions.py `ComponentInstance.threads` โ€” a Table, so `thread.index`. */
50
52
  threads = new Table();
51
- mayEnter = true;
53
+ /** cabi's real-instance discriminator; see the class doc. */
54
+ [COMPONENT_INSTANCE] = true;
52
55
  /** definitions.py `backpressure: int` โ€” a *counter* (backpressure.{inc,dec}). */
53
56
  backpressure = 0;
54
57
  /** definitions.py `num_waiting_to_enter`. */
55
58
  numWaitingToEnter = 0;
56
59
  /** definitions.py `exclusive_thread`. */
57
60
  exclusiveThread = null;
58
- /**
59
- * definitions.py `ComponentInstance.parent`.
60
- *
61
- * The plan still gives us a flat instance space, but the tree is no longer
62
- * needed: every instance of one instantiation gets the same **synthetic
63
- * root** as its parent (contracts/plan-format.md v3 amendment 4 /
64
- * polyengine#101). See `enteringSet` for why that is observably equivalent to
65
- * the real chain. The root itself has no parent.
66
- */
67
- parent;
68
61
  store;
69
- /**
70
- * The synthetic per-instantiation root (v3 amendment 4). One per `Store`:
71
- * a `Store` is exactly one component instantiation's scheduling scope, so
72
- * "all `ComponentInstanceState`s sharing a `Store`" is the set that shares
73
- * a top-level component โ€” which is the granularity wasmtime's own
74
- * top-level-instance-id comparison uses (concurrent.rs:1876-1886).
75
- *
76
- * It is a real `ComponentInstanceState` (index -1) rather than a bare flag
77
- * so it flows through `selfAndAncestors`/`enteringSet` unchanged; its
78
- * handle table stays empty and no task ever runs on it.
79
- */
80
- static rootOf(store) {
81
- let root = syntheticRoots.get(store);
82
- if (root === undefined) {
83
- root = new ComponentInstanceState(ROOT_INDEX, store, ROOT_TOKEN);
84
- syntheticRoots.set(store, root);
85
- }
86
- return root;
87
- }
88
- /** Is this the synthetic root (never a real component instance)? */
89
- get isSyntheticRoot() {
90
- return this.index === ROOT_INDEX && this.parent === null;
91
- }
92
- constructor(index, store, root) {
62
+ constructor(index, store) {
93
63
  this.index = index;
94
64
  this.store = store ?? new Store();
95
65
  this.flags = new WebAssembly.Global({ value: "i32", mutable: true }, 1);
96
- // The root is its own tree's top; everything else hangs off it. Built
97
- // lazily here so no call site has to remember to wire it up.
98
- this.parent = root === ROOT_TOKEN
99
- ? null
100
- : ComponentInstanceState.rootOf(this.store);
101
- }
102
- /**
103
- * Release the synthetic root after a trap broke the enter/leave bracket
104
- * (v3 amendment 4, and a **named divergence** from the reference).
105
- *
106
- * definitions.py poisons the whole entering set: `Store.lift` never reaches
107
- * `leave_to`, so the root โ€” which is in every host entry's entering set โ€”
108
- * stays `may_enter == False` forever and NO instance of the component can
109
- * be entered again. wasmtime is the same by other means (it poisons the
110
- * store). polyengine deliberately supports post-trap re-entry of instances the
111
- * trap did not touch (exec/boundary.ts `poison`: "sibling instances stay
112
- * usable, which is why the lock is released per-instance rather than by
113
- * poisoning a whole store the way wasmtime does"), and the synthetic root
114
- * must not silently convert that documented divergence into store-wide
115
- * poisoning. So a trap poisons the LEAF set only, and the root is released
116
- * here โ€” the reentrance gate the root exists for (a *second, concurrent*
117
- * host entry) is about a live entry, and after a trap unwinds to the host
118
- * there is none.
119
- */
120
- releaseSyntheticRootOnPoison() {
121
- for (const inst of this.selfAndAncestors()) {
122
- if (inst.isSyntheticRoot)
123
- inst.mayEnter = true;
124
- }
125
66
  }
126
67
  get mayLeave() {
127
68
  return this.flags.value !== 0;
@@ -129,84 +70,6 @@ export class ComponentInstanceState {
129
70
  set mayLeave(v) {
130
71
  this.flags.value = v ? 1 : 0;
131
72
  }
132
- /** definitions.py `ComponentInstance.self_and_ancestors` (line 236). */
133
- selfAndAncestors() {
134
- const s = new Set([this]);
135
- let a = this.parent;
136
- while (a !== null) {
137
- s.add(a);
138
- a = a.parent;
139
- }
140
- return s;
141
- }
142
- /**
143
- * definitions.py `ComponentInstance.entering_set` (line 230):
144
- * `self_and_ancestors() - caller.self_and_ancestors()`.
145
- *
146
- * CONTRACT (contracts/plan-format.md v3 amendment 4, polyengine#101): the plan
147
- * still carries no wire form for the component-instance tree, and it no
148
- * longer needs one. Every instance's parent is the synthetic
149
- * per-instantiation root, so:
150
- *
151
- * * host entry (`caller === null`): `{this, root}` โ€” the reference's
152
- * entering set for a host entry is `self_and_ancestors()`, which always
153
- * contains the top-level root, so a second host entry anywhere in the
154
- * tree trips on the root either way. This is the divergence #101
155
- * reported (host -> A.f -> host import -> host enters a *different*
156
- * instance): now caught.
157
- * * guest-to-guest (`caller !== null`): `{this}` โ€” the root is in the
158
- * caller's ancestor set and cancels out. Intermediate ancestors would
159
- * be the only difference from the real chain, and they are never
160
- * reachably consulted: FACT compiles same-instance and ancestor calls
161
- * to unconditional compile-time traps, and sibling cycles are
162
- * unreachable because instance imports form a DAG (polyengine#99
163
- * adjudication).
164
- *
165
- * So the synthetic root is observably equivalent to the full chain, and it
166
- * matches wasmtime's own shortcut โ€” a top-level instance-id comparison
167
- * (concurrent.rs:1876-1886) โ€” by construction. This reopens only if some
168
- * future upstream shape makes nesting depth observable.
169
- *
170
- * One deliberate departure remains, at the trap path rather than here: see
171
- * `releaseSyntheticRootOnPoison`.
172
- */
173
- enteringSet(caller) {
174
- const mine = this.selfAndAncestors();
175
- if (caller === null)
176
- return mine;
177
- for (const c of caller.selfAndAncestors())
178
- mine.delete(c);
179
- return mine;
180
- }
181
- /** definitions.py `ComponentInstance.may_enter_from` (line 214). */
182
- mayEnterFrom(caller) {
183
- for (const inst of this.enteringSet(caller)) {
184
- if (!inst.mayEnter)
185
- return false;
186
- }
187
- return true;
188
- }
189
- /** definitions.py `ComponentInstance.enter_from` (line 220). */
190
- enterFrom(caller) {
191
- for (const inst of this.enteringSet(caller)) {
192
- assert_(inst.mayEnter, "enter_from without may_enter");
193
- inst.mayEnter = false;
194
- }
195
- }
196
- /** definitions.py `ComponentInstance.leave_to` (line 225). */
197
- leaveTo(caller) {
198
- for (const inst of this.enteringSet(caller)) {
199
- assert_(!inst.mayEnter, "leave_to without a matching enter_from");
200
- inst.mayEnter = true;
201
- }
202
- }
203
- /** Backwards-compatible host-entry helpers (the M0 spelling). */
204
- enter() {
205
- this.enterFrom(null);
206
- }
207
- leave() {
208
- this.leaveTo(null);
209
- }
210
73
  }
211
74
  /** definitions.py `LiftOptions.equal` (line 643): encoding + memory identity. */
212
75
  export function liftOptionsEqual(a, b) {
@@ -258,7 +121,8 @@ export class Task {
258
121
  * A FACT callee task's result type arrives as the raw wasmtime
259
122
  * `TypeTupleIndex` `prepare-call` passes as `task_return_type`; v3's
260
123
  * `task-return.results` / `resultType` pair is the dictionary for it
261
- * (contracts/plan-format.md v3 amendment 3). It resolves for every callee
124
+ * (the task-return trampoline's raw `results` key + interned `resultType`;
125
+ * contracts/plan-format.md schema). It resolves for every callee
262
126
  * that has a `task.return` trampoline of its own โ€” which is every callee
263
127
  * that can call `task.return` โ€” but a callee with none (sync-lifted,
264
128
  * reached through an async-to-sync adapter) contributes no entry, and then
@@ -376,12 +240,16 @@ export class Task {
376
240
  thread.index = null;
377
241
  }
378
242
  /**
379
- * definitions.py `Task.request_cancellation` (line 519). Delivered to a
380
- * cancellable thread if one exists and the instance is enterable; otherwise
381
- * recorded as pending, to be picked up at the next cancellable block point
382
- * (`deliverPendingCancel`).
243
+ * definitions.py `Task.request_cancellation` (@ 2f13265). Delivered to a
244
+ * cancellable thread if one exists; otherwise recorded as pending, to be
245
+ * picked up at the next cancellable block point (`deliverPendingCancel`).
246
+ *
247
+ * `caller` is retained for the call-site shape (fact_calls.ts's
248
+ * `subtask.onCancel`) and for diagnostics; no condition here consults it
249
+ * (CM#705: entry into a live instance is ungated).
383
250
  */
384
251
  requestCancellation(caller) {
252
+ void caller;
385
253
  if (this.state === "initial") {
386
254
  this.state = "cancel-delivered";
387
255
  this.implicitThread.resume(CANCELLED_TRUE);
@@ -408,39 +276,34 @@ export class Task {
408
276
  }
409
277
  }
410
278
  }
411
- if (candidates.length > 0 && this.inst.mayEnterFrom(caller)) {
279
+ // Merged reference (definitions.py @ 2f13265): `if candidates: deliver`,
280
+ // full stop โ€” no enterability condition, no bracket (CM#705).
281
+ //
282
+ // ONE divergence conjunct survives: a POISONED instance is a corpse whose
283
+ // threads never resume, so the request parks as pending-cancel forever โ€”
284
+ // which is the honest state, since a corpse can never reach a cancellable
285
+ // suspension to deliver at. The reference never faces this because a trap
286
+ // there kills the whole store. The marker is the authoritative input.
287
+ if (candidates.length > 0 && !isInstancePoisoned(this.inst)) {
412
288
  this.state = "cancel-delivered";
413
- this.inst.enterFrom(caller);
414
289
  try {
415
290
  chooseCandidate(candidates).resume(CANCELLED_TRUE);
416
291
  }
417
292
  catch (e) {
418
- // Deliberately NOT a `finally`, mirroring `Store.tick`'s
419
- // bracket-break discipline (scheduler.ts): the reference wraps the
420
- // delivery `resume(Cancelled.TRUE)` in no handler at all
421
- // (definitions.py `Task.request_cancellation`, lines 519-532; the
422
- // delivery is line 531), so a Trap escaping it never reaches
423
- // `leave_to` on line 532 โ€” the entered set stays locked, i.e. the
424
- // Component Model's instance poisoning. A `finally` here would
425
- // un-poison a half-unwound callee.
293
+ // A trap escaping the delivery poisons the callee instance
294
+ // (polyengine#164/#212) โ€” polyengine's per-instance corpse divergence;
295
+ // the reference wraps this `resume(Cancelled.TRUE)` in no handler at
296
+ // all and simply ends the world.
426
297
  //
427
298
  // Capability signals are the exception, exactly as in `tick`: they
428
- // mark this RUNTIME incomplete, not the component faulted, and in
429
- // the reference the blocking operation completes and `leave_to` IS
430
- // reached.
431
- if (e instanceof NeedsJspi || e instanceof PendingCapability) {
432
- this.inst.leaveTo(caller);
433
- }
434
- else {
435
- // The synthetic root is released so the poisoning stays
436
- // per-instance (plan v3 amendment 4); for a guest caller the
437
- // entering set is the leaf alone and the release is a no-op.
438
- this.inst.releaseSyntheticRootOnPoison();
299
+ // mark this RUNTIME incomplete, not the component faulted, and in the
300
+ // reference the blocking operation they stand in for completes
301
+ // normally.
302
+ if (!(e instanceof NeedsJspi) && !(e instanceof PendingCapability)) {
439
303
  notifyInstancePoisoned(this.inst, e);
440
304
  }
441
305
  throw e;
442
306
  }
443
- this.inst.leaveTo(caller);
444
307
  }
445
308
  else {
446
309
  this.state = "pending-cancel";