fizzyx 0.1.1

Safe, ergonomic Rust bindings for the Fizzy WebAssembly interpreter.
Documentation
//! WebAssembly value and type definitions.
//!
//! Fizzy implements the WebAssembly 1.0 (MVP) specification, so only the four
//! numeric value types (`i32`, `i64`, `f32`, `f64`) exist and a function returns
//! at most a single result.

use crate::error::{Error, Result};
use fizzyx_sys as sys;

/// A WebAssembly value type.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ValType {
    /// A 32-bit integer.
    I32,
    /// A 64-bit integer.
    I64,
    /// A 32-bit IEEE-754 floating point number.
    F32,
    /// A 64-bit IEEE-754 floating point number.
    F64,
}

impl ValType {
    /// Converts a raw Fizzy value type into a [`ValType`].
    ///
    /// Returns [`Error::UnsupportedType`] for `void` or any unknown encoding.
    pub(crate) fn from_sys(raw: sys::FizzyValueType) -> Result<Self> {
        match raw {
            sys::FizzyValueTypeI32 => Ok(Self::I32),
            sys::FizzyValueTypeI64 => Ok(Self::I64),
            sys::FizzyValueTypeF32 => Ok(Self::F32),
            sys::FizzyValueTypeF64 => Ok(Self::F64),
            _ => Err(Error::UnsupportedType),
        }
    }

    /// Returns the textual name of the value type (e.g. `"i32"`).
    pub fn name(self) -> &'static str {
        match self {
            Self::I32 => "i32",
            Self::I64 => "i64",
            Self::F32 => "f32",
            Self::F64 => "f64",
        }
    }

    /// Converts the [`ValType`] into its raw Fizzy encoding.
    pub(crate) fn to_sys(self) -> sys::FizzyValueType {
        match self {
            Self::I32 => sys::FizzyValueTypeI32,
            Self::I64 => sys::FizzyValueTypeI64,
            Self::F32 => sys::FizzyValueTypeF32,
            Self::F64 => sys::FizzyValueTypeF64,
        }
    }
}

/// A WebAssembly value.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Val {
    /// A 32-bit integer.
    I32(i32),
    /// A 64-bit integer.
    I64(i64),
    /// A 32-bit float, stored as raw bits to preserve NaN payloads.
    F32(f32),
    /// A 64-bit float, stored as raw bits to preserve NaN payloads.
    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 zero 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 value as `i32` if it is an [`Val::I32`].
    pub fn i32(&self) -> Option<i32> {
        match self {
            Self::I32(value) => Some(*value),
            _ => None,
        }
    }

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

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

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

    /// Converts this value into the raw Fizzy value union.
    pub(crate) fn to_sys(self) -> sys::FizzyValue {
        match self {
            Self::I32(value) => sys::FizzyValue { i32_: value as u32 },
            Self::I64(value) => sys::FizzyValue { i64_: value as u64 },
            Self::F32(value) => sys::FizzyValue { f32_: value },
            Self::F64(value) => sys::FizzyValue { f64_: value },
        }
    }

    /// Reinterprets a raw Fizzy value union as the given [`ValType`].
    ///
    /// # Safety
    ///
    /// `raw` must have been produced for a value of type `ty`; the union is
    /// otherwise untyped.
    pub(crate) fn from_sys(raw: sys::FizzyValue, ty: ValType) -> Self {
        // SAFETY: the caller guarantees `raw` holds a value of type `ty`, and
        // every union field is a plain `Copy` scalar of matching size.
        unsafe {
            match ty {
                ValType::I32 => Self::I32(raw.i32_ as i32),
                ValType::I64 => Self::I64(raw.i64_ as i64),
                ValType::F32 => Self::F32(raw.f32_),
                ValType::F64 => Self::F64(raw.f64_),
            }
        }
    }
}

impl From<i32> for Val {
    fn from(value: i32) -> Self {
        Self::I32(value)
    }
}

impl From<i64> for Val {
    fn from(value: i64) -> Self {
        Self::I64(value)
    }
}

impl From<f32> for Val {
    fn from(value: f32) -> Self {
        Self::F32(value)
    }
}

impl From<f64> for Val {
    fn from(value: f64) -> Self {
        Self::F64(value)
    }
}

/// The mutability of a global variable.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Mutability {
    /// An immutable (`const`) global.
    Const,
    /// A mutable (`var`) global.
    Mutable,
}

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

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

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

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

    pub(crate) fn from_sys(raw: sys::FizzyGlobalType) -> Result<Self> {
        let content = ValType::from_sys(raw.value_type)?;
        let mutability = if raw.is_mutable {
            Mutability::Mutable
        } else {
            Mutability::Const
        };
        Ok(Self::new(content, mutability))
    }
}

/// The type of a linear memory: its minimum and optional maximum size in pages.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct MemoryType {
    minimum: u32,
    maximum: Option<u32>,
}

impl MemoryType {
    /// Creates a new [`MemoryType`] from a minimum and optional maximum page count.
    pub fn new(minimum: u32, maximum: Option<u32>) -> Self {
        Self { minimum, maximum }
    }

    /// Returns the minimum size of the memory in pages (64 KiB each).
    pub fn minimum(&self) -> u32 {
        self.minimum
    }

    /// Returns the maximum size of the memory in pages, if any.
    pub fn maximum(&self) -> Option<u32> {
        self.maximum
    }

    pub(crate) fn from_sys(raw: sys::FizzyLimits) -> Self {
        Self::new(raw.min, raw.has_max.then_some(raw.max))
    }
}

/// The signature of a function: its parameter and result types.
///
/// Since Fizzy targets WebAssembly 1.0, [`FuncType::results`] contains at most
/// one entry.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FuncType {
    params: Box<[ValType]>,
    results: Box<[ValType]>,
}

impl FuncType {
    /// Creates a new [`FuncType`] from the given 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 of the function.
    pub fn params(&self) -> &[ValType] {
        &self.params
    }

    /// Returns the result types of the function (at most one for Fizzy).
    pub fn results(&self) -> &[ValType] {
        &self.results
    }

    pub(crate) fn from_sys(raw: &sys::FizzyFunctionType) -> Result<Self> {
        let params = if raw.inputs_size == 0 {
            Vec::new()
        } else {
            // SAFETY: Fizzy guarantees `inputs` points to `inputs_size` valid
            // value types when `inputs_size > 0`.
            let inputs = unsafe { core::slice::from_raw_parts(raw.inputs, raw.inputs_size) };
            inputs
                .iter()
                .map(|&ty| ValType::from_sys(ty))
                .collect::<Result<Vec<_>>>()?
        };
        let results = if raw.output == sys::FizzyValueTypeVoid {
            Vec::new()
        } else {
            vec![ValType::from_sys(raw.output)?]
        };
        Ok(Self::new(params, results))
    }

    /// Returns the raw input value-type array and the output value type used to
    /// build a [`sys::FizzyFunctionType`].
    pub(crate) fn to_sys_parts(&self) -> (Vec<sys::FizzyValueType>, sys::FizzyValueType) {
        let inputs = self.params.iter().map(|ty| ty.to_sys()).collect();
        let output = self
            .results
            .first()
            .map(|ty| ty.to_sys())
            .unwrap_or(sys::FizzyValueTypeVoid);
        (inputs, output)
    }
}