Skip to main content

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