polyplug_abi 0.1.1

ABI type definitions for the polyplug plugin runtime
//! Host Contract Interface — for host-provided services.
//!
//! This module defines `HostContractInterface`, the interface hosts register
//! for services they provide to plugins (e.g., logging, file access, networking).
//!
//! # Who provides
//! Host application code creates this struct and registers it with the runtime.
//!
//! # Who calls
//! Guest (plugin) code calls the dispatch functions after obtaining an instance
//! via `HostApi::get_host_contract()`.
//!
//! # Ownership
//! Must be `'static`. The runtime holds a reference for the plugin lifetime.
//!
//! # Singleton Mode
//! - `singleton == true`: Same instance returned for all callers
//! - `singleton == false`: New instance per caller (caller must destroy)

use core::ffi::c_void;

use polyplug_utils::HostContractId;

use crate::{
    dispatch::{dispatch_mechanisms::DispatchMechanisms, dispatch_type::DispatchType},
    host::HostContractInstance,
    types::Version,
};

/// Host Contract Interface — for host-provided services.
///
/// Host contracts are services provided by the host application to plugins.
///
/// # Who provides
/// Host application code creates this struct and registers it with the runtime.
/// Must be `'static` or intentionally leaked.
///
/// # Who calls
/// Guest (plugin) code calls the dispatch functions after obtaining an instance
/// via `HostApi::get_host_contract()`.
///
/// # Ownership
/// Must be `'static`. The runtime holds a reference for the plugin lifetime.
/// Never freed while runtime lives.
///
/// # Lifetime
/// Lives for the entire runtime lifetime. Must survive hot-reload.
///
/// # Singleton Mode
/// - `singleton == true`: Same instance returned for all callers
/// - `singleton == false`: New instance per caller (caller must destroy)
///
/// # Self-Passing Pattern
/// `create_instance` and `destroy_instance` take `self: *const HostContractInterface`.
/// The runtime field provides access to runtime services.
#[repr(C)]
pub struct HostContractInterface {
    /// FNV-1a hash of "host_contract:name@major_version".
    ///
    /// Generated by polyplugc from contract name and major version.
    /// Used for contract lookup via `get_host_contract`.
    pub contract_id: HostContractId,
    /// Contract version (major, minor, patch).
    ///
    /// Used for version compatibility checks during lookup.
    pub contract_version: Version,
    /// Whether this contract provides a singleton instance.
    ///
    /// If true, `get_host_contract` returns the same instance for all callers.
    /// If false, each call returns a new instance that caller must destroy.
    pub singleton: bool,
    /// Dispatch mechanism type (Native or VirtualMachine).
    ///
    /// Host contracts typically use Native dispatch for direct calls.
    pub dispatch_type: DispatchType,
    /// Opaque pointer to Runtime.
    ///
    /// Host contracts can use this for runtime-specific operations.
    /// Set during interface registration.
    ///
    /// # Ownership
    /// Owned by the runtime. Host contract implementations must not free.
    pub runtime: *mut c_void,
    /// Opaque per-interface user-data pointer.
    ///
    /// `create_instance` and `destroy_instance` can read it via their `this`
    /// parameter (`(*this).user_data`) to recover registrant-owned context.
    ///
    /// # Ownership
    /// Owned by the registrant (the host application). The runtime never reads,
    /// writes, or frees the pointee — it only stores the pointer.
    pub user_data: *mut c_void,
    /// Create a new instance of this host contract.
    ///
    /// For singleton contracts, this is typically called once and the instance
    /// is cached. For multi-instance contracts, called for each get_host_contract.
    ///
    /// # Arguments
    /// - `this`: HostContractInterface pointer (self-passing pattern)
    /// - `args`: Optional initialization arguments (contract-specific)
    /// - `out_instance`: out-param; the new instance handle is written here. A
    ///   null `data` denotes a stateless instance or construction failure.
    ///
    /// # Nullability
    /// REQUIRED — never null. Failure is signalled by writing a null
    /// *instance handle*, not by a null callback. `register_host_contract`
    /// rejects interfaces whose `create_instance` bits are null.
    pub create_instance: unsafe extern "C" fn(
        this: *const HostContractInterface,
        args: *const (),
        out_instance: *mut HostContractInstance,
    ),
    /// Destroy an instance of this host contract.
    ///
    /// For singleton contracts, this is typically a no-op (singleton lives forever).
    /// For multi-instance contracts, caller must destroy after use.
    ///
    /// # Arguments
    /// - `this`: HostContractInterface pointer (self-passing pattern)
    /// - `instance`: Instance handle to destroy
    ///
    /// # Nullability
    /// REQUIRED — never null. Singleton/stateless contracts must supply a
    /// no-op function. `register_host_contract` rejects interfaces whose
    /// `destroy_instance` bits are null.
    ///
    /// # Safety
    /// After calling destroy_instance, the instance handle is invalid.
    pub destroy_instance:
        unsafe extern "C" fn(this: *const HostContractInterface, instance: HostContractInstance),
    /// Union of dispatch mechanisms — access based on dispatch_type.
    ///
    /// For Native dispatch: use `dispatch.native.functions[fn_id]`.
    /// For VM dispatch: use `dispatch.vm.call(loader_data, instance, fn_id, args, out, arena, out_err)`.
    pub dispatch: DispatchMechanisms,
}

// SAFETY: HostContractInterface contains an opaque pointer and function pointers.
// The opaque pointer is managed by the runtime.
// Function pointers are inherently thread-safe to call from any thread
// (the functions themselves must handle their own synchronization).
unsafe impl Send for HostContractInterface {}

// SAFETY: HostContractInterface contains an opaque pointer and function pointers.
// Concurrent calls to the same interface are safe because the runtime
// handles internal synchronization.
unsafe impl Sync for HostContractInterface {}

#[cfg(test)]
mod tests {
    use super::HostContractInterface;
    use core::mem::{align_of, offset_of, size_of};

    #[test]
    fn layout_host_contract_interface() {
        // HostContractInterface layout:
        //   contract_id (HostContractId/u64): 8 bytes @ offset 0
        //   contract_version (Version/3xu32): 12 bytes @ offset 8
        //   singleton (bool): 1 byte @ offset 20
        //   [padding 3 bytes for alignment to DispatchType (4-byte align)]
        //   dispatch_type (DispatchType/u32): 4 bytes @ offset 24
        //   [padding 4 bytes for alignment to *mut c_void (8-byte align)]
        //   runtime (*mut c_void): 8 bytes @ offset 32
        //   user_data (*mut c_void): 8 bytes @ offset 40
        //   create_instance (fn ptr): 8 bytes @ offset 48
        //   destroy_instance (fn ptr): 8 bytes @ offset 56
        //   dispatch (union): 16 bytes @ offset 64
        // Total: 80 bytes
        assert_eq!(size_of::<HostContractInterface>(), 80);
        assert_eq!(align_of::<HostContractInterface>(), 8);
        assert_eq!(offset_of!(HostContractInterface, contract_id), 0);
        assert_eq!(offset_of!(HostContractInterface, contract_version), 8);
        assert_eq!(offset_of!(HostContractInterface, singleton), 20);
        assert_eq!(offset_of!(HostContractInterface, dispatch_type), 24);
        assert_eq!(offset_of!(HostContractInterface, runtime), 32);
        assert_eq!(offset_of!(HostContractInterface, user_data), 40);
        assert_eq!(offset_of!(HostContractInterface, create_instance), 48);
        assert_eq!(offset_of!(HostContractInterface, destroy_instance), 56);
        assert_eq!(offset_of!(HostContractInterface, dispatch), 64);
    }
}