Skip to main content

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}