-
Notifications
You must be signed in to change notification settings - Fork 223
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
Generate Firefox Desktop front-end bindings with WebIDL #255
Conversation
uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl
Outdated
Show resolved
Hide resolved
/// Returns a dummy empty value for the given type. This is used to | ||
/// implement the `cpp::bail` macro to return early if a WebIDL function or | ||
/// method throws an exception. | ||
pub fn default_return_value_cpp(type_: &Type) -> Option<String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s unfortunate that this has to exist. Basically, if you want to throw an error from inside a WebIDL method with a signature like int32_t Foo(ErrorResult& aRv)
, you can call aRv.Throw(...)
in the method, but you still need to return some value—even though it’ll get ignored by the bindings code. Methods that return results via out params don’t need to do this; they can just do return;
and not set the param.
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl
Outdated
Show resolved
Hide resolved
@@ -0,0 +1,141 @@ | |||
add_task(async function test_rondpoint() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't believe this is real 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is add_task
? I can see it used in this file, but I can't see it anywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine the idea is to run these tests using mach xpcshell-test
somehow - no idea what process lina has in mind for having mach find them though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, this file is an xpcshell test!
no idea what process lina has in mind for having mach find them though
Haha, my workflow so far has been “symlink rondpoint
into m-c and add it to moz.build
with all the other stuff”, but that’s not going to fly for anything more than a demo. Still, if you want to take it for a spin locally, there are only 11 easy steps! 😆
It compiles in Gecko, but haven't tried running it or anything. There are probably memory safety bugs galore. But now it's time to relax, I'll fix these up on Friday.
* Remove XPIDL generation and wire up WebIDL. * Generate serializers for enums and dictionaries. * Generate proper C++ WebIDL implementations.
When building in Gecko, `cargo_metadata` panics in a macro. This is fixed in oli-obk/cargo_metadata#123 (and in Serde upstream), but pulling `uniffi` in as a path dependency in Gecko, and depending on `cargo_metadata` as a Git dependency, confuses Cargo. It doesn't update the vendored versions of Serde or other dependencies, then fails to build complaining it can't satisfy the newer versions that `uniffi` requests. In the interest of getting a demo working, I've just commented out the dependencies for now, and downgraded the other versions to match what's already vendored in m-c.
...But doesn't link, since I haven't figured out how to get `uniffi` vendored correctly. Progress, though, we're correctly generating WebIDL bindings to where they compile!
We can now generate (and compile!) the Rondpoint binding.
...And pass only the fields from the `ComponentInterface` that each template needs, not the full interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. A lot of boilerplate-y stuff, but that's unavoidable, and it's easy to read and well-commented. I have a few nits, mostly about improving comments.
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
struct Serializable { | ||
/// Returns the size of the serialized value, in bytes. This is used to | ||
/// calculate the allocation size for the Rust byte buffer. | ||
static CheckedInt<size_t> Size(const T& aValue) = delete; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the return type CheckedInt
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was using CheckedInt
throughout to avoid accidental overflows when serializing, but with the RustBuffer
changes on main
now, Size
isn't necessary anymore. I think we can remove CheckedInt
from the other uses, too, since RustBuffer
s can only hold an int32_t
worth of bytes.
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl
Outdated
Show resolved
Hide resolved
This makes it hard to symlink UniFFI into the Firefox tree, but once this PR is merged, we shouldn't need that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really good.
I mean, I kinda glazed over a bit at the intricacies of the C++ code because I trust that more experienced folks are looked at it, but I'm confident I could use the guideposts here to dig in more to the details if needed!
I note that the generated bindings occupy a kind of global namespace, e.g. we get a class mozilla::dom::Optionnuer
in a header file conditioned on #ifndef mozilla_dom_Optionneur
. Are we able to add the uniffi package name as a level here, to give e.g. mozilla::dom::Rondpointer::Optionnuer
and similar? I'm concerned about accidental name conflicts in future, but I'm also cautious about potential limitations of the way Firefox generates the bindings.
Similarly, if we had multiple uniffi libraries being build together in Firefox, would they run into trouble by both declaring RustBuffer
structs etc? I think maybe it's OK because the RondpointShared.h
file is only included by its .cpp
files, not by any other header files. But I'm no expert, so kind of just looking for explicit re-assurance here :-)
A couple of other broad nits I noticed:
- When running
cargo test
in this branch, I get quite a few warnings about unused symbols which it's probably worth cleaning up. - Running
uniffi-bindgen
via thecargo run --
method you describe in the PR works, but if I try to build and run the executable via./target/debug/uniffi-bindgen
using the same arguments, it produces an error'Missing $CARGO_MANIFEST_DIR, cannot load config file: NotPresent'
. We should expect that some consumers will use an externaluniffi-bindgen
rather than building and running it from within this repo.
uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h
Outdated
Show resolved
Hide resolved
uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl
Outdated
Show resolved
Hide resolved
Thank you so much for digging into this, Ryan, that's mainly what I wanted from your review! 🚀
I don't think so 😞 I covered this in the Theory of Operation—which would be nice to convert into Markdown and check in to the repo, adding that to my list for this week ☑️ —but the binding declarations generated by Firefox's
Yes, they would, because they're declared in the |
I have perhaps too much faith in Sufficiently Smart Compilers, but hopefully these duplicated struct definitions would not result in duplicate code in the compiled artifacts, would they? |
Hrm. I can see this being a problem (and worse, one that catches us by surprise). If you think there's scope for adding some sort of unique-ifying prefix to these names, it's probably worth putting the infra for that in place sooner rather than later. |
Good question—my knowledge of how C++ compilers inline templates, functions, and types is nonexistent 😭 Just like you suggested profiling the size impact of vendoring Nimbus and the bindings, it would be worth measuring the impact of vendoring multiple UniFFI components. The one thing I'm worried about that might make inlining difficult is that the serialization helpers call into component-specific allocation functions. In |
Good idea. Let's do that now, so that this doesn't sneak up on us later. |
Fix #301 cargo tests by updating and reenabling cargo_metadata
Finish the fix for #301
There are two pieces of information that we want to pass along to filters: * The `definition_prefix` config option, used to prefix the generated namespace, interface, enum, and dictionary names. * The `ffi_namespace`, used to generate the names for the `RustBuffer`, `RustError`, and `ForeignBytes` structs. Threading these arguments through Askama is tricky. For example, if the `type_ffi` filter needs to know the `ffi_namespace`, that infects any filters that call it (like `lift_cpp` and `lower_cpp`), all the way up the call chain. The same happens for the `definition_prefix`, which changes the names of the types declared in the WebIDL...so now all type declaration filters need to know about the prefix. Making this work in the generated code is difficult, so we take the least-worst possible option of passing around a `Context` argument (other, less-generic names welcome!) with all the names we need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Woohoo, thanks so much everyone! 😅 The latest push has the I'll go ahead and mash the "merge" button now and we can file follow-ups for anything we forgot! |
This PR is a first cut at a UniFFI backend for Firefox! It's a work in progress, but can generate working bindings now, so I wanted to get it up for review. I'd love your comments! 💜
The backend works a little differently than the others. Firefox expects one header file per interface (and a separate one for the namespace that holds top-level functions), so UniFFI emits multiple
.h
and.cpp
files. There's also complexity around handling different types. Some are reflected differently depending on whether they're arguments, type parameters, or local variables; some are returned directly and others are passed as out parameters; and the WebIDL implementation signatures have extra leading and trailing arguments. All this makesmacros.cpp
pretty gnarly 🙀SharedHeaderTemplate.h
contains theRustBuffer
helpers and serialization logic. The ideas are the same as the Swift, Kotlin, and Python backends, but the implementation is a pile of C++ code. I'll add some more comments throughout it in the coming days, so that it's a bit easier to digest. This part can also be reused wholesale for our pure-C++ Nimbus interface, there's not much that's WebIDL specific here.Getting this building
The setup for this is a little janky for now, but here's how to get this running! You'll need a full (not artifact) build of Firefox, since this touches lots of C++ and Rust code.
git checkout gecko
to switch to the Gecko branch.mkdir rondpoint-gecko
cd uniffi_bindgen; and cargo run -- generate ../examples/rondpoint/src/rondpoint.idl -l gecko -o ../rondpoint-gecko
dom/chrome-webidl
in m-c:ln -s /uniffi-rs/rondpoint-gecko/Rondpoint.webidl /gecko/dom/chrome-webidl/Rondpoint.webidl
Rondpoint.webidl
todom/chrome-webidl/moz.build
, in theWEBIDL_FILES
section.rondpoint-gecko
folder from step (2) into an existing (or new!) directory in m-c that has amoz.build
file. Any one will do; I pickedtoolkit/components/extensions/storage
for no other reason than it's a pretty small directory and makes it easy to do directory-specific builds.uniffi-rs
into m-c.uniffi-example-rondpoint
as a dependency totoolkit/library/rust/shared/Cargo.toml
, as apath
dependency inside the symlink from step (7).extern crate uniffi_rondpoint
totoolkit/library/rust/shared/lib.rs
.mach vendor rust
.moz.build
in the folder from step (6), like this:And then run
mach build
, and you should be good to go! You can runmach xpcshell-test toolkit/components/extensions/storage/uniffi-rs/examples/rondpoint/tests/gecko/xpcshell.ini
after the build is done! 😊Hacks
I had to make two changes to the
Cargo.toml
s in UniFFI to get this building in Firefox, which we'll want to fix before merging:Cargo.toml
, so that I could symlinkuniffi-rs
into my m-c tree and adduniffi-example-rondpoint
as apath
dependency. I'm not sure this is necessary; if we can avoid symlinkinguniffi-rs
into m-c, we can do without.cargo_metadata
dependency. When I tried building Firefox with the version ofcargo_metadata
from Crates.io, I got a panic in one of itsderive
macros. Looks lik that was fixed in bump serde dependency to 1.0.107 oli-obk/cargo_metadata#123 by bumping the Serde version, but that hasn't been published yet, somach vendor rust
doesn't know to pull the new version of Serde in. But changingcargo_metadata
to a Git dependency confused Cargo:mach vendor rust
didn't vendor any of UniFFI's dependencies, and thenmach build
complained that it couldn't resolve the versions properly. I'm not sure what's going on there, and didn't dig too much more into it.I also had to change the
crate-type
to["cdylib", "lib"]
, so that we could link the crate intogkrust
. If it's justcdylib
,extern crate
won't find it.