wasmer_compiler_singlepass/
unwind_winx64.rs

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