-
Notifications
You must be signed in to change notification settings - Fork 242
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
Design and agree on a 1.0 FFI #2155
Comments
As mentioned in the description, this issue is a working document. The above represents my understanding of the issue after talking it over with a couple other UniFFI devs. If you disagree with anything or feel like something's missing, please add a comment. |
I agree that performance is something that should be optimized more before 1.0. Once we have a uniffi-bindgen-java version of our library that already exists natively in Java, we'll be benchmarking ourselves and may come up with ideas for improvements. Right now we have Rust, Python, and Kotlin benchmarks for that library but don't have a good comparison point to find improvements there. Naively it seems to me like 1.0 should have either one of proc-macros or UDL but not both. I think that will simplify usage and make it easier to come up with a cohesive set of documentation. Even though I'm implementing Java bindings, that's primarily translating Kotlin bindings, so I don't grok the bindgen internals well enough to give good improvement feedback there. I do think there's a lot to be gained in good documentation of what templates/code types need to be implemented and handled for bindings to support each feature UniFFI supports, but that's not really a maintenance improvement. |
Hey! Exciting with I will re-iterate up some things I wrote here, I think bindings can be improved quite a bit - also with multi-crate setup in mind. Instead of generating huge binding files - one per UniFFI crate (in a possible multi-UniFFI-crate-workspace repo) - also with multiple copies various UniFFI FfiConverters, which at least is the case for Swift, we could generate many small files. Where meant to be internal symbols have appropriate access modifiers ( For Swift specifically we ought to re-design with Swift 6 language mode and I think it is excellent timing to redesign the Swift bindings part of 1.0 of UniFFI to be prepared for Swift 6 and |
That can be done today, all the components are passed to the swift generator. All the rest sounds great too! |
@Sajjon for reference I found out today about JNR. I think @badboy had a branch already trying out JNA direct mapping for performance benefits. Looking at benchmarks it may be worth trying out JNR. This would only help Kotlin/Java performance, not a general library improvement. |
I've already written up some of what we'd like to have in this comment and this issue, but I'll expand a bit more here. We maintain a very large monorepo that contains code for multiple apps. Each app requires a particular set of features, but many of these features are shared between two or more apps. As such, we've split each feature into its own crates:
In order to keep compilation times to a minimum and avoid scaling issues, we run UniFFI bindgen on each library that exposes a UniFFI interface independently. This means that API crates, implementation crates, and type library crates will each invoke the UniFFI bindgen binary once, instead of a single invocation on the output shared library. This results in the following compilation graph:
Each library has its own two-step compilation process:
And many such two-step compilation processes can be parallelized when there is no directed edge between their corresponding libraries. This model also ensures that we can re-compile the foreign interface very fast, since API and types crates are guaranteed to only depend on each other, and not to include extraneous code that would otherwise increase the compilation type. As such, in most cases, iOS, Android, and Web engineers do not have to pay the cost of the implementation compilation times when working on features. However, in order to make this work, we've made a small patch to UniFFI bindgen to allow it to read from multiple rlibs directly. For instance, in order to generate the foreign bindings for API A, we need to include the rlibs of TYPES 1, TYPES 2, and API A. This will properly generate the foreign code for API A, but will also re-generate the foreign bindings of TYPES 1 and TYPES 2, which we'll already have generated and compiled upstream. Another issue is that all generated foreign bindings expect to be shipped as part of the same foreign library. For instance, in Swift, this means a single Swift library. As such, they all expect the other modules' symbols to already be imported in the current scope. However, in our case, these symbols need to be imported from the other Swift libraries: the foreign bindings for API A will need to So in order to make this work, we:
The drawbacks of this approach are:
Currently, our implementation is tailored to our needs and, as such, cuts many corners and breaks some features we don't use. We would love to see UniFFI support this natively. |
Just wanted to add here that we were able to make use of multiple rlibs by creating our own bindgen binary using lower level API from vanilla uniffi. Pretty sure that it might be possible to cache/import bindings generated upstream too, but, as @alexkirsz pointed out, code gen is fast enough for this not to be a concern. At least for now. |
Lately, we've been discussing the idea of UniFFI 1.0 and what it would entail. As part of this effort, we should think about how we could improve on our current FFI layer. There's basically 2 reasons for this:
General framework for making these decisions:
FFI-1.0
.The text was updated successfully, but these errors were encountered: