@polyengine/runtime 0.5.0 → 0.6.0
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 +170 -41
- 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 +318 -165
- package/esm/exec/executor.js +51 -24
- package/esm/exec/host_streams.js +31 -31
- package/esm/intrinsics/async_builtins.js +19 -8
- 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 +10 -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 +66 -42
- 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/jspi/suspending.d.ts +1 -1
- 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/task/streams.js
CHANGED
|
@@ -142,7 +142,7 @@ export class GuestBuffer {
|
|
|
142
142
|
}
|
|
143
143
|
this.progress += vs.length;
|
|
144
144
|
}
|
|
145
|
-
// ---
|
|
145
|
+
// --- direct-access byte edges (embedder-api.md §"Streams and futures" (polyengine#128)) ---
|
|
146
146
|
//
|
|
147
147
|
// `ByteWindow`, implemented for the `stream<u8>` case only. The two methods
|
|
148
148
|
// together are the copy `read`/`write` would have done, split so that the
|
|
@@ -184,7 +184,7 @@ function isDirectBuffer(b) {
|
|
|
184
184
|
* Collapses to definitions.py's `dst_buffer.write(src_buffer.read(n))`
|
|
185
185
|
* whenever neither side is a direct session — which is every guest↔guest,
|
|
186
186
|
* guest↔host-chunk and host-chunk↔host-chunk rendezvous, i.e. everything
|
|
187
|
-
* that existed before
|
|
187
|
+
* that existed before direct-access byte edge.
|
|
188
188
|
*/
|
|
189
189
|
function rendezvousCopy(src, dst, n) {
|
|
190
190
|
const srcDirect = isDirectBuffer(src);
|
|
@@ -199,12 +199,12 @@ function rendezvousCopy(src, dst, n) {
|
|
|
199
199
|
? src.runDirect(dst, n)
|
|
200
200
|
: dst.runDirect(src, n);
|
|
201
201
|
}
|
|
202
|
-
/** The
|
|
202
|
+
/** The rejection for a rendezvous of two direct-access sessions. */
|
|
203
203
|
function bothDirectError() {
|
|
204
204
|
return new TypeError("at least one side of a host-to-host rendezvous must use the chunk " +
|
|
205
205
|
"forms: two direct-access sessions cannot rendezvous with each other " +
|
|
206
206
|
"because neither side owns the memory the other would write into " +
|
|
207
|
-
|
|
207
|
+
`(embedder-api.md §"Streams and futures" ("Direct-access byte edges"), polyengine#128)`);
|
|
208
208
|
}
|
|
209
209
|
/**
|
|
210
210
|
* definitions.py `none_or_number_type` (line 1070). Guards the "temporary"
|
|
@@ -243,7 +243,7 @@ export class SharedStreamImpl {
|
|
|
243
243
|
* OUT of a guest table. The receiver — the host, or the destination of a
|
|
244
244
|
* guest-to-guest hop, in which case the immediately following lower fires
|
|
245
245
|
* `onLowered` — may now act on the transferred end. Host wrappers use it to
|
|
246
|
-
* re-arm their activity (#162, embedder-api
|
|
246
|
+
* re-arm their activity (#162, contracts/embedder-api.md §"Streams and futures"). Guest-owned
|
|
247
247
|
* objects leave it unset.
|
|
248
248
|
*/
|
|
249
249
|
onLifted = null;
|
|
@@ -261,9 +261,9 @@ export class SharedStreamImpl {
|
|
|
261
261
|
pendingOnCopyDone = null;
|
|
262
262
|
/**
|
|
263
263
|
* Observers fired once, when this stream becomes dropped — by EITHER
|
|
264
|
-
* side, including the
|
|
264
|
+
* side, including the loud component fault teardown walk (`dropSharedForTeardown`). The
|
|
265
265
|
* embedder's producer pump uses this to cancel a producer parked on an
|
|
266
|
-
* external event (
|
|
266
|
+
* external event (§"Streams and futures"'s cancellation companion: an
|
|
267
267
|
* accept-shaped producer holds a live platform resource while parked,
|
|
268
268
|
* and the reader dropping is its only stop signal). `null` = already
|
|
269
269
|
* fired.
|
|
@@ -336,7 +336,7 @@ export class SharedStreamImpl {
|
|
|
336
336
|
if (this.pendingBuffer.remain() > 0) {
|
|
337
337
|
if (dstBuffer.remain() > 0) {
|
|
338
338
|
const n = Math.min(dstBuffer.remain(), this.pendingBuffer.remain());
|
|
339
|
-
//
|
|
339
|
+
// direct-access byte edge seam (#128). `"chunk"` is the reference line verbatim.
|
|
340
340
|
const pendingIsDirect = isDirectBuffer(this.pendingBuffer);
|
|
341
341
|
const out = rendezvousCopy(this.pendingBuffer, dstBuffer, n);
|
|
342
342
|
if (out === "both-direct") {
|
|
@@ -374,7 +374,7 @@ export class SharedStreamImpl {
|
|
|
374
374
|
if (this.pendingBuffer.remain() > 0) {
|
|
375
375
|
if (srcBuffer.remain() > 0) {
|
|
376
376
|
const n = Math.min(srcBuffer.remain(), this.pendingBuffer.remain());
|
|
377
|
-
//
|
|
377
|
+
// direct-access byte edge seam (#128). `"chunk"` is the reference line verbatim.
|
|
378
378
|
const pendingIsDirect = isDirectBuffer(this.pendingBuffer);
|
|
379
379
|
const out = rendezvousCopy(srcBuffer, this.pendingBuffer, n);
|
|
380
380
|
if (out === "both-direct") {
|
|
@@ -404,7 +404,7 @@ export class SharedStreamImpl {
|
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
/**
|
|
407
|
-
*
|
|
407
|
+
* direct-access byte edge (#128): route a rendezvous whose direct session did NOT copy.
|
|
408
408
|
*
|
|
409
409
|
* Two outcomes land here, and both share one invariant: the peer's parked
|
|
410
410
|
* operation survives, no event is delivered, and the stream is not dropped
|
|
@@ -471,7 +471,7 @@ export class SharedFutureImpl {
|
|
|
471
471
|
* OUT of a guest table. The receiver — the host, or the destination of a
|
|
472
472
|
* guest-to-guest hop, in which case the immediately following lower fires
|
|
473
473
|
* `onLowered` — may now act on the transferred end. Host wrappers use it to
|
|
474
|
-
* re-arm their activity (#162, embedder-api
|
|
474
|
+
* re-arm their activity (#162, contracts/embedder-api.md §"Streams and futures"). Guest-owned
|
|
475
475
|
* objects leave it unset.
|
|
476
476
|
*/
|
|
477
477
|
onLifted = null;
|
|
@@ -511,9 +511,9 @@ export class SharedFutureImpl {
|
|
|
511
511
|
pendingOnCopyDone = null;
|
|
512
512
|
/**
|
|
513
513
|
* Observers fired once, when this future becomes dropped — by EITHER side,
|
|
514
|
-
* including the
|
|
515
|
-
* this for
|
|
516
|
-
* for a host wrapper's activity arm (#162,
|
|
514
|
+
* including the loud component fault teardown walk (`dropSharedForTeardown`). Streams grew
|
|
515
|
+
* this for resource stream producer cancellation; futures need it as the release hook
|
|
516
|
+
* for a host wrapper's activity arm (#162, §"Streams and futures"): a guest dropping
|
|
517
517
|
* its end with no host operation parked, and the `readResult()`
|
|
518
518
|
* already-dropped fast path, both bypass every other close site. `null` =
|
|
519
519
|
* already fired.
|
|
@@ -677,8 +677,8 @@ export class WritableFutureEnd extends CopyEnd {
|
|
|
677
677
|
/**
|
|
678
678
|
* Failures recorded against shared stream/future objects whose peer end died
|
|
679
679
|
* inside a trap-poisoned instance's handle table. The embedder layer consults
|
|
680
|
-
* this to reject host operations loudly (contracts/embedder-api.md
|
|
681
|
-
*
|
|
680
|
+
* this to reject host operations loudly (contracts/embedder-api.md
|
|
681
|
+
* §"Streams and futures") instead of letting them hang forever or fake a clean end-of-stream.
|
|
682
682
|
*/
|
|
683
683
|
const poisonFailures = new WeakMap();
|
|
684
684
|
/** The recorded poisoning failure for a shared stream/future value, if any. */
|
|
@@ -732,51 +732,35 @@ export function abandonSharedFuture(shared, reason) {
|
|
|
732
732
|
* **poisoned** guest instance is retired silently via `resetPending`.
|
|
733
733
|
* Notifying it would queue a phantom event into the corpse's waitables, and
|
|
734
734
|
* a later driving loop servicing it would resume machinery whose instance
|
|
735
|
-
* can no longer be entered (`tick`
|
|
735
|
+
* can no longer be entered (`tick` excludes poisoned instances). Host sentinels are
|
|
736
736
|
* not instances at all, so they are always notified.
|
|
737
737
|
*
|
|
738
|
-
* #100: THE HEALTH TEST IS "POISONED", NOT "
|
|
739
|
-
*
|
|
740
|
-
* unsound in one direction
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
* scheduler's enter/leave bracket, and the bracket's `leaveTo` runs on the
|
|
747
|
-
* way out (task/scheduler.ts `Store.tick` :905-917, task/thread.ts
|
|
748
|
-
* `Thread.resumeWith` :157-179, whose resume-side
|
|
749
|
-
* `assert_(mayEnterFrom(null))` would fire otherwise). Blocking inside a
|
|
750
|
-
* wasm frame does NOT hold the enter bracket.
|
|
751
|
-
* * (unsound converse) `mayEnter === false` does not imply "poisoned". An
|
|
752
|
-
* instance that is merely mid-call is also non-enterable, and a CALLER
|
|
753
|
-
* instance stays non-enterable for the whole duration of a
|
|
754
|
-
* cross-component (FACT) call into an instance that traps
|
|
755
|
-
* (`ComponentInstanceState.enterFrom` clears `mayEnter` on the callee's
|
|
756
|
-
* entering set only, task/mod.ts). A *different*, healthy task of that
|
|
757
|
-
* caller, parked on an end of a stream/future the trapping callee also
|
|
758
|
-
* held, was classified dead here and retired silently — stranded, the
|
|
759
|
-
* exact outcome #66 exists to prevent.
|
|
738
|
+
* #100: THE HEALTH TEST IS "POISONED", NOT "BUSY". Deadness must be judged
|
|
739
|
+
* by the poison marker and nothing weaker: any liveness proxy that also
|
|
740
|
+
* covers a merely mid-call instance is unsound in one direction and strands
|
|
741
|
+
* healthy tasks. Under such a proxy a caller mid cross-component (FACT) call
|
|
742
|
+
* into an instance that trapped would drag its *other*, healthy tasks down
|
|
743
|
+
* with it — one parked on an end of a stream/future the trapping callee also
|
|
744
|
+
* held would be classified dead here and retired silently, the exact outcome
|
|
745
|
+
* #66 exists to prevent.
|
|
760
746
|
*
|
|
761
747
|
* So the test consults the poison marker itself. It is per-instance and
|
|
762
|
-
* recorded at the single seam every
|
|
748
|
+
* recorded at the single seam every poisoning site routes through
|
|
763
749
|
* (`notifyInstancePoisoned`, task/scheduler.ts: exec/boundary.ts `poison`,
|
|
764
750
|
* `Store.tick`, `Thread.resumeWith`, the FACT cross-component catches in
|
|
765
751
|
* intrinsics/fact_calls.ts, and cabi/handles.ts's gated destructor call),
|
|
766
752
|
* and it is recorded *before* the retirement walk runs, so an instance's own
|
|
767
753
|
* parked ends still see it during its own walk. `retiredInstances` is
|
|
768
754
|
* consulted alongside it because the walk is also reachable directly (it is
|
|
769
|
-
* set at walk entry, so the two agree)
|
|
770
|
-
* per-instantiation root, which every poison site skips or releases (plan v3
|
|
771
|
-
* amendment 4, `releaseSyntheticRootOnPoison`).
|
|
755
|
+
* set at walk entry, so the two agree).
|
|
772
756
|
*
|
|
773
757
|
* Why this does not re-open review B2 (phantom events into a corpse): the
|
|
774
758
|
* concern is that a DROPPED event queued onto a waitable of an instance that
|
|
775
759
|
* can never be entered again would be serviced by a later driving loop and
|
|
776
|
-
* resume machinery
|
|
777
|
-
* again" is precisely poisoning — a mid-call instance
|
|
778
|
-
*
|
|
779
|
-
*
|
|
760
|
+
* resume machinery `tick` deliberately excludes. "Can never be entered
|
|
761
|
+
* again" is precisely poisoning — a merely mid-call instance is entirely
|
|
762
|
+
* ordinary, and its parked task resumes normally and consumes the event.
|
|
763
|
+
* The narrowed predicate
|
|
780
764
|
* therefore excludes exactly the population B2 is about, and admits only
|
|
781
765
|
* peers that will run again.
|
|
782
766
|
*
|
|
@@ -798,8 +782,8 @@ export function dropSharedForTeardown(shared) {
|
|
|
798
782
|
}
|
|
799
783
|
// The drop observers also fire on the teardown path: a stream producer
|
|
800
784
|
// parked behind a trap-poisoned reader must be cancelled the same as behind
|
|
801
|
-
// a cleanly-dropped one
|
|
802
|
-
// released the same way (#162,
|
|
785
|
+
// a cleanly-dropped one, and a host wrapper's activity arm must be
|
|
786
|
+
// released the same way (#162, §"Streams and futures"). Both classes carry the
|
|
803
787
|
// observer machinery, so this is unconditional.
|
|
804
788
|
shared.notifyDropped();
|
|
805
789
|
}
|
|
@@ -807,8 +791,9 @@ export function dropSharedForTeardown(shared) {
|
|
|
807
791
|
* Retire every live stream/future end in a trap-poisoned instance's handle
|
|
808
792
|
* table (#66).
|
|
809
793
|
*
|
|
810
|
-
* Rationale:
|
|
811
|
-
*
|
|
794
|
+
* Rationale: a trapped instance is a corpse (polyengine's per-instance
|
|
795
|
+
* poisoning divergence), so no task of this instance can ever rendezvous
|
|
796
|
+
* again. Its
|
|
812
797
|
* table's `CopyEnd`s are therefore unreachable-forever — leaving their shared
|
|
813
798
|
* objects live strands the peers: a parked HOST operation never settles (its
|
|
814
799
|
* promise hangs), and a LATER host operation would "succeed" against the
|
|
@@ -816,7 +801,7 @@ export function dropSharedForTeardown(shared) {
|
|
|
816
801
|
* Dropping the shared object now converts both into the spec-shaped DROPPED
|
|
817
802
|
* outcome, and the recorded failure lets the embedder layer brand it.
|
|
818
803
|
*
|
|
819
|
-
* Called from every
|
|
804
|
+
* Called from every poisoning site — exec/boundary.ts `poison()` (the
|
|
820
805
|
* sync-lift path), scheduler.ts `Store.tick` and thread.ts
|
|
821
806
|
* `Thread.resumeWith` (traps during a resumed thread), and the FACT
|
|
822
807
|
* cross-component catches (intrinsics/fact_calls.ts, callee side) — with the
|
|
@@ -885,7 +870,7 @@ export function retireInstanceAsyncEnds(inst, cause) {
|
|
|
885
870
|
if (failed)
|
|
886
871
|
throw first;
|
|
887
872
|
}
|
|
888
|
-
// `Store.tick`'s
|
|
873
|
+
// `Store.tick`'s poisoning site reaches the walk through this seam (its
|
|
889
874
|
// module cannot import ours — see `setOnInstancePoisoned`); the sync-lift
|
|
890
875
|
// site (exec/boundary.ts `poison`) imports it directly.
|
|
891
876
|
setOnInstancePoisoned(retireInstanceAsyncEnds);
|
|
@@ -902,7 +887,7 @@ export class ErrorContext {
|
|
|
902
887
|
this.debugMessage = debugMessage;
|
|
903
888
|
}
|
|
904
889
|
}
|
|
905
|
-
//
|
|
890
|
+
// module identity brand (contracts/embedder-api.md §"Module identity"): error-contexts are
|
|
906
891
|
// STATEFUL — they live in a component instance's handle table — so the brand
|
|
907
892
|
// exists to make a foreign one diagnosable at the lowering sites, never
|
|
908
893
|
// usable. Both this internal class and the embedder-facing wrapper
|
package/esm/task/subtask.js
CHANGED
|
@@ -72,8 +72,8 @@ export class Subtask extends Waitable {
|
|
|
72
72
|
/**
|
|
73
73
|
* Release a never-delivered subtask's lenders after its call broke off a
|
|
74
74
|
* non-poisoning exit — trap-rethrow past the CALLEE, capability bail, or
|
|
75
|
-
* an abandoned park (contracts/intrinsics.md
|
|
76
|
-
*
|
|
75
|
+
* an abandoned park (contracts/intrinsics.md §A's trap-unwind/lender-release
|
|
76
|
+
* obligation; the park legs are #102/#106).
|
|
77
77
|
*
|
|
78
78
|
* The reference has no analogue because it never resumes after a trap:
|
|
79
79
|
* the store dies with the lent handles inside it. The resolution state
|
package/esm/task/thread.js
CHANGED
|
@@ -107,64 +107,43 @@ export class Thread {
|
|
|
107
107
|
this.awaiting = null;
|
|
108
108
|
this.#store.awaiting.delete(this);
|
|
109
109
|
this.#state = "suspended";
|
|
110
|
-
//
|
|
110
|
+
// Not a bracketed resumption: post-CM#705 (definitions.py @ 2f13265)
|
|
111
|
+
// `Store.tick` resumes a ready thread with no enter/leave bracket at all,
|
|
112
|
+
// and this path — the same thread body, woken by a Promise instead of a
|
|
113
|
+
// ready-condition — matches it.
|
|
111
114
|
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
// `
|
|
115
|
-
// (
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// (and any wasm it runs) is the same — so it takes the same bracket.
|
|
119
|
-
// Without it, a trap delivered as an `awaitValue` rejection (how EVERY
|
|
120
|
-
// guest trap in a suspended activation arrives under jspi, pin (e))
|
|
121
|
-
// unwound cleanly and the instance stayed enterable: the second call of
|
|
122
|
-
// `builtin-trap-poisons-instance.wast` then re-ran the guest and
|
|
123
|
-
// reported "cannot drop busy stream" where the suite demands the
|
|
115
|
+
// What the catch preserves is polyengine's per-instance poisoning, which
|
|
116
|
+
// must be MARKER-recorded here specifically: a trap delivered as an
|
|
117
|
+
// `awaitValue` rejection is how EVERY guest trap in a suspended
|
|
118
|
+
// activation arrives under jspi (pin (e)), and if it unwound silently the
|
|
119
|
+
// second call of `builtin-trap-poisons-instance.wast` would re-run the
|
|
120
|
+
// guest and report "cannot drop busy stream" where the suite demands the
|
|
124
121
|
// poisoned-instance "cannot enter component instance".
|
|
125
122
|
//
|
|
126
|
-
// Capability signals
|
|
123
|
+
// Capability signals do not poison, for the same reason as in `tick`:
|
|
127
124
|
// they mark the RUNTIME incomplete, not the component faulted.
|
|
128
125
|
const inst = this.task.inst;
|
|
129
126
|
// A poisoned instance's parked segments never run again: this settle
|
|
130
127
|
// belongs to an activation that was in flight when a SIBLING activation
|
|
131
|
-
// trapped (
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// deadlock trap naming the export.
|
|
128
|
+
// trapped (#66 retired the handle tables). Resuming would re-enter the
|
|
129
|
+
// corpse, and asserting turned one legible trap into an assert cascade
|
|
130
|
+
// (the classic double-fault shape: `list too long`, then this assert as second
|
|
131
|
+
// victim). Retire quietly: the abandoned call's own driver reports, via
|
|
132
|
+
// its deadlock trap naming the export.
|
|
137
133
|
if (isInstancePoisoned(inst))
|
|
138
134
|
return;
|
|
139
|
-
// The enterability check below is an internal BACKSTOP, not a live gate:
|
|
140
|
-
// every dispatch site (`Store.serviceSettled`, `driveAsync`'s race-winner
|
|
141
|
-
// path) now guards enterable-or-poisoned before calling and DEFERS the
|
|
142
|
-
// tail otherwise (issue #156) — under the shared synthetic root, a host
|
|
143
|
-
// entry into any instance of the graph makes every sibling
|
|
144
|
-
// non-enterable, so this assert was reachable, and (mutating before
|
|
145
|
-
// asserting) it stranded the thread and lost the settle. It stays to
|
|
146
|
-
// protect the invariant for any future caller.
|
|
147
|
-
assert_(inst.mayEnterFrom(null), "resumeWith: parked thread's instance is not enterable from the host");
|
|
148
|
-
inst.enterFrom(null);
|
|
149
135
|
try {
|
|
150
136
|
this.#resumeInternal(value, failure);
|
|
151
137
|
}
|
|
152
138
|
catch (e) {
|
|
153
|
-
if (e instanceof NeedsJspi
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// The bracket stays broken (instance poisoned, comment above) — same
|
|
158
|
-
// as `Store.tick`: retire the poisoned table's stream/future ends so
|
|
159
|
-
// parked host peers settle instead of hanging (#66), and release the
|
|
160
|
-
// synthetic root so the poisoning stays per-instance (plan v3
|
|
161
|
-
// amendment 4; `releaseSyntheticRootOnPoison`).
|
|
162
|
-
inst.releaseSyntheticRootOnPoison();
|
|
139
|
+
if (!(e instanceof NeedsJspi) && !(e instanceof PendingCapability)) {
|
|
140
|
+
// Retire the poisoned table's stream/future ends so parked host peers
|
|
141
|
+
// settle instead of hanging (#66), and record the marker — the whole
|
|
142
|
+
// entry-refusal mechanism since #251's re-key.
|
|
163
143
|
notifyInstancePoisoned(inst, e);
|
|
164
144
|
}
|
|
165
145
|
throw e;
|
|
166
146
|
}
|
|
167
|
-
inst.leaveTo(null);
|
|
168
147
|
}
|
|
169
148
|
resume(cancelled = CANCELLED_FALSE) {
|
|
170
149
|
assert_(!this.running() && !this.done(), "resume() on a running or finished thread");
|
package/esm/task/waitable.js
CHANGED
|
@@ -13,7 +13,6 @@ export var EventCode;
|
|
|
13
13
|
EventCode[EventCode["FUTURE_WRITE"] = 5] = "FUTURE_WRITE";
|
|
14
14
|
EventCode[EventCode["TASK_CANCELLED"] = 6] = "TASK_CANCELLED";
|
|
15
15
|
})(EventCode || (EventCode = {}));
|
|
16
|
-
export const NO_EVENT = [EventCode.NONE, 0, 0];
|
|
17
16
|
/**
|
|
18
17
|
* definitions.py `class Waitable` (line 767).
|
|
19
18
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polyengine/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A WebAssembly Component Model host for JavaScript engines: plan executor, canonical ABI, 0.3 task scheduler, JSPI bridge, and embedder API.",
|
|
5
5
|
"homepage": "https://github.com/polymorph-components/polyengine#readme",
|
|
6
6
|
"repository": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"access": "public"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@polyengine/protocol": "^0.
|
|
58
|
+
"@polyengine/protocol": "^0.3.0"
|
|
59
59
|
},
|
|
60
60
|
"_generatedBy": "dnt@0.43.2"
|
|
61
61
|
}
|
|
@@ -2,8 +2,9 @@ import type { LiftLowerContext } from "./context.js";
|
|
|
2
2
|
import type { BorrowType, OwnType, ValType } from "./types.js";
|
|
3
3
|
import { ErrorContext, type SharedBase } from "../task/streams.js";
|
|
4
4
|
/**
|
|
5
|
-
* Diagnostic for a handle-table entry that carries the
|
|
6
|
-
* without being one of THIS copy's `ErrorContext`s (
|
|
5
|
+
* Diagnostic for a handle-table entry that carries the error-context brand
|
|
6
|
+
* without being one of THIS copy's `ErrorContext`s (contracts/embedder-api.md
|
|
7
|
+
* §"Module identity").
|
|
7
8
|
*
|
|
8
9
|
* A backstop, deliberately: the embedder's lowering site (embedder/values.ts)
|
|
9
10
|
* refuses a foreign error-context before it can ever reach a handle table, so
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type MemInst } from "./memory.js";
|
|
2
2
|
import type { ComponentValue } from "./types.js";
|
|
3
3
|
export declare const PLATFORM_LITTLE_ENDIAN: boolean;
|
|
4
|
-
/** Kinds these helpers handle (besides them, u8 has its own path). */
|
|
5
|
-
export declare function isBulkListKind(kind: string): boolean;
|
|
6
4
|
/**
|
|
7
5
|
* Bulk lift of `length` elements of `kind` at `ptr`. Returns `null` when the
|
|
8
6
|
* kind is not handled here (caller falls back to the per-element loop) —
|
package/types/cabi/context.d.ts
CHANGED
|
@@ -23,13 +23,24 @@ export declare function mkCanonicalOptions(partial?: Partial<CanonicalOptions>):
|
|
|
23
23
|
export declare function requireMemory(opts: LiftOptions): MemInst;
|
|
24
24
|
/**
|
|
25
25
|
* Minimal component-instance stand-in for the value interpreter: a handle
|
|
26
|
-
* table plus the `may_leave` gate. The full ComponentInstance (
|
|
27
|
-
*
|
|
26
|
+
* table plus the `may_leave` gate. The full ComponentInstance (backpressure,
|
|
27
|
+
* threads, ...) belongs to the task machinery, which cabi must not import.
|
|
28
28
|
*/
|
|
29
29
|
export interface ComponentInstanceLike {
|
|
30
30
|
handles: Table<unknown>;
|
|
31
31
|
mayLeave: boolean;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Brand marking a value as a REAL component instance (task/mod.ts
|
|
35
|
+
* `ComponentInstanceState`), as opposed to the many structural
|
|
36
|
+
* `ComponentInstanceLike` stand-ins — imported/host resources carry no
|
|
37
|
+
* instance at all, and test harnesses supply bare `{handles, mayLeave}`
|
|
38
|
+
* doubles. cabi must not depend on task/, so the symbol lives here and
|
|
39
|
+
* `ComponentInstanceState` declares it; cabi/handles.ts `isComponentInstance`
|
|
40
|
+
* is the only reader. `ComponentInstanceLike` stays deliberately structural:
|
|
41
|
+
* the brand is NOT part of it.
|
|
42
|
+
*/
|
|
43
|
+
export declare const COMPONENT_INSTANCE: unique symbol;
|
|
33
44
|
/**
|
|
34
45
|
* Borrow scopes (definitions.py `LiftLowerContext.borrow_scope`):
|
|
35
46
|
* - lifting a borrow requires the *subtask* side: `add_lender`.
|
|
@@ -53,9 +64,8 @@ export declare class LiftLowerContext {
|
|
|
53
64
|
* with `may_leave` cleared, so a realloc that lowers an import traps
|
|
54
65
|
* (`canon_lower`'s `trap_if(not ...may_leave)`, implemented here by
|
|
55
66
|
* exec/boundary.ts `createLoweredImport`). That bracket is implemented
|
|
56
|
-
* below
|
|
57
|
-
* through `canon_lift
|
|
58
|
-
* routing, leaving this bracket as the whole story. polyengine issue #147.
|
|
67
|
+
* below, and it is the whole story: the pinned reference does not route
|
|
68
|
+
* the call through `canon_lift` (CM#705). polyengine issue #147.
|
|
59
69
|
*/
|
|
60
70
|
reallocate(old: number, oldByteLength: number, alignment: number, newByteLength: number): number;
|
|
61
71
|
allocate(alignment: number, byteLength: number): number;
|
package/types/cabi/flatten.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { type CaseType, type CoreFuncType, type CoreType, type FieldType, type F
|
|
|
2
2
|
import type { CanonicalOptions, LiftOptions } from "./context.js";
|
|
3
3
|
export declare const MAX_FLAT_PARAMS = 16;
|
|
4
4
|
export declare const MAX_FLAT_ASYNC_PARAMS = 4;
|
|
5
|
-
export declare
|
|
6
|
-
export declare function setMaxFlatResultsForTesting(n: number): number;
|
|
5
|
+
export declare const MAX_FLAT_RESULTS = 1;
|
|
7
6
|
export type FlattenContext = "lift" | "lower";
|
|
8
7
|
export declare function flattenFunctype(opts: CanonicalOptions, ft: FuncType, context: FlattenContext): CoreFuncType;
|
|
9
8
|
export declare function flattenTypes(ts: ValType[], opts: LiftOptions): CoreType[];
|
|
9
|
+
export declare function flatCount(ts: ValType[], opts: LiftOptions): number;
|
|
10
10
|
export declare function flattenType(t: ValType, opts: LiftOptions): CoreType[];
|
|
11
11
|
export declare function flattenList(elemType: ValType, maybeLength: number | null, opts: LiftOptions): CoreType[];
|
|
12
12
|
export declare function flattenRecord(fields: FieldType[], opts: LiftOptions): CoreType[];
|
package/types/cabi/handles.d.ts
CHANGED
|
@@ -23,11 +23,8 @@ export declare function lowerOwn(cx: LiftLowerContext, rep: number, t: OwnType):
|
|
|
23
23
|
export declare function lowerBorrow(cx: LiftLowerContext, rep: number, t: BorrowType): number;
|
|
24
24
|
export declare function canonResourceNew(inst: ComponentInstanceLike, rt: ResourceTypeInfo, rep: number): number;
|
|
25
25
|
/**
|
|
26
|
-
* Invoke a resource destructor
|
|
27
|
-
*
|
|
28
|
-
* definitions.py `canon_resource_drop` (line 2319) does not call `rt.dtor`
|
|
29
|
-
* directly. It builds the dtor into a function instance and calls it through
|
|
30
|
-
* `Store.lift` / `Store.lower` (lines 2330-2333):
|
|
26
|
+
* Invoke a resource destructor, as definitions.py `canon_resource_drop`
|
|
27
|
+
* (@ 2f13265) does — through `Store.lift`/`Store.lower`:
|
|
31
28
|
*
|
|
32
29
|
* ```python
|
|
33
30
|
* dtor = rt.dtor or (lambda rep: [])
|
|
@@ -36,34 +33,26 @@ export declare function canonResourceNew(inst: ComponentInstanceLike, rt: Resour
|
|
|
36
33
|
* caller([h.rep])
|
|
37
34
|
* ```
|
|
38
35
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* then `leave_to(caller)` — which a trap skips, leaving the *implementing*
|
|
42
|
-
* instance permanently unenterable (poisoned).
|
|
43
|
-
*
|
|
44
|
-
* Two consequences that are easy to get wrong, both taken from the reference
|
|
45
|
-
* rather than from intuition:
|
|
36
|
+
* That lift carries NO gate (CM#705): dropping a handle whose implementing
|
|
37
|
+
* instance is mid-execution is VALID, including the dtor-less case.
|
|
46
38
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* propagating trap at its own level; here only the callee is retired.
|
|
39
|
+
* What this adds is polyengine's per-instance poisoning divergence, and it
|
|
40
|
+
* applies to `rt.impl`, not to the dropping instance: a trap out of the dtor
|
|
41
|
+
* buries the implementing instance (refusal names the original trap,
|
|
42
|
+
* polyengine#145; its live stream/future ends are retired, #66). The
|
|
43
|
+
* dropper is poisoned, if at all, by the same trap propagating at its own
|
|
44
|
+
* level. `entryRefusal`'s `caller !== callee` guard keeps a component
|
|
45
|
+
* dropping a handle to its OWN resource admissible even against a marked
|
|
46
|
+
* instance.
|
|
56
47
|
*
|
|
57
48
|
* Capability signals (`NeedsJspi`, `PendingCapability`) are not traps — see
|
|
58
|
-
* `isCapabilitySignal` in exec/boundary.ts — so they
|
|
49
|
+
* `isCapabilitySignal` in exec/boundary.ts — so they do not poison.
|
|
59
50
|
*
|
|
60
51
|
* SCOPE (#160): this is the **guest-initiated** path only. A guest-initiated
|
|
61
52
|
* drop must complete synchronously (the reference lifts the dtor with
|
|
62
53
|
* `async_ = False`), so a thenable here is a trap. The host-initiated path
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* harness instead (`hostDtorCall` in exec/boundary.ts), which is what
|
|
66
|
-
* definitions.py actually does and what unwedges #160.
|
|
54
|
+
* goes through the full lift harness instead (`hostDtorCall` in
|
|
55
|
+
* exec/boundary.ts), which is what definitions.py actually does.
|
|
67
56
|
*/
|
|
68
57
|
export declare function callDtorGated(rt: ResourceTypeInfo, rep: number, caller: unknown): void;
|
|
69
58
|
export declare function canonResourceDrop(inst: ComponentInstanceLike, rt: ResourceTypeInfo, i: number): void;
|
package/types/cabi/layout.d.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
|
-
import { type CaseType, type FieldType, type PtrType, type ValType } from "./types.js";
|
|
1
|
+
import { type CaseType, type DespecializedValType, type FieldType, type PtrType, type ValType } from "./types.js";
|
|
2
2
|
export declare function alignTo(ptr: number, alignment: number): number;
|
|
3
|
+
/**
|
|
4
|
+
* Everything the lift/lower paths need to know about one type's byte layout,
|
|
5
|
+
* computed once and shared (issue #261).
|
|
6
|
+
*
|
|
7
|
+
* Deliberately ONE flat interface rather than a discriminated union of
|
|
8
|
+
* per-kind layouts: every `Layout` then has the same hidden class, so the hot
|
|
9
|
+
* property loads in `load`/`store` stay monomorphic. Fields that do not apply
|
|
10
|
+
* to a kind carry null/0 — please do not "improve" this into a union.
|
|
11
|
+
*/
|
|
12
|
+
export interface Layout {
|
|
13
|
+
/** The despecialized type (definitions.py `despecialize`). */
|
|
14
|
+
readonly d: DespecializedValType;
|
|
15
|
+
readonly align: number;
|
|
16
|
+
readonly size: number;
|
|
17
|
+
/** record: field i occupies `base + fieldOffsets[i]`; null otherwise. */
|
|
18
|
+
readonly fieldOffsets: readonly number[] | null;
|
|
19
|
+
/** variant: discriminant width, and the offset of the case payload; 0 otherwise. */
|
|
20
|
+
readonly discSize: 0 | 1 | 2 | 4;
|
|
21
|
+
readonly payloadOffset: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function layoutOf(t: ValType, ptrType: PtrType): Layout;
|
|
3
24
|
export declare function alignment(t: ValType, ptrType: PtrType): number;
|
|
4
25
|
export declare function alignmentList(elemType: ValType, maybeLength: number | null, ptrType: PtrType): number;
|
|
5
26
|
export declare function alignmentRecord(fields: FieldType[], ptrType: PtrType): number;
|
package/types/cabi/load.d.ts
CHANGED
|
@@ -6,8 +6,16 @@ export declare function convertIntToBool(i: number): boolean;
|
|
|
6
6
|
export declare function loadList(cx: LiftLowerContext, ptr: number, elemType: ValType, maybeLength: number | null): ComponentValue;
|
|
7
7
|
export declare function loadListFromRange(cx: LiftLowerContext, ptr: number | bigint, length: number | bigint, elemType: ValType): ComponentValue;
|
|
8
8
|
export declare function loadListFromValidRange(cx: LiftLowerContext, ptr: number, length: number, elemType: ValType): ComponentValue;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `offsets[i]` is field i's byte offset from `ptr`, precomputed on the layout
|
|
11
|
+
* node, so the loop is an indexed read rather than the per-field
|
|
12
|
+
* `alignTo`/`alignment`/`elemSize` recomputation it used to be (issue #261).
|
|
13
|
+
* Taking the offsets rather than the whole `Layout` keeps the function
|
|
14
|
+
* self-consistent: its two arguments are the ones the result depends on, and
|
|
15
|
+
* there is no unchecked "these came from the same type" invariant to violate.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadRecord(cx: LiftLowerContext, ptr: number, fields: FieldType[], offsets: readonly number[]): ComponentValue;
|
|
18
|
+
export declare function loadVariant(cx: LiftLowerContext, ptr: number, cases: CaseType[], discSize: 1 | 2 | 4, payloadOffset: number): ComponentValue;
|
|
11
19
|
export declare function loadFlags(cx: LiftLowerContext, ptr: number, labels: string[]): ComponentValue;
|
|
12
20
|
export declare function unpackFlagsFromInt(i: number, labels: string[]): {
|
|
13
21
|
[label: string]: ComponentValue;
|
package/types/cabi/store.d.ts
CHANGED
|
@@ -4,9 +4,11 @@ export declare function store(cx: LiftLowerContext, v: ComponentValue, t: ValTyp
|
|
|
4
4
|
export declare function storeList(cx: LiftLowerContext, v: ArrayLike<ComponentValue>, ptr: number, elemType: ValType, maybeLength: number | null): void;
|
|
5
5
|
export declare function storeListIntoRange(cx: LiftLowerContext, v: ArrayLike<ComponentValue>, elemType: ValType): [number, number];
|
|
6
6
|
export declare function storeListIntoValidRange(cx: LiftLowerContext, v: ArrayLike<ComponentValue>, ptr: number, elemType: ValType): void;
|
|
7
|
-
|
|
7
|
+
/** The store-side mirror of `loadRecord`: indexed offset writes, no per-field
|
|
8
|
+
* layout recomputation (issue #261). */
|
|
9
|
+
export declare function storeRecord(cx: LiftLowerContext, v: Record<string, ComponentValue>, ptr: number, fields: FieldType[], offsets: readonly number[]): void;
|
|
8
10
|
/** definitions.py match_case: the value is a single-key object. */
|
|
9
11
|
export declare function matchCase(v: Record<string, ComponentValue>, cases: CaseType[]): [number, ComponentValue];
|
|
10
|
-
export declare function storeVariant(cx: LiftLowerContext, v: Record<string, ComponentValue>, ptr: number, cases: CaseType[]): void;
|
|
12
|
+
export declare function storeVariant(cx: LiftLowerContext, v: Record<string, ComponentValue>, ptr: number, cases: CaseType[], discSize: 1 | 2 | 4, payloadOffset: number): void;
|
|
11
13
|
export declare function storeFlags(cx: LiftLowerContext, v: Record<string, ComponentValue>, ptr: number, labels: string[]): void;
|
|
12
14
|
export declare function packFlagsIntoInt(v: Record<string, ComponentValue>, labels: string[]): number;
|
package/types/cabi/types.d.ts
CHANGED
|
@@ -176,14 +176,33 @@ export type ComponentValue = AsyncValue | boolean | number | bigint | string | n
|
|
|
176
176
|
export type CoreValue = number | bigint;
|
|
177
177
|
export type DespecializedValType = Exclude<ValType, TupleType | EnumType | OptionType | ResultType | MapType>;
|
|
178
178
|
export declare function despecialize(t: ValType): DespecializedValType;
|
|
179
|
+
/**
|
|
180
|
+
* The discriminant WIDTH — the single mirror of definitions.py
|
|
181
|
+
* `discriminant_type`'s arithmetic (line 1234), including its `assert(0 < n <
|
|
182
|
+
* (1 << 32))`. `math.ceil(log2(n)/8)`: 0|1 -> u8, 2 -> u16, 3 -> u32.
|
|
183
|
+
*
|
|
184
|
+
* The width is what the layout code actually wanted: on every variant lifted
|
|
185
|
+
* or lowered it used to build a `PrimType` purely to hand it to
|
|
186
|
+
* `alignment`/`elemSize` (issue #261). u8/u16/u32 each have alignment equal to
|
|
187
|
+
* their size, so this one number is both the discriminant's size and its
|
|
188
|
+
* alignment — which is why the variant layout kernels need nothing else.
|
|
189
|
+
*/
|
|
190
|
+
export declare function discriminantSize(caseCount: number): 1 | 2 | 4;
|
|
191
|
+
/**
|
|
192
|
+
* definitions.py `discriminant_type`. The table itself lives in
|
|
193
|
+
* `discriminantSize`; this is only the width -> type mapping, so the bound
|
|
194
|
+
* check, the thresholds and the error string exist once. Flattening
|
|
195
|
+
* (flatten.ts:157) and layout (layout.ts `alignmentVariant`,
|
|
196
|
+
* `elemSizeVariant`) therefore cannot drift apart on the discriminant width.
|
|
197
|
+
*/
|
|
179
198
|
export declare function discriminantType(cases: CaseType[]): PrimType;
|
|
199
|
+
export declare function caseIndexOf(cases: CaseType[]): ReadonlyMap<string, number>;
|
|
180
200
|
export declare function containsBorrow(t: ValType | null): boolean;
|
|
181
|
-
export declare function containsAsyncValue(t: ValType | null): boolean;
|
|
182
201
|
export declare function contains(t: ValType | null, p: (t: DespecializedValType) => boolean): boolean;
|
|
183
202
|
/**
|
|
184
203
|
* Structural `ValType` equality.
|
|
185
204
|
*
|
|
186
|
-
* CONTRACT (bugfix,
|
|
205
|
+
* CONTRACT (bugfix, generalized during the #18 tls smoke): naive
|
|
187
206
|
* `JSON.stringify(a) === JSON.stringify(b)` recurses into `own`/`borrow`'s
|
|
188
207
|
* `ResourceTypeInfo` — a class whose `impl` field is documented "Compared by
|
|
189
208
|
* object identity everywhere" (see `ResourceTypeInfo` above) and which cycles
|
|
@@ -191,7 +210,7 @@ export declare function contains(t: ValType | null, p: (t: DespecializedValType)
|
|
|
191
210
|
* tables that reference their types), so `JSON.stringify` throws
|
|
192
211
|
* `TypeError: Converting circular structure to JSON` on ANY type containing
|
|
193
212
|
* `own<R>`/`borrow<R>` at any depth. First hit by `task.return` result types
|
|
194
|
-
* (
|
|
213
|
+
* (polymorph-test's `list<own<test-case>>`), then by stream/future
|
|
195
214
|
* element types (polymorph-tls streams carrying resource-bearing payloads).
|
|
196
215
|
* Object-identity types (`ResourceTypeInfo`) are compared by reference, per
|
|
197
216
|
* the documented invariant.
|
package/types/digest/mod.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* **This is not embedder API.** The entry point exists as a support surface
|
|
7
7
|
* for bindgen-generated bindings — whose typed `instantiate` wrapper
|
|
8
8
|
* verifies the digest before instantiating (contracts/embedder-api.md
|
|
9
|
-
*
|
|
9
|
+
* §"Module wiring and instantiation") — and for the runtime's own internals. No host program
|
|
10
10
|
* should hand-write an import of this module.
|
|
11
11
|
*
|
|
12
12
|
* **Its contents are completely unstable: there is no compatibility promise
|