Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/src/isa/unwind/winarm64.rs
1693 views
1
//! Windows Arm64 ABI unwind information.
2
3
use alloc::vec::Vec;
4
#[cfg(feature = "enable-serde")]
5
use serde_derive::{Deserialize, Serialize};
6
7
use crate::binemit::CodeOffset;
8
use crate::isa::unwind::UnwindInst;
9
use crate::result::CodegenResult;
10
11
use super::Writer;
12
13
/// The supported unwind codes for the Arm64 Windows ABI.
14
///
15
/// See: <https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling>
16
/// Only what is needed to describe the prologues generated by the Cranelift AArch64 ISA are represented here.
17
#[derive(Clone, Debug, PartialEq, Eq)]
18
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
19
pub(crate) enum UnwindCode {
20
/// Save int register, or register pair.
21
SaveReg {
22
reg: u8,
23
stack_offset: u16,
24
is_pair: bool,
25
},
26
/// Save floating point register, or register pair.
27
SaveFReg {
28
reg: u8,
29
stack_offset: u16,
30
is_pair: bool,
31
},
32
/// Save frame-pointer register (X29) and LR register pair.
33
SaveFpLrPair {
34
stack_offset: u16,
35
},
36
// Small (<512b) stack allocation.
37
AllocS {
38
size: u16,
39
},
40
// Medium (<32Kb) stack allocation.
41
AllocM {
42
size: u16,
43
},
44
// Large (<256Mb) stack allocation.
45
AllocL {
46
size: u32,
47
},
48
/// PAC sign the LR register.
49
PacSignLr,
50
/// Set the frame-pointer register to the stack-pointer register.
51
SetFp,
52
/// Set the frame-pointer register to the stack-pointer register with an
53
/// offset.
54
AddFp {
55
offset: u16,
56
},
57
}
58
59
/// Represents Windows Arm64 unwind information.
60
///
61
/// For information about Windows Arm64 unwind info, see:
62
/// <https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling>
63
#[derive(Clone, Debug, PartialEq, Eq)]
64
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
65
pub struct UnwindInfo {
66
pub(crate) unwind_codes: Vec<UnwindCode>,
67
}
68
69
impl UnwindInfo {
70
/// Calculate the number of words needed to encode the unwind codes.
71
pub fn code_words(&self) -> u8 {
72
let mut bytes = 0u16;
73
for code in self.unwind_codes.iter() {
74
let next_bytes = match code {
75
UnwindCode::SaveFpLrPair { .. }
76
| UnwindCode::AllocS { .. }
77
| UnwindCode::PacSignLr
78
| UnwindCode::SetFp => 1,
79
UnwindCode::SaveReg { .. }
80
| UnwindCode::SaveFReg { .. }
81
| UnwindCode::AllocM { .. }
82
| UnwindCode::AddFp { .. } => 2,
83
UnwindCode::AllocL { .. } => 4,
84
};
85
bytes = bytes.checked_add(next_bytes).unwrap();
86
}
87
88
bytes.div_ceil(4).try_into().unwrap()
89
}
90
91
/// Emits the unwind information into the given mutable byte slice.
92
///
93
/// This function will panic if the slice is not at least `emit_size` in length.
94
pub fn emit(&self, buf: &mut [u8]) {
95
fn encode_stack_offset<const BITS: u8>(stack_offset: u16) -> u16 {
96
let encoded = (stack_offset / 8) - 1;
97
assert!(encoded < (1 << BITS), "Stack offset too large");
98
encoded
99
}
100
101
// NOTE: Unwind codes are written in big-endian!
102
103
let mut writer = Writer::new(buf);
104
for code in self.unwind_codes.iter().rev() {
105
match code {
106
&UnwindCode::SaveReg {
107
reg,
108
stack_offset,
109
is_pair,
110
} => {
111
assert!(reg >= 19, "Can't save registers before X19");
112
let reg = u16::from(reg - 19);
113
let encoding = if is_pair {
114
let mut encoding = 0b11001100_00000000u16;
115
encoding |= reg << 6;
116
encoding |= encode_stack_offset::<6>(stack_offset);
117
encoding
118
} else {
119
let mut encoding = 0b11010100_00000000u16;
120
encoding |= reg << 5;
121
encoding |= encode_stack_offset::<5>(stack_offset);
122
encoding
123
};
124
writer.write_u16_be(encoding);
125
}
126
&UnwindCode::SaveFReg {
127
reg,
128
stack_offset,
129
is_pair,
130
} => {
131
assert!(reg >= 8, "Can't save registers before D8");
132
let reg = u16::from(reg - 8);
133
let encoding = if is_pair {
134
let mut encoding = 0b11011010_00000000u16;
135
encoding |= reg << 6;
136
encoding |= encode_stack_offset::<6>(stack_offset);
137
encoding
138
} else {
139
let mut encoding = 0b11011110_00000000u16;
140
encoding |= reg << 5;
141
encoding |= encode_stack_offset::<5>(stack_offset);
142
encoding
143
};
144
writer.write_u16_be(encoding);
145
}
146
&UnwindCode::SaveFpLrPair { stack_offset } => {
147
if stack_offset == 0 {
148
writer.write_u8(0b01000000);
149
} else {
150
let encoding = 0b10000000u8
151
| u8::try_from(encode_stack_offset::<6>(stack_offset)).unwrap();
152
writer.write_u8(encoding);
153
}
154
}
155
&UnwindCode::AllocS { size } => {
156
// Size is measured in double 64-bit words.
157
let encoding = size / 16;
158
assert!(encoding < (1 << 5), "Stack alloc size too large");
159
// Tag is 0b000, so we don't need to encode that.
160
writer.write_u8(encoding.try_into().unwrap());
161
}
162
&UnwindCode::AllocM { size } => {
163
// Size is measured in double 64-bit words.
164
let mut encoding = size / 16;
165
assert!(encoding < (1 << 11), "Stack alloc size too large");
166
encoding |= 0b11000 << 11;
167
writer.write_u16_be(encoding);
168
}
169
&UnwindCode::AllocL { size } => {
170
// Size is measured in double 64-bit words.
171
let mut encoding = size / 16;
172
assert!(encoding < (1 << 24), "Stack alloc size too large");
173
encoding |= 0b11100000 << 24;
174
writer.write_u32_be(encoding);
175
}
176
UnwindCode::PacSignLr => {
177
writer.write_u8(0b11111100);
178
}
179
UnwindCode::SetFp => {
180
writer.write_u8(0b11100001);
181
}
182
&UnwindCode::AddFp { mut offset } => {
183
offset /= 8;
184
assert!(offset & !0xFF == 0, "Offset too large");
185
let encoding = (0b11100010 << 8) | offset;
186
writer.write_u16_be(encoding);
187
}
188
}
189
}
190
}
191
}
192
193
pub(crate) fn create_unwind_info_from_insts(
194
insts: &[(CodeOffset, UnwindInst)],
195
) -> CodegenResult<UnwindInfo> {
196
let mut unwind_codes = vec![];
197
let mut last_stackalloc = None;
198
let mut last_clobber_offset = None;
199
for &(_, ref inst) in insts {
200
match inst {
201
&UnwindInst::PushFrameRegs { .. } => {
202
unwind_codes.push(UnwindCode::SaveFpLrPair { stack_offset: 16 });
203
unwind_codes.push(UnwindCode::SetFp);
204
}
205
&UnwindInst::DefineNewFrame {
206
offset_downward_to_clobbers,
207
..
208
} => {
209
assert!(last_clobber_offset.is_none(), "More than one frame defined");
210
last_clobber_offset = Some(offset_downward_to_clobbers);
211
212
// If we've seen a stackalloc, then we were adjusting the stack
213
// to make space for additional arguments, so encode that now.
214
if let &Some(last_stackalloc) = &last_stackalloc {
215
assert!(last_stackalloc < (1u32 << 8) * 8);
216
unwind_codes.push(UnwindCode::AddFp {
217
offset: u16::try_from(last_stackalloc).unwrap(),
218
});
219
unwind_codes.push(UnwindCode::SaveFpLrPair { stack_offset: 0 });
220
unwind_codes.push(UnwindCode::SetFp);
221
}
222
}
223
&UnwindInst::StackAlloc { size } => {
224
last_stackalloc = Some(size);
225
assert!(size % 16 == 0, "Size must be a multiple of 16");
226
const SMALL_STACK_ALLOC_MAX: u32 = (1 << 5) * 16 - 1;
227
const MEDIUM_STACK_ALLOC_MIN: u32 = SMALL_STACK_ALLOC_MAX + 1;
228
const MEDIUM_STACK_ALLOC_MAX: u32 = (1 << 11) * 16 - 1;
229
const LARGE_STACK_ALLOC_MIN: u32 = MEDIUM_STACK_ALLOC_MAX + 1;
230
const LARGE_STACK_ALLOC_MAX: u32 = (1 << 24) * 16 - 1;
231
match size {
232
0..=SMALL_STACK_ALLOC_MAX => unwind_codes.push(UnwindCode::AllocS {
233
size: size.try_into().unwrap(),
234
}),
235
MEDIUM_STACK_ALLOC_MIN..=MEDIUM_STACK_ALLOC_MAX => {
236
unwind_codes.push(UnwindCode::AllocM {
237
size: size.try_into().unwrap(),
238
})
239
}
240
LARGE_STACK_ALLOC_MIN..=LARGE_STACK_ALLOC_MAX => {
241
unwind_codes.push(UnwindCode::AllocL { size })
242
}
243
_ => panic!("Stack allocation size too large"),
244
}
245
}
246
&UnwindInst::SaveReg {
247
clobber_offset,
248
reg,
249
} => {
250
// We're given the clobber offset, but we need to encode how far
251
// the stack was adjusted, so calculate that based on the last
252
// clobber offset we saw.
253
let last_clobber_offset = last_clobber_offset.as_mut().expect("No frame defined");
254
if *last_clobber_offset > clobber_offset {
255
let stack_offset = *last_clobber_offset - clobber_offset;
256
*last_clobber_offset = clobber_offset;
257
258
assert!(stack_offset % 8 == 0, "Offset must be a multiple of 8");
259
match reg.class() {
260
regalloc2::RegClass::Int => {
261
let reg = reg.hw_enc();
262
if reg < 19 {
263
panic!("Can't save registers before X19");
264
}
265
unwind_codes.push(UnwindCode::SaveReg {
266
reg,
267
stack_offset: stack_offset.try_into().unwrap(),
268
is_pair: false,
269
});
270
}
271
regalloc2::RegClass::Float => {
272
let reg = reg.hw_enc();
273
if reg < 8 {
274
panic!("Can't save registers before D8");
275
}
276
unwind_codes.push(UnwindCode::SaveFReg {
277
reg,
278
stack_offset: stack_offset.try_into().unwrap(),
279
is_pair: false,
280
});
281
}
282
regalloc2::RegClass::Vector => unreachable!(),
283
}
284
} else {
285
// If we see a clobber offset within the last offset amount,
286
// then we're actually saving a pair of registers.
287
let last_unwind_code = unwind_codes.last_mut().unwrap();
288
match last_unwind_code {
289
UnwindCode::SaveReg { is_pair, .. } => {
290
assert_eq!(reg.class(), regalloc2::RegClass::Int);
291
assert!(!*is_pair);
292
*is_pair = true;
293
}
294
UnwindCode::SaveFReg { is_pair, .. } => {
295
assert_eq!(reg.class(), regalloc2::RegClass::Float);
296
assert!(!*is_pair);
297
*is_pair = true;
298
}
299
_ => unreachable!("Previous code should have been a register save"),
300
}
301
}
302
}
303
&UnwindInst::RegStackOffset { .. } => {
304
unreachable!("only supported with DWARF");
305
}
306
&UnwindInst::Aarch64SetPointerAuth { return_addresses } => {
307
assert!(
308
return_addresses,
309
"Windows doesn't support explicitly disabling return address signing"
310
);
311
unwind_codes.push(UnwindCode::PacSignLr);
312
}
313
}
314
}
315
316
Ok(UnwindInfo { unwind_codes })
317
}
318
319