Skip to main content

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