From 36ae4b9d7b9bafb297769ac4557e344400451f27 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Mon, 30 Oct 2023 18:58:05 -0400 Subject: [PATCH] Adding `FfiType::Handle` and use it for Rust futures FfiType::Handle is a opaque 64-bit handle that's used to pass objects across the FFI. This PR changes the rustfuture code to use it and also renames the type used to represent callback interfaces from `u64` to handle. The plan is to use it for all object types, including interfaces and trait interfaces, but do that in future PRs. Accompianing that code is the `HandleAlloc` trait, which has a general system for allocating/using/freeing handles for objects. It's essentially the same system(s) we're currently using, but now the code is all in one place. On the bindings side, switched the code to using the `u64` type rather than having a `UniffiHandle` type alias. I don't think the type alias is very useful since handles are only used internally. Also, I remember running into Switch issues with the type alias and multiple crates. Most of this code was copied from the handles PR (#1823). --- .../src/bindings/kotlin/gen_kotlin/mod.rs | 3 +- .../src/bindings/kotlin/templates/Async.kt | 8 +- .../templates/CallbackInterfaceRuntime.kt | 6 +- .../templates/CallbackInterfaceTemplate.kt | 2 +- .../bindings/kotlin/templates/HandleMap.kt | 11 +- .../src/bindings/python/gen_python/mod.rs | 3 +- .../src/bindings/ruby/gen_ruby/mod.rs | 4 +- .../src/bindings/swift/gen_swift/mod.rs | 6 +- .../src/bindings/swift/templates/Async.swift | 8 +- .../templates/CallbackInterfaceTemplate.swift | 9 +- .../bindings/swift/templates/HandleMap.swift | 11 +- uniffi_bindgen/src/interface/ffi.rs | 8 +- uniffi_bindgen/src/interface/mod.rs | 10 +- uniffi_core/src/ffi/ffidefault.rs | 6 + uniffi_core/src/ffi/handle.rs | 46 ++++++++ uniffi_core/src/ffi/mod.rs | 2 + uniffi_core/src/ffi/rustfuture/future.rs | 4 +- uniffi_core/src/ffi/rustfuture/mod.rs | 87 ++++++++------ uniffi_core/src/ffi_converter_traits.rs | 108 +++++++++++++++++- uniffi_core/src/lib.rs | 3 +- uniffi_macros/src/export/scaffolding.rs | 2 +- uniffi_macros/src/setup_scaffolding.rs | 20 ++-- 22 files changed, 271 insertions(+), 96 deletions(-) create mode 100644 uniffi_core/src/ffi/handle.rs diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index be16218aaf..aa9d13fe76 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -370,6 +370,7 @@ impl KotlinCodeOracle { FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), + FfiType::Handle => "Long".to_string(), FfiType::RustArcPtr(_) => "Pointer".to_string(), FfiType::RustBuffer(maybe_suffix) => { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) @@ -378,7 +379,7 @@ impl KotlinCodeOracle { FfiType::Callback(name) => self.ffi_callback_name(name), FfiType::Struct(name) => self.ffi_struct_name(name), FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner), - FfiType::VoidPointer | FfiType::RustFutureHandle => "Pointer".to_string(), + FfiType::VoidPointer => "Pointer".to_string(), } } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt index 33fdff69b5..0c9c8053c4 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -13,10 +13,10 @@ internal object uniffiRustFutureContinuationCallback: UniffiRustFutureContinuati } internal suspend fun uniffiRustCallAsync( - rustFuture: Pointer, - pollFunc: (Pointer, UniffiRustFutureContinuationCallback, Long) -> Unit, - completeFunc: (Pointer, UniffiRustCallStatus) -> F, - freeFunc: (Pointer) -> Unit, + rustFuture: Long, + pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit, + completeFunc: (Long, UniffiRustCallStatus) -> F, + freeFunc: (Long) -> Unit, liftFunc: (F) -> T, errorHandler: UniffiRustCallStatusErrorHandler ): T { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt index 6bd6c93fb9..81b0493d65 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt @@ -6,14 +6,14 @@ internal const val UNIFFI_CALLBACK_SUCCESS = 0 internal const val UNIFFI_CALLBACK_ERROR = 1 internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 -public abstract class FfiConverterCallbackInterface: FfiConverter { +public abstract class FfiConverterCallbackInterface: FfiConverter { internal val handleMap = UniffiHandleMap() - internal fun drop(handle: UniffiHandle) { + internal fun drop(handle: Long) { handleMap.remove(handle) } - override fun lift(value: UniffiHandle): CallbackInterface { + override fun lift(value: Long): CallbackInterface { return handleMap.get(value) } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt index 5285cbc4ab..d2cdee4f33 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -9,5 +9,5 @@ {% include "Interface.kt" %} {% include "CallbackInterfaceImpl.kt" %} -// The ffiConverter which transforms the Callbacks in to UniffiHandles to pass to Rust. +// The ffiConverter which transforms the Callbacks in to handles to pass to Rust. public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ interface_name }}>() diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt b/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt index a06c4468ba..3a56648190 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt @@ -1,30 +1,27 @@ -// Handle from a UniffiHandleMap -internal typealias UniffiHandle = Long - // Map handles to objects // // This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. internal class UniffiHandleMap { - private val map = ConcurrentHashMap() + private val map = ConcurrentHashMap() private val counter = java.util.concurrent.atomic.AtomicLong(0) val size: Int get() = map.size // Insert a new object into the handle map and get a handle for it - fun insert(obj: T): UniffiHandle { + fun insert(obj: T): Long { val handle = counter.getAndAdd(1) map.put(handle, obj) return handle } // Get an object from the handle map - fun get(handle: UniffiHandle): T { + fun get(handle: Long): T { return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") } // Remove an entry from the handlemap and get the Kotlin object back - fun remove(handle: UniffiHandle): T { + fun remove(handle: Long): T { return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") } } diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index d25314659e..6c1996eb5c 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -361,6 +361,7 @@ impl PythonCodeOracle { FfiType::UInt64 => "ctypes.c_uint64".to_string(), FfiType::Float32 => "ctypes.c_float".to_string(), FfiType::Float64 => "ctypes.c_double".to_string(), + FfiType::Handle => "ctypes.c_uint64".to_string(), FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), @@ -371,7 +372,7 @@ impl PythonCodeOracle { FfiType::Struct(name) => self.ffi_struct_name(name), // Pointer to an `asyncio.EventLoop` instance FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)), - FfiType::VoidPointer | FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), + FfiType::VoidPointer => "ctypes.c_void_p".to_string(), } } diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index a2d6d41247..07da3882b6 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -149,6 +149,7 @@ mod filters { FfiType::UInt64 => ":uint64".to_string(), FfiType::Float32 => ":float".to_string(), FfiType::Float64 => ":double".to_string(), + FfiType::Handle => ":uint64".to_string(), FfiType::RustArcPtr(_) => ":pointer".to_string(), FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), @@ -162,9 +163,6 @@ mod filters { FfiType::Struct(_) => { unimplemented!("Structs are not implemented") } - FfiType::RustFutureHandle => { - unimplemented!("Async functions are not implemented") - } }) } diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 6f1464efe7..3969f6094e 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -529,6 +529,7 @@ impl SwiftCodeOracle { FfiType::UInt64 => "UInt64".into(), FfiType::Float32 => "Float".into(), FfiType::Float64 => "Double".into(), + FfiType::Handle => "UInt64".into(), FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), @@ -537,7 +538,7 @@ impl SwiftCodeOracle { FfiType::Reference(inner) => { format!("UnsafeMutablePointer<{}>", self.ffi_type_label(inner)) } - FfiType::VoidPointer | FfiType::RustFutureHandle => "UnsafeMutableRawPointer".into(), + FfiType::VoidPointer => "UnsafeMutableRawPointer".into(), } } @@ -630,6 +631,7 @@ pub mod filters { FfiType::UInt64 => "uint64_t".into(), FfiType::Float32 => "float".into(), FfiType::Float64 => "double".into(), + FfiType::Handle => "uint64_t".into(), FfiType::RustArcPtr(_) => "void*_Nonnull".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), @@ -638,7 +640,7 @@ pub mod filters { } FfiType::Struct(name) => SwiftCodeOracle.ffi_struct_name(name), FfiType::Reference(inner) => format!("{}* _Nonnull", header_ffi_type_name(inner)?), - FfiType::VoidPointer | FfiType::RustFutureHandle => "void* _Nonnull".into(), + FfiType::VoidPointer => "void* _Nonnull".into(), }) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/uniffi_bindgen/src/bindings/swift/templates/Async.swift index b91e264fe8..761e0dd70e 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Async.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -4,10 +4,10 @@ private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 fileprivate let uniffiContinuationHandleMap = UniffiHandleMap>() fileprivate func uniffiRustCallAsync( - rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), - completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, - freeFunc: (UnsafeMutableRawPointer) -> (), + rustFutureFunc: () -> UInt64, + pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), + completeFunc: (UInt64, UnsafeMutablePointer) -> F, + freeFunc: (UInt64) -> (), liftFunc: (F) throws -> T, errorHandler: ((RustBuffer) throws -> Error)? ) async throws -> T { diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index 1827dc19bf..7aa1cca9b2 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -18,19 +18,18 @@ fileprivate struct {{ ffi_converter_name }} { extension {{ ffi_converter_name }} : FfiConverter { typealias SwiftType = {{ type_name }} - // We can use Handle as the FfiType because it's a typealias to UInt64 - typealias FfiType = UniffiHandle + typealias FfiType = UInt64 - public static func lift(_ handle: UniffiHandle) throws -> SwiftType { + public static func lift(_ handle: UInt64) throws -> SwiftType { try handleMap.get(handle: handle) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { - let handle: UniffiHandle = try readInt(&buf) + let handle: UInt64 = try readInt(&buf) return try lift(handle) } - public static func lower(_ v: SwiftType) -> UniffiHandle { + public static func lower(_ v: SwiftType) -> UInt64 { return handleMap.insert(obj: v) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift b/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift index c636ca6be6..af0305872b 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift @@ -1,10 +1,9 @@ -fileprivate typealias UniffiHandle = UInt64 fileprivate class UniffiHandleMap { - private var map: [UniffiHandle: T] = [:] + private var map: [UInt64: T] = [:] private let lock = NSLock() - private var currentHandle: UniffiHandle = 1 + private var currentHandle: UInt64 = 1 - func insert(obj: T) -> UniffiHandle { + func insert(obj: T) -> UInt64 { lock.withLock { let handle = currentHandle currentHandle += 1 @@ -13,7 +12,7 @@ fileprivate class UniffiHandleMap { } } - func get(handle: UniffiHandle) throws -> T { + func get(handle: UInt64) throws -> T { try lock.withLock { guard let obj = map[handle] else { throw UniffiInternalError.unexpectedStaleHandle @@ -23,7 +22,7 @@ fileprivate class UniffiHandleMap { } @discardableResult - func remove(handle: UniffiHandle) throws -> T { + func remove(handle: UInt64) throws -> T { try lock.withLock { guard let obj = map.removeValue(forKey: handle) else { throw UniffiInternalError.unexpectedStaleHandle diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index 44b09d3fea..5b3efdbb67 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -53,8 +53,10 @@ pub enum FfiType { /// Pointer to a FFI struct (e.g. a VTable). The inner value matches one of the items in /// [crate::ComponentInterface::ffi_struct_definitions]. Struct(String), - /// Pointer to a Rust future - RustFutureHandle, + /// Opaque 64-bit handle + /// + /// These are used to pass objects across the FFI. + Handle, /// Pointer to an FfiType. Reference(Box), /// Opaque pointer @@ -202,7 +204,7 @@ impl FfiFunction { ) { self.arguments = args.into_iter().collect(); if self.is_async() { - self.return_type = Some(FfiType::RustFutureHandle); + self.return_type = Some(FfiType::Handle); self.has_rust_call_status_arg = false; } else { self.return_type = return_type; diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index 375f7c0ea2..fe90fd5a14 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -514,7 +514,7 @@ impl ComponentInterface { arguments: vec![ FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }, FfiArgument { name: "callback".to_owned(), @@ -522,7 +522,7 @@ impl ComponentInterface { }, FfiArgument { name: "callback_data".to_owned(), - type_: FfiType::UInt64, + type_: FfiType::Handle, }, ], return_type: None, @@ -540,7 +540,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: return_ffi_type, has_rust_call_status_arg: true, @@ -555,7 +555,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -570,7 +570,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, diff --git a/uniffi_core/src/ffi/ffidefault.rs b/uniffi_core/src/ffi/ffidefault.rs index d3ca9438d8..97f12fb38f 100644 --- a/uniffi_core/src/ffi/ffidefault.rs +++ b/uniffi_core/src/ffi/ffidefault.rs @@ -39,6 +39,12 @@ impl FfiDefault for () { fn ffi_default() {} } +impl FfiDefault for crate::Handle { + fn ffi_default() -> Self { + Self::default() + } +} + impl FfiDefault for *const std::ffi::c_void { fn ffi_default() -> Self { std::ptr::null() diff --git a/uniffi_core/src/ffi/handle.rs b/uniffi_core/src/ffi/handle.rs new file mode 100644 index 0000000000..8ee2f46c35 --- /dev/null +++ b/uniffi_core/src/ffi/handle.rs @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Object handle +/// +/// Handles opaque `u64` values used to pass objects across the FFI, both for objects implemented in +/// Rust and ones implemented in the foreign language. +/// +/// Rust handles are generated by leaking a raw pointer +/// Foreign handles are generated with a handle map that only generates odd values. +/// For all currently supported architectures and hopefully any ones we add in the future: +/// * 0 is an invalid value. +/// * The lowest bit will always be set for foreign handles and never set for Rust ones (since the +/// leaked pointer will be aligned). +/// +/// Rust handles are mainly managed is through the [crate::HandleAlloc] trait. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Handle(u64); + +impl Handle { + pub fn from_pointer(ptr: *const T) -> Self { + Self(ptr as u64) + } + + pub fn as_pointer(&self) -> *const T { + self.0 as *const T + } + + pub fn from_raw(raw: u64) -> Option { + if raw == 0 { + None + } else { + Some(Self(raw)) + } + } + + pub fn from_raw_unchecked(raw: u64) -> Self { + Self(raw) + } + + pub fn as_raw(&self) -> u64 { + self.0 + } +} diff --git a/uniffi_core/src/ffi/mod.rs b/uniffi_core/src/ffi/mod.rs index 24b9bbaa84..8e26be37b0 100644 --- a/uniffi_core/src/ffi/mod.rs +++ b/uniffi_core/src/ffi/mod.rs @@ -8,6 +8,7 @@ pub mod callbackinterface; pub mod ffidefault; pub mod foreignbytes; pub mod foreigncallbacks; +pub mod handle; pub mod rustbuffer; pub mod rustcalls; pub mod rustfuture; @@ -16,6 +17,7 @@ pub use callbackinterface::*; pub use ffidefault::FfiDefault; pub use foreignbytes::*; pub use foreigncallbacks::*; +pub use handle::*; pub use rustbuffer::*; pub use rustcalls::*; pub use rustfuture::*; diff --git a/uniffi_core/src/ffi/rustfuture/future.rs b/uniffi_core/src/ffi/rustfuture/future.rs index a42729bf36..93c34e7543 100644 --- a/uniffi_core/src/ffi/rustfuture/future.rs +++ b/uniffi_core/src/ffi/rustfuture/future.rs @@ -12,7 +12,7 @@ //! //! 0. At startup, register a [RustFutureContinuationCallback] by calling //! rust_future_continuation_callback_set. -//! 1. Call the scaffolding function to get a [RustFutureHandle] +//! 1. Call the scaffolding function to get a [Handle] //! 2a. In a loop: //! - Call [rust_future_poll] //! - Suspend the function until the [rust_future_poll] continuation function is called @@ -288,7 +288,7 @@ where /// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and /// only create those functions for each of the 13 possible FFI return types. #[doc(hidden)] -pub trait RustFutureFfi { +pub trait RustFutureFfi: Send + Sync { fn ffi_poll(self: Arc, callback: RustFutureContinuationCallback, data: u64); fn ffi_cancel(&self); fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; diff --git a/uniffi_core/src/ffi/rustfuture/mod.rs b/uniffi_core/src/ffi/rustfuture/mod.rs index 3aec88a2a8..39529f2db1 100644 --- a/uniffi_core/src/ffi/rustfuture/mod.rs +++ b/uniffi_core/src/ffi/rustfuture/mod.rs @@ -12,7 +12,7 @@ use scheduler::*; #[cfg(test)] mod tests; -use crate::{LowerReturn, RustCallStatus}; +use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; /// Result code for [rust_future_poll]. This is passed to the continuation function. #[repr(i8)] @@ -30,17 +30,13 @@ pub enum RustFuturePoll { /// to continue progress on the future. pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, RustFuturePoll); -/// Opaque handle for a Rust future that's stored by the foreign language code -#[repr(transparent)] -pub struct RustFutureHandle(*const ()); - // === Public FFI API === -/// Create a new [RustFutureHandle] +/// Create a new [Handle] for a Rust future /// /// For each exported async function, UniFFI will create a scaffolding function that uses this to -/// create the [RustFutureHandle] to pass to the foreign code. -pub fn rust_future_new(future: F, tag: UT) -> RustFutureHandle +/// create the [Handle] to pass to the foreign code. +pub fn rust_future_new(future: F, tag: UT) -> Handle where // F is the future type returned by the exported async function. It needs to be Send + `static // since it will move between threads for an indeterminate amount of time as the foreign @@ -52,15 +48,12 @@ where T: LowerReturn + Send + 'static, // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. UT: Send + 'static, + // Needed to allocate a handle + dyn RustFutureFfi: HandleAlloc, { - // Create a RustFuture and coerce to `Arc`, which is what we use to - // implement the FFI - let future_ffi = RustFuture::new(future, tag) as Arc>; - // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it - // to the foreign code. - let boxed_ffi = Box::new(future_ffi); - // We can now create a RustFutureHandle - RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) + as HandleAlloc>::new_handle( + RustFuture::new(future, tag) as Arc> + ) } /// Poll a Rust future @@ -71,14 +64,15 @@ where /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll( - handle: RustFutureHandle, +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll( + handle: Handle, callback: RustFutureContinuationCallback, data: u64, -) { - let future = &*(handle.0 as *mut Arc>); - future.clone().ffi_poll(callback, data) +) where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_poll(callback, data) } /// Cancel a Rust future @@ -90,10 +84,12 @@ pub unsafe fn rust_future_poll( /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_cancel(handle: RustFutureHandle) { - let future = &*(handle.0 as *mut Arc>); - future.clone().ffi_cancel() +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel(handle: Handle) +where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_cancel() } /// Complete a Rust future @@ -103,15 +99,17 @@ pub unsafe fn rust_future_cancel(handle: RustFutureHandle) { /// /// # Safety /// -/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] +/// - The [Handle] must not previously have been passed to [rust_future_free] /// - The `T` param must correctly correspond to the [rust_future_new] call. It must /// be `>::ReturnType` -pub unsafe fn rust_future_complete( - handle: RustFutureHandle, +pub unsafe fn rust_future_complete( + handle: Handle, out_status: &mut RustCallStatus, -) -> ReturnType { - let future = &*(handle.0 as *mut Arc>); - future.ffi_complete(out_status) +) -> ReturnType +where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_complete(out_status) } /// Free a Rust future, dropping the strong reference and releasing all references held by the @@ -119,8 +117,25 @@ pub unsafe fn rust_future_complete( /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_free(handle: RustFutureHandle) { - let future = Box::from_raw(handle.0 as *mut Arc>); - future.ffi_free() +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free(handle: Handle) +where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_free() } + +// Derive HandleAlloc for dyn RustFutureFfi for all FFI return types +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi<*const std::ffi::c_void>); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi<()>); diff --git a/uniffi_core/src/ffi_converter_traits.rs b/uniffi_core/src/ffi_converter_traits.rs index 987a47c414..4e7b9e06fa 100644 --- a/uniffi_core/src/ffi_converter_traits.rs +++ b/uniffi_core/src/ffi_converter_traits.rs @@ -52,7 +52,7 @@ use anyhow::bail; use bytes::Buf; use crate::{ - FfiDefault, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, + FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, UnexpectedUniFFICallbackError, }; @@ -381,6 +381,66 @@ pub trait ConvertError: Sized { fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result; } +/// Manage handles for `Arc` instances +/// +/// Handles are used to manage objects that are passed across the FFI. They general usage is: +/// +/// * Rust creates an `Arc<>` +/// * Rust uses `new_handle` to create a handle that represents the Arc reference +/// * Rust passes the handle to the foreign code as a `u64` +/// * The foreign code passes the handle back to `Rust` to refer to the object: +/// * Handle are usually passed as borrowed values. When an FFI function inputs a handle as an +/// argument, the foreign code simply passes a copy of the `u64` to Rust, which calls `get_arc` +/// to get a new `Arc<>` clone for it. +/// * Handles are returned as owned values. When an FFI function returns a handle, the foreign +/// code either stops using the handle after returning it or calls `clone_handle` and returns +/// the clone. +/// * Eventually the foreign code may destroy their handle by passing it into a "free" FFI +/// function. This functions input an owned handle and consume it. +/// +/// The foreign code also defines their own handles. These represent foreign objects that are +/// passed to Rust. Using foreign handles is essentially the same as above, but in reverse. +/// +/// Handles must always be `Send` and the objects they reference must always be `Sync`. +/// This means that it must be safe to send handles to other threads and use them there. +/// +/// Note: this only needs to be derived for unsized types, there's a blanket impl for `T: Sized`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +/// `&T` using the Arc. +pub unsafe trait HandleAlloc: Send + Sync { + /// Create a new handle for an Arc value + /// + /// Use this to lower an Arc into a handle value before passing it across the FFI. + /// The newly-created handle will have reference count = 1. + fn new_handle(value: Arc) -> Handle; + + /// Clone a handle + /// + /// This creates a new handle from an existing one. + /// It's used when the foreign code wants to pass back an owned handle and still keep a copy + /// for themselves. + fn clone_handle(handle: Handle) -> Handle; + + /// Get a clone of the `Arc<>` using a "borrowed" handle. + /// + /// Take care that the handle can not be destroyed between when it's passed and when + /// `get_arc()` is called. #1797 is a cautionary tale. + fn get_arc(handle: Handle) -> Arc { + Self::consume_handle(Self::clone_handle(handle)) + } + + /// Consume a handle, getting back the initial `Arc<>` + fn consume_handle(handle: Handle) -> Arc; +} + /// Derive FFI traits /// /// This can be used to derive: @@ -494,4 +554,50 @@ macro_rules! derive_ffi_traits { } } }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + // Derived HandleAlloc implementation. + // + // This is only needed for !Sized types like `dyn Trait`, below is a blanket implementation + // for any sized type. + unsafe impl $(<$($generic),*>)* $crate::HandleAlloc<$ut> for $ty $(where $($where)*)* + { + // To implement HandleAlloc for an unsized type, wrap it with a second Arc which + // converts the wide pointer into a normal pointer. + + fn new_handle(value: ::std::sync::Arc) -> $crate::Handle { + $crate::Handle::from_pointer(::std::sync::Arc::into_raw(::std::sync::Arc::new(value))) + } + + fn clone_handle(handle: $crate::Handle) -> $crate::Handle { + unsafe { + ::std::sync::Arc::<::std::sync::Arc>::increment_strong_count(handle.as_pointer::<::std::sync::Arc>()); + } + handle + } + + fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc { + unsafe { + ::std::sync::Arc::::clone( + &std::sync::Arc::<::std::sync::Arc::>::from_raw(handle.as_pointer::<::std::sync::Arc>()) + ) + } + } + } + }; +} + +unsafe impl HandleAlloc for T { + fn new_handle(value: Arc) -> Handle { + Handle::from_pointer(Arc::into_raw(value)) + } + + fn clone_handle(handle: Handle) -> Handle { + unsafe { Arc::increment_strong_count(handle.as_pointer::()) }; + handle + } + + fn consume_handle(handle: Handle) -> Arc { + unsafe { Arc::from_raw(handle.as_pointer()) } + } } diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 9003b08f9b..3ae2983eab 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -45,7 +45,8 @@ pub mod metadata; pub use ffi::*; pub use ffi_converter_traits::{ - ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, + ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower, + LowerReturn, }; pub use metadata::*; diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index 0e0da87a2e..31c4e83232 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -264,7 +264,7 @@ pub(super) fn gen_ffi_function( quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::RustFutureHandle { + pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::Handle { ::uniffi::deps::log::debug!(#name); let uniffi_lift_args = #lift_closure; match uniffi_lift_args() { diff --git a/uniffi_macros/src/setup_scaffolding.rs b/uniffi_macros/src/setup_scaffolding.rs index 58bb1ee483..08bb2cc568 100644 --- a/uniffi_macros/src/setup_scaffolding.rs +++ b/uniffi_macros/src/setup_scaffolding.rs @@ -129,12 +129,12 @@ pub fn setup_scaffolding(namespace: String) -> Result { /// Generates the rust_future_* functions /// -/// The foreign side uses a type-erased `RustFutureHandle` to interact with futures, which presents +/// The foreign side uses a type-erased `Handle` to interact with futures, which presents /// a problem when creating scaffolding functions. What is the `ReturnType` parameter of `RustFutureFfi`? /// /// Handle this by using some brute-force monomorphization. For each possible ffi type, we /// generate a set of scaffolding functions. The bindings code is responsible for calling the one -/// corresponds the scaffolding function that created the `RustFutureHandle`. +/// corresponds the scaffolding function that created the `Handle`. /// /// This introduces safety issues, but we do get some type checking. If the bindings code calls /// the wrong rust_future_complete function, they should get an unexpected return type, which @@ -166,32 +166,32 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::RustFutureHandle, callback: ::uniffi::RustFutureContinuationCallback, data: u64) { - ::uniffi::ffi::rust_future_poll::<#return_type>(handle, callback, data); + pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::Handle, callback: ::uniffi::RustFutureContinuationCallback, data: u64) { + ::uniffi::ffi::rust_future_poll::<#return_type, crate::UniFfiTag>(handle, callback, data); } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_cancel::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_cancel::<#return_type, crate::UniFfiTag>(handle) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn #ffi_rust_future_complete( - handle: ::uniffi::RustFutureHandle, + handle: ::uniffi::Handle, out_status: &mut ::uniffi::RustCallStatus ) -> #return_type { - ::uniffi::ffi::rust_future_complete::<#return_type>(handle, out_status) + ::uniffi::ffi::rust_future_complete::<#return_type, crate::UniFfiTag>(handle, out_status) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_free::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_free::<#return_type, crate::UniFfiTag>(handle) } } })