Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220//! 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<dyn HostFuncTrampoline>;
/// 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<Vec<WasmValue>, 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<Vec<WasmValue>, 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<Vec<WasmValue>, Trap> { self.call(args) }}
impl<F> HostFuncTrampoline for Fwhere F: Fn(&[WasmValue]) -> Result<Vec<WasmValue>, Trap>,{ fn call(&self, args: &[WasmValue]) -> Result<Vec<WasmValue>, Trap> { (self)(args) }}
/// One function instance owned by the [`Store`].////// [`Store`]: crate::runtime::Storepub 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<ValType>, /// Instruction stream, including the trailing `End`. body: Vec<Instruction>, }, /// 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<ModuleAddr> { 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<ValType>, results: Vec<ValType>) -> 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<Vec<WasmValue>, 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); }}