@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,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
|
+
}
|