Skip to content

Commit 88bc12c

Browse files
authored
fix: handle wbg_cast functions by rewriting patch module (#5026)
* fix: handle wbg_cast functions by rewriting patch module * clippy * dont use workspace deps
1 parent 1d2dec1 commit 88bc12c

File tree

4 files changed

+54
-7
lines changed

4 files changed

+54
-7
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ futures = "0.3.31"
249249
futures-channel = "0.3.31"
250250
futures-util = { version = "0.3", default-features = false }
251251
rustc-hash = "2.1.1"
252-
wasm-bindgen = "0.2.100"
252+
wasm-bindgen = "0.2.105"
253253
wasm-bindgen-futures = "0.4.50"
254254
js-sys = "0.3"
255255
web-sys = { version = "0.3.77", default-features = false }

packages/cli/src/build/patch.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu
468468
// `relocation-model=pic` synthesizes can reference the functions via the indirect function table
469469
// even if they are not normally synthesized in regular wasm code generation.
470470
//
471-
// Normally, the dynaic linker setup would resolve GOT.func against the same GOT.func export in
471+
// Normally, the dynamic linker setup would resolve GOT.func against the same GOT.func export in
472472
// the main module, but we don't have that. Instead, we simply re-parse the main module, aggregate
473473
// its ifunc table, and then resolve directly to the index in that table.
474474
for (import_id, ifunc_index) in got_funcs {
@@ -584,19 +584,23 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu
584584

585585
if let Some(table_idx) = name_to_ifunc_old.get(import.name.as_str()) {
586586
new.imports.delete(env_func_import);
587-
convert_import_to_ifunc_call(
587+
convert_func_to_ifunc_call(
588588
&mut new,
589589
ifunc_table_initializer,
590590
func_id,
591591
*table_idx,
592592
name.clone(),
593593
);
594+
continue;
594595
}
595596

596597
if name_is_bindgen_symbol(&name) {
597598
new.imports.delete(env_func_import);
598-
convert_import_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name);
599+
convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name);
600+
continue;
599601
}
602+
603+
tracing::warn!("[hotpatching]: Symbol slipped through the cracks: {}", name);
600604
}
601605

602606
// Wire up the preserved intrinsic functions that we saved before running wasm-bindgen to the expected
@@ -613,7 +617,34 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu
613617
if name_is_bindgen_symbol(&import.name) {
614618
let name = import.name.as_str().to_string();
615619
new.imports.delete(import_id);
616-
convert_import_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name);
620+
convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, 0, name);
621+
}
622+
}
623+
624+
// Rewrite the wbg_cast functions to call the indirect functions from the original module.
625+
// This is necessary because wasm-bindgen uses these calls to perform dynamic type casting through
626+
// the JS layer. If we don't rewrite these, they end up as calls to `breaks_if_inlined` functions
627+
// which are no-ops and get rewritten by the wbindgen post-processing step.
628+
//
629+
// Here, we find the corresponding wbg_cast function in the old module by name and then rewrite
630+
// the patch module's cast function to call the indirect function from the original module.
631+
//
632+
// See the wbg_cast implementation in wasm-bindgen for more details:
633+
// <https://github.com/wasm-bindgen/wasm-bindgen/blob/f61a588f674304964a2062b2307edb304aed4d16/src/rt/mod.rs#L30>
634+
let new_func_ids = new.funcs.iter().map(|f| f.id()).collect::<Vec<_>>();
635+
for func_id in new_func_ids {
636+
let Some(name) = new.funcs.get(func_id).name.as_deref() else {
637+
continue;
638+
};
639+
640+
if name.contains("wasm_bindgen4__rt8wbg_cast") && !name.contains("breaks_if_inline") {
641+
let name = name.to_string();
642+
let old_idx = name_to_ifunc_old
643+
.get(&name)
644+
.copied()
645+
.ok_or_else(|| anyhow::anyhow!("Could not find matching wbg_cast function for [{name}] - must generate new JS bindings."))?;
646+
647+
convert_func_to_ifunc_call(&mut new, ifunc_table_initializer, func_id, old_idx, name);
617648
}
618649
}
619650

@@ -642,8 +673,18 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu
642673
let ifunc_count = name_to_ifunc_new.len() as u64;
643674
let mut map = AddressMap::default();
644675
for (name, idx) in name_to_ifunc_new.iter() {
676+
// Find the corresponding ifunc in the old module by name
645677
if let Some(old_idx) = name_to_ifunc_old.get(*name) {
646678
map.insert(*old_idx as u64, *idx as u64);
679+
continue;
680+
}
681+
682+
// Warn if there might be a null entry
683+
if !name.contains("breaks_if_inlined") {
684+
tracing::warn!(
685+
"[hotpatching]: Failed to find ifunc entry in old module for function: {}",
686+
name
687+
);
647688
}
648689
}
649690

@@ -656,7 +697,7 @@ pub fn create_wasm_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resu
656697
})
657698
}
658699

659-
fn convert_import_to_ifunc_call(
700+
fn convert_func_to_ifunc_call(
660701
new: &mut Module,
661702
ifunc_table_initializer: TableId,
662703
func_id: FunctionId,
@@ -1309,7 +1350,6 @@ fn name_is_bindgen_symbol(name: &str) -> bool {
13091350
|| name.contains("wasm_bindgen..describe..WasmDescribe")
13101351
|| name.contains("wasm_bindgen..closure..WasmClosure$GT$8describe")
13111352
|| name.contains("wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe")
1312-
|| name.contains("wasm_bindgen4__rt8wbg_")
13131353
}
13141354

13151355
/// Manually parse the data section from a wasm module

packages/playwright-tests/web-hot-patch/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ publish = false
88

99
[dependencies]
1010
dioxus = { path = "../../dioxus", features = ["web"] }
11+
wasm-bindgen = { version = "0.2.100" }
12+
web-sys = { version = "*", features = ["MouseEvent"] }
1113

1214
[workspace]

packages/playwright-tests/web-hot-patch/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ const IMAGE: Asset = asset!("/assets/toasts.png");
66
fn app() -> Element {
77
let mut num = use_signal(|| 0);
88

9+
// make sure to emit funky closure code in this module to test wasm-bindgen handling
10+
let _closures = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::MouseEvent)>::new(
11+
move |event: web_sys::MouseEvent| {},
12+
);
13+
914
rsx! {
1015
document::Link {
1116
href: CSS,

0 commit comments

Comments
 (0)