xabi 0.1.2

Generate stable native ABI glue from Rust traits
Documentation
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::marker::PhantomData;

use crate::{Error, Result, XabiOption, XabiOwnedBytes, XabiOwnedBytesOwner};

/// Trait implemented by ABI descriptors generated by [`crate::xabi`].
///
/// Exporting modules do not implement this manually in normal use; the generated
/// ABI type is consumed by [`crate::module`].
pub trait XabiContract<P: 'static> {
    /// Stable ABI identifier exported by this contract.
    const ID: &'static str;

    /// Export a concrete implementation as an ABI-specific vtable.
    fn export(plugin: P) -> *mut c_void;
}

/// Rust type that has a stable xabi representation.
///
/// Types used by value in an xabi trait must implement this trait. Most users
/// should generate the implementation with [`crate::data`]. Generated data
/// payloads use exact-version wire layouts rather than prefix compatibility.
pub trait XabiType: Sized {
    /// C-compatible wire type passed across the ABI boundary.
    type Wire: Copy + 'static;

    /// Stable snapshot name for the wire type.
    const WIRE_TYPE_NAME: &'static str;

    /// Convert this value into its wire representation.
    fn into_wire(self) -> Self::Wire;

    /// Decode this value from a borrowed wire pointer.
    ///
    /// # Safety
    ///
    /// `wire` must be valid for reads of `Self::Wire`.
    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self>;

    /// Claim a wire value passed to a generated export thunk.
    ///
    /// This is an internal ownership hook used by generated call glue. The
    /// default implementation decodes the wire without changing it. Types that
    /// transfer ownership can override this method to leave the wire in a
    /// non-owning state before returning.
    ///
    /// # Safety
    ///
    /// `wire` must point to a wire value produced by [`XabiType::into_wire`]
    /// and be valid for exclusive access for the duration of this call.
    #[doc(hidden)]
    unsafe fn xabi_take_from_wire(wire: *mut Self::Wire) -> Result<Self> {
        unsafe { Self::from_wire(wire.cast_const()) }
    }

    /// Release resources that remain owned by an unclaimed wire value.
    ///
    /// Generated caller glue invokes this exactly once after the ABI call, or
    /// while unwinding before the call. Ownership-transferring implementations
    /// must make this a no-op after [`XabiType::xabi_take_from_wire`] claims the
    /// value.
    ///
    /// # Safety
    ///
    /// `wire` must point to a wire value produced by [`XabiType::into_wire`]
    /// that has not already been released through this hook.
    #[doc(hidden)]
    unsafe fn xabi_drop_wire(_wire: *mut Self::Wire) {}

    /// Collect ABI layout entries required by this type.
    fn collect_xabi_layout(_collector: &mut dyn crate::XabiLayoutCollector) {}

    /// Retain the dynamic module required by any decoded function pointers.
    ///
    /// Generated host handles call this after decoding values from a loaded
    /// module. Most value types do not retain module code and use the default
    /// no-op implementation. Manual container implementations must propagate
    /// this call to fields that may own module-defined callbacks.
    #[doc(hidden)]
    fn retain_module(&mut self, _module: &std::sync::Arc<crate::ModuleHandle>) {}

    /// Encode this value as an owned payload.
    ///
    /// The default implementation copies the wire representation. Manual
    /// implementations that use this default must return fully initialized wire
    /// values, including padding bytes. Types with variable-sized payloads
    /// should override this method.
    fn into_payload(self) -> XabiOwnedBytes {
        let wire = self.into_wire();
        let bytes = unsafe {
            std::slice::from_raw_parts(
                std::ptr::addr_of!(wire).cast::<u8>(),
                std::mem::size_of::<Self::Wire>(),
            )
        };
        XabiOwnedBytes::from_vec(bytes.to_vec())
    }

    /// Decode this value from an owned payload.
    ///
    /// The default implementation requires the payload length to match the wire
    /// type exactly before decoding it.
    ///
    /// # Safety
    ///
    /// `payload` must have been produced by a compatible xabi implementation.
    unsafe fn from_payload(payload: XabiOwnedBytes) -> Result<Self> {
        let bytes = unsafe { payload.to_vec_and_free() }?;
        if bytes.len() != std::mem::size_of::<Self::Wire>() {
            return Err(Error::AbiMismatch(format!(
                "xabi payload size {} does not match expected {}",
                bytes.len(),
                std::mem::size_of::<Self::Wire>()
            )));
        }
        let mut wire = std::mem::MaybeUninit::<Self::Wire>::uninit();
        unsafe {
            std::ptr::copy_nonoverlapping(
                bytes.as_ptr(),
                wire.as_mut_ptr().cast::<u8>(),
                bytes.len(),
            );
            Self::from_wire(wire.as_ptr())
        }
    }
}

/// Call-scoped storage that reclaims an unclaimed ownership-transferring wire.
#[doc(hidden)]
pub struct XabiWire<T: XabiType> {
    wire: UnsafeCell<T::Wire>,
}

impl<T: XabiType> XabiWire<T> {
    /// Lower a Rust value into guarded wire storage.
    pub fn new(value: T) -> Self {
        Self {
            wire: UnsafeCell::new(value.into_wire()),
        }
    }

    /// Return the wire pointer passed to a generated ABI thunk.
    pub fn as_ptr(&self) -> *const T::Wire {
        self.wire.get().cast_const()
    }
}

impl<T: XabiType> Drop for XabiWire<T> {
    fn drop(&mut self) {
        unsafe { T::xabi_drop_wire(self.wire.get()) };
    }
}

macro_rules! impl_xabi_type_for_int {
    ($($ty:ty),* $(,)?) => {
        $(
            impl XabiType for $ty {
                type Wire = $ty;
                const WIRE_TYPE_NAME: &'static str = stringify!($ty);

                fn into_wire(self) -> Self::Wire {
                    self
                }

                unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
                    unsafe {
                        wire.as_ref()
                            .copied()
                            .ok_or(Error::NullPointer(concat!(stringify!($ty), " pointer")))
                    }
                }
            }
        )*
    };
}

impl_xabi_type_for_int!(
    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
);

impl XabiType for bool {
    type Wire = u8;
    const WIRE_TYPE_NAME: &'static str = "u8";

    fn into_wire(self) -> Self::Wire {
        self as u8
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        unsafe {
            match wire
                .as_ref()
                .copied()
                .ok_or(Error::NullPointer("bool pointer"))?
            {
                0 => Ok(false),
                1 => Ok(true),
                other => Err(Error::AbiMismatch(format!(
                    "bool wire value {other} is not 0 or 1"
                ))),
            }
        }
    }
}

impl XabiType for XabiOwnedBytesOwner {
    type Wire = XabiOwnedBytes;
    const WIRE_TYPE_NAME: &'static str = "XabiOwnedBytes";

    fn into_wire(self) -> Self::Wire {
        self.into_raw()
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        let raw = unsafe {
            wire.as_ref()
                .copied()
                .ok_or(Error::NullPointer("XabiOwnedBytesOwner pointer"))?
        };
        unsafe { XabiOwnedBytesOwner::from_raw(raw) }
    }

    unsafe fn xabi_take_from_wire(wire: *mut Self::Wire) -> Result<Self> {
        let wire = unsafe {
            wire.as_mut()
                .ok_or(Error::NullPointer("XabiOwnedBytesOwner pointer"))?
        };
        let raw = std::mem::replace(wire, XabiOwnedBytes::empty());
        unsafe { XabiOwnedBytesOwner::from_raw(raw) }
    }

    unsafe fn xabi_drop_wire(wire: *mut Self::Wire) {
        let Some(wire) = (unsafe { wire.as_mut() }) else {
            return;
        };
        let raw = std::mem::replace(wire, XabiOwnedBytes::empty());
        drop(unsafe { XabiOwnedBytesOwner::from_raw(raw) });
    }

    fn into_payload(self) -> XabiOwnedBytes {
        self.into_raw()
    }

    unsafe fn from_payload(payload: XabiOwnedBytes) -> Result<Self> {
        unsafe { XabiOwnedBytesOwner::from_raw(payload) }
    }

    fn retain_module(&mut self, module: &std::sync::Arc<crate::ModuleHandle>) {
        XabiOwnedBytesOwner::retain_module(self, module);
    }
}

impl XabiType for Vec<u8> {
    type Wire = XabiOwnedBytes;
    const WIRE_TYPE_NAME: &'static str = "XabiOwnedBytes";

    fn into_wire(self) -> Self::Wire {
        XabiOwnedBytes::from_vec(self)
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        let owner = unsafe { XabiOwnedBytesOwner::from_wire(wire) }?;
        Ok(owner.into_vec())
    }

    unsafe fn xabi_take_from_wire(wire: *mut Self::Wire) -> Result<Self> {
        let owner = unsafe { <XabiOwnedBytesOwner as XabiType>::xabi_take_from_wire(wire) }?;
        Ok(owner.into_vec())
    }

    unsafe fn xabi_drop_wire(wire: *mut Self::Wire) {
        unsafe { <XabiOwnedBytesOwner as XabiType>::xabi_drop_wire(wire) };
    }

    fn into_payload(self) -> XabiOwnedBytes {
        XabiOwnedBytes::from_vec(self)
    }

    unsafe fn from_payload(payload: XabiOwnedBytes) -> Result<Self> {
        let owner = unsafe { XabiOwnedBytesOwner::from_payload(payload) }?;
        Ok(owner.into_vec())
    }
}

impl XabiType for String {
    type Wire = XabiOwnedBytes;
    const WIRE_TYPE_NAME: &'static str = "XabiOwnedBytes";

    fn into_wire(self) -> Self::Wire {
        XabiOwnedBytes::from_string(self)
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        let wire = unsafe {
            wire.as_ref()
                .copied()
                .ok_or(Error::NullPointer("String pointer"))?
        };
        unsafe { wire.to_string_and_free() }
    }

    fn into_payload(self) -> XabiOwnedBytes {
        XabiOwnedBytes::from_string(self)
    }

    unsafe fn from_payload(payload: XabiOwnedBytes) -> Result<Self> {
        unsafe { payload.to_string_and_free() }
    }
}

impl<T> XabiType for Option<T>
where
    T: XabiType + 'static,
{
    type Wire = XabiOption;
    const WIRE_TYPE_NAME: &'static str = "XabiOption";

    fn collect_xabi_layout(collector: &mut dyn crate::XabiLayoutCollector) {
        T::collect_xabi_layout(collector);
    }

    fn retain_module(&mut self, module: &std::sync::Arc<crate::ModuleHandle>) {
        if let Some(value) = self {
            T::retain_module(value, module);
        }
    }

    fn into_wire(self) -> Self::Wire {
        match self {
            Some(value) => XabiOption::some(value.into_payload()),
            None => XabiOption::none(),
        }
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        let wire = unsafe {
            wire.as_ref()
                .ok_or(Error::NullPointer("XabiOption pointer"))?
        };
        wire.validate()?;
        if wire.is_some == 0 {
            return Ok(None);
        }
        unsafe { T::from_payload(wire.payload).map(Some) }
    }
}

/// Sendable wrapper for raw pointers that are only dereferenced on a known-safe thread.
///
/// This is useful when a raw ABI pointer must be moved into an async task but
/// the caller controls where it is dereferenced.
///
/// ```
/// let mut value = 1_u32;
/// let ptr = xabi::SendPtr::new(&mut value as *mut u32);
/// let raw = ptr.as_ptr();
/// unsafe { *raw = 2 };
/// assert_eq!(value, 2);
/// ```
pub struct SendPtr<T> {
    value: usize,
    _marker: PhantomData<*mut T>,
}

impl<T> SendPtr<T> {
    /// Wrap a raw mutable pointer.
    pub fn new(ptr: *mut T) -> Self {
        Self {
            value: ptr as usize,
            _marker: PhantomData,
        }
    }

    /// Return the wrapped raw pointer.
    pub fn as_ptr(self) -> *mut T {
        self.value as *mut T
    }
}

unsafe impl<T> Send for SendPtr<T> {}

#[cfg(test)]
mod tests {
    use super::*;

    fn assert_send<T: Send>() {}

    #[test]
    fn send_ptr_is_send() {
        assert_send::<SendPtr<u8>>();
    }

    #[test]
    fn send_ptr_roundtrips_pointer_value() {
        let mut value = 5_u32;
        let ptr = &mut value as *mut u32;

        assert_eq!(SendPtr::new(ptr).as_ptr(), ptr);
    }
}