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