wasmer_compiler_cli/
store.rs

1//! Common module with common used structures across different
2//! commands.
3
4use crate::common::WasmFeatures;
5use anyhow::Result;
6use clap::Parser;
7#[cfg(doc)]
8use std::path::PathBuf;
9#[allow(unused_imports)]
10use std::sync::Arc;
11use wasmer_compiler::{CompilerConfig, EngineBuilder, Features};
12#[cfg(doc)]
13use wasmer_types::Type;
14use wasmer_types::target::{PointerWidth, Target};
15use wasmer_types::{MemoryStyle, MemoryType, Pages, TableStyle, TableType};
16
17/// Minimul Subset of Tunable parameters for WebAssembly compilation.
18#[derive(Clone)]
19pub struct SubsetTunables {
20    /// For static heaps, the size in wasm pages of the heap protected by bounds checking.
21    pub static_memory_bound: Pages,
22
23    /// The size in bytes of the offset guard for static heaps.
24    pub static_memory_offset_guard_size: u64,
25
26    /// The size in bytes of the offset guard for dynamic heaps.
27    pub dynamic_memory_offset_guard_size: u64,
28}
29
30impl SubsetTunables {
31    /// Get the `BaseTunables` for a specific Target
32    pub fn for_target(target: &Target) -> Self {
33        let triple = target.triple();
34        let pointer_width: PointerWidth = triple.pointer_width().unwrap();
35        let (static_memory_bound, static_memory_offset_guard_size): (Pages, u64) =
36            match pointer_width {
37                PointerWidth::U16 => (0x400.into(), 0x1000),
38                PointerWidth::U32 => (0x4000.into(), 0x1_0000),
39                // Static Memory Bound:
40                //   Allocating 4 GiB of address space let us avoid the
41                //   need for explicit bounds checks.
42                // Static Memory Guard size:
43                //   Allocating 2 GiB of address space lets us translate wasm
44                //   offsets into x86 offsets as aggressively as we can.
45                PointerWidth::U64 => (0x1_0000.into(), 0x8000_0000),
46            };
47
48        // Allocate a small guard to optimize common cases but without
49        // wasting too much memory.
50        // The Windows memory manager seems more laxed than the other ones
51        // And a guard of just 1 page may not be enough is some borderline cases
52        // So using 2 pages for guard on this platform
53        #[cfg(target_os = "windows")]
54        let dynamic_memory_offset_guard_size: u64 = 0x2_0000;
55        #[cfg(not(target_os = "windows"))]
56        let dynamic_memory_offset_guard_size: u64 = 0x1_0000;
57
58        Self {
59            static_memory_bound,
60            static_memory_offset_guard_size,
61            dynamic_memory_offset_guard_size,
62        }
63    }
64    /// Get a `MemoryStyle` for the provided `MemoryType`
65    pub fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
66        // A heap with a maximum that doesn't exceed the static memory bound specified by the
67        // tunables make it static.
68        //
69        // If the module doesn't declare an explicit maximum treat it as 4GiB.
70        let maximum = memory.maximum.unwrap_or_else(Pages::max_value);
71        if maximum <= self.static_memory_bound {
72            MemoryStyle::Static {
73                // Bound can be larger than the maximum for performance reasons
74                bound: self.static_memory_bound,
75                offset_guard_size: self.static_memory_offset_guard_size,
76            }
77        } else {
78            MemoryStyle::Dynamic {
79                offset_guard_size: self.dynamic_memory_offset_guard_size,
80            }
81        }
82    }
83
84    /// Get a [`TableStyle`] for the provided [`TableType`].
85    pub fn table_style(&self, _table: &TableType) -> TableStyle {
86        TableStyle::CallerChecksSignature
87    }
88}
89
90#[derive(Debug, Clone, Parser, Default)]
91/// The compiler and engine options
92pub struct StoreOptions {
93    #[clap(flatten)]
94    compiler: CompilerOptions,
95}
96
97#[derive(Debug, Clone, Parser, Default)]
98/// The compiler options
99pub struct CompilerOptions {
100    /// Use Singlepass compiler.
101    #[clap(long, conflicts_with_all = &["cranelift", "llvm"])]
102    singlepass: bool,
103
104    /// Use Cranelift compiler.
105    #[clap(long, conflicts_with_all = &["singlepass", "llvm"])]
106    cranelift: bool,
107
108    /// Use LLVM compiler.
109    #[clap(long, conflicts_with_all = &["singlepass", "cranelift"])]
110    llvm: bool,
111
112    /// Enable compiler internal verification.
113    #[allow(unused)]
114    #[clap(long)]
115    #[allow(dead_code)]
116    enable_verifier: bool,
117
118    /// LLVM debug directory, where IR and object files will be written to.
119    #[allow(unused)]
120    #[cfg(feature = "llvm")]
121    #[cfg_attr(feature = "llvm", clap(long, parse(from_os_str)))]
122    llvm_debug_dir: Option<PathBuf>,
123
124    #[clap(flatten)]
125    features: WasmFeatures,
126}
127
128impl CompilerOptions {
129    fn get_compiler(&self) -> Result<CompilerType> {
130        if self.cranelift {
131            Ok(CompilerType::Cranelift)
132        } else if self.llvm {
133            Ok(CompilerType::LLVM)
134        } else if self.singlepass {
135            Ok(CompilerType::Singlepass)
136        } else {
137            // Auto mode, we choose the best compiler for that platform
138            cfg_if::cfg_if! {
139                if #[cfg(all(feature = "cranelift", any(target_arch = "x86_64", target_arch = "aarch64")))] {
140                    Ok(CompilerType::Cranelift)
141                }
142                else if #[cfg(all(feature = "singlepass", any(target_arch = "x86_64", target_arch = "aarch64")))] {
143                    Ok(CompilerType::Singlepass)
144                }
145                else if #[cfg(feature = "llvm")] {
146                    Ok(CompilerType::LLVM)
147                } else {
148                    bail!("There are no available compilers for your architecture");
149                }
150            }
151        }
152    }
153
154    /// Get the enaled Wasm features.
155    pub fn get_features(&self, mut features: Features) -> Result<Features> {
156        if self.features.threads || self.features.all {
157            features.threads(true);
158        }
159        if self.features.multi_value || self.features.all {
160            features.multi_value(true);
161        }
162        if self.features.simd || self.features.all {
163            features.simd(true);
164        }
165        if self.features.bulk_memory || self.features.all {
166            features.bulk_memory(true);
167        }
168        if self.features.reference_types || self.features.all {
169            features.reference_types(true);
170        }
171        Ok(features)
172    }
173
174    fn get_engine_by_type(
175        &self,
176        target: Target,
177        compiler_config: Box<dyn CompilerConfig>,
178    ) -> Result<EngineBuilder> {
179        let features = self.get_features(compiler_config.default_features_for_target(&target))?;
180        let engine: EngineBuilder = EngineBuilder::new(compiler_config)
181            .set_target(Some(target))
182            .set_features(Some(features));
183
184        Ok(engine)
185    }
186
187    /// Get the Compiler Config for the current options
188    #[allow(unused_variables)]
189    pub(crate) fn get_compiler_config(&self) -> Result<(Box<dyn CompilerConfig>, CompilerType)> {
190        let compiler = self.get_compiler()?;
191        let compiler_config: Box<dyn CompilerConfig> = match compiler {
192            CompilerType::Headless => bail!("The headless engine can't be chosen"),
193            #[cfg(feature = "singlepass")]
194            CompilerType::Singlepass => {
195                let mut config = wasmer_compiler_singlepass::Singlepass::new();
196                if self.enable_verifier {
197                    config.enable_verifier();
198                }
199                Box::new(config)
200            }
201            #[cfg(feature = "cranelift")]
202            CompilerType::Cranelift => {
203                let mut config = wasmer_compiler_cranelift::Cranelift::new();
204                if self.enable_verifier {
205                    config.enable_verifier();
206                }
207                Box::new(config)
208            }
209            #[cfg(feature = "llvm")]
210            CompilerType::LLVM => {
211                use std::fmt;
212                use std::fs::File;
213                use std::io::Write;
214                use wasmer_compiler_llvm::{
215                    CompiledKind, InkwellMemoryBuffer, InkwellModule, LLVMCallbacks,
216                };
217                use wasmer_types::entity::EntityRef;
218                let mut config = LLVM::new();
219                struct Callbacks {
220                    debug_dir: PathBuf,
221                }
222                impl Callbacks {
223                    fn new(debug_dir: PathBuf) -> Result<Self> {
224                        // Create the debug dir in case it doesn't exist
225                        std::fs::create_dir_all(&debug_dir)?;
226                        Ok(Self { debug_dir })
227                    }
228                }
229                // Converts a kind into a filename, that we will use to dump
230                // the contents of the IR object file to.
231                fn types_to_signature(types: &[Type]) -> String {
232                    types
233                        .iter()
234                        .map(|ty| match ty {
235                            Type::I32 => "i".to_string(),
236                            Type::I64 => "I".to_string(),
237                            Type::F32 => "f".to_string(),
238                            Type::F64 => "F".to_string(),
239                            Type::V128 => "v".to_string(),
240                            Type::ExternRef => "e".to_string(),
241                            Type::FuncRef => "r".to_string(),
242                        })
243                        .collect::<Vec<_>>()
244                        .join("")
245                }
246                // Converts a kind into a filename, that we will use to dump
247                // the contents of the IR object file to.
248                fn function_kind_to_filename(kind: &CompiledKind) -> String {
249                    match kind {
250                        CompiledKind::Local(local_index) => {
251                            format!("function_{}", local_index.index())
252                        }
253                        CompiledKind::FunctionCallTrampoline(func_type) => format!(
254                            "trampoline_call_{}_{}",
255                            types_to_signature(&func_type.params()),
256                            types_to_signature(&func_type.results())
257                        ),
258                        CompiledKind::DynamicFunctionTrampoline(func_type) => format!(
259                            "trampoline_dynamic_{}_{}",
260                            types_to_signature(&func_type.params()),
261                            types_to_signature(&func_type.results())
262                        ),
263                        CompiledKind::Module => "module".into(),
264                    }
265                }
266                impl LLVMCallbacks for Callbacks {
267                    fn preopt_ir(&self, kind: &CompiledKind, module: &InkwellModule) {
268                        let mut path = self.debug_dir.clone();
269                        path.push(format!("{}.preopt.ll", function_kind_to_filename(kind)));
270                        module
271                            .print_to_file(&path)
272                            .expect("Error while dumping pre optimized LLVM IR");
273                    }
274                    fn postopt_ir(&self, kind: &CompiledKind, module: &InkwellModule) {
275                        let mut path = self.debug_dir.clone();
276                        path.push(format!("{}.postopt.ll", function_kind_to_filename(kind)));
277                        module
278                            .print_to_file(&path)
279                            .expect("Error while dumping post optimized LLVM IR");
280                    }
281                    fn obj_memory_buffer(
282                        &self,
283                        kind: &CompiledKind,
284                        memory_buffer: &InkwellMemoryBuffer,
285                    ) {
286                        let mut path = self.debug_dir.clone();
287                        path.push(format!("{}.o", function_kind_to_filename(kind)));
288                        let mem_buf_slice = memory_buffer.as_slice();
289                        let mut file = File::create(path)
290                            .expect("Error while creating debug object file from LLVM IR");
291                        let mut pos = 0;
292                        while pos < mem_buf_slice.len() {
293                            pos += file.write(&mem_buf_slice[pos..]).unwrap();
294                        }
295                    }
296
297                    fn asm_memory_buffer(
298                        &self,
299                        kind: &CompiledKind,
300                        asm_memory_buffer: &InkwellMemoryBuffer,
301                    ) {
302                        let mut path = self.debug_dir.clone();
303                        path.push(format!("{}.s", function_kind_to_filename(kind)));
304                        let mem_buf_slice = asm_memory_buffer.as_slice();
305                        let mut file = File::create(path)
306                            .expect("Error while creating debug object file from LLVM IR");
307                        let mut pos = 0;
308                        while pos < mem_buf_slice.len() {
309                            pos += file.write(&mem_buf_slice[pos..]).unwrap();
310                        }
311                    }
312                }
313
314                impl fmt::Debug for Callbacks {
315                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
316                        write!(f, "LLVMCallbacks")
317                    }
318                }
319
320                if let Some(ref llvm_debug_dir) = self.llvm_debug_dir {
321                    config.callbacks(Some(Arc::new(Callbacks::new(llvm_debug_dir.clone())?)));
322                }
323                if self.enable_verifier {
324                    config.enable_verifier();
325                }
326                Box::new(config)
327            }
328            #[cfg(not(all(feature = "singlepass", feature = "cranelift", feature = "llvm")))]
329            compiler => {
330                bail!("The `{compiler}` compiler is not included in this binary.")
331            }
332        };
333
334        #[allow(unreachable_code)]
335        Ok((compiler_config, compiler))
336    }
337}
338
339/// The compiler used for the store
340#[derive(Debug, PartialEq, Eq)]
341pub enum CompilerType {
342    /// Singlepass compiler
343    Singlepass,
344    /// Cranelift compiler
345    Cranelift,
346    /// LLVM compiler
347    LLVM,
348    /// Headless compiler
349    #[allow(dead_code)]
350    Headless,
351}
352
353impl CompilerType {
354    /// Return all enabled compilers
355    pub fn enabled() -> Vec<CompilerType> {
356        vec![
357            #[cfg(feature = "singlepass")]
358            Self::Singlepass,
359            #[cfg(feature = "cranelift")]
360            Self::Cranelift,
361            #[cfg(feature = "llvm")]
362            Self::LLVM,
363        ]
364    }
365}
366
367impl std::fmt::Display for CompilerType {
368    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369        write!(
370            f,
371            "{}",
372            match self {
373                Self::Singlepass => "singlepass",
374                Self::Cranelift => "cranelift",
375                Self::LLVM => "llvm",
376                Self::Headless => "headless",
377            }
378        )
379    }
380}
381
382impl StoreOptions {
383    /// Get a EngineBulder for the Target
384    pub fn get_engine_for_target(&self, target: Target) -> Result<(EngineBuilder, CompilerType)> {
385        let (compiler_config, compiler_type) = self.compiler.get_compiler_config()?;
386        let engine = self.get_engine_with_compiler(target, compiler_config)?;
387        Ok((engine, compiler_type))
388    }
389
390    fn get_engine_with_compiler(
391        &self,
392        target: Target,
393        compiler_config: Box<dyn CompilerConfig>,
394    ) -> Result<EngineBuilder> {
395        self.compiler.get_engine_by_type(target, compiler_config)
396    }
397
398    /// Get (Subset)Tunables for the Target
399    pub fn get_tunables_for_target(&self, target: &Target) -> Result<SubsetTunables> {
400        let tunables = SubsetTunables::for_target(target);
401        Ok(tunables)
402    }
403}