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