Skip to content

feat(codegen): #225 PR3c — WasmGC CPS transform, Async→Async chaining#238

Merged
hyperpolymath merged 1 commit into
mainfrom
stage-c/225-pr3c-chaining
May 19, 2026
Merged

feat(codegen): #225 PR3c — WasmGC CPS transform, Async→Async chaining#238
hyperpolymath merged 1 commit into
mainfrom
stage-c/225-pr3c-chaining

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

#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 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. 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)

  1. find_free_vars ExprBlock used the original bound_vars for the tail expr, not the stmt-threaded set → block-local lets reported free → detect spuriously rejected chains. Now threads bound_after.
  2. Nested lambda_id reusenext_lambda_id bumped only after body-gen; now advanced before.
  3. Lost nested lambda + wrong dispatchExprLambda rebuilt the result ctx from pre-body ctx_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 combines s1(=200, A-derived) with B status(=201) ⇒ 200201; A issued synchronously, 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). Slice 3 of former PR3. Refs #225 #160 (NOT Closes — joint-close is the final #225 PR).

🤖 Generated with Claude Code

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
@hyperpolymath hyperpolymath merged commit dc5b817 into main May 19, 2026
4 checks passed
@hyperpolymath hyperpolymath deleted the stage-c/225-pr3c-chaining branch May 19, 2026 09:03
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 44 issues detected

Severity Count
🔴 Critical 12
🟠 High 21
🟡 Medium 11

⚠️ Action Required: Critical security issues found!

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant