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