Path: blob/main/cranelift/codegen/src/isa/unwind/winarm64.rs
1693 views
//! Windows Arm64 ABI unwind information.12use alloc::vec::Vec;3#[cfg(feature = "enable-serde")]4use serde_derive::{Deserialize, Serialize};56use crate::binemit::CodeOffset;7use crate::isa::unwind::UnwindInst;8use crate::result::CodegenResult;910use super::Writer;1112/// The supported unwind codes for the Arm64 Windows ABI.13///14/// See: <https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling>15/// Only what is needed to describe the prologues generated by the Cranelift AArch64 ISA are represented here.16#[derive(Clone, Debug, PartialEq, Eq)]17#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]18pub(crate) enum UnwindCode {19/// Save int register, or register pair.20SaveReg {21reg: u8,22stack_offset: u16,23is_pair: bool,24},25/// Save floating point register, or register pair.26SaveFReg {27reg: u8,28stack_offset: u16,29is_pair: bool,30},31/// Save frame-pointer register (X29) and LR register pair.32SaveFpLrPair {33stack_offset: u16,34},35// Small (<512b) stack allocation.36AllocS {37size: u16,38},39// Medium (<32Kb) stack allocation.40AllocM {41size: u16,42},43// Large (<256Mb) stack allocation.44AllocL {45size: u32,46},47/// PAC sign the LR register.48PacSignLr,49/// Set the frame-pointer register to the stack-pointer register.50SetFp,51/// Set the frame-pointer register to the stack-pointer register with an52/// offset.53AddFp {54offset: u16,55},56}5758/// Represents Windows Arm64 unwind information.59///60/// For information about Windows Arm64 unwind info, see:61/// <https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling>62#[derive(Clone, Debug, PartialEq, Eq)]63#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]64pub struct UnwindInfo {65pub(crate) unwind_codes: Vec<UnwindCode>,66}6768impl UnwindInfo {69/// Calculate the number of words needed to encode the unwind codes.70pub fn code_words(&self) -> u8 {71let mut bytes = 0u16;72for code in self.unwind_codes.iter() {73let next_bytes = match code {74UnwindCode::SaveFpLrPair { .. }75| UnwindCode::AllocS { .. }76| UnwindCode::PacSignLr77| UnwindCode::SetFp => 1,78UnwindCode::SaveReg { .. }79| UnwindCode::SaveFReg { .. }80| UnwindCode::AllocM { .. }81| UnwindCode::AddFp { .. } => 2,82UnwindCode::AllocL { .. } => 4,83};84bytes = bytes.checked_add(next_bytes).unwrap();85}8687bytes.div_ceil(4).try_into().unwrap()88}8990/// Emits the unwind information into the given mutable byte slice.91///92/// This function will panic if the slice is not at least `emit_size` in length.93pub fn emit(&self, buf: &mut [u8]) {94fn encode_stack_offset<const BITS: u8>(stack_offset: u16) -> u16 {95let encoded = (stack_offset / 8) - 1;96assert!(encoded < (1 << BITS), "Stack offset too large");97encoded98}99100// NOTE: Unwind codes are written in big-endian!101102let mut writer = Writer::new(buf);103for code in self.unwind_codes.iter().rev() {104match code {105&UnwindCode::SaveReg {106reg,107stack_offset,108is_pair,109} => {110assert!(reg >= 19, "Can't save registers before X19");111let reg = u16::from(reg - 19);112let encoding = if is_pair {113let mut encoding = 0b11001100_00000000u16;114encoding |= reg << 6;115encoding |= encode_stack_offset::<6>(stack_offset);116encoding117} else {118let mut encoding = 0b11010100_00000000u16;119encoding |= reg << 5;120encoding |= encode_stack_offset::<5>(stack_offset);121encoding122};123writer.write_u16_be(encoding);124}125&UnwindCode::SaveFReg {126reg,127stack_offset,128is_pair,129} => {130assert!(reg >= 8, "Can't save registers before D8");131let reg = u16::from(reg - 8);132let encoding = if is_pair {133let mut encoding = 0b11011010_00000000u16;134encoding |= reg << 6;135encoding |= encode_stack_offset::<6>(stack_offset);136encoding137} else {138let mut encoding = 0b11011110_00000000u16;139encoding |= reg << 5;140encoding |= encode_stack_offset::<5>(stack_offset);141encoding142};143writer.write_u16_be(encoding);144}145&UnwindCode::SaveFpLrPair { stack_offset } => {146if stack_offset == 0 {147writer.write_u8(0b01000000);148} else {149let encoding = 0b10000000u8150| u8::try_from(encode_stack_offset::<6>(stack_offset)).unwrap();151writer.write_u8(encoding);152}153}154&UnwindCode::AllocS { size } => {155// Size is measured in double 64-bit words.156let encoding = size / 16;157assert!(encoding < (1 << 5), "Stack alloc size too large");158// Tag is 0b000, so we don't need to encode that.159writer.write_u8(encoding.try_into().unwrap());160}161&UnwindCode::AllocM { size } => {162// Size is measured in double 64-bit words.163let mut encoding = size / 16;164assert!(encoding < (1 << 11), "Stack alloc size too large");165encoding |= 0b11000 << 11;166writer.write_u16_be(encoding);167}168&UnwindCode::AllocL { size } => {169// Size is measured in double 64-bit words.170let mut encoding = size / 16;171assert!(encoding < (1 << 24), "Stack alloc size too large");172encoding |= 0b11100000 << 24;173writer.write_u32_be(encoding);174}175UnwindCode::PacSignLr => {176writer.write_u8(0b11111100);177}178UnwindCode::SetFp => {179writer.write_u8(0b11100001);180}181&UnwindCode::AddFp { mut offset } => {182offset /= 8;183assert!(offset & !0xFF == 0, "Offset too large");184let encoding = (0b11100010 << 8) | offset;185writer.write_u16_be(encoding);186}187}188}189}190}191192pub(crate) fn create_unwind_info_from_insts(193insts: &[(CodeOffset, UnwindInst)],194) -> CodegenResult<UnwindInfo> {195let mut unwind_codes = vec![];196let mut last_stackalloc = None;197let mut last_clobber_offset = None;198for &(_, ref inst) in insts {199match inst {200&UnwindInst::PushFrameRegs { .. } => {201unwind_codes.push(UnwindCode::SaveFpLrPair { stack_offset: 16 });202unwind_codes.push(UnwindCode::SetFp);203}204&UnwindInst::DefineNewFrame {205offset_downward_to_clobbers,206..207} => {208assert!(last_clobber_offset.is_none(), "More than one frame defined");209last_clobber_offset = Some(offset_downward_to_clobbers);210211// If we've seen a stackalloc, then we were adjusting the stack212// to make space for additional arguments, so encode that now.213if let &Some(last_stackalloc) = &last_stackalloc {214assert!(last_stackalloc < (1u32 << 8) * 8);215unwind_codes.push(UnwindCode::AddFp {216offset: u16::try_from(last_stackalloc).unwrap(),217});218unwind_codes.push(UnwindCode::SaveFpLrPair { stack_offset: 0 });219unwind_codes.push(UnwindCode::SetFp);220}221}222&UnwindInst::StackAlloc { size } => {223last_stackalloc = Some(size);224assert!(size % 16 == 0, "Size must be a multiple of 16");225const SMALL_STACK_ALLOC_MAX: u32 = (1 << 5) * 16 - 1;226const MEDIUM_STACK_ALLOC_MIN: u32 = SMALL_STACK_ALLOC_MAX + 1;227const MEDIUM_STACK_ALLOC_MAX: u32 = (1 << 11) * 16 - 1;228const LARGE_STACK_ALLOC_MIN: u32 = MEDIUM_STACK_ALLOC_MAX + 1;229const LARGE_STACK_ALLOC_MAX: u32 = (1 << 24) * 16 - 1;230match size {2310..=SMALL_STACK_ALLOC_MAX => unwind_codes.push(UnwindCode::AllocS {232size: size.try_into().unwrap(),233}),234MEDIUM_STACK_ALLOC_MIN..=MEDIUM_STACK_ALLOC_MAX => {235unwind_codes.push(UnwindCode::AllocM {236size: size.try_into().unwrap(),237})238}239LARGE_STACK_ALLOC_MIN..=LARGE_STACK_ALLOC_MAX => {240unwind_codes.push(UnwindCode::AllocL { size })241}242_ => panic!("Stack allocation size too large"),243}244}245&UnwindInst::SaveReg {246clobber_offset,247reg,248} => {249// We're given the clobber offset, but we need to encode how far250// the stack was adjusted, so calculate that based on the last251// clobber offset we saw.252let last_clobber_offset = last_clobber_offset.as_mut().expect("No frame defined");253if *last_clobber_offset > clobber_offset {254let stack_offset = *last_clobber_offset - clobber_offset;255*last_clobber_offset = clobber_offset;256257assert!(stack_offset % 8 == 0, "Offset must be a multiple of 8");258match reg.class() {259regalloc2::RegClass::Int => {260let reg = reg.hw_enc();261if reg < 19 {262panic!("Can't save registers before X19");263}264unwind_codes.push(UnwindCode::SaveReg {265reg,266stack_offset: stack_offset.try_into().unwrap(),267is_pair: false,268});269}270regalloc2::RegClass::Float => {271let reg = reg.hw_enc();272if reg < 8 {273panic!("Can't save registers before D8");274}275unwind_codes.push(UnwindCode::SaveFReg {276reg,277stack_offset: stack_offset.try_into().unwrap(),278is_pair: false,279});280}281regalloc2::RegClass::Vector => unreachable!(),282}283} else {284// If we see a clobber offset within the last offset amount,285// then we're actually saving a pair of registers.286let last_unwind_code = unwind_codes.last_mut().unwrap();287match last_unwind_code {288UnwindCode::SaveReg { is_pair, .. } => {289assert_eq!(reg.class(), regalloc2::RegClass::Int);290assert!(!*is_pair);291*is_pair = true;292}293UnwindCode::SaveFReg { is_pair, .. } => {294assert_eq!(reg.class(), regalloc2::RegClass::Float);295assert!(!*is_pair);296*is_pair = true;297}298_ => unreachable!("Previous code should have been a register save"),299}300}301}302&UnwindInst::RegStackOffset { .. } => {303unreachable!("only supported with DWARF");304}305&UnwindInst::Aarch64SetPointerAuth { return_addresses } => {306assert!(307return_addresses,308"Windows doesn't support explicitly disabling return address signing"309);310unwind_codes.push(UnwindCode::PacSignLr);311}312}313}314315Ok(UnwindInfo { unwind_codes })316}317318319