wasmer_compiler_singlepass/
unwind_winx64.rs

1//! Windows x64 ABI unwind information.
2
3#[cfg(feature = "enable-serde")]
4use serde_derive::{Deserialize, Serialize};
5
6use crate::{
7    location::Reg,
8    unwind::{UnwindOps, UnwindRegister},
9    x64_decl::{GPR, XMM},
10};
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 = matches!(self, Self::SaveXmm { .. });
107                let (op_small, op_large) = if is_xmm {
108                    (UnwindOperation::SaveXmm128, UnwindOperation::SaveXmm128Far)
109                } else {
110                    (
111                        UnwindOperation::SaveNonVolatileRegister,
112                        UnwindOperation::SaveNonVolatileRegisterFar,
113                    )
114                };
115                writer.write_u8(*instruction_offset);
116                let scaled_stack_offset = stack_offset / 16;
117                if scaled_stack_offset <= u16::MAX as u32 {
118                    writer.write_u8((*reg << 4) | (op_small as u8));
119                    writer.write_u16_le(scaled_stack_offset as u16);
120                } else {
121                    writer.write_u8((*reg << 4) | (op_large as u8));
122                    writer.write_u16_le(*stack_offset as u16);
123                    writer.write_u16_le((stack_offset >> 16) as u16);
124                }
125            }
126            Self::StackAlloc {
127                instruction_offset,
128                size,
129            } => {
130                // Stack allocations on Windows must be a multiple of 8 and be at least 1 slot
131                assert!(*size >= 8);
132                assert!((*size % 8) == 0);
133
134                writer.write_u8(*instruction_offset);
135                if *size <= SMALL_ALLOC_MAX_SIZE {
136                    writer.write_u8(
137                        ((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8,
138                    );
139                } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
140                    writer.write_u8(UnwindOperation::LargeStackAlloc as u8);
141                    writer.write_u16_le((*size / 8) as u16);
142                } else {
143                    writer.write_u8((1 << 4) | (UnwindOperation::LargeStackAlloc as u8));
144                    writer.write_u32_le(*size);
145                }
146            }
147            Self::SetFPReg { instruction_offset } => {
148                writer.write_u8(*instruction_offset);
149                writer.write_u8(UnwindOperation::SetFPReg as u8);
150            }
151        }
152    }
153
154    fn node_count(&self) -> usize {
155        match self {
156            Self::StackAlloc { size, .. } => {
157                if *size <= SMALL_ALLOC_MAX_SIZE {
158                    1
159                } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
160                    2
161                } else {
162                    3
163                }
164            }
165            Self::SaveXmm { stack_offset, .. } | Self::SaveReg { stack_offset, .. } => {
166                if *stack_offset <= u16::MAX as u32 {
167                    2
168                } else {
169                    3
170                }
171            }
172            _ => 1,
173        }
174    }
175}
176
177/// Represents Windows x64 unwind information.
178///
179/// For information about Windows x64 unwind info, see:
180/// <https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64>
181#[derive(Clone, Debug, PartialEq, Eq)]
182#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
183pub struct UnwindInfo {
184    pub(crate) flags: u8,
185    pub(crate) prologue_size: u8,
186    pub(crate) frame_register: Option<u8>,
187    pub(crate) frame_register_offset: u8,
188    pub(crate) unwind_codes: Vec<UnwindCode>,
189}
190
191impl UnwindInfo {
192    /// Gets the emit size of the unwind information, in bytes.
193    pub fn emit_size(&self) -> usize {
194        let node_count = self.node_count();
195
196        // Calculation of the size requires no SHE handler or chained info
197        assert!(self.flags == 0);
198
199        // Size of fixed part of UNWIND_INFO is 4 bytes
200        // Then comes the UNWIND_CODE nodes (2 bytes each)
201        // Then comes 2 bytes of padding for the unwind codes if necessary
202        // Next would come the SHE data, but we assert above that the function doesn't have SHE data
203
204        4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 }
205    }
206
207    /// Emits the unwind information into the given mutable byte slice.
208    ///
209    /// This function will panic if the slice is not at least `emit_size` in length.
210    pub fn emit(&self, buf: &mut [u8]) {
211        const UNWIND_INFO_VERSION: u8 = 1;
212
213        let node_count = self.node_count();
214        assert!(node_count <= 256);
215
216        let mut writer = Writer::new(buf);
217
218        writer.write_u8((self.flags << 3) | UNWIND_INFO_VERSION);
219        writer.write_u8(self.prologue_size);
220        writer.write_u8(node_count as u8);
221
222        if let Some(reg) = self.frame_register {
223            writer.write_u8((self.frame_register_offset << 4) | reg);
224        } else {
225            writer.write_u8(0);
226        }
227
228        // Unwind codes are written in reverse order (prologue offset descending)
229        for code in self.unwind_codes.iter().rev() {
230            code.emit(&mut writer);
231        }
232
233        // To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes
234        if (node_count & 1) == 1 {
235            writer.write_u16_le(0);
236        }
237
238        // Ensure the correct number of bytes was emitted
239        assert_eq!(writer.offset, self.emit_size());
240    }
241
242    fn node_count(&self) -> usize {
243        self.unwind_codes
244            .iter()
245            .fold(0, |nodes, c| nodes + c.node_count())
246    }
247}
248
249const UNWIND_RBP_REG: u8 = 5;
250
251pub(crate) fn create_unwind_info_from_insts(
252    insts: &[(usize, UnwindOps<GPR, XMM>)],
253) -> Option<UnwindInfo> {
254    let mut unwind_codes = vec![];
255    let mut frame_register_offset = 0;
256    let mut max_unwind_offset = 0;
257    for &(instruction_offset, ref inst) in insts {
258        let instruction_offset = ensure_unwind_offset(instruction_offset as u32)?;
259        match *inst {
260            UnwindOps::PushFP { .. } => {
261                unwind_codes.push(UnwindCode::PushRegister {
262                    instruction_offset,
263                    reg: UNWIND_RBP_REG,
264                });
265            }
266            UnwindOps::DefineNewFrame => {
267                frame_register_offset = ensure_unwind_offset(32)?;
268                unwind_codes.push(UnwindCode::SetFPReg { instruction_offset });
269            }
270            UnwindOps::SaveRegister { reg, bp_neg_offset } => match reg {
271                UnwindRegister::GPR(reg) => unwind_codes.push(UnwindCode::SaveReg {
272                    instruction_offset,
273                    // NOTE: We declare the register order in the same way as expected by the x64 exception handling ABI.
274                    reg: reg.into_index() as u8,
275                    stack_offset: bp_neg_offset as u32,
276                }),
277                UnwindRegister::FPR(reg) => unwind_codes.push(UnwindCode::SaveXmm {
278                    instruction_offset,
279                    // NOTE: We declare the register order in the same way as expected by the x64 exception handling ABI.
280                    reg: reg.into_index() as u8,
281                    stack_offset: bp_neg_offset as u32,
282                }),
283            },
284            UnwindOps::Push2Regs { .. } => {
285                unreachable!("no aarch64 on x64");
286            }
287            UnwindOps::SubtractFP { .. } => unreachable!(),
288        }
289        max_unwind_offset = instruction_offset;
290    }
291
292    Some(UnwindInfo {
293        flags: 0,
294        prologue_size: max_unwind_offset,
295        frame_register: Some(UNWIND_RBP_REG),
296        frame_register_offset,
297        unwind_codes,
298    })
299}
300
301fn ensure_unwind_offset(offset: u32) -> Option<u8> {
302    if offset > 255 {
303        panic!("function prologues cannot exceed 255 bytes in size for Windows x64");
304    }
305    Some(offset as u8)
306}