wasmer_compiler_llvm/
config.rs

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