cranelift_codegen/isa/unwind/
winarm64.rs

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