wasmer_wasix/state/linker/
mod.rs

1// TODO: The linker *can* exist in the runtime, since technically, there's nothing that
2// prevents us from having a non-WASIX linker. However, there is currently no use-case
3// for a non-WASIX linker, so we'll refrain from making it generic for the time being.
4
5//! Linker for loading and linking dynamic modules at runtime. The linker is designed to
6//! work with output from clang (version 19 was used at the time of creating this code).
7//! Note that dynamic linking of WASM modules is considered unstable in clang/LLVM, so
8//! this code may need to be updated for future versions of clang.
9//!
10//! The linker doesn't care about where code exists and how modules call each other, but
11//! the way we have found to be most effective is:
12//!     * The main module carries with it all of wasix-libc, and exports everything
13//!     * Side module don't link wasix-libc in, instead importing it from the main module
14//!
15//! This way, we only need one instance of wasix-libc, and one instance of all the static
16//! data that it requires to function. Indeed, if there were multiple instances of its
17//! static data, it would more than likely just break completely; one needs only imagine
18//! what would happen if there were multiple memory allocators (malloc) running at the same
19//! time. Emscripten (the only WASM runtime that supports dynamic linking, at the time of
20//! this writing) takes the same approach.
21//!
22//! While locating modules by relative or absolute paths is possible, it is recommended
23//! to put every side module into /lib, where they can be located by name as well as by
24//! path.
25//!
26//! The linker starts from a dynamically-linked main module. It scans the dylink.0 section
27//! for memory and table-related information and the list of needed modules. The module
28//! tree requires a memory, an indirect function table, and stack-related parameters
29//! (including the __stack_pointer global), which are created. Since dynamically-linked
30//! modules use PIC (position-independent code), the stack is not fixed and can be resized
31//! at runtime.
32//!
33//! After the memory, function table and stack are created, the linker proceeds to load in
34//! needed modules. Needed modules are always loaded in and initialized before modules that
35//! asked for them, since it is expected that the needed module needs to be usable before
36//! the module that needs it can be initialized.
37//!
38//! However, we also need to support circular dependencies between the modules; the most
39//! common case is when the main needs a side module and imports function from it, and the
40//! side imports wasix-libc functions from the main. To support this, the linker generates
41//! stub functions for all the imports that cannot be resolved when a module is being
42//! loaded in. The stub functions will then resolve the function once (and only once) at
43//! runtime when they're first called. This *does*, however, mean that link errors can happen
44//! at runtime, after the linker has reported successful linking of the modules. Such errors
45//! are turned into a [`WasiError::DlSymbolResolutionFailed`] error and will terminate
46//! execution completely.
47//!
48//! # Threading Support
49//!
50//! The linker supports the concept of "Instance Groups", which are multiple instances
51//! of the same module tree. This corresponds very closely to WASIX threads, but is
52//! named an instance group so as to keep the logic decoupled from the threading logic
53//! in WASIX.
54//!
55//! Each instance group has its own store, indirect function table, and stack pointer,
56//! but shares its memory with every other instance group. Note that even though the
57//! underlying memory is the same, we need to create a new [`Memory`] instance
58//! for each group via [`Memory::share_and_detach`] +
59//! [`DetachedMemory::attach`](wasmer::DetachedMemory::attach). Also, when placing a symbol
60//! in the function table, the linker always updates all function tables at the same
61//! time. This is because function "pointers" can be passed across instance groups
62//! (read: sent to other threads) by the guest code, so all function tables should
63//! have exactly the same content at all times.
64//!
65//! One important aspect of instance groups is that they do *not* share the same store;
66//! this lets us put different instance groups on different OS threads. However, this
67//! also means that one call to [`Linker::load_module`], etc. cannot update every
68//! instance group as each one has its own function table. To make the linker work
69//! across threads, we need a "stop-the-world" lock on every instance group. The group
70//! the load/resolve request originates from sets a flag, which other instance
71//! groups are required to check periodically by calling [`Linker::do_pending_link_operations`].
72//! Once all instance groups are stopped in that function, the original can proceed to
73//! perform the operation, and report its results to all other instance groups so they
74//! can make the same changes to their function table as well.
75//!
76//! In WASIX, the periodic check is performed at the start of most (but not all) syscalls.
77//! This means a thread that doesn't make any syscalls can potentially block all other
78//! threads if a DL operation is performed. This also means that two instance groups
79//! cannot co-exist on the same OS thread, as the first one will block the OS thread
80//! and the second can't enter the "lock" again to let the first continue its work.
81//!
82//! To also get cooperation from threads that are waiting in a syscall, a
83//! [`Signal::Sigwakeup`](wasmer_wasix_types::wasi::Signal::Sigwakeup) signal is sent to
84//! all threads when a DL operation needs to be synchronized.
85//!
86//! # About TLS
87//!
88//! Each instance of each group gets its own TLS area, so there are 4 cases to consider:
89//!     * Main instance of main module: TLS area will be allocated by the compiler, and be
90//!       placed at the start of the memory region requested by the `dylink.0` section.
91//!     * Main instance of side modules: Almost same as main module, but tls_base will be
92//!       non-zero because side modules get a non-zero memory_base. It is very important
93//!       to note that the main instance of a side module lives in the instance group
94//!       that initially loads it in. This **does not** have to be the main instance
95//!       group.
96//!     * Other instances of main module: Each worker thread gets its TLS area
97//!       allocated by the code in pthread_create, and a pointer to the TLS area is passed
98//!       through the thread start args. This pointer is read by the code in thread_spawn,
99//!       and passed through to us as part of the environment's memory layout.
100//!     * Other instances of side modules: This is where the linker comes in. When the
101//!       new instance is created, the linker will call its `__wasix_init_tls` function,
102//!       which is responsible for setting up the TLS area for the thread.
103//!
104//! Since we only want to call `__wasix_init_tls` for non-main instances of side modules,
105//! it is enough to call it only within [`InstanceGroupState::instantiate_side_module_from_linker`].
106//!
107//! # Module Loading
108//!
109//! Module loading happens as an orchestrated effort between the shared linker state, the
110//! state of the instance group that started (or "instigated") the operation, and other
111//! instance groups. Access to a set of instances is required for resolution of exports,
112//! which is why the linker state alone (which only stores modules) is not enough.
113//!
114//! Even though most (if not all) operations require access to both the shared linker state
115//! and a/the instance group state, they're separated into three sets:
116//!     * Operations that deal with metadata exist as impls on [`LinkerState`]. These take
117//!       a (read-only) instance group state for export resolution, as well as a
118//!       [`StoreRef`](wasmer::StoreRef). They're guaranteed not to alter the store or the
119//!       instance group state.
120//!     * Operations that deal with the actual instances (instantiating, putting symbols in the
121//!       function table, etc.) and are started by the instigating group exist as impls on
122//!       [`InstanceGroupState`] that also take a mutable reference to the shared linker state, and
123//!       require it to be locked for writing. These operations can and will update the linker state,
124//!       mainly to store symbol resolution records.
125//!     * Operations that deal with replicating changes to instances from another thread also exits
126//!       as impls on [`InstanceGroupState`], but take a read-only reference to the shared linker
127//!       state. This is important because all the information needed for replicating the change to
128//!       the instigating group's instances should already be in the linker state. See
129//!       [`InstanceGroupState::populate_imports_from_linker`] and
130//!       [`InstanceGroupState::instantiate_side_module_from_linker`] for the two most important ones.
131//!
132//! Module loading generally works by going through these steps:
133//!     * [`LinkerState::load_module_tree`] loads modules (and their needed modules) and assigns
134//!       module handles
135//!     * Then, for each new module:
136//!         * Memory and table space is allocated
137//!         * Imports are resolved (see next section)
138//!         * The module is instantiated
139//!     * After all modules have been instantiated, pending imports (resulting from circular
140//!       dependencies) are resolved
141//!     * Finally, module initializers are called
142//!
143//! ## Symbol resolution
144//!
145//! To support replicating operations from the instigating group to other groups, symbol resolution
146//! happens in 3 steps:
147//!     * [`LinkerState::resolve_symbols`] goes through the imports of a soon-to-be-loaded module,
148//!       recording the imports as [`NeededSymbolResolutionKey`]s and creating
149//!       [`InProgressSymbolResolution`]s in response to each one.
150//!     * [`InstanceGroupState::populate_imports_from_link_state`] then goes through the results
151//!       and resolves each import to its final value, while also recording enough information (in the
152//!       shape of [`SymbolResolutionResult`]s) for other groups to resolve the symbol from their own
153//!       instances.
154//!     * Finally, instances are created and finalized, and initializers are called.
155//!
156//! ## Stub functions
157//!
158//! As noted above, stub functions are generated in response to circular dependencies. The stub
159//! functions do take previous symbol resolution records into account, so that the stub corresponding
160//! to a single import cannot resolve to different exports in different groups. If no such record is
161//! found, then a new record is created by the stub function. However, there's a catch.
162//!
163//! It must be noted that, during initialization, the shared linker state has to remain write-locked
164//! so as to prevent other threads from starting another operation (the replication logic only works
165//! with one active operation at a time). Stub functions need a write lock on the shared linker state
166//! to store new resolution records, and as such, they can't store resolution records if they're
167//! called in response to a module's initialization routines. This can happen easily if:
168//! * A side module is needed by the main
169//! * That side module accesses any libc functions, such as printing something to stdout.
170//!
171//! To work around this, stub functions only *try* to lock the shared linker state, and if they can't,
172//! they won't store anything. A follow-up call to the stub function can resolve the symbol again,
173//! store it for use by further calls to the function, and also create a resolution record. This does
174//! create a few hard-to-reach edge cases:
175//!     * If the symbol happens to resolve differently between the two calls to the stub, unpredictable
176//!       behavior can happen; however, this is impossible in the current implementation.
177//!     * If the shared state is locked by a different instance group, then the stub won't store its
178//!       lookup results anyway, even though it could have if it had waited.
179//!
180//! ## Locating side modules
181//!
182//! Side modules are located according to these steps:
183//!     * If the name contains a slash (/), it is treated as a relative or absolute path.   
184//!     * Otherwise, the name is searched for in `/lib`, `/usr/lib` and `/usr/local/lib`.
185//!       LD_LIBRARY_PATH is not supported yet.
186//!
187//! # Building dynamically-linked modules
188//!
189//! Note that building modules that conform the specific requirements of this linker requires
190//! careful configuration of clang. A PIC sysroot is required. The steps to build a main
191//! module are:
192//!
193//! ```bash
194//! clang-19 \
195//!   --target=wasm32-wasi --sysroot=/path/to/sysroot32-pic \
196//!   -matomics -mbulk-memory -mmutable-globals -pthread \
197//!   -mthread-model posix -ftls-model=local-exec \
198//!   -fno-trapping-math -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL \
199//!   -D_WASI_EMULATED_PROCESS_CLOCKS \
200//!   # PIC is required for all modules, main and side
201//!   -fPIC \
202//!   # We need to compile to an object file we can manually link in the next step
203//!   -c main.c -o main.o
204//!
205//! wasm-ld-19 \
206//!   # To link needed side modules, assuming `libsidewasm.so` exists in the current directory:
207//!   -L. -lsidewasm \
208//!   -L/path/to/sysroot32-pic/lib \
209//!   -L/path/to/sysroot32-pic/lib/wasm32-wasi \
210//!   # Make wasm-ld search everywhere and export everything, needed for wasix-libc functions to
211//!   # be exported correctly from the main module
212//!   --whole-archive --export-all \
213//!   # The object file from the last step
214//!   main.o \
215//!   # The crt1.o file contains the _start and _main_void functions
216//!   /path/to/sysroot32-pic/lib/wasm32-wasi/crt1.o \
217//!   # Statically link the sysroot's libraries
218//!   -lc -lresolv -lrt -lm -lpthread -lwasi-emulated-mman \
219//!   # The usual linker config for wasix modules
220//!   --import-memory --shared-memory --extra-features=atomics,bulk-memory,mutable-globals \
221//!   --export=__wasm_signal --export=__tls_size --export=__tls_align \
222//!   --export=__tls_base --export=__wasm_call_ctors --export-if-defined=__wasm_apply_data_relocs \
223//!   # Again, PIC is very important, as well as producing a location-independent executable with -pie
224//!   --experimental-pic -pie \
225//!   -o main.wasm
226//! ```
227//!
228//! And the steps to build a side module are:
229//!
230//! ```bash
231//! clang-19 \
232//!   --target=wasm32-wasi --sysroot=/path/to/sysroot32-pic \
233//!   -matomics -mbulk-memory -mmutable-globals -pthread \
234//!   -mthread-model posix -ftls-model=local-exec \
235//!   -fno-trapping-math -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL \
236//!   -D_WASI_EMULATED_PROCESS_CLOCKS \
237//!   # We need PIC
238//!   -fPIC \
239//!   # Make it export everything that's not hidden explicitly
240//!   -fvisibility=default \
241//!   -c side.c -o side.o
242//!
243//! wasm-ld-19 \
244//!   # Note: we don't link against wasix-libc, so no -lc etc., because we want
245//!   # those symbols to be imported.
246//!   --extra-features=atomics,bulk-memory,mutable-globals \
247//!   --export=__wasm_call_ctors --export-if-defined=__wasm_apply_data_relocs \
248//!   # Need PIC
249//!   --experimental-pic \
250//!   # Import everything that's undefined, including wasix-libc functions
251//!   --unresolved-symbols=import-dynamic \
252//!   # build a shared library
253//!   -shared \
254//!   # Import a shared memory
255//!   --shared-memory \
256//!   # Conform to the libxxx.so naming so clang can find it via -lxxx
257//!   -o libsidewasm.so side.o
258//! ```
259
260#![allow(clippy::result_large_err)]
261
262mod dylink;
263mod error;
264mod instance_group;
265mod internal_types;
266mod linker_state;
267mod locator;
268mod memory_allocator;
269mod sync;
270mod types;
271mod wasm_utils;
272
273pub use dylink::*;
274pub use error::*;
275pub use types::*;
276
277use instance_group::*;
278use internal_types::*;
279use linker_state::*;
280use locator::*;
281use memory_allocator::*;
282use sync::*;
283use wasm_utils::*;
284
285use std::{
286    collections::{BTreeMap, HashMap},
287    ops::DerefMut,
288    path::Path,
289    sync::{Arc, Mutex, MutexGuard, atomic::Ordering},
290};
291
292use bus::Bus;
293use tracing::trace;
294use wasmer::{AsStoreMut, Engine, FunctionEnvMut, Instance, Memory, Module, StoreMut, Tag, Type};
295use wasmer_wasix_types::wasix::WasiMemoryLayout;
296
297use crate::{WasiEnv, WasiFunctionEnv, WasiModuleTreeHandles, import_object_for_all_wasi_versions};
298
299use super::WasiModuleInstanceHandles;
300
301// Module handle 1 is always the main module. Side modules get handles starting from the next one after the main module.
302pub static MAIN_MODULE_HANDLE: ModuleHandle = ModuleHandle(1);
303static INVALID_MODULE_HANDLE: ModuleHandle = ModuleHandle(u32::MAX);
304
305// Need to keep the zeroth index null to catch null function pointers at runtime
306static MAIN_MODULE_TABLE_BASE: u64 = 1;
307
308/// The linker is responsible for loading and linking dynamic modules at runtime,
309/// and managing the shared memory and indirect function table.
310/// Each linker instance represents a specific instance group. Cloning a linker
311/// instance does *not* create a new instance group though; the clone will refer
312/// to the same group as the original.
313#[derive(Clone)]
314pub struct Linker {
315    shared: LinkerShared,
316    instance_group_state: Arc<Mutex<Option<InstanceGroupState>>>,
317}
318
319impl std::fmt::Debug for Linker {
320    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321        f.debug_struct("Linker").finish()
322    }
323}
324
325impl Linker {
326    /// Creates a new linker for the given main module. The module is expected to be a
327    /// PIE executable. Imports for the module will be fulfilled, so that it can start
328    /// running, and a Linker instance is returned which can then be used for the
329    /// loading/linking of further side modules.
330    pub fn new(
331        engine: Engine,
332        main_module: &Module,
333        store: &mut StoreMut<'_>,
334        memory: Option<Memory>,
335        func_env: &mut WasiFunctionEnv,
336        stack_size: u64,
337        ld_library_path: &[&Path],
338    ) -> Result<(Self, LinkedMainModule), LinkError> {
339        let dylink_section = parse_dylink0_section(main_module)?;
340
341        trace!(?dylink_section, "Loading main module");
342
343        let mut imports = import_object_for_all_wasi_versions(main_module, store, &func_env.env);
344
345        let function_table_type = main_module_function_table_type(main_module)?;
346
347        let expected_table_length =
348            dylink_section.mem_info.table_size + MAIN_MODULE_TABLE_BASE as u32;
349        let indirect_function_table =
350            create_indirect_function_table(store, function_table_type, expected_table_length)?;
351
352        // Give modules a non-zero memory base, since we don't want
353        // any valid pointers to point to the zero address
354        let memory_base = 2u64.pow(dylink_section.mem_info.memory_alignment);
355
356        let memory_type = main_module_memory_type(main_module)?;
357
358        let memory = match memory {
359            Some(m) => m,
360            None => Memory::new(store, memory_type)?,
361        };
362
363        let stack_low = {
364            let data_end = memory_base + dylink_section.mem_info.memory_size as u64;
365            if !data_end.is_multiple_of(1024) {
366                data_end + 1024 - (data_end % 1024)
367            } else {
368                data_end
369            }
370        };
371
372        if !stack_size.is_multiple_of(1024) {
373            panic!("Stack size must be 1024-bit aligned");
374        }
375
376        let stack_high = stack_low + stack_size;
377
378        // Allocate memory for the stack. This does not need to go through the memory allocator
379        // because it's always placed directly after the main module's data
380        memory.grow_at_least(store, stack_high)?;
381
382        trace!(
383            memory_pages = ?memory.grow(store, 0).unwrap(),
384            memory_base,
385            stack_low,
386            stack_high,
387            "Memory layout"
388        );
389
390        let stack_pointer = create_main_stack_pointer_global(store, main_module, stack_high)?;
391
392        let c_longjmp = Tag::new(store, vec![Type::I32]);
393        let cpp_exception = Tag::new(store, vec![Type::I32]);
394
395        let mut barrier_tx = Bus::new(1);
396        let barrier_rx = barrier_tx.add_rx();
397        let mut operation_tx = Bus::new(1);
398        let operation_rx = operation_tx.add_rx();
399
400        let mut instance_group = InstanceGroupState {
401            main_instance: None,
402            // The TLS base for the main instance is determined by reading the
403            // `__tls_base` global export from the instance after instantiation.
404            main_instance_tls_base: None,
405            side_instances: HashMap::new(),
406            stack_pointer,
407            memory: memory.clone(),
408            indirect_function_table: indirect_function_table.clone(),
409            c_longjmp,
410            cpp_exception,
411            recv_pending_operation_barrier: barrier_rx,
412            recv_pending_operation: operation_rx,
413        };
414
415        let mut linker_state = LinkerState {
416            engine,
417            main_module: main_module.clone(),
418            main_module_dylink_info: dylink_section,
419            main_module_memory_base: memory_base,
420            side_modules: BTreeMap::new(),
421            side_modules_by_name: HashMap::new(),
422            next_module_handle: MAIN_MODULE_HANDLE.0 + 1,
423            memory_allocator: MemoryAllocator::new(),
424            allocated_closure_functions: BTreeMap::new(),
425            available_closure_functions: Vec::new(),
426            heap_base: stack_high,
427            symbol_resolution_records: HashMap::new(),
428            send_pending_operation_barrier: barrier_tx,
429            send_pending_operation: operation_tx,
430        };
431
432        let mut link_state = InProgressLinkState::default();
433
434        let well_known_imports = [
435            ("env", "__memory_base", memory_base),
436            ("env", "__table_base", MAIN_MODULE_TABLE_BASE),
437            ("GOT.mem", "__stack_high", stack_high),
438            ("GOT.mem", "__stack_low", stack_low),
439            ("GOT.mem", "__heap_base", stack_high),
440        ];
441
442        trace!("Resolving main module's symbols");
443        linker_state.resolve_symbols(
444            &instance_group,
445            store,
446            main_module,
447            MAIN_MODULE_HANDLE,
448            &mut link_state,
449            &well_known_imports,
450        )?;
451
452        trace!("Populating main module's imports object");
453        instance_group.populate_imports_from_link_state(
454            MAIN_MODULE_HANDLE,
455            &mut linker_state,
456            &mut link_state,
457            store,
458            main_module,
459            &mut imports,
460            &func_env.env,
461            &well_known_imports,
462        )?;
463
464        // TODO: figure out which way is faster (stubs in main or stubs in sides),
465        // use that ordering. My *guess* is that, since main exports all the libc
466        // functions and those are called frequently by basically any code, then giving
467        // stubs to main will be faster, but we need numbers before we decide this.
468        let main_instance = Instance::new(store, main_module, &imports)?;
469        instance_group.main_instance = Some(main_instance.clone());
470
471        let tls_base = get_tls_base_export(&main_instance, store)?;
472        instance_group.main_instance_tls_base = tls_base;
473
474        let runtime_path = linker_state.main_module_dylink_info.runtime_path.clone();
475        for needed in linker_state.main_module_dylink_info.needed.clone() {
476            // A successful load_module will add the module to the side_modules list,
477            // from which symbols can be resolved in the following call to
478            // guard.resolve_imports.
479            trace!(name = needed, "Loading module needed by main");
480            let wasi_env = func_env.data(store);
481            linker_state.load_module_tree(
482                DlModuleSpec::FileSystem {
483                    module_spec: Path::new(needed.as_str()),
484                    ld_library_path,
485                },
486                &mut link_state,
487                &wasi_env.runtime,
488                &wasi_env.state,
489                runtime_path.as_ref(),
490                // HACK: The main module doesn't have to exist in the virtual FS at all; e.g.
491                // if one runs `wasmer ../module.wasm --volume .`, we won't have access to the
492                // main module's folder within the virtual FS. This is why we're picking PWD
493                // as the $ORIGIN of the main module, which should at least be slightly
494                // sensible. The `main.wasm` file name will be stripped and only the `./`
495                // will be taken into account by `locate_module`.
496                Some(Path::new("./main.wasm")),
497            )?;
498        }
499
500        for module_handle in link_state
501            .new_modules
502            .iter()
503            .map(|m| m.handle)
504            .collect::<Vec<_>>()
505        {
506            trace!(?module_handle, "Instantiating module");
507            instance_group.instantiate_side_module_from_link_state(
508                &mut linker_state,
509                store,
510                &func_env.env,
511                &mut link_state,
512                module_handle,
513            )?;
514        }
515
516        let linker = Self {
517            shared: LinkerShared::new(linker_state),
518            instance_group_state: Arc::new(Mutex::new(Some(instance_group))),
519        };
520
521        let stack_layout = WasiMemoryLayout {
522            stack_lower: stack_low,
523            stack_upper: stack_high,
524            stack_size: stack_high - stack_low,
525            guard_size: 0,
526            tls_base,
527        };
528        let module_handles = WasiModuleTreeHandles::Dynamic {
529            linker: linker.clone(),
530            main_module_instance_handles: WasiModuleInstanceHandles::new(
531                memory.clone(),
532                store,
533                main_instance.clone(),
534                Some(indirect_function_table.clone()),
535            ),
536        };
537
538        func_env
539            .initialize_handles_and_layout(
540                store,
541                main_instance.clone(),
542                module_handles,
543                Some(stack_layout),
544                true,
545            )
546            .map_err(LinkError::MainModuleHandleInitFailed)?;
547
548        {
549            trace!(?link_state, "Finalizing linking of main module");
550
551            let mut group_guard = linker.instance_group_state.lock().unwrap();
552            unsafe {
553                linker.shared.bootstrap_exclusive_write_then(|ls| {
554                    let group_state = group_guard.as_mut().unwrap();
555                    group_state.finalize_pending_globals(
556                        ls,
557                        store,
558                        &link_state.unresolved_globals,
559                    )?;
560
561                    trace!("Calling data relocator function for main module");
562                    call_initialization_function::<()>(
563                        &main_instance,
564                        store,
565                        "__wasm_apply_data_relocs",
566                    )?;
567                    call_initialization_function::<()>(
568                        &main_instance,
569                        store,
570                        "__wasm_apply_tls_relocs",
571                    )?;
572
573                    linker.initialize_new_modules(group_guard, store, link_state)
574                })?;
575            }
576        }
577
578        trace!("Calling main module's _initialize function");
579        call_initialization_function::<()>(&main_instance, store, "_initialize")?;
580
581        trace!("Link complete");
582
583        Ok((
584            linker,
585            LinkedMainModule {
586                instance: main_instance,
587                memory,
588                indirect_function_table,
589                stack_low,
590                stack_high,
591            },
592        ))
593    }
594
595    /// This method gathers all necessary data from a parent thread's
596    /// environment, so a child thread can later call [`Self::create_instance_group`]
597    /// and have its own instance group, letting it take part in dynamic linking.
598    /// This two-part process is needed because the parent and child each have
599    /// their own [`Store`], and [`Store`]s are not `Send`.
600    pub fn prepare_for_instance_group(
601        &self,
602        parent_ctx: &mut FunctionEnvMut<'_, WasiEnv>,
603    ) -> Result<PreparedInstanceGroupData, LinkError> {
604        trace!("Preparing for new instance group");
605
606        lock_instance_group_state!(
607            parent_group_state_guard,
608            parent_group_state,
609            self,
610            LinkError::InstanceGroupIsDead
611        );
612
613        // Lease topology only: parent does not mutate shared `LinkerState` here; the child takes
614        // the blocking write in `create_instance_group` while holding the moved token.
615        let env = parent_ctx.as_ref();
616        let mut store = parent_ctx.as_store_mut();
617        let topology_token =
618            self.shared
619                .acquire_topology_token(parent_group_state, &mut store, &env)?;
620
621        let parent_store = parent_ctx.as_store_mut();
622
623        let memory = parent_group_state
624            .memory
625            .as_shared(&parent_store)
626            .ok_or_else(|| LinkError::MemoryNotShared)?;
627
628        let indirect_function_table_type =
629            parent_group_state.indirect_function_table.ty(&parent_store);
630
631        let expected_table_length = parent_group_state
632            .indirect_function_table
633            .size(&parent_store);
634
635        Ok(PreparedInstanceGroupData {
636            linker_shared: self.shared.clone(),
637            topology_token,
638            memory,
639            indirect_function_table_type,
640            expected_table_length,
641        })
642    }
643
644    pub(crate) fn do_pending_link_operations(
645        &self,
646        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
647        fast: bool,
648    ) -> Result<(), LinkError> {
649        if !self.shared.dl_operation_pending_load(if fast {
650            Ordering::Relaxed
651        } else {
652            Ordering::SeqCst
653        }) {
654            return Ok(());
655        }
656
657        lock_instance_group_state!(guard, group_state, self, LinkError::InstanceGroupIsDead);
658
659        let env = ctx.as_ref();
660        let mut store = ctx.as_store_mut();
661        self.shared
662            .do_pending_link_operations_internal(group_state, &mut store, &env)
663    }
664
665    pub fn create_instance_group(
666        prepared_instance_group_data: PreparedInstanceGroupData,
667        store: &mut StoreMut<'_>,
668        func_env: &mut WasiFunctionEnv,
669    ) -> Result<(Self, LinkedMainModule), LinkError> {
670        trace!("Spawning new instance group");
671
672        let PreparedInstanceGroupData {
673            linker_shared,
674            topology_token,
675            memory,
676            indirect_function_table_type,
677            expected_table_length,
678        } = prepared_instance_group_data;
679
680        let (topology_hold, mut ls_write) =
681            linker_shared.write_linker_state_blocking_holding_topology(topology_token);
682
683        let main_module = ls_write.main_module.clone();
684
685        let mut imports = import_object_for_all_wasi_versions(&main_module, store, &func_env.env);
686
687        let memory = memory.attach(store);
688
689        let indirect_function_table = create_indirect_function_table(
690            store,
691            indirect_function_table_type,
692            expected_table_length,
693        )?;
694
695        // Since threads initialize their own stack space, we can only rely on the layout being
696        // initialized beforehand, which is the case with the thread_spawn syscall.
697        // FIXME: this needs to become a parameter if we ever decouple the linker from WASIX
698        let (stack_low, stack_high, tls_base) = {
699            let layout = &func_env.env.as_ref(store).layout;
700            (
701                layout.stack_lower,
702                layout.stack_upper,
703                layout.tls_base.expect(
704                    "tls_base must be set in memory layout of new instance group's main instance",
705                ),
706            )
707        };
708
709        trace!(stack_low, stack_high, "Memory layout");
710
711        // WASIX threads initialize their own stack pointer global in wasi_thread_start,
712        // so no need to initialize it to a value here.
713        let stack_pointer = create_main_stack_pointer_global(store, &main_module, 0)?;
714
715        let c_longjmp = Tag::new(store, vec![Type::I32]);
716        let cpp_exception = Tag::new(store, vec![Type::I32]);
717
718        let barrier_rx = ls_write.send_pending_operation_barrier.add_rx();
719        let operation_rx = ls_write.send_pending_operation.add_rx();
720
721        let mut instance_group = InstanceGroupState {
722            main_instance: None,
723            main_instance_tls_base: Some(tls_base),
724            side_instances: HashMap::new(),
725            stack_pointer,
726            memory: memory.clone(),
727            indirect_function_table: indirect_function_table.clone(),
728            c_longjmp,
729            cpp_exception,
730            recv_pending_operation_barrier: barrier_rx,
731            recv_pending_operation: operation_rx,
732        };
733
734        let mut pending_resolutions = PendingResolutionsFromLinker::default();
735
736        let well_known_imports = [
737            ("env", "__memory_base", ls_write.main_module_memory_base),
738            ("env", "__table_base", MAIN_MODULE_TABLE_BASE),
739            ("GOT.mem", "__stack_high", stack_high),
740            ("GOT.mem", "__stack_low", stack_low),
741            ("GOT.mem", "__heap_base", ls_write.heap_base),
742        ];
743
744        trace!("Populating imports object for new instance group's main instance");
745        instance_group.populate_imports_from_linker(
746            MAIN_MODULE_HANDLE,
747            &ls_write,
748            store,
749            &main_module,
750            &mut imports,
751            &func_env.env,
752            &well_known_imports,
753            &mut pending_resolutions,
754        )?;
755
756        let main_instance = Instance::new(store, &main_module, &imports)?;
757
758        instance_group.main_instance = Some(main_instance.clone());
759
760        for side in &ls_write.side_modules {
761            trace!(module_handle = ?side.0, "Instantiating existing side module");
762            instance_group.instantiate_side_module_from_linker(
763                &ls_write,
764                store,
765                &func_env.env,
766                *side.0,
767                &mut pending_resolutions,
768            )?;
769        }
770
771        trace!("Finalizing pending functions");
772        instance_group.finalize_pending_resolutions_from_linker(&pending_resolutions, store)?;
773
774        trace!("Applying externally-requested function table entries");
775        instance_group.apply_requested_symbols_from_linker(store, &ls_write)?;
776
777        drop(ls_write);
778        drop(topology_hold);
779
780        let linker = Self {
781            shared: linker_shared,
782            instance_group_state: Arc::new(Mutex::new(Some(instance_group))),
783        };
784
785        let module_handles = WasiModuleTreeHandles::Dynamic {
786            linker: linker.clone(),
787            main_module_instance_handles: WasiModuleInstanceHandles::new(
788                memory.clone(),
789                store,
790                main_instance.clone(),
791                Some(indirect_function_table.clone()),
792            ),
793        };
794
795        func_env
796            .initialize_handles_and_layout(
797                store,
798                main_instance.clone(),
799                module_handles,
800                None,
801                false,
802            )
803            .map_err(LinkError::MainModuleHandleInitFailed)?;
804
805        trace!("Instance group spawned successfully");
806
807        Ok((
808            linker,
809            LinkedMainModule {
810                instance: main_instance,
811                memory,
812                indirect_function_table,
813                stack_low,
814                stack_high,
815            },
816        ))
817    }
818
819    pub fn shutdown_instance_group(
820        &self,
821        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
822    ) -> Result<(), LinkError> {
823        trace!("Shutting instance group down");
824
825        let mut guard = self.instance_group_state.lock().unwrap();
826        match guard.as_mut() {
827            None => Ok(()),
828            Some(group_state) => {
829                // We need to do this even if the results of an incoming dl op will be thrown away;
830                // this is because the instigating group will have counted us and we need to hit the
831                // barrier twice to unblock everybody else.
832                let linker_state = self.shared.write_linker_state(group_state, ctx)?;
833                guard.take();
834                drop(linker_state);
835
836                trace!("Instance group shut down");
837
838                Ok(())
839            }
840        }
841    }
842
843    /// Allocate a index for a closure in the indirect function table
844    pub fn allocate_closure_index(
845        &self,
846        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
847    ) -> Result<u32, LinkError> {
848        lock_instance_group_state!(
849            group_state_guard,
850            group_state,
851            self,
852            LinkError::InstanceGroupIsDead
853        );
854        let mut linker_state = self.shared.write_linker_state(group_state, ctx)?;
855
856        // Use a previously allocated slot if possible
857        if let Some(function_index) = linker_state.available_closure_functions.pop() {
858            linker_state
859                .allocated_closure_functions
860                .insert(function_index, true);
861            return Ok(function_index);
862        }
863
864        drop(linker_state);
865
866        let (topology_token, mut linker_state) = self
867            .shared
868            .write_linker_state_with_topology(group_state, ctx)?;
869
870        let mut store = ctx.as_store_mut();
871
872        // Another group may have refilled slots while we released the linker lock.
873        if let Some(function_index) = linker_state.available_closure_functions.pop() {
874            linker_state
875                .allocated_closure_functions
876                .insert(function_index, true);
877            drop(linker_state);
878            drop(topology_token);
879            return Ok(function_index);
880        }
881
882        // Allocate more closures than we need to reduce the number of sync operations
883        const CLOSURE_ALLOCATION_SIZE: u32 = 100;
884
885        let function_index = group_state
886            .allocate_function_table(&mut store, CLOSURE_ALLOCATION_SIZE, 0)
887            .map_err(LinkError::TableAllocationError)? as u32;
888
889        linker_state
890            .available_closure_functions
891            .reserve(CLOSURE_ALLOCATION_SIZE as usize - 1);
892        for i in 1..CLOSURE_ALLOCATION_SIZE {
893            linker_state
894                .available_closure_functions
895                .push(function_index + i);
896            linker_state
897                .allocated_closure_functions
898                .insert(function_index + i, false);
899        }
900        linker_state
901            .allocated_closure_functions
902            .insert(function_index, true);
903
904        self.shared.synchronize_link_operation(
905            topology_token,
906            DlOperation::AllocateFunctionTable {
907                index: function_index,
908                size: CLOSURE_ALLOCATION_SIZE,
909            },
910            linker_state,
911            group_state,
912            &ctx.data().process,
913            ctx.data().tid(),
914        );
915
916        Ok(function_index)
917    }
918
919    /// Remove a previously allocated slot for a closure in the indirect function table
920    ///
921    /// After calling this it is undefined behavior to call the function at the given index.
922    pub fn free_closure_index(
923        &self,
924        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
925        function_id: u32,
926    ) -> Result<(), LinkError> {
927        lock_instance_group_state!(
928            group_state_guard,
929            group_state,
930            self,
931            LinkError::InstanceGroupIsDead
932        );
933        let mut linker_state = self.shared.write_linker_state(group_state, ctx)?;
934
935        let Some(entry) = linker_state
936            .allocated_closure_functions
937            .get_mut(&function_id)
938        else {
939            // Not allocated
940            return Ok(());
941        };
942        if !*entry {
943            // Not used
944            return Ok(());
945        }
946
947        *entry = false;
948        linker_state.available_closure_functions.push(function_id);
949        Ok(())
950    }
951
952    /// Check if an indirect_function_table entry is reserved for closures.
953    /// Returns false if the entry is not reserved for closures.
954    /// Requires a FunctionEnvMut because pending DL operations should always
955    /// be processed before acquiring any lock on the linker.
956    // TODO: we can cache this information within the group state so we don't
957    // need a write lock on the linker state here
958    pub fn is_closure(
959        &self,
960        function_id: u32,
961        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
962    ) -> Result<bool, LinkError> {
963        // If we can get a read lock on the linker state, do it
964        if let Ok(linker_state) = self.shared.try_read_linker_state() {
965            return Ok(linker_state
966                .allocated_closure_functions
967                .contains_key(&function_id));
968        }
969
970        // Otherwise, fall back to the path where we apply DL ops and acquire
971        // a write lock afterwards
972        lock_instance_group_state!(
973            group_state_guard,
974            group_state,
975            self,
976            LinkError::InstanceGroupIsDead
977        );
978        let linker_state = self.shared.write_linker_state(group_state, ctx)?;
979        Ok(linker_state
980            .allocated_closure_functions
981            .contains_key(&function_id))
982    }
983
984    /// Loads a side module from the given path, linking it against the existing module tree
985    /// and instantiating it. Symbols from the module can then be retrieved by calling
986    /// [`Linker::resolve_export`].
987    pub fn load_module(
988        &self,
989        module_spec: DlModuleSpec,
990        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
991    ) -> Result<ModuleHandle, LinkError> {
992        trace!(?module_spec, "Loading module");
993
994        lock_instance_group_state!(
995            group_state_guard,
996            group_state,
997            self,
998            LinkError::InstanceGroupIsDead
999        );
1000
1001        // TODO: differentiate between an actual link error and an error that occurs as the
1002        // result of a pending operation that needs to be applied first. Currently, errors
1003        // from pending ops are treated as link errors and just reported to guest code rather
1004        // than terminating the process.
1005        let (topology_token, mut linker_state) = self
1006            .shared
1007            .write_linker_state_with_topology(group_state, ctx)?;
1008
1009        let mut link_state = InProgressLinkState::default();
1010        let env = ctx.as_ref();
1011        let mut store = ctx.as_store_mut();
1012
1013        trace!("Loading module tree for requested module");
1014        let wasi_env = env.as_ref(&store);
1015        let runtime_path: &[String] = &[];
1016        let module_handle = linker_state.load_module_tree(
1017            module_spec,
1018            &mut link_state,
1019            &wasi_env.runtime,
1020            &wasi_env.state,
1021            runtime_path,          // No runtime path when loading a module via dlopen
1022            Option::<&Path>::None, // Empty runtime path means we don't need the module's path either
1023        )?;
1024
1025        let new_modules = link_state
1026            .new_modules
1027            .iter()
1028            .map(|m| m.handle)
1029            .collect::<Vec<_>>();
1030
1031        for handle in &new_modules {
1032            trace!(?module_handle, "Instantiating module");
1033            group_state.instantiate_side_module_from_link_state(
1034                &mut linker_state,
1035                &mut store,
1036                &env,
1037                &mut link_state,
1038                *handle,
1039            )?;
1040        }
1041
1042        trace!("Finalizing link");
1043        self.finalize_link_operation(group_state_guard, &mut linker_state, &mut store, link_state)?;
1044
1045        if !new_modules.is_empty() {
1046            // The group state is unlocked for stub functions, now lock it again
1047            lock_instance_group_state!(
1048                group_state_guard,
1049                group_state,
1050                self,
1051                LinkError::InstanceGroupIsDead
1052            );
1053
1054            self.shared.synchronize_link_operation(
1055                topology_token,
1056                DlOperation::LoadModules(new_modules),
1057                linker_state,
1058                group_state,
1059                &ctx.data().process,
1060                ctx.data().tid(),
1061            );
1062        }
1063
1064        // FIXME: If we fail at an intermediate step, we should reset the linker's state, a la:
1065        // if result.is_err() {
1066        //     let mut guard = self.state.lock().unwrap();
1067        //     let memory = guard.memory.clone();
1068
1069        //     for module_handle in link_state.module_handles.iter().cloned() {
1070        //         let module = guard.side_modules.remove(&module_handle).unwrap();
1071        //         guard
1072        //             .side_module_names
1073        //             .retain(|_, handle| *handle != module_handle);
1074        //         // We already have an error we need to report, so ignore memory deallocation errors
1075        //         _ = guard
1076        //             .memory_allocator
1077        //             .deallocate(&memory, store, module.memory_base);
1078        //     }
1079        // }
1080
1081        trace!("Module load complete");
1082
1083        Ok(module_handle)
1084    }
1085
1086    fn finalize_link_operation(
1087        &self,
1088        // Take ownership of the guard and drop it ourselves to ensure no deadlock can happen
1089        mut group_state_guard: MutexGuard<'_, Option<InstanceGroupState>>,
1090        linker_state: &mut LinkerState,
1091        store: &mut impl AsStoreMut,
1092        link_state: InProgressLinkState,
1093    ) -> Result<(), LinkError> {
1094        let group_state = group_state_guard.as_mut().unwrap();
1095
1096        trace!(?link_state, "Finalizing link operation");
1097
1098        group_state.finalize_pending_globals(
1099            linker_state,
1100            store,
1101            &link_state.unresolved_globals,
1102        )?;
1103
1104        self.initialize_new_modules(group_state_guard, store, link_state)
1105    }
1106
1107    fn initialize_new_modules(
1108        &self,
1109        // Take ownership of the guard and drop it ourselves to ensure no deadlock can happen
1110        mut group_state_guard: MutexGuard<'_, Option<InstanceGroupState>>,
1111        store: &mut impl AsStoreMut,
1112        link_state: InProgressLinkState,
1113    ) -> Result<(), LinkError> {
1114        let group_state = group_state_guard.as_mut().unwrap();
1115
1116        let new_instances = link_state
1117            .new_modules
1118            .iter()
1119            .map(|m| group_state.side_instances[&m.handle].instance.clone())
1120            .collect::<Vec<_>>();
1121
1122        // The instance group must be unlocked for the next step, since modules may need to resolve
1123        // stub functions and that requires a lock on the instance group's state
1124        drop(group_state_guard);
1125
1126        // These functions are exported from PIE executables, and need to be run before calling
1127        // _initialize or _start. More info:
1128        // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md
1129        trace!("Calling data relocation functions");
1130        for instance in &new_instances {
1131            call_initialization_function::<()>(instance, store, "__wasm_apply_data_relocs")?;
1132            call_initialization_function::<()>(instance, store, "__wasm_apply_tls_relocs")?;
1133        }
1134
1135        trace!("Calling ctor functions");
1136        for instance in &new_instances {
1137            call_initialization_function::<()>(instance, store, "__wasm_call_ctors")?;
1138        }
1139
1140        Ok(())
1141    }
1142
1143    // TODO: Support RTLD_NEXT
1144    /// Resolves an export from the module corresponding to the given module handle.
1145    /// Only functions and globals can be resolved.
1146    ///
1147    /// If the symbol is a global, the returned value will be the absolute address of
1148    /// the data corresponding to that global within the shared linear memory.
1149    ///
1150    /// If it's a function, it'll be placed into the indirect function table,
1151    /// which creates a "function pointer" that can be used from WASM code.
1152    pub fn resolve_export(
1153        &self,
1154        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
1155        module_handle: Option<ModuleHandle>,
1156        symbol: &str,
1157    ) -> Result<ResolvedExport, ResolveError> {
1158        trace!(?module_handle, symbol, "Resolving symbol");
1159
1160        let resolution_key = SymbolResolutionKey::Requested {
1161            resolve_from: module_handle,
1162            name: symbol.to_string(),
1163        };
1164
1165        lock_instance_group_state!(guard, group_state, self, ResolveError::InstanceGroupIsDead);
1166
1167        if let Ok(linker_state) = self.shared.try_read_linker_state()
1168            && let Some(resolution) = linker_state.symbol_resolution_records.get(&resolution_key)
1169        {
1170            trace!(?resolution, "Already have a resolution for this symbol");
1171            match resolution {
1172                SymbolResolutionResult::FunctionPointer {
1173                    function_table_index: addr,
1174                    ..
1175                } => {
1176                    return Ok(ResolvedExport::Function {
1177                        func_ptr: *addr as u64,
1178                    });
1179                }
1180                SymbolResolutionResult::Memory(addr) => {
1181                    return Ok(ResolvedExport::Global { data_ptr: *addr });
1182                }
1183                SymbolResolutionResult::Tls {
1184                    resolved_from,
1185                    offset,
1186                } => {
1187                    let Some(tls_base) = group_state.tls_base(*resolved_from) else {
1188                        return Err(ResolveError::NoTlsBaseGlobalExport);
1189                    };
1190                    return Ok(ResolvedExport::Global {
1191                        data_ptr: tls_base + offset,
1192                    });
1193                }
1194                r => panic!(
1195                    "Internal error: unexpected symbol resolution \
1196                        {r:?} for requested symbol {symbol}"
1197                ),
1198            }
1199        }
1200
1201        let (topology_token, mut linker_state) = self
1202            .shared
1203            .write_linker_state_with_topology(group_state, ctx)?;
1204
1205        let mut store = ctx.as_store_mut();
1206
1207        trace!("Resolving export");
1208        let (export, resolved_from) =
1209            group_state.resolve_export(&linker_state, &mut store, module_handle, symbol, false)?;
1210
1211        trace!(?export, ?resolved_from, "Resolved export");
1212
1213        match export {
1214            PartiallyResolvedExport::Global(addr) => {
1215                linker_state
1216                    .symbol_resolution_records
1217                    .insert(resolution_key, SymbolResolutionResult::Memory(addr));
1218
1219                Ok(ResolvedExport::Global { data_ptr: addr })
1220            }
1221            PartiallyResolvedExport::Tls { offset, final_addr } => {
1222                linker_state.symbol_resolution_records.insert(
1223                    resolution_key,
1224                    SymbolResolutionResult::Tls {
1225                        resolved_from,
1226                        offset,
1227                    },
1228                );
1229
1230                Ok(ResolvedExport::Global {
1231                    data_ptr: final_addr,
1232                })
1233            }
1234            PartiallyResolvedExport::Function(func) => {
1235                let func_ptr = group_state
1236                    .append_to_function_table(&mut store, func.clone())
1237                    .map_err(ResolveError::TableAllocationError)?;
1238                trace!(
1239                    ?func_ptr,
1240                    table_size = group_state.indirect_function_table.size(&store),
1241                    "Placed resolved function into table"
1242                );
1243                linker_state.symbol_resolution_records.insert(
1244                    resolution_key,
1245                    SymbolResolutionResult::FunctionPointer {
1246                        resolved_from,
1247                        function_table_index: func_ptr,
1248                    },
1249                );
1250
1251                self.shared.synchronize_link_operation(
1252                    topology_token,
1253                    DlOperation::ResolveFunction {
1254                        name: symbol.to_string(),
1255                        resolved_from,
1256                        function_table_index: func_ptr,
1257                    },
1258                    linker_state,
1259                    group_state,
1260                    &ctx.data().process,
1261                    ctx.data().tid(),
1262                );
1263
1264                Ok(ResolvedExport::Function {
1265                    func_ptr: func_ptr as u64,
1266                })
1267            }
1268        }
1269    }
1270
1271    pub fn is_handle_valid(
1272        &self,
1273        handle: ModuleHandle,
1274        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
1275    ) -> Result<bool, LinkError> {
1276        // If we can get a read lock on the linker state, do it
1277        if let Ok(linker_state) = self.shared.try_read_linker_state() {
1278            return Ok(linker_state.side_modules.contains_key(&handle));
1279        }
1280
1281        // Otherwise, fall back to the path where we apply DL ops and acquire
1282        // a write lock afterwards
1283        lock_instance_group_state!(guard, group_state, self, LinkError::InstanceGroupIsDead);
1284        let linker_state = self.shared.write_linker_state(group_state, ctx)?;
1285        Ok(linker_state.side_modules.contains_key(&handle))
1286    }
1287}