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