/* 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 crate::{Handle, RustBuffer, RustCallStatus, RustCallStatusCode}; use std::{mem::ManuallyDrop, ptr::NonNull}; /// FFIBuffer element /// /// This is the union of all possible primitive FFI types. /// Composite FFI types like `RustBuffer` and `RustCallStatus` are stored using multiple elements. #[repr(C)] #[derive(Clone, Copy)] pub union FfiBufferElement { pub u8: u8, pub i8: i8, pub u16: u16, pub i16: i16, pub u32: u32, pub i32: i32, pub u64: u64, pub i64: i64, pub float: std::ffi::c_float, pub double: std::ffi::c_double, pub ptr: *const std::ffi::c_void, } impl Default for FfiBufferElement { fn default() -> Self { Self { u64: 0 } } } /// Serialize a FFI value to a buffer /// /// This trait allows FFI types to be read from/written to FFIBufferElement slices. /// It's similar, to the [crate::Lift::read] and [crate::Lower::write] methods, but implemented on the FFI types rather than Rust types. /// It's useful to compare the two: /// /// - [crate::Lift] and [crate::Lower] are implemented on Rust types like String and user-defined records. /// - [FfiSerialize] is implemented on the FFI types like RustBuffer, RustCallStatus, and vtable structs. /// - All 3 traits are implemented for simple cases where the FFI type and Rust type are the same, for example numeric types. /// - [FfiSerialize] uses FFIBuffer elements rather than u8 elements. Using a union eliminates the need to cast values and creates better alignment. /// - [FfiSerialize] uses a constant size to store each type. /// /// [FfiSerialize] is used to generate alternate forms of the scaffolding functions that simplify work needed to implement the bindings on the other side. /// This is currently only used in the gecko-js bindings for Firefox, but could maybe be useful for other external bindings or even some of the builtin bindings like Python/Kotlin. /// /// The FFI-buffer version of the scaffolding functions: /// - Input two pointers to ffi buffers, one to read arguments from and one to write the return value to. /// - Rather than inputting an out pointer for `RustCallStatus` it's written to the return buffer after the normal return value. /// pub trait FfiSerialize: Sized { /// Number of elements required to store this FFI type const SIZE: usize; /// Get a value from a ffi buffer /// /// Note: `buf` should be thought of as `&[FFIBufferElement; Self::SIZE]`, but it can't be spelled out that way /// since Rust doesn't support that usage of const generics yet. fn get(buf: &[FfiBufferElement]) -> Self; /// Put a value to a ffi buffer /// /// Note: `buf` should be thought of as `&[FFIBufferElement; Self::SIZE]`, but it can't be spelled out that way /// since Rust doesn't support that usage of const generics yet. fn put(buf: &mut [FfiBufferElement], value: Self); /// Read a value from a ffi buffer ref and advance it /// /// buf must have a length of at least `Self::Size` fn read(buf: &mut &[FfiBufferElement]) -> Self { let value = Self::get(buf); *buf = &buf[Self::SIZE..]; value } /// Write a value to a ffi buffer ref and advance it /// /// buf must have a length of at least `Self::Size` fn write(buf: &mut &mut [FfiBufferElement], value: Self) { Self::put(buf, value); // Lifetime dance taken from `bytes::BufMut` let (_, new_buf) = ::core::mem::take(buf).split_at_mut(Self::SIZE); *buf = new_buf; } } /// Get the FFI buffer size for list of types #[macro_export] macro_rules! ffi_buffer_size { ($($T:ty),* $(,)?) => { ( 0 $( + <$T as $crate::FfiSerialize>::SIZE )* ) } } macro_rules! define_ffi_serialize_simple_cases { ($(($name: ident, $T:ty)),* $(,)?) => { $( impl FfiSerialize for $T { const SIZE: usize = 1; fn get(buf: &[FfiBufferElement]) -> Self { // Safety: the foreign bindings are responsible for sending us the correct data. unsafe { buf[0].$name } } fn put(buf: &mut[FfiBufferElement], value: Self) { buf[0].$name = value } } )* }; } define_ffi_serialize_simple_cases! { (i8, i8), (u8, u8), (i16, i16), (u16, u16), (i32, i32), (u32, u32), (i64, i64), (u64, u64), (ptr, *const std::ffi::c_void), } impl FfiSerialize for f32 { const SIZE: usize = 1; fn get(buf: &[FfiBufferElement]) -> Self { // Safety: the foreign bindings are responsible for sending us the correct data. unsafe { buf[0].float as Self } } fn put(buf: &mut [FfiBufferElement], value: Self) { // Use a cast since it's theoretically possible for float to not be f32 on some systems. buf[0].float = value as std::ffi::c_float; } } impl FfiSerialize for f64 { const SIZE: usize = 1; fn get(buf: &[FfiBufferElement]) -> Self { // Safety: the foreign bindings are responsible for sending us the correct data. unsafe { buf[0].double as Self } } fn put(buf: &mut [FfiBufferElement], value: Self) { // Use a cast since it's theoretically possible for double to not be f64 on some systems. buf[0].double = value as std::ffi::c_double; } } impl FfiSerialize for bool { const SIZE: usize = 1; fn get(buf: &[FfiBufferElement]) -> Self { // Safety: the foreign bindings are responsible for sending us the correct data. unsafe { buf[0].i8 == 1 } } fn put(buf: &mut [FfiBufferElement], value: Self) { buf[0].i8 = if value { 1 } else { 0 } } } impl FfiSerialize for () { const SIZE: usize = 0; fn get(_buf: &[FfiBufferElement]) -> Self {} fn put(_buf: &mut [FfiBufferElement], _value: Self) {} } impl FfiSerialize for NonNull { const SIZE: usize = 1; fn get(buf: &[FfiBufferElement]) -> Self { // Safety: this relies on the foreign code passing us valid pointers unsafe { Self::new_unchecked(buf[0].ptr as *mut T) } } fn put(buf: &mut [FfiBufferElement], value: Self) { buf[0].ptr = value.as_ptr() as *const std::ffi::c_void } } impl FfiSerialize for Handle { const SIZE: usize = 1; fn get(buf: &[FfiBufferElement]) -> Self { unsafe { Handle::from_raw_unchecked(buf[0].u64) } } fn put(buf: &mut [FfiBufferElement], value: Self) { buf[0].u64 = value.as_raw() } } impl FfiSerialize for RustBuffer { const SIZE: usize = 3; fn get(buf: &[FfiBufferElement]) -> Self { // Safety: the foreign bindings are responsible for sending us the correct data. let (capacity, len, data) = unsafe { (buf[0].u64, buf[1].u64, buf[2].ptr as *mut u8) }; unsafe { RustBuffer::from_raw_parts(data, len, capacity) } } fn put(buf: &mut [FfiBufferElement], value: Self) { buf[0].u64 = value.capacity; buf[1].u64 = value.len; buf[2].ptr = value.data as *const std::ffi::c_void; } } impl FfiSerialize for RustCallStatus { const SIZE: usize = 4; fn get(buf: &[FfiBufferElement]) -> Self { // Safety: the foreign bindings are responsible for sending us the correct data. let code = unsafe { buf[0].i8 }; Self { code: RustCallStatusCode::try_from(code).unwrap_or(RustCallStatusCode::UnexpectedError), error_buf: ManuallyDrop::new(RustBuffer::get(&buf[1..])), } } fn put(buf: &mut [FfiBufferElement], value: Self) { buf[0].i8 = value.code as i8; RustBuffer::put(&mut buf[1..], ManuallyDrop::into_inner(value.error_buf)) } } #[cfg(test)] mod test { use super::*; use crate::{Handle, RustBuffer, RustCallStatus, RustCallStatusCode}; #[test] fn test_ffi_buffer_size() { assert_eq!(ffi_buffer_size!(u8), 1); assert_eq!(ffi_buffer_size!(i8), 1); assert_eq!(ffi_buffer_size!(u16), 1); assert_eq!(ffi_buffer_size!(i16), 1); assert_eq!(ffi_buffer_size!(u32), 1); assert_eq!(ffi_buffer_size!(i32), 1); assert_eq!(ffi_buffer_size!(u64), 1); assert_eq!(ffi_buffer_size!(i64), 1); assert_eq!(ffi_buffer_size!(f32), 1); assert_eq!(ffi_buffer_size!(f64), 1); assert_eq!(ffi_buffer_size!(bool), 1); assert_eq!(ffi_buffer_size!(*const std::ffi::c_void), 1); assert_eq!(ffi_buffer_size!(RustBuffer), 3); assert_eq!(ffi_buffer_size!(RustCallStatus), 4); assert_eq!(ffi_buffer_size!(Handle), 1); assert_eq!(ffi_buffer_size!(()), 0); assert_eq!(ffi_buffer_size!(u8, f32, bool, Handle, (), RustBuffer), 7); } #[test] fn test_ffi_serialize() { let mut some_data = vec![1, 2, 3]; let void_ptr = some_data.as_mut_ptr() as *const std::ffi::c_void; let rust_buffer = unsafe { RustBuffer::from_raw_parts(some_data.as_mut_ptr(), 2, 3) }; let orig_rust_buffer_data = ( rust_buffer.data_pointer(), rust_buffer.len(), rust_buffer.capacity(), ); let handle = unsafe { Handle::from_raw(101).unwrap() }; let rust_call_status = RustCallStatus::default(); let rust_call_status_error_buf = &rust_call_status.error_buf; let orig_rust_call_status_buffer_data = ( rust_call_status_error_buf.data_pointer(), rust_call_status_error_buf.len(), rust_call_status_error_buf.capacity(), ); let mut buf = [FfiBufferElement::default(); 21]; let mut buf_writer = buf.as_mut_slice(); ::write(&mut buf_writer, 0); ::write(&mut buf_writer, 1); ::write(&mut buf_writer, 2); ::write(&mut buf_writer, 3); ::write(&mut buf_writer, 4); ::write(&mut buf_writer, 5); ::write(&mut buf_writer, 6); ::write(&mut buf_writer, 7); ::write(&mut buf_writer, 0.1); ::write(&mut buf_writer, 0.2); ::write(&mut buf_writer, true); <*const std::ffi::c_void as FfiSerialize>::write(&mut buf_writer, void_ptr); ::write(&mut buf_writer, rust_buffer); ::write(&mut buf_writer, rust_call_status); ::write(&mut buf_writer, handle.clone()); #[allow(clippy::needless_borrows_for_generic_args)] <() as FfiSerialize>::write(&mut buf_writer, ()); let mut buf_reader = buf.as_slice(); assert_eq!(::read(&mut buf_reader), 0); assert_eq!(::read(&mut buf_reader), 1); assert_eq!(::read(&mut buf_reader), 2); assert_eq!(::read(&mut buf_reader), 3); assert_eq!(::read(&mut buf_reader), 4); assert_eq!(::read(&mut buf_reader), 5); assert_eq!(::read(&mut buf_reader), 6); assert_eq!(::read(&mut buf_reader), 7); assert_eq!(::read(&mut buf_reader), 0.1); assert_eq!(::read(&mut buf_reader), 0.2); assert!(::read(&mut buf_reader)); assert_eq!( <*const std::ffi::c_void as FfiSerialize>::read(&mut buf_reader), void_ptr ); let rust_buffer2 = ::read(&mut buf_reader); assert_eq!( ( rust_buffer2.data_pointer(), rust_buffer2.len(), rust_buffer2.capacity() ), orig_rust_buffer_data, ); let rust_call_status2 = ::read(&mut buf_reader); assert_eq!(rust_call_status2.code, RustCallStatusCode::Success); let rust_call_status2_error_buf = ManuallyDrop::into_inner(rust_call_status2.error_buf); assert_eq!( ( rust_call_status2_error_buf.data_pointer(), rust_call_status2_error_buf.len(), rust_call_status2_error_buf.capacity(), ), orig_rust_call_status_buffer_data ); assert_eq!(::read(&mut buf_reader), handle); // Ensure that `read` with a unit struct doesn't panic. No need to assert anything, since // the return type is (). <() as FfiSerialize>::read(&mut buf_reader); } }