//! Support for compiling with Singlepass. // Allow unused imports while developing. #![allow(unused_imports, dead_code)] use crate::codegen::FuncGen; use crate::config::Singlepass; #[cfg(feature = "unwind")] use crate::dwarf::WriterRelocate; use crate::machine::Machine; use crate::machine::{ gen_import_call_trampoline, gen_std_dynamic_import_trampoline, gen_std_trampoline, CodegenError, }; use crate::machine_arm64::MachineARM64; use crate::machine_x64::MachineX86_64; #[cfg(feature = "unwind")] use crate::unwind::{create_systemv_cie, UnwindFrame}; #[cfg(feature = "unwind")] use gimli::write::{EhFrame, FrameTable}; #[cfg(feature = "rayon")] use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use std::sync::Arc; use wasmer_compiler::{ Compiler, CompilerConfig, FunctionBinaryReader, FunctionBodyData, MiddlewareBinaryReader, ModuleMiddleware, ModuleMiddlewareChain, ModuleTranslationState, }; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::{ Architecture, CallingConvention, Compilation, CompileError, CompileModuleInfo, CompiledFunction, CpuFeature, Dwarf, FunctionBody, FunctionIndex, FunctionType, LocalFunctionIndex, MemoryIndex, ModuleInfo, OperatingSystem, SectionIndex, TableIndex, Target, TrapCode, TrapInformation, VMOffsets, }; impl From for CompileError { fn from(err: CodegenError) -> Self { Self::Codegen(err.message) } } /// A compiler that compiles a WebAssembly module with Singlepass. /// It does the compilation in one pass pub struct SinglepassCompiler { config: Singlepass, } impl SinglepassCompiler { /// Creates a new Singlepass compiler pub fn new(config: Singlepass) -> Self { Self { config } } /// Gets the config for this Compiler fn config(&self) -> &Singlepass { &self.config } } impl Compiler for SinglepassCompiler { /// Get the middlewares for this compiler fn get_middlewares(&self) -> &[Arc] { &self.config.middlewares } /// Compile the module using Singlepass, producing a compilation result with /// associated relocations. fn compile_module( &self, target: &Target, compile_info: &CompileModuleInfo, _module_translation: &ModuleTranslationState, function_body_inputs: PrimaryMap>, ) -> Result { match target.triple().architecture { Architecture::X86_64 => {} Architecture::Aarch64(_) => {} _ => { return Err(CompileError::UnsupportedTarget( target.triple().architecture.to_string(), )) } } let simd_arch = match target.triple().architecture { Architecture::X86_64 => { if target.cpu_features().contains(CpuFeature::AVX) { Some(CpuFeature::AVX) } else if target.cpu_features().contains(CpuFeature::SSE42) { Some(CpuFeature::SSE42) } else { return Err(CompileError::UnsupportedTarget( "x86_64 without AVX or SSE 4.2".to_string(), )); } } _ => None, }; let calling_convention = match target.triple().default_calling_convention() { Ok(CallingConvention::WindowsFastcall) => CallingConvention::WindowsFastcall, Ok(CallingConvention::SystemV) => CallingConvention::SystemV, Ok(CallingConvention::AppleAarch64) => CallingConvention::AppleAarch64, _ => { return Err(CompileError::UnsupportedTarget( "Unsupported Calling convention for Singlepass compiler".to_string(), )) } }; // Generate the frametable #[cfg(feature = "unwind")] let dwarf_frametable = if function_body_inputs.is_empty() { // If we have no function body inputs, we don't need to // construct the `FrameTable`. Constructing it, with empty // FDEs will cause some issues in Linux. None } else { match target.triple().default_calling_convention() { Ok(CallingConvention::SystemV) => { match create_systemv_cie(target.triple().architecture) { Some(cie) => { let mut dwarf_frametable = FrameTable::default(); let cie_id = dwarf_frametable.add_cie(cie); Some((dwarf_frametable, cie_id)) } None => None, } } _ => None, } }; let memory_styles = &compile_info.memory_styles; let table_styles = &compile_info.table_styles; let vmoffsets = VMOffsets::new(8, &compile_info.module); let module = &compile_info.module; let mut custom_sections: PrimaryMap = (0..module.num_imported_functions) .map(FunctionIndex::new) .collect::>() .into_par_iter_if_rayon() .map(|i| { gen_import_call_trampoline( &vmoffsets, i, &module.signatures[module.functions[i]], target, calling_convention, ) }) .collect::>() .into_iter() .collect(); let (functions, fdes): (Vec, Vec<_>) = function_body_inputs .iter() .collect::)>>() .into_par_iter_if_rayon() .map(|(i, input)| { let middleware_chain = self .config .middlewares .generate_function_middleware_chain(i); let mut reader = MiddlewareBinaryReader::new_with_offset(input.data, input.module_offset); reader.set_middleware_chain(middleware_chain); // This local list excludes arguments. let mut locals = vec![]; let num_locals = reader.read_local_count()?; for _ in 0..num_locals { let (count, ty) = reader.read_local_decl()?; for _ in 0..count { locals.push(ty); } } match target.triple().architecture { Architecture::X86_64 => { let machine = MachineX86_64::new(simd_arch); let mut generator = FuncGen::new( module, &self.config, &vmoffsets, memory_styles, table_styles, i, &locals, machine, calling_convention, ) .map_err(to_compile_error)?; while generator.has_control_frames() { generator.set_srcloc(reader.original_position() as u32); let op = reader.read_operator()?; generator.feed_operator(op).map_err(to_compile_error)?; } generator.finalize(input).map_err(to_compile_error) } Architecture::Aarch64(_) => { let machine = MachineARM64::new(); let mut generator = FuncGen::new( module, &self.config, &vmoffsets, memory_styles, table_styles, i, &locals, machine, calling_convention, ) .map_err(to_compile_error)?; while generator.has_control_frames() { generator.set_srcloc(reader.original_position() as u32); let op = reader.read_operator()?; generator.feed_operator(op).map_err(to_compile_error)?; } generator.finalize(input).map_err(to_compile_error) } _ => unimplemented!(), } }) .collect::, CompileError>>()? .into_iter() .unzip(); let function_call_trampolines = module .signatures .values() .collect::>() .into_par_iter_if_rayon() .map(|func_type| gen_std_trampoline(func_type, target, calling_convention)) .collect::>() .into_iter() .collect::>(); let dynamic_function_trampolines = module .imported_function_types() .collect::>() .into_par_iter_if_rayon() .map(|func_type| { gen_std_dynamic_import_trampoline( &vmoffsets, &func_type, target, calling_convention, ) }) .collect::>() .into_iter() .collect::>(); #[cfg(feature = "unwind")] let dwarf = if let Some((mut dwarf_frametable, cie_id)) = dwarf_frametable { for fde in fdes.into_iter().flatten() { match fde { UnwindFrame::SystemV(fde) => dwarf_frametable.add_fde(cie_id, fde), } } let mut eh_frame = EhFrame(WriterRelocate::new(target.triple().endianness().ok())); dwarf_frametable.write_eh_frame(&mut eh_frame).unwrap(); let eh_frame_section = eh_frame.0.into_section(); custom_sections.push(eh_frame_section); Some(Dwarf::new(SectionIndex::new(custom_sections.len() - 1))) } else { None }; #[cfg(not(feature = "unwind"))] let dwarf = None; Ok(Compilation::new( functions.into_iter().collect(), custom_sections, function_call_trampolines, dynamic_function_trampolines, dwarf, )) } } trait ToCompileError { fn to_compile_error(self) -> CompileError; } impl ToCompileError for CodegenError { fn to_compile_error(self) -> CompileError { CompileError::Codegen(self.message) } } fn to_compile_error(x: T) -> CompileError { x.to_compile_error() } trait IntoParIterIfRayon { type Output; fn into_par_iter_if_rayon(self) -> Self::Output; } impl IntoParIterIfRayon for Vec { #[cfg(not(feature = "rayon"))] type Output = std::vec::IntoIter; #[cfg(feature = "rayon")] type Output = rayon::vec::IntoIter; fn into_par_iter_if_rayon(self) -> Self::Output { #[cfg(not(feature = "rayon"))] return self.into_iter(); #[cfg(feature = "rayon")] return self.into_par_iter(); } } #[cfg(test)] mod tests { use super::*; use std::str::FromStr; use target_lexicon::triple; use wasmer_compiler::Features; use wasmer_types::{CpuFeature, MemoryStyle, TableStyle, Triple}; fn dummy_compilation_ingredients<'a>() -> ( CompileModuleInfo, ModuleTranslationState, PrimaryMap>, ) { let compile_info = CompileModuleInfo { features: Features::new(), module: ModuleInfo::new(), memory_styles: PrimaryMap::::new(), table_styles: PrimaryMap::::new(), }; let module_translation = ModuleTranslationState::new(); let function_body_inputs = PrimaryMap::>::new(); (compile_info, module_translation, function_body_inputs) } #[test] fn errors_for_unsupported_targets() { let compiler = SinglepassCompiler::new(Singlepass::default()); // Compile for 32bit Linux let linux32 = Target::new(triple!("i686-unknown-linux-gnu"), CpuFeature::for_host()); let (mut info, translation, inputs) = dummy_compilation_ingredients(); let result = compiler.compile_module(&linux32, &mut info, &translation, inputs); match result.unwrap_err() { CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), error => panic!("Unexpected error: {:?}", error), }; // Compile for win32 let win32 = Target::new(triple!("i686-pc-windows-gnu"), CpuFeature::for_host()); let (mut info, translation, inputs) = dummy_compilation_ingredients(); let result = compiler.compile_module(&win32, &mut info, &translation, inputs); match result.unwrap_err() { CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), // Windows should be checked before architecture error => panic!("Unexpected error: {:?}", error), }; } }