xabi 0.1.0-alpha.3

Generate stable native ABI glue from Rust traits
Documentation
use crate::{
    XabiBytes, XabiErrorWire, XabiExport, XabiFuture, XabiManifest, XabiOption, XabiOwnedBytes,
    XabiResult, XabiSlice, XabiStr, XabiWaker,
};

macro_rules! field {
    ($name:literal, $ty:ty, $field:tt, $field_ty:literal) => {
        XabiFieldLayout::new($name, std::mem::offset_of!($ty, $field), $field_ty)
    };
}

macro_rules! tuple_field {
    ($name:literal, $ty:ty, $field:tt, $field_ty:literal) => {
        XabiFieldLayout::new($name, std::mem::offset_of!($ty, $field), $field_ty)
    };
}

/// A compiled xabi module layout.
///
/// Values of this type are generated by [`crate::module`]. Test helpers can
/// collect the current layout and compare it with committed snapshots.
pub struct XabiLayout {
    /// Cargo package that produced this layout.
    pub package: &'static str,
    /// Rust module path that produced this layout.
    pub module: &'static str,
    /// Collect layout entries for this module.
    pub collect: fn(&mut dyn XabiLayoutCollector),
}

/// Collector used by generated layout descriptors.
pub trait XabiLayoutCollector {
    /// Add one layout item.
    fn push(&mut self, item: XabiLayoutItem);
}

impl XabiLayoutCollector for Vec<XabiLayoutItem> {
    fn push(&mut self, item: XabiLayoutItem) {
        Vec::push(self, item);
    }
}

/// A generated source of xabi layout entries.
pub trait XabiLayoutSource {
    /// Collect layout entries produced by this source.
    fn collect_xabi_layout(collector: &mut dyn XabiLayoutCollector);
}

/// One item in a xabi layout snapshot.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum XabiLayoutItem {
    /// An exported implementation entry.
    Export(XabiExportLayout),
    /// A C-compatible type layout.
    Type(XabiTypeLayout),
    /// Prefix information for a generated vtable.
    VTable(XabiVTableLayout),
}

/// Stability rules for a type layout.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum XabiLayoutStability {
    /// The type layout is fixed and must not change without a new ABI format.
    Fixed,
    /// The type may grow by appending fields after the existing prefix.
    Prefix,
}

impl XabiLayoutStability {
    /// Return the snapshot spelling for this stability mode.
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Fixed => "fixed",
            Self::Prefix => "prefix",
        }
    }
}

/// Layout metadata for an exported implementation.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct XabiExportLayout {
    /// Stable trait ABI identifier.
    pub abi_id: &'static str,
    /// Export name reported in the module manifest.
    pub name: &'static str,
    /// Contract ABI version.
    pub contract_version: u32,
}

impl XabiExportLayout {
    /// Create export layout metadata.
    pub const fn new(abi_id: &'static str, name: &'static str, contract_version: u32) -> Self {
        Self {
            abi_id,
            name,
            contract_version,
        }
    }
}

/// Layout metadata for a C-compatible type.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct XabiTypeLayout {
    /// Stable type name used in snapshots.
    pub name: &'static str,
    /// Compatibility rule for this type.
    pub stability: XabiLayoutStability,
    /// Runtime size of the type.
    pub size: usize,
    /// Runtime alignment of the type.
    pub align: usize,
    /// Field layouts in declaration order.
    pub fields: &'static [XabiFieldLayout],
}

impl XabiTypeLayout {
    /// Create type layout metadata.
    pub const fn new(
        name: &'static str,
        stability: XabiLayoutStability,
        size: usize,
        align: usize,
        fields: &'static [XabiFieldLayout],
    ) -> Self {
        Self {
            name,
            stability,
            size,
            align,
            fields,
        }
    }
}

/// Layout metadata for a C-compatible field.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct XabiFieldLayout {
    /// Field name.
    pub name: &'static str,
    /// Runtime byte offset from the start of the containing type.
    pub offset: usize,
    /// Stable field type name used in snapshots.
    pub ty: &'static str,
}

impl XabiFieldLayout {
    /// Create field layout metadata.
    pub const fn new(name: &'static str, offset: usize, ty: &'static str) -> Self {
        Self { name, offset, ty }
    }
}

/// Prefix metadata for a generated vtable.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct XabiVTableLayout {
    /// Stable vtable type name used in snapshots.
    pub name: &'static str,
    /// Full runtime size of the vtable.
    pub full_size: usize,
    /// Minimum prefix accepted by the generated host glue.
    pub min_size: usize,
}

impl XabiVTableLayout {
    /// Create vtable layout metadata.
    pub const fn new(name: &'static str, full_size: usize, min_size: usize) -> Self {
        Self {
            name,
            full_size,
            min_size,
        }
    }
}

#[doc(hidden)]
pub fn collect_runtime_layout(collector: &mut dyn XabiLayoutCollector) {
    const XABI_STR_FIELDS: &[XabiFieldLayout] = &[
        field!("ptr", XabiStr, ptr, "*const u8"),
        field!("len", XabiStr, len, "usize"),
    ];
    const XABI_SLICE_U8_FIELDS: &[XabiFieldLayout] = &[
        field!("ptr", XabiSlice<u8>, ptr, "*const u8"),
        field!("len", XabiSlice<u8>, len, "usize"),
    ];
    const XABI_BYTES_FIELDS: &[XabiFieldLayout] =
        &[tuple_field!("0", XabiBytes, 0, "XabiSlice<u8>")];
    const XABI_OWNED_BYTES_FIELDS: &[XabiFieldLayout] = &[
        field!("ptr", XabiOwnedBytes, ptr, "*mut u8"),
        field!("len", XabiOwnedBytes, len, "usize"),
        field!(
            "free",
            XabiOwnedBytes,
            free,
            "unsafe extern \"C\" fn(*mut u8, usize)"
        ),
    ];
    const XABI_OPTION_FIELDS: &[XabiFieldLayout] = &[
        field!("size", XabiOption, size, "usize"),
        field!("abi_version", XabiOption, abi_version, "u32"),
        field!("is_some", XabiOption, is_some, "u8"),
        field!("payload", XabiOption, payload, "XabiOwnedBytes"),
    ];
    const XABI_RESULT_FIELDS: &[XabiFieldLayout] = &[
        field!("code", XabiResult, code, "i32"),
        field!("payload", XabiResult, payload, "XabiOwnedBytes"),
    ];
    const XABI_EXPORT_FIELDS: &[XabiFieldLayout] = &[
        field!("size", XabiExport, size, "usize"),
        field!("abi_version", XabiExport, abi_version, "u32"),
        field!("abi_id", XabiExport, abi_id, "XabiStr"),
        field!("name", XabiExport, name, "XabiStr"),
        field!("contract_version", XabiExport, contract_version, "u32"),
        field!("export_version", XabiExport, export_version, "u32"),
        field!("capabilities", XabiExport, capabilities, "u64"),
        field!(
            "make",
            XabiExport,
            make,
            "unsafe extern \"C\" fn() -> *mut c_void"
        ),
    ];
    const XABI_MANIFEST_FIELDS: &[XabiFieldLayout] = &[
        field!("size", XabiManifest, size, "usize"),
        field!("abi_version", XabiManifest, abi_version, "u32"),
        field!("exports", XabiManifest, exports, "XabiSlice<XabiExport>"),
    ];
    const XABI_ERROR_WIRE_FIELDS: &[XabiFieldLayout] = &[
        field!("size", XabiErrorWire, size, "usize"),
        field!("abi_version", XabiErrorWire, abi_version, "u32"),
        field!("kind", XabiErrorWire, kind, "u32"),
    ];
    const XABI_WAKER_FIELDS: &[XabiFieldLayout] = &[
        field!("size", XabiWaker, size, "usize"),
        field!("abi_version", XabiWaker, abi_version, "u32"),
        field!("instance", XabiWaker, instance, "*mut c_void"),
        field!(
            "clone",
            XabiWaker,
            clone,
            "unsafe extern \"C\" fn(*mut c_void) -> XabiWaker"
        ),
        field!(
            "wake",
            XabiWaker,
            wake,
            "unsafe extern \"C\" fn(*mut c_void)"
        ),
        field!(
            "wake_by_ref",
            XabiWaker,
            wake_by_ref,
            "unsafe extern \"C\" fn(*mut c_void)"
        ),
        field!(
            "release",
            XabiWaker,
            release,
            "unsafe extern \"C\" fn(*mut c_void)"
        ),
    ];
    const XABI_FUTURE_FIELDS: &[XabiFieldLayout] = &[
        field!("size", XabiFuture, size, "usize"),
        field!("abi_version", XabiFuture, abi_version, "u32"),
        field!("instance", XabiFuture, instance, "*mut c_void"),
        field!(
            "poll",
            XabiFuture,
            poll,
            "unsafe extern \"C\" fn(*mut c_void, *const XabiWaker, *mut XabiResult) -> i32"
        ),
        field!(
            "release",
            XabiFuture,
            release,
            "unsafe extern \"C\" fn(*mut c_void)"
        ),
    ];

    type_layout::<XabiStr>(
        collector,
        "xabi::XabiStr",
        XabiLayoutStability::Fixed,
        XABI_STR_FIELDS,
    );
    type_layout::<XabiSlice<u8>>(
        collector,
        "xabi::XabiSlice<u8>",
        XabiLayoutStability::Fixed,
        XABI_SLICE_U8_FIELDS,
    );
    type_layout::<XabiBytes>(
        collector,
        "xabi::XabiBytes",
        XabiLayoutStability::Fixed,
        XABI_BYTES_FIELDS,
    );
    type_layout::<XabiOwnedBytes>(
        collector,
        "xabi::XabiOwnedBytes",
        XabiLayoutStability::Fixed,
        XABI_OWNED_BYTES_FIELDS,
    );
    type_layout::<XabiOption>(
        collector,
        "xabi::XabiOption",
        XabiLayoutStability::Prefix,
        XABI_OPTION_FIELDS,
    );
    type_layout::<XabiResult>(
        collector,
        "xabi::XabiResult",
        XabiLayoutStability::Fixed,
        XABI_RESULT_FIELDS,
    );
    type_layout::<XabiExport>(
        collector,
        "xabi::XabiExport",
        XabiLayoutStability::Prefix,
        XABI_EXPORT_FIELDS,
    );
    type_layout::<XabiManifest>(
        collector,
        "xabi::XabiManifest",
        XabiLayoutStability::Prefix,
        XABI_MANIFEST_FIELDS,
    );
    type_layout::<XabiErrorWire>(
        collector,
        "xabi::XabiErrorWire",
        XabiLayoutStability::Prefix,
        XABI_ERROR_WIRE_FIELDS,
    );
    type_layout::<XabiWaker>(
        collector,
        "xabi::XabiWaker",
        XabiLayoutStability::Prefix,
        XABI_WAKER_FIELDS,
    );
    type_layout::<XabiFuture>(
        collector,
        "xabi::XabiFuture",
        XabiLayoutStability::Prefix,
        XABI_FUTURE_FIELDS,
    );
}

fn type_layout<T>(
    collector: &mut dyn XabiLayoutCollector,
    name: &'static str,
    stability: XabiLayoutStability,
    fields: &'static [XabiFieldLayout],
) {
    collector.push(XabiLayoutItem::Type(XabiTypeLayout::new(
        name,
        stability,
        std::mem::size_of::<T>(),
        std::mem::align_of::<T>(),
        fields,
    )));
}