cranelift_codegen/isa/unwind/
winx64.rs

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