fizzyx 0.1.1

Safe, ergonomic Rust bindings for the Fizzy WebAssembly interpreter.
Documentation
//! Instantiated WebAssembly modules and access to their exports.

use crate::func::Func;
use crate::global::Global;
use crate::memory::Memory;
use crate::trampoline::HostFunc;
use crate::value::{FuncType, GlobalType};
use fizzyx_sys as sys;
use std::ffi::CString;
use std::mem::MaybeUninit;
use std::rc::Rc;

/// An instantiated WebAssembly module.
///
/// An instance owns its linear memory and globals and provides access to its
/// exports. It is created via [`Linker::instantiate`](crate::Linker::instantiate).
pub struct Instance {
    inner: *mut sys::FizzyInstance,
    // Host closures referenced by the instance's imports; must outlive it.
    _keepalive: Vec<Rc<HostFunc>>,
}

impl Instance {
    /// Wraps a raw, owned Fizzy instance pointer.
    ///
    /// # Safety
    ///
    /// `inner` must be a non-null instance returned by Fizzy, and `keepalive`
    /// must contain every host closure referenced by the instance's imports.
    pub(crate) unsafe fn from_raw(
        inner: *mut sys::FizzyInstance,
        keepalive: Vec<Rc<HostFunc>>,
    ) -> Self {
        Self {
            inner,
            _keepalive: keepalive,
        }
    }

    pub(crate) fn as_ptr(&self) -> *mut sys::FizzyInstance {
        self.inner
    }

    /// Looks up an exported function by name.
    pub fn get_func(&self, name: &str) -> Option<Func> {
        let c_name = CString::new(name).ok()?;
        // SAFETY: `self.inner` is a valid instance; the returned module pointer is
        // a non-owning view valid for the instance's lifetime.
        let module = unsafe { sys::fizzy_get_instance_module(self.inner) };
        let mut idx = 0u32;
        // SAFETY: valid module pointer and NUL-terminated name.
        let found =
            unsafe { sys::fizzy_find_exported_function_index(module, c_name.as_ptr(), &mut idx) };
        if !found {
            return None;
        }
        // SAFETY: `idx` came from a successful lookup and is therefore in range.
        let raw = unsafe { sys::fizzy_get_function_type(module, idx) };
        let ty = FuncType::from_sys(&raw).ok()?;
        Some(Func::new(idx, ty))
    }

    /// Looks up an exported linear memory by name.
    pub fn get_memory(&self, name: &str) -> Option<Memory> {
        let c_name = CString::new(name).ok()?;
        let mut out = MaybeUninit::<sys::FizzyExternalMemory>::uninit();
        // SAFETY: valid instance and NUL-terminated name; `out` is a valid
        // out-pointer that Fizzy fills iff it returns `true`.
        let found = unsafe {
            sys::fizzy_find_exported_memory(self.inner, c_name.as_ptr(), out.as_mut_ptr())
        };
        found.then(Memory::new)
    }

    /// Looks up an exported global by name.
    pub fn get_global(&self, name: &str) -> Option<Global> {
        let c_name = CString::new(name).ok()?;
        let mut out = MaybeUninit::<sys::FizzyExternalGlobal>::uninit();
        // SAFETY: valid instance and NUL-terminated name; `out` is a valid
        // out-pointer that Fizzy fills iff it returns `true`.
        let found = unsafe {
            sys::fizzy_find_exported_global(self.inner, c_name.as_ptr(), out.as_mut_ptr())
        };
        if !found {
            return None;
        }
        // SAFETY: `out` was populated by the successful lookup above.
        let out = unsafe { out.assume_init() };
        let ty = GlobalType::from_sys(out.type_).ok()?;
        Some(Global::new(c_name, ty))
    }
}

impl Drop for Instance {
    fn drop(&mut self) {
        // SAFETY: `self.inner` is owned by this `Instance`. `fizzy_free_instance`
        // is NULL-safe. Freed before `_keepalive`, so imports outlive the instance.
        unsafe { sys::fizzy_free_instance(self.inner) }
    }
}