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 Nov 1, 2023
1 parent 9629c96 commit 99dbabd
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 45 deletions.
28 changes: 18 additions & 10 deletions uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,19 @@ impl KotlinCodeOracle {
}
}

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 @@ -297,13 +302,11 @@ 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::ForeignExecutorHandle => "USize".to_string(),
FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
FfiType::RustFutureHandle => "Pointer".to_string(),
FfiType::RustFutureContinuationCallback => {
"UniFffiRustFutureContinuationCallbackType".to_string()
}
FfiType::RustFutureContinuationData => "USize".to_string(),
}
}
Expand Down Expand Up @@ -469,11 +472,11 @@ pub mod filters {

/// Get the Kotlin syntax for representing a given low-level `FfiType`.
pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle::ffi_type_label(type_))
Ok(KotlinCodeOracle.ffi_type_label(type_))
}

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_))
}

// Some FfiTypes have the same ffi_type_label - this makes a vec of them unique.
Expand All @@ -483,7 +486,7 @@ pub mod filters {
let mut seen = HashSet::new();
let mut result = Vec::new();
for t in types {
if seen.insert(KotlinCodeOracle::ffi_type_label(&t)) {
if seen.insert(KotlinCodeOracle.ffi_type_label(&t)) {
result.push(t)
}
}
Expand Down Expand Up @@ -523,6 +526,11 @@ pub mod filters {
.type_label())
}

/// 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 error_canonical_name(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle
.find_as_error(&as_type.as_type())
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 }},
{%- endfor -%}
)
{%- match callback.return_type() %}
{%- when Some(return_type) %}: {{ return_type|ffi_type_name }},
{%- 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 @@ -299,7 +299,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 @@ -317,12 +322,12 @@ 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::ForeignExecutorHandle => "ctypes.c_size_t".to_string(),
FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(),
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 @@ -439,7 +444,7 @@ pub mod filters {

/// Get the Python syntax for representing a given low-level `FfiType`.
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 @@ -462,6 +467,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 @@ -68,6 +68,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 @@ -153,6 +153,7 @@ 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(),
Expand All @@ -162,9 +163,7 @@ mod filters {
FfiType::ForeignExecutorHandle => {
unimplemented!("Foreign executors are not implemented")
}
FfiType::RustFutureHandle
| FfiType::RustFutureContinuationCallback
| FfiType::RustFutureContinuationData => {
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
unimplemented!("Async functions are not implemented")
}
})
Expand Down
19 changes: 14 additions & 5 deletions uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,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 @@ -458,10 +463,10 @@ 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::ForeignExecutorHandle => "Int".into(),
FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(),
FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(),
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
"UnsafeMutableRawPointer".into()
}
Expand All @@ -473,7 +478,6 @@ impl SwiftCodeOracle {
FfiType::ForeignCallback
| FfiType::ForeignExecutorCallback
| FfiType::RustFutureHandle
| FfiType::RustFutureContinuationCallback
| FfiType::RustFutureContinuationData => {
format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type))
}
Expand Down Expand Up @@ -574,12 +578,12 @@ 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(),
FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(),
FfiType::ForeignExecutorHandle => "size_t".into(),
FfiType::RustFutureContinuationCallback => {
"UniFfiRustFutureContinuation _Nonnull".into()
}
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
"void* _Nonnull".into()
}
Expand Down Expand Up @@ -617,6 +621,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))
}

pub fn error_handler(result: &ResultType) -> Result<String, askama::Error> {
Ok(match &result.throws_type {
Some(t) => format!("{}.lift", ffi_converter_name(t)?),
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 @@ -59,8 +59,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
Loading

0 comments on commit 99dbabd

Please sign in to comment.