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