@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,564 @@
1
+ // Host trampolines (contracts/intrinsics.md §B) and FACT-adapter intrinsic
2
+ // obligations (§A) — the M0 subset, with instantiate-time (never call-time)
3
+ // milestone-aware failures for everything else.
4
+ //
5
+ // Implemented in M0:
6
+ // lower-import host function call through descriptor-IR lift/lower
7
+ // trap FACT `Trap` import -> ComponentTrap
8
+ // enter/exit-sync-call degenerate sync-call bookkeeping (assert-and-count)
9
+ // resource-new/rep/drop sync resource paths over cabi handle tables
10
+ // (M1 schedule, implemented early: the resources
11
+ // fixture references them at instantiation)
12
+ //
13
+ // Everything else fails at instantiate time with the milestone at which
14
+ // intrinsics.md §B schedules it — "this component needs the M2 task core"
15
+ // is a feature, not a crash.
16
+ import { canonResourceDrop, canonResourceNew, canonResourceRep, trap, } from "../cabi/mod.js";
17
+ import { ResourceHandle } from "../cabi/handles.js";
18
+ import { trapIf } from "../cabi/trap.js";
19
+ import { assert_ } from "../cabi/trap.js";
20
+ import { maybeCurrentThread, PendingCapability, withPoisonCause } from "../task/mod.js";
21
+ import { UnsupportedFeatureError } from "./errors.js";
22
+ import { createBackpressureDec, createBackpressureInc, createSubtaskCancel, createSubtaskDrop, createTaskCancel, createTaskReturn, createThreadYield, createWaitableJoin, createWaitableSetDrop, createWaitableSetNew, createWaitableSetPoll, createWaitableSetWait, } from "./async_builtins.js";
23
+ import { createAsyncStartCall, createPrepareCall, createSyncStartCall, } from "./fact_calls.js";
24
+ import { createErrorContextDebugMessage, createErrorContextDrop, createErrorContextNew, createFutureCancelRead, createFutureCancelWrite, createFutureDropReadable, createFutureDropWritable, createFutureNew, createFutureRead, createFutureWrite, createStreamCancelRead, createStreamCancelWrite, createStreamDropReadable, createStreamDropWritable, createStreamNew, createStreamRead, createStreamWrite, createStreamTransfer, createFutureTransfer, createErrorContextTransfer, } from "./stream_builtins.js";
25
+ import { createTranscoder, TRANSCODE_OPS, } from "./transcode.js";
26
+ export * from "./transcode.js";
27
+ export * from "./context.js";
28
+ export * from "./async_builtins.js";
29
+ export * from "./fact_calls.js";
30
+ export * from "./stream_builtins.js";
31
+ /**
32
+ * Trap-code → message, from wasmtime-environ 47.0.3 `trap_encoding.rs`
33
+ * (`generate_trap_type!`), whose ordinals are what FACT passes to the
34
+ * `runtime.trap` import. Only the codes a sync FACT adapter can raise are
35
+ * listed; anything else falls back to the numeric code.
36
+ *
37
+ * Rendered with wasmtime's `"wasm trap: "` prefix (its `impl Display for
38
+ * Trap`), because that is the text the official suite's `assert_trap`
39
+ * commands expect for adapter-raised traps
40
+ * (e.g. `values/realloc.wast:67,94`).
41
+ */
42
+ const FACT_TRAP_MESSAGES = {
43
+ 9: "wasm `unreachable` instruction executed",
44
+ 17: "cannot enter component instance",
45
+ 23: "cannot leave component instance",
46
+ 24: "cannot block a synchronous task before returning",
47
+ 25: "invalid `char` bit pattern",
48
+ 30: "string content out-of-bounds",
49
+ 31: "list content out-of-bounds",
50
+ 32: "invalid variant discriminant",
51
+ 33: "unaligned pointer",
52
+ 46: "reference count overflow",
53
+ 49: "uncaught exception propagated out of component",
54
+ };
55
+ /** Ordinal of `Trap::UncaughtException` in wasmtime's trap encoding. */
56
+ const TRAP_UNCAUGHT_EXCEPTION = 49;
57
+ export { UnsupportedFeatureError } from "./errors.js";
58
+ /** Milestone at which each trampoline kind stops instantiate-failing. */
59
+ const TRAMPOLINE_MILESTONE = {
60
+ "lower-import": "M0",
61
+ "trap": "M0",
62
+ "enter-sync-call": "M0",
63
+ "exit-sync-call": "M0",
64
+ "resource-new": "M1",
65
+ "resource-rep": "M1",
66
+ "resource-drop": "M1",
67
+ "transcoder": "M1",
68
+ "resource-transfer-own": "M1",
69
+ "resource-transfer-borrow": "M1",
70
+ };
71
+ function milestoneOf(kind) {
72
+ return TRAMPOLINE_MILESTONE[kind] ?? "M2";
73
+ }
74
+ /**
75
+ * The borrow bookkeeping of one in-flight synchronous cross-component call,
76
+ * bracketed by the FACT adapter's `enter-sync-call` / `exit-sync-call`
77
+ * imports (wasmtime-environ 47.0.3 `fact/trampoline.rs:810,904`: enter is
78
+ * emitted *before* argument translation and exit *after* the callee returns,
79
+ * so every resource transfer for the call happens inside the bracket).
80
+ *
81
+ * It plays the role definitions.py gives the callee `Subtask`/`Task`:
82
+ *
83
+ * - `lenders` — handles lent to the callee (`Subtask.lenders`); each
84
+ * `num_lends` is dropped again when the call returns, which is what makes
85
+ * a lender's own handle liftable again afterwards.
86
+ * - `numBorrows` — borrow handles lowered into the callee's table
87
+ * (`Task.num_borrows`); the callee must drop them all before returning
88
+ * (definitions.py `Task.return_`: `trap_if(self.num_borrows > 0)`).
89
+ *
90
+ * Structurally satisfies cabi's `TaskBorrowScope` and `SubtaskBorrowScope`.
91
+ */
92
+ export class SyncCallScope {
93
+ numBorrows = 0;
94
+ lenders = [];
95
+ /**
96
+ * definitions.py `Subtask.add_lender` (line 890) — note there is **no**
97
+ * `own` check, and `lift_borrow` (line 1516) calls it unconditionally: a
98
+ * component that received a borrow may lend it onward, and the borrow
99
+ * handle's own `num_lends` is what blocks `resource.drop` on it until the
100
+ * onward call returns (`canon_resource_drop`, line 2325, traps on
101
+ * `num_lends != 0` for owning *and* borrowed handles alike).
102
+ * wasmtime 47.0.3 `vm/component/resources.rs:285` (`resource_lift_borrow`)
103
+ * agrees.
104
+ */
105
+ addLender(h) {
106
+ h.numLends += 1;
107
+ this.lenders.push(h);
108
+ }
109
+ /** definitions.py `Subtask.deliver_resolve` (lines 902-906): releases lenders at delivery time. */
110
+ releaseLenders() {
111
+ for (const h of this.lenders)
112
+ h.numLends -= 1;
113
+ this.lenders.length = 0;
114
+ }
115
+ }
116
+ /**
117
+ * Create the JS function backing one plan trampoline. Called during
118
+ * initializer/arg/export resolution — i.e. at instantiate time — so an
119
+ * unsupported kind fails instantiation, not the first call
120
+ * (plan-format.md "Executor obligations"). Unreferenced trampolines are
121
+ * never created and therefore never fail (intrinsics.md §B tolerates e.g.
122
+ * an unreferenced task-return until M2).
123
+ */
124
+ export function createTrampoline(decl, ctx) {
125
+ const fn = createTrampolineBody(decl, ctx);
126
+ // Remember host traps so the FACT exception barrier cannot swallow them
127
+ // (see `HostTrapState`). This wraps the `trap` trampoline too, which is
128
+ // what keeps a specific trap specific across *nested* adapters: the inner
129
+ // barrier's `trap` trampoline restores and rethrows the real trap, this
130
+ // wrapper re-records it, and the outer barrier restores it again instead
131
+ // of reporting the generic `UncaughtException`.
132
+ return (...args) => {
133
+ try {
134
+ return fn(...args);
135
+ }
136
+ catch (e) {
137
+ ctx.trapState.pending = e;
138
+ throw e;
139
+ }
140
+ };
141
+ }
142
+ /**
143
+ * A trampoline that instantiates fine but fails at its first call, naming the
144
+ * phase that will implement it. See the CONTRACT note at the stream/future
145
+ * cases for why these are not instantiate-time failures.
146
+ */
147
+ function deferredCapability(kind, capability) {
148
+ return () => {
149
+ throw new PendingCapability(`built-in '${kind}' is not implemented yet: ${capability}`);
150
+ };
151
+ }
152
+ /**
153
+ * The component instance a trampoline is declared in (wasmtime names it in
154
+ * every instance-scoped `Trampoline` variant). This is the static answer to
155
+ * definitions.py's `current_instance()`, and unlike it, it is defined during
156
+ * instantiation — when a core module's start function may already be calling
157
+ * these built-ins. See the header of ./async_builtins.ts.
158
+ */
159
+ /** Narrow the trampoline context to what the stream built-ins need. */
160
+ function sctx(ctx) {
161
+ return ctx;
162
+ }
163
+ function declaredInstance(decl, ctx) {
164
+ const instance = decl.instance;
165
+ assert_(typeof instance === "number", `trampoline '${decl.kind}' has no declared component instance`);
166
+ return ctx.componentInstance(instance);
167
+ }
168
+ /**
169
+ * The FACT sync-call bracket stack in force right now: the running task's
170
+ * (activations interleave since background activations exist, so a single
171
+ * executor-wide stack is not a stack), or the executor's when no task is
172
+ * running — instantiation-time start functions.
173
+ */
174
+ // deno-lint-ignore no-explicit-any
175
+ const SCOPE_TRACE = (() => {
176
+ try {
177
+ return Deno.env.get("CE_SCOPE_TRACE") === "1";
178
+ }
179
+ catch {
180
+ return false;
181
+ }
182
+ })();
183
+ const taskIds = new WeakMap();
184
+ let nextTaskId = 1;
185
+ function taskId(t) {
186
+ if (t === undefined || t === null)
187
+ return "NONE(->ctx fallback)";
188
+ let id = taskIds.get(t);
189
+ if (id === undefined) {
190
+ id = nextTaskId++;
191
+ taskIds.set(t, id);
192
+ }
193
+ return `T${id}`;
194
+ }
195
+ function syncScopes(ctx, site = "?") {
196
+ const thread = maybeCurrentThread();
197
+ const scopes = thread?.syncCallStack ?? ctx.syncCallStack;
198
+ if (SCOPE_TRACE) {
199
+ console.error(`[scope] ${site} act=${taskId(thread)} depth=${scopes.length}`);
200
+ }
201
+ return scopes;
202
+ }
203
+ function createTrampolineBody(decl, ctx) {
204
+ switch (decl.kind) {
205
+ case "lower-import": {
206
+ const d = decl;
207
+ return ctx.loweredImport(d);
208
+ }
209
+ case "trap":
210
+ // FACT `runtime.trap` import: `(code: i32) -> ()` followed by
211
+ // `unreachable` (wasmtime-environ `fact/trampoline.rs:3932`).
212
+ return (code) => {
213
+ if (code === TRAP_UNCAUGHT_EXCEPTION) {
214
+ const pending = ctx.trapState.pending;
215
+ if (pending !== undefined) {
216
+ // Deliberately *not* cleared: an enclosing adapter's barrier will
217
+ // catch this rethrow and needs to restore the same trap. The slot
218
+ // is reset per lifted-export call (exec/boundary.ts), which is
219
+ // what bounds its lifetime.
220
+ throw pending;
221
+ }
222
+ }
223
+ const message = code === undefined
224
+ ? undefined
225
+ : FACT_TRAP_MESSAGES[code];
226
+ trap(message === undefined
227
+ ? `FACT adapter trap (code ${code ?? "?"})`
228
+ : `wasm trap: ${message}`);
229
+ };
230
+ // Sync-call task bookkeeping (intrinsics.md §A: "degenerate-case
231
+ // implementation in M0: assert-and-count"). wasmtime 47 signatures:
232
+ // enter-sync-call carries the caller/callee instance pair, which is what
233
+ // the reentrance gate below needs; balance of the bracket is asserted at
234
+ // component teardown by tests.
235
+ // Signatures (wasmtime-environ 47.0.3 `fact.rs:743,754`):
236
+ // async.enter-sync-call(caller_instance: i32, async: i32,
237
+ // callee_instance: i32) -> ()
238
+ // async.exit-sync-call() -> ()
239
+ case "enter-sync-call":
240
+ return (callerInstance, async_, calleeInstance) => {
241
+ // Reference reentrance gate. Every guest->guest call in
242
+ // definitions.py routes through the callee's lift wrapper:
243
+ // canon_lower (line 2312) calls
244
+ // `callee(on_start, on_resolve, caller = thread.task.inst)`,
245
+ // and `callee` is `Store.lift`'s `func_inst` (lines 578-585), whose
246
+ // first act is
247
+ // `trap_if(not inst.may_enter_from(caller))` (line 581)
248
+ // with `entering_set(caller) = callee.self_and_ancestors()
249
+ // - caller.self_and_ancestors()`
250
+ // (lines 230-234).
251
+ // A sync fused adapter is an *optimization* of that path, so the gate
252
+ // belongs here (issue #99).
253
+ //
254
+ // Note on the shape of the entering set, which is what makes this
255
+ // check safe for the legal shapes:
256
+ // * caller == callee, or either an ancestor of the other -> the
257
+ // entering set is empty and this never traps. Those pairs never
258
+ // reach this trampoline anyway: FACT emits an unconditional
259
+ // `CannotEnterComponent` trap for them at compile time
260
+ // (wasmtime-environ 47.0.3 `fact/trampoline.rs:120-127`), which
261
+ // is what `test/async/trap-on-reenter.wast` cases 2 and 3 pin.
262
+ // * an *idle* sibling -> `mayEnter` is true, no trap. This is what
263
+ // `test/async/sync-barges-in.wast` needs: an async callee that is
264
+ // merely blocked has already run `leave_to` (its `canon_lift`
265
+ // returned), so a sync sibling may barge in.
266
+ // * an *entered* sibling -> trap, which is the A -> C -> A cycle.
267
+ //
268
+ // CONTRACT / reachability: a pure guest-to-guest sibling cycle is
269
+ // unreachable by construction, because component instance imports
270
+ // form a DAG (a callee must be instantiated before its caller, so it
271
+ // cannot hold an import of its caller; `wasm-tools` rejects the
272
+ // mutual-import composition outright). wasmtime relies on exactly
273
+ // that to elide the runtime check in fused adapters -- see the
274
+ // comment in `may_enter`, wasmtime 47.0.3
275
+ // `runtime/component/concurrent.rs:1876-1886`, and
276
+ // `enter_guest_sync_call` (concurrent.rs:1723) which performs no
277
+ // reentrance check at all. The gate is kept anyway because the
278
+ // reference mandates it and no corpus test pins the permissive
279
+ // behaviour; it is cheap, and it is the honest place for the
280
+ // invariant to be asserted rather than assumed.
281
+ //
282
+ // Deliberately *not* done here: `enter_from` / `leave_to` around the
283
+ // bracket. The reference locks the callee for the duration, which
284
+ // would additionally trap host-mediated reentrance (host -> A.f ->
285
+ // C.g -> host import -> host invokes C.g).
286
+ //
287
+ // ADJUDICATED 2026-08-20 (polyengine#165; named divergence in
288
+ // docs/architecture.md section 6): accepted — the bracket stays
289
+ // omitted. Three grounds: wasmtime parity (`enter_guest_sync_call`
290
+ // checks nothing); taking the bracket would create a guest-to-guest
291
+ // lock spanning suspension points, reintroducing the await-spanning
292
+ // -lock class removed by #156/#160; and upstream is deleting the
293
+ // trap outright — CM PR #705 ("CABI: remove the may_enter
294
+ // flag/trap") makes previously-trapping reentrance valid, so this
295
+ // divergence is a trailing indicator of the removal and
296
+ // self-resolves at the submodule pin advance (migration map:
297
+ // polyengine#173). Until that pin advance the reference's checks polyengine
298
+ // DOES enforce stay in force.
299
+ if (typeof callerInstance === "number" &&
300
+ typeof calleeInstance === "number") {
301
+ const callerInst = ctx.componentInstance(callerInstance >>> 0);
302
+ const calleeInst = ctx.componentInstance(calleeInstance >>> 0);
303
+ // A poisoned callee's refusal names the original trap (polyengine#145).
304
+ if (!calleeInst.mayEnterFrom(callerInst)) {
305
+ trap(withPoisonCause(calleeInst, "cannot enter component instance"));
306
+ }
307
+ }
308
+ // `async_` records whether the callee is *async-lifted*. wasmtime
309
+ // stores it on the guest task it creates here
310
+ // (`concurrent.rs:1723` `enter_guest_sync_call`, whose `callee_async`
311
+ // parameter flows into `GuestTask::new`) and never traps on it.
312
+ //
313
+ // Before the task core existed this trampoline refused `async_ == 1`
314
+ // rather than silently treating an async callee as sync. That guard is
315
+ // now stale and actively wrong: a sync-lowered caller reaching an
316
+ // async-lifted export is the `sync-start-call` path
317
+ // (intrinsics/fact_calls.ts), and the task it needs is created by
318
+ // `prepare-call`, not here. What remains of this bracket for us is the
319
+ // borrow bookkeeping (`SyncCallScope`), which applies either way.
320
+ void async_;
321
+ ctx.stats.enterSyncCalls++;
322
+ // Per task where there is one; the executor-wide stack is the
323
+ // fallback for a start function running at instantiation time, which
324
+ // has no task (see `maybeCurrentTask`).
325
+ // Invariant, per ACTIVATION: every `enter` is matched by exactly one
326
+ // `exit` on the same stack. Recorded here so the `exit` side can
327
+ // assert it structurally rather than only by depth (CE_SCOPE_TRACE
328
+ // proved this is where the interesting failures live).
329
+ const scopes = syncScopes(ctx, "enter");
330
+ scopes.push(new SyncCallScope());
331
+ };
332
+ case "exit-sync-call":
333
+ return (..._args) => {
334
+ ctx.stats.exitSyncCalls++;
335
+ assert_(ctx.stats.exitSyncCalls <= ctx.stats.enterSyncCalls, "exit-sync-call without matching enter-sync-call");
336
+ const scope = syncScopes(ctx, "exit").pop();
337
+ assert_(scope !== undefined,
338
+ // If this fires, an `exit` reached an activation that never ran the
339
+ // matching `enter` -- the bracket is attached to the wrong unit
340
+ // again. See `Thread.syncCallStack`.
341
+ "exit-sync-call with an empty sync-call stack");
342
+ // definitions.py `Task.return_`: the callee may not return while it
343
+ // still holds borrow handles. Wording parity with wasmtime's
344
+ // exit-time check (drop-cross-task-borrow.wast:309 pins the async
345
+ // path; the sync bracket is the same check).
346
+ trapIf(scope.numBorrows > 0, "borrow handles still remain at the end of the call");
347
+ scope.releaseLenders();
348
+ };
349
+ // Guest-side resource built-ins (sync paths of docs/architecture.md §7 over the cabi
350
+ // handle tables). rep is always i32 in current wasmtime.
351
+ case "resource-new": {
352
+ const d = decl;
353
+ const inst = ctx.componentInstance(d.instance);
354
+ const rt = ctx.resourceToken(d.resource);
355
+ return (rep) => canonResourceNew(inst, rt, rep >>> 0);
356
+ }
357
+ case "resource-rep": {
358
+ const d = decl;
359
+ const inst = ctx.componentInstance(d.instance);
360
+ const rt = ctx.resourceToken(d.resource);
361
+ return (handle) => canonResourceRep(inst, rt, handle >>> 0);
362
+ }
363
+ case "resource-drop": {
364
+ const d = decl;
365
+ const inst = ctx.componentInstance(d.instance);
366
+ const rt = ctx.resourceToken(d.resource);
367
+ return (handle) => {
368
+ canonResourceDrop(inst, rt, handle >>> 0);
369
+ };
370
+ }
371
+ // FACT resource transfer (contracts/intrinsics.md §A, wasmtime-environ
372
+ // 47.0.3 `fact.rs:721` — signature `(i32 src_handle, i32 src_table,
373
+ // i32 dst_table) -> i32 dst_handle`). These are the fused-adapter form of
374
+ // `lift_own`/`lower_own` and `lift_borrow`/`lower_borrow`
375
+ // (definitions.py) with the src/dst tables named by index rather than
376
+ // implied by the running instance.
377
+ // FACT string transcoders (contracts/intrinsics.md §B "M1"). The plan
378
+ // carries the op name plus the source/destination `RuntimeMemoryIndex`es;
379
+ // `./transcode.ts` holds the twelve operations.
380
+ case "transcoder": {
381
+ const d = decl;
382
+ if (d.from64 || d.to64) {
383
+ // 64-bit linear memories are out of scope (https://github.com/polymorph-components/polyengine/issues/12); refusing at
384
+ // instantiate time keeps "instantiate-time, never call-time".
385
+ throw new UnsupportedFeatureError("M2", `transcoder '${d.op}' over a 64-bit linear memory`);
386
+ }
387
+ if (!TRANSCODE_OPS.includes(d.op)) {
388
+ throw new UnsupportedFeatureError("M2", `unknown string transcode operation '${d.op}'`);
389
+ }
390
+ return createTranscoder(d.op, ctx.runtimeMemory(d.from), ctx.runtimeMemory(d.to));
391
+ }
392
+ // --- 0.3 async built-ins (contracts/intrinsics.md §B "M2") -------------
393
+ // All ported in ./async_builtins.ts; the ones that would have to block a
394
+ // wasm frame fail there, at the call site, with a JSPI-shaped message.
395
+ case "task-return":
396
+ return createTaskReturn(decl, ctx);
397
+ case "task-cancel":
398
+ return createTaskCancel();
399
+ // No `backpressure-set` case on purpose: wasmtime-environ 47.0.3 has only
400
+ // `Trampoline::BackpressureInc` / `BackpressureDec`
401
+ // (`component/info.rs:775,781`) — there is no `BackpressureSet` variant to
402
+ // dispatch, so a case for it would be unreachable code implying a wire
403
+ // shape that cannot occur. definitions.py's own dead
404
+ // `canon_backpressure_set` was removed upstream (CM PR #690); see
405
+ // upstream-component-model-repo-findings.md CM-2, RESOLVED.
406
+ case "backpressure-inc":
407
+ return createBackpressureInc(declaredInstance(decl, ctx));
408
+ case "backpressure-dec":
409
+ return createBackpressureDec(declaredInstance(decl, ctx));
410
+ case "waitable-set-new":
411
+ return createWaitableSetNew(declaredInstance(decl, ctx));
412
+ case "waitable-set-wait":
413
+ return createWaitableSetWait(decl, ctx, declaredInstance(decl, ctx), ctx.suspensionMode);
414
+ case "waitable-set-poll":
415
+ return createWaitableSetPoll(decl, ctx, declaredInstance(decl, ctx));
416
+ case "waitable-set-drop":
417
+ return createWaitableSetDrop(declaredInstance(decl, ctx));
418
+ case "waitable-join":
419
+ return createWaitableJoin(declaredInstance(decl, ctx));
420
+ case "subtask-drop":
421
+ return createSubtaskDrop(declaredInstance(decl, ctx));
422
+ case "subtask-cancel":
423
+ return createSubtaskCancel(decl, declaredInstance(decl, ctx), ctx.suspensionMode);
424
+ case "thread-yield":
425
+ return createThreadYield(decl, ctx.suspensionMode);
426
+ // --- FACT cross-component calls (see ./fact_calls.ts) -----------------
427
+ case "prepare-call":
428
+ return createPrepareCall(decl, ctx);
429
+ case "sync-start-call":
430
+ return createSyncStartCall(decl, ctx);
431
+ case "async-start-call":
432
+ return createAsyncStartCall(decl, ctx);
433
+ // --- stream / future / error-context (see ./stream_builtins.ts) -------
434
+ case "stream-new":
435
+ return createStreamNew(decl, sctx(ctx), declaredInstance(decl, ctx));
436
+ case "future-new":
437
+ return createFutureNew(decl, sctx(ctx), declaredInstance(decl, ctx));
438
+ case "stream-read":
439
+ return createStreamRead(decl, sctx(ctx), declaredInstance(decl, ctx));
440
+ case "stream-write":
441
+ return createStreamWrite(decl, sctx(ctx), declaredInstance(decl, ctx));
442
+ case "future-read":
443
+ return createFutureRead(decl, sctx(ctx), declaredInstance(decl, ctx));
444
+ case "future-write":
445
+ return createFutureWrite(decl, sctx(ctx), declaredInstance(decl, ctx));
446
+ case "stream-cancel-read":
447
+ return createStreamCancelRead(decl, sctx(ctx), declaredInstance(decl, ctx));
448
+ case "stream-cancel-write":
449
+ return createStreamCancelWrite(decl, sctx(ctx), declaredInstance(decl, ctx));
450
+ case "future-cancel-read":
451
+ return createFutureCancelRead(decl, sctx(ctx), declaredInstance(decl, ctx));
452
+ case "future-cancel-write":
453
+ return createFutureCancelWrite(decl, sctx(ctx), declaredInstance(decl, ctx));
454
+ case "stream-drop-readable":
455
+ return createStreamDropReadable(decl, sctx(ctx), declaredInstance(decl, ctx));
456
+ case "stream-drop-writable":
457
+ return createStreamDropWritable(decl, sctx(ctx), declaredInstance(decl, ctx));
458
+ case "future-drop-readable":
459
+ return createFutureDropReadable(decl, sctx(ctx), declaredInstance(decl, ctx));
460
+ case "future-drop-writable":
461
+ return createFutureDropWritable(decl, sctx(ctx), declaredInstance(decl, ctx));
462
+ case "error-context-new":
463
+ return createErrorContextNew(decl, sctx(ctx), declaredInstance(decl, ctx));
464
+ case "error-context-debug-message":
465
+ return createErrorContextDebugMessage(decl, sctx(ctx), declaredInstance(decl, ctx));
466
+ case "error-context-drop":
467
+ return createErrorContextDrop(declaredInstance(decl, ctx));
468
+ case "stream-transfer":
469
+ return createStreamTransfer(ctx);
470
+ case "future-transfer":
471
+ return createFutureTransfer(ctx);
472
+ case "error-context-transfer":
473
+ return createErrorContextTransfer(ctx,
474
+ // plan v3: the transfer's table arguments are
475
+ // `TypeComponentLocalErrorContextTableIndex`es (fact/trampoline.rs:
476
+ // 3526-3539), resolved through the plan's own `errorContextTables`
477
+ // section. Before v3 this went through `resourceTableInstance` — a
478
+ // different index space, which mis-routed silently whenever a
479
+ // concrete resource table happened to exist at the colliding slot
480
+ // (polyengine#89).
481
+ (t) => ctx.errorContextTableInstance(t));
482
+ case "resource-transfer-own":
483
+ return (handle, srcTable, dstTable) => transferOwn(ctx, handle >>> 0, srcTable, dstTable);
484
+ case "resource-transfer-borrow":
485
+ return (handle, srcTable, dstTable) => transferBorrow(ctx, handle >>> 0, srcTable, dstTable);
486
+ default:
487
+ throw new UnsupportedFeatureError(milestoneOf(decl.kind) === "M1" ? "M1" : "M2", `component requires host trampoline '${decl.kind}'`);
488
+ }
489
+ }
490
+ // ---------------------------------------------------------------------------
491
+ // Resource transfer (FACT `resource.transfer-own` / `transfer-borrow`)
492
+ // ---------------------------------------------------------------------------
493
+ /**
494
+ * `lift_own` out of the source table followed by `lower_own` into the
495
+ * destination table (definitions.py `lift_own` / `lower_own`): the source
496
+ * handle is *removed* (ownership moves), must be owning, and must not be
497
+ * lent out.
498
+ */
499
+ function transferOwn(ctx, handle, srcTable, dstTable) {
500
+ const src = ctx.resourceTableInstance(srcTable);
501
+ const dst = ctx.resourceTableInstance(dstTable);
502
+ const srcRt = ctx.resourceToken(srcTable);
503
+ const dstRt = ctx.resourceToken(dstTable);
504
+ const h = src.handles.remove(handle);
505
+ trapIf(!(h instanceof ResourceHandle), "transfer-own: not a resource handle");
506
+ const rh = h;
507
+ trapIf(rh.rt !== srcRt, "transfer-own: resource type mismatch");
508
+ // definitions.py `lift_own`: `trap_if(h.num_lends != 0)`.
509
+ trapIf(rh.numLends !== 0, "cannot remove owned resource while borrowed (handle still lent out)");
510
+ trapIf(!rh.own, "transfer-own: expected an owning handle");
511
+ return dst.handles.add(new ResourceHandle(dstRt, rh.rep, true));
512
+ }
513
+ /**
514
+ * `lift_borrow` from the source table followed by `lower_borrow` into the
515
+ * destination table. The source handle stays in place; the destination gets a
516
+ * non-owning handle.
517
+ *
518
+ * Two deviations from the plain lift/lower pair, both taken from
519
+ * definitions.py:
520
+ *
521
+ * - `lower_borrow` returns the *rep* directly when the destination instance
522
+ * is the one that implements the resource ("own the resource" fast path),
523
+ * since a component always has direct access to its own reps.
524
+ * - lender / `num_borrows` bookkeeping is attached to the enclosing
525
+ * `SyncCallScope` (the `enter-sync-call` / `exit-sync-call` bracket),
526
+ * which is this path's stand-in for the callee `Subtask`/`Task` of
527
+ * definitions.py.
528
+ */
529
+ // CONTRACT: contracts/intrinsics.md §A schedules ResourceTransfer* at "M1,
530
+ // resources milestone" and describes them only as "handle-table moves between
531
+ // component instances" — the borrow-scope interaction is unspecified there.
532
+ // The reading implemented here is taken from definitions.py
533
+ // (`lift_borrow`/`lower_borrow` + `Subtask.lenders`/`Task.num_borrows`) and
534
+ // is what makes `test/resources/borrows.wast:162` (`lend-trap`) trap.
535
+ function transferBorrow(ctx, handle, srcTable, dstTable) {
536
+ const src = ctx.resourceTableInstance(srcTable);
537
+ const dst = ctx.resourceTableInstance(dstTable);
538
+ const srcRt = ctx.resourceToken(srcTable);
539
+ const dstRt = ctx.resourceToken(dstTable);
540
+ // Innermost-scope resolution. A FACT `[async-start]` copy window is
541
+ // strictly synchronous and innermost when present (the copy adapters
542
+ // cannot make nested calls), so it wins over any enclosing sync bracket.
543
+ const fact = ctx.factStartScopes[ctx.factStartScopes.length - 1];
544
+ const stack = syncScopes(ctx);
545
+ const scope = stack[stack.length - 1];
546
+ assert_(fact !== undefined || scope !== undefined, "transfer-borrow outside an enter-sync-call/exit-sync-call bracket " +
547
+ "or FACT start window");
548
+ const h = src.handles.get(handle);
549
+ trapIf(!(h instanceof ResourceHandle), "transfer-borrow: not a resource handle");
550
+ const rh = h;
551
+ trapIf(rh.rt !== srcRt, "transfer-borrow: resource type mismatch");
552
+ // definitions.py `lift_borrow`: the source handle becomes a lender of the
553
+ // callee's activation, which is what makes lifting it as an `own` trap for
554
+ // the duration of the call.
555
+ (fact?.lenders ?? scope).addLender(rh);
556
+ // definitions.py `lower_borrow`: `if inst is t.rt.impl: return rep` — a
557
+ // component that implements the resource is handed the rep directly and
558
+ // gets no handle (and therefore no `num_borrows` obligation).
559
+ if (dstRt.impl !== null && dstRt.impl === dst)
560
+ return rh.rep;
561
+ const borrowScope = fact !== undefined ? fact.taskScope : scope;
562
+ borrowScope.numBorrows += 1;
563
+ return dst.handles.add(new ResourceHandle(dstRt, rh.rep, false, borrowScope));
564
+ }