Skip to content

wasm-ld --export-all does not work with wasm-bindgen #4494

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

Open
jkelleyrtp opened this issue Apr 29, 2025 · 0 comments
Open

wasm-ld --export-all does not work with wasm-bindgen #4494

jkelleyrtp opened this issue Apr 29, 2025 · 0 comments
Labels

Comments

@jkelleyrtp
Copy link

jkelleyrtp commented Apr 29, 2025

Describe the Bug

I've implemented WASM hot patching for Rust.

DioxusLabs/dioxus#3797 (comment)

432065352-b81f1a60-b6b2-422d-a536-d9e0bcd75221.mp4

Part of this working properly requires every symbol from the dependency rlibs to make it into the final executable. All other platforms (mac/win/linux/android/ios) have facilities to force all symbols (--no-gc-sections, --whole-archive). Unfortunately, wasm-bindgen is the one tool that always runs GC and doesn't give an option to not run GC.

This means I need to prevent symbols from being dissolved by the wasm-bindgen GC pass, but relying on markers is fraught with issues and will likely break in the future. I hacked together something that barely works today but I can't expect it to work tomorrow:

    // We're going to add *all* the imports that start with __wbindgen to a function that's exported
    // This prevents them from being dissovlved
    for (imp, f) in imports {
        let name = &module.imports.get(imp).name;
        if name.starts_with("__wbindgen") && !name.starts_with("__wbindgen_describe") {
            module.exports.add(name, f);
            module.exports.add(&format!("{name}_subsecond"), f);
            tracing::info!("Hoisting import -> {name}");
            already_exported.insert(name.clone());
            make_indirect.push(f);
        }
    }

    for (name, index) in symbols.code_symbol_map.iter() {
        let func = module.funcs.get(ids[*index]);

        if let FunctionKind::Local(_) = &func.kind {
            if !already_exported.contains(*name) && !name_is_bindgen_symbol(name) {
                module.exports.add(name, func.id());
                already_exported.insert(name.to_string());
            }

            if !name_is_bindgen_symbol(name) {
                if !ifuncs.contains(&func.id()) {
                    make_indirect.push(func.id());
                }
            }
        }
    }

fn name_is_bindgen_symbol(name: &str) -> bool {
    name.contains("__wbindgen_describe")
        || name.contains("wasm_bindgen..describe..WasmDescribe")
        || name.contains("wasm_bindgen..closure..WasmClosure$GT$8describe")
        || name.contains("wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe")
        || name.contains("__wbindgen_describe_closure")
        || name.contains("__wbindgen_externref_xform")
}

If I force the wasm-bindgen intrinsics into an export, then the wasm-bindgen pass deletes them. If I put them in the ifunc table, the generated JS is invalid.

These imports:

12:20:22 [dev] Hoisting import -> __wbindgen_function_table
12:20:22 [dev] Hoisting import -> __wbindgen_number_new
12:20:22 [dev] Hoisting import -> __wbindgen_neg
12:20:22 [dev] Hoisting import -> __wbindgen_string_get
12:20:22 [dev] Hoisting import -> __wbindgen_throw
12:20:22 [dev] Hoisting import -> __wbindgen_bigint_from_i64
12:20:22 [dev] Hoisting import -> __wbindgen_in
12:20:22 [dev] Hoisting import -> __wbindgen_boolean_get
12:20:22 [dev] Hoisting import -> __wbindgen_externref_table_grow
12:20:22 [dev] Hoisting import -> __wbindgen_string_new
12:20:22 [dev] Hoisting import -> __wbindgen_error_new
12:20:22 [dev] Hoisting import -> __wbindgen_is_bigint
12:20:22 [dev] Hoisting import -> __wbindgen_debug_string
12:20:22 [dev] Hoisting import -> __wbindgen_exports
12:20:22 [dev] Hoisting import -> __wbindgen_array_push
12:20:22 [dev] Hoisting import -> __wbindgen_bigint_from_u64
12:20:22 [dev] Hoisting import -> __wbindgen_jsval_eq
12:20:22 [dev] Hoisting import -> __wbindgen_memory
12:20:22 [dev] Hoisting import -> __wbindgen_biguint64_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_is_object
12:20:22 [dev] Hoisting import -> __wbindgen_symbol_named_new
12:20:22 [dev] Hoisting import -> __wbindgen_object_drop_ref
12:20:22 [dev] Hoisting import -> __wbindgen_is_string
12:20:22 [dev] Hoisting import -> __wbindgen_object_clone_ref
12:20:22 [dev] Hoisting import -> __wbindgen_cb_drop
12:20:22 [dev] Hoisting import -> __wbindgen_is_undefined
12:20:22 [dev] Hoisting import -> __wbindgen_bigint_from_u128
12:20:22 [dev] Hoisting import -> __wbindgen_symbol_anonymous_new
12:20:22 [dev] Hoisting import -> __wbindgen_externref_table_set_null
12:20:22 [dev] Hoisting import -> __wbindgen_number_get
12:20:22 [dev] Hoisting import -> __wbindgen_externref_heap_live_count
12:20:22 [dev] Hoisting import -> __wbindgen_rethrow
12:20:22 [dev] Hoisting import -> __wbindgen_uint16_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_float32_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_as_number
12:20:22 [dev] Hoisting import -> __wbindgen_lt
12:20:22 [dev] Hoisting import -> __wbindgen_uint32_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_int32_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_int8_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_bigint_get_as_i64
12:20:22 [dev] Hoisting import -> __wbindgen_uint8_clamped_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_bigint64_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_uint8_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_float64_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_bigint_from_i128
12:20:22 [dev] Hoisting import -> __wbindgen_checked_div
12:20:22 [dev] Hoisting import -> __wbindgen_module
12:20:22 [dev] Hoisting import -> __wbindgen_is_function
12:20:22 [dev] Hoisting import -> __wbindgen_int16_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_array_new
12:20:22 [dev] Hoisting import -> __wbindgen_jsval_loose_eq

Will end up leaving the __wbindgen_externref_xform__ import in the generated JS bindings:

import * as __wbg_star5 from '__wbindgen_externref_xform__';

If I hoist every symbol (ie with --export-all) then the describe functions stick around and aren't valid JS.

// ...
// these imports still remain
import * as __wbg_star5 from '__wbindgen_externref_xform__';
import * as __wbg_star6 from '__wbindgen_placeholder__';

// ...
// this is not valid JS
function __wbg_adapter_102(arg0, arg1) {
    _assertNum(arg0);
    _assertNum(arg1);
    wasm._ZN132_$LT$dyn$u20$core..ops..function..FnMut$LT$$LP$$RP$$GT$$u2b$Output$u20$$u3d$$u20$R$u20$as$u20$wasm_bindgen..closure..WasmClosure$GT$8describe6invoke17h4336be12b5d12f09E(arg0, arg1);
}

Ideally, I can pass --export-all, get every interface properly wasm-bindgen-ed, and then load subsequent patches that don't require any bindgen at all. Currently it's not clear if I can even load the patches without running wasm-bindgen on them (due to the transform passes), and if I have to, then I can't use dynamic linking because of #4493.

Steps to Reproduce

Running rustc with the export-all flag will trigger the issues:

cargo rustc --package app --target wasm32-unknown-unknown -Clinker-arg=-Wl,--export-all

Expected Behavior

You can export all symbols from a wasm module.

Actual Behavior

The generated JS is invalid.

Additional Context

Add any other context about the problem here.

@jkelleyrtp jkelleyrtp added the bug label Apr 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant