Skip to main content

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