-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Support cargo-driven wasm-bindgen linking #27179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| [build] | ||
| target = "wasm32-unknown-emscripten" | ||
| rustflags = [ | ||
| "-Cllvm-args=-enable-emscripten-cxx-exceptions=0", | ||
| "-Cpanic=abort", | ||
| "-Crelocation-model=static", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| [package] | ||
| name = "bindgen_greeter" | ||
| edition = "2021" | ||
|
|
||
| [[bin]] | ||
| name = "bindgen_greeter" | ||
| path = "src/main.rs" | ||
|
|
||
| [dependencies] | ||
| wasm-bindgen = "=0.2.126" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| use wasm_bindgen::prelude::*; | ||
|
|
||
| #[wasm_bindgen] | ||
| pub struct Greeter { | ||
| greeting: String, | ||
| } | ||
|
|
||
| #[wasm_bindgen] | ||
| impl Greeter { | ||
| #[wasm_bindgen(constructor)] | ||
| pub fn new(greeting: String) -> Greeter { | ||
| Greeter { greeting } | ||
| } | ||
|
|
||
| pub fn greet(&self, name: String) -> String { | ||
| format!("{}, {}!", self.greeting, name) | ||
| } | ||
| } | ||
|
|
||
| fn main() { | ||
| // Matches the emscripten idiom: main runs automatically on init. | ||
| println!("main ran"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,10 @@ | |
| _is_ar_cache: dict[str, bool] = {} | ||
| # the exports the user requested | ||
| user_requested_exports: set[str] = set() | ||
| # JS library symbols that were exported (MODULARIZE=instance), derived from the | ||
| # JS compiler's librarySymbols and EXPORTED_FUNCTIONS; the WASM_ESM_INTEGRATION | ||
| # wrapper re-exports them. | ||
| exported_js_library_symbols: set[str] = set() | ||
| # A list of feature flags to pass to each binaryen invocation (like `wasm-opt`, | ||
| # etc.). This is received by the first call to binaryen (e.g. `wasm-emscripten-finalize`) | ||
| # which reads it using `--detect-features`. | ||
|
|
@@ -1285,6 +1289,14 @@ def run_wasm_opt(infile, outfile=None, args=[], **kwargs): # noqa | |
| return run_binaryen_command('wasm-opt', infile, outfile, args=args, **kwargs) | ||
|
|
||
|
|
||
| def is_wasm_bindgen_module(wasm_file): | ||
| # wasm-bindgen marks modules built for the emscripten target with this custom | ||
| # section so emcc, when used as the linker (e.g. by cargo/rustc), knows to run | ||
| # wasm-bindgen as a post-link step. | ||
| with webassembly.Module(wasm_file) as module: | ||
| return module.get_custom_section('__wasm_bindgen_emscripten_marker') is not None | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we just have the rust compiler pass a flag rather than having magic behaviour like this? Maybe
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm open to a flag. For
Not sure what the best term is here either! |
||
|
|
||
|
|
||
| def run_wasm_bindgen(infile): | ||
| bindgen_out_dir = os.path.join(get_emscripten_temp_dir(), 'bindgen_out') | ||
|
|
||
|
|
@@ -1299,16 +1311,33 @@ def run_wasm_bindgen(infile): | |
| '--out-dir', | ||
| bindgen_out_dir, | ||
| ] | ||
| exports_before = {e.name for e in webassembly.get_exports(infile)} | ||
|
|
||
| check_call(cmd) | ||
|
|
||
| # Don't try to predict the .wasm filename that wasm-bindgen outputs. Instead | ||
| # just grab the .wasm file itself. | ||
| all_output_files = os.listdir(bindgen_out_dir) | ||
| new_wasm_file = [x for x in all_output_files if x.endswith('.wasm')][0] | ||
| new_wasm_path = os.path.join(bindgen_out_dir, new_wasm_file) | ||
|
|
||
| # Report which placeholder exports wasm-bindgen consumed so the caller can | ||
| # drop them from EXPORTED_FUNCTIONS. | ||
| removed_exports = exports_before - {e.name for e in webassembly.get_exports(new_wasm_path)} | ||
|
|
||
| shutil.copyfile(new_wasm_path, infile) | ||
|
|
||
| shutil.copyfile(os.path.join(bindgen_out_dir, new_wasm_file), infile) | ||
| # wasm-bindgen emits imported JS snippets into `snippets/` and the `import` | ||
| # statements referencing them into `library_bindgen.extern-pre.js`, only when | ||
| # the crate actually imports JS. | ||
| extern_pre_js = os.path.join(bindgen_out_dir, 'library_bindgen.extern-pre.js') | ||
| if not os.path.exists(extern_pre_js): | ||
| extern_pre_js = None | ||
| snippets_dir = os.path.join(bindgen_out_dir, 'snippets') | ||
| if not os.path.isdir(snippets_dir): | ||
| snippets_dir = None | ||
|
|
||
| return os.path.join(bindgen_out_dir, 'library_bindgen.js') | ||
| return os.path.join(bindgen_out_dir, 'library_bindgen.js'), removed_exports, extern_pre_js, snippets_dir | ||
|
|
||
|
|
||
| intermediate_counter = 0 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are other places in codebase that also use wasmExports.. maybe we should remove the
&& WASM_BINDGENso that they start working underWASM_ESM_INTEGRATION?Actually I wonder how those codepaths work today? For example
registerTLSInit(wasmExports['_emscripten_tls_init']);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done — dropped the
&& WASM_BINDGENso the namespace import applies to allWASM_ESM_INTEGRATIONbuilds. It's safe because everywasmExports = ...assignment lives under#if !WASM_ESM_INTEGRATION(insidecreateWasm) or the non-instance branch, andWASM_ESM_INTEGRATIONrequiresMODULARIZE=instance— so nothing assigns to the now read-only binding. Verified a plain C++ ESM build still builds and runs.Re your TLS question: under ESM integration
registerTLSInitalready uses the named import (registerTLSInit(__emscripten_tls_init)) rather thanwasmExports['_emscripten_tls_init'], which is why that path worked. This change makes the generic by-namewasmExports[...]accesses work under ESM integration too.