tinywasm/
imports.rs

1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::rc::Rc;
4use alloc::string::{String, ToString};
5use alloc::vec::Vec;
6use core::fmt::Debug;
7
8use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple};
9use crate::{log, LinkingError, MemoryRef, MemoryRefMut, Result};
10use tinywasm_types::*;
11
12/// The internal representation of a function
13#[derive(Debug, Clone)]
14pub enum Function {
15    /// A host function
16    Host(Rc<HostFunction>),
17
18    /// A pointer to a WebAssembly function
19    Wasm(Rc<WasmFunction>),
20}
21
22impl Function {
23    pub(crate) fn ty(&self) -> &FuncType {
24        match self {
25            Self::Host(f) => &f.ty,
26            Self::Wasm(f) => &f.ty,
27        }
28    }
29}
30
31/// A host function
32pub struct HostFunction {
33    pub(crate) ty: tinywasm_types::FuncType,
34    pub(crate) func: HostFuncInner,
35}
36
37impl HostFunction {
38    /// Get the function's type
39    pub fn ty(&self) -> &tinywasm_types::FuncType {
40        &self.ty
41    }
42
43    /// Call the function
44    pub fn call(&self, ctx: FuncContext<'_>, args: &[WasmValue]) -> Result<Vec<WasmValue>> {
45        (self.func)(ctx, args)
46    }
47}
48
49pub(crate) type HostFuncInner = Box<dyn Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>>>;
50
51/// The context of a host-function call
52#[derive(Debug)]
53pub struct FuncContext<'a> {
54    pub(crate) store: &'a mut crate::Store,
55    pub(crate) module_addr: ModuleInstanceAddr,
56}
57
58impl FuncContext<'_> {
59    /// Get a reference to the store
60    pub fn store(&self) -> &crate::Store {
61        self.store
62    }
63
64    /// Get a mutable reference to the store
65    pub fn store_mut(&mut self) -> &mut crate::Store {
66        self.store
67    }
68
69    /// Get a reference to the module instance
70    pub fn module(&self) -> crate::ModuleInstance {
71        self.store.get_module_instance_raw(self.module_addr)
72    }
73
74    /// Get a reference to an exported memory
75    pub fn exported_memory(&mut self, name: &str) -> Result<MemoryRef<'_>> {
76        self.module().exported_memory(self.store, name)
77    }
78
79    /// Get a reference to an exported memory
80    pub fn exported_memory_mut(&mut self, name: &str) -> Result<MemoryRefMut<'_>> {
81        self.module().exported_memory_mut(self.store, name)
82    }
83}
84
85impl Debug for HostFunction {
86    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87        f.debug_struct("HostFunction").field("ty", &self.ty).field("func", &"...").finish()
88    }
89}
90
91#[derive(Debug, Clone)]
92#[non_exhaustive]
93/// An external value
94pub enum Extern {
95    /// A global value
96    Global {
97        /// The type of the global value.
98        ty: GlobalType,
99        /// The actual value of the global, encapsulated in `WasmValue`.
100        val: WasmValue,
101    },
102
103    /// A table
104    Table {
105        /// Defines the type of the table, including its element type and limits.
106        ty: TableType,
107        /// The initial value of the table.
108        init: WasmValue,
109    },
110
111    /// A memory
112    Memory {
113        /// Defines the type of the memory, including its limits and the type of its pages.
114        ty: MemoryType,
115    },
116
117    /// A function
118    Function(Function),
119}
120
121impl Extern {
122    /// Create a new global import
123    pub fn global(val: WasmValue, mutable: bool) -> Self {
124        Self::Global { ty: GlobalType { ty: val.val_type(), mutable }, val }
125    }
126
127    /// Create a new table import
128    pub fn table(ty: TableType, init: WasmValue) -> Self {
129        Self::Table { ty, init }
130    }
131
132    /// Create a new memory import
133    pub fn memory(ty: MemoryType) -> Self {
134        Self::Memory { ty }
135    }
136
137    /// Create a new function import
138    pub fn func(
139        ty: &tinywasm_types::FuncType,
140        func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result<Vec<WasmValue>> + 'static,
141    ) -> Self {
142        Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(func), ty: ty.clone() })))
143    }
144
145    /// Create a new typed function import
146    pub fn typed_func<P, R>(func: impl Fn(FuncContext<'_>, P) -> Result<R> + 'static) -> Self
147    where
148        P: FromWasmValueTuple + ValTypesFromTuple,
149        R: IntoWasmValueTuple + ValTypesFromTuple + Debug,
150    {
151        let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result<Vec<WasmValue>> {
152            let args = P::from_wasm_value_tuple(args)?;
153            let result = func(ctx, args)?;
154            Ok(result.into_wasm_value_tuple().to_vec())
155        };
156
157        let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() };
158        Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty })))
159    }
160
161    /// Get the kind of the external value
162    pub fn kind(&self) -> ExternalKind {
163        match self {
164            Self::Global { .. } => ExternalKind::Global,
165            Self::Table { .. } => ExternalKind::Table,
166            Self::Memory { .. } => ExternalKind::Memory,
167            Self::Function { .. } => ExternalKind::Func,
168        }
169    }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
173/// Name of an import
174pub struct ExternName {
175    module: String,
176    name: String,
177}
178
179impl From<&Import> for ExternName {
180    fn from(import: &Import) -> Self {
181        Self { module: import.module.to_string(), name: import.name.to_string() }
182    }
183}
184
185#[derive(Debug, Default)]
186/// Imports for a module instance
187///
188/// This is used to link a module instance to its imports
189///
190/// ## Example
191/// ```rust
192/// # use log;
193/// # fn main() -> tinywasm::Result<()> {
194/// use tinywasm::{Imports, Extern};
195/// use tinywasm::types::{ValType, TableType, MemoryType, WasmValue};
196/// let mut imports = Imports::new();
197///
198/// // function args can be either a single
199/// // value that implements `TryFrom<WasmValue>` or a tuple of them
200/// let print_i32 = Extern::typed_func(|_ctx: tinywasm::FuncContext<'_>, arg: i32| {
201///     log::debug!("print_i32: {}", arg);
202///     Ok(())
203/// });
204///
205/// let table_type = TableType::new(ValType::RefFunc, 10, Some(20));
206/// let table_init = WasmValue::default_for(ValType::RefFunc);
207///
208/// imports
209///     .define("my_module", "print_i32", print_i32)?
210///     .define("my_module", "table", Extern::table(table_type, table_init))?
211///     .define("my_module", "memory", Extern::memory(MemoryType::new_32(1, Some(2))))?
212///     .define("my_module", "global_i32", Extern::global(WasmValue::I32(666), false))?
213///     .link_module("my_other_module", 0)?;
214/// # Ok(())
215/// # }
216/// ```
217///
218/// Note that module instance addresses for [`Imports::link_module`] can be obtained from [`crate::ModuleInstance::id`].
219/// Now, the imports object can be passed to [`crate::ModuleInstance::instantiate`].
220#[derive(Clone)]
221pub struct Imports {
222    values: BTreeMap<ExternName, Extern>,
223    modules: BTreeMap<String, ModuleInstanceAddr>,
224}
225
226pub(crate) enum ResolvedExtern<S, V> {
227    Store(S),  // already in the store
228    Extern(V), // needs to be added to the store, provided value
229}
230
231pub(crate) struct ResolvedImports {
232    pub(crate) globals: Vec<GlobalAddr>,
233    pub(crate) tables: Vec<TableAddr>,
234    pub(crate) memories: Vec<MemAddr>,
235    pub(crate) funcs: Vec<FuncAddr>,
236}
237
238impl ResolvedImports {
239    pub(crate) fn new() -> Self {
240        Self { globals: Vec::new(), tables: Vec::new(), memories: Vec::new(), funcs: Vec::new() }
241    }
242}
243
244impl Imports {
245    /// Create a new empty import set
246    pub fn new() -> Self {
247        Imports { values: BTreeMap::new(), modules: BTreeMap::new() }
248    }
249
250    /// Merge two import sets
251    pub fn merge(mut self, other: Self) -> Self {
252        self.values.extend(other.values);
253        self.modules.extend(other.modules);
254        self
255    }
256
257    /// Link a module
258    ///
259    /// This will automatically link all imported values on instantiation
260    pub fn link_module(&mut self, name: &str, addr: ModuleInstanceAddr) -> Result<&mut Self> {
261        self.modules.insert(name.to_string(), addr);
262        Ok(self)
263    }
264
265    /// Define an import
266    pub fn define(&mut self, module: &str, name: &str, value: Extern) -> Result<&mut Self> {
267        self.values.insert(ExternName { module: module.to_string(), name: name.to_string() }, value);
268        Ok(self)
269    }
270
271    pub(crate) fn take(
272        &mut self,
273        store: &mut crate::Store,
274        import: &Import,
275    ) -> Option<ResolvedExtern<ExternVal, Extern>> {
276        let name = ExternName::from(import);
277        if let Some(v) = self.values.get(&name) {
278            return Some(ResolvedExtern::Extern(v.clone()));
279        }
280        if let Some(addr) = self.modules.get(&name.module) {
281            let instance = store.get_module_instance(*addr)?;
282            return Some(ResolvedExtern::Store(instance.export_addr(&import.name)?));
283        }
284
285        None
286    }
287
288    fn compare_types<T: Debug + PartialEq>(import: &Import, actual: &T, expected: &T) -> Result<()> {
289        if expected != actual {
290            log::error!("failed to link import {}, expected {:?}, got {:?}", import.name, expected, actual);
291            return Err(LinkingError::incompatible_import_type(import).into());
292        }
293        Ok(())
294    }
295
296    fn compare_table_types(import: &Import, expected: &TableType, actual: &TableType) -> Result<()> {
297        Self::compare_types(import, &actual.element_type, &expected.element_type)?;
298
299        if actual.size_initial > expected.size_initial {
300            return Err(LinkingError::incompatible_import_type(import).into());
301        }
302
303        match (expected.size_max, actual.size_max) {
304            (None, Some(_)) => return Err(LinkingError::incompatible_import_type(import).into()),
305            (Some(expected_max), Some(actual_max)) if actual_max < expected_max => {
306                return Err(LinkingError::incompatible_import_type(import).into())
307            }
308            _ => {}
309        }
310
311        Ok(())
312    }
313
314    fn compare_memory_types(
315        import: &Import,
316        expected: &MemoryType,
317        actual: &MemoryType,
318        real_size: Option<usize>,
319    ) -> Result<()> {
320        Self::compare_types(import, &expected.arch, &actual.arch)?;
321
322        if actual.page_count_initial > expected.page_count_initial
323            && real_size.map_or(true, |size| actual.page_count_initial > size as u64)
324        {
325            return Err(LinkingError::incompatible_import_type(import).into());
326        }
327
328        if expected.page_count_max.is_none() && actual.page_count_max.is_some() {
329            return Err(LinkingError::incompatible_import_type(import).into());
330        }
331
332        if let (Some(expected_max), Some(actual_max)) = (expected.page_count_max, actual.page_count_max) {
333            if actual_max < expected_max {
334                return Err(LinkingError::incompatible_import_type(import).into());
335            }
336        }
337
338        Ok(())
339    }
340
341    pub(crate) fn link(
342        mut self,
343        store: &mut crate::Store,
344        module: &crate::Module,
345        idx: ModuleInstanceAddr,
346    ) -> Result<ResolvedImports> {
347        let mut imports = ResolvedImports::new();
348
349        for import in &module.0.imports {
350            let val = self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))?;
351
352            match val {
353                // A link to something that needs to be added to the store
354                ResolvedExtern::Extern(ex) => match (ex, &import.kind) {
355                    (Extern::Global { ty, val }, ImportKind::Global(import_ty)) => {
356                        Self::compare_types(import, &ty, import_ty)?;
357                        imports.globals.push(store.add_global(ty, val.into(), idx)?);
358                    }
359                    (Extern::Table { ty, .. }, ImportKind::Table(import_ty)) => {
360                        Self::compare_table_types(import, &ty, import_ty)?;
361                        imports.tables.push(store.add_table(ty, idx)?);
362                    }
363                    (Extern::Memory { ty }, ImportKind::Memory(import_ty)) => {
364                        Self::compare_memory_types(import, &ty, import_ty, None)?;
365                        imports.memories.push(store.add_mem(ty, idx)?);
366                    }
367                    (Extern::Function(extern_func), ImportKind::Function(ty)) => {
368                        let import_func_type = module
369                            .0
370                            .func_types
371                            .get(*ty as usize)
372                            .ok_or_else(|| LinkingError::incompatible_import_type(import))?;
373
374                        Self::compare_types(import, extern_func.ty(), import_func_type)?;
375                        imports.funcs.push(store.add_func(extern_func, idx)?);
376                    }
377                    _ => return Err(LinkingError::incompatible_import_type(import).into()),
378                },
379
380                // A link to something already in the store
381                ResolvedExtern::Store(val) => {
382                    // check if the kind matches
383                    if val.kind() != (&import.kind).into() {
384                        return Err(LinkingError::incompatible_import_type(import).into());
385                    }
386
387                    match (val, &import.kind) {
388                        (ExternVal::Global(global_addr), ImportKind::Global(ty)) => {
389                            let global = store.get_global(global_addr);
390                            Self::compare_types(import, &global.ty, ty)?;
391                            imports.globals.push(global_addr);
392                        }
393                        (ExternVal::Table(table_addr), ImportKind::Table(ty)) => {
394                            let table = store.get_table(table_addr);
395                            let mut kind = table.kind.clone();
396                            kind.size_initial = table.size() as u32;
397                            Self::compare_table_types(import, &kind, ty)?;
398                            imports.tables.push(table_addr);
399                        }
400                        (ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => {
401                            let mem = store.get_mem(memory_addr);
402                            let (size, kind) = { (mem.page_count, mem.kind) };
403                            Self::compare_memory_types(import, &kind, ty, Some(size))?;
404                            imports.memories.push(memory_addr);
405                        }
406                        (ExternVal::Func(func_addr), ImportKind::Function(ty)) => {
407                            let func = store.get_func(func_addr);
408                            let import_func_type = module
409                                .0
410                                .func_types
411                                .get(*ty as usize)
412                                .ok_or_else(|| LinkingError::incompatible_import_type(import))?;
413
414                            Self::compare_types(import, func.func.ty(), import_func_type)?;
415                            imports.funcs.push(func_addr);
416                        }
417                        _ => return Err(LinkingError::incompatible_import_type(import).into()),
418                    }
419                }
420            }
421        }
422
423        Ok(imports)
424    }
425}