wasmer_compiler_singlepass/
compiler.rs

1//! Support for compiling with Singlepass.
2// Allow unused imports while developing.
3#![allow(unused_imports, dead_code)]
4
5use crate::codegen::FuncGen;
6use crate::config::{self, Singlepass};
7#[cfg(feature = "unwind")]
8use crate::dwarf::WriterRelocate;
9use crate::machine::Machine;
10use crate::machine::{
11    gen_import_call_trampoline, gen_std_dynamic_import_trampoline, gen_std_trampoline,
12};
13use crate::machine_arm64::MachineARM64;
14use crate::machine_riscv::MachineRiscv;
15use crate::machine_x64::MachineX86_64;
16#[cfg(feature = "unwind")]
17use crate::unwind::{UnwindFrame, create_systemv_cie};
18use enumset::EnumSet;
19#[cfg(feature = "unwind")]
20use gimli::write::{EhFrame, FrameTable, Writer};
21#[cfg(feature = "rayon")]
22use rayon::prelude::{IntoParallelIterator, ParallelIterator};
23use std::collections::HashMap;
24use std::sync::Arc;
25use wasmer_compiler::misc::{CompiledKind, save_assembly_to_file, types_to_signature};
26use wasmer_compiler::progress::ProgressContext;
27use wasmer_compiler::{
28    Compiler, CompilerConfig, FunctionBinaryReader, FunctionBodyData, MiddlewareBinaryReader,
29    ModuleMiddleware, ModuleMiddlewareChain, ModuleTranslationState,
30    types::{
31        function::{Compilation, CompiledFunction, FunctionBody, UnwindInfo},
32        module::CompileModuleInfo,
33        section::SectionIndex,
34    },
35};
36use wasmer_types::entity::{EntityRef, PrimaryMap};
37use wasmer_types::target::{Architecture, CallingConvention, CpuFeature, Target};
38use wasmer_types::{
39    CompilationProgressCallback, CompileError, FunctionIndex, FunctionType, LocalFunctionIndex,
40    MemoryIndex, ModuleInfo, TableIndex, TrapCode, TrapInformation, Type, VMOffsets,
41};
42
43/// A compiler that compiles a WebAssembly module with Singlepass.
44/// It does the compilation in one pass
45#[derive(Debug)]
46pub struct SinglepassCompiler {
47    config: Singlepass,
48}
49
50impl SinglepassCompiler {
51    /// Creates a new Singlepass compiler
52    pub fn new(config: Singlepass) -> Self {
53        Self { config }
54    }
55
56    /// Gets the config for this Compiler
57    fn config(&self) -> &Singlepass {
58        &self.config
59    }
60
61    fn compile_module_internal(
62        &self,
63        target: &Target,
64        compile_info: &CompileModuleInfo,
65        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
66        progress_callback: Option<&CompilationProgressCallback>,
67    ) -> Result<Compilation, CompileError> {
68        let arch = target.triple().architecture;
69        match arch {
70            Architecture::X86_64 => {}
71            Architecture::Aarch64(_) => {}
72            Architecture::Riscv64(_) => {}
73            _ => {
74                return Err(CompileError::UnsupportedTarget(
75                    target.triple().architecture.to_string(),
76                ));
77            }
78        };
79
80        let calling_convention = match target.triple().default_calling_convention() {
81            Ok(CallingConvention::WindowsFastcall) => CallingConvention::WindowsFastcall,
82            Ok(CallingConvention::SystemV) => CallingConvention::SystemV,
83            Ok(CallingConvention::AppleAarch64) => CallingConvention::AppleAarch64,
84            _ => match target.triple().architecture {
85                Architecture::Riscv64(_) => CallingConvention::SystemV,
86                _ => {
87                    return Err(CompileError::UnsupportedTarget(
88                        "Unsupported Calling convention for Singlepass compiler".to_string(),
89                    ));
90                }
91            },
92        };
93
94        let module = &compile_info.module;
95        let total_functions = function_body_inputs.len() as u64;
96        let total_function_call_trampolines = module.signatures.len() as u64;
97        let total_dynamic_trampolines = module.num_imported_functions as u64;
98        let total_steps =
99            total_functions + total_function_call_trampolines + total_dynamic_trampolines;
100        let progress = progress_callback
101            .cloned()
102            .map(|cb| ProgressContext::new(cb, total_steps, "singlepass::functions"));
103
104        // Generate the frametable
105        #[cfg(feature = "unwind")]
106        let dwarf_frametable = if function_body_inputs.is_empty() {
107            // If we have no function body inputs, we don't need to
108            // construct the `FrameTable`. Constructing it, with empty
109            // FDEs will cause some issues in Linux.
110            None
111        } else {
112            match target.triple().default_calling_convention() {
113                Ok(CallingConvention::SystemV) => {
114                    match create_systemv_cie(target.triple().architecture) {
115                        Some(cie) => {
116                            let mut dwarf_frametable = FrameTable::default();
117                            let cie_id = dwarf_frametable.add_cie(cie);
118                            Some((dwarf_frametable, cie_id))
119                        }
120                        None => None,
121                    }
122                }
123                _ => None,
124            }
125        };
126
127        let memory_styles = &compile_info.memory_styles;
128        let table_styles = &compile_info.table_styles;
129        let vmoffsets = VMOffsets::new(8, &compile_info.module);
130        let module = &compile_info.module;
131        #[cfg_attr(not(feature = "unwind"), allow(unused_mut))]
132        let mut custom_sections: PrimaryMap<SectionIndex, _> = (0..module.num_imported_functions)
133            .map(FunctionIndex::new)
134            .collect::<Vec<_>>()
135            .into_par_iter_if_rayon()
136            .map(|i| {
137                gen_import_call_trampoline(
138                    &vmoffsets,
139                    i,
140                    &module.signatures[module.functions[i]],
141                    target,
142                    calling_convention,
143                )
144            })
145            .collect::<Result<Vec<_>, _>>()?
146            .into_iter()
147            .collect();
148        #[cfg_attr(not(feature = "unwind"), allow(unused_variables))]
149        let (functions, fdes): (Vec<CompiledFunction>, Vec<_>) = function_body_inputs
150            .iter()
151            .collect::<Vec<(LocalFunctionIndex, &FunctionBodyData<'_>)>>()
152            .into_par_iter_if_rayon()
153            .map(|(i, input)| {
154                let middleware_chain = self
155                    .config
156                    .middlewares
157                    .generate_function_middleware_chain(i);
158                let mut reader =
159                    MiddlewareBinaryReader::new_with_offset(input.data, input.module_offset);
160                reader.set_middleware_chain(middleware_chain);
161
162                // This local list excludes arguments.
163                let mut locals = vec![];
164                let num_locals = reader.read_local_count()?;
165                for _ in 0..num_locals {
166                    let (count, ty) = reader.read_local_decl()?;
167                    for _ in 0..count {
168                        locals.push(ty);
169                    }
170                }
171
172                let res = match arch {
173                    Architecture::X86_64 => {
174                        let machine = MachineX86_64::new(Some(target.clone()))?;
175                        let mut generator = FuncGen::new(
176                            module,
177                            &self.config,
178                            &vmoffsets,
179                            memory_styles,
180                            table_styles,
181                            i,
182                            &locals,
183                            machine,
184                            calling_convention,
185                        )?;
186                        while generator.has_control_frames() {
187                            generator.set_srcloc(reader.original_position() as u32);
188                            let op = reader.read_operator()?;
189                            generator.feed_operator(op)?;
190                        }
191
192                        generator.finalize(input, arch)
193                    }
194                    Architecture::Aarch64(_) => {
195                        let machine = MachineARM64::new(Some(target.clone()));
196                        let mut generator = FuncGen::new(
197                            module,
198                            &self.config,
199                            &vmoffsets,
200                            memory_styles,
201                            table_styles,
202                            i,
203                            &locals,
204                            machine,
205                            calling_convention,
206                        )?;
207                        while generator.has_control_frames() {
208                            generator.set_srcloc(reader.original_position() as u32);
209                            let op = reader.read_operator()?;
210                            generator.feed_operator(op)?;
211                        }
212
213                        generator.finalize(input, arch)
214                    }
215                    Architecture::Riscv64(_) => {
216                        let machine = MachineRiscv::new(Some(target.clone()))?;
217                        let mut generator = FuncGen::new(
218                            module,
219                            &self.config,
220                            &vmoffsets,
221                            memory_styles,
222                            table_styles,
223                            i,
224                            &locals,
225                            machine,
226                            calling_convention,
227                        )?;
228                        while generator.has_control_frames() {
229                            generator.set_srcloc(reader.original_position() as u32);
230                            let op = reader.read_operator()?;
231                            generator.feed_operator(op)?;
232                        }
233
234                        generator.finalize(input, arch)
235                    }
236                    _ => unimplemented!(),
237                }?;
238
239                if let Some(progress) = progress.as_ref() {
240                    progress.notify()?;
241                }
242
243                Ok(res)
244            })
245            .collect::<Result<Vec<_>, CompileError>>()?
246            .into_iter()
247            .unzip();
248
249        let module_hash = module.hash_string();
250        let function_call_trampolines = module
251            .signatures
252            .values()
253            .collect::<Vec<_>>()
254            .into_par_iter_if_rayon()
255            .map(|func_type| -> Result<FunctionBody, CompileError> {
256                let body = gen_std_trampoline(func_type, target, calling_convention)?;
257                if let Some(callbacks) = self.config.callbacks.as_ref() {
258                    callbacks.obj_memory_buffer(
259                        &CompiledKind::FunctionCallTrampoline(func_type.clone()),
260                        &module_hash,
261                        &body.body,
262                    );
263                    callbacks.asm_memory_buffer(
264                        &CompiledKind::FunctionCallTrampoline(func_type.clone()),
265                        &module_hash,
266                        arch,
267                        &body.body,
268                        HashMap::new(),
269                    )?;
270                }
271                if let Some(progress) = progress.as_ref() {
272                    progress.notify()?;
273                }
274
275                Ok(body)
276            })
277            .collect::<Result<Vec<_>, _>>()?
278            .into_iter()
279            .collect::<PrimaryMap<_, _>>();
280
281        let dynamic_function_trampolines = module
282            .imported_function_types()
283            .collect::<Vec<_>>()
284            .into_par_iter_if_rayon()
285            .map(|func_type| -> Result<FunctionBody, CompileError> {
286                let body = gen_std_dynamic_import_trampoline(
287                    &vmoffsets,
288                    &func_type,
289                    target,
290                    calling_convention,
291                )?;
292                if let Some(callbacks) = self.config.callbacks.as_ref() {
293                    callbacks.obj_memory_buffer(
294                        &CompiledKind::DynamicFunctionTrampoline(func_type.clone()),
295                        &module_hash,
296                        &body.body,
297                    );
298                    callbacks.asm_memory_buffer(
299                        &CompiledKind::DynamicFunctionTrampoline(func_type.clone()),
300                        &module_hash,
301                        arch,
302                        &body.body,
303                        HashMap::new(),
304                    )?;
305                }
306                if let Some(progress) = progress.as_ref() {
307                    progress.notify()?;
308                }
309                Ok(body)
310            })
311            .collect::<Result<Vec<_>, _>>()?
312            .into_iter()
313            .collect::<PrimaryMap<FunctionIndex, FunctionBody>>();
314
315        #[allow(unused_mut)]
316        let mut unwind_info = UnwindInfo::default();
317
318        #[cfg(feature = "unwind")]
319        if let Some((mut dwarf_frametable, cie_id)) = dwarf_frametable {
320            for fde in fdes.into_iter().flatten() {
321                match fde {
322                    UnwindFrame::SystemV(fde) => dwarf_frametable.add_fde(cie_id, fde),
323                }
324            }
325            let mut eh_frame = EhFrame(WriterRelocate::new(target.triple().endianness().ok()));
326            dwarf_frametable.write_eh_frame(&mut eh_frame).unwrap();
327            eh_frame.write(&[0, 0, 0, 0]).unwrap(); // Write a 0 length at the end of the table.
328
329            let eh_frame_section = eh_frame.0.into_section();
330            custom_sections.push(eh_frame_section);
331            unwind_info.eh_frame = Some(SectionIndex::new(custom_sections.len() - 1))
332        };
333
334        let got = wasmer_compiler::types::function::GOT::empty();
335
336        Ok(Compilation {
337            functions: functions.into_iter().collect(),
338            custom_sections,
339            function_call_trampolines,
340            dynamic_function_trampolines,
341            unwind_info,
342            got,
343        })
344    }
345}
346
347impl Compiler for SinglepassCompiler {
348    fn name(&self) -> &str {
349        "singlepass"
350    }
351
352    fn deterministic_id(&self) -> String {
353        String::from("singlepass")
354    }
355
356    /// Get the middlewares for this compiler
357    fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>] {
358        &self.config.middlewares
359    }
360
361    /// Compile the module using Singlepass, producing a compilation result with
362    /// associated relocations.
363    fn compile_module(
364        &self,
365        target: &Target,
366        compile_info: &CompileModuleInfo,
367        _module_translation: &ModuleTranslationState,
368        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
369        progress_callback: Option<&CompilationProgressCallback>,
370    ) -> Result<Compilation, CompileError> {
371        #[cfg(feature = "rayon")]
372        {
373            let num_threads = self.config.num_threads.get();
374            let pool = rayon::ThreadPoolBuilder::new()
375                .num_threads(num_threads)
376                .build()
377                .map_err(|e| {
378                    CompileError::Codegen(format!("failed to build rayon thread pool: {e}"))
379                })?;
380
381            pool.install(|| {
382                self.compile_module_internal(
383                    target,
384                    compile_info,
385                    function_body_inputs,
386                    progress_callback,
387                )
388            })
389        }
390
391        #[cfg(not(feature = "rayon"))]
392        {
393            self.compile_module_internal(
394                target,
395                compile_info,
396                function_body_inputs,
397                progress_callback,
398            )
399        }
400    }
401
402    fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
403        let used = CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1;
404        cpu_features.intersection(used)
405    }
406}
407
408trait IntoParIterIfRayon {
409    type Output;
410    fn into_par_iter_if_rayon(self) -> Self::Output;
411}
412
413impl<T: Send> IntoParIterIfRayon for Vec<T> {
414    #[cfg(not(feature = "rayon"))]
415    type Output = std::vec::IntoIter<T>;
416    #[cfg(feature = "rayon")]
417    type Output = rayon::vec::IntoIter<T>;
418
419    fn into_par_iter_if_rayon(self) -> Self::Output {
420        #[cfg(not(feature = "rayon"))]
421        return self.into_iter();
422        #[cfg(feature = "rayon")]
423        return self.into_par_iter();
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430    use std::str::FromStr;
431    use target_lexicon::triple;
432    use wasmer_compiler::Features;
433    use wasmer_types::{
434        MemoryStyle, TableStyle,
435        target::{CpuFeature, Triple},
436    };
437
438    fn dummy_compilation_ingredients<'a>() -> (
439        CompileModuleInfo,
440        ModuleTranslationState,
441        PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
442    ) {
443        let compile_info = CompileModuleInfo {
444            features: Features::new(),
445            module: Arc::new(ModuleInfo::new()),
446            memory_styles: PrimaryMap::<MemoryIndex, MemoryStyle>::new(),
447            table_styles: PrimaryMap::<TableIndex, TableStyle>::new(),
448        };
449        let module_translation = ModuleTranslationState::new();
450        let function_body_inputs = PrimaryMap::<LocalFunctionIndex, FunctionBodyData<'_>>::new();
451        (compile_info, module_translation, function_body_inputs)
452    }
453
454    #[test]
455    fn errors_for_unsupported_targets() {
456        let compiler = SinglepassCompiler::new(Singlepass::default());
457
458        // Compile for 32bit Linux
459        let linux32 = Target::new(triple!("i686-unknown-linux-gnu"), CpuFeature::for_host());
460        let (info, translation, inputs) = dummy_compilation_ingredients();
461        let result = compiler.compile_module(&linux32, &info, &translation, inputs, None);
462        match result.unwrap_err() {
463            CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"),
464            error => panic!("Unexpected error: {error:?}"),
465        };
466
467        // Compile for win32
468        let win32 = Target::new(triple!("i686-pc-windows-gnu"), CpuFeature::for_host());
469        let (info, translation, inputs) = dummy_compilation_ingredients();
470        let result = compiler.compile_module(&win32, &info, &translation, inputs, None);
471        match result.unwrap_err() {
472            CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), // Windows should be checked before architecture
473            error => panic!("Unexpected error: {error:?}"),
474        };
475    }
476
477    #[test]
478    fn errors_for_unsuported_cpufeatures() {
479        let compiler = SinglepassCompiler::new(Singlepass::default());
480        let mut features =
481            CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1;
482        // simple test
483        assert!(
484            compiler.get_cpu_features_used(&features).is_subset(
485                CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1
486            )
487        );
488        // check that an AVX build don't work on SSE4.2 only host
489        assert!(
490            !compiler
491                .get_cpu_features_used(&features)
492                .is_subset(CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1)
493        );
494        // check that having a host with AVX512 doesn't change anything
495        features.insert_all(CpuFeature::AVX512DQ | CpuFeature::AVX512F);
496        assert!(
497            compiler.get_cpu_features_used(&features).is_subset(
498                CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1
499            )
500        );
501    }
502}