wasmer_compiler_cranelift/
compiler.rs

1//! Support for compiling with Cranelift.
2
3#[cfg(feature = "unwind")]
4use crate::dwarf::WriterRelocate;
5
6#[cfg(feature = "unwind")]
7use crate::eh::{build_function_lsda, build_lsda_section, build_tag_section};
8
9#[cfg(feature = "unwind")]
10use crate::translator::CraneliftUnwindInfo;
11use crate::{
12    address_map::get_function_address_map,
13    config::Cranelift,
14    func_environ::{FuncEnvironment, get_function_name},
15    trampoline::{
16        FunctionBuilderContext, make_trampoline_dynamic_function, make_trampoline_function_call,
17    },
18    translator::{
19        FuncTranslator, compiled_function_unwind_info, irlibcall_to_libcall,
20        irreloc_to_relocationkind, signature_to_cranelift_ir,
21    },
22};
23use cranelift_codegen::{
24    Context, FinalizedMachReloc, FinalizedRelocTarget, MachTrap,
25    ir::{self, ExternalName, UserFuncName},
26};
27
28#[cfg(feature = "unwind")]
29use gimli::{
30    constants::DW_EH_PE_absptr,
31    write::{Address, EhFrame, FrameTable, Writer},
32};
33
34#[cfg(feature = "rayon")]
35use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
36#[cfg(feature = "unwind")]
37use std::collections::HashMap;
38use std::sync::Arc;
39
40#[cfg(feature = "unwind")]
41use wasmer_compiler::types::{section::SectionIndex, unwind::CompiledFunctionUnwindInfo};
42use wasmer_compiler::{
43    Compiler, FunctionBinaryReader, FunctionBodyData, MiddlewareBinaryReader, ModuleMiddleware,
44    ModuleMiddlewareChain, ModuleTranslationState,
45    types::{
46        function::{
47            Compilation, CompiledFunction, CompiledFunctionFrameInfo, FunctionBody, UnwindInfo,
48        },
49        module::CompileModuleInfo,
50        relocation::{Relocation, RelocationTarget},
51    },
52};
53#[cfg(feature = "unwind")]
54use wasmer_types::entity::EntityRef;
55use wasmer_types::entity::PrimaryMap;
56#[cfg(feature = "unwind")]
57use wasmer_types::target::CallingConvention;
58use wasmer_types::target::Target;
59use wasmer_types::{
60    CompileError, FunctionIndex, LocalFunctionIndex, ModuleInfo, SignatureIndex, TrapCode,
61    TrapInformation,
62};
63
64/// A compiler that compiles a WebAssembly module with Cranelift, translating the Wasm to Cranelift IR,
65/// optimizing it and then translating to assembly.
66#[derive(Debug)]
67pub struct CraneliftCompiler {
68    config: Cranelift,
69}
70
71impl CraneliftCompiler {
72    /// Creates a new Cranelift compiler
73    pub fn new(config: Cranelift) -> Self {
74        Self { config }
75    }
76
77    /// Gets the WebAssembly features for this Compiler
78    pub fn config(&self) -> &Cranelift {
79        &self.config
80    }
81
82    // Helper function to create an easy scope boundary for the thread pool used
83    // in [`Self::compile_module`].
84    fn compile_module_internal(
85        &self,
86        target: &Target,
87        compile_info: &CompileModuleInfo,
88        module_translation_state: &ModuleTranslationState,
89        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
90    ) -> Result<Compilation, CompileError> {
91        let isa = self
92            .config()
93            .isa(target)
94            .map_err(|error| CompileError::Codegen(error.to_string()))?;
95        let frontend_config = isa.frontend_config();
96        #[cfg(feature = "unwind")]
97        let pointer_bytes = frontend_config.pointer_bytes();
98        let memory_styles = &compile_info.memory_styles;
99        let table_styles = &compile_info.table_styles;
100        let module = &compile_info.module;
101        let signatures = module
102            .signatures
103            .iter()
104            .map(|(_sig_index, func_type)| signature_to_cranelift_ir(func_type, frontend_config))
105            .collect::<PrimaryMap<SignatureIndex, ir::Signature>>();
106
107        // Generate the frametable
108        #[cfg(feature = "unwind")]
109        let dwarf_frametable = if function_body_inputs.is_empty() {
110            // If we have no function body inputs, we don't need to
111            // construct the `FrameTable`. Constructing it, with empty
112            // FDEs will cause some issues in Linux.
113            None
114        } else {
115            match target.triple().default_calling_convention() {
116                Ok(CallingConvention::SystemV) => match isa.create_systemv_cie() {
117                    Some(mut cie) => {
118                        cie.personality = Some((
119                            DW_EH_PE_absptr,
120                            Address::Symbol {
121                                symbol: WriterRelocate::PERSONALITY_SYMBOL,
122                                addend: 0,
123                            },
124                        ));
125                        cie.lsda_encoding = Some(DW_EH_PE_absptr);
126                        let mut dwarf_frametable = FrameTable::default();
127                        let cie_id = dwarf_frametable.add_cie(cie);
128                        Some((dwarf_frametable, cie_id))
129                    }
130                    // Even though we are in a SystemV system, Cranelift doesn't support it
131                    None => None,
132                },
133                _ => None,
134            }
135        };
136
137        let compile_function =
138            |func_translator: &mut FuncTranslator,
139             (i, input): (&LocalFunctionIndex, &FunctionBodyData)| {
140                let func_index = module.func_index(*i);
141                let mut context = Context::new();
142                let mut func_env = FuncEnvironment::new(
143                    isa.frontend_config(),
144                    module,
145                    &signatures,
146                    memory_styles,
147                    table_styles,
148                );
149                context.func.name = match get_function_name(&mut context.func, func_index) {
150                    ExternalName::User(nameref) => {
151                        if context.func.params.user_named_funcs().is_valid(nameref) {
152                            let name = &context.func.params.user_named_funcs()[nameref];
153                            UserFuncName::User(name.clone())
154                        } else {
155                            UserFuncName::default()
156                        }
157                    }
158                    ExternalName::TestCase(testcase) => UserFuncName::Testcase(testcase),
159                    _ => UserFuncName::default(),
160                };
161                context.func.signature = signatures[module.functions[func_index]].clone();
162                // if generate_debug_info {
163                //     context.func.collect_debug_info();
164                // }
165
166                let mut reader =
167                    MiddlewareBinaryReader::new_with_offset(input.data, input.module_offset);
168                reader.set_middleware_chain(
169                    self.config
170                        .middlewares
171                        .generate_function_middleware_chain(*i),
172                );
173
174                func_translator.translate(
175                    module_translation_state,
176                    &mut reader,
177                    &mut context.func,
178                    &mut func_env,
179                    *i,
180                )?;
181
182                if let Some(callbacks) = self.config.callbacks.as_ref() {
183                    use wasmer_compiler::misc::CompiledKind;
184
185                    callbacks.preopt_ir(
186                        &CompiledKind::Local(*i, compile_info.module.get_function_name(func_index)),
187                        context.func.display().to_string().as_bytes(),
188                    );
189                }
190
191                let mut code_buf: Vec<u8> = Vec::new();
192                let mut ctrl_plane = Default::default();
193                let func_name_map = context.func.params.user_named_funcs().clone();
194                let result = context
195                    .compile(&*isa, &mut ctrl_plane)
196                    .map_err(|error| CompileError::Codegen(format!("{error:#?}")))?;
197                code_buf.extend_from_slice(result.code_buffer());
198
199                if let Some(callbacks) = self.config.callbacks.as_ref() {
200                    use wasmer_compiler::misc::CompiledKind;
201
202                    callbacks.obj_memory_buffer(
203                        &CompiledKind::Local(*i, compile_info.module.get_function_name(func_index)),
204                        &code_buf,
205                    );
206                    callbacks.asm_memory_buffer(
207                        &CompiledKind::Local(*i, compile_info.module.get_function_name(func_index)),
208                        target.triple().architecture,
209                        &code_buf,
210                    )?;
211                }
212
213                let func_relocs = result
214                    .buffer
215                    .relocs()
216                    .iter()
217                    .map(|r| mach_reloc_to_reloc(module, &func_name_map, r))
218                    .collect::<Vec<_>>();
219
220                let traps = result
221                    .buffer
222                    .traps()
223                    .iter()
224                    .map(mach_trap_to_trap)
225                    .collect::<Vec<_>>();
226
227                #[cfg(feature = "unwind")]
228                let function_lsda = if dwarf_frametable.is_some() {
229                    build_function_lsda(
230                        result.buffer.call_sites(),
231                        result.buffer.data().len(),
232                        pointer_bytes,
233                    )
234                } else {
235                    None
236                };
237
238                #[cfg(not(feature = "unwind"))]
239                let function_lsda = ();
240
241                let (unwind_info, fde) = match compiled_function_unwind_info(&*isa, &context)? {
242                    #[cfg(feature = "unwind")]
243                    CraneliftUnwindInfo::Fde(fde) => {
244                        if dwarf_frametable.is_some() {
245                            let fde = fde.to_fde(Address::Symbol {
246                                // The symbol is the kind of relocation.
247                                // "0" is used for functions
248                                symbol: WriterRelocate::FUNCTION_SYMBOL,
249                                // We use the addend as a way to specify the
250                                // function index
251                                addend: i.index() as _,
252                            });
253                            // The unwind information is inserted into the dwarf section
254                            (Some(CompiledFunctionUnwindInfo::Dwarf), Some(fde))
255                        } else {
256                            (None, None)
257                        }
258                    }
259                    #[cfg(feature = "unwind")]
260                    other => (other.maybe_into_to_windows_unwind(), None),
261
262                    // This is a bit hacky, but necessary since gimli is not
263                    // available when the "unwind" feature is disabled.
264                    #[cfg(not(feature = "unwind"))]
265                    other => (other.maybe_into_to_windows_unwind(), None::<()>),
266                };
267
268                let range = reader.range();
269                let address_map = get_function_address_map(&context, range, code_buf.len());
270
271                Ok((
272                    CompiledFunction {
273                        body: FunctionBody {
274                            body: code_buf,
275                            unwind_info,
276                        },
277                        relocations: func_relocs,
278                        frame_info: CompiledFunctionFrameInfo { address_map, traps },
279                    },
280                    fde,
281                    function_lsda,
282                ))
283            };
284
285        #[cfg_attr(not(feature = "unwind"), allow(unused_mut))]
286        let mut custom_sections = PrimaryMap::new();
287
288        #[cfg(not(feature = "rayon"))]
289        let mut func_translator = FuncTranslator::new();
290        #[cfg(not(feature = "rayon"))]
291        let results = function_body_inputs
292            .iter()
293            .collect::<Vec<(LocalFunctionIndex, &FunctionBodyData<'_>)>>()
294            .into_iter()
295            .map(|(i, input)| compile_function(&mut func_translator, (&i, input)))
296            .collect::<Result<Vec<_>, CompileError>>()?;
297        #[cfg(feature = "rayon")]
298        let results = function_body_inputs
299            .iter()
300            .collect::<Vec<(LocalFunctionIndex, &FunctionBodyData<'_>)>>()
301            .par_iter()
302            .map_init(FuncTranslator::new, |func_translator, &(i, input)| {
303                compile_function(func_translator, (&i, input))
304            })
305            .collect::<Result<Vec<_>, CompileError>>()?;
306
307        let mut functions = Vec::with_capacity(function_body_inputs.len());
308        let mut fdes = Vec::with_capacity(function_body_inputs.len());
309        let mut lsda_data = Vec::with_capacity(function_body_inputs.len());
310
311        for (func, fde, lsda) in results {
312            functions.push(func);
313            fdes.push(fde);
314            lsda_data.push(lsda);
315        }
316
317        #[cfg(feature = "unwind")]
318        let (_tag_section_index, lsda_section_index, function_lsda_offsets) =
319            if dwarf_frametable.is_some() {
320                let mut tag_section_index = None;
321                let mut tag_offsets = HashMap::new();
322                if let Some((tag_section, offsets)) = build_tag_section(&lsda_data) {
323                    custom_sections.push(tag_section);
324                    tag_section_index = Some(SectionIndex::new(custom_sections.len() - 1));
325                    tag_offsets = offsets;
326                }
327                let lsda_vec = lsda_data;
328                let (lsda_section, offsets_per_function) =
329                    build_lsda_section(lsda_vec, pointer_bytes, &tag_offsets, tag_section_index);
330                let mut lsda_section_index = None;
331                if let Some(section) = lsda_section {
332                    custom_sections.push(section);
333                    lsda_section_index = Some(SectionIndex::new(custom_sections.len() - 1));
334                }
335                (tag_section_index, lsda_section_index, offsets_per_function)
336            } else {
337                (None, None, vec![None; functions.len()])
338            };
339
340        #[cfg_attr(not(feature = "unwind"), allow(unused_mut))]
341        let mut unwind_info = UnwindInfo::default();
342
343        #[cfg(feature = "unwind")]
344        if let Some((mut dwarf_frametable, cie_id)) = dwarf_frametable {
345            for (func_idx, fde_opt) in fdes.into_iter().enumerate() {
346                if let Some(mut fde) = fde_opt {
347                    let has_lsda = function_lsda_offsets
348                        .get(func_idx)
349                        .and_then(|v| *v)
350                        .is_some();
351                    let lsda_address = if has_lsda {
352                        debug_assert!(
353                            lsda_section_index.is_some(),
354                            "LSDA offsets require an LSDA section"
355                        );
356                        if lsda_section_index.is_some() {
357                            let symbol =
358                                WriterRelocate::lsda_symbol(LocalFunctionIndex::new(func_idx));
359                            Address::Symbol { symbol, addend: 0 }
360                        } else {
361                            Address::Constant(0)
362                        }
363                    } else {
364                        Address::Constant(0)
365                    };
366                    fde.lsda = Some(lsda_address);
367                    dwarf_frametable.add_fde(cie_id, fde);
368                }
369            }
370
371            let mut writer = WriterRelocate::new(target.triple().endianness().ok());
372            if let Some(lsda_section_index) = lsda_section_index {
373                for (func_idx, offset) in function_lsda_offsets.iter().enumerate() {
374                    if let Some(offset) = offset {
375                        writer.register_lsda_symbol(
376                            WriterRelocate::lsda_symbol(LocalFunctionIndex::new(func_idx)),
377                            RelocationTarget::CustomSection(lsda_section_index),
378                            *offset,
379                        );
380                    }
381                }
382            }
383
384            let mut eh_frame = EhFrame(writer);
385            dwarf_frametable.write_eh_frame(&mut eh_frame).unwrap();
386            eh_frame.write(&[0, 0, 0, 0]).unwrap(); // Write a 0 length at the end of the table.
387
388            let eh_frame_section = eh_frame.0.into_section();
389            custom_sections.push(eh_frame_section);
390            unwind_info.eh_frame = Some(SectionIndex::new(custom_sections.len() - 1));
391        };
392
393        #[cfg(not(feature = "unwind"))]
394        let _ = fdes;
395
396        // function call trampolines (only for local functions, by signature)
397        #[cfg(not(feature = "rayon"))]
398        let mut cx = FunctionBuilderContext::new();
399        #[cfg(not(feature = "rayon"))]
400        let function_call_trampolines = module
401            .signatures
402            .values()
403            .collect::<Vec<_>>()
404            .into_iter()
405            .map(|sig| {
406                make_trampoline_function_call(
407                    &self.config().callbacks,
408                    &*isa,
409                    target.triple().architecture,
410                    &mut cx,
411                    sig,
412                )
413            })
414            .collect::<Result<Vec<FunctionBody>, CompileError>>()?
415            .into_iter()
416            .collect();
417        #[cfg(feature = "rayon")]
418        let function_call_trampolines = module
419            .signatures
420            .values()
421            .collect::<Vec<_>>()
422            .par_iter()
423            .map_init(FunctionBuilderContext::new, |cx, sig| {
424                make_trampoline_function_call(
425                    &self.config().callbacks,
426                    &*isa,
427                    target.triple().architecture,
428                    cx,
429                    sig,
430                )
431            })
432            .collect::<Result<Vec<FunctionBody>, CompileError>>()?
433            .into_iter()
434            .collect();
435
436        use wasmer_types::VMOffsets;
437        let offsets = VMOffsets::new_for_trampolines(frontend_config.pointer_bytes());
438        // dynamic function trampolines (only for imported functions)
439        #[cfg(not(feature = "rayon"))]
440        let mut cx = FunctionBuilderContext::new();
441        #[cfg(not(feature = "rayon"))]
442        let dynamic_function_trampolines = module
443            .imported_function_types()
444            .collect::<Vec<_>>()
445            .into_iter()
446            .map(|func_type| {
447                make_trampoline_dynamic_function(
448                    &self.config().callbacks,
449                    &*isa,
450                    target.triple().architecture,
451                    &offsets,
452                    &mut cx,
453                    &func_type,
454                )
455            })
456            .collect::<Result<Vec<_>, CompileError>>()?
457            .into_iter()
458            .collect();
459        #[cfg(feature = "rayon")]
460        let dynamic_function_trampolines = module
461            .imported_function_types()
462            .collect::<Vec<_>>()
463            .par_iter()
464            .map_init(FunctionBuilderContext::new, |cx, func_type| {
465                make_trampoline_dynamic_function(
466                    &self.config().callbacks,
467                    &*isa,
468                    target.triple().architecture,
469                    &offsets,
470                    cx,
471                    func_type,
472                )
473            })
474            .collect::<Result<Vec<_>, CompileError>>()?
475            .into_iter()
476            .collect();
477
478        let got = wasmer_compiler::types::function::GOT::empty();
479
480        Ok(Compilation {
481            functions: functions.into_iter().collect(),
482            custom_sections,
483            function_call_trampolines,
484            dynamic_function_trampolines,
485            unwind_info,
486            got,
487        })
488    }
489}
490
491impl Compiler for CraneliftCompiler {
492    fn name(&self) -> &str {
493        "cranelift"
494    }
495
496    fn get_perfmap_enabled(&self) -> bool {
497        self.config.enable_perfmap
498    }
499
500    fn deterministic_id(&self) -> String {
501        String::from("cranelift")
502    }
503
504    /// Get the middlewares for this compiler
505    fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>] {
506        &self.config.middlewares
507    }
508
509    /// Compile the module using Cranelift, producing a compilation result with
510    /// associated relocations.
511    fn compile_module(
512        &self,
513        target: &Target,
514        compile_info: &CompileModuleInfo,
515        module_translation_state: &ModuleTranslationState,
516        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
517    ) -> Result<Compilation, CompileError> {
518        #[cfg(feature = "rayon")]
519        {
520            let num_threads = self.config.num_threads.get();
521            let pool = rayon::ThreadPoolBuilder::new()
522                .num_threads(num_threads)
523                .build()
524                .unwrap();
525
526            pool.install(|| {
527                self.compile_module_internal(
528                    target,
529                    compile_info,
530                    module_translation_state,
531                    function_body_inputs,
532                )
533            })
534        }
535
536        #[cfg(not(feature = "rayon"))]
537        {
538            self.compile_module_internal(
539                target,
540                compile_info,
541                module_translation_state,
542                function_body_inputs,
543            )
544        }
545    }
546}
547
548fn mach_reloc_to_reloc(
549    module: &ModuleInfo,
550    func_index_map: &cranelift_entity::PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
551    reloc: &FinalizedMachReloc,
552) -> Relocation {
553    let FinalizedMachReloc {
554        offset,
555        kind,
556        addend,
557        target,
558    } = &reloc;
559    let name = match target {
560        FinalizedRelocTarget::ExternalName(external_name) => external_name,
561        FinalizedRelocTarget::Func(_) => {
562            unimplemented!("relocations to offset in the same function are not yet supported")
563        }
564    };
565    let reloc_target: RelocationTarget = if let ExternalName::User(extname_ref) = name {
566        let func_index = func_index_map[*extname_ref].index;
567        //debug_assert_eq!(namespace, 0);
568        RelocationTarget::LocalFunc(
569            module
570                .local_func_index(FunctionIndex::from_u32(func_index))
571                .expect("The provided function should be local"),
572        )
573    } else if let ExternalName::LibCall(libcall) = name {
574        RelocationTarget::LibCall(irlibcall_to_libcall(*libcall))
575    } else {
576        panic!("unrecognized external target")
577    };
578    Relocation {
579        kind: irreloc_to_relocationkind(*kind),
580        reloc_target,
581        offset: *offset,
582        addend: *addend,
583    }
584}
585
586fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation {
587    let &MachTrap { offset, code } = trap;
588    TrapInformation {
589        code_offset: offset,
590        trap_code: translate_ir_trapcode(code),
591    }
592}
593
594/// Translates the Cranelift IR TrapCode into generic Trap Code
595fn translate_ir_trapcode(trap: ir::TrapCode) -> TrapCode {
596    if trap == ir::TrapCode::STACK_OVERFLOW {
597        TrapCode::StackOverflow
598    } else if trap == ir::TrapCode::HEAP_OUT_OF_BOUNDS {
599        TrapCode::HeapAccessOutOfBounds
600    } else if trap == crate::TRAP_HEAP_MISALIGNED {
601        TrapCode::UnalignedAtomic
602    } else if trap == crate::TRAP_TABLE_OUT_OF_BOUNDS {
603        TrapCode::TableAccessOutOfBounds
604    } else if trap == crate::TRAP_INDIRECT_CALL_TO_NULL {
605        TrapCode::IndirectCallToNull
606    } else if trap == crate::TRAP_BAD_SIGNATURE {
607        TrapCode::BadSignature
608    } else if trap == ir::TrapCode::INTEGER_OVERFLOW {
609        TrapCode::IntegerOverflow
610    } else if trap == ir::TrapCode::INTEGER_DIVISION_BY_ZERO {
611        TrapCode::IntegerDivisionByZero
612    } else if trap == ir::TrapCode::BAD_CONVERSION_TO_INTEGER {
613        TrapCode::BadConversionToInteger
614    } else if trap == crate::TRAP_UNREACHABLE {
615        TrapCode::UnreachableCodeReached
616    } else if trap == crate::TRAP_INTERRUPT {
617        unimplemented!("Interrupts not supported")
618    } else if trap == crate::TRAP_NULL_REFERENCE || trap == crate::TRAP_NULL_I31_REF {
619        unimplemented!("Null reference not supported")
620    } else {
621        unimplemented!("Trap code {trap:?} not supported")
622    }
623}