@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
package/esm/exec/executor.js
CHANGED
|
@@ -12,7 +12,7 @@ import { Trap } from "../cabi/trap.js";
|
|
|
12
12
|
import { ComponentInstanceState, Store } from "../task/mod.js";
|
|
13
13
|
import { anySuspendingImport, assertModeConsistent, chooseMode, isAbortable, isDeferCancel, isSuspending, planNeedsSuspension, suspendingImport, trampolineCanBlock, trampolineNeedsSuspension, } from "../jspi/mod.js";
|
|
14
14
|
import { loadPlan, PlanError, resourceIndexOfDefined, } from "../plan/loader.js";
|
|
15
|
-
import {
|
|
15
|
+
import { SYNC_ENTRY, createDtorEntry, createLiftedFunction, createLoweredImport, LiveMemory, newStats, } from "./boundary.js";
|
|
16
16
|
import { createTrampoline, createUnsafeIntrinsic, TranscodeMemory, } from "../intrinsics/mod.js";
|
|
17
17
|
/**
|
|
18
18
|
* Standing probe (CE_COPY_TRACE): log which import made a core instance
|
|
@@ -179,7 +179,7 @@ class Executor {
|
|
|
179
179
|
/** Scratch: set by `importValue` while one module's imports are resolved. */
|
|
180
180
|
sawBlockingImport = false;
|
|
181
181
|
/** LoweredIndex-es whose host functions carry the `suspending()` brand —
|
|
182
|
-
* populated by `buildLoweredImport`, read by `importValue
|
|
182
|
+
* populated by `buildLoweredImport`, read by `importValue`. */
|
|
183
183
|
suspendableLowerings = new Set();
|
|
184
184
|
/** Host trap held across a FACT exception barrier (see `HostTrapState`). */
|
|
185
185
|
trapState = { pending: undefined };
|
|
@@ -192,13 +192,13 @@ class Executor {
|
|
|
192
192
|
this.adapterBytes = input.adapters ?? new Map();
|
|
193
193
|
this.hostImports = input.imports ?? {};
|
|
194
194
|
this.verifyHash = input.verifyHash ?? true;
|
|
195
|
-
// AUTO-DETECTION IS ON
|
|
195
|
+
// AUTO-DETECTION IS ON by default. `chooseMode` picks jspi when the
|
|
196
196
|
// embedder opts in OR when the plan needs suspension: a stackful async
|
|
197
197
|
// lift, or a genuinely blocking built-in — classified per DECLARATION
|
|
198
198
|
// (`trampolineNeedsSuspension`; the async form of a copy/cancel built-in
|
|
199
199
|
// never blocks and is not evidence). An explicit `jspi: false` still
|
|
200
200
|
// forces plain, and a sync-only component never detects as needing
|
|
201
|
-
// suspension, so the
|
|
201
|
+
// suspension, so the synchronous API is untouched (pinned by
|
|
202
202
|
// bridge_test "plain mode: lifted exports still return values" and the
|
|
203
203
|
// planNeedsSuspension(hello) === false pin beside it).
|
|
204
204
|
//
|
|
@@ -223,7 +223,7 @@ class Executor {
|
|
|
223
223
|
// stackful async lift or a blocking built-in — per-declaration), and
|
|
224
224
|
// the IMPORTS RECORD (a `suspending()`-marked host function: the
|
|
225
225
|
// embedder's declared intent to park a sync-lowered frame, which no
|
|
226
|
-
// plan field can express — embedder-api.md
|
|
226
|
+
// plan field can express — contracts/embedder-api.md §"Functions and async").
|
|
227
227
|
planNeedsSuspension(loaded.wire) || anySuspendingImport(this.hostImports));
|
|
228
228
|
}
|
|
229
229
|
async verifyComponent() {
|
|
@@ -492,15 +492,15 @@ class Executor {
|
|
|
492
492
|
case "resource": {
|
|
493
493
|
// Wire the dtor + implementing instance into every concrete
|
|
494
494
|
// resource-table token for this defined resource
|
|
495
|
-
// (tolerate-if-unreferenced
|
|
495
|
+
// (tolerate-if-unreferenced; plan-format.md open item).
|
|
496
496
|
const dtor = init.dtor === null
|
|
497
497
|
? null
|
|
498
498
|
: this.resolveFunction(init.dtor, `resource ${init.index} dtor`);
|
|
499
499
|
const inst = this.componentInstance(init.instance);
|
|
500
500
|
// `init.index` is a DefinedResourceIndex; resource *tables* key off
|
|
501
501
|
// the component-wide ResourceIndex, which counts imported resources
|
|
502
|
-
// first (plan-format.md
|
|
503
|
-
//
|
|
502
|
+
// first (the `importedResources` field, contracts/plan-format.md
|
|
503
|
+
// schema; wasmtime `Component::resource_index`).
|
|
504
504
|
const resourceIndex = resourceIndexOfDefined(this.loaded, init.index);
|
|
505
505
|
this.wire.resourceTables.forEach((table, tableIndex) => {
|
|
506
506
|
if (table.kind === "concrete" && table.resource === resourceIndex) {
|
|
@@ -622,17 +622,22 @@ class Executor {
|
|
|
622
622
|
syncCallStack: this.syncCallStack,
|
|
623
623
|
allInstances: () => this.componentInstances.values(),
|
|
624
624
|
});
|
|
625
|
-
//
|
|
626
|
-
// (see
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
// (
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
625
|
+
// Every SYNC-TYPED export additionally carries a plain-entered
|
|
626
|
+
// variant (see SYNC_ENTRY, contracts/embedder-api.md §"Functions and async"):
|
|
627
|
+
// in jspi mode the promising-wrapped entry above necessarily returns
|
|
628
|
+
// a Promise, which some host contexts cannot use however promptly it
|
|
629
|
+
// resolves — a JS class constructor cannot await it at all, and the
|
|
630
|
+
// embedder's `sync()` adapter exists to ask for the synchronous form
|
|
631
|
+
// of any sync-typed export. Async-typed exports have no synchronous
|
|
632
|
+
// form by definition and get none.
|
|
633
|
+
//
|
|
634
|
+
// Deliberately NOT noteEntry()-recorded — this is the documented
|
|
635
|
+
// exception to the bridge invariant (entries wrapped iff imports
|
|
636
|
+
// wrapped), safe because a synchronously-completing activation never
|
|
637
|
+
// reaches the Suspending seam. sync() extends the exception from
|
|
638
|
+
// constructors to all sync entries.
|
|
639
|
+
if (this.suspensionMode === "jspi" && !ft.async) {
|
|
640
|
+
value[SYNC_ENTRY] = createLiftedFunction({
|
|
636
641
|
name: `${path} (sync entry)`,
|
|
637
642
|
ft,
|
|
638
643
|
opts,
|
|
@@ -642,6 +647,12 @@ class Executor {
|
|
|
642
647
|
trapState: this.trapState,
|
|
643
648
|
syncCallStack: this.syncCallStack,
|
|
644
649
|
allInstances: () => this.componentInstances.values(),
|
|
650
|
+
// sync() arm 2: a synchronous caller cannot be deferred by the
|
|
651
|
+
// hop-quiescence gate, so it refuses (SyncEntryBusy) instead.
|
|
652
|
+
// This deliberately changes constructor behaviour: the
|
|
653
|
+
// constructor sync entry previously bypassed the gate
|
|
654
|
+
// entirely, a latent lift-corruption window.
|
|
655
|
+
refuseOnEntryHops: true,
|
|
645
656
|
});
|
|
646
657
|
}
|
|
647
658
|
return { kind: "value", value };
|
|
@@ -664,8 +675,9 @@ class Executor {
|
|
|
664
675
|
reason: "type export: no runtime surface",
|
|
665
676
|
};
|
|
666
677
|
case "module": {
|
|
667
|
-
// plan-format.md
|
|
668
|
-
// surfaces as the already-compiled
|
|
678
|
+
// The `module` export kind (contracts/plan-format.md schema notes):
|
|
679
|
+
// an exported embedded core module surfaces as the already-compiled
|
|
680
|
+
// `WebAssembly.Module` — the same
|
|
669
681
|
// compilation `instantiate-module` initializers use.
|
|
670
682
|
const module = this.modules[exp.module];
|
|
671
683
|
if (module === undefined) {
|
|
@@ -747,7 +759,7 @@ class Executor {
|
|
|
747
759
|
return value;
|
|
748
760
|
const optionsAsync = (i) => this.wire.canonicalOptions[i]?.async === true;
|
|
749
761
|
const d = decl;
|
|
750
|
-
// Host lowers
|
|
762
|
+
// Host lowers: `trampolineCanBlock` classifies DECLARATIONS and a
|
|
751
763
|
// `lower-import` declaration says nothing about the host's intent — the
|
|
752
764
|
// evidence is the `suspending()` brand on the host function, recorded by
|
|
753
765
|
// `buildLoweredImport` into `suspendableLowerings` (which resolving this
|
|
@@ -931,14 +943,14 @@ class Executor {
|
|
|
931
943
|
const ft = this.funcType(decl.type, `import '${label}'`);
|
|
932
944
|
const opts = this.resolveOptions(decl.options);
|
|
933
945
|
const suspendable = isSuspending(value);
|
|
934
|
-
//
|
|
946
|
+
// cancellation discard (contracts/embedder-api.md §"Functions and async"): does this import
|
|
935
947
|
// opt out of cancel-discard? Unlike `suspendable` above, this needs no
|
|
936
948
|
// executor-state detour — the brand is consumed by `createLoweredImport`
|
|
937
949
|
// itself (it only decides which `onCancel` the lowered import installs, not
|
|
938
950
|
// whether the CoreFn gets wrapped), so nothing downstream has to read a
|
|
939
951
|
// brand off a replaced function identity.
|
|
940
952
|
const deferCancel = isDeferCancel(value);
|
|
941
|
-
//
|
|
953
|
+
// abortable() (same section): does this import want a per-call `AbortSignal`?
|
|
942
954
|
// Read exactly like `deferCancel` above and for the same reason — the
|
|
943
955
|
// brand is consumed inside `createLoweredImport`, which mints the
|
|
944
956
|
// controller and appends the signal itself, so no function identity is
|
|
@@ -968,7 +980,7 @@ class Executor {
|
|
|
968
980
|
/**
|
|
969
981
|
* Resolve one plan import against the host-provided import record: index by
|
|
970
982
|
* the component's exact import string, then walk `path` (instance imports —
|
|
971
|
-
* plan-format.md
|
|
983
|
+
* `imports[].path`, contracts/plan-format.md schema).
|
|
972
984
|
*/
|
|
973
985
|
lookupHostImport(name, path, label) {
|
|
974
986
|
if (!(name in this.hostImports)) {
|
package/esm/exec/host_streams.js
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
// the event loop".
|
|
43
43
|
//
|
|
44
44
|
// Retention, stated as the rule the arm implements (#162, embedder-api
|
|
45
|
-
//
|
|
45
|
+
// §"Streams and futures"): the arm is live iff the host holds a retained end, a parked
|
|
46
46
|
// host operation, or an unfinished producer pump. Which ends the host holds
|
|
47
47
|
// follows from where the wrapper came from — a host-CREATED stream keeps its
|
|
48
48
|
// writable end across every lower (only readable ends transfer,
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
// a guest and then never writes to it or drops it will *hang* rather than
|
|
57
57
|
// trap. That is the honest outcome — the component is not deadlocked, the
|
|
58
58
|
// embedder simply has not done its half — and it matches how any other
|
|
59
|
-
// unresolved Promise behaves in JS. That policy is unchanged by
|
|
59
|
+
// unresolved Promise behaves in JS. That policy is unchanged by deadlock-verdict suppression; what
|
|
60
60
|
// changed is that the claim now EXPIRES with retention, so a store that once
|
|
61
61
|
// round-tripped a stream through the host no longer misreports every later
|
|
62
62
|
// genuine deadlock as this hang.
|
|
63
63
|
//
|
|
64
|
-
// The inverse case is NOT a hang (#66, embedder-api
|
|
64
|
+
// The inverse case is NOT a hang (#66, contracts/embedder-api.md §"Streams and futures"): when the
|
|
65
65
|
// GUEST side dies — a trap poisons the instance holding the peer end — the
|
|
66
66
|
// poisoned table's ends are retired (task/streams.ts
|
|
67
67
|
// `retireInstanceAsyncEnds`), so a parked host operation settles DROPPED-
|
|
@@ -186,14 +186,14 @@ export class HostBuffer {
|
|
|
186
186
|
}
|
|
187
187
|
return out;
|
|
188
188
|
}
|
|
189
|
-
// ---
|
|
189
|
+
// --- `ByteWindow` (embedder-api.md §"Streams and futures" ("Direct-access byte edges"), polyengine#128) ---
|
|
190
190
|
//
|
|
191
191
|
// A host buffer can be the PEER of a direct session on the other end of a
|
|
192
192
|
// host↔host rendezvous. Which of the two shapes it takes follows from the
|
|
193
193
|
// direction it was built for, exactly as `read`/`write` above do:
|
|
194
194
|
//
|
|
195
195
|
// * SOURCE (`values !== null`, a parked `write`): the window is a view of
|
|
196
|
-
// the offered chunk itself — the
|
|
196
|
+
// the offered chunk itself — the stream/future round-trip borrow, scoped to the callback. No
|
|
197
197
|
// extra copy at all.
|
|
198
198
|
// * DESTINATION (`values === null`, a parked/arriving `read(max)`): there
|
|
199
199
|
// is no landing zone to view, so the window is a fresh scratch; the
|
|
@@ -255,14 +255,14 @@ export class HostBuffer {
|
|
|
255
255
|
* driving loop treats "waiting for the embedder" as progress-is-possible
|
|
256
256
|
* rather than deadlock. Re-arms after every notification.
|
|
257
257
|
*
|
|
258
|
-
* RETENTION IS THE LIVENESS RULE (#162, embedder-api
|
|
258
|
+
* RETENTION IS THE LIVENESS RULE (#162, contracts/embedder-api.md §"Streams and futures"). The arm
|
|
259
259
|
* is live iff the host retains a way to act on this shared object: a retained
|
|
260
260
|
* end, a parked host operation, or an unfinished producer pump. The claim it
|
|
261
261
|
* makes to the deadlock verdicts — "the embedder may still act" — therefore
|
|
262
262
|
* *expires*. Three state transitions implement it:
|
|
263
263
|
*
|
|
264
264
|
* * `close()` — terminal: DROPPED, an explicit drop, or the shared object's
|
|
265
|
-
* drop observers (either end, the
|
|
265
|
+
* drop observers (either end, the loud component fault teardown walk). Nothing can revive
|
|
266
266
|
* the wrapper.
|
|
267
267
|
* * `disarm()` — NON-terminal: the host handed its last end back to a guest
|
|
268
268
|
* (a lifted stream/future lowered back in — the identity round trip). The
|
|
@@ -319,9 +319,9 @@ class HostActivity {
|
|
|
319
319
|
* outstanding — by handing the store to the *same* loop an export call
|
|
320
320
|
* would have used, `driveStoreAsync`. Without the asynchronous half a guest
|
|
321
321
|
* parked in a background forwarding task would never be resumed to consume
|
|
322
|
-
* what we just offered, and the host read would await forever (
|
|
323
|
-
*
|
|
324
|
-
* awaited `store.pendingHostCalls`, so a writer parked on a
|
|
322
|
+
* what we just offered, and the host read would await forever (host-pump
|
|
323
|
+
* starvation: the previous local drain only serviced `store.awaiting` and
|
|
324
|
+
* never awaited `store.pendingHostCalls`, so a writer parked on a
|
|
325
325
|
* Promise-returning host import stalled the reader).
|
|
326
326
|
*
|
|
327
327
|
* Traps from the synchronous half propagate to the caller of the host
|
|
@@ -435,7 +435,7 @@ class HostActivity {
|
|
|
435
435
|
}
|
|
436
436
|
/**
|
|
437
437
|
* The host retains no way to act: its lifted end was lowered back into a
|
|
438
|
-
* guest, which now owns it (#162,
|
|
438
|
+
* guest, which now owns it (#162, §"Streams and futures"). NON-terminal — a re-lift
|
|
439
439
|
* of the same shared object restores retention via `rearm()`.
|
|
440
440
|
*
|
|
441
441
|
* Resolving the stale arm is required, not tidiness: a `driveAsync` parked
|
|
@@ -455,7 +455,7 @@ class HostActivity {
|
|
|
455
455
|
r?.();
|
|
456
456
|
}
|
|
457
457
|
/**
|
|
458
|
-
* A lift handed the host the readable end again — the
|
|
458
|
+
* A lift handed the host the readable end again — the stream/future round-trip cache-hit wrapper
|
|
459
459
|
* for a shared object that round-tripped back out of the guest (#162).
|
|
460
460
|
* A no-op for a closed activity (the object is gone for good) and for one
|
|
461
461
|
* that was never disarmed.
|
|
@@ -504,7 +504,7 @@ class DirectScope {
|
|
|
504
504
|
if (this.marked + n > this.capacity) {
|
|
505
505
|
throw new TypeError(`${who}(${n}) would take the invocation's cumulative mark to ` +
|
|
506
506
|
`${this.marked + n}, past the ${this.capacity} byte(s) the view ` +
|
|
507
|
-
`held on entry (embedder-api
|
|
507
|
+
`held on entry (embedder-api.md §"Streams and futures" ("Direct-access byte edges"))`);
|
|
508
508
|
}
|
|
509
509
|
this.marked += n;
|
|
510
510
|
}
|
|
@@ -512,8 +512,8 @@ class DirectScope {
|
|
|
512
512
|
if (!this.#live) {
|
|
513
513
|
throw new TypeError("this direct-access view is dead: a DirectDestination/DirectSource " +
|
|
514
514
|
"is scoped to the synchronous callback invocation it was passed " +
|
|
515
|
-
"to, and retaining one past its return is misuse (embedder-api " +
|
|
516
|
-
"
|
|
515
|
+
"to, and retaining one past its return is misuse (embedder-api.md " +
|
|
516
|
+
`§"Streams and futures" ("Direct-access byte edges"), polyengine#128)`);
|
|
517
517
|
}
|
|
518
518
|
}
|
|
519
519
|
/**
|
|
@@ -544,7 +544,7 @@ class DirectSession {
|
|
|
544
544
|
total = 0;
|
|
545
545
|
/** The callback said `"done"`, or the session failed / was settled. */
|
|
546
546
|
ended = false;
|
|
547
|
-
/** `ended` because the callback said so (
|
|
547
|
+
/** `ended` because the callback said so (loud component fault precision; see `DirectSessionInfo`). */
|
|
548
548
|
endedByVerdict = false;
|
|
549
549
|
/** Installed in the shared object's pending slot right now. */
|
|
550
550
|
pending = false;
|
|
@@ -567,10 +567,10 @@ class DirectSession {
|
|
|
567
567
|
return false;
|
|
568
568
|
}
|
|
569
569
|
read(_n) {
|
|
570
|
-
throw new Error("internal: a direct session must go through the
|
|
570
|
+
throw new Error("internal: a direct session must go through the direct-access byte edge seam");
|
|
571
571
|
}
|
|
572
572
|
write(_vs) {
|
|
573
|
-
throw new Error("internal: a direct session must go through the
|
|
573
|
+
throw new Error("internal: a direct session must go through the direct-access byte edge seam");
|
|
574
574
|
}
|
|
575
575
|
// --- the direct protocol ---
|
|
576
576
|
runDirect(peer, n) {
|
|
@@ -600,7 +600,7 @@ class DirectSession {
|
|
|
600
600
|
scope.die();
|
|
601
601
|
if (verdict !== "more" && verdict !== "done") {
|
|
602
602
|
this.#fail(new TypeError(`a direct-access callback must return "more" or "done", got ` +
|
|
603
|
-
`${JSON.stringify(verdict)} (embedder-api
|
|
603
|
+
`${JSON.stringify(verdict)} (embedder-api.md §"Streams and futures" ("Direct-access byte edges"))`));
|
|
604
604
|
return "failed";
|
|
605
605
|
}
|
|
606
606
|
const k = scope.marked;
|
|
@@ -614,7 +614,7 @@ class DirectSession {
|
|
|
614
614
|
}
|
|
615
615
|
this.#fail(new TypeError('a direct-access callback returned "more" without marking any ' +
|
|
616
616
|
"bytes; a session that has nothing to offer retracts by " +
|
|
617
|
-
'returning "done" (embedder-api
|
|
617
|
+
'returning "done" (embedder-api.md §"Streams and futures" ("Direct-access byte edges") (polyengine#128))'));
|
|
618
618
|
return "failed";
|
|
619
619
|
}
|
|
620
620
|
// Marks acknowledge ON CLEAN RETURN ONLY: this is the first and only
|
|
@@ -662,18 +662,18 @@ class DirectSession {
|
|
|
662
662
|
s?.("reissue");
|
|
663
663
|
}
|
|
664
664
|
}
|
|
665
|
-
/**
|
|
665
|
+
/** direct-access byte edge is `stream<u8>` only; `null` (zero-width) is not u8 either. */
|
|
666
666
|
function requireU8Element(t, who) {
|
|
667
667
|
if (t === null || despecialize(t).kind !== "u8") {
|
|
668
668
|
throw new TypeError(`${who} is available on stream<u8> only; this stream's element type ` +
|
|
669
669
|
`is ${t === null ? "the zero-width payload" : despecialize(t).kind} ` +
|
|
670
|
-
`(embedder-api
|
|
670
|
+
`(embedder-api.md §"Streams and futures" ("Direct-access byte edges") (polyengine#128))`);
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
673
|
/**
|
|
674
674
|
* Attach host-activity bookkeeping to a shared object at the CABI seam.
|
|
675
675
|
*
|
|
676
|
-
* `kind` is the retention model (#162,
|
|
676
|
+
* `kind` is the retention model (#162, §"Streams and futures") — WHICH ends the host
|
|
677
677
|
* holds, which is decided entirely by where the wrapper came from:
|
|
678
678
|
*
|
|
679
679
|
* * `"created"` — `hostStream()`/`hostFuture()`. Only READABLE ends
|
|
@@ -700,7 +700,7 @@ function bindOnLower(shared, activity, kind, alsoOnLowered) {
|
|
|
700
700
|
// activity binding for future lowers (review advisory, host-streams
|
|
701
701
|
// round). The public entry points cannot get here with a wrapped object —
|
|
702
702
|
// `hostStreamFor`/`hostFutureFor` return the cached wrapper instead
|
|
703
|
-
// (
|
|
703
|
+
// (§"Streams and futures") — so a trip here is a bug in this module. The class field
|
|
704
704
|
// initializes to null; == null covers both sentinels.
|
|
705
705
|
assert_(holder.onLowered == null, "internal: a second host wrapper was built for an already-wrapped " +
|
|
706
706
|
"stream/future (the wrapper cache should have returned the first)");
|
|
@@ -768,13 +768,13 @@ function mkStreamEnds(shared, activity) {
|
|
|
768
768
|
else
|
|
769
769
|
activity.notify();
|
|
770
770
|
};
|
|
771
|
-
/** The live direct session on each end, if any (
|
|
771
|
+
/** The live direct session on each end, if any (direct-access byte edge, polyengine#128). */
|
|
772
772
|
const direct = {
|
|
773
773
|
read: null,
|
|
774
774
|
write: null,
|
|
775
775
|
};
|
|
776
776
|
/**
|
|
777
|
-
* Drive one direct session from park to end
|
|
777
|
+
* Drive one direct session from park to end.
|
|
778
778
|
*
|
|
779
779
|
* Two shapes reach us, and the difference is *which side arrived second*:
|
|
780
780
|
*
|
|
@@ -841,8 +841,8 @@ function mkStreamEnds(shared, activity) {
|
|
|
841
841
|
};
|
|
842
842
|
/** Shared tail of `cancelWrite`/`cancelRead` for a parked direct session. */
|
|
843
843
|
const cancelDirect = (session) => {
|
|
844
|
-
//
|
|
845
|
-
// total (
|
|
844
|
+
// direct-access byte edge: cancelling RETRACTS the session — it resolves with its running
|
|
845
|
+
// total (future abandonment's indistinguishability caveats unchanged). `shared.cancel()`
|
|
846
846
|
// only when the session actually holds the pending slot: a session caught
|
|
847
847
|
// between two issuances holds nothing, and `SharedBase.cancel` asserts
|
|
848
848
|
// that something is pending.
|
|
@@ -919,7 +919,7 @@ function mkStreamEnds(shared, activity) {
|
|
|
919
919
|
},
|
|
920
920
|
writeDirect(produce, info) {
|
|
921
921
|
// Same one-in-flight-per-end rule, same wording shape as `write`:
|
|
922
|
-
// `writeDirect` participates in it exactly as `write` does
|
|
922
|
+
// `writeDirect` participates in it exactly as `write` does.
|
|
923
923
|
if (parked.write) {
|
|
924
924
|
throw new TypeError("a write is already in flight on this stream's writable end; " +
|
|
925
925
|
"await it or cancelWrite() first");
|
|
@@ -1025,8 +1025,8 @@ function mkStreamEnds(shared, activity) {
|
|
|
1025
1025
|
};
|
|
1026
1026
|
}
|
|
1027
1027
|
/**
|
|
1028
|
-
* One host wrapper per shared object, by identity (embedder-api
|
|
1029
|
-
*
|
|
1028
|
+
* One host wrapper per shared object, by identity (contracts/embedder-api.md
|
|
1029
|
+
* §"Streams and futures"). A stream/future value that round-trips host → guest → host lifts back
|
|
1030
1030
|
* as the SAME wrapper the host already holds, so wrapping is idempotent —
|
|
1031
1031
|
* there is never a second `HostActivity` competing to pump one shared object
|
|
1032
1032
|
* (the hazard the old double-wrap assert guarded against), and the readable
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The 0.3 async canonical built-ins, as host trampolines
|
|
2
|
-
// (contracts/intrinsics.md §B
|
|
2
|
+
// (contracts/intrinsics.md §B): task.{return,cancel},
|
|
3
3
|
// backpressure.{set,inc,dec}, waitable-set.{new,wait,poll,drop},
|
|
4
4
|
// waitable.join, subtask.{drop,cancel} and thread.yield.
|
|
5
5
|
//
|
|
@@ -77,8 +77,9 @@ export function createTaskReturn(decl, ctx) {
|
|
|
77
77
|
//
|
|
78
78
|
// Plan v3 enables this for FACT cross-component tasks too: the callee
|
|
79
79
|
// task's declared result type is now resolvable from the raw
|
|
80
|
-
// `TypeTupleIndex` `prepare-call` carried (
|
|
81
|
-
//
|
|
80
|
+
// `TypeTupleIndex` `prepare-call` carried (the task-return trampoline's
|
|
81
|
+
// raw `results` key + interned `resultType`, contracts/plan-format.md
|
|
82
|
+
// schema; wired in fact_calls.ts). It remains skipped for the one
|
|
82
83
|
// case v3 does not answer — a callee the plan maps no `task.return`
|
|
83
84
|
// tuple for, where `ft.results` is a placeholder rather than a
|
|
84
85
|
// declaration (`factResultTypesKnown === false`); comparing against a
|
|
@@ -415,7 +416,7 @@ export function createSubtaskCancel(decl, inst, mode = "plain") {
|
|
|
415
416
|
// cancel sits parked non-cancellably, which is determinate, so the
|
|
416
417
|
// genuine BLOCKED answer is still immediate. Host-import subtasks
|
|
417
418
|
// carry no callee task, and their state cannot be mid-hop: the
|
|
418
|
-
// default
|
|
419
|
+
// default onCancel resolves them before this branch is ever
|
|
419
420
|
// reached, and a `deferCancel` import's no-op onCancel leaves them
|
|
420
421
|
// simply unresolved — either way the pre-jspi immediate answer
|
|
421
422
|
// stands.
|
|
@@ -495,6 +496,13 @@ function requireWaitableSet(inst, si, what) {
|
|
|
495
496
|
trapIf(!(wset instanceof WaitableSet), `${what}: handle ${si} is not a waitable set`);
|
|
496
497
|
return wset;
|
|
497
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* The two event payload words. Hoisted out of `unpackEvent` because
|
|
501
|
+
* cabi/layout.ts and cabi/types.ts memoize on `ValType` identity (issue
|
|
502
|
+
* #261): a fresh literal per call is a guaranteed cache miss plus a
|
|
503
|
+
* `WeakMap.set` on immediate garbage, twice per event delivered.
|
|
504
|
+
*/
|
|
505
|
+
const EVENT_PAYLOAD_TYPE = Object.freeze({ kind: "u32" });
|
|
498
506
|
/**
|
|
499
507
|
* definitions.py `unpack_event` (line 2422): store the two payload words at
|
|
500
508
|
* `ptr` and return the event code.
|
|
@@ -502,11 +510,11 @@ function requireWaitableSet(inst, si, what) {
|
|
|
502
510
|
function unpackEvent(opts, inst, ptr, e) {
|
|
503
511
|
const [event, p1, p2] = e;
|
|
504
512
|
const cx = new LiftLowerContext(cabiOptions(opts), inst, null);
|
|
505
|
-
storeValue(cx, p1,
|
|
506
|
-
storeValue(cx, p2,
|
|
513
|
+
storeValue(cx, p1, EVENT_PAYLOAD_TYPE, ptr);
|
|
514
|
+
storeValue(cx, p2, EVENT_PAYLOAD_TYPE, ptr + 4);
|
|
507
515
|
return event;
|
|
508
516
|
}
|
|
509
|
-
// Structural `ValType` equality (the
|
|
517
|
+
// Structural `ValType` equality (the circular-structure bugfix) moved to cabi/types.ts
|
|
510
518
|
// (`valTypesEqual`) when the #18 tls smoke found its stream-element sibling;
|
|
511
519
|
// the contract note lives there now.
|
|
512
520
|
/** Unused-import guard: `trap` is re-exported for symmetry with cabi. */
|
|
@@ -83,7 +83,7 @@ export function createUnsafeIntrinsic(symbol,
|
|
|
83
83
|
inst) {
|
|
84
84
|
const match = /^context-(get|set)-i32-(\d+)$/.exec(symbol);
|
|
85
85
|
if (match === null) {
|
|
86
|
-
throw new UnsupportedFeatureError("
|
|
86
|
+
throw new UnsupportedFeatureError("task-core", `component imports the unsafe intrinsic '${symbol}', which has no ` +
|
|
87
87
|
`portable meaning in a JS host (only context.{get,set} do)`);
|
|
88
88
|
}
|
|
89
89
|
const slot = Number(match[2]);
|
package/esm/intrinsics/errors.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// Shared failure type for trampolines/intrinsics
|
|
2
|
-
//
|
|
3
|
-
// without importing the (much larger) trampoline dispatcher.
|
|
4
|
-
/** Instantiate-time failure for functionality
|
|
1
|
+
// Shared failure type for trampolines/intrinsics gated on a not-yet-built
|
|
2
|
+
// runtime capability. Split out of ./mod.ts so sibling intrinsic modules can
|
|
3
|
+
// raise it without importing the (much larger) trampoline dispatcher.
|
|
4
|
+
/** Instantiate-time failure for functionality gated on a missing capability. */
|
|
5
5
|
export class UnsupportedFeatureError extends Error {
|
|
6
|
-
|
|
7
|
-
constructor(
|
|
8
|
-
super(`${what} —
|
|
9
|
-
`executor (contracts/intrinsics.md §B)`);
|
|
10
|
-
this.
|
|
6
|
+
capability;
|
|
7
|
+
constructor(capability, what) {
|
|
8
|
+
super(`${what} — needs the "${capability}" capability, not yet implemented ` +
|
|
9
|
+
`in the current executor (contracts/intrinsics.md §B)`);
|
|
10
|
+
this.capability = capability;
|
|
11
11
|
this.name = "UnsupportedFeatureError";
|
|
12
12
|
}
|
|
13
13
|
}
|