@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.
- package/LICENSE +202 -0
- package/README.md +23 -0
- package/esm/cabi/async_values.js +162 -0
- package/esm/cabi/bulk_lists.js +198 -0
- package/esm/cabi/context.js +42 -0
- package/esm/cabi/flatten.js +145 -0
- package/esm/cabi/float.js +67 -0
- package/esm/cabi/handles.js +253 -0
- package/esm/cabi/layout.js +149 -0
- package/esm/cabi/lift.js +196 -0
- package/esm/cabi/load.js +146 -0
- package/esm/cabi/lower.js +141 -0
- package/esm/cabi/memory.js +182 -0
- package/esm/cabi/mod.js +22 -0
- package/esm/cabi/store.js +186 -0
- package/esm/cabi/strings.js +336 -0
- package/esm/cabi/trap.js +38 -0
- package/esm/cabi/types.js +264 -0
- package/esm/cabi/values.js +64 -0
- package/esm/cache/core.js +156 -0
- package/esm/cache/dir.js +170 -0
- package/esm/cache/mod.js +4 -0
- package/esm/cache/web.js +136 -0
- package/esm/digest/digest.js +332 -0
- package/esm/digest/mod.js +3 -0
- package/esm/digest/verify.js +129 -0
- package/esm/embedder/casing.js +56 -0
- package/esm/embedder/copy.js +42 -0
- package/esm/embedder/errors.js +26 -0
- package/esm/embedder/imports.js +63 -0
- package/esm/embedder/instantiate.js +978 -0
- package/esm/embedder/mod.js +40 -0
- package/esm/embedder/resources.js +406 -0
- package/esm/embedder/streams.js +770 -0
- package/esm/embedder/values.js +450 -0
- package/esm/embedder/version.js +273 -0
- package/esm/exec/boundary.js +1902 -0
- package/esm/exec/executor.js +1004 -0
- package/esm/exec/host_streams.js +818 -0
- package/esm/exec/mod.js +4 -0
- package/esm/intrinsics/async_builtins.js +510 -0
- package/esm/intrinsics/context.js +90 -0
- package/esm/intrinsics/errors.js +13 -0
- package/esm/intrinsics/fact_calls.js +865 -0
- package/esm/intrinsics/mod.js +564 -0
- package/esm/intrinsics/stream_builtins.js +578 -0
- package/esm/intrinsics/transcode.js +443 -0
- package/esm/jspi/bridge.js +579 -0
- package/esm/jspi/mechanics.js +89 -0
- package/esm/jspi/mod.js +5 -0
- package/esm/jspi/suspending.js +15 -0
- package/esm/jspi/types.js +29 -0
- package/esm/package.json +3 -0
- package/esm/plan/format.js +5 -0
- package/esm/plan/loader.js +657 -0
- package/esm/plan/mod.js +3 -0
- package/esm/shim/mod.js +2 -0
- package/esm/shim/translator.js +94 -0
- package/esm/task/mod.js +483 -0
- package/esm/task/scheduler.js +1028 -0
- package/esm/task/streams.js +786 -0
- package/esm/task/subtask.js +135 -0
- package/esm/task/thread.js +255 -0
- package/esm/task/waitable.js +144 -0
- package/package.json +91 -0
- package/types/cabi/async_values.d.ts +35 -0
- package/types/cabi/bulk_lists.d.ts +18 -0
- package/types/cabi/context.d.ts +59 -0
- package/types/cabi/flatten.d.ts +14 -0
- package/types/cabi/float.d.ts +14 -0
- package/types/cabi/handles.d.ts +70 -0
- package/types/cabi/layout.d.ts +13 -0
- package/types/cabi/lift.d.ts +25 -0
- package/types/cabi/load.d.ts +14 -0
- package/types/cabi/lower.d.ts +10 -0
- package/types/cabi/memory.d.ts +58 -0
- package/types/cabi/mod.d.ts +15 -0
- package/types/cabi/store.d.ts +12 -0
- package/types/cabi/strings.d.ts +23 -0
- package/types/cabi/trap.d.ts +11 -0
- package/types/cabi/types.d.ts +206 -0
- package/types/cabi/values.d.ts +5 -0
- package/types/cache/core.d.ts +97 -0
- package/types/cache/dir.d.ts +6 -0
- package/types/cache/mod.d.ts +3 -0
- package/types/cache/web.d.ts +10 -0
- package/types/digest/digest.d.ts +17 -0
- package/types/digest/mod.d.ts +2 -0
- package/types/digest/verify.d.ts +48 -0
- package/types/embedder/casing.d.ts +40 -0
- package/types/embedder/copy.d.ts +24 -0
- package/types/embedder/errors.d.ts +11 -0
- package/types/embedder/imports.d.ts +47 -0
- package/types/embedder/instantiate.d.ts +88 -0
- package/types/embedder/mod.d.ts +11 -0
- package/types/embedder/resources.d.ts +158 -0
- package/types/embedder/streams.d.ts +202 -0
- package/types/embedder/values.d.ts +70 -0
- package/types/embedder/version.d.ts +85 -0
- package/types/exec/boundary.d.ts +360 -0
- package/types/exec/executor.d.ts +125 -0
- package/types/exec/host_streams.d.ts +165 -0
- package/types/exec/mod.d.ts +3 -0
- package/types/intrinsics/async_builtins.d.ts +69 -0
- package/types/intrinsics/context.d.ts +28 -0
- package/types/intrinsics/errors.d.ts +5 -0
- package/types/intrinsics/fact_calls.d.ts +120 -0
- package/types/intrinsics/mod.d.ts +187 -0
- package/types/intrinsics/stream_builtins.d.ts +113 -0
- package/types/intrinsics/transcode.d.ts +21 -0
- package/types/jspi/bridge.d.ts +227 -0
- package/types/jspi/mechanics.d.ts +50 -0
- package/types/jspi/mod.d.ts +3 -0
- package/types/jspi/suspending.d.ts +1 -0
- package/types/jspi/types.d.ts +26 -0
- package/types/plan/format.d.ts +369 -0
- package/types/plan/loader.d.ts +113 -0
- package/types/plan/mod.d.ts +2 -0
- package/types/shim/mod.d.ts +1 -0
- package/types/shim/translator.d.ts +55 -0
- package/types/task/mod.d.ts +257 -0
- package/types/task/scheduler.d.ts +421 -0
- package/types/task/streams.d.ts +370 -0
- package/types/task/subtask.d.ts +96 -0
- package/types/task/thread.d.ts +73 -0
- package/types/task/waitable.d.ts +67 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { Trap } from "../cabi/trap.js";
|
|
2
|
+
import { LiftLowerContext } from "../cabi/context.js";
|
|
3
|
+
import type { ComponentValue, ValType } from "../cabi/types.js";
|
|
4
|
+
import { Waitable } from "./waitable.js";
|
|
5
|
+
/** Structural element-type equality (`null` = the zero-width payload).
|
|
6
|
+
* Delegates to `valTypeEqual`: naive `JSON.stringify` comparison throws on
|
|
7
|
+
* resource-bearing element types (cabi/types.ts `valTypeEqual` contract
|
|
8
|
+
* note; found by the #18 polymorph-tls smoke). */
|
|
9
|
+
export declare function sameElemType(a: ValType | null, b: ValType | null): boolean;
|
|
10
|
+
/** definitions.py `Buffer.MAX_LENGTH`. */
|
|
11
|
+
export declare const BUFFER_MAX_LENGTH: number;
|
|
12
|
+
/**
|
|
13
|
+
* One rendezvous chunk. u8 payloads travel as `Uint8Array` — the lift out of
|
|
14
|
+
* guest memory and the conventions layer's lowering both produce typed
|
|
15
|
+
* chunks, and every buffer in the copy path keeps them whole (issue #54: the
|
|
16
|
+
* typed shape is what makes both the guest-memory store and a host→host
|
|
17
|
+
* hand-off bulk). Every other element type travels as a plain array.
|
|
18
|
+
*/
|
|
19
|
+
export type PayloadChunk = ComponentValue[] | Uint8Array;
|
|
20
|
+
/** definitions.py `CopyResult` (line 977). */
|
|
21
|
+
export declare enum CopyResult {
|
|
22
|
+
COMPLETED = 0,
|
|
23
|
+
DROPPED = 1,
|
|
24
|
+
CANCELLED = 2
|
|
25
|
+
}
|
|
26
|
+
/** definitions.py `CopyState` (line 1075). */
|
|
27
|
+
export declare enum CopyState {
|
|
28
|
+
IDLE = 1,
|
|
29
|
+
COPYING = 2,
|
|
30
|
+
CANCELLING_COPY = 3,
|
|
31
|
+
DONE = 4
|
|
32
|
+
}
|
|
33
|
+
export type ReclaimBuffer = () => void;
|
|
34
|
+
export type OnCopy = (reclaim: ReclaimBuffer) => void;
|
|
35
|
+
export type OnCopyDone = (result: CopyResult) => void;
|
|
36
|
+
/**
|
|
37
|
+
* A cursor over `length` elements of type `t` at `ptr` in one guest's memory.
|
|
38
|
+
* `t === null` is the zero-width element type (`stream` with no payload),
|
|
39
|
+
* where only the *count* is meaningful.
|
|
40
|
+
*/
|
|
41
|
+
export declare class GuestBuffer {
|
|
42
|
+
readonly t: ValType | null;
|
|
43
|
+
readonly cx: LiftLowerContext;
|
|
44
|
+
ptr: number;
|
|
45
|
+
readonly length: number;
|
|
46
|
+
progress: number;
|
|
47
|
+
constructor(t: ValType | null, cx: LiftLowerContext, ptr: number, length: number);
|
|
48
|
+
remain(): number;
|
|
49
|
+
isZeroLength(): boolean;
|
|
50
|
+
/** definitions.py `ReadableBufferGuestImpl.read`. */
|
|
51
|
+
read(n: number): PayloadChunk;
|
|
52
|
+
/** definitions.py `WritableBufferGuestImpl.write`. */
|
|
53
|
+
write(vs: PayloadChunk): void;
|
|
54
|
+
}
|
|
55
|
+
/** Common shape of the object a `stream`/`future` *value* refers to. */
|
|
56
|
+
export interface SharedBase {
|
|
57
|
+
readonly t: ValType | null;
|
|
58
|
+
dropped: boolean;
|
|
59
|
+
cancel(): void;
|
|
60
|
+
drop(): void;
|
|
61
|
+
}
|
|
62
|
+
export declare class SharedStreamImpl implements SharedBase {
|
|
63
|
+
#private;
|
|
64
|
+
readonly t: ValType | null;
|
|
65
|
+
/**
|
|
66
|
+
* Optional hook fired when this shared object is lowered into a component
|
|
67
|
+
* instance (`lower_stream`/`lower_future`). Host-owned ends use it to learn
|
|
68
|
+
* which `Store` is driving the guest they were just handed to; guest-owned
|
|
69
|
+
* streams leave it unset. Keeps `cabi` free of any host-stream knowledge.
|
|
70
|
+
*/
|
|
71
|
+
onLowered: ((inst: {
|
|
72
|
+
store: unknown;
|
|
73
|
+
}) => void) | null;
|
|
74
|
+
/**
|
|
75
|
+
* Optional hook fired by `liftAsyncValue` whenever this object is lifted
|
|
76
|
+
* OUT of a guest table. The receiver — the host, or the destination of a
|
|
77
|
+
* guest-to-guest hop, in which case the immediately following lower fires
|
|
78
|
+
* `onLowered` — may now act on the transferred end. Host wrappers use it to
|
|
79
|
+
* re-arm their activity (#162, embedder-api amendment A15). Guest-owned
|
|
80
|
+
* objects leave it unset.
|
|
81
|
+
*/
|
|
82
|
+
onLifted: ((inst: {
|
|
83
|
+
store: unknown;
|
|
84
|
+
}) => void) | null;
|
|
85
|
+
/**
|
|
86
|
+
* The `Store` driving the component this object has been handed to, set the
|
|
87
|
+
* first time it is lifted or lowered. Host ends need it to pump the guest
|
|
88
|
+
* between export calls (see exec/host_streams.ts `HostActivity.pump`); a
|
|
89
|
+
* purely guest-to-guest stream never reads it.
|
|
90
|
+
*/
|
|
91
|
+
boundStore: unknown;
|
|
92
|
+
dropped: boolean;
|
|
93
|
+
pendingInst: unknown;
|
|
94
|
+
pendingBuffer: GuestBuffer | null;
|
|
95
|
+
pendingOnCopy: OnCopy | null;
|
|
96
|
+
pendingOnCopyDone: OnCopyDone | null;
|
|
97
|
+
/** Register `fn` for the drop notification (fires now if already dropped). */
|
|
98
|
+
whenDropped(fn: () => void): void;
|
|
99
|
+
/** @internal — fire the drop observers (idempotent; never throws). */
|
|
100
|
+
notifyDropped(): void;
|
|
101
|
+
constructor(t: ValType | null);
|
|
102
|
+
resetPending(): void;
|
|
103
|
+
setPending(inst: unknown, buffer: GuestBuffer | null, onCopy: OnCopy | null, onCopyDone: OnCopyDone | null): void;
|
|
104
|
+
resetAndNotifyPending(result: CopyResult): void;
|
|
105
|
+
cancel(): void;
|
|
106
|
+
drop(): void;
|
|
107
|
+
/** definitions.py `SharedStreamImpl.read` (line 1032). */
|
|
108
|
+
read(inst: unknown, dstBuffer: GuestBuffer, onCopy: OnCopy, onCopyDone: OnCopyDone): void;
|
|
109
|
+
/** definitions.py `SharedStreamImpl.write` (line 1050). */
|
|
110
|
+
write(inst: unknown, srcBuffer: GuestBuffer, onCopy: OnCopy, onCopyDone: OnCopyDone): void;
|
|
111
|
+
}
|
|
112
|
+
/** definitions.py `class SharedFutureImpl` (line 1119). Exactly one element. */
|
|
113
|
+
export declare class SharedFutureImpl implements SharedBase {
|
|
114
|
+
#private;
|
|
115
|
+
readonly t: ValType | null;
|
|
116
|
+
/**
|
|
117
|
+
* Optional hook fired when this shared object is lowered into a component
|
|
118
|
+
* instance (`lower_stream`/`lower_future`). Host-owned ends use it to learn
|
|
119
|
+
* which `Store` is driving the guest they were just handed to; guest-owned
|
|
120
|
+
* streams leave it unset. Keeps `cabi` free of any host-stream knowledge.
|
|
121
|
+
*/
|
|
122
|
+
onLowered: ((inst: {
|
|
123
|
+
store: unknown;
|
|
124
|
+
}) => void) | null;
|
|
125
|
+
/**
|
|
126
|
+
* Optional hook fired by `liftAsyncValue` whenever this object is lifted
|
|
127
|
+
* OUT of a guest table. The receiver — the host, or the destination of a
|
|
128
|
+
* guest-to-guest hop, in which case the immediately following lower fires
|
|
129
|
+
* `onLowered` — may now act on the transferred end. Host wrappers use it to
|
|
130
|
+
* re-arm their activity (#162, embedder-api amendment A15). Guest-owned
|
|
131
|
+
* objects leave it unset.
|
|
132
|
+
*/
|
|
133
|
+
onLifted: ((inst: {
|
|
134
|
+
store: unknown;
|
|
135
|
+
}) => void) | null;
|
|
136
|
+
/**
|
|
137
|
+
* The `Store` driving the component this object has been handed to, set the
|
|
138
|
+
* first time it is lifted or lowered. Host ends need it to pump the guest
|
|
139
|
+
* between export calls (see exec/host_streams.ts `HostActivity.pump`); a
|
|
140
|
+
* purely guest-to-guest stream never reads it.
|
|
141
|
+
*/
|
|
142
|
+
boundStore: unknown;
|
|
143
|
+
dropped: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Set when the future's **writable** side went away without ever delivering
|
|
146
|
+
* its one value (#84 teardown of a trap-poisoned instance, #90 host
|
|
147
|
+
* `drop()` on a lowered-but-unwritten future).
|
|
148
|
+
*
|
|
149
|
+
* definitions.py keeps this state unreachable: `WritableFutureEnd.drop`
|
|
150
|
+
* traps unless the end is DONE (definitions.py:1183-1184), so a readable
|
|
151
|
+
* future end can never observe DROPPED (`future_copy`'s `on_copy_done`
|
|
152
|
+
* assertion, definitions.py:2607). Our two teardown paths deliberately
|
|
153
|
+
* bypass that trap — a poisoned instance cannot be asked to trap again, and
|
|
154
|
+
* the host `drop()` is a public API door — so the state exists here and has
|
|
155
|
+
* to be *total*: an unwritten future whose writer died can never satisfy
|
|
156
|
+
* its reader, so the reader is told at its rendezvous point, with a
|
|
157
|
+
* **trap**, never a DROPPED/COMPLETED answer and never a silent hang.
|
|
158
|
+
*
|
|
159
|
+
* Consumers of the flag:
|
|
160
|
+
* * `read` below, for a reader that has not parked yet (trap on the spot);
|
|
161
|
+
* * intrinsics/stream_builtins.ts `futureCopy`, for a parked guest reader
|
|
162
|
+
* (the pending event's thunk throws instead of producing a tuple);
|
|
163
|
+
* * exec/host_streams.ts leaves host readers on their existing DROPPED
|
|
164
|
+
* path — the conventions layer already brands that outcome.
|
|
165
|
+
*/
|
|
166
|
+
abandonReason: Error | null;
|
|
167
|
+
pendingInst: unknown;
|
|
168
|
+
pendingBuffer: GuestBuffer | null;
|
|
169
|
+
pendingOnCopyDone: OnCopyDone | null;
|
|
170
|
+
/** Register `fn` for the drop notification (fires now if already dropped). */
|
|
171
|
+
whenDropped(fn: () => void): void;
|
|
172
|
+
/** @internal — fire the drop observers (idempotent; never throws). */
|
|
173
|
+
notifyDropped(): void;
|
|
174
|
+
constructor(t: ValType | null);
|
|
175
|
+
resetPending(): void;
|
|
176
|
+
setPending(inst: unknown, buffer: GuestBuffer | null, onCopyDone: OnCopyDone | null): void;
|
|
177
|
+
resetAndNotifyPending(result: CopyResult): void;
|
|
178
|
+
cancel(): void;
|
|
179
|
+
drop(): void;
|
|
180
|
+
read(inst: unknown, dstBuffer: GuestBuffer, onCopyDone: OnCopyDone): void;
|
|
181
|
+
write(inst: unknown, srcBuffer: GuestBuffer, onCopyDone: OnCopyDone): void;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* One guest-visible end of a stream or future. It **is** a `Waitable`, so it
|
|
185
|
+
* joins waitable sets and delivers events through the machinery the subtask
|
|
186
|
+
* path already uses.
|
|
187
|
+
*/
|
|
188
|
+
export declare abstract class CopyEnd extends Waitable {
|
|
189
|
+
readonly shared: SharedBase;
|
|
190
|
+
state: CopyState;
|
|
191
|
+
constructor(shared: SharedBase);
|
|
192
|
+
/** "stream" | "future" — trap-wording parity with wasmtime. */
|
|
193
|
+
abstract readonly kind: "stream" | "future";
|
|
194
|
+
/**
|
|
195
|
+
* Which end this is. Wasmtime words a busy READABLE-end drop as a table
|
|
196
|
+
* removal ("cannot remove busy stream") and a busy WRITABLE-end drop as a
|
|
197
|
+
* drop ("cannot drop busy stream") — the suite pins both spellings side by
|
|
198
|
+
* side (drop-stream.wast:158 read end vs :160 / builtin-trap-poisons-
|
|
199
|
+
* instance.wast:38 write end).
|
|
200
|
+
*/
|
|
201
|
+
abstract readonly side: "readable" | "writable";
|
|
202
|
+
copying(): boolean;
|
|
203
|
+
drop(): void;
|
|
204
|
+
}
|
|
205
|
+
export declare class ReadableStreamEnd extends CopyEnd {
|
|
206
|
+
readonly kind = "stream";
|
|
207
|
+
readonly side = "readable";
|
|
208
|
+
readonly shared: SharedStreamImpl;
|
|
209
|
+
copy(inst: unknown, dst: GuestBuffer, onCopy: OnCopy, onCopyDone: OnCopyDone): void;
|
|
210
|
+
}
|
|
211
|
+
export declare class WritableStreamEnd extends CopyEnd {
|
|
212
|
+
readonly kind = "stream";
|
|
213
|
+
readonly side = "writable";
|
|
214
|
+
readonly shared: SharedStreamImpl;
|
|
215
|
+
copy(inst: unknown, src: GuestBuffer, onCopy: OnCopy, onCopyDone: OnCopyDone): void;
|
|
216
|
+
}
|
|
217
|
+
export declare class ReadableFutureEnd extends CopyEnd {
|
|
218
|
+
readonly kind = "future";
|
|
219
|
+
readonly side = "readable";
|
|
220
|
+
readonly shared: SharedFutureImpl;
|
|
221
|
+
copy(inst: unknown, dst: GuestBuffer, onCopyDone: OnCopyDone): void;
|
|
222
|
+
}
|
|
223
|
+
export declare class WritableFutureEnd extends CopyEnd {
|
|
224
|
+
readonly kind = "future";
|
|
225
|
+
readonly side = "writable";
|
|
226
|
+
readonly shared: SharedFutureImpl;
|
|
227
|
+
copy(inst: unknown, src: GuestBuffer, onCopyDone: OnCopyDone): void;
|
|
228
|
+
/**
|
|
229
|
+
* definitions.py `WritableFutureEnd.drop` (line 1183): a future's writable
|
|
230
|
+
* end may only be dropped once it has actually delivered its one value —
|
|
231
|
+
* `test/async/futures-must-write.wast` is the case this exists for.
|
|
232
|
+
*/
|
|
233
|
+
drop(): void;
|
|
234
|
+
}
|
|
235
|
+
/** The recorded poisoning failure for a shared stream/future value, if any. */
|
|
236
|
+
export declare function poisonFailureOf(shared: unknown): Error | undefined;
|
|
237
|
+
/**
|
|
238
|
+
* The trap a reader of an abandoned future observes at its rendezvous point.
|
|
239
|
+
*
|
|
240
|
+
* `Trap` is the guest-visible fault vocabulary (cabi/trap.ts); the recorded
|
|
241
|
+
* reason rides as `cause` so the embedder/host layers can still attribute the
|
|
242
|
+
* original fault. (`Trap`'s constructor takes only a message, so `cause` is
|
|
243
|
+
* attached after construction rather than through `ErrorOptions`.)
|
|
244
|
+
*/
|
|
245
|
+
export declare function futureAbandonTrap(reason: Error): Trap;
|
|
246
|
+
/** The abandonment reason of a shared future, if it has one (#84/#90). */
|
|
247
|
+
export declare function abandonReasonOf(shared: unknown): Error | null;
|
|
248
|
+
/**
|
|
249
|
+
* Mark a future's writable side as gone-without-a-value and settle the
|
|
250
|
+
* rendezvous (#90's host `drop()` door; the poisoning walk below routes
|
|
251
|
+
* through `dropSharedForTeardown` instead, which adds the dead-guest
|
|
252
|
+
* discipline).
|
|
253
|
+
*
|
|
254
|
+
* Never throws, and idempotent: a second call on an already-dropped future is
|
|
255
|
+
* a no-op, so `drop()`/`Symbol.dispose` at the layers above are total.
|
|
256
|
+
*/
|
|
257
|
+
export declare function abandonSharedFuture(shared: SharedFutureImpl, reason: Error): void;
|
|
258
|
+
/** The structural slice of `ComponentInstanceState` the walk needs. */
|
|
259
|
+
interface PoisonedInstanceLike {
|
|
260
|
+
readonly index?: number;
|
|
261
|
+
handles: Iterable<unknown>;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Drop a shared stream/future as *teardown*, without waking a doomed guest.
|
|
265
|
+
*
|
|
266
|
+
* Same outcome as `drop()` for host ends and healthy guest peers (a DROPPED
|
|
267
|
+
* notification), with one difference: a parked side belonging to a
|
|
268
|
+
* **poisoned** guest instance is retired silently via `resetPending`.
|
|
269
|
+
* Notifying it would queue a phantom event into the corpse's waitables, and
|
|
270
|
+
* a later driving loop servicing it would resume machinery whose instance
|
|
271
|
+
* can no longer be entered (`tick` asserts enterability). Host sentinels are
|
|
272
|
+
* not instances at all, so they are always notified.
|
|
273
|
+
*
|
|
274
|
+
* #100: THE HEALTH TEST IS "POISONED", NOT "`mayEnter === false`". The
|
|
275
|
+
* original test used non-enterability as a proxy for deadness. The proxy is
|
|
276
|
+
* unsound in one direction, and the unsoundness stranded healthy tasks:
|
|
277
|
+
*
|
|
278
|
+
* * (sound half, #84 audit) a healthy guest peer always parks with
|
|
279
|
+
* `mayEnter === true`. Every park — the callback ABI's waitable-set wait,
|
|
280
|
+
* and equally a sync-lowered/JSPI peer blocked inside `finishCopy`'s
|
|
281
|
+
* SITE 4 via `blockCurrentActivation` — yields the thread out of the
|
|
282
|
+
* scheduler's enter/leave bracket, and the bracket's `leaveTo` runs on the
|
|
283
|
+
* way out (task/scheduler.ts `Store.tick` :905-917, task/thread.ts
|
|
284
|
+
* `Thread.resumeWith` :157-179, whose resume-side
|
|
285
|
+
* `assert_(mayEnterFrom(null))` would fire otherwise). Blocking inside a
|
|
286
|
+
* wasm frame does NOT hold the enter bracket.
|
|
287
|
+
* * (unsound converse) `mayEnter === false` does not imply "poisoned". An
|
|
288
|
+
* instance that is merely mid-call is also non-enterable, and a CALLER
|
|
289
|
+
* instance stays non-enterable for the whole duration of a
|
|
290
|
+
* cross-component (FACT) call into an instance that traps
|
|
291
|
+
* (`ComponentInstanceState.enterFrom` clears `mayEnter` on the callee's
|
|
292
|
+
* entering set only, task/mod.ts). A *different*, healthy task of that
|
|
293
|
+
* caller, parked on an end of a stream/future the trapping callee also
|
|
294
|
+
* held, was classified dead here and retired silently — stranded, the
|
|
295
|
+
* exact outcome #66 exists to prevent.
|
|
296
|
+
*
|
|
297
|
+
* So the test consults the poison marker itself. It is per-instance and
|
|
298
|
+
* recorded at the single seam every bracket-break site routes through
|
|
299
|
+
* (`notifyInstancePoisoned`, task/scheduler.ts: exec/boundary.ts `poison`,
|
|
300
|
+
* `Store.tick`, `Thread.resumeWith`, the FACT cross-component catches in
|
|
301
|
+
* intrinsics/fact_calls.ts, and cabi/handles.ts's gated destructor call),
|
|
302
|
+
* and it is recorded *before* the retirement walk runs, so an instance's own
|
|
303
|
+
* parked ends still see it during its own walk. `retiredInstances` is
|
|
304
|
+
* consulted alongside it because the walk is also reachable directly (it is
|
|
305
|
+
* set at walk entry, so the two agree); neither ever contains the synthetic
|
|
306
|
+
* per-instantiation root, which every poison site skips or releases (plan v3
|
|
307
|
+
* amendment 4, `releaseSyntheticRootOnPoison`).
|
|
308
|
+
*
|
|
309
|
+
* Why this does not re-open review B2 (phantom events into a corpse): the
|
|
310
|
+
* concern is that a DROPPED event queued onto a waitable of an instance that
|
|
311
|
+
* can never be entered again would be serviced by a later driving loop and
|
|
312
|
+
* resume machinery whose `tick` asserts enterability. "Can never be entered
|
|
313
|
+
* again" is precisely poisoning — a mid-call instance's `mayEnter` is
|
|
314
|
+
* restored by its own `leaveTo` when the call returns, and its parked task
|
|
315
|
+
* then resumes normally and consumes the event. The narrowed predicate
|
|
316
|
+
* therefore excludes exactly the population B2 is about, and admits only
|
|
317
|
+
* peers that will run again.
|
|
318
|
+
*
|
|
319
|
+
* Used by the poisoning walk below and by the trapping-import abandonment
|
|
320
|
+
* path (embedder/instantiate.ts `releaseAsyncArgs`). Idempotent.
|
|
321
|
+
*/
|
|
322
|
+
export declare function dropSharedForTeardown(shared: SharedStreamImpl | SharedFutureImpl): void;
|
|
323
|
+
/**
|
|
324
|
+
* Retire every live stream/future end in a trap-poisoned instance's handle
|
|
325
|
+
* table (#66).
|
|
326
|
+
*
|
|
327
|
+
* Rationale: after a trap breaks the enter/leave bracket, `mayEnter` stays
|
|
328
|
+
* false forever, so no task of this instance can ever rendezvous again. Its
|
|
329
|
+
* table's `CopyEnd`s are therefore unreachable-forever — leaving their shared
|
|
330
|
+
* objects live strands the peers: a parked HOST operation never settles (its
|
|
331
|
+
* promise hangs), and a LATER host operation would "succeed" against the
|
|
332
|
+
* corpse (a copy into memory nothing will ever read — silent data loss).
|
|
333
|
+
* Dropping the shared object now converts both into the spec-shaped DROPPED
|
|
334
|
+
* outcome, and the recorded failure lets the embedder layer brand it.
|
|
335
|
+
*
|
|
336
|
+
* Called from every bracket-break site — exec/boundary.ts `poison()` (the
|
|
337
|
+
* sync-lift path), scheduler.ts `Store.tick` and thread.ts
|
|
338
|
+
* `Thread.resumeWith` (traps during a resumed thread), and the FACT
|
|
339
|
+
* cross-component catches (intrinsics/fact_calls.ts, callee side) — with the
|
|
340
|
+
* trap as `cause`. Idempotent per instance. The parked-side notification
|
|
341
|
+
* discipline lives in `dropSharedForTeardown` above.
|
|
342
|
+
*
|
|
343
|
+
* Two refinements over the original #66 walk, both from #84:
|
|
344
|
+
*
|
|
345
|
+
* 1. FUTURES ARE NOT STREAMS. A `stream`'s reader may legitimately observe
|
|
346
|
+
* DROPPED (that is end-of-stream), but a `future`'s reader may not
|
|
347
|
+
* (definitions.py:2607) — the reference keeps the state unreachable by
|
|
348
|
+
* trapping an early writable-end drop (definitions.py:1183-1184), which
|
|
349
|
+
* a poisoned instance can no longer be made to do. So an unwritten
|
|
350
|
+
* writable future end in this table marks its shared object *abandoned*
|
|
351
|
+
* (first pass below) and its reader traps instead. A writable end that
|
|
352
|
+
* already reached `CopyState.DONE` delivered its value; nothing is owed.
|
|
353
|
+
*
|
|
354
|
+
* 2. ONE END'S FAILURE MUST NOT STRAND THE REST. The notification of a
|
|
355
|
+
* retired end runs arbitrary peer callbacks (host settlers, event
|
|
356
|
+
* thunks); a throw used to abort the loop mid-table, leaving the
|
|
357
|
+
* remaining ends live and their peers hanging — exactly the outcome this
|
|
358
|
+
* walk exists to prevent. The walk now always completes and rethrows the
|
|
359
|
+
* first failure afterwards.
|
|
360
|
+
*/
|
|
361
|
+
export declare function retireInstanceAsyncEnds(inst: PoisonedInstanceLike, cause: unknown): void;
|
|
362
|
+
/**
|
|
363
|
+
* definitions.py models the debug message as a `String` triple; we keep the
|
|
364
|
+
* decoded JS string plus its encoding, which is all `store_string` needs.
|
|
365
|
+
*/
|
|
366
|
+
export declare class ErrorContext {
|
|
367
|
+
readonly debugMessage: string;
|
|
368
|
+
constructor(debugMessage: string);
|
|
369
|
+
}
|
|
370
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ResourceHandle } from "../cabi/handles.js";
|
|
2
|
+
import type { CoreValue } from "../cabi/types.js";
|
|
3
|
+
import { Waitable } from "./waitable.js";
|
|
4
|
+
/** definitions.py `Subtask.State` (line 859). */
|
|
5
|
+
export declare enum SubtaskState {
|
|
6
|
+
STARTING = 0,
|
|
7
|
+
STARTED = 1,
|
|
8
|
+
RETURNED = 2,
|
|
9
|
+
CANCELLED_BEFORE_STARTED = 3,
|
|
10
|
+
CANCELLED_BEFORE_RETURNED = 4
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Anything that can be lent to a callee. `ResourceHandle` is the only
|
|
14
|
+
* implementor today; typed structurally so cabi's borrow-scope interfaces
|
|
15
|
+
* keep working unchanged.
|
|
16
|
+
*/
|
|
17
|
+
export interface Lendable {
|
|
18
|
+
numLends: number;
|
|
19
|
+
}
|
|
20
|
+
/** Cancellation callback handed back by a lifted callee (`OnCancel`). */
|
|
21
|
+
export type OnCancel = (caller: any) => void;
|
|
22
|
+
export declare class Subtask extends Waitable {
|
|
23
|
+
state: SubtaskState;
|
|
24
|
+
onCancel: OnCancel | null;
|
|
25
|
+
cancellationRequested: boolean;
|
|
26
|
+
flatResults: CoreValue[];
|
|
27
|
+
/**
|
|
28
|
+
* The callee TASK behind this subtask, when there is one (FACT
|
|
29
|
+
* cross-component calls; host-import subtasks have none). `subtask.cancel`
|
|
30
|
+
* needs it under jspi: a cancellation delivered to a suspended activation
|
|
31
|
+
* resumes it on a MICROTASK (the engine's, not ours), so the async form
|
|
32
|
+
* must wait until the callee's state is determinate before choosing
|
|
33
|
+
* between BLOCKED and the resolved state — the same determinacy question
|
|
34
|
+
* `async-start-call` answers, and it needs the same object to ask it of.
|
|
35
|
+
*/
|
|
36
|
+
calleeTask: any;
|
|
37
|
+
/**
|
|
38
|
+
* Handles lent to the callee for the duration of the call. `null` once
|
|
39
|
+
* `deliverResolve` has run — the reference uses exactly this
|
|
40
|
+
* `lenders is None` sentinel to mean "resolve delivered" (line 908), so the
|
|
41
|
+
* nullability is semantic, not an optimization.
|
|
42
|
+
*/
|
|
43
|
+
lenders: Lendable[] | null;
|
|
44
|
+
/** definitions.py `Subtask.resolved` (line 880). */
|
|
45
|
+
resolved(): boolean;
|
|
46
|
+
/** definitions.py `Subtask.add_lender` (line 890). */
|
|
47
|
+
addLender(h: Lendable): void;
|
|
48
|
+
/** definitions.py `Subtask.resolve` (line 895). */
|
|
49
|
+
resolve(state: SubtaskState, flatResults: CoreValue[]): void;
|
|
50
|
+
/** definitions.py `Subtask.deliver_resolve` (line 902). */
|
|
51
|
+
deliverResolve(): void;
|
|
52
|
+
/** definitions.py `Subtask.resolve_delivered` (line 908). */
|
|
53
|
+
resolveDelivered(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Release a never-delivered subtask's lenders after its call broke off a
|
|
56
|
+
* non-poisoning exit — trap-rethrow past the CALLEE, capability bail, or
|
|
57
|
+
* an abandoned park (contracts/intrinsics.md v0.2 amendment 2, #91 scope
|
|
58
|
+
* clarification; the park legs are #102/#106).
|
|
59
|
+
*
|
|
60
|
+
* The reference has no analogue because it never resumes after a trap:
|
|
61
|
+
* the store dies with the lent handles inside it. The resolution state
|
|
62
|
+
* mirrors `canon_lower`'s `on_resolve(None)` branch (definitions.py
|
|
63
|
+
* line 2267): CANCELLED_BEFORE_STARTED if the callee never started,
|
|
64
|
+
* CANCELLED_BEFORE_RETURNED otherwise.
|
|
65
|
+
*
|
|
66
|
+
* Idempotent, and a no-op when the resolution was already delivered — a
|
|
67
|
+
* settled hook can call it unconditionally without disturbing the success
|
|
68
|
+
* path's own `deliverResolve`.
|
|
69
|
+
*/
|
|
70
|
+
unwindLenders(): void;
|
|
71
|
+
/** definitions.py `Subtask.drop` (line 912). */
|
|
72
|
+
drop(): void;
|
|
73
|
+
/**
|
|
74
|
+
* definitions.py `canon_lower`'s `on_progress`/`subtask_event` closure
|
|
75
|
+
* (lines 2297-2298). The event payload is computed **at delivery time** and
|
|
76
|
+
* delivering it is what runs `deliver_resolve` — so the lent handles are
|
|
77
|
+
* released exactly when the guest observes the resolution, not when it
|
|
78
|
+
* happens.
|
|
79
|
+
*
|
|
80
|
+
* The `!this.resolveDelivered()` guard has no reference analogue: it exists
|
|
81
|
+
* so this can coexist with `unwindLenders()` (which may itself have already
|
|
82
|
+
* delivered the resolve on an abandoned path). The reference's
|
|
83
|
+
* `subtask_event` calls `deliver_resolve()` unconditionally and would
|
|
84
|
+
* assert on a double delivery.
|
|
85
|
+
*/
|
|
86
|
+
setSubtaskPendingEvent(subtaski: number): void;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Pack a `canon_lower` async return value: `state | (subtaski << 4)`
|
|
90
|
+
* (definitions.py line 2306, with the accompanying asserts on the ranges).
|
|
91
|
+
*/
|
|
92
|
+
export declare function packSubtaskResult(state: SubtaskState, subtaski: number): number;
|
|
93
|
+
/** Inverse of {@link packSubtaskResult}; used by tests mirroring the reference. */
|
|
94
|
+
export declare function unpackSubtaskResult(packed: number): [state: SubtaskState, subtaski: number];
|
|
95
|
+
/** Re-exported for callers that only import from this module. */
|
|
96
|
+
export type { ResourceHandle };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type BlockRequest, type Cancelled, type SchedulableThread, type ThreadBody } from "./scheduler.js";
|
|
2
|
+
export declare class Thread implements SchedulableThread {
|
|
3
|
+
#private;
|
|
4
|
+
task: any;
|
|
5
|
+
/**
|
|
6
|
+
* Per-thread context slots (definitions.py `Thread.storage`, line 323 —
|
|
7
|
+
* initialised `[0,0]`). `canon_context_{get,set}` (lines 2348/2358) read and
|
|
8
|
+
* write *this*, not per-task state: two threads of the same task have
|
|
9
|
+
* independent context. wit-bindgen 0.60 keeps its async task pointer in
|
|
10
|
+
* slot 0.
|
|
11
|
+
*
|
|
12
|
+
* Slots are plain JS numbers: a `context.set` of an i64 value above
|
|
13
|
+
* 2^53-1 would lose precision. Moot while memory64/threads support is
|
|
14
|
+
* deferred (issue #12) — revisit this when that issue's closure lands.
|
|
15
|
+
*/
|
|
16
|
+
readonly storage: number[];
|
|
17
|
+
/**
|
|
18
|
+
* The FACT sync-call bracket stack for THIS activation.
|
|
19
|
+
*
|
|
20
|
+
* `enter-sync-call` pushes and `exit-sync-call` pops; FACT emits both from
|
|
21
|
+
* the same activation, so the activation is the continuity that makes this a
|
|
22
|
+
* stack. See the note on `Task.syncCallStack` for why per-task was not
|
|
23
|
+
* enough.
|
|
24
|
+
*/
|
|
25
|
+
readonly syncCallStack: any[];
|
|
26
|
+
/** Slot in `inst.threads`, assigned by `Task.registerThread`. */
|
|
27
|
+
index: number | null;
|
|
28
|
+
/** definitions.py `Thread.cancellable` — set at each block point. */
|
|
29
|
+
cancellable: boolean;
|
|
30
|
+
constructor(task: any, body: ThreadBody);
|
|
31
|
+
running(): boolean;
|
|
32
|
+
suspended(): boolean;
|
|
33
|
+
waiting(): boolean;
|
|
34
|
+
done(): boolean;
|
|
35
|
+
/** definitions.py `Thread.ready` (line 334). */
|
|
36
|
+
ready(): boolean;
|
|
37
|
+
/** definitions.py `Thread.resume_later` (line 361). */
|
|
38
|
+
resumeLater(): void;
|
|
39
|
+
/**
|
|
40
|
+
* definitions.py `Thread.resume` (line 366): run the body until it blocks
|
|
41
|
+
* again or finishes.
|
|
42
|
+
*
|
|
43
|
+
* The reference's loop over `switch_to` targets is omitted (see the module
|
|
44
|
+
* header). What remains is: leave the waiting list if we were on it, become
|
|
45
|
+
* the current thread, and step the generator with the cancelled flag.
|
|
46
|
+
*/
|
|
47
|
+
/** Pending `awaitValue` promise, if this thread is parked on one. */
|
|
48
|
+
awaiting: Promise<unknown> | null;
|
|
49
|
+
/** Resume a promise-parked thread with the settled result. */
|
|
50
|
+
resumeWith(value: unknown, failure?: {
|
|
51
|
+
error: unknown;
|
|
52
|
+
}): void;
|
|
53
|
+
resume(cancelled?: Cancelled): void;
|
|
54
|
+
/**
|
|
55
|
+
* definitions.py `Thread.wait_until` (line 396), as a generator-side helper.
|
|
56
|
+
*
|
|
57
|
+
* Call it from a thread body with `yield*`:
|
|
58
|
+
* `const cancelled = yield* thread.waitUntil(() => cond, true);`
|
|
59
|
+
*
|
|
60
|
+
* Deviation from the reference, deliberate: the reference may return
|
|
61
|
+
* immediately when `ready_func()` already holds
|
|
62
|
+
* (`if ready_func() and not DETERMINISTIC_PROFILE and random.randint(0,1)`).
|
|
63
|
+
* We always take the blocking path, i.e. we behave as the reference's
|
|
64
|
+
* `DETERMINISTIC_PROFILE`. Blocking-then-immediately-ready is observably
|
|
65
|
+
* equivalent (the scheduler will find this thread ready on the next
|
|
66
|
+
* candidate scan) and it removes a coin flip from every wait.
|
|
67
|
+
*/
|
|
68
|
+
waitUntil(readyFunc: () => boolean, cancellable?: boolean): Generator<BlockRequest, Cancelled, Cancelled>;
|
|
69
|
+
/** definitions.py `Thread.suspend` (line 390). */
|
|
70
|
+
suspend(cancellable: boolean): Generator<BlockRequest, Cancelled, Cancelled>;
|
|
71
|
+
/** definitions.py `Thread.yield_` (line 405): `wait_until(lambda: True)`. */
|
|
72
|
+
yield_(cancellable: boolean): Generator<BlockRequest, Cancelled, Cancelled>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { BlockRequest, Cancelled } from "./scheduler.js";
|
|
2
|
+
import type { Thread } from "./thread.js";
|
|
3
|
+
/** definitions.py `EventCode` (line 756). */
|
|
4
|
+
export declare enum EventCode {
|
|
5
|
+
NONE = 0,
|
|
6
|
+
SUBTASK = 1,
|
|
7
|
+
STREAM_READ = 2,
|
|
8
|
+
STREAM_WRITE = 3,
|
|
9
|
+
FUTURE_READ = 4,
|
|
10
|
+
FUTURE_WRITE = 5,
|
|
11
|
+
TASK_CANCELLED = 6
|
|
12
|
+
}
|
|
13
|
+
/** definitions.py `EventTuple` = `(EventCode, int, int)`. */
|
|
14
|
+
export type EventTuple = [code: EventCode, p1: number, p2: number];
|
|
15
|
+
export declare const NO_EVENT: EventTuple;
|
|
16
|
+
/**
|
|
17
|
+
* definitions.py `class Waitable` (line 767).
|
|
18
|
+
*
|
|
19
|
+
* The pending event is a **thunk**, not a value: the reference computes the
|
|
20
|
+
* payload at delivery time (`get_pending_event` calls it), which is what lets
|
|
21
|
+
* a subtask report its *final* state even if it advanced between the event
|
|
22
|
+
* being set and being read. Keeping the thunk is load-bearing — see
|
|
23
|
+
* `Subtask.setPendingEvent`.
|
|
24
|
+
*/
|
|
25
|
+
export declare class Waitable {
|
|
26
|
+
pendingEvent: (() => EventTuple) | null;
|
|
27
|
+
wset: WaitableSet | null;
|
|
28
|
+
hasSyncWaiter: boolean;
|
|
29
|
+
setPendingEvent(pendingEvent: () => EventTuple): void;
|
|
30
|
+
hasPendingEvent(): boolean;
|
|
31
|
+
inWaitableSet(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* definitions.py `Waitable.wait_for_pending_event` (line 786): a
|
|
34
|
+
* *non-cancellable* block until this waitable has an event, used by the
|
|
35
|
+
* synchronous `subtask.cancel` path.
|
|
36
|
+
*/
|
|
37
|
+
waitForPendingEvent(thread: Thread): Generator<BlockRequest, void, Cancelled>;
|
|
38
|
+
getPendingEvent(): EventTuple;
|
|
39
|
+
/** definitions.py `Waitable.join` (line 797). */
|
|
40
|
+
join(wset: WaitableSet | null): void;
|
|
41
|
+
/** definitions.py `Waitable.drop` (line 805). */
|
|
42
|
+
drop(): void;
|
|
43
|
+
}
|
|
44
|
+
/** definitions.py `class WaitableSet` (line 810). */
|
|
45
|
+
export declare class WaitableSet {
|
|
46
|
+
readonly elems: Waitable[];
|
|
47
|
+
numWaiting: number;
|
|
48
|
+
hasPendingEvent(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* definitions.py `WaitableSet.get_pending_event` (line 821). The reference
|
|
51
|
+
* shuffles `elems` before scanning; we scan in **join order** under the
|
|
52
|
+
* default FIFO policy (`chooseCandidate` over the ready elements), which is
|
|
53
|
+
* within the same allowed nondeterminism — see scheduler.ts's policy note.
|
|
54
|
+
*/
|
|
55
|
+
getPendingEvent(): EventTuple;
|
|
56
|
+
/** definitions.py `WaitableSet.wait_for_event_and` (line 829). */
|
|
57
|
+
waitForEventAnd(thread: Thread, readyFunc: () => boolean, cancellable: boolean): Generator<BlockRequest, EventTuple, Cancelled>;
|
|
58
|
+
/** definitions.py `WaitableSet.wait_for_event` (line 841). */
|
|
59
|
+
waitForEvent(thread: Thread, cancellable: boolean): Generator<BlockRequest, EventTuple, Cancelled>;
|
|
60
|
+
/**
|
|
61
|
+
* definitions.py `WaitableSet.poll` (line 844). Never blocks, so it is a
|
|
62
|
+
* plain function rather than a generator.
|
|
63
|
+
*/
|
|
64
|
+
poll(task: any, cancellable: boolean): EventTuple;
|
|
65
|
+
/** definitions.py `WaitableSet.drop` (line 852). */
|
|
66
|
+
drop(): void;
|
|
67
|
+
}
|