Coverage Report

Created: 2026-02-23 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasmtime/cranelift/codegen/src/isa/unwind/winarm64.rs
Line
Count
Source
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
0
    pub fn code_words(&self) -> u8 {
72
0
        let mut bytes = 0u16;
73
0
        for code in self.unwind_codes.iter() {
74
0
            let next_bytes = match code {
75
                UnwindCode::SaveFpLrPair { .. }
76
                | UnwindCode::AllocS { .. }
77
                | UnwindCode::PacSignLr
78
0
                | UnwindCode::SetFp => 1,
79
                UnwindCode::SaveReg { .. }
80
                | UnwindCode::SaveFReg { .. }
81
                | UnwindCode::AllocM { .. }
82
0
                | UnwindCode::AddFp { .. } => 2,
83
0
                UnwindCode::AllocL { .. } => 4,
84
            };
85
0
            bytes = bytes.checked_add(next_bytes).unwrap();
86
        }
87
88
0
        bytes.div_ceil(4).try_into().unwrap()
89
0
    }
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
0
    pub fn emit(&self, buf: &mut [u8]) {
95
0
        fn encode_stack_offset<const BITS: u8>(stack_offset: u16) -> u16 {
96
0
            let encoded = (stack_offset / 8) - 1;
97
0
            assert!(encoded < (1 << BITS), "Stack offset too large");
98
0
            encoded
99
0
        }
Unexecuted instantiation: <cranelift_codegen::isa::unwind::winarm64::UnwindInfo>::emit::encode_stack_offset::<5>
Unexecuted instantiation: <cranelift_codegen::isa::unwind::winarm64::UnwindInfo>::emit::encode_stack_offset::<6>
100
101
        // NOTE: Unwind codes are written in big-endian!
102
103
0
        let mut writer = Writer::new(buf);
104
0
        for code in self.unwind_codes.iter().rev() {
105
0
            match code {
106
                &UnwindCode::SaveReg {
107
0
                    reg,
108
0
                    stack_offset,
109
0
                    is_pair,
110
                } => {
111
0
                    assert!(reg >= 19, "Can't save registers before X19");
112
0
                    let reg = u16::from(reg - 19);
113
0
                    let encoding = if is_pair {
114
0
                        let mut encoding = 0b11001100_00000000u16;
115
0
                        encoding |= reg << 6;
116
0
                        encoding |= encode_stack_offset::<6>(stack_offset);
117
0
                        encoding
118
                    } else {
119
0
                        let mut encoding = 0b11010100_00000000u16;
120
0
                        encoding |= reg << 5;
121
0
                        encoding |= encode_stack_offset::<5>(stack_offset);
122
0
                        encoding
123
                    };
124
0
                    writer.write_u16_be(encoding);
125
                }
126
                &UnwindCode::SaveFReg {
127
0
                    reg,
128
0
                    stack_offset,
129
0
                    is_pair,
130
                } => {
131
0
                    assert!(reg >= 8, "Can't save registers before D8");
132
0
                    let reg = u16::from(reg - 8);
133
0
                    let encoding = if is_pair {
134
0
                        let mut encoding = 0b11011010_00000000u16;
135
0
                        encoding |= reg << 6;
136
0
                        encoding |= encode_stack_offset::<6>(stack_offset);
137
0
                        encoding
138
                    } else {
139
0
                        let mut encoding = 0b11011110_00000000u16;
140
0
                        encoding |= reg << 5;
141
0
                        encoding |= encode_stack_offset::<5>(stack_offset);
142
0
                        encoding
143
                    };
144
0
                    writer.write_u16_be(encoding);
145
                }
146
0
                &UnwindCode::SaveFpLrPair { stack_offset } => {
147
0
                    if stack_offset == 0 {
148
0
                        writer.write_u8(0b01000000);
149
0
                    } else {
150
0
                        let encoding = 0b10000000u8
151
0
                            | u8::try_from(encode_stack_offset::<6>(stack_offset)).unwrap();
152
0
                        writer.write_u8(encoding);
153
0
                    }
154
                }
155
0
                &UnwindCode::AllocS { size } => {
156
                    // Size is measured in double 64-bit words.
157
0
                    let encoding = size / 16;
158
0
                    assert!(encoding < (1 << 5), "Stack alloc size too large");
159
                    // Tag is 0b000, so we don't need to encode that.
160
0
                    writer.write_u8(encoding.try_into().unwrap());
161
                }
162
0
                &UnwindCode::AllocM { size } => {
163
                    // Size is measured in double 64-bit words.
164
0
                    let mut encoding = size / 16;
165
0
                    assert!(encoding < (1 << 11), "Stack alloc size too large");
166
0
                    encoding |= 0b11000 << 11;
167
0
                    writer.write_u16_be(encoding);
168
                }
169
0
                &UnwindCode::AllocL { size } => {
170
                    // Size is measured in double 64-bit words.
171
0
                    let mut encoding = size / 16;
172
0
                    assert!(encoding < (1 << 24), "Stack alloc size too large");
173
0
                    encoding |= 0b11100000 << 24;
174
0
                    writer.write_u32_be(encoding);
175
                }
176
0
                UnwindCode::PacSignLr => {
177
0
                    writer.write_u8(0b11111100);
178
0
                }
179
0
                UnwindCode::SetFp => {
180
0
                    writer.write_u8(0b11100001);
181
0
                }
182
0
                &UnwindCode::AddFp { mut offset } => {
183
0
                    offset /= 8;
184
0
                    assert!(offset & !0xFF == 0, "Offset too large");
185
0
                    let encoding = (0b11100010 << 8) | offset;
186
0
                    writer.write_u16_be(encoding);
187
                }
188
            }
189
        }
190
0
    }
191
}
192
193
0
pub(crate) fn create_unwind_info_from_insts(
194
0
    insts: &[(CodeOffset, UnwindInst)],
195
0
) -> CodegenResult<UnwindInfo> {
196
0
    let mut unwind_codes = vec![];
197
0
    let mut last_stackalloc = None;
198
0
    let mut last_clobber_offset = None;
199
0
    for &(_, ref inst) in insts {
200
0
        match inst {
201
0
            &UnwindInst::PushFrameRegs { .. } => {
202
0
                unwind_codes.push(UnwindCode::SaveFpLrPair { stack_offset: 16 });
203
0
                unwind_codes.push(UnwindCode::SetFp);
204
0
            }
205
            &UnwindInst::DefineNewFrame {
206
0
                offset_downward_to_clobbers,
207
                ..
208
            } => {
209
0
                assert!(last_clobber_offset.is_none(), "More than one frame defined");
210
0
                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
0
                if let &Some(last_stackalloc) = &last_stackalloc {
215
0
                    assert!(last_stackalloc < (1u32 << 8) * 8);
216
0
                    unwind_codes.push(UnwindCode::AddFp {
217
0
                        offset: u16::try_from(last_stackalloc).unwrap(),
218
0
                    });
219
0
                    unwind_codes.push(UnwindCode::SaveFpLrPair { stack_offset: 0 });
220
0
                    unwind_codes.push(UnwindCode::SetFp);
221
0
                }
222
            }
223
0
            &UnwindInst::StackAlloc { size } => {
224
0
                last_stackalloc = Some(size);
225
0
                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
0
                match size {
232
0
                    0..=SMALL_STACK_ALLOC_MAX => unwind_codes.push(UnwindCode::AllocS {
233
0
                        size: size.try_into().unwrap(),
234
0
                    }),
235
0
                    MEDIUM_STACK_ALLOC_MIN..=MEDIUM_STACK_ALLOC_MAX => {
236
0
                        unwind_codes.push(UnwindCode::AllocM {
237
0
                            size: size.try_into().unwrap(),
238
0
                        })
239
                    }
240
0
                    LARGE_STACK_ALLOC_MIN..=LARGE_STACK_ALLOC_MAX => {
241
0
                        unwind_codes.push(UnwindCode::AllocL { size })
242
                    }
243
0
                    _ => panic!("Stack allocation size too large"),
244
                }
245
            }
246
            &UnwindInst::SaveReg {
247
0
                clobber_offset,
248
0
                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
0
                let last_clobber_offset = last_clobber_offset.as_mut().expect("No frame defined");
254
0
                if *last_clobber_offset > clobber_offset {
255
0
                    let stack_offset = *last_clobber_offset - clobber_offset;
256
0
                    *last_clobber_offset = clobber_offset;
257
258
0
                    assert!(stack_offset % 8 == 0, "Offset must be a multiple of 8");
259
0
                    match reg.class() {
260
                        regalloc2::RegClass::Int => {
261
0
                            let reg = reg.hw_enc();
262
0
                            if reg < 19 {
263
0
                                panic!("Can't save registers before X19");
264
0
                            }
265
0
                            unwind_codes.push(UnwindCode::SaveReg {
266
0
                                reg,
267
0
                                stack_offset: stack_offset.try_into().unwrap(),
268
0
                                is_pair: false,
269
0
                            });
270
                        }
271
                        regalloc2::RegClass::Float => {
272
0
                            let reg = reg.hw_enc();
273
0
                            if reg < 8 {
274
0
                                panic!("Can't save registers before D8");
275
0
                            }
276
0
                            unwind_codes.push(UnwindCode::SaveFReg {
277
0
                                reg,
278
0
                                stack_offset: stack_offset.try_into().unwrap(),
279
0
                                is_pair: false,
280
0
                            });
281
                        }
282
0
                        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
0
                    let last_unwind_code = unwind_codes.last_mut().unwrap();
288
0
                    match last_unwind_code {
289
0
                        UnwindCode::SaveReg { is_pair, .. } => {
290
0
                            assert_eq!(reg.class(), regalloc2::RegClass::Int);
291
0
                            assert!(!*is_pair);
292
0
                            *is_pair = true;
293
                        }
294
0
                        UnwindCode::SaveFReg { is_pair, .. } => {
295
0
                            assert_eq!(reg.class(), regalloc2::RegClass::Float);
296
0
                            assert!(!*is_pair);
297
0
                            *is_pair = true;
298
                        }
299
0
                        _ => unreachable!("Previous code should have been a register save"),
300
                    }
301
                }
302
            }
303
            &UnwindInst::RegStackOffset { .. } => {
304
0
                unreachable!("only supported with DWARF");
305
            }
306
0
            &UnwindInst::Aarch64SetPointerAuth { return_addresses } => {
307
0
                assert!(
308
0
                    return_addresses,
309
                    "Windows doesn't support explicitly disabling return address signing"
310
                );
311
0
                unwind_codes.push(UnwindCode::PacSignLr);
312
            }
313
        }
314
    }
315
316
0
    Ok(UnwindInfo { unwind_codes })
317
0
}