wasmer_engine/
resolver.rs

1//! Define the `Resolver` trait, allowing custom resolution for external
2//! references.
3
4use crate::{Export, ExportFunctionMetadata, ImportError, LinkError};
5use more_asserts::assert_ge;
6use wasmer_types::entity::{BoxedSlice, EntityRef, PrimaryMap};
7use wasmer_types::{ExternType, FunctionIndex, ImportIndex, MemoryIndex, ModuleInfo, TableIndex};
8
9use wasmer_vm::{
10    FunctionBodyPtr, ImportFunctionEnv, Imports, MemoryStyle, TableStyle, VMFunctionBody,
11    VMFunctionEnvironment, VMFunctionImport, VMFunctionKind, VMGlobalImport, VMMemoryImport,
12    VMTableImport,
13};
14
15/// Import resolver connects imports with available exported values.
16pub trait Resolver {
17    /// Resolves an import a WebAssembly module to an export it's hooked up to.
18    ///
19    /// The `index` provided is the index of the import in the wasm module
20    /// that's being resolved. For example 1 means that it's the second import
21    /// listed in the wasm module.
22    ///
23    /// The `module` and `field` arguments provided are the module/field names
24    /// listed on the import itself.
25    ///
26    /// # Notes:
27    ///
28    /// The index is useful because some WebAssembly modules may rely on that
29    /// for resolving ambiguity in their imports. Such as:
30    /// ```ignore
31    /// (module
32    ///   (import "" "" (func))
33    ///   (import "" "" (func (param i32) (result i32)))
34    /// )
35    /// ```
36    fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export>;
37}
38
39/// Import resolver connects imports with available exported values.
40///
41/// This is a specific subtrait for [`Resolver`] for those users who don't
42/// care about the `index`, but only about the `module` and `field` for
43/// the resolution.
44pub trait NamedResolver {
45    /// Resolves an import a WebAssembly module to an export it's hooked up to.
46    ///
47    /// It receives the `module` and `field` names and return the [`Export`] in
48    /// case it's found.
49    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export>;
50}
51
52// All NamedResolvers should extend `Resolver`.
53impl<T: NamedResolver> Resolver for T {
54    /// By default this method will be calling [`NamedResolver::resolve_by_name`],
55    /// dismissing the provided `index`.
56    fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export> {
57        self.resolve_by_name(module, field)
58    }
59}
60
61impl<T: NamedResolver> NamedResolver for &T {
62    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
63        (**self).resolve_by_name(module, field)
64    }
65}
66
67impl NamedResolver for Box<dyn NamedResolver + Send + Sync> {
68    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
69        (**self).resolve_by_name(module, field)
70    }
71}
72
73impl NamedResolver for () {
74    /// Always returns `None`.
75    fn resolve_by_name(&self, _module: &str, _field: &str) -> Option<Export> {
76        None
77    }
78}
79
80/// `Resolver` implementation that always resolves to `None`. Equivalent to `()`.
81pub struct NullResolver {}
82
83impl Resolver for NullResolver {
84    fn resolve(&self, _idx: u32, _module: &str, _field: &str) -> Option<Export> {
85        None
86    }
87}
88
89/// Get an `ExternType` given a import index.
90fn get_extern_from_import(module: &ModuleInfo, import_index: &ImportIndex) -> ExternType {
91    match import_index {
92        ImportIndex::Function(index) => {
93            let func = module.signatures[module.functions[*index]].clone();
94            ExternType::Function(func)
95        }
96        ImportIndex::Table(index) => {
97            let table = module.tables[*index];
98            ExternType::Table(table)
99        }
100        ImportIndex::Memory(index) => {
101            let memory = module.memories[*index];
102            ExternType::Memory(memory)
103        }
104        ImportIndex::Global(index) => {
105            let global = module.globals[*index];
106            ExternType::Global(global)
107        }
108    }
109}
110
111/// Get an `ExternType` given an export (and Engine signatures in case is a function).
112fn get_extern_from_export(_module: &ModuleInfo, export: &Export) -> ExternType {
113    match export {
114        Export::Function(ref f) => ExternType::Function(f.vm_function.signature.clone()),
115        Export::Table(ref t) => ExternType::Table(*t.ty()),
116        Export::Memory(ref m) => ExternType::Memory(m.ty()),
117        Export::Global(ref g) => {
118            let global = g.from.ty();
119            ExternType::Global(*global)
120        }
121    }
122}
123
124/// This function allows to match all imports of a `ModuleInfo` with concrete definitions provided by
125/// a `Resolver`.
126///
127/// If all imports are satisfied returns an `Imports` instance required for a module instantiation.
128pub fn resolve_imports(
129    module: &ModuleInfo,
130    resolver: &dyn Resolver,
131    finished_dynamic_function_trampolines: &BoxedSlice<FunctionIndex, FunctionBodyPtr>,
132    memory_styles: &PrimaryMap<MemoryIndex, MemoryStyle>,
133    _table_styles: &PrimaryMap<TableIndex, TableStyle>,
134) -> Result<Imports, LinkError> {
135    let mut function_imports = PrimaryMap::with_capacity(module.num_imported_functions);
136    let mut host_function_env_initializers =
137        PrimaryMap::with_capacity(module.num_imported_functions);
138    let mut table_imports = PrimaryMap::with_capacity(module.num_imported_tables);
139    let mut memory_imports = PrimaryMap::with_capacity(module.num_imported_memories);
140    let mut global_imports = PrimaryMap::with_capacity(module.num_imported_globals);
141
142    for ((module_name, field, import_idx), import_index) in module.imports.iter() {
143        let resolved = resolver.resolve(*import_idx, module_name, field);
144        let import_extern = get_extern_from_import(module, import_index);
145        let resolved = match resolved {
146            None => {
147                return Err(LinkError::Import(
148                    module_name.to_string(),
149                    field.to_string(),
150                    ImportError::UnknownImport(import_extern),
151                ));
152            }
153            Some(r) => r,
154        };
155        let export_extern = get_extern_from_export(module, &resolved);
156        if !export_extern.is_compatible_with(&import_extern) {
157            return Err(LinkError::Import(
158                module_name.to_string(),
159                field.to_string(),
160                ImportError::IncompatibleType(import_extern, export_extern),
161            ));
162        }
163        match resolved {
164            Export::Function(ref f) => {
165                let address = match f.vm_function.kind {
166                    VMFunctionKind::Dynamic => {
167                        // If this is a dynamic imported function,
168                        // the address of the function is the address of the
169                        // reverse trampoline.
170                        let index = FunctionIndex::new(function_imports.len());
171                        finished_dynamic_function_trampolines[index].0 as *mut VMFunctionBody as _
172
173                        // TODO: We should check that the f.vmctx actually matches
174                        // the shape of `VMDynamicFunctionImportContext`
175                    }
176                    VMFunctionKind::Static => f.vm_function.address,
177                };
178
179                // Clone the host env for this `Instance`.
180                let env = if let Some(ExportFunctionMetadata {
181                    host_env_clone_fn: clone,
182                    ..
183                }) = f.metadata.as_deref()
184                {
185                    // TODO: maybe start adding asserts in all these
186                    // unsafe blocks to prevent future changes from
187                    // horribly breaking things.
188                    unsafe {
189                        assert!(!f.vm_function.vmctx.host_env.is_null());
190                        (clone)(f.vm_function.vmctx.host_env)
191                    }
192                } else {
193                    // No `clone` function means we're dealing with some
194                    // other kind of `vmctx`, not a host env of any
195                    // kind.
196                    unsafe { f.vm_function.vmctx.host_env }
197                };
198
199                function_imports.push(VMFunctionImport {
200                    body: address,
201                    environment: VMFunctionEnvironment { host_env: env },
202                });
203
204                let initializer = f.metadata.as_ref().and_then(|m| m.import_init_function_ptr);
205                let clone = f.metadata.as_ref().map(|m| m.host_env_clone_fn);
206                let destructor = f.metadata.as_ref().map(|m| m.host_env_drop_fn);
207                let import_function_env =
208                    if let (Some(clone), Some(destructor)) = (clone, destructor) {
209                        ImportFunctionEnv::Env {
210                            env,
211                            clone,
212                            initializer,
213                            destructor,
214                        }
215                    } else {
216                        ImportFunctionEnv::NoEnv
217                    };
218
219                host_function_env_initializers.push(import_function_env);
220            }
221            Export::Table(ref t) => match import_index {
222                ImportIndex::Table(index) => {
223                    let import_table_ty = t.from.ty();
224                    let expected_table_ty = &module.tables[*index];
225                    if import_table_ty.ty != expected_table_ty.ty {
226                        return Err(LinkError::Import(
227                            module_name.to_string(),
228                            field.to_string(),
229                            ImportError::IncompatibleType(import_extern, export_extern),
230                        ));
231                    }
232
233                    table_imports.push(VMTableImport {
234                        definition: t.from.vmtable(),
235                        from: t.from.clone(),
236                    });
237                }
238                _ => {
239                    unreachable!("Table resolution did not match");
240                }
241            },
242            Export::Memory(ref m) => {
243                match import_index {
244                    ImportIndex::Memory(index) => {
245                        // Sanity-check: Ensure that the imported memory has at least
246                        // guard-page protections the importing module expects it to have.
247                        let export_memory_style = m.style();
248                        let import_memory_style = &memory_styles[*index];
249                        if let (
250                            MemoryStyle::Static { bound, .. },
251                            MemoryStyle::Static {
252                                bound: import_bound,
253                                ..
254                            },
255                        ) = (export_memory_style.clone(), &import_memory_style)
256                        {
257                            assert_ge!(bound, *import_bound);
258                        }
259                        assert_ge!(
260                            export_memory_style.offset_guard_size(),
261                            import_memory_style.offset_guard_size()
262                        );
263                    }
264                    _ => {
265                        // This should never be reached, as we did compatibility
266                        // checks before
267                        panic!("Memory resolution didn't matched");
268                    }
269                }
270
271                memory_imports.push(VMMemoryImport {
272                    definition: m.from.vmmemory(),
273                    from: m.from.clone(),
274                });
275            }
276
277            Export::Global(ref g) => {
278                global_imports.push(VMGlobalImport {
279                    definition: g.from.vmglobal(),
280                    from: g.from.clone(),
281                });
282            }
283        }
284    }
285
286    Ok(Imports::new(
287        function_imports,
288        host_function_env_initializers,
289        table_imports,
290        memory_imports,
291        global_imports,
292    ))
293}
294
295/// A [`Resolver`] that links two resolvers together in a chain.
296pub struct NamedResolverChain<A: NamedResolver + Send + Sync, B: NamedResolver + Send + Sync> {
297    a: A,
298    b: B,
299}
300
301/// A trait for chaining resolvers together.
302///
303/// ```
304/// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
305/// # fn chainable_test<A, B>(imports1: A, imports2: B)
306/// # where A: NamedResolver + Sized + Send + Sync,
307/// #       B: NamedResolver + Sized + Send + Sync,
308/// # {
309/// // override duplicates with imports from `imports2`
310/// imports1.chain_front(imports2);
311/// # }
312/// ```
313pub trait ChainableNamedResolver: NamedResolver + Sized + Send + Sync {
314    /// Chain a resolver in front of the current resolver.
315    ///
316    /// This will cause the second resolver to override the first.
317    ///
318    /// ```
319    /// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
320    /// # fn chainable_test<A, B>(imports1: A, imports2: B)
321    /// # where A: NamedResolver + Sized + Send + Sync,
322    /// #       B: NamedResolver + Sized + Send + Sync,
323    /// # {
324    /// // override duplicates with imports from `imports2`
325    /// imports1.chain_front(imports2);
326    /// # }
327    /// ```
328    fn chain_front<U>(self, other: U) -> NamedResolverChain<U, Self>
329    where
330        U: NamedResolver + Send + Sync,
331    {
332        NamedResolverChain { a: other, b: self }
333    }
334
335    /// Chain a resolver behind the current resolver.
336    ///
337    /// This will cause the first resolver to override the second.
338    ///
339    /// ```
340    /// # use wasmer_engine::{ChainableNamedResolver, NamedResolver};
341    /// # fn chainable_test<A, B>(imports1: A, imports2: B)
342    /// # where A: NamedResolver + Sized + Send + Sync,
343    /// #       B: NamedResolver + Sized + Send + Sync,
344    /// # {
345    /// // override duplicates with imports from `imports1`
346    /// imports1.chain_back(imports2);
347    /// # }
348    /// ```
349    fn chain_back<U>(self, other: U) -> NamedResolverChain<Self, U>
350    where
351        U: NamedResolver + Send + Sync,
352    {
353        NamedResolverChain { a: self, b: other }
354    }
355}
356
357// We give these chain methods to all types implementing NamedResolver
358impl<T: NamedResolver + Send + Sync> ChainableNamedResolver for T {}
359
360impl<A, B> NamedResolver for NamedResolverChain<A, B>
361where
362    A: NamedResolver + Send + Sync,
363    B: NamedResolver + Send + Sync,
364{
365    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
366        self.a
367            .resolve_by_name(module, field)
368            .or_else(|| self.b.resolve_by_name(module, field))
369    }
370}
371
372impl<A, B> Clone for NamedResolverChain<A, B>
373where
374    A: NamedResolver + Clone + Send + Sync,
375    B: NamedResolver + Clone + Send + Sync,
376{
377    fn clone(&self) -> Self {
378        Self {
379            a: self.a.clone(),
380            b: self.b.clone(),
381        }
382    }
383}