diff options
Diffstat (limited to 'third_party/rust/uniffi_core/src')
18 files changed, 1360 insertions, 1603 deletions
diff --git a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs index 7be66880bb..e7a4faab64 100644 --- a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs +++ b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs @@ -91,121 +91,20 @@ //! //! Uniffi generates a protocol or interface in client code in the foreign language must implement. //! -//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals` -//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface. +//! For each callback interface, UniFFI defines a VTable. +//! This is a `repr(C)` struct where each field is a `repr(C)` callback function pointer. +//! There is one field for each method, plus an extra field for the `uniffi_free` method. +//! The foreign code registers one VTable per callback interface with Rust. //! -//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the -//! `KeychainCallbackInternals` to store the instance in a handlemap. -//! -//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements -//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to -//! client code as `Box<dyn Keychain>`. -//! -//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`. -//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the -//! object handle, and the method selector. -//! -//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap, -//! and calls the actual implementation of the method. -//! -//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for -//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct -//! type and then returns to client code. +//! VTable methods have a similar signature to Rust scaffolding functions. +//! The one difference is that values are returned via an out pointer to work around a Python bug (https://bugs.python.org/issue5710). //! +//! The foreign object that implements the interface is represented by an opaque handle. +//! UniFFI generates a struct that implements the trait by calling VTable methods, passing the handle as the first parameter. +//! When the struct is dropped, the `uniffi_free` method is called. -use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer}; use std::fmt; -/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, -/// and it can be deleted from the handle map. -pub const IDX_CALLBACK_FREE: u32 = 0; - -/// Result of a foreign callback invocation -#[repr(i32)] -#[derive(Debug, PartialEq, Eq)] -pub enum CallbackResult { - /// Successful call. - /// The return value is serialized to `buf_ptr`. - Success = 0, - /// Expected error. - /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result. - /// The error value is serialized to `buf_ptr`. - Error = 1, - /// Unexpected error. - /// An error message string is serialized to `buf_ptr`. - UnexpectedError = 2, -} - -impl TryFrom<i32> for CallbackResult { - // On errors we return the unconverted value - type Error = i32; - - fn try_from(value: i32) -> Result<Self, i32> { - match value { - 0 => Ok(Self::Success), - 1 => Ok(Self::Error), - 2 => Ok(Self::UnexpectedError), - n => Err(n), - } - } -} - -/// Struct to hold a foreign callback. -pub struct ForeignCallbackInternals { - callback_cell: ForeignCallbackCell, -} - -impl ForeignCallbackInternals { - pub const fn new() -> Self { - ForeignCallbackInternals { - callback_cell: ForeignCallbackCell::new(), - } - } - - pub fn set_callback(&self, callback: ForeignCallback) { - self.callback_cell.set(callback); - } - - /// Invoke a callback interface method on the foreign side and return the result - pub fn invoke_callback<R, UniFfiTag>(&self, handle: u64, method: u32, args: RustBuffer) -> R - where - R: LiftReturn<UniFfiTag>, - { - let mut ret_rbuf = RustBuffer::new(); - let callback = self.callback_cell.get(); - let raw_result = unsafe { - callback( - handle, - method, - args.data_pointer(), - args.len() as i32, - &mut ret_rbuf, - ) - }; - let result = CallbackResult::try_from(raw_result) - .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}")); - match result { - CallbackResult::Success => R::lift_callback_return(ret_rbuf), - CallbackResult::Error => R::lift_callback_error(ret_rbuf), - CallbackResult::UnexpectedError => { - let reason = if !ret_rbuf.is_empty() { - match <String as Lift<UniFfiTag>>::try_lift(ret_rbuf) { - Ok(s) => s, - Err(e) => { - log::error!("{{ trait_name }} Error reading ret_buf: {e}"); - String::from("[Error reading reason]") - } - } - } else { - RustBuffer::destroy(ret_rbuf); - String::from("[Unknown Reason]") - }; - R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason }) - } - } - } -} - /// Used when internal/unexpected error happened when calling a foreign callback, for example when /// a unknown exception is raised /// @@ -216,8 +115,10 @@ pub struct UnexpectedUniFFICallbackError { } impl UnexpectedUniFFICallbackError { - pub fn from_reason(reason: String) -> Self { - Self { reason } + pub fn new(reason: impl fmt::Display) -> Self { + Self { + reason: reason.to_string(), + } } } diff --git a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs index 1f86f6b13b..a992ab7384 100644 --- a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs +++ b/third_party/rust/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() @@ -51,9 +57,10 @@ impl FfiDefault for crate::RustBuffer { } } -impl FfiDefault for crate::ForeignExecutorHandle { +impl FfiDefault for crate::ForeignFuture { fn ffi_default() -> Self { - Self(std::ptr::null()) + extern "C" fn free(_handle: u64) {} + crate::ForeignFuture { handle: 0, free } } } diff --git a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs index ac2463cd8e..326ff12747 100644 --- a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs +++ b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs @@ -8,96 +8,32 @@ //! code loads the exported library. For each callback type, we also define a "cell" type for //! storing the callback. -use std::sync::atomic::{AtomicUsize, Ordering}; - -use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback}; - -/// ForeignCallback is the Rust representation of a foreign language function. -/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, -/// at library start up time. -/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language. -/// -/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object -/// that implements the callback interface/trait. -/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from -/// the IDL. The list is 1 indexed. Note that the list of methods is generated by UniFFI from the IDL and used in all -/// bindings, so we can rely on the method list being stable within the same run of UniFFI. -/// * `args_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code -/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the -/// arguments from the buffer and passes them to the user's callback. -/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a -/// buffer to write the result into. -/// * Callbacks return one of the `CallbackResult` values -/// Note: The output buffer might still contain 0 bytes of data. -pub type ForeignCallback = unsafe extern "C" fn( - handle: u64, - method: u32, - args_data: *const u8, - args_len: i32, - buf_ptr: *mut RustBuffer, -) -> i32; - -/// Callback to schedule a Rust call with a `ForeignExecutor`. The bindings code registers exactly -/// one of these with the Rust code. -/// -/// Delay is an approximate amount of ms to wait before scheduling the call. Delay is usually 0, -/// which means schedule sometime soon. -/// -/// As a special case, when Rust drops the foreign executor, with `task=null`. The foreign -/// bindings should release the reference to the executor that was reserved for Rust. -/// -/// This callback can be invoked from any thread, including threads created by Rust. -/// -/// The callback should return one of the `ForeignExecutorCallbackResult` values. -pub type ForeignExecutorCallback = extern "C" fn( - executor: ForeignExecutorHandle, - delay: u32, - task: Option<RustTaskCallback>, - task_data: *const (), -) -> i8; - -/// Store a [ForeignCallback] pointer -pub(crate) struct ForeignCallbackCell(AtomicUsize); - -/// Store a [ForeignExecutorCallback] pointer -pub(crate) struct ForeignExecutorCallbackCell(AtomicUsize); - -/// Macro to define foreign callback types as well as the callback cell. -macro_rules! impl_foreign_callback_cell { - ($callback_type:ident, $cell_type:ident) => { - // Overly-paranoid sanity checking to ensure that these types are - // convertible between each-other. `transmute` actually should check this for - // us too, but this helps document the invariants we rely on in this code. - // - // Note that these are guaranteed by - // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html - // and thus this is a little paranoid. - static_assertions::assert_eq_size!(usize, $callback_type); - static_assertions::assert_eq_size!(usize, Option<$callback_type>); - - impl $cell_type { - pub const fn new() -> Self { - Self(AtomicUsize::new(0)) - } - - pub fn set(&self, callback: $callback_type) { - // Store the pointer using Ordering::Relaxed. This is sufficient since callback - // should be set at startup, before there's any chance of using them. - self.0.store(callback as usize, Ordering::Relaxed); - } - - pub fn get(&self) -> $callback_type { - let ptr_value = self.0.load(Ordering::Relaxed); - unsafe { - // SAFETY: self.0 was set in `set` from our function pointer type, so - // it's safe to transmute it back here. - ::std::mem::transmute::<usize, Option<$callback_type>>(ptr_value) - .expect("Bug: callback not set. This is likely a uniffi bug.") - } - } +use std::{ + ptr::{null_mut, NonNull}, + sync::atomic::{AtomicPtr, Ordering}, +}; + +// Cell type that stores any NonNull<T> +#[doc(hidden)] +pub struct UniffiForeignPointerCell<T>(AtomicPtr<T>); + +impl<T> UniffiForeignPointerCell<T> { + pub const fn new() -> Self { + Self(AtomicPtr::new(null_mut())) + } + + pub fn set(&self, callback: NonNull<T>) { + self.0.store(callback.as_ptr(), Ordering::Relaxed); + } + + pub fn get(&self) -> &T { + unsafe { + NonNull::new(self.0.load(Ordering::Relaxed)) + .expect("Foreign pointer not set. This is likely a uniffi bug.") + .as_mut() } - }; + } } -impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell); -impl_foreign_callback_cell!(ForeignExecutorCallback, ForeignExecutorCallbackCell); +unsafe impl<T> Send for UniffiForeignPointerCell<T> {} +unsafe impl<T> Sync for UniffiForeignPointerCell<T> {} diff --git a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs b/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs deleted file mode 100644 index 7b1cb9bd80..0000000000 --- a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs +++ /dev/null @@ -1,487 +0,0 @@ -/* 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/. */ - -//! Schedule tasks using a foreign executor. - -use std::panic; - -use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell}; - -/// Opaque handle for a foreign task executor. -/// -/// Foreign code can either use an actual pointer, or use an integer value casted to it. -#[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct ForeignExecutorHandle(pub(crate) *const ()); - -// Implement Send + Sync for `ForeignExecutor`. The foreign bindings code is responsible for -// making the `ForeignExecutorCallback` thread-safe. -unsafe impl Send for ForeignExecutorHandle {} - -unsafe impl Sync for ForeignExecutorHandle {} - -/// Result code returned by `ForeignExecutorCallback` -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum ForeignExecutorCallbackResult { - /// Callback was scheduled successfully - Success = 0, - /// Callback couldn't be scheduled because the foreign executor is canceled/closed. - Cancelled = 1, - /// Callback couldn't be scheduled because of some other error - Error = 2, -} - -impl ForeignExecutorCallbackResult { - /// Check the result code for the foreign executor callback - /// - /// If the result was `ForeignExecutorCallbackResult.Success`, this method returns `true`. - /// - /// If not, this method returns `false`, logging errors for any unexpected return values - pub fn check_result_code(result: i8) -> bool { - match result { - n if n == ForeignExecutorCallbackResult::Success as i8 => true, - n if n == ForeignExecutorCallbackResult::Cancelled as i8 => false, - n if n == ForeignExecutorCallbackResult::Error as i8 => { - log::error!( - "ForeignExecutorCallbackResult::Error returned by foreign executor callback" - ); - false - } - n => { - log::error!("Unknown code ({n}) returned by foreign executor callback"); - false - } - } - } -} - -// Option<RustTaskCallback> should use the null pointer optimization and be represented in C as a -// regular pointer. Let's check that. -static_assertions::assert_eq_size!(usize, Option<RustTaskCallback>); - -/// Callback for a Rust task, this is what the foreign executor invokes -/// -/// The task will be passed the `task_data` passed to `ForeignExecutorCallback` in addition to one -/// of the `RustTaskCallbackCode` values. -pub type RustTaskCallback = extern "C" fn(*const (), RustTaskCallbackCode); - -/// Passed to a `RustTaskCallback` function when the executor invokes them. -/// -/// Every `RustTaskCallback` will be invoked eventually, this code is used to distinguish the times -/// when it's invoked successfully vs times when the callback is being called because the foreign -/// executor has been cancelled / shutdown -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum RustTaskCallbackCode { - /// Successful task callback invocation - Success = 0, - /// The `ForeignExecutor` has been cancelled. - /// - /// This signals that any progress using the executor should be halted. In particular, Futures - /// should not continue to progress. - Cancelled = 1, -} - -static FOREIGN_EXECUTOR_CALLBACK: ForeignExecutorCallbackCell = ForeignExecutorCallbackCell::new(); - -/// Set the global ForeignExecutorCallback. This is called by the foreign bindings, normally -/// during initialization. -pub fn foreign_executor_callback_set(callback: ForeignExecutorCallback) { - FOREIGN_EXECUTOR_CALLBACK.set(callback); -} - -/// Schedule Rust calls using a foreign executor -#[derive(Debug)] -pub struct ForeignExecutor { - pub(crate) handle: ForeignExecutorHandle, -} - -impl ForeignExecutor { - pub fn new(executor: ForeignExecutorHandle) -> Self { - Self { handle: executor } - } - - /// Schedule a closure to be run. - /// - /// This method can be used for "fire-and-forget" style calls, where the calling code doesn't - /// need to await the result. - /// - /// Closure requirements: - /// - Send: since the closure will likely run on a different thread - /// - 'static: since it runs at an arbitrary time, so all references need to be 'static - /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data - pub fn schedule<F: FnOnce() + Send + 'static + panic::UnwindSafe>(&self, delay: u32, task: F) { - let leaked_ptr: *mut F = Box::leak(Box::new(task)); - if !schedule_raw( - self.handle, - delay, - schedule_callback::<F>, - leaked_ptr as *const (), - ) { - // If schedule_raw() failed, drop the leaked box since `schedule_callback()` has not been - // scheduled to run. - unsafe { - drop(Box::<F>::from_raw(leaked_ptr)); - }; - } - } - - /// Schedule a closure to be run and get a Future for the result - /// - /// Closure requirements: - /// - Send: since the closure will likely run on a different thread - /// - 'static: since it runs at an arbitrary time, so all references need to be 'static - /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data - pub async fn run<F, T>(&self, delay: u32, closure: F) -> T - where - F: FnOnce() -> T + Send + 'static + panic::UnwindSafe, - T: Send + 'static, - { - // Create a oneshot channel to handle the future - let (sender, receiver) = oneshot::channel(); - // We can use `AssertUnwindSafe` here because: - // - The closure is unwind safe - // - `Sender` is not marked unwind safe, maybe this is just an oversight in the oneshot - // library. However, calling `send()` and dropping the Sender should certainly be - // unwind safe. `send()` should probably not panic at all and if it does it shouldn't - // do it in a way that breaks the Receiver. - // - Calling `expect` may result in a panic, but this should should not break either the - // Sender or Receiver. - self.schedule( - delay, - panic::AssertUnwindSafe(move || { - sender.send(closure()).expect("Error sending future result") - }), - ); - receiver.await.expect("Error receiving future result") - } -} - -/// Low-level schedule interface -/// -/// When using this function, take care to ensure that the `ForeignExecutor` that holds the -/// `ForeignExecutorHandle` has not been dropped. -/// -/// Returns true if the callback was successfully scheduled -pub(crate) fn schedule_raw( - handle: ForeignExecutorHandle, - delay: u32, - callback: RustTaskCallback, - data: *const (), -) -> bool { - let result_code = (FOREIGN_EXECUTOR_CALLBACK.get())(handle, delay, Some(callback), data); - ForeignExecutorCallbackResult::check_result_code(result_code) -} - -impl Drop for ForeignExecutor { - fn drop(&mut self) { - (FOREIGN_EXECUTOR_CALLBACK.get())(self.handle, 0, None, std::ptr::null()); - } -} - -extern "C" fn schedule_callback<F>(data: *const (), status_code: RustTaskCallbackCode) -where - F: FnOnce() + Send + 'static + panic::UnwindSafe, -{ - // No matter what, we need to call Box::from_raw() to balance the Box::leak() call. - let task = unsafe { Box::from_raw(data as *mut F) }; - // Skip running the task for the `RustTaskCallbackCode::Cancelled` code - if status_code == RustTaskCallbackCode::Success { - run_task(task); - } -} - -/// Run a scheduled task, catching any panics. -/// -/// If there are panics, then we will log a warning and return None. -fn run_task<F: FnOnce() -> T + panic::UnwindSafe, T>(task: F) -> Option<T> { - match panic::catch_unwind(task) { - Ok(v) => Some(v), - Err(cause) => { - let message = if let Some(s) = cause.downcast_ref::<&'static str>() { - (*s).to_string() - } else if let Some(s) = cause.downcast_ref::<String>() { - s.clone() - } else { - "Unknown panic!".to_string() - }; - log::warn!("Error calling UniFFI callback function: {message}"); - None - } - } -} - -#[cfg(test)] -pub use test::MockEventLoop; - -#[cfg(test)] -mod test { - use super::*; - use std::{ - future::Future, - pin::Pin, - sync::{ - atomic::{AtomicU32, Ordering}, - Arc, Mutex, Once, - }, - task::{Context, Poll, Wake, Waker}, - }; - - /// Simulate an event loop / task queue / coroutine scope on the foreign side - /// - /// This simply collects scheduled calls into a Vec for testing purposes. - /// - /// Most of the MockEventLoop methods are `pub` since it's also used by the `rustfuture` tests. - pub struct MockEventLoop { - // Wrap everything in a mutex since we typically share access to MockEventLoop via an Arc - inner: Mutex<MockEventLoopInner>, - } - - pub struct MockEventLoopInner { - // calls that have been scheduled - calls: Vec<(u32, Option<RustTaskCallback>, *const ())>, - // has the event loop been shutdown? - is_shutdown: bool, - } - - unsafe impl Send for MockEventLoopInner {} - - static FOREIGN_EXECUTOR_CALLBACK_INIT: Once = Once::new(); - - impl MockEventLoop { - pub fn new() -> Arc<Self> { - // Make sure we install a foreign executor callback that can deal with mock event loops - FOREIGN_EXECUTOR_CALLBACK_INIT - .call_once(|| foreign_executor_callback_set(mock_executor_callback)); - - Arc::new(Self { - inner: Mutex::new(MockEventLoopInner { - calls: vec![], - is_shutdown: false, - }), - }) - } - - /// Create a new ForeignExecutorHandle - pub fn new_handle(self: &Arc<Self>) -> ForeignExecutorHandle { - // To keep the memory management simple, we simply leak an arc reference for this. We - // only create a handful of these in the tests so there's no need for proper cleanup. - ForeignExecutorHandle(Arc::into_raw(Arc::clone(self)) as *const ()) - } - - pub fn new_executor(self: &Arc<Self>) -> ForeignExecutor { - ForeignExecutor { - handle: self.new_handle(), - } - } - - /// Get the current number of scheduled calls - pub fn call_count(&self) -> usize { - self.inner.lock().unwrap().calls.len() - } - - /// Get the last scheduled call - pub fn last_call(&self) -> (u32, Option<RustTaskCallback>, *const ()) { - self.inner - .lock() - .unwrap() - .calls - .last() - .cloned() - .expect("no calls scheduled") - } - - /// Run all currently scheduled calls - pub fn run_all_calls(&self) { - let mut inner = self.inner.lock().unwrap(); - let is_shutdown = inner.is_shutdown; - for (_delay, callback, data) in inner.calls.drain(..) { - if !is_shutdown { - callback.unwrap()(data, RustTaskCallbackCode::Success); - } else { - callback.unwrap()(data, RustTaskCallbackCode::Cancelled); - } - } - } - - /// Shutdown the eventloop, causing scheduled calls and future calls to be cancelled - pub fn shutdown(&self) { - self.inner.lock().unwrap().is_shutdown = true; - } - } - - // `ForeignExecutorCallback` that we install for testing - extern "C" fn mock_executor_callback( - handle: ForeignExecutorHandle, - delay: u32, - task: Option<RustTaskCallback>, - task_data: *const (), - ) -> i8 { - let eventloop = handle.0 as *const MockEventLoop; - let mut inner = unsafe { (*eventloop).inner.lock().unwrap() }; - if inner.is_shutdown { - ForeignExecutorCallbackResult::Cancelled as i8 - } else { - inner.calls.push((delay, task, task_data)); - ForeignExecutorCallbackResult::Success as i8 - } - } - - #[test] - fn test_schedule_raw() { - extern "C" fn callback(data: *const (), _status_code: RustTaskCallbackCode) { - unsafe { - *(data as *mut u32) += 1; - } - } - - let eventloop = MockEventLoop::new(); - - let value: u32 = 0; - assert_eq!(eventloop.call_count(), 0); - - schedule_raw( - eventloop.new_handle(), - 0, - callback, - &value as *const u32 as *const (), - ); - assert_eq!(eventloop.call_count(), 1); - assert_eq!(value, 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(value, 1); - } - - #[test] - fn test_schedule() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - let value = Arc::new(AtomicU32::new(0)); - assert_eq!(eventloop.call_count(), 0); - - let value2 = value.clone(); - executor.schedule(0, move || { - value2.fetch_add(1, Ordering::Relaxed); - }); - assert_eq!(eventloop.call_count(), 1); - assert_eq!(value.load(Ordering::Relaxed), 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(value.load(Ordering::Relaxed), 1); - } - - #[derive(Default)] - struct MockWaker { - wake_count: AtomicU32, - } - - impl Wake for MockWaker { - fn wake(self: Arc<Self>) { - self.wake_count.fetch_add(1, Ordering::Relaxed); - } - } - - #[test] - fn test_run() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - let mock_waker = Arc::new(MockWaker::default()); - let waker = Waker::from(mock_waker.clone()); - let mut context = Context::from_waker(&waker); - assert_eq!(eventloop.call_count(), 0); - - let mut future = executor.run(0, move || "test-return-value"); - unsafe { - assert_eq!( - Pin::new_unchecked(&mut future).poll(&mut context), - Poll::Pending - ); - } - assert_eq!(eventloop.call_count(), 1); - assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 1); - unsafe { - assert_eq!( - Pin::new_unchecked(&mut future).poll(&mut context), - Poll::Ready("test-return-value") - ); - } - } - - #[test] - fn test_drop() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - - drop(executor); - // Calling drop should schedule a call with null task data. - assert_eq!(eventloop.call_count(), 1); - assert_eq!(eventloop.last_call().1, None); - } - - // Test that cancelled calls never run - #[test] - fn test_cancelled_call() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - // Create a shared counter - let counter = Arc::new(AtomicU32::new(0)); - // schedule increments using both `schedule()` and run()` - let counter_clone = Arc::clone(&counter); - executor.schedule(0, move || { - counter_clone.fetch_add(1, Ordering::Relaxed); - }); - let counter_clone = Arc::clone(&counter); - let future = executor.run(0, move || { - counter_clone.fetch_add(1, Ordering::Relaxed); - }); - // shutdown the eventloop before the scheduled call gets a chance to run. - eventloop.shutdown(); - // `run_all_calls()` will cause the scheduled task callbacks to run, but will pass - // `RustTaskCallbackCode::Cancelled` to it. This drop the scheduled closure without executing - // it. - eventloop.run_all_calls(); - - assert_eq!(counter.load(Ordering::Relaxed), 0); - drop(future); - } - - // Test that when scheduled calls are cancelled, the closures are dropped properly - #[test] - fn test_cancellation_drops_closures() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - - // Create an Arc<> that we will move into the closures to test if they are dropped or not - let arc = Arc::new(0); - let arc_clone = Arc::clone(&arc); - executor.schedule(0, move || assert_eq!(*arc_clone, 0)); - let arc_clone = Arc::clone(&arc); - let future = executor.run(0, move || assert_eq!(*arc_clone, 0)); - - // shutdown the eventloop and run the (cancelled) scheduled calls. - eventloop.shutdown(); - eventloop.run_all_calls(); - // try to schedule some more calls now that the loop has been shutdown - let arc_clone = Arc::clone(&arc); - executor.schedule(0, move || assert_eq!(*arc_clone, 0)); - let arc_clone = Arc::clone(&arc); - let future2 = executor.run(0, move || assert_eq!(*arc_clone, 0)); - - // Drop the futures so they don't hold on to any references - drop(future); - drop(future2); - - // All of these closures should have been dropped by now, there only remaining arc - // reference should be the original - assert_eq!(Arc::strong_count(&arc), 1); - } -} diff --git a/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs new file mode 100644 index 0000000000..be6a214e84 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs @@ -0,0 +1,241 @@ +/* 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/. */ + +//! This module defines a Rust Future that wraps an async foreign function call. +//! +//! The general idea is to create a [oneshot::Channel], hand the sender to the foreign side, and +//! await the receiver side on the Rust side. +//! +//! The foreign side should: +//! * Input a [ForeignFutureCallback] and a `u64` handle in their scaffolding function. +//! This is the sender, converted to a raw pointer, and an extern "C" function that sends the result. +//! * Return a [ForeignFuture], which represents the foreign task object corresponding to the async function. +//! * Call the [ForeignFutureCallback] when the async function completes with: +//! * The `u64` handle initially passed in +//! * The `ForeignFutureResult` for the call +//! * Wait for the [ForeignFutureHandle::free] function to be called to free the task object. +//! If this is called before the task completes, then the task will be cancelled. + +use crate::{LiftReturn, RustCallStatus, UnexpectedUniFFICallbackError}; + +/// Handle for a foreign future +pub type ForeignFutureHandle = u64; + +/// Handle for a callback data associated with a foreign future. +pub type ForeignFutureCallbackData = *mut (); + +/// Callback that's passed to a foreign async functions. +/// +/// See `LiftReturn` trait for how this is implemented. +pub type ForeignFutureCallback<FfiType> = + extern "C" fn(oneshot_handle: u64, ForeignFutureResult<FfiType>); + +/// C struct that represents the result of a foreign future +#[repr(C)] +pub struct ForeignFutureResult<T> { + // Note: for void returns, T is `()`, which isn't directly representable with C since it's a ZST. + // Foreign code should treat that case as if there was no `return_value` field. + return_value: T, + call_status: RustCallStatus, +} + +/// Perform a call to a foreign async method + +/// C struct that represents the foreign future. +/// +/// This is what's returned by the async scaffolding functions. +#[repr(C)] +pub struct ForeignFuture { + pub handle: ForeignFutureHandle, + pub free: extern "C" fn(handle: ForeignFutureHandle), +} + +impl Drop for ForeignFuture { + fn drop(&mut self) { + (self.free)(self.handle) + } +} + +unsafe impl Send for ForeignFuture {} + +pub async fn foreign_async_call<F, T, UT>(call_scaffolding_function: F) -> T +where + F: FnOnce(ForeignFutureCallback<T::ReturnType>, u64) -> ForeignFuture, + T: LiftReturn<UT>, +{ + let (sender, receiver) = oneshot::channel::<ForeignFutureResult<T::ReturnType>>(); + // Keep the ForeignFuture around, even though we don't ever use it. + // The important thing is that the ForeignFuture will be dropped when this Future is. + let _foreign_future = + call_scaffolding_function(foreign_future_complete::<T, UT>, sender.into_raw() as u64); + match receiver.await { + Ok(result) => T::lift_foreign_return(result.return_value, result.call_status), + Err(e) => { + // This shouldn't happen in practice, but we can do our best to recover + T::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(format!( + "Error awaiting foreign future: {e}" + ))) + } + } +} + +pub extern "C" fn foreign_future_complete<T: LiftReturn<UT>, UT>( + oneshot_handle: u64, + result: ForeignFutureResult<T::ReturnType>, +) { + let channel = unsafe { oneshot::Sender::from_raw(oneshot_handle as *mut ()) }; + // Ignore errors in send. + // + // Error means the receiver was already dropped which will happen when the future is cancelled. + let _ = channel.send(result); +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{Lower, RustBuffer}; + use once_cell::sync::OnceCell; + use std::{ + future::Future, + pin::Pin, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, + task::{Context, Poll, Wake}, + }; + + struct MockForeignFuture { + freed: Arc<AtomicU32>, + callback_info: Arc<OnceCell<(ForeignFutureCallback<RustBuffer>, u64)>>, + rust_future: Option<Pin<Box<dyn Future<Output = String>>>>, + } + + impl MockForeignFuture { + fn new() -> Self { + let callback_info = Arc::new(OnceCell::new()); + let freed = Arc::new(AtomicU32::new(0)); + + let rust_future: Pin<Box<dyn Future<Output = String>>> = { + let callback_info = callback_info.clone(); + let freed = freed.clone(); + Box::pin(foreign_async_call::<_, String, crate::UniFfiTag>( + move |callback, data| { + callback_info.set((callback, data)).unwrap(); + ForeignFuture { + handle: Arc::into_raw(freed) as *mut () as u64, + free: Self::free, + } + }, + )) + }; + let rust_future = Some(rust_future); + let mut mock_foreign_future = Self { + freed, + callback_info, + rust_future, + }; + // Poll the future once, to start it up. This ensures that `callback_info` is set. + let _ = mock_foreign_future.poll(); + mock_foreign_future + } + + fn poll(&mut self) -> Poll<String> { + let waker = Arc::new(NoopWaker).into(); + let mut context = Context::from_waker(&waker); + self.rust_future + .as_mut() + .unwrap() + .as_mut() + .poll(&mut context) + } + + fn complete_success(&self, value: String) { + let (callback, data) = self.callback_info.get().unwrap(); + callback( + *data, + ForeignFutureResult { + return_value: <String as Lower<crate::UniFfiTag>>::lower(value), + call_status: RustCallStatus::new(), + }, + ); + } + + fn complete_error(&self, error_message: String) { + let (callback, data) = self.callback_info.get().unwrap(); + callback( + *data, + ForeignFutureResult { + return_value: RustBuffer::default(), + call_status: RustCallStatus::error(error_message), + }, + ); + } + + fn drop_future(&mut self) { + self.rust_future = None + } + + fn free_count(&self) -> u32 { + self.freed.load(Ordering::Relaxed) + } + + extern "C" fn free(handle: u64) { + let flag = unsafe { Arc::from_raw(handle as *mut AtomicU32) }; + flag.fetch_add(1, Ordering::Relaxed); + } + } + + struct NoopWaker; + + impl Wake for NoopWaker { + fn wake(self: Arc<Self>) {} + } + + #[test] + fn test_foreign_future() { + let mut mock_foreign_future = MockForeignFuture::new(); + assert_eq!(mock_foreign_future.poll(), Poll::Pending); + mock_foreign_future.complete_success("It worked!".to_owned()); + assert_eq!( + mock_foreign_future.poll(), + Poll::Ready("It worked!".to_owned()) + ); + // Since the future is complete, it should free the foreign future + assert_eq!(mock_foreign_future.free_count(), 1); + } + + #[test] + #[should_panic] + fn test_foreign_future_error() { + let mut mock_foreign_future = MockForeignFuture::new(); + assert_eq!(mock_foreign_future.poll(), Poll::Pending); + mock_foreign_future.complete_error("It Failed!".to_owned()); + let _ = mock_foreign_future.poll(); + } + + #[test] + fn test_drop_after_complete() { + let mut mock_foreign_future = MockForeignFuture::new(); + mock_foreign_future.complete_success("It worked!".to_owned()); + assert_eq!(mock_foreign_future.free_count(), 0); + assert_eq!( + mock_foreign_future.poll(), + Poll::Ready("It worked!".to_owned()) + ); + // Dropping the future after it's complete should not panic, and not cause a double-free + mock_foreign_future.drop_future(); + assert_eq!(mock_foreign_future.free_count(), 1); + } + + #[test] + fn test_drop_before_complete() { + let mut mock_foreign_future = MockForeignFuture::new(); + mock_foreign_future.complete_success("It worked!".to_owned()); + // Dropping the future before it's complete should cancel the future + assert_eq!(mock_foreign_future.free_count(), 0); + mock_foreign_future.drop_future(); + assert_eq!(mock_foreign_future.free_count(), 1); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/handle.rs b/third_party/rust/uniffi_core/src/ffi/handle.rs new file mode 100644 index 0000000000..8ee2f46c35 --- /dev/null +++ b/third_party/rust/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<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 + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/mod.rs b/third_party/rust/uniffi_core/src/ffi/mod.rs index b606323297..acaf2b0d06 100644 --- a/third_party/rust/uniffi_core/src/ffi/mod.rs +++ b/third_party/rust/uniffi_core/src/ffi/mod.rs @@ -8,7 +8,8 @@ pub mod callbackinterface; pub mod ffidefault; pub mod foreignbytes; pub mod foreigncallbacks; -pub mod foreignexecutor; +pub mod foreignfuture; +pub mod handle; pub mod rustbuffer; pub mod rustcalls; pub mod rustfuture; @@ -17,7 +18,8 @@ pub use callbackinterface::*; pub use ffidefault::FfiDefault; pub use foreignbytes::*; pub use foreigncallbacks::*; -pub use foreignexecutor::*; +pub use foreignfuture::*; +pub use handle::*; pub use rustbuffer::*; pub use rustcalls::*; pub use rustfuture::*; diff --git a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs index e09e3be89a..8b2972968c 100644 --- a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs +++ b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs @@ -52,11 +52,11 @@ use crate::ffi::{rust_call, ForeignBytes, RustCallStatus}; #[derive(Debug)] pub struct RustBuffer { /// The allocated capacity of the underlying `Vec<u8>`. - /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. - capacity: i32, + /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple. + capacity: u64, /// The occupied length of the underlying `Vec<u8>`. - /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. - len: i32, + /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple. + len: u64, /// The pointer to the allocated buffer of the `Vec<u8>`. data: *mut u8, } @@ -84,7 +84,7 @@ impl RustBuffer { /// # Safety /// /// You must ensure that the raw parts uphold the documented invariants of this class. - pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self { + pub unsafe fn from_raw_parts(data: *mut u8, len: u64, capacity: u64) -> Self { Self { capacity, len, @@ -126,12 +126,8 @@ impl RustBuffer { /// /// Panics if the requested size is too large to fit in an `i32`, and /// hence would risk incompatibility with some foreign-language code. - pub fn new_with_size(size: usize) -> Self { - assert!( - size < i32::MAX as usize, - "RustBuffer requested size too large" - ); - Self::from_vec(vec![0u8; size]) + pub fn new_with_size(size: u64) -> Self { + Self::from_vec(vec![0u8; size as usize]) } /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`. @@ -144,8 +140,8 @@ impl RustBuffer { /// Panics if the vector's length or capacity are too large to fit in an `i32`, /// and hence would risk incompatibility with some foreign-language code. pub fn from_vec(v: Vec<u8>) -> Self { - let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32."); - let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32."); + let capacity = u64::try_from(v.capacity()).expect("buffer capacity cannot fit into a u64."); + let len = u64::try_from(v.len()).expect("buffer length cannot fit into a u64."); let mut v = std::mem::ManuallyDrop::new(v); unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) } } @@ -198,39 +194,18 @@ impl Default for RustBuffer { } } -// extern "C" functions for the RustBuffer functionality. +// Functions for the RustBuffer functionality. // -// These are used in two ways: -// 1. Code that statically links to UniFFI can use these directly to handle RustBuffer -// allocation/destruction. The plan is to use this for the Firefox desktop JS bindings. -// -// 2. The scaffolding code re-exports these functions, prefixed with the component name and UDL -// hash This creates a separate set of functions for each UniFFIed component, which is needed -// in the case where we create multiple dylib artifacts since each dylib will have its own -// allocator. +// The scaffolding code re-exports these functions, prefixed with the component name and UDL hash +// This creates a separate set of functions for each UniFFIed component, which is needed in the +// case where we create multiple dylib artifacts since each dylib will have its own allocator. /// This helper allocates a new byte buffer owned by the Rust code, and returns it /// to the foreign-language code as a `RustBuffer` struct. Callers must eventually /// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined /// below, or by passing ownership of the buffer back into Rust code. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_alloc( - size: i32, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_alloc(size, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] -pub fn uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { - _uniffi_rustbuffer_alloc(size, call_status) -} - -fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { - rust_call(call_status, || { - Ok(RustBuffer::new_with_size(size.max(0) as usize)) - }) +pub fn uniffi_rustbuffer_alloc(size: u64, call_status: &mut RustCallStatus) -> RustBuffer { + rust_call(call_status, || Ok(RustBuffer::new_with_size(size))) } /// This helper copies bytes owned by the foreign-language code into a new byte buffer owned @@ -241,27 +216,10 @@ fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> Rust /// # Safety /// This function will dereference a provided pointer in order to copy bytes from it, so /// make sure the `ForeignBytes` struct contains a valid pointer and length. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_from_bytes( - bytes: ForeignBytes, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_from_bytes(bytes, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] pub fn uniffi_rustbuffer_from_bytes( bytes: ForeignBytes, call_status: &mut RustCallStatus, ) -> RustBuffer { - _uniffi_rustbuffer_from_bytes(bytes, call_status) -} - -fn _uniffi_rustbuffer_from_bytes( - bytes: ForeignBytes, - call_status: &mut RustCallStatus, -) -> RustBuffer { rust_call(call_status, || { let bytes = bytes.as_slice(); Ok(RustBuffer::from_vec(bytes.to_vec())) @@ -274,18 +232,7 @@ fn _uniffi_rustbuffer_from_bytes( /// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call /// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or /// corrupting the allocator state. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { - _uniffi_rustbuffer_free(buf, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { - _uniffi_rustbuffer_free(buf, call_status) -} - -fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { rust_call(call_status, || { RustBuffer::destroy(buf); Ok(()) @@ -307,28 +254,9 @@ fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { /// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call /// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or /// corrupting the allocator state. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_reserve( - buf: RustBuffer, - additional: i32, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_reserve(buf, additional, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] pub fn uniffi_rustbuffer_reserve( buf: RustBuffer, - additional: i32, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_reserve(buf, additional, call_status) -} - -fn _uniffi_rustbuffer_reserve( - buf: RustBuffer, - additional: i32, + additional: u64, call_status: &mut RustCallStatus, ) -> RustBuffer { rust_call(call_status, || { @@ -394,24 +322,6 @@ mod test { #[test] #[should_panic] - fn test_rustbuffer_provided_capacity_must_be_non_negative() { - // We guard against foreign-language code providing this kind of invalid struct. - let mut v = vec![0u8, 1, 2]; - let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) }; - rbuf.destroy_into_vec(); - } - - #[test] - #[should_panic] - fn test_rustbuffer_provided_len_must_be_non_negative() { - // We guard against foreign-language code providing this kind of invalid struct. - let mut v = vec![0u8, 1, 2]; - let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) }; - rbuf.destroy_into_vec(); - } - - #[test] - #[should_panic] fn test_rustbuffer_provided_len_must_not_exceed_capacity() { // We guard against foreign-language code providing this kind of invalid struct. let mut v = vec![0u8, 1, 2]; diff --git a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs index 53265393c0..16b0c76f2e 100644 --- a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs +++ b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs @@ -56,6 +56,13 @@ pub struct RustCallStatus { } impl RustCallStatus { + pub fn new() -> Self { + Self { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + pub fn cancelled() -> Self { Self { code: RustCallStatusCode::Cancelled, @@ -102,7 +109,7 @@ pub enum RustCallStatusCode { /// Handle a scaffolding calls /// /// `callback` is responsible for making the actual Rust call and returning a special result type: -/// - For successfull calls, return `Ok(value)` +/// - For successful calls, return `Ok(value)` /// - For errors that should be translated into thrown exceptions in the foreign code, serialize /// the error into a `RustBuffer`, then return `Ok(buf)` /// - The success type, must implement `FfiDefault`. diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture.rs deleted file mode 100644 index 0c1a24174b..0000000000 --- a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs +++ /dev/null @@ -1,735 +0,0 @@ -/* 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/. */ - -//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. -//! -//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. -//! -//! # The big picture -//! -//! We implement async foreign functions using a simplified version of the Future API: -//! -//! 0. At startup, register a [RustFutureContinuationCallback] by calling -//! rust_future_continuation_callback_set. -//! 1. Call the scaffolding function to get a [RustFutureHandle] -//! 2a. In a loop: -//! - Call [rust_future_poll] -//! - Suspend the function until the [rust_future_poll] continuation function is called -//! - If the continuation was function was called with [RustFuturePoll::Ready], then break -//! otherwise continue. -//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the -//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to -//! enter a cancelled state. -//! 3. Call [rust_future_complete] to get the result of the future. -//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: -//! - Releases any resources held by the future -//! - Calls any continuation callbacks that have not been called yet -//! -//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` -//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", -//! and manually monomorphized in the case of [rust_future_complete]. See -//! `uniffi_macros/src/setup_scaffolding.rs` for details. -//! -//! ## How does `Future` work exactly? -//! -//! A [`Future`] in Rust does nothing. When calling an async function, it just -//! returns a `Future` but nothing has happened yet. To start the computation, -//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if -//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically -//! means: -//! -//! > Please, try to poll me later, maybe the result will be ready! -//! -//! This model is very different than what other languages do, but it can actually -//! be translated quite easily, fortunately for us! -//! -//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does -//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the -//! `Future`: that's where they are polled. -//! -//! But… wait another minute… how does the executor know when to poll a [`Future`]? -//! Does it poll them randomly in an endless loop? Well, no, actually it depends -//! on the executor! A well-designed `Future` and executor work as follows. -//! Normally, when [`Future::poll`] is called, a [`Context`] argument is -//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a -//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will -//! signal the executor to poll a particular `Future`. A `Future` will clone -//! or pass-by-ref the waker to somewhere, as a callback, a completion, a -//! function, or anything, to the system that is responsible to notify when a -//! task is completed. So, to recap, the waker is _not_ responsible for waking the -//! `Future`, it _is_ responsible for _signaling_ the executor that a particular -//! `Future` should be polled again. That's why the documentation of -//! [`Poll::Pending`] specifies: -//! -//! > When a function returns `Pending`, the function must also ensure that the -//! > current task is scheduled to be awoken when progress can be made. -//! -//! “awakening” is done by using the `Waker`. -//! -//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html -//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll -//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready -//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending -//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html -//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html -//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html - -use std::{ - future::Future, - marker::PhantomData, - mem, - ops::Deref, - panic, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll, Wake}, -}; - -use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; - -/// Result code for [rust_future_poll]. This is passed to the continuation function. -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum RustFuturePoll { - /// The future is ready and is waiting for [rust_future_complete] to be called - Ready = 0, - /// The future might be ready and [rust_future_poll] should be called again - MaybeReady = 1, -} - -/// Foreign callback that's passed to [rust_future_poll] -/// -/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again -/// to continue progress on the future. -pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), 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] -/// -/// 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<F, T, UT>(future: F, tag: UT) -> RustFutureHandle -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 - // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, - // since we synchronize all access to the values. - F: Future<Output = T> + Send + 'static, - // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + - // 'static for the same reason as F. - T: LowerReturn<UT> + Send + 'static, - // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. - UT: Send + 'static, -{ - // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to - // implement the FFI - let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>; - // 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 ()) -} - -/// Poll a Rust future -/// -/// When the future is ready to progress the continuation will be called with the `data` value and -/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called -/// exactly once. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll<ReturnType>( - handle: RustFutureHandle, - callback: RustFutureContinuationCallback, - data: *const (), -) { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.clone().ffi_poll(callback, data) -} - -/// Cancel a Rust future -/// -/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. -/// -/// This is needed for languages like Swift, which continuation to wait for the continuation to be -/// called when tasks are cancelled. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.clone().ffi_cancel() -} - -/// Complete a Rust future -/// -/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for -/// each supported FFI type. -/// -/// # Safety -/// -/// - The [RustFutureHandle] 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 `<Output as LowerReturn<UT>>::ReturnType` -pub unsafe fn rust_future_complete<ReturnType>( - handle: RustFutureHandle, - out_status: &mut RustCallStatus, -) -> ReturnType { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.ffi_complete(out_status) -} - -/// Free a Rust future, dropping the strong reference and releasing all references held by the -/// future. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_free<ReturnType>(handle: RustFutureHandle) { - let future = Box::from_raw(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.ffi_free() -} - -/// Thread-safe storage for [RustFutureContinuationCallback] data -/// -/// The basic guarantee is that all data pointers passed in are passed out exactly once to the -/// foreign continuation callback. This enables us to uphold the [rust_future_poll] guarantee. -/// -/// [ContinuationDataCell] also tracks cancellation, which is closely tied to continuation data. -#[derive(Debug)] -enum ContinuationDataCell { - /// No continuations set, neither wake() nor cancel() called. - Empty, - /// `wake()` was called when there was no continuation set. The next time `store` is called, - /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` - Waked, - /// The future has been cancelled, any future `store` calls should immediately result in the - /// continuation being called with `RustFuturePoll::Ready`. - Cancelled, - /// Continuation set, the next time `wake()` is called is called, we should invoke it. - Set(RustFutureContinuationCallback, *const ()), -} - -impl ContinuationDataCell { - fn new() -> Self { - Self::Empty - } - - /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or - /// `Cancelled` state, call the continuation immediately with the data. - fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) { - match self { - Self::Empty => *self = Self::Set(callback, data), - Self::Set(old_callback, old_data) => { - log::error!( - "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" - ); - old_callback(*old_data, RustFuturePoll::Ready); - *self = Self::Set(callback, data); - } - Self::Waked => { - *self = Self::Empty; - callback(data, RustFuturePoll::MaybeReady); - } - Self::Cancelled => { - callback(data, RustFuturePoll::Ready); - } - } - } - - fn wake(&mut self) { - match self { - // If we had a continuation set, then call it and transition to the `Empty` state. - Self::Set(callback, old_data) => { - let old_data = *old_data; - let callback = *callback; - *self = Self::Empty; - callback(old_data, RustFuturePoll::MaybeReady); - } - // If we were in the `Empty` state, then transition to `Waked`. The next time `store` - // is called, we will immediately call the continuation. - Self::Empty => *self = Self::Waked, - // This is a no-op if we were in the `Cancelled` or `Waked` state. - _ => (), - } - } - - fn cancel(&mut self) { - if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { - callback(old_data, RustFuturePoll::Ready); - } - } - - fn is_cancelled(&self) -> bool { - matches!(self, Self::Cancelled) - } -} - -// ContinuationDataCell is Send + Sync as long we handle the *const () pointer correctly - -unsafe impl Send for ContinuationDataCell {} -unsafe impl Sync for ContinuationDataCell {} - -/// Wraps the actual future we're polling -struct WrappedFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - // Note: this could be a single enum, but that would make it easy to mess up the future pinning - // guarantee. For example you might want to call `std::mem::take()` to try to get the result, - // but if the future happened to be stored that would move and break all internal references. - future: Option<F>, - result: Option<Result<T::ReturnType, RustCallStatus>>, -} - -impl<F, T, UT> WrappedFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn new(future: F) -> Self { - Self { - future: Some(future), - result: None, - } - } - - // Poll the future and check if it's ready or not - fn poll(&mut self, context: &mut Context<'_>) -> bool { - if self.result.is_some() { - true - } else if let Some(future) = &mut self.future { - // SAFETY: We can call Pin::new_unchecked because: - // - This is the only time we get a &mut to `self.future` - // - We never poll the future after it's moved (for example by using take()) - // - We never move RustFuture, which contains us. - // - RustFuture is private to this module so no other code can move it. - let pinned = unsafe { Pin::new_unchecked(future) }; - // Run the poll and lift the result if it's ready - let mut out_status = RustCallStatus::default(); - let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status( - &mut out_status, - // This closure uses a `&mut F` value, which means it's not UnwindSafe by - // default. If the future panics, it may be in an invalid state. - // - // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` - // case below and we will never poll the future again. - panic::AssertUnwindSafe(|| match pinned.poll(context) { - Poll::Pending => Ok(Poll::Pending), - Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), - }), - ); - match result { - Some(Poll::Pending) => false, - Some(Poll::Ready(v)) => { - self.future = None; - self.result = Some(Ok(v)); - true - } - None => { - self.future = None; - self.result = Some(Err(out_status)); - true - } - } - } else { - log::error!("poll with neither future nor result set"); - true - } - } - - fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { - let mut return_value = T::ReturnType::ffi_default(); - match self.result.take() { - Some(Ok(v)) => return_value = v, - Some(Err(call_status)) => *out_status = call_status, - None => *out_status = RustCallStatus::cancelled(), - } - self.free(); - return_value - } - - fn free(&mut self) { - self.future = None; - self.result = None; - } -} - -// If F and T are Send, then WrappedFuture is too -// -// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising -// that we will treat the raw pointer properly, for example by not returning it twice. -unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ -} - -/// Future that the foreign code is awaiting -struct RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - // This Mutex should never block if our code is working correctly, since there should not be - // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. - future: Mutex<WrappedFuture<F, T, UT>>, - continuation_data: Mutex<ContinuationDataCell>, - // UT is used as the generic parameter for [LowerReturn]. - // Let's model this with PhantomData as a function that inputs a UT value. - _phantom: PhantomData<fn(UT) -> ()>, -} - -impl<F, T, UT> RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn new(future: F, _tag: UT) -> Arc<Self> { - Arc::new(Self { - future: Mutex::new(WrappedFuture::new(future)), - continuation_data: Mutex::new(ContinuationDataCell::new()), - _phantom: PhantomData, - }) - } - - fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { - let ready = self.is_cancelled() || { - let mut locked = self.future.lock().unwrap(); - let waker: std::task::Waker = Arc::clone(&self).into(); - locked.poll(&mut Context::from_waker(&waker)) - }; - if ready { - callback(data, RustFuturePoll::Ready) - } else { - self.continuation_data.lock().unwrap().store(callback, data); - } - } - - fn is_cancelled(&self) -> bool { - self.continuation_data.lock().unwrap().is_cancelled() - } - - fn wake(&self) { - self.continuation_data.lock().unwrap().wake(); - } - - fn cancel(&self) { - self.continuation_data.lock().unwrap().cancel(); - } - - fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { - self.future.lock().unwrap().complete(call_status) - } - - fn free(self: Arc<Self>) { - // Call cancel() to send any leftover data to the continuation callback - self.continuation_data.lock().unwrap().cancel(); - // Ensure we drop our inner future, releasing all held references - self.future.lock().unwrap().free(); - } -} - -impl<F, T, UT> Wake for RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn wake(self: Arc<Self>) { - self.deref().wake() - } - - fn wake_by_ref(self: &Arc<Self>) { - self.deref().wake() - } -} - -/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to -/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this -/// allows you to use RustFuture functionality without knowing the concrete Future type, which is -/// unnamable. -/// -/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of -/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need -/// to create a poll, cancel, complete, and free scaffolding function for each exported async -/// function. That would add ~1kb binary size per exported function based on a quick estimate on a -/// 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)] -trait RustFutureFfi<ReturnType> { - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()); - fn ffi_cancel(&self); - fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; - fn ffi_free(self: Arc<Self>); -} - -impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { - self.poll(callback, data) - } - - fn ffi_cancel(&self) { - self.cancel() - } - - fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { - self.complete(call_status) - } - - fn ffi_free(self: Arc<Self>) { - self.free(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; - use once_cell::sync::OnceCell; - use std::task::Waker; - - // Sender/Receiver pair that we use for testing - struct Channel { - result: Option<Result<String, TestError>>, - waker: Option<Waker>, - } - - struct Sender(Arc<Mutex<Channel>>); - - impl Sender { - fn wake(&self) { - let inner = self.0.lock().unwrap(); - if let Some(waker) = &inner.waker { - waker.wake_by_ref(); - } - } - - fn send(&self, value: Result<String, TestError>) { - let mut inner = self.0.lock().unwrap(); - if inner.result.replace(value).is_some() { - panic!("value already sent"); - } - if let Some(waker) = &inner.waker { - waker.wake_by_ref(); - } - } - } - - struct Receiver(Arc<Mutex<Channel>>); - - impl Future for Receiver { - type Output = Result<String, TestError>; - - fn poll( - self: Pin<&mut Self>, - context: &mut Context<'_>, - ) -> Poll<Result<String, TestError>> { - let mut inner = self.0.lock().unwrap(); - match &inner.result { - Some(v) => Poll::Ready(v.clone()), - None => { - inner.waker = Some(context.waker().clone()); - Poll::Pending - } - } - } - } - - // Create a sender and rust future that we can use for testing - fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) { - let channel = Arc::new(Mutex::new(Channel { - result: None, - waker: None, - })); - let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); - (Sender(channel), rust_future) - } - - /// Poll a Rust future and get an OnceCell that's set when the continuation is called - fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> { - let cell = Arc::new(OnceCell::new()); - let cell_ptr = Arc::into_raw(cell.clone()) as *const (); - rust_future.clone().ffi_poll(poll_continuation, cell_ptr); - cell - } - - extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) { - let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; - cell.set(code).expect("Error setting OnceCell"); - } - - fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) { - let mut out_status_code = RustCallStatus::default(); - let return_value = rust_future.ffi_complete(&mut out_status_code); - (return_value, out_status_code) - } - - #[test] - fn test_success() { - let (sender, rust_future) = channel(); - - // Test polling the rust future before it's ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.wake(); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - // Test polling the rust future when it's ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.send(Ok("All done".into())); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - // Future polls should immediately return ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - // Complete the future - let (return_buf, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), - "All done" - ); - } - - #[test] - fn test_error() { - let (sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.send(Err("Something went wrong".into())); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - let (_, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Error); - unsafe { - assert_eq!( - <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer( - call_status.error_buf.assume_init() - ) - .unwrap(), - TestError::from("Something went wrong"), - ) - } - } - - // Once `complete` is called, the inner future should be released, even if wakers still hold a - // reference to the RustFuture - #[test] - fn test_cancel() { - let (_sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - rust_future.ffi_cancel(); - // Cancellation should immediately invoke the callback with RustFuturePoll::Ready - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - // Future polls should immediately invoke the callback with RustFuturePoll::Ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - let (_, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Cancelled); - } - - // Once `free` is called, the inner future should be released, even if wakers still hold a - // reference to the RustFuture - #[test] - fn test_release_future() { - let (sender, rust_future) = channel(); - // Create a weak reference to the channel to use to check if rust_future has dropped its - // future. - let channel_weak = Arc::downgrade(&sender.0); - drop(sender); - // Create an extra ref to rust_future, simulating a waker that still holds a reference to - // it - let rust_future2 = rust_future.clone(); - - // Complete the rust future - rust_future.ffi_free(); - // Even though rust_future is still alive, the channel shouldn't be - assert!(Arc::strong_count(&rust_future2) > 0); - assert_eq!(channel_weak.strong_count(), 0); - assert!(channel_weak.upgrade().is_none()); - } - - // If `free` is called with a continuation still stored, we should call it them then. - // - // This shouldn't happen in practice, but it seems like good defensive programming - #[test] - fn test_complete_with_stored_continuation() { - let (_sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - rust_future.ffi_free(); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - } - - // Test what happens if we see a `wake()` call while we're polling the future. This can - // happen, for example, with futures that are handled by a tokio thread pool. We should - // schedule another poll of the future in this case. - #[test] - fn test_wake_during_poll() { - let mut first_time = true; - let future = std::future::poll_fn(move |ctx| { - if first_time { - first_time = false; - // Wake the future while we are in the middle of polling it - ctx.waker().clone().wake(); - Poll::Pending - } else { - // The second time we're polled, we're ready - Poll::Ready("All done".to_owned()) - } - }); - let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = - RustFuture::new(future, crate::UniFfiTag); - let continuation_result = poll(&rust_future); - // The continuation function should called immediately - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - // A second poll should finish the future - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - let (return_buf, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), - "All done" - ); - } -} diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs new file mode 100644 index 0000000000..93c34e7543 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs @@ -0,0 +1,320 @@ +/* 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/. */ + +//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. +//! +//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. +//! +//! # The big picture +//! +//! We implement async foreign functions using a simplified version of the Future API: +//! +//! 0. At startup, register a [RustFutureContinuationCallback] by calling +//! rust_future_continuation_callback_set. +//! 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 +//! - If the continuation was function was called with [RustFuturePoll::Ready], then break +//! otherwise continue. +//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the +//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to +//! enter a cancelled state. +//! 3. Call [rust_future_complete] to get the result of the future. +//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: +//! - Releases any resources held by the future +//! - Calls any continuation callbacks that have not been called yet +//! +//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` +//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", +//! and manually monomorphized in the case of [rust_future_complete]. See +//! `uniffi_macros/src/setup_scaffolding.rs` for details. +//! +//! ## How does `Future` work exactly? +//! +//! A [`Future`] in Rust does nothing. When calling an async function, it just +//! returns a `Future` but nothing has happened yet. To start the computation, +//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if +//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically +//! means: +//! +//! > Please, try to poll me later, maybe the result will be ready! +//! +//! This model is very different than what other languages do, but it can actually +//! be translated quite easily, fortunately for us! +//! +//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does +//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the +//! `Future`: that's where they are polled. +//! +//! But… wait another minute… how does the executor know when to poll a [`Future`]? +//! Does it poll them randomly in an endless loop? Well, no, actually it depends +//! on the executor! A well-designed `Future` and executor work as follows. +//! Normally, when [`Future::poll`] is called, a [`Context`] argument is +//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a +//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will +//! signal the executor to poll a particular `Future`. A `Future` will clone +//! or pass-by-ref the waker to somewhere, as a callback, a completion, a +//! function, or anything, to the system that is responsible to notify when a +//! task is completed. So, to recap, the waker is _not_ responsible for waking the +//! `Future`, it _is_ responsible for _signaling_ the executor that a particular +//! `Future` should be polled again. That's why the documentation of +//! [`Poll::Pending`] specifies: +//! +//! > When a function returns `Pending`, the function must also ensure that the +//! > current task is scheduled to be awoken when progress can be made. +//! +//! “awakening” is done by using the `Waker`. +//! +//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html +//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll +//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready +//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending +//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html +//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html +//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html + +use std::{ + future::Future, + marker::PhantomData, + ops::Deref, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Wake}, +}; + +use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler}; +use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; + +/// Wraps the actual future we're polling +struct WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // Note: this could be a single enum, but that would make it easy to mess up the future pinning + // guarantee. For example you might want to call `std::mem::take()` to try to get the result, + // but if the future happened to be stored that would move and break all internal references. + future: Option<F>, + result: Option<Result<T::ReturnType, RustCallStatus>>, +} + +impl<F, T, UT> WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn new(future: F) -> Self { + Self { + future: Some(future), + result: None, + } + } + + // Poll the future and check if it's ready or not + fn poll(&mut self, context: &mut Context<'_>) -> bool { + if self.result.is_some() { + true + } else if let Some(future) = &mut self.future { + // SAFETY: We can call Pin::new_unchecked because: + // - This is the only time we get a &mut to `self.future` + // - We never poll the future after it's moved (for example by using take()) + // - We never move RustFuture, which contains us. + // - RustFuture is private to this module so no other code can move it. + let pinned = unsafe { Pin::new_unchecked(future) }; + // Run the poll and lift the result if it's ready + let mut out_status = RustCallStatus::default(); + let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status( + &mut out_status, + // This closure uses a `&mut F` value, which means it's not UnwindSafe by + // default. If the future panics, it may be in an invalid state. + // + // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` + // case below and we will never poll the future again. + panic::AssertUnwindSafe(|| match pinned.poll(context) { + Poll::Pending => Ok(Poll::Pending), + Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), + }), + ); + match result { + Some(Poll::Pending) => false, + Some(Poll::Ready(v)) => { + self.future = None; + self.result = Some(Ok(v)); + true + } + None => { + self.future = None; + self.result = Some(Err(out_status)); + true + } + } + } else { + log::error!("poll with neither future nor result set"); + true + } + } + + fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { + let mut return_value = T::ReturnType::ffi_default(); + match self.result.take() { + Some(Ok(v)) => return_value = v, + Some(Err(call_status)) => *out_status = call_status, + None => *out_status = RustCallStatus::cancelled(), + } + self.free(); + return_value + } + + fn free(&mut self) { + self.future = None; + self.result = None; + } +} + +// If F and T are Send, then WrappedFuture is too +// +// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising +// that we will treat the raw pointer properly, for example by not returning it twice. +unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ +} + +/// Future that the foreign code is awaiting +pub(super) struct RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // This Mutex should never block if our code is working correctly, since there should not be + // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. + future: Mutex<WrappedFuture<F, T, UT>>, + scheduler: Mutex<Scheduler>, + // UT is used as the generic parameter for [LowerReturn]. + // Let's model this with PhantomData as a function that inputs a UT value. + _phantom: PhantomData<fn(UT) -> ()>, +} + +impl<F, T, UT> RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + pub(super) fn new(future: F, _tag: UT) -> Arc<Self> { + Arc::new(Self { + future: Mutex::new(WrappedFuture::new(future)), + scheduler: Mutex::new(Scheduler::new()), + _phantom: PhantomData, + }) + } + + pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { + let ready = self.is_cancelled() || { + let mut locked = self.future.lock().unwrap(); + let waker: std::task::Waker = Arc::clone(&self).into(); + locked.poll(&mut Context::from_waker(&waker)) + }; + if ready { + callback(data, RustFuturePoll::Ready) + } else { + self.scheduler.lock().unwrap().store(callback, data); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + self.scheduler.lock().unwrap().is_cancelled() + } + + pub(super) fn wake(&self) { + self.scheduler.lock().unwrap().wake(); + } + + pub(super) fn cancel(&self) { + self.scheduler.lock().unwrap().cancel(); + } + + pub(super) fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.future.lock().unwrap().complete(call_status) + } + + pub(super) fn free(self: Arc<Self>) { + // Call cancel() to send any leftover data to the continuation callback + self.scheduler.lock().unwrap().cancel(); + // Ensure we drop our inner future, releasing all held references + self.future.lock().unwrap().free(); + } +} + +impl<F, T, UT> Wake for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn wake(self: Arc<Self>) { + self.deref().wake() + } + + fn wake_by_ref(self: &Arc<Self>) { + self.deref().wake() + } +} + +/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to +/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this +/// allows you to use RustFuture functionality without knowing the concrete Future type, which is +/// unnamable. +/// +/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of +/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need +/// to create a poll, cancel, complete, and free scaffolding function for each exported async +/// function. That would add ~1kb binary size per exported function based on a quick estimate on a +/// 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>: 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; + fn ffi_free(self: Arc<Self>); +} + +impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { + self.poll(callback, data) + } + + fn ffi_cancel(&self) { + self.cancel() + } + + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.complete(call_status) + } + + fn ffi_free(self: Arc<Self>) { + self.free(); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs new file mode 100644 index 0000000000..3d3505e5ef --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs @@ -0,0 +1,141 @@ +/* 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/. */ + +use std::{future::Future, sync::Arc}; + +mod future; +mod scheduler; +use future::*; +use scheduler::*; + +#[cfg(test)] +mod tests; + +use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; + +/// Result code for [rust_future_poll]. This is passed to the continuation function. +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustFuturePoll { + /// The future is ready and is waiting for [rust_future_complete] to be called + Ready = 0, + /// The future might be ready and [rust_future_poll] should be called again + MaybeReady = 1, +} + +/// Foreign callback that's passed to [rust_future_poll] +/// +/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again +/// to continue progress on the future. +pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, RustFuturePoll); + +// === Public FFI API === + +/// 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 [Handle] to pass to the foreign code. +pub fn rust_future_new<F, T, UT>(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 + // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, + // since we synchronize all access to the values. + F: Future<Output = T> + Send + 'static, + // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + + // 'static for the same reason as F. + T: LowerReturn<UT> + Send + 'static, + // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. + UT: Send + 'static, + // Needed to allocate a handle + dyn RustFutureFfi<T::ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<T::ReturnType> as HandleAlloc<UT>>::new_handle( + RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>> + ) +} + +/// Poll a Rust future +/// +/// When the future is ready to progress the continuation will be called with the `data` value and +/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called +/// exactly once. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll<ReturnType, UT>( + handle: Handle, + callback: RustFutureContinuationCallback, + data: u64, +) where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_poll(callback, data) +} + +/// Cancel a Rust future +/// +/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. +/// +/// This is needed for languages like Swift, which continuation to wait for the continuation to be +/// called when tasks are cancelled. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel<ReturnType, UT>(handle: Handle) +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_cancel() +} + +/// Complete a Rust future +/// +/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for +/// each supported FFI type. +/// +/// # Safety +/// +/// - 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 `<Output as LowerReturn<UT>>::ReturnType` +pub unsafe fn rust_future_complete<ReturnType, UT>( + handle: Handle, + out_status: &mut RustCallStatus, +) -> ReturnType +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_complete(out_status) +} + +/// Free a Rust future, dropping the strong reference and releasing all references held by the +/// future. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free<ReturnType, UT>(handle: Handle) +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::consume_handle(handle).ffi_free() +} + +// Derive HandleAlloc for dyn RustFutureFfi<T> for all FFI return types +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u8>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i8>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u16>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i16>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<*const std::ffi::c_void>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<crate::RustBuffer>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<()>); diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs new file mode 100644 index 0000000000..629ee0c109 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs @@ -0,0 +1,96 @@ +/* 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/. */ + +use std::mem; + +use super::{RustFutureContinuationCallback, RustFuturePoll}; + +/// Schedules a [crate::RustFuture] by managing the continuation data +/// +/// This struct manages the continuation callback and data that comes from the foreign side. It +/// is responsible for calling the continuation callback when the future is ready to be woken up. +/// +/// The basic guarantees are: +/// +/// * Each callback will be invoked exactly once, with its associated data. +/// * If `wake()` is called, the callback will be invoked to wake up the future -- either +/// immediately or the next time we get a callback. +/// * If `cancel()` is called, the same will happen and the schedule will stay in the cancelled +/// state, invoking any future callbacks as soon as they're stored. + +#[derive(Debug)] +pub(super) enum Scheduler { + /// No continuations set, neither wake() nor cancel() called. + Empty, + /// `wake()` was called when there was no continuation set. The next time `store` is called, + /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` + Waked, + /// The future has been cancelled, any future `store` calls should immediately result in the + /// continuation being called with `RustFuturePoll::Ready`. + Cancelled, + /// Continuation set, the next time `wake()` is called is called, we should invoke it. + Set(RustFutureContinuationCallback, u64), +} + +impl Scheduler { + pub(super) fn new() -> Self { + Self::Empty + } + + /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or + /// `Cancelled` state, call the continuation immediately with the data. + pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: u64) { + match self { + Self::Empty => *self = Self::Set(callback, data), + Self::Set(old_callback, old_data) => { + log::error!( + "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" + ); + old_callback(*old_data, RustFuturePoll::Ready); + *self = Self::Set(callback, data); + } + Self::Waked => { + *self = Self::Empty; + callback(data, RustFuturePoll::MaybeReady); + } + Self::Cancelled => { + callback(data, RustFuturePoll::Ready); + } + } + } + + pub(super) fn wake(&mut self) { + match self { + // If we had a continuation set, then call it and transition to the `Empty` state. + Self::Set(callback, old_data) => { + let old_data = *old_data; + let callback = *callback; + *self = Self::Empty; + callback(old_data, RustFuturePoll::MaybeReady); + } + // If we were in the `Empty` state, then transition to `Waked`. The next time `store` + // is called, we will immediately call the continuation. + Self::Empty => *self = Self::Waked, + // This is a no-op if we were in the `Cancelled` or `Waked` state. + _ => (), + } + } + + pub(super) fn cancel(&mut self) { + if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { + callback(old_data, RustFuturePoll::Ready); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + matches!(self, Self::Cancelled) + } +} + +// The `*const ()` data pointer references an object on the foreign side. +// This object must be `Sync` in Rust terminology -- it must be safe for us to pass the pointer to the continuation callback from any thread. +// If the foreign side upholds their side of the contract, then `Scheduler` is Send + Sync. + +unsafe impl Send for Scheduler {} +unsafe impl Sync for Scheduler {} diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs new file mode 100644 index 0000000000..886ee27c71 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs @@ -0,0 +1,223 @@ +use once_cell::sync::OnceCell; +use std::{ + future::Future, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, +}; + +use super::*; +use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; + +// Sender/Receiver pair that we use for testing +struct Channel { + result: Option<Result<String, TestError>>, + waker: Option<Waker>, +} + +struct Sender(Arc<Mutex<Channel>>); + +impl Sender { + fn wake(&self) { + let inner = self.0.lock().unwrap(); + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } + + fn send(&self, value: Result<String, TestError>) { + let mut inner = self.0.lock().unwrap(); + if inner.result.replace(value).is_some() { + panic!("value already sent"); + } + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } +} + +struct Receiver(Arc<Mutex<Channel>>); + +impl Future for Receiver { + type Output = Result<String, TestError>; + + fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<String, TestError>> { + let mut inner = self.0.lock().unwrap(); + match &inner.result { + Some(v) => Poll::Ready(v.clone()), + None => { + inner.waker = Some(context.waker().clone()); + Poll::Pending + } + } + } +} + +// Create a sender and rust future that we can use for testing +fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) { + let channel = Arc::new(Mutex::new(Channel { + result: None, + waker: None, + })); + let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); + (Sender(channel), rust_future) +} + +/// Poll a Rust future and get an OnceCell that's set when the continuation is called +fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> { + let cell = Arc::new(OnceCell::new()); + let handle = Arc::into_raw(cell.clone()) as u64; + rust_future.clone().ffi_poll(poll_continuation, handle); + cell +} + +extern "C" fn poll_continuation(data: u64, code: RustFuturePoll) { + let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; + cell.set(code).expect("Error setting OnceCell"); +} + +fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) { + let mut out_status_code = RustCallStatus::default(); + let return_value = rust_future.ffi_complete(&mut out_status_code); + (return_value, out_status_code) +} + +#[test] +fn test_success() { + let (sender, rust_future) = channel(); + + // Test polling the rust future before it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.wake(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Test polling the rust future when it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Ok("All done".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Future polls should immediately return ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Complete the future + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); +} + +#[test] +fn test_error() { + let (sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Err("Something went wrong".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Error); + unsafe { + assert_eq!( + <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer( + call_status.error_buf.assume_init() + ) + .unwrap(), + TestError::from("Something went wrong"), + ) + } +} + +// Once `complete` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_cancel() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + rust_future.ffi_cancel(); + // Cancellation should immediately invoke the callback with RustFuturePoll::Ready + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Future polls should immediately invoke the callback with RustFuturePoll::Ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Cancelled); +} + +// Once `free` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_release_future() { + let (sender, rust_future) = channel(); + // Create a weak reference to the channel to use to check if rust_future has dropped its + // future. + let channel_weak = Arc::downgrade(&sender.0); + drop(sender); + // Create an extra ref to rust_future, simulating a waker that still holds a reference to + // it + let rust_future2 = rust_future.clone(); + + // Complete the rust future + rust_future.ffi_free(); + // Even though rust_future is still alive, the channel shouldn't be + assert!(Arc::strong_count(&rust_future2) > 0); + assert_eq!(channel_weak.strong_count(), 0); + assert!(channel_weak.upgrade().is_none()); +} + +// If `free` is called with a continuation still stored, we should call it them then. +// +// This shouldn't happen in practice, but it seems like good defensive programming +#[test] +fn test_complete_with_stored_continuation() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + rust_future.ffi_free(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); +} + +// Test what happens if we see a `wake()` call while we're polling the future. This can +// happen, for example, with futures that are handled by a tokio thread pool. We should +// schedule another poll of the future in this case. +#[test] +fn test_wake_during_poll() { + let mut first_time = true; + let future = std::future::poll_fn(move |ctx| { + if first_time { + first_time = false; + // Wake the future while we are in the middle of polling it + ctx.waker().clone().wake(); + Poll::Pending + } else { + // The second time we're polled, we're ready + Poll::Ready("All done".to_owned()) + } + }); + let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = RustFuture::new(future, crate::UniFfiTag); + let continuation_result = poll(&rust_future); + // The continuation function should called immediately + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + // A second poll should finish the future + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); +} diff --git a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs index af18f3873b..aec093154a 100644 --- a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs +++ b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs @@ -20,11 +20,11 @@ /// /// This crate needs to implement `FFIConverter<UT>` on `UniFfiTag` instances for all UniFFI /// consumer crates. To do this, it defines blanket impls like `impl<UT> FFIConverter<UT> for u8`. -/// "UT" means an abitrary `UniFfiTag` type. +/// "UT" means an arbitrary `UniFfiTag` type. use crate::{ check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata, - ConvertError, FfiConverter, ForeignExecutor, Lift, LiftReturn, Lower, LowerReturn, - MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError, + ConvertError, FfiConverter, Lift, LiftRef, LiftReturn, Lower, LowerReturn, MetadataBuffer, + Result, RustBuffer, UnexpectedUniFFICallbackError, }; use anyhow::bail; use bytes::buf::{Buf, BufMut}; @@ -405,47 +405,6 @@ where .concat(V::TYPE_ID_META); } -/// FFI support for [ForeignExecutor] -/// -/// These are passed over the FFI as opaque pointer-sized types representing the foreign executor. -/// The foreign bindings may use an actual pointer to the executor object, or a usized integer -/// handle. -unsafe impl<UT> FfiConverter<UT> for ForeignExecutor { - type FfiType = crate::ForeignExecutorHandle; - - // Passing these back to the foreign bindings is currently not supported - fn lower(executor: Self) -> Self::FfiType { - executor.handle - } - - fn write(executor: Self, buf: &mut Vec<u8>) { - // Use native endian when writing these values, so they can be casted to pointer values - match std::mem::size_of::<usize>() { - // Use native endian when reading these values, so they can be casted to pointer values - 4 => buf.put_u32_ne(executor.handle.0 as u32), - 8 => buf.put_u64_ne(executor.handle.0 as u64), - n => panic!("Invalid usize width: {n}"), - }; - } - - fn try_lift(executor: Self::FfiType) -> Result<Self> { - Ok(ForeignExecutor::new(executor)) - } - - fn try_read(buf: &mut &[u8]) -> Result<Self> { - let usize_val = match std::mem::size_of::<usize>() { - // Use native endian when reading these values, so they can be casted to pointer values - 4 => buf.get_u32_ne() as usize, - 8 => buf.get_u64_ne() as usize, - n => panic!("Invalid usize width: {n}"), - }; - <Self as FfiConverter<UT>>::try_lift(crate::ForeignExecutorHandle(usize_val as *const ())) - } - - const TYPE_ID_META: MetadataBuffer = - MetadataBuffer::from_code(metadata::codes::TYPE_FOREIGN_EXECUTOR); -} - derive_ffi_traits!(blanket u8); derive_ffi_traits!(blanket i8); derive_ffi_traits!(blanket u16); @@ -460,7 +419,6 @@ derive_ffi_traits!(blanket bool); derive_ffi_traits!(blanket String); derive_ffi_traits!(blanket Duration); derive_ffi_traits!(blanket SystemTime); -derive_ffi_traits!(blanket ForeignExecutor); // For composite types, derive LowerReturn, LiftReturn, etc, from Lift/Lower. // @@ -498,7 +456,11 @@ unsafe impl<UT> LowerReturn<UT> for () { } unsafe impl<UT> LiftReturn<UT> for () { - fn lift_callback_return(_buf: RustBuffer) -> Self {} + type ReturnType = (); + + fn try_lift_successful_return(_: ()) -> Result<Self> { + Ok(()) + } const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); } @@ -535,13 +497,15 @@ where unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E> where R: LiftReturn<UT>, - E: Lift<UT> + ConvertError<UT>, + E: Lift<UT, FfiType = RustBuffer> + ConvertError<UT>, { - fn lift_callback_return(buf: RustBuffer) -> Self { - Ok(R::lift_callback_return(buf)) + type ReturnType = R::ReturnType; + + fn try_lift_successful_return(v: R::ReturnType) -> Result<Self> { + R::try_lift_successful_return(v).map(Ok) } - fn lift_callback_error(buf: RustBuffer) -> Self { + fn lift_error(buf: RustBuffer) -> Self { match E::try_lift_from_rust_buffer(buf) { Ok(lifted_error) => Err(lifted_error), Err(anyhow_error) => { @@ -560,3 +524,14 @@ where .concat(R::TYPE_ID_META) .concat(E::TYPE_ID_META); } + +unsafe impl<T, UT> LiftRef<UT> for [T] +where + T: Lift<UT>, +{ + type LiftType = Vec<T>; +} + +unsafe impl<UT> LiftRef<UT> for str { + type LiftType = String; +} diff --git a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs index 3b5914e32f..4e7b9e06fa 100644 --- a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs +++ b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs @@ -51,7 +51,10 @@ 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, RustCallStatus, RustCallStatusCode, + UnexpectedUniFFICallbackError, +}; /// Generalized FFI conversions /// @@ -302,14 +305,41 @@ pub unsafe trait LowerReturn<UT>: Sized { /// 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. pub unsafe trait LiftReturn<UT>: Sized { - /// Lift a Rust value for a callback interface method result - fn lift_callback_return(buf: RustBuffer) -> Self; + /// FFI return type for trait interfaces + type ReturnType; + + /// Lift a successfully returned value from a trait interface + fn try_lift_successful_return(v: Self::ReturnType) -> Result<Self>; + + /// Lift a foreign returned value from a trait interface + /// + /// When we call a foreign-implemented trait interface method, we pass a &mut RustCallStatus + /// and get [Self::ReturnType] returned. This method takes both of those and lifts `Self` from + /// it. + fn lift_foreign_return(ffi_return: Self::ReturnType, call_status: RustCallStatus) -> Self { + match call_status.code { + RustCallStatusCode::Success => Self::try_lift_successful_return(ffi_return) + .unwrap_or_else(|e| { + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + }), + RustCallStatusCode::Error => { + Self::lift_error(unsafe { call_status.error_buf.assume_init() }) + } + _ => { + let e = <String as FfiConverter<crate::UniFfiTag>>::try_lift(unsafe { + call_status.error_buf.assume_init() + }) + .unwrap_or_else(|e| format!("(Error lifting message: {e}")); + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + } + } + } /// Lift a Rust value for a callback interface method error result /// /// This is called for "expected errors" -- the callback method returns a Result<> type and the /// foreign code throws an exception that corresponds to the error type. - fn lift_callback_error(_buf: RustBuffer) -> Self { + fn lift_error(_buf: RustBuffer) -> Self { panic!("Callback interface method returned unexpected error") } @@ -351,6 +381,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. +/// * 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>: 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<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: @@ -439,9 +529,10 @@ macro_rules! derive_ffi_traits { (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)* { - fn lift_callback_return(buf: $crate::RustBuffer) -> Self { - <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf) - .expect("Error reading callback interface result") + type ReturnType = <Self as $crate::Lift<$ut>>::FfiType; + + fn try_lift_successful_return(v: Self::ReturnType) -> $crate::Result<Self> { + <Self as $crate::Lift<$ut>>::try_lift(v) } const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META; @@ -463,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<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: Send + Sync, 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()) } + } } diff --git a/third_party/rust/uniffi_core/src/lib.rs b/third_party/rust/uniffi_core/src/lib.rs index c84b403dce..1f3a2403f8 100644 --- a/third_party/rust/uniffi_core/src/lib.rs +++ b/third_party/rust/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::*; @@ -57,9 +58,8 @@ pub mod deps { pub use async_compat; pub use bytes; pub use log; + pub use oneshot; pub use static_assertions; - // Export this dependency for the 0.25 branch so that we can use it in `setup_scaffolding.rs` - pub use once_cell; } mod panichook; diff --git a/third_party/rust/uniffi_core/src/metadata.rs b/third_party/rust/uniffi_core/src/metadata.rs index 770d2b36d5..dc61a1bfcb 100644 --- a/third_party/rust/uniffi_core/src/metadata.rs +++ b/third_party/rust/uniffi_core/src/metadata.rs @@ -32,13 +32,14 @@ pub mod codes { pub const RECORD: u8 = 2; pub const ENUM: u8 = 3; pub const INTERFACE: u8 = 4; - pub const ERROR: u8 = 5; pub const NAMESPACE: u8 = 6; pub const CONSTRUCTOR: u8 = 7; pub const UDL_FILE: u8 = 8; pub const CALLBACK_INTERFACE: u8 = 9; pub const TRAIT_METHOD: u8 = 10; pub const UNIFFI_TRAIT: u8 = 11; + pub const TRAIT_INTERFACE: u8 = 12; + pub const CALLBACK_TRAIT_INTERFACE: u8 = 13; pub const UNKNOWN: u8 = 255; // Type codes @@ -66,20 +67,24 @@ pub mod codes { pub const TYPE_CALLBACK_INTERFACE: u8 = 21; pub const TYPE_CUSTOM: u8 = 22; pub const TYPE_RESULT: u8 = 23; - pub const TYPE_FUTURE: u8 = 24; - pub const TYPE_FOREIGN_EXECUTOR: u8 = 25; + pub const TYPE_TRAIT_INTERFACE: u8 = 24; + pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25; pub const TYPE_UNIT: u8 = 255; - // Literal codes for LiteralMetadata - note that we don't support - // all variants in the "emit/reader" context. + // Literal codes for LiteralMetadata pub const LIT_STR: u8 = 0; pub const LIT_INT: u8 = 1; pub const LIT_FLOAT: u8 = 2; pub const LIT_BOOL: u8 = 3; - pub const LIT_NULL: u8 = 4; + pub const LIT_NONE: u8 = 4; + pub const LIT_SOME: u8 = 5; + pub const LIT_EMPTY_SEQ: u8 = 6; } -const BUF_SIZE: usize = 4096; +// For large errors (e.g. enums) a buffer size of ~4k - ~8k +// is not enough. See issues on Github: #1968 and #2041 and +// for an example see fixture/large-error +const BUF_SIZE: usize = 16384; // This struct is a kludge around the fact that Rust const generic support doesn't quite handle our // needs. @@ -168,7 +173,17 @@ impl MetadataBuffer { self.concat_value(value as u8) } - // Concatenate a string to this buffer. + // Option<bool> + pub const fn concat_option_bool(self, value: Option<bool>) -> Self { + self.concat_value(match value { + None => 0, + Some(false) => 1, + Some(true) => 2, + }) + } + + // Concatenate a string to this buffer. The maximum string length is 255 bytes. For longer strings, + // use `concat_long_str()`. // // Strings are encoded as a `u8` length, followed by the utf8 data. // @@ -189,6 +204,28 @@ impl MetadataBuffer { self } + // Concatenate a longer string to this buffer. + // + // Strings are encoded as a `u16` length, followed by the utf8 data. + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_long_str(mut self, string: &str) -> Self { + assert!(self.size + string.len() + 1 < BUF_SIZE); + let [lo, hi] = (string.len() as u16).to_le_bytes(); + self.bytes[self.size] = lo; + self.bytes[self.size + 1] = hi; + self.size += 2; + let bytes = string.as_bytes(); + let mut i = 0; + while i < bytes.len() { + self.bytes[self.size] = bytes[i]; + self.size += 1; + i += 1; + } + self + } + // Create an array from this MetadataBuffer // // SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust |