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