wasmer_compiler_cranelift/
compiler.rs

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