@polyengine/runtime 0.1.0-pre.g633468a

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 (126) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +23 -0
  3. package/esm/cabi/async_values.js +162 -0
  4. package/esm/cabi/bulk_lists.js +198 -0
  5. package/esm/cabi/context.js +42 -0
  6. package/esm/cabi/flatten.js +145 -0
  7. package/esm/cabi/float.js +67 -0
  8. package/esm/cabi/handles.js +253 -0
  9. package/esm/cabi/layout.js +149 -0
  10. package/esm/cabi/lift.js +196 -0
  11. package/esm/cabi/load.js +146 -0
  12. package/esm/cabi/lower.js +141 -0
  13. package/esm/cabi/memory.js +182 -0
  14. package/esm/cabi/mod.js +22 -0
  15. package/esm/cabi/store.js +186 -0
  16. package/esm/cabi/strings.js +336 -0
  17. package/esm/cabi/trap.js +38 -0
  18. package/esm/cabi/types.js +264 -0
  19. package/esm/cabi/values.js +64 -0
  20. package/esm/cache/core.js +156 -0
  21. package/esm/cache/dir.js +170 -0
  22. package/esm/cache/mod.js +4 -0
  23. package/esm/cache/web.js +136 -0
  24. package/esm/digest/digest.js +332 -0
  25. package/esm/digest/mod.js +3 -0
  26. package/esm/digest/verify.js +129 -0
  27. package/esm/embedder/casing.js +56 -0
  28. package/esm/embedder/copy.js +42 -0
  29. package/esm/embedder/errors.js +26 -0
  30. package/esm/embedder/imports.js +63 -0
  31. package/esm/embedder/instantiate.js +978 -0
  32. package/esm/embedder/mod.js +40 -0
  33. package/esm/embedder/resources.js +406 -0
  34. package/esm/embedder/streams.js +770 -0
  35. package/esm/embedder/values.js +450 -0
  36. package/esm/embedder/version.js +273 -0
  37. package/esm/exec/boundary.js +1902 -0
  38. package/esm/exec/executor.js +1004 -0
  39. package/esm/exec/host_streams.js +818 -0
  40. package/esm/exec/mod.js +4 -0
  41. package/esm/intrinsics/async_builtins.js +510 -0
  42. package/esm/intrinsics/context.js +90 -0
  43. package/esm/intrinsics/errors.js +13 -0
  44. package/esm/intrinsics/fact_calls.js +865 -0
  45. package/esm/intrinsics/mod.js +564 -0
  46. package/esm/intrinsics/stream_builtins.js +578 -0
  47. package/esm/intrinsics/transcode.js +443 -0
  48. package/esm/jspi/bridge.js +579 -0
  49. package/esm/jspi/mechanics.js +89 -0
  50. package/esm/jspi/mod.js +5 -0
  51. package/esm/jspi/suspending.js +15 -0
  52. package/esm/jspi/types.js +29 -0
  53. package/esm/package.json +3 -0
  54. package/esm/plan/format.js +5 -0
  55. package/esm/plan/loader.js +657 -0
  56. package/esm/plan/mod.js +3 -0
  57. package/esm/shim/mod.js +2 -0
  58. package/esm/shim/translator.js +94 -0
  59. package/esm/task/mod.js +483 -0
  60. package/esm/task/scheduler.js +1028 -0
  61. package/esm/task/streams.js +786 -0
  62. package/esm/task/subtask.js +135 -0
  63. package/esm/task/thread.js +255 -0
  64. package/esm/task/waitable.js +144 -0
  65. package/package.json +91 -0
  66. package/types/cabi/async_values.d.ts +35 -0
  67. package/types/cabi/bulk_lists.d.ts +18 -0
  68. package/types/cabi/context.d.ts +59 -0
  69. package/types/cabi/flatten.d.ts +14 -0
  70. package/types/cabi/float.d.ts +14 -0
  71. package/types/cabi/handles.d.ts +70 -0
  72. package/types/cabi/layout.d.ts +13 -0
  73. package/types/cabi/lift.d.ts +25 -0
  74. package/types/cabi/load.d.ts +14 -0
  75. package/types/cabi/lower.d.ts +10 -0
  76. package/types/cabi/memory.d.ts +58 -0
  77. package/types/cabi/mod.d.ts +15 -0
  78. package/types/cabi/store.d.ts +12 -0
  79. package/types/cabi/strings.d.ts +23 -0
  80. package/types/cabi/trap.d.ts +11 -0
  81. package/types/cabi/types.d.ts +206 -0
  82. package/types/cabi/values.d.ts +5 -0
  83. package/types/cache/core.d.ts +97 -0
  84. package/types/cache/dir.d.ts +6 -0
  85. package/types/cache/mod.d.ts +3 -0
  86. package/types/cache/web.d.ts +10 -0
  87. package/types/digest/digest.d.ts +17 -0
  88. package/types/digest/mod.d.ts +2 -0
  89. package/types/digest/verify.d.ts +48 -0
  90. package/types/embedder/casing.d.ts +40 -0
  91. package/types/embedder/copy.d.ts +24 -0
  92. package/types/embedder/errors.d.ts +11 -0
  93. package/types/embedder/imports.d.ts +47 -0
  94. package/types/embedder/instantiate.d.ts +88 -0
  95. package/types/embedder/mod.d.ts +11 -0
  96. package/types/embedder/resources.d.ts +158 -0
  97. package/types/embedder/streams.d.ts +202 -0
  98. package/types/embedder/values.d.ts +70 -0
  99. package/types/embedder/version.d.ts +85 -0
  100. package/types/exec/boundary.d.ts +360 -0
  101. package/types/exec/executor.d.ts +125 -0
  102. package/types/exec/host_streams.d.ts +165 -0
  103. package/types/exec/mod.d.ts +3 -0
  104. package/types/intrinsics/async_builtins.d.ts +69 -0
  105. package/types/intrinsics/context.d.ts +28 -0
  106. package/types/intrinsics/errors.d.ts +5 -0
  107. package/types/intrinsics/fact_calls.d.ts +120 -0
  108. package/types/intrinsics/mod.d.ts +187 -0
  109. package/types/intrinsics/stream_builtins.d.ts +113 -0
  110. package/types/intrinsics/transcode.d.ts +21 -0
  111. package/types/jspi/bridge.d.ts +227 -0
  112. package/types/jspi/mechanics.d.ts +50 -0
  113. package/types/jspi/mod.d.ts +3 -0
  114. package/types/jspi/suspending.d.ts +1 -0
  115. package/types/jspi/types.d.ts +26 -0
  116. package/types/plan/format.d.ts +369 -0
  117. package/types/plan/loader.d.ts +113 -0
  118. package/types/plan/mod.d.ts +2 -0
  119. package/types/shim/mod.d.ts +1 -0
  120. package/types/shim/translator.d.ts +55 -0
  121. package/types/task/mod.d.ts +257 -0
  122. package/types/task/scheduler.d.ts +421 -0
  123. package/types/task/streams.d.ts +370 -0
  124. package/types/task/subtask.d.ts +96 -0
  125. package/types/task/thread.d.ts +73 -0
  126. package/types/task/waitable.d.ts +67 -0
@@ -0,0 +1,135 @@
1
+ // definitions.py `### Subtask State` (line 858): one in-progress call from
2
+ // this component to an import.
3
+ import { assert_, trapIf } from "../cabi/trap.js";
4
+ import { EventCode, Waitable } from "./waitable.js";
5
+ /** definitions.py `Subtask.State` (line 859). */
6
+ export var SubtaskState;
7
+ (function (SubtaskState) {
8
+ SubtaskState[SubtaskState["STARTING"] = 0] = "STARTING";
9
+ SubtaskState[SubtaskState["STARTED"] = 1] = "STARTED";
10
+ SubtaskState[SubtaskState["RETURNED"] = 2] = "RETURNED";
11
+ SubtaskState[SubtaskState["CANCELLED_BEFORE_STARTED"] = 3] = "CANCELLED_BEFORE_STARTED";
12
+ SubtaskState[SubtaskState["CANCELLED_BEFORE_RETURNED"] = 4] = "CANCELLED_BEFORE_RETURNED";
13
+ })(SubtaskState || (SubtaskState = {}));
14
+ export class Subtask extends Waitable {
15
+ state = SubtaskState.STARTING;
16
+ onCancel = null;
17
+ cancellationRequested = false;
18
+ flatResults = [];
19
+ /**
20
+ * The callee TASK behind this subtask, when there is one (FACT
21
+ * cross-component calls; host-import subtasks have none). `subtask.cancel`
22
+ * needs it under jspi: a cancellation delivered to a suspended activation
23
+ * resumes it on a MICROTASK (the engine's, not ours), so the async form
24
+ * must wait until the callee's state is determinate before choosing
25
+ * between BLOCKED and the resolved state — the same determinacy question
26
+ * `async-start-call` answers, and it needs the same object to ask it of.
27
+ */
28
+ // deno-lint-ignore no-explicit-any
29
+ calleeTask = null;
30
+ /**
31
+ * Handles lent to the callee for the duration of the call. `null` once
32
+ * `deliverResolve` has run — the reference uses exactly this
33
+ * `lenders is None` sentinel to mean "resolve delivered" (line 908), so the
34
+ * nullability is semantic, not an optimization.
35
+ */
36
+ lenders = [];
37
+ /** definitions.py `Subtask.resolved` (line 880). */
38
+ resolved() {
39
+ switch (this.state) {
40
+ case SubtaskState.STARTING:
41
+ case SubtaskState.STARTED:
42
+ return false;
43
+ default:
44
+ return true;
45
+ }
46
+ }
47
+ /** definitions.py `Subtask.add_lender` (line 890). */
48
+ addLender(h) {
49
+ assert_(!this.resolveDelivered() && !this.resolved(), "addLender on a resolved subtask");
50
+ h.numLends += 1;
51
+ this.lenders.push(h);
52
+ }
53
+ /** definitions.py `Subtask.resolve` (line 895). */
54
+ resolve(state, flatResults) {
55
+ assert_(state === SubtaskState.RETURNED || flatResults.length === 0, "non-RETURNED subtask resolution carries results");
56
+ assert_(!this.resolved(), "resolve on an already-resolved subtask");
57
+ this.state = state;
58
+ this.flatResults = flatResults;
59
+ }
60
+ /** definitions.py `Subtask.deliver_resolve` (line 902). */
61
+ deliverResolve() {
62
+ assert_(!this.resolveDelivered() && this.resolved(), "deliverResolve on an unresolved or already-delivered subtask");
63
+ for (const h of this.lenders)
64
+ h.numLends -= 1;
65
+ this.lenders = null;
66
+ }
67
+ /** definitions.py `Subtask.resolve_delivered` (line 908). */
68
+ resolveDelivered() {
69
+ assert_(this.lenders !== null || this.resolved(), "lenders released on an unresolved subtask");
70
+ return this.lenders === null;
71
+ }
72
+ /**
73
+ * Release a never-delivered subtask's lenders after its call broke off a
74
+ * non-poisoning exit — trap-rethrow past the CALLEE, capability bail, or
75
+ * an abandoned park (contracts/intrinsics.md v0.2 amendment 2, #91 scope
76
+ * clarification; the park legs are #102/#106).
77
+ *
78
+ * The reference has no analogue because it never resumes after a trap:
79
+ * the store dies with the lent handles inside it. The resolution state
80
+ * mirrors `canon_lower`'s `on_resolve(None)` branch (definitions.py
81
+ * line 2267): CANCELLED_BEFORE_STARTED if the callee never started,
82
+ * CANCELLED_BEFORE_RETURNED otherwise.
83
+ *
84
+ * Idempotent, and a no-op when the resolution was already delivered — a
85
+ * settled hook can call it unconditionally without disturbing the success
86
+ * path's own `deliverResolve`.
87
+ */
88
+ unwindLenders() {
89
+ if (!this.resolved()) {
90
+ this.resolve(this.state === SubtaskState.STARTING
91
+ ? SubtaskState.CANCELLED_BEFORE_STARTED
92
+ : SubtaskState.CANCELLED_BEFORE_RETURNED, []);
93
+ }
94
+ if (!this.resolveDelivered())
95
+ this.deliverResolve();
96
+ }
97
+ /** definitions.py `Subtask.drop` (line 912). */
98
+ drop() {
99
+ trapIf(!this.resolveDelivered(), "cannot drop a subtask which has not yet resolved");
100
+ super.drop();
101
+ }
102
+ /**
103
+ * definitions.py `canon_lower`'s `on_progress`/`subtask_event` closure
104
+ * (lines 2297-2298). The event payload is computed **at delivery time** and
105
+ * delivering it is what runs `deliver_resolve` — so the lent handles are
106
+ * released exactly when the guest observes the resolution, not when it
107
+ * happens.
108
+ *
109
+ * The `!this.resolveDelivered()` guard has no reference analogue: it exists
110
+ * so this can coexist with `unwindLenders()` (which may itself have already
111
+ * delivered the resolve on an abandoned path). The reference's
112
+ * `subtask_event` calls `deliver_resolve()` unconditionally and would
113
+ * assert on a double delivery.
114
+ */
115
+ setSubtaskPendingEvent(subtaski) {
116
+ this.setPendingEvent(() => {
117
+ if (this.resolved() && !this.resolveDelivered())
118
+ this.deliverResolve();
119
+ return [EventCode.SUBTASK, subtaski, this.state];
120
+ });
121
+ }
122
+ }
123
+ /**
124
+ * Pack a `canon_lower` async return value: `state | (subtaski << 4)`
125
+ * (definitions.py line 2306, with the accompanying asserts on the ranges).
126
+ */
127
+ export function packSubtaskResult(state, subtaski) {
128
+ assert_(subtaski > 0 && subtaski <= 2 ** 28 - 1, "subtask index out of packing range");
129
+ assert_(state >= 0 && state < 2 ** 4, "subtask state out of packing range");
130
+ return (state | (subtaski << 4)) >>> 0;
131
+ }
132
+ /** Inverse of {@link packSubtaskResult}; used by tests mirroring the reference. */
133
+ export function unpackSubtaskResult(packed) {
134
+ return [(packed & 0xf), packed >>> 4];
135
+ }
@@ -0,0 +1,255 @@
1
+ // definitions.py `class Thread` (line 317), reimplemented over JS generators.
2
+ //
3
+ // Mapping to the reference, state for state:
4
+ //
5
+ // reference here
6
+ // ------------------------------------------------------------------
7
+ // cont is None (running) #state === "running"
8
+ // cont set, ready_func None #state === "suspended"
9
+ // cont set, ready_func set #state === "waiting" (in store.waiting)
10
+ // thread.storage[2] storage: [0, 0] (context.{get,set})
11
+ // thread.index index (inst.threads table slot)
12
+ //
13
+ // The reference's `resume()` drives a chain of `switch_to` handoffs
14
+ // (`suspend_then_resume` and friends, lines 408-437). Those are the 🧵
15
+ // shared-everything-threads built-ins, which https://github.com/polymorph-components/polyengine/issues/12 defers along with
16
+ // memory64; `resume()` here therefore handles a single thread, and the
17
+ // switch-to variants are absent rather than approximated.
18
+ import { assert_ } from "../cabi/trap.js";
19
+ import { CANCELLED_FALSE, CANCELLED_TRUE, NeedsJspi, notifyInstancePoisoned, isInstancePoisoned, PendingCapability, popCurrentThread, pushCurrentThread, } from "./scheduler.js";
20
+ export class Thread {
21
+ task;
22
+ /**
23
+ * Per-thread context slots (definitions.py `Thread.storage`, line 323 —
24
+ * initialised `[0,0]`). `canon_context_{get,set}` (lines 2348/2358) read and
25
+ * write *this*, not per-task state: two threads of the same task have
26
+ * independent context. wit-bindgen 0.60 keeps its async task pointer in
27
+ * slot 0.
28
+ *
29
+ * Slots are plain JS numbers: a `context.set` of an i64 value above
30
+ * 2^53-1 would lose precision. Moot while memory64/threads support is
31
+ * deferred (issue #12) — revisit this when that issue's closure lands.
32
+ */
33
+ storage = [0, 0];
34
+ /**
35
+ * The FACT sync-call bracket stack for THIS activation.
36
+ *
37
+ * `enter-sync-call` pushes and `exit-sync-call` pops; FACT emits both from
38
+ * the same activation, so the activation is the continuity that makes this a
39
+ * stack. See the note on `Task.syncCallStack` for why per-task was not
40
+ * enough.
41
+ */
42
+ // deno-lint-ignore no-explicit-any
43
+ syncCallStack = [];
44
+ /** Slot in `inst.threads`, assigned by `Task.registerThread`. */
45
+ index = null;
46
+ /** definitions.py `Thread.cancellable` — set at each block point. */
47
+ cancellable = false;
48
+ #state = "suspended";
49
+ #body;
50
+ #readyFunc = null;
51
+ #store;
52
+ // deno-lint-ignore no-explicit-any
53
+ constructor(task, body) {
54
+ this.task = task;
55
+ this.#body = body;
56
+ this.#store = task.inst.store;
57
+ }
58
+ running() {
59
+ return this.#state === "running";
60
+ }
61
+ suspended() {
62
+ return this.#state === "suspended";
63
+ }
64
+ waiting() {
65
+ return this.#state === "waiting";
66
+ }
67
+ done() {
68
+ return this.#state === "done";
69
+ }
70
+ /** definitions.py `Thread.ready` (line 334). */
71
+ ready() {
72
+ return this.waiting() && this.#readyFunc !== null && this.#readyFunc();
73
+ }
74
+ /** definitions.py `Thread.start_waiting_internal` (line 350). */
75
+ #startWaiting(readyFunc) {
76
+ assert_(!this.waiting() && this.#readyFunc === null);
77
+ this.#readyFunc = readyFunc;
78
+ this.#state = "waiting";
79
+ this.#store.startWaiting(this);
80
+ }
81
+ /** definitions.py `Thread.stop_waiting_internal` (line 355). */
82
+ #stopWaiting(cancelled) {
83
+ assert_(this.waiting() && this.#readyFunc !== null);
84
+ assert_(cancelled || this.ready(), "stopWaiting on a thread that is neither ready nor cancelled");
85
+ this.#readyFunc = null;
86
+ this.#state = "suspended";
87
+ this.#store.stopWaiting(this);
88
+ }
89
+ /** definitions.py `Thread.resume_later` (line 361). */
90
+ resumeLater() {
91
+ assert_(this.suspended(), "resume_later on a non-suspended thread");
92
+ this.#startWaiting(() => true);
93
+ }
94
+ /**
95
+ * definitions.py `Thread.resume` (line 366): run the body until it blocks
96
+ * again or finishes.
97
+ *
98
+ * The reference's loop over `switch_to` targets is omitted (see the module
99
+ * header). What remains is: leave the waiting list if we were on it, become
100
+ * the current thread, and step the generator with the cancelled flag.
101
+ */
102
+ /** Pending `awaitValue` promise, if this thread is parked on one. */
103
+ awaiting = null;
104
+ /** Resume a promise-parked thread with the settled result. */
105
+ resumeWith(value, failure) {
106
+ assert_(this.awaiting !== null, "resumeWith on a thread that is not awaiting");
107
+ this.awaiting = null;
108
+ this.#store.awaiting.delete(this);
109
+ this.#state = "suspended";
110
+ // The reentrance bracket, exactly as `Store.tick` puts around `resume()`.
111
+ //
112
+ // Every thread resumption in the reference runs under the instance's
113
+ // entered lock, and a trap propagating out of the resumed thread skips
114
+ // `leave_to` — which is the Component Model's instance poisoning
115
+ // (definitions.py `Store.tick` line 597; see the matching comment in
116
+ // scheduler.ts). This path is a resumption too — the value arrived
117
+ // through a Promise instead of a ready-condition, but the thread body
118
+ // (and any wasm it runs) is the same — so it takes the same bracket.
119
+ // Without it, a trap delivered as an `awaitValue` rejection (how EVERY
120
+ // guest trap in a suspended activation arrives under jspi, pin (e))
121
+ // unwound cleanly and the instance stayed enterable: the second call of
122
+ // `builtin-trap-poisons-instance.wast` then re-ran the guest and
123
+ // reported "cannot drop busy stream" where the suite demands the
124
+ // poisoned-instance "cannot enter component instance".
125
+ //
126
+ // Capability signals release the lock, for the same reason as in `tick`:
127
+ // they mark the RUNTIME incomplete, not the component faulted.
128
+ const inst = this.task.inst;
129
+ // A poisoned instance's parked segments never run again: this settle
130
+ // belongs to an activation that was in flight when a SIBLING activation
131
+ // trapped (the trap kept the reentrance lock — CM poisoning — and #66
132
+ // retired the handle tables). Resuming would re-enter the corpse, and
133
+ // asserting turned one legible trap into an assert cascade (the
134
+ // wosh-M2 shape: `list too long`, then this assert as second victim).
135
+ // Retire quietly: the abandoned call's own driver reports, via its
136
+ // deadlock trap naming the export.
137
+ if (isInstancePoisoned(inst))
138
+ return;
139
+ // The enterability check below is an internal BACKSTOP, not a live gate:
140
+ // every dispatch site (`Store.serviceSettled`, `driveAsync`'s race-winner
141
+ // path) now guards enterable-or-poisoned before calling and DEFERS the
142
+ // tail otherwise (issue #156) — under the shared synthetic root, a host
143
+ // entry into any instance of the graph makes every sibling
144
+ // non-enterable, so this assert was reachable, and (mutating before
145
+ // asserting) it stranded the thread and lost the settle. It stays to
146
+ // protect the invariant for any future caller.
147
+ assert_(inst.mayEnterFrom(null), "resumeWith: parked thread's instance is not enterable from the host");
148
+ inst.enterFrom(null);
149
+ try {
150
+ this.#resumeInternal(value, failure);
151
+ }
152
+ catch (e) {
153
+ if (e instanceof NeedsJspi || e instanceof PendingCapability) {
154
+ inst.leaveTo(null);
155
+ }
156
+ else {
157
+ // The bracket stays broken (instance poisoned, comment above) — same
158
+ // as `Store.tick`: retire the poisoned table's stream/future ends so
159
+ // parked host peers settle instead of hanging (#66), and release the
160
+ // synthetic root so the poisoning stays per-instance (plan v3
161
+ // amendment 4; `releaseSyntheticRootOnPoison`).
162
+ inst.releaseSyntheticRootOnPoison();
163
+ notifyInstancePoisoned(inst, e);
164
+ }
165
+ throw e;
166
+ }
167
+ inst.leaveTo(null);
168
+ }
169
+ resume(cancelled = CANCELLED_FALSE) {
170
+ assert_(!this.running() && !this.done(), "resume() on a running or finished thread");
171
+ assert_(this.cancellable || !cancelled, "cancelled resume of a non-cancellable block point");
172
+ if (this.waiting())
173
+ this.#stopWaiting(cancelled);
174
+ this.#resumeInternal(cancelled);
175
+ }
176
+ #resumeInternal(sendValue, failure) {
177
+ this.#state = "running";
178
+ pushCurrentThread(this);
179
+ let step;
180
+ try {
181
+ step = failure === undefined
182
+ ? this.#body.next(sendValue)
183
+ // Throw the rejection *into* the body so a post-resume trap unwinds
184
+ // through the same `finally`s a synchronous one would (jspi pin (e)).
185
+ : this.#body.throw(failure.error);
186
+ }
187
+ catch (e) {
188
+ // The body threw (a trap, or one of our capability errors). The thread
189
+ // is finished either way; the exception propagates to whoever was
190
+ // driving the scheduler.
191
+ this.#state = "done";
192
+ throw e;
193
+ }
194
+ finally {
195
+ popCurrentThread(this);
196
+ }
197
+ if (step.done) {
198
+ this.#state = "done";
199
+ return;
200
+ }
201
+ const req = step.value;
202
+ this.cancellable = req.cancellable;
203
+ if (req.awaitValue !== undefined) {
204
+ // Parked on a Promise, not on a scheduler condition. The driving loop
205
+ // owns it from here (exec/boundary.ts `drive`); parking through
206
+ // `noteAwaiting` arms the eager settle tracking the scheduler's
207
+ // phantom-state gate depends on (see `Store.settled`).
208
+ this.#state = "suspended";
209
+ this.awaiting = req.awaitValue;
210
+ this.#store.noteAwaiting(this, req.awaitValue);
211
+ return;
212
+ }
213
+ if (req.readyFunc === null) {
214
+ // `suspend`: resumable only by an explicit `resume`/`resumeLater`.
215
+ this.#state = "suspended";
216
+ }
217
+ else {
218
+ this.#state = "suspended";
219
+ this.#startWaiting(req.readyFunc);
220
+ }
221
+ }
222
+ /**
223
+ * definitions.py `Thread.wait_until` (line 396), as a generator-side helper.
224
+ *
225
+ * Call it from a thread body with `yield*`:
226
+ * `const cancelled = yield* thread.waitUntil(() => cond, true);`
227
+ *
228
+ * Deviation from the reference, deliberate: the reference may return
229
+ * immediately when `ready_func()` already holds
230
+ * (`if ready_func() and not DETERMINISTIC_PROFILE and random.randint(0,1)`).
231
+ * We always take the blocking path, i.e. we behave as the reference's
232
+ * `DETERMINISTIC_PROFILE`. Blocking-then-immediately-ready is observably
233
+ * equivalent (the scheduler will find this thread ready on the next
234
+ * candidate scan) and it removes a coin flip from every wait.
235
+ */
236
+ *waitUntil(readyFunc, cancellable = false) {
237
+ assert_(this.running(), "waitUntil on a non-running thread");
238
+ if (this.task.deliverPendingCancel(cancellable))
239
+ return CANCELLED_TRUE;
240
+ const cancelled = yield { readyFunc, cancellable };
241
+ return cancelled;
242
+ }
243
+ /** definitions.py `Thread.suspend` (line 390). */
244
+ *suspend(cancellable) {
245
+ assert_(this.running(), "suspend on a non-running thread");
246
+ if (this.task.deliverPendingCancel(cancellable))
247
+ return CANCELLED_TRUE;
248
+ const cancelled = yield { readyFunc: null, cancellable };
249
+ return cancelled;
250
+ }
251
+ /** definitions.py `Thread.yield_` (line 405): `wait_until(lambda: True)`. */
252
+ *yield_(cancellable) {
253
+ return yield* this.waitUntil(() => true, cancellable);
254
+ }
255
+ }
@@ -0,0 +1,144 @@
1
+ // definitions.py `### Waitable State` (line 754) — the event protocol shared
2
+ // by subtasks and (later) stream/future ends.
3
+ import { assert_, trapIf } from "../cabi/trap.js";
4
+ import { chooseCandidate } from "./scheduler.js";
5
+ /** definitions.py `EventCode` (line 756). */
6
+ export var EventCode;
7
+ (function (EventCode) {
8
+ EventCode[EventCode["NONE"] = 0] = "NONE";
9
+ EventCode[EventCode["SUBTASK"] = 1] = "SUBTASK";
10
+ EventCode[EventCode["STREAM_READ"] = 2] = "STREAM_READ";
11
+ EventCode[EventCode["STREAM_WRITE"] = 3] = "STREAM_WRITE";
12
+ EventCode[EventCode["FUTURE_READ"] = 4] = "FUTURE_READ";
13
+ EventCode[EventCode["FUTURE_WRITE"] = 5] = "FUTURE_WRITE";
14
+ EventCode[EventCode["TASK_CANCELLED"] = 6] = "TASK_CANCELLED";
15
+ })(EventCode || (EventCode = {}));
16
+ export const NO_EVENT = [EventCode.NONE, 0, 0];
17
+ /**
18
+ * definitions.py `class Waitable` (line 767).
19
+ *
20
+ * The pending event is a **thunk**, not a value: the reference computes the
21
+ * payload at delivery time (`get_pending_event` calls it), which is what lets
22
+ * a subtask report its *final* state even if it advanced between the event
23
+ * being set and being read. Keeping the thunk is load-bearing — see
24
+ * `Subtask.setPendingEvent`.
25
+ */
26
+ export class Waitable {
27
+ pendingEvent = null;
28
+ wset = null;
29
+ hasSyncWaiter = false;
30
+ setPendingEvent(pendingEvent) {
31
+ this.pendingEvent = pendingEvent;
32
+ }
33
+ hasPendingEvent() {
34
+ return this.pendingEvent !== null;
35
+ }
36
+ inWaitableSet() {
37
+ return this.wset !== null;
38
+ }
39
+ /**
40
+ * definitions.py `Waitable.wait_for_pending_event` (line 786): a
41
+ * *non-cancellable* block until this waitable has an event, used by the
42
+ * synchronous `subtask.cancel` path.
43
+ */
44
+ *waitForPendingEvent(thread) {
45
+ assert_(!this.inWaitableSet() && !this.hasSyncWaiter, "waitForPendingEvent on a joined or already-awaited waitable");
46
+ this.hasSyncWaiter = true;
47
+ yield* thread.waitUntil(() => this.hasPendingEvent(), false);
48
+ this.hasSyncWaiter = false;
49
+ }
50
+ getPendingEvent() {
51
+ const pendingEvent = this.pendingEvent;
52
+ assert_(pendingEvent !== null, "getPendingEvent with no pending event");
53
+ this.pendingEvent = null;
54
+ return pendingEvent();
55
+ }
56
+ /** definitions.py `Waitable.join` (line 797). */
57
+ join(wset) {
58
+ assert_(!this.hasSyncWaiter, "join on a waitable with a sync waiter");
59
+ if (this.wset) {
60
+ const i = this.wset.elems.indexOf(this);
61
+ assert_(i !== -1, "waitable not in its own waitable set");
62
+ this.wset.elems.splice(i, 1);
63
+ }
64
+ this.wset = wset;
65
+ if (wset)
66
+ wset.elems.push(this);
67
+ }
68
+ /** definitions.py `Waitable.drop` (line 805). */
69
+ drop() {
70
+ assert_(!this.hasPendingEvent(), "dropping a waitable with a pending event");
71
+ assert_(!this.hasSyncWaiter, "dropping a waitable with a sync waiter");
72
+ this.join(null);
73
+ }
74
+ }
75
+ const EV_TRACE = (() => {
76
+ try {
77
+ return Deno.env.get("CE_EVENT_TRACE") === "1";
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ })();
83
+ /** definitions.py `class WaitableSet` (line 810). */
84
+ export class WaitableSet {
85
+ elems = [];
86
+ numWaiting = 0;
87
+ hasPendingEvent() {
88
+ return this.elems.some((w) => w.hasPendingEvent());
89
+ }
90
+ /**
91
+ * definitions.py `WaitableSet.get_pending_event` (line 821). The reference
92
+ * shuffles `elems` before scanning; we scan in **join order** under the
93
+ * default FIFO policy (`chooseCandidate` over the ready elements), which is
94
+ * within the same allowed nondeterminism — see scheduler.ts's policy note.
95
+ */
96
+ getPendingEvent() {
97
+ const ready = this.elems.filter((w) => w.hasPendingEvent());
98
+ assert_(ready.length > 0, "getPendingEvent on a set with no pending event");
99
+ const w = chooseCandidate(ready);
100
+ assert_(w.wset === this, "waitable/waitable-set back-reference mismatch");
101
+ const ev = w.getPendingEvent();
102
+ if (EV_TRACE) {
103
+ console.error(`[event] deliver code=${ev[0]} idx=${ev[1]} payload=${ev[2]} ` +
104
+ `readyCount=${ready.length} setSize=${this.elems.length} ` +
105
+ `chosenPos=${this.elems.indexOf(w)}`);
106
+ }
107
+ return ev;
108
+ }
109
+ /** definitions.py `WaitableSet.wait_for_event_and` (line 829). */
110
+ *waitForEventAnd(thread, readyFunc, cancellable) {
111
+ this.numWaiting += 1;
112
+ try {
113
+ const cancelled = yield* thread.waitUntil(() => readyFunc() && this.hasPendingEvent(), cancellable);
114
+ return cancelled
115
+ ? [EventCode.TASK_CANCELLED, 0, 0]
116
+ : this.getPendingEvent();
117
+ }
118
+ finally {
119
+ this.numWaiting -= 1;
120
+ }
121
+ }
122
+ /** definitions.py `WaitableSet.wait_for_event` (line 841). */
123
+ *waitForEvent(thread, cancellable) {
124
+ return yield* this.waitForEventAnd(thread, () => true, cancellable);
125
+ }
126
+ /**
127
+ * definitions.py `WaitableSet.poll` (line 844). Never blocks, so it is a
128
+ * plain function rather than a generator.
129
+ */
130
+ // deno-lint-ignore no-explicit-any
131
+ poll(task, cancellable) {
132
+ if (task.deliverPendingCancel(cancellable)) {
133
+ return [EventCode.TASK_CANCELLED, 0, 0];
134
+ }
135
+ if (!this.hasPendingEvent())
136
+ return [EventCode.NONE, 0, 0];
137
+ return this.getPendingEvent();
138
+ }
139
+ /** definitions.py `WaitableSet.drop` (line 852). */
140
+ drop() {
141
+ trapIf(this.elems.length > 0, "cannot drop waitable set with waitables");
142
+ trapIf(this.numWaiting > 0, "cannot drop waitable set with waiters");
143
+ }
144
+ }
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@polyengine/runtime",
3
+ "version": "0.1.0-pre.g633468a",
4
+ "description": "A WebAssembly Component Model host for JavaScript engines: plan executor, canonical ABI, 0.3 task scheduler, JSPI bridge, and embedder API.",
5
+ "homepage": "https://github.com/polymorph-components/polyengine#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/polymorph-components/polyengine.git"
9
+ },
10
+ "license": "Apache-2.0",
11
+ "bugs": {
12
+ "url": "https://github.com/polymorph-components/polyengine/issues"
13
+ },
14
+ "module": "./esm/cabi/mod.js",
15
+ "types": "./types/cabi/mod.d.ts",
16
+ "exports": {
17
+ "./cabi": {
18
+ "import": {
19
+ "types": "./types/cabi/mod.d.ts",
20
+ "default": "./esm/cabi/mod.js"
21
+ }
22
+ },
23
+ "./cache": {
24
+ "import": {
25
+ "types": "./types/cache/mod.d.ts",
26
+ "default": "./esm/cache/mod.js"
27
+ }
28
+ },
29
+ "./plan": {
30
+ "import": {
31
+ "types": "./types/plan/mod.d.ts",
32
+ "default": "./esm/plan/mod.js"
33
+ }
34
+ },
35
+ "./task": {
36
+ "import": {
37
+ "types": "./types/task/mod.d.ts",
38
+ "default": "./esm/task/mod.js"
39
+ }
40
+ },
41
+ "./exec": {
42
+ "import": {
43
+ "types": "./types/exec/mod.d.ts",
44
+ "default": "./esm/exec/mod.js"
45
+ }
46
+ },
47
+ "./embedder": {
48
+ "import": {
49
+ "types": "./types/embedder/mod.d.ts",
50
+ "default": "./esm/embedder/mod.js"
51
+ }
52
+ },
53
+ "./digest": {
54
+ "import": {
55
+ "types": "./types/digest/mod.d.ts",
56
+ "default": "./esm/digest/mod.js"
57
+ }
58
+ },
59
+ "./intrinsics": {
60
+ "import": {
61
+ "types": "./types/intrinsics/mod.d.ts",
62
+ "default": "./esm/intrinsics/mod.js"
63
+ }
64
+ },
65
+ "./shim": {
66
+ "import": {
67
+ "types": "./types/shim/mod.d.ts",
68
+ "default": "./esm/shim/mod.js"
69
+ }
70
+ },
71
+ "./jspi": {
72
+ "import": {
73
+ "types": "./types/jspi/mod.d.ts",
74
+ "default": "./esm/jspi/mod.js"
75
+ }
76
+ },
77
+ "./package.json": "./package.json"
78
+ },
79
+ "scripts": {},
80
+ "type": "module",
81
+ "engines": {
82
+ "node": ">=22.14.0"
83
+ },
84
+ "publishConfig": {
85
+ "access": "public"
86
+ },
87
+ "dependencies": {
88
+ "@polyengine/protocol": "0.1.0-pre.g633468a"
89
+ },
90
+ "_generatedBy": "dnt@0.43.2"
91
+ }
@@ -0,0 +1,35 @@
1
+ import type { LiftLowerContext } from "./context.js";
2
+ import type { BorrowType, OwnType, ValType } from "./types.js";
3
+ import { ErrorContext, type SharedBase } from "../task/streams.js";
4
+ /**
5
+ * Diagnostic for a handle-table entry that carries the A9 error-context brand
6
+ * without being one of THIS copy's `ErrorContext`s (amendment A9, issue #83).
7
+ *
8
+ * A backstop, deliberately: the embedder's lowering site (embedder/values.ts)
9
+ * refuses a foreign error-context before it can ever reach a handle table, so
10
+ * this branch should be unreachable. It exists because "handle is not an
11
+ * error-context" is precisely the misleading generic that made #83 expensive
12
+ * to diagnose — if a path ever does get here, it says what happened.
13
+ *
14
+ * This layer is below `embedder/`, so it composes the census from
15
+ * `@polyengine/protocol` directly rather than importing `embedder/copy.ts`.
16
+ */
17
+ export declare function errorContextTrapMessage(where: string, e: unknown): string;
18
+ export declare function liftStream(cx: LiftLowerContext, i: number, t: ValType & {
19
+ kind: "stream";
20
+ element: ValType | null;
21
+ }): SharedBase;
22
+ export declare function liftFuture(cx: LiftLowerContext, i: number, t: ValType & {
23
+ kind: "future";
24
+ element: ValType | null;
25
+ }): SharedBase;
26
+ /** definitions.py `lower_stream` (line 1828). */
27
+ export declare function lowerStream(cx: LiftLowerContext, v: SharedBase, t: ValType): number;
28
+ /** definitions.py `lower_future` (line 1833). */
29
+ export declare function lowerFuture(cx: LiftLowerContext, v: SharedBase, t: ValType): number;
30
+ /** definitions.py `lift_error_context` (line 1451). Does NOT remove the handle. */
31
+ export declare function liftErrorContext(cx: LiftLowerContext, i: number): ErrorContext;
32
+ /** definitions.py `lower_error_context` (line 1757). */
33
+ export declare function lowerErrorContext(cx: LiftLowerContext, v: ErrorContext): number;
34
+ /** Unused-import guards for the type-only imports above. */
35
+ export type _AsyncValueTypes = OwnType | BorrowType;
@@ -0,0 +1,18 @@
1
+ import { type MemInst } from "./memory.js";
2
+ import type { ComponentValue } from "./types.js";
3
+ export declare const PLATFORM_LITTLE_ENDIAN: boolean;
4
+ /** Kinds these helpers handle (besides them, u8 has its own path). */
5
+ export declare function isBulkListKind(kind: string): boolean;
6
+ /**
7
+ * Bulk lift of `length` elements of `kind` at `ptr`. Returns `null` when the
8
+ * kind is not handled here (caller falls back to the per-element loop) —
9
+ * never for a handled kind on a little-endian platform with an aligned view.
10
+ * The caller has already trap-checked alignment and bounds.
11
+ */
12
+ export declare function tryLoadNumericList(mem: MemInst, ptr: number, length: number, kind: string): ComponentValue[] | null;
13
+ /**
14
+ * Bulk store of `v` as elements of `kind` at `ptr`. Returns false when not
15
+ * handled (caller falls back). The caller has already trap-checked alignment
16
+ * and bounds.
17
+ */
18
+ export declare function tryStoreNumericList(mem: MemInst, v: ArrayLike<ComponentValue>, ptr: number, kind: string): boolean;