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                        // Sanity-check: Ensure that the imported memory has at least
161                        // guard-page protections the importing module expects it to have.
162                        let export_memory_style = m.style();
163                        let import_memory_style = &memory_styles[*index];
164                        if let (
165                            MemoryStyle::Static { bound, .. },
166                            MemoryStyle::Static {
167                                bound: import_bound,
168                                ..
169                            },
170                        ) = (export_memory_style, &import_memory_style)
171                        {
172                            assert_ge!(bound, *import_bound);
173                        }
174                        assert_ge!(
175                            export_memory_style.offset_guard_size(),
176                            import_memory_style.offset_guard_size()
177                        );
178                    }
179                    _ => {
180                        // This should never be reached, as we did compatibility
181                        // checks before
182                        panic!("Memory resolution didn't matched");
183                    }
184                }
185
186                memory_imports.push(VMMemoryImport {
187                    definition: m.vmmemory(),
188                    handle,
189                });
190            }
191
192            VMExtern::Global(handle) => {
193                let g = handle.get(context);
194                global_imports.push(VMGlobalImport {
195                    definition: g.vmglobal(),
196                    handle,
197                });
198            }
199
200            VMExtern::Tag(_) => unreachable!("We already filtered tags out"),
201        }
202    }
203
204    Ok(Imports::new(
205        function_imports,
206        table_imports,
207        memory_imports,
208        global_imports,
209    ))
210}
211
212/// This function resolves all tags of a `ModuleInfo`. Imported tags are resolved from
213/// the `StoreObjects`, whereas local tags are created and pushed to it. This is because
214/// we need every tag to have a unique `VMSharedTagIndex` in the `StoreObjects`, regardless
215/// of whether it's local or imported, so that exception handling can correctly resolve
216/// cross-module exceptions.
217// 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?
218// TODO: don't we create store handles for everything else as well? Should tags get special handling here?
219#[allow(clippy::result_large_err)]
220pub fn resolve_tags(
221    module: &ModuleInfo,
222    imports: &[VMExtern],
223    context: &mut StoreObjects,
224) -> Result<BoxedSlice<TagIndex, InternalStoreHandle<VMTag>>, LinkError> {
225    let mut tags = PrimaryMap::with_capacity(module.tags.len());
226
227    for (import_key, import_index) in module
228        .imports
229        .iter()
230        .filter(|(_, import_index)| matches!(import_index, ImportIndex::Tag(_)))
231    {
232        let ResolvedImport {
233            resolved,
234            import_extern,
235            extern_type,
236        } = resolve_import(module, imports, context, import_key, import_index)?;
237        match *resolved {
238            VMExtern::Tag(handle) => {
239                let t = handle.get(context);
240                match import_index {
241                    ImportIndex::Tag(index) => {
242                        let import_tag_ty = &t.signature;
243                        let expected_tag_ty = if let Some(expected_tag_ty) =
244                            module.signatures.get(module.tags[*index])
245                        {
246                            expected_tag_ty
247                        } else {
248                            return Err(LinkError::Resource(format!(
249                                "Could not find matching signature for tag index {index:?}"
250                            )));
251                        };
252                        if *import_tag_ty != *expected_tag_ty {
253                            return Err(LinkError::Import(
254                                import_key.module.to_string(),
255                                import_key.field.to_string(),
256                                ImportError::IncompatibleType(import_extern, extern_type),
257                            ));
258                        }
259
260                        tags.push(handle);
261                    }
262                    _ => {
263                        unreachable!("Tag resolution did not match");
264                    }
265                }
266            }
267            _ => unreachable!("We already filtered everything else out"),
268        }
269    }
270
271    // Now, create local tags.
272    // Local tags are created in the StoreObjects once per instance, so that
273    // when two instances of the same module are executing, they don't end
274    // up catching each other's exceptions.
275    for (tag_index, signature_index) in module.tags.iter() {
276        if module.is_imported_tag(tag_index) {
277            continue;
278        }
279        let sig_ty = if let Some(sig_ty) = module.signatures.get(*signature_index) {
280            sig_ty
281        } else {
282            return Err(LinkError::Resource(format!(
283                "Could not find matching signature for tag index {tag_index:?}"
284            )));
285        };
286        let handle =
287            InternalStoreHandle::new(context, VMTag::new(TagKind::Exception, sig_ty.clone()));
288        tags.push(handle);
289    }
290
291    Ok(tags.into_boxed_slice())
292}
293
294struct ResolvedImport<'a> {
295    resolved: &'a VMExtern,
296    import_extern: ExternType,
297    extern_type: ExternType,
298}
299
300#[allow(clippy::result_large_err)]
301fn resolve_import<'a>(
302    module: &ModuleInfo,
303    imports: &'a [VMExtern],
304    context: &mut StoreObjects,
305    import: &wasmer_types::ImportKey,
306    import_index: &ImportIndex,
307) -> Result<ResolvedImport<'a>, LinkError> {
308    let import_extern = get_extern_from_import(module, import_index);
309    let resolved = if let Some(r) = imports.get(import.import_idx as usize) {
310        r
311    } else {
312        return Err(LinkError::Import(
313            import.module.to_string(),
314            import.field.to_string(),
315            ImportError::UnknownImport(import_extern),
316        ));
317    };
318    let extern_type = get_extern_type(context, resolved);
319    let runtime_size = get_runtime_size(context, resolved);
320    if !extern_type.is_compatible_with(&import_extern, runtime_size) {
321        return Err(LinkError::Import(
322            import.module.to_string(),
323            import.field.to_string(),
324            ImportError::IncompatibleType(import_extern, extern_type),
325        ));
326    }
327    Ok(ResolvedImport {
328        resolved,
329        import_extern,
330        extern_type,
331    })
332}