From e9bf8a07f398003fb22c89c84dc00bae0c2b7163 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 23 Dec 2024 13:31:09 -0500 Subject: [PATCH 01/41] first pass at a hack --- src/preamble.js | 1 + tools/building.py | 20 ++++++++++++++++++++ tools/emscripten.py | 11 ++++++----- tools/link.py | 6 ++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index 2e91449ac76fc..fb9321ec44ca4 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -908,6 +908,7 @@ function getWasmImports() { #else // MINIFY_WASM_IMPORTED_MODULES 'env': wasmImports, '{{{ WASI_MODULE_NAME }}}': wasmImports, + 'wbg': wasmImports, #endif // MINIFY_WASM_IMPORTED_MODULES #if SPLIT_MODULE 'placeholder': new Proxy({}, splitModuleProxyHandler), diff --git a/tools/building.py b/tools/building.py index c1caf75ca6840..9042269b774ca 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1232,6 +1232,26 @@ def run_wasm_opt(infile, outfile=None, args=[], **kwargs): # noqa return run_binaryen_command('wasm-opt', infile, outfile, args=args, **kwargs) +def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa + if not os.path.exists(infile): + exit_with_error('wasm-bindgen: wasm file not found (%s).' % infile) + + cmd = [ + '/usr/local/google/home/mitchfoley/repos/wasm-bindgen/target/debug/wasm-bindgen', + infile, + '--target', + 'web', + '--keep-lld-exports', + '--out-dir', + # os.path.dirname(outfile) + '/wbg_out' + './wbg_out' + ] + ret = check_call(cmd).stdout + #check_call(['cp', os.path.dirname(outfile) + '/wbg_out/*_bg.wasm', infile]) + check_call(['cp', './wbg_out/a.wasm', outfile]) + return ret + + def save_intermediate(src, dst): if DEBUG: dst = 'emcc-%02d-%s' % (save_intermediate.counter, dst) diff --git a/tools/emscripten.py b/tools/emscripten.py index 0db61d81e9c61..e3550c7bb4266 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -578,10 +578,11 @@ def finalize_wasm(infile, outfile, js_syms): if not need_name_section: strip_sections += ['name'] - if strip_sections or not settings.GENERATE_DWARF: - building.save_intermediate(outfile, 'strip.wasm') - building.strip(infile, outfile, debug=not settings.GENERATE_DWARF, - sections=strip_sections) + # TODO(walkingeyerobot): make this work. it appears to not like the wasm file from wasm-bindgen + #if strip_sections or not settings.GENERATE_DWARF: + # building.save_intermediate(outfile, 'strip.wasm') + # building.strip(infile, outfile, debug=not settings.GENERATE_DWARF, + # sections=strip_sections) metadata = get_metadata(outfile, outfile, modify_wasm, args) @@ -951,7 +952,7 @@ def install_wrapper(sym): else: wrapper += f"wasmExports['{name}']" - wrappers.append(wrapper) + # TODO(walkingeyerobot): if the export is from rust, it should probably be excluded here and dealt with elsewhere. return wrappers diff --git a/tools/link.py b/tools/link.py index 6c3ca352ff6d7..d37d8b18c3008 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1932,6 +1932,8 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ settings.TARGET_JS_NAME = os.path.basename(state.js_target) + phase_wasm_bindgen(in_wasm, in_wasm) + metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata) if settings.EMBIND_AOT: @@ -1950,6 +1952,10 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ phase_final_emitting(options, state, target, wasm_target) +def phase_wasm_bindgen(in_wasm, wasm_target): + building.run_wasm_bindgen(in_wasm, wasm_target) + + @ToolchainProfiler.profile_block('emscript') def phase_emscript(in_wasm, wasm_target, js_syms, base_metadata): # Emscripten From 135405fd6b7ea2465515650dac8d764d28960691 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 23 Dec 2024 16:11:08 -0500 Subject: [PATCH 02/41] oops --- tools/emscripten.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/emscripten.py b/tools/emscripten.py index e3550c7bb4266..ec982d002a140 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -953,6 +953,7 @@ def install_wrapper(sym): wrapper += f"wasmExports['{name}']" # TODO(walkingeyerobot): if the export is from rust, it should probably be excluded here and dealt with elsewhere. + wrappers.append(wrapper) return wrappers From e5b67a754dc9a5cf4999e9393faa9c2cb447d883 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 23 Dec 2024 16:31:52 -0500 Subject: [PATCH 03/41] wasm bindgen setting --- src/settings.js | 4 ++++ tools/link.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/settings.js b/src/settings.js index c838823a12e18..a7025e7b942dc 100644 --- a/src/settings.js +++ b/src/settings.js @@ -2186,6 +2186,10 @@ var LEGACY_RUNTIME = false; // [link] var SIGNATURE_CONVERSIONS = []; +// Run wasm-bindgen and integrate the rust-exported symbols into the rest of Emscripten's JS output. +// [link] +var WASM_BINDGEN = 0; + // For renamed settings the format is: // [OLD_NAME, NEW_NAME] // For removed settings (which now effectively have a fixed value and can no diff --git a/tools/link.py b/tools/link.py index d37d8b18c3008..8906392412fe3 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1932,7 +1932,8 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ settings.TARGET_JS_NAME = os.path.basename(state.js_target) - phase_wasm_bindgen(in_wasm, in_wasm) + if settings.WASM_BINDGEN: + phase_wasm_bindgen(in_wasm, in_wasm) metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata) From 6732eeccdfbcf561c789e3f66872ed1d06e53d46 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 13 Jan 2025 17:53:36 -0500 Subject: [PATCH 04/41] link in actual js output by wasm-bindgen --- tools/building.py | 15 ++++++++------- tools/config.py | 4 ++++ tools/link.py | 7 +++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tools/building.py b/tools/building.py index 9042269b774ca..f3f7ba85ce1b1 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1236,19 +1236,20 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa if not os.path.exists(infile): exit_with_error('wasm-bindgen: wasm file not found (%s).' % infile) - cmd = [ - '/usr/local/google/home/mitchfoley/repos/wasm-bindgen/target/debug/wasm-bindgen', + cmd = config.WASM_BINDGEN + [ infile, '--target', - 'web', + 'emscripten', '--keep-lld-exports', '--out-dir', - # os.path.dirname(outfile) + '/wbg_out' - './wbg_out' + get_emscripten_temp_dir() + '/wbg_out' ] ret = check_call(cmd).stdout - #check_call(['cp', os.path.dirname(outfile) + '/wbg_out/*_bg.wasm', infile]) - check_call(['cp', './wbg_out/a.wasm', outfile]) + new_wasm_file = get_emscripten_temp_dir() + '/wbg_out/' + os.path.basename(infile).split('.')[0] + '.wasm' + if outfile == None: + outfile = infile + check_call(['cp', new_wasm_file, infile]) + return ret diff --git a/tools/config.py b/tools/config.py index 43c738cf8feb0..ba5dc6a7177ea 100644 --- a/tools/config.py +++ b/tools/config.py @@ -36,6 +36,7 @@ CACHE = None PORTS = None COMPILER_WRAPPER = None +WASM_BINDGEN = None # Set by init() EM_CONFIG = None @@ -58,6 +59,7 @@ def fix_js_engine(old, new): def normalize_config_settings(): global CACHE, PORTS, LLVM_ADD_VERSION, CLANG_ADD_VERSION, CLOSURE_COMPILER global NODE_JS, NODE_JS_TEST, V8_ENGINE, JS_ENGINES, SPIDERMONKEY_ENGINE, WASM_ENGINES + global WASM_BINDGEN # EM_CONFIG stuff if not JS_ENGINES: @@ -75,6 +77,7 @@ def normalize_config_settings(): JS_ENGINES = [listify(engine) for engine in JS_ENGINES] WASM_ENGINES = [listify(engine) for engine in WASM_ENGINES] CLOSURE_COMPILER = listify(CLOSURE_COMPILER) + WASM_BINDGEN = listify(WASM_BINDGEN) if not CACHE: CACHE = path_from_root('cache') if not PORTS: @@ -124,6 +127,7 @@ def parse_config_file(): 'CACHE', 'PORTS', 'COMPILER_WRAPPER', + 'WASM_BINDGEN', ) # Only propagate certain settings from the config file. diff --git a/tools/link.py b/tools/link.py index 8906392412fe3..f627bd1a5e1e1 100644 --- a/tools/link.py +++ b/tools/link.py @@ -38,6 +38,7 @@ from .shared import in_temp, safe_copy, do_replace, OFormat from .shared import DEBUG, WINDOWS, DYNAMICLIB_ENDINGS, STATICLIB_ENDINGS from .shared import unsuffixed, unsuffixed_basename, get_file_suffix +from .shared import get_emscripten_temp_dir from .settings import settings, default_setting, user_settings, JS_ONLY_SETTINGS, DEPRECATED_SETTINGS from .minimal_runtime_shell import generate_minimal_runtime_html @@ -1933,7 +1934,9 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ settings.TARGET_JS_NAME = os.path.basename(state.js_target) if settings.WASM_BINDGEN: - phase_wasm_bindgen(in_wasm, in_wasm) + phase_wasm_bindgen(in_wasm) + settings.PRE_JS_FILES += [os.path.abspath(get_emscripten_temp_dir() + '/wbg_out/wbg_pre.js')] + settings.JS_LIBRARIES += [os.path.abspath(get_emscripten_temp_dir() + '/wbg_out/library_wbg.js')] metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata) @@ -1953,7 +1956,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ phase_final_emitting(options, state, target, wasm_target) -def phase_wasm_bindgen(in_wasm, wasm_target): +def phase_wasm_bindgen(in_wasm, wasm_target=None): building.run_wasm_bindgen(in_wasm, wasm_target) From 1708fdfeb94fdab81a2743e99fe5c027240167e9 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Tue, 28 Jan 2025 23:33:01 -0500 Subject: [PATCH 05/41] don't try to predict the wasm filename --- tools/building.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/building.py b/tools/building.py index f3f7ba85ce1b1..5deaf5cf595dd 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1245,10 +1245,17 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa get_emscripten_temp_dir() + '/wbg_out' ] ret = check_call(cmd).stdout - new_wasm_file = get_emscripten_temp_dir() + '/wbg_out/' + os.path.basename(infile).split('.')[0] + '.wasm' + + bindgen_out_dir = get_emscripten_temp_dir() + '/wbg_out/' + + # TODO(walkingeye): don't try to predict the .wasm filename that wasm-bindgen + # outputs. instead just grab the .wasm file itself (there will only ever be one). + all_output_files = os.listdir(bindgen_out_dir) + new_wasm_file = list(filter(lambda x: x.endswith('.wasm'), all_output_files))[0] if outfile == None: outfile = infile - check_call(['cp', new_wasm_file, infile]) + + check_call(['cp', new_wasm_file, outfile]) return ret From 5f3feae9d7d95f867abb145278cf3b79e33166f7 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Tue, 28 Jan 2025 23:46:17 -0500 Subject: [PATCH 06/41] address code review comments --- tools/building.py | 13 ++++--------- tools/link.py | 10 +++------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/tools/building.py b/tools/building.py index 5deaf5cf595dd..c88010bae9c32 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1233,8 +1233,7 @@ def run_wasm_opt(infile, outfile=None, args=[], **kwargs): # noqa def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa - if not os.path.exists(infile): - exit_with_error('wasm-bindgen: wasm file not found (%s).' % infile) + bindgen_out_dir = get_emscripten_temp_dir() + '/bindgen_out/' cmd = config.WASM_BINDGEN + [ infile, @@ -1242,11 +1241,9 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa 'emscripten', '--keep-lld-exports', '--out-dir', - get_emscripten_temp_dir() + '/wbg_out' + bindgen_out_dir, ] - ret = check_call(cmd).stdout - - bindgen_out_dir = get_emscripten_temp_dir() + '/wbg_out/' + check_call(cmd) # TODO(walkingeye): don't try to predict the .wasm filename that wasm-bindgen # outputs. instead just grab the .wasm file itself (there will only ever be one). @@ -1255,9 +1252,7 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa if outfile == None: outfile = infile - check_call(['cp', new_wasm_file, outfile]) - - return ret + shutil.copyfile(bindgen_out_dir + new_wasm_file, outfile) def save_intermediate(src, dst): diff --git a/tools/link.py b/tools/link.py index f627bd1a5e1e1..bac1121002acb 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1934,9 +1934,9 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ settings.TARGET_JS_NAME = os.path.basename(state.js_target) if settings.WASM_BINDGEN: - phase_wasm_bindgen(in_wasm) - settings.PRE_JS_FILES += [os.path.abspath(get_emscripten_temp_dir() + '/wbg_out/wbg_pre.js')] - settings.JS_LIBRARIES += [os.path.abspath(get_emscripten_temp_dir() + '/wbg_out/library_wbg.js')] + building.run_wasm_bindgen(in_wasm) + settings.PRE_JS_FILES += [get_emscripten_temp_dir() + '/bindgen_out/wbg_pre.js'] + settings.JS_LIBRARIES += [get_emscripten_temp_dir() + '/bindgen_out/library_wbg.js'] metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata) @@ -1956,10 +1956,6 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ phase_final_emitting(options, state, target, wasm_target) -def phase_wasm_bindgen(in_wasm, wasm_target=None): - building.run_wasm_bindgen(in_wasm, wasm_target) - - @ToolchainProfiler.profile_block('emscript') def phase_emscript(in_wasm, wasm_target, js_syms, base_metadata): # Emscripten From 8ff45805507c94a3580db892e56c94be92ebbd62 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Thu, 13 Feb 2025 13:54:59 -0500 Subject: [PATCH 07/41] switch to single file js for bindgen --- tools/link.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/link.py b/tools/link.py index bac1121002acb..4198061452c84 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1935,8 +1935,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_ if settings.WASM_BINDGEN: building.run_wasm_bindgen(in_wasm) - settings.PRE_JS_FILES += [get_emscripten_temp_dir() + '/bindgen_out/wbg_pre.js'] - settings.JS_LIBRARIES += [get_emscripten_temp_dir() + '/bindgen_out/library_wbg.js'] + settings.JS_LIBRARIES += [get_emscripten_temp_dir() + '/bindgen_out/library_bindgen.js'] metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata) From 836fbe614b0af1666244515bd7bdfa57765a8b5f Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Fri, 21 Feb 2025 17:26:36 -0500 Subject: [PATCH 08/41] minor cleanup --- src/preamble.js | 2 ++ tools/building.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index 5b14e168ae5b4..a43117103c64c 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -842,7 +842,9 @@ function getWasmImports() { #else // MINIFY_WASM_IMPORTED_MODULES 'env': wasmImports, '{{{ WASI_MODULE_NAME }}}': wasmImports, +#if WASM_BINDGEN 'wbg': wasmImports, +#endif // WASM_BINDGEN #endif // MINIFY_WASM_IMPORTED_MODULES #if SPLIT_MODULE 'placeholder': new Proxy({}, splitModuleProxyHandler), diff --git a/tools/building.py b/tools/building.py index 43c13899cd60f..121e4577cacbf 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1237,8 +1237,8 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa ] check_call(cmd) - # TODO(walkingeye): don't try to predict the .wasm filename that wasm-bindgen - # outputs. instead just grab the .wasm file itself (there will only ever be one). + # 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 = list(filter(lambda x: x.endswith('.wasm'), all_output_files))[0] if outfile == None: From 2debab97382f34466bf079e256e78adecbade451 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 24 Feb 2025 13:54:01 -0500 Subject: [PATCH 09/41] first test --- test/rust/basics/.cargo/config.toml | 2 ++ .../bindgen_integration/.cargo/config.toml | 7 ++++ test/rust/bindgen_integration/Cargo.toml | 9 +++++ test/rust/bindgen_integration/src/lib.rs | 6 ++++ test/test_other.py | 36 ++++++++++++++++++- 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 test/rust/basics/.cargo/config.toml create mode 100644 test/rust/bindgen_integration/.cargo/config.toml create mode 100644 test/rust/bindgen_integration/Cargo.toml create mode 100644 test/rust/bindgen_integration/src/lib.rs diff --git a/test/rust/basics/.cargo/config.toml b/test/rust/basics/.cargo/config.toml new file mode 100644 index 0000000000000..3301c205541b8 --- /dev/null +++ b/test/rust/basics/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-emscripten" diff --git a/test/rust/bindgen_integration/.cargo/config.toml b/test/rust/bindgen_integration/.cargo/config.toml new file mode 100644 index 0000000000000..c9be0d51ea09a --- /dev/null +++ b/test/rust/bindgen_integration/.cargo/config.toml @@ -0,0 +1,7 @@ +[build] +target = "wasm32-unknown-emscripten" +rustflags = [ + "-Cllvm-args=-enable-emscripten-cxx-exceptions=0", + "-Cpanic=abort", + "-Crelocation-model=static", +] diff --git a/test/rust/bindgen_integration/Cargo.toml b/test/rust/bindgen_integration/Cargo.toml new file mode 100644 index 0000000000000..1b2fb82dbb621 --- /dev/null +++ b/test/rust/bindgen_integration/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bindgen_integration" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +wasm-bindgen = { path = "../../../wasm-bindgen" } diff --git a/test/rust/bindgen_integration/src/lib.rs b/test/rust/bindgen_integration/src/lib.rs new file mode 100644 index 0000000000000..541bc882468cb --- /dev/null +++ b/test/rust/bindgen_integration/src/lib.rs @@ -0,0 +1,6 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn rs_add(a: i32, b: i32) -> i32 { + return a + b; +} diff --git a/test/test_other.py b/test/test_other.py index 5720d8c294d66..a9490de4151e4 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -15587,7 +15587,7 @@ def test_embool(self): @requires_rust def test_rust_integration_basics(self): copytree(test_file('rust/basics'), '.') - self.run_process(['cargo', 'build', '--target=wasm32-unknown-emscripten']) + self.run_process(['cargo', 'build']) lib = 'target/wasm32-unknown-emscripten/debug/libbasics.a' self.assertExists(lib) @@ -15599,6 +15599,40 @@ def test_rust_integration_basics(self): }''') self.do_runf('main.cpp', 'Hello from rust!', emcc_args=[lib]) + @requires_rust + def test_wasm_bindgen_integration(self): + copytree(test_file('rust/bindgen_integration'), '.') + self.run_process(['cargo', 'build']) + lib = 'target/wasm32-unknown-emscripten/debug/libbindgen_integration.a' + self.assertExists(lib) + + create_file('main.cpp', '') + create_file('post.js', ''' + Module.onRuntimeInitialized = () => { + out(Module.rs_add(17, 25)); + }; + ''') + exported_funcs = [ + '___wbindgen_describe_rs_add', + '_rs_add', + '___externref_drop_slice', + '___externref_heap_live_count', + '___externref_table_alloc', + '___externref_table_dealloc', + '___wbindgen_exn_store', + '___wbindgen_free', + '___wbindgen_malloc', + '___wbindgen_realloc'] + emcc_args = [ + lib, + '-sWASM_BINDGEN', + '--post-js', + 'post.js', + '-Wno-undefined', + '-sEXPORTED_FUNCTIONS=' + ','.join(exported_funcs), + ] + self.do_runf('main.cpp', '42', emcc_args=emcc_args) + def test_relative_em_cache(self): with env_modify({'EM_CACHE': 'foo'}): err = self.expect_fail([EMCC, '-c', test_file('hello_world.c')]) From ea4eab0e12d0075809213a6b3d7e23f1fdfc0ead Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Tue, 11 Mar 2025 17:25:12 -0400 Subject: [PATCH 10/41] only avoid stripping if wasm bindgen is enabled --- tools/emscripten.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/emscripten.py b/tools/emscripten.py index 90b0c830a0a72..5033acc782c7a 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -559,10 +559,10 @@ def finalize_wasm(infile, outfile, js_syms): strip_sections += ['name'] # TODO(walkingeyerobot): make this work. it appears to not like the wasm file from wasm-bindgen - #if strip_sections or not settings.GENERATE_DWARF: - # building.save_intermediate(outfile, 'strip.wasm') - # building.strip(infile, outfile, debug=not settings.GENERATE_DWARF, - # sections=strip_sections) + if not settings.WASM_BINDGEN and (strip_sections or not settings.GENERATE_DWARF): + building.save_intermediate(outfile, 'strip.wasm') + building.strip(infile, outfile, debug=not settings.GENERATE_DWARF, + sections=strip_sections) metadata = get_metadata(outfile, outfile, modify_wasm, args) From d7faa6b482ca9710e6bceac4798e9d94896cf86c Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Tue, 11 Mar 2025 17:26:23 -0400 Subject: [PATCH 11/41] remove irrelevant comment; going to solve this a different way --- tools/emscripten.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/emscripten.py b/tools/emscripten.py index 5033acc782c7a..2efa5b8873082 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -935,7 +935,6 @@ def install_wrapper(sym): else: wrapper += f"wasmExports['{name}']" - # TODO(walkingeyerobot): if the export is from rust, it should probably be excluded here and dealt with elsewhere. wrappers.append(wrapper) return wrappers From cb281cbcea9064b2e030599f5ed3bff4e2e65c93 Mon Sep 17 00:00:00 2001 From: google-yfyang Date: Wed, 12 Mar 2025 15:25:47 -0400 Subject: [PATCH 12/41] Make HEAP_DATA_VIEW available for wasm-bindgen regardless of endianness --- src/preamble.js | 2 +- src/preamble_minimal.js | 2 +- src/runtime_shared.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index a43117103c64c..76c260ba1d3ec 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -130,7 +130,7 @@ var HEAP, /** @type {!Float64Array} */ HEAPF64; -#if SUPPORT_BIG_ENDIAN +#if SUPPORT_BIG_ENDIAN || WASM_BINDGEN var HEAP_DATA_VIEW; #endif diff --git a/src/preamble_minimal.js b/src/preamble_minimal.js index e02b4b3368e3f..a8c05ce8d15f6 100644 --- a/src/preamble_minimal.js +++ b/src/preamble_minimal.js @@ -36,7 +36,7 @@ var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64, #if WASM_BIGINT HEAP64, HEAPU64, #endif -#if SUPPORT_BIG_ENDIAN +#if SUPPORT_BIG_ENDIAN || WASM_BINDGEN HEAP_DATA_VIEW, #endif wasmMemory; diff --git a/src/runtime_shared.js b/src/runtime_shared.js index 850e0859ae051..b4f6d09313f79 100644 --- a/src/runtime_shared.js +++ b/src/runtime_shared.js @@ -66,7 +66,7 @@ var wasmOffsetConverter; function updateMemoryViews() { var b = wasmMemory.buffer; -#if SUPPORT_BIG_ENDIAN +#if SUPPORT_BIG_ENDIAN || WASM_BINDGEN {{{ maybeExportHeap('HEAP_DATA_VIEW') }}} HEAP_DATA_VIEW = new DataView(b); #endif {{{ maybeExportHeap('HEAP8') }}}HEAP8 = new Int8Array(b); From 2843e36b489fd5c1ebc08fa2a3ed649567e8390f Mon Sep 17 00:00:00 2001 From: Walter Lee Date: Thu, 13 Mar 2025 09:18:24 -0400 Subject: [PATCH 13/41] Automatically infer what symbols to export for wasm-bindgen Export all C symbols, except perhaps those from system libraries. --- emcc.py | 2 +- tools/building.py | 34 +++++++++++++++++++++++++++++++++- tools/link.py | 12 ++++++++---- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/emcc.py b/emcc.py index 9c160a39a6443..810ee96ce1c8c 100644 --- a/emcc.py +++ b/emcc.py @@ -997,7 +997,7 @@ def compile_source_file(input_file): # Default to assuming the inputs are object files and pass them to the linker pass - return [f.value for f in linker_args] + return linker_args def version_string(): diff --git a/tools/building.py b/tools/building.py index 121e4577cacbf..eeb2c5ad84d47 100644 --- a/tools/building.py +++ b/tools/building.py @@ -254,7 +254,35 @@ def lld_flags_for_executable(external_symbols): return cmd -def link_lld(args, target, external_symbols=None): +def get_wasm_bindgen_exported_symbols(input_files): + if not os.path.exists(LLVM_NM): + exit_with_error('llvm-nm not found in LLVM directory: %s', LLVM_NM) + + nm_args = [ + LLVM_NM, + '--defined-only', + '--extern-only', + '--format=just-symbols', + '--print-file-name', + '--quiet', + ] + + result = run_process(nm_args + input_files, stdout=subprocess.PIPE) + symbols = [] + for line in result.stdout.splitlines(): + (path, symbol) = line.split() + # Skip system libraries which are not needed and causes bloat. + if path.find('bin/third_party/crosstool/rust/') != -1: + continue + # Skip mangled (non-C) symbols + if symbol.startswith('_Z') or symbol.startswith('_R'): + continue + symbols.append(symbol) + + return symbols + + +def link_lld(args, target, external_symbols=None, linker_inputs=[]): if not os.path.exists(WASM_LD): exit_with_error('linker binary not found in LLVM directory: %s', WASM_LD) # runs lld to link things. @@ -263,6 +291,10 @@ def link_lld(args, target, external_symbols=None): # grouping. args = [a for a in args if a not in ('--start-group', '--end-group')] + if settings.WASM_BINDGEN: + exported_symbols = get_wasm_bindgen_exported_symbols(linker_inputs) + args.extend(f'--export-if-defined={e}' for e in exported_symbols) + # Emscripten currently expects linkable output (SIDE_MODULE/MAIN_MODULE) to # include all archive contents. if settings.LINKABLE: diff --git a/tools/link.py b/tools/link.py index c199db8b2c825..e6e95e78040f2 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1856,7 +1856,7 @@ def phase_calculate_system_libraries(options): @ToolchainProfiler.profile_block('link') -def phase_link(linker_args, wasm_target, js_syms): +def phase_link(linker_args, linker_inputs, wasm_target, js_syms): logger.debug(f'linking: {linker_args}') # Make a final pass over settings.EXPORTED_FUNCTIONS to remove any @@ -1878,11 +1878,12 @@ def phase_link(linker_args, wasm_target, js_syms): # TODO(sbc): Remove this double execution of wasm-ld if we ever find a way to # distinguish EMSCRIPTEN_KEEPALIVE exports from `--export-dynamic` exports. settings.LINKABLE = False - building.link_lld(linker_args, wasm_target, external_symbols=js_syms) + building.link_lld(linker_args, wasm_target, external_symbols=js_syms, + linker_inputs=linker_inputs) settings.LINKABLE = True rtn = extract_metadata.extract_metadata(wasm_target) - building.link_lld(linker_args, wasm_target, external_symbols=js_syms) + building.link_lld(linker_args, wasm_target, external_symbols=js_syms, linker_inputs=linker_inputs) return rtn @@ -3125,6 +3126,9 @@ def run(options, linker_args): if not linker_args: exit_with_error('no input files') + linker_inputs = [f.value for f in linker_args if f.is_file] + linker_args = [f.value for f in linker_args] + if options.output_file and options.output_file.startswith('-'): exit_with_error(f'invalid output filename: `{options.output_file}`') @@ -3174,7 +3178,7 @@ def add_js_deps(sym): settings.ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = settings.ASYNCIFY_IMPORTS[:] settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']] - base_metadata = phase_link(linker_args, wasm_target, js_syms) + base_metadata = phase_link(linker_args, linker_inputs, wasm_target, js_syms) # Special handling for when the user passed '-Wl,--version'. In this case the linker # does not create the output file, but just prints its version and exits with 0. From 26cd774bf827c8638abdc9aa1df492665e467184 Mon Sep 17 00:00:00 2001 From: Walter Lee Date: Thu, 13 Mar 2025 16:03:49 -0400 Subject: [PATCH 14/41] Remove hack not to export system libraries --- tools/building.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/building.py b/tools/building.py index eeb2c5ad84d47..e43bb60f83d14 100644 --- a/tools/building.py +++ b/tools/building.py @@ -271,9 +271,6 @@ def get_wasm_bindgen_exported_symbols(input_files): symbols = [] for line in result.stdout.splitlines(): (path, symbol) = line.split() - # Skip system libraries which are not needed and causes bloat. - if path.find('bin/third_party/crosstool/rust/') != -1: - continue # Skip mangled (non-C) symbols if symbol.startswith('_Z') or symbol.startswith('_R'): continue From c2dfc912172615e268036573565f3ca7ffe75cdb Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 17 Mar 2025 10:54:36 -0400 Subject: [PATCH 15/41] wasm bindgen now uses the 'env' property instead of its own 'wbg' --- src/preamble.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index 76c260ba1d3ec..30b0e3ebc0282 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -842,9 +842,6 @@ function getWasmImports() { #else // MINIFY_WASM_IMPORTED_MODULES 'env': wasmImports, '{{{ WASI_MODULE_NAME }}}': wasmImports, -#if WASM_BINDGEN - 'wbg': wasmImports, -#endif // WASM_BINDGEN #endif // MINIFY_WASM_IMPORTED_MODULES #if SPLIT_MODULE 'placeholder': new Proxy({}, splitModuleProxyHandler), From 4196290912b3e67432a744be596c15a5a1018804 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 24 Mar 2025 16:34:46 -0400 Subject: [PATCH 16/41] with newer rustc, there appears to be a bunch of anon symbols that we need to exclude --- tools/building.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/building.py b/tools/building.py index e43bb60f83d14..908b56e6d9467 100644 --- a/tools/building.py +++ b/tools/building.py @@ -272,7 +272,7 @@ def get_wasm_bindgen_exported_symbols(input_files): for line in result.stdout.splitlines(): (path, symbol) = line.split() # Skip mangled (non-C) symbols - if symbol.startswith('_Z') or symbol.startswith('_R'): + if symbol.startswith('_Z') or symbol.startswith('_R') or symbol.startswith('anon.'): continue symbols.append(symbol) From 8c08d8e5fb0951604d09bc70d75d02741d2e4aec Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Mon, 28 Apr 2025 19:09:36 -0400 Subject: [PATCH 17/41] pass --keep-debug to wasm-bindgen always. --- tools/building.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/building.py b/tools/building.py index 908b56e6d9467..f28cb939b3292 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1261,6 +1261,7 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa '--target', 'emscripten', '--keep-lld-exports', + '--keep-debug', '--out-dir', bindgen_out_dir, ] From 50c4fe1be40ba9e5e2a1cf5b5595874bab8c170a Mon Sep 17 00:00:00 2001 From: google-yfyang Date: Wed, 4 Jun 2025 11:31:54 -0400 Subject: [PATCH 18/41] Get typescript with wasm-bindgen to work --- tools/emscripten.py | 11 ++++++++++- tools/link.py | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/emscripten.py b/tools/emscripten.py index 680bd6d40647e..1fd3896250eca 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -644,7 +644,7 @@ def create_tsd_exported_runtime_methods(metadata): return utils.read_file(tsc_output_file) -def create_tsd(metadata, embind_tsd): +def create_tsd(metadata, embind_tsd, bindgen_tsd = None): out = '// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.\n' if settings.EXPORTED_RUNTIME_METHODS: out += create_tsd_exported_runtime_methods(metadata) @@ -674,6 +674,15 @@ def create_tsd(metadata, embind_tsd): # Add in embind definitions. if embind_tsd: export_interfaces += ' & EmbindModule' + if settings.WASM_BINDGEN and bindgen_tsd: + out += 'interface BindgenModule {\n' + for file_path in bindgen_tsd: + indent = " " + with open(file_path, 'r') as file: + for line in file: + out += f'{indent}{line}' + out += '}\n\n' + export_interfaces += ' & BindgenModule' out += f'export type MainModule = {export_interfaces};\n' if settings.MODULARIZE: return_type = 'MainModule' diff --git a/tools/link.py b/tools/link.py index 57209c3a04424..094eb50b15460 100644 --- a/tools/link.py +++ b/tools/link.py @@ -2089,7 +2089,10 @@ def phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata): embind_tsd = '' if settings.EMBIND: embind_tsd = run_embind_gen(options, wasm_target, js_syms, {'EMBIND_AOT': False}) - all_tsd = emscripten.create_tsd(metadata, embind_tsd) + bindgen_ts_files = glob.glob(get_emscripten_temp_dir() + "/bindgen_out/*.d.ts", recursive=False) + # This list comprehension then filters out any files that end with .wasm.d.ts. + bindgen_ts_files = [file for file in bindgen_ts_files if not file.endswith('.wasm.d.ts')] + all_tsd = emscripten.create_tsd(metadata, embind_tsd, bindgen_ts_files) out_file = os.path.join(os.path.dirname(js_target), filename) write_file(out_file, all_tsd) From fe3db012c1e372a19710778a3191309ee7320e0f Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Tue, 24 Jun 2025 16:11:21 -0400 Subject: [PATCH 19/41] don't do big endian stuff with bindgen stuff unless explicitly asked to --- src/runtime_common.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/runtime_common.js b/src/runtime_common.js index f2795c53a7023..2f26d0d0942dd 100644 --- a/src/runtime_common.js +++ b/src/runtime_common.js @@ -146,7 +146,9 @@ function updateMemoryViews() { {{{ maybeExportHeap('HEAPU64') }}}HEAPU64 = new BigUint64Array(b); #endif #if SUPPORT_BIG_ENDIAN || WASM_BINDGEN - {{{ maybeExportHeap('HEAP_DATA_VIEW') }}} HEAP_DATA_VIEW = new DataView(b); + {{{ maybeExportHeap('HEAP_DATA_VIEW') }}}HEAP_DATA_VIEW = new DataView(b); +#endif +#if SUPPORT_BIG_ENDIAN LE_HEAP_UPDATE(); #endif } From 6d1c5ffcd083a9b06aaa2cba5312da22d2564a24 Mon Sep 17 00:00:00 2001 From: google-yfyang Date: Wed, 25 Jun 2025 11:42:44 -0400 Subject: [PATCH 20/41] make typescript work with emscripten (part2) --- tools/emscripten.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/emscripten.py b/tools/emscripten.py index 5000961a654d4..3fde70664e4fd 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -675,13 +675,10 @@ def create_tsd(metadata, embind_tsd, bindgen_tsd = None): if embind_tsd: export_interfaces += ' & EmbindModule' if settings.WASM_BINDGEN and bindgen_tsd: - out += 'interface BindgenModule {\n' for file_path in bindgen_tsd: - indent = " " with open(file_path, 'r') as file: for line in file: - out += f'{indent}{line}' - out += '}\n\n' + out += f'{line}' export_interfaces += ' & BindgenModule' out += f'export type MainModule = {export_interfaces};\n' if settings.MODULARIZE: From 9627ee3f6cbeb0d480ba32d149531cfb1eba1f82 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Wed, 18 Feb 2026 11:15:47 -0500 Subject: [PATCH 21/41] add missing import glob --- tools/link.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/link.py b/tools/link.py index 915b52e22f608..842ecc85b23b4 100644 --- a/tools/link.py +++ b/tools/link.py @@ -4,6 +4,7 @@ # found in the LICENSE file. import base64 +import glob import json import logging import os From 44d3059c2f32583fdec074fdebe1cce790db3977 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Thu, 2 Apr 2026 15:30:30 -0400 Subject: [PATCH 22/41] remove unneeded --target flag --- tools/building.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/building.py b/tools/building.py index ba9e6d16edf7a..32e35c2e4c3f1 100644 --- a/tools/building.py +++ b/tools/building.py @@ -1343,8 +1343,6 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa cmd = config.WASM_BINDGEN + [ infile, - '--target', - 'emscripten', '--keep-lld-exports', '--keep-debug', '--out-dir', From 1b728518633f1ebae88c2eadb9c6a0039549a32d Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Thu, 2 Apr 2026 16:05:59 -0400 Subject: [PATCH 23/41] update docs --- site/source/docs/tools_reference/settings_reference.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index b7777880f414b..8b6fb680daddc 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -3301,6 +3301,15 @@ Example use ``-sSIGNATURE_CONVERSIONS=someFunction:_p,anotherFunction:p`` Default value: [] +.. _wasm_bindgen: + +WASM_BINDGEN +============ + +Run wasm-bindgen and integrate the rust-exported symbols into the rest of Emscripten's JS output. + +Default value: 0 + .. _source_phase_imports: SOURCE_PHASE_IMPORTS From afebddcd97637e745b7c72b30e2aedad531aadb6 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Thu, 2 Apr 2026 16:08:53 -0400 Subject: [PATCH 24/41] fix python warnings --- tools/emscripten.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/emscripten.py b/tools/emscripten.py index beb041bf63585..5b06005289e02 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -710,7 +710,7 @@ def create_tsd(metadata, embind_tsd, bindgen_tsd = None): export_interfaces += ' & EmbindModule' if settings.WASM_BINDGEN and bindgen_tsd: for file_path in bindgen_tsd: - with open(file_path, 'r') as file: + with open(file_path, encoding='utf-8') as file: for line in file: out += f'{line}' export_interfaces += ' & BindgenModule' From 80d0b6741ad9465e861d8a719d7198c3cd89075c Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Thu, 2 Apr 2026 16:30:12 -0400 Subject: [PATCH 25/41] python lint fix --- tools/emscripten.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/emscripten.py b/tools/emscripten.py index 5b06005289e02..8219b98501468 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -678,7 +678,7 @@ def create_tsd_exported_runtime_methods(metadata): return utils.read_file(tsc_output_file) -def create_tsd(metadata, embind_tsd, bindgen_tsd = None): +def create_tsd(metadata, embind_tsd, bindgen_tsd=None): out = '// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.\n' if settings.EXPORTED_RUNTIME_METHODS: out += create_tsd_exported_runtime_methods(metadata) From 3cd3b31a2824181d8176aec81d18cb968e23b204 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Thu, 2 Apr 2026 16:45:26 -0400 Subject: [PATCH 26/41] more python lint fixes --- test/common.py | 3 +-- tools/building.py | 12 +++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/test/common.py b/test/common.py index 33f538c8c7829..a64c9e6376792 100644 --- a/test/common.py +++ b/test/common.py @@ -1390,8 +1390,7 @@ def ccshared(src, linkto=None): cfunc_ptr(); return 0; } - ''' % locals(), - 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n', cflags=extra_args) + ''', 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n', cflags=extra_args) def do_run(self, src, expected_output=None, force_c=False, **kwargs): if 'no_build' in kwargs: diff --git a/tools/building.py b/tools/building.py index 32e35c2e4c3f1..17c448ff6611a 100644 --- a/tools/building.py +++ b/tools/building.py @@ -291,20 +291,22 @@ def get_wasm_bindgen_exported_symbols(input_files): '--print-file-name', '--quiet', ] + if input_files is not None: + nm_args += input_files - result = run_process(nm_args + input_files, stdout=subprocess.PIPE) + result = run_process(nm_args, stdout=subprocess.PIPE) symbols = [] for line in result.stdout.splitlines(): (path, symbol) = line.split() # Skip mangled (non-C) symbols - if symbol.startswith('_Z') or symbol.startswith('_R') or symbol.startswith('anon.'): + if symbol.startswith(('_Z', '_R', 'anon.')): continue symbols.append(symbol) return symbols -def lld_flags(args, linker_inputs=[]): +def lld_flags(args, linker_inputs=None): # lld doesn't currently support --start-group/--end-group since the # semantics are more like the windows linker where there is no need for # grouping. @@ -347,7 +349,7 @@ def lld_flags(args, linker_inputs=[]): return args -def link_lld(args, target, external_symbols=None, linker_inputs=[]): +def link_lld(args, target, external_symbols=None, linker_inputs=None): # runs lld to link things. if not os.path.exists(WASM_LD): exit_with_error('linker binary not found in LLVM directory: %s', WASM_LD) @@ -1354,7 +1356,7 @@ def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa # just grab the .wasm file itself. all_output_files = os.listdir(bindgen_out_dir) new_wasm_file = list(filter(lambda x: x.endswith('.wasm'), all_output_files))[0] - if outfile == None: + if outfile is None: outfile = infile shutil.copyfile(bindgen_out_dir + new_wasm_file, outfile) From 68eb9b27e27ae59d700c71b1a489a581590a15be Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Fri, 17 Apr 2026 01:17:49 -0400 Subject: [PATCH 27/41] use wasm-bindgen for testing properly --- test/rust/bindgen_integration/Cargo.toml | 1 - test/test_other.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/rust/bindgen_integration/Cargo.toml b/test/rust/bindgen_integration/Cargo.toml index 1b2fb82dbb621..044293686a9e6 100644 --- a/test/rust/bindgen_integration/Cargo.toml +++ b/test/rust/bindgen_integration/Cargo.toml @@ -6,4 +6,3 @@ edition = "2021" crate-type = ["staticlib"] [dependencies] -wasm-bindgen = { path = "../../../wasm-bindgen" } diff --git a/test/test_other.py b/test/test_other.py index c148a67e86cd8..f001f248e795c 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -14915,6 +14915,8 @@ def test_rust_integration_basics(self): @requires_rust def test_wasm_bindgen_integration(self): copytree(test_file('rust/bindgen_integration'), '.') + self.run_process(['cargo', 'install', 'wasm-bindgen-cli', '--root', '.']) + self.run_process(['cargo', 'add', 'wasm-bindgen']) self.run_process(['cargo', 'build']) lib = 'target/wasm32-unknown-emscripten/debug/libbindgen_integration.a' self.assertExists(lib) @@ -14944,7 +14946,8 @@ def test_wasm_bindgen_integration(self): '-Wno-undefined', '-sEXPORTED_FUNCTIONS=' + ','.join(exported_funcs), ] - self.do_runf('main.cpp', '42', emcc_args=emcc_args) + with env_modify({'WASM_BINDGEN': os.path.abspath('bin/wasm-bindgen')}): + self.do_runf('main.cpp', '42', emcc_args=emcc_args) def test_relative_em_cache(self): with env_modify({'EM_CACHE': 'foo'}): From e55a8a1f3876d85b733a2ae9a644c10fa5c0daaa Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Fri, 17 Apr 2026 23:08:16 -0400 Subject: [PATCH 28/41] fix the test once and for all --- .circleci/config.yml | 2 ++ test/test_other.py | 18 ++---------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3d15ba5af15d6..81f0b7fba0464 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,6 +77,8 @@ commands: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y export PATH=${HOME}/.cargo/bin:${PATH} rustup target add wasm32-unknown-emscripten + cargo install wasm-bindgen-cli + echo "WASM_BINDGEN=${HOME}/.cargo/bin/wasm-bindgen" >> ~/emsdk/.emscripten echo "export PATH=\"\$HOME/.cargo/bin:\$PATH\"" >> $BASH_ENV install-node-version: description: "install a specific version of node" diff --git a/test/test_other.py b/test/test_other.py index 299002fb52728..725fd14d17d67 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -14933,7 +14933,6 @@ def test_rust_integration_basics(self): @requires_rust def test_wasm_bindgen_integration(self): copytree(test_file('rust/bindgen_integration'), '.') - self.run_process(['cargo', 'install', 'wasm-bindgen-cli', '--root', '.']) self.run_process(['cargo', 'add', 'wasm-bindgen']) self.run_process(['cargo', 'build']) lib = 'target/wasm32-unknown-emscripten/debug/libbindgen_integration.a' @@ -14945,27 +14944,14 @@ def test_wasm_bindgen_integration(self): out(Module.rs_add(17, 25)); }; ''') - exported_funcs = [ - '___wbindgen_describe_rs_add', - '_rs_add', - '___externref_drop_slice', - '___externref_heap_live_count', - '___externref_table_alloc', - '___externref_table_dealloc', - '___wbindgen_exn_store', - '___wbindgen_free', - '___wbindgen_malloc', - '___wbindgen_realloc'] emcc_args = [ lib, '-sWASM_BINDGEN', '--post-js', 'post.js', - '-Wno-undefined', - '-sEXPORTED_FUNCTIONS=' + ','.join(exported_funcs), ] - with env_modify({'WASM_BINDGEN': os.path.abspath('bin/wasm-bindgen')}): - self.do_runf('main.cpp', '42', emcc_args=emcc_args) + + self.do_runf('main.cpp', '42', cflags=emcc_args) def test_relative_em_cache(self): with env_modify({'EM_CACHE': 'foo'}): From bc3a3f05d40dfa5b4b5f9ca493040b66278486a9 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Fri, 17 Apr 2026 23:30:10 -0400 Subject: [PATCH 29/41] ...once and for all --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 81f0b7fba0464..f2ecd8e5439fd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -71,6 +71,7 @@ commands: command: << parameters.python >> -m pip install -r requirements-dev.txt install-rust: steps: + - install-emsdk - run: name: install rust command: | From bf9d095cd1269f9a2ed6aa522531035f34594b64 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Sat, 18 Apr 2026 00:00:54 -0400 Subject: [PATCH 30/41] ONCE AND FOR ALL --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f2ecd8e5439fd..3eeb662ffe2f3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -71,6 +71,8 @@ commands: command: << parameters.python >> -m pip install -r requirements-dev.txt install-rust: steps: + - checkout + - pip-install - install-emsdk - run: name: install rust From 6029dd71e369a524e991aeefce471df4972be237 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Sat, 18 Apr 2026 01:03:09 -0400 Subject: [PATCH 31/41] ok maybe I'm a little lost --- test/test_other.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_other.py b/test/test_other.py index 725fd14d17d67..409c681990547 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -14937,6 +14937,7 @@ def test_wasm_bindgen_integration(self): self.run_process(['cargo', 'build']) lib = 'target/wasm32-unknown-emscripten/debug/libbindgen_integration.a' self.assertExists(lib) + self.assertExists(os.path.abspath(config.WASM_BINDGEN[0])) create_file('main.cpp', '') create_file('post.js', ''' From c082ea1a7c5186fc4c2b4c70df8a8f9825381cd8 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Sat, 18 Apr 2026 01:47:16 -0400 Subject: [PATCH 32/41] my hopes have never been higher, and yet... --- .circleci/config.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3eeb662ffe2f3..8746741346ff6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -71,9 +71,6 @@ commands: command: << parameters.python >> -m pip install -r requirements-dev.txt install-rust: steps: - - checkout - - pip-install - - install-emsdk - run: name: install rust command: | @@ -81,7 +78,6 @@ commands: export PATH=${HOME}/.cargo/bin:${PATH} rustup target add wasm32-unknown-emscripten cargo install wasm-bindgen-cli - echo "WASM_BINDGEN=${HOME}/.cargo/bin/wasm-bindgen" >> ~/emsdk/.emscripten echo "export PATH=\"\$HOME/.cargo/bin:\$PATH\"" >> $BASH_ENV install-node-version: description: "install a specific version of node" @@ -164,6 +160,7 @@ commands: # We use an out-of-tree cache directory so it can be part of the # persisted workspace (see below). echo "CACHE = os.path.expanduser('~/cache')" >> .emscripten + echo "WASM_BINDGEN = os.path.expanduser('~/.cargo/bin/wasm-bindgen')" # Refer to commit 0bc3640 if we need to pin V8 version. echo "V8_ENGINE = [os.path.expanduser('~/.jsvu/bin/v8')]" >> .emscripten echo "JS_ENGINES = [NODE_JS]" >> .emscripten From 78b0a55780e4ba2ee2645e3ade0ff10d810a1677 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Sat, 18 Apr 2026 01:47:59 -0400 Subject: [PATCH 33/41] this one for sure --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8746741346ff6..e65f542f8be52 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -139,6 +139,7 @@ commands: install-emsdk: description: "Install emsdk" steps: + - install-rust - run: name: install emsdk command: | From 1925b0533ca742ffa2e3004abeb54d925a9e2fdb Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Sat, 18 Apr 2026 02:09:24 -0400 Subject: [PATCH 34/41] I should probably go to bed --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e65f542f8be52..342bc39a1c2ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -161,7 +161,7 @@ commands: # We use an out-of-tree cache directory so it can be part of the # persisted workspace (see below). echo "CACHE = os.path.expanduser('~/cache')" >> .emscripten - echo "WASM_BINDGEN = os.path.expanduser('~/.cargo/bin/wasm-bindgen')" + echo "WASM_BINDGEN = os.path.expanduser('~/.cargo/bin/wasm-bindgen')" >> .emscripten # Refer to commit 0bc3640 if we need to pin V8 version. echo "V8_ENGINE = [os.path.expanduser('~/.jsvu/bin/v8')]" >> .emscripten echo "JS_ENGINES = [NODE_JS]" >> .emscripten From b009b6111b2650ea9a5571cf550a8db590d26b98 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Sat, 18 Apr 2026 02:14:20 -0400 Subject: [PATCH 35/41] don't require rust --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 342bc39a1c2ac..9cac3787bf722 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -139,7 +139,6 @@ commands: install-emsdk: description: "Install emsdk" steps: - - install-rust - run: name: install emsdk command: | From 4b275657cd0e3f42d73c9a767a22c5fc0272a7f8 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Sun, 19 Apr 2026 02:36:39 -0400 Subject: [PATCH 36/41] this appears to work now --- tools/emscripten.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/emscripten.py b/tools/emscripten.py index 8219b98501468..15d2fe9e2db1b 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -584,8 +584,7 @@ def finalize_wasm(infile, outfile, js_syms): if not settings.GENERATE_DWARF: strip_sections += ['.debug*'] - # TODO(walkingeyerobot): make this work. it appears to not like the wasm file from wasm-bindgen - if strip_sections and not settings.WASM_BINDGEN: + if strip_sections: building.save_intermediate(outfile, 'strip.wasm') building.strip_sections(infile, outfile, strip_sections) From 094743a88e2edada602fe60e3438b01806d148a0 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Wed, 22 Apr 2026 15:08:33 -0400 Subject: [PATCH 37/41] prevent export renaming for wasm-bindgen test --- test/test_other.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_other.py b/test/test_other.py index d87d15d5d9ce3..0ee3738ca655f 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -14953,6 +14953,7 @@ def test_wasm_bindgen_integration(self): '-sWASM_BINDGEN', '--post-js', 'post.js', + '-lexports.js', ] self.do_runf('main.cpp', '42', cflags=emcc_args) From dc705f4f7e8ea2c146516e11c8cbc903b69968f0 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Fri, 24 Apr 2026 15:32:36 -0400 Subject: [PATCH 38/41] fix an error where strings were being appended instead of LinkFlag objects --- emcc.py | 16 +--------------- tools/link.py | 3 ++- tools/utils.py | 13 +++++++++++++ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/emcc.py b/emcc.py index e7383a6c9c05a..05d60c1741396 100644 --- a/emcc.py +++ b/emcc.py @@ -26,7 +26,6 @@ import shutil import sys import tarfile -from dataclasses import dataclass from enum import Enum, auto, unique # This assert needs to happen early, before any too-recent python syntax is used. @@ -50,7 +49,7 @@ from tools.settings import COMPILE_TIME_SETTINGS, default_setting, settings, user_settings from tools.shared import DEBUG, DYLIB_EXTENSIONS, in_temp from tools.toolchain_profiler import ToolchainProfiler -from tools.utils import exit_with_error, get_file_suffix, read_file, unsuffixed_basename +from tools.utils import exit_with_error, get_file_suffix, read_file, unsuffixed_basename, LinkFlag logger = logging.getLogger('emcc') @@ -95,19 +94,6 @@ class Mode(Enum): COMPILE_AND_LINK = auto() -@dataclass -class LinkFlag: - """Used to represent a linker flag. - - The flag value is stored along with a bool that distinguishes input - files from non-files. - - A list of these is returned by separate_linker_flags. - """ - value: str - is_file: int - - class EmccState: def __init__(self, args): self.mode = Mode.COMPILE_AND_LINK diff --git a/tools/link.py b/tools/link.py index 4e5510463535f..b35bb971fd242 100644 --- a/tools/link.py +++ b/tools/link.py @@ -54,6 +54,7 @@ unsuffixed, unsuffixed_basename, write_file, + LinkFlag, ) logger = logging.getLogger('link') @@ -3082,7 +3083,7 @@ def run(options, linker_args): settings.limit_settings(None) if settings.RUNTIME_LINKED_LIBS: - linker_args += settings.RUNTIME_LINKED_LIBS + linker_args += [LinkFlag(f, False) for f in settings.RUNTIME_LINKED_LIBS] if not linker_args: exit_with_error('no input files') diff --git a/tools/utils.py b/tools/utils.py index 5eaf8718e7232..bbbb76615011c 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -15,6 +15,7 @@ import stat import subprocess import sys +from dataclasses import dataclass from pathlib import Path from . import diagnostics @@ -232,3 +233,15 @@ def set_version_globals(): EMSCRIPTEN_VERSION = read_file(filename).strip().strip('"') parts = [int(x) for x in EMSCRIPTEN_VERSION.split('-')[0].split('.')] EMSCRIPTEN_VERSION_MAJOR, EMSCRIPTEN_VERSION_MINOR, EMSCRIPTEN_VERSION_TINY = parts + +@dataclass +class LinkFlag: + """Used to represent a linker flag. + + The flag value is stored along with a bool that distinguishes input + files from non-files. + + A list of these is returned by separate_linker_flags. + """ + value: str + is_file: int From 06cf81a0d903f907578a64a5c87a8021cd2575c1 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Fri, 24 Apr 2026 15:58:19 -0400 Subject: [PATCH 39/41] python lint fixes --- emcc.py | 2 +- tools/link.py | 2 +- tools/utils.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/emcc.py b/emcc.py index 05d60c1741396..b916751f64d71 100644 --- a/emcc.py +++ b/emcc.py @@ -49,7 +49,7 @@ from tools.settings import COMPILE_TIME_SETTINGS, default_setting, settings, user_settings from tools.shared import DEBUG, DYLIB_EXTENSIONS, in_temp from tools.toolchain_profiler import ToolchainProfiler -from tools.utils import exit_with_error, get_file_suffix, read_file, unsuffixed_basename, LinkFlag +from tools.utils import LinkFlag, exit_with_error, get_file_suffix, read_file, unsuffixed_basename logger = logging.getLogger('emcc') diff --git a/tools/link.py b/tools/link.py index b35bb971fd242..a88582b2f7d33 100644 --- a/tools/link.py +++ b/tools/link.py @@ -46,6 +46,7 @@ from .toolchain_profiler import ToolchainProfiler from .utils import ( WINDOWS, + LinkFlag, delete_file, exit_with_error, get_file_suffix, @@ -54,7 +55,6 @@ unsuffixed, unsuffixed_basename, write_file, - LinkFlag, ) logger = logging.getLogger('link') diff --git a/tools/utils.py b/tools/utils.py index bbbb76615011c..43dfc1d3c36ed 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -234,6 +234,7 @@ def set_version_globals(): parts = [int(x) for x in EMSCRIPTEN_VERSION.split('-')[0].split('.')] EMSCRIPTEN_VERSION_MAJOR, EMSCRIPTEN_VERSION_MINOR, EMSCRIPTEN_VERSION_TINY = parts + @dataclass class LinkFlag: """Used to represent a linker flag. From 7898c9da10816907b9d83cf9aa42ba427d6d56b8 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Wed, 29 Apr 2026 16:16:45 -0400 Subject: [PATCH 40/41] fix bad merge on my part --- emcc.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/emcc.py b/emcc.py index d181aa325ac69..2939afb40ce59 100644 --- a/emcc.py +++ b/emcc.py @@ -95,20 +95,6 @@ class Mode(Enum): COMPILE_AND_LINK = auto() -@dataclass -class LinkFlag: - """Used to represent a linker flag. - - The flag value is stored along with a bool that distinguishes input - files from non-files. - - A list of these is returned by separate_linker_flags. - """ - - value: str - is_file: int - - class EmccState: def __init__(self, args): self.mode = Mode.COMPILE_AND_LINK From 389f901e8742498698df7d48f95f7c40e1f40336 Mon Sep 17 00:00:00 2001 From: Mitch Foley Date: Wed, 29 Apr 2026 16:21:34 -0400 Subject: [PATCH 41/41] how did this not fail earlier --- tools/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/utils.py b/tools/utils.py index 2ff8062136701..e29695be80157 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -246,5 +246,6 @@ class LinkFlag: A list of these is returned by separate_linker_flags. """ + value: str is_file: int