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