summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cranelift-codegen/src/isa/unwind
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cranelift-codegen/src/isa/unwind')
-rw-r--r--third_party/rust/cranelift-codegen/src/isa/unwind/systemv.rs313
-rw-r--r--third_party/rust/cranelift-codegen/src/isa/unwind/winx64.rs294
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)
+}