cranelift_codegen/isa/unwind/
winx64.rs1use 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
14const SMALL_ALLOC_MAX_SIZE: u32 = 128;
16const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280;
18
19#[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 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 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
175pub(crate) trait RegisterMapper<Reg> {
177 fn map(reg: Reg) -> MappedRegister;
179}
180
181#[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 pub fn emit_size(&self) -> usize {
198 let node_count = self.node_count();
199
200 assert!(self.flags == 0);
202
203 4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 }
209 }
210
211 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 for code in self.unwind_codes.iter().rev() {
234 code.emit(&mut writer);
235 }
236
237 if (node_count & 1) == 1 {
239 writer.write_u16_le(0);
240 }
241
242 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
255const 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}