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