Skip to content

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

Description

@proggeramlug

Summary

Mango's source compiles to WASM cleanly (no compile-time errors) but the produced .wasm is rejected by V8 at instantiation time:

Boot error: CompileError: WebAssembly.instantiate(): Compiling function #1174 failed: expected 1 elements on the stack for return, found 0 @+1652382

This is invalid WASM — a function declared to return a value, but its body falls through to return without pushing the value first. The validator catches it; instantiation fails; the app never boots.

Environment

  • Perry: 0.5.1008
  • Target: --target web
  • Mango source: https://github.com/MangoQuery/app at commit 2863473
  • Browser: Chrome (V8) headless --headless=new
  • macOS arm64, host is also where the build was produced

Reproducing

git clone https://github.com/MangoQuery/app
cd app
PERRY_ALLOW_PERRY_FEATURES=1 PERRY_ALLOW_DYNAMIC_STDLIB=1 \
  perry compile src/app.ts --target web -o dist/mango.html
# Serve and load in Chrome (any HTTP server, file:// also reproduces in
# permissive contexts). Console + DevTools shows the CompileError above.

The Mango source is ~5000 lines and uses: async functions extensively, perry/ui widgets including Toggle/Picker/SecureField/ZStack/SplitView/ProgressView, perry/thread, perry/system Keychain, better-sqlite3 (gated to native via __platform__), @perryts/mongodb (also native-only), and a generated DOM bridge via the web target's WASM<->JS shim.

Minimal repro doesn't trigger it

A small test app does compile + run successfully:

import { App, VStack, Text, Button } from 'perry/ui';
const b = Button('Hi', () => { console.log('clicked'); });
App({ title: 'Tiny', width: 400, height: 300,
      body: VStack(0, [Text('Hello'), b]) });

Compiles to a 203 KB self-contained HTML; loads cleanly in headless Chrome; body renders "Hello\nHi"; no console errors. So the bug isn't in the runtime trampoline or general control-flow emission — it's something pattern-specific in Mango's source.

What function #1174 might be

Without symbols on the wasm side I can't tell which JS function maps to #1174. Notable patterns in Mango that might be involved (educated guesses, not validated):

  • restoreLastSession is split into a sync entry + an async tail specifically to dodge a different Perry async-codegen bug (CFTimer-driven promise-resolution spin at 100% CPU). The sync wrapper looks like:

    function restoreLastSession(): void {
      const lastUri = getState('lastConnUri');
      const lastName = getState('lastConnName');
      const lastId = getState('lastConnId');
      if (!lastUri) return;
      restoreLastSessionAsync(lastUri, lastName, lastId);
    }

    Early-return-without-value in a function whose call site doesn't use the return value.

  • Many handlers shaped async () => { ... await ... ... if (!ok) return; ... } — early returns inside async closures.

  • Recursive renderJsonNode(value, label, path, depth) that returns a Widget and has a branch that returns headBtn (single value) and another branch that returns VStack(0, [headBtn, childStack]) — both branches return values, but Perry's inference might be losing track in one branch.

  • Generator-like state machines aren't used, no try/finally without explicit return.

Happy to bisect if helpful — let me know what shape of repro would land best (e.g. a stripped-down branch that still triggers it).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions