@@ -802,3 +802,73 @@ references = [
802802 "justfile (build / build-loud recipes)",
803803 "lib/parser.mly (expr_assign return/resume; record #{ )",
804804]
805+
806+ [[adr]]
807+ id = "ADR-013"
808+ status = "accepted"
809+ date = "2026-05-18"
810+ title = "Async on the WasmGC backend: transparent CPS continuation transform"
811+ context = """
812+ stdlib/Http.affine exposes one portable surface
813+ `fetch(req) -> Response / { Net, Async }`. The Deno-ESM backend lowers
814+ it to a direct `await` (#226, shipped). The WasmGC backend cannot:
815+ extern calls are synchronous, the boundary is i32-only, and the
816+ estate's async-over-wasm mechanism (#205) is callback-shaped
817+ (`-> Thenable` + thenableThen/thenableResultJson). Issue #225 Option 2
818+ (owner-chosen) requires one BYTE-IDENTICAL source surface on both
819+ targets, so the wasm backend must hide the continuation plumbing.
820+ typed-wasm is the shared convergence ABI (ADR-004); Ephapax is a
821+ co-stakeholder for the async ABI.
822+ """
823+ decision = """
824+ On the WasmGC backend ONLY, functions whose effect row includes `Async`
825+ are compiled via a selective continuation-passing (CPS) transform.
826+ Pure / non-Async functions are untouched (no codegen or perf change).
827+
828+ - Each async boundary (call to an extern returning under /Async, or to
829+ another Async function) splits the body; code after it becomes a
830+ generated continuation function whose env is the live-local capture
831+ set, marshalled via the EXISTING #199 closure ABI
832+ ([fnId@0,envPtr@4] through __indirect_function_table).
833+ - The split lowers to thenableThen(handle, <continuation-closure>) (the
834+ EXISTING #205 host->guest re-entry); the enclosing Async function
835+ itself returns a Thenable handle, so Thenable composes transparently
836+ up the call chain. At the host boundary the outermost Thenable is
837+ awaited. The programmer never sees Thenable — effect row is the
838+ abstraction, backend chooses the mechanism (as on the Deno side).
839+ - New orchestration over three proven primitives (find_free_vars, #199
840+ closure ABI, #205 re-entry); NOT a new runtime, NOT JSPI (rejected),
841+ NOT a general delimited-continuation feature.
842+
843+ Correctness obligations (binding, pre-merge):
844+ 1. A linear/own local captured into a continuation is the borrow
845+ checker's single use; double-resumption impossible (Thenable settles
846+ once; thenableThen fires once — asserted).
847+ 2. Transform triggers iff Async in fd_eff; Net/other effects ride
848+ along; fd_is_async reused as predicate.
849+ 3. Response reconstruction uses a minimal typed reader (jsonField is
850+ insufficient for headers: [(String,String)]); general decode
851+ deferred to #161. No silent lossiness.
852+ 4. Full `dune test --force` (258) green at every commit; wasm e2e
853+ parity test mirrors tests/codegen-deno/http_fetch.*.
854+ """
855+ consequences = """
856+ - Single portable Http surface across Deno-ESM and WasmGC; #160 closes
857+ only when both targets are green (joint-close with #225).
858+ - The Thenable-handle + thenableThen continuation protocol is the
859+ agreed async ABI for the typed-wasm convergence layer; Ephapax
860+ co-stakeholder review is required for the convergence-spec section.
861+ - Delivered incrementally (4 PRs, each behind the 258 gate); scope is
862+ WasmGC Async functions only.
863+ - This decision is settled; do not reopen without amending this ADR.
864+ """
865+ references = [
866+ "https://github.com/hyperpolymath/affinescript/issues/225",
867+ "https://github.com/hyperpolymath/affinescript/issues/160",
868+ "https://github.com/hyperpolymath/affinescript/pull/226",
869+ "docs/specs/async-on-wasm-cps.adoc (full design)",
870+ "docs/specs/SETTLED-DECISIONS.adoc (ADR-013 section)",
871+ "lib/codegen.ml (find_free_vars; #199 closure ABI)",
872+ "stdlib/Vscode.affine + packages/affine-vscode/mod.js (#205 thenableThen)",
873+ "typed-wasm ADR-004 (convergence / aggregate library; Ephapax)",
874+ ]
0 commit comments