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