wasmer_compiler_singlepass/
unwind_winx64.rs1use crate::{
4 location::Reg,
5 unwind::{UnwindOps, UnwindRegister},
6 x64_decl::{GPR, XMM},
7};
8
9const SMALL_ALLOC_MAX_SIZE: u32 = 128;
11const 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#[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 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#[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 pub fn emit_size(&self) -> usize {
190 let node_count = self.node_count();
191
192 assert!(self.flags == 0);
194
195 4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 }
201 }
202
203 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 for code in self.unwind_codes.iter().rev() {
226 code.emit(&mut writer);
227 }
228
229 if (node_count & 1) == 1 {
231 writer.write_u16_le(0);
232 }
233
234 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 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 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}