@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.
- 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/types/jspi/bridge.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export type SuspensionMode = "plain" | "jspi";
|
|
|
8
8
|
* `requested` is the embedder's opt-in. We additionally require the engine to
|
|
9
9
|
* actually implement JSPI: on an engine without it every blocking site falls
|
|
10
10
|
* back to the precise `NeedsJspi` it raised before this module existed, which
|
|
11
|
-
* is the
|
|
11
|
+
* is the browser-matrix degradation path for engines without JSPI (see
|
|
12
|
+
* `just browsers`).
|
|
12
13
|
*/
|
|
13
14
|
export declare function chooseMode(requested: boolean | undefined, needed?: boolean): SuspensionMode;
|
|
14
15
|
/**
|
|
@@ -140,8 +141,8 @@ export declare class SuspensionPoint<T = unknown> implements SchedulableThread {
|
|
|
140
141
|
* transition this point takes — produce-success, produce-throw, or
|
|
141
142
|
* `abandon` (issue #102). It is the seam a blocking built-in uses to
|
|
142
143
|
* discharge state it owns for the duration of the park (the FACT
|
|
143
|
-
* start-calls' borrow-lender scopes, contracts/intrinsics.md
|
|
144
|
-
*
|
|
144
|
+
* start-calls' borrow-lender scopes, contracts/intrinsics.md §A's
|
|
145
|
+
* trap-unwind/lender-release obligation) without having to trust that `produce` runs.
|
|
145
146
|
*
|
|
146
147
|
* INVARIANTS this hook must respect, so bridge.ts's own contracts are
|
|
147
148
|
* not disturbed:
|
|
@@ -180,8 +181,8 @@ export declare class SuspensionPoint<T = unknown> implements SchedulableThread {
|
|
|
180
181
|
* transition this point takes — produce-success, produce-throw, or
|
|
181
182
|
* `abandon` (issue #102). It is the seam a blocking built-in uses to
|
|
182
183
|
* discharge state it owns for the duration of the park (the FACT
|
|
183
|
-
* start-calls' borrow-lender scopes, contracts/intrinsics.md
|
|
184
|
-
*
|
|
184
|
+
* start-calls' borrow-lender scopes, contracts/intrinsics.md §A's
|
|
185
|
+
* trap-unwind/lender-release obligation) without having to trust that `produce` runs.
|
|
185
186
|
*
|
|
186
187
|
* INVARIANTS this hook must respect, so bridge.ts's own contracts are
|
|
187
188
|
* not disturbed:
|
package/types/plan/format.d.ts
CHANGED
|
@@ -53,10 +53,9 @@ export interface WirePlan {
|
|
|
53
53
|
* `ResourceIndex = importedResources.length + DefinedResourceIndex`
|
|
54
54
|
* (wasmtime `Component::resource_index`).
|
|
55
55
|
*
|
|
56
|
-
* plan-format.md
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* which is exactly what v0.1 asserted.
|
|
56
|
+
* The `importedResources` field (contracts/plan-format.md schema); optional
|
|
57
|
+
* here so plans produced by an older shim still load — absent is read as
|
|
58
|
+
* "no imported resources".
|
|
60
59
|
*/
|
|
61
60
|
importedResources?: WireImportedResource[];
|
|
62
61
|
imports: WireImport[];
|
|
@@ -157,9 +156,9 @@ export interface WireExportItem {
|
|
|
157
156
|
}
|
|
158
157
|
/**
|
|
159
158
|
* Trampoline declarations, tag-for-tag with the wasmtime `Trampoline` enum.
|
|
160
|
-
* Only the
|
|
161
|
-
* matched by `kind` and rejected at instantiate time with
|
|
162
|
-
* errors (contracts/intrinsics.md §B).
|
|
159
|
+
* Only the variants the executor implements are given precise field types;
|
|
160
|
+
* the rest are matched by `kind` and rejected at instantiate time with
|
|
161
|
+
* capability-aware errors (contracts/intrinsics.md §B).
|
|
163
162
|
* @internal
|
|
164
163
|
*/
|
|
165
164
|
export type WireTrampoline = {
|
|
@@ -190,8 +189,9 @@ export type WireTrampoline = {
|
|
|
190
189
|
*/
|
|
191
190
|
results: number;
|
|
192
191
|
/**
|
|
193
|
-
* `results` interned into `plan.types` as a tuple type (
|
|
194
|
-
*
|
|
192
|
+
* `results` interned into `plan.types` as a tuple type (the task-return
|
|
193
|
+
* trampoline's raw `results` key + interned `resultType`;
|
|
194
|
+
* contracts/plan-format.md schema). `null` is accepted on the
|
|
195
195
|
* wire for a task with no declared result type; the current producer
|
|
196
196
|
* never emits it (wasmtime's `TaskReturn.results` is not an `Option` —
|
|
197
197
|
* a no-result task carries the empty tuple).
|
|
@@ -343,7 +343,8 @@ export type WireExport = {
|
|
|
343
343
|
}
|
|
344
344
|
/**
|
|
345
345
|
* An exported embedded core module; `module` indexes the static module
|
|
346
|
-
* space (`modules`)
|
|
346
|
+
* space (`modules`); the `module` export kind (contracts/plan-format.md
|
|
347
|
+
* schema notes).
|
|
347
348
|
*/
|
|
348
349
|
| {
|
|
349
350
|
kind: "module";
|
package/types/plan/loader.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare class TranslateError extends Error {
|
|
|
35
35
|
* the *resource*-table mapping before, a different space) — and
|
|
36
36
|
* `task-return`'s `resultType` / raw `results` split, which lets a FACT
|
|
37
37
|
* callee task carry its declared result type.
|
|
38
|
-
* v2
|
|
38
|
+
* v2: `streamTables` / `futureTables` — the element types the
|
|
39
39
|
* stream and future built-ins need to size their copy buffers.
|
|
40
40
|
* v1 (contracts/plan-format.md v0.3): `CoreDef` gained `"unsafe-intrinsic"`.
|
|
41
41
|
* The change is purely additive, but the contract's compat rule is a strict
|
|
@@ -72,7 +72,7 @@ export interface LoadedPlan {
|
|
|
72
72
|
/**
|
|
73
73
|
* Number of imported resource types. `ResourceIndex =
|
|
74
74
|
* numImportedResources + DefinedResourceIndex`
|
|
75
|
-
* (plan-format.md
|
|
75
|
+
* (the `importedResources` field; contracts/plan-format.md schema).
|
|
76
76
|
*/
|
|
77
77
|
numImportedResources: number;
|
|
78
78
|
/** Element type per stream table (plan v2); `null` = zero-width payload. */
|
|
@@ -18,8 +18,8 @@ export declare class Translator {
|
|
|
18
18
|
* (docs/architecture.md §10): the wire envelope's `producer` block records
|
|
19
19
|
* `{shimVersion, wasmtimeEnviron, features}`, which does NOT change when
|
|
20
20
|
* the shim wasm is rebuilt from the same source versions (e.g. a local
|
|
21
|
-
* patch or a different toolchain producing different codegen)
|
|
22
|
-
*
|
|
21
|
+
* patch or a different toolchain producing different codegen). Digesting
|
|
22
|
+
* the actual bytes is the only sound cache key
|
|
23
23
|
* component for translator identity.
|
|
24
24
|
*/
|
|
25
25
|
readonly buildHash: string | null;
|
package/types/task/mod.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Table } from "../cabi/handles.js";
|
|
2
|
+
import { COMPONENT_INSTANCE } from "../cabi/context.js";
|
|
2
3
|
import type { ComponentInstanceLike } from "../cabi/context.js";
|
|
3
4
|
import type { ComponentValue, FuncType } from "../cabi/types.js";
|
|
4
5
|
import { type Cancelled, Store } from "./scheduler.js";
|
|
@@ -10,8 +11,6 @@ export * from "./thread.js";
|
|
|
10
11
|
export * from "./waitable.js";
|
|
11
12
|
export * from "./subtask.js";
|
|
12
13
|
export * from "./streams.js";
|
|
13
|
-
/** Constructor marker: "this one IS the root, do not give it a parent". */
|
|
14
|
-
declare const ROOT_TOKEN: unique symbol;
|
|
15
14
|
/** Anything a component instance's handle table can hold. */
|
|
16
15
|
export type HandleTableEntry = unknown;
|
|
17
16
|
/**
|
|
@@ -21,8 +20,19 @@ export type HandleTableEntry = unknown;
|
|
|
21
20
|
* `mayLeave` is backed by a real `WebAssembly.Global(i32, mutable)` because
|
|
22
21
|
* FACT adapters import that global (`flags` namespace) and read/write it as
|
|
23
22
|
* the may_leave boolean (wasmtime 47 FACT treats the whole flags global as
|
|
24
|
-
* may_leave; there is no bitmask). Initial value 1 (true).
|
|
25
|
-
*
|
|
23
|
+
* may_leave; there is no bitmask). Initial value 1 (true).
|
|
24
|
+
*
|
|
25
|
+
* There is no `may_enter` counterpart and no instance tree: at the pinned
|
|
26
|
+
* reference (definitions.py @ 2f13265, CM#705) there is no `may_enter`,
|
|
27
|
+
* `parent`, `entering_set`, `enter_from` or `leave_to`, so nothing gates
|
|
28
|
+
* entry into a live instance. What polyengine adds beyond the reference is
|
|
29
|
+
* per-instance POISONING — a named divergence
|
|
30
|
+
* living entirely in ./scheduler.ts (`isInstancePoisoned`, `entryRefusal`),
|
|
31
|
+
* not in any state on this class.
|
|
32
|
+
*
|
|
33
|
+
* `COMPONENT_INSTANCE` brands this class as a real component instance for
|
|
34
|
+
* the layers that only see the structural `ComponentInstanceLike`
|
|
35
|
+
* (cabi/handles.ts `isComponentInstance`; cabi must not import task/).
|
|
26
36
|
*/
|
|
27
37
|
export declare class ComponentInstanceState implements ComponentInstanceLike {
|
|
28
38
|
readonly index: number;
|
|
@@ -30,103 +40,18 @@ export declare class ComponentInstanceState implements ComponentInstanceLike {
|
|
|
30
40
|
handles: Table<HandleTableEntry>;
|
|
31
41
|
/** definitions.py `ComponentInstance.threads` — a Table, so `thread.index`. */
|
|
32
42
|
readonly threads: Table<Thread>;
|
|
33
|
-
|
|
43
|
+
/** cabi's real-instance discriminator; see the class doc. */
|
|
44
|
+
readonly [COMPONENT_INSTANCE] = true;
|
|
34
45
|
/** definitions.py `backpressure: int` — a *counter* (backpressure.{inc,dec}). */
|
|
35
46
|
backpressure: number;
|
|
36
47
|
/** definitions.py `num_waiting_to_enter`. */
|
|
37
48
|
numWaitingToEnter: number;
|
|
38
49
|
/** definitions.py `exclusive_thread`. */
|
|
39
50
|
exclusiveThread: Thread | null;
|
|
40
|
-
/**
|
|
41
|
-
* definitions.py `ComponentInstance.parent`.
|
|
42
|
-
*
|
|
43
|
-
* The plan still gives us a flat instance space, but the tree is no longer
|
|
44
|
-
* needed: every instance of one instantiation gets the same **synthetic
|
|
45
|
-
* root** as its parent (contracts/plan-format.md v3 amendment 4 /
|
|
46
|
-
* polyengine#101). See `enteringSet` for why that is observably equivalent to
|
|
47
|
-
* the real chain. The root itself has no parent.
|
|
48
|
-
*/
|
|
49
|
-
parent: ComponentInstanceState | null;
|
|
50
51
|
readonly store: Store;
|
|
51
|
-
|
|
52
|
-
* The synthetic per-instantiation root (v3 amendment 4). One per `Store`:
|
|
53
|
-
* a `Store` is exactly one component instantiation's scheduling scope, so
|
|
54
|
-
* "all `ComponentInstanceState`s sharing a `Store`" is the set that shares
|
|
55
|
-
* a top-level component — which is the granularity wasmtime's own
|
|
56
|
-
* top-level-instance-id comparison uses (concurrent.rs:1876-1886).
|
|
57
|
-
*
|
|
58
|
-
* It is a real `ComponentInstanceState` (index -1) rather than a bare flag
|
|
59
|
-
* so it flows through `selfAndAncestors`/`enteringSet` unchanged; its
|
|
60
|
-
* handle table stays empty and no task ever runs on it.
|
|
61
|
-
*/
|
|
62
|
-
static rootOf(store: Store): ComponentInstanceState;
|
|
63
|
-
/** Is this the synthetic root (never a real component instance)? */
|
|
64
|
-
get isSyntheticRoot(): boolean;
|
|
65
|
-
constructor(index: number, store?: Store, root?: typeof ROOT_TOKEN);
|
|
66
|
-
/**
|
|
67
|
-
* Release the synthetic root after a trap broke the enter/leave bracket
|
|
68
|
-
* (v3 amendment 4, and a **named divergence** from the reference).
|
|
69
|
-
*
|
|
70
|
-
* definitions.py poisons the whole entering set: `Store.lift` never reaches
|
|
71
|
-
* `leave_to`, so the root — which is in every host entry's entering set —
|
|
72
|
-
* stays `may_enter == False` forever and NO instance of the component can
|
|
73
|
-
* be entered again. wasmtime is the same by other means (it poisons the
|
|
74
|
-
* store). polyengine deliberately supports post-trap re-entry of instances the
|
|
75
|
-
* trap did not touch (exec/boundary.ts `poison`: "sibling instances stay
|
|
76
|
-
* usable, which is why the lock is released per-instance rather than by
|
|
77
|
-
* poisoning a whole store the way wasmtime does"), and the synthetic root
|
|
78
|
-
* must not silently convert that documented divergence into store-wide
|
|
79
|
-
* poisoning. So a trap poisons the LEAF set only, and the root is released
|
|
80
|
-
* here — the reentrance gate the root exists for (a *second, concurrent*
|
|
81
|
-
* host entry) is about a live entry, and after a trap unwinds to the host
|
|
82
|
-
* there is none.
|
|
83
|
-
*/
|
|
84
|
-
releaseSyntheticRootOnPoison(): void;
|
|
52
|
+
constructor(index: number, store?: Store);
|
|
85
53
|
get mayLeave(): boolean;
|
|
86
54
|
set mayLeave(v: boolean);
|
|
87
|
-
/** definitions.py `ComponentInstance.self_and_ancestors` (line 236). */
|
|
88
|
-
selfAndAncestors(): Set<ComponentInstanceState>;
|
|
89
|
-
/**
|
|
90
|
-
* definitions.py `ComponentInstance.entering_set` (line 230):
|
|
91
|
-
* `self_and_ancestors() - caller.self_and_ancestors()`.
|
|
92
|
-
*
|
|
93
|
-
* CONTRACT (contracts/plan-format.md v3 amendment 4, polyengine#101): the plan
|
|
94
|
-
* still carries no wire form for the component-instance tree, and it no
|
|
95
|
-
* longer needs one. Every instance's parent is the synthetic
|
|
96
|
-
* per-instantiation root, so:
|
|
97
|
-
*
|
|
98
|
-
* * host entry (`caller === null`): `{this, root}` — the reference's
|
|
99
|
-
* entering set for a host entry is `self_and_ancestors()`, which always
|
|
100
|
-
* contains the top-level root, so a second host entry anywhere in the
|
|
101
|
-
* tree trips on the root either way. This is the divergence #101
|
|
102
|
-
* reported (host -> A.f -> host import -> host enters a *different*
|
|
103
|
-
* instance): now caught.
|
|
104
|
-
* * guest-to-guest (`caller !== null`): `{this}` — the root is in the
|
|
105
|
-
* caller's ancestor set and cancels out. Intermediate ancestors would
|
|
106
|
-
* be the only difference from the real chain, and they are never
|
|
107
|
-
* reachably consulted: FACT compiles same-instance and ancestor calls
|
|
108
|
-
* to unconditional compile-time traps, and sibling cycles are
|
|
109
|
-
* unreachable because instance imports form a DAG (polyengine#99
|
|
110
|
-
* adjudication).
|
|
111
|
-
*
|
|
112
|
-
* So the synthetic root is observably equivalent to the full chain, and it
|
|
113
|
-
* matches wasmtime's own shortcut — a top-level instance-id comparison
|
|
114
|
-
* (concurrent.rs:1876-1886) — by construction. This reopens only if some
|
|
115
|
-
* future upstream shape makes nesting depth observable.
|
|
116
|
-
*
|
|
117
|
-
* One deliberate departure remains, at the trap path rather than here: see
|
|
118
|
-
* `releaseSyntheticRootOnPoison`.
|
|
119
|
-
*/
|
|
120
|
-
enteringSet(caller: ComponentInstanceState | null): Set<ComponentInstanceState>;
|
|
121
|
-
/** definitions.py `ComponentInstance.may_enter_from` (line 214). */
|
|
122
|
-
mayEnterFrom(caller: ComponentInstanceState | null): boolean;
|
|
123
|
-
/** definitions.py `ComponentInstance.enter_from` (line 220). */
|
|
124
|
-
enterFrom(caller: ComponentInstanceState | null): void;
|
|
125
|
-
/** definitions.py `ComponentInstance.leave_to` (line 225). */
|
|
126
|
-
leaveTo(caller: ComponentInstanceState | null): void;
|
|
127
|
-
/** Backwards-compatible host-entry helpers (the M0 spelling). */
|
|
128
|
-
enter(): void;
|
|
129
|
-
leave(): void;
|
|
130
55
|
}
|
|
131
56
|
/** definitions.py `Task.State` (line 445). */
|
|
132
57
|
export type TaskState = "initial" | "started" | "pending-cancel" | "cancel-delivered" | "resolved";
|
|
@@ -190,7 +115,8 @@ export declare class Task {
|
|
|
190
115
|
* A FACT callee task's result type arrives as the raw wasmtime
|
|
191
116
|
* `TypeTupleIndex` `prepare-call` passes as `task_return_type`; v3's
|
|
192
117
|
* `task-return.results` / `resultType` pair is the dictionary for it
|
|
193
|
-
* (
|
|
118
|
+
* (the task-return trampoline's raw `results` key + interned `resultType`;
|
|
119
|
+
* contracts/plan-format.md schema). It resolves for every callee
|
|
194
120
|
* that has a `task.return` trampoline of its own — which is every callee
|
|
195
121
|
* that can call `task.return` — but a callee with none (sync-lifted,
|
|
196
122
|
* reached through an async-to-sync adapter) contributes no entry, and then
|
|
@@ -238,10 +164,13 @@ export declare class Task {
|
|
|
238
164
|
/** definitions.py `Task.unregister_thread` (line 510). */
|
|
239
165
|
unregisterThread(thread: Thread): void;
|
|
240
166
|
/**
|
|
241
|
-
* definitions.py `Task.request_cancellation` (
|
|
242
|
-
* cancellable thread if one exists
|
|
243
|
-
*
|
|
244
|
-
*
|
|
167
|
+
* definitions.py `Task.request_cancellation` (@ 2f13265). Delivered to a
|
|
168
|
+
* cancellable thread if one exists; otherwise recorded as pending, to be
|
|
169
|
+
* picked up at the next cancellable block point (`deliverPendingCancel`).
|
|
170
|
+
*
|
|
171
|
+
* `caller` is retained for the call-site shape (fact_calls.ts's
|
|
172
|
+
* `subtask.onCancel`) and for diagnostics; no condition here consults it
|
|
173
|
+
* (CM#705: entry into a live instance is ungated).
|
|
245
174
|
*/
|
|
246
175
|
requestCancellation(caller: ComponentInstanceState | null): void;
|
|
247
176
|
/** definitions.py `Task.deliver_pending_cancel` (line 536). */
|
|
@@ -46,8 +46,28 @@ export declare class NeedsJspi extends Error {
|
|
|
46
46
|
}
|
|
47
47
|
export declare function needsJspi(what: string): never;
|
|
48
48
|
/**
|
|
49
|
-
* Failure raised
|
|
50
|
-
*
|
|
49
|
+
* Failure raised when a synchronous entry into an instance would race a
|
|
50
|
+
* pending lift (contracts/embedder-api.md §"Functions and async",
|
|
51
|
+
* failure-ladder arm 2).
|
|
52
|
+
*
|
|
53
|
+
* In jspi mode a promising-wrapped entry settles through a microtask hop even
|
|
54
|
+
* when nothing suspended, and the hop-quiescence gate (exec/boundary.ts)
|
|
55
|
+
* defers Promise-surface calls that would enter during that window. A
|
|
56
|
+
* synchronous caller — a resource constructor, or the embedder's `sync()`
|
|
57
|
+
* adapter — cannot be deferred, so it refuses instead.
|
|
58
|
+
*
|
|
59
|
+
* Deliberately *not* a `Trap`, and deliberately raised BEFORE the instance is
|
|
60
|
+
* entered: nothing was entered, so there is nothing to poison. The refusal is
|
|
61
|
+
* transient — the instance stays enterable, and the call succeeds on retry
|
|
62
|
+
* once the in-flight activity settles, or immediately through the
|
|
63
|
+
* Promise-shaped surface, which defers rather than refusing.
|
|
64
|
+
*/
|
|
65
|
+
export declare class SyncEntryBusy extends Error {
|
|
66
|
+
constructor(what: string);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Failure raised where a not-yet-implemented capability is required. Same
|
|
70
|
+
* rationale as `NeedsJspi`: never a `Trap`.
|
|
51
71
|
*/
|
|
52
72
|
export declare class PendingCapability extends Error {
|
|
53
73
|
constructor(what: string);
|
|
@@ -65,17 +85,6 @@ export declare function notifyInstancePoisoned(inst: {
|
|
|
65
85
|
handles: Iterable<unknown>;
|
|
66
86
|
}, cause: unknown): void;
|
|
67
87
|
export declare function isInstancePoisoned(inst: object): boolean;
|
|
68
|
-
/**
|
|
69
|
-
* May a settled activation tail for parked thread `t` be DISPATCHED now
|
|
70
|
-
* (issue #156)? True iff its instance is host-enterable — `Thread.resumeWith`
|
|
71
|
-
* brackets the resumption with `enterFrom(null)` — or POISONED, in which case
|
|
72
|
-
* `resumeWith`'s early return retires it and deferring would leak forever.
|
|
73
|
-
*
|
|
74
|
-
* CONTRACT: a parked entry without a reachable `task.inst` (the partial
|
|
75
|
-
* thread doubles the host-pump tests park in `Store.awaiting`) holds no
|
|
76
|
-
* reentrance state, so there is nothing to defer on: dispatchable.
|
|
77
|
-
*/
|
|
78
|
-
export declare function dispatchableTail(t: any): boolean;
|
|
79
88
|
/**
|
|
80
89
|
* The recorded cause of an instance's poisoning: the original trap that
|
|
81
90
|
* broke the enter/leave bracket (polyengine#145). `undefined` when the instance
|
|
@@ -85,16 +94,39 @@ export declare function dispatchableTail(t: any): boolean;
|
|
|
85
94
|
export declare function instancePoisonCause(inst: object): unknown;
|
|
86
95
|
/**
|
|
87
96
|
* Append the recorded poison cause to an entry-refusal trap message
|
|
88
|
-
* (polyengine#145 ask 1). "cannot enter component instance"
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* the
|
|
97
|
+
* (polyengine#145 ask 1). "cannot enter component instance" has exactly one
|
|
98
|
+
* cause — a permanently poisoned instance, the corpse of an earlier trap —
|
|
99
|
+
* and this
|
|
100
|
+
* suffix names the trap that made it one. The call is kept unconditional at
|
|
101
|
+
* the refusal sites (returning `base` unchanged for an unmarked instance) so
|
|
102
|
+
* the message construction stays in one place; the suffix is
|
|
94
103
|
* conformance-safe because the official suite matches trap messages by
|
|
95
104
|
* substring (harness/src/runner.ts).
|
|
96
105
|
*/
|
|
97
106
|
export declare function withPoisonCause(inst: object, base: string): string;
|
|
107
|
+
/**
|
|
108
|
+
* The entry-refusal decision, in one place: may `caller` enter `callee` right
|
|
109
|
+
* now, and if not, what does the refusal trap say? Returns `null` when entry
|
|
110
|
+
* is allowed, otherwise the exact trap message for `base`.
|
|
111
|
+
*
|
|
112
|
+
* POISONING IS THE WHOLE MECHANISM (CM#705). There is no transient
|
|
113
|
+
* reentrance gate: at the pinned reference (definitions.py @ 2f13265)
|
|
114
|
+
* `may_enter`, `entering_set`, `enter_from`, `leave_to` and
|
|
115
|
+
* `ComponentInstance.parent` do not exist — `Store.lift` runs `canon_lift`
|
|
116
|
+
* with no gate at all, so host-mediated reentrance into a live instance is
|
|
117
|
+
* simply VALID.
|
|
118
|
+
*
|
|
119
|
+
* Against that, per-instance poisoning is polyengine's NAMED DIVERGENCE. A
|
|
120
|
+
* trapped instance is a corpse — entry is refused permanently, with the
|
|
121
|
+
* recorded cause appended (polyengine#145 ask 1) — where wasmtime instead
|
|
122
|
+
* kills the whole store. The reference never faces the question because a
|
|
123
|
+
* trap there is the end of the world.
|
|
124
|
+
*
|
|
125
|
+
* The `caller !== callee` guard keeps a self-call out of the refusal: a dtor
|
|
126
|
+
* invoked from inside its own instance (cabi/handles.ts) is the live case —
|
|
127
|
+
* it must not be refused by its own instance's marker.
|
|
128
|
+
*/
|
|
129
|
+
export declare function entryRefusal(callee: object, caller: unknown, base: string): string | null;
|
|
98
130
|
/** Test hook: switch policy at runtime. `null` restores FIFO. */
|
|
99
131
|
export declare function schedulerSeedForTesting(value: number | null): void;
|
|
100
132
|
/**
|
|
@@ -277,22 +309,18 @@ export declare class Store {
|
|
|
277
309
|
* wedges the loops, because an activation that merely hopped legitimately
|
|
278
310
|
* holds an ambient while the scheduler is free to proceed.
|
|
279
311
|
*
|
|
280
|
-
* PER-STORE and MULTI-ENTRY
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* tier-3 attribution unambiguity, which no longer exists (see
|
|
289
|
-
* `resolveAmbient`), so it is gone with the slot; the entries and their
|
|
290
|
-
* release edges are otherwise unchanged, per entry.
|
|
312
|
+
* PER-STORE and MULTI-ENTRY (issues #158 mechanism B, #210), both load
|
|
313
|
+
* bearing. MULTI-ENTRY because two engine resumptions can legitimately be
|
|
314
|
+
* pending at once: a running activation X may deliver a resume to Z while
|
|
315
|
+
* Y's resumption is still outstanding, and a one-claimant gate cannot
|
|
316
|
+
* represent that. PER-STORE because a claim held store-wide makes every
|
|
317
|
+
* driver on EVERY store yield: an idle store's `driveStoreAsync` dies at
|
|
318
|
+
* the 10,000-hop assert (~311ms) while another store merely dwells on a
|
|
319
|
+
* slow host import.
|
|
291
320
|
*
|
|
292
321
|
* Cross-store de-serialization is safe by disjointness: an activation
|
|
293
|
-
* belongs to exactly one store. Same-store
|
|
294
|
-
*
|
|
295
|
-
* died, rather than crashing on the second.
|
|
322
|
+
* belongs to exactly one store. Same-store the set is conservative — the
|
|
323
|
+
* gate keeps refusing until EVERY pending entry has died.
|
|
296
324
|
*
|
|
297
325
|
* Release edges, per entry: the activation PARKS again
|
|
298
326
|
* (`blockCurrentActivation` -> `consumePendingIfRunning`), it FINISHES (its
|
|
@@ -395,37 +423,28 @@ export declare class Store {
|
|
|
395
423
|
* throw (trap unwinding); callers propagate or park it exactly as they do
|
|
396
424
|
* for `tick`.
|
|
397
425
|
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* `resumeWith` brackets the resumption with `enterFrom(null)`, and under
|
|
401
|
-
* the shared synthetic per-instantiation root a host entry into ANY
|
|
402
|
-
* instance of the graph locks the root, so while one instance is entered a
|
|
403
|
-
* sibling's tail cannot be dispatched: dispatching it tripped
|
|
404
|
-
* `resumeWith`'s enterability assert (which, mutating before asserting,
|
|
405
|
-
* also stranded the thread and lost the settle).
|
|
426
|
+
* Every non-stale tail is dispatched immediately, in queue order: there is
|
|
427
|
+
* no enterability condition to defer on (CM#705).
|
|
406
428
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
* phantom-state gate
|
|
411
|
-
*
|
|
429
|
+
* The ordering discipline is therefore settle order, full stop — and it is
|
|
430
|
+
* the reason this queue exists rather than a direct resumption from the
|
|
431
|
+
* settle continuation: in definitions.py the tail runs atomically inside
|
|
432
|
+
* the entered bracket, so the phantom-state gate (`tick` refuses while an
|
|
433
|
+
* unserviced tail is queued, see `hasServiceableSettled`) is what keeps a
|
|
434
|
+
* parked activation's tail from being observed out of order.
|
|
412
435
|
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* conforming schedule nondeterminism: in definitions.py the tail runs
|
|
416
|
-
* atomically inside the entered bracket, so a host entry admitted during a
|
|
417
|
-
* park necessarily orders before the parked activation's tail there.
|
|
418
|
-
*
|
|
419
|
-
* A POISONED instance's tail is still dispatched: `resumeWith`'s poison
|
|
420
|
-
* early-return retires it, and deferring it would leak forever — a
|
|
421
|
-
* poisoned leaf keeps its lock permanently.
|
|
436
|
+
* A POISONED instance's tail is dispatched like any other: `resumeWith`'s
|
|
437
|
+
* poison early-return retires it, so it drains rather than leaking.
|
|
422
438
|
*/
|
|
423
439
|
serviceSettled(): boolean;
|
|
424
440
|
/**
|
|
425
|
-
* "Would a `serviceSettled` call make progress right now?" — i.e.
|
|
426
|
-
* entry
|
|
427
|
-
*
|
|
428
|
-
*
|
|
441
|
+
* "Would a `serviceSettled` call make progress right now?" — i.e. is any
|
|
442
|
+
* entry queued at all. Every entry either dispatches or is dropped as
|
|
443
|
+
* stale, so a non-empty queue always makes progress.
|
|
444
|
+
*
|
|
445
|
+
* It exists to gate `tick` (and to keep the driving loops from parking)
|
|
446
|
+
* behind unserviced tails: resuming some other thread while a settled tail
|
|
447
|
+
* waits would expose the out-of-order state the queue is there to prevent.
|
|
429
448
|
*/
|
|
430
449
|
hasServiceableSettled(): boolean;
|
|
431
450
|
/**
|
|
@@ -473,9 +492,9 @@ export declare class Store {
|
|
|
473
492
|
*/
|
|
474
493
|
hasRunnableWork(inst: unknown, excludeTask: unknown): boolean;
|
|
475
494
|
/**
|
|
476
|
-
* definitions.py `Store.tick` (
|
|
477
|
-
*
|
|
478
|
-
*
|
|
495
|
+
* definitions.py `Store.tick` (@ 2f13265): resume one ready thread. There
|
|
496
|
+
* is no bracket and no gate — the reference body is exactly "pick a ready
|
|
497
|
+
* thread, resume it" (CM#705).
|
|
479
498
|
*
|
|
480
499
|
* Returns false when no thread was ready, so callers can distinguish
|
|
481
500
|
* "made progress" from "stuck" without inspecting the queue themselves.
|
package/types/task/streams.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export interface RendezvousBuffer {
|
|
|
77
77
|
write(vs: PayloadChunk): void;
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
*
|
|
80
|
+
* direct-access byte edge: the peer half of a direct rendezvous — a buffer that can expose its
|
|
81
81
|
* remaining range as bytes and be advanced without a copy.
|
|
82
82
|
*
|
|
83
83
|
* Implemented by `GuestBuffer` (a view into guest linear memory: the
|
|
@@ -99,7 +99,7 @@ export interface ByteWindow {
|
|
|
99
99
|
endWindow?(): void;
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
102
|
+
* direct-access byte edge: the parked direct session, as the rendezvous sees it. It presents the
|
|
103
103
|
* ordinary buffer surface (so `remain()`/`isZeroLength()` keep the reference
|
|
104
104
|
* control flow working) but its `read`/`write` are never called — the seam
|
|
105
105
|
* routes it through `runDirect` instead.
|
|
@@ -159,7 +159,7 @@ export declare class SharedStreamImpl implements SharedBase {
|
|
|
159
159
|
* OUT of a guest table. The receiver — the host, or the destination of a
|
|
160
160
|
* guest-to-guest hop, in which case the immediately following lower fires
|
|
161
161
|
* `onLowered` — may now act on the transferred end. Host wrappers use it to
|
|
162
|
-
* re-arm their activity (#162, embedder-api
|
|
162
|
+
* re-arm their activity (#162, contracts/embedder-api.md §"Streams and futures"). Guest-owned
|
|
163
163
|
* objects leave it unset.
|
|
164
164
|
*/
|
|
165
165
|
onLifted: ((inst: {
|
|
@@ -210,7 +210,7 @@ export declare class SharedFutureImpl implements SharedBase {
|
|
|
210
210
|
* OUT of a guest table. The receiver — the host, or the destination of a
|
|
211
211
|
* guest-to-guest hop, in which case the immediately following lower fires
|
|
212
212
|
* `onLowered` — may now act on the transferred end. Host wrappers use it to
|
|
213
|
-
* re-arm their activity (#162, embedder-api
|
|
213
|
+
* re-arm their activity (#162, contracts/embedder-api.md §"Streams and futures"). Guest-owned
|
|
214
214
|
* objects leave it unset.
|
|
215
215
|
*/
|
|
216
216
|
onLifted: ((inst: {
|
|
@@ -351,51 +351,35 @@ interface PoisonedInstanceLike {
|
|
|
351
351
|
* **poisoned** guest instance is retired silently via `resetPending`.
|
|
352
352
|
* Notifying it would queue a phantom event into the corpse's waitables, and
|
|
353
353
|
* a later driving loop servicing it would resume machinery whose instance
|
|
354
|
-
* can no longer be entered (`tick`
|
|
354
|
+
* can no longer be entered (`tick` excludes poisoned instances). Host sentinels are
|
|
355
355
|
* not instances at all, so they are always notified.
|
|
356
356
|
*
|
|
357
|
-
* #100: THE HEALTH TEST IS "POISONED", NOT "
|
|
358
|
-
*
|
|
359
|
-
* unsound in one direction
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
* scheduler's enter/leave bracket, and the bracket's `leaveTo` runs on the
|
|
366
|
-
* way out (task/scheduler.ts `Store.tick` :905-917, task/thread.ts
|
|
367
|
-
* `Thread.resumeWith` :157-179, whose resume-side
|
|
368
|
-
* `assert_(mayEnterFrom(null))` would fire otherwise). Blocking inside a
|
|
369
|
-
* wasm frame does NOT hold the enter bracket.
|
|
370
|
-
* * (unsound converse) `mayEnter === false` does not imply "poisoned". An
|
|
371
|
-
* instance that is merely mid-call is also non-enterable, and a CALLER
|
|
372
|
-
* instance stays non-enterable for the whole duration of a
|
|
373
|
-
* cross-component (FACT) call into an instance that traps
|
|
374
|
-
* (`ComponentInstanceState.enterFrom` clears `mayEnter` on the callee's
|
|
375
|
-
* entering set only, task/mod.ts). A *different*, healthy task of that
|
|
376
|
-
* caller, parked on an end of a stream/future the trapping callee also
|
|
377
|
-
* held, was classified dead here and retired silently — stranded, the
|
|
378
|
-
* exact outcome #66 exists to prevent.
|
|
357
|
+
* #100: THE HEALTH TEST IS "POISONED", NOT "BUSY". Deadness must be judged
|
|
358
|
+
* by the poison marker and nothing weaker: any liveness proxy that also
|
|
359
|
+
* covers a merely mid-call instance is unsound in one direction and strands
|
|
360
|
+
* healthy tasks. Under such a proxy a caller mid cross-component (FACT) call
|
|
361
|
+
* into an instance that trapped would drag its *other*, healthy tasks down
|
|
362
|
+
* with it — one parked on an end of a stream/future the trapping callee also
|
|
363
|
+
* held would be classified dead here and retired silently, the exact outcome
|
|
364
|
+
* #66 exists to prevent.
|
|
379
365
|
*
|
|
380
366
|
* So the test consults the poison marker itself. It is per-instance and
|
|
381
|
-
* recorded at the single seam every
|
|
367
|
+
* recorded at the single seam every poisoning site routes through
|
|
382
368
|
* (`notifyInstancePoisoned`, task/scheduler.ts: exec/boundary.ts `poison`,
|
|
383
369
|
* `Store.tick`, `Thread.resumeWith`, the FACT cross-component catches in
|
|
384
370
|
* intrinsics/fact_calls.ts, and cabi/handles.ts's gated destructor call),
|
|
385
371
|
* and it is recorded *before* the retirement walk runs, so an instance's own
|
|
386
372
|
* parked ends still see it during its own walk. `retiredInstances` is
|
|
387
373
|
* consulted alongside it because the walk is also reachable directly (it is
|
|
388
|
-
* set at walk entry, so the two agree)
|
|
389
|
-
* per-instantiation root, which every poison site skips or releases (plan v3
|
|
390
|
-
* amendment 4, `releaseSyntheticRootOnPoison`).
|
|
374
|
+
* set at walk entry, so the two agree).
|
|
391
375
|
*
|
|
392
376
|
* Why this does not re-open review B2 (phantom events into a corpse): the
|
|
393
377
|
* concern is that a DROPPED event queued onto a waitable of an instance that
|
|
394
378
|
* can never be entered again would be serviced by a later driving loop and
|
|
395
|
-
* resume machinery
|
|
396
|
-
* again" is precisely poisoning — a mid-call instance
|
|
397
|
-
*
|
|
398
|
-
*
|
|
379
|
+
* resume machinery `tick` deliberately excludes. "Can never be entered
|
|
380
|
+
* again" is precisely poisoning — a merely mid-call instance is entirely
|
|
381
|
+
* ordinary, and its parked task resumes normally and consumes the event.
|
|
382
|
+
* The narrowed predicate
|
|
399
383
|
* therefore excludes exactly the population B2 is about, and admits only
|
|
400
384
|
* peers that will run again.
|
|
401
385
|
*
|
|
@@ -407,8 +391,9 @@ export declare function dropSharedForTeardown(shared: SharedStreamImpl | SharedF
|
|
|
407
391
|
* Retire every live stream/future end in a trap-poisoned instance's handle
|
|
408
392
|
* table (#66).
|
|
409
393
|
*
|
|
410
|
-
* Rationale:
|
|
411
|
-
*
|
|
394
|
+
* Rationale: a trapped instance is a corpse (polyengine's per-instance
|
|
395
|
+
* poisoning divergence), so no task of this instance can ever rendezvous
|
|
396
|
+
* again. Its
|
|
412
397
|
* table's `CopyEnd`s are therefore unreachable-forever — leaving their shared
|
|
413
398
|
* objects live strands the peers: a parked HOST operation never settles (its
|
|
414
399
|
* promise hangs), and a LATER host operation would "succeed" against the
|
|
@@ -416,7 +401,7 @@ export declare function dropSharedForTeardown(shared: SharedStreamImpl | SharedF
|
|
|
416
401
|
* Dropping the shared object now converts both into the spec-shaped DROPPED
|
|
417
402
|
* outcome, and the recorded failure lets the embedder layer brand it.
|
|
418
403
|
*
|
|
419
|
-
* Called from every
|
|
404
|
+
* Called from every poisoning site — exec/boundary.ts `poison()` (the
|
|
420
405
|
* sync-lift path), scheduler.ts `Store.tick` and thread.ts
|
|
421
406
|
* `Thread.resumeWith` (traps during a resumed thread), and the FACT
|
|
422
407
|
* cross-component catches (intrinsics/fact_calls.ts, callee side) — with the
|
package/types/task/subtask.d.ts
CHANGED
|
@@ -54,8 +54,8 @@ export declare class Subtask extends Waitable {
|
|
|
54
54
|
/**
|
|
55
55
|
* Release a never-delivered subtask's lenders after its call broke off a
|
|
56
56
|
* non-poisoning exit — trap-rethrow past the CALLEE, capability bail, or
|
|
57
|
-
* an abandoned park (contracts/intrinsics.md
|
|
58
|
-
*
|
|
57
|
+
* an abandoned park (contracts/intrinsics.md §A's trap-unwind/lender-release
|
|
58
|
+
* obligation; the park legs are #102/#106).
|
|
59
59
|
*
|
|
60
60
|
* The reference has no analogue because it never resumes after a trap:
|
|
61
61
|
* the store dies with the lent handles inside it. The resolution state
|
package/types/task/waitable.d.ts
CHANGED
|
@@ -12,7 +12,6 @@ export declare enum EventCode {
|
|
|
12
12
|
}
|
|
13
13
|
/** definitions.py `EventTuple` = `(EventCode, int, int)`. */
|
|
14
14
|
export type EventTuple = [code: EventCode, p1: number, p2: number];
|
|
15
|
-
export declare const NO_EVENT: EventTuple;
|
|
16
15
|
/**
|
|
17
16
|
* definitions.py `class Waitable` (line 767).
|
|
18
17
|
*
|