Skip to content

--target web: codegen diverges based on file: dep path — strips FFI imports OR emits invalid WASM (#687 stack fallthru) #183

@proggeramlug

Description

@proggeramlug

Summary

On perry compile --target web, Perry's output diverges dramatically depending on whether the file: dependency path in package.json points inside or outside the project root. The same source tree, same perry 0.5.178, same engine produces two different broken builds:

package.json bloom dep modules found wasm size ffi imports WASM valid? runtime behavior
"file:./vendor/bloom/" 1 native 216 902 B 9 ✅ OK runs but every bloom/core-wrapped call silently no-ops (see below)
"file:../engine/" 10 native 413 500 B 140 FAIL WebAssembly.compile(): Compiling function #687 failed: expected 0 elements on the stack for fallthru, found 130

Real-world impact: Bloom Jump (the game) uses the first path in CI → ships to https://bloomengine.dev/jump/ as a white screen, because runGame(callback)'s bloom_run_game FFI import was stripped.

Repro

Project layout:

  • src/main.ts — imports runGame, initWindow, loadSound, … from "bloom/core", "bloom/audio", etc., plus declares some bloom_* functions directly (declare function bloom_load_texture(...)).
  • package.json — has one dependency: "bloom": "file:<path>".
  • perry.toml — standard project config.

Compile the identical src/main.ts twice, only changing package.json's bloom dep path:

A. "bloom": "file:./vendor/bloom/" (engine cloned/symlinked into vendor/bloom/ inside the project)

$ perry compile --target web src/main.ts -o out
Found 1 module(s): 1 native, 0 JavaScript
Generating WebAssembly...
WASM output: out.html (715.7 KB)

$ wasm-tools dump out.wasm | grep -cE 'module: "ffi"'
9

The only ffi imports emitted are the 9 declare function bloom_*(...) in main.ts. Every FFI call routed through the bloom/core (and sibling) module's re-exports (runGame → bloom_run_game, initWindow → bloom_init_window, loadSound → bloom_load_sound, getPlatform → bloom_get_platform, …) is stripped. WASM compiles and runs, but the affected calls silently do nothing — in the jump game, runGame's web branch never registers a frame callback, so the canvas stays blank and the game never starts.

B. "bloom": "file:../engine/" (engine living outside the project)

$ perry compile --target web src/main.ts -o out
Found 10 module(s): 10 native, 0 JavaScript
Generating WebAssembly...
WASM output: out.html (1232.2 KB)

$ wasm-tools dump out.wasm | grep -cE 'module: "ffi"'
140

$ node -e 'WebAssembly.compile(require("fs").readFileSync("out.wasm")).catch(e => console.log(e.message))'
WebAssembly.compile(): Compiling function #687 failed: expected 0 elements on the stack for fallthru, found 130 @+408287

Perry now discovers all 10 bloom submodules and emits 140 ffi imports — correct. But the resulting WASM function #687 has a stack-fallthru validation error, so the browser refuses to instantiate the module. (The exact "found N" count varies with engine state: 130 with my local engine checkout, 103 with a fresh origin/main clone.)

Expected

Both paths should produce the same valid WASM — either both with the full 140 imports, or both with the subset that survives DCE, but with consistent DCE such that the game's call graph isn't partially severed.

Environment

I can hand over the full compiled out.wasm files from both A and B if that helps diagnose — just point me at where to drop them.

Workaround

None from the jump side that I've found:

  • Adding declare function bloom_run_game(callback: number): void; + bloom_run_game(cb as any) directly in main.ts does get the import emitted under path A — but only under path B (which then hits the #687 codegen bug).
  • Rewriting every bloom/core call as a direct declare function in main.ts would require ~30 declarations and still only fixes path A.

The fix needs to land in Perry's web-target codegen.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions