Skip to content

Commit

Permalink
Adding Handle and HandleAlloc
Browse files Browse the repository at this point in the history
`Handle` is a u64 newtype that I hope to use to represent objects that
get passed across the FFI. `HandleAlloc` is an ffi trait that converts
between `Arc<>` and `Handle`.
  • Loading branch information
bendk committed Nov 2, 2023
1 parent 15a3eb3 commit c3e6007
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 2 deletions.
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
42 changes: 42 additions & 0 deletions uniffi_core/src/ffi/handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* 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 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 @@ -9,6 +9,7 @@ pub mod ffidefault;
pub mod foreignbytes;
pub mod foreigncallbacks;
pub mod foreignexecutor;
pub mod handle;
pub mod rustbuffer;
pub mod rustcalls;
pub mod rustfuture;
Expand All @@ -18,6 +19,7 @@ pub use ffidefault::FfiDefault;
pub use foreignbytes::*;
pub use foreigncallbacks::*;
pub use foreignexecutor::*;
pub use handle::*;
pub use rustbuffer::*;
pub use rustcalls::*;
pub use rustfuture::*;
106 changes: 105 additions & 1 deletion uniffi_core/src/ffi_converter_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ use std::{borrow::Borrow, sync::Arc};
use anyhow::bail;
use bytes::Buf;

use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError};
use crate::{
FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError,
};

/// Generalized FFI conversions
///
Expand Down Expand Up @@ -351,6 +353,66 @@ pub trait ConvertError<UT>: Sized {
fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result<Self>;
}

/// Manage handles for `Arc<Self>` 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. TODO: actually start doing this (#1797)
/// * 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<UT> {
/// 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<Self>) -> 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> {
Self::consume_handle(Self::clone_handle(handle))
}

/// Consume a handle, getting back the initial `Arc<>`
fn consume_handle(handle: Handle) -> Arc<Self>;
}

/// Derive FFI traits
///
/// This can be used to derive:
Expand Down Expand Up @@ -463,4 +525,46 @@ macro_rules! derive_ffi_traits {
}
}
};

(impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => {
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<Self>) -> $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<Self>>::increment_strong_count(handle.as_pointer::<::std::sync::Arc<Self>>());
}
handle
}

fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc<Self> {
unsafe {
::std::sync::Arc::<Self>::clone(
&std::sync::Arc::<::std::sync::Arc::<Self>>::from_raw(handle.as_pointer::<::std::sync::Arc<Self>>())
)
}
}
}
};
}

unsafe impl<T: Sized, UT> HandleAlloc<UT> for T {
fn new_handle(value: Arc<Self>) -> Handle {
Handle::from_pointer(Arc::into_raw(value))
}

fn clone_handle(handle: Handle) -> Handle {
unsafe { Arc::increment_strong_count(handle.as_pointer::<T>()) };
handle
}

fn consume_handle(handle: Handle) -> Arc<Self> {
unsafe { Arc::from_raw(handle.as_pointer()) }
}
}
3 changes: 2 additions & 1 deletion uniffi_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand Down

0 comments on commit c3e6007

Please sign in to comment.