summaryrefslogtreecommitdiff
path: root/crates/tinywasm/src/instance.rs
blob: 11deedc2f72fc965756693ccceba392384d49304 (plain)
use alloc::{boxed::Box, format, string::ToString, sync::Arc};
use tinywasm_types::{
    DataAddr, ElemAddr, Export, ExternVal, ExternalKind, FuncAddr, FuncType, GlobalAddr, Import, MemAddr,
    ModuleInstanceAddr, TableAddr,
};

use crate::{
    func::{FromWasmValueTuple, IntoWasmValueTuple},
    Error, FuncHandle, Imports, Module, Result, Store, TypedFuncHandle,
};

/// A WebAssembly Module Instance
///
/// Backed by an Arc, so cloning is cheap
///
/// See <https://webassembly.github.io/spec/core/exec/runtime.html#module-instances>
#[derive(Debug, Clone)]
pub struct ModuleInstance(Arc<ModuleInstanceInner>);

#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct ModuleInstanceInner {
    pub(crate) store_id: usize,
    pub(crate) idx: ModuleInstanceAddr,

    pub(crate) types: Box<[FuncType]>,

    pub(crate) func_addrs: Box<[FuncAddr]>,
    pub(crate) table_addrs: Box<[TableAddr]>,
    pub(crate) mem_addrs: Box<[MemAddr]>,
    pub(crate) global_addrs: Box<[GlobalAddr]>,
    pub(crate) elem_addrs: Box<[ElemAddr]>,
    pub(crate) data_addrs: Box<[DataAddr]>,

    pub(crate) func_start: Option<FuncAddr>,
    pub(crate) imports: Box<[Import]>,
    pub(crate) exports: Box<[Export]>,
}

impl ModuleInstance {
    // drop the module instance reference and swap it with another one
    pub(crate) fn swap(&mut self, other: Self) {
        self.0 = other.0;
    }

    /// Get the module instance's address
    pub fn id(&self) -> ModuleInstanceAddr {
        self.0.idx
    }

    /// Instantiate the module in the given store
    ///
    /// See <https://webassembly.github.io/spec/core/exec/modules.html#exec-instantiation>
    pub fn instantiate(store: &mut Store, module: Module, imports: Option<Imports>) -> Result<Self> {
        // This doesn't completely follow the steps in the spec, but the end result is the same
        // Constant expressions are evaluated directly where they are used, so we
        // don't need to create a auxiliary frame etc.

        let idx = store.next_module_instance_idx();
        let imports = imports.unwrap_or_default();

        let mut addrs = imports.link(store, &module, idx)?;
        let data = module.data;

        // TODO: check if the compiler correctly optimizes this to prevent wasted allocations
        addrs.globals.extend(store.init_globals(data.globals.into(), idx)?);
        addrs.funcs.extend(store.init_funcs(data.funcs.into(), idx)?);
        addrs.tables.extend(store.init_tables(data.table_types.into(), idx)?);
        addrs.memories.extend(store.init_memories(data.memory_types.into(), idx)?);

        let elem_addrs = store.init_elements(&addrs.tables, data.elements.into(), idx)?;
        let data_addrs = store.init_datas(&addrs.memories, data.data.into(), idx)?;

        let instance = ModuleInstanceInner {
            store_id: store.id(),
            idx,

            types: data.func_types,
            func_addrs: addrs.funcs.into_boxed_slice(),
            table_addrs: addrs.tables.into_boxed_slice(),
            mem_addrs: addrs.memories.into_boxed_slice(),
            global_addrs: addrs.globals.into_boxed_slice(),
            elem_addrs,
            data_addrs: data_addrs,
            func_start: data.start_func,
            imports: data.imports,
            exports: data.exports,
        };

        let instance = ModuleInstance::new(instance);
        store.add_instance(instance.clone())?;

        Ok(instance)
    }

    /// Get a export by name
    pub fn export(&self, name: &str) -> Option<ExternVal> {
        let exports = self.0.exports.iter().find(|e| e.name == name.into())?;
        let kind = exports.kind.clone();
        let addr = match kind {
            ExternalKind::Func => self.0.func_addrs.get(exports.index as usize)?,
            ExternalKind::Table => self.0.table_addrs.get(exports.index as usize)?,
            ExternalKind::Memory => self.0.mem_addrs.get(exports.index as usize)?,
            ExternalKind::Global => self.0.global_addrs.get(exports.index as usize)?,
        };

        Some(ExternVal::new(kind, *addr))
    }

    pub(crate) fn func_addrs(&self) -> &[FuncAddr] {
        &self.0.func_addrs
    }

    pub(crate) fn _global_addrs(&self) -> &[GlobalAddr] {
        &self.0.global_addrs
    }

    pub(crate) fn func_ty_addrs(&self) -> &[FuncType] {
        &self.0.types
    }

    pub(crate) fn new(inner: ModuleInstanceInner) -> Self {
        Self(Arc::new(inner))
    }

    pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType {
        self.0.types.get(addr as usize).expect("No func type for func, this is a bug")
    }

    // resolve a function address to the global store address
    pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr {
        *self.0.func_addrs.get(addr as usize).expect("No func addr for func, this is a bug")
    }

    // resolve a table address to the global store address
    pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr {
        *self.0.table_addrs.get(addr as usize).expect("No table addr for table, this is a bug")
    }

    // resolve a memory address to the global store address
    pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr {
        *self.0.mem_addrs.get(addr as usize).expect("No mem addr for mem, this is a bug")
    }

    // resolve a memory address to the global store address
    pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr {
        *self.0.elem_addrs.get(addr as usize).expect("No elem addr for elem, this is a bug")
    }

    // resolve a global address to the global store address
    pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr {
        self.0.global_addrs[addr as usize]
    }

    /// Get an exported function by name
    pub fn exported_func_by_name(&self, store: &Store, name: &str) -> Result<FuncHandle> {
        if self.0.store_id != store.id() {
            return Err(Error::InvalidStore);
        }

        let export = self.export(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?;
        let ExternVal::Func(func_addr) = export else {
            return Err(Error::Other(format!("Export is not a function: {}", name)));
        };

        let func_inst = store.get_func(func_addr as usize)?;
        let ty = func_inst.func.ty();

        Ok(FuncHandle { addr: func_addr, module: self.clone(), name: Some(name.to_string()), ty: ty.clone() })
    }

    /// Get a typed exported function by name
    pub fn typed_func<P, R>(&self, store: &Store, name: &str) -> Result<TypedFuncHandle<P, R>>
    where
        P: IntoWasmValueTuple,
        R: FromWasmValueTuple,
    {
        let func = self.exported_func_by_name(store, name)?;
        Ok(TypedFuncHandle { func, marker: core::marker::PhantomData })
    }

    /// Get the start function of the module
    ///
    /// Returns None if the module has no start function
    /// If no start function is specified, also checks for a _start function in the exports
    /// (which is not part of the spec, but used by llvm)
    ///
    /// See <https://webassembly.github.io/spec/core/syntax/modules.html#start-function>
    pub fn start_func(&self, store: &Store) -> Result<Option<FuncHandle>> {
        if self.0.store_id != store.id() {
            return Err(Error::InvalidStore);
        }

        let func_index = match self.0.func_start {
            Some(func_index) => func_index,
            None => {
                // alternatively, check for a _start function in the exports
                let Some(ExternVal::Func(func_addr)) = self.export("_start") else {
                    return Ok(None);
                };

                func_addr
            }
        };

        let func_addr = self.0.func_addrs.get(func_index as usize).expect("No func addr for start func, this is a bug");
        let func_inst = store.get_func(*func_addr as usize)?;
        let ty = func_inst.func.ty();

        Ok(Some(FuncHandle { module: self.clone(), addr: *func_addr, ty: ty.clone(), name: None }))
    }

    /// Invoke the start function of the module
    ///
    /// Returns None if the module has no start function
    ///
    /// See <https://webassembly.github.io/spec/core/syntax/modules.html#syntax-start>
    pub fn start(&self, store: &mut Store) -> Result<Option<()>> {
        let Some(func) = self.start_func(store)? else {
            return Ok(None);
        };

        let _ = func.call(store, &[])?;
        Ok(Some(()))
    }
}