Skip to content

Commit

Permalink
Adding FfiType::Handle and use it for Rust futures
Browse files Browse the repository at this point in the history
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 (mozilla#1823).
  • Loading branch information
bendk committed Jan 30, 2024
1 parent 132635c commit 36ae4b9
Show file tree
Hide file tree
Showing 22 changed files with 271 additions and 96 deletions.
3 changes: 2 additions & 1 deletion uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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(),
}
}

Expand Down
8 changes: 4 additions & 4 deletions uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ internal object uniffiRustFutureContinuationCallback: UniffiRustFutureContinuati
}

internal suspend fun<T, F, E: Exception> 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<E>
): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CallbackInterface: Any>: FfiConverter<CallbackInterface, UniffiHandle> {
public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: FfiConverter<CallbackInterface, Long> {
internal val handleMap = UniffiHandleMap<CallbackInterface>()

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}>()
11 changes: 4 additions & 7 deletions uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt
Original file line number Diff line number Diff line change
@@ -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<T: Any> {
private val map = ConcurrentHashMap<UniffiHandle, T>()
private val map = ConcurrentHashMap<Long, T>()
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")
}
}
3 changes: 2 additions & 1 deletion uniffi_bindgen/src/bindings/python/gen_python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"),
Expand All @@ -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(),
}
}

Expand Down
4 changes: 1 addition & 3 deletions uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -162,9 +163,6 @@ mod filters {
FfiType::Struct(_) => {
unimplemented!("Structs are not implemented")
}
FfiType::RustFutureHandle => {
unimplemented!("Async functions are not implemented")
}
})
}

Expand Down
6 changes: 4 additions & 2 deletions uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
})
}

Expand Down
8 changes: 4 additions & 4 deletions uniffi_bindgen/src/bindings/swift/templates/Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1
fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>()

fileprivate func uniffiRustCallAsync<F, T>(
rustFutureFunc: () -> UnsafeMutableRawPointer,
pollFunc: (UnsafeMutableRawPointer, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (),
completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F,
freeFunc: (UnsafeMutableRawPointer) -> (),
rustFutureFunc: () -> UInt64,
pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (),
completeFunc: (UInt64, UnsafeMutablePointer<RustCallStatus>) -> F,
freeFunc: (UInt64) -> (),
liftFunc: (F) throws -> T,
errorHandler: ((RustBuffer) throws -> Error)?
) async throws -> T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
11 changes: 5 additions & 6 deletions uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
fileprivate typealias UniffiHandle = UInt64
fileprivate class UniffiHandleMap<T> {
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
Expand All @@ -13,7 +12,7 @@ fileprivate class UniffiHandleMap<T> {
}
}

func get(handle: UniffiHandle) throws -> T {
func get(handle: UInt64) throws -> T {
try lock.withLock {
guard let obj = map[handle] else {
throw UniffiInternalError.unexpectedStaleHandle
Expand All @@ -23,7 +22,7 @@ fileprivate class UniffiHandleMap<T> {
}

@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
Expand Down
8 changes: 5 additions & 3 deletions uniffi_bindgen/src/interface/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FfiType>),
/// Opaque pointer
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions uniffi_bindgen/src/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,15 +514,15 @@ impl ComponentInterface {
arguments: vec![
FfiArgument {
name: "handle".to_owned(),
type_: FfiType::RustFutureHandle,
type_: FfiType::Handle,
},
FfiArgument {
name: "callback".to_owned(),
type_: FfiType::Callback("RustFutureContinuationCallback".to_owned()),
},
FfiArgument {
name: "callback_data".to_owned(),
type_: FfiType::UInt64,
type_: FfiType::Handle,
},
],
return_type: None,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions uniffi_core/src/ffi/ffidefault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
46 changes: 46 additions & 0 deletions uniffi_core/src/ffi/handle.rs
Original file line number Diff line number Diff line change
@@ -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<T>(ptr: *const T) -> Self {
Self(ptr as u64)
}

pub fn as_pointer<T>(&self) -> *const T {
self.0 as *const T
}

pub fn from_raw(raw: u64) -> Option<Self> {
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
}
}
2 changes: 2 additions & 0 deletions uniffi_core/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;
4 changes: 2 additions & 2 deletions uniffi_core/src/ffi/rustfuture/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ReturnType> {
pub trait RustFutureFfi<ReturnType>: Send + Sync {
fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64);
fn ffi_cancel(&self);
fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType;
Expand Down
Loading

0 comments on commit 36ae4b9

Please sign in to comment.