Skip to main content

wasmer_compiler/
compiler.rs

1//! This module mainly outputs the `Compiler` trait that custom
2//! compilers will need to implement.
3
4use std::cmp::Reverse;
5use std::collections::HashMap;
6use std::fs::File;
7use std::path::{Path, PathBuf};
8use std::sync::Mutex;
9
10use crate::EH_FRAME_SECTION_NAME;
11use crate::misc::{CompiledFunctionExt, CompiledKind};
12use crate::object::get_object_for_target;
13use crate::progress::ProgressContext;
14use crate::types::function::Compilation;
15use crate::types::module::CompileModuleInfo;
16use crate::{
17    FunctionBodyData, ModuleTranslationState, WASMER_FUNCTION_OFFSETS_SECTION_NAME,
18    WASMER_TRAP_FUNCTION_OFFSETS_SECTION_NAME, translator::ModuleMiddleware,
19};
20use crossbeam_channel::unbounded;
21use enumset::EnumSet;
22use itertools::Itertools;
23use libwild::{
24    Args, FileSystem, FileType, InputFileData, Linker, OutputFileData, OutputOptions, error,
25};
26use object::write::{Relocation, StandardSegment, Symbol as ObjSymbol, SymbolSection};
27use object::{
28    RelocationEncoding, RelocationFlags, RelocationKind, SectionFlags, SectionKind, SymbolFlags,
29    SymbolKind, SymbolScope, elf,
30};
31use std::{boxed::Box, sync::Arc};
32use wasmer_types::{
33    CompilationProgressCallback, Features, FunctionIndex, LocalFunctionIndex,
34    entity::{EntityRef, PrimaryMap},
35    error::CompileError,
36    target::{CpuFeature, Target, UserCompilerOptimizations},
37};
38use wasmer_types::{FunctionType, SignatureIndex};
39#[cfg(feature = "translator")]
40use wasmparser::{Validator, WasmFeatures};
41
42/// A debugger command-file format for registering JIT-compiled modules.
43#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
44pub enum Debugger {
45    /// GDB command file.
46    #[strum(serialize = "GDB")]
47    Gdb,
48    /// LLDB command file.
49    #[strum(serialize = "LLDB")]
50    Lldb,
51}
52
53/// The compiler configuration options.
54pub trait CompilerConfig {
55    /// Enable the experimental artifact format.
56    fn experimental_artifact(&mut self, _enable: bool) {}
57
58    /// Enable Position Independent Code (PIC).
59    ///
60    /// This is required for shared object generation (Native Engine),
61    /// but will make the JIT Engine to fail, since PIC is not yet
62    /// supported in the JIT linking phase.
63    fn enable_pic(&mut self) {
64        // By default we do nothing, each backend will need to customize this
65        // in case they do something special for emitting PIC code.
66    }
67
68    /// Enable compiler IR verification.
69    ///
70    /// For compilers capable of doing so, this enables internal consistency
71    /// checking.
72    fn enable_verifier(&mut self) {
73        // By default we do nothing, each backend will need to customize this
74        // in case they create an IR that they can verify.
75    }
76
77    /// Enable generation of perfmaps to sample the JIT compiled frames.
78    fn enable_perfmap(&mut self) {
79        // By default we do nothing, each backend will need to customize this.
80    }
81
82    /// Enable generation of a debugger command file for JIT compiled frames.
83    fn enable_debugger(&mut self, _debugger: Debugger) {
84        // By default we do nothing, each backend will need to customize this.
85    }
86
87    /// For the LLVM compiler, we can use non-volatile memory operations which lead to a better performance
88    /// (but are not 100% SPEC compliant).
89    fn enable_non_volatile_memops(&mut self) {}
90
91    /// Enable run-time handling of potentially unaligned memory accesses.
92    ///
93    /// This feature is experimental and currently supports only Cranelift scalar types
94    /// and Singlepass on RISC-V for integral types.
95    fn enable_experimental_unaligned_memory_accesses(&mut self) {}
96
97    /// Enables treating eligible funcref tables as read-only so the backend can
98    /// place them in read-only data.
99    fn enable_readonly_funcref_table(&mut self) {}
100
101    /// Enable NaN canonicalization.
102    ///
103    /// NaN canonicalization is useful when trying to run WebAssembly
104    /// deterministically across different architectures.
105    fn canonicalize_nans(&mut self, _enable: bool) {
106        // By default we do nothing, each backend will need to customize this
107        // in case they create an IR that they can verify.
108    }
109
110    /// Gets the custom compiler config
111    fn compiler(self: Box<Self>) -> Box<dyn Compiler>;
112
113    /// Gets the default features for this compiler in the given target
114    fn default_features_for_target(&self, target: &Target) -> Features {
115        self.supported_features_for_target(target)
116    }
117
118    /// Gets the supported features for this compiler in the given target
119    fn supported_features_for_target(&self, _target: &Target) -> Features {
120        Features::default()
121    }
122
123    /// Pushes a middleware onto the back of the middleware chain.
124    fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>);
125}
126
127impl<T> From<T> for Box<dyn CompilerConfig + 'static>
128where
129    T: CompilerConfig + 'static,
130{
131    fn from(other: T) -> Self {
132        Box::new(other)
133    }
134}
135
136/// An implementation of a Compiler from parsed WebAssembly module to Compiled native code.
137pub trait Compiler: Send + std::fmt::Debug {
138    /// Returns a descriptive name for this compiler.
139    ///
140    /// Note that this is an API breaking change since 3.0
141    fn name(&self) -> &str;
142
143    /// Returns the deterministic id of this compiler. Same compilers with different
144    /// optimizations map to different deterministic IDs.
145    fn deterministic_id(&self) -> String;
146
147    /// Add suggested optimizations to this compiler.
148    ///
149    /// # Note
150    ///
151    /// Not every compiler supports every optimization. This function may fail (i.e. not set the
152    /// suggested optimizations) silently if the underlying compiler does not support one or
153    /// more optimizations.
154    fn with_opts(
155        &mut self,
156        suggested_compiler_opts: &UserCompilerOptimizations,
157    ) -> Result<(), CompileError> {
158        _ = suggested_compiler_opts;
159        Ok(())
160    }
161
162    /// Validates a module.
163    ///
164    /// It returns the a successful Result in case is valid, `CompileError` in case is not.
165    #[cfg(feature = "translator")]
166    fn validate_module(&self, features: &Features, data: &[u8]) -> Result<(), CompileError> {
167        let mut wasm_features = WasmFeatures::empty();
168        wasm_features.set(WasmFeatures::BULK_MEMORY, features.bulk_memory);
169        wasm_features.set(WasmFeatures::THREADS, features.threads);
170        wasm_features.set(WasmFeatures::REFERENCE_TYPES, features.reference_types);
171        wasm_features.set(WasmFeatures::MULTI_VALUE, features.multi_value);
172        wasm_features.set(WasmFeatures::SIMD, features.simd);
173        wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
174        wasm_features.set(WasmFeatures::MULTI_MEMORY, features.multi_memory);
175        wasm_features.set(WasmFeatures::MEMORY64, features.memory64);
176        wasm_features.set(WasmFeatures::EXCEPTIONS, features.exceptions);
177        wasm_features.set(WasmFeatures::EXTENDED_CONST, features.extended_const);
178        wasm_features.set(WasmFeatures::RELAXED_SIMD, features.relaxed_simd);
179        wasm_features.set(WasmFeatures::WIDE_ARITHMETIC, features.wide_arithmetic);
180        wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
181        wasm_features.set(WasmFeatures::MUTABLE_GLOBAL, true);
182        wasm_features.set(WasmFeatures::SATURATING_FLOAT_TO_INT, true);
183        wasm_features.set(WasmFeatures::FLOATS, true);
184        wasm_features.set(WasmFeatures::SIGN_EXTENSION, true);
185        wasm_features.set(WasmFeatures::GC_TYPES, true);
186
187        let mut validator = Validator::new_with_features(wasm_features);
188        validator
189            .validate_all(data)
190            .map_err(|e| CompileError::Validate(format!("{e}")))?;
191        Ok(())
192    }
193
194    /// Compiles a parsed module.
195    ///
196    /// It returns the [`Compilation`] or a [`CompileError`].
197    fn compile_module(
198        &self,
199        target: &Target,
200        module: &CompileModuleInfo,
201        compile_info_blob: &[u8],
202        module_translation: &ModuleTranslationState,
203        // The list of function bodies
204        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
205        progress_callback: Option<&CompilationProgressCallback>,
206    ) -> Result<Compilation, CompileError>;
207
208    /// Get the middlewares for this compiler
209    fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>];
210
211    /// Get whether translation-time readonly funcref table analysis should run.
212    fn enable_readonly_funcref_table(&self) -> bool {
213        false
214    }
215
216    /// Get the CpuFeatures used by the compiler
217    fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
218        *cpu_features
219    }
220
221    /// Get whether `perfmap` is enabled or not.
222    fn get_perfmap_enabled(&self) -> bool {
223        false
224    }
225
226    /// Get the enabled debugger command-file format, if any.
227    fn get_debugger(&self) -> Option<Debugger> {
228        None
229    }
230}
231
232/// A bucket containing a group of functions and their total size, used to balance compilation units for parallel compilation.
233pub struct FunctionBucket<'a> {
234    functions: Vec<(LocalFunctionIndex, &'a FunctionBodyData<'a>)>,
235    /// IR size of the bucket (in bytes).
236    pub size: usize,
237}
238
239impl<'a> FunctionBucket<'a> {
240    /// Creates a new, empty `FunctionBucket`.
241    pub fn new() -> Self {
242        Self {
243            functions: Vec::new(),
244            size: 0,
245        }
246    }
247}
248
249/// Build buckets sized by function length to keep compilation units balanced for parallel compilation.
250pub fn build_function_buckets<'a>(
251    function_body_inputs: &'a PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
252    bucket_threshold_size: u64,
253) -> Vec<FunctionBucket<'a>> {
254    let mut function_bodies = function_body_inputs
255        .iter()
256        .sorted_by_key(|(id, body)| Reverse((body.data.len(), id.as_u32())))
257        .collect_vec();
258
259    let mut buckets = Vec::new();
260
261    while !function_bodies.is_empty() {
262        let mut next_function_body = Vec::with_capacity(function_bodies.len());
263        let mut bucket = FunctionBucket::new();
264
265        for (fn_index, fn_body) in function_bodies.into_iter() {
266            if bucket.size + fn_body.data.len() <= bucket_threshold_size as usize
267                // Huge functions must fit into a bucket!
268                || bucket.size == 0
269            {
270                bucket.size += fn_body.data.len();
271                bucket.functions.push((fn_index, fn_body));
272            } else {
273                next_function_body.push((fn_index, fn_body));
274            }
275        }
276
277        function_bodies = next_function_body;
278        buckets.push(bucket);
279    }
280
281    buckets
282}
283
284/// Represents a function that has been compiled by the backend compiler.
285pub trait CompiledFunction {}
286
287/// Translates a function from its input representation to a compiled form.
288pub trait FuncTranslator {}
289
290/// Compile function buckets largest-first via the channel (instead of Rayon's par_iter).
291#[allow(clippy::too_many_arguments)]
292pub fn translate_function_buckets<'a, C, T, F, G>(
293    pool: &rayon::ThreadPool,
294    func_translator_builder: F,
295    translate_fn: G,
296    progress: Option<ProgressContext>,
297    buckets: &[FunctionBucket<'a>],
298) -> Result<Vec<C>, CompileError>
299where
300    T: FuncTranslator,
301    C: CompiledFunction + Send + Sync,
302    F: Fn() -> T + Send + Sync + Copy,
303    G: Fn(&mut T, &LocalFunctionIndex, &FunctionBodyData) -> Result<C, CompileError>
304        + Send
305        + Sync
306        + Copy,
307{
308    let progress = progress.as_ref();
309
310    let functions = pool.install(|| {
311        let (bucket_tx, bucket_rx) = unbounded::<&FunctionBucket<'a>>();
312        for bucket in buckets {
313            bucket_tx.send(bucket).map_err(|e| {
314                CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
315            })?;
316        }
317        drop(bucket_tx);
318
319        let (result_tx, result_rx) =
320            unbounded::<Result<Vec<(LocalFunctionIndex, C)>, CompileError>>();
321
322        pool.scope(|s| {
323            let worker_count = pool.current_num_threads().max(1);
324            for _ in 0..worker_count {
325                let bucket_rx = bucket_rx.clone();
326                let result_tx = result_tx.clone();
327                s.spawn(move |_| {
328                    let mut func_translator = func_translator_builder();
329
330                    while let Ok(bucket) = bucket_rx.recv() {
331                        let bucket_result = (|| {
332                            let mut translated_functions = Vec::new();
333                            for (i, input) in bucket.functions.iter() {
334                                let translated = translate_fn(&mut func_translator, i, input)?;
335                                if let Some(progress) = progress {
336                                    progress.notify_steps(input.data.len() as u64)?;
337                                }
338                                translated_functions.push((*i, translated));
339                            }
340                            Ok(translated_functions)
341                        })();
342
343                        if result_tx.send(bucket_result).is_err() {
344                            break;
345                        }
346                    }
347                });
348            }
349        });
350
351        drop(result_tx);
352        let mut functions = Vec::with_capacity(buckets.iter().map(|b| b.functions.len()).sum());
353        for _ in 0..buckets.len() {
354            match result_rx.recv().map_err(|e| {
355                CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
356            })? {
357                Ok(bucket_functions) => functions.extend(bucket_functions),
358                Err(err) => return Err(err),
359            }
360        }
361        Ok(functions)
362    })?;
363
364    Ok(functions
365        .into_iter()
366        .sorted_by_key(|x| x.0)
367        .map(|(_, body)| body)
368        .collect_vec())
369}
370
371/// Byte size threshold for a function that is considered large.
372pub const WASM_LARGE_FUNCTION_THRESHOLD: u64 = 100_000;
373
374/// Estimated byte size of a trampoline (used for progress bar reporting).
375pub const WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE: u64 = 1_000;
376
377/// Holds the sets of compiled object buffers produced during compilation.
378///
379/// Counts of each category are derived from the slice lengths.
380pub struct CompiledObjects {
381    /// Objects for local (user-defined) functions.
382    pub object_files: Vec<Vec<u8>>,
383    /// Objects for imported function call trampolines.
384    pub import_trampoline_object_files: Vec<Vec<u8>>,
385    /// Objects for static trampolines.
386    pub trampoline_object_files: Vec<Vec<u8>>,
387    /// Objects for dynamic trampolines.
388    pub dynamic_trampoline_object_files: Vec<Vec<u8>>,
389}
390
391fn emit_wasmer_meta_object(
392    target: &Target,
393    compile_info_blob: &[u8],
394    compiled_objects: &CompiledObjects,
395) -> Result<Vec<u8>, String> {
396    let mut obj = get_object_for_target(target.triple())
397        .map_err(|e| format!("failed to create Wasmer meta object: {e}"))?;
398
399    let section_id = obj.add_section(
400        obj.segment_name(StandardSegment::Data).to_vec(),
401        crate::WASMER_MODULE_INFO_SECTION_NAME.to_vec(),
402        SectionKind::Other,
403    );
404    obj.append_section_data(section_id, compile_info_blob, 8);
405    obj.section_mut(section_id).flags = SectionFlags::Elf {
406        sh_type: elf::SHT_PROGBITS,
407        sh_flags: elf::SHF_GNU_RETAIN,
408    };
409
410    // Emit zero sentinel for the .eh_frame section.
411    let section_id = obj.add_section(
412        obj.segment_name(StandardSegment::Debug).to_vec(),
413        EH_FRAME_SECTION_NAME.to_vec(),
414        SectionKind::Debug,
415    );
416    obj.append_section_data(section_id, &0u64.to_ne_bytes(), 4);
417
418    // Emit offsets of the functions
419    let section_id = obj.add_section(
420        obj.segment_name(StandardSegment::Data).to_vec(),
421        WASMER_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
422        SectionKind::Other,
423    );
424    obj.section_mut(section_id).flags = SectionFlags::Elf {
425        sh_type: elf::SHT_PROGBITS,
426        sh_flags: elf::SHF_GNU_RETAIN,
427    };
428    let pointer_size = target
429        .triple()
430        .pointer_width()
431        .map_err(|_| "unknown pointer width".to_string())?
432        .bytes() as u64;
433    let pointer_bits = (pointer_size * 8) as u8;
434    let zero_pointer = vec![0; pointer_size as usize];
435
436    let function_offset_names = (0..compiled_objects.object_files.len())
437        .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).linkage_name())
438        .chain(
439            (0..compiled_objects.trampoline_object_files.len()).map(|i| {
440                CompiledKind::FunctionCallTrampoline(
441                    SignatureIndex::new(i),
442                    // Unused by the linkage_name.
443                    FunctionType::new([], []),
444                )
445                .linkage_name()
446            }),
447        )
448        .chain(
449            (0..compiled_objects.dynamic_trampoline_object_files.len()).map(|i| {
450                CompiledKind::DynamicFunctionTrampoline(
451                    FunctionIndex::new(i),
452                    // Unused by the linkage_name.
453                    FunctionType::new([], []),
454                )
455                .linkage_name()
456            }),
457        );
458    for function_name in function_offset_names {
459        let offset = obj.append_section_data(section_id, &zero_pointer, pointer_size);
460        let symbol_id = obj.add_symbol(ObjSymbol {
461            name: function_name.to_owned().into(),
462            value: 0,
463            size: 0,
464            kind: SymbolKind::Text,
465            scope: SymbolScope::Unknown,
466            weak: false,
467            section: SymbolSection::Undefined,
468            flags: SymbolFlags::None,
469        });
470        obj.add_relocation(
471            section_id,
472            Relocation {
473                offset,
474                flags: RelocationFlags::Generic {
475                    kind: RelocationKind::Absolute,
476                    encoding: RelocationEncoding::Generic,
477                    size: pointer_bits,
478                },
479                symbol: symbol_id,
480                addend: 0,
481            },
482        )
483        .map_err(|e| {
484            format!("failed to add function offset relocation for {function_name}: {e}")
485        })?;
486    }
487
488    let trap_fn_offsets_section_id = obj.add_section(
489        obj.segment_name(StandardSegment::Data).to_vec(),
490        WASMER_TRAP_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
491        SectionKind::Other,
492    );
493    obj.section_mut(trap_fn_offsets_section_id).flags = SectionFlags::Elf {
494        sh_type: elf::SHT_PROGBITS,
495        sh_flags: elf::SHF_GNU_RETAIN,
496    };
497    for traps_name in (0..compiled_objects.object_files.len())
498        .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).traps_name())
499    {
500        let offset =
501            obj.append_section_data(trap_fn_offsets_section_id, &zero_pointer, pointer_size);
502        let symbol_id = obj.add_symbol(ObjSymbol {
503            name: traps_name.as_bytes().into(),
504            value: 0,
505            size: 0,
506            kind: SymbolKind::Data,
507            scope: SymbolScope::Linkage,
508            weak: true,
509            section: SymbolSection::Undefined,
510            flags: SymbolFlags::None,
511        });
512        obj.add_relocation(
513            trap_fn_offsets_section_id,
514            Relocation {
515                offset,
516                flags: RelocationFlags::Generic {
517                    kind: RelocationKind::Absolute,
518                    encoding: RelocationEncoding::Generic,
519                    size: pointer_bits,
520                },
521                symbol: symbol_id,
522                addend: 0,
523            },
524        )
525        .map_err(|e| {
526            format!("failed to add function trap offset relocation for {traps_name}: {e}")
527        })?;
528    }
529
530    obj.write()
531        .map_err(|e| format!("failed to serialize Wasmer meta object: {e}"))
532}
533
534#[derive(Clone, Default)]
535struct InMemoryFileSystem {
536    files: Arc<Mutex<HashMap<PathBuf, Arc<Vec<u8>>>>>,
537}
538
539#[derive(Debug)]
540struct InMemoryInput(Arc<Vec<u8>>);
541
542impl InputFileData for InMemoryInput {
543    fn bytes(&self) -> &[u8] {
544        &self.0
545    }
546}
547
548struct InMemoryOutput {
549    path: PathBuf,
550    bytes: Vec<u8>,
551    files: Arc<Mutex<HashMap<PathBuf, Arc<Vec<u8>>>>>,
552}
553
554impl OutputFileData for InMemoryOutput {
555    fn bytes(&self) -> &[u8] {
556        &self.bytes
557    }
558    fn bytes_mut(&mut self) -> &mut [u8] {
559        &mut self.bytes
560    }
561    fn finish(mut self) -> error::Result {
562        self.files
563            .lock()
564            .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
565            .insert(self.path, Arc::new(std::mem::take(&mut self.bytes)));
566        Ok(())
567    }
568}
569
570impl FileSystem for InMemoryFileSystem {
571    type Input = InMemoryInput;
572    type Output = InMemoryOutput;
573
574    fn open_input(&self, path: &Path, _: bool) -> error::Result<(Self::Input, Option<Arc<File>>)> {
575        let bytes = self
576            .files
577            .lock()
578            .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
579            .get(path)
580            .map(Arc::clone)
581            .ok_or_else(|| error!("No such in-memory file: {}", path.display()))?;
582        Ok((InMemoryInput(bytes), None))
583    }
584
585    fn file_type(&self, path: &Path) -> error::Result<FileType> {
586        self.files
587            .lock()
588            .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
589            .contains_key(path)
590            .then_some(FileType::File)
591            .ok_or_else(|| error!("no such in-memory file"))
592    }
593
594    fn canonicalize(&self, path: &Path) -> error::Result<PathBuf> {
595        Ok(path.to_path_buf())
596    }
597    fn remove_file(&self, path: &Path) -> error::Result<()> {
598        self.files
599            .lock()
600            .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
601            .remove(path)
602            .map(|_| ())
603            .ok_or_else(|| error!("no such in-memory file"))
604    }
605    fn rename_file(&self, path: &Path, new_path: &Path) -> error::Result<()> {
606        let mut files = self
607            .files
608            .lock()
609            .map_err(|e| format!("cannot lock in-memory FS: {e}"))?;
610        let bytes = files
611            .remove(path)
612            .ok_or_else(|| error!("no such in-memory file"))?;
613        files.insert(new_path.to_path_buf(), bytes);
614        Ok(())
615    }
616    fn create_output(
617        &self,
618        path: Arc<Path>,
619        options: OutputOptions,
620    ) -> error::Result<Self::Output> {
621        let size = usize::try_from(options.size).map_err(|_| error!("output is too large"))?;
622        Ok(InMemoryOutput {
623            path: path.to_path_buf(),
624            bytes: vec![0; size],
625            files: Arc::clone(&self.files),
626        })
627    }
628    fn write_auxiliary(&self, path: &Path, bytes: &[u8]) -> error::Result {
629        self.files
630            .lock()
631            .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
632            .insert(path.to_path_buf(), Arc::new(bytes.to_vec()));
633        Ok(())
634    }
635}
636
637const WASMER_IMAGE_FILENAME: &str = "wasmer-image.so";
638const WASMER_META_FILENAME: &str = "__wasmer_meta.o";
639
640/// Emits Wasmer metadata sections and links backend-generated object buffers into a shared object.
641pub fn emit_metadata_and_link(
642    pool: &rayon::ThreadPool,
643    target: &Target,
644    compile_info_blob: &[u8],
645    compiled_objects: CompiledObjects,
646    mut debug_dir: Option<PathBuf>,
647    module_hash: Option<String>,
648) -> Result<Vec<u8>, CompileError> {
649    pool.install(|| {
650        let meta_object = emit_wasmer_meta_object(target, compile_info_blob, &compiled_objects)
651            .map_err(CompileError::Codegen)?;
652        let CompiledObjects {
653            object_files,
654            import_trampoline_object_files,
655            trampoline_object_files,
656            dynamic_trampoline_object_files,
657        } = compiled_objects;
658        let fs = InMemoryFileSystem::default();
659        let mut link_args = vec![
660            "ld".to_string(),
661            // Allow resolution of the public symbols directly without PLT entries!
662            "-Bsymbolic".to_string(),
663            "-shared".to_string(),
664            "-z".to_string(),
665            "now".to_string(),
666            "-z".to_string(),
667            "relro".to_string(),
668            "-o".to_string(),
669            WASMER_IMAGE_FILENAME.to_string(),
670        ];
671
672        {
673            let mut files = fs
674                .files
675                .lock()
676                .map_err(|e| CompileError::Codegen(format!("cannot lock in-memory FS: {e}")))?;
677            for (index, object) in object_files
678                .into_iter()
679                .chain(import_trampoline_object_files)
680                .chain(trampoline_object_files)
681                .chain(dynamic_trampoline_object_files)
682                .enumerate()
683            {
684                let path = PathBuf::from(format!("object-{index}.o"));
685                files.insert(path.clone(), Arc::new(object));
686                link_args.push(path.display().to_string());
687            }
688            files.insert(PathBuf::from(WASMER_META_FILENAME), Arc::new(meta_object));
689        }
690        // Keep the synthetic `.eh_frame` terminator after the real CIE/FDE
691        // records. Linkers concatenate input sections in object order, and a
692        // leading terminator makes frame registration see an empty table.
693        link_args.push(WASMER_META_FILENAME.to_string());
694
695        let mut wild_args = Args::new(|| link_args.iter().map(String::as_str)).map_err(|e| {
696            CompileError::Codegen(format!("failed to initialize Wild linker: {e:?}"))
697        })?;
698        wild_args
699            .parse(|| link_args.iter().map(String::as_str))
700            .map_err(|e| {
701                CompileError::Codegen(format!("failed to parse Wild linker args: {e:?}"))
702            })?;
703        Linker::with_file_system(fs.clone())
704            .run(&wild_args)
705            .map_err(|e| CompileError::Codegen(format!("Wild linker failed: {e:?}")))?;
706
707        let image = fs
708            .files
709            .lock()
710            .map_err(|e| CompileError::Codegen(format!("cannot lock in-memory FS: {e}")))?
711            .remove(Path::new(WASMER_IMAGE_FILENAME))
712            .ok_or_else(|| CompileError::Codegen("Wild linker did not produce an output".into()))?;
713        let image = Arc::try_unwrap(image).map_err(|_| {
714            CompileError::Codegen("Wild linker retained a reference to the output buffer".into())
715        })?;
716
717        // If compiler-debug-dir is set, copy the final linked .so image
718        // into the module_hash subfolder.
719        if let Some(debug_dir) = debug_dir.as_mut() {
720            if let Some(ref hash) = module_hash {
721                debug_dir.push(hash);
722            }
723            std::fs::create_dir_all(&debug_dir).ok();
724            debug_dir.push(WASMER_IMAGE_FILENAME);
725            let _ = std::fs::write(debug_dir, &image);
726        }
727        Ok(image)
728    })
729}