diff --git a/Cargo.lock b/Cargo.lock index 5ce9de9b3a..ee30dd9d23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli", + "gimli 0.28.0", ] [[package]] @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.0.4" @@ -316,9 +328,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "blocking" @@ -618,6 +630,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "2.3.0" @@ -749,6 +767,17 @@ dependencies = [ "slab", ] +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "indexmap 1.9.3", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.28.0" @@ -790,6 +819,22 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -825,6 +870,12 @@ dependencies = [ "itoa", ] +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + [[package]] name = "idna" version = "0.4.0" @@ -835,6 +886,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -842,7 +903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", "serde", ] @@ -912,6 +973,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.172" @@ -1108,9 +1175,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -1193,7 +1260,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.9.4", "errno", "libc", "linux-raw-sys", @@ -1318,6 +1385,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1484,7 +1557,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", @@ -2057,7 +2130,7 @@ dependencies = [ "glob", "goblin", "heck", - "indexmap", + "indexmap 2.9.0", "once_cell", "serde", "tempfile", @@ -2068,6 +2141,7 @@ dependencies = [ "uniffi_pipeline", "uniffi_testing", "uniffi_udl", + "walrus", ] [[package]] @@ -2095,7 +2169,7 @@ name = "uniffi_internal_macros" version = "0.29.4" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.9.0", "proc-macro2", "quote", "syn", @@ -2133,7 +2207,7 @@ version = "0.29.4" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.9.0", "tempfile", "uniffi_internal_macros", ] @@ -2192,6 +2266,12 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "waker-fn" version = "1.1.0" @@ -2208,6 +2288,34 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "walrus" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6481311b98508f4bc2d0abbfa5d42172e7a54b4b24d8f15e28b0dc650be0c59f" +dependencies = [ + "anyhow", + "gimli 0.26.2", + "id-arena", + "leb128", + "log", + "walrus-macro", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "walrus-macro" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ad39ff894c43c9649fa724cdde9a6fc50b855d517ef071a93e5df82fe51d3" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2278,6 +2386,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.214.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff694f02a8d7a50b6922b197ae03883fbf18cdb2ae9fbee7b6148456f5f44041" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmparser" +version = "0.214.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5309c1090e3e84dad0d382f42064e9933fdaedb87e468cc239f0eabea73ddcb6" +dependencies = [ + "ahash", + "bitflags 2.9.4", + "hashbrown 0.14.5", + "indexmap 2.9.0", + "semver", + "serde", +] + [[package]] name = "web-sys" version = "0.3.64" @@ -2483,3 +2614,23 @@ checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/uniffi_bindgen/Cargo.toml b/uniffi_bindgen/Cargo.toml index dfa9a893b3..995be31225 100644 --- a/uniffi_bindgen/Cargo.toml +++ b/uniffi_bindgen/Cargo.toml @@ -38,6 +38,7 @@ uniffi_udl = { path = "../uniffi_udl", version = "=0.29.4" } # Don't include the `unicode-linebreak` or `unicode-width` since that functionality isn't needed for # docstrings. textwrap = { version = "0.16", features=["smawk"], default-features = false } +walrus = "0.23" [package.metadata.docs.rs] rustdoc-args = ["--generate-link-to-definition"] diff --git a/uniffi_bindgen/src/library_mode.rs b/uniffi_bindgen/src/library_mode.rs index 4b09b4aa9c..90090471c8 100644 --- a/uniffi_bindgen/src/library_mode.rs +++ b/uniffi_bindgen/src/library_mode.rs @@ -89,7 +89,7 @@ pub fn generate_bindings( // If `library_path` is a C dynamic library, return its name pub fn calc_cdylib_name(library_path: &Utf8Path) -> Option<&str> { - let cdylib_extensions = [".so", ".dll", ".dylib"]; + let cdylib_extensions = [".so", ".dll", ".dylib", ".wasm"]; let filename = library_path.file_name()?; let filename = filename.strip_prefix("lib").unwrap_or(filename); for ext in cdylib_extensions { diff --git a/uniffi_bindgen/src/macro_metadata/extract.rs b/uniffi_bindgen/src/macro_metadata/extract.rs index 98ba669b06..d6f44a7d44 100644 --- a/uniffi_bindgen/src/macro_metadata/extract.rs +++ b/uniffi_bindgen/src/macro_metadata/extract.rs @@ -14,6 +14,7 @@ use goblin::{ }; use std::collections::HashSet; use uniffi_meta::Metadata; +use walrus::{ir::Value, ConstExpr, DataKind, ExportItem, GlobalId, GlobalKind, Module}; /// Extract metadata written by the `uniffi::export` macro from a library file /// @@ -31,7 +32,10 @@ fn extract_from_bytes(file_data: &[u8]) -> anyhow::Result> { Object::Mach(mach) => extract_from_mach(mach, file_data), Object::Archive(archive) => extract_from_archive(archive, file_data), Object::COFF(coff) => extract_from_coff(coff, file_data), - _ => bail!("Unknown library format"), + _ => match Module::from_buffer(file_data) { + Ok(module) => extract_from_wasm(&module), + Err(_) => bail!("Unknown library format"), + }, } } @@ -232,6 +236,75 @@ pub fn extract_from_coff(coff: Coff<'_>, file_data: &[u8]) -> anyhow::Result anyhow::Result> { + fn get_global_positive_usize(module: &Module, global_id: GlobalId) -> Option { + let global = module.globals.get(global_id); + let GlobalKind::Local(const_expr) = &global.kind else { + return None; + }; + get_const_expr_positive_usize(module, const_expr) + } + + fn get_const_expr_positive_usize(module: &Module, const_expr: &ConstExpr) -> Option { + match const_expr { + ConstExpr::Value(value) => match value { + Value::I32(i32) => usize::try_from(*i32).ok(), + Value::I64(i64) => usize::try_from(*i64).ok(), + _ => None, + }, + ConstExpr::Global(id) => get_global_positive_usize(module, *id), + _ => None, + } + } + + let mut extracted = ExtractedItems::new(); + + for export in module.exports.iter() { + if !is_metadata_symbol(&export.name) { + continue; + } + let ExportItem::Global(global_id) = &export.item else { + continue; + }; + // The exported global contains a pointer value to the data in memory 0. + let Some(address) = get_global_positive_usize(module, *global_id) else { + continue; + }; + // An active data segment contains a memory ID (0 if not specified) and a byte array. + // When the WASM module is initialized, the designated range of the memory is initialized + // with this byte array data. + for data in module.data.iter() { + // A WASM module built with Rust usually stores global variable data in memory 0; + // i.e., given a Rust module, `memory` should be 0 here. + let DataKind::Active { + offset: data_offset, + // memory, + .. + } = &data.kind + else { + continue; + }; + let Some(data_offset) = get_const_expr_positive_usize(module, data_offset) else { + continue; + }; + if !(data_offset..data_offset + data.value.len()).contains(&address) { + continue; + } + // We have found the data segment that contains the UniFFI metadata. + extracted.extract_item( + &export.name, + &data.value, + address + .checked_sub(data_offset) + .context("Error getting global data offset")?, + )?; + break; + } + } + + Ok(extracted.into_metadata()) +} + /// Container for extracted metadata items #[derive(Default)] struct ExtractedItems {