mycelium_kernel/
wasm.rs

1use alloc::borrow::ToOwned;
2use core::convert::TryFrom;
3use core::fmt;
4
5mod convert;
6mod wasi;
7
8use self::convert::WasmPrimitive;
9
10macro_rules! option_helper {
11    (Some $rt:expr) => {
12        Some($rt)
13    };
14    (Some) => {
15        None
16    };
17}
18
19#[derive(Debug)]
20pub struct Host {
21    pub module: wasmi::ModuleRef,
22    // FIXME: The wasmi crate currently provides no way to determine the
23    // caller's module or memory from a host function. This effectively means
24    // that if a module imports and calls a function from another module, and
25    // that module calls a host function, the memory region of the first module
26    // will be used.
27    //
28    // We will need to either modify the wasmi interpreter and host function API
29    // to pass in function context when calling host methods, or use host
30    // function trampolines which change the `Host` value, to avoid this issue.
31    pub memory: wasmi::MemoryRef,
32}
33
34impl Host {
35    // Create a new host for the given instance.
36    // NOTE: The instance may not have been started yet.
37    pub fn new(instance: &wasmi::ModuleRef) -> Result<Self, wasmi::Error> {
38        let memory = match instance.export_by_name("memory") {
39            Some(wasmi::ExternVal::Memory(memory)) => memory,
40            _ => {
41                return Err(wasmi::Error::Instantiation(
42                    "required memory export".to_owned(),
43                ))
44            }
45        };
46
47        Ok(Host {
48            module: instance.clone(),
49            memory,
50        })
51    }
52}
53
54macro_rules! host_funcs {
55    ($(
56        fn $module:literal :: $name:literal ($($p:ident : $t:ident),*) $( -> $rt:ident)?
57            as $variant:ident impl $method:path;
58    )*) => {
59        #[repr(usize)]
60        #[derive(Copy, Clone, Debug, Eq, PartialEq)]
61        enum HostFunc {
62            $($variant),*
63        }
64
65        impl HostFunc {
66            fn resolve_func(
67                module_name: &str,
68                field_name: &str,
69                _signature: &wasmi::Signature,
70            ) -> Result<HostFunc, wasmi::Error> {
71                match (module_name, field_name) {
72                    $(($module, $name) => Ok(HostFunc::$variant),)*
73                    _ => Err(wasmi::Error::Instantiation("unresolved func import".to_owned()))
74                }
75            }
76
77            fn signature(self) -> wasmi::Signature {
78                match self {
79                    $(
80                        HostFunc::$variant => wasmi::Signature::new(
81                            &[$(<$t as WasmPrimitive>::TYPE),*][..],
82                            option_helper!(Some $(<$rt as WasmPrimitive>::TYPE)?),
83                        )
84                    ),*
85                }
86            }
87
88            fn func_ref(self) -> wasmi::FuncRef {
89                wasmi::FuncInstance::alloc_host(self.signature(), self as usize)
90            }
91
92            fn module_name(self) -> &'static str {
93                match self {
94                    $(HostFunc::$variant => $module),*
95                }
96            }
97
98            fn field_name(self) -> &'static str {
99                match self {
100                    $(HostFunc::$variant => $name),*
101                }
102            }
103        }
104
105        impl TryFrom<usize> for HostFunc {
106            type Error = wasmi::Trap;
107            fn try_from(x: usize) -> Result<Self, Self::Error> {
108                $(
109                    if x == (HostFunc::$variant as usize) {
110                        return Ok(HostFunc::$variant);
111                    }
112                )*
113                Err(wasmi::TrapKind::UnexpectedSignature.into())
114            }
115        }
116
117        impl fmt::Display for HostFunc {
118            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119                write!(f, "{}::{}", self.module_name(), self.field_name())
120            }
121        }
122
123        impl wasmi::Externals for Host {
124            fn invoke_index(
125                &mut self,
126                index: usize,
127                args: wasmi::RuntimeArgs,
128            ) -> Result<Option<wasmi::RuntimeValue>, wasmi::Trap> {
129                let span = tracing::trace_span!("invoke_index", index, ?args);
130                let _enter = span.enter();
131
132                match HostFunc::try_from(index)? {
133                    $(
134                        HostFunc::$variant => match args.as_ref() {
135                            [$($p),*] => {
136                                let _result = $method(
137                                    self,
138                                    $(<$t as WasmPrimitive>::from_wasm_value(*$p)?),*
139                                )?;
140                                Ok(option_helper!(
141                                    Some $(<$rt as WasmPrimitive>::into_wasm_value(_result))?
142                                ))
143                            }
144                            _ => Err(wasmi::TrapKind::UnexpectedSignature.into()),
145                        }
146                    ),*
147                }
148            }
149        }
150    }
151}
152
153host_funcs! {
154    fn "wasi_unstable"::"fd_write"(fd: u32, iovs: u32, iovs_len: u32, nwritten: u32) -> u16
155        as FdWrite impl wasi::fd_write;
156}
157
158struct HostResolver;
159impl wasmi::ImportResolver for HostResolver {
160    fn resolve_func(
161        &self,
162        module_name: &str,
163        field_name: &str,
164        signature: &wasmi::Signature,
165    ) -> Result<wasmi::FuncRef, wasmi::Error> {
166        let host_fn = HostFunc::resolve_func(module_name, field_name, signature)?;
167        Ok(host_fn.func_ref())
168    }
169
170    fn resolve_global(
171        &self,
172        module_name: &str,
173        field_name: &str,
174        descriptor: &wasmi::GlobalDescriptor,
175    ) -> Result<wasmi::GlobalRef, wasmi::Error> {
176        tracing::error!(
177            module_name,
178            field_name,
179            ?descriptor,
180            "unresolved global import"
181        );
182        Err(wasmi::Error::Instantiation(
183            "unresolved global import".to_owned(),
184        ))
185    }
186
187    fn resolve_memory(
188        &self,
189        module_name: &str,
190        field_name: &str,
191        descriptor: &wasmi::MemoryDescriptor,
192    ) -> Result<wasmi::MemoryRef, wasmi::Error> {
193        tracing::error!(
194            module_name,
195            field_name,
196            ?descriptor,
197            "unresolved memory import"
198        );
199        Err(wasmi::Error::Instantiation(
200            "unresolved memory import".to_owned(),
201        ))
202    }
203
204    fn resolve_table(
205        &self,
206        module_name: &str,
207        field_name: &str,
208        descriptor: &wasmi::TableDescriptor,
209    ) -> Result<wasmi::TableRef, wasmi::Error> {
210        tracing::error!(
211            module_name,
212            field_name,
213            ?descriptor,
214            "unresolved table import"
215        );
216        Err(wasmi::Error::Instantiation(
217            "unresolved table import".to_owned(),
218        ))
219    }
220}
221
222pub fn run_wasm(binary: &[u8]) -> Result<(), wasmi::Error> {
223    let module = wasmi::Module::from_buffer(binary)?;
224    // Instantiate the module and it's corresponding `Host` instance.
225    let instance = wasmi::ModuleInstance::new(&module, &HostResolver)?;
226    let mut host = Host::new(instance.not_started_instance())?;
227    let instance = instance.run_start(&mut host)?;
228
229    // FIXME: We should probably use resumable calls here.
230    instance.invoke_export("_start", &[], &mut host)?;
231    Ok(())
232}