wasmer_compiler_llvm/
compiler.rs

1use crate::config::LLVM;
2use crate::config::OptimizationStyle;
3use crate::translator::FuncTrampoline;
4use crate::translator::FuncTranslator;
5use rayon::ThreadPoolBuilder;
6use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
7use std::{
8    borrow::Cow,
9    collections::{HashMap, HashSet},
10    sync::Arc,
11};
12use wasmer_compiler::progress::ProgressContext;
13use wasmer_compiler::types::function::{Compilation, UnwindInfo};
14use wasmer_compiler::types::module::CompileModuleInfo;
15use wasmer_compiler::types::relocation::RelocationKind;
16use wasmer_compiler::{
17    Compiler, FunctionBodyData, ModuleMiddleware, ModuleTranslationState,
18    types::{
19        relocation::RelocationTarget,
20        section::{CustomSection, CustomSectionProtection, SectionBody, SectionIndex},
21        symbols::{Symbol, SymbolRegistry},
22    },
23};
24use wasmer_compiler::{
25    WASM_LARGE_FUNCTION_THRESHOLD, WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE, build_function_buckets,
26    translate_function_buckets,
27};
28use wasmer_types::ExportIndex;
29use wasmer_types::entity::{EntityRef, PrimaryMap};
30use wasmer_types::target::Target;
31use wasmer_types::{
32    CompilationProgressCallback, CompileError, FunctionIndex, LocalFunctionIndex, ModuleInfo,
33    SignatureIndex,
34};
35use wasmer_vm::LibCall;
36
37/// A compiler that compiles a WebAssembly module with LLVM, translating the Wasm to LLVM IR,
38/// optimizing it and then translating to assembly.
39#[derive(Debug)]
40pub struct LLVMCompiler {
41    config: LLVM,
42}
43
44impl LLVMCompiler {
45    /// Creates a new LLVM compiler
46    pub fn new(config: LLVM) -> LLVMCompiler {
47        LLVMCompiler { config }
48    }
49
50    /// Gets the config for this Compiler
51    fn config(&self) -> &LLVM {
52        &self.config
53    }
54}
55
56struct ShortNames {}
57
58impl SymbolRegistry for ShortNames {
59    fn symbol_to_name(&self, symbol: Symbol) -> String {
60        match symbol {
61            Symbol::Metadata => "M".to_string(),
62            Symbol::LocalFunction(index) => format!("f{}", index.index()),
63            Symbol::Section(index) => format!("s{}", index.index()),
64            Symbol::FunctionCallTrampoline(index) => format!("t{}", index.index()),
65            Symbol::DynamicFunctionTrampoline(index) => format!("d{}", index.index()),
66        }
67    }
68
69    fn name_to_symbol(&self, name: &str) -> Option<Symbol> {
70        if name.len() < 2 {
71            return None;
72        }
73        let (ty, idx) = name.split_at(1);
74        if ty.starts_with('M') {
75            return Some(Symbol::Metadata);
76        }
77
78        let idx = idx.parse::<u32>().ok()?;
79        match ty.chars().next().unwrap() {
80            'f' => Some(Symbol::LocalFunction(LocalFunctionIndex::from_u32(idx))),
81            's' => Some(Symbol::Section(SectionIndex::from_u32(idx))),
82            't' => Some(Symbol::FunctionCallTrampoline(SignatureIndex::from_u32(
83                idx,
84            ))),
85            'd' => Some(Symbol::DynamicFunctionTrampoline(FunctionIndex::from_u32(
86                idx,
87            ))),
88            _ => None,
89        }
90    }
91}
92
93pub(crate) struct ModuleBasedSymbolRegistry {
94    wasm_module: Arc<ModuleInfo>,
95    local_func_names: HashMap<String, LocalFunctionIndex>,
96    short_names: ShortNames,
97}
98
99impl ModuleBasedSymbolRegistry {
100    const PROBLEMATIC_PREFIXES: &[&'static str] = &[
101        ".L",    // .L is used for local symbols
102        "llvm.", // llvm. is used for LLVM's own intrinsics
103    ];
104
105    fn new(wasm_module: Arc<ModuleInfo>) -> Self {
106        let local_func_names = HashMap::from_iter(
107            wasm_module
108                .function_names
109                .iter()
110                .map(|(f, v)| (wasm_module.local_func_index(*f), v))
111                .filter(|(f, _)| f.is_some())
112                .map(|(f, v)| (format!("{}_{}", v.clone(), f.unwrap().as_u32()), f.unwrap())),
113        );
114        Self {
115            wasm_module,
116            local_func_names,
117            short_names: ShortNames {},
118        }
119    }
120
121    // If the name starts with a problematic prefix, we prefix it with an underscore.
122    fn fixup_problematic_name(name: &str) -> Cow<'_, str> {
123        for prefix in Self::PROBLEMATIC_PREFIXES {
124            if name.starts_with(prefix) {
125                return format!("_{name}").into();
126            }
127        }
128        name.into()
129    }
130
131    // If the name starts with an underscore and the rest starts with a problematic prefix,
132    // remove the underscore to get back the original name. This is necessary to be able
133    // to match the name back to the original name in the wasm module.
134    fn unfixup_problematic_name(name: &str) -> &str {
135        if let Some(stripped_name) = name.strip_prefix('_') {
136            for prefix in Self::PROBLEMATIC_PREFIXES {
137                if stripped_name.starts_with(prefix) {
138                    return stripped_name;
139                }
140            }
141        }
142
143        name
144    }
145}
146
147impl SymbolRegistry for ModuleBasedSymbolRegistry {
148    fn symbol_to_name(&self, symbol: Symbol) -> String {
149        match symbol {
150            Symbol::LocalFunction(index) => self
151                .wasm_module
152                .function_names
153                .get(&self.wasm_module.func_index(index))
154                .map(|name| format!("{}_{}", Self::fixup_problematic_name(name), index.as_u32()))
155                .unwrap_or(self.short_names.symbol_to_name(symbol)),
156            _ => self.short_names.symbol_to_name(symbol),
157        }
158    }
159
160    fn name_to_symbol(&self, name: &str) -> Option<Symbol> {
161        let name = Self::unfixup_problematic_name(name);
162        if let Some(idx) = self.local_func_names.get(name) {
163            Some(Symbol::LocalFunction(*idx))
164        } else {
165            self.short_names.name_to_symbol(name)
166        }
167    }
168}
169
170impl Compiler for LLVMCompiler {
171    fn name(&self) -> &str {
172        "llvm"
173    }
174
175    fn get_perfmap_enabled(&self) -> bool {
176        self.config.enable_perfmap
177    }
178
179    fn deterministic_id(&self) -> String {
180        format!(
181            "llvm-{}",
182            match self.config.opt_level {
183                inkwell::OptimizationLevel::None => "opt0",
184                inkwell::OptimizationLevel::Less => "optl",
185                inkwell::OptimizationLevel::Default => "optd",
186                inkwell::OptimizationLevel::Aggressive => "opta",
187            }
188        )
189    }
190
191    /// Get the middlewares for this compiler
192    fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>] {
193        &self.config.middlewares
194    }
195
196    fn enable_readonly_funcref_table(&self) -> bool {
197        self.config.enable_readonly_funcref_table
198    }
199
200    /// Compile the module using LLVM, producing a compilation result with
201    /// associated relocations.
202    fn compile_module(
203        &self,
204        target: &Target,
205        compile_info: &CompileModuleInfo,
206        module_translation: &ModuleTranslationState,
207        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
208        progress_callback: Option<&CompilationProgressCallback>,
209    ) -> Result<Compilation, CompileError> {
210        let binary_format = self.config.target_binary_format(target);
211
212        let module = &compile_info.module;
213        let module_hash = module.hash_string();
214
215        let total_function_call_trampolines = module.signatures.len();
216        let total_dynamic_trampolines = module.num_imported_functions;
217        let total_steps = WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE
218            * ((total_dynamic_trampolines + total_function_call_trampolines) as u64)
219            + function_body_inputs
220                .iter()
221                .map(|(_, body)| body.data.len() as u64)
222                .sum::<u64>();
223
224        let progress = progress_callback
225            .cloned()
226            .map(|cb| ProgressContext::new(cb, total_steps, "Compiling functions"));
227
228        // TODO: merge constants in sections.
229
230        let mut module_custom_sections = PrimaryMap::new();
231
232        let mut eh_frame_section_bytes = vec![];
233        let mut eh_frame_section_relocations = vec![];
234
235        let mut compact_unwind_section_bytes = vec![];
236        let mut compact_unwind_section_relocations = vec![];
237
238        let mut got_targets: HashSet<wasmer_compiler::types::relocation::RelocationTarget> = if matches!(
239            target.triple().binary_format,
240            target_lexicon::BinaryFormat::Macho
241        ) {
242            HashSet::from_iter(vec![RelocationTarget::LibCall(LibCall::EHPersonality)])
243        } else {
244            HashSet::default()
245        };
246
247        let symbol_registry = ModuleBasedSymbolRegistry::new(module.clone());
248        let module = &compile_info.module;
249        let memory_styles = &compile_info.memory_styles;
250        let table_styles = &compile_info.table_styles;
251        let signature_hashes = &module.signature_hashes;
252
253        let pool = ThreadPoolBuilder::new()
254            .num_threads(self.config.num_threads.get())
255            .build()
256            .map_err(|e| CompileError::Resource(e.to_string()))?;
257
258        let buckets =
259            build_function_buckets(&function_body_inputs, WASM_LARGE_FUNCTION_THRESHOLD / 3);
260        let largest_bucket = buckets.first().map(|b| b.size).unwrap_or_default();
261        tracing::debug!(buckets = buckets.len(), largest_bucket, "buckets built");
262        let functions = translate_function_buckets(
263            &pool,
264            || {
265                let compiler = &self;
266                let target_machines = enum_iterator::all::<OptimizationStyle>()
267                    .map(|style| {
268                        (
269                            style,
270                            compiler.config().target_machine_with_opt(target, style),
271                        )
272                    })
273                    .collect();
274                let pointer_width = target.triple().pointer_width().unwrap().bytes();
275                FuncTranslator::new(
276                    target.triple().clone(),
277                    target_machines,
278                    binary_format,
279                    pointer_width,
280                    *target.cpu_features(),
281                    self.config.enable_non_volatile_memops,
282                    module
283                        .exports
284                        .get("__wasm_apply_data_relocs")
285                        .and_then(|export| {
286                            if let ExportIndex::Function(index) = export {
287                                Some(*index)
288                            } else {
289                                None
290                            }
291                        }),
292                )
293                .unwrap()
294            },
295            |func_translator, i, input| {
296                func_translator.translate(
297                    module,
298                    module_translation,
299                    signature_hashes,
300                    i,
301                    input,
302                    self.config(),
303                    memory_styles,
304                    table_styles,
305                    &symbol_registry,
306                    target.triple(),
307                )
308            },
309            progress.clone(),
310            &buckets,
311        )?;
312
313        let functions = functions
314            .into_iter()
315            .map(|mut compiled_function| {
316                let first_section = module_custom_sections.len() as u32;
317                for (section_index, custom_section) in compiled_function.custom_sections.iter() {
318                    // TODO: remove this call to clone()
319                    let mut custom_section = custom_section.clone();
320                    for reloc in &mut custom_section.relocations {
321                        if let RelocationTarget::CustomSection(index) = reloc.reloc_target {
322                            reloc.reloc_target = RelocationTarget::CustomSection(
323                                SectionIndex::from_u32(first_section + index.as_u32()),
324                            )
325                        }
326
327                        if reloc.kind.needs_got() {
328                            got_targets.insert(reloc.reloc_target);
329                        }
330                    }
331
332                    if compiled_function
333                        .eh_frame_section_indices
334                        .contains(&section_index)
335                    {
336                        let offset = eh_frame_section_bytes.len() as u32;
337                        for reloc in &mut custom_section.relocations {
338                            reloc.offset += offset;
339                        }
340                        eh_frame_section_bytes.extend_from_slice(custom_section.bytes.as_slice());
341                        // Terminate the eh_frame info with a zero-length CIE.
342                        eh_frame_section_bytes.extend_from_slice(&[0, 0, 0, 0]);
343                        eh_frame_section_relocations.extend(custom_section.relocations);
344                        // TODO: we do this to keep the count right, remove it.
345                        module_custom_sections.push(CustomSection {
346                            protection: CustomSectionProtection::Read,
347                            alignment: None,
348                            bytes: SectionBody::new_with_vec(vec![]),
349                            relocations: vec![],
350                        });
351                    } else if compiled_function
352                        .compact_unwind_section_indices
353                        .contains(&section_index)
354                    {
355                        let offset = compact_unwind_section_bytes.len() as u32;
356                        for reloc in &mut custom_section.relocations {
357                            reloc.offset += offset;
358                        }
359                        compact_unwind_section_bytes
360                            .extend_from_slice(custom_section.bytes.as_slice());
361                        compact_unwind_section_relocations.extend(custom_section.relocations);
362                        // TODO: we do this to keep the count right, remove it.
363                        module_custom_sections.push(CustomSection {
364                            protection: CustomSectionProtection::Read,
365                            alignment: None,
366                            bytes: SectionBody::new_with_vec(vec![]),
367                            relocations: vec![],
368                        });
369                    } else {
370                        module_custom_sections.push(custom_section);
371                    }
372                }
373                for reloc in &mut compiled_function.compiled_function.relocations {
374                    if let RelocationTarget::CustomSection(index) = reloc.reloc_target {
375                        reloc.reloc_target = RelocationTarget::CustomSection(
376                            SectionIndex::from_u32(first_section + index.as_u32()),
377                        )
378                    }
379
380                    if reloc.kind.needs_got() {
381                        got_targets.insert(reloc.reloc_target);
382                    }
383                }
384                compiled_function.compiled_function
385            })
386            .collect::<PrimaryMap<LocalFunctionIndex, _>>();
387
388        let progress = progress.clone();
389        let function_call_trampolines = pool.install(|| {
390            module
391                .signatures
392                .values()
393                .collect::<Vec<_>>()
394                .par_iter()
395                .map_init(
396                    || {
397                        let target_machine = self.config().target_machine(target);
398                        FuncTrampoline::new(target_machine, target.triple().clone(), binary_format)
399                            .unwrap()
400                    },
401                    |func_trampoline, sig| {
402                        let trampoline =
403                            func_trampoline.trampoline(sig, self.config(), "", compile_info);
404                        if let Some(progress) = progress.as_ref() {
405                            progress.notify_steps(WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE)?;
406                        }
407                        trampoline
408                    },
409                )
410                .collect::<Vec<_>>()
411                .into_iter()
412                .collect::<Result<PrimaryMap<_, _>, CompileError>>()
413        })?;
414
415        // TODO: I removed the parallel processing of dynamic trampolines because we're passing
416        // the sections bytes and relocations directly into the trampoline generation function.
417        // We can move that logic out and re-enable parallel processing. Hopefully, there aren't
418        // enough dynamic trampolines to actually cause a noticeable performance degradation.
419        let dynamic_function_trampolines = {
420            let progress = progress.clone();
421            let target_machine = self.config().target_machine(target);
422            let func_trampoline =
423                FuncTrampoline::new(target_machine, target.triple().clone(), binary_format)
424                    .unwrap();
425            module
426                .imported_function_types()
427                .collect::<Vec<_>>()
428                .into_iter()
429                .enumerate()
430                .map(|(index, func_type)| {
431                    let trampoline = func_trampoline.dynamic_trampoline(
432                        &func_type,
433                        self.config(),
434                        "",
435                        index as u32,
436                        &mut module_custom_sections,
437                        &mut eh_frame_section_bytes,
438                        &mut eh_frame_section_relocations,
439                        &mut compact_unwind_section_bytes,
440                        &mut compact_unwind_section_relocations,
441                        &module_hash,
442                    )?;
443                    if let Some(progress) = progress.as_ref() {
444                        progress.notify_steps(WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE)?;
445                    }
446                    Ok(trampoline)
447                })
448                .collect::<Vec<_>>()
449                .into_iter()
450                .collect::<Result<PrimaryMap<_, _>, CompileError>>()?
451        };
452
453        let mut unwind_info = UnwindInfo::default();
454
455        if !eh_frame_section_bytes.is_empty() {
456            let eh_frame_idx = SectionIndex::from_u32(module_custom_sections.len() as u32);
457            module_custom_sections.push(CustomSection {
458                protection: CustomSectionProtection::Read,
459                alignment: None,
460                bytes: SectionBody::new_with_vec(eh_frame_section_bytes),
461                relocations: eh_frame_section_relocations,
462            });
463            unwind_info.eh_frame = Some(eh_frame_idx);
464        }
465
466        if !compact_unwind_section_bytes.is_empty() {
467            let cu_index = SectionIndex::from_u32(module_custom_sections.len() as u32);
468            module_custom_sections.push(CustomSection {
469                protection: CustomSectionProtection::Read,
470                alignment: None,
471                bytes: SectionBody::new_with_vec(compact_unwind_section_bytes),
472                relocations: compact_unwind_section_relocations,
473            });
474            unwind_info.compact_unwind = Some(cu_index);
475        }
476
477        let mut got = wasmer_compiler::types::function::GOT::empty();
478
479        if !got_targets.is_empty() {
480            let pointer_width = target
481                .triple()
482                .pointer_width()
483                .map_err(|_| CompileError::Codegen("Could not get pointer width".to_string()))?;
484
485            let got_entry_size = match pointer_width {
486                target_lexicon::PointerWidth::U64 => 8,
487                target_lexicon::PointerWidth::U32 => 4,
488                target_lexicon::PointerWidth::U16 => todo!(),
489            };
490
491            let got_entry_reloc_kind = match pointer_width {
492                target_lexicon::PointerWidth::U64 => RelocationKind::Abs8,
493                target_lexicon::PointerWidth::U32 => RelocationKind::Abs4,
494                target_lexicon::PointerWidth::U16 => todo!(),
495            };
496
497            let got_data: Vec<u8> = vec![0; got_targets.len() * got_entry_size];
498            let mut got_relocs = vec![];
499
500            for (i, target) in got_targets.into_iter().enumerate() {
501                got_relocs.push(wasmer_compiler::types::relocation::Relocation {
502                    kind: got_entry_reloc_kind,
503                    reloc_target: target,
504                    offset: (i * got_entry_size) as u32,
505                    addend: 0,
506                });
507            }
508
509            let got_idx = SectionIndex::from_u32(module_custom_sections.len() as u32);
510            module_custom_sections.push(CustomSection {
511                protection: CustomSectionProtection::Read,
512                alignment: None,
513                bytes: SectionBody::new_with_vec(got_data),
514                relocations: got_relocs,
515            });
516            got.index = Some(got_idx);
517        };
518
519        Ok(Compilation {
520            functions,
521            custom_sections: module_custom_sections,
522            function_call_trampolines,
523            dynamic_function_trampolines,
524            unwind_info,
525            got,
526        })
527    }
528
529    fn with_opts(
530        &mut self,
531        _suggested_compiler_opts: &wasmer_types::target::UserCompilerOptimizations,
532    ) -> Result<(), CompileError> {
533        Ok(())
534    }
535}