wasm3x 0.1.0

Safe, Wasmi/Wasmtime-shaped Rust bindings for the Wasm3 interpreter.
Documentation
//! The [`Engine`] and its [`Config`].

use std::rc::Rc;

use wasm3x_sys as ffi;

/// The strategy used to compile Wasm functions to Wasm3's internal bytecode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CompilationMode {
    /// Compile each function lazily on first call (Wasm3's native behavior).
    #[default]
    Lazy,
    /// Eagerly compile all functions of a module at instantiation time.
    ///
    /// This surfaces compilation errors up front but requires that all imports
    /// are linked before instantiation.
    Eager,
}

/// Configuration for an [`Engine`].
#[derive(Debug, Clone)]
pub struct Config {
    compilation_mode: CompilationMode,
    stack_size: u32,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            compilation_mode: CompilationMode::Lazy,
            // 1 MiB value stack; large enough for deeply recursive workloads.
            stack_size: 1024 * 1024,
        }
    }
}

impl Config {
    /// Creates a new default [`Config`].
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the [`CompilationMode`] used for modules created with this engine.
    pub fn compilation_mode(&mut self, mode: CompilationMode) -> &mut Self {
        self.compilation_mode = mode;
        self
    }

    /// Sets the size (in bytes) of the interpreter value stack of each store.
    pub fn stack_size(&mut self, bytes: usize) -> &mut Self {
        self.stack_size = bytes as u32;
        self
    }

    /// Returns the configured [`CompilationMode`].
    pub fn get_compilation_mode(&self) -> CompilationMode {
        self.compilation_mode
    }

    pub(crate) fn get_stack_size(&self) -> u32 {
        self.stack_size
    }
}

/// A Wasm3 execution environment, shared by all stores created from it.
///
/// An [`Engine`] wraps a Wasm3 `IM3Environment`. It is cheap to clone (an
/// atomically reference-counted handle). It is **not** thread-safe and thus is
/// neither [`Send`] nor [`Sync`].
#[derive(Clone)]
pub struct Engine {
    inner: Rc<EngineInner>,
}

struct EngineInner {
    env: ffi::IM3Environment,
    config: Config,
}

impl Drop for EngineInner {
    fn drop(&mut self) {
        // SAFETY: `env` was created by `m3_NewEnvironment` and is freed once.
        unsafe { ffi::m3_FreeEnvironment(self.env) }
    }
}

impl Engine {
    /// Creates a new [`Engine`] with the given [`Config`].
    pub fn new(config: &Config) -> Self {
        // SAFETY: trivial FFI call.
        let env = unsafe { ffi::m3_NewEnvironment() };
        assert!(!env.is_null(), "wasm3: failed to allocate environment");
        Self {
            inner: Rc::new(EngineInner {
                env,
                config: config.clone(),
            }),
        }
    }

    /// Returns the [`Config`] this engine was created with.
    pub fn config(&self) -> &Config {
        &self.inner.config
    }

    /// Returns `true` if `a` and `b` refer to the same underlying environment.
    pub fn same(a: &Engine, b: &Engine) -> bool {
        Rc::ptr_eq(&a.inner, &b.inner)
    }

    pub(crate) fn raw(&self) -> ffi::IM3Environment {
        self.inner.env
    }
}

impl Default for Engine {
    fn default() -> Self {
        Self::new(&Config::default())
    }
}