wasmer_wasix/state/linker/
instance_group.rs

1use std::{
2    collections::HashMap,
3    sync::{Arc, Barrier},
4};
5
6use tracing::trace;
7use wasmer::{AsStoreMut, FunctionEnv, Global, Instance, Memory, Table, Tag};
8
9use crate::{WasiEnv, import_object_for_all_wasi_versions};
10
11use super::{
12    DlModule, DlOperation, DylinkInfo, InProgressLinkState, InProgressSymbolResolution, LinkError,
13    LinkerState, MAIN_MODULE_HANDLE, ModuleHandle, NeededSymbolResolutionKey,
14    PartiallyResolvedExport, PendingFunctionResolutionFromLinkerState,
15    PendingResolutionsFromLinker, PendingTlsPointer, ResolveError, SymbolResolutionKey,
16    SymbolResolutionResult, UnresolvedGlobal, WasiModuleInstanceHandles,
17    call_initialization_function, define_integer_global_import, get_tls_base_export,
18    set_integer_global,
19};
20
21mod exports;
22mod imports;
23mod table;
24
25pub(super) struct DlInstance {
26    pub(super) instance: Instance,
27    #[allow(dead_code)]
28    pub(super) instance_handles: WasiModuleInstanceHandles,
29    pub(super) tls_base: Option<u64>,
30}
31
32pub(super) struct InstanceGroupState {
33    pub(super) main_instance: Option<Instance>,
34    pub(super) main_instance_tls_base: Option<u64>,
35
36    pub(super) side_instances: HashMap<ModuleHandle, DlInstance>,
37
38    pub(super) stack_pointer: Global,
39    pub(super) memory: Memory,
40    pub(super) indirect_function_table: Table,
41    pub(super) c_longjmp: Tag,
42    pub(super) cpp_exception: Tag,
43
44    // Once the dl_operation_pending flag is set, a barrier is created and broadcast
45    // by the instigating group, which others must use to rendezvous with it.
46    pub(super) recv_pending_operation_barrier: bus::BusReader<Arc<Barrier>>,
47    // The corresponding sender is stored in the shared linker state, and is used
48    // by the instigating instance group  to broadcast the results.
49    pub(super) recv_pending_operation: bus::BusReader<DlOperation>,
50}
51
52// TODO: split further
53impl InstanceGroupState {
54    fn main_instance(&self) -> Option<&Instance> {
55        self.main_instance.as_ref()
56    }
57
58    pub(super) fn tls_base(&self, module_handle: ModuleHandle) -> Option<u64> {
59        if module_handle == MAIN_MODULE_HANDLE {
60            // Main's TLS area is at the beginning of its memory
61            self.main_instance_tls_base
62        } else {
63            self.side_instances
64                .get(&module_handle)
65                .expect("Internal error: bad module handle")
66                .tls_base
67        }
68    }
69
70    fn try_instance(&self, handle: ModuleHandle) -> Option<&Instance> {
71        if handle == MAIN_MODULE_HANDLE {
72            self.main_instance.as_ref()
73        } else {
74            self.side_instances.get(&handle).map(|i| &i.instance)
75        }
76    }
77
78    fn instance(&self, handle: ModuleHandle) -> &Instance {
79        self.try_instance(handle)
80            .expect("Internal error: bad module handle or not instantiated in this group")
81    }
82
83    pub(super) fn instantiate_side_module_from_link_state(
84        &mut self,
85        linker_state: &mut LinkerState,
86        store: &mut impl AsStoreMut,
87        env: &FunctionEnv<WasiEnv>,
88        link_state: &mut InProgressLinkState,
89        module_handle: ModuleHandle,
90    ) -> Result<(), LinkError> {
91        let Some(pending_module) = link_state
92            .new_modules
93            .iter()
94            .find(|m| m.handle == module_handle)
95        else {
96            panic!(
97                "Only recently-loaded modules in the link state can be instantiated \
98                by instantiate_side_module_from_link_state"
99            )
100        };
101
102        trace!(
103            ?module_handle,
104            ?link_state,
105            "Instantiating module from link state"
106        );
107
108        let memory_base = linker_state.allocate_memory(
109            store,
110            &self.memory,
111            &pending_module.dylink_info.mem_info,
112        )?;
113        let table_base = self
114            .allocate_function_table(
115                store,
116                pending_module.dylink_info.mem_info.table_size,
117                pending_module.dylink_info.mem_info.table_alignment,
118            )
119            .map_err(LinkError::TableAllocationError)?;
120
121        trace!(
122            memory_base,
123            table_base, "Allocated memory and table for module"
124        );
125
126        let mut imports = import_object_for_all_wasi_versions(&pending_module.module, store, env);
127
128        let well_known_imports = [
129            ("env", "__memory_base", memory_base),
130            ("env", "__table_base", table_base),
131        ];
132
133        let module = pending_module.module.clone();
134        let dylink_info = pending_module.dylink_info.clone();
135
136        trace!(?module_handle, "Resolving symbols");
137        linker_state.resolve_symbols(
138            self,
139            store,
140            &module,
141            module_handle,
142            link_state,
143            &well_known_imports,
144        )?;
145
146        trace!(?module_handle, "Populating imports object");
147        self.populate_imports_from_link_state(
148            module_handle,
149            linker_state,
150            link_state,
151            store,
152            &module,
153            &mut imports,
154            env,
155            &well_known_imports,
156        )?;
157
158        let instance = Instance::new(store, &module, &imports)?;
159
160        let instance_handles = WasiModuleInstanceHandles::new(
161            self.memory.clone(),
162            store,
163            instance.clone(),
164            Some(self.indirect_function_table.clone()),
165        );
166
167        let dl_module = DlModule {
168            module,
169            dylink_info,
170            memory_base,
171            table_base,
172        };
173
174        let tls_base = get_tls_base_export(&instance, store)?;
175
176        let dl_instance = DlInstance {
177            instance: instance.clone(),
178            instance_handles,
179            // The TLS base of a side module's main instance is read from the module's
180            // `__tls_base` export via `get_tls_base_export`, and is not necessarily at the
181            // beginning of its memory.
182            tls_base,
183        };
184
185        linker_state.side_modules.insert(module_handle, dl_module);
186        self.side_instances.insert(module_handle, dl_instance);
187
188        trace!(?module_handle, "Module instantiated");
189
190        Ok(())
191    }
192
193    // For when we receive a module loaded DL operation
194    pub(super) fn instantiate_side_module_from_linker(
195        &mut self,
196        linker_state: &LinkerState,
197        store: &mut impl AsStoreMut,
198        env: &FunctionEnv<WasiEnv>,
199        module_handle: ModuleHandle,
200        pending_resolutions: &mut PendingResolutionsFromLinker,
201    ) -> Result<(), LinkError> {
202        if self.side_instances.contains_key(&module_handle) {
203            panic!(
204                "Internal error: Module with handle {module_handle:?} \
205                was already instantiated in this group"
206            )
207        };
208
209        trace!(?module_handle, "Instantiating existing module from linker");
210
211        let dl_module = linker_state
212            .side_modules
213            .get(&module_handle)
214            .expect("Internal error: module not loaded into linker");
215
216        let mut imports = import_object_for_all_wasi_versions(&dl_module.module, store, env);
217
218        let well_known_imports = [
219            ("env", "__memory_base", dl_module.memory_base),
220            ("env", "__table_base", dl_module.table_base),
221        ];
222
223        trace!(?module_handle, "Populating imports object");
224        self.populate_imports_from_linker(
225            module_handle,
226            linker_state,
227            store,
228            &dl_module.module,
229            &mut imports,
230            env,
231            &well_known_imports,
232            pending_resolutions,
233        )?;
234
235        let instance = Instance::new(store, &dl_module.module, &imports)?;
236
237        // This is a non-main instance of a side module, so it needs a new TLS area
238        let tls_base = call_initialization_function::<i32>(&instance, store, "__wasix_init_tls")?
239            .map(|v| v as u64);
240
241        let instance_handles = WasiModuleInstanceHandles::new(
242            self.memory.clone(),
243            store,
244            instance.clone(),
245            Some(self.indirect_function_table.clone()),
246        );
247
248        let dl_instance = DlInstance {
249            instance: instance.clone(),
250            instance_handles,
251            tls_base,
252        };
253
254        self.side_instances.insert(module_handle, dl_instance);
255
256        // Initialization logic must only be run once, so no init calls here; it is
257        // assumed that the module was instantiated and its init callbacks were called
258        // by whichever thread first called instantiate_side_module_from_link_state.
259
260        trace!(?module_handle, "Existing module instantiated successfully");
261
262        Ok(())
263    }
264
265    pub(super) fn finalize_pending_resolutions_from_linker(
266        &self,
267        pending_resolutions: &PendingResolutionsFromLinker,
268        store: &mut impl AsStoreMut,
269    ) -> Result<(), LinkError> {
270        trace!("Finalizing pending functions");
271
272        for pending in &pending_resolutions.functions {
273            let func = self
274                .instance(pending.resolved_from)
275                .exports
276                .get_function(&pending.name)
277                .unwrap_or_else(|e| {
278                    panic!(
279                        "Internal error: failed to resolve exported function {}: {e:?}",
280                        pending.name
281                    )
282                });
283
284            self.place_in_function_table_at(store, func.clone(), pending.function_table_index)
285                .map_err(LinkError::TableAllocationError)?;
286
287            trace!(?pending, "Placed pending function in table");
288        }
289
290        for tls in &pending_resolutions.tls {
291            let Some(tls_base) = self.tls_base(tls.resolved_from) else {
292                // This is a panic since this error should have been caught when the symbol
293                // was originally resolved by the instigating instance group. We're just replaying
294                // the changes.
295                panic!(
296                    "Internal error: Tried to import TLS symbol from module {} that \
297                    has no TLS base",
298                    tls.resolved_from.0
299                );
300            };
301
302            let final_addr = tls_base + tls.offset;
303            set_integer_global(store, "<pending TLS global>", &tls.global, final_addr)?;
304            trace!(?tls, tls_base, final_addr, "Setting pending TLS global");
305        }
306
307        Ok(())
308    }
309
310    pub(super) fn apply_requested_symbols_from_linker(
311        &self,
312        store: &mut impl AsStoreMut,
313        linker_state: &LinkerState,
314    ) -> Result<(), LinkError> {
315        for (key, val) in &linker_state.symbol_resolution_records {
316            if let SymbolResolutionKey::Requested { name, .. } = key
317                && let SymbolResolutionResult::FunctionPointer {
318                    resolved_from,
319                    function_table_index,
320                } = val
321            {
322                self.apply_resolved_function(store, name, *resolved_from, *function_table_index)?;
323            }
324        }
325        Ok(())
326    }
327
328    pub(super) fn apply_dl_operation(
329        &mut self,
330        linker_state: &LinkerState,
331        operation: DlOperation,
332        store: &mut impl AsStoreMut,
333        env: &FunctionEnv<WasiEnv>,
334    ) -> Result<(), LinkError> {
335        trace!(?operation, "Applying operation");
336        match operation {
337            DlOperation::LoadModules(module_handles) => {
338                let mut pending_functions = PendingResolutionsFromLinker::default();
339                for handle in module_handles {
340                    // We need to do table allocation in exactly the same order as the instigating
341                    // group, which is:
342                    //   * Allocate module's own table space
343                    //   * Fill GOT.func entries (through instantiating the module)
344                    //   * Then repeat for the next module.
345                    self.allocate_function_table_for_existing_module(linker_state, store, handle)?;
346                    self.instantiate_side_module_from_linker(
347                        linker_state,
348                        store,
349                        env,
350                        handle,
351                        &mut pending_functions,
352                    )?;
353                }
354                self.finalize_pending_resolutions_from_linker(&pending_functions, store)?;
355            }
356            DlOperation::ResolveFunction {
357                name,
358                resolved_from,
359                function_table_index,
360            } => self.apply_resolved_function(store, &name, resolved_from, function_table_index)?,
361            DlOperation::AllocateFunctionTable { index, size } => {
362                self.apply_function_table_allocation(store, index, size)?
363            }
364        };
365        trace!("Operation applied successfully");
366        Ok(())
367    }
368
369    pub(super) fn finalize_pending_globals(
370        &self,
371        linker_state: &mut LinkerState,
372        store: &mut impl AsStoreMut,
373        unresolved_globals: &Vec<UnresolvedGlobal>,
374    ) -> Result<(), LinkError> {
375        trace!("Finalizing pending globals");
376
377        for unresolved in unresolved_globals {
378            let key = unresolved.key();
379            let import_metadata = &linker_state.dylink_info(key.module_handle).import_metadata;
380            let is_weak = import_metadata
381                .get(&(key.import_module.to_owned(), key.import_name.to_owned()))
382                // clang seems to like putting the import-info in the "env" module
383                // sometimes, so try that as well
384                .or_else(|| import_metadata.get(&("env".to_owned(), key.import_name.to_owned())))
385                .map(|flags| flags.contains(wasmparser::SymbolFlags::BINDING_WEAK))
386                .unwrap_or(false);
387            trace!(?unresolved, is_weak, "Resolving pending global");
388
389            match (
390                unresolved,
391                self.resolve_export(linker_state, store, None, &key.import_name, true),
392            ) {
393                (
394                    UnresolvedGlobal::Mem(key, global),
395                    Ok((PartiallyResolvedExport::Global(addr), resolved_from)),
396                ) => {
397                    trace!(
398                        ?unresolved,
399                        ?resolved_from,
400                        addr,
401                        "Resolved to memory address"
402                    );
403                    set_integer_global(store, &key.import_name, global, addr)?;
404                    linker_state.symbol_resolution_records.insert(
405                        SymbolResolutionKey::Needed(key.clone()),
406                        SymbolResolutionResult::Memory(addr),
407                    );
408                }
409
410                (
411                    UnresolvedGlobal::Mem(key, global),
412                    Ok((PartiallyResolvedExport::Tls { offset, final_addr }, resolved_from)),
413                ) => {
414                    trace!(
415                        ?unresolved,
416                        ?resolved_from,
417                        offset,
418                        final_addr,
419                        "Resolved to TLS address"
420                    );
421                    set_integer_global(store, &key.import_name, global, final_addr)?;
422                    linker_state.symbol_resolution_records.insert(
423                        SymbolResolutionKey::Needed(key.clone()),
424                        SymbolResolutionResult::Tls {
425                            resolved_from,
426                            offset,
427                        },
428                    );
429                }
430
431                (
432                    UnresolvedGlobal::Func(key, global),
433                    Ok((PartiallyResolvedExport::Function(func), resolved_from)),
434                ) => {
435                    let func_handle = self
436                        .append_to_function_table(store, func)
437                        .map_err(LinkError::TableAllocationError)?;
438                    trace!(
439                        ?unresolved,
440                        ?resolved_from,
441                        function_table_index = ?func_handle,
442                        "Resolved to function pointer"
443                    );
444                    set_integer_global(store, &key.import_name, global, func_handle as u64)?;
445                    linker_state.symbol_resolution_records.insert(
446                        SymbolResolutionKey::Needed(key.clone()),
447                        SymbolResolutionResult::FunctionPointer {
448                            resolved_from,
449                            function_table_index: func_handle,
450                        },
451                    );
452                }
453
454                // Expected memory address, resolved function or vice-versa
455                (_, Ok(_)) => {
456                    return Err(LinkError::UnresolvedGlobal(
457                        unresolved.import_module().to_string(),
458                        key.import_name.clone(),
459                        Box::new(ResolveError::MissingExport),
460                    ));
461                }
462
463                // Missing weak symbols get resolved to a null address
464                (_, Err(ResolveError::MissingExport)) if is_weak => {
465                    trace!(?unresolved, "Weak global not found");
466                    set_integer_global(store, &key.import_name, unresolved.global(), 0)?;
467                    linker_state.symbol_resolution_records.insert(
468                        SymbolResolutionKey::Needed(key.clone()),
469                        SymbolResolutionResult::Memory(0),
470                    );
471                }
472
473                (_, Err(e)) => {
474                    return Err(LinkError::UnresolvedGlobal(
475                        "GOT.mem".to_string(),
476                        key.import_name.clone(),
477                        Box::new(e),
478                    ));
479                }
480            }
481        }
482
483        Ok(())
484    }
485}