Skip to content

fix(frb_rust): cfg-gate wasm_bindgen::module() call on emscripten target#3062

Closed
gergelyvagujhelyi wants to merge 1 commit intofzyzcjy:masterfrom
gergelyvagujhelyi:fix/emscripten-worker-pool-module-cfg-gate
Closed

fix(frb_rust): cfg-gate wasm_bindgen::module() call on emscripten target#3062
gergelyvagujhelyi wants to merge 1 commit intofzyzcjy:masterfrom
gergelyvagujhelyi:fix/emscripten-worker-pool-module-cfg-gate

Conversation

@gergelyvagujhelyi
Copy link
Copy Markdown

@gergelyvagujhelyi gergelyvagujhelyi commented Apr 22, 2026

Summary

WorkerPool::spawn in frb_rust/src/third_party/wasm_bindgen/worker_pool.rs calls wasm_bindgen::module() to forward the current Wasm module handle to a newly-spawned Web Worker. That intrinsic is only supported by wasm-bindgen-cli's OutputMode::{Web, NoModules, Node, Module} — it explicitly bails for Bundler and Emscripten (see wasm-bindgen-cli-support/src/js/mod.rs, Intrinsic::Module).

An emscripten fork with -sWASM_BINDGEN integration (e.g. the walkingeyerobot/emscripten draft) inserts a __wasm_bindgen_emscripten_marker custom section, which forces wasm-bindgen-cli into OutputMode::Emscripten regardless of any --target flag. In that mode the __wbindgen_module intrinsic bails, so wasm-bindgen post-link fails with:

failed to generates bindings for import of
`__wbindgen_placeholder__::__wbg___wbindgen_module_<hash>`

The failure happens even when spawn is never actually called — the mere reference in the dep graph is enough to pull the import into the wasm and kill the bindgen step. This makes flutter_rust_bridge unusable on wasm32-unknown-emscripten today.

Fix

Two layered changes, both gated on #[cfg(target_os = "emscripten")]:

  1. Split spawn into two cfg variants. Non-emscripten keeps the original implementation verbatim. Emscripten returns Err(JsValue) with a clear message pointing at the underlying cause.

  2. Skip the eager spawn loop in new_raw. Otherwise Default::default() (which uses navigator.hardwareConcurrency as the initial count and then .expect("fail to create WorkerPool")s) would panic as soon as anything touches THREAD_POOL / FLUTTER_RUST_BRIDGE_HANDLER — meaning on the first FFI dispatch through flutter_rust_bridge's generated bindings. With the gate, the pool initialises empty on emscripten; explicit spawn / execute calls still return the same descriptive Err.

Imports and struct fields that are used only in the non-emscripten body are cfg-gated or allow(dead_code)-marked so emscripten builds stay warning-free.

Why target_os = "emscripten" is the right gate

In principle someone could build for wasm32-unknown-emscripten without the walkingeyerobot fork (stock emscripten + manual wasm-bindgen --target web). In that case OutputMode::Emscripten wouldn't be auto-selected and wasm_bindgen::module() would work. In practice this is not how anyone uses wasm-bindgen with emscripten today — the fork exists specifically to bridge the two — and the fix is strictly additive for the stock-emscripten flow (they get a compile-time error at runtime instead of silently-maybe-working workers).

If a better per-toolchain detection exists, happy to switch.

Verification

Compiled frb_rust on three targets; all warning-free after the cfg gates:

Target Result
wasm32-unknown-emscripten ✅ 0 warnings
wasm32-unknown-unknown ✅ 0 warnings
aarch64-apple-darwin (native) ✅ 0 warnings

End-to-end: a downstream crate that depends on flutter_rust_bridge now builds on wasm32-unknown-emscripten via the walkingeyerobot fork + wasm-bindgen-cli 0.2.118. The resulting ~15 MB release .wasm + matching JS glue load and instantiate in Node and expose all generated FRB dispatcher/opaque-handle symbols.

Notes

  • No public API change — spawn and new_raw are unchanged signatures; just their bodies differ per target.
  • No behavior change on non-emscripten targets: the #[cfg(not(target_os = "emscripten"))] body is a verbatim copy of the existing code.
  • Companion discussion about upstream wasm-bindgen Emscripten-mode gaps (Module intrinsic, Bundler fallback) is outside the scope of this PR — worth a separate issue if you want me to file one.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces conditional compilation for the WorkerPool to support the emscripten target by gating specific imports, struct fields, and the spawn method. The spawn method on Emscripten now returns an error because wasm_bindgen::module() is unsupported in that environment. Feedback highlights that this error return could cause a panic during application startup if the pool attempts to initialize workers based on hardware concurrency, suggesting that the initialization logic should be adjusted to default to zero workers for Emscripten targets.

Comment thread frb_rust/src/third_party/wasm_bindgen/worker_pool.rs
…arget

`WorkerPool::spawn` calls `wasm_bindgen::module()` to forward the
current Wasm module handle to a newly-spawned Web Worker. That
intrinsic is only supported by wasm-bindgen-cli's OutputMode::{Web,
NoModules, Node, Module} (see `wasm-bindgen-cli-support/src/js/mod.rs`
`Intrinsic::Module`, which bails out for Bundler and Emscripten).

An emscripten fork with `-sWASM_BINDGEN` integration (e.g. the
`walkingeyerobot/emscripten` draft upstream of
emscripten-core/emscripten#23493) inserts the
`__wasm_bindgen_emscripten_marker` custom section, which forces
wasm-bindgen-cli into `OutputMode::Emscripten` unconditionally. In that
mode the `__wbindgen_module` intrinsic bails, so post-link fails with:

    failed to generates bindings for import of
    `__wbindgen_placeholder__::__wbg___wbindgen_module_<hash>`

This happens even when the caller never spawns a worker — the mere
reference in the dep graph is enough. Web Workers aren't usable from
the emscripten target regardless (single-threaded model), so fall back
to a null module handle when `target_os = "emscripten"`. Any runtime
call to `spawn` would have failed on that target anyway; this just
keeps the import out of the wasm so wasm-bindgen post-link can complete.

Verified on `wasm32-unknown-emscripten` with wasm-bindgen-cli 0.2.118 +
the walkingeyerobot emscripten fork: `cargo build --release --bin
nobodywho_flutter_web --target wasm32-unknown-emscripten` now produces
a ~15 MB .wasm + matching JS glue that loads and instantiates in Node.
@gergelyvagujhelyi gergelyvagujhelyi force-pushed the fix/emscripten-worker-pool-module-cfg-gate branch from 80b185d to f0e1039 Compare April 22, 2026 23:23
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.78%. Comparing base (7ce1020) to head (f0e1039).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3062      +/-   ##
==========================================
- Coverage   98.34%   96.78%   -1.56%     
==========================================
  Files         470      470              
  Lines       19428    19262     -166     
==========================================
- Hits        19106    18643     -463     
- Misses        322      619     +297     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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