wasmer_compiler_llvm/
config.rs

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