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