Skip to content

fix(wasm): #1081 — invalid stack balance on Stmt::Return of void call - #1086

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-agent-a133759f311c48c59
May 19, 2026
Merged

fix(wasm): #1081 — invalid stack balance on Stmt::Return of void call#1086
proggeramlug merged 1 commit into
mainfrom
worktree-agent-a133759f311c48c59

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Closes #1081.

Summary

  • WASM validator was rejecting Mango's compiled .wasm with expected 1 elements on the stack for return, found 0. The trigger is an expression-bodied arrow whose body is a void call, e.g. v => console.log(v).
  • Such an arrow lowers to a closure with Stmt::Return(Some(console.log(v))). The closure's WASM signature is [i64] -> [i64], but emit_expr for console.log/warn/error (and other emit_memcall_void paths) returns from the match arm without pushing a result. The subsequent Instruction::Return then violates the function signature.
  • Mirror JS semantics (a void call evaluates to undefined) by pushing TAG_UNDEFINED when expr_has_value(e) is false in Stmt::Return(Some(e)). Same pattern Stmt::Expr already uses (Drop only when expr_has_value).

Files

  • crates/perry-codegen-wasm/src/emit.rs — push TAG_UNDEFINED after emit_expr in Stmt::Return(Some(e)) when the expression is void.
  • tests/wasm/22_return_void_expr.{ts,expected} — regression test covering g().then(v => console.log(v)) and const log = (v: number) => console.log(v).

Test plan

  • WebAssembly.compile accepts the produced module (was failing without the fix).
  • All 6 candidate repros from the issue triage compile (C1–C6 plus the c2c/c2d/c2e variants that isolate the void-call closure body).
  • tests/wasm/run_wasm_tests.sh: 15 passed / 7 failed; the 7 pre-existing failures match main and the new 22_return_void_expr passes.
  • cargo fmt --all; cargo build --release -p perry-codegen-wasm -p perry-runtime -p perry-stdlib -p perry.

No version bump or changelog change — maintainer will fold those in at merge time.

`v => console.log(v)` lowers to a closure with body `Stmt::Return(Some(console.log(v)))`.
The closure's WASM signature is `[i64] -> [i64]`, but emit_expr for the console.* call
emits `emit_memcall_void` + early `return` from the match arm, leaving the operand
stack empty. The subsequent `Instruction::Return` then fails validation with
"expected 1 elements on the stack for return, found 0" — the V8 error from #1081.

Mirror JS semantics (a void call evaluates to `undefined`) by pushing TAG_UNDEFINED
when `expr_has_value(e)` is false in `Stmt::Return(Some(e))`. This is the same
pattern `Stmt::Expr` already uses (Drop only when expr_has_value).

Regression test: tests/wasm/22_return_void_expr.ts.
@proggeramlug
proggeramlug merged commit 2c148df into main May 19, 2026
9 checks passed
@proggeramlug
proggeramlug deleted the worktree-agent-a133759f311c48c59 branch May 19, 2026 10:10
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.

WASM codegen: invalid stack-balance in generated function (function #1174, real-world app)

1 participant