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;
5
6use crate::progress::ProgressContext;
7use crate::types::{module::CompileModuleInfo, symbols::SymbolRegistry};
8use crate::{
9    FunctionBodyData, ModuleTranslationState,
10    lib::std::{boxed::Box, sync::Arc},
11    translator::ModuleMiddleware,
12    types::function::Compilation,
13};
14use crossbeam_channel::unbounded;
15use enumset::EnumSet;
16use itertools::Itertools;
17use wasmer_types::{
18    CompilationProgressCallback, Features, LocalFunctionIndex,
19    entity::PrimaryMap,
20    error::CompileError,
21    target::{CpuFeature, Target, UserCompilerOptimizations},
22};
23#[cfg(feature = "translator")]
24use wasmparser::{Validator, WasmFeatures};
25
26/// The compiler configuration options.
27pub trait CompilerConfig {
28    /// Enable Position Independent Code (PIC).
29    ///
30    /// This is required for shared object generation (Native Engine),
31    /// but will make the JIT Engine to fail, since PIC is not yet
32    /// supported in the JIT linking phase.
33    fn enable_pic(&mut self) {
34        // By default we do nothing, each backend will need to customize this
35        // in case they do something special for emitting PIC code.
36    }
37
38    /// Enable compiler IR verification.
39    ///
40    /// For compilers capable of doing so, this enables internal consistency
41    /// checking.
42    fn enable_verifier(&mut self) {
43        // By default we do nothing, each backend will need to customize this
44        // in case they create an IR that they can verify.
45    }
46
47    /// Enable generation of perfmaps to sample the JIT compiled frames.
48    fn enable_perfmap(&mut self) {
49        // By default we do nothing, each backend will need to customize this
50        // in case they create an IR that they can verify.
51    }
52
53    /// Enable NaN canonicalization.
54    ///
55    /// NaN canonicalization is useful when trying to run WebAssembly
56    /// deterministically across different architectures.
57    fn canonicalize_nans(&mut self, _enable: bool) {
58        // By default we do nothing, each backend will need to customize this
59        // in case they create an IR that they can verify.
60    }
61
62    /// Gets the custom compiler config
63    fn compiler(self: Box<Self>) -> Box<dyn Compiler>;
64
65    /// Gets the default features for this compiler in the given target
66    fn default_features_for_target(&self, target: &Target) -> Features {
67        self.supported_features_for_target(target)
68    }
69
70    /// Gets the supported features for this compiler in the given target
71    fn supported_features_for_target(&self, _target: &Target) -> Features {
72        Features::default()
73    }
74
75    /// Pushes a middleware onto the back of the middleware chain.
76    fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>);
77}
78
79impl<T> From<T> for Box<dyn CompilerConfig + 'static>
80where
81    T: CompilerConfig + 'static,
82{
83    fn from(other: T) -> Self {
84        Box::new(other)
85    }
86}
87
88/// An implementation of a Compiler from parsed WebAssembly module to Compiled native code.
89pub trait Compiler: Send + std::fmt::Debug {
90    /// Returns a descriptive name for this compiler.
91    ///
92    /// Note that this is an API breaking change since 3.0
93    fn name(&self) -> &str;
94
95    /// Returns the deterministic id of this compiler. Same compilers with different
96    /// optimizations map to different deterministic IDs.
97    fn deterministic_id(&self) -> String;
98
99    /// Add suggested optimizations to this compiler.
100    ///
101    /// # Note
102    ///
103    /// Not every compiler supports every optimization. This function may fail (i.e. not set the
104    /// suggested optimizations) silently if the underlying compiler does not support one or
105    /// more optimizations.
106    fn with_opts(
107        &mut self,
108        suggested_compiler_opts: &UserCompilerOptimizations,
109    ) -> Result<(), CompileError> {
110        _ = suggested_compiler_opts;
111        Ok(())
112    }
113
114    /// Validates a module.
115    ///
116    /// It returns the a succesful Result in case is valid, `CompileError` in case is not.
117    #[cfg(feature = "translator")]
118    fn validate_module(&self, features: &Features, data: &[u8]) -> Result<(), CompileError> {
119        let mut wasm_features = WasmFeatures::default();
120        wasm_features.set(WasmFeatures::BULK_MEMORY, features.bulk_memory);
121        wasm_features.set(WasmFeatures::THREADS, features.threads);
122        wasm_features.set(WasmFeatures::REFERENCE_TYPES, features.reference_types);
123        wasm_features.set(WasmFeatures::MULTI_VALUE, features.multi_value);
124        wasm_features.set(WasmFeatures::SIMD, features.simd);
125        wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
126        wasm_features.set(WasmFeatures::MULTI_MEMORY, features.multi_memory);
127        wasm_features.set(WasmFeatures::MEMORY64, features.memory64);
128        wasm_features.set(WasmFeatures::EXCEPTIONS, features.exceptions);
129        wasm_features.set(WasmFeatures::EXTENDED_CONST, features.extended_const);
130        wasm_features.set(WasmFeatures::RELAXED_SIMD, features.relaxed_simd);
131        wasm_features.set(WasmFeatures::MUTABLE_GLOBAL, true);
132        wasm_features.set(WasmFeatures::SATURATING_FLOAT_TO_INT, true);
133        wasm_features.set(WasmFeatures::FLOATS, true);
134        wasm_features.set(WasmFeatures::SIGN_EXTENSION, true);
135        wasm_features.set(WasmFeatures::GC_TYPES, true);
136
137        // Not supported
138        wasm_features.set(WasmFeatures::COMPONENT_MODEL, false);
139        wasm_features.set(WasmFeatures::FUNCTION_REFERENCES, false);
140        wasm_features.set(WasmFeatures::MEMORY_CONTROL, false);
141        wasm_features.set(WasmFeatures::GC, false);
142        wasm_features.set(WasmFeatures::CM_VALUES, false);
143        wasm_features.set(WasmFeatures::CM_NESTED_NAMES, false);
144
145        let mut validator = Validator::new_with_features(wasm_features);
146        validator
147            .validate_all(data)
148            .map_err(|e| CompileError::Validate(format!("{e}")))?;
149        Ok(())
150    }
151
152    /// Compiles a parsed module.
153    ///
154    /// It returns the [`Compilation`] or a [`CompileError`].
155    fn compile_module(
156        &self,
157        target: &Target,
158        module: &CompileModuleInfo,
159        module_translation: &ModuleTranslationState,
160        // The list of function bodies
161        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
162        progress_callback: Option<&CompilationProgressCallback>,
163    ) -> Result<Compilation, CompileError>;
164
165    /// Compiles a module into a native object file.
166    ///
167    /// It returns the bytes as a `&[u8]` or a [`CompileError`].
168    fn experimental_native_compile_module(
169        &self,
170        _target: &Target,
171        _module: &CompileModuleInfo,
172        _module_translation: &ModuleTranslationState,
173        // The list of function bodies
174        _function_body_inputs: &PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
175        _symbol_registry: &dyn SymbolRegistry,
176        // The metadata to inject into the wasmer_metadata section of the object file.
177        _wasmer_metadata: &[u8],
178    ) -> Option<Result<Vec<u8>, CompileError>> {
179        None
180    }
181
182    /// Get the middlewares for this compiler
183    fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>];
184
185    /// Get the CpuFeatues used by the compiler
186    fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
187        *cpu_features
188    }
189
190    /// Get whether `perfmap` is enabled or not.
191    fn get_perfmap_enabled(&self) -> bool {
192        false
193    }
194}
195
196/// A bucket containing a group of functions and their total size, used to balance compilation units for parallel compilation.
197pub struct FunctionBucket<'a> {
198    functions: Vec<(LocalFunctionIndex, &'a FunctionBodyData<'a>)>,
199    /// IR size of the bucket (in bytes).
200    pub size: usize,
201}
202
203impl<'a> FunctionBucket<'a> {
204    /// Creates a new, empty `FunctionBucket`.
205    pub fn new() -> Self {
206        Self {
207            functions: Vec::new(),
208            size: 0,
209        }
210    }
211}
212
213/// Build buckets sized by function length to keep compilation units balanced for parallel compilation.
214pub fn build_function_buckets<'a>(
215    function_body_inputs: &'a PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
216    bucket_threshold_size: u64,
217) -> Vec<FunctionBucket<'a>> {
218    let mut function_bodies = function_body_inputs
219        .iter()
220        .sorted_by_key(|(id, body)| Reverse((body.data.len(), id.as_u32())))
221        .collect_vec();
222
223    let mut buckets = Vec::new();
224
225    while !function_bodies.is_empty() {
226        let mut next_function_body = Vec::with_capacity(function_bodies.len());
227        let mut bucket = FunctionBucket::new();
228
229        for (fn_index, fn_body) in function_bodies.into_iter() {
230            if bucket.size + fn_body.data.len() <= bucket_threshold_size as usize
231                // Huge functions must fit into a bucket!
232                || bucket.size == 0
233            {
234                bucket.size += fn_body.data.len();
235                bucket.functions.push((fn_index, fn_body));
236            } else {
237                next_function_body.push((fn_index, fn_body));
238            }
239        }
240
241        function_bodies = next_function_body;
242        buckets.push(bucket);
243    }
244
245    buckets
246}
247
248/// Represents a function that has been compiled by the backend compiler.
249pub trait CompiledFunction {}
250
251/// Translates a function from its input representation to a compiled form.
252pub trait FuncTranslator {}
253
254/// Compile function buckets largest-first via the channel (instead of Rayon's par_iter).
255#[allow(clippy::too_many_arguments)]
256pub fn translate_function_buckets<'a, C, T, F, G>(
257    pool: &rayon::ThreadPool,
258    func_translator_builder: F,
259    translate_fn: G,
260    progress: Option<ProgressContext>,
261    buckets: &[FunctionBucket<'a>],
262) -> Result<Vec<C>, CompileError>
263where
264    T: FuncTranslator,
265    C: CompiledFunction + Send + Sync,
266    F: Fn() -> T + Send + Sync + Copy,
267    G: Fn(&mut T, &LocalFunctionIndex, &FunctionBodyData) -> Result<C, CompileError>
268        + Send
269        + Sync
270        + Copy,
271{
272    let progress = progress.as_ref();
273
274    let functions = pool.install(|| {
275        let (bucket_tx, bucket_rx) = unbounded::<&FunctionBucket<'a>>();
276        for bucket in buckets {
277            bucket_tx.send(bucket).map_err(|e| {
278                CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
279            })?;
280        }
281        drop(bucket_tx);
282
283        let (result_tx, result_rx) =
284            unbounded::<Result<Vec<(LocalFunctionIndex, C)>, CompileError>>();
285
286        pool.scope(|s| {
287            let worker_count = pool.current_num_threads().max(1);
288            for _ in 0..worker_count {
289                let bucket_rx = bucket_rx.clone();
290                let result_tx = result_tx.clone();
291                s.spawn(move |_| {
292                    let mut func_translator = func_translator_builder();
293
294                    while let Ok(bucket) = bucket_rx.recv() {
295                        let bucket_result = (|| {
296                            let mut translated_functions = Vec::new();
297                            for (i, input) in bucket.functions.iter() {
298                                let translated = translate_fn(&mut func_translator, i, input)?;
299                                if let Some(progress) = progress {
300                                    progress.notify_steps(input.data.len() as u64)?;
301                                }
302                                translated_functions.push((*i, translated));
303                            }
304                            Ok(translated_functions)
305                        })();
306
307                        if result_tx.send(bucket_result).is_err() {
308                            break;
309                        }
310                    }
311                });
312            }
313        });
314
315        drop(result_tx);
316        let mut functions = Vec::with_capacity(buckets.iter().map(|b| b.functions.len()).sum());
317        for _ in 0..buckets.len() {
318            match result_rx.recv().map_err(|e| {
319                CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
320            })? {
321                Ok(bucket_functions) => functions.extend(bucket_functions),
322                Err(err) => return Err(err),
323            }
324        }
325        Ok(functions)
326    })?;
327
328    Ok(functions
329        .into_iter()
330        .sorted_by_key(|x| x.0)
331        .map(|(_, body)| body)
332        .collect_vec())
333}
334
335/// Byte size threshold for a function that is considered large.
336pub const WASM_LARGE_FUNCTION_THRESHOLD: u64 = 100_000;
337
338/// Estimated byte size of a trampoline (used for progress bar reporting).
339pub const WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE: u64 = 1_000;