fizzyx 0.1.0

Safe, ergonomic Rust bindings for the Fizzy WebAssembly interpreter.
Documentation
//! Handles to exported functions.

use crate::error::{Error, Result};
use crate::instance::Instance;
use crate::value::{FuncType, Val};
use fizzyx_sys as sys;

/// A handle to a function exported by an [`Instance`].
///
/// Obtained via [`Instance::get_func`]. A [`Func`] records the function's index
/// and signature; calling it requires the owning instance to be passed back in.
#[derive(Debug, Clone)]
pub struct Func {
    func_idx: u32,
    ty: FuncType,
}

impl Func {
    pub(crate) fn new(func_idx: u32, ty: FuncType) -> Self {
        Self { func_idx, ty }
    }

    /// Returns the signature of the function.
    pub fn ty(&self) -> &FuncType {
        &self.ty
    }

    /// Calls the function on `instance` with `params`, writing any result into
    /// `results`.
    ///
    /// `params` must match the function's parameter types exactly, and `results`
    /// must have space for the function's results (at most one for Fizzy).
    ///
    /// # Errors
    ///
    /// Returns [`Error::ArityMismatch`] or [`Error::TypeMismatch`] if the buffers
    /// do not match the signature, or [`Error::Trap`] if execution traps.
    pub fn call(&self, instance: &mut Instance, params: &[Val], results: &mut [Val]) -> Result<()> {
        let expected_params = self.ty.params();
        let expected_results = self.ty.results();

        if params.len() != expected_params.len() {
            return Err(Error::ArityMismatch {
                expected: expected_params.len(),
                provided: params.len(),
            });
        }
        if results.len() < expected_results.len() {
            return Err(Error::ArityMismatch {
                expected: expected_results.len(),
                provided: results.len(),
            });
        }
        for (index, (param, &expected)) in params.iter().zip(expected_params).enumerate() {
            if param.ty() != expected {
                return Err(Error::TypeMismatch {
                    index,
                    expected: expected.name(),
                    found: param.ty().name(),
                });
            }
        }

        let args: Vec<sys::FizzyValue> = params.iter().map(|value| value.to_sys()).collect();
        let args_ptr = if args.is_empty() {
            core::ptr::null()
        } else {
            args.as_ptr()
        };

        // SAFETY: `instance` is valid, `func_idx` was obtained from it, and `args`
        // matches the checked parameter arity/types. A NULL execution context asks
        // Fizzy to allocate a default one for the call.
        let result = unsafe {
            sys::fizzy_execute(
                instance.as_ptr(),
                self.func_idx,
                args_ptr,
                core::ptr::null_mut(),
            )
        };

        if result.trapped {
            return Err(Error::Trap);
        }
        if let Some(&result_ty) = expected_results.first() {
            results[0] = Val::from_sys(result.value, result_ty);
        }
        Ok(())
    }
}