wasmer_compiler_llvm/
config.rs

1use crate::compiler::LLVMCompiler;
2use enum_iterator::Sequence;
3pub use inkwell::OptimizationLevel as LLVMOptLevel;
4use inkwell::targets::{
5    CodeModel, InitializationConfig, RelocMode, Target as InkwellTarget, TargetMachine,
6    TargetMachineOptions, TargetTriple,
7};
8use itertools::Itertools;
9use std::fs::File;
10use std::io::{self, Write};
11use std::path::PathBuf;
12use std::sync::Arc;
13use std::{fmt::Debug, num::NonZero};
14use target_lexicon::BinaryFormat;
15use wasmer_compiler::misc::{CompiledKind, function_kind_to_filename};
16use wasmer_compiler::{Compiler, CompilerConfig, Engine, EngineBuilder, ModuleMiddleware};
17use wasmer_types::{
18    Features,
19    target::{Architecture, OperatingSystem, Target, Triple},
20};
21
22/// The InkWell ModuleInfo type
23pub type InkwellModule<'ctx> = inkwell::module::Module<'ctx>;
24
25/// The InkWell MemoryBuffer type
26pub type InkwellMemoryBuffer = inkwell::memory_buffer::MemoryBuffer;
27
28/// Callbacks to the different LLVM compilation phases.
29#[derive(Debug, Clone)]
30pub struct LLVMCallbacks {
31    debug_dir: PathBuf,
32}
33
34impl LLVMCallbacks {
35    pub fn new(debug_dir: PathBuf) -> Result<Self, io::Error> {
36        // Create the debug dir in case it doesn't exist
37        std::fs::create_dir_all(&debug_dir)?;
38        Ok(Self { debug_dir })
39    }
40
41    fn base_path(&self, module_hash: &Option<String>) -> PathBuf {
42        let mut path = self.debug_dir.clone();
43        if let Some(hash) = module_hash {
44            path.push(hash);
45        }
46        std::fs::create_dir_all(&path)
47            .unwrap_or_else(|_| panic!("cannot create debug directory: {}", path.display()));
48        path
49    }
50
51    pub fn preopt_ir(
52        &self,
53        kind: &CompiledKind,
54        module_hash: &Option<String>,
55        module: &InkwellModule,
56    ) {
57        let mut path = self.base_path(module_hash);
58        path.push(function_kind_to_filename(kind, ".preopt.ll"));
59        module
60            .print_to_file(&path)
61            .expect("Error while dumping pre optimized LLVM IR");
62    }
63    pub fn postopt_ir(
64        &self,
65        kind: &CompiledKind,
66        module_hash: &Option<String>,
67        module: &InkwellModule,
68    ) {
69        let mut path = self.base_path(module_hash);
70        path.push(function_kind_to_filename(kind, ".postopt.ll"));
71        module
72            .print_to_file(&path)
73            .expect("Error while dumping post optimized LLVM IR");
74    }
75    pub fn obj_memory_buffer(
76        &self,
77        kind: &CompiledKind,
78        module_hash: &Option<String>,
79        memory_buffer: &InkwellMemoryBuffer,
80    ) {
81        let mut path = self.base_path(module_hash);
82        path.push(function_kind_to_filename(kind, ".o"));
83        let mem_buf_slice = memory_buffer.as_slice();
84        let mut file =
85            File::create(path).expect("Error while creating debug object file from LLVM IR");
86        file.write_all(mem_buf_slice).unwrap();
87    }
88
89    pub fn asm_memory_buffer(
90        &self,
91        kind: &CompiledKind,
92        module_hash: &Option<String>,
93        asm_memory_buffer: &InkwellMemoryBuffer,
94    ) {
95        let mut path = self.base_path(module_hash);
96        path.push(function_kind_to_filename(kind, ".s"));
97        let mem_buf_slice = asm_memory_buffer.as_slice();
98        let mut file =
99            File::create(path).expect("Error while creating debug assembly file from LLVM IR");
100        file.write_all(mem_buf_slice).unwrap();
101    }
102}
103
104#[derive(Debug, Clone)]
105pub struct LLVM {
106    pub(crate) enable_nan_canonicalization: bool,
107    pub(crate) enable_non_volatile_memops: bool,
108    pub(crate) enable_readonly_funcref_table: bool,
109    pub(crate) enable_verifier: bool,
110    pub(crate) enable_perfmap: bool,
111    pub(crate) opt_level: LLVMOptLevel,
112    is_pic: bool,
113    pub(crate) callbacks: Option<LLVMCallbacks>,
114    /// The middleware chain.
115    pub(crate) middlewares: Vec<Arc<dyn ModuleMiddleware>>,
116    /// Number of threads to use when compiling a module.
117    pub(crate) num_threads: NonZero<usize>,
118    pub(crate) verbose_asm: bool,
119}
120
121#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, Sequence)]
122pub(crate) enum OptimizationStyle {
123    ForSpeed,
124    ForSize,
125    Disabled,
126}
127
128impl LLVM {
129    /// Creates a new configuration object with the default configuration
130    /// specified.
131    pub fn new() -> Self {
132        Self {
133            enable_nan_canonicalization: false,
134            enable_non_volatile_memops: false,
135            enable_readonly_funcref_table: false,
136            enable_verifier: false,
137            enable_perfmap: false,
138            opt_level: LLVMOptLevel::Aggressive,
139            is_pic: false,
140            callbacks: None,
141            middlewares: vec![],
142            verbose_asm: false,
143            num_threads: std::thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap()),
144        }
145    }
146
147    /// The optimization levels when optimizing the IR.
148    pub fn opt_level(&mut self, opt_level: LLVMOptLevel) -> &mut Self {
149        self.opt_level = opt_level;
150        self
151    }
152
153    pub fn num_threads(&mut self, num_threads: NonZero<usize>) -> &mut Self {
154        self.num_threads = num_threads;
155        self
156    }
157
158    pub fn verbose_asm(&mut self, verbose_asm: bool) -> &mut Self {
159        self.verbose_asm = verbose_asm;
160        self
161    }
162
163    /// Callbacks that will triggered in the different compilation
164    /// phases in LLVM.
165    pub fn callbacks(&mut self, callbacks: Option<LLVMCallbacks>) -> &mut Self {
166        self.callbacks = callbacks;
167        self
168    }
169
170    /// For the LLVM compiler, we can use non-volatile memory operations which lead to a better performance
171    /// (but are not 100% SPEC compliant).
172    pub fn non_volatile_memops(&mut self, enable_non_volatile_memops: bool) -> &mut Self {
173        self.enable_non_volatile_memops = enable_non_volatile_memops;
174        self
175    }
176
177    /// Enables treating eligible funcref tables as read-only so the backend can
178    /// place them in read-only data.
179    pub fn readonly_funcref_table(&mut self, enable_readonly_funcref_table: bool) -> &mut Self {
180        self.enable_readonly_funcref_table = enable_readonly_funcref_table;
181        self
182    }
183
184    fn reloc_mode(&self, binary_format: BinaryFormat) -> RelocMode {
185        if matches!(binary_format, BinaryFormat::Macho) {
186            return RelocMode::Static;
187        }
188
189        if self.is_pic {
190            RelocMode::PIC
191        } else {
192            RelocMode::Static
193        }
194    }
195
196    fn code_model(&self, binary_format: BinaryFormat) -> CodeModel {
197        // We normally use the large code model, but when targeting shared
198        // objects, we are required to use PIC. If we use PIC anyways, we lose
199        // any benefit from large code model and there's some cost on all
200        // platforms, plus some platforms (MachO) don't support PIC + large
201        // at all.
202        if matches!(binary_format, BinaryFormat::Macho) {
203            return CodeModel::Default;
204        }
205
206        if self.is_pic {
207            CodeModel::Small
208        } else {
209            CodeModel::Large
210        }
211    }
212
213    pub(crate) fn target_operating_system(&self, target: &Target) -> OperatingSystem {
214        match target.triple().operating_system {
215            OperatingSystem::Darwin(deployment) if !self.is_pic => {
216                // LLVM detects static relocation + darwin + 64-bit and
217                // force-enables PIC because MachO doesn't support that
218                // combination. They don't check whether they're targeting
219                // MachO, they check whether the OS is set to Darwin.
220                //
221                // Since both linux and darwin use SysV ABI, this should work.
222                //  but not in the case of Aarch64, there the ABI is slightly different
223                #[allow(clippy::match_single_binding)]
224                match target.triple().architecture {
225                    Architecture::Aarch64(_) => OperatingSystem::Darwin(deployment),
226                    _ => OperatingSystem::Linux,
227                }
228            }
229            other => other,
230        }
231    }
232
233    pub(crate) fn target_binary_format(&self, target: &Target) -> target_lexicon::BinaryFormat {
234        if self.is_pic {
235            target.triple().binary_format
236        } else {
237            match self.target_operating_system(target) {
238                OperatingSystem::Darwin(_) => target_lexicon::BinaryFormat::Macho,
239                _ => target_lexicon::BinaryFormat::Elf,
240            }
241        }
242    }
243
244    fn target_triple(&self, target: &Target) -> TargetTriple {
245        let architecture = if target.triple().architecture
246            == Architecture::Riscv64(target_lexicon::Riscv64Architecture::Riscv64gc)
247        {
248            target_lexicon::Architecture::Riscv64(target_lexicon::Riscv64Architecture::Riscv64)
249        } else {
250            target.triple().architecture
251        };
252        // Hack: we're using is_pic to determine whether this is a native
253        // build or not.
254
255        let operating_system = self.target_operating_system(target);
256        let binary_format = self.target_binary_format(target);
257
258        let triple = Triple {
259            architecture,
260            vendor: target.triple().vendor.clone(),
261            operating_system,
262            environment: target.triple().environment,
263            binary_format,
264        };
265        TargetTriple::create(&triple.to_string())
266    }
267
268    /// Generates the target machine for the current target
269    pub fn target_machine(&self, target: &Target) -> TargetMachine {
270        self.target_machine_with_opt(target, OptimizationStyle::ForSpeed)
271    }
272
273    pub(crate) fn target_machine_with_opt(
274        &self,
275        target: &Target,
276        opt_style: OptimizationStyle,
277    ) -> TargetMachine {
278        let triple = target.triple();
279        let cpu_features = &target.cpu_features();
280
281        match triple.architecture {
282            Architecture::X86_64 | Architecture::X86_32(_) => {
283                InkwellTarget::initialize_x86(&InitializationConfig {
284                    asm_parser: true,
285                    asm_printer: true,
286                    base: true,
287                    disassembler: true,
288                    info: true,
289                    machine_code: true,
290                })
291            }
292            Architecture::Aarch64(_) => InkwellTarget::initialize_aarch64(&InitializationConfig {
293                asm_parser: true,
294                asm_printer: true,
295                base: true,
296                disassembler: true,
297                info: true,
298                machine_code: true,
299            }),
300            Architecture::Riscv64(_) | Architecture::Riscv32(_) => {
301                InkwellTarget::initialize_riscv(&InitializationConfig {
302                    asm_parser: true,
303                    asm_printer: true,
304                    base: true,
305                    disassembler: true,
306                    info: true,
307                    machine_code: true,
308                })
309            }
310            Architecture::LoongArch64 => {
311                InkwellTarget::initialize_loongarch(&InitializationConfig {
312                    asm_parser: true,
313                    asm_printer: true,
314                    base: true,
315                    disassembler: true,
316                    info: true,
317                    machine_code: true,
318                })
319            }
320            _ => unimplemented!("target {} not yet supported in Wasmer", triple),
321        }
322
323        // The CPU features formatted as LLVM strings
324        // We can safely map to gcc-like features as the CPUFeatures
325        // are compliant with the same string representations as gcc.
326        let llvm_cpu_features = cpu_features
327            .iter()
328            .map(|feature| format!("+{feature}"))
329            .join(",");
330
331        let target_triple = self.target_triple(target);
332        let llvm_target = InkwellTarget::from_triple(&target_triple).unwrap();
333        let mut llvm_target_machine_options = TargetMachineOptions::new()
334            .set_cpu(match triple.architecture {
335                Architecture::Riscv64(_) => "generic-rv64",
336                Architecture::Riscv32(_) => "generic-rv32",
337                Architecture::LoongArch64 => "generic-la64",
338                _ => "generic",
339            })
340            .set_features(match triple.architecture {
341                Architecture::Riscv64(_) => "+m,+a,+c,+d,+f",
342                Architecture::Riscv32(_) => "+m,+a,+c,+d,+f",
343                Architecture::LoongArch64 => "+f,+d",
344                _ => &llvm_cpu_features,
345            })
346            .set_level(match opt_style {
347                OptimizationStyle::ForSpeed => self.opt_level,
348                OptimizationStyle::ForSize => LLVMOptLevel::Less,
349                OptimizationStyle::Disabled => LLVMOptLevel::None,
350            })
351            .set_reloc_mode(self.reloc_mode(self.target_binary_format(target)))
352            .set_code_model(match triple.architecture {
353                Architecture::LoongArch64 | Architecture::Riscv64(_) | Architecture::Riscv32(_) => {
354                    CodeModel::Medium
355                }
356                _ => self.code_model(self.target_binary_format(target)),
357            });
358        if let Architecture::Riscv64(_) = triple.architecture {
359            llvm_target_machine_options = llvm_target_machine_options.set_abi("lp64d");
360        }
361        let target_machine = llvm_target
362            .create_target_machine_from_options(&target_triple, llvm_target_machine_options)
363            .unwrap();
364        target_machine.set_asm_verbosity(self.verbose_asm);
365        target_machine
366    }
367}
368
369impl CompilerConfig for LLVM {
370    /// Emit code suitable for dlopen.
371    fn enable_pic(&mut self) {
372        // TODO: although we can emit PIC, the object file parser does not yet
373        // support all the relocations.
374        self.is_pic = true;
375    }
376
377    fn enable_perfmap(&mut self) {
378        self.enable_perfmap = true
379    }
380
381    /// Whether to verify compiler IR.
382    fn enable_verifier(&mut self) {
383        self.enable_verifier = true;
384    }
385
386    /// For the LLVM compiler, we can use non-volatile memory operations which lead to a better performance
387    /// (but are not 100% SPEC compliant).
388    fn enable_non_volatile_memops(&mut self) {
389        self.enable_non_volatile_memops = true;
390    }
391
392    /// Enables treating eligible funcref tables as read-only so the backend can
393    /// place them in read-only data.
394    fn enable_readonly_funcref_table(&mut self) {
395        self.enable_readonly_funcref_table = true;
396    }
397
398    fn canonicalize_nans(&mut self, enable: bool) {
399        self.enable_nan_canonicalization = enable;
400    }
401
402    /// Transform it into the compiler.
403    fn compiler(self: Box<Self>) -> Box<dyn Compiler> {
404        Box::new(LLVMCompiler::new(*self))
405    }
406
407    /// Pushes a middleware onto the back of the middleware chain.
408    fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>) {
409        self.middlewares.push(middleware);
410    }
411
412    fn supported_features_for_target(&self, _target: &Target) -> wasmer_types::Features {
413        let mut feats = Features::default();
414        feats.exceptions(true);
415        feats.relaxed_simd(true);
416        feats.wide_arithmetic(true);
417        feats.tail_call(true);
418        feats
419    }
420}
421
422impl Default for LLVM {
423    fn default() -> LLVM {
424        Self::new()
425    }
426}
427
428impl From<LLVM> for Engine {
429    fn from(config: LLVM) -> Self {
430        EngineBuilder::new(config).engine()
431    }
432}