Skip to content

Conversation

paxbun
Copy link
Contributor

@paxbun paxbun commented Sep 6, 2025

We're trying to add UniFFI WASM support without wasm-bindgen in the next version of Gobley. This PR adds support for extracting metadata from WASM files using the walrus crate of wasm-bindgen, so the next version of UniFFI can generate bindings without building other kinds of cdylib files.

Tested with the following procedure:

  1. Add "wasm-unstable-single-threaded" to the UniFFI dependency of fixtures/coverall/Cargo.toml.
diff --git a/fixtures/coverall/Cargo.toml b/fixtures/coverall/Cargo.toml
index 22fd4beea..cf1719107 100644
--- a/fixtures/coverall/Cargo.toml
+++ b/fixtures/coverall/Cargo.toml
@@ -11,7 +11,10 @@ name = "uniffi_coverall"
 
 [dependencies]
 # Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
-uniffi = { workspace = true, features=["scaffolding-ffi-buffer-fns"]}
+uniffi = { workspace = true, features = [
+    "scaffolding-ffi-buffer-fns",
+    "wasm-unstable-single-threaded",
+] }
 once_cell = "1.12"
 thiserror = "1.0"
  1. Run the following commands:
# Build a cdylib for the host target and generate bindings in ./out
# Replace aarch64-apple-darwin with the host target
cargo build --target aarch64-apple-darwin -p uniffi-fixture-coverall 
cargo run -p uniffi --features=cli -- generate -l kotlin --out-dir out --library --no-format ./target/aarch64-apple-darwin/debug/libuniffi_coverall.dylib

# Build a WASM module and generate bindings in ./out-wasm
cargo build --target wasm32-unknown-unknown -p uniffi-fixture-coverall 
cargo run -p uniffi --features=cli -- generate -l kotlin --out-dir out-wasm --library --no-format ./target/wasm32-unknown-unknown/debug/uniffi_coverall.wasm
  1. Compare the contents.
diff -r out out-wasm
# exit code: 0

@paxbun paxbun requested a review from a team as a code owner September 6, 2025 07:47
@paxbun paxbun requested review from gruberb and removed request for a team September 6, 2025 07:47
@paxbun
Copy link
Contributor Author

paxbun commented Sep 6, 2025

We have a WASM example here that demonstrates WASM transformations required to implement the FFI logic of UniFFI: https://github.com/gobley/gobley/blob/536fd52d1209c1ab09da54187db2484297e071b4/tests/gradle/js-only/src/jsMain/kotlin/RustLibrary.kt

@mhammond
Copy link
Member

mhammond commented Sep 7, 2025

I support the idea, but this new dependency is likely to be a problem for us - even without the new crate duplicates this introduces. I wonder if we can work out how to sanely push this out to the consumer (ie, so crates can reasonably have their own uniffi_bindgen with this capability)?

@mhammond
Copy link
Member

mhammond commented Sep 8, 2025

this is somewhat similar to https://github.com/jrmuizel/uniffi-rust-parse (unpolished, helped by cursor via an unrelated proejct), which is an experiment using Rust Analyzer to create metadata and avoid using a .so - but it's a similarly shaped problem. All metadata collection should maybe be shifted? Not sure how or really what I mean though :)

@bendk
Copy link
Contributor

bendk commented Sep 8, 2025

I support the idea, but this new dependency is likely to be a problem for us - even without the new crate duplicates this introduces. I wonder if we can work out how to sanely push this out to the consumer (ie, so crates can reasonably have their own uniffi_bindgen with this capability)?

My general feeling is that external bindings might want to move away from functions like generate_bindings() that input a BindingGenerator impl and drive the entire process with it. Instead, they can roll their own top-level function to does the driving. See uniffi-bindgen-gecko-js for an example. That one uses the new pipeline system, but I think it should also work with a bindings generate based on ComponentInterface too. Maybe we'd need to define and/or expose some utility functions to help out, which I'd be happy to help with.

One advantage of that is that it's easy to alter the process. You could replace the call to macro_metadata::extract_from_library(), with your own call that extracts the data from wasm modules. I also think that it's simpler to think about.

Would something like that work for Gobley?

bendk added a commit to bendk/uniffi-rs that referenced this pull request Sep 8, 2025
In mozilla#2640 I speculated that
maybe external bindings would want to roll their own metadata-extraction
functions.  I'm not sure if it'll work for that case, but it seems like
a good thing in general.

Making these functions `pub` is a step in that direction.  I could see
external bindings code try their specialized `extract_from_foo` function
first, then fallback to calling `extract_from_bytes` to see if one of
the standard cases works.
@paxbun
Copy link
Contributor Author

paxbun commented Sep 8, 2025

@bendk Thanks for the comment! For now, we only need to alter the metadata parsing logic, so a helper function similar to uniffi_bindgen::library_mode::generate_bindings with an option to customize that would be helpful. Since we have our own cdylib name setting logic and don't check the file name of the library file as in uniffi_bindgen::gen_library_mode, customizing calc_cdylib_name might not be needed.

Speaking of customizing the entire process that extracts metadata and builds CIs, I'm investigating name obfuscation features (for Android/WASM, using llvm-objcopy/walrus), so I expect we'll eventually end up with maintaining our own CI-building logic in the future when we're ready to implement those features.

@bendk
Copy link
Contributor

bendk commented Sep 8, 2025

@bendk Thanks for the comment! For now, we only need to alter the metadata parsing logic, so a helper function similar to uniffi_bindgen::library_mode::generate_bindings with an option to customize that would be helpful.

I think that would be possible, we could add something like specialized_extract_metadata(file_data: &[u8]) -> anyhow::Result<Option<Vec<Metadata>>>. If that returns Ok(None) that means the bindings weren't able to do specialized metadata extraction and we should fall back to using extract_from_bytes() like normal. However, my general feeling is that's not so simple to understand and the whole inversion-of-control pattern was a mistake there.

If you had to write and maintain your own generate_bindings() function how hard would it be? Probably we'd add some new utility functions so you wouldn't need to maintain a blocks of code like this. What's left would a function that you own that makes calls into our functions, rather than the other way around. I feel like it would be simpler that way, but I'd love to hear your perspective on that.

@paxbun
Copy link
Contributor Author

paxbun commented Sep 9, 2025

It would be trivial if the code range that needs rewriting and and maintaining is confined to uniffi_bindgen/src/library_mode.rs. Would I need to maintain our own uniffi_bindgen/src/lib.rs (for uniffi_bindgen::generate_external_bindings for "non-library" mode)? Even in that case, I expect it will take 3-4 hours at most to make external bindgens reflect the changes in this repo. I've just had a quick check of visibilities of entities used in these two files, and it seems we can start maintaing our own one even now.

bendk added a commit that referenced this pull request Sep 9, 2025
In #2640 I speculated that
maybe external bindings would want to roll their own metadata-extraction
functions.  I'm not sure if it'll work for that case, but it seems like
a good thing in general.

Making these functions `pub` is a step in that direction.  I could see
external bindings code try their specialized `extract_from_foo` function
first, then fallback to calling `extract_from_bytes` to see if one of
the standard cases works.
@bendk
Copy link
Contributor

bendk commented Sep 9, 2025

It would be trivial if the code range that needs rewriting and and maintaining is confined to uniffi_bindgen/src/library_mode.rs. Would I need to maintain our own uniffi_bindgen/src/lib.rs (for uniffi_bindgen::generate_external_bindings for "non-library" mode)? Even in that case, I expect it will take 3-4 hours at most to make external bindgens reflect the changes in this repo. I've just had a quick check of visibilities of entities used in these two files, and it seems we can start maintaing our own one even now.

I don't think it should be hard and I also remembered that we have a uniffi-bindgen-swift that uses this strategy. I think we could update it to handle non-library mode by changing this to an if with a second branch that uses parse_udl. I'm going to try to update that code to support non-library mode and see how hard it is.

bendk added a commit to bendk/uniffi-rs that referenced this pull request Sep 10, 2025
This is a new way for binding generators to load the CIs, Configs,
metadata, etc.  I'm hoping that this can replace the `generate_bindings`
and `generate_external_bindings` functions, as well some other functions
like `library_mode::find_components`.

The goal for the new design is to let bindings generators drive the
process instead of a function like `generate_external_bindings`.  The
advantage of this is that it's easier to customize and we don't need to
keep adding new hook methods.  It also feels simpler overall to me.  See
mozilla#2640 for a discussion of this.

If we adopt this new system, then I think we can use it to remove a
bunch of duplicate code.  We can keep around the old functions for
backwards-compatibility, but I think they can just be wrappers around
`BindgenLoader`.  Also, we should figure out a nice way to hook this up
to the pipeline code.

Made `uniffi-bindgen-swift` use the new system.  This was mostly to test
the code and to serve as an example, but a nice side-benefit is that
`uniffi-bindgen-swift` now can handle UDL files.  I also added a
`uniffi-bindgen-swift` binary file to go with the `uniffi-bindgen`
binary.
@bendk bendk mentioned this pull request Sep 10, 2025
bendk added a commit to bendk/uniffi-rs that referenced this pull request Sep 10, 2025
This is a new way for binding generators to load the CIs, Configs,
metadata, etc.  I'm hoping that this can replace the `generate_bindings`
and `generate_external_bindings` functions, as well some other functions
like `library_mode::find_components`.

The goal for the new design is to let bindings generators drive the
process instead of a function like `generate_external_bindings`.  The
advantage of this is that it's easier to customize and we don't need to
keep adding new hook methods.  It also feels simpler overall to me.  See
mozilla#2640 for a discussion of this.

If we adopt this new system, then I think we can use it to remove a
bunch of duplicate code.  We can keep around the old functions for
backwards-compatibility, but I think they can just be wrappers around
`BindgenLoader`.  Also, we should figure out a nice way to hook this up
to the pipeline code.

Made `uniffi-bindgen-swift` use the new system.  This was mostly to test
the code and to serve as an example, but a nice side-benefit is that
`uniffi-bindgen-swift` now can handle UDL files.  I also added a
`uniffi-bindgen-swift` binary file to go with the `uniffi-bindgen`
binary.
bendk added a commit to bendk/uniffi-rs that referenced this pull request Sep 10, 2025
This is a new way for binding generators to load the CIs, Configs,
metadata, etc.  I'm hoping that this can replace the `generate_bindings`
and `generate_external_bindings` functions, as well some other functions
like `library_mode::find_components`.

The goal for the new design is to let bindings generators drive the
process instead of a function like `generate_external_bindings`.  The
advantage of this is that it's easier to customize and we don't need to
keep adding new hook methods.  It also feels simpler overall to me.  See
mozilla#2640 for a discussion of this.

If we adopt this new system, then I think we can use it to remove a
bunch of duplicate code.  We can keep around the old functions for
backwards-compatibility, but I think they can just be wrappers around
`BindgenLoader`.  Also, we should figure out a nice way to hook this up
to the pipeline code.

Made `uniffi-bindgen-swift` use the new system.  This was mostly to test
the code and to serve as an example, but a nice side-benefit is that
`uniffi-bindgen-swift` now can handle UDL files.  I also added a
`uniffi-bindgen-swift` binary file to go with the `uniffi-bindgen`
binary.
bendk added a commit to bendk/uniffi-rs that referenced this pull request Sep 10, 2025
This is a new way for binding generators to load the CIs, Configs,
metadata, etc.  I'm hoping that this can replace the `generate_bindings`
and `generate_external_bindings` functions, as well some other functions
like `library_mode::find_components`.

The goal for the new design is to let bindings generators drive the
process instead of a function like `generate_external_bindings`.  The
advantage of this is that it's easier to customize and we don't need to
keep adding new hook methods.  It also feels simpler overall to me.  See
mozilla#2640 for a discussion of this.

If we adopt this new system, then I think we can use it to remove a
bunch of duplicate code.  We can keep around the old functions for
backwards-compatibility, but I think they can just be wrappers around
`BindgenLoader`.  Also, we should figure out a nice way to hook this up
to the pipeline code.

Made `uniffi-bindgen-swift` use the new system.  This was mostly to test
the code and to serve as an example, but a nice side-benefit is that
`uniffi-bindgen-swift` now can handle UDL files.  I also added a
`uniffi-bindgen-swift` binary file to go with the `uniffi-bindgen`
binary.
bendk added a commit to bendk/uniffi-rs that referenced this pull request Sep 10, 2025
This is a new way for binding generators to load the CIs, Configs,
metadata, etc.  I'm hoping that this can replace the `generate_bindings`
and `generate_external_bindings` functions, as well some other functions
like `library_mode::find_components`.

The goal for the new design is to let bindings generators drive the
process instead of a function like `generate_external_bindings`.  The
advantage of this is that it's easier to customize and we don't need to
keep adding new hook methods.  It also feels simpler overall to me.  See
mozilla#2640 for a discussion of this.

If we adopt this new system, then I think we can use it to remove a
bunch of duplicate code.  We can keep around the old functions for
backwards-compatibility, but I think they can just be wrappers around
`BindgenLoader`.  Also, we should figure out a nice way to hook this up
to the pipeline code.

Made `uniffi-bindgen-swift` use the new system.  This was mostly to test
the code and to serve as an example, but a nice side-benefit is that
`uniffi-bindgen-swift` now can handle UDL files.  I also added a
`uniffi-bindgen-swift` binary file to go with the `uniffi-bindgen`
binary.
bendk added a commit that referenced this pull request Sep 16, 2025
This is a new way for binding generators to load the CIs, Configs,
metadata, etc.  I'm hoping that this can replace the `generate_bindings`
and `generate_external_bindings` functions, as well some other functions
like `library_mode::find_components`.

The goal for the new design is to let bindings generators drive the
process instead of a function like `generate_external_bindings`.  The
advantage of this is that it's easier to customize and we don't need to
keep adding new hook methods.  It also feels simpler overall to me.  See
#2640 for a discussion of this.

If we adopt this new system, then I think we can use it to remove a
bunch of duplicate code.  We can keep around the old functions for
backwards-compatibility, but I think they can just be wrappers around
`BindgenLoader`.  Also, we should figure out a nice way to hook this up
to the pipeline code.

Made `uniffi-bindgen-swift` use the new system.  This was mostly to test
the code and to serve as an example, but a nice side-benefit is that
`uniffi-bindgen-swift` now can handle UDL files.  I also added a
`uniffi-bindgen-swift` binary file to go with the `uniffi-bindgen`
binary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants