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