fizzyx 0.1.1

Safe, ergonomic Rust bindings for the Fizzy WebAssembly interpreter.
Documentation
//! The C callback that bridges Fizzy host-function calls to Rust closures.

use crate::value::{FuncType, Val};
use fizzyx_sys as sys;
use std::panic::{AssertUnwindSafe, catch_unwind};

/// The boxed Rust closure backing a host function.
pub(crate) type HostFn = Box<dyn Fn(&[Val], &mut [Val])>;

/// A host function registered with a [`Linker`](crate::Linker).
///
/// The boxed closure and its signature are kept behind a stable heap address so
/// that the raw `context` pointer handed to Fizzy stays valid for as long as any
/// instance using it is alive.
pub(crate) struct HostFunc {
    ty: FuncType,
    func: HostFn,
}

impl HostFunc {
    pub(crate) fn new(ty: FuncType, func: HostFn) -> Self {
        Self { ty, func }
    }

    pub(crate) fn ty(&self) -> &FuncType {
        &self.ty
    }
}

/// The single generic trampoline used for every registered host function.
///
/// Fizzy calls this with the `context` pointer we stored, which points at a
/// [`HostFunc`]. Panics are caught and turned into traps, since unwinding across
/// the `extern "C"` boundary into Fizzy (which is `noexcept`) is undefined
/// behaviour.
pub(crate) unsafe extern "C" fn trampoline(
    host_ctx: *mut core::ffi::c_void,
    _instance: *mut sys::FizzyInstance,
    args: *const sys::FizzyValue,
    _exec_ctx: *mut sys::FizzyExecutionContext,
) -> sys::FizzyExecutionResult {
    let outcome = catch_unwind(AssertUnwindSafe(|| {
        // SAFETY: `host_ctx` is the `HostFunc` pointer we registered, kept alive
        // by the calling `Instance`.
        let host_func = unsafe { &*(host_ctx as *const HostFunc) };
        let params_ty = host_func.ty().params();
        let results_ty = host_func.ty().results();

        let mut params = Vec::with_capacity(params_ty.len());
        for (i, &ty) in params_ty.iter().enumerate() {
            // SAFETY: Fizzy guarantees `args` points to at least `params_ty.len()`
            // values of the declared types when the arity is non-zero.
            let raw = unsafe { *args.add(i) };
            params.push(Val::from_sys(raw, ty));
        }

        let mut results: Vec<Val> = results_ty
            .iter()
            .map(|&ty| Val::default_for_ty(ty))
            .collect();
        (host_func.func)(&params, &mut results);

        match results.first() {
            Some(value) => sys::FizzyExecutionResult {
                trapped: false,
                has_value: true,
                value: value.to_sys(),
            },
            None => sys::FizzyExecutionResult {
                trapped: false,
                has_value: false,
                value: sys::FizzyValue { i64_: 0 },
            },
        }
    }));

    outcome.unwrap_or(sys::FizzyExecutionResult {
        trapped: true,
        has_value: false,
        value: sys::FizzyValue { i64_: 0 },
    })
}