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