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