@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,578 @@
1
+ // The stream / future / error-context canonical built-ins
2
+ // (definitions.py `canon_stream_new` line 2504 through `canon_error_context_drop`
3
+ // line 2803).
4
+ //
5
+ // The copy built-ins all share one shape, which is worth stating once:
6
+ //
7
+ // 1. validate the end (right class, right element type, IDLE, and — for the
8
+ // synchronous form — not already in a waitable set);
9
+ // 2. build a `GuestBuffer` over the caller's memory;
10
+ // 3. set `CopyState.COPYING` and hand the buffer to the shared object, which
11
+ // either parks it or rendezvouses with the other side;
12
+ // 4. if an event landed synchronously (rendezvous happened, or the stream was
13
+ // already dropped) return its packed payload; otherwise the call is
14
+ // *blocked* — `BLOCKED` for the async form, and for the sync form a
15
+ // genuine wasm-frame block, i.e. JSPI.
16
+ //
17
+ // Step 4 is where the reference's `e.wait_for_pending_event()` sits. A
18
+ // stackless runtime cannot do that, so the sync form returns its answer when
19
+ // the rendezvous already happened and reports `NeedsJspi` otherwise — the same
20
+ // rule already applied to `waitable-set.wait` and `sync-start-call`.
21
+ import { blockCurrentActivation } from "../jspi/mod.js";
22
+ import { assert_, trapIf } from "../cabi/trap.js";
23
+ import { errorContextTrapMessage } from "../cabi/async_values.js";
24
+ import { LiftLowerContext } from "../cabi/context.js";
25
+ import { loadStringFromRange, storeString } from "../cabi/strings.js";
26
+ import { abandonReasonOf, BUFFER_MAX_LENGTH, CopyResult, CopyState, currentInstance, currentTask, ErrorContext, EventCode, futureAbandonTrap, GuestBuffer, needsJspi, ReadableFutureEnd, ReadableStreamEnd, SharedFutureImpl, SharedStreamImpl, sameElemType as sameElem, WritableFutureEnd, WritableStreamEnd, } from "../task/mod.js";
27
+ import { cabiOptions } from "../exec/boundary.js";
28
+ import { BLOCKED } from "./async_builtins.js";
29
+ /**
30
+ * Standing probe (CE_COPY_TRACE=1): per-call return codes of the copy /
31
+ * cancel built-ins, in both modes. This is the plain-vs-jspi differential's
32
+ * data source — a divergence list of packed codes beats stack traces when the
33
+ * guest asserts exact `expect-code` values (big-interleaving-test.wast).
34
+ */
35
+ const COPY_TRACE = (() => {
36
+ try {
37
+ return Deno.env.get("CE_COPY_TRACE") === "1";
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ })();
43
+ export function traceCopy(msg) {
44
+ if (COPY_TRACE)
45
+ console.error(`[copy] ${msg}`);
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // stream.new / future.new
49
+ // ---------------------------------------------------------------------------
50
+ /**
51
+ * definitions.py `canon_stream_new` (line 2504) / `canon_future_new` (2512).
52
+ * Returns both handles packed into an i64: `ri | (wi << 32)`.
53
+ */
54
+ export function createStreamNew(decl, ctx, inst) {
55
+ return () => {
56
+ trapIf(!inst.mayLeave, "stream.new: cannot leave component instance");
57
+ const shared = new SharedStreamImpl(ctx.streamElem(decl.streamTable));
58
+ const ri = inst.handles.add(new ReadableStreamEnd(shared));
59
+ const wi = inst.handles.add(new WritableStreamEnd(shared));
60
+ return packEnds(ri, wi);
61
+ };
62
+ }
63
+ export function createFutureNew(decl, ctx, inst) {
64
+ return () => {
65
+ trapIf(!inst.mayLeave, "future.new: cannot leave component instance");
66
+ const shared = new SharedFutureImpl(ctx.futureElem(decl.futureTable));
67
+ const ri = inst.handles.add(new ReadableFutureEnd(shared));
68
+ const wi = inst.handles.add(new WritableFutureEnd(shared));
69
+ return packEnds(ri, wi);
70
+ };
71
+ }
72
+ /** `ri | (wi << 32)` as an i64 core value. */
73
+ function packEnds(ri, wi) {
74
+ return BigInt(ri >>> 0) | (BigInt(wi >>> 0) << 32n);
75
+ }
76
+ /** definitions.py `stream_copy` (line 2530). */
77
+ function streamCopy(input) {
78
+ const { EndT, reading, eventCode, elem, opts, inst, i, ptr, n } = input;
79
+ const mode = input.mode ?? "plain";
80
+ trapIf(!inst.mayLeave, "stream copy: cannot leave component instance");
81
+ const e = inst.handles.get(i);
82
+ trapIf(!(e instanceof EndT), "stream copy: wrong end type for this handle");
83
+ const end = e;
84
+ trapIf(!sameElem(end.shared.t, elem), "stream copy: element type mismatch");
85
+ // wasmtime distinguishes the two non-IDLE states in its message, and the
86
+ // suite asserts the exact text: DONE means the other end has gone away (or
87
+ // this end's single-shot operation already finished), COPYING means the
88
+ // guest issued a second operation while one was in flight.
89
+ trapIf(end.state === CopyState.DONE, reading
90
+ ? "cannot read from stream after being notified that the writable end dropped"
91
+ : "cannot write to stream after being notified that the readable end dropped");
92
+ trapIf(end.state !== CopyState.IDLE, "cannot have concurrent operations active on a future/stream");
93
+ trapIf(end.inWaitableSet() && !opts.async, "synchronous stream copy on an end that is in a waitable set");
94
+ const cx = new LiftLowerContext(cabiOptions(opts), inst, null);
95
+ const buffer = new GuestBuffer(elem, cx, ptr, n);
96
+ // definitions.py `stream_copy`: `assert(not isinstance(stream_t, CharType))`
97
+ // — plan validation is expected to reject a char-typed stream before this
98
+ // point (streams of `char` are not a representable component type), so this
99
+ // documents that invariant rather than defending against a reachable case.
100
+ assert_(elem === null || elem.kind !== "char", "stream copy: char element");
101
+ // definitions.py `stream_event`: the payload is computed at *delivery* time,
102
+ // so `buffer.progress` reflects everything copied by the time the guest
103
+ // looks — including copies that happened after the event was armed.
104
+ const streamEvent = (result, reclaim) => {
105
+ reclaim();
106
+ assert_(end.copying(), "stream event on a non-copying end");
107
+ end.state = result === CopyResult.DROPPED
108
+ ? CopyState.DONE
109
+ : CopyState.IDLE;
110
+ assert_(buffer.progress <= BUFFER_MAX_LENGTH, "stream progress out of packing range");
111
+ // definitions.py `stream_copy`/`stream_event`: `assert(0 <= result < 2**4)`.
112
+ // `CopyResult` is a fixed 0..2 enum so this can't fire; kept for parity.
113
+ assert_(result >= 0 && result < 2 ** 4, "stream event: packed result out of 4-bit range");
114
+ return [eventCode, i, (result | (buffer.progress << 4)) >>> 0];
115
+ };
116
+ end.state = CopyState.COPYING;
117
+ const onCopy = (reclaim) => end.setPendingEvent(() => streamEvent(CopyResult.COMPLETED, reclaim));
118
+ const onCopyDone = (result) => end.setPendingEvent(() => streamEvent(result, () => { }));
119
+ if (reading) {
120
+ end.copy(inst, buffer, onCopy, onCopyDone);
121
+ }
122
+ else {
123
+ end.copy(inst, buffer, onCopy, onCopyDone);
124
+ }
125
+ return finishCopy(end, eventCode, i, opts.async, "stream", inst, mode);
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // future.{read,write}
129
+ // ---------------------------------------------------------------------------
130
+ /** definitions.py `future_copy` (line 2584). */
131
+ function futureCopy(input) {
132
+ const { EndT, reading, eventCode, elem, opts, inst, i, ptr } = input;
133
+ const mode = input.mode ?? "plain";
134
+ trapIf(!inst.mayLeave, "future copy: cannot leave component instance");
135
+ const e = inst.handles.get(i);
136
+ trapIf(!(e instanceof EndT), "future copy: wrong end type for this handle");
137
+ const end = e;
138
+ trapIf(!sameElem(end.shared.t, elem), "future copy: element type mismatch");
139
+ // The writable side reaches DONE by two different routes — its own write
140
+ // completed, or it was notified the readable end dropped — and wasmtime's
141
+ // message names both (`futures_and_streams.rs:3522`). The shorter text the
142
+ // other two assertions in `trap-if-done.wast` use (:446, :448) is a prefix
143
+ // of this one, so the single full string satisfies all three.
144
+ trapIf(end.state === CopyState.DONE, reading
145
+ ? "cannot read from future after previous read succeeded"
146
+ : "cannot write to future after previous write succeeded or readable end dropped");
147
+ trapIf(end.state !== CopyState.IDLE, "cannot have concurrent operations active on a future/stream");
148
+ trapIf(end.inWaitableSet() && !opts.async, "synchronous future copy on an end that is in a waitable set");
149
+ const cx = new LiftLowerContext(cabiOptions(opts), inst, null);
150
+ const buffer = new GuestBuffer(elem, cx, ptr, 1);
151
+ const futureEvent = (result) => {
152
+ assert_((buffer.remain() === 0) === (result === CopyResult.COMPLETED), "future event/progress disagreement");
153
+ assert_(end.copying(), "future event on a non-copying end");
154
+ // A future is single-shot: both COMPLETED and DROPPED retire the end.
155
+ end.state = result === CopyResult.DROPPED || result === CopyResult.COMPLETED
156
+ ? CopyState.DONE
157
+ : CopyState.IDLE;
158
+ return [eventCode, i, result];
159
+ };
160
+ end.state = CopyState.COPYING;
161
+ const onCopyDone = (result) => {
162
+ // #84/#90: an unwritten future whose writable side was torn down (a
163
+ // trap-poisoned instance's table, or the host's `drop()` door) can never
164
+ // satisfy this reader. definitions.py keeps that state unreachable
165
+ // (:1183-1184 traps the early writable drop, so :2607 may assert a
166
+ // readable end never sees DROPPED); where we bypass the trap we owe the
167
+ // reader a *trap at its rendezvous point* instead of a DROPPED answer.
168
+ //
169
+ // The pending event stays a thunk, so the trap is raised exactly where
170
+ // the reader observes it: `waitable-set.wait`'s delivery (both the
171
+ // fast-path and the JSPI `produce`, intrinsics/async_builtins.ts:290/311),
172
+ // the callback loop's `waitForEventAnd` (exec/boundary.ts:1766), and
173
+ // `finishCopy`'s `take()` below — every one of which is inside the
174
+ // reader's guest activation, so the throw propagates as that task's trap
175
+ // and poisons *its* instance, and nothing else.
176
+ const abandoned = reading && result === CopyResult.DROPPED
177
+ ? abandonReasonOf(end.shared)
178
+ : null;
179
+ if (abandoned !== null) {
180
+ end.setPendingEvent(() => {
181
+ throw futureAbandonTrap(abandoned);
182
+ });
183
+ return;
184
+ }
185
+ assert_(result !== CopyResult.DROPPED || eventCode === EventCode.FUTURE_WRITE, "a readable future end cannot observe DROPPED");
186
+ end.setPendingEvent(() => futureEvent(result));
187
+ };
188
+ if (reading) {
189
+ end.copy(inst, buffer, onCopyDone);
190
+ }
191
+ else {
192
+ end.copy(inst, buffer, onCopyDone);
193
+ }
194
+ return finishCopy(end, eventCode, i, opts.async, "future", inst, mode);
195
+ }
196
+ /**
197
+ * The shared tail of `stream_copy` / `future_copy`: deliver the event if one
198
+ * landed, else block.
199
+ */
200
+ function finishCopy(end, eventCode, i, async_, what, inst, mode = "plain") {
201
+ const take = () => {
202
+ const [code, index, payload] = end.getPendingEvent();
203
+ assert_(code === eventCode && index === i, `unexpected event delivered by a ${what} copy`);
204
+ return payload;
205
+ };
206
+ if (!end.hasPendingEvent()) {
207
+ if (!async_) {
208
+ // definitions.py `e.wait_for_pending_event()`: block this wasm frame
209
+ // until the other end shows up.
210
+ if (mode === "jspi" && inst !== undefined) {
211
+ // SITE 4 (lit). `hasSyncWaiter` marks the end as having a blocked
212
+ // synchronous reader/writer, which is what makes a concurrent
213
+ // `cancel-copy` on it a trap (see `cancelCopy`). Setting it only now
214
+ // is correct: before this point nothing was actually waiting.
215
+ end.hasSyncWaiter = true;
216
+ traceCopy(`${what} sync copy i=${i} BLOCKS`);
217
+ return blockCurrentActivation({
218
+ store: inst.store,
219
+ task: currentTask(),
220
+ readyFunc: () => end.hasPendingEvent(),
221
+ cancellable: false,
222
+ produce: () => {
223
+ end.hasSyncWaiter = false;
224
+ const p = take();
225
+ traceCopy(`${what} sync copy i=${i} RESUME -> 0x${p.toString(16)}`);
226
+ return p;
227
+ },
228
+ // #106: `abandon` never runs `produce`; without the backstop the
229
+ // flag stayed set forever and a later `cancel-copy` on this end
230
+ // trapped "sync waiter" against a waiter that no longer exists.
231
+ // Idempotent, so the success path's clear-before-`take` ordering
232
+ // inside `produce` is untouched.
233
+ onSettled: () => {
234
+ end.hasSyncWaiter = false;
235
+ },
236
+ });
237
+ }
238
+ needsJspi(`synchronous ${what} copy with no counterpart ready (the calling ` +
239
+ `wasm frame must block until the other end arrives)`);
240
+ }
241
+ traceCopy(`${what} async copy i=${i} -> BLOCKED`);
242
+ return BLOCKED;
243
+ }
244
+ const p = take();
245
+ traceCopy(`${what} copy(async=${async_}) i=${i} -> 0x${p.toString(16)}`);
246
+ return p;
247
+ }
248
+ // ---------------------------------------------------------------------------
249
+ // cancel-{read,write}
250
+ // ---------------------------------------------------------------------------
251
+ /**
252
+ * `cancel_copy`'s reporting tail, shared by its immediate and blocking exits.
253
+ * Kept verbatim (including the wasmtime divergence below) so the blocking form
254
+ * cannot drift from the non-blocking one.
255
+ */
256
+ function takeCancelEvent(end, eventCode, i, what) {
257
+ const [code, index, payload] = end.getPendingEvent();
258
+ assert_(!end.copying() && code === eventCode && index === i, `unexpected event delivered by ${what}`);
259
+ // UPSTREAM DIVERGENCE (definitions.py is wrong here; wasmtime is right).
260
+ //
261
+ // `cancel_copy` (definitions.py line 2654) returns an already-armed pending
262
+ // event verbatim, so cancelling a stream write that had been partially
263
+ // satisfied yields COMPLETED with the copied count. wasmtime instead
264
+ // *supersedes* an undelivered stream COMPLETED with CANCELLED, keeping the
265
+ // count (`futures_and_streams.rs:4004-4015`):
266
+ //
267
+ // (ReturnCode::Completed(count), Event::StreamWrite { .. })
268
+ // => ReturnCode::Cancelled(count),
269
+ // (ReturnCode::Dropped(_) | ReturnCode::Completed(_), _) => code,
270
+ //
271
+ // and `test/async/big-interleaving-test.wast:1526-1531` asserts wasmtime's
272
+ // answer (0x42 = CANCELLED | 4<<4, not 0x40). The reasoning is sound: the
273
+ // guest never observed the completion, so reporting it as completed would
274
+ // lose the fact that the operation was cancelled. Note the two exclusions
275
+ // encoded below — DROPPED keeps its code, and a *future* COMPLETED keeps
276
+ // its code (only `Event::Stream{Read,Write}` is converted).
277
+ const isStreamEvent = eventCode === EventCode.STREAM_READ ||
278
+ eventCode === EventCode.STREAM_WRITE;
279
+ if (isStreamEvent && (payload & 0xf) === CopyResult.COMPLETED) {
280
+ const p = ((payload & ~0xf) | CopyResult.CANCELLED) >>> 0;
281
+ traceCopy(`${what} i=${i} -> 0x${p.toString(16)} (superseded COMPLETED)`);
282
+ return p;
283
+ }
284
+ traceCopy(`${what} i=${i} -> 0x${payload.toString(16)}`);
285
+ return payload;
286
+ }
287
+ /** definitions.py `cancel_copy` (line 2636). */
288
+ function cancelCopy(input) {
289
+ const { EndT, eventCode, elem, inst, async_, i, what } = input;
290
+ const mode = input.mode ?? "plain";
291
+ trapIf(!inst.mayLeave, `${what}: cannot leave component instance`);
292
+ const e = inst.handles.get(i);
293
+ trapIf(!(e instanceof EndT), `${what}: wrong end type for this handle`);
294
+ const end = e;
295
+ trapIf(!sameElem(end.shared.t, elem), `${what}: element type mismatch`);
296
+ trapIf(end.state !== CopyState.COPYING || end.hasSyncWaiter, `${what}: end is not in a cancellable copy`);
297
+ trapIf(end.inWaitableSet() && !async_, `${what}: synchronous cancel on an end that is in a waitable set`);
298
+ end.state = CopyState.CANCELLING_COPY;
299
+ if (!end.hasPendingEvent()) {
300
+ end.shared.cancel();
301
+ if (!end.hasPendingEvent()) {
302
+ if (!async_) {
303
+ if (mode === "jspi") {
304
+ // SITE 4b (lit): definitions.py `cancel_copy` blocks until the
305
+ // cancellation settles, then reports through the same tail.
306
+ return blockCurrentActivation({
307
+ store: inst.store,
308
+ task: currentTask(),
309
+ readyFunc: () => end.hasPendingEvent(),
310
+ cancellable: false,
311
+ produce: () => takeCancelEvent(end, eventCode, i, what),
312
+ });
313
+ }
314
+ needsJspi(`synchronous ${what} whose copy did not settle immediately (the ` +
315
+ `calling wasm frame must block)`);
316
+ }
317
+ return BLOCKED;
318
+ }
319
+ }
320
+ return takeCancelEvent(end, eventCode, i, what);
321
+ }
322
+ // ---------------------------------------------------------------------------
323
+ // drop-{readable,writable}
324
+ // ---------------------------------------------------------------------------
325
+ /** definitions.py `drop` (line 2670). */
326
+ function dropEnd(EndT, elem, inst, hi, what) {
327
+ trapIf(!inst.mayLeave, `${what}: cannot leave component instance`);
328
+ const e = inst.handles.remove(hi);
329
+ trapIf(!(e instanceof EndT), `${what}: wrong end type for this handle`);
330
+ const end = e;
331
+ trapIf(!sameElem(end.shared.t, elem), `${what}: element type mismatch`);
332
+ end.drop();
333
+ }
334
+ // ---------------------------------------------------------------------------
335
+ // error-context
336
+ // ---------------------------------------------------------------------------
337
+ /**
338
+ * definitions.py `canon_error_context_new` (line 2778).
339
+ *
340
+ * The reference is deliberately non-committal about the message: under
341
+ * `DETERMINISTIC_PROFILE` it stores the empty string, otherwise it may apply a
342
+ * `host_defined_transformation`. We keep the guest's message verbatim — the
343
+ * most useful behaviour for a debugging aid, and within what the spec allows
344
+ * (the message is explicitly not semantically load-bearing).
345
+ */
346
+ export function createErrorContextNew(decl, ctx, inst) {
347
+ const opts = ctx.options(decl.options);
348
+ return (ptr, taggedCodeUnits) => {
349
+ trapIf(!inst.mayLeave, "error-context.new: cannot leave component instance");
350
+ const cx = new LiftLowerContext(cabiOptions(opts), inst, null);
351
+ const s = loadStringFromRange(cx, ptr ?? 0, taggedCodeUnits ?? 0);
352
+ return inst.handles.add(new ErrorContext(s));
353
+ };
354
+ }
355
+ /** definitions.py `canon_error_context_debug_message` (line 2792). */
356
+ export function createErrorContextDebugMessage(decl, ctx, inst) {
357
+ const opts = ctx.options(decl.options);
358
+ return (i, ptr) => {
359
+ trapIf(!inst.mayLeave, "error-context.debug-message: cannot leave component instance");
360
+ const e = inst.handles.get(i ?? 0);
361
+ trapIf(!(e instanceof ErrorContext), errorContextTrapMessage("error-context.debug-message", e));
362
+ const cx = new LiftLowerContext(cabiOptions(opts), inst, null);
363
+ storeString(cx, e.debugMessage, ptr ?? 0);
364
+ };
365
+ }
366
+ /** definitions.py `canon_error_context_drop` (line 2803). */
367
+ export function createErrorContextDrop(inst) {
368
+ return (i) => {
369
+ trapIf(!inst.mayLeave, "error-context.drop: cannot leave component instance");
370
+ const e = inst.handles.remove(i ?? 0);
371
+ trapIf(!(e instanceof ErrorContext), errorContextTrapMessage("error-context.drop", e));
372
+ };
373
+ }
374
+ // ---------------------------------------------------------------------------
375
+ // Trampoline factories
376
+ // ---------------------------------------------------------------------------
377
+ export function createStreamRead(d, ctx, inst) {
378
+ const opts = ctx.options(d.options);
379
+ const elem = ctx.streamElem(d.streamTable);
380
+ return (i, ptr, n) => streamCopy({
381
+ mode: ctx.suspensionMode ?? "plain",
382
+ EndT: ReadableStreamEnd,
383
+ reading: true,
384
+ eventCode: EventCode.STREAM_READ,
385
+ elem,
386
+ opts,
387
+ inst,
388
+ i: i ?? 0,
389
+ ptr: ptr ?? 0,
390
+ n: n ?? 0,
391
+ });
392
+ }
393
+ export function createStreamWrite(d, ctx, inst) {
394
+ const opts = ctx.options(d.options);
395
+ const elem = ctx.streamElem(d.streamTable);
396
+ return (i, ptr, n) => streamCopy({
397
+ mode: ctx.suspensionMode ?? "plain",
398
+ EndT: WritableStreamEnd,
399
+ reading: false,
400
+ eventCode: EventCode.STREAM_WRITE,
401
+ elem,
402
+ opts,
403
+ inst,
404
+ i: i ?? 0,
405
+ ptr: ptr ?? 0,
406
+ n: n ?? 0,
407
+ });
408
+ }
409
+ export function createFutureRead(d, ctx, inst) {
410
+ const opts = ctx.options(d.options);
411
+ const elem = ctx.futureElem(d.futureTable);
412
+ return (i, ptr) => futureCopy({
413
+ mode: ctx.suspensionMode ?? "plain",
414
+ EndT: ReadableFutureEnd,
415
+ reading: true,
416
+ eventCode: EventCode.FUTURE_READ,
417
+ elem,
418
+ opts,
419
+ inst,
420
+ i: i ?? 0,
421
+ ptr: ptr ?? 0,
422
+ });
423
+ }
424
+ export function createFutureWrite(d, ctx, inst) {
425
+ const opts = ctx.options(d.options);
426
+ const elem = ctx.futureElem(d.futureTable);
427
+ return (i, ptr) => futureCopy({
428
+ mode: ctx.suspensionMode ?? "plain",
429
+ EndT: WritableFutureEnd,
430
+ reading: false,
431
+ eventCode: EventCode.FUTURE_WRITE,
432
+ elem,
433
+ opts,
434
+ inst,
435
+ i: i ?? 0,
436
+ ptr: ptr ?? 0,
437
+ });
438
+ }
439
+ export function createStreamCancelRead(d, ctx, inst) {
440
+ const elem = ctx.streamElem(d.streamTable);
441
+ return (i) => cancelCopy({
442
+ mode: ctx.suspensionMode ?? "plain",
443
+ EndT: ReadableStreamEnd,
444
+ eventCode: EventCode.STREAM_READ,
445
+ elem,
446
+ inst,
447
+ async_: d.async === true,
448
+ i: i ?? 0,
449
+ what: "stream.cancel-read",
450
+ });
451
+ }
452
+ export function createStreamCancelWrite(d, ctx, inst) {
453
+ const elem = ctx.streamElem(d.streamTable);
454
+ return (i) => cancelCopy({
455
+ mode: ctx.suspensionMode ?? "plain",
456
+ EndT: WritableStreamEnd,
457
+ eventCode: EventCode.STREAM_WRITE,
458
+ elem,
459
+ inst,
460
+ async_: d.async === true,
461
+ i: i ?? 0,
462
+ what: "stream.cancel-write",
463
+ });
464
+ }
465
+ export function createFutureCancelRead(d, ctx, inst) {
466
+ const elem = ctx.futureElem(d.futureTable);
467
+ return (i) => cancelCopy({
468
+ mode: ctx.suspensionMode ?? "plain",
469
+ EndT: ReadableFutureEnd,
470
+ eventCode: EventCode.FUTURE_READ,
471
+ elem,
472
+ inst,
473
+ async_: d.async === true,
474
+ i: i ?? 0,
475
+ what: "future.cancel-read",
476
+ });
477
+ }
478
+ export function createFutureCancelWrite(d, ctx, inst) {
479
+ const elem = ctx.futureElem(d.futureTable);
480
+ return (i) => cancelCopy({
481
+ mode: ctx.suspensionMode ?? "plain",
482
+ EndT: WritableFutureEnd,
483
+ eventCode: EventCode.FUTURE_WRITE,
484
+ elem,
485
+ inst,
486
+ async_: d.async === true,
487
+ i: i ?? 0,
488
+ what: "future.cancel-write",
489
+ });
490
+ }
491
+ export function createStreamDropReadable(d, ctx, inst) {
492
+ const elem = ctx.streamElem(d.streamTable);
493
+ return (i) => dropEnd(ReadableStreamEnd, elem, inst, i ?? 0, "stream.drop-readable");
494
+ }
495
+ export function createStreamDropWritable(d, ctx, inst) {
496
+ const elem = ctx.streamElem(d.streamTable);
497
+ return (i) => dropEnd(WritableStreamEnd, elem, inst, i ?? 0, "stream.drop-writable");
498
+ }
499
+ export function createFutureDropReadable(d, ctx, inst) {
500
+ const elem = ctx.futureElem(d.futureTable);
501
+ return (i) => dropEnd(ReadableFutureEnd, elem, inst, i ?? 0, "future.drop-readable");
502
+ }
503
+ export function createFutureDropWritable(d, ctx, inst) {
504
+ const elem = ctx.futureElem(d.futureTable);
505
+ return (i) => dropEnd(WritableFutureEnd, elem, inst, i ?? 0, "future.drop-writable");
506
+ }
507
+ /** Unused-import guards. */
508
+ void currentInstance;
509
+ void currentTask;
510
+ function transferAsyncEnd(input) {
511
+ const { EndT, srcInst, dstInst, srcElem, dstElem, srcIdx, what } = input;
512
+ const e = srcInst.handles.remove(srcIdx);
513
+ trapIf(!(e instanceof EndT), `${what}: handle is not a readable ${what} end`);
514
+ const end = e;
515
+ trapIf(!sameElem(end.shared.t, srcElem), `${what}: source element mismatch`);
516
+ trapIf(!sameElem(end.shared.t, dstElem), `${what}: destination element mismatch`);
517
+ // definitions.py `lift_async_value`: an end that is mid-copy or parked in a
518
+ // waitable set cannot be handed on. The messages match the suite's
519
+ // `assert_trap` text.
520
+ trapIf(end.state === CopyState.DONE, what === "future"
521
+ ? "cannot lift future after previous read succeeded"
522
+ : "cannot lift stream after being notified that the writable end dropped");
523
+ trapIf(end.state !== CopyState.IDLE, `cannot remove busy ${what}`);
524
+ trapIf(end.inWaitableSet(), `cannot lift ${what} while it's in a waitable set`);
525
+ const Ctor = EndT;
526
+ return dstInst.handles.add(new Ctor(end.shared));
527
+ }
528
+ export function createStreamTransfer(ctx) {
529
+ return (srcIdx, srcTable, dstTable) => transferAsyncEnd({
530
+ EndT: ReadableStreamEnd,
531
+ srcInst: ctx.streamTableInstance(srcTable ?? 0),
532
+ dstInst: ctx.streamTableInstance(dstTable ?? 0),
533
+ srcElem: ctx.streamElem(srcTable ?? 0),
534
+ dstElem: ctx.streamElem(dstTable ?? 0),
535
+ srcIdx: srcIdx ?? 0,
536
+ what: "stream",
537
+ });
538
+ }
539
+ export function createFutureTransfer(ctx) {
540
+ return (srcIdx, srcTable, dstTable) => transferAsyncEnd({
541
+ EndT: ReadableFutureEnd,
542
+ srcInst: ctx.futureTableInstance(srcTable ?? 0),
543
+ dstInst: ctx.futureTableInstance(dstTable ?? 0),
544
+ srcElem: ctx.futureElem(srcTable ?? 0),
545
+ dstElem: ctx.futureElem(dstTable ?? 0),
546
+ srcIdx: srcIdx ?? 0,
547
+ what: "future",
548
+ });
549
+ }
550
+ /**
551
+ * error-context transfer. Unlike stream/future ends, an `error-context` is
552
+ * shareable: `lift_error_context` (definitions.py line 1451) *reads* the handle
553
+ * rather than removing it, so the source keeps its own.
554
+ */
555
+ /**
556
+ * Plan v3 (contracts/plan-format.md v3 amendment 2): `instanceOf` resolves
557
+ * through the plan's `errorContextTables` section — the
558
+ * `TypeComponentLocalErrorContextTableIndex` space these arguments actually
559
+ * live in. It replaced a resource-table lookup, which shared neither the
560
+ * index space nor (in a multi-instance composition) the answer.
561
+ *
562
+ * The arguments are the trampoline's own core parameters, so a missing one is
563
+ * an arity fault, not a zero: no `?? 0` defaults — `instanceOf(undefined!)`
564
+ * would be a silent table-0 read. `assert_` instead, and the accessor itself
565
+ * raises a `PlanError` for an out-of-range table.
566
+ */
567
+ export function createErrorContextTransfer(ctx, instanceOf) {
568
+ void ctx;
569
+ return (srcIdx, srcTable, dstTable) => {
570
+ assert_(typeof srcIdx === "number" && typeof srcTable === "number" &&
571
+ typeof dstTable === "number", "error-context transfer: expected (handle, srcTable, dstTable)");
572
+ const srcInst = instanceOf(srcTable);
573
+ const dstInst = instanceOf(dstTable);
574
+ const e = srcInst.handles.get(srcIdx);
575
+ trapIf(!(e instanceof ErrorContext), errorContextTrapMessage("error-context transfer", e));
576
+ return dstInst.handles.add(e);
577
+ };
578
+ }