//! Function instances: either a wasm body or a host callback. //! //! A wasm function carries its declared type, an expanded locals layout, //! the body's bytecode, and a back-pointer to the [`ModuleInstance`] it was //! defined in so the interpreter can resolve module-local indices (functions, //! memories, tables, globals) on every dispatch. A host function instead //! holds a Rust callable that runs synchronously inside the interpreter when //! the function is invoked. use crate::instruction::Instruction; use crate::runtime::store::ModuleAddr; use crate::runtime::trap::Trap; use crate::runtime::value::WasmValue; use crate::types::{FuncType, ValType}; /// A boxed Rust callable used by [`FunctionInstance::Host`]. We don't require /// `Send`/`Sync`: the engine runs single-threaded inside one JS realm, and /// keeping the bound looser lets hosts capture `Rc`/`RefCell` state. pub type HostFunc = Box; /// Host-side context available to callbacks that need to cross back into an /// embedder. Plain Rust host functions ignore this and use [`call`]. pub trait HostContext { /// Invoke an embedder-owned host function by id. fn invoke_host_func(&mut self, id: u32, args: &[WasmValue]) -> Result, Trap>; } /// Trait object behind [`HostFunc`]. Boxed so multiple host functions can /// share a function table without leaking the concrete closure type. pub trait HostFuncTrampoline { /// Invoke the host function with the given wasm arguments. `args` is /// already coerced and length-checked against the function type; the /// callee returns its result vector (which the interpreter then /// type-checks against the declared results). fn call(&self, args: &[WasmValue]) -> Result, Trap>; /// Context-aware entry point for embedders that need to bridge a wasm host /// call into another runtime, such as JavaScript. fn call_with_context( &self, args: &[WasmValue], _context: Option<&mut dyn HostContext>, ) -> Result, Trap> { self.call(args) } } impl HostFuncTrampoline for F where F: Fn(&[WasmValue]) -> Result, Trap>, { fn call(&self, args: &[WasmValue]) -> Result, Trap> { (self)(args) } } /// One function instance owned by the [`Store`]. /// /// [`Store`]: crate::runtime::Store pub enum FunctionInstance { /// A wasm-defined function. Wasm { ty: FuncType, /// The module that defined this function. Used to resolve local /// indices (globals, memories, tables, …) when the interpreter /// executes the body. module: ModuleAddr, /// Expanded locals: one [`ValType`] per local slot, with the /// declared parameter types already removed (they live on the /// activation frame as initial locals). locals: Vec, /// Instruction stream, including the trailing `End`. body: Vec, }, /// A function supplied by the host (typically the JS bindings). Host { ty: FuncType, callback: HostFunc }, } impl FunctionInstance { /// Declared type — params + results. pub fn ty(&self) -> &FuncType { match self { FunctionInstance::Wasm { ty, .. } | FunctionInstance::Host { ty, .. } => ty, } } /// True if this is a host function rather than a wasm body. pub fn is_host(&self) -> bool { matches!(self, FunctionInstance::Host { .. }) } /// Returns the locals layout for a wasm function. None for host /// functions. pub fn locals(&self) -> Option<&[ValType]> { match self { FunctionInstance::Wasm { locals, .. } => Some(locals), FunctionInstance::Host { .. } => None, } } /// Returns the body for a wasm function. None for host functions. pub fn body(&self) -> Option<&[Instruction]> { match self { FunctionInstance::Wasm { body, .. } => Some(body), FunctionInstance::Host { .. } => None, } } /// Returns the owning module address for a wasm function. None for /// host functions. pub fn module(&self) -> Option { match self { FunctionInstance::Wasm { module, .. } => Some(*module), FunctionInstance::Host { .. } => None, } } } impl std::fmt::Debug for FunctionInstance { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FunctionInstance::Wasm { ty, module, locals, body, } => f .debug_struct("FunctionInstance::Wasm") .field("ty", ty) .field("module", module) .field("locals", locals) .field("body_len", &body.len()) .finish(), FunctionInstance::Host { ty, .. } => f .debug_struct("FunctionInstance::Host") .field("ty", ty) .finish_non_exhaustive(), } } } #[cfg(test)] mod tests { use super::*; fn ft(params: Vec, results: Vec) -> FuncType { FuncType { params, results } } #[test] fn wasm_function_accessors() { let f = FunctionInstance::Wasm { ty: ft(vec![ValType::I32], vec![ValType::I32]), module: ModuleAddr::from_raw(3), locals: vec![ValType::I32, ValType::I64], body: vec![Instruction::LocalGet(0), Instruction::End], }; assert!(!f.is_host()); assert_eq!(f.ty().params, vec![ValType::I32]); assert_eq!(f.ty().results, vec![ValType::I32]); assert_eq!(f.locals().unwrap(), &[ValType::I32, ValType::I64]); assert_eq!(f.body().unwrap().len(), 2); assert_eq!(f.module(), Some(ModuleAddr::from_raw(3))); } #[test] fn host_function_invocation() { let cb: HostFunc = Box::new(|args: &[WasmValue]| -> Result, Trap> { match args { [WasmValue::I32(a), WasmValue::I32(b)] => Ok(vec![WasmValue::I32(a + b)]), _ => Err(Trap::IndirectCallTypeMismatch), } }); let f = FunctionInstance::Host { ty: ft(vec![ValType::I32, ValType::I32], vec![ValType::I32]), callback: cb, }; assert!(f.is_host()); assert!(f.locals().is_none()); assert!(f.body().is_none()); assert!(f.module().is_none()); let result = match &f { FunctionInstance::Host { callback, .. } => { callback.call(&[WasmValue::I32(2), WasmValue::I32(3)]) } _ => unreachable!(), }; assert_eq!(result.unwrap(), vec![WasmValue::I32(5)]); } #[test] fn host_function_propagates_trap() { let cb: HostFunc = Box::new(|_: &[WasmValue]| Err(Trap::Unreachable)); let f = FunctionInstance::Host { ty: ft(vec![], vec![]), callback: cb, }; let res = match &f { FunctionInstance::Host { callback, .. } => callback.call(&[]), _ => unreachable!(), }; assert_eq!(res, Err(Trap::Unreachable)); } #[test] fn debug_format_does_not_dump_body() { let f = FunctionInstance::Wasm { ty: ft(vec![], vec![]), module: ModuleAddr::from_raw(0), locals: vec![], body: vec![Instruction::End; 10_000], }; let s = format!("{:?}", f); assert!(s.contains("body_len")); assert!(s.contains("10000")); // Make sure we're not formatting the 10k entries inline. assert!(s.len() < 500); } }