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(
221                            Some(target.clone()),
222                            self.config.allow_experimental_unaligned_memory_accesses,
223                        )?;
224                        let mut generator = FuncGen::new(
225                            module,
226                            &self.config,
227                            &vmoffsets,
228                            memory_styles,
229                            table_styles,
230                            i,
231                            &locals,
232                            machine,
233                            calling_convention,
234                        )?;
235                        while generator.has_control_frames() {
236                            generator.set_srcloc(reader.original_position() as u32);
237                            let op = reader.read_operator()?;
238                            generator.feed_operator(op)?;
239                        }
240
241                        generator.finalize(input, arch)
242                    }
243                    _ => unimplemented!(),
244                }?;
245
246                if let Some(progress) = progress.as_ref() {
247                    progress.notify_steps(input.data.len() as u64)?;
248                }
249
250                Ok(res)
251            })
252            .collect::<Result<Vec<_>, CompileError>>()?
253            .into_iter()
254            .unzip();
255
256        let module_hash = module.hash_string();
257        let function_call_trampolines = module
258            .signatures
259            .values()
260            .collect::<Vec<_>>()
261            .into_par_iter_if_rayon()
262            .map(|func_type| -> Result<FunctionBody, CompileError> {
263                let body = gen_std_trampoline(func_type, target, calling_convention)?;
264                if let Some(callbacks) = self.config.callbacks.as_ref() {
265                    callbacks.obj_memory_buffer(
266                        &CompiledKind::FunctionCallTrampoline(func_type.clone()),
267                        &module_hash,
268                        &body.body,
269                    );
270                    callbacks.asm_memory_buffer(
271                        &CompiledKind::FunctionCallTrampoline(func_type.clone()),
272                        &module_hash,
273                        arch,
274                        &body.body,
275                        HashMap::new(),
276                    )?;
277                }
278                if let Some(progress) = progress.as_ref() {
279                    progress.notify_steps(WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE)?;
280                }
281
282                Ok(body)
283            })
284            .collect::<Result<Vec<_>, _>>()?
285            .into_iter()
286            .collect::<PrimaryMap<_, _>>();
287
288        let dynamic_function_trampolines = module
289            .imported_function_types()
290            .collect::<Vec<_>>()
291            .into_par_iter_if_rayon()
292            .map(|func_type| -> Result<FunctionBody, CompileError> {
293                let body = gen_std_dynamic_import_trampoline(
294                    &vmoffsets,
295                    &func_type,
296                    target,
297                    calling_convention,
298                )?;
299                if let Some(callbacks) = self.config.callbacks.as_ref() {
300                    callbacks.obj_memory_buffer(
301                        &CompiledKind::DynamicFunctionTrampoline(func_type.clone()),
302                        &module_hash,
303                        &body.body,
304                    );
305                    callbacks.asm_memory_buffer(
306                        &CompiledKind::DynamicFunctionTrampoline(func_type.clone()),
307                        &module_hash,
308                        arch,
309                        &body.body,
310                        HashMap::new(),
311                    )?;
312                }
313                if let Some(progress) = progress.as_ref() {
314                    progress.notify_steps(WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE)?;
315                }
316                Ok(body)
317            })
318            .collect::<Result<Vec<_>, _>>()?
319            .into_iter()
320            .collect::<PrimaryMap<FunctionIndex, FunctionBody>>();
321
322        #[allow(unused_mut)]
323        let mut unwind_info = UnwindInfo::default();
324
325        #[cfg(feature = "unwind")]
326        if let Some((mut dwarf_frametable, cie_id)) = dwarf_frametable {
327            for fde in fdes.into_iter().flatten() {
328                match fde {
329                    UnwindFrame::SystemV(fde) => dwarf_frametable.add_fde(cie_id, fde),
330                }
331            }
332            let mut eh_frame = EhFrame(WriterRelocate::new(target.triple().endianness().ok()));
333            dwarf_frametable.write_eh_frame(&mut eh_frame).unwrap();
334            eh_frame.write(&[0, 0, 0, 0]).unwrap(); // Write a 0 length at the end of the table.
335
336            let eh_frame_section = eh_frame.0.into_section();
337            custom_sections.push(eh_frame_section);
338            unwind_info.eh_frame = Some(SectionIndex::new(custom_sections.len() - 1))
339        };
340
341        let got = wasmer_compiler::types::function::GOT::empty();
342
343        Ok(Compilation {
344            functions: functions.into_iter().collect(),
345            custom_sections,
346            function_call_trampolines,
347            dynamic_function_trampolines,
348            unwind_info,
349            got,
350        })
351    }
352}
353
354impl Compiler for SinglepassCompiler {
355    fn name(&self) -> &str {
356        "singlepass"
357    }
358
359    fn deterministic_id(&self) -> String {
360        String::from("singlepass")
361    }
362
363    /// Get the middlewares for this compiler
364    fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>] {
365        &self.config.middlewares
366    }
367
368    /// Compile the module using Singlepass, producing a compilation result with
369    /// associated relocations.
370    fn compile_module(
371        &self,
372        target: &Target,
373        compile_info: &CompileModuleInfo,
374        _module_translation: &ModuleTranslationState,
375        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
376        progress_callback: Option<&CompilationProgressCallback>,
377    ) -> Result<Compilation, CompileError> {
378        #[cfg(feature = "rayon")]
379        {
380            let num_threads = self.config.num_threads.get();
381            let pool = rayon::ThreadPoolBuilder::new()
382                .num_threads(num_threads)
383                .build()
384                .map_err(|e| {
385                    CompileError::Codegen(format!("failed to build rayon thread pool: {e}"))
386                })?;
387
388            pool.install(|| {
389                self.compile_module_internal(
390                    target,
391                    compile_info,
392                    function_body_inputs,
393                    progress_callback,
394                )
395            })
396        }
397
398        #[cfg(not(feature = "rayon"))]
399        {
400            self.compile_module_internal(
401                target,
402                compile_info,
403                function_body_inputs,
404                progress_callback,
405            )
406        }
407    }
408
409    fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
410        let used = CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1;
411        cpu_features.intersection(used)
412    }
413}
414
415trait IntoParIterIfRayon {
416    type Output;
417    fn into_par_iter_if_rayon(self) -> Self::Output;
418}
419
420impl<T: Send> IntoParIterIfRayon for Vec<T> {
421    #[cfg(not(feature = "rayon"))]
422    type Output = std::vec::IntoIter<T>;
423    #[cfg(feature = "rayon")]
424    type Output = rayon::vec::IntoIter<T>;
425
426    fn into_par_iter_if_rayon(self) -> Self::Output {
427        #[cfg(not(feature = "rayon"))]
428        return self.into_iter();
429        #[cfg(feature = "rayon")]
430        return self.into_par_iter();
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437    use std::str::FromStr;
438    use target_lexicon::triple;
439    use wasmer_compiler::Features;
440    use wasmer_types::{
441        MemoryStyle, TableStyle,
442        target::{CpuFeature, Triple},
443    };
444
445    fn dummy_compilation_ingredients<'a>() -> (
446        CompileModuleInfo,
447        ModuleTranslationState,
448        PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
449    ) {
450        let compile_info = CompileModuleInfo {
451            features: Features::new(),
452            module: Arc::new(ModuleInfo::new()),
453            memory_styles: PrimaryMap::<MemoryIndex, MemoryStyle>::new(),
454            table_styles: PrimaryMap::<TableIndex, TableStyle>::new(),
455        };
456        let module_translation = ModuleTranslationState::new();
457        let function_body_inputs = PrimaryMap::<LocalFunctionIndex, FunctionBodyData<'_>>::new();
458        (compile_info, module_translation, function_body_inputs)
459    }
460
461    #[test]
462    fn errors_for_unsupported_targets() {
463        let compiler = SinglepassCompiler::new(Singlepass::default());
464
465        // Compile for 32bit Linux
466        let linux32 = Target::new(triple!("i686-unknown-linux-gnu"), CpuFeature::for_host());
467        let (info, translation, inputs) = dummy_compilation_ingredients();
468        let result = compiler.compile_module(&linux32, &info, &translation, inputs, None);
469        match result.unwrap_err() {
470            CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"),
471            error => panic!("Unexpected error: {error:?}"),
472        };
473
474        // Compile for win32
475        let win32 = Target::new(triple!("i686-pc-windows-gnu"), CpuFeature::for_host());
476        let (info, translation, inputs) = dummy_compilation_ingredients();
477        let result = compiler.compile_module(&win32, &info, &translation, inputs, None);
478        match result.unwrap_err() {
479            CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), // Windows should be checked before architecture
480            error => panic!("Unexpected error: {error:?}"),
481        };
482    }
483
484    #[test]
485    fn errors_for_unsupported_cpufeatures() {
486        let compiler = SinglepassCompiler::new(Singlepass::default());
487        let mut features =
488            CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1;
489        // simple test
490        assert!(
491            compiler.get_cpu_features_used(&features).is_subset(
492                CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1
493            )
494        );
495        // check that an AVX build don't work on SSE4.2 only host
496        assert!(
497            !compiler
498                .get_cpu_features_used(&features)
499                .is_subset(CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1)
500        );
501        // check that having a host with AVX512 doesn't change anything
502        features.insert_all(CpuFeature::AVX512DQ | CpuFeature::AVX512F);
503        assert!(
504            compiler.get_cpu_features_used(&features).is_subset(
505                CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1
506            )
507        );
508    }
509}