cranelift_codegen/isa/unwind/
winx64.rs

1//! Windows x64 ABI unwind information.
2
3use alloc::vec::Vec;
4use log::warn;
5#[cfg(feature = "enable-serde")]
6use serde_derive::{Deserialize, Serialize};
7
8use crate::binemit::CodeOffset;
9use crate::isa::unwind::UnwindInst;
10use crate::result::{CodegenError, CodegenResult};
11
12use super::Writer;
13
14/// Maximum (inclusive) size of a "small" stack allocation
15const SMALL_ALLOC_MAX_SIZE: u32 = 128;
16/// Maximum (inclusive) size of a "large" stack allocation that can represented in 16-bits
17const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280;
18
19/// The supported unwind codes for the x64 Windows ABI.
20///
21/// See: <https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64>
22/// Only what is needed to describe the prologues generated by the Cranelift x86 ISA are represented here.
23/// Note: the Cranelift x86 ISA RU enum matches the Windows unwind GPR encoding values.
24#[allow(dead_code)]
25#[derive(Clone, Debug, PartialEq, Eq)]
26#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
27pub(crate) enum UnwindCode {
28    PushRegister {
29        instruction_offset: u8,
30        reg: u8,
31    },
32    SaveReg {
33        instruction_offset: u8,
34        reg: u8,
35        stack_offset: u32,
36    },
37    SaveXmm {
38        instruction_offset: u8,
39        reg: u8,
40        stack_offset: u32,
41    },
42    StackAlloc {
43        instruction_offset: u8,
44        size: u32,
45    },
46    SetFPReg {
47        instruction_offset: u8,
48    },
49}
50
51impl UnwindCode {
52    fn emit(&self, writer: &mut Writer) {
53        enum UnwindOperation {
54            PushNonvolatileRegister = 0,
55            LargeStackAlloc = 1,
56            SmallStackAlloc = 2,
57            SetFPReg = 3,
58            SaveNonVolatileRegister = 4,
59            SaveNonVolatileRegisterFar = 5,
60            SaveXmm128 = 8,
61            SaveXmm128Far = 9,
62        }
63
64        match self {
65            Self::PushRegister {
66                instruction_offset,
67                reg,
68            } => {
69                writer.write_u8(*instruction_offset);
70                writer.write_u8((*reg << 4) | (UnwindOperation::PushNonvolatileRegister as u8));
71            }
72            Self::SaveReg {
73                instruction_offset,
74                reg,
75                stack_offset,
76            }
77            | Self::SaveXmm {
78                instruction_offset,
79                reg,
80                stack_offset,
81            } => {
82                let is_xmm = match self {
83                    Self::SaveXmm { .. } => true,
84                    _ => false,
85                };
86                let (op_small, op_large) = if is_xmm {
87                    (UnwindOperation::SaveXmm128, UnwindOperation::SaveXmm128Far)
88                } else {
89                    (
90                        UnwindOperation::SaveNonVolatileRegister,
91                        UnwindOperation::SaveNonVolatileRegisterFar,
92                    )
93                };
94                writer.write_u8(*instruction_offset);
95                let scaled_stack_offset = stack_offset / 16;
96                if scaled_stack_offset <= core::u16::MAX as u32 {
97                    writer.write_u8((*reg << 4) | (op_small as u8));
98                    writer.write_u16_le(scaled_stack_offset as u16);
99                } else {
100                    writer.write_u8((*reg << 4) | (op_large as u8));
101                    writer.write_u16_le(*stack_offset as u16);
102                    writer.write_u16_le((stack_offset >> 16) as u16);
103                }
104            }
105            Self::StackAlloc {
106                instruction_offset,
107                size,
108            } => {
109                // Stack allocations on Windows must be a multiple of 8 and be at least 1 slot
110                assert!(*size >= 8);
111                assert!((*size % 8) == 0);
112
113                writer.write_u8(*instruction_offset);
114                if *size <= SMALL_ALLOC_MAX_SIZE {
115                    writer.write_u8(
116                        ((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8,
117                    );
118                } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
119                    writer.write_u8(UnwindOperation::LargeStackAlloc as u8);
120                    writer.write_u16_le((*size / 8) as u16);
121                } else {
122                    writer.write_u8((1 << 4) | (UnwindOperation::LargeStackAlloc as u8));
123                    writer.write_u32_le(*size);
124                }
125            }
126            Self::SetFPReg { instruction_offset } => {
127                writer.write_u8(*instruction_offset);
128                writer.write_u8(UnwindOperation::SetFPReg as u8);
129            }
130        }
131    }
132
133    fn node_count(&self) -> usize {
134        match self {
135            Self::StackAlloc { size, .. } => {
136                if *size <= SMALL_ALLOC_MAX_SIZE {
137                    1
138                } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
139                    2
140                } else {
141                    3
142                }
143            }
144            Self::SaveXmm { stack_offset, .. } | Self::SaveReg { stack_offset, .. } => {
145                if *stack_offset <= core::u16::MAX as u32 {
146                    2
147                } else {
148                    3
149                }
150            }
151            _ => 1,
152        }
153    }
154}
155
156pub(crate) enum MappedRegister {
157    Int(u8),
158    Xmm(u8),
159}
160
161/// Maps UnwindInfo register to Windows x64 unwind data.
162pub(crate) trait RegisterMapper<Reg> {
163    /// Maps a Reg to a Windows unwind register number.
164    fn map(reg: Reg) -> MappedRegister;
165}
166
167/// Represents Windows x64 unwind information.
168///
169/// For information about Windows x64 unwind info, see:
170/// <https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64>
171#[derive(Clone, Debug, PartialEq, Eq)]
172#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
173pub struct UnwindInfo {
174    pub(crate) flags: u8,
175    pub(crate) prologue_size: u8,
176    pub(crate) frame_register: Option<u8>,
177    pub(crate) frame_register_offset: u8,
178    pub(crate) unwind_codes: Vec<UnwindCode>,
179}
180
181impl UnwindInfo {
182    /// Gets the emit size of the unwind information, in bytes.
183    pub fn emit_size(&self) -> usize {
184        let node_count = self.node_count();
185
186        // Calculation of the size requires no SEH handler or chained info
187        assert!(self.flags == 0);
188
189        // Size of fixed part of UNWIND_INFO is 4 bytes
190        // Then comes the UNWIND_CODE nodes (2 bytes each)
191        // Then comes 2 bytes of padding for the unwind codes if necessary
192        // Next would come the SEH data, but we assert above that the function doesn't have SEH data
193
194        4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 }
195    }
196
197    /// Emits the unwind information into the given mutable byte slice.
198    ///
199    /// This function will panic if the slice is not at least `emit_size` in length.
200    pub fn emit(&self, buf: &mut [u8]) {
201        const UNWIND_INFO_VERSION: u8 = 1;
202
203        let node_count = self.node_count();
204        assert!(node_count <= 256);
205
206        let mut writer = Writer::new(buf);
207
208        writer.write_u8((self.flags << 3) | UNWIND_INFO_VERSION);
209        writer.write_u8(self.prologue_size);
210        writer.write_u8(node_count as u8);
211
212        if let Some(reg) = self.frame_register {
213            writer.write_u8((self.frame_register_offset << 4) | reg);
214        } else {
215            writer.write_u8(0);
216        }
217
218        // Unwind codes are written in reverse order (prologue offset descending)
219        for code in self.unwind_codes.iter().rev() {
220            code.emit(&mut writer);
221        }
222
223        // To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes
224        if (node_count & 1) == 1 {
225            writer.write_u16_le(0);
226        }
227
228        // Ensure the correct number of bytes was emitted
229        assert_eq!(writer.offset, self.emit_size());
230    }
231
232    fn node_count(&self) -> usize {
233        self.unwind_codes
234            .iter()
235            .fold(0, |nodes, c| nodes + c.node_count())
236    }
237}
238
239const UNWIND_RBP_REG: u8 = 5;
240
241pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<crate::machinst::Reg>>(
242    insts: &[(CodeOffset, UnwindInst)],
243) -> CodegenResult<UnwindInfo> {
244    let mut unwind_codes = vec![];
245    let mut frame_register_offset = 0;
246    let mut max_unwind_offset = 0;
247    for &(instruction_offset, ref inst) in insts {
248        let instruction_offset = ensure_unwind_offset(instruction_offset)?;
249        match inst {
250            &UnwindInst::PushFrameRegs { .. } => {
251                unwind_codes.push(UnwindCode::PushRegister {
252                    instruction_offset,
253                    reg: UNWIND_RBP_REG,
254                });
255            }
256            &UnwindInst::DefineNewFrame {
257                offset_downward_to_clobbers,
258                ..
259            } => {
260                frame_register_offset = ensure_unwind_offset(offset_downward_to_clobbers)?;
261                unwind_codes.push(UnwindCode::SetFPReg { instruction_offset });
262            }
263            &UnwindInst::StackAlloc { size } => {
264                unwind_codes.push(UnwindCode::StackAlloc {
265                    instruction_offset,
266                    size,
267                });
268            }
269            &UnwindInst::SaveReg {
270                clobber_offset,
271                reg,
272            } => match MR::map(reg.into()) {
273                MappedRegister::Int(reg) => {
274                    unwind_codes.push(UnwindCode::SaveReg {
275                        instruction_offset,
276                        reg,
277                        stack_offset: clobber_offset,
278                    });
279                }
280                MappedRegister::Xmm(reg) => {
281                    unwind_codes.push(UnwindCode::SaveXmm {
282                        instruction_offset,
283                        reg,
284                        stack_offset: clobber_offset,
285                    });
286                }
287            },
288            &UnwindInst::RegStackOffset { .. } => {
289                unreachable!("only supported with DWARF");
290            }
291            &UnwindInst::Aarch64SetPointerAuth { .. } => {
292                unreachable!("no aarch64 on x64");
293            }
294        }
295        max_unwind_offset = instruction_offset;
296    }
297
298    Ok(UnwindInfo {
299        flags: 0,
300        prologue_size: max_unwind_offset,
301        frame_register: Some(UNWIND_RBP_REG),
302        frame_register_offset,
303        unwind_codes,
304    })
305}
306
307fn ensure_unwind_offset(offset: u32) -> CodegenResult<u8> {
308    if offset > 255 {
309        warn!("function prologues cannot exceed 255 bytes in size for Windows x64");
310        return Err(CodegenError::CodeTooLarge);
311    }
312    Ok(offset as u8)
313}