-
Notifications
You must be signed in to change notification settings - Fork 235
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
FFI callbacks and vtables #1818
Conversation
27ff2f8
to
cfdb66b
Compare
3b63929
to
da19f25
Compare
2803505
to
06801af
Compare
4be2eed
to
5308aa0
Compare
6610dfc
to
9f3a7d5
Compare
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 is a large patch, but it makes a lof of sense and makes callbacks more first-class instead of bolted on. I think you need a CHANGELOG entry for the maintainers of other bindings, and we should avoid landing this until 0.26 is (nearly?) out the door, but it looks great, thanks!
static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new(); | ||
struct #vtable_type { | ||
#(#vtable_fields)* | ||
uniffi_dec_ref: extern "C" fn(handle: u64), |
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.
maybe uniffi_drop
here too?
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.
dec_ref
takes the place of drop now.
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.
Oops, I thought you were suggesting to add a uniffi_drop
field, but now I realize you were probably just saying that the name doesn't make sense which is true. I'll rename this to uniffi_drop
.
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.
less "doesn't make sense" and more "would be more consistent and clearer" but yeah, it was all about the name.
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 went with uniffi_free
, which matches the name in the foreign code and uniffi_core
.
FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"), | ||
// Note: references are not really used by the Ruby bindings yet, maybe this should | ||
// change to a different type when they are actually implemented. | ||
FfiType::Reference(_) => ":pointer".to_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.
just unimplemented
like the above?
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.
Unfortunately, the tests fail if it's unimplemented and this is the easiest way to make them pass. I updated the docstring to explain the situation better.
835efe0
to
880cbcf
Compare
a9054a1
to
f9b3be4
Compare
This type can be used whenever we need to define a callback functions. Rather than adding the type by hand, which was quite annoying when I was doing the async work, we now can just add an item in `ffi_callback_definitions()`. This also can help avoid FFI type mismatches, like how the Kotlin signature used to an i16 instead of an i8 by accident. Use the new type for the future continuation callback. I have another plan for callback interface callbacks. I didn't touch the foreign executor callback, I hope to work on that very soon.
Instead of registering a single method to implement a callback interface, foreign code now registers a VTable. VTable sounds fancy, but it's just a repr(C) struct where each field is a callback function. This gives us some more flexibility with the method signatures. Before, all arguments were passed using a RustBuffer, but not all FFI types can be written to a RustBuffer. In particular, I want to be able to pass callback function pointers. This also makes the callback interface FFI closer to the Rust one. I wanted to make it match exactly, but it didn't work out. Unfortunately, we can't directly return the return value on Python because of an old ctypes bug (https://bugs.python.org/issue5710). Instead, input an out param for the return type. The other main possibility would be to change `RustBuffer` to be a simple `*mut u8` (mozilla#1779), which would then be returnable by Python. However, it seems bad to restrict ourselves from ever returning a struct in the future. Eventually, we want to stop using `RustBuffer` for all complex data types and that probably means using a struct instead in some cases. Renamed `CALL_PANIC` to `CALL_UNEXPECTED_ERROR` in the foreign bindings templates. This matches the name in the Rust code and makes more sense for foreign trait implementations. Removed the reexport-scaffolding-macro fixture. I don't think this one is giving us a lot of value anymore and I don't want to keep updating it when the FFI changes.
I think this one is ready now, officially re-requesting review. |
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.
🚢
@@ -304,8 +304,8 @@ open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name } | |||
} | |||
|
|||
{%- if obj.has_callback_interface() %} | |||
{%- let callback_handler_class = format!("UniffiCallbackInterface{}", name) %} | |||
{%- let callback_handler_obj = format!("uniffiCallbackInterface{}", name) %} | |||
{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} |
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 appears unused in this file, but is used in CallbackInterfaceTempl - maybe move the let there?
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 depends on obj
though, so I don't think I can move it. I think of this as ObjectTemplate.kt
passing arguments to CallbalkInterfaceImplTemplate.kt
This PR updates the callback interface code by adding new
FfiType
variants:FfiType::Callback
is a callback function.FfiType::Struct
is a repr(C) struct ofFfiType
fields.FfiType::Reference
is a pointer/reference to anotherFfiType
.FfiType::Callback
andFfiType::Struct
store a name that corresponds to a callback/struct definition that the foreign bindings render. IOW, we can now define callbacks/structs in theComponentInterface
, then refer to them by name as ffi types.These types are used to replace the callback interface code. Instead of having the foreign code register a
FfiType::ForeignCallback
, it now registers a reference to a vtable: a struct where each field is a callback that implements one of the callback interface methods, plus a field for theuniffi_free
method.The primary motivation for this is to prepare async callback methods. I think those methods should input an extra argument that's a callback to call when the async method completes. However, that presents a couple challenges to the current system:
FfiType
, which is a new thing that could get very messy. This code will allow us to handle the monomorphization in theComponentInterface
code and keep the bindings generators simple.RustBuffer
, but I don't want to read a function pointer out of that. For one, it feels like too much transmuting to me and I don't like making too many assumptions about function pointers. Secondly, it seems like it's mixing abstractions. Theread
operation should be used forType
values, not forFfiType
. VTables give us the flexibility to avoid this since we can just add a newFfiType
parameter for async methods only.I think this change will also improve our lives in general. It's currently pretty tedious to add a new callback type or refactor an existing one. I think this system will make that easier with a lot less fiddling around to get the signatures and FFI types correct for each language. Also, the concept of vtables opens up some interesting possibilities and if we had that before the initial async work it could have simplified some things.