diff options
Diffstat (limited to 'third_party/rust/cranelift-codegen/src/isa/unwind')
-rw-r--r-- | third_party/rust/cranelift-codegen/src/isa/unwind/systemv.rs | 313 | ||||
-rw-r--r-- | third_party/rust/cranelift-codegen/src/isa/unwind/winx64.rs | 294 |
2 files changed, 607 insertions, 0 deletions
diff --git a/third_party/rust/cranelift-codegen/src/isa/unwind/systemv.rs b/third_party/rust/cranelift-codegen/src/isa/unwind/systemv.rs new file mode 100644 index 0000000000..dfb2ef5936 --- /dev/null +++ b/third_party/rust/cranelift-codegen/src/isa/unwind/systemv.rs @@ -0,0 +1,313 @@ +//! System V ABI unwind information. + +use crate::isa::unwind::input; +use crate::result::{CodegenError, CodegenResult}; +use alloc::vec::Vec; +use gimli::write::{Address, FrameDescriptionEntry}; +use thiserror::Error; + +#[cfg(feature = "enable-serde")] +use serde::{Deserialize, Serialize}; + +type Register = u16; + +/// Enumerate the errors possible in mapping Cranelift registers to their DWARF equivalent. +#[allow(missing_docs)] +#[derive(Error, Debug, PartialEq, Eq)] +pub enum RegisterMappingError { + #[error("unable to find bank for register info")] + MissingBank, + #[error("register mapping is currently only implemented for x86_64")] + UnsupportedArchitecture, + #[error("unsupported register bank: {0}")] + UnsupportedRegisterBank(&'static str), +} + +// This mirrors gimli's CallFrameInstruction, but is serializable +// This excludes CfaExpression, Expression, ValExpression due to +// https://github.com/gimli-rs/gimli/issues/513. +// TODO: if gimli ever adds serialization support, remove this type +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub(crate) enum CallFrameInstruction { + Cfa(Register, i32), + CfaRegister(Register), + CfaOffset(i32), + Restore(Register), + Undefined(Register), + SameValue(Register), + Offset(Register, i32), + ValOffset(Register, i32), + Register(Register, Register), + RememberState, + RestoreState, + ArgsSize(u32), +} + +impl From<gimli::write::CallFrameInstruction> for CallFrameInstruction { + fn from(cfi: gimli::write::CallFrameInstruction) -> Self { + use gimli::write::CallFrameInstruction; + + match cfi { + CallFrameInstruction::Cfa(reg, offset) => Self::Cfa(reg.0, offset), + CallFrameInstruction::CfaRegister(reg) => Self::CfaRegister(reg.0), + CallFrameInstruction::CfaOffset(offset) => Self::CfaOffset(offset), + CallFrameInstruction::Restore(reg) => Self::Restore(reg.0), + CallFrameInstruction::Undefined(reg) => Self::Undefined(reg.0), + CallFrameInstruction::SameValue(reg) => Self::SameValue(reg.0), + CallFrameInstruction::Offset(reg, offset) => Self::Offset(reg.0, offset), + CallFrameInstruction::ValOffset(reg, offset) => Self::ValOffset(reg.0, offset), + CallFrameInstruction::Register(reg1, reg2) => Self::Register(reg1.0, reg2.0), + CallFrameInstruction::RememberState => Self::RememberState, + CallFrameInstruction::RestoreState => Self::RestoreState, + CallFrameInstruction::ArgsSize(size) => Self::ArgsSize(size), + _ => { + // Cranelift's unwind support does not generate `CallFrameInstruction`s with + // Expression at this moment, and it is not trivial to + // serialize such instructions. + panic!("CallFrameInstruction with Expression not supported"); + } + } + } +} + +impl Into<gimli::write::CallFrameInstruction> for CallFrameInstruction { + fn into(self) -> gimli::write::CallFrameInstruction { + use gimli::{write::CallFrameInstruction, Register}; + + match self { + Self::Cfa(reg, offset) => CallFrameInstruction::Cfa(Register(reg), offset), + Self::CfaRegister(reg) => CallFrameInstruction::CfaRegister(Register(reg)), + Self::CfaOffset(offset) => CallFrameInstruction::CfaOffset(offset), + Self::Restore(reg) => CallFrameInstruction::Restore(Register(reg)), + Self::Undefined(reg) => CallFrameInstruction::Undefined(Register(reg)), + Self::SameValue(reg) => CallFrameInstruction::SameValue(Register(reg)), + Self::Offset(reg, offset) => CallFrameInstruction::Offset(Register(reg), offset), + Self::ValOffset(reg, offset) => CallFrameInstruction::ValOffset(Register(reg), offset), + Self::Register(reg1, reg2) => { + CallFrameInstruction::Register(Register(reg1), Register(reg2)) + } + Self::RememberState => CallFrameInstruction::RememberState, + Self::RestoreState => CallFrameInstruction::RestoreState, + Self::ArgsSize(size) => CallFrameInstruction::ArgsSize(size), + } + } +} + +/// Maps UnwindInfo register to gimli's index space. +pub(crate) trait RegisterMapper<Reg> { + /// Maps Reg. + fn map(&self, reg: Reg) -> Result<Register, RegisterMappingError>; + /// Gets stack pointer register. + fn sp(&self) -> Register; +} + +/// Represents unwind information for a single System V ABI function. +/// +/// This representation is not ISA specific. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct UnwindInfo { + instructions: Vec<(u32, CallFrameInstruction)>, + len: u32, +} + +impl UnwindInfo { + pub(crate) fn build<'b, Reg: PartialEq + Copy>( + unwind: input::UnwindInfo<Reg>, + map_reg: &'b dyn RegisterMapper<Reg>, + ) -> CodegenResult<Self> { + use input::UnwindCode; + let mut builder = InstructionBuilder::new(unwind.initial_sp_offset, map_reg); + + for (offset, c) in unwind.prologue_unwind_codes.iter().chain( + unwind + .epilogues_unwind_codes + .iter() + .map(|c| c.iter()) + .flatten(), + ) { + match c { + UnwindCode::SaveRegister { reg, stack_offset } => { + builder + .save_reg(*offset, *reg, *stack_offset) + .map_err(CodegenError::RegisterMappingError)?; + } + UnwindCode::StackAlloc { size } => { + builder.adjust_sp_down_imm(*offset, *size as i64); + } + UnwindCode::StackDealloc { size } => { + builder.adjust_sp_up_imm(*offset, *size as i64); + } + UnwindCode::RestoreRegister { reg } => { + builder + .restore_reg(*offset, *reg) + .map_err(CodegenError::RegisterMappingError)?; + } + UnwindCode::SetFramePointer { reg } => { + builder + .set_cfa_reg(*offset, *reg) + .map_err(CodegenError::RegisterMappingError)?; + } + UnwindCode::RestoreFramePointer => { + builder.restore_cfa(*offset); + } + UnwindCode::RememberState => { + builder.remember_state(*offset); + } + UnwindCode::RestoreState => { + builder.restore_state(*offset); + } + } + } + + let instructions = builder.instructions; + let len = unwind.function_size; + + Ok(Self { instructions, len }) + } + + /// Converts the unwind information into a `FrameDescriptionEntry`. + pub fn to_fde(&self, address: Address) -> gimli::write::FrameDescriptionEntry { + let mut fde = FrameDescriptionEntry::new(address, self.len); + + for (offset, inst) in &self.instructions { + fde.add_instruction(*offset, inst.clone().into()); + } + + fde + } +} + +struct InstructionBuilder<'a, Reg: PartialEq + Copy> { + sp_offset: i32, + frame_register: Option<Reg>, + saved_state: Option<(i32, Option<Reg>)>, + map_reg: &'a dyn RegisterMapper<Reg>, + instructions: Vec<(u32, CallFrameInstruction)>, +} + +impl<'a, Reg: PartialEq + Copy> InstructionBuilder<'a, Reg> { + fn new(sp_offset: u8, map_reg: &'a (dyn RegisterMapper<Reg> + 'a)) -> Self { + Self { + sp_offset: sp_offset as i32, // CFA offset starts at the specified offset to account for the return address on stack + saved_state: None, + frame_register: None, + map_reg, + instructions: Vec::new(), + } + } + + fn save_reg( + &mut self, + offset: u32, + reg: Reg, + stack_offset: u32, + ) -> Result<(), RegisterMappingError> { + // Pushes in the prologue are register saves, so record an offset of the save + self.instructions.push(( + offset, + CallFrameInstruction::Offset( + self.map_reg.map(reg)?, + stack_offset as i32 - self.sp_offset, + ), + )); + + Ok(()) + } + + fn adjust_sp_down_imm(&mut self, offset: u32, imm: i64) { + assert!(imm <= core::u32::MAX as i64); + + self.sp_offset += imm as i32; + + // Don't adjust the CFA if we're using a frame pointer + if self.frame_register.is_some() { + return; + } + + self.instructions + .push((offset, CallFrameInstruction::CfaOffset(self.sp_offset))); + } + + fn adjust_sp_up_imm(&mut self, offset: u32, imm: i64) { + assert!(imm <= core::u32::MAX as i64); + + self.sp_offset -= imm as i32; + + // Don't adjust the CFA if we're using a frame pointer + if self.frame_register.is_some() { + return; + } + + let cfa_inst_ofs = { + // Scan to find and merge with CFA instruction with the same offset. + let mut it = self.instructions.iter_mut(); + loop { + match it.next_back() { + Some((i_offset, i)) if *i_offset == offset => { + if let CallFrameInstruction::Cfa(_, o) = i { + break Some(o); + } + } + _ => { + break None; + } + } + } + }; + + if let Some(o) = cfa_inst_ofs { + // Update previous CFA instruction. + *o = self.sp_offset; + } else { + // Add just CFA offset instruction. + self.instructions + .push((offset, CallFrameInstruction::CfaOffset(self.sp_offset))); + } + } + + fn set_cfa_reg(&mut self, offset: u32, reg: Reg) -> Result<(), RegisterMappingError> { + self.instructions.push(( + offset, + CallFrameInstruction::CfaRegister(self.map_reg.map(reg)?), + )); + self.frame_register = Some(reg); + Ok(()) + } + + fn restore_cfa(&mut self, offset: u32) { + // Restore SP and its offset. + self.instructions.push(( + offset, + CallFrameInstruction::Cfa(self.map_reg.sp(), self.sp_offset), + )); + self.frame_register = None; + } + + fn restore_reg(&mut self, offset: u32, reg: Reg) -> Result<(), RegisterMappingError> { + // Pops in the epilogue are register restores, so record a "same value" for the register + self.instructions.push(( + offset, + CallFrameInstruction::SameValue(self.map_reg.map(reg)?), + )); + + Ok(()) + } + + fn remember_state(&mut self, offset: u32) { + self.saved_state = Some((self.sp_offset, self.frame_register)); + + self.instructions + .push((offset, CallFrameInstruction::RememberState)); + } + + fn restore_state(&mut self, offset: u32) { + let (sp_offset, frame_register) = self.saved_state.take().unwrap(); + self.sp_offset = sp_offset; + self.frame_register = frame_register; + + self.instructions + .push((offset, CallFrameInstruction::RestoreState)); + } +} diff --git a/third_party/rust/cranelift-codegen/src/isa/unwind/winx64.rs b/third_party/rust/cranelift-codegen/src/isa/unwind/winx64.rs new file mode 100644 index 0000000000..b3c21fc473 --- /dev/null +++ b/third_party/rust/cranelift-codegen/src/isa/unwind/winx64.rs @@ -0,0 +1,294 @@ +//! Windows x64 ABI unwind information. + +use crate::isa::{unwind::input, RegUnit}; +use crate::result::{CodegenError, CodegenResult}; +use alloc::vec::Vec; +use byteorder::{ByteOrder, LittleEndian}; +use log::warn; +#[cfg(feature = "enable-serde")] +use serde::{Deserialize, Serialize}; + +/// 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<T: ByteOrder>(&mut self, v: u16) { + T::write_u16(&mut self.buf[self.offset..(self.offset + 2)], v); + self.offset += 2; + } + + fn write_u32<T: ByteOrder>(&mut self, v: u32) { + T::write_u32(&mut self.buf[self.offset..(self.offset + 4)], v); + self.offset += 4; + } +} + +/// The supported unwind codes for the x64 Windows ABI. +/// +/// See: https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64 +/// 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. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub(crate) enum UnwindCode { + PushRegister { + offset: u8, + reg: u8, + }, + SaveXmm { + offset: u8, + reg: u8, + stack_offset: u32, + }, + StackAlloc { + offset: u8, + size: u32, + }, +} + +impl UnwindCode { + fn emit(&self, writer: &mut Writer) { + enum UnwindOperation { + PushNonvolatileRegister = 0, + LargeStackAlloc = 1, + SmallStackAlloc = 2, + SaveXmm128 = 8, + SaveXmm128Far = 9, + } + + match self { + Self::PushRegister { offset, reg } => { + writer.write_u8(*offset); + writer.write_u8((*reg << 4) | (UnwindOperation::PushNonvolatileRegister as u8)); + } + Self::SaveXmm { + offset, + reg, + stack_offset, + } => { + writer.write_u8(*offset); + let scaled_stack_offset = stack_offset / 16; + if scaled_stack_offset <= core::u16::MAX as u32 { + writer.write_u8((*reg << 4) | (UnwindOperation::SaveXmm128 as u8)); + writer.write_u16::<LittleEndian>(scaled_stack_offset as u16); + } else { + writer.write_u8((*reg << 4) | (UnwindOperation::SaveXmm128Far as u8)); + writer.write_u16::<LittleEndian>(*stack_offset as u16); + writer.write_u16::<LittleEndian>((stack_offset >> 16) as u16); + } + } + Self::StackAlloc { 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(*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::<LittleEndian>((*size / 8) as u16); + } else { + writer.write_u8((1 << 4) | (UnwindOperation::LargeStackAlloc as u8)); + writer.write_u32::<LittleEndian>(*size); + } + } + }; + } + + 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, .. } => { + if *stack_offset <= core::u16::MAX as u32 { + 2 + } else { + 3 + } + } + _ => 1, + } + } +} + +pub(crate) enum MappedRegister { + Int(u8), + Xmm(u8), +} + +/// Maps UnwindInfo register to Windows x64 unwind data. +pub(crate) trait RegisterMapper { + /// Maps RegUnit. + fn map(reg: RegUnit) -> MappedRegister; +} + +/// Represents Windows x64 unwind information. +/// +/// For information about Windows x64 unwind info, see: +/// https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64 +#[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<u8>, + pub(crate) frame_register_offset: u8, + pub(crate) unwind_codes: Vec<UnwindCode>, +} + +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::<LittleEndian>(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()) + } + + pub(crate) fn build<MR: RegisterMapper>( + unwind: input::UnwindInfo<RegUnit>, + ) -> CodegenResult<Self> { + use crate::isa::unwind::input::UnwindCode as InputUnwindCode; + + let word_size: u32 = unwind.word_size.into(); + let mut unwind_codes = Vec::new(); + for (offset, c) in unwind.prologue_unwind_codes.iter() { + match c { + InputUnwindCode::SaveRegister { reg, stack_offset } => { + let reg = MR::map(*reg); + let offset = ensure_unwind_offset(*offset)?; + match reg { + MappedRegister::Int(reg) => { + // Attempt to convert sequence of the `InputUnwindCode`: + // `StackAlloc { size = word_size }`, `SaveRegister { stack_offset: 0 }` + // to the shorter `UnwindCode::PushRegister`. + let push_reg_sequence = if let Some(UnwindCode::StackAlloc { + offset: alloc_offset, + size, + }) = unwind_codes.last() + { + *size == word_size && offset == *alloc_offset && *stack_offset == 0 + } else { + false + }; + if push_reg_sequence { + *unwind_codes.last_mut().unwrap() = + UnwindCode::PushRegister { offset, reg }; + } else { + // TODO add `UnwindCode::SaveRegister` to handle multiple register + // pushes with single `UnwindCode::StackAlloc`. + return Err(CodegenError::Unsupported( + "Unsupported UnwindCode::PushRegister sequence".into(), + )); + } + } + MappedRegister::Xmm(reg) => { + unwind_codes.push(UnwindCode::SaveXmm { + offset, + reg, + stack_offset: *stack_offset, + }); + } + } + } + InputUnwindCode::StackAlloc { size } => { + unwind_codes.push(UnwindCode::StackAlloc { + offset: ensure_unwind_offset(*offset)?, + size: *size, + }); + } + _ => {} + } + } + + Ok(Self { + flags: 0, // this assumes cranelift functions have no SEH handlers + prologue_size: ensure_unwind_offset(unwind.prologue_size)?, + frame_register: None, + frame_register_offset: 0, + unwind_codes, + }) + } +} + +fn ensure_unwind_offset(offset: u32) -> CodegenResult<u8> { + if offset > 255 { + warn!("function prologues cannot exceed 255 bytes in size for Windows x64"); + return Err(CodegenError::CodeTooLarge); + } + Ok(offset as u8) +} |