Skip to main content

wasmer_compiler/engine/
resolver.rs

1//! Custom resolution for external references.
2
3use crate::LinkError;
4use more_asserts::assert_ge;
5use wasmer_types::{
6    ExternType, FunctionIndex, ImportError, ImportIndex, MemoryIndex, ModuleInfo, TableIndex,
7    TagType,
8};
9use wasmer_types::{
10    TagIndex, TagKind,
11    entity::{BoxedSlice, EntityRef, PrimaryMap},
12};
13
14use wasmer_vm::{
15    FunctionBodyPtr, Imports, InternalStoreHandle, LinearMemory, MemoryStyle, StoreObjects,
16    TableStyle, VMExtern, VMFunctionBody, VMFunctionImport, VMFunctionKind, VMGlobalImport,
17    VMMemoryImport, VMTableImport, VMTag,
18};
19
20/// Get an `ExternType` given a import index.
21fn get_extern_from_import(module: &ModuleInfo, import_index: &ImportIndex) -> ExternType {
22    match import_index {
23        ImportIndex::Function(index) => {
24            let func = module.signatures[module.functions[*index]].clone();
25            ExternType::Function(func)
26        }
27        ImportIndex::Table(index) => {
28            let table = module.tables[*index];
29            ExternType::Table(table)
30        }
31        ImportIndex::Memory(index) => {
32            let memory = module.memories[*index];
33            ExternType::Memory(memory)
34        }
35        ImportIndex::Global(index) => {
36            let global = module.globals[*index];
37            ExternType::Global(global)
38        }
39        ImportIndex::Tag(index) => {
40            let func = module.signatures[module.tags[*index]].clone();
41            ExternType::Tag(TagType::from_fn_type(
42                wasmer_types::TagKind::Exception,
43                func,
44            ))
45        }
46    }
47}
48
49/// Get an `ExternType` given an export (and Engine signatures in case is a function).
50fn get_extern_type(context: &StoreObjects, extern_: &VMExtern) -> ExternType {
51    match extern_ {
52        VMExtern::Tag(f) => ExternType::Tag(wasmer_types::TagType::from_fn_type(
53            wasmer_types::TagKind::Exception,
54            f.get(context).signature.clone(),
55        )),
56        VMExtern::Function(f) => ExternType::Function(f.get(context).signature.clone()),
57        VMExtern::Table(t) => ExternType::Table(*t.get(context).ty()),
58        VMExtern::Memory(m) => ExternType::Memory(m.get(context).ty()),
59        VMExtern::Global(g) => {
60            let global = g.get(context).ty();
61            ExternType::Global(*global)
62        }
63    }
64}
65
66fn get_runtime_size(context: &StoreObjects, extern_: &VMExtern) -> Option<u32> {
67    match extern_ {
68        VMExtern::Table(t) => Some(t.get(context).get_runtime_size()),
69        VMExtern::Memory(m) => Some(m.get(context).get_runtime_size()),
70        _ => None,
71    }
72}
73
74/// This function allows to match all imports of a `ModuleInfo` with concrete definitions provided by
75/// a `Resolver`, except for tags which are resolved separately through `resolve_tags`.
76///
77/// If all imports are satisfied returns an `Imports` instance required for a module instantiation.
78#[allow(clippy::result_large_err)]
79pub fn resolve_imports(
80    module: &ModuleInfo,
81    imports: &[VMExtern],
82    context: &mut StoreObjects,
83    finished_dynamic_function_trampolines: &BoxedSlice<FunctionIndex, FunctionBodyPtr>,
84    memory_styles: &PrimaryMap<MemoryIndex, MemoryStyle>,
85    _table_styles: &PrimaryMap<TableIndex, TableStyle>,
86) -> Result<Imports, LinkError> {
87    let mut function_imports = PrimaryMap::with_capacity(module.num_imported_functions);
88    let mut table_imports = PrimaryMap::with_capacity(module.num_imported_tables);
89    let mut memory_imports = PrimaryMap::with_capacity(module.num_imported_memories);
90    let mut global_imports = PrimaryMap::with_capacity(module.num_imported_globals);
91
92    for (import_key, import_index) in module
93        .imports
94        .iter()
95        .filter(|(_, import_index)| !matches!(import_index, ImportIndex::Tag(_)))
96    {
97        let ResolvedImport {
98            resolved,
99            import_extern,
100            extern_type,
101        } = resolve_import(module, imports, context, import_key, import_index)?;
102        match *resolved {
103            VMExtern::Function(handle) => {
104                let f = handle.get_mut(context);
105                let address = match f.kind {
106                    VMFunctionKind::Dynamic => {
107                        // If this is a dynamic imported function,
108                        // the address of the function is the address of the
109                        // reverse trampoline.
110                        let index = FunctionIndex::new(function_imports.len());
111                        let ptr = finished_dynamic_function_trampolines[index].0
112                            as *mut VMFunctionBody as _;
113                        // The logic is currently handling the "resolution" of dynamic imported functions at instantiation time.
114                        // However, ideally it should be done even before then, as you may have dynamic imported functions that
115                        // are linked at runtime and not instantiation time. And those will not work properly with the current logic.
116                        // Ideally, this logic should be done directly in the `wasmer-vm` crate.
117                        // TODO (@syrusakbary): Get rid of `VMFunctionKind`
118                        unsafe { f.anyfunc.as_ptr().as_mut() }.func_ptr = ptr;
119                        ptr
120                    }
121                    VMFunctionKind::Static => unsafe { f.anyfunc.as_ptr().as_ref().func_ptr },
122                };
123
124                function_imports.push(VMFunctionImport {
125                    body: address,
126                    environment: unsafe { f.anyfunc.as_ptr().as_ref().vmctx },
127                    handle,
128                    // TODO: use nicer way of how to detect WA-native functions
129                    include_m0_param: handle.get(context).host_data.is::<()>(),
130                });
131            }
132            VMExtern::Table(handle) => {
133                let t = handle.get(context);
134                match import_index {
135                    ImportIndex::Table(index) => {
136                        let import_table_ty = t.ty();
137                        let expected_table_ty = &module.tables[*index];
138                        if import_table_ty.ty != expected_table_ty.ty {
139                            return Err(LinkError::Import(
140                                import_key.module.to_string(),
141                                import_key.field.to_string(),
142                                ImportError::IncompatibleType(import_extern, extern_type),
143                            ));
144                        }
145
146                        table_imports.push(VMTableImport {
147                            definition: t.vmtable(),
148                            handle,
149                        });
150                    }
151                    _ => {
152                        unreachable!("Table resolution did not match");
153                    }
154                }
155            }
156            VMExtern::Memory(handle) => {
157                let m = handle.get(context);
158                match import_index {
159                    ImportIndex::Memory(index) => {
160                        // Ensure that the imported memory has the allocation guarantees
161                        // assumed by the importing module's generated code.
162                        let export_memory_style = m.style();
163                        let import_memory_style = &memory_styles[*index];
164                        if matches!(import_memory_style, MemoryStyle::Static)
165                            && !matches!(export_memory_style, MemoryStyle::Static)
166                        {
167                            return Err(LinkError::Import(
168                                import_key.module.to_string(),
169                                import_key.field.to_string(),
170                                ImportError::MemoryError(
171                                    "a static memory import cannot use a dynamic allocation"
172                                        .to_string(),
173                                ),
174                            ));
175                        }
176                        assert_ge!(
177                            export_memory_style.offset_guard_size(),
178                            import_memory_style.offset_guard_size()
179                        );
180                    }
181                    _ => {
182                        // This should never be reached, as we did compatibility
183                        // checks before
184                        panic!("Memory resolution didn't matched");
185                    }
186                }
187
188                memory_imports.push(VMMemoryImport {
189                    definition: m.vmmemory(),
190                    handle,
191                });
192            }
193
194            VMExtern::Global(handle) => {
195                let g = handle.get(context);
196                global_imports.push(VMGlobalImport {
197                    definition: g.vmglobal(),
198                    handle,
199                });
200            }
201
202            VMExtern::Tag(_) => unreachable!("We already filtered tags out"),
203        }
204    }
205
206    Ok(Imports::new(
207        function_imports,
208        table_imports,
209        memory_imports,
210        global_imports,
211    ))
212}
213
214/// This function resolves all tags of a `ModuleInfo`. Imported tags are resolved from
215/// the `StoreObjects`, whereas local tags are created and pushed to it. This is because
216/// we need every tag to have a unique `VMSharedTagIndex` in the `StoreObjects`, regardless
217/// of whether it's local or imported, so that exception handling can correctly resolve
218/// cross-module exceptions.
219// TODO: I feel this code can be cleaned up. Maybe we can handle tag indices better, so we don't have to search through the imports again?
220// TODO: don't we create store handles for everything else as well? Should tags get special handling here?
221#[allow(clippy::result_large_err)]
222pub fn resolve_tags(
223    module: &ModuleInfo,
224    imports: &[VMExtern],
225    context: &mut StoreObjects,
226) -> Result<BoxedSlice<TagIndex, InternalStoreHandle<VMTag>>, LinkError> {
227    let mut tags = PrimaryMap::with_capacity(module.tags.len());
228
229    for (import_key, import_index) in module
230        .imports
231        .iter()
232        .filter(|(_, import_index)| matches!(import_index, ImportIndex::Tag(_)))
233    {
234        let ResolvedImport {
235            resolved,
236            import_extern,
237            extern_type,
238        } = resolve_import(module, imports, context, import_key, import_index)?;
239        match *resolved {
240            VMExtern::Tag(handle) => {
241                let t = handle.get(context);
242                match import_index {
243                    ImportIndex::Tag(index) => {
244                        let import_tag_ty = &t.signature;
245                        let expected_tag_ty = if let Some(expected_tag_ty) =
246                            module.signatures.get(module.tags[*index])
247                        {
248                            expected_tag_ty
249                        } else {
250                            return Err(LinkError::Resource(format!(
251                                "Could not find matching signature for tag index {index:?}"
252                            )));
253                        };
254                        if *import_tag_ty != *expected_tag_ty {
255                            return Err(LinkError::Import(
256                                import_key.module.to_string(),
257                                import_key.field.to_string(),
258                                ImportError::IncompatibleType(import_extern, extern_type),
259                            ));
260                        }
261
262                        tags.push(handle);
263                    }
264                    _ => {
265                        unreachable!("Tag resolution did not match");
266                    }
267                }
268            }
269            _ => unreachable!("We already filtered everything else out"),
270        }
271    }
272
273    // Now, create local tags.
274    // Local tags are created in the StoreObjects once per instance, so that
275    // when two instances of the same module are executing, they don't end
276    // up catching each other's exceptions.
277    for (tag_index, signature_index) in module.tags.iter() {
278        if module.is_imported_tag(tag_index) {
279            continue;
280        }
281        let sig_ty = if let Some(sig_ty) = module.signatures.get(*signature_index) {
282            sig_ty
283        } else {
284            return Err(LinkError::Resource(format!(
285                "Could not find matching signature for tag index {tag_index:?}"
286            )));
287        };
288        let handle =
289            InternalStoreHandle::new(context, VMTag::new(TagKind::Exception, sig_ty.clone()));
290        tags.push(handle);
291    }
292
293    Ok(tags.into_boxed_slice())
294}
295
296struct ResolvedImport<'a> {
297    resolved: &'a VMExtern,
298    import_extern: ExternType,
299    extern_type: ExternType,
300}
301
302#[allow(clippy::result_large_err)]
303fn resolve_import<'a>(
304    module: &ModuleInfo,
305    imports: &'a [VMExtern],
306    context: &mut StoreObjects,
307    import: &wasmer_types::ImportKey,
308    import_index: &ImportIndex,
309) -> Result<ResolvedImport<'a>, LinkError> {
310    let import_extern = get_extern_from_import(module, import_index);
311    let resolved = if let Some(r) = imports.get(import.import_idx as usize) {
312        r
313    } else {
314        return Err(LinkError::Import(
315            import.module.to_string(),
316            import.field.to_string(),
317            ImportError::UnknownImport(import_extern),
318        ));
319    };
320    let extern_type = get_extern_type(context, resolved);
321    let runtime_size = get_runtime_size(context, resolved);
322    if !extern_type.is_compatible_with(&import_extern, runtime_size) {
323        return Err(LinkError::Import(
324            import.module.to_string(),
325            import.field.to_string(),
326            ImportError::IncompatibleType(import_extern, extern_type),
327        ));
328    }
329    Ok(ResolvedImport {
330        resolved,
331        import_extern,
332        extern_type,
333    })
334}