@polyengine/runtime 0.5.1 → 0.6.1
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/esm/cabi/async_values.js +6 -5
- package/esm/cabi/bulk_lists.js +0 -5
- package/esm/cabi/context.js +13 -3
- package/esm/cabi/flatten.js +41 -9
- package/esm/cabi/handles.js +57 -54
- package/esm/cabi/layout.js +113 -52
- package/esm/cabi/load.js +31 -23
- package/esm/cabi/store.js +33 -26
- package/esm/cabi/trap.js +2 -2
- package/esm/cabi/types.js +138 -25
- package/esm/cabi/values.js +25 -7
- package/esm/cache/core.js +2 -11
- package/esm/digest/digest.js +10 -8
- package/esm/digest/mod.js +1 -1
- package/esm/digest/verify.js +6 -86
- package/esm/embedder/casing.js +24 -9
- package/esm/embedder/copy.js +6 -6
- package/esm/embedder/errors.js +2 -2
- package/esm/embedder/imports.js +3 -3
- package/esm/embedder/instantiate.js +132 -37
- package/esm/embedder/mod.js +9 -8
- package/esm/embedder/resources.js +39 -16
- package/esm/embedder/streams.js +36 -37
- package/esm/embedder/sync.js +242 -0
- package/esm/embedder/values.js +84 -22
- package/esm/embedder/version.js +9 -9
- package/esm/exec/boundary.js +123 -161
- package/esm/exec/executor.js +37 -25
- package/esm/exec/host_streams.js +31 -31
- package/esm/intrinsics/async_builtins.js +15 -7
- package/esm/intrinsics/context.js +1 -1
- package/esm/intrinsics/errors.js +9 -9
- package/esm/intrinsics/fact_calls.js +37 -49
- package/esm/intrinsics/mod.js +54 -117
- package/esm/intrinsics/stream_builtins.js +2 -2
- package/esm/intrinsics/transcode.js +1 -1
- package/esm/jspi/bridge.js +4 -3
- package/esm/jspi/suspending.js +5 -5
- package/esm/plan/loader.js +5 -5
- package/esm/shim/translator.js +2 -2
- package/esm/task/mod.js +45 -182
- package/esm/task/scheduler.js +154 -185
- package/esm/task/streams.js +39 -54
- package/esm/task/subtask.js +2 -2
- package/esm/task/thread.js +20 -41
- package/esm/task/waitable.js +0 -1
- package/package.json +2 -2
- package/types/cabi/async_values.d.ts +3 -2
- package/types/cabi/bulk_lists.d.ts +0 -2
- package/types/cabi/context.d.ts +15 -5
- package/types/cabi/flatten.d.ts +2 -2
- package/types/cabi/handles.d.ts +15 -26
- package/types/cabi/layout.d.ts +22 -1
- package/types/cabi/load.d.ts +10 -2
- package/types/cabi/store.d.ts +4 -2
- package/types/cabi/types.d.ts +22 -3
- package/types/digest/mod.d.ts +1 -1
- package/types/digest/verify.d.ts +3 -19
- package/types/embedder/casing.d.ts +9 -1
- package/types/embedder/copy.d.ts +4 -4
- package/types/embedder/instantiate.d.ts +4 -4
- package/types/embedder/mod.d.ts +3 -2
- package/types/embedder/resources.d.ts +20 -7
- package/types/embedder/streams.d.ts +5 -6
- package/types/embedder/sync.d.ts +81 -0
- package/types/embedder/values.d.ts +2 -2
- package/types/exec/boundary.d.ts +55 -44
- package/types/exec/executor.d.ts +3 -2
- package/types/exec/host_streams.d.ts +8 -8
- package/types/intrinsics/errors.d.ts +3 -3
- package/types/intrinsics/mod.d.ts +1 -1
- package/types/intrinsics/stream_builtins.d.ts +2 -2
- package/types/jspi/bridge.d.ts +6 -5
- package/types/plan/format.d.ts +11 -10
- package/types/plan/loader.d.ts +2 -2
- package/types/shim/translator.d.ts +2 -2
- package/types/task/mod.d.ts +26 -97
- package/types/task/scheduler.d.ts +81 -62
- package/types/task/streams.d.ts +23 -38
- package/types/task/subtask.d.ts +2 -2
- package/types/task/waitable.d.ts +0 -1
|
@@ -60,14 +60,15 @@
|
|
|
60
60
|
// * Reentrance between *related* instances is resolved statically:
|
|
61
61
|
// `trampoline.rs:116-127` emits an unconditional
|
|
62
62
|
// `trap(Trap::CannotEnterComponent)` when the lower and lift instances are
|
|
63
|
-
// the same or are ancestors of one another.
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
63
|
+
// the same or are ancestors of one another. The runtime-side counterpart
|
|
64
|
+
// — "is the callee instance currently executing" — does not exist at the
|
|
65
|
+
// pinned reference (definitions.py @ 2f13265 has no `may_enter`, CM#705),
|
|
66
|
+
// so the only refusal at these sites is polyengine's poisoned-corpse check
|
|
67
|
+
// (`entryRefusal`). The flat-instance-tree gap recorded in task/mod.ts is
|
|
68
|
+
// doubly not load-bearing here.
|
|
68
69
|
import { assert_, trap } from "../cabi/trap.js";
|
|
69
70
|
import { MAX_FLAT_RESULTS } from "../cabi/mod.js";
|
|
70
|
-
import { NeedsJspi, currentTask, maybeCurrentTask, needsJspi, notifyInstancePoisoned, packSubtaskResult, PendingCapability, Subtask, SubtaskState, Task, Thread,
|
|
71
|
+
import { NeedsJspi, currentTask, maybeCurrentTask, needsJspi, notifyInstancePoisoned, packSubtaskResult, PendingCapability, Subtask, SubtaskState, Task, Thread, entryRefusal, } from "../task/mod.js";
|
|
71
72
|
import { blockCurrentActivation, enterWasm } from "../jspi/mod.js";
|
|
72
73
|
import { awaitCore, callCore, normalizeCoreValues, runCallbackLoop, } from "../exec/boundary.js";
|
|
73
74
|
import { traceCopy } from "./stream_builtins.js";
|
|
@@ -203,8 +204,9 @@ function mkCalleeTask(input) {
|
|
|
203
204
|
// `task_return_type` at all: fact.rs's comment on `PrepareCall.memory` says
|
|
204
205
|
// the same for the memory check).
|
|
205
206
|
// `task_return_type` arrives as wasmtime's *own* `TypeTupleIndex` — a
|
|
206
|
-
// runtime argument, not a plan field.
|
|
207
|
-
//
|
|
207
|
+
// runtime argument, not a plan field. The task-return trampoline's raw
|
|
208
|
+
// `results` key + interned `resultType` (contracts/plan-format.md schema)
|
|
209
|
+
// supplies the dictionary for it: every `task-return`
|
|
208
210
|
// trampoline decl carries that raw index alongside its interned
|
|
209
211
|
// `plan.types` entry, so the callee task CAN now carry its declared result
|
|
210
212
|
// types and `canon_task_return`'s `trap_if(result_type != task.ft.result)`
|
|
@@ -287,8 +289,8 @@ function mkCalleeTask(input) {
|
|
|
287
289
|
// entry that owns a FACT sync-call bracket: `enter-sync-call` runs here
|
|
288
290
|
// under this task, and if the callee suspends, the engine resumes it later
|
|
289
291
|
// with no driver. Without the ambient travelling with the activation the
|
|
290
|
-
// matching `exit-sync-call` had no task in scope at all (traced
|
|
291
|
-
//
|
|
292
|
+
// matching `exit-sync-call` had no task in scope at all (traced as
|
|
293
|
+
// `ENTER-SYNC owner=K26` / `EXIT-SYNC owner=EXECUTOR`).
|
|
292
294
|
//
|
|
293
295
|
// Entries deliberately NOT wrapped: `realloc`, `post-return`, resource
|
|
294
296
|
// destructors and the `[async-start]`/`[async-return]` copy adapters.
|
|
@@ -303,7 +305,7 @@ function mkCalleeTask(input) {
|
|
|
303
305
|
// line 12). This is only coherent together with site 1 below blocking
|
|
304
306
|
// rather than raising `NeedsJspi`, since a promising callee resolves on a
|
|
305
307
|
// later turn by construction.
|
|
306
|
-
// NOTE
|
|
308
|
+
// NOTE: this wrap is RIGHT for a callee that blocks and
|
|
307
309
|
// Wrap ONLY a callee that can actually reach a suspension point.
|
|
308
310
|
//
|
|
309
311
|
// The wrap is required when the callee blocks: without its own `promising`
|
|
@@ -452,35 +454,30 @@ export function createSyncStartCall(decl, ctx) {
|
|
|
452
454
|
},
|
|
453
455
|
lenderScope,
|
|
454
456
|
});
|
|
455
|
-
// Reference `Store.lift
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
|
|
457
|
+
// Reference `Store.lift` (@ 2f13265) has no reentrance gate; the only
|
|
458
|
+
// refusal left is polyengine's poisoned-corpse check, whose message names
|
|
459
|
+
// the original trap (polyengine#145).
|
|
460
|
+
{
|
|
461
|
+
const refusal = entryRefusal(prepared.calleeInst, prepared.callerInst, "cannot enter component instance");
|
|
462
|
+
if (refusal !== null)
|
|
463
|
+
trap(refusal);
|
|
460
464
|
}
|
|
461
|
-
prepared.calleeInst.enterFrom(prepared.callerInst);
|
|
462
|
-
let ok = false;
|
|
463
465
|
try {
|
|
464
466
|
const thread = spawn(task, body);
|
|
465
467
|
thread.resume();
|
|
466
|
-
ok = true;
|
|
467
468
|
}
|
|
468
469
|
catch (e) {
|
|
469
|
-
// A trap
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
else {
|
|
476
|
-
// Retire the poisoned CALLEE's stream/future ends (#66): this is a
|
|
477
|
-
// bracket-break site like `Store.tick`'s, and the trap unwinds to a
|
|
478
|
-
// hooked site that walks only the CALLER's chain — a composed
|
|
479
|
-
// component's callee would otherwise strand its host peers.
|
|
470
|
+
// A trap poisons the callee instance. A *capability signal* does not —
|
|
471
|
+
// see the `isCapabilitySignal` note in exec/boundary.ts.
|
|
472
|
+
if (!(e instanceof NeedsJspi) && !(e instanceof PendingCapability)) {
|
|
473
|
+
// Retire the poisoned CALLEE's stream/future ends (#66): the trap
|
|
474
|
+
// unwinds to a hooked site that walks only the CALLER's chain — a
|
|
475
|
+
// composed component's callee would otherwise strand its host peers.
|
|
480
476
|
notifyInstancePoisoned(prepared.calleeInst, e);
|
|
481
477
|
}
|
|
482
478
|
// The lent handles are the CALLER's, and the caller is not poisoned by
|
|
483
|
-
// either exit (contracts/intrinsics.md
|
|
479
|
+
// either exit (contracts/intrinsics.md §A's trap-unwind/lender-release
|
|
480
|
+
// obligation: this runtime
|
|
484
481
|
// deliberately supports post-trap re-entry on the caller side, where
|
|
485
482
|
// the reference kills the whole store, so the sync-call scopes it
|
|
486
483
|
// skipped have to be unwound explicitly). Leaving `numLends` elevated
|
|
@@ -490,8 +487,6 @@ export function createSyncStartCall(decl, ctx) {
|
|
|
490
487
|
lenderScope.releaseLenders();
|
|
491
488
|
throw e;
|
|
492
489
|
}
|
|
493
|
-
if (ok)
|
|
494
|
-
prepared.calleeInst.leaveTo(prepared.callerInst);
|
|
495
490
|
if (callerResults === null) {
|
|
496
491
|
// The callee did not resolve within its first activation. definitions.py
|
|
497
492
|
// `canon_lower`'s sync path blocks here — `thread.wait_until(
|
|
@@ -550,7 +545,7 @@ export function createSyncStartCall(decl, ctx) {
|
|
|
550
545
|
// sits in `store.waiting`, e.g. the caller's whole host call was
|
|
551
546
|
// abandoned). No JS runs, so nothing can release; the lent
|
|
552
547
|
// handles die with the store, which is the reference's own
|
|
553
|
-
// outcome. Out of scope for
|
|
548
|
+
// outcome. Out of scope for the trap-unwind/lender-release obligation (no non-poisoning exit).
|
|
554
549
|
// 6. Trap-poisoning of the parked instance: does not settle this
|
|
555
550
|
// point by itself — it reaches the guest either as (2) (a
|
|
556
551
|
// produce-time trap) or as (4) (teardown abandons the park), so
|
|
@@ -679,25 +674,20 @@ export function createAsyncStartCall(decl, ctx) {
|
|
|
679
674
|
subtask.onCancel = (callerInst) => task.requestCancellation(callerInst);
|
|
680
675
|
subtask.calleeTask = task;
|
|
681
676
|
// A poisoned callee's refusal names the original trap (polyengine#145).
|
|
682
|
-
|
|
683
|
-
|
|
677
|
+
{
|
|
678
|
+
const refusal = entryRefusal(prepared.calleeInst, prepared.callerInst, "cannot enter component instance");
|
|
679
|
+
if (refusal !== null)
|
|
680
|
+
trap(refusal);
|
|
684
681
|
}
|
|
685
|
-
prepared.calleeInst.enterFrom(prepared.callerInst);
|
|
686
|
-
let ok = false;
|
|
687
682
|
let thread;
|
|
688
683
|
try {
|
|
689
684
|
thread = spawn(task, body);
|
|
690
685
|
thread.resume();
|
|
691
|
-
ok = true;
|
|
692
686
|
}
|
|
693
687
|
catch (e) {
|
|
694
688
|
// See the sync form above and `isCapabilitySignal` in exec/boundary.ts.
|
|
695
|
-
if (e instanceof NeedsJspi
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
// Bracket-break site — retire the poisoned callee's ends (#66),
|
|
700
|
-
// as in the sync form above.
|
|
689
|
+
if (!(e instanceof NeedsJspi) && !(e instanceof PendingCapability)) {
|
|
690
|
+
// Retire the poisoned callee's ends (#66), as in the sync form above.
|
|
701
691
|
notifyInstancePoisoned(prepared.calleeInst, e);
|
|
702
692
|
}
|
|
703
693
|
// The subtask never reached `report()`, so it has no handle in the
|
|
@@ -710,8 +700,6 @@ export function createAsyncStartCall(decl, ctx) {
|
|
|
710
700
|
unwindSubtaskLenders(subtask);
|
|
711
701
|
throw e;
|
|
712
702
|
}
|
|
713
|
-
if (ok)
|
|
714
|
-
prepared.calleeInst.leaveTo(prepared.callerInst);
|
|
715
703
|
const report = () => {
|
|
716
704
|
if (subtask.resolved()) {
|
|
717
705
|
// Eager completion: no handle, no event (definitions.py line 2293).
|
|
@@ -729,7 +717,7 @@ export function createAsyncStartCall(decl, ctx) {
|
|
|
729
717
|
// NO WAIT FOR RESOLUTION HERE, deliberately. An async-lowered caller must
|
|
730
718
|
// not block on its callee's *completion* -- that is the entire point of
|
|
731
719
|
// async lowering: it takes a subtask handle and learns of completion
|
|
732
|
-
// through events. An earlier attempt
|
|
720
|
+
// through events. An earlier attempt parked the caller here
|
|
733
721
|
// until the callee resolved. It made cross-abi-calls agree in both modes,
|
|
734
722
|
// and it broke the thing it had no business touching: the caller's
|
|
735
723
|
// activation was now suspended, so the sync-lowered parked caller of
|
|
@@ -833,7 +821,7 @@ export function createAsyncStartCall(decl, ctx) {
|
|
|
833
821
|
// subtask index (it either threw before `handles.add`, or after it
|
|
834
822
|
// with the index lost), so nothing will ever deliver this subtask's
|
|
835
823
|
// resolution — exactly the state `unwindSubtaskLenders` exists for
|
|
836
|
-
// (contracts/intrinsics.md
|
|
824
|
+
// (contracts/intrinsics.md §A's trap-unwind/lender-release obligation).
|
|
837
825
|
let produced = false;
|
|
838
826
|
return blockCurrentActivation({
|
|
839
827
|
store: prepared.callerInst.store,
|
package/esm/intrinsics/mod.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
// Host trampolines (contracts/intrinsics.md §B) and FACT-adapter intrinsic
|
|
2
|
-
// obligations (§A) — the
|
|
3
|
-
//
|
|
2
|
+
// obligations (§A) — the core subset, with instantiate-time (never
|
|
3
|
+
// call-time) capability-gated failures for everything else.
|
|
4
4
|
//
|
|
5
|
-
// Implemented
|
|
5
|
+
// Implemented from the core subset:
|
|
6
6
|
// lower-import host function call through descriptor-IR lift/lower
|
|
7
7
|
// trap FACT `Trap` import -> ComponentTrap
|
|
8
8
|
// enter/exit-sync-call degenerate sync-call bookkeeping (assert-and-count)
|
|
9
9
|
// resource-new/rep/drop sync resource paths over cabi handle tables
|
|
10
|
-
// (
|
|
11
|
-
// fixture references them at instantiation)
|
|
10
|
+
// (resources capability, implemented early: the
|
|
11
|
+
// resources fixture references them at instantiation)
|
|
12
12
|
//
|
|
13
|
-
// Everything else fails at instantiate time
|
|
14
|
-
// intrinsics.md §B schedules it — "this component needs the
|
|
13
|
+
// Everything else fails at instantiate time naming the capability that
|
|
14
|
+
// intrinsics.md §B schedules it under — "this component needs the task core"
|
|
15
15
|
// is a feature, not a crash.
|
|
16
16
|
import { canonResourceDrop, canonResourceNew, canonResourceRep, trap, } from "../cabi/mod.js";
|
|
17
17
|
import { ResourceHandle } from "../cabi/handles.js";
|
|
18
18
|
import { trapIf } from "../cabi/trap.js";
|
|
19
19
|
import { assert_ } from "../cabi/trap.js";
|
|
20
|
-
import {
|
|
20
|
+
import { dbgId, entryRefusal, maybeCurrentThread } from "../task/mod.js";
|
|
21
21
|
import { UnsupportedFeatureError } from "./errors.js";
|
|
22
22
|
import { createBackpressureDec, createBackpressureInc, createSubtaskCancel, createSubtaskDrop, createTaskCancel, createTaskReturn, createThreadYield, createWaitableJoin, createWaitableSetDrop, createWaitableSetNew, createWaitableSetPoll, createWaitableSetWait, } from "./async_builtins.js";
|
|
23
23
|
import { createAsyncStartCall, createPrepareCall, createSyncStartCall, } from "./fact_calls.js";
|
|
@@ -55,21 +55,21 @@ const FACT_TRAP_MESSAGES = {
|
|
|
55
55
|
/** Ordinal of `Trap::UncaughtException` in wasmtime's trap encoding. */
|
|
56
56
|
const TRAP_UNCAUGHT_EXCEPTION = 49;
|
|
57
57
|
export { UnsupportedFeatureError } from "./errors.js";
|
|
58
|
-
/**
|
|
59
|
-
const
|
|
60
|
-
"lower-import": "
|
|
61
|
-
"trap": "
|
|
62
|
-
"enter-sync-call": "
|
|
63
|
-
"exit-sync-call": "
|
|
64
|
-
"resource-new": "
|
|
65
|
-
"resource-rep": "
|
|
66
|
-
"resource-drop": "
|
|
67
|
-
"transcoder": "
|
|
68
|
-
"resource-transfer-own": "
|
|
69
|
-
"resource-transfer-borrow": "
|
|
58
|
+
/** Capability at which each trampoline kind stops instantiate-failing. */
|
|
59
|
+
const TRAMPOLINE_CAPABILITY = {
|
|
60
|
+
"lower-import": "core",
|
|
61
|
+
"trap": "core",
|
|
62
|
+
"enter-sync-call": "core",
|
|
63
|
+
"exit-sync-call": "core",
|
|
64
|
+
"resource-new": "resources",
|
|
65
|
+
"resource-rep": "resources",
|
|
66
|
+
"resource-drop": "resources",
|
|
67
|
+
"transcoder": "resources",
|
|
68
|
+
"resource-transfer-own": "resources",
|
|
69
|
+
"resource-transfer-borrow": "resources",
|
|
70
70
|
};
|
|
71
|
-
function
|
|
72
|
-
return
|
|
71
|
+
function capabilityOf(kind) {
|
|
72
|
+
return TRAMPOLINE_CAPABILITY[kind] ?? "task-core";
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* The borrow bookkeeping of one in-flight synchronous cross-component call,
|
|
@@ -119,7 +119,7 @@ export class SyncCallScope {
|
|
|
119
119
|
* unsupported kind fails instantiation, not the first call
|
|
120
120
|
* (plan-format.md "Executor obligations"). Unreferenced trampolines are
|
|
121
121
|
* never created and therefore never fail (intrinsics.md §B tolerates e.g.
|
|
122
|
-
* an unreferenced task-return until
|
|
122
|
+
* an unreferenced task-return until the task core exists).
|
|
123
123
|
*/
|
|
124
124
|
export function createTrampoline(decl, ctx) {
|
|
125
125
|
const fn = createTrampolineBody(decl, ctx);
|
|
@@ -139,16 +139,6 @@ export function createTrampoline(decl, ctx) {
|
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
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
142
|
/**
|
|
153
143
|
* The component instance a trampoline is declared in (wasmtime names it in
|
|
154
144
|
* every instance-scoped `Trampoline` variant). This is the static answer to
|
|
@@ -180,23 +170,12 @@ const SCOPE_TRACE = (() => {
|
|
|
180
170
|
return false;
|
|
181
171
|
}
|
|
182
172
|
})();
|
|
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
173
|
function syncScopes(ctx, site = "?") {
|
|
196
174
|
const thread = maybeCurrentThread();
|
|
197
175
|
const scopes = thread?.syncCallStack ?? ctx.syncCallStack;
|
|
198
176
|
if (SCOPE_TRACE) {
|
|
199
|
-
console.error(`[scope] ${site} act=${
|
|
177
|
+
console.error(`[scope] ${site} act=${thread ? dbgId(thread) : "NONE(->ctx fallback)"} ` +
|
|
178
|
+
`depth=${scopes.length}`);
|
|
200
179
|
}
|
|
201
180
|
return scopes;
|
|
202
181
|
}
|
|
@@ -227,8 +206,8 @@ function createTrampolineBody(decl, ctx) {
|
|
|
227
206
|
? `FACT adapter trap (code ${code ?? "?"})`
|
|
228
207
|
: `wasm trap: ${message}`);
|
|
229
208
|
};
|
|
230
|
-
// Sync-call task bookkeeping (intrinsics.md §A
|
|
231
|
-
//
|
|
209
|
+
// Sync-call task bookkeeping (intrinsics.md §A) — assert-and-count.
|
|
210
|
+
// wasmtime 47 signatures:
|
|
232
211
|
// enter-sync-call carries the caller/callee instance pair, which is what
|
|
233
212
|
// the reentrance gate below needs; balance of the bracket is asserted at
|
|
234
213
|
// component teardown by tests.
|
|
@@ -238,72 +217,31 @@ function createTrampolineBody(decl, ctx) {
|
|
|
238
217
|
// async.exit-sync-call() -> ()
|
|
239
218
|
case "enter-sync-call":
|
|
240
219
|
return (callerInstance, async_, calleeInstance) => {
|
|
241
|
-
//
|
|
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.
|
|
220
|
+
// ENTRY REFUSAL at the fused sync-call boundary.
|
|
267
221
|
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// `
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
// behaviour; it is cheap, and it is the honest place for the
|
|
280
|
-
// invariant to be asserted rather than assumed.
|
|
222
|
+
// The reference has no reentrance gate (CM#705; definitions.py @
|
|
223
|
+
// 2f13265 has no `may_enter`/`entering_set`/`enter_from`): a
|
|
224
|
+
// guest->guest call through `Store.lift` runs `canon_lift`
|
|
225
|
+
// unconditionally, and host-mediated reentrance — host -> A.f -> C.g
|
|
226
|
+
// -> host import -> host invokes C.g — is simply valid. wasmtime's
|
|
227
|
+
// fused adapters agreed all along: `enter_guest_sync_call`
|
|
228
|
+
// (47.0.3 `runtime/component/concurrent.rs:1723`) performs no
|
|
229
|
+
// reentrance check, and `fact/trampoline.rs:120-127` decides the
|
|
230
|
+
// caller==callee / ancestor pairs statically at compile time (what
|
|
231
|
+
// `test/async/trap-on-reenter.wast` cases 2 and 3 pin — a translation
|
|
232
|
+
// -time trap, not this site).
|
|
281
233
|
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
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.
|
|
234
|
+
// What this site does check is polyengine's per-instance poisoning: a
|
|
235
|
+
// callee that trapped is a corpse and may never be entered again,
|
|
236
|
+
// and the refusal names the original trap (polyengine#145). That is
|
|
237
|
+
// the whole content of this check.
|
|
299
238
|
if (typeof callerInstance === "number" &&
|
|
300
239
|
typeof calleeInstance === "number") {
|
|
301
240
|
const callerInst = ctx.componentInstance(callerInstance >>> 0);
|
|
302
241
|
const calleeInst = ctx.componentInstance(calleeInstance >>> 0);
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
trap(
|
|
306
|
-
}
|
|
242
|
+
const refusal = entryRefusal(calleeInst, callerInst, "cannot enter component instance");
|
|
243
|
+
if (refusal !== null)
|
|
244
|
+
trap(refusal);
|
|
307
245
|
}
|
|
308
246
|
// `async_` records whether the callee is *async-lifted*. wasmtime
|
|
309
247
|
// stores it on the guest task it creates here
|
|
@@ -374,7 +312,7 @@ function createTrampolineBody(decl, ctx) {
|
|
|
374
312
|
// `lift_own`/`lower_own` and `lift_borrow`/`lower_borrow`
|
|
375
313
|
// (definitions.py) with the src/dst tables named by index rather than
|
|
376
314
|
// implied by the running instance.
|
|
377
|
-
// FACT string transcoders (contracts/intrinsics.md §B
|
|
315
|
+
// FACT string transcoders (contracts/intrinsics.md §B). The plan
|
|
378
316
|
// carries the op name plus the source/destination `RuntimeMemoryIndex`es;
|
|
379
317
|
// `./transcode.ts` holds the twelve operations.
|
|
380
318
|
case "transcoder": {
|
|
@@ -382,14 +320,14 @@ function createTrampolineBody(decl, ctx) {
|
|
|
382
320
|
if (d.from64 || d.to64) {
|
|
383
321
|
// 64-bit linear memories are out of scope (https://github.com/polymorph-components/polyengine/issues/12); refusing at
|
|
384
322
|
// instantiate time keeps "instantiate-time, never call-time".
|
|
385
|
-
throw new UnsupportedFeatureError("
|
|
323
|
+
throw new UnsupportedFeatureError("task-core", `transcoder '${d.op}' over a 64-bit linear memory`);
|
|
386
324
|
}
|
|
387
325
|
if (!TRANSCODE_OPS.includes(d.op)) {
|
|
388
|
-
throw new UnsupportedFeatureError("
|
|
326
|
+
throw new UnsupportedFeatureError("task-core", `unknown string transcode operation '${d.op}'`);
|
|
389
327
|
}
|
|
390
328
|
return createTranscoder(d.op, ctx.runtimeMemory(d.from), ctx.runtimeMemory(d.to));
|
|
391
329
|
}
|
|
392
|
-
// --- 0.3 async built-ins (contracts/intrinsics.md §B
|
|
330
|
+
// --- 0.3 async built-ins (contracts/intrinsics.md §B) -------------
|
|
393
331
|
// All ported in ./async_builtins.ts; the ones that would have to block a
|
|
394
332
|
// wasm frame fail there, at the call site, with a JSPI-shaped message.
|
|
395
333
|
case "task-return":
|
|
@@ -484,7 +422,7 @@ function createTrampolineBody(decl, ctx) {
|
|
|
484
422
|
case "resource-transfer-borrow":
|
|
485
423
|
return (handle, srcTable, dstTable) => transferBorrow(ctx, handle >>> 0, srcTable, dstTable);
|
|
486
424
|
default:
|
|
487
|
-
throw new UnsupportedFeatureError(
|
|
425
|
+
throw new UnsupportedFeatureError(capabilityOf(decl.kind) === "resources" ? "resources" : "task-core", `component requires host trampoline '${decl.kind}'`);
|
|
488
426
|
}
|
|
489
427
|
}
|
|
490
428
|
// ---------------------------------------------------------------------------
|
|
@@ -526,10 +464,9 @@ function transferOwn(ctx, handle, srcTable, dstTable) {
|
|
|
526
464
|
* which is this path's stand-in for the callee `Subtask`/`Task` of
|
|
527
465
|
* definitions.py.
|
|
528
466
|
*/
|
|
529
|
-
// CONTRACT: contracts/intrinsics.md §A
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
// The reading implemented here is taken from definitions.py
|
|
467
|
+
// CONTRACT: contracts/intrinsics.md §A describes ResourceTransfer* only as
|
|
468
|
+
// "handle-table moves between component instances" — the borrow-scope
|
|
469
|
+
// interaction beyond the lender registration is taken from definitions.py
|
|
533
470
|
// (`lift_borrow`/`lower_borrow` + `Subtask.lenders`/`Task.num_borrows`) and
|
|
534
471
|
// is what makes `test/resources/borrows.wast:162` (`lend-trap`) trap.
|
|
535
472
|
function transferBorrow(ctx, handle, srcTable, dstTable) {
|
|
@@ -553,8 +553,8 @@ export function createFutureTransfer(ctx) {
|
|
|
553
553
|
* rather than removing it, so the source keeps its own.
|
|
554
554
|
*/
|
|
555
555
|
/**
|
|
556
|
-
*
|
|
557
|
-
*
|
|
556
|
+
* `instanceOf` resolves through the plan's `errorContextTables` section
|
|
557
|
+
* (contracts/plan-format.md schema) — the
|
|
558
558
|
* `TypeComponentLocalErrorContextTableIndex` space these arguments actually
|
|
559
559
|
* live in. It replaced a resource-table lookup, which shared neither the
|
|
560
560
|
* index space nor (in a multi-instance composition) the answer.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FACT string-transcoder intrinsics (`wasmtime_environ::component::Trampoline
|
|
2
|
-
// ::Transcoder`, contracts/intrinsics.md §B
|
|
2
|
+
// ::Transcoder`, contracts/intrinsics.md §B).
|
|
3
3
|
//
|
|
4
4
|
// FACT emits these when a cross-component call has to move a string between
|
|
5
5
|
// two components whose canonical `string-encoding` options disagree (or, for
|
package/esm/jspi/bridge.js
CHANGED
|
@@ -49,7 +49,8 @@ import { withActivation, claimActivationAmbient, dbgId, maybeCurrentThread, rele
|
|
|
49
49
|
* `requested` is the embedder's opt-in. We additionally require the engine to
|
|
50
50
|
* actually implement JSPI: on an engine without it every blocking site falls
|
|
51
51
|
* back to the precise `NeedsJspi` it raised before this module existed, which
|
|
52
|
-
* is the
|
|
52
|
+
* is the browser-matrix degradation path for engines without JSPI (see
|
|
53
|
+
* `just browsers`).
|
|
53
54
|
*/
|
|
54
55
|
export function chooseMode(requested, needed) {
|
|
55
56
|
if (requested === false)
|
|
@@ -395,8 +396,8 @@ export class SuspensionPoint {
|
|
|
395
396
|
* transition this point takes — produce-success, produce-throw, or
|
|
396
397
|
* `abandon` (issue #102). It is the seam a blocking built-in uses to
|
|
397
398
|
* discharge state it owns for the duration of the park (the FACT
|
|
398
|
-
* start-calls' borrow-lender scopes, contracts/intrinsics.md
|
|
399
|
-
*
|
|
399
|
+
* start-calls' borrow-lender scopes, contracts/intrinsics.md §A's
|
|
400
|
+
* trap-unwind/lender-release obligation) without having to trust that `produce` runs.
|
|
400
401
|
*
|
|
401
402
|
* INVARIANTS this hook must respect, so bridge.ts's own contracts are
|
|
402
403
|
* not disturbed:
|
package/esm/jspi/suspending.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The per-declaration suspendability marker (contracts/embedder-api.md
|
|
2
|
-
// §"Functions and async"
|
|
2
|
+
// §"Functions and async"; docs/architecture.md §5).
|
|
3
3
|
//
|
|
4
|
-
// The canonical definitions live in `@polyengine/protocol` since
|
|
4
|
+
// The canonical definitions live in `@polyengine/protocol` since §"Module identity and @polyengine/protocol":
|
|
5
5
|
// the mark is a process-global `Symbol.for("polyengine.suspending/1")` brand, so
|
|
6
6
|
// a function marked by ANY runtime copy is honored by every other copy
|
|
7
7
|
// (issue #83 — a module-local symbol made a copy-B mark invisible to copy A's
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
// surfacing far away as `NeedsJspi`).
|
|
10
10
|
//
|
|
11
11
|
// Layering: this module was import-free on purpose (jspi/ stays standalone);
|
|
12
|
-
//
|
|
12
|
+
// module identity relaxes that to "imports `@polyengine/protocol` only" — the protocol package
|
|
13
13
|
// is itself dependency-free, so jspi/ still pulls in no runtime machinery.
|
|
14
|
-
//
|
|
14
|
+
// cancellation discard (`deferCancel`/`isDeferCancel`) and abortable() (`abortable`/`isAbortable`)
|
|
15
15
|
// ride the same re-export: they are the other per-declaration host-import
|
|
16
16
|
// marks, they live in the same dependency-free package, and
|
|
17
17
|
// `exec/executor.ts` reads all three through `jspi/mod.ts`.
|
|
18
18
|
// (Host modules import the marks from `@polyengine/protocol` directly —
|
|
19
|
-
// the embedder surface stopped re-exporting the vocabulary at
|
|
19
|
+
// the embedder surface stopped re-exporting the vocabulary at host-ABI version.)
|
|
20
20
|
export { abortable, anySuspendingImport, deferCancel, isAbortable, isDeferCancel, isSuspending, suspending, } from "@polyengine/protocol";
|
package/esm/plan/loader.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// of the wire descriptor IR (contracts/descriptor-ir.md JSON) into the cabi
|
|
3
3
|
// in-memory type model (runtime/src/cabi/types.ts, the normative model).
|
|
4
4
|
//
|
|
5
|
-
// Wire -> in-memory deltas handled here
|
|
6
|
-
// friction report):
|
|
5
|
+
// Wire -> in-memory deltas handled here:
|
|
7
6
|
// - `result.err` (wire, per descriptor-ir.md) -> `result.error` (types.ts)
|
|
8
7
|
// - func params `{label, type}[]` (wire) -> unlabeled `ValType[]`
|
|
9
8
|
// (types.ts drops ABI-irrelevant names; labels are preserved separately
|
|
@@ -56,7 +55,7 @@ export class TranslateError extends Error {
|
|
|
56
55
|
* the *resource*-table mapping before, a different space) — and
|
|
57
56
|
* `task-return`'s `resultType` / raw `results` split, which lets a FACT
|
|
58
57
|
* callee task carry its declared result type.
|
|
59
|
-
* v2
|
|
58
|
+
* v2: `streamTables` / `futureTables` — the element types the
|
|
60
59
|
* stream and future built-ins need to size their copy buffers.
|
|
61
60
|
* v1 (contracts/plan-format.md v0.3): `CoreDef` gained `"unsafe-intrinsic"`.
|
|
62
61
|
* The change is purely additive, but the contract's compat rule is a strict
|
|
@@ -142,7 +141,7 @@ export function loadPlan(wire) {
|
|
|
142
141
|
}
|
|
143
142
|
}
|
|
144
143
|
// Identity tokens: one per RESOURCE, aliased through every table that
|
|
145
|
-
// names it — NOT one per table. plan-format.md
|
|
144
|
+
// names it — NOT one per table. plan-format.md "Type exports index into `resourceTables`": "one
|
|
146
145
|
// resource type can be reachable through several distinct table indices …
|
|
147
146
|
// Consumers keying per-resource state must key by `resourceTables[n]
|
|
148
147
|
// .resource`, treating table indices as aliases." Minting per-table broke
|
|
@@ -526,7 +525,8 @@ function validateExport(exp, where) {
|
|
|
526
525
|
validateTypeExport(e.type, `${where}.type`);
|
|
527
526
|
return;
|
|
528
527
|
case "module":
|
|
529
|
-
// plan-format.md
|
|
528
|
+
// The `module` export kind (contracts/plan-format.md schema notes):
|
|
529
|
+
// exported embedded core module.
|
|
530
530
|
expectString(e, "name", where);
|
|
531
531
|
expectNumber(e, "module", where);
|
|
532
532
|
return;
|
package/esm/shim/translator.js
CHANGED
|
@@ -19,8 +19,8 @@ export class Translator {
|
|
|
19
19
|
* (docs/architecture.md §10): the wire envelope's `producer` block records
|
|
20
20
|
* `{shimVersion, wasmtimeEnviron, features}`, which does NOT change when
|
|
21
21
|
* the shim wasm is rebuilt from the same source versions (e.g. a local
|
|
22
|
-
* patch or a different toolchain producing different codegen)
|
|
23
|
-
*
|
|
22
|
+
* patch or a different toolchain producing different codegen). Digesting
|
|
23
|
+
* the actual bytes is the only sound cache key
|
|
24
24
|
* component for translator identity.
|
|
25
25
|
*/
|
|
26
26
|
buildHash;
|