Skip to content

add wasm-bindgen support#23493

Open
walkingeyerobot wants to merge 82 commits intoemscripten-core:mainfrom
walkingeyerobot:wbg-walkingeyerobot
Open

add wasm-bindgen support#23493
walkingeyerobot wants to merge 82 commits intoemscripten-core:mainfrom
walkingeyerobot:wbg-walkingeyerobot

Conversation

@walkingeyerobot
Copy link
Copy Markdown
Collaborator

This is an early draft PR for the purposes of gathering feedback early. There are also pending changes to wasm-bindgen.

How this works:

  1. Cargo builds Rust code targeting wasm32-unknown-emscripten into a .a file.
  2. Emscripten is invoked with any C++ sources and the just built Rust .a file.
  3. Emscripten builds C++ sources and then calls out to wasm-ld to link the C++ and Rust into a .wasm file.
  4. wasm-bindgen is run on that .wasm file, producing a new .wasm file, a library.js file, and a pre.js file.
  5. Emscripten constructs its own .js, integrating the wasm-bindgen .js files.

You can see a demo more easily at https://github.com/walkingeyerobot/cxx-rust-demo. library_wbg.js and pre.js are approximately what will be produced by wasm-bindgen for consumption by Emscripten.

Some TODOs:

  1. Figure out how to pass the exported symbols from the rust compiler to Emscripten. These are symbols that need to be passed to wasm-ld so they're not removed in the final .wasm but that may not necessarily be present after wasm-bindgen processes the .wasm. wasm-bindgen at compile time puts the information it needs to generate JS inside the .wasm file itself in the form of _describe functions. These functions are then removed after JS generation.
  2. Merge the .js files produced by wasm-bindgen. This shouldn't be that hard; I just haven't gotten around to it yet. This would simplify the code for both Emscripten and wasm-bindgen.
  3. Get wasm-bindgen tests to pass. Early efforts here have revealed some very odd compiler differences between -unknown and -emscripten that I'll have to fix.
  4. Have this work end-to-end via wasm-pack. I'll have a draft PR for this soon (tm).

I'm mostly looking for feedback on the first point about exported symbols and about the general addition of -sWASM_BINDGEN to Emscripten. Again, this is very early, but it's a pretty big feature, so I thought it best to start discussions now.

cc @daxpedda, who I've been working with on the wasm-bindgen side.

@kripken
Copy link
Copy Markdown
Member

kripken commented Jan 24, 2025

wasm-bindgen at compile time puts the information it needs to generate JS inside the .wasm file itself in the form of _describe functions.

Does rustc then read the wasm to find those function names, and pass those names to wasm-ld? (if not, how does it find those names?)

In general if we need to read metadata-type info from the wasm, then we have a minimal parser in tools/webassembly.py. If we need something more complex, a binaryen pass is an option.

@walkingeyerobot
Copy link
Copy Markdown
Collaborator Author

wasm-bindgen itself is two pieces: a library that allows you to annotate your rust code marking things to be exported, and a tool that consumes a .wasm file and reads those annotations to produce a companion js file. rustc knows about those function names because wasm-bindgen as a library provided the annotations. If rustc invokes the linker itself, it's able to pass that information along. However, because we need to also build C++, we're only using rustc to compile and not drive the whole process, so we need to have it output that information elsewhere.

One (very naive) possibility is to have rustc invoke a fake linker that just writes the -sEXPORTED_FUNCTIONS to a file for emscripten to read later.

Comment thread tools/link.py Outdated
Comment thread tools/link.py Outdated
Comment thread tools/building.py Outdated
Comment thread tools/building.py Outdated
Comment thread tools/building.py Outdated
Comment thread tools/building.py Outdated
gergelyvagujhelyi added a commit to gergelyvagujhelyi/flutter_rust_bridge that referenced this pull request Apr 22, 2026
…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 added a commit to gergelyvagujhelyi/flutter_rust_bridge that referenced this pull request Apr 23, 2026
…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.
@walkingeyerobot walkingeyerobot marked this pull request as ready for review April 27, 2026 19:40
@walkingeyerobot
Copy link
Copy Markdown
Collaborator Author

I believe this is ready for review! :D

@walkingeyerobot walkingeyerobot changed the title [DRAFT] add wasm-bindgen support add wasm-bindgen support Apr 29, 2026
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.

5 participants