baedeker_core/runtime/host.rs
1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Host functions: native callables registered with a [`Store`](crate::runtime::Store) that WASM
5//! modules can import and call.
6//!
7//! Registration is by `(module, name)` against the module's function import
8//! declarations, with the host function's signature checked at registration.
9//! Calls resolve lazily: an unregistered import fails only when called.
10
11use alloc::boxed::Box;
12use alloc::vec::Vec;
13
14use crate::runtime::{RuntimeError, RuntimeErrorKind, Value, execute_func_in};
15use crate::types::FuncType;
16
17/// The native closure behind a [`HostFunction`].
18type HostClosure = Box<dyn FnMut(&[Value]) -> Result<Vec<Value>, RuntimeError>>;
19
20/// A host function callable from WASM, wrapping a native closure with its
21/// declared WASM signature.
22pub struct HostFunction {
23 ty: FuncType,
24 func: HostClosure,
25}
26
27impl HostFunction {
28 /// Wrap a closure with its WASM signature. The signature is checked
29 /// against the import declaration at registration; argument values are
30 /// passed positionally and results must match the declared result types.
31 pub fn new(
32 ty: FuncType,
33 func: impl FnMut(&[Value]) -> Result<Vec<Value>, RuntimeError> + 'static,
34 ) -> Self {
35 Self {
36 ty,
37 func: Box::new(func),
38 }
39 }
40
41 /// The declared WASM signature.
42 pub fn ty(&self) -> &FuncType {
43 &self.ty
44 }
45
46 /// Invoke the host function.
47 pub fn call(&mut self, args: &[Value]) -> Result<Vec<Value>, RuntimeError> {
48 (self.func)(args)
49 }
50}
51
52impl core::fmt::Debug for HostFunction {
53 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54 f.debug_struct("HostFunction")
55 .field("ty", &self.ty)
56 .finish()
57 }
58}
59
60/// Link a function exported by another module instance: the resulting
61/// [`HostFunction`] executes `func_idx` against that instance's store when
62/// called, so cross-module calls work with each instance's own state.
63///
64/// Register it as an import on the calling module's store, e.g.
65/// `store_b.register_host_func("a", "add", link_func(module_a, store_a, idx, ty))`.
66///
67/// Cross-module call chains are bounded by [`RuntimeErrorKind::ReentrantStore`]:
68/// a call that re-enters a store already executing fails rather than
69/// deadlocking (mutual recursion across modules is not yet supported).
70pub fn link_func(
71 module: alloc::rc::Rc<crate::lower::RegModule>,
72 store: alloc::rc::Rc<core::cell::RefCell<crate::runtime::Store>>,
73 func_idx: crate::types::FuncIdx,
74 ty: FuncType,
75) -> HostFunction {
76 HostFunction::new(ty, move |args| {
77 let Some(func) = module.funcs.iter().find(|func| func.idx == func_idx) else {
78 return Err(RuntimeError {
79 kind: RuntimeErrorKind::UnknownFunction { func: func_idx.0 },
80 });
81 };
82 let store = store.try_borrow().map_err(|_| RuntimeError {
83 kind: RuntimeErrorKind::ReentrantStore,
84 })?;
85 execute_func_in(Some(&module), Some(&*store), func, args, 0)
86 })
87}