@polyengine/runtime 0.1.0-pre.g633468a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +23 -0
  3. package/esm/cabi/async_values.js +162 -0
  4. package/esm/cabi/bulk_lists.js +198 -0
  5. package/esm/cabi/context.js +42 -0
  6. package/esm/cabi/flatten.js +145 -0
  7. package/esm/cabi/float.js +67 -0
  8. package/esm/cabi/handles.js +253 -0
  9. package/esm/cabi/layout.js +149 -0
  10. package/esm/cabi/lift.js +196 -0
  11. package/esm/cabi/load.js +146 -0
  12. package/esm/cabi/lower.js +141 -0
  13. package/esm/cabi/memory.js +182 -0
  14. package/esm/cabi/mod.js +22 -0
  15. package/esm/cabi/store.js +186 -0
  16. package/esm/cabi/strings.js +336 -0
  17. package/esm/cabi/trap.js +38 -0
  18. package/esm/cabi/types.js +264 -0
  19. package/esm/cabi/values.js +64 -0
  20. package/esm/cache/core.js +156 -0
  21. package/esm/cache/dir.js +170 -0
  22. package/esm/cache/mod.js +4 -0
  23. package/esm/cache/web.js +136 -0
  24. package/esm/digest/digest.js +332 -0
  25. package/esm/digest/mod.js +3 -0
  26. package/esm/digest/verify.js +129 -0
  27. package/esm/embedder/casing.js +56 -0
  28. package/esm/embedder/copy.js +42 -0
  29. package/esm/embedder/errors.js +26 -0
  30. package/esm/embedder/imports.js +63 -0
  31. package/esm/embedder/instantiate.js +978 -0
  32. package/esm/embedder/mod.js +40 -0
  33. package/esm/embedder/resources.js +406 -0
  34. package/esm/embedder/streams.js +770 -0
  35. package/esm/embedder/values.js +450 -0
  36. package/esm/embedder/version.js +273 -0
  37. package/esm/exec/boundary.js +1902 -0
  38. package/esm/exec/executor.js +1004 -0
  39. package/esm/exec/host_streams.js +818 -0
  40. package/esm/exec/mod.js +4 -0
  41. package/esm/intrinsics/async_builtins.js +510 -0
  42. package/esm/intrinsics/context.js +90 -0
  43. package/esm/intrinsics/errors.js +13 -0
  44. package/esm/intrinsics/fact_calls.js +865 -0
  45. package/esm/intrinsics/mod.js +564 -0
  46. package/esm/intrinsics/stream_builtins.js +578 -0
  47. package/esm/intrinsics/transcode.js +443 -0
  48. package/esm/jspi/bridge.js +579 -0
  49. package/esm/jspi/mechanics.js +89 -0
  50. package/esm/jspi/mod.js +5 -0
  51. package/esm/jspi/suspending.js +15 -0
  52. package/esm/jspi/types.js +29 -0
  53. package/esm/package.json +3 -0
  54. package/esm/plan/format.js +5 -0
  55. package/esm/plan/loader.js +657 -0
  56. package/esm/plan/mod.js +3 -0
  57. package/esm/shim/mod.js +2 -0
  58. package/esm/shim/translator.js +94 -0
  59. package/esm/task/mod.js +483 -0
  60. package/esm/task/scheduler.js +1028 -0
  61. package/esm/task/streams.js +786 -0
  62. package/esm/task/subtask.js +135 -0
  63. package/esm/task/thread.js +255 -0
  64. package/esm/task/waitable.js +144 -0
  65. package/package.json +91 -0
  66. package/types/cabi/async_values.d.ts +35 -0
  67. package/types/cabi/bulk_lists.d.ts +18 -0
  68. package/types/cabi/context.d.ts +59 -0
  69. package/types/cabi/flatten.d.ts +14 -0
  70. package/types/cabi/float.d.ts +14 -0
  71. package/types/cabi/handles.d.ts +70 -0
  72. package/types/cabi/layout.d.ts +13 -0
  73. package/types/cabi/lift.d.ts +25 -0
  74. package/types/cabi/load.d.ts +14 -0
  75. package/types/cabi/lower.d.ts +10 -0
  76. package/types/cabi/memory.d.ts +58 -0
  77. package/types/cabi/mod.d.ts +15 -0
  78. package/types/cabi/store.d.ts +12 -0
  79. package/types/cabi/strings.d.ts +23 -0
  80. package/types/cabi/trap.d.ts +11 -0
  81. package/types/cabi/types.d.ts +206 -0
  82. package/types/cabi/values.d.ts +5 -0
  83. package/types/cache/core.d.ts +97 -0
  84. package/types/cache/dir.d.ts +6 -0
  85. package/types/cache/mod.d.ts +3 -0
  86. package/types/cache/web.d.ts +10 -0
  87. package/types/digest/digest.d.ts +17 -0
  88. package/types/digest/mod.d.ts +2 -0
  89. package/types/digest/verify.d.ts +48 -0
  90. package/types/embedder/casing.d.ts +40 -0
  91. package/types/embedder/copy.d.ts +24 -0
  92. package/types/embedder/errors.d.ts +11 -0
  93. package/types/embedder/imports.d.ts +47 -0
  94. package/types/embedder/instantiate.d.ts +88 -0
  95. package/types/embedder/mod.d.ts +11 -0
  96. package/types/embedder/resources.d.ts +158 -0
  97. package/types/embedder/streams.d.ts +202 -0
  98. package/types/embedder/values.d.ts +70 -0
  99. package/types/embedder/version.d.ts +85 -0
  100. package/types/exec/boundary.d.ts +360 -0
  101. package/types/exec/executor.d.ts +125 -0
  102. package/types/exec/host_streams.d.ts +165 -0
  103. package/types/exec/mod.d.ts +3 -0
  104. package/types/intrinsics/async_builtins.d.ts +69 -0
  105. package/types/intrinsics/context.d.ts +28 -0
  106. package/types/intrinsics/errors.d.ts +5 -0
  107. package/types/intrinsics/fact_calls.d.ts +120 -0
  108. package/types/intrinsics/mod.d.ts +187 -0
  109. package/types/intrinsics/stream_builtins.d.ts +113 -0
  110. package/types/intrinsics/transcode.d.ts +21 -0
  111. package/types/jspi/bridge.d.ts +227 -0
  112. package/types/jspi/mechanics.d.ts +50 -0
  113. package/types/jspi/mod.d.ts +3 -0
  114. package/types/jspi/suspending.d.ts +1 -0
  115. package/types/jspi/types.d.ts +26 -0
  116. package/types/plan/format.d.ts +369 -0
  117. package/types/plan/loader.d.ts +113 -0
  118. package/types/plan/mod.d.ts +2 -0
  119. package/types/shim/mod.d.ts +1 -0
  120. package/types/shim/translator.d.ts +55 -0
  121. package/types/task/mod.d.ts +257 -0
  122. package/types/task/scheduler.d.ts +421 -0
  123. package/types/task/streams.d.ts +370 -0
  124. package/types/task/subtask.d.ts +96 -0
  125. package/types/task/thread.d.ts +73 -0
  126. package/types/task/waitable.d.ts +67 -0
@@ -0,0 +1,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
+ }