diff --git a/Cargo.lock b/Cargo.lock index 03d4f7b7f65..c17e743ed73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -976,6 +976,11 @@ name = "gimli" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] [[package]] name = "glob" @@ -2952,6 +2957,7 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", + "gimli 0.26.1", "hashbrown 0.11.2", "lazy_static", "loupe", diff --git a/lib/compiler-singlepass/Cargo.toml b/lib/compiler-singlepass/Cargo.toml index 7bb2ca1dfa1..8227ec4c963 100644 --- a/lib/compiler-singlepass/Cargo.toml +++ b/lib/compiler-singlepass/Cargo.toml @@ -17,6 +17,7 @@ wasmer-vm = { path = "../vm", version = "=2.2.1" } wasmer-types = { path = "../types", version = "=2.2.1", default-features = false, features = ["std"] } rayon = { version = "1.5", optional = true } hashbrown = { version = "0.11", optional = true } +gimli = { version = "0.26", optional = true } more-asserts = "0.2" dynasm = "1.2.1" dynasmrt = "1.2.1" @@ -32,8 +33,9 @@ target-lexicon = { version = "0.12.2", default-features = false } maintenance = { status = "actively-developed" } [features] -default = ["std", "rayon", "avx"] +default = ["std", "rayon", "unwind", "avx"] std = ["wasmer-compiler/std", "wasmer-types/std"] core = ["hashbrown", "wasmer-types/core"] +unwind = ["gimli"] sse = [] avx = [] diff --git a/lib/compiler-singlepass/src/arm64_decl.rs b/lib/compiler-singlepass/src/arm64_decl.rs index 6aab5dbc13e..ea91cbdc1f4 100644 --- a/lib/compiler-singlepass/src/arm64_decl.rs +++ b/lib/compiler-singlepass/src/arm64_decl.rs @@ -141,6 +141,9 @@ impl AbstractReg for GPR { ]; GPRS.iter() } + fn to_dwarf(self) -> u16 { + self.into_index() as u16 + } } impl AbstractReg for NEON { @@ -196,6 +199,9 @@ impl AbstractReg for NEON { ]; NEONS.iter() } + fn to_dwarf(self) -> u16 { + self.into_index() as u16 + 64 + } } /// A machine register under the x86-64 architecture. diff --git a/lib/compiler-singlepass/src/codegen.rs b/lib/compiler-singlepass/src/codegen.rs index a42571e736e..cd0236fcdbd 100644 --- a/lib/compiler-singlepass/src/codegen.rs +++ b/lib/compiler-singlepass/src/codegen.rs @@ -1,11 +1,18 @@ use crate::address_map::get_function_address_map; +#[cfg(feature = "unwind")] +use crate::dwarf::WriterRelocate; use crate::location::{Location, Reg}; use crate::machine::{CodegenError, Label, Machine, MachineStackOffset, NATIVE_PAGE_SIZE}; +use crate::unwind::UnwindFrame; use crate::{common_decl::*, config::Singlepass}; +#[cfg(feature = "unwind")] +use gimli::write::Address; use smallvec::{smallvec, SmallVec}; use std::cmp; use std::iter; use wasmer_compiler::wasmparser::{Operator, Type as WpType, TypeOrFuncType as WpTypeOrFuncType}; +#[cfg(feature = "unwind")] +use wasmer_compiler::CompiledFunctionUnwindInfo; use wasmer_compiler::{ CallingConvention, CompiledFunction, CompiledFunctionFrameInfo, FunctionBody, FunctionBodyData, Relocation, RelocationTarget, SectionIndex, @@ -5852,7 +5859,7 @@ impl<'a, M: Machine> FuncGen<'a, M> { Ok(()) } - pub fn finalize(mut self, data: &FunctionBodyData) -> CompiledFunction { + pub fn finalize(mut self, data: &FunctionBodyData) -> (CompiledFunction, Option) { // Generate actual code for special labels. self.machine .emit_label(self.special_labels.integer_division_by_zero); @@ -5892,23 +5899,50 @@ impl<'a, M: Machine> FuncGen<'a, M> { self.machine.finalize_function(); let body_len = self.machine.assembler_get_offset().0; + + let mut unwind_info = None; + let mut fde = None; + #[cfg(feature = "unwind")] + match self.calling_convention { + CallingConvention::SystemV | CallingConvention::AppleAarch64 => { + let unwind = self.machine.gen_dwarf_unwind_info(body_len); + if let Some(unwind) = unwind { + fde = Some(unwind.to_fde(Address::Symbol { + symbol: WriterRelocate::FUNCTION_SYMBOL, + addend: self.fsm.local_function_id as _, + })); + unwind_info = Some(CompiledFunctionUnwindInfo::Dwarf); + } + } + CallingConvention::WindowsFastcall => { + let unwind = self.machine.gen_windows_unwind_info(body_len); + if let Some(unwind) = unwind { + unwind_info = Some(CompiledFunctionUnwindInfo::WindowsX64(unwind)); + } + } + _ => (), + }; + let address_map = get_function_address_map(self.machine.instructions_address_map(), data, body_len); let traps = self.machine.collect_trap_information(); let body = self.machine.assembler_finalize(); - CompiledFunction { - body: FunctionBody { - body: body, - unwind_info: None, - }, - relocations: self.relocations.clone(), - jt_offsets: SecondaryMap::new(), - frame_info: CompiledFunctionFrameInfo { - traps: traps, - address_map, + ( + CompiledFunction { + body: FunctionBody { + body: body, + unwind_info, + }, + relocations: self.relocations.clone(), + jt_offsets: SecondaryMap::new(), + frame_info: CompiledFunctionFrameInfo { + traps: traps, + address_map, + }, }, - } + fde, + ) } // FIXME: This implementation seems to be not enough to resolve all kinds of register dependencies // at call place. diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 0c04789b561..958a6b3fe7c 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -4,21 +4,28 @@ 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}; use loupe::MemoryUsage; #[cfg(feature = "rayon")] use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use std::sync::Arc; use wasmer_compiler::{ Architecture, CallingConvention, Compilation, CompileError, CompileModuleInfo, - CompiledFunction, Compiler, CompilerConfig, CpuFeature, FunctionBinaryReader, FunctionBody, - FunctionBodyData, MiddlewareBinaryReader, ModuleMiddleware, ModuleMiddlewareChain, - ModuleTranslationState, OperatingSystem, SectionIndex, Target, TrapInformation, + CompiledFunction, Compiler, CompilerConfig, CpuFeature, Dwarf, FunctionBinaryReader, + FunctionBody, FunctionBodyData, MiddlewareBinaryReader, ModuleMiddleware, + ModuleMiddlewareChain, ModuleTranslationState, OperatingSystem, SectionIndex, Target, + TrapInformation, }; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::{ @@ -94,11 +101,34 @@ impl Compiler for SinglepassCompiler { _ => panic!("Unsupported Calling convention for Singlepass compiler"), }; + // 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 import_trampolines: PrimaryMap = (0..module.num_imported_functions) + let mut custom_sections: PrimaryMap = (0..module.num_imported_functions) .map(FunctionIndex::new) .collect::>() .into_par_iter_if_rayon() @@ -114,7 +144,7 @@ impl Compiler for SinglepassCompiler { .collect::>() .into_iter() .collect(); - let functions = function_body_inputs + let (functions, fdes): (Vec, Vec<_>) = function_body_inputs .iter() .collect::)>>() .into_par_iter_if_rayon() @@ -185,9 +215,9 @@ impl Compiler for SinglepassCompiler { _ => unimplemented!(), } }) - .collect::, CompileError>>()? + .collect::, CompileError>>()? .into_iter() - .collect::>(); + .unzip(); let function_call_trampolines = module .signatures @@ -215,12 +245,33 @@ impl Compiler for SinglepassCompiler { .into_iter() .collect::>(); + #[cfg(feature = "unwind")] + let dwarf = if let Some((mut dwarf_frametable, cie_id)) = dwarf_frametable { + for fde in fdes { + if let Some(fde) = fde { + 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, - import_trampolines, + functions.into_iter().collect(), + custom_sections, function_call_trampolines, dynamic_function_trampolines, - None, + dwarf, )) } } diff --git a/lib/compiler-singlepass/src/dwarf.rs b/lib/compiler-singlepass/src/dwarf.rs new file mode 100644 index 00000000000..07e67bbdb00 --- /dev/null +++ b/lib/compiler-singlepass/src/dwarf.rs @@ -0,0 +1,102 @@ +use gimli::write::{Address, EndianVec, Result, Writer}; +use gimli::{RunTimeEndian, SectionId}; +use wasmer_compiler::{CustomSection, CustomSectionProtection, SectionBody}; +use wasmer_compiler::{Endianness, Relocation, RelocationKind, RelocationTarget}; +use wasmer_types::entity::EntityRef; +use wasmer_types::LocalFunctionIndex; + +#[derive(Clone, Debug)] +pub struct WriterRelocate { + pub relocs: Vec, + writer: EndianVec, +} + +impl WriterRelocate { + pub const FUNCTION_SYMBOL: usize = 0; + pub fn new(endianness: Option) -> Self { + let endianness = match endianness { + Some(Endianness::Little) => RunTimeEndian::Little, + Some(Endianness::Big) => RunTimeEndian::Big, + // We autodetect it, based on the host + None => RunTimeEndian::default(), + }; + WriterRelocate { + relocs: Vec::new(), + writer: EndianVec::new(endianness), + } + } + + pub fn into_section(mut self) -> CustomSection { + // GCC expects a terminating "empty" length, so write a 0 length at the end of the table. + self.writer.write_u32(0).unwrap(); + let data = self.writer.into_vec(); + CustomSection { + protection: CustomSectionProtection::Read, + bytes: SectionBody::new_with_vec(data), + relocations: self.relocs, + } + } +} + +impl Writer for WriterRelocate { + type Endian = RunTimeEndian; + + fn endian(&self) -> Self::Endian { + self.writer.endian() + } + + fn len(&self) -> usize { + self.writer.len() + } + + fn write(&mut self, bytes: &[u8]) -> Result<()> { + self.writer.write(bytes) + } + + fn write_at(&mut self, offset: usize, bytes: &[u8]) -> Result<()> { + self.writer.write_at(offset, bytes) + } + + fn write_address(&mut self, address: Address, size: u8) -> Result<()> { + match address { + Address::Constant(val) => self.write_udata(val, size), + Address::Symbol { symbol, addend } => { + // Is a function relocation + if symbol == Self::FUNCTION_SYMBOL { + // We use the addend to detect the function index + let function_index = LocalFunctionIndex::new(addend as _); + let reloc_target = RelocationTarget::LocalFunc(function_index); + let offset = self.len() as u32; + let kind = match size { + 8 => RelocationKind::Abs8, + _ => unimplemented!("dwarf relocation size not yet supported: {}", size), + }; + let addend = 0; + self.relocs.push(Relocation { + kind, + reloc_target, + offset, + addend, + }); + self.write_udata(addend as u64, size) + } else { + unreachable!("Symbol {} in DWARF not recognized", symbol); + } + } + } + } + + fn write_offset(&mut self, _val: usize, _section: SectionId, _size: u8) -> Result<()> { + unimplemented!("write_offset not yet implemented"); + } + + fn write_offset_at( + &mut self, + _offset: usize, + _val: usize, + _section: SectionId, + _size: u8, + ) -> Result<()> { + unimplemented!("write_offset_at not yet implemented"); + } +} diff --git a/lib/compiler-singlepass/src/lib.rs b/lib/compiler-singlepass/src/lib.rs index 48329748abf..fea110ab868 100644 --- a/lib/compiler-singlepass/src/lib.rs +++ b/lib/compiler-singlepass/src/lib.rs @@ -14,12 +14,17 @@ mod codegen; mod common_decl; mod compiler; mod config; +#[cfg(feature = "unwind")] +mod dwarf; mod emitter_arm64; mod emitter_x64; mod location; mod machine; mod machine_arm64; mod machine_x64; +mod unwind; +#[cfg(feature = "unwind")] +mod unwind_winx64; mod x64_decl; pub use crate::compiler::SinglepassCompiler; diff --git a/lib/compiler-singlepass/src/location.rs b/lib/compiler-singlepass/src/location.rs index ca55828fcb3..4992fbf03af 100644 --- a/lib/compiler-singlepass/src/location.rs +++ b/lib/compiler-singlepass/src/location.rs @@ -44,6 +44,7 @@ pub trait Reg: Copy + Clone + Eq + PartialEq + Debug + Hash + Ord { fn into_index(self) -> usize; fn from_index(i: usize) -> Result; fn iterator() -> Iter<'static, Self>; + fn to_dwarf(self) -> u16; } pub trait Descriptor { diff --git a/lib/compiler-singlepass/src/machine.rs b/lib/compiler-singlepass/src/machine.rs index a1b55e88626..753640b209c 100644 --- a/lib/compiler-singlepass/src/machine.rs +++ b/lib/compiler-singlepass/src/machine.rs @@ -2,6 +2,7 @@ use crate::common_decl::*; use crate::location::{Location, Reg}; use crate::machine_arm64::MachineARM64; use crate::machine_x64::MachineX86_64; +use crate::unwind::UnwindInstructions; use dynasmrt::{AssemblyOffset, DynamicLabel}; use std::collections::BTreeMap; use std::fmt::Debug; @@ -2190,6 +2191,10 @@ pub trait Machine { sig: &FunctionType, calling_convention: CallingConvention, ) -> CustomSection; + /// generate eh_frame instruction (or None if not possible / supported) + fn gen_dwarf_unwind_info(&mut self, code_len: usize) -> Option; + /// generate Windows unwind instructions (or None if not possible / supported) + fn gen_windows_unwind_info(&mut self, code_len: usize) -> Option>; } /// Standard entry trampoline generation diff --git a/lib/compiler-singlepass/src/machine_arm64.rs b/lib/compiler-singlepass/src/machine_arm64.rs index 1069c5e31a4..50bdaaec8a4 100644 --- a/lib/compiler-singlepass/src/machine_arm64.rs +++ b/lib/compiler-singlepass/src/machine_arm64.rs @@ -5,7 +5,10 @@ use crate::emitter_arm64::*; use crate::location::Location as AbstractLocation; use crate::location::Reg; use crate::machine::*; +use crate::unwind::{UnwindInstructions, UnwindOps}; use dynasmrt::{aarch64::Aarch64Relocation, VecAssembler}; +#[cfg(feature = "unwind")] +use gimli::{write::CallFrameInstruction, AArch64}; use wasmer_compiler::wasmparser::Type as WpType; use wasmer_compiler::{ CallingConvention, CustomSection, FunctionBody, InstructionAddressMap, Relocation, @@ -17,6 +20,83 @@ use wasmer_vm::{TrapCode, VMOffsets}; type Assembler = VecAssembler; type Location = AbstractLocation; +#[cfg(feature = "unwind")] +fn dwarf_index(reg: u16) -> gimli::Register { + static DWARF_GPR: [gimli::Register; 32] = [ + AArch64::X0, + AArch64::X1, + AArch64::X2, + AArch64::X3, + AArch64::X4, + AArch64::X5, + AArch64::X6, + AArch64::X7, + AArch64::X8, + AArch64::X9, + AArch64::X10, + AArch64::X11, + AArch64::X12, + AArch64::X13, + AArch64::X14, + AArch64::X15, + AArch64::X16, + AArch64::X17, + AArch64::X18, + AArch64::X19, + AArch64::X20, + AArch64::X21, + AArch64::X22, + AArch64::X23, + AArch64::X24, + AArch64::X25, + AArch64::X26, + AArch64::X27, + AArch64::X28, + AArch64::X29, + AArch64::X30, + AArch64::SP, + ]; + static DWARF_NEON: [gimli::Register; 32] = [ + AArch64::V0, + AArch64::V1, + AArch64::V2, + AArch64::V3, + AArch64::V4, + AArch64::V5, + AArch64::V6, + AArch64::V7, + AArch64::V8, + AArch64::V9, + AArch64::V10, + AArch64::V11, + AArch64::V12, + AArch64::V13, + AArch64::V14, + AArch64::V15, + AArch64::V16, + AArch64::V17, + AArch64::V18, + AArch64::V19, + AArch64::V20, + AArch64::V21, + AArch64::V22, + AArch64::V23, + AArch64::V24, + AArch64::V25, + AArch64::V26, + AArch64::V27, + AArch64::V28, + AArch64::V29, + AArch64::V30, + AArch64::V31, + ]; + match reg { + 0..=31 => DWARF_GPR[reg as usize], + 64..=95 => DWARF_NEON[reg as usize - 64], + _ => panic!("Unknown register index {}", reg), + } +} + pub struct MachineARM64 { assembler: Assembler, used_gprs: u32, @@ -30,6 +110,8 @@ pub struct MachineARM64 { src_loc: u32, /// is last push on a 8byte multiple or 16bytes? pushed: bool, + /// Vector of unwind operations with offset + unwind_ops: Vec<(usize, UnwindOps)>, } #[allow(dead_code)] @@ -63,6 +145,7 @@ impl MachineARM64 { instructions_address_map: vec![], src_loc: 0, pushed: false, + unwind_ops: vec![], } } fn compatible_imm(&self, imm: i64, ty: ImmType) -> bool { @@ -1165,6 +1248,9 @@ impl MachineARM64 { self.used_simd &= !(1 << r.into_index()); ret } + fn emit_unwind_op(&mut self, op: UnwindOps) { + self.unwind_ops.push((self.get_offset().0, op)); + } } impl Machine for MachineARM64 { @@ -1545,6 +1631,21 @@ impl Machine for MachineARM64 { self.assembler .emit_str(Size::S64, location, Location::GPR(tmp)); } + match location { + Location::GPR(x) => { + self.emit_unwind_op(UnwindOps::SaveRegister { + reg: x.to_dwarf(), + bp_neg_offset: stack_offset, + }); + } + Location::SIMD(x) => { + self.emit_unwind_op(UnwindOps::SaveRegister { + reg: x.to_dwarf(), + bp_neg_offset: stack_offset, + }); + } + _ => (), + } } // List of register to save, depending on the CallingConvention @@ -1981,7 +2082,17 @@ impl Machine for MachineARM64 { fn emit_function_prolog(&mut self) { self.emit_double_push(Size::S64, Location::GPR(GPR::X29), Location::GPR(GPR::X30)); // save LR too + self.emit_unwind_op(UnwindOps::Push2Regs { + reg1: GPR::X29.to_dwarf(), + reg2: GPR::X30.to_dwarf(), + up_to_sp: 16, + }); self.emit_double_push(Size::S64, Location::GPR(GPR::X27), Location::GPR(GPR::X28)); + self.emit_unwind_op(UnwindOps::Push2Regs { + reg1: GPR::X27.to_dwarf(), + reg2: GPR::X28.to_dwarf(), + up_to_sp: 32, + }); // cannot use mov, because XSP is XZR there. Need to use ADD with #0 self.assembler.emit_add( Size::S64, @@ -1989,6 +2100,7 @@ impl Machine for MachineARM64 { Location::Imm8(0), Location::GPR(GPR::X29), ); + self.emit_unwind_op(UnwindOps::DefineNewFrame); } fn emit_function_epilog(&mut self) { @@ -5088,4 +5200,63 @@ impl Machine for MachineARM64 { ) -> CustomSection { gen_import_call_trampoline_arm64(vmoffsets, index, sig, calling_convention) } + #[cfg(feature = "unwind")] + fn gen_dwarf_unwind_info(&mut self, code_len: usize) -> Option { + let mut instructions = vec![]; + for &(instruction_offset, ref inst) in &self.unwind_ops { + let instruction_offset = instruction_offset as u32; + match inst { + &UnwindOps::PushFP { up_to_sp } => { + instructions.push(( + instruction_offset, + CallFrameInstruction::CfaOffset(up_to_sp as i32), + )); + instructions.push(( + instruction_offset, + CallFrameInstruction::Offset(AArch64::X29, -(up_to_sp as i32)), + )); + } + &UnwindOps::Push2Regs { + reg1, + reg2, + up_to_sp, + } => { + instructions.push(( + instruction_offset, + CallFrameInstruction::CfaOffset(up_to_sp as i32), + )); + instructions.push(( + instruction_offset, + CallFrameInstruction::Offset(dwarf_index(reg2), -(up_to_sp as i32) + 8), + )); + instructions.push(( + instruction_offset, + CallFrameInstruction::Offset(dwarf_index(reg1), -(up_to_sp as i32)), + )); + } + &UnwindOps::DefineNewFrame => { + instructions.push(( + instruction_offset, + CallFrameInstruction::CfaRegister(AArch64::X29), + )); + } + &UnwindOps::SaveRegister { reg, bp_neg_offset } => instructions.push(( + instruction_offset, + CallFrameInstruction::Offset(dwarf_index(reg), -bp_neg_offset), + )), + } + } + Some(UnwindInstructions { + instructions, + len: code_len as u32, + }) + } + #[cfg(not(feature = "unwind"))] + fn gen_dwarf_unwind_info(&mut self, _code_len: usize) -> Option { + None + } + + fn gen_windows_unwind_info(&mut self, _code_len: usize) -> Option> { + None + } } diff --git a/lib/compiler-singlepass/src/machine_x64.rs b/lib/compiler-singlepass/src/machine_x64.rs index 646ae520e4e..e4870d3f3f8 100644 --- a/lib/compiler-singlepass/src/machine_x64.rs +++ b/lib/compiler-singlepass/src/machine_x64.rs @@ -3,9 +3,14 @@ use crate::emitter_x64::*; use crate::location::Location as AbstractLocation; use crate::location::Reg; use crate::machine::*; +use crate::unwind::{UnwindInstructions, UnwindOps}; +#[cfg(feature = "unwind")] +use crate::unwind_winx64::create_unwind_info_from_insts; use crate::x64_decl::new_machine_state; use crate::x64_decl::{ArgumentRegisterAllocator, X64Register, GPR, XMM}; use dynasmrt::{x64::X64Relocation, DynasmError, VecAssembler}; +#[cfg(feature = "unwind")] +use gimli::{write::CallFrameInstruction, X86_64}; use std::ops::{Deref, DerefMut}; use wasmer_compiler::wasmparser::Type as WpType; use wasmer_compiler::{ @@ -55,6 +60,51 @@ impl DerefMut for AssemblerX64 { type Location = AbstractLocation; +#[cfg(feature = "unwind")] +fn dwarf_index(reg: u16) -> gimli::Register { + static DWARF_GPR: [gimli::Register; 16] = [ + X86_64::RAX, + X86_64::RDX, + X86_64::RCX, + X86_64::RBX, + X86_64::RSI, + X86_64::RDI, + X86_64::RBP, + X86_64::RSP, + X86_64::R8, + X86_64::R9, + X86_64::R10, + X86_64::R11, + X86_64::R12, + X86_64::R13, + X86_64::R14, + X86_64::R15, + ]; + static DWARF_XMM: [gimli::Register; 16] = [ + X86_64::XMM0, + X86_64::XMM1, + X86_64::XMM2, + X86_64::XMM3, + X86_64::XMM4, + X86_64::XMM5, + X86_64::XMM6, + X86_64::XMM7, + X86_64::XMM8, + X86_64::XMM9, + X86_64::XMM10, + X86_64::XMM11, + X86_64::XMM12, + X86_64::XMM13, + X86_64::XMM14, + X86_64::XMM15, + ]; + match reg { + 0..=15 => DWARF_GPR[reg as usize], + 17..=24 => DWARF_XMM[reg as usize - 17], + _ => panic!("Unknown register index {}", reg), + } +} + pub struct MachineX86_64 { assembler: AssemblerX64, used_gprs: u32, @@ -66,6 +116,8 @@ pub struct MachineX86_64 { instructions_address_map: Vec, /// The source location for the current operator. src_loc: u32, + /// Vector of unwind operations with offset + unwind_ops: Vec<(usize, UnwindOps)>, } impl MachineX86_64 { @@ -77,6 +129,7 @@ impl MachineX86_64 { trap_table: TrapTable::default(), instructions_address_map: vec![], src_loc: 0, + unwind_ops: vec![], } } pub fn emit_relaxed_binop( @@ -1615,6 +1668,9 @@ impl MachineX86_64 { self.used_simd &= !(1 << r.into_index()); ret } + fn emit_unwind_op(&mut self, op: UnwindOps) { + self.unwind_ops.push((self.get_offset().0, op)); + } } impl Machine for MachineX86_64 { @@ -1934,6 +1990,21 @@ impl Machine for MachineX86_64 { location, Location::Memory(GPR::RBP, -stack_offset), ); + match location { + Location::GPR(x) => { + self.emit_unwind_op(UnwindOps::SaveRegister { + reg: x.to_dwarf(), + bp_neg_offset: stack_offset, + }); + } + Location::SIMD(x) => { + self.emit_unwind_op(UnwindOps::SaveRegister { + reg: x.to_dwarf(), + bp_neg_offset: stack_offset, + }); + } + _ => (), + } } // List of register to save, depending on the CallingConvention @@ -2180,7 +2251,9 @@ impl Machine for MachineX86_64 { fn emit_function_prolog(&mut self) { self.emit_push(Size::S64, Location::GPR(GPR::RBP)); + self.emit_unwind_op(UnwindOps::PushFP { up_to_sp: 16 }); self.move_location(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RBP)); + self.emit_unwind_op(UnwindOps::DefineNewFrame); } fn emit_function_epilog(&mut self) { @@ -7080,4 +7153,60 @@ impl Machine for MachineX86_64 { relocations: vec![], } } + #[cfg(feature = "unwind")] + fn gen_dwarf_unwind_info(&mut self, code_len: usize) -> Option { + let mut instructions = vec![]; + for &(instruction_offset, ref inst) in &self.unwind_ops { + let instruction_offset = instruction_offset as u32; + match inst { + &UnwindOps::PushFP { up_to_sp } => { + instructions.push(( + instruction_offset, + CallFrameInstruction::CfaOffset(up_to_sp as i32), + )); + instructions.push(( + instruction_offset, + CallFrameInstruction::Offset(X86_64::RBP, -(up_to_sp as i32)), + )); + } + &UnwindOps::DefineNewFrame => { + instructions.push(( + instruction_offset, + CallFrameInstruction::CfaRegister(X86_64::RBP), + )); + } + &UnwindOps::SaveRegister { reg, bp_neg_offset } => instructions.push(( + instruction_offset, + CallFrameInstruction::Offset(dwarf_index(reg), -bp_neg_offset), + )), + &UnwindOps::Push2Regs { .. } => unimplemented!(), + } + } + Some(UnwindInstructions { + instructions, + len: code_len as u32, + }) + } + #[cfg(not(feature = "unwind"))] + fn gen_dwarf_unwind_info(&mut self, _code_len: usize) -> Option { + None + } + + #[cfg(feature = "unwind")] + fn gen_windows_unwind_info(&mut self, _code_len: usize) -> Option> { + let unwind_info = create_unwind_info_from_insts(&self.unwind_ops); + if let Some(unwind) = unwind_info { + let sz = unwind.emit_size(); + let mut tbl = vec![0; sz]; + unwind.emit(&mut tbl); + Some(tbl) + } else { + None + } + } + + #[cfg(not(feature = "unwind"))] + fn gen_windows_unwind_info(&mut self, _code_len: usize) -> Option> { + None + } } diff --git a/lib/compiler-singlepass/src/unwind.rs b/lib/compiler-singlepass/src/unwind.rs new file mode 100644 index 00000000000..03ffe6ee642 --- /dev/null +++ b/lib/compiler-singlepass/src/unwind.rs @@ -0,0 +1,81 @@ +#[cfg(feature = "unwind")] +use gimli::write::{Address, CallFrameInstruction, CommonInformationEntry, FrameDescriptionEntry}; +#[cfg(feature = "unwind")] +use gimli::{AArch64, Encoding, Format, X86_64}; +use std::fmt::Debug; +#[cfg(feature = "unwind")] +use wasmer_compiler::Architecture; + +#[derive(Clone, Debug)] +pub enum UnwindOps { + PushFP { up_to_sp: u32 }, + Push2Regs { reg1: u16, reg2: u16, up_to_sp: u32 }, + DefineNewFrame, + SaveRegister { reg: u16, bp_neg_offset: i32 }, +} + +#[cfg(not(feature = "unwind"))] +pub type CallFrameInstruction = u32; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UnwindInstructions { + pub instructions: Vec<(u32, CallFrameInstruction)>, + pub len: u32, +} + +#[cfg(feature = "unwind")] +pub enum UnwindFrame { + SystemV(gimli::write::FrameDescriptionEntry), +} + +#[cfg(not(feature = "unwind"))] +pub type UnwindFrame = u32; + +#[cfg(feature = "unwind")] +impl UnwindInstructions { + /// Converts the unwind information into a `FrameDescriptionEntry`. + pub fn to_fde(&self, address: Address) -> UnwindFrame { + let mut fde = FrameDescriptionEntry::new(address, self.len); + for (offset, inst) in &self.instructions { + fde.add_instruction(*offset, inst.clone().into()); + } + UnwindFrame::SystemV(fde) + } +} + +/// generate a default systemv cie +#[cfg(feature = "unwind")] +pub fn create_systemv_cie(arch: Architecture) -> Option { + match arch { + Architecture::X86_64 => { + let mut entry = CommonInformationEntry::new( + Encoding { + address_size: 8, + format: Format::Dwarf32, + version: 1, + }, + 1, + -8, + X86_64::RA, + ); + entry.add_instruction(CallFrameInstruction::Cfa(X86_64::RSP, 8)); + entry.add_instruction(CallFrameInstruction::Offset(X86_64::RA, -8)); + Some(entry) + } + Architecture::Aarch64(_) => { + let mut entry = CommonInformationEntry::new( + Encoding { + address_size: 8, + format: Format::Dwarf32, + version: 1, + }, + 1, + -8, + AArch64::X30, + ); + entry.add_instruction(CallFrameInstruction::Cfa(AArch64::SP, 0)); + Some(entry) + } + _ => None, + } +} diff --git a/lib/compiler-singlepass/src/unwind_winx64.rs b/lib/compiler-singlepass/src/unwind_winx64.rs new file mode 100644 index 00000000000..1e67bcb5db9 --- /dev/null +++ b/lib/compiler-singlepass/src/unwind_winx64.rs @@ -0,0 +1,306 @@ +//! Windows x64 ABI unwind information. + +use crate::unwind::UnwindOps; + +/// Maximum (inclusive) size of a "small" stack allocation +const SMALL_ALLOC_MAX_SIZE: u32 = 128; +/// Maximum (inclusive) size of a "large" stack allocation that can represented in 16-bits +const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280; + +struct Writer<'a> { + buf: &'a mut [u8], + offset: usize, +} + +impl<'a> Writer<'a> { + pub fn new(buf: &'a mut [u8]) -> Self { + Self { buf, offset: 0 } + } + + fn write_u8(&mut self, v: u8) { + self.buf[self.offset] = v; + self.offset += 1; + } + + fn write_u16_le(&mut self, v: u16) { + self.buf[self.offset..(self.offset + 2)].copy_from_slice(&v.to_le_bytes()); + self.offset += 2; + } + + fn write_u32_le(&mut self, v: u32) { + self.buf[self.offset..(self.offset + 4)].copy_from_slice(&v.to_le_bytes()); + self.offset += 4; + } +} + +/// The supported unwind codes for the x64 Windows ABI. +/// +/// See: +/// Only what is needed to describe the prologues generated by the Cranelift x86 ISA are represented here. +/// Note: the Cranelift x86 ISA RU enum matches the Windows unwind GPR encoding values. +#[allow(dead_code)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum UnwindCode { + PushRegister { + instruction_offset: u8, + reg: u8, + }, + SaveReg { + instruction_offset: u8, + reg: u8, + stack_offset: u32, + }, + SaveXmm { + instruction_offset: u8, + reg: u8, + stack_offset: u32, + }, + StackAlloc { + instruction_offset: u8, + size: u32, + }, + SetFPReg { + instruction_offset: u8, + }, +} + +impl UnwindCode { + fn emit(&self, writer: &mut Writer) { + enum UnwindOperation { + PushNonvolatileRegister = 0, + LargeStackAlloc = 1, + SmallStackAlloc = 2, + SetFPReg = 3, + SaveNonVolatileRegister = 4, + SaveNonVolatileRegisterFar = 5, + SaveXmm128 = 8, + SaveXmm128Far = 9, + } + + match self { + Self::PushRegister { + instruction_offset, + reg, + } => { + writer.write_u8(*instruction_offset); + writer.write_u8((*reg << 4) | (UnwindOperation::PushNonvolatileRegister as u8)); + } + Self::SaveReg { + instruction_offset, + reg, + stack_offset, + } + | Self::SaveXmm { + instruction_offset, + reg, + stack_offset, + } => { + let is_xmm = match self { + Self::SaveXmm { .. } => true, + _ => false, + }; + let (op_small, op_large) = if is_xmm { + (UnwindOperation::SaveXmm128, UnwindOperation::SaveXmm128Far) + } else { + ( + UnwindOperation::SaveNonVolatileRegister, + UnwindOperation::SaveNonVolatileRegisterFar, + ) + }; + writer.write_u8(*instruction_offset); + let scaled_stack_offset = stack_offset / 16; + if scaled_stack_offset <= core::u16::MAX as u32 { + writer.write_u8((*reg << 4) | (op_small as u8)); + writer.write_u16_le(scaled_stack_offset as u16); + } else { + writer.write_u8((*reg << 4) | (op_large as u8)); + writer.write_u16_le(*stack_offset as u16); + writer.write_u16_le((stack_offset >> 16) as u16); + } + } + Self::StackAlloc { + instruction_offset, + size, + } => { + // Stack allocations on Windows must be a multiple of 8 and be at least 1 slot + assert!(*size >= 8); + assert!((*size % 8) == 0); + + writer.write_u8(*instruction_offset); + if *size <= SMALL_ALLOC_MAX_SIZE { + writer.write_u8( + ((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8, + ); + } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE { + writer.write_u8(UnwindOperation::LargeStackAlloc as u8); + writer.write_u16_le((*size / 8) as u16); + } else { + writer.write_u8((1 << 4) | (UnwindOperation::LargeStackAlloc as u8)); + writer.write_u32_le(*size); + } + } + Self::SetFPReg { instruction_offset } => { + writer.write_u8(*instruction_offset); + writer.write_u8(UnwindOperation::SetFPReg as u8); + } + } + } + + fn node_count(&self) -> usize { + match self { + Self::StackAlloc { size, .. } => { + if *size <= SMALL_ALLOC_MAX_SIZE { + 1 + } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE { + 2 + } else { + 3 + } + } + Self::SaveXmm { stack_offset, .. } | Self::SaveReg { stack_offset, .. } => { + if *stack_offset <= core::u16::MAX as u32 { + 2 + } else { + 3 + } + } + _ => 1, + } + } +} + +/// Represents Windows x64 unwind information. +/// +/// For information about Windows x64 unwind info, see: +/// +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct UnwindInfo { + pub(crate) flags: u8, + pub(crate) prologue_size: u8, + pub(crate) frame_register: Option, + pub(crate) frame_register_offset: u8, + pub(crate) unwind_codes: Vec, +} + +impl UnwindInfo { + /// Gets the emit size of the unwind information, in bytes. + pub fn emit_size(&self) -> usize { + let node_count = self.node_count(); + + // Calculation of the size requires no SEH handler or chained info + assert!(self.flags == 0); + + // Size of fixed part of UNWIND_INFO is 4 bytes + // Then comes the UNWIND_CODE nodes (2 bytes each) + // Then comes 2 bytes of padding for the unwind codes if necessary + // Next would come the SEH data, but we assert above that the function doesn't have SEH data + + 4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 } + } + + /// Emits the unwind information into the given mutable byte slice. + /// + /// This function will panic if the slice is not at least `emit_size` in length. + pub fn emit(&self, buf: &mut [u8]) { + const UNWIND_INFO_VERSION: u8 = 1; + + let node_count = self.node_count(); + assert!(node_count <= 256); + + let mut writer = Writer::new(buf); + + writer.write_u8((self.flags << 3) | UNWIND_INFO_VERSION); + writer.write_u8(self.prologue_size); + writer.write_u8(node_count as u8); + + if let Some(reg) = self.frame_register { + writer.write_u8((self.frame_register_offset << 4) | reg); + } else { + writer.write_u8(0); + } + + // Unwind codes are written in reverse order (prologue offset descending) + for code in self.unwind_codes.iter().rev() { + code.emit(&mut writer); + } + + // To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes + if (node_count & 1) == 1 { + writer.write_u16_le(0); + } + + // Ensure the correct number of bytes was emitted + assert_eq!(writer.offset, self.emit_size()); + } + + fn node_count(&self) -> usize { + self.unwind_codes + .iter() + .fold(0, |nodes, c| nodes + c.node_count()) + } +} + +const UNWIND_RBP_REG: u8 = 5; + +pub(crate) fn create_unwind_info_from_insts(insts: &Vec<(usize, UnwindOps)>) -> Option { + let mut unwind_codes = vec![]; + let mut frame_register_offset = 0; + let mut max_unwind_offset = 0; + for &(instruction_offset, ref inst) in insts { + let instruction_offset = ensure_unwind_offset(instruction_offset as u32)?; + match inst { + &UnwindOps::PushFP { .. } => { + unwind_codes.push(UnwindCode::PushRegister { + instruction_offset, + reg: UNWIND_RBP_REG, + }); + } + &UnwindOps::DefineNewFrame => { + frame_register_offset = ensure_unwind_offset(32)?; + unwind_codes.push(UnwindCode::SetFPReg { instruction_offset }); + } + &UnwindOps::SaveRegister { reg, bp_neg_offset } => match reg { + 0..=15 => { + // GPR reg + static FROM_DWARF: [u8; 16] = + [0, 2, 1, 3, 6, 7, 5, 4, 8, 9, 10, 11, 12, 13, 14, 15]; + unwind_codes.push(UnwindCode::SaveReg { + instruction_offset, + reg: FROM_DWARF[reg as usize], + stack_offset: bp_neg_offset as u32, + }); + } + 17..=32 => { + unwind_codes.push(UnwindCode::SaveXmm { + instruction_offset, + reg: reg as u8 - 17, + stack_offset: bp_neg_offset as u32, + }); + } + _ => { + unreachable!("unknown register index {}", reg); + } + }, + &UnwindOps::Push2Regs { .. } => { + unreachable!("no aarch64 on x64"); + } + } + max_unwind_offset = instruction_offset; + } + + Some(UnwindInfo { + flags: 0, + prologue_size: max_unwind_offset, + frame_register: Some(UNWIND_RBP_REG), + frame_register_offset, + unwind_codes, + }) +} + +fn ensure_unwind_offset(offset: u32) -> Option { + if offset > 255 { + panic!("function prologues cannot exceed 255 bytes in size for Windows x64"); + } + Some(offset as u8) +} diff --git a/lib/compiler-singlepass/src/x64_decl.rs b/lib/compiler-singlepass/src/x64_decl.rs index b9626f947bd..372a3b86886 100644 --- a/lib/compiler-singlepass/src/x64_decl.rs +++ b/lib/compiler-singlepass/src/x64_decl.rs @@ -94,6 +94,26 @@ impl AbstractReg for GPR { ]; GPRS.iter() } + fn to_dwarf(self) -> u16 { + match self { + GPR::RAX => 0, + GPR::RDX => 1, + GPR::RCX => 2, + GPR::RBX => 3, + GPR::RSI => 4, + GPR::RDI => 5, + GPR::RBP => 6, + GPR::RSP => 7, + GPR::R8 => 8, + GPR::R9 => 9, + GPR::R10 => 10, + GPR::R11 => 11, + GPR::R12 => 12, + GPR::R13 => 13, + GPR::R14 => 14, + GPR::R15 => 15, + } + } } impl AbstractReg for XMM { @@ -137,6 +157,9 @@ impl AbstractReg for XMM { ]; XMMS.iter() } + fn to_dwarf(self) -> u16 { + self.into_index() as u16 + 17 + } } /// A machine register under the x86-64 architecture. @@ -164,11 +187,61 @@ impl CombinedRegister for X64Register { fn from_simd(x: u16) -> Self { X64Register::XMM(XMM::from_index(x as usize).unwrap()) } - + /* x86_64-abi-0.99.pdf + * Register Name | Number | Abbreviation + * General Purpose Register RAX | 0 | %rax + * General Purpose Register RDX | 1 | %rdx + * General Purpose Register RCX | 2 | %rcx + * General Purpose Register RBX | 3 | %rbx + * General Purpose Register RSI | 4 | %rsi + * General Purpose Register RDI | 5 | %rdi + * Frame Pointer Register RBP | 6 | %rbp + * Stack Pointer Register RSP | 7 | %rsp + * Extended Integer Registers 8-15 | 8-15 | %r8-%r15 + * Return Address RA | 16 | + * Vector Registers 0-7 | 17-24 | %xmm0-%xmm7 + * Extended Vector Registers 8-15 | 25-32 | %xmm8-%xmm15 + * Floating Point Registers 0-7 | 33-40 | %st0-%st7 + * MMX Registers 0-7 | 41-48 | %mm0-%mm7 + * Flag Register | 49 | %rFLAGS + * Segment Register ES | 50 | %es + * Segment Register CS | 51 | %cs + * Segment Register SS | 52 | %ss + * Segment Register DS | 53 | %ds + * Segment Register FS | 54 | %fs + * Segment Register GS | 55 | %gs + * Reserved | 56-57 | + * FS Base address | 58 | %fs.base + * GS Base address | 59 | %gs.base + * Reserved | 60-61 | + * Task Register | 62 | %tr + * LDT Register | 63 | %ldtr + * 128-bit Media Control and Status | 64 | %mxcsr + * x87 Control Word | 65 | %fcw + * x87 Status Word | 66 | %fsw + */ /// Converts a DWARF regnum to X64Register. fn _from_dwarf_regnum(x: u16) -> Option { + static DWARF_REGS: [GPR; 16] = [ + GPR::RAX, + GPR::RDX, + GPR::RCX, + GPR::RBX, + GPR::RSI, + GPR::RDI, + GPR::RBP, + GPR::RSP, + GPR::R8, + GPR::R9, + GPR::R10, + GPR::R11, + GPR::R12, + GPR::R13, + GPR::R14, + GPR::R15, + ]; Some(match x { - 0..=15 => X64Register::GPR(GPR::from_index(x as usize).unwrap()), + 0..=15 => X64Register::GPR(DWARF_REGS[x as usize]), 17..=24 => X64Register::XMM(XMM::from_index(x as usize - 17).unwrap()), _ => return None, }) diff --git a/tests/ignores.txt b/tests/ignores.txt index b88b372c32e..9fde32b0a12 100644 --- a/tests/ignores.txt +++ b/tests/ignores.txt @@ -9,28 +9,28 @@ musl+dylib * # Dynamic loading not supported in Musl ## Traps. Tracing doesn't work properly in Singlepass ## Unwinding is not properly implemented in Singlepass # Needs investigation -singlepass traps::test_trap_trace +singlepass+aarch64+macos traps::test_trap_trace dylib traps::test_trap_trace -aarch64 traps::test_trap_trace -singlepass traps::test_trap_stack_overflow # Need to investigate +cranelift+aarch64 traps::test_trap_trace +singlepass+aarch64+macos traps::test_trap_stack_overflow # Need to investigate dylib traps::test_trap_stack_overflow # Need to investigate -aarch64 traps::test_trap_stack_overflow # Need to investigate -singlepass traps::trap_display_pretty +cranelift+aarch64 traps::test_trap_stack_overflow # Need to investigate +singlepass+aarch64+macos traps::trap_display_pretty llvm traps::trap_display_pretty dylib traps::trap_display_pretty -aarch64 traps::trap_display_pretty -singlepass traps::trap_display_multi_module +cranelift+aarch64 traps::trap_display_pretty +singlepass+aarch64+macos traps::trap_display_multi_module llvm traps::trap_display_multi_module dylib traps::trap_display_multi_module -aarch64 traps::trap_display_multi_module -singlepass traps::call_signature_mismatch +cranelift+aarch64 traps::trap_display_multi_module +singlepass traps::call_signature_mismatch # Need to investigate, get foo (a[0]:0x33) instead of 0x30 for inderect call llvm traps::call_signature_mismatch dylib traps::call_signature_mismatch macos+aarch64 traps::call_signature_mismatch -singlepass traps::start_trap_pretty +singlepass+aarch64+macos traps::start_trap_pretty llvm traps::start_trap_pretty dylib traps::start_trap_pretty -aarch64 traps::start_trap_pretty +cranelift+aarch64 traps::start_trap_pretty singlepass multi_value_imports::dylib # Singlepass doesn't support multivalue singlepass multi_value_imports::dynamic # Singlepass doesn't support multivalue