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