feat(codegen): #225 PR3c — WasmGC CPS transform, Async→Async chaining#238
Merged
Conversation
A continuation may itself be an async boundary; Thenables now compose
up the call chain. The transform is reachable from the ExprLambda
continuation-body site via a forward-ref hook (async_transform_hook,
set at module init — avoids relocating the transform into the gen_expr
rec group), so it re-applies recursively. detect_async_base_case gains
~extra (live locals capturable at the call site, incl. the outer
binder/captures for a recursively-transformed inner continuation); the
single-boundary rejection on cont is dropped (chaining); a nested
async primitive in a pre value is still unsupported ⇒ fall back.
Recursion terminates: each application peels one boundary off a
strictly-smaller finite continuation.
Three further latent defects in the never-fully-exercised codegen,
all surfaced by the first-ever NESTED lambda (a chained continuation),
fixed at root:
1. find_free_vars ExprBlock computed the tail expr's free vars with
the original bound_vars, not the stmt-threaded set, so block-local
`let`s were reported free — detect's escaping check then
spuriously rejected chains. Now threads bound_after.
2. ExprLambda reserved lambda_id but bumped next_lambda_id only after
body-gen, so a lambda created while lowering this body reused the
id. next_lambda_id is now advanced before body-gen.
3. ExprLambda rebuilt the result ctx from the pre-body ctx_after_env,
discarding a nested lambda + its types/globals/datas; and the
closure stored the pre-reserved id while the element segment maps
table slot = lambda LIST POSITION. Now: post-body module state is
threaded forward (enclosing locals kept from ctx_after_env), and
the closure stores the lambda's append position (= table slot),
correct regardless of nesting order.
All three were invisible pre-PR3c (no nested lambdas ever existed).
New e2e tests/codegen/http_cps_chain.{affine,mjs}: 2-hop chain,
launch -> req A -> outer cont issues req B -> inner cont combines
s1(=200) with B status(=201) ⇒ 200201; A issued sync, B deferred to
the continuation. Full tools/run_codegen_wasm_tests.sh green (PR2
base + PR3a capture + PR1 skeleton, no regression); dune test --force
260 green.
Conforms to typed-wasm ADR-005 accessor model. Refs #225 #160
🔍 Hypatia Security ScanFindings: 44 issues detected
View findings[
{
"reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
"type": "banned",
"file": "AI.a2ml",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Superseded by 0-AI-MANIFEST.a2ml",
"type": "banned",
"file": "AI.djot",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Issue in quality.yml",
"type": "missing_workflow",
"file": "quality.yml",
"action": "create",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in security-policy.yml",
"type": "missing_workflow",
"file": "security-policy.yml",
"action": "create",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
hyperpolymath
added a commit
that referenced
this pull request
May 19, 2026
…lete) Both targets of the portable Http primitive are green: - Deno-ESM #160 — PR #226 (shipped). - typed-wasm #225 — the ADR-013 CPS line PR1..PR3d all merged: #227 (PR1 verified Thenable foundation), #233 (PR2 base case), #236 (PR3a env capture), #237 (PR3b affine-capture obligation), #238 (PR3c Async→Async chaining), #266 (PR3d typed Response reader + http_fetch-parity wasm e2e). The transparent `fetch/get -> Response` source surface is delivered on both targets; ADR-013's delivery plan is complete. Ledger STDLIB-01 + the ADR-013 delivery plan truthed. Two follow-ups remain independently tracked (NOT part of this slice): effect-threaded boundary generalisation #234; estate-wide #199/#205 re-validation #235. Gate: `dune test --force` 278/278 (docs only; zero regression). Closes #160 Closes #225
hyperpolymath
added a commit
that referenced
this pull request
May 19, 2026
…lete) (#268) Both targets of the portable Http primitive are green: - Deno-ESM #160 — PR #226 (shipped). - typed-wasm #225 — the ADR-013 CPS line PR1..PR3d all merged: #227 (PR1 verified Thenable foundation), #233 (PR2 base case), #236 (PR3a env capture), #237 (PR3b affine-capture obligation), #238 (PR3c Async→Async chaining), #266 (PR3d typed Response reader + http_fetch-parity wasm e2e). The transparent `fetch/get -> Response` source surface is delivered on both targets; ADR-013's delivery plan is complete. Ledger STDLIB-01 + the ADR-013 delivery plan truthed. Two follow-ups remain independently tracked (NOT part of this slice): effect-threaded boundary generalisation #234; estate-wide #199/#205 re-validation #235. Gate: `dune test --force` 278/278 (docs only; zero regression). Closes #160 Closes #225
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.
#225 PR 3c — WasmGC CPS transform: Async→Async chaining
A continuation may itself be an async boundary; Thenables now compose up the call chain. The transform is reachable from the
ExprLambdacontinuation-body site via a forward-ref hook (async_transform_hook, set at module init — avoids relocating the transform into thegen_exprrec group) so it re-applies recursively.detect_async_base_casegains~extra(live locals capturable at the call site, incl. the outer binder/captures for a recursively-transformed inner continuation); the single-boundary rejection oncontis dropped. Recursion terminates (each step peels one boundary off a strictly-smaller finite continuation).Three further root-cause fixes (all surfaced by the first-ever nested lambda)
find_free_varsExprBlockused the originalbound_varsfor the tail expr, not the stmt-threaded set → block-locallets reported free →detectspuriously rejected chains. Now threadsbound_after.lambda_idreuse —next_lambda_idbumped only after body-gen; now advanced before.ExprLambdarebuilt the result ctx from pre-bodyctx_after_env(discarding the nested lambda/types/globals), and the closure stored the pre-reserved id while the element segment maps table slot = lambda list position. Now post-body module state is threaded forward (enclosing locals kept) and the closure stores the lambda's append position (= table slot), correct under any nesting order.All three were invisible before PR3c (no nested lambdas had ever existed in generated code). Consistent with the PR2/PR3 pattern: the #199 path had only ever been static-verified.
Verification
New e2e
tests/codegen/http_cps_chain.{affine,mjs}: 2-hop chain —launch→ req A → outer continuation issues req B → inner continuation combiness1(=200, A-derived) with B status(=201) ⇒ 200201; A issued synchronously, B deferred to the continuation. Fulltools/run_codegen_wasm_tests.shgreen (PR2 base + PR3a capture + PR1 skeleton, no regression);dune test --force260 green.Conforms to typed-wasm ADR-005 (accessor model). Slice 3 of former PR3. Refs #225 #160 (NOT Closes — joint-close is the final #225 PR).
🤖 Generated with Claude Code