Skip to content

Commit

Permalink
Move UnwindInfo definition out of x86 ABI.
Browse files Browse the repository at this point in the history
This commit moves the opaque definition of Windows x64 UnwindInfo out of the
ISA and into a location that can be easily used by the top level `UnwindInfo`
enum.

This allows the `unwind` feature to be independent of the individual ISAs
supported.
  • Loading branch information
peterhuene committed Apr 13, 2020
1 parent 8d7beeb commit f8c2fe6
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 219 deletions.
4 changes: 2 additions & 2 deletions cranelift/codegen/src/isa/unwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
use serde::{Deserialize, Serialize};

pub mod systemv;
pub mod winx64;

/// Represents unwind information for a single function.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum UnwindInfo {
/// Windows x64 ABI unwind information.
#[cfg(feature = "x86")]
WindowsX64(super::x86::unwind::windows::UnwindInfo),
WindowsX64(winx64::UnwindInfo),
/// System V ABI unwind information.
SystemV(systemv::UnwindInfo),
}
216 changes: 216 additions & 0 deletions cranelift/codegen/src/isa/unwind/winx64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
//! System V ABI unwind information.
use alloc::vec::Vec;
use byteorder::{ByteOrder, LittleEndian};
#[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,
},
SetFramePointer {
offset: u8,
sp_offset: u8,
},
}

impl UnwindCode {
fn emit(&self, writer: &mut Writer) {
enum UnwindOperation {
PushNonvolatileRegister = 0,
LargeStackAlloc = 1,
SmallStackAlloc = 2,
SetFramePointer = 3,
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 stack_offset = stack_offset / 16;
if stack_offset <= core::u16::MAX as u32 {
writer.write_u8((*reg << 4) | (UnwindOperation::SaveXmm128 as u8));
writer.write_u16::<LittleEndian>(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);
}
}
Self::SetFramePointer { offset, sp_offset } => {
writer.write_u8(*offset);
writer.write_u8((*sp_offset << 4) | (UnwindOperation::SetFramePointer 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, .. } => {
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:
/// 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())
}
}
2 changes: 1 addition & 1 deletion cranelift/codegen/src/isa/x86/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@ pub fn create_unwind_info(
.map(|u| UnwindInfo::SystemV(u))
}
CallConv::WindowsFastcall => {
super::unwind::windows::create_unwind_info(func, isa, Some(RU::rbp.into()))?
super::unwind::winx64::create_unwind_info(func, isa, Some(RU::rbp.into()))?
.map(|u| UnwindInfo::WindowsX64(u))
}
_ => None,
Expand Down
2 changes: 1 addition & 1 deletion cranelift/codegen/src/isa/x86/unwind.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Module for x86 unwind generation for supported ABIs.
pub mod systemv;
pub mod windows;
pub mod winx64;
Loading

0 comments on commit f8c2fe6

Please sign in to comment.