Skip to content

Commit

Permalink
Adding FfiType::Callback
Browse files Browse the repository at this point in the history
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 I made the Kotlin
signature input a i16 instead of an i8.

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.
  • Loading branch information
bendk committed Dec 12, 2023
1 parent 68b14c9 commit 6786d35
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 40 deletions.
24 changes: 16 additions & 8 deletions uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,19 @@ impl KotlinCodeOracle {
nm.to_string().to_shouty_snake_case()
}

fn ffi_type_label_by_value(ffi_type: &FfiType) -> String {
/// Get the idiomatic Python rendering of an FFI callback function
fn ffi_callback_name(&self, nm: &str) -> String {
format!("Uniffi{}", nm.to_upper_camel_case())
}

fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)),
_ => Self::ffi_type_label(ffi_type),
FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)),
_ => self.ffi_type_label(ffi_type),
}
}

fn ffi_type_label(ffi_type: &FfiType) -> String {
fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
// Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not
// support them yet. Thus, we use the signed variants to represent both signed and unsigned
Expand All @@ -331,11 +336,9 @@ impl KotlinCodeOracle {
format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
}
FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
FfiType::Callback(name) => self.ffi_callback_name(name),
FfiType::ForeignCallback => "ForeignCallback".to_string(),
FfiType::RustFutureHandle => "Pointer".to_string(),
FfiType::RustFutureContinuationCallback => {
"UniFffiRustFutureContinuationCallbackType".to_string()
}
FfiType::RustFutureContinuationData => "USize".to_string(),
}
}
Expand Down Expand Up @@ -472,7 +475,7 @@ mod filters {
}

pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle::ffi_type_label_by_value(type_))
Ok(KotlinCodeOracle.ffi_type_label_by_value(type_))
}

/// Get the idiomatic Kotlin rendering of a function name.
Expand Down Expand Up @@ -500,6 +503,11 @@ mod filters {
Ok(KotlinCodeOracle.convert_error_suffix(&name))
}

/// Get the idiomatic Kotlin rendering of an FFI callback function name
pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle.ffi_callback_name(nm))
}

pub fn object_names(
obj: &Object,
ci: &ComponentInterface,
Expand Down
16 changes: 8 additions & 8 deletions uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
// Async return type handlers

internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort()
internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort()
internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()

internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>()
internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Byte>>()

// FFI type for Rust future continuations
internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType {
override fun callback(continuationHandle: USize, pollResult: Short) {
uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult)
internal object uniffiRustFutureContinuationCallback: UniffiRustFutureContinuationCallback {
override fun callback(data: USize, pollResult: Byte) {
uniffiContinuationHandleMap.remove(data)?.resume(pollResult)
}
}

internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
rustFuture: Pointer,
pollFunc: (Pointer, UniFffiRustFutureContinuationCallbackType, USize) -> Unit,
pollFunc: (Pointer, UniffiRustFutureContinuationCallback, USize) -> Unit,
completeFunc: (Pointer, RustCallStatus) -> F,
freeFunc: (Pointer) -> Unit,
liftFunc: (F) -> T,
errorHandler: CallStatusErrorHandler<E>
): T {
try {
do {
val pollResult = suspendCancellableCoroutine<Short> { continuation ->
val pollResult = suspendCancellableCoroutine<Byte> { continuation ->
pollFunc(
rustFuture,
uniffiRustFutureContinuationCallback,
Expand Down
5 changes: 0 additions & 5 deletions uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,3 @@ internal class UniFfiHandleMap<T: Any> {
return map.remove(handle)
}
}

// FFI type for Rust future continuations
internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback {
fun callback(continuationHandle: USize, pollResult: Short);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ private inline fun <reified Lib : Library> loadIndirect(
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
}

// Define FFI callback types
{%- for callback in ci.ffi_callback_definitions() %}
internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback {
fun callback(
{%- for arg in callback.arguments() -%}
{{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
{%- endfor -%}
)
{%- match callback.return_type() %}
{%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }},
{%- when None %}
{%- endmatch %}
}

{%- endfor %}

// A JNA Library to expose the extern-C FFI definitions.
// This is an implementation detail which will be called internally by the public API.

Expand Down
16 changes: 13 additions & 3 deletions uniffi_bindgen/src/bindings/python/gen_python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,12 @@ impl PythonCodeOracle {
fixup_keyword(nm.to_string().to_shouty_snake_case())
}

fn ffi_type_label(ffi_type: &FfiType) -> String {
/// Get the idiomatic Python rendering of an FFI callback function
fn ffi_callback_name(&self, nm: &str) -> String {
format!("UNIFFI_{}", nm.to_shouty_snake_case())
}

fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::Int8 => "ctypes.c_int8".to_string(),
FfiType::UInt8 => "ctypes.c_uint8".to_string(),
Expand All @@ -355,10 +360,10 @@ impl PythonCodeOracle {
None => "_UniffiRustBuffer".to_string(),
},
FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(),
FfiType::Callback(name) => self.ffi_callback_name(name),
FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(),
// Pointer to an `asyncio.EventLoop` instance
FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
}
}
Expand Down Expand Up @@ -482,7 +487,7 @@ pub mod filters {
}

pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
Ok(PythonCodeOracle::ffi_type_label(type_))
Ok(PythonCodeOracle.ffi_type_label(type_))
}

/// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
Expand All @@ -505,6 +510,11 @@ pub mod filters {
Ok(PythonCodeOracle.enum_variant_name(nm))
}

/// Get the idiomatic Python rendering of a FFI callback function name
pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.ffi_callback_name(nm))
}

/// Get the idiomatic Python rendering of an individual enum variant.
pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> {
Ok(PythonCodeOracle.object_names(obj))
Expand Down
2 changes: 1 addition & 1 deletion uniffi_bindgen/src/bindings/python/templates/Async.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# Continuation callback for async functions
# lift the return value or error and resolve the future, causing the async function to resume.
@_UNIFFI_FUTURE_CONTINUATION_T
@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK
def _uniffi_continuation_callback(future_ptr, poll_code):
(eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr)
eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code)
Expand Down
3 changes: 0 additions & 3 deletions uniffi_bindgen/src/bindings/python/templates/Helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,3 @@ def _uniffi_check_call_status(error_ffi_converter, call_status):
# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int`
_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer))

# UniFFI future continuation
_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8)

Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ def _uniffi_check_api_checksums(lib):
pass
{%- endfor %}


# Define FFI callback types
{%- for callback in ci.ffi_callback_definitions() %}
{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE(
{%- match callback.return_type() %}
{%- when Some(return_type) %}{{ return_type|ffi_type_name }},
{%- when None %}None,
{%- endmatch %}
{%- for arg in callback.arguments() -%}
{{ arg.type_().borrow()|ffi_type_name }},
{%- endfor -%}
)
{%- endfor %}

# A ctypes library to expose the extern-C FFI definitions.
# This is an implementation detail which will be called internally by the public API.

Expand Down
5 changes: 2 additions & 3 deletions uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,11 @@ mod filters {
FfiType::RustArcPtr(_) => ":pointer".to_string(),
FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
FfiType::ForeignBytes => "ForeignBytes".to_string(),
FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"),
// Callback interfaces are not yet implemented, but this needs to return something in
// order for the coverall tests to pass.
FfiType::ForeignCallback => ":pointer".to_string(),
FfiType::RustFutureHandle
| FfiType::RustFutureContinuationCallback
| FfiType::RustFutureContinuationData => {
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
unimplemented!("Async functions are not implemented")
}
})
Expand Down
21 changes: 19 additions & 2 deletions uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,11 @@ impl SwiftCodeOracle {
nm.to_string().to_lower_camel_case()
}

/// Get the idiomatic Python rendering of an FFI callback function
fn ffi_callback_name(&self, nm: &str) -> String {
format!("Uniffi{}", nm.to_upper_camel_case())
}

fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::Int8 => "Int8".into(),
Expand All @@ -522,8 +527,8 @@ impl SwiftCodeOracle {
FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(),
FfiType::RustBuffer(_) => "RustBuffer".into(),
FfiType::ForeignBytes => "ForeignBytes".into(),
FfiType::Callback(name) => self.ffi_callback_name(name),
FfiType::ForeignCallback => "ForeignCallback".into(),
FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(),
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
"UnsafeMutableRawPointer".into()
}
Expand All @@ -534,7 +539,6 @@ impl SwiftCodeOracle {
match ffi_type {
FfiType::ForeignCallback
| FfiType::RustFutureHandle
| FfiType::RustFutureContinuationCallback
| FfiType::RustFutureContinuationData => {
format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type))
}
Expand Down Expand Up @@ -635,10 +639,18 @@ pub mod filters {
FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
FfiType::RustBuffer(_) => "RustBuffer".into(),
FfiType::ForeignBytes => "ForeignBytes".into(),
FfiType::Callback(name) => {
format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name))
}
FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(),
<<<<<<< HEAD
FfiType::RustFutureContinuationCallback => {
"UniFfiRustFutureContinuation _Nonnull".into()
}
=======
FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(),
FfiType::ForeignExecutorHandle => "size_t".into(),
>>>>>>> 1f20f993c (Adding `FfiType::Callback`)
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
"void* _Nonnull".into()
}
Expand Down Expand Up @@ -676,6 +688,11 @@ pub mod filters {
Ok(oracle().enum_variant_name(nm))
}

/// Get the idiomatic Swift rendering of an ffi callback function name
pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
Ok(oracle().ffi_callback_name(nm))
}

/// Get the idiomatic Swift rendering of docstring
pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
let middle = textwrap::indent(&textwrap::dedent(docstring), " * ");
Expand Down
2 changes: 1 addition & 1 deletion uniffi_bindgen/src/bindings/swift/templates/Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1

fileprivate func uniffiRustCallAsync<F, T>(
rustFutureFunc: () -> UnsafeMutableRawPointer,
pollFunc: (UnsafeMutableRawPointer, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (),
pollFunc: (UnsafeMutableRawPointer, @escaping UniffiRustFutureContinuationCallback, UnsafeMutableRawPointer) -> (),
completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F,
freeFunc: (UnsafeMutableRawPointer) -> (),
liftFunc: (F) throws -> T,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,17 @@ typedef struct RustCallStatus {
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H

// Continuation callback for UniFFI Futures
typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
// Define FFI callback types
{%- for callback in ci.ffi_callback_definitions() %}
typedef
{%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|ffi_type_name }} {% when None %} void {% endmatch -%}
(*{{ callback.name()|ffi_callback_name }})(
{%- for arg in callback.arguments() -%}
{{ arg.type_().borrow()|header_ffi_type_name }}
{%- if !loop.last %}, {% endif %}
{%- endfor -%}
);
{%- endfor %}

// Scaffolding functions
{%- for func in ci.iter_ffi_function_definitions() %}
Expand Down
43 changes: 41 additions & 2 deletions uniffi_bindgen/src/interface/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ pub enum FfiType {
/// A borrowed reference to some raw bytes owned by foreign language code.
/// The provider of this reference must keep it alive for the duration of the receiving call.
ForeignBytes,
/// Pointer to a callback function. The inner type is the name of the callback, which matches
/// one of the items in [crate::ComponentInterface::ffi_callback_definitions].
Callback(String),
/// Pointer to a callback function that handles all callbacks on the foreign language side.
ForeignCallback,
/// Pointer to a Rust future
RustFutureHandle,
/// Continuation function for a Rust future
RustFutureContinuationCallback,
RustFutureContinuationData,
// TODO: you can imagine a richer structural typesystem here, e.g. `Ref<String>` or something.
// We don't need that yet and it's possible we never will, so it isn't here for now.
Expand Down Expand Up @@ -224,14 +225,52 @@ pub struct FfiArgument {
}

impl FfiArgument {
pub fn new(name: impl Into<String>, type_: FfiType) -> Self {
Self {
name: name.into(),
type_,
}
}

pub fn name(&self) -> &str {
&self.name
}

pub fn type_(&self) -> FfiType {
self.type_.clone()
}
}

/// Represents an "extern C"-style callback function
///
/// These are defined in the foreign code and passed to Rust as a function pointer.
#[derive(Debug, Default, Clone)]
pub struct FfiCallbackFunction {
// Name for this function type. This matches the value inside `FfiType::Callback`
pub(super) name: String,
pub(super) arguments: Vec<FfiArgument>,
pub(super) return_type: Option<FfiType>,
pub(super) has_rust_call_status_arg: bool,
}

impl FfiCallbackFunction {
pub fn name(&self) -> &str {
&self.name
}

pub fn arguments(&self) -> Vec<&FfiArgument> {
self.arguments.iter().collect()
}

pub fn return_type(&self) -> Option<&FfiType> {
self.return_type.as_ref()
}

pub fn has_rust_call_status_arg(&self) -> bool {
self.has_rust_call_status_arg
}
}

#[cfg(test)]
mod test {
// There's not really much to test here to be honest,
Expand Down
Loading

0 comments on commit 6786d35

Please sign in to comment.