wasm3x 0.1.0

Safe, Wasmi/Wasmtime-shaped Rust bindings for the Wasm3 interpreter.
Documentation
//! Wasm value and type primitives.

use std::ffi::CString;

use wasm3x_sys as ffi;

use crate::error::{Error, Result};

/// The type of a Wasm value.
///
/// Wasm3's raw C-API additionally distinguishes `v128` and `unknown`, which are
/// not representable through this safe wrapper.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValType {
    /// A 32-bit integer.
    I32,
    /// A 64-bit integer.
    I64,
    /// A 32-bit IEEE-754 float.
    F32,
    /// A 64-bit IEEE-754 float.
    F64,
}

impl ValType {
    /// Converts a raw `M3ValueType` into a [`ValType`], if representable.
    pub(crate) fn from_ffi(raw: ffi::M3ValueType::Type) -> Option<Self> {
        match raw {
            ffi::M3ValueType::c_m3Type_i32 => Some(Self::I32),
            ffi::M3ValueType::c_m3Type_i64 => Some(Self::I64),
            ffi::M3ValueType::c_m3Type_f32 => Some(Self::F32),
            ffi::M3ValueType::c_m3Type_f64 => Some(Self::F64),
            _ => None,
        }
    }

    pub(crate) fn to_ffi(self) -> ffi::M3ValueType::Type {
        match self {
            Self::I32 => ffi::M3ValueType::c_m3Type_i32,
            Self::I64 => ffi::M3ValueType::c_m3Type_i64,
            Self::F32 => ffi::M3ValueType::c_m3Type_f32,
            Self::F64 => ffi::M3ValueType::c_m3Type_f64,
        }
    }

    /// The Wasm3 signature character used when linking host functions.
    pub(crate) fn signature_char(self) -> u8 {
        match self {
            Self::I32 => b'i',
            Self::I64 => b'I',
            Self::F32 => b'f',
            Self::F64 => b'F',
        }
    }
}

/// A runtime Wasm value.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Val {
    /// A 32-bit integer value.
    I32(i32),
    /// A 64-bit integer value.
    I64(i64),
    /// A 32-bit float value.
    F32(f32),
    /// A 64-bit float value.
    F64(f64),
}

impl Val {
    /// Returns the [`ValType`] of this value.
    pub fn ty(&self) -> ValType {
        match self {
            Self::I32(_) => ValType::I32,
            Self::I64(_) => ValType::I64,
            Self::F32(_) => ValType::F32,
            Self::F64(_) => ValType::F64,
        }
    }

    /// Returns the default (zeroed) value for the given [`ValType`].
    pub fn default_for_ty(ty: ValType) -> Self {
        match ty {
            ValType::I32 => Self::I32(0),
            ValType::I64 => Self::I64(0),
            ValType::F32 => Self::F32(0.0),
            ValType::F64 => Self::F64(0.0),
        }
    }

    /// Returns the contained `i32`, if this is an [`Val::I32`].
    pub fn i32(&self) -> Option<i32> {
        match self {
            Self::I32(v) => Some(*v),
            _ => None,
        }
    }

    /// Returns the contained `i64`, if this is an [`Val::I64`].
    pub fn i64(&self) -> Option<i64> {
        match self {
            Self::I64(v) => Some(*v),
            _ => None,
        }
    }

    /// Returns the contained `f32`, if this is an [`Val::F32`].
    pub fn f32(&self) -> Option<f32> {
        match self {
            Self::F32(v) => Some(*v),
            _ => None,
        }
    }

    /// Returns the contained `f64`, if this is an [`Val::F64`].
    pub fn f64(&self) -> Option<f64> {
        match self {
            Self::F64(v) => Some(*v),
            _ => None,
        }
    }

    /// Encodes this value into a 64-bit Wasm3 stack slot (little-endian).
    pub(crate) fn to_slot(self) -> u64 {
        match self {
            Self::I32(v) => v as u32 as u64,
            Self::I64(v) => v as u64,
            Self::F32(v) => v.to_bits() as u64,
            Self::F64(v) => v.to_bits(),
        }
    }

    /// Decodes a 64-bit Wasm3 stack slot into a value of the given type.
    pub(crate) fn from_slot(ty: ValType, slot: u64) -> Self {
        match ty {
            ValType::I32 => Self::I32(slot as u32 as i32),
            ValType::I64 => Self::I64(slot as i64),
            ValType::F32 => Self::F32(f32::from_bits(slot as u32)),
            ValType::F64 => Self::F64(f64::from_bits(slot)),
        }
    }
}

impl From<i32> for Val {
    fn from(v: i32) -> Self {
        Self::I32(v)
    }
}
impl From<i64> for Val {
    fn from(v: i64) -> Self {
        Self::I64(v)
    }
}
impl From<f32> for Val {
    fn from(v: f32) -> Self {
        Self::F32(v)
    }
}
impl From<f64> for Val {
    fn from(v: f64) -> Self {
        Self::F64(v)
    }
}

/// Whether a global variable can be mutated after instantiation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Mutability {
    /// The global is mutable (declared `(mut T)`).
    Mutable,
    /// The global is immutable (a constant).
    Const,
}

/// The type of a global variable: its value type and mutability.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GlobalType {
    content: ValType,
    mutability: Mutability,
}

impl GlobalType {
    /// Creates a new [`GlobalType`] from its value type and mutability.
    pub fn new(content: ValType, mutability: Mutability) -> Self {
        Self {
            content,
            mutability,
        }
    }

    /// Returns the value type of the global.
    pub fn content(&self) -> ValType {
        self.content
    }

    /// Returns the mutability of the global.
    pub fn mutability(&self) -> Mutability {
        self.mutability
    }
}

/// The signature (parameter and result types) of a Wasm function.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FuncType {
    params: Box<[ValType]>,
    results: Box<[ValType]>,
}

impl FuncType {
    /// Creates a new [`FuncType`] from parameter and result types.
    pub fn new<P, R>(params: P, results: R) -> Self
    where
        P: IntoIterator<Item = ValType>,
        R: IntoIterator<Item = ValType>,
    {
        Self {
            params: params.into_iter().collect(),
            results: results.into_iter().collect(),
        }
    }

    /// Returns the parameter types.
    pub fn params(&self) -> &[ValType] {
        &self.params
    }

    /// Returns the result types.
    pub fn results(&self) -> &[ValType] {
        &self.results
    }

    /// Builds the Wasm3 signature string (e.g. `"i(iiI)"`) for host linking.
    ///
    /// Wasm3's signature format only encodes a single return value, so host
    /// functions with more than one result cannot be expressed.
    pub(crate) fn signature_cstring(&self) -> Result<CString> {
        if self.results.len() > 1 {
            return Err(Error::mismatch(
                "wasm3 host functions cannot return more than one value",
            ));
        }
        let mut bytes = Vec::with_capacity(self.params.len() + 3);
        bytes.push(self.results.first().map_or(b'v', |ty| ty.signature_char()));
        bytes.push(b'(');
        for param in self.params.iter() {
            bytes.push(param.signature_char());
        }
        bytes.push(b')');
        CString::new(bytes).map_err(|_| Error::mismatch("invalid function signature"))
    }
}