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