@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.
- package/esm/cabi/async_values.js +6 -5
- package/esm/cabi/bulk_lists.js +0 -5
- package/esm/cabi/context.js +13 -3
- package/esm/cabi/flatten.js +41 -9
- package/esm/cabi/handles.js +57 -54
- package/esm/cabi/layout.js +113 -52
- package/esm/cabi/load.js +31 -23
- package/esm/cabi/store.js +33 -26
- package/esm/cabi/trap.js +2 -2
- package/esm/cabi/types.js +138 -25
- package/esm/cabi/values.js +25 -7
- package/esm/cache/core.js +2 -11
- package/esm/digest/digest.js +10 -8
- package/esm/digest/mod.js +1 -1
- package/esm/digest/verify.js +6 -86
- package/esm/embedder/casing.js +24 -9
- package/esm/embedder/copy.js +6 -6
- package/esm/embedder/errors.js +2 -2
- package/esm/embedder/imports.js +3 -3
- package/esm/embedder/instantiate.js +132 -37
- package/esm/embedder/mod.js +9 -8
- package/esm/embedder/resources.js +39 -16
- package/esm/embedder/streams.js +36 -37
- package/esm/embedder/sync.js +242 -0
- package/esm/embedder/values.js +84 -22
- package/esm/embedder/version.js +9 -9
- package/esm/exec/boundary.js +123 -161
- package/esm/exec/executor.js +37 -25
- package/esm/exec/host_streams.js +31 -31
- package/esm/intrinsics/async_builtins.js +15 -7
- package/esm/intrinsics/context.js +1 -1
- package/esm/intrinsics/errors.js +9 -9
- package/esm/intrinsics/fact_calls.js +37 -49
- package/esm/intrinsics/mod.js +54 -117
- package/esm/intrinsics/stream_builtins.js +2 -2
- package/esm/intrinsics/transcode.js +1 -1
- package/esm/jspi/bridge.js +4 -3
- package/esm/jspi/suspending.js +5 -5
- package/esm/plan/loader.js +5 -5
- package/esm/shim/translator.js +2 -2
- package/esm/task/mod.js +45 -182
- package/esm/task/scheduler.js +154 -185
- package/esm/task/streams.js +39 -54
- package/esm/task/subtask.js +2 -2
- package/esm/task/thread.js +20 -41
- package/esm/task/waitable.js +0 -1
- package/package.json +2 -2
- package/types/cabi/async_values.d.ts +3 -2
- package/types/cabi/bulk_lists.d.ts +0 -2
- package/types/cabi/context.d.ts +15 -5
- package/types/cabi/flatten.d.ts +2 -2
- package/types/cabi/handles.d.ts +15 -26
- package/types/cabi/layout.d.ts +22 -1
- package/types/cabi/load.d.ts +10 -2
- package/types/cabi/store.d.ts +4 -2
- package/types/cabi/types.d.ts +22 -3
- package/types/digest/mod.d.ts +1 -1
- package/types/digest/verify.d.ts +3 -19
- package/types/embedder/casing.d.ts +9 -1
- package/types/embedder/copy.d.ts +4 -4
- package/types/embedder/instantiate.d.ts +4 -4
- package/types/embedder/mod.d.ts +3 -2
- package/types/embedder/resources.d.ts +20 -7
- package/types/embedder/streams.d.ts +5 -6
- package/types/embedder/sync.d.ts +81 -0
- package/types/embedder/values.d.ts +2 -2
- package/types/exec/boundary.d.ts +55 -44
- package/types/exec/executor.d.ts +3 -2
- package/types/exec/host_streams.d.ts +8 -8
- package/types/intrinsics/errors.d.ts +3 -3
- package/types/intrinsics/mod.d.ts +1 -1
- package/types/intrinsics/stream_builtins.d.ts +2 -2
- package/types/jspi/bridge.d.ts +6 -5
- package/types/plan/format.d.ts +11 -10
- package/types/plan/loader.d.ts +2 -2
- package/types/shim/translator.d.ts +2 -2
- package/types/task/mod.d.ts +26 -97
- package/types/task/scheduler.d.ts +81 -62
- package/types/task/streams.d.ts +23 -38
- package/types/task/subtask.d.ts +2 -2
- 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).
|
|
43
|
-
*
|
|
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
|
-
|
|
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
|
-
* (
|
|
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` (
|
|
380
|
-
* cancellable thread if one exists
|
|
381
|
-
*
|
|
382
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
//
|
|
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
|
-
//
|
|
430
|
-
//
|
|
431
|
-
if (e instanceof NeedsJspi
|
|
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";
|