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