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
21pub type InkwellModule<'ctx> = inkwell::module::Module<'ctx>;
23
24pub type InkwellMemoryBuffer = inkwell::memory_buffer::MemoryBuffer;
26
27#[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 std::fs::create_dir_all(&debug_dir)?;
37 Ok(Self { debug_dir })
38 }
39
40 fn base_path(&self, module_hash: &Option<String>) -> PathBuf {
41 let mut path = self.debug_dir.clone();
42 if let Some(hash) = module_hash {
43 path.push(hash);
44 }
45 std::fs::create_dir_all(&path)
46 .unwrap_or_else(|_| panic!("cannot create debug directory: {}", path.display()));
47 path
48 }
49
50 pub fn preopt_ir(
51 &self,
52 kind: &CompiledKind,
53 module_hash: &Option<String>,
54 module: &InkwellModule,
55 ) {
56 let mut path = self.base_path(module_hash);
57 path.push(function_kind_to_filename(kind, ".preopt.ll"));
58 module
59 .print_to_file(&path)
60 .expect("Error while dumping pre optimized LLVM IR");
61 }
62 pub fn postopt_ir(
63 &self,
64 kind: &CompiledKind,
65 module_hash: &Option<String>,
66 module: &InkwellModule,
67 ) {
68 let mut path = self.base_path(module_hash);
69 path.push(function_kind_to_filename(kind, ".postopt.ll"));
70 module
71 .print_to_file(&path)
72 .expect("Error while dumping post optimized LLVM IR");
73 }
74 pub fn obj_memory_buffer(
75 &self,
76 kind: &CompiledKind,
77 module_hash: &Option<String>,
78 memory_buffer: &InkwellMemoryBuffer,
79 ) {
80 let mut path = self.base_path(module_hash);
81 path.push(function_kind_to_filename(kind, ".o"));
82 let mem_buf_slice = memory_buffer.as_slice();
83 let mut file =
84 File::create(path).expect("Error while creating debug object file from LLVM IR");
85 file.write_all(mem_buf_slice).unwrap();
86 }
87
88 pub fn asm_memory_buffer(
89 &self,
90 kind: &CompiledKind,
91 module_hash: &Option<String>,
92 asm_memory_buffer: &InkwellMemoryBuffer,
93 ) {
94 let mut path = self.base_path(module_hash);
95 path.push(function_kind_to_filename(kind, ".s"));
96 let mem_buf_slice = asm_memory_buffer.as_slice();
97 let mut file =
98 File::create(path).expect("Error while creating debug assembly file from LLVM IR");
99 file.write_all(mem_buf_slice).unwrap();
100 }
101}
102
103#[derive(Debug, Clone)]
104pub struct LLVM {
105 pub(crate) enable_nan_canonicalization: bool,
106 pub(crate) enable_non_volatile_memops: bool,
107 pub(crate) enable_verifier: bool,
108 pub(crate) enable_perfmap: bool,
109 pub(crate) opt_level: LLVMOptLevel,
110 is_pic: bool,
111 pub(crate) callbacks: Option<LLVMCallbacks>,
112 pub(crate) middlewares: Vec<Arc<dyn ModuleMiddleware>>,
114 pub(crate) num_threads: NonZero<usize>,
116 pub(crate) verbose_asm: bool,
117}
118
119#[derive(Clone, Copy, Debug)]
120pub(crate) enum OptimizationStyle {
121 ForSpeed,
122 ForSize,
123}
124
125impl LLVM {
126 pub fn new() -> Self {
129 Self {
130 enable_nan_canonicalization: false,
131 enable_non_volatile_memops: false,
132 enable_verifier: false,
133 enable_perfmap: false,
134 opt_level: LLVMOptLevel::Aggressive,
135 is_pic: false,
136 callbacks: None,
137 middlewares: vec![],
138 verbose_asm: false,
139 num_threads: std::thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap()),
140 }
141 }
142
143 pub fn opt_level(&mut self, opt_level: LLVMOptLevel) -> &mut Self {
145 self.opt_level = opt_level;
146 self
147 }
148
149 pub fn num_threads(&mut self, num_threads: NonZero<usize>) -> &mut Self {
150 self.num_threads = num_threads;
151 self
152 }
153
154 pub fn verbose_asm(&mut self, verbose_asm: bool) -> &mut Self {
155 self.verbose_asm = verbose_asm;
156 self
157 }
158
159 pub fn callbacks(&mut self, callbacks: Option<LLVMCallbacks>) -> &mut Self {
162 self.callbacks = callbacks;
163 self
164 }
165
166 pub fn non_volatile_memops(&mut self, enable_non_volatile_memops: bool) -> &mut Self {
169 self.enable_non_volatile_memops = enable_non_volatile_memops;
170 self
171 }
172
173 fn reloc_mode(&self, binary_format: BinaryFormat) -> RelocMode {
174 if matches!(binary_format, BinaryFormat::Macho) {
175 return RelocMode::Static;
176 }
177
178 if self.is_pic {
179 RelocMode::PIC
180 } else {
181 RelocMode::Static
182 }
183 }
184
185 fn code_model(&self, binary_format: BinaryFormat) -> CodeModel {
186 if matches!(binary_format, BinaryFormat::Macho) {
192 return CodeModel::Default;
193 }
194
195 if self.is_pic {
196 CodeModel::Small
197 } else {
198 CodeModel::Large
199 }
200 }
201
202 pub(crate) fn target_operating_system(&self, target: &Target) -> OperatingSystem {
203 match target.triple().operating_system {
204 OperatingSystem::Darwin(deployment) if !self.is_pic => {
205 #[allow(clippy::match_single_binding)]
213 match target.triple().architecture {
214 Architecture::Aarch64(_) => OperatingSystem::Darwin(deployment),
215 _ => OperatingSystem::Linux,
216 }
217 }
218 other => other,
219 }
220 }
221
222 pub(crate) fn target_binary_format(&self, target: &Target) -> target_lexicon::BinaryFormat {
223 if self.is_pic {
224 target.triple().binary_format
225 } else {
226 match self.target_operating_system(target) {
227 OperatingSystem::Darwin(_) => target_lexicon::BinaryFormat::Macho,
228 _ => target_lexicon::BinaryFormat::Elf,
229 }
230 }
231 }
232
233 fn target_triple(&self, target: &Target) -> TargetTriple {
234 let architecture = if target.triple().architecture
235 == Architecture::Riscv64(target_lexicon::Riscv64Architecture::Riscv64gc)
236 {
237 target_lexicon::Architecture::Riscv64(target_lexicon::Riscv64Architecture::Riscv64)
238 } else {
239 target.triple().architecture
240 };
241 let operating_system = self.target_operating_system(target);
245 let binary_format = self.target_binary_format(target);
246
247 let triple = Triple {
248 architecture,
249 vendor: target.triple().vendor.clone(),
250 operating_system,
251 environment: target.triple().environment,
252 binary_format,
253 };
254 TargetTriple::create(&triple.to_string())
255 }
256
257 pub fn target_machine(&self, target: &Target) -> TargetMachine {
259 self.target_machine_with_opt(target, OptimizationStyle::ForSpeed)
260 }
261
262 pub(crate) fn target_machine_with_opt(
263 &self,
264 target: &Target,
265 opt_style: OptimizationStyle,
266 ) -> TargetMachine {
267 let triple = target.triple();
268 let cpu_features = &target.cpu_features();
269
270 match triple.architecture {
271 Architecture::X86_64 | Architecture::X86_32(_) => {
272 InkwellTarget::initialize_x86(&InitializationConfig {
273 asm_parser: true,
274 asm_printer: true,
275 base: true,
276 disassembler: true,
277 info: true,
278 machine_code: true,
279 })
280 }
281 Architecture::Aarch64(_) => InkwellTarget::initialize_aarch64(&InitializationConfig {
282 asm_parser: true,
283 asm_printer: true,
284 base: true,
285 disassembler: true,
286 info: true,
287 machine_code: true,
288 }),
289 Architecture::Riscv64(_) | Architecture::Riscv32(_) => {
290 InkwellTarget::initialize_riscv(&InitializationConfig {
291 asm_parser: true,
292 asm_printer: true,
293 base: true,
294 disassembler: true,
295 info: true,
296 machine_code: true,
297 })
298 }
299 Architecture::LoongArch64 => {
300 InkwellTarget::initialize_loongarch(&InitializationConfig {
301 asm_parser: true,
302 asm_printer: true,
303 base: true,
304 disassembler: true,
305 info: true,
306 machine_code: true,
307 })
308 }
309 _ => unimplemented!("target {} not yet supported in Wasmer", triple),
310 }
311
312 let llvm_cpu_features = cpu_features
316 .iter()
317 .map(|feature| format!("+{feature}"))
318 .join(",");
319
320 let target_triple = self.target_triple(target);
321 let llvm_target = InkwellTarget::from_triple(&target_triple).unwrap();
322 let mut llvm_target_machine_options = TargetMachineOptions::new()
323 .set_cpu(match triple.architecture {
324 Architecture::Riscv64(_) => "generic-rv64",
325 Architecture::Riscv32(_) => "generic-rv32",
326 Architecture::LoongArch64 => "generic-la64",
327 _ => "generic",
328 })
329 .set_features(match triple.architecture {
330 Architecture::Riscv64(_) => "+m,+a,+c,+d,+f",
331 Architecture::Riscv32(_) => "+m,+a,+c,+d,+f",
332 Architecture::LoongArch64 => "+f,+d",
333 _ => &llvm_cpu_features,
334 })
335 .set_level(match opt_style {
336 OptimizationStyle::ForSpeed => self.opt_level,
337 OptimizationStyle::ForSize => LLVMOptLevel::Less,
338 })
339 .set_reloc_mode(self.reloc_mode(self.target_binary_format(target)))
340 .set_code_model(match triple.architecture {
341 Architecture::LoongArch64 | Architecture::Riscv64(_) | Architecture::Riscv32(_) => {
342 CodeModel::Medium
343 }
344 _ => self.code_model(self.target_binary_format(target)),
345 });
346 if let Architecture::Riscv64(_) = triple.architecture {
347 llvm_target_machine_options = llvm_target_machine_options.set_abi("lp64d");
348 }
349 let target_machine = llvm_target
350 .create_target_machine_from_options(&target_triple, llvm_target_machine_options)
351 .unwrap();
352 target_machine.set_asm_verbosity(self.verbose_asm);
353 target_machine
354 }
355}
356
357impl CompilerConfig for LLVM {
358 fn enable_pic(&mut self) {
360 self.is_pic = true;
363 }
364
365 fn enable_perfmap(&mut self) {
366 self.enable_perfmap = true
367 }
368
369 fn enable_verifier(&mut self) {
371 self.enable_verifier = true;
372 }
373
374 fn enable_non_volatile_memops(&mut self) {
377 self.enable_non_volatile_memops = true;
378 }
379
380 fn canonicalize_nans(&mut self, enable: bool) {
381 self.enable_nan_canonicalization = enable;
382 }
383
384 fn compiler(self: Box<Self>) -> Box<dyn Compiler> {
386 Box::new(LLVMCompiler::new(*self))
387 }
388
389 fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>) {
391 self.middlewares.push(middleware);
392 }
393
394 fn supported_features_for_target(&self, _target: &Target) -> wasmer_types::Features {
395 let mut feats = Features::default();
396 feats.exceptions(true);
397 feats.relaxed_simd(true);
398 feats.wide_arithmetic(true);
399 feats.tail_call(true);
400 feats
401 }
402}
403
404impl Default for LLVM {
405 fn default() -> LLVM {
406 Self::new()
407 }
408}
409
410impl From<LLVM> for Engine {
411 fn from(config: LLVM) -> Self {
412 EngineBuilder::new(config).engine()
413 }
414}