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