fix(frb_rust): cfg-gate wasm_bindgen::module() call on emscripten target#3062
Conversation
There was a problem hiding this comment.
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.
…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.
80b185d to
f0e1039
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
Summary
WorkerPool::spawninfrb_rust/src/third_party/wasm_bindgen/worker_pool.rscallswasm_bindgen::module()to forward the current Wasm module handle to a newly-spawned Web Worker. That intrinsic is only supported bywasm-bindgen-cli'sOutputMode::{Web, NoModules, Node, Module}— it explicitly bails forBundlerandEmscripten(seewasm-bindgen-cli-support/src/js/mod.rs,Intrinsic::Module).An emscripten fork with
-sWASM_BINDGENintegration (e.g. the walkingeyerobot/emscripten draft) inserts a__wasm_bindgen_emscripten_markercustom section, which forceswasm-bindgen-cliintoOutputMode::Emscriptenregardless of any--targetflag. In that mode the__wbindgen_moduleintrinsic bails, sowasm-bindgenpost-link fails with:The failure happens even when
spawnis 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 makesflutter_rust_bridgeunusable onwasm32-unknown-emscriptentoday.Fix
Two layered changes, both gated on
#[cfg(target_os = "emscripten")]:Split
spawninto two cfg variants. Non-emscripten keeps the original implementation verbatim. Emscripten returnsErr(JsValue)with a clear message pointing at the underlying cause.Skip the eager spawn loop in
new_raw. OtherwiseDefault::default()(which usesnavigator.hardwareConcurrencyas the initial count and then.expect("fail to create WorkerPool")s) would panic as soon as anything touchesTHREAD_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; explicitspawn/executecalls still return the same descriptiveErr.Imports and struct fields that are used only in the non-emscripten body are
cfg-gated orallow(dead_code)-marked so emscripten builds stay warning-free.Why
target_os = "emscripten"is the right gateIn principle someone could build for
wasm32-unknown-emscriptenwithout the walkingeyerobot fork (stock emscripten + manualwasm-bindgen --target web). In that caseOutputMode::Emscriptenwouldn't be auto-selected andwasm_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_ruston three targets; all warning-free after the cfg gates:wasm32-unknown-emscriptenwasm32-unknown-unknownaarch64-apple-darwin(native)End-to-end: a downstream crate that depends on
flutter_rust_bridgenow builds onwasm32-unknown-emscriptenvia 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
spawnandnew_raware unchanged signatures; just their bodies differ per target.#[cfg(not(target_os = "emscripten"))]body is a verbatim copy of the existing code.