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