Skip to content
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

Adding FfiType::Handle and use it for Rust futures #1972

Merged
merged 1 commit into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

- The callback interface code was reworked to use vtables rather than a single callback method.
See https://github.com/mozilla/uniffi-rs/pull/1818 for details and how the other bindings were updated.
- Removed `FfiType::RustFutureContinuationData`. `RustFutureContinuation` callbacks now take a `u64` handle.
- Added the `FfiType::Handle` variant. This is a general-purpose opaque handle type used for
passing objects cross the FFI. This type is always 64 bits and replaces the various older handle
types including:
- Rust futures (replacing `FfiType::RustFutureHandle` which was removed)
- Rust future continuation data (Replacing `FfiType::RustFutureContinuationData` which was moved).

## v0.26.0 (backend crates: v0.26.0) - (_2024-01-23_)

Expand Down
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::*;
Loading