Suspendable host imports: suspending()-marked sync lowers park the frame (A1) - #51
Merged
Conversation
…frame (A1)
The M2 jspi flip lit the CM-async builtins' suspension sites but never
the host-lower site: createLoweredImport threw NeedsJspi ('M2 phase 3')
on a Promise from a sync-typed import in EVERY mode, lower-import
trampolines were excluded from the Suspending wrap, and
contracts/embedder-api.md v0.1 nevertheless documented the cast as
permitted. No consumer or suite command ever drove the path (callback-ABI
consumers use async-typed imports; the suite blocks through builtins), so
the residue survived until the polymorph-iroh upstream-iroh review put
genuinely-blocking p2 guests (tokio parked in wasi:io/poll) on the
roadmap.
Design (embedder-api.md v0.2, amendment A1):
- suspending(fn) — a per-declaration marker on the imports record, the
static counterpart of v0.1's 'visible, deliberate cast'. Marking is
never inferred: per jspi pin (j) every call through a Suspending
import pays a continuation hop even on plain-value returns, and per
pin (c) a marked import reached from a start function traps — costs an
embedder must opt into per function, not per component. Unmarked
imports keep the plain convention; sync-only components keep the
zero-cost pin.
- A marked import is auto-detection evidence (chooseMode), a genuine
blocker for transitive suspendability (importer's entries get
promising-wrapped, satisfying pin (c)), and Suspending-wrapped at
importValue. The wrap decision rides executor state (LoweredIndex
set) rather than a function brand: createTrampoline's trap-recording
wrapper replaces the CoreFn identity, which silently killed the
branded-relay design (measured: the returned Promise coerced to 0
through the unwrapped import).
- The park is definitions.py canon_lower line 2286
(thread.wait_until(subtask.resolved)): blockCurrentActivation with
cancellable=false, gate HELD across the park (the #43 hold rule),
registered in pendingHostCalls so the deadlock probe counts it as
externally wakeable. The settle handler only records the outcome; all
CABI work (onResolve's result lowering — which can re-enter the guest
through realloc — and deliverResolve) is deferred to produce, running
under the suspension point's ambient claim per the issue-#24
attribution discipline. Rejections rethrow from produce and ride the
builtins' post-resume-trap rail (empirical fact (e)).
- The embedder facade relays the brand across its two wrapper layers
(dispatch closure, lazy leaf wrapper); #wrapImportFn's existing
thenable chaining gives suspending imports full result adaptation and
the WitError-rejection -> result::err conversion unchanged.
Unmarked Promise-returning sync lowers still refuse cleanly in every
mode, now naming the marker; the plain-mode guard pin in
async_lower_test.ts keeps the NeedsJspi shape.
Pinned by tests/embedder/suspending_imports_test.ts: park round trip
(post-resume continuation reaches later imports), resume-time string
lowering through guest realloc, marked-but-sync fast path, unmarked and
jspi:false refusals, unbranded rejection surfacing, WitError rejection
over a park -> err case, pin-(c) start-function trap, marker mechanics.
Gates: full core matrix green locally (build, test-rust, shells,
test-runtime 336/0, test-wasi-shims, test-ct-runner, test-bundle,
conformance 1254/0/95 xfail with 0 unexpected + 0 stale, sched-seeds,
test-ports, test-webrtc); smoke-tls green; websocket-conformance 55/55;
iroh-exam 4/5 (scenario-3 WebRTC backend failure is the documented
pre-existing environmental on this machine, upstream-consumer-findings.md
IROH-1 disposition); smoke-c0 not runnable here (missing experiment-mosh
consumer checkout).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last unlit JSPI suspension site:
createLoweredImportthrewNeedsJspi(message still tagged 'M2 phase 3') on a Promise from a sync-typed host import in every mode, whilecontracts/embedder-api.mdv0.1 documented the cast as permitted. Surfaced by the polymorph-irohupstream-irohreview: their route puts genuinely-blocking p2 guests (tokio parked inwasi:io/poll) on the consumer roadmap, and that class needs exactly this park.Contract (embedder-api.md → v0.2, amendment A1; intrinsics.md status note):
suspending(fn)is a per-declaration marker — auto-detection evidence, transitive-suspendability contamination (entries promising-wrapped per pin (c)), Suspending wrap. Never inferred: pin (j) makes every marked call pay a continuation hop and pin (c) makes marked imports illegal from start functions, so the costs are opt-in per function. Unmarked imports refuse exactly as before (message now names the marker); sync-only components keep the zero-cost pin.Mechanics: the park is definitions.py
canon_lowerline 2286 (thread.wait_until(subtask.resolved)) viablockCurrentActivation— non-cancellable, gate held (#43 hold rule), registered inpendingHostCallsfor the deadlock probe. Settle handler records only; result lowering (realloc re-entry!) anddeliverResolverun inproduceunder the resume-time ambient claim (issue-#24 discipline). Wrap decision rides executor state keyed by LoweredIndex — a function-identity brand relay dies atcreateTrampoline's trap-recording wrapper (measured: Promise coerced to 0 through the unwrapped import).Pins:
tests/embedder/suspending_imports_test.ts— park round trip, resume-time string lowering through guest realloc, WitError rejection over a park →err, unbranded rejection → failure, pin-(c) start trap, marked-sync fast path, both refusal modes, marker mechanics. Plain-mode guard stays inasync_lower_test.ts.Gates: full core matrix green locally; conformance 1254/0 with 95 xfails, 0 unexpected, 0 stale; sched-seeds green; smoke-tls green; websocket-conformance 55/55 (consumer tree verified clean before/after); iroh-exam 4/5 — scenario 3 is the documented pre-existing WebRTC-backend environmental on this machine (findings tracker, IROH-1 disposition); smoke-c0 not runnable here (no experiment-mosh checkout).
Follow-up track (separate PR): the wasi-shims blocking profile — parking-pollable kernel over this mechanism.